Source code
Revision control
Copy as Markdown
Other Tools
Test Info:
/* Any copyright is dedicated to the Public Domain.
"use strict";
// Verify that SpeechRecognition.install() shows a permission doorhanger and
// that accepting it resolves the promise with true, while dismissing (via the
// Not Now button or the Escape key) resolves with false and removes the
// notification. Also verifies the prompt is skipped entirely once the model
// is already installed.
const PAGE =
"empty.html";
const MODEL_DOWNLOAD_NOTIFICATION_ID = "speech-recognition-model-download";
const MODEL_DOWNLOAD_PROGRESS_NOTIFICATION_ID =
"speech-recognition-model-download-progress";
async function triggerInstall(browser) {
await SpecialPowers.spawn(browser, [], () => {
// install() requires transient user activation (it can trigger a large
// download); synthesize it since this call isn't from a real user gesture.
content.document.notifyUserGestureActivation();
// Do not await — we need to return immediately so the popup can appear.
content.SpeechRecognition.install({
langs: ["en-US"],
processLocally: true,
});
});
}
add_setup(async function () {
await SpecialPowers.pushPrefEnv({
set: [["media.webspeech.recognition.enable", true]],
});
});
add_task(async function test_install_shows_doorhanger() {
await BrowserTestUtils.withNewTab(PAGE, async browser => {
let popupShown = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown"
);
await triggerInstall(browser);
await popupShown;
let notification = PopupNotifications.getNotification(
MODEL_DOWNLOAD_NOTIFICATION_ID,
browser
);
ok(notification, "speech-recognition-model-download notification exists");
// Dismiss without downloading.
let popupHidden = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popuphidden"
);
notification.remove();
await popupHidden;
});
});
// Asserts that a content-process install() call resolves with `false`.
// `installResultPromise` must be the raw promise returned from within a
// SpecialPowers.spawn task, so the settlement happens in the content process.
async function assertInstallResolvesFalse(installResultPromise, why) {
let result;
try {
result = await installResultPromise;
} catch (e) {
ok(false, `install() unexpectedly rejected in content ${why}: ${e}`);
throw e;
}
is(result, false, `install() resolves false in content ${why}`);
}
async function assertNotificationGone(browser, why) {
ok(
!PopupNotifications.getNotification(
MODEL_DOWNLOAD_NOTIFICATION_ID,
browser
),
`notification is actually removed (not just dismissed) ${why}`
);
}
add_task(async function test_install_not_now_resolves_false() {
await BrowserTestUtils.withNewTab(PAGE, async browser => {
let popupShown = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown"
);
let installResult = SpecialPowers.spawn(browser, [], async () => {
content.document.notifyUserGestureActivation();
return content.SpeechRecognition.install({
langs: ["en-US"],
processLocally: true,
});
});
await popupShown;
let notification = PopupNotifications.getNotification(
MODEL_DOWNLOAD_NOTIFICATION_ID,
browser
);
ok(notification, "Notification present before dismissal");
let popupHidden = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popuphidden"
);
let popup = document.getElementById(
"speech-recognition-model-download-notification"
);
await popup.secondaryButton.updateComplete;
popup.secondaryButton.click();
await popupHidden;
await assertNotificationGone(browser, "after clicking Not Now");
await assertInstallResolvesFalse(installResult, "after clicking Not Now");
});
});
// Verify that dismissing the prompt with the Escape key resolves install()
// with false and removes the notification, same as clicking Not Now.
add_task(async function test_install_escape_resolves_false() {
await BrowserTestUtils.withNewTab(PAGE, async browser => {
let popupShown = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown"
);
let installResult = SpecialPowers.spawn(browser, [], async () => {
content.document.notifyUserGestureActivation();
return content.SpeechRecognition.install({
langs: ["en-US"],
processLocally: true,
});
});
await popupShown;
let notification = PopupNotifications.getNotification(
MODEL_DOWNLOAD_NOTIFICATION_ID,
browser
);
ok(notification, "Notification present before dismissal");
let popupHidden = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popuphidden"
);
EventUtils.synthesizeKey("KEY_Escape");
await popupHidden;
// Let any pending _update() run before asserting the notification stays gone.
await TestUtils.waitForTick();
await assertNotificationGone(browser, "after pressing Escape");
Assert.notEqual(
PopupNotifications.panel.state,
"open",
"panel did not reopen after Escape"
);
await assertInstallResolvesFalse(installResult, "after pressing Escape");
});
});
add_task(async function test_overlapping_installs_share_prompt_and_result() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.ml.modelHub.testing", true],
["media.webspeech.recognition.model-download.prompt.testing", false],
["media.navigator.permission.disabled", false],
],
});
await BrowserTestUtils.withNewTab(PAGE, async browser => {
let popupShownCount = 0;
let onPopupShown = () => {
popupShownCount++;
};
PopupNotifications.panel.addEventListener("popupshown", onPopupShown);
try {
let popupShown = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popupshown"
);
await SpecialPowers.spawn(browser, [], () => {
content.document.notifyUserGestureActivation();
content.overlappingSpeechInstalls = [
content.SpeechRecognition.install({
langs: ["fr-FR"],
processLocally: true,
}),
];
});
await popupShown;
await SpecialPowers.spawn(browser, [], () => {
for (let i = 0; i < 2; i++) {
content.document.notifyUserGestureActivation();
content.overlappingSpeechInstalls.push(
content.SpeechRecognition.install({
langs: ["fr-FR"],
processLocally: true,
})
);
}
});
await TestUtils.waitForTick();
is(popupShownCount, 1, "overlapping install() calls share one prompt");
let popup = document.getElementById(
"speech-recognition-model-download-notification"
);
await popup.button.updateComplete;
popup.button.click();
let results = await SpecialPowers.spawn(browser, [], () =>
Promise.all(content.overlappingSpeechInstalls)
);
is(
results.join(","),
"true,true,true",
"all overlapping install() calls resolve with the same result"
);
let progressNotification = PopupNotifications.getNotification(
MODEL_DOWNLOAD_PROGRESS_NOTIFICATION_ID,
browser
);
if (progressNotification) {
let popupHidden;
if (PopupNotifications.panel.state != "closed") {
popupHidden = BrowserTestUtils.waitForEvent(
PopupNotifications.panel,
"popuphidden"
);
}
progressNotification.remove();
if (popupHidden) {
await popupHidden;
}
}
} finally {
PopupNotifications.panel.removeEventListener("popupshown", onPopupShown);
}
});
await SpecialPowers.popPrefEnv();
});
// install() must skip the permission prompt entirely once the model is
// already installed: there is nothing to download, so nothing to consent to.
add_task(async function test_install_skips_prompt_when_already_installed() {
// Mock backend; auto-allow the first install so the model becomes installed.
await SpecialPowers.pushPrefEnv({
set: [
["browser.ml.modelHub.testing", true],
["media.webspeech.recognition.model-download.prompt.testing", true],
["media.navigator.permission.disabled", true],
],
});
await BrowserTestUtils.withNewTab(PAGE, async browser => {
await SpecialPowers.spawn(browser, [], () => {
content.document.notifyUserGestureActivation();
return content.SpeechRecognition.install({
langs: ["en-US"],
processLocally: true,
});
});
let status = await SpecialPowers.spawn(browser, [], () =>
content.SpeechRecognition.available({
langs: ["en-US"],
processLocally: true,
})
);
is(status, "available", "model reports available after first install");
});
// Real prompt path. The model is now installed, so a second install() must
// resolve true without ever showing the permission prompt.
await SpecialPowers.pushPrefEnv({
set: [
["media.webspeech.recognition.model-download.prompt.testing", false],
["media.navigator.permission.disabled", false],
],
});
await BrowserTestUtils.withNewTab(PAGE, async browser => {
let popupShown = false;
let listener = () => {
popupShown = true;
};
PopupNotifications.panel.addEventListener("popupshown", listener);
let installed = await SpecialPowers.spawn(browser, [], () => {
content.document.notifyUserGestureActivation();
return content.SpeechRecognition.install({
langs: ["en-US"],
processLocally: true,
});
});
PopupNotifications.panel.removeEventListener("popupshown", listener);
ok(installed, "install() resolves true for an already-installed model");
ok(
!popupShown,
"install() must not show the prompt when already installed"
);
});
// One pop per pushPrefEnv() above, not a copy-paste duplicate.
await SpecialPowers.popPrefEnv();
await SpecialPowers.popPrefEnv();
});