File: SpeechRecognition-availableOnDevice.https.html

package info (click to toggle)
thunderbird 1%3A140.4.0esr-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 4,609,432 kB
  • sloc: cpp: 7,672,442; javascript: 5,901,613; ansic: 3,898,954; python: 1,413,343; xml: 653,997; asm: 462,286; java: 180,927; sh: 113,489; makefile: 20,460; perl: 14,288; objc: 13,059; yacc: 4,583; pascal: 3,352; lex: 1,720; ruby: 1,222; exp: 762; sql: 715; awk: 580; php: 436; lisp: 430; sed: 70; csh: 10
file content (183 lines) | stat: -rw-r--r-- 6,604 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
<!DOCTYPE html>
<title>SpeechRecognition availableOnDevice</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
promise_test(async (t) => {
  const lang = "en-US";
  window.SpeechRecognition = window.SpeechRecognition ||
    window.webkitSpeechRecognition;

  // Test that it returns a promise.
  const resultPromise = SpeechRecognition.availableOnDevice(lang);
  assert_true(
    resultPromise instanceof Promise,
    "availableOnDevice 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 availableOnDevice promise should be a string."
  );

  assert_true(
    result === "unavailable" || result === "downloadable" ||
    result === "downloading" || result === "available",
    "The resolved value of the availableOnDevice promise should be a " +
    "valid value."
  );
}, "SpeechRecognition.availableOnDevice resolves with a string value.");

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;

  iframe.remove();
  await promise_rejects_dom(
    t,
    "InvalidStateError",
    frameDOMException,
    frameSpeechRecognition.availableOnDevice("en-US"),
  );
}, "SpeechRecognition.availableOnDevice rejects in a detached context.");

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.availableOnDevice,
    "availableOnDevice method should exist on SpeechRecognition in iframe.");

  // Call availableOnDevice and expect it to resolve to "unavailable".
  const availabilityStatus =
    await frameSpeechRecognition.availableOnDevice("en-US");
  assert_equals(availabilityStatus, "unavailable",
    "availableOnDevice should resolve to 'unavailable' if " +
    "'on-device-speech-recognition' Permission Policy is 'none'."
  );
}, "SpeechRecognition.availableOnDevice resolves to 'unavailable' 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 !== "runTestCallAvailableOnDevice") return;

        try {
          const SpeechRecognition = window.SpeechRecognition ||
                                    window.webkitSpeechRecognition;
          if (!SpeechRecognition || !SpeechRecognition.availableOnDevice) {
            parent.postMessage({
              type: "error", // Use "error" for API not found or other issues.
              name: "NotSupportedError",
              message: "SpeechRecognition.availableOnDevice API not " +
                       "available in iframe"
            }, "*");
            return;
          }

          // Call availableOnDevice and post its resolution.
          const availabilityStatus =
              await SpeechRecognition.availableOnDevice("en-US");
          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("runTestCallAvailableOnDevice", "*");
  });

  // 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",
    "availableOnDevice should resolve to 'unavailable' in a cross-origin " +
    "iframe."
  );
}, "SpeechRecognition.availableOnDevice should resolve to 'unavailable' " +
   "in a cross-origin iframe.");
</script>