tor-browser

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

SpeechRecognition-installOnDevice.https.html (10528B)


      1 <!DOCTYPE html>
      2 <title>SpeechRecognition install</title>
      3 <script src="/resources/testharness.js"></script>
      4 <script src="/resources/testharnessreport.js"></script>
      5 <script src="/resources/testdriver.js"></script>
      6 <script src="/resources/testdriver-vendor.js"></script>
      7 <script>
      8 promise_test(async (t) => {
      9  const validLang = "en-US";
     10  const validLangAlternateLocale = "en-GB";
     11  const invalidLang = "invalid language code";
     12  window.SpeechRecognition =
     13    window.SpeechRecognition || window.webkitSpeechRecognition;
     14  const validOptions = { langs: [validLang], processLocally: true };
     15  const validAlternateOptions = { langs: [validLangAlternateLocale], processLocally: true };
     16  const invalidOptions = { langs: [invalidLang], processLocally: true };
     17 
     18  // Check the availablility of the on-device language pack.
     19  const initialAvailabilityPromise =
     20    SpeechRecognition.available(validOptions);
     21  assert_true(
     22    initialAvailabilityPromise instanceof Promise,
     23    "available should return a Promise."
     24  );
     25 
     26  const initialAvailabilityResult = await initialAvailabilityPromise;
     27  assert_true(
     28    typeof initialAvailabilityResult === "string",
     29    "The resolved value of the available promise should be a string."
     30  );
     31 
     32  if (initialAvailabilityResult === "downloadable") {
     33    // Attempt to call install directly, without a user gesture with a
     34    // language that is downloadable but not installed.
     35    const installWithoutUserGesturePromise =
     36      SpeechRecognition.install(validOptions);
     37 
     38    // Assert that the promise rejects with NotAllowedError.
     39    await promise_rejects_dom(
     40      t,
     41      "NotAllowedError",
     42      window.DOMException,
     43      installWithoutUserGesturePromise, "SpeechRecognition.install() " +
     44      "must reject with NotAllowedError if called without a user gesture for on-device."
     45    );
     46 
     47    // Test that it returns a promise when called with a valid language.
     48    const validResultPromise = test_driver.bless(
     49      "Call SpeechRecognition.install with a valid language",
     50      () => SpeechRecognition.install(validOptions)
     51    );
     52    const validInstallPromise = test_driver.bless(
     53      "Call SpeechRecognition.install with valid options for on-device",
     54      () => SpeechRecognition.install(validOptions)
     55    );
     56    assert_true(
     57      validInstallPromise instanceof Promise,
     58      "install (with gesture, for on-device) should return a Promise."
     59    );
     60 
     61    // Verify the resolved value is a boolean.
     62    const validInstallResult = await validInstallPromise;
     63    assert_true(
     64      typeof validInstallResult === "boolean",
     65      "The resolved value of the install promise (on-device) should be a boolean."
     66    );
     67 
     68    // Verify that the method returns true when called with a supported language.
     69    assert_equals(
     70      validInstallResult,
     71      true,
     72      "install should resolve with `true` when called with " +
     73      "supported options for on-device."
     74    );
     75 
     76    // Verify that the newly installed language pack is available.
     77    const availableOnDeviceResultPromise =
     78      SpeechRecognition.available(validOptions);
     79    assert_true(
     80      availableOnDeviceResultPromise instanceof Promise,
     81      "available should return a Promise."
     82    );
     83 
     84    const availableOnDeviceResult = await availableOnDeviceResultPromise;
     85    assert_true(
     86      typeof availableOnDeviceResult === "string",
     87      "The resolved value of the available promise should be a string."
     88    );
     89 
     90    assert_true(
     91      availableOnDeviceResult === "available",
     92      "The resolved value of the available promise (on-device) should be " +
     93      "'available'."
     94    );
     95 
     96    // Verify that the newly installed language pack is available for an alternate locale.
     97    const alternateLocaleResultPromise =
     98      SpeechRecognition.available(validAlternateOptions);
     99    assert_true(
    100      alternateLocaleResultPromise instanceof Promise,
    101      "available should return a Promise."
    102    );
    103 
    104    const alternateLocaleResult = await alternateLocaleResultPromise;
    105    assert_true(
    106      typeof alternateLocaleResult === "string",
    107      "The resolved value of the available promise should be a string."
    108    );
    109 
    110    assert_true(
    111      alternateLocaleResult === "available",
    112      "The resolved value of the available promise (on-device, alternate locale) should be " +
    113      "'available'."
    114    );
    115 
    116    // Verify that installing an already installed language resolves to true.
    117    const secondInstallPromise = test_driver.bless(
    118      "Call SpeechRecognition.install for an already installed on-device language",
    119      () => SpeechRecognition.install(validOptions)
    120    );
    121    assert_true(
    122      secondInstallPromise instanceof Promise,
    123      "install (with gesture, for already installed on-device language) should " +
    124      "return a Promise."
    125    );
    126    const secondInstallResult = await secondInstallPromise;
    127    assert_true(
    128      typeof secondInstallResult === "boolean",
    129      "The resolved value of the second install promise (on-device) should be a " +
    130        "boolean."
    131    );
    132    assert_equals(
    133      secondInstallResult,
    134      true,
    135      "install should resolve with `true` if the on-device language is already " +
    136      "installed."
    137    );
    138  }
    139 
    140  // Test that it returns a promise and resolves to false for unsupported lang.
    141  const invalidInstallPromise = test_driver.bless(
    142    "Call SpeechRecognition.install with unsupported on-device options",
    143    () => SpeechRecognition.install(invalidOptions)
    144  );
    145  assert_true(
    146    invalidInstallPromise instanceof Promise,
    147    "install (with gesture, for unsupported on-device options) should return " +
    148      "a Promise."
    149  );
    150  const invalidInstallResult = await invalidInstallPromise;
    151  assert_true(
    152    typeof invalidInstallResult === "boolean",
    153    "The resolved value of the install promise (unsupported on-device options) " +
    154      "should be a boolean."
    155  );
    156  assert_equals(
    157    invalidInstallResult,
    158    false,
    159    "install should resolve with `false` when called with " +
    160    "unsupported on-device options."
    161  );
    162 }, "SpeechRecognition.install resolves with a boolean value for on-device " +
    163   "(with user gesture).");
    164 
    165 promise_test(async (t) => {
    166  const iframe = document.createElement("iframe");
    167  document.body.appendChild(iframe);
    168  const frameWindow = iframe.contentWindow;
    169  const frameDOMException = frameWindow.DOMException;
    170  const frameSpeechRecognition =
    171    frameWindow.SpeechRecognition || frameWindow.webkitSpeechRecognition;
    172  const options = { langs: ["en-US"], processLocally: true };
    173 
    174  iframe.remove();
    175  await promise_rejects_dom(
    176    t,
    177    "InvalidStateError",
    178    frameDOMException,
    179    test_driver.bless(
    180      "Call SpeechRecognition.install in a detached frame context for on-device",
    181      () => {
    182        return frameSpeechRecognition.install(options);
    183      }
    184    )
    185  );
    186 }, "SpeechRecognition.install rejects in a detached context for on-device.");
    187 
    188 promise_test(async (t) => {
    189  const iframe = document.createElement("iframe");
    190  iframe.setAttribute("allow",
    191    "on-device-speech-recognition 'none'");
    192  document.body.appendChild(iframe);
    193  t.add_cleanup(() => iframe.remove());
    194 
    195  await new Promise(resolve => {
    196    if (iframe.contentWindow &&
    197        iframe.contentWindow.document.readyState === 'complete') {
    198      resolve();
    199    } else {
    200      iframe.onload = resolve;
    201    }
    202  });
    203 
    204  const frameWindow = iframe.contentWindow;
    205  const frameSpeechRecognition = frameWindow.SpeechRecognition ||
    206    frameWindow.webkitSpeechRecognition;
    207  const frameDOMException = frameWindow.DOMException;
    208 
    209  assert_true(!!frameSpeechRecognition,
    210    "SpeechRecognition should exist in iframe.");
    211  assert_true(!!frameSpeechRecognition.install,
    212    "install method should exist on SpeechRecognition in iframe.");
    213 
    214  const options = { langs: ["en-US"], processLocally: true };
    215  await promise_rejects_dom(
    216    t,
    217    "NotAllowedError",
    218    frameDOMException,
    219    frameSpeechRecognition.install(options),
    220    "install should reject with NotAllowedError if " +
    221    "'install-on-device-speech-recognition' Permission Policy is " +
    222    "disabled."
    223  );
    224 }, "SpeechRecognition.install rejects for on-device if " +
    225  "'install-on-device-speech-recognition' Permission Policy is disabled.");
    226 
    227 promise_test(async (t) => {
    228  const html = `
    229    <!DOCTYPE html>
    230    <script>
    231      window.addEventListener('message', async (event) => {
    232        try {
    233          const SpeechRecognition = window.SpeechRecognition ||
    234                                    window.webkitSpeechRecognition;
    235          if (!SpeechRecognition || !SpeechRecognition.install) {
    236            parent.postMessage({
    237              type: "rejection",
    238              name: "NotSupportedError",
    239              message: "API not available"
    240            }, "*");
    241            return;
    242          }
    243 
    244          const options = { langs: ["en-US"], processLocally: true };
    245          await SpeechRecognition.install(options);
    246          parent.postMessage({ type: "resolution", result: "success" }, "*");
    247        } catch (err) {
    248          parent.postMessage({
    249            type: "rejection",
    250            name: err.name,
    251            message: err.message
    252          }, "*");
    253        }
    254      });
    255    <\/script>
    256  `;
    257 
    258  // Create a cross-origin Blob URL by fetching from remote origin
    259  const blob = new Blob([html], { type: "text/html" });
    260  const blobUrl = URL.createObjectURL(blob);
    261 
    262  const iframe = document.createElement("iframe");
    263  iframe.src = blobUrl;
    264  iframe.setAttribute("sandbox", "allow-scripts");
    265  document.body.appendChild(iframe);
    266  t.add_cleanup(() => iframe.remove());
    267 
    268  await new Promise(resolve => iframe.onload = resolve);
    269 
    270  const testResult = await new Promise((resolve, reject) => {
    271    const timeoutId = t.step_timeout(() => {
    272      reject(new Error("Timed out waiting for message from iframe"));
    273    }, 6000);
    274 
    275    window.addEventListener("message", t.step_func((event) => {
    276      if (event.source !== iframe.contentWindow) return;
    277      clearTimeout(timeoutId);
    278      resolve(event.data);
    279    }));
    280 
    281    iframe.contentWindow.postMessage("runTest", "*");
    282  });
    283 
    284  assert_equals(
    285    testResult.type,
    286    "rejection",
    287    "Should reject due to cross-origin restriction"
    288  );
    289  assert_equals(
    290    testResult.name,
    291    "NotAllowedError",
    292    "Should reject with NotAllowedError"
    293  );
    294  assert_true(
    295    testResult.message.includes("cross-origin iframe") ||
    296    testResult.message.includes("cross-site subframe"),
    297    `Error message should reference cross-origin. Got: "${testResult.message}"`
    298  );
    299 }, "SpeechRecognition.install should reject for on-device in a cross-origin iframe.");
    300 </script>