fune/browser/components/preferences/tests/head.js

264 lines
8.1 KiB
JavaScript

/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
const { Promise } = ChromeUtils.import("resource://gre/modules/Promise.jsm");
const { PermissionTestUtils } = ChromeUtils.import(
"resource://testing-common/PermissionTestUtils.jsm"
);
const kDefaultWait = 2000;
function is_element_visible(aElement, aMsg) {
isnot(aElement, null, "Element should not be null, when checking visibility");
ok(!BrowserTestUtils.is_hidden(aElement), aMsg);
}
function is_element_hidden(aElement, aMsg) {
isnot(aElement, null, "Element should not be null, when checking visibility");
ok(BrowserTestUtils.is_hidden(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, reject) => {
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));
}
);
});
}
/**
* Waits a specified number of miliseconds for a specified event to be
* fired on a specified element.
*
* Usage:
* let receivedEvent = waitForEvent(element, "eventName");
* // Do some processing here that will cause the event to be fired
* // ...
* // Now yield until the Promise is fulfilled
* yield receivedEvent;
* if (receivedEvent && !(receivedEvent instanceof Error)) {
* receivedEvent.msg == "eventName";
* // ...
* }
*
* @param aSubject the element that should receive the event
* @param aEventName the event to wait for
* @param aTimeoutMs the number of miliseconds to wait before giving up
* @returns a Promise that resolves to the received event, or to an Error
*/
function waitForEvent(aSubject, aEventName, aTimeoutMs, aTarget) {
let eventDeferred = Promise.defer();
let timeoutMs = aTimeoutMs || kDefaultWait;
let stack = new Error().stack;
let timerID = setTimeout(function wfe_canceller() {
aSubject.removeEventListener(aEventName, listener);
eventDeferred.reject(new Error(aEventName + " event timeout at " + stack));
}, timeoutMs);
var listener = function(aEvent) {
if (aTarget && aTarget !== aEvent.target) {
return;
}
// stop the timeout clock and resume
clearTimeout(timerID);
eventDeferred.resolve(aEvent);
};
function cleanup(aEventOrError) {
// unhook listener in case of success or failure
aSubject.removeEventListener(aEventName, listener);
return aEventOrError;
}
aSubject.addEventListener(aEventName, listener);
return eventDeferred.promise.then(cleanup, cleanup);
}
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");
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 evaluateSearchResults(
keyword,
searchReults,
includeExperiments = false
) {
searchReults = Array.isArray(searchReults) ? searchReults : [searchReults];
searchReults.push("header-searchResults");
let searchInput = gBrowser.contentDocument.getElementById("searchInput");
searchInput.focus();
let searchCompletedPromise = BrowserTestUtils.waitForEvent(
gBrowser.contentWindow,
"PreferencesSearchCompleted",
evt => evt.detail == keyword
);
EventUtils.sendString(keyword);
await searchCompletedPromise;
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 (searchReults.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.import(
"resource://testing-common/httpd.js"
);
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-avif",
description: "pane-experimental-description2",
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;
}
}