Bug 2064747 - Combine a urlbar result's context menu and three-dot menu. r=daisuke,fluent-reviewers,urlbar-reviewers,bolsson
With contextMenu.featureGate off, the three-dot menu is unchanged. Differential Revision: https://phabricator.services.mozilla.com/D324402
This commit is contained in:
committed by
dgottwald@mozilla.com
parent
7386a62c9e
commit
bd204a6887
@@ -161,22 +161,6 @@
|
||||
class="urlbar-container"
|
||||
removable="false"
|
||||
overflows="false">
|
||||
<html:panel-list id="urlbarView-context-menu" suppress-contextmenu="">
|
||||
<html:panel-item id="urlbar-view-context-menu-open-in-tab"
|
||||
data-l10n-id="urlbar-view-context-menu-open-in-tab2"
|
||||
data-l10n-attrs="accesskey"></html:panel-item>
|
||||
<html:panel-item submenu="urlbarView-context-menu-open-in-container-tab-submenu"
|
||||
id="urlbarView-context-menu-open-in-container-tab-menu">
|
||||
<html:span data-l10n-id="urlbar-view-context-menu-open-in-container-tab2"></html:span>
|
||||
<html:panel-list slot="submenu" id="urlbarView-context-menu-open-in-container-tab-submenu"></html:panel-list>
|
||||
</html:panel-item>
|
||||
<html:panel-item id="urlbarView-context-menu-open-in-window"
|
||||
data-l10n-id="urlbar-view-context-menu-open-in-window2"
|
||||
data-l10n-attrs="accesskey"></html:panel-item>
|
||||
<html:panel-item id="urlbarView-context-menu-open-in-private-window"
|
||||
data-l10n-id="urlbar-view-context-menu-open-in-private-window2"
|
||||
data-l10n-attrs="accesskey"></html:panel-item>
|
||||
</html:panel-list>
|
||||
<toolbartabstop/>
|
||||
<html:moz-urlbar id="urlbar"
|
||||
class="urlbar"
|
||||
|
||||
@@ -1714,6 +1714,17 @@ ${
|
||||
this.pickResult({ result, event, element });
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether pickResult() implements the result menu's commands for opening a
|
||||
* result in a new tab or window. The container-tab submenu is built by a
|
||||
* chrome window helper, so a bar hosted in a content page can't offer them.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get handlesOpenInCommands() {
|
||||
return typeof this.window.createUserContextMenu == "function";
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a result is picked.
|
||||
*
|
||||
@@ -1820,34 +1831,31 @@ ${
|
||||
private: this.isPrivate,
|
||||
};
|
||||
|
||||
let isContextMenu = event
|
||||
?.composedPath()
|
||||
.some(el => el.id == "urlbarView-context-menu");
|
||||
let userContextId = element?.dataset.usercontextid;
|
||||
let openIn = userContextId ? "container-tab" : element?.dataset.openIn;
|
||||
|
||||
if (isContextMenu) {
|
||||
switch (element.id) {
|
||||
case "urlbar-view-context-menu-open-in-tab": {
|
||||
if (openIn) {
|
||||
switch (openIn) {
|
||||
case "tab": {
|
||||
where = "tab";
|
||||
break;
|
||||
}
|
||||
case "urlbarView-context-menu-open-in-window": {
|
||||
where = "window";
|
||||
break;
|
||||
}
|
||||
case "urlbarView-context-menu-open-in-private-window": {
|
||||
where = "window";
|
||||
openParams.private = true;
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// Open in a container tab.
|
||||
case "container-tab": {
|
||||
where = "tab";
|
||||
openParams.userContextId = parseInt(
|
||||
element.getAttribute("data-usercontextid")
|
||||
);
|
||||
openParams.userContextId = parseInt(userContextId);
|
||||
openParams.eventDetail = {
|
||||
containerSource: "urlbar_result_context_menu",
|
||||
};
|
||||
break;
|
||||
}
|
||||
case "window": {
|
||||
where = "window";
|
||||
break;
|
||||
}
|
||||
case "private-window": {
|
||||
where = "window";
|
||||
openParams.private = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5429,9 +5437,7 @@ ${
|
||||
}
|
||||
// Don't close the view when clicking on a tab; we may want to keep the
|
||||
// view open on tab switch, and the TabSelect event arrived earlier.
|
||||
// Also ignore mousedown on the urlbarView context menu: opening/closing the
|
||||
// view is already handled by the result opening flow.
|
||||
if (event.target.closest?.("tab, #urlbarView-context-menu")) {
|
||||
if (event.target.closest?.("tab")) {
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,13 +28,6 @@ const RESULT_MENU_COMMANDS = {
|
||||
MANAGE: "manage",
|
||||
};
|
||||
|
||||
// The context menu items that we handle with pickResult.
|
||||
const CONTEXT_MENU_OPEN_IDS = new Set([
|
||||
"urlbar-view-context-menu-open-in-tab",
|
||||
"urlbarView-context-menu-open-in-window",
|
||||
"urlbarView-context-menu-open-in-private-window",
|
||||
]);
|
||||
|
||||
const getBoundsWithoutFlushing = UrlbarShared.getBoundsWithoutFlushing;
|
||||
|
||||
// Used to get a unique id to use for row elements, it wraps at 9999, that
|
||||
@@ -1208,18 +1201,12 @@ export class UrlbarView {
|
||||
}
|
||||
|
||||
isResultMenuOpen() {
|
||||
return (
|
||||
this.resultMenu.hasAttribute("open") ||
|
||||
// Also checks the contextmenu but will be addressed by:
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=2064747
|
||||
!!this.#contextMenu?.hasAttribute("open")
|
||||
);
|
||||
return this.resultMenu.hasAttribute("open");
|
||||
}
|
||||
|
||||
// Private properties and methods below.
|
||||
#announceTabToSearchOnSelection;
|
||||
#blobUrlsByResultUrl = null;
|
||||
#contextMenu;
|
||||
#containerWidthOnLastClose = 0;
|
||||
#l10nCache;
|
||||
#mousedownSelectedElement;
|
||||
@@ -4150,11 +4137,73 @@ export class UrlbarView {
|
||||
return idArgs;
|
||||
}
|
||||
|
||||
/**
|
||||
* The commands that open a result in a new tab or window. They're offered
|
||||
* alongside a result's own menu commands, gated on `contextMenu.featureGate`.
|
||||
*
|
||||
* @returns {UrlbarResultCommand[]}
|
||||
*/
|
||||
get #openInCommands() {
|
||||
/** @type {UrlbarResultCommand[]} */
|
||||
let commands = [
|
||||
{
|
||||
openIn: "tab",
|
||||
l10n: { id: "urlbar-view-context-menu-open-in-tab2" },
|
||||
},
|
||||
];
|
||||
if (
|
||||
!this.input.isPrivate &&
|
||||
UrlbarPrefs.get("privacy.userContext.enabled")
|
||||
) {
|
||||
commands.push({
|
||||
openIn: "container-tab",
|
||||
submenu: true,
|
||||
l10n: { id: "urlbar-view-context-menu-open-in-container-tab2" },
|
||||
});
|
||||
}
|
||||
commands.push(
|
||||
{
|
||||
openIn: "window",
|
||||
l10n: { id: "urlbar-view-context-menu-open-in-window2" },
|
||||
},
|
||||
{
|
||||
openIn: "private-window",
|
||||
l10n: { id: "urlbar-view-context-menu-open-in-private-window2" },
|
||||
}
|
||||
);
|
||||
return commands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UrlbarResult} result
|
||||
* The result to get menu commands for.
|
||||
* @returns {?UrlbarResultCommand[]}
|
||||
* Everything the result's menu shows, null if it has nothing to show. The
|
||||
* three-dot button and a right-click both open this menu, so it combines
|
||||
* the result's own commands with the ones that open it in a new tab or
|
||||
* window.
|
||||
*/
|
||||
#getMenuCommands(result) {
|
||||
let commands = this.#getResultMenuCommands(result);
|
||||
if (
|
||||
!UrlbarPrefs.get("contextMenu.featureGate") ||
|
||||
!this.input.handlesOpenInCommands ||
|
||||
!UrlbarShared.getLoadRequestFromResult(result)
|
||||
) {
|
||||
return commands;
|
||||
}
|
||||
let openInCommands = this.#openInCommands;
|
||||
return commands
|
||||
? [...openInCommands, { name: "separator" }, ...commands]
|
||||
: openInCommands;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {UrlbarResult} result
|
||||
* The result to get menu commands for.
|
||||
* @returns {Array}
|
||||
* Array of menu commands available for the result, null if there are none.
|
||||
* Array of the result's own menu commands, null if there are none. This
|
||||
* also decides whether the result's row gets a three-dot button.
|
||||
*/
|
||||
#getResultMenuCommands(result) {
|
||||
if (this.#resultMenuCommands.has(result)) {
|
||||
@@ -4217,9 +4266,31 @@ export class UrlbarView {
|
||||
continue;
|
||||
}
|
||||
let menuitem = this.document.createElement("panel-item");
|
||||
menuitem.dataset.command = data.name;
|
||||
menuitem.classList.add("urlbarView-result-menuitem");
|
||||
this.#l10nCache.setElementL10n(menuitem, data.l10n);
|
||||
if (data.openIn) {
|
||||
menuitem.dataset.openIn = data.openIn;
|
||||
} else {
|
||||
menuitem.dataset.command = data.name;
|
||||
}
|
||||
if (data.submenu) {
|
||||
// The submenu is populated when it's about to be shown, so that it
|
||||
// doesn't go stale. Fluent replaces the contents of the element it
|
||||
// localizes, so the label goes in a child and its accesskey stays on
|
||||
// the item, where pressing it activates the item.
|
||||
menuitem.toggleAttribute("submenu", true);
|
||||
let label = this.document.createElement("span");
|
||||
this.#l10nCache.setElementL10n(label, data.l10n);
|
||||
if (label.hasAttribute("accesskey")) {
|
||||
menuitem.setAttribute("accesskey", label.getAttribute("accesskey"));
|
||||
label.removeAttribute("accesskey");
|
||||
}
|
||||
menuitem.appendChild(label);
|
||||
let submenu = this.document.createElement("panel-list");
|
||||
submenu.slot = "submenu";
|
||||
menuitem.appendChild(submenu);
|
||||
} else {
|
||||
this.#l10nCache.setElementL10n(menuitem, data.l10n);
|
||||
}
|
||||
panel.appendChild(menuitem);
|
||||
}
|
||||
}
|
||||
@@ -4523,81 +4594,47 @@ export class UrlbarView {
|
||||
}
|
||||
|
||||
on_click(event) {
|
||||
if (event.currentTarget == this.resultMenu) {
|
||||
let result = this.#resultMenuResult;
|
||||
this.#resultMenuResult = null;
|
||||
let menuitem = event.target;
|
||||
switch (menuitem.dataset.command) {
|
||||
case RESULT_MENU_COMMANDS.HELP:
|
||||
menuitem.dataset.url =
|
||||
result.payload.helpUrl ||
|
||||
UrlbarContentUtils.getSupportUrl("awesome-bar-result-menu");
|
||||
break;
|
||||
}
|
||||
this.input.pickResult({ result, event, element: menuitem });
|
||||
} else if (event.currentTarget == this.#contextMenu) {
|
||||
let target = event
|
||||
.composedPath()
|
||||
.find(node => node.localName == "panel-item");
|
||||
if (
|
||||
!target ||
|
||||
(!CONTEXT_MENU_OPEN_IDS.has(target.id) && !target.dataset.usercontextid)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let row = this.#contextMenu.lastAnchorNode?.closest(".urlbarView-row");
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
this.input.pickResult({
|
||||
result: row.result,
|
||||
event,
|
||||
element: target,
|
||||
});
|
||||
let menuitem = event
|
||||
.composedPath()
|
||||
.find(node => node.localName == "panel-item");
|
||||
if (
|
||||
!menuitem ||
|
||||
// Clicking a submenu's parent item only opens the submenu.
|
||||
menuitem.hasSubmenu ||
|
||||
// The container submenu also holds items that manage containers, which
|
||||
// bring up their own UI instead of picking the result.
|
||||
!(
|
||||
menuitem.dataset.command ||
|
||||
menuitem.dataset.openIn ||
|
||||
menuitem.dataset.usercontextid
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
let result = this.#resultMenuResult;
|
||||
this.#resultMenuResult = null;
|
||||
if (menuitem.dataset.command == RESULT_MENU_COMMANDS.HELP) {
|
||||
menuitem.dataset.url =
|
||||
result.payload.helpUrl ||
|
||||
UrlbarContentUtils.getSupportUrl("awesome-bar-result-menu");
|
||||
}
|
||||
this.input.pickResult({ result, event, element: menuitem });
|
||||
}
|
||||
|
||||
on_showing(event) {
|
||||
if (event.target.id == "urlbarView-context-menu") {
|
||||
let row = this.#contextMenu.lastAnchorNode.closest(".urlbarView-row");
|
||||
|
||||
// Set the menu-trigger attribute on the row so it can be styled
|
||||
// as if it were hovered while the context menu is open.
|
||||
row.toggleAttribute("menu-trigger", true);
|
||||
|
||||
let loadRequest = UrlbarShared.getLoadRequestFromResult(row.result, {
|
||||
element: row,
|
||||
});
|
||||
for (let item of event.target.querySelectorAll("panel-item")) {
|
||||
item.disabled = !loadRequest;
|
||||
}
|
||||
|
||||
let containerTabItem = event.target.querySelector(
|
||||
"#urlbarView-context-menu-open-in-container-tab-menu"
|
||||
);
|
||||
if (containerTabItem) {
|
||||
containerTabItem.hidden =
|
||||
this.input.isPrivate ||
|
||||
!UrlbarPrefs.get("privacy.userContext.enabled");
|
||||
}
|
||||
} else if (
|
||||
event.target.id == "urlbarView-context-menu-open-in-container-tab-menu"
|
||||
) {
|
||||
event.target.documentGlobal.createUserContextMenu(event, {
|
||||
target: event.target.submenuPanel,
|
||||
isContextMenu: true,
|
||||
isPanelList: true,
|
||||
containerSource: "urlbar_result_context_menu",
|
||||
});
|
||||
} else if (event.currentTarget == this.resultMenu) {
|
||||
let commands;
|
||||
let splitButton = event.target.triggeringEvent.detail.target.closest(
|
||||
".urlbarView-splitbutton"
|
||||
);
|
||||
if (event.target == this.resultMenu) {
|
||||
// Set the menu-trigger attribute on the row so it can be styled as if it
|
||||
// were hovered while the menu is open.
|
||||
this.resultMenu.lastAnchorNode
|
||||
.closest(".urlbarView-row")
|
||||
.toggleAttribute("menu-trigger", true);
|
||||
|
||||
let triggeringEvent = this.resultMenu.triggeringEvent;
|
||||
let splitButton =
|
||||
triggeringEvent.type == "ResultMenuTriggered" &&
|
||||
triggeringEvent.detail.target.closest(".urlbarView-splitbutton");
|
||||
|
||||
let commands;
|
||||
if (splitButton) {
|
||||
// Show the commands the are defined in its Split Button.
|
||||
let mainButton = splitButton.firstElementChild;
|
||||
@@ -4606,10 +4643,17 @@ export class UrlbarView {
|
||||
b => b.name == buttonName
|
||||
).menu;
|
||||
} else {
|
||||
commands = this.#getResultMenuCommands(this.#resultMenuResult);
|
||||
commands = this.#getMenuCommands(this.#resultMenuResult);
|
||||
}
|
||||
|
||||
this.#populateResultMenu({ commands });
|
||||
} else if (event.target.dataset.openIn == "container-tab") {
|
||||
this.chromeWindow.createUserContextMenu(event, {
|
||||
target: event.target.submenuPanel,
|
||||
isContextMenu: true,
|
||||
isPanelList: true,
|
||||
containerSource: "urlbar_result_context_menu",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4626,31 +4670,26 @@ export class UrlbarView {
|
||||
// The context menu associated with this event is either for something above
|
||||
// the urlbar in the DOM, like the toolbar, or for something specific in the
|
||||
// input, like the `<html:input>`. We want to suppress the former, propagate
|
||||
// the latter, and open our own context menu for events on rows.
|
||||
// the latter, and open the result menu for events on rows.
|
||||
if (event.target.closest(".urlbar-input-container")) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (
|
||||
!UrlbarPrefs.get("contextMenu.featureGate") ||
|
||||
!event.target.closest(".urlbarView-row")
|
||||
) {
|
||||
// Don't show the context menu from the background or the group label etc.
|
||||
if (!UrlbarPrefs.get("contextMenu.featureGate")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.#contextMenu) {
|
||||
this.#contextMenu = this.document.querySelector(
|
||||
"#urlbarView-context-menu"
|
||||
);
|
||||
this.#contextMenu.addEventListener("click", this);
|
||||
this.#contextMenu.addEventListener("showing", this);
|
||||
this.#contextMenu.addEventListener("hidden", this);
|
||||
// Don't open the menu from the background or the group label etc., nor for
|
||||
// a result that has nothing to show in it.
|
||||
let row = event.target.closest(".urlbarView-row");
|
||||
if (!row || !this.#getMenuCommands(row.result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#contextMenu.toggle(event);
|
||||
this.#resultMenuResult = row.result;
|
||||
this.resultMenu.toggle(event);
|
||||
}
|
||||
|
||||
clearTopSitesCache() {
|
||||
|
||||
@@ -26,33 +26,33 @@ add_task(async function basic() {
|
||||
const TEST_CASES = [
|
||||
{
|
||||
preferences: [["browser.tabs.loadInBackground", true]],
|
||||
menuItemId: "urlbar-view-context-menu-open-in-tab",
|
||||
openIn: "tab",
|
||||
expectedTarget: "tab",
|
||||
expectedOption: { background: true },
|
||||
},
|
||||
{
|
||||
preferences: [["browser.tabs.loadInBackground", false]],
|
||||
menuItemId: "urlbar-view-context-menu-open-in-tab",
|
||||
openIn: "tab",
|
||||
expectedTarget: "tab",
|
||||
},
|
||||
{
|
||||
preferences: [["browser.tabs.loadInBackground", true]],
|
||||
menuItemId: "urlbarView-context-menu-open-in-container-tab-menu",
|
||||
openIn: "container-tab",
|
||||
expectedTarget: "tab",
|
||||
expectedOption: { background: true, userContextId: 1 },
|
||||
},
|
||||
{
|
||||
preferences: [["browser.tabs.loadInBackground", false]],
|
||||
menuItemId: "urlbarView-context-menu-open-in-container-tab-menu",
|
||||
openIn: "container-tab",
|
||||
expectedTarget: "tab",
|
||||
expectedOption: { userContextId: 3 },
|
||||
},
|
||||
{
|
||||
menuItemId: "urlbarView-context-menu-open-in-window",
|
||||
openIn: "window",
|
||||
expectedTarget: "window",
|
||||
},
|
||||
{
|
||||
menuItemId: "urlbarView-context-menu-open-in-private-window",
|
||||
openIn: "private-window",
|
||||
expectedTarget: "window",
|
||||
expectedOption: { private: true },
|
||||
},
|
||||
@@ -60,13 +60,11 @@ add_task(async function basic() {
|
||||
|
||||
for (let {
|
||||
preferences = [],
|
||||
menuItemId,
|
||||
openIn,
|
||||
expectedTarget,
|
||||
expectedOption = {},
|
||||
} of TEST_CASES) {
|
||||
info(
|
||||
`Test for ${JSON.stringify({ preferences, menuItemId, expectedOption })}`
|
||||
);
|
||||
info(`Test for ${JSON.stringify({ preferences, openIn, expectedOption })}`);
|
||||
|
||||
info("Set preferences");
|
||||
await SpecialPowers.pushPrefEnv({ set: preferences });
|
||||
@@ -76,9 +74,9 @@ add_task(async function basic() {
|
||||
? BrowserTestUtils.waitForNewTab(gBrowser, "https://example.com/")
|
||||
: BrowserTestUtils.waitForNewWindow({ url: "https://example.com/" });
|
||||
|
||||
let contextMenu = await openContextMenuOnFirstResult();
|
||||
let menuItem = contextMenu.querySelector(`#${menuItemId}`);
|
||||
Assert.ok(menuItem, `Found the menu item ${menuItemId}`);
|
||||
let menu = await openContextMenuOnFirstResult();
|
||||
let menuItem = menu.querySelector(`[data-open-in="${openIn}"]`);
|
||||
Assert.ok(menuItem, `Found the menu item for ${openIn}`);
|
||||
|
||||
if (expectedOption.userContextId) {
|
||||
let subMenuItem = await openContainerSubMenuItem(
|
||||
@@ -138,6 +136,72 @@ add_task(async function basic() {
|
||||
await PlacesUtils.history.clear();
|
||||
});
|
||||
|
||||
// The three-dot button and a right-click open the same menu, and it holds the
|
||||
// result's own commands as well as the ones that open it in a new target.
|
||||
add_task(async function same_menu_from_both_triggers() {
|
||||
await PlacesTestUtils.addVisits(["https://example.com/"]);
|
||||
|
||||
let resultIndex = await promiseResultWithMenuButton();
|
||||
let { element } = await UrlbarTestUtils.getDetailsOfResultAt(
|
||||
window,
|
||||
resultIndex
|
||||
);
|
||||
|
||||
await UrlbarTestUtils.openResultMenu(window, { resultIndex, byMouse: true });
|
||||
let fromMenuButton = await promiseMenuDescription();
|
||||
gURLBar.view.resultMenu.hide(undefined, { force: true });
|
||||
|
||||
let menu = await openContextMenu(element.row);
|
||||
let fromContextMenu = await promiseMenuDescription();
|
||||
menu.hide(undefined, { force: true });
|
||||
|
||||
Assert.deepEqual(
|
||||
fromContextMenu,
|
||||
fromMenuButton,
|
||||
"Both triggers open the same menu"
|
||||
);
|
||||
Assert.deepEqual(
|
||||
fromMenuButton.filter(item => item.openIn),
|
||||
["tab", "container-tab", "window", "private-window"].map(openIn => ({
|
||||
openIn,
|
||||
})),
|
||||
"The menu opens the result in a new target"
|
||||
);
|
||||
Assert.ok(
|
||||
fromMenuButton.some(item => item.command),
|
||||
"The menu keeps the result's own commands"
|
||||
);
|
||||
|
||||
gURLBar.view.close();
|
||||
await PlacesUtils.history.clear();
|
||||
});
|
||||
|
||||
// With the feature gate off, the three-dot menu holds only the result's own
|
||||
// commands and a right-click opens nothing.
|
||||
add_task(async function feature_gate_off() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [["browser.urlbar.contextMenu.featureGate", false]],
|
||||
});
|
||||
await PlacesTestUtils.addVisits(["https://example.com/"]);
|
||||
|
||||
let resultIndex = await promiseResultWithMenuButton();
|
||||
await UrlbarTestUtils.openResultMenu(window, { resultIndex, byMouse: true });
|
||||
let items = await promiseMenuDescription();
|
||||
Assert.ok(
|
||||
items.some(item => item.command),
|
||||
"The menu holds the result's own commands"
|
||||
);
|
||||
Assert.ok(
|
||||
items.every(item => !item.openIn),
|
||||
"The menu doesn't open the result in a new target"
|
||||
);
|
||||
gURLBar.view.resultMenu.hide(undefined, { force: true });
|
||||
|
||||
gURLBar.view.close();
|
||||
await PlacesUtils.history.clear();
|
||||
await SpecialPowers.popPrefEnv();
|
||||
});
|
||||
|
||||
add_task(async function toolbar_context_menu() {
|
||||
let TEST_TARGETS = [
|
||||
".searchmode-switcher",
|
||||
@@ -220,7 +284,7 @@ add_task(async function no_context_menu() {
|
||||
});
|
||||
|
||||
add_task(async function keep_view_open_on_context_menu_mousedown() {
|
||||
let contextMenu = await openContextMenuOnFirstResult();
|
||||
let menu = await openContextMenuOnFirstResult();
|
||||
Assert.ok(
|
||||
gURLBar.view.isOpen,
|
||||
"The view should remain open after the context menu is shown"
|
||||
@@ -228,7 +292,7 @@ add_task(async function keep_view_open_on_context_menu_mousedown() {
|
||||
|
||||
info("Mouse down on a context menu item");
|
||||
EventUtils.synthesizeMouseAtCenter(
|
||||
contextMenu.querySelector("#urlbar-view-context-menu-open-in-tab"),
|
||||
menu.querySelector('[data-open-in="tab"]'),
|
||||
{ type: "mousedown" }
|
||||
);
|
||||
|
||||
@@ -237,10 +301,64 @@ add_task(async function keep_view_open_on_context_menu_mousedown() {
|
||||
"The view stays open after a mousedown on the context menu"
|
||||
);
|
||||
|
||||
contextMenu.hide(undefined, { force: true });
|
||||
menu.hide(undefined, { force: true });
|
||||
gURLBar.view.close();
|
||||
});
|
||||
|
||||
// Returns the menu's items as the command or open-in target each one picks, in
|
||||
// the order they are shown, separators included.
|
||||
async function promiseMenuDescription() {
|
||||
let menu = gURLBar.view.resultMenu;
|
||||
await TestUtils.waitForCondition(
|
||||
() => menu.children.length,
|
||||
"Waiting for the menu to be populated"
|
||||
);
|
||||
return [...menu.children].map(item => {
|
||||
if (item.localName == "hr") {
|
||||
return "separator";
|
||||
}
|
||||
let { command, openIn } = item.dataset;
|
||||
return command ? { command } : { openIn };
|
||||
});
|
||||
}
|
||||
|
||||
// Searches for "example" and returns the index of a result that has a menu
|
||||
// button, which is also a result the menu can open in a new target.
|
||||
async function promiseResultWithMenuButton() {
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
value: "example",
|
||||
window,
|
||||
fireInputEvent: true,
|
||||
});
|
||||
for (let i = 0; i < UrlbarTestUtils.getResultCount(window); i++) {
|
||||
let { element, url } = await UrlbarTestUtils.getDetailsOfResultAt(
|
||||
window,
|
||||
i
|
||||
);
|
||||
if (url && element.row.hasAttribute("has-menu-button")) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new Error("No result with a menu button");
|
||||
}
|
||||
|
||||
async function openContextMenu(row) {
|
||||
info("Open the context menu");
|
||||
let menu = gURLBar.view.resultMenu;
|
||||
let onShown = BrowserTestUtils.waitForEvent(menu, "shown");
|
||||
EventUtils.synthesizeMouseAtCenter(row, {
|
||||
button: 2,
|
||||
type: "mousedown",
|
||||
});
|
||||
EventUtils.synthesizeMouseAtCenter(row, {
|
||||
button: 2,
|
||||
type: "contextmenu",
|
||||
});
|
||||
await onShown;
|
||||
|
||||
return menu;
|
||||
}
|
||||
|
||||
async function openContextMenuOnFirstResult() {
|
||||
info("Open urlbar results");
|
||||
await UrlbarTestUtils.promiseAutocompleteResultPopup({
|
||||
@@ -249,21 +367,7 @@ async function openContextMenuOnFirstResult() {
|
||||
fireInputEvent: true,
|
||||
});
|
||||
let { element } = await UrlbarTestUtils.getDetailsOfResultAt(window, 0);
|
||||
|
||||
info("Open context menu");
|
||||
let contextMenu = document.getElementById("urlbarView-context-menu");
|
||||
let onShown = BrowserTestUtils.waitForEvent(contextMenu, "shown");
|
||||
EventUtils.synthesizeMouseAtCenter(element.row, {
|
||||
button: 2,
|
||||
type: "mousedown",
|
||||
});
|
||||
EventUtils.synthesizeMouseAtCenter(element.row, {
|
||||
button: 2,
|
||||
type: "contextmenu",
|
||||
});
|
||||
await onShown;
|
||||
|
||||
return contextMenu;
|
||||
return openContextMenu(element.row);
|
||||
}
|
||||
|
||||
// Opens the submenu of the given item, the same way hovering it does, and
|
||||
|
||||
@@ -22,9 +22,9 @@ type UrlbarResult = import("../content/UrlbarResult.mjs").UrlbarResult;
|
||||
*/
|
||||
type UrlbarResultCommand = {
|
||||
/**
|
||||
* The name of the command. Must be specified unless `children` is present.
|
||||
* When a command is picked, its name will be passed as `details.selType` to
|
||||
* `onEngagement()`. The special name "separator" will create a menu separator.
|
||||
* The name of the command. When a command is picked, its name will be passed
|
||||
* as `details.selType` to `onEngagement()`. The special name "separator" will
|
||||
* create a menu separator.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
@@ -33,7 +33,14 @@ type UrlbarResultCommand = {
|
||||
*/
|
||||
l10n?: L10nIdArgs;
|
||||
/**
|
||||
* If specified, a submenu will be created with the given child commands.
|
||||
* Where the command opens the result, for the view's own commands. Passed to
|
||||
* `pickResult()` in place of a `name`, so that these picks are recorded as
|
||||
* ordinary result picks.
|
||||
*/
|
||||
children?: UrlbarResultCommand[];
|
||||
openIn?: "tab" | "container-tab" | "window" | "private-window";
|
||||
/**
|
||||
* Whether the command's menu item holds a submenu, populated when it's about
|
||||
* to be shown.
|
||||
*/
|
||||
submenu?: boolean;
|
||||
};
|
||||
|
||||
@@ -934,7 +934,7 @@ urlbar-result-action-switch-to-tabgroup = Switch to { $group }
|
||||
# $group (String): the name of the tab group to re-open
|
||||
urlbar-result-action-open-saved-tabgroup = Open { $group }
|
||||
|
||||
## Used in the context menu in urlbar view.
|
||||
## Used in the menu of a urlbar result.
|
||||
|
||||
urlbar-view-context-menu-open-in-tab2 = Open in New Tab
|
||||
.accesskey = w
|
||||
|
||||
@@ -537,8 +537,7 @@
|
||||
|
||||
/* This should be supported within panel-{item,list} rather than modifying it, to be fixed by
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1826841 */
|
||||
.urlbarView-result-menuitem::part(button),
|
||||
#urlbarView-context-menu panel-item::part(button) {
|
||||
.urlbarView-result-menuitem::part(button) {
|
||||
padding-inline-start: 12px;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user