290 lines
9.7 KiB
HTML
290 lines
9.7 KiB
HTML
<!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>
|