Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

<!DOCTYPE html>
<title>SpeechRecognition install</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/resources/testdriver.js"></script>
<script src="/resources/testdriver-vendor.js"></script>
<script>
promise_test(async (t) => {
const validLang = "en-US";
const validLangAlternateLocale = "en-GB";
const invalidLang = "invalid language code";
window.SpeechRecognition =
window.SpeechRecognition || window.webkitSpeechRecognition;
const validOptions = { langs: [validLang], processLocally: true };
const validAlternateOptions = { langs: [validLangAlternateLocale], processLocally: true };
const invalidOptions = { langs: [invalidLang], processLocally: true };
// Check the availablility of the on-device language pack.
const initialAvailabilityPromise =
SpeechRecognition.available(validOptions);
assert_true(
initialAvailabilityPromise instanceof Promise,
"available should return a Promise."
);
const initialAvailabilityResult = await initialAvailabilityPromise;
assert_true(
typeof initialAvailabilityResult === "string",
"The resolved value of the available promise should be a string."
);
if (initialAvailabilityResult === "downloadable") {
// Attempt to call install directly, without a user gesture with a
// language that is downloadable but not installed.
const installWithoutUserGesturePromise =
SpeechRecognition.install(validOptions);
// Assert that the promise rejects with NotAllowedError.
await promise_rejects_dom(
t,
"NotAllowedError",
window.DOMException,
installWithoutUserGesturePromise, "SpeechRecognition.install() " +
"must reject with NotAllowedError if called without a user gesture for on-device."
);
// Test that it returns a promise when called with a valid language.
const validResultPromise = test_driver.bless(
"Call SpeechRecognition.install with a valid language",
() => SpeechRecognition.install(validOptions)
);
const validInstallPromise = test_driver.bless(
"Call SpeechRecognition.install with valid options for on-device",
() => SpeechRecognition.install(validOptions)
);
assert_true(
validInstallPromise instanceof Promise,
"install (with gesture, for on-device) should return a Promise."
);
// Verify the resolved value is a boolean.
const validInstallResult = await validInstallPromise;
assert_true(
typeof validInstallResult === "boolean",
"The resolved value of the install promise (on-device) should be a boolean."
);
// Verify that the method returns true when called with a supported language.
assert_equals(
validInstallResult,
true,
"install should resolve with `true` when called with " +
"supported options for on-device."
);
// Verify that the newly installed language pack is available.
const availableOnDeviceResultPromise =
SpeechRecognition.available(validOptions);
assert_true(
availableOnDeviceResultPromise instanceof Promise,
"available should return a Promise."
);
const availableOnDeviceResult = await availableOnDeviceResultPromise;
assert_true(
typeof availableOnDeviceResult === "string",
"The resolved value of the available promise should be a string."
);
assert_true(
availableOnDeviceResult === "available",
"The resolved value of the available promise (on-device) should be " +
"'available'."
);
// Verify that the newly installed language pack is available for an alternate locale.
const alternateLocaleResultPromise =
SpeechRecognition.available(validAlternateOptions);
assert_true(
alternateLocaleResultPromise instanceof Promise,
"available should return a Promise."
);
const alternateLocaleResult = await alternateLocaleResultPromise;
assert_true(
typeof alternateLocaleResult === "string",
"The resolved value of the available promise should be a string."
);
assert_true(
alternateLocaleResult === "available",
"The resolved value of the available promise (on-device, alternate locale) should be " +
"'available'."
);
// Verify that installing an already installed language resolves to true.
const secondInstallPromise = test_driver.bless(
"Call SpeechRecognition.install for an already installed on-device language",
() => SpeechRecognition.install(validOptions)
);
assert_true(
secondInstallPromise instanceof Promise,
"install (with gesture, for already installed on-device language) should " +
"return a Promise."
);
const secondInstallResult = await secondInstallPromise;
assert_true(
typeof secondInstallResult === "boolean",
"The resolved value of the second install promise (on-device) should be a " +
"boolean."
);
assert_equals(
secondInstallResult,
true,
"install should resolve with `true` if the on-device language is already " +
"installed."
);
}
// Test that it returns a promise and resolves to false for unsupported lang.
const invalidInstallPromise = test_driver.bless(
"Call SpeechRecognition.install with unsupported on-device options",
() => SpeechRecognition.install(invalidOptions)
);
assert_true(
invalidInstallPromise instanceof Promise,
"install (with gesture, for unsupported on-device options) should return " +
"a Promise."
);
const invalidInstallResult = await invalidInstallPromise;
assert_true(
typeof invalidInstallResult === "boolean",
"The resolved value of the install promise (unsupported on-device options) " +
"should be a boolean."
);
assert_equals(
invalidInstallResult,
false,
"install should resolve with `false` when called with " +
"unsupported on-device options."
);
}, "SpeechRecognition.install resolves with a boolean value for on-device " +
"(with user gesture).");
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,
test_driver.bless(
"Call SpeechRecognition.install in a detached frame context for on-device",
() => {
return frameSpeechRecognition.install(options);
}
)
);
}, "SpeechRecognition.install rejects in a detached context for on-device.");
promise_test(async (t) => {
const iframe = document.createElement("iframe");
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;
const frameDOMException = frameWindow.DOMException;
assert_true(!!frameSpeechRecognition,
"SpeechRecognition should exist in iframe.");
assert_true(!!frameSpeechRecognition.install,
"install method should exist on SpeechRecognition in iframe.");
const options = { langs: ["en-US"], processLocally: true };
await promise_rejects_dom(
t,
"NotAllowedError",
frameDOMException,
frameSpeechRecognition.install(options),
"install should reject with NotAllowedError if " +
"'install-on-device-speech-recognition' Permission Policy is " +
"disabled."
);
}, "SpeechRecognition.install rejects for on-device if " +
"'install-on-device-speech-recognition' Permission Policy is disabled.");
promise_test(async (t) => {
const html = `
<!DOCTYPE html>
<script>
window.addEventListener('message', async (event) => {
try {
const SpeechRecognition = window.SpeechRecognition ||
window.webkitSpeechRecognition;
if (!SpeechRecognition || !SpeechRecognition.install) {
parent.postMessage({
type: "rejection",
name: "NotSupportedError",
message: "API not available"
}, "*");
return;
}
const options = { langs: ["en-US"], processLocally: true };
await SpeechRecognition.install(options);
parent.postMessage({ type: "resolution", result: "success" }, "*");
} catch (err) {
parent.postMessage({
type: "rejection",
name: err.name,
message: err.message
}, "*");
}
});
<\/script>
`;
// Create a cross-origin Blob URL by fetching from remote origin
const blob = new Blob([html], { type: "text/html" });
const blobUrl = URL.createObjectURL(blob);
const iframe = document.createElement("iframe");
iframe.src = blobUrl;
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("Timed out waiting for message from iframe"));
}, 6000);
window.addEventListener("message", t.step_func((event) => {
if (event.source !== iframe.contentWindow) return;
clearTimeout(timeoutId);
resolve(event.data);
}));
iframe.contentWindow.postMessage("runTest", "*");
});
assert_equals(
testResult.type,
"rejection",
"Should reject due to cross-origin restriction"
);
assert_equals(
testResult.name,
"NotAllowedError",
"Should reject with NotAllowedError"
);
assert_true(
testResult.message.includes("cross-origin iframe") ||
testResult.message.includes("cross-site subframe"),
`Error message should reference cross-origin. Got: "${testResult.message}"`
);
}, "SpeechRecognition.install should reject for on-device in a cross-origin iframe.");
</script>