From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- browser/components/customizableui/test/head.js | 536 +++++++++++++++++++++++++ 1 file changed, 536 insertions(+) create mode 100644 browser/components/customizableui/test/head.js (limited to 'browser/components/customizableui/test/head.js') diff --git a/browser/components/customizableui/test/head.js b/browser/components/customizableui/test/head.js new file mode 100644 index 0000000000..cf1b86d929 --- /dev/null +++ b/browser/components/customizableui/test/head.js @@ -0,0 +1,536 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +"use strict"; + +ChromeUtils.defineESModuleGetters(this, { + CustomizableUI: "resource:///modules/CustomizableUI.sys.mjs", + CustomizableUITestUtils: + "resource://testing-common/CustomizableUITestUtils.sys.mjs", +}); + +var EventUtils = {}; +Services.scriptloader.loadSubScript( + "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", + EventUtils +); + +/** + * Instance of CustomizableUITestUtils for the current browser window. + */ +var gCUITestUtils = new CustomizableUITestUtils(window); + +Services.prefs.setBoolPref("browser.uiCustomization.skipSourceNodeCheck", true); +registerCleanupFunction(() => + Services.prefs.clearUserPref("browser.uiCustomization.skipSourceNodeCheck") +); + +var { synthesizeDrop, synthesizeMouseAtCenter } = EventUtils; + +const kForceOverflowWidthPx = 450; + +function createDummyXULButton(id, label, win = window) { + let btn = win.document.createXULElement("toolbarbutton"); + btn.id = id; + btn.setAttribute("label", label || id); + btn.className = "toolbarbutton-1 chromeclass-toolbar-additional"; + win.gNavToolbox.palette.appendChild(btn); + return btn; +} + +var gAddedToolbars = new Set(); + +function createToolbarWithPlacements(id, placements = [], properties = {}) { + gAddedToolbars.add(id); + let tb = document.createXULElement("toolbar"); + tb.id = id; + tb.setAttribute("customizable", "true"); + + properties.type = CustomizableUI.TYPE_TOOLBAR; + properties.defaultPlacements = placements; + CustomizableUI.registerArea(id, properties); + gNavToolbox.appendChild(tb); + CustomizableUI.registerToolbarNode(tb); + return tb; +} + +function createOverflowableToolbarWithPlacements(id, placements) { + gAddedToolbars.add(id); + + let tb = document.createXULElement("toolbar"); + tb.id = id; + tb.setAttribute("customizationtarget", id + "-target"); + + let customizationtarget = document.createXULElement("hbox"); + customizationtarget.id = id + "-target"; + customizationtarget.setAttribute("flex", "1"); + tb.appendChild(customizationtarget); + + let overflowPanel = document.createXULElement("panel"); + overflowPanel.id = id + "-overflow"; + document.getElementById("mainPopupSet").appendChild(overflowPanel); + + let overflowList = document.createXULElement("vbox"); + overflowList.id = id + "-overflow-list"; + overflowPanel.appendChild(overflowList); + + let chevron = document.createXULElement("toolbarbutton"); + chevron.id = id + "-chevron"; + tb.appendChild(chevron); + + CustomizableUI.registerArea(id, { + type: CustomizableUI.TYPE_TOOLBAR, + defaultPlacements: placements, + overflowable: true, + }); + + tb.setAttribute("customizable", "true"); + tb.setAttribute("overflowable", "true"); + tb.setAttribute("default-overflowpanel", overflowPanel.id); + tb.setAttribute("default-overflowtarget", overflowList.id); + tb.setAttribute("default-overflowbutton", chevron.id); + tb.setAttribute("addon-webext-overflowbutton", "unified-extensions-button"); + tb.setAttribute("addon-webext-overflowtarget", "overflowed-extensions-list"); + + gNavToolbox.appendChild(tb); + CustomizableUI.registerToolbarNode(tb); + return tb; +} + +function removeCustomToolbars() { + CustomizableUI.reset(); + for (let toolbarId of gAddedToolbars) { + CustomizableUI.unregisterArea(toolbarId, true); + let tb = document.getElementById(toolbarId); + if (tb.hasAttribute("overflowpanel")) { + let panel = document.getElementById(tb.getAttribute("overflowpanel")); + if (panel) { + panel.remove(); + } + } + tb.remove(); + } + gAddedToolbars.clear(); +} + +function resetCustomization() { + return CustomizableUI.reset(); +} + +function isInDevEdition() { + return AppConstants.MOZ_DEV_EDITION; +} + +function removeNonReleaseButtons(areaPanelPlacements) { + if (isInDevEdition() && areaPanelPlacements.includes("developer-button")) { + areaPanelPlacements.splice( + areaPanelPlacements.indexOf("developer-button"), + 1 + ); + } +} + +function removeNonOriginalButtons() { + CustomizableUI.removeWidgetFromArea("sync-button"); +} + +function assertAreaPlacements(areaId, expectedPlacements) { + let actualPlacements = getAreaWidgetIds(areaId); + placementArraysEqual(areaId, actualPlacements, expectedPlacements); +} + +function placementArraysEqual(areaId, actualPlacements, expectedPlacements) { + info("Actual placements: " + actualPlacements.join(", ")); + info("Expected placements: " + expectedPlacements.join(", ")); + is( + actualPlacements.length, + expectedPlacements.length, + "Area " + areaId + " should have " + expectedPlacements.length + " items." + ); + let minItems = Math.min(expectedPlacements.length, actualPlacements.length); + for (let i = 0; i < minItems; i++) { + if (typeof expectedPlacements[i] == "string") { + is( + actualPlacements[i], + expectedPlacements[i], + "Item " + i + " in " + areaId + " should match expectations." + ); + } else if (expectedPlacements[i] instanceof RegExp) { + ok( + expectedPlacements[i].test(actualPlacements[i]), + "Item " + + i + + " (" + + actualPlacements[i] + + ") in " + + areaId + + " should match " + + expectedPlacements[i] + ); + } else { + ok( + false, + "Unknown type of expected placement passed to " + + " assertAreaPlacements. Is your test broken?" + ); + } + } +} + +function todoAssertAreaPlacements(areaId, expectedPlacements) { + let actualPlacements = getAreaWidgetIds(areaId); + let isPassing = actualPlacements.length == expectedPlacements.length; + let minItems = Math.min(expectedPlacements.length, actualPlacements.length); + for (let i = 0; i < minItems; i++) { + if (typeof expectedPlacements[i] == "string") { + isPassing = isPassing && actualPlacements[i] == expectedPlacements[i]; + } else if (expectedPlacements[i] instanceof RegExp) { + isPassing = isPassing && expectedPlacements[i].test(actualPlacements[i]); + } else { + ok( + false, + "Unknown type of expected placement passed to " + + " assertAreaPlacements. Is your test broken?" + ); + } + } + todo( + isPassing, + "The area placements for " + + areaId + + " should equal the expected placements." + ); +} + +function getAreaWidgetIds(areaId) { + return CustomizableUI.getWidgetIdsInArea(areaId); +} + +function simulateItemDrag(aToDrag, aTarget, aEvent = {}, aOffset = 2) { + let ev = aEvent; + if (ev == "end" || ev == "start") { + let win = aTarget.ownerGlobal; + const dwu = win.windowUtils; + let bounds = dwu.getBoundsWithoutFlushing(aTarget); + if (ev == "end") { + ev = { + clientX: bounds.right - aOffset, + clientY: bounds.bottom - aOffset, + }; + } else { + ev = { clientX: bounds.left + aOffset, clientY: bounds.top + aOffset }; + } + } + ev._domDispatchOnly = true; + synthesizeDrop( + aToDrag.parentNode, + aTarget, + null, + null, + aToDrag.ownerGlobal, + aTarget.ownerGlobal, + ev + ); + // Ensure dnd suppression is cleared. + synthesizeMouseAtCenter(aTarget, { type: "mouseup" }, aTarget.ownerGlobal); +} + +function endCustomizing(aWindow = window) { + if (aWindow.document.documentElement.getAttribute("customizing") != "true") { + return true; + } + let afterCustomizationPromise = BrowserTestUtils.waitForEvent( + aWindow.gNavToolbox, + "aftercustomization" + ); + aWindow.gCustomizeMode.exit(); + return afterCustomizationPromise; +} + +function startCustomizing(aWindow = window) { + if (aWindow.document.documentElement.getAttribute("customizing") == "true") { + return null; + } + let customizationReadyPromise = BrowserTestUtils.waitForEvent( + aWindow.gNavToolbox, + "customizationready" + ); + aWindow.gCustomizeMode.enter(); + return customizationReadyPromise; +} + +function promiseObserverNotified(aTopic) { + return new Promise(resolve => { + Services.obs.addObserver(function onNotification(subject, topic, data) { + Services.obs.removeObserver(onNotification, topic); + resolve({ subject, data }); + }, aTopic); + }); +} + +function openAndLoadWindow(aOptions, aWaitForDelayedStartup = false) { + return new Promise(resolve => { + let win = OpenBrowserWindow(aOptions); + if (aWaitForDelayedStartup) { + Services.obs.addObserver(function onDS(aSubject, aTopic, aData) { + if (aSubject != win) { + return; + } + Services.obs.removeObserver(onDS, "browser-delayed-startup-finished"); + resolve(win); + }, "browser-delayed-startup-finished"); + } else { + win.addEventListener( + "load", + function () { + resolve(win); + }, + { once: true } + ); + } + }); +} + +function promiseWindowClosed(win) { + return new Promise(resolve => { + win.addEventListener( + "unload", + function () { + resolve(); + }, + { once: true } + ); + win.close(); + }); +} + +function promiseOverflowShown(win) { + let panelEl = win.document.getElementById("widget-overflow"); + return promisePanelElementShown(win, panelEl); +} + +function promisePanelElementShown(win, aPanel) { + return new Promise((resolve, reject) => { + let timeoutId = win.setTimeout(() => { + reject("Panel did not show within 20 seconds."); + }, 20000); + function onPanelOpen(e) { + aPanel.removeEventListener("popupshown", onPanelOpen); + win.clearTimeout(timeoutId); + resolve(); + } + aPanel.addEventListener("popupshown", onPanelOpen); + }); +} + +function promiseOverflowHidden(win) { + let panelEl = win.PanelUI.overflowPanel; + return promisePanelElementHidden(win, panelEl); +} + +function promisePanelElementHidden(win, aPanel) { + return new Promise((resolve, reject) => { + let timeoutId = win.setTimeout(() => { + reject("Panel did not hide within 20 seconds."); + }, 20000); + function onPanelClose(e) { + aPanel.removeEventListener("popuphidden", onPanelClose); + win.clearTimeout(timeoutId); + executeSoon(resolve); + } + aPanel.addEventListener("popuphidden", onPanelClose); + }); +} + +function isPanelUIOpen() { + return PanelUI.panel.state == "open" || PanelUI.panel.state == "showing"; +} + +function isOverflowOpen() { + let panel = document.getElementById("widget-overflow"); + return panel.state == "open" || panel.state == "showing"; +} + +function subviewShown(aSubview) { + return new Promise((resolve, reject) => { + let win = aSubview.ownerGlobal; + let timeoutId = win.setTimeout(() => { + reject("Subview (" + aSubview.id + ") did not show within 20 seconds."); + }, 20000); + function onViewShown(e) { + aSubview.removeEventListener("ViewShown", onViewShown); + win.clearTimeout(timeoutId); + resolve(); + } + aSubview.addEventListener("ViewShown", onViewShown); + }); +} + +function subviewHidden(aSubview) { + return new Promise((resolve, reject) => { + let win = aSubview.ownerGlobal; + let timeoutId = win.setTimeout(() => { + reject("Subview (" + aSubview.id + ") did not hide within 20 seconds."); + }, 20000); + function onViewHiding(e) { + aSubview.removeEventListener("ViewHiding", onViewHiding); + win.clearTimeout(timeoutId); + resolve(); + } + aSubview.addEventListener("ViewHiding", onViewHiding); + }); +} + +function waitFor(aTimeout = 100) { + return new Promise(resolve => { + setTimeout(() => resolve(), aTimeout); + }); +} + +/** + * Starts a load in an existing tab and waits for it to finish (via some event). + * + * @param aTab The tab to load into. + * @param aUrl The url to load. + * @param aEventType The load event type to wait for. Defaults to "load". + * @return {Promise} resolved when the event is handled. + */ +function promiseTabLoadEvent(aTab, aURL) { + let browser = aTab.linkedBrowser; + + BrowserTestUtils.loadURIString(browser, aURL); + return BrowserTestUtils.browserLoaded(browser); +} + +/** + * Wait for an attribute on a node to change + * + * @param aNode Node on which the mutation is expected + * @param aAttribute The attribute we're interested in + * @param aFilterFn A function to check if the new value is what we want. + * @return {Promise} resolved when the requisite mutation shows up. + */ +function promiseAttributeMutation(aNode, aAttribute, aFilterFn) { + return new Promise((resolve, reject) => { + info("waiting for mutation of attribute '" + aAttribute + "'."); + let obs = new MutationObserver(mutations => { + for (let mut of mutations) { + let attr = mut.attributeName; + let newValue = mut.target.getAttribute(attr); + if (aFilterFn(newValue)) { + ok( + true, + "mutation occurred: attribute '" + + attr + + "' changed to '" + + newValue + + "' from '" + + mut.oldValue + + "'." + ); + obs.disconnect(); + resolve(); + } else { + info( + "Ignoring mutation that produced value " + + newValue + + " because of filter." + ); + } + } + }); + obs.observe(aNode, { attributeFilter: [aAttribute] }); + }); +} + +function popupShown(aPopup) { + return BrowserTestUtils.waitForPopupEvent(aPopup, "shown"); +} + +function popupHidden(aPopup) { + return BrowserTestUtils.waitForPopupEvent(aPopup, "hidden"); +} + +// This is a simpler version of the context menu check that +// exists in contextmenu_common.js. +function checkContextMenu(aContextMenu, aExpectedEntries, aWindow = window) { + let children = [...aContextMenu.children]; + // Ignore hidden nodes: + children = children.filter(n => !n.hidden); + + for (let i = 0; i < children.length; i++) { + let menuitem = children[i]; + try { + if (aExpectedEntries[i][0] == "---") { + is(menuitem.localName, "menuseparator", "menuseparator expected"); + continue; + } + + let selector = aExpectedEntries[i][0]; + ok( + menuitem.matches(selector), + "menuitem should match " + selector + " selector" + ); + let commandValue = menuitem.getAttribute("command"); + let relatedCommand = commandValue + ? aWindow.document.getElementById(commandValue) + : null; + let menuItemDisabled = relatedCommand + ? relatedCommand.getAttribute("disabled") == "true" + : menuitem.getAttribute("disabled") == "true"; + is( + menuItemDisabled, + !aExpectedEntries[i][1], + "disabled state for " + selector + ); + } catch (e) { + ok(false, "Exception when checking context menu: " + e); + } + } +} + +function waitForOverflowButtonShown(win = window) { + info("Waiting for overflow button to show"); + let ov = win.document.getElementById("nav-bar-overflow-button"); + return waitForElementShown(ov.icon); +} +function waitForElementShown(element) { + return BrowserTestUtils.waitForCondition(() => { + info("Checking if element has non-0 size"); + // We intentionally flush layout to ensure the element is actually shown. + let rect = element.getBoundingClientRect(); + return rect.width > 0 && rect.height > 0; + }); +} + +/** + * Opens the history panel through the history toolbarbutton in the + * navbar and returns a promise that resolves as soon as the panel is open + * is showing. + */ +async function openHistoryPanel(doc = document) { + await waitForOverflowButtonShown(); + await doc.getElementById("nav-bar").overflowable.show(); + info("Menu panel was opened"); + + let historyButton = doc.getElementById("history-panelmenu"); + Assert.ok(historyButton, "History button appears in Panel Menu"); + + historyButton.click(); + + let historyPanel = doc.getElementById("PanelUI-history"); + return BrowserTestUtils.waitForEvent(historyPanel, "ViewShown"); +} + +/** + * Closes the history panel and returns a promise that resolves as sooon + * as the panel is closed. + */ +async function hideHistoryPanel(doc = document) { + let historyView = doc.getElementById("PanelUI-history"); + let historyPanel = historyView.closest("panel"); + let promise = BrowserTestUtils.waitForEvent(historyPanel, "popuphidden"); + historyPanel.hidePopup(); + return promise; +} -- cgit v1.2.3