Bug 1691454 - Hide the Page actions overflow (meatball) menu from the address bar. r=adw

The page actions menu is normally hidden.
When the window is smaller than a threshold, and there's more than one action,
the single actions are hidden while the menu is shown. This allows for a nicer
overflow experience.

This patch introduces a pageActions-proton test folder where we'll move proton
tests temporarily. The head.js file is just a copy of the original one, we'll
clean it up in bug 1700582 after porting the other tests.

Differential Revision: https://phabricator.services.mozilla.com/D109606
This commit is contained in:
Marco Bonardo 2021-03-24 23:19:14 +00:00
Родитель 0326b528a5
Коммит c304d1cd46
8 изменённых файлов: 296 добавлений и 2 удалений

Просмотреть файл

@ -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) {

Просмотреть файл

@ -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 {

Просмотреть файл

@ -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/**"):

Просмотреть файл

@ -0,0 +1,5 @@
"use strict";
module.exports = {
extends: ["plugin:mozilla/browser-test"],
};

Просмотреть файл

@ -0,0 +1,7 @@
[DEFAULT]
prefs =
browser.proton.urlbar.enabled=true
support-files =
head.js
[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: ["<all_urls>"] },
},
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,<h1>A Page</h1>");
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;
}

Просмотреть файл

@ -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(() => {});
}

Просмотреть файл

@ -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",