summaryrefslogtreecommitdiffstats
path: root/browser/components/places/tests/browser/head.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/places/tests/browser/head.js')
-rw-r--r--browser/components/places/tests/browser/head.js533
1 files changed, 533 insertions, 0 deletions
diff --git a/browser/components/places/tests/browser/head.js b/browser/components/places/tests/browser/head.js
new file mode 100644
index 0000000000..1827d84cbc
--- /dev/null
+++ b/browser/components/places/tests/browser/head.js
@@ -0,0 +1,533 @@
+ChromeUtils.defineESModuleGetters(this, {
+ PlacesTestUtils: "resource://testing-common/PlacesTestUtils.sys.mjs",
+});
+
+XPCOMUtils.defineLazyGetter(this, "gFluentStrings", function () {
+ return new Localization(["branding/brand.ftl", "browser/browser.ftl"], true);
+});
+
+function openLibrary(callback, aLeftPaneRoot) {
+ let library = window.openDialog(
+ "chrome://browser/content/places/places.xhtml",
+ "",
+ "chrome,toolbar=yes,dialog=no,resizable",
+ aLeftPaneRoot
+ );
+ waitForFocus(function () {
+ checkLibraryPaneVisibility(library, aLeftPaneRoot);
+ callback(library);
+ }, library);
+
+ return library;
+}
+
+/**
+ * Returns a handle to a Library window.
+ * If one is opens returns itm otherwise it opens a new one.
+ *
+ * @param {object} aLeftPaneRoot
+ * Hierarchy to open and select in the left pane.
+ * @returns {Promise}
+ * Resolves to the handle to the library window.
+ */
+function promiseLibrary(aLeftPaneRoot) {
+ return new Promise(resolve => {
+ let library = Services.wm.getMostRecentWindow("Places:Organizer");
+ if (library && !library.closed) {
+ if (aLeftPaneRoot) {
+ library.PlacesOrganizer.selectLeftPaneContainerByHierarchy(
+ aLeftPaneRoot
+ );
+ }
+ checkLibraryPaneVisibility(library, aLeftPaneRoot);
+ resolve(library);
+ } else {
+ openLibrary(resolve, aLeftPaneRoot);
+ }
+ });
+}
+
+function promiseLibraryClosed(organizer) {
+ return new Promise(resolve => {
+ if (organizer.closed) {
+ resolve();
+ return;
+ }
+ // Wait for the Organizer window to actually be closed
+ organizer.addEventListener(
+ "unload",
+ function () {
+ executeSoon(resolve);
+ },
+ { once: true }
+ );
+
+ // Close Library window.
+ organizer.close();
+ });
+}
+
+function checkLibraryPaneVisibility(library, selectedPane) {
+ // Make sure right view is shown
+ if (selectedPane == "Downloads") {
+ Assert.ok(
+ library.ContentTree.view.hidden,
+ "Bookmark/History tree is hidden"
+ );
+ Assert.ok(
+ !library.document.getElementById("downloadsListBox").hidden,
+ "Downloads are shown"
+ );
+ } else {
+ Assert.ok(
+ !library.ContentTree.view.hidden,
+ "Bookmark/History tree is shown"
+ );
+ Assert.ok(
+ library.document.getElementById("downloadsListBox").hidden,
+ "Downloads are hidden"
+ );
+ }
+
+ // Check currentView getter
+ Assert.ok(!library.ContentArea.currentView.hidden, "Current view is shown");
+}
+
+/**
+ * Waits for a clipboard operation to complete, looking for the expected type.
+ *
+ * @see waitForClipboard
+ *
+ * @param {Function} aPopulateClipboardFn
+ * Function to populate the clipboard.
+ * @param {string} aFlavor
+ * Data flavor to expect.
+ * @returns {Promise}
+ * A promise that is resolved with the data.
+ */
+function promiseClipboard(aPopulateClipboardFn, aFlavor) {
+ return new Promise((resolve, reject) => {
+ waitForClipboard(
+ data => !!data,
+ aPopulateClipboardFn,
+ resolve,
+ reject,
+ aFlavor
+ );
+ });
+}
+
+function synthesizeClickOnSelectedTreeCell(aTree, aOptions) {
+ if (aTree.view.selection.count < 1) {
+ throw new Error("The test node should be successfully selected");
+ }
+ // Get selection rowID.
+ let min = {},
+ max = {};
+ aTree.view.selection.getRangeAt(0, min, max);
+ let rowID = min.value;
+ aTree.ensureRowIsVisible(rowID);
+ // Calculate the click coordinates.
+ var rect = aTree.getCoordsForCellItem(rowID, aTree.columns[0], "text");
+ var x = rect.x + rect.width / 2;
+ var y = rect.y + rect.height / 2;
+ // Simulate the click.
+ EventUtils.synthesizeMouse(
+ aTree.body,
+ x,
+ y,
+ aOptions || {},
+ aTree.ownerGlobal
+ );
+}
+
+/**
+ * Makes the specified toolbar visible or invisible and returns a Promise object
+ * that is resolved when the toolbar has completed any animations associated
+ * with hiding or showing the toolbar.
+ *
+ * Note that this code assumes that changes to a toolbar's visibility trigger
+ * a transition on the max-height property of the toolbar element.
+ * Changes to this styling could cause the returned Promise object to be
+ * resolved too early or not at all.
+ *
+ * @param {object} aToolbar
+ * The toolbar to update.
+ * @param {boolean} aVisible
+ * True to make the toolbar visible, false to make it hidden.
+ *
+ * @returns {Promise} Any animation associated with updating the toolbar's
+ * visibility has finished.
+ */
+function promiseSetToolbarVisibility(aToolbar, aVisible) {
+ if (isToolbarVisible(aToolbar) != aVisible) {
+ let visibilityChanged = TestUtils.waitForCondition(
+ () => aToolbar.collapsed != aVisible
+ );
+ setToolbarVisibility(aToolbar, aVisible, undefined, false);
+ return visibilityChanged;
+ }
+ return Promise.resolve();
+}
+
+/**
+ * Helper function to determine if the given toolbar is in the visible
+ * state according to its autohide/collapsed attribute.
+ *
+ * @param {object} aToolbar The toolbar to query.
+ *
+ * @returns {boolean} True if the relevant attribute on |aToolbar| indicates it is
+ * visible, false otherwise.
+ */
+function isToolbarVisible(aToolbar) {
+ let hidingAttribute =
+ aToolbar.getAttribute("type") == "menubar" ? "autohide" : "collapsed";
+ let hidingValue = aToolbar.getAttribute(hidingAttribute).toLowerCase();
+ // Check for both collapsed="true" and collapsed="collapsed"
+ return hidingValue !== "true" && hidingValue !== hidingAttribute;
+}
+
+/**
+ * Executes a task after opening the bookmarks dialog, then cancels the dialog.
+ *
+ * @param {boolean} autoCancel
+ * whether to automatically cancel the dialog at the end of the task
+ * @param {Function} openFn
+ * generator function causing the dialog to open
+ * @param {Function} taskFn
+ * the task to execute once the dialog is open
+ * @param {Function} closeFn
+ * A function to be used to wait for pending work when the dialog is
+ * closing. It is passed the dialog window handle and should return a promise.
+ * @returns {string} guid
+ * Bookmark guid
+ */
+var withBookmarksDialog = async function (autoCancel, openFn, taskFn, closeFn) {
+ let dialogUrl = "chrome://browser/content/places/bookmarkProperties.xhtml";
+ let closed = false;
+ // We can't show the in-window prompt for windows which don't have
+ // gDialogBox, like the library (Places:Organizer) window.
+ let hasDialogBox = !!Services.wm.getMostRecentWindow("").gDialogBox;
+ let dialogPromise;
+ if (hasDialogBox) {
+ dialogPromise = BrowserTestUtils.promiseAlertDialogOpen(null, dialogUrl, {
+ isSubDialog: true,
+ });
+ } else {
+ dialogPromise = BrowserTestUtils.domWindowOpenedAndLoaded(null, win => {
+ return win.document.documentURI.startsWith(dialogUrl);
+ }).then(win => {
+ ok(
+ win.location.href.startsWith(dialogUrl),
+ "The bookmark properties dialog is open: " + win.location.href
+ );
+ // This is needed for the overlay.
+ return SimpleTest.promiseFocus(win).then(() => win);
+ });
+ }
+ let dialogClosePromise = dialogPromise.then(win => {
+ if (!hasDialogBox) {
+ return BrowserTestUtils.domWindowClosed(win);
+ }
+ let container = win.top.document.getElementById("window-modal-dialog");
+ return BrowserTestUtils.waitForEvent(container, "close").then(() => {
+ return BrowserTestUtils.waitForMutationCondition(
+ container,
+ { childList: true, attributes: true },
+ () => !container.hasChildNodes() && !container.open
+ );
+ });
+ });
+ dialogClosePromise.then(() => {
+ closed = true;
+ });
+
+ info("withBookmarksDialog: opening the dialog");
+ // The dialog might be modal and could block our events loop, so executeSoon.
+ executeSoon(openFn);
+
+ info("withBookmarksDialog: waiting for the dialog");
+ let dialogWin = await dialogPromise;
+
+ // Ensure overlay is loaded
+ info("waiting for the overlay to be loaded");
+ await dialogWin.document.mozSubdialogReady;
+
+ // Check the first input is focused.
+ let doc = dialogWin.document;
+ let elt = doc.querySelector('input:not([hidden="true"])');
+ ok(elt, "There should be an input to focus.");
+
+ if (elt) {
+ info("waiting for focus on the first textfield");
+ await TestUtils.waitForCondition(
+ () => doc.activeElement == elt,
+ "The first non collapsed input should have been focused"
+ );
+ }
+
+ info("withBookmarksDialog: executing the task");
+
+ let closePromise = () => Promise.resolve();
+ if (closeFn) {
+ closePromise = closeFn(dialogWin);
+ }
+ let guid;
+ try {
+ await taskFn(dialogWin);
+ } finally {
+ if (!closed && autoCancel) {
+ info("withBookmarksDialog: canceling the dialog");
+ doc.getElementById("bookmarkpropertiesdialog").cancelDialog();
+ await closePromise;
+ }
+ guid = await PlacesUIUtils.lastBookmarkDialogDeferred.promise;
+ // Give the dialog a little time to close itself.
+ await dialogClosePromise;
+ }
+ return guid;
+};
+
+/**
+ * Opens the contextual menu on the element pointed by the given selector.
+ *
+ * @param {object} browser
+ * The associated browser element.
+ * @param {object} selector
+ * Valid selector syntax
+ * @returns {Promise}
+ * Returns a Promise that resolves once the context menu has been
+ * opened.
+ */
+var openContextMenuForContentSelector = async function (browser, selector) {
+ info("wait for the context menu");
+ let contextPromise = BrowserTestUtils.waitForEvent(
+ document.getElementById("contentAreaContextMenu"),
+ "popupshown"
+ );
+ await SpecialPowers.spawn(browser, [{ selector }], async function (args) {
+ let doc = content.document;
+ let elt = doc.querySelector(args.selector);
+ dump(`openContextMenuForContentSelector: found ${elt}\n`);
+
+ /* Open context menu so chrome can access the element */
+ const domWindowUtils = content.windowUtils;
+ let rect = elt.getBoundingClientRect();
+ let left = rect.left + rect.width / 2;
+ let top = rect.top + rect.height / 2;
+ domWindowUtils.sendMouseEvent(
+ "contextmenu",
+ left,
+ top,
+ 2,
+ 1,
+ 0,
+ false,
+ 0,
+ 0,
+ true
+ );
+ });
+ await contextPromise;
+};
+
+/**
+ * Fills a bookmarks dialog text field ensuring to cause expected edit events.
+ *
+ * @param {string} id
+ * id of the text field
+ * @param {string} text
+ * text to fill in
+ * @param {object} win
+ * dialog window
+ * @param {boolean} [blur]
+ * whether to blur at the end.
+ */
+function fillBookmarkTextField(id, text, win, blur = true) {
+ let elt = win.document.getElementById(id);
+ elt.focus();
+ elt.select();
+ if (!text) {
+ EventUtils.synthesizeKey("VK_DELETE", {}, win);
+ } else {
+ for (let c of text.split("")) {
+ EventUtils.synthesizeKey(c, {}, win);
+ }
+ }
+ if (blur) {
+ elt.blur();
+ }
+}
+
+/**
+ * Executes a task after opening the bookmarks or history sidebar. Takes care
+ * of closing the sidebar once done.
+ *
+ * @param {string} type
+ * either "bookmarks" or "history".
+ * @param {Function} taskFn
+ * The task to execute once the sidebar is ready. Will get the Places
+ * tree view as input.
+ */
+var withSidebarTree = async function (type, taskFn) {
+ let sidebar = document.getElementById("sidebar");
+ info("withSidebarTree: waiting sidebar load");
+ let sidebarLoadedPromise = new Promise(resolve => {
+ sidebar.addEventListener(
+ "load",
+ function () {
+ executeSoon(resolve);
+ },
+ { capture: true, once: true }
+ );
+ });
+ let sidebarId =
+ type == "bookmarks" ? "viewBookmarksSidebar" : "viewHistorySidebar";
+ SidebarUI.show(sidebarId);
+ await sidebarLoadedPromise;
+
+ let treeId = type == "bookmarks" ? "bookmarks-view" : "historyTree";
+ let tree = sidebar.contentDocument.getElementById(treeId);
+
+ // Need to executeSoon since the tree is initialized on sidebar load.
+ info("withSidebarTree: executing the task");
+ try {
+ await taskFn(tree);
+ } finally {
+ SidebarUI.hide();
+ }
+};
+
+/**
+ * Executes a task after opening the Library on a given root. Takes care
+ * of closing the library once done.
+ *
+ * @param {string} hierarchy
+ * The left pane hierarchy to open.
+ * @param {Function} taskFn
+ * The task to execute once the Library is ready.
+ * Will get { left, right } trees as argument.
+ */
+var withLibraryWindow = async function (hierarchy, taskFn) {
+ let library = await promiseLibrary(hierarchy);
+ let left = library.document.getElementById("placesList");
+ let right = library.document.getElementById("placeContent");
+ info("withLibrary: executing the task");
+ try {
+ await taskFn({ left, right });
+ } finally {
+ await promiseLibraryClosed(library);
+ }
+};
+
+function promisePlacesInitComplete() {
+ const gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"].getService(
+ Ci.nsIObserver
+ );
+
+ let placesInitCompleteObserved = TestUtils.topicObserved(
+ "places-browser-init-complete"
+ );
+
+ gBrowserGlue.observe(
+ null,
+ "browser-glue-test",
+ "places-browser-init-complete"
+ );
+
+ return placesInitCompleteObserved;
+}
+
+// Function copied from browser/base/content/test/general/head.js.
+function promisePopupShown(popup) {
+ return new Promise(resolve => {
+ if (popup.state == "open") {
+ resolve();
+ } else {
+ let onPopupShown = event => {
+ popup.removeEventListener("popupshown", onPopupShown);
+ resolve();
+ };
+ popup.addEventListener("popupshown", onPopupShown);
+ }
+ });
+}
+
+// Function copied from browser/base/content/test/general/head.js.
+function promisePopupHidden(popup) {
+ return new Promise(resolve => {
+ let onPopupHidden = event => {
+ popup.removeEventListener("popuphidden", onPopupHidden);
+ resolve();
+ };
+ popup.addEventListener("popuphidden", onPopupHidden);
+ });
+}
+
+// Identify a bookmark node in the Bookmarks Toolbar by its guid.
+function getToolbarNodeForItemGuid(itemGuid) {
+ let children = document.getElementById("PlacesToolbarItems").childNodes;
+ for (let child of children) {
+ if (itemGuid === child._placesNode.bookmarkGuid) {
+ return child;
+ }
+ }
+ return null;
+}
+
+// Open the bookmarks Star UI by clicking the star button on the address bar.
+async function clickBookmarkStar(win = window) {
+ let shownPromise = promisePopupShown(
+ win.document.getElementById("editBookmarkPanel")
+ );
+ win.BookmarkingUI.star.click();
+ await shownPromise;
+
+ // Additionally await for the async init to complete.
+ let menuList = win.document.getElementById("editBMPanel_folderMenuList");
+ await BrowserTestUtils.waitForMutationCondition(
+ menuList,
+ { attributes: true },
+ () => !!menuList.getAttribute("selectedGuid"),
+ "Should select the menu folder item"
+ );
+}
+
+// Close the bookmarks Star UI by clicking the "Done" button.
+async function hideBookmarksPanel(win = window) {
+ let hiddenPromise = promisePopupHidden(
+ win.document.getElementById("editBookmarkPanel")
+ );
+ // Confirm and close the dialog.
+ win.document.getElementById("editBookmarkPanelDoneButton").click();
+ await hiddenPromise;
+}
+
+// Create a temporary folder, set it as the default folder,
+// then remove the folder. This is used to ensure that the
+// default folder gets reset properly.
+async function createAndRemoveDefaultFolder() {
+ let tempFolder = await PlacesUtils.bookmarks.insertTree({
+ guid: PlacesUtils.bookmarks.unfiledGuid,
+ children: [
+ {
+ title: "temp folder",
+ type: PlacesUtils.bookmarks.TYPE_FOLDER,
+ },
+ ],
+ });
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.bookmarks.defaultLocation", tempFolder[0].guid]],
+ });
+
+ let defaultGUID = await PlacesUIUtils.defaultParentGuid;
+ is(defaultGUID, tempFolder[0].guid, "check default guid");
+
+ await PlacesUtils.bookmarks.remove(tempFolder);
+}
+
+registerCleanupFunction(async () => {
+ Services.prefs.clearUserPref("browser.bookmarks.defaultLocation");
+ await PlacesTransactions.clearTransactionsHistory(true, true);
+});