448 lines
14 KiB
JavaScript
448 lines
14 KiB
JavaScript
/* Any copyright is dedicated to the Public Domain.
|
|
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
|
|
const { PermissionTestUtils } = ChromeUtils.importESModule(
|
|
"resource://testing-common/PermissionTestUtils.sys.mjs"
|
|
);
|
|
|
|
ChromeUtils.defineLazyGetter(this, "QuickSuggestTestUtils", () => {
|
|
const { QuickSuggestTestUtils: module } = ChromeUtils.importESModule(
|
|
"resource://testing-common/QuickSuggestTestUtils.sys.mjs"
|
|
);
|
|
module.init(this);
|
|
return module;
|
|
});
|
|
|
|
const kDefaultWait = 2000;
|
|
|
|
function is_element_visible(aElement, aMsg) {
|
|
isnot(aElement, null, "Element should not be null, when checking visibility");
|
|
ok(!BrowserTestUtils.isHidden(aElement), aMsg);
|
|
}
|
|
|
|
function is_element_hidden(aElement, aMsg) {
|
|
isnot(aElement, null, "Element should not be null, when checking visibility");
|
|
ok(BrowserTestUtils.isHidden(aElement), aMsg);
|
|
}
|
|
|
|
function open_preferences(aCallback) {
|
|
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:preferences");
|
|
let newTabBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
|
|
newTabBrowser.addEventListener(
|
|
"Initialized",
|
|
function () {
|
|
aCallback(gBrowser.contentWindow);
|
|
},
|
|
{ capture: true, once: true }
|
|
);
|
|
}
|
|
|
|
function openAndLoadSubDialog(
|
|
aURL,
|
|
aFeatures = null,
|
|
aParams = null,
|
|
aClosingCallback = null
|
|
) {
|
|
let promise = promiseLoadSubDialog(aURL);
|
|
content.gSubDialog.open(
|
|
aURL,
|
|
{ features: aFeatures, closingCallback: aClosingCallback },
|
|
aParams
|
|
);
|
|
return promise;
|
|
}
|
|
|
|
function promiseLoadSubDialog(aURL) {
|
|
return new Promise(resolve => {
|
|
content.gSubDialog._dialogStack.addEventListener(
|
|
"dialogopen",
|
|
function dialogopen(aEvent) {
|
|
if (
|
|
aEvent.detail.dialog._frame.contentWindow.location == "about:blank"
|
|
) {
|
|
return;
|
|
}
|
|
content.gSubDialog._dialogStack.removeEventListener(
|
|
"dialogopen",
|
|
dialogopen
|
|
);
|
|
|
|
is(
|
|
aEvent.detail.dialog._frame.contentWindow.location.toString(),
|
|
aURL,
|
|
"Check the proper URL is loaded"
|
|
);
|
|
|
|
// Check visibility
|
|
is_element_visible(aEvent.detail.dialog._overlay, "Overlay is visible");
|
|
|
|
// Check that stylesheets were injected
|
|
let expectedStyleSheetURLs =
|
|
aEvent.detail.dialog._injectedStyleSheets.slice(0);
|
|
for (let styleSheet of aEvent.detail.dialog._frame.contentDocument
|
|
.styleSheets) {
|
|
let i = expectedStyleSheetURLs.indexOf(styleSheet.href);
|
|
if (i >= 0) {
|
|
info("found " + styleSheet.href);
|
|
expectedStyleSheetURLs.splice(i, 1);
|
|
}
|
|
}
|
|
is(
|
|
expectedStyleSheetURLs.length,
|
|
0,
|
|
"All expectedStyleSheetURLs should have been found"
|
|
);
|
|
|
|
// Wait for the next event tick to make sure the remaining part of the
|
|
// testcase runs after the dialog gets ready for input.
|
|
executeSoon(() => resolve(aEvent.detail.dialog._frame.contentWindow));
|
|
}
|
|
);
|
|
});
|
|
}
|
|
|
|
async function openPreferencesViaOpenPreferencesAPI(aPane, aOptions) {
|
|
let finalPaneEvent = Services.prefs.getBoolPref("identity.fxaccounts.enabled")
|
|
? "sync-pane-loaded"
|
|
: "privacy-pane-loaded";
|
|
let finalPrefPaneLoaded = TestUtils.topicObserved(finalPaneEvent, () => true);
|
|
gBrowser.selectedTab = BrowserTestUtils.addTab(gBrowser, "about:blank", {
|
|
allowInheritPrincipal: true,
|
|
});
|
|
openPreferences(aPane, aOptions);
|
|
let newTabBrowser = gBrowser.selectedBrowser;
|
|
|
|
if (!newTabBrowser.contentWindow) {
|
|
await BrowserTestUtils.waitForEvent(newTabBrowser, "Initialized", true);
|
|
await BrowserTestUtils.waitForEvent(newTabBrowser.contentWindow, "load");
|
|
await finalPrefPaneLoaded;
|
|
}
|
|
|
|
let win = gBrowser.contentWindow;
|
|
let selectedPane = win.history.state;
|
|
if (!aOptions || !aOptions.leaveOpen) {
|
|
gBrowser.removeCurrentTab();
|
|
}
|
|
return { selectedPane };
|
|
}
|
|
|
|
async function runSearchInput(input) {
|
|
let searchInput = gBrowser.contentDocument.getElementById("searchInput");
|
|
searchInput.focus();
|
|
let searchCompletedPromise = BrowserTestUtils.waitForEvent(
|
|
gBrowser.contentWindow,
|
|
"PreferencesSearchCompleted",
|
|
evt => evt.detail == input
|
|
);
|
|
EventUtils.sendString(input);
|
|
await searchCompletedPromise;
|
|
}
|
|
|
|
async function evaluateSearchResults(
|
|
keyword,
|
|
searchResults,
|
|
includeExperiments = false
|
|
) {
|
|
searchResults = Array.isArray(searchResults)
|
|
? searchResults
|
|
: [searchResults];
|
|
searchResults.push("header-searchResults");
|
|
|
|
await runSearchInput(keyword);
|
|
|
|
let mainPrefTag = gBrowser.contentDocument.getElementById("mainPrefPane");
|
|
for (let i = 0; i < mainPrefTag.childElementCount; i++) {
|
|
let child = mainPrefTag.children[i];
|
|
if (!includeExperiments && child.id?.startsWith("pane-experimental")) {
|
|
continue;
|
|
}
|
|
if (searchResults.includes(child.id)) {
|
|
is_element_visible(child, `${child.id} should be in search results`);
|
|
} else if (child.id) {
|
|
is_element_hidden(child, `${child.id} should not be in search results`);
|
|
}
|
|
}
|
|
}
|
|
|
|
function waitForMutation(target, opts, cb) {
|
|
return new Promise(resolve => {
|
|
let observer = new MutationObserver(() => {
|
|
if (!cb || cb(target)) {
|
|
observer.disconnect();
|
|
resolve();
|
|
}
|
|
});
|
|
observer.observe(target, opts);
|
|
});
|
|
}
|
|
|
|
// Used to add sample experimental features for testing. To use, create
|
|
// a DefinitionServer, then call addDefinition as needed.
|
|
class DefinitionServer {
|
|
constructor(definitionOverrides = []) {
|
|
let { HttpServer } = ChromeUtils.importESModule(
|
|
"resource://testing-common/httpd.sys.mjs"
|
|
);
|
|
|
|
this.server = new HttpServer();
|
|
this.server.registerPathHandler("/definitions.json", this);
|
|
this.definitions = {};
|
|
|
|
for (const override of definitionOverrides) {
|
|
this.addDefinition(override);
|
|
}
|
|
|
|
this.server.start();
|
|
registerCleanupFunction(
|
|
() => new Promise(resolve => this.server.stop(resolve))
|
|
);
|
|
}
|
|
|
|
// for nsIHttpRequestHandler
|
|
handle(request, response) {
|
|
response.write(JSON.stringify(this.definitions));
|
|
}
|
|
|
|
get definitionsUrl() {
|
|
const { primaryScheme, primaryHost, primaryPort } = this.server.identity;
|
|
return `${primaryScheme}://${primaryHost}:${primaryPort}/definitions.json`;
|
|
}
|
|
|
|
addDefinition(overrides = {}) {
|
|
const definition = {
|
|
id: "test-feature",
|
|
// These l10n IDs are just random so we have some text to display
|
|
title: "experimental-features-media-jxl",
|
|
group: "experimental-features-group-customize-browsing",
|
|
description: "pane-experimental-description3",
|
|
restartRequired: false,
|
|
type: "boolean",
|
|
preference: "test.feature",
|
|
defaultValue: false,
|
|
isPublic: false,
|
|
...overrides,
|
|
};
|
|
// convert targeted values, used by fromId
|
|
definition.isPublic = { default: definition.isPublic };
|
|
definition.defaultValue = { default: definition.defaultValue };
|
|
this.definitions[definition.id] = definition;
|
|
return definition;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates observer that waits for and then compares all perm-changes with the observances in order.
|
|
* @param {Array} observances permission changes to observe (order is important)
|
|
* @returns {Promise} Promise object that resolves once all permission changes have been observed
|
|
*/
|
|
function createObserveAllPromise(observances) {
|
|
// Create new promise that resolves once all items
|
|
// in observances array have been observed.
|
|
return new Promise(resolve => {
|
|
let permObserver = {
|
|
observe(aSubject, aTopic, aData) {
|
|
if (aTopic != "perm-changed") {
|
|
return;
|
|
}
|
|
|
|
if (!observances.length) {
|
|
// See bug 1063410
|
|
return;
|
|
}
|
|
|
|
let permission = aSubject.QueryInterface(Ci.nsIPermission);
|
|
let expected = observances.shift();
|
|
|
|
info(
|
|
`observed perm-changed for ${permission.principal.origin} (remaining ${observances.length})`
|
|
);
|
|
|
|
is(aData, expected.data, "type of message should be the same");
|
|
for (let prop of ["type", "capability", "expireType"]) {
|
|
if (expected[prop]) {
|
|
is(
|
|
permission[prop],
|
|
expected[prop],
|
|
`property: "${prop}" should be equal (${permission.principal.origin})`
|
|
);
|
|
}
|
|
}
|
|
|
|
if (expected.origin) {
|
|
is(
|
|
permission.principal.origin,
|
|
expected.origin,
|
|
`property: "origin" should be equal (${permission.principal.origin})`
|
|
);
|
|
}
|
|
|
|
if (!observances.length) {
|
|
Services.obs.removeObserver(permObserver, "perm-changed");
|
|
executeSoon(resolve);
|
|
}
|
|
},
|
|
};
|
|
Services.obs.addObserver(permObserver, "perm-changed");
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Waits for preference to be set and asserts the value.
|
|
* @param {string} pref - Preference key.
|
|
* @param {*} expectedValue - Expected value of the preference.
|
|
* @param {string} message - Assertion message.
|
|
*/
|
|
async function waitForAndAssertPrefState(pref, expectedValue, message) {
|
|
await TestUtils.waitForPrefChange(pref, value => {
|
|
if (value != expectedValue) {
|
|
return false;
|
|
}
|
|
is(value, expectedValue, message);
|
|
return true;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* The Relay promo is not shown for distributions with a custom FxA instance,
|
|
* since Relay requires an account on our own server. These prefs are set to a
|
|
* dummy address by the test harness, filling the prefs with a "user value."
|
|
* This temporarily sets the default value equal to the dummy value, so that
|
|
* Firefox thinks we've configured the correct FxA server.
|
|
* @returns {Promise<MockFxAUtilityFunctions>} { mock, unmock }
|
|
*/
|
|
async function mockDefaultFxAInstance() {
|
|
/**
|
|
* @typedef {Object} MockFxAUtilityFunctions
|
|
* @property {function():void} mock - Makes the dummy values default, creating
|
|
* the illusion of a production FxA instance.
|
|
* @property {function():void} unmock - Restores the true defaults, creating
|
|
* the illusion of a custom FxA instance.
|
|
*/
|
|
|
|
const defaultPrefs = Services.prefs.getDefaultBranch("");
|
|
const userPrefs = Services.prefs.getBranch("");
|
|
const realAuth = defaultPrefs.getCharPref("identity.fxaccounts.auth.uri");
|
|
const realRoot = defaultPrefs.getCharPref("identity.fxaccounts.remote.root");
|
|
const mockAuth = userPrefs.getCharPref("identity.fxaccounts.auth.uri");
|
|
const mockRoot = userPrefs.getCharPref("identity.fxaccounts.remote.root");
|
|
const mock = () => {
|
|
defaultPrefs.setCharPref("identity.fxaccounts.auth.uri", mockAuth);
|
|
defaultPrefs.setCharPref("identity.fxaccounts.remote.root", mockRoot);
|
|
userPrefs.clearUserPref("identity.fxaccounts.auth.uri");
|
|
userPrefs.clearUserPref("identity.fxaccounts.remote.root");
|
|
};
|
|
const unmock = () => {
|
|
defaultPrefs.setCharPref("identity.fxaccounts.auth.uri", realAuth);
|
|
defaultPrefs.setCharPref("identity.fxaccounts.remote.root", realRoot);
|
|
userPrefs.setCharPref("identity.fxaccounts.auth.uri", mockAuth);
|
|
userPrefs.setCharPref("identity.fxaccounts.remote.root", mockRoot);
|
|
};
|
|
|
|
mock();
|
|
registerCleanupFunction(unmock);
|
|
|
|
return { mock, unmock };
|
|
}
|
|
|
|
/**
|
|
* Runs a test that checks the visibility of the Firefox Suggest preferences UI.
|
|
* An initial Suggest scenario is set and visibility is checked. Then a Nimbus
|
|
* experiment is installed and visibility is checked again. Finally the page is
|
|
* reopened and visibility is checked again.
|
|
*
|
|
* @param {array} initialScenarios
|
|
* Array of Suggest scenario names. The test will be run once per scenario,
|
|
* with each test starting with a given scenario.
|
|
* @param {object} initialExpected
|
|
* The expected visibility after setting the initial scenario. It should be an
|
|
* object that can be passed to `assertSuggestVisibility()`.
|
|
* @param {object} nimbusVariables
|
|
* An object mapping Nimbus variable names to values.
|
|
* @param {object} newExpected
|
|
* The expected visibility after installing the Nimbus experiment. It should
|
|
* be an object that can be passed to `assertSuggestVisibility()`.
|
|
* @param {string} pane
|
|
* The pref pane to open.
|
|
*/
|
|
async function doSuggestVisibilityTest({
|
|
initialScenarios,
|
|
initialExpected,
|
|
nimbusVariables,
|
|
newExpected = initialExpected,
|
|
pane = "search",
|
|
}) {
|
|
for (let scenario of initialScenarios) {
|
|
info(
|
|
"Running Suggest visibility test: " +
|
|
JSON.stringify(
|
|
{
|
|
scenario,
|
|
initialExpected,
|
|
nimbusVariables,
|
|
newExpected,
|
|
},
|
|
null,
|
|
2
|
|
)
|
|
);
|
|
|
|
// Set the initial scenario.
|
|
await QuickSuggestTestUtils.setScenario(scenario);
|
|
|
|
// Open prefs and check the initial visibility.
|
|
await openPreferencesViaOpenPreferencesAPI(pane, { leaveOpen: true });
|
|
assertSuggestVisibility(initialExpected);
|
|
|
|
// Install a Nimbus experiment.
|
|
await QuickSuggestTestUtils.withExperiment({
|
|
valueOverrides: nimbusVariables,
|
|
callback: async () => {
|
|
// Check visibility again.
|
|
assertSuggestVisibility(newExpected);
|
|
|
|
// To make sure visibility is properly updated on load, close the tab,
|
|
// open the prefs again, and check visibility.
|
|
gBrowser.removeCurrentTab();
|
|
await openPreferencesViaOpenPreferencesAPI(pane, { leaveOpen: true });
|
|
assertSuggestVisibility(newExpected);
|
|
},
|
|
});
|
|
|
|
gBrowser.removeCurrentTab();
|
|
}
|
|
|
|
await QuickSuggestTestUtils.setScenario(null);
|
|
}
|
|
|
|
/**
|
|
* Checks the visibility of the Suggest UI.
|
|
*
|
|
* @param {object} expectedByElementId
|
|
* An object that maps IDs of elements in the current tab to objects with the
|
|
* following properties:
|
|
*
|
|
* {bool} isVisible
|
|
* Whether the element is expected to be visible.
|
|
* {string} l10nId
|
|
* The expected l10n ID of the element. Optional.
|
|
*/
|
|
function assertSuggestVisibility(expectedByElementId) {
|
|
let doc = gBrowser.selectedBrowser.contentDocument;
|
|
for (let [elementId, { isVisible, l10nId }] of Object.entries(
|
|
expectedByElementId
|
|
)) {
|
|
let element = doc.getElementById(elementId);
|
|
Assert.strictEqual(
|
|
BrowserTestUtils.isVisible(element),
|
|
isVisible,
|
|
"The element should be visible as expected"
|
|
);
|
|
if (l10nId) {
|
|
Assert.equal(
|
|
element.dataset.l10nId,
|
|
l10nId,
|
|
"The l10n ID should be correct for element " + elementId
|
|
);
|
|
}
|
|
}
|
|
}
|