diff --git a/browser/base/content/browser-pageActions.js b/browser/base/content/browser-pageActions.js index 073404a8db2b..00ebd78ec4d9 100644 --- a/browser/base/content/browser-pageActions.js +++ b/browser/base/content/browser-pageActions.js @@ -96,6 +96,7 @@ var BrowserPageActions = { for (let action of urlbarActions) { this.placeActionInUrlbar(action); } + this._updateMainButtonAttributes(); }, /** @@ -128,6 +129,7 @@ var BrowserPageActions = { placeAction(action) { this.placeActionInPanel(action); this.placeActionInUrlbar(action); + this._updateMainButtonAttributes(); }, /** @@ -213,6 +215,13 @@ var BrowserPageActions = { } }, + _updateMainButtonAttributes() { + this.mainButtonNode.toggleAttribute( + "multiple-children", + PageActions.actions.length > 1 + ); + }, + /** * Returns the node before which an action's node should be inserted. * @@ -545,6 +554,7 @@ var BrowserPageActions = { this._removeActionFromPanel(action); this._removeActionFromUrlbar(action); action.onRemovedFromWindow(window); + this._updateMainButtonAttributes(); }, _removeActionFromUrlbar(action) { diff --git a/browser/base/content/browser.css b/browser/base/content/browser.css index 7164f9c575e0..3c0269001237 100644 --- a/browser/base/content/browser.css +++ b/browser/base/content/browser.css @@ -762,6 +762,15 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks { display: none; } } + +@supports -moz-bool-pref("browser.proton.urlbar.enabled") { + /* The page actions menu is hidden by default, it is only shown in small + windows as the overflow target of multiple page action buttons */ + #pageActionButton { + visibility: collapse; + } +} + /* 680px is just below half of popular 1366px wide screens, so when putting two browser windows next to each other on such a screen, they'll be above this threshold. */ @@ -769,9 +778,15 @@ toolbar:not(#TabsToolbar) > #personal-bookmarks { /* Page action buttons are duplicated in the page action menu so we can safely hide them in small windows. */ #pageActionSeparator, - #pageActionButton ~ .urlbar-page-action { + #pageActionButton[multiple-children] ~ .urlbar-page-action { display: none; } + + @supports -moz-bool-pref("browser.proton.urlbar.enabled") { + #pageActionButton[multiple-children] { + visibility: visible; + } + } } @media (max-width: 550px) { #urlbar-container { diff --git a/browser/base/content/moz.build b/browser/base/content/moz.build index fd21ea5dd94b..f495d12e5181 100644 --- a/browser/base/content/moz.build +++ b/browser/base/content/moz.build @@ -43,7 +43,7 @@ with Files("test/keyboard/**"): with Files("test/outOfProcess/**"): BUG_COMPONENT = ("Firefox", "General") -with Files("test/pageActions/**"): +with Files("test/pageActions*/**"): BUG_COMPONENT = ("Firefox", "Toolbars and Customization") with Files("test/pageinfo/**"): diff --git a/browser/base/content/test/pageActions-proton/.eslintrc.js b/browser/base/content/test/pageActions-proton/.eslintrc.js new file mode 100644 index 000000000000..1779fd7f1cf8 --- /dev/null +++ b/browser/base/content/test/pageActions-proton/.eslintrc.js @@ -0,0 +1,5 @@ +"use strict"; + +module.exports = { + extends: ["plugin:mozilla/browser-test"], +}; diff --git a/browser/base/content/test/pageActions-proton/browser.ini b/browser/base/content/test/pageActions-proton/browser.ini new file mode 100644 index 000000000000..30dc7d771b71 --- /dev/null +++ b/browser/base/content/test/pageActions-proton/browser.ini @@ -0,0 +1,7 @@ +[DEFAULT] +prefs = + browser.proton.urlbar.enabled=true +support-files = + head.js + +[browser_PageActions_overflow.js] diff --git a/browser/base/content/test/pageActions-proton/browser_PageActions_overflow.js b/browser/base/content/test/pageActions-proton/browser_PageActions_overflow.js new file mode 100644 index 000000000000..02976e4c7b54 --- /dev/null +++ b/browser/base/content/test/pageActions-proton/browser_PageActions_overflow.js @@ -0,0 +1,90 @@ +/* Any copyright is dedicated to the Public Domain. + * https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function init() { + // We use an extension that shows a page action so that we can test the + // "remove extension" item in the context menu. + let extension = ExtensionTestUtils.loadExtension({ + manifest: { + name: "Test contextMenu", + page_action: { show_matches: [""] }, + }, + + useAddonManager: "temporary", + }); + + await extension.startup(); + let actionId = ExtensionCommon.makeWidgetId(extension.id); + + let win = await BrowserTestUtils.openNewBrowserWindow(); + await SimpleTest.promiseFocus(win); + let originalWindowWidth = win.outerWidth; + Assert.greater(originalWindowWidth, 700, "window is bigger than 700px"); + BrowserTestUtils.loadURI(win.gBrowser, "data:text/html,

A Page

