Automatic update from web-platform-tests Update on-device Web Speech API with spec changes This CL updates the Web Speech API with the following spec[1] changes: - Rename "availableOnDevice" and "installOnDevice" to "available" and "install" - Update both APIs to support querying availability/installing multiple languages in a single call via a new SpeechRecognitionOptions - Replace the SpeechRecognitionMode mode with a processLocally attribute [1] https://webaudio.github.io/web-speech-api/ Bug: 40286514 Change-Id: Ib9a924a1a69bcaa9cdde219c591bcd945bbe7a86 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6599497 Commit-Queue: Evan Liu <evliu@google.com> Reviewed-by: Daniel Cheng <dcheng@chromium.org> Reviewed-by: Frank Liberato <liberato@chromium.org> Reviewed-by: Fred Shih <ffred@chromium.org> Cr-Commit-Position: refs/heads/main@{#1468347} -- wpt-commits: 02dd61b64c13e0d6c177af9b9e6d5f6dc06a4c70 wpt-pr: 52905
187 lines
6.6 KiB
HTML
187 lines
6.6 KiB
HTML
<!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>
|