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 --- .../tests/browser/browser_views_liveupdate.js | 493 +++++++++++++++++++++ 1 file changed, 493 insertions(+) create mode 100644 browser/components/places/tests/browser/browser_views_liveupdate.js (limited to 'browser/components/places/tests/browser/browser_views_liveupdate.js') diff --git a/browser/components/places/tests/browser/browser_views_liveupdate.js b/browser/components/places/tests/browser/browser_views_liveupdate.js new file mode 100644 index 0000000000..cce35941f3 --- /dev/null +++ b/browser/components/places/tests/browser/browser_views_liveupdate.js @@ -0,0 +1,493 @@ +/* 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/. */ + +/** + * Tests Places views (menu, toolbar, tree) for liveupdate. + */ + +var toolbar = document.getElementById("PersonalToolbar"); +var wasCollapsed = toolbar.collapsed; + +/** + * Simulates popup opening causing it to populate. + * We cannot just use menu.open, since it would not work on Mac due to native menubar. + * + * @param {object} aPopup + * The popup element + */ +function fakeOpenPopup(aPopup) { + var popupEvent = document.createEvent("MouseEvent"); + popupEvent.initMouseEvent( + "popupshowing", + true, + true, + window, + 0, + 0, + 0, + 0, + 0, + false, + false, + false, + false, + 0, + null + ); + aPopup.dispatchEvent(popupEvent); +} + +async function testInFolder(folderGuid, prefix) { + let addedBookmarks = []; + let item = await PlacesUtils.bookmarks.insert({ + parentGuid: folderGuid, + title: `${prefix}1`, + url: `http://${prefix}1.mozilla.org/`, + }); + await bookmarksObserver.assertViewsUpdatedCorrectly(); + + item.title = `${prefix}1_edited`; + await PlacesUtils.bookmarks.update(item); + await bookmarksObserver.assertViewsUpdatedCorrectly(); + + addedBookmarks.push(item); + + item = await PlacesUtils.bookmarks.insert({ + parentGuid: folderGuid, + title: `${prefix}2`, + url: "place:", + }); + await bookmarksObserver.assertViewsUpdatedCorrectly(); + + item.title = `${prefix}2_edited`; + await PlacesUtils.bookmarks.update(item); + await bookmarksObserver.assertViewsUpdatedCorrectly(); + addedBookmarks.push(item); + + item = await PlacesUtils.bookmarks.insert({ + parentGuid: folderGuid, + type: PlacesUtils.bookmarks.TYPE_SEPARATOR, + }); + await bookmarksObserver.assertViewsUpdatedCorrectly(); + addedBookmarks.push(item); + + item = await PlacesUtils.bookmarks.insert({ + parentGuid: folderGuid, + title: `${prefix}f`, + type: PlacesUtils.bookmarks.TYPE_FOLDER, + }); + await bookmarksObserver.assertViewsUpdatedCorrectly(); + + item.title = `${prefix}f_edited`; + await PlacesUtils.bookmarks.update(item); + await bookmarksObserver.assertViewsUpdatedCorrectly(); + addedBookmarks.push(item); + + item = await PlacesUtils.bookmarks.insert({ + parentGuid: item.guid, + title: `${prefix}f1`, + url: `http://${prefix}f1.mozilla.org/`, + }); + await bookmarksObserver.assertViewsUpdatedCorrectly(); + addedBookmarks.push(item); + + item.index = 0; + item.parentGuid = folderGuid; + await PlacesUtils.bookmarks.update(item); + await bookmarksObserver.assertViewsUpdatedCorrectly(); + + return addedBookmarks; +} + +add_task(async function test() { + // Uncollapse the personal toolbar if needed. + if (wasCollapsed) { + await promiseSetToolbarVisibility(toolbar, true); + } + + // Open bookmarks menu. + var popup = document.getElementById("bookmarksMenuPopup"); + ok(popup, "Menu popup element exists"); + fakeOpenPopup(popup); + + // Open bookmarks sidebar. + await withSidebarTree("bookmarks", async () => { + // Add observers. + bookmarksObserver.handlePlacesEvents = + bookmarksObserver.handlePlacesEvents.bind(bookmarksObserver); + PlacesUtils.observers.addListener( + ["bookmark-added", "bookmark-removed"], + bookmarksObserver.handlePlacesEvents + ); + var addedBookmarks = []; + + // MENU + info("*** Acting on menu bookmarks"); + addedBookmarks = addedBookmarks.concat( + await testInFolder(PlacesUtils.bookmarks.menuGuid, "bm") + ); + + // TOOLBAR + info("*** Acting on toolbar bookmarks"); + addedBookmarks = addedBookmarks.concat( + await testInFolder(PlacesUtils.bookmarks.toolbarGuid, "tb") + ); + + // UNSORTED + info("*** Acting on unsorted bookmarks"); + addedBookmarks = addedBookmarks.concat( + await testInFolder(PlacesUtils.bookmarks.unfiledGuid, "ub") + ); + + // Remove all added bookmarks. + for (let bm of addedBookmarks) { + // If we remove an item after its containing folder has been removed, + // this will throw, but we can ignore that. + try { + await PlacesUtils.bookmarks.remove(bm); + } catch (ex) {} + await bookmarksObserver.assertViewsUpdatedCorrectly(); + } + + // Remove observers. + PlacesUtils.observers.removeListener( + ["bookmark-added", "bookmark-removed"], + bookmarksObserver.handlePlacesEvents + ); + }); + + // Collapse the personal toolbar if needed. + if (wasCollapsed) { + await promiseSetToolbarVisibility(toolbar, false); + } +}); + +/** + * The observer is where magic happens, for every change we do it will look for + * nodes positions in the affected views. + */ +var bookmarksObserver = { + _notifications: [], + + handlePlacesEvents(events) { + for (let { type, parentGuid, guid, index } of events) { + switch (type) { + case "bookmark-added": + this._notifications.push([ + "assertItemAdded", + parentGuid, + guid, + index, + ]); + break; + case "bookmark-removed": + this._notifications.push(["assertItemRemoved", parentGuid, guid]); + break; + } + } + }, + + async assertViewsUpdatedCorrectly() { + for (let notification of this._notifications) { + let assertFunction = notification.shift(); + + let views = await getViewsForFolder(notification.shift()); + Assert.greater( + views.length, + 0, + "Should have found one or more views for the parent folder." + ); + + await this[assertFunction](views, ...notification); + } + + this._notifications = []; + }, + + async assertItemAdded(views, guid, expectedIndex) { + for (let i = 0; i < views.length; i++) { + let [node, index] = searchItemInView(guid, views[i]); + Assert.notEqual(node, null, "Should have found the view in " + views[i]); + Assert.equal( + index, + expectedIndex, + "Should have found the node at the expected index" + ); + } + }, + + async assertItemRemoved(views, guid) { + for (let i = 0; i < views.length; i++) { + let [node] = searchItemInView(guid, views[i]); + Assert.equal(node, null, "Should not have found the node"); + } + }, + + async assertItemMoved(views, guid, newIndex) { + // Check that item has been moved in the correct position. + for (let i = 0; i < views.length; i++) { + let [node, index] = searchItemInView(guid, views[i]); + Assert.notEqual(node, null, "Should have found the view in " + views[i]); + Assert.equal( + index, + newIndex, + "Should have found the node at the expected index" + ); + } + }, + + async assertItemChanged(views, guid, newValue) { + let validator = function (aElementOrTreeIndex) { + if (typeof aElementOrTreeIndex == "number") { + let sidebar = document.getElementById("sidebar"); + let tree = sidebar.contentDocument.getElementById("bookmarks-view"); + let cellText = tree.view.getCellText( + aElementOrTreeIndex, + tree.columns.getColumnAt(0) + ); + if (!newValue) { + return ( + cellText == + PlacesUIUtils.getBestTitle( + tree.view.nodeForTreeIndex(aElementOrTreeIndex), + true + ) + ); + } + return cellText == newValue; + } + if (!newValue && aElementOrTreeIndex.localName != "toolbarbutton") { + return ( + aElementOrTreeIndex.getAttribute("label") == + PlacesUIUtils.getBestTitle(aElementOrTreeIndex._placesNode) + ); + } + return aElementOrTreeIndex.getAttribute("label") == newValue; + }; + + for (let i = 0; i < views.length; i++) { + let [node, , valid] = searchItemInView(guid, views[i], validator); + Assert.notEqual(node, null, "Should have found the view in " + views[i]); + Assert.equal( + node.title, + newValue, + "Node should have the correct new title" + ); + Assert.ok(valid, "Node element should have the correct label"); + } + }, +}; + +/** + * Search an item guid in a view. + * + * @param {string} itemGuid + * item guid of the item to search. + * @param {string} view + * either "toolbar", "menu" or "sidebar" + * @param {Function} validator + * function to check validity of the found node element. + * @returns {Array} + * [node, index, valid] or [null, null, false] if not found. + */ +function searchItemInView(itemGuid, view, validator) { + switch (view) { + case "toolbar": + return getNodeForToolbarItem(itemGuid, validator); + case "menu": + return getNodeForMenuItem(itemGuid, validator); + case "sidebar": + return getNodeForSidebarItem(itemGuid, validator); + } + + return [null, null, false]; +} + +/** + * Get places node and index for an itemGuid in bookmarks toolbar view. + * + * @param {string} itemGuid + * item guid of the item to search. + * @param {Function} validator + * function to check validity of the found node element. + * @returns {Array} + * [node, index] or [null, null] if not found. + */ +function getNodeForToolbarItem(itemGuid, validator) { + var placesToolbarItems = document.getElementById("PlacesToolbarItems"); + + function findNode(aContainer) { + var children = aContainer.children; + for (var i = 0, staticNodes = 0; i < children.length; i++) { + var child = children[i]; + + // Is this a Places node? + if (!child._placesNode) { + staticNodes++; + continue; + } + + if (child._placesNode.bookmarkGuid == itemGuid) { + let valid = validator ? validator(child) : true; + return [child._placesNode, i - staticNodes, valid]; + } + + // Don't search in queries, they could contain our item in a + // different position. Search only folders + if (PlacesUtils.nodeIsFolder(child._placesNode)) { + var popup = child.menupopup; + popup.openPopup(); + var foundNode = findNode(popup); + popup.hidePopup(); + if (foundNode[0] != null) { + return foundNode; + } + } + } + return [null, null]; + } + + return findNode(placesToolbarItems); +} + +/** + * Get places node and index for an itemGuid in bookmarks menu view. + * + * @param {string} itemGuid + * item guid of the item to search. + * @param {Function} validator + * function to check validity of the found node element. + * @returns {Array} + * [node, index] or [null, null] if not found. + */ +function getNodeForMenuItem(itemGuid, validator) { + var menu = document.getElementById("bookmarksMenu"); + + function findNode(aContainer) { + var children = aContainer.children; + for (var i = 0, staticNodes = 0; i < children.length; i++) { + var child = children[i]; + + // Is this a Places node? + if (!child._placesNode) { + staticNodes++; + continue; + } + + if (child._placesNode.bookmarkGuid == itemGuid) { + let valid = validator ? validator(child) : true; + return [child._placesNode, i - staticNodes, valid]; + } + + // Don't search in queries, they could contain our item in a + // different position. Search only folders + if (PlacesUtils.nodeIsFolder(child._placesNode)) { + var popup = child.lastElementChild; + fakeOpenPopup(popup); + var foundNode = findNode(popup); + + child.open = false; + if (foundNode[0] != null) { + return foundNode; + } + } + } + return [null, null, false]; + } + + return findNode(menu.lastElementChild); +} + +/** + * Get places node and index for an itemGuid in sidebar tree view. + * + * @param {string} itemGuid + * item guid of the item to search. + * @param {Function} validator + * function to check validity of the found node element. + * @returns {Array} + * [node, index] or [null, null] if not found. + */ +function getNodeForSidebarItem(itemGuid, validator) { + var sidebar = document.getElementById("sidebar"); + var tree = sidebar.contentDocument.getElementById("bookmarks-view"); + + function findNode(aContainerIndex) { + if (tree.view.isContainerEmpty(aContainerIndex)) { + return [null, null, false]; + } + + // The rowCount limit is just for sanity, but we will end looping when + // we have checked the last child of this container or we have found node. + for (var i = aContainerIndex + 1; i < tree.view.rowCount; i++) { + var node = tree.view.nodeForTreeIndex(i); + + if (node.bookmarkGuid == itemGuid) { + // Minus one because we want relative index inside the container. + let valid = validator ? validator(i) : true; + return [node, i - tree.view.getParentIndex(i) - 1, valid]; + } + + if (PlacesUtils.nodeIsFolder(node)) { + // Open container. + tree.view.toggleOpenState(i); + // Search inside it. + var foundNode = findNode(i); + // Close container. + tree.view.toggleOpenState(i); + // Return node if found. + if (foundNode[0] != null) { + return foundNode; + } + } + + // We have finished walking this container. + if (!tree.view.hasNextSibling(aContainerIndex + 1, i)) { + break; + } + } + return [null, null, false]; + } + + // Root node is hidden, so we need to manually walk the first level. + for (var i = 0; i < tree.view.rowCount; i++) { + // Open container. + tree.view.toggleOpenState(i); + // Search inside it. + var foundNode = findNode(i); + // Close container. + tree.view.toggleOpenState(i); + // Return node if found. + if (foundNode[0] != null) { + return foundNode; + } + } + return [null, null, false]; +} + +/** + * Get views affected by changes to a folder. + * + * @param {string} folderGuid + * item guid of the folder we have changed. + * @returns {Array<"toolbar" | "menu" | "sidebar">} + * subset of views: ["toolbar", "menu", "sidebar"] + */ +async function getViewsForFolder(folderGuid) { + let rootGuid = folderGuid; + while (!PlacesUtils.isRootItem(rootGuid)) { + let itemData = await PlacesUtils.bookmarks.fetch(rootGuid); + rootGuid = itemData.parentGuid; + } + + switch (rootGuid) { + case PlacesUtils.bookmarks.toolbarGuid: + return ["toolbar", "sidebar"]; + case PlacesUtils.bookmarks.menuGuid: + return ["menu", "sidebar"]; + case PlacesUtils.bookmarks.unfiledGuid: + return ["sidebar"]; + } + return []; +} -- cgit v1.2.3