"); + await BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser); + + // The pageAction implementation enables the button at the next animation + // frame, so before we look for the button we should wait one animation frame + // as well. + await promiseAnimationFrame(win); + + registerCleanupFunction(async () => { + win.resizeTo(originalWindowWidth, win.outerHeight); + await BrowserTestUtils.closeWindow(win); + }); + + info("Check page action buttons are visible, the meatball button is not"); + let addonButton = win.BrowserPageActions.urlbarButtonNodeForActionID( + actionId + ); + Assert.ok(BrowserTestUtils.is_visible(addonButton)); + let starButton = win.BrowserPageActions.urlbarButtonNodeForActionID( + "bookmark" + ); + Assert.ok(BrowserTestUtils.is_visible(starButton)); + let meatballButton = win.document.getElementById("pageActionButton"); + Assert.ok(!BrowserTestUtils.is_visible(meatballButton)); + + info( + "Shrink the window, check page action buttons are not visible, the meatball menu is visible" + ); + await promiseStableResize(500, win); + Assert.ok(!BrowserTestUtils.is_visible(addonButton)); + Assert.ok(!BrowserTestUtils.is_visible(starButton)); + Assert.ok(BrowserTestUtils.is_visible(meatballButton)); + + info( + "Remove the extension, check the only page action button is visible, the meatball menu is not visible" + ); + let promiseUninstalled = promiseAddonUninstalled(extension.id); + await extension.unload(); + await promiseUninstalled; + Assert.ok(BrowserTestUtils.is_visible(starButton)); + Assert.ok(!BrowserTestUtils.is_visible(meatballButton)); + Assert.deepEqual( + win.BrowserPageActions.urlbarButtonNodeForActionID(actionId), + null + ); +}); + +// TODO (Bug 1700780): Why is this necessary? Without this trick the test +// fails intermittently on Ubuntu. +function promiseStableResize(expectedWidth, win = window) { + let deferred = PromiseUtils.defer(); + let id; + function listener() { + win.clearTimeout(id); + info(`Got resize event: ${win.innerWidth} x ${win.innerHeight}`); + if (win.innerWidth <= expectedWidth) { + id = win.setTimeout(() => { + win.removeEventListener("resize", listener); + deferred.resolve(); + }, 100); + } + } + win.addEventListener("resize", listener); + win.resizeTo(expectedWidth, win.outerHeight); + return deferred.promise; +} diff --git a/browser/base/content/test/pageActions-proton/head.js b/browser/base/content/test/pageActions-proton/head.js new file mode 100644 index 000000000000..a2c012e88bad --- /dev/null +++ b/browser/base/content/test/pageActions-proton/head.js @@ -0,0 +1,166 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +XPCOMUtils.defineLazyModuleGetters(this, { + ExtensionCommon: "resource://gre/modules/ExtensionCommon.jsm", + PlacesTestUtils: "resource://testing-common/PlacesTestUtils.jsm", +}); + +function promisePageActionPanelOpen(eventDict = {}) { + let dwu = window.windowUtils; + return BrowserTestUtils.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, + eventDict + ); + return shownPromise; + }) + .then(() => { + // Wait for items in the panel to become visible. + return promisePageActionViewChildrenVisible( + BrowserPageActions.mainViewNode + ); + }); +} + +async function waitForActivatedActionPanel() { + if (!BrowserPageActions.activatedActionPanelNode) { + info("Waiting for activated-action panel to be added to mainPopupSet"); + await new Promise(resolve => { + let observer = new MutationObserver(mutations => { + if (BrowserPageActions.activatedActionPanelNode) { + observer.disconnect(); + resolve(); + } + }); + let popupSet = document.getElementById("mainPopupSet"); + observer.observe(popupSet, { childList: true }); + }); + info("Activated-action panel added to mainPopupSet"); + } + if (!BrowserPageActions.activatedActionPanelNode.state == "open") { + info("Waiting for activated-action panel popupshown"); + await promisePanelShown(BrowserPageActions.activatedActionPanelNode); + info("Got activated-action panel popupshown"); + } + let panelView = BrowserPageActions.activatedActionPanelNode.querySelector( + "panelview" + ); + if (panelView) { + await BrowserTestUtils.waitForEvent( + BrowserPageActions.activatedActionPanelNode, + "ViewShown" + ); + await promisePageActionViewChildrenVisible(panelView); + } + return panelView; +} + +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; + }); +} + +function promisePageActionViewChildrenVisible(panelViewNode) { + return promiseNodeVisible(panelViewNode.firstElementChild.firstElementChild); +} + +function promiseNodeVisible(node) { + info( + `promiseNodeVisible waiting, node.id=${node.id} node.localeName=${node.localName}\n` + ); + let dwu = window.windowUtils; + return BrowserTestUtils.waitForCondition(() => { + let bounds = dwu.getBoundsWithoutFlushing(node); + if (bounds.width > 0 && bounds.height > 0) { + info( + `promiseNodeVisible OK, node.id=${node.id} node.localeName=${node.localName}\n` + ); + return true; + } + return false; + }); +} + +function promiseAddonUninstalled(addonId) { + return new Promise(resolve => { + let listener = {}; + listener.onUninstalled = addon => { + if (addon.id == addonId) { + AddonManager.removeAddonListener(listener); + resolve(); + } + }; + AddonManager.addAddonListener(listener); + }); +} + +async function promiseAnimationFrame(win = window) { + await new Promise(resolve => win.requestAnimationFrame(resolve)); + await win.promiseDocumentFlushed(() => {}); +} diff --git a/browser/base/moz.build b/browser/base/moz.build index c97c964da0e6..2df182d913c6 100644 --- a/browser/base/moz.build +++ b/browser/base/moz.build @@ -33,6 +33,7 @@ BROWSER_CHROME_MANIFESTS += [ "content/test/menubar/browser.ini", "content/test/metaTags/browser.ini", "content/test/outOfProcess/browser.ini", + "content/test/pageActions-proton/browser.ini", "content/test/pageActions/browser.ini", "content/test/pageinfo/browser.ini", "content/test/pageStyle/browser.ini",