Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Warnings
- This test has a WPT meta file that expects 4 subtest issues.
- This WPT test may be referenced by the following Test IDs:
- /speech-api/SpeechRecognition-installOnDevice.https.html - WPT Dashboard Interop Dashboard
<!DOCTYPE html>
<title>SpeechRecognition installOnDevice</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;
// Check the availablility of the on-device language pack.
const initialAvailabilityPromise =
SpeechRecognition.availableOnDevice(validLang);
assert_true(
initialAvailabilityPromise instanceof Promise,
"availableOnDevice should return a Promise."
);
const initialAvailabilityResult = await initialAvailabilityPromise;
assert_true(
typeof initialAvailabilityResult === "string",
"The resolved value of the availableOnDevice promise should be a string."
);
if (initialAvailabilityResult === "downloadable") {
// Attempt to call installOnDevice directly, without a user gesture with a
// language that is downloadable but not installed.
const installWithoutUserGesturePromise =
SpeechRecognition.installOnDevice(validLang);
// Assert that the promise rejects with NotAllowedError.
await promise_rejects_dom(
t,
"NotAllowedError",
window.DOMException,
installWithoutUserGesturePromise, "SpeechRecognition.installOnDevice() " +
"must reject with NotAllowedError if called without a user gesture."
);
// Test that it returns a promise when called with a valid language.
const validResultPromise = test_driver.bless(
"Call SpeechRecognition.installOnDevice with a valid language",
() => SpeechRecognition.installOnDevice(validLang)
);
assert_true(
validResultPromise instanceof Promise,
"installOnDevice (with gesture) should return a Promise."
);
// Verify the resolved value is a boolean.
const validResult = await validResultPromise;
assert_true(
typeof validResult === "boolean",
"The resolved value of the installOnDevice promise should be a boolean."
);
// Verify that the method returns true when called with a supported language.
assert_equals(
validResult,
true,
"installOnDevice should resolve with `true` when called with a " +
"supported language code."
);
// Verify that the newly installed language pack is available.
const availableOnDeviceResultPromise =
SpeechRecognition.availableOnDevice(validLang);
assert_true(
availableOnDeviceResultPromise instanceof Promise,
"availableOnDevice should return a Promise."
);
const availableOnDeviceResult = await availableOnDeviceResultPromise;
assert_true(
typeof availableOnDeviceResult === "string",
"The resolved value of the availableOnDevice promise should be a string."
);
assert_true(
availableOnDeviceResult === "available",
"The resolved value of the availableOnDevice promise should be " +
"'available'."
);
// Verify that the newly installed language pack is available for an alternate locale.
const alternateLocaleResultPromise =
SpeechRecognition.availableOnDevice(validLangAlternateLocale);
assert_true(
alternateLocaleResultPromise instanceof Promise,
"availableOnDevice should return a Promise."
);
const alternateLocaleResult = await alternateLocaleResultPromise;
assert_true(
typeof alternateLocaleResult === "string",
"The resolved value of the availableOnDevice promise should be a string."
);
assert_true(
alternateLocaleResult === "available",
"The resolved value of the availableOnDevice promise should be " +
"'available'."
);
// Verify that installing an already installed language resolves to true.
const secondResultPromise = test_driver.bless(
"Call SpeechRecognition.installOnDevice for an already installed language",
() => SpeechRecognition.installOnDevice(validLang)
);
assert_true(
secondResultPromise instanceof Promise,
"installOnDevice (with gesture, for already installed language) should " +
"return a Promise."
);
const secondResult = await secondResultPromise;
assert_true(
typeof secondResult === "boolean",
"The resolved value of the second installOnDevice promise should be a " +
"boolean."
);
assert_equals(
secondResult,
true,
"installOnDevice should resolve with `true` if the language is already " +
"installed."
);
}
// Test that it returns a promise and resolves to false for unsupported lang.
const invalidResultPromise = test_driver.bless(
"Call SpeechRecognition.installOnDevice with an unsupported language code",
() => SpeechRecognition.installOnDevice(invalidLang)
);
assert_true(
invalidResultPromise instanceof Promise,
"installOnDevice (with gesture, for unsupported language) should return " +
"a Promise."
);
const invalidResult = await invalidResultPromise;
assert_true(
typeof invalidResult === "boolean",
"The resolved value of the installOnDevice promise (unsupported language) " +
"should be a boolean."
);
assert_equals(
invalidResult,
false,
"installOnDevice should resolve with `false` when called with an " +
"unsupported language code."
);
}, "SpeechRecognition.installOnDevice resolves with a boolean value " +
"(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;
iframe.remove();
await promise_rejects_dom(
t,
"InvalidStateError",
frameDOMException,
test_driver.bless(
"Call SpeechRecognition.installOnDevice in a detached frame context",
() => {
return frameSpeechRecognition.installOnDevice("en-US");
}
)
);
}, "SpeechRecognition.installOnDevice rejects in a detached context.");
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.installOnDevice,
"installOnDevice method should exist on SpeechRecognition in iframe.");
await promise_rejects_dom(
t,
"NotAllowedError",
frameDOMException,
frameSpeechRecognition.installOnDevice("en-US"),
"installOnDevice should reject with NotAllowedError if " +
"'install-on-device-speech-recognition' Permission Policy is " +
"disabled."
);
}, "SpeechRecognition.installOnDevice rejects 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.installOnDevice) {
parent.postMessage({
type: "rejection",
name: "NotSupportedError",
message: "API not available"
}, "*");
return;
}
await SpeechRecognition.installOnDevice("en-US");
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.installOnDevice should reject in a cross-origin iframe.");
</script>