Bug 1995696 - Don't populate Places menus if they create a recursive structure a=pascalc
Original Revision: https://phabricator.services.mozilla.com/D269599 Differential Revision: https://phabricator.services.mozilla.com/D271039
This commit is contained in:
committed by
pchevrel@mozilla.com
parent
3d600c8b5d
commit
fa313641c0
@@ -797,6 +797,15 @@ class PlacesViewBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
|
if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) {
|
||||||
|
if (this.#isPopupForRecursiveFolderShortcut(popup)) {
|
||||||
|
// Show as an empty container for now. We may want to show a better
|
||||||
|
// message in the future, but since we are likely to remove recursive
|
||||||
|
// shortcuts in maintenance at a certain point, this should be enough.
|
||||||
|
this._setEmptyPopupStatus(popup, true);
|
||||||
|
popup._built = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!popup._placesNode.containerOpen) {
|
if (!popup._placesNode.containerOpen) {
|
||||||
popup._placesNode.containerOpen = true;
|
popup._placesNode.containerOpen = true;
|
||||||
}
|
}
|
||||||
@@ -819,6 +828,33 @@ class PlacesViewBase {
|
|||||||
aObject.removeEventListener(aEventNames[i], this, aCapturing);
|
aObject.removeEventListener(aEventNames[i], this, aCapturing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walks up the parent chain to detect whether a folder shortcut resolves to
|
||||||
|
* a folder already present in the ancestry.
|
||||||
|
*
|
||||||
|
* @param {DOMElement} popup
|
||||||
|
* @returns {boolean} Whether this popup is for a recursive folder shortcut.
|
||||||
|
*/
|
||||||
|
#isPopupForRecursiveFolderShortcut(popup) {
|
||||||
|
if (
|
||||||
|
!popup._placesNode ||
|
||||||
|
!PlacesUtils.nodeIsFolderOrShortcut(popup._placesNode)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let guid = PlacesUtils.getConcreteItemGuid(popup._placesNode);
|
||||||
|
for (
|
||||||
|
let parentView = popup.parentNode?.parentNode;
|
||||||
|
parentView?._placesNode;
|
||||||
|
parentView = parentView.parentNode?.parentNode
|
||||||
|
) {
|
||||||
|
if (PlacesUtils.getConcreteItemGuid(parentView._placesNode) == guid) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -214,6 +214,8 @@ skip-if = ["verify && debug && os == 'win'"]
|
|||||||
|
|
||||||
["browser_paste_resets_cut_highlights.js"]
|
["browser_paste_resets_cut_highlights.js"]
|
||||||
|
|
||||||
|
["browser_recursive_hierarchies.js"]
|
||||||
|
|
||||||
["browser_remove_bookmarks.js"]
|
["browser_remove_bookmarks.js"]
|
||||||
|
|
||||||
["browser_sidebar_bookmarks_telemetry.js"]
|
["browser_sidebar_bookmarks_telemetry.js"]
|
||||||
|
|||||||
@@ -0,0 +1,160 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify that menu popups are not populated when doing so would create a
|
||||||
|
* recursive hierarchy. Some consumers may walk menu trees and could enter an
|
||||||
|
* infinite loop if recursion were allowed.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
async function openMenuAndWaitForPopup(menu) {
|
||||||
|
let popup = menu.menupopup;
|
||||||
|
let popupShown = BrowserTestUtils.waitForEvent(popup, "popupshown");
|
||||||
|
// This seems necessary, at least for the toolbar.
|
||||||
|
EventUtils.synthesizeNativeMouseEvent({
|
||||||
|
type: "mousemove",
|
||||||
|
target: menu,
|
||||||
|
atCenter: true,
|
||||||
|
});
|
||||||
|
menu.open = true;
|
||||||
|
await popupShown;
|
||||||
|
return popup;
|
||||||
|
}
|
||||||
|
|
||||||
|
function findNode(guid, view) {
|
||||||
|
for (let node of view.childNodes) {
|
||||||
|
console.log("visiting node", node, node._placesNode?.bookmarkGuid);
|
||||||
|
if (node._placesNode?.bookmarkGuid == guid) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function fakeOpenMenu(popup) {
|
||||||
|
popup.dispatchEvent(new MouseEvent("popupshowing", { bubbles: true }));
|
||||||
|
popup.dispatchEvent(new MouseEvent("popupshown", { bubbles: true }));
|
||||||
|
}
|
||||||
|
|
||||||
|
function findPlacesNode(guid, container) {
|
||||||
|
for (let i = 0; i < container.childCount; i++) {
|
||||||
|
let node = container.getChild(i);
|
||||||
|
if (node.bookmarkGuid == guid) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_task(async function test() {
|
||||||
|
// Make sure the bookmarks bar is visible and restore its state on cleanup.
|
||||||
|
let toolbar = document.getElementById("PersonalToolbar");
|
||||||
|
ok(toolbar, "PersonalToolbar should not be null");
|
||||||
|
if (toolbar.collapsed) {
|
||||||
|
await promiseSetToolbarVisibility(toolbar, true);
|
||||||
|
registerCleanupFunction(function () {
|
||||||
|
return promiseSetToolbarVisibility(toolbar, false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let menubar = document.getElementById("toolbar-menubar");
|
||||||
|
// Force the menu to be shown.
|
||||||
|
const kAutohide = menubar.getAttribute("autohide");
|
||||||
|
menubar.removeAttribute("autohide");
|
||||||
|
registerCleanupFunction(function () {
|
||||||
|
menubar.setAttribute("autohide", kAutohide);
|
||||||
|
});
|
||||||
|
|
||||||
|
registerCleanupFunction(PlacesUtils.bookmarks.eraseEverything);
|
||||||
|
|
||||||
|
// Create a folder structure with a recursive shortcut.
|
||||||
|
let folderA = await PlacesUtils.bookmarks.insert({
|
||||||
|
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
|
||||||
|
title: "Folder A",
|
||||||
|
type: PlacesUtils.bookmarks.TYPE_FOLDER,
|
||||||
|
});
|
||||||
|
let selfShortcut = await PlacesUtils.bookmarks.insert({
|
||||||
|
parentGuid: folderA.guid,
|
||||||
|
title: "Shortcut to A",
|
||||||
|
url: `place:parent=${folderA.guid}`,
|
||||||
|
});
|
||||||
|
let toolbarShortcut = await PlacesUtils.bookmarks.insert({
|
||||||
|
parentGuid: folderA.guid,
|
||||||
|
title: "Shortcut to toolbar",
|
||||||
|
url: `place:parent=${PlacesUtils.bookmarks.toolbarGuid}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// This also ensures that toolbar content is updated.
|
||||||
|
Assert.ok(!(await PlacesToolbarHelper.getIsEmpty()), "Toolbar is not empty");
|
||||||
|
|
||||||
|
// Open the popup for the shortcut from the bookmarks toolbar.
|
||||||
|
let toolbarItems = document.getElementById("PlacesToolbarItems");
|
||||||
|
let folderANode = findNode(folderA.guid, toolbarItems);
|
||||||
|
let folderAPopup = await openMenuAndWaitForPopup(folderANode);
|
||||||
|
let selfShortcutNode = findNode(selfShortcut.guid, folderAPopup);
|
||||||
|
let selfShortcutPopup = await openMenuAndWaitForPopup(selfShortcutNode);
|
||||||
|
|
||||||
|
Assert.ok(
|
||||||
|
!selfShortcutPopup._placesNode.containerOpen,
|
||||||
|
"Self shortcut container is not open"
|
||||||
|
);
|
||||||
|
Assert.ok(
|
||||||
|
selfShortcutPopup.hasAttribute("emptyplacesresult"),
|
||||||
|
"Self shortcut popup is empty"
|
||||||
|
);
|
||||||
|
|
||||||
|
let toolbarShortcutNode = findNode(
|
||||||
|
toolbarShortcut.guid,
|
||||||
|
folderANode.menupopup
|
||||||
|
);
|
||||||
|
let toolbarShortcutPopup = await openMenuAndWaitForPopup(toolbarShortcutNode);
|
||||||
|
|
||||||
|
Assert.ok(
|
||||||
|
!toolbarShortcutPopup._placesNode.containerOpen,
|
||||||
|
"Toolbar shortcut container is not open"
|
||||||
|
);
|
||||||
|
Assert.ok(
|
||||||
|
toolbarShortcutPopup.hasAttribute("emptyplacesresult"),
|
||||||
|
"Toolbar shortcut popup is empty"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Also insert a toolbar shortcut in the bookmarks menu and check the
|
||||||
|
// previously inserted recursive toolbar shortcut there.
|
||||||
|
info("Test native bookmarks menu");
|
||||||
|
let shortcutInMenu = await PlacesUtils.bookmarks.insert({
|
||||||
|
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||||
|
title: "Shortcut to menu",
|
||||||
|
url: `place:parent=${PlacesUtils.bookmarks.menuGuid}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (Services.appinfo.nativeMenubar) {
|
||||||
|
// With native menubar we can only simulate popupshowing as we cannot
|
||||||
|
// directly open menus.
|
||||||
|
let bookmarksMenuPopup = document.getElementById("bookmarksMenuPopup");
|
||||||
|
fakeOpenMenu(bookmarksMenuPopup);
|
||||||
|
let shortcutInMenuNode = findNode(shortcutInMenu.guid, bookmarksMenuPopup);
|
||||||
|
fakeOpenMenu(shortcutInMenuNode.menupopup);
|
||||||
|
Assert.ok(
|
||||||
|
!shortcutInMenuNode.menupopup._placesNode.containerOpen,
|
||||||
|
"menu shortcut container is not open"
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let bookmarksMenu = document.getElementById("bookmarksMenu");
|
||||||
|
let bookmarksPopup = await openMenuAndWaitForPopup(bookmarksMenu);
|
||||||
|
let shortcutInMenuNode = findNode(shortcutInMenu.guid, bookmarksPopup);
|
||||||
|
let shortcutInMenuPopup = await openMenuAndWaitForPopup(
|
||||||
|
shortcutInMenuNode,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
Assert.ok(
|
||||||
|
!shortcutInMenuPopup._placesNode.containerOpen,
|
||||||
|
"Toolbar shortcut container is not open"
|
||||||
|
);
|
||||||
|
Assert.ok(
|
||||||
|
shortcutInMenuPopup.hasAttribute("emptyplacesresult"),
|
||||||
|
"Toolbar shortcut popup is empty"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user