summaryrefslogtreecommitdiffstats
path: root/browser/modules/test/browser/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/modules/test/browser/head.js')
-rw-r--r--browser/modules/test/browser/head.js331
1 files changed, 331 insertions, 0 deletions
diff --git a/browser/modules/test/browser/head.js b/browser/modules/test/browser/head.js
new file mode 100644
index 0000000000..f852cdd641
--- /dev/null
+++ b/browser/modules/test/browser/head.js
@@ -0,0 +1,331 @@
+ChromeUtils.defineESModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+ TelemetryTestUtils: "resource://testing-common/TelemetryTestUtils.sys.mjs",
+});
+
+const SINGLE_TRY_TIMEOUT = 100;
+const NUMBER_OF_TRIES = 30;
+
+function waitForConditionPromise(
+ condition,
+ timeoutMsg,
+ tryCount = NUMBER_OF_TRIES
+) {
+ return new Promise((resolve, reject) => {
+ let tries = 0;
+ function checkCondition() {
+ if (tries >= tryCount) {
+ reject(timeoutMsg);
+ }
+ var conditionPassed;
+ try {
+ conditionPassed = condition();
+ } catch (e) {
+ return reject(e);
+ }
+ if (conditionPassed) {
+ return resolve();
+ }
+ tries++;
+ setTimeout(checkCondition, SINGLE_TRY_TIMEOUT);
+ return undefined;
+ }
+ setTimeout(checkCondition, SINGLE_TRY_TIMEOUT);
+ });
+}
+
+function waitForCondition(condition, nextTest, errorMsg) {
+ waitForConditionPromise(condition, errorMsg).then(nextTest, reason => {
+ ok(false, reason + (reason.stack ? "\n" + reason.stack : ""));
+ });
+}
+
+/**
+ * An utility function to write some text in the search input box
+ * in a content page.
+ * @param {Object} browser
+ * The browser that contains the content.
+ * @param {String} text
+ * The string to write in the search field.
+ * @param {String} fieldName
+ * The name of the field to write to.
+ */
+let typeInSearchField = async function (browser, text, fieldName) {
+ await SpecialPowers.spawn(
+ browser,
+ [[fieldName, text]],
+ async function ([contentFieldName, contentText]) {
+ // Put the focus on the search box.
+ let searchInput = content.document.getElementById(contentFieldName);
+ searchInput.focus();
+ searchInput.value = contentText;
+ }
+ );
+};
+
+/**
+ * Given a <xul:browser> at some non-internal web page,
+ * return something that resembles an nsIContentPermissionRequest,
+ * using the browsers currently loaded document to get a principal.
+ *
+ * @param browser (<xul:browser>)
+ * The browser that we'll create a nsIContentPermissionRequest
+ * for.
+ * @returns A nsIContentPermissionRequest-ish object.
+ */
+function makeMockPermissionRequest(browser) {
+ let type = {
+ options: Cc["@mozilla.org/array;1"].createInstance(Ci.nsIArray),
+ QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionType"]),
+ };
+ let types = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+ types.appendElement(type);
+ let principal = browser.contentPrincipal;
+ let result = {
+ types,
+ isHandlingUserInput: false,
+ principal,
+ topLevelPrincipal: principal,
+ requester: null,
+ _cancelled: false,
+ cancel() {
+ this._cancelled = true;
+ },
+ _allowed: false,
+ allow() {
+ this._allowed = true;
+ },
+ getDelegatePrincipal(aType) {
+ return principal;
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIContentPermissionRequest"]),
+ };
+
+ // In the e10s-case, nsIContentPermissionRequest will have
+ // element defined. window is defined otherwise.
+ if (browser.isRemoteBrowser) {
+ result.element = browser;
+ } else {
+ result.window = browser.contentWindow;
+ }
+
+ return result;
+}
+
+/**
+ * For an opened PopupNotification, clicks on the main action,
+ * and waits for the panel to fully close.
+ *
+ * @return {Promise}
+ * Resolves once the panel has fired the "popuphidden"
+ * event.
+ */
+function clickMainAction() {
+ let removePromise = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ let popupNotification = getPopupNotificationNode();
+ popupNotification.button.click();
+ return removePromise;
+}
+
+/**
+ * For an opened PopupNotification, clicks on the secondary action,
+ * and waits for the panel to fully close.
+ *
+ * @param actionIndex (Number)
+ * The index of the secondary action to be clicked. The default
+ * secondary action (the button shown directly in the panel) is
+ * treated as having index 0.
+ *
+ * @return {Promise}
+ * Resolves once the panel has fired the "popuphidden"
+ * event.
+ */
+function clickSecondaryAction(actionIndex) {
+ let removePromise = BrowserTestUtils.waitForEvent(
+ PopupNotifications.panel,
+ "popuphidden"
+ );
+ let popupNotification = getPopupNotificationNode();
+ if (!actionIndex) {
+ popupNotification.secondaryButton.click();
+ return removePromise;
+ }
+
+ return (async function () {
+ // Click the dropmarker arrow and wait for the menu to show up.
+ let dropdownPromise = BrowserTestUtils.waitForEvent(
+ popupNotification.menupopup,
+ "popupshown"
+ );
+ await EventUtils.synthesizeMouseAtCenter(popupNotification.menubutton, {});
+ await dropdownPromise;
+
+ // The menuitems in the dropdown are accessible as direct children of the panel,
+ // because they are injected into a <children> node in the XBL binding.
+ // The target action is the menuitem at index actionIndex - 1, because the first
+ // secondary action (index 0) is the button shown directly in the panel.
+ let actionMenuItem =
+ popupNotification.querySelectorAll("menuitem")[actionIndex - 1];
+ await EventUtils.synthesizeMouseAtCenter(actionMenuItem, {});
+ await removePromise;
+ })();
+}
+
+/**
+ * Makes sure that 1 (and only 1) <xul:popupnotification> is being displayed
+ * by PopupNotification, and then returns that <xul:popupnotification>.
+ *
+ * @return {<xul:popupnotification>}
+ */
+function getPopupNotificationNode() {
+ // PopupNotification is a bit overloaded here, so to be
+ // clear, popupNotifications is a list of <xul:popupnotification>
+ // nodes.
+ let popupNotifications = PopupNotifications.panel.childNodes;
+ Assert.equal(
+ popupNotifications.length,
+ 1,
+ "Should be showing a <xul:popupnotification>"
+ );
+ return popupNotifications[0];
+}
+
+/**
+ * Disable non-release page actions (that are tested elsewhere).
+ *
+ * @return void
+ */
+async function disableNonReleaseActions() {
+ if (!["release", "esr"].includes(AppConstants.MOZ_UPDATE_CHANNEL)) {
+ SpecialPowers.Services.prefs.setBoolPref(
+ "extensions.webcompat-reporter.enabled",
+ false
+ );
+ }
+}
+
+function assertActivatedPageActionPanelHidden() {
+ Assert.ok(
+ !document.getElementById(BrowserPageActions._activatedActionPanelID)
+ );
+}
+
+function promiseOpenPageActionPanel() {
+ let dwu = window.windowUtils;
+ return TestUtils.waitForCondition(() => {
+ // Wait for the main page action button to become visible. It's hidden for
+ // some URIs, so depending on when this is called, it may not yet be quite
+ // visible. It's up to the caller to make sure it will be visible.
+ info("Waiting for main page action button to have non-0 size");
+ let bounds = dwu.getBoundsWithoutFlushing(
+ BrowserPageActions.mainButtonNode
+ );
+ return bounds.width > 0 && bounds.height > 0;
+ })
+ .then(() => {
+ // Wait for the panel to become open, by clicking the button if necessary.
+ info("Waiting for main page action panel to be open");
+ if (BrowserPageActions.panelNode.state == "open") {
+ return Promise.resolve();
+ }
+ let shownPromise = promisePageActionPanelShown();
+ EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
+ return shownPromise;
+ })
+ .then(() => {
+ // Wait for items in the panel to become visible.
+ return promisePageActionViewChildrenVisible(
+ BrowserPageActions.mainViewNode
+ );
+ });
+}
+
+function promisePageActionPanelShown() {
+ return promisePanelShown(BrowserPageActions.panelNode);
+}
+
+function promisePageActionPanelHidden() {
+ return promisePanelHidden(BrowserPageActions.panelNode);
+}
+
+function promisePanelShown(panelIDOrNode) {
+ return promisePanelEvent(panelIDOrNode, "popupshown");
+}
+
+function promisePanelHidden(panelIDOrNode) {
+ return promisePanelEvent(panelIDOrNode, "popuphidden");
+}
+
+function promisePanelEvent(panelIDOrNode, eventType) {
+ return new Promise(resolve => {
+ let panel = panelIDOrNode;
+ if (typeof panel == "string") {
+ panel = document.getElementById(panelIDOrNode);
+ if (!panel) {
+ throw new Error(`Panel with ID "${panelIDOrNode}" does not exist.`);
+ }
+ }
+ if (
+ (eventType == "popupshown" && panel.state == "open") ||
+ (eventType == "popuphidden" && panel.state == "closed")
+ ) {
+ executeSoon(resolve);
+ return;
+ }
+ panel.addEventListener(
+ eventType,
+ () => {
+ executeSoon(resolve);
+ },
+ { once: true }
+ );
+ });
+}
+
+function promisePageActionViewShown() {
+ info("promisePageActionViewShown waiting for ViewShown");
+ return BrowserTestUtils.waitForEvent(
+ BrowserPageActions.panelNode,
+ "ViewShown"
+ ).then(async event => {
+ let panelViewNode = event.originalTarget;
+ await promisePageActionViewChildrenVisible(panelViewNode);
+ return panelViewNode;
+ });
+}
+
+async function promisePageActionViewChildrenVisible(panelViewNode) {
+ info(
+ "promisePageActionViewChildrenVisible waiting for a child node to be visible"
+ );
+ await new Promise(requestAnimationFrame);
+ let dwu = window.windowUtils;
+ return TestUtils.waitForCondition(() => {
+ let bodyNode = panelViewNode.firstElementChild;
+ for (let childNode of bodyNode.children) {
+ let bounds = dwu.getBoundsWithoutFlushing(childNode);
+ if (bounds.width > 0 && bounds.height > 0) {
+ return true;
+ }
+ }
+ return false;
+ });
+}
+
+async function initPageActionsTest() {
+ await disableNonReleaseActions();
+
+ // Ensure screenshots is really disabled (bug 1498738)
+ const addon = await AddonManager.getAddonByID("screenshots@mozilla.org");
+ await addon.disable({ allowSystemAddons: true });
+
+ // Make the main button visible. It's not unless the window is narrow. This
+ // test isn't concerned with that behavior. We have other tests for that.
+ BrowserPageActions.mainButtonNode.style.visibility = "visible";
+ registerCleanupFunction(() => {
+ BrowserPageActions.mainButtonNode.style.removeProperty("visibility");
+ });
+}