diff options
author | wolfbeast <mcwerewolf@gmail.com> | 2018-07-18 08:24:24 +0200 |
---|---|---|
committer | wolfbeast <mcwerewolf@gmail.com> | 2018-07-18 08:24:24 +0200 |
commit | fc61780b35af913801d72086456f493f63197da6 (patch) | |
tree | f85891288a7bd988da9f0f15ae64e5c63f00d493 /browser/components/places/content | |
parent | 69f7f9e5f1475891ce11cc4f431692f965b0cd30 (diff) | |
parent | 50d3e596bbe89c95615f96eb71f6bc5be737a1db (diff) | |
download | UXP-fc61780b35af913801d72086456f493f63197da6.tar UXP-fc61780b35af913801d72086456f493f63197da6.tar.gz UXP-fc61780b35af913801d72086456f493f63197da6.tar.lz UXP-fc61780b35af913801d72086456f493f63197da6.tar.xz UXP-fc61780b35af913801d72086456f493f63197da6.zip |
Merge commit '50d3e596bbe89c95615f96eb71f6bc5be737a1db' into Basilisk-releasev2018.07.18
# Conflicts:
# browser/app/profile/firefox.js
# browser/components/preferences/jar.mn
Diffstat (limited to 'browser/components/places/content')
22 files changed, 0 insertions, 11671 deletions
diff --git a/browser/components/places/content/bookmarkProperties.js b/browser/components/places/content/bookmarkProperties.js deleted file mode 100644 index afcf65736..000000000 --- a/browser/components/places/content/bookmarkProperties.js +++ /dev/null @@ -1,693 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -/** - * The panel is initialized based on data given in the js object passed - * as window.arguments[0]. The object must have the following fields set: - * @ action (String). Possible values: - * - "add" - for adding a new item. - * @ type (String). Possible values: - * - "bookmark" - * @ loadBookmarkInSidebar - optional, the default state for the - * "Load this bookmark in the sidebar" field. - * - "folder" - * @ URIList (Array of nsIURI objects) - optional, list of uris to - * be bookmarked under the new folder. - * - "livemark" - * @ uri (nsIURI object) - optional, the default uri for the new item. - * The property is not used for the "folder with items" type. - * @ title (String) - optional, the default title for the new item. - * @ description (String) - optional, the default description for the new - * item. - * @ defaultInsertionPoint (InsertionPoint JS object) - optional, the - * default insertion point for the new item. - * @ keyword (String) - optional, the default keyword for the new item. - * @ postData (String) - optional, POST data to accompany the keyword. - * @ charSet (String) - optional, character-set to accompany the keyword. - * Notes: - * 1) If |uri| is set for a bookmark/livemark item and |title| isn't, - * the dialog will query the history tables for the title associated - * with the given uri. If the dialog is set to adding a folder with - * bookmark items under it (see URIList), a default static title is - * used ("[Folder Name]"). - * 2) The index field of the default insertion point is ignored if - * the folder picker is shown. - * - "edit" - for editing a bookmark item or a folder. - * @ type (String). Possible values: - * - "bookmark" - * @ node (an nsINavHistoryResultNode object) - a node representing - * the bookmark. - * - "folder" (also applies to livemarks) - * @ node (an nsINavHistoryResultNode object) - a node representing - * the folder. - * @ hiddenRows (Strings array) - optional, list of rows to be hidden - * regardless of the item edited or added by the dialog. - * Possible values: - * - "title" - * - "location" - * - "description" - * - "keyword" - * - "tags" - * - "loadInSidebar" - * - "folderPicker" - hides both the tree and the menu. - * - * window.arguments[0].performed is set to true if any transaction has - * been performed by the dialog. - */ - -Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); -XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils", - "resource://gre/modules/PromiseUtils.jsm"); - -const BOOKMARK_ITEM = 0; -const BOOKMARK_FOLDER = 1; -const LIVEMARK_CONTAINER = 2; - -const ACTION_EDIT = 0; -const ACTION_ADD = 1; - -var elementsHeight = new Map(); - -var BookmarkPropertiesPanel = { - - /** UI Text Strings */ - __strings: null, - get _strings() { - if (!this.__strings) { - this.__strings = document.getElementById("stringBundle"); - } - return this.__strings; - }, - - _action: null, - _itemType: null, - _itemId: -1, - _uri: null, - _loadInSidebar: false, - _title: "", - _description: "", - _URIs: [], - _keyword: "", - _postData: null, - _charSet: "", - _feedURI: null, - _siteURI: null, - - _defaultInsertionPoint: null, - _hiddenRows: [], - _batching: false, - - /** - * This method returns the correct label for the dialog's "accept" - * button based on the variant of the dialog. - */ - _getAcceptLabel: function BPP__getAcceptLabel() { - if (this._action == ACTION_ADD) { - if (this._URIs.length) - return this._strings.getString("dialogAcceptLabelAddMulti"); - - if (this._itemType == LIVEMARK_CONTAINER) - return this._strings.getString("dialogAcceptLabelAddLivemark"); - - if (this._dummyItem || this._loadInSidebar) - return this._strings.getString("dialogAcceptLabelAddItem"); - - return this._strings.getString("dialogAcceptLabelSaveItem"); - } - return this._strings.getString("dialogAcceptLabelEdit"); - }, - - /** - * This method returns the correct title for the current variant - * of this dialog. - */ - _getDialogTitle: function BPP__getDialogTitle() { - if (this._action == ACTION_ADD) { - if (this._itemType == BOOKMARK_ITEM) - return this._strings.getString("dialogTitleAddBookmark"); - if (this._itemType == LIVEMARK_CONTAINER) - return this._strings.getString("dialogTitleAddLivemark"); - - // add folder - NS_ASSERT(this._itemType == BOOKMARK_FOLDER, "Unknown item type"); - if (this._URIs.length) - return this._strings.getString("dialogTitleAddMulti"); - - return this._strings.getString("dialogTitleAddFolder"); - } - if (this._action == ACTION_EDIT) { - return this._strings.getFormattedString("dialogTitleEdit", [this._title]); - } - return ""; - }, - - /** - * Determines the initial data for the item edited or added by this dialog - */ - _determineItemInfo() { - let dialogInfo = window.arguments[0]; - this._action = dialogInfo.action == "add" ? ACTION_ADD : ACTION_EDIT; - this._hiddenRows = dialogInfo.hiddenRows ? dialogInfo.hiddenRows : []; - if (this._action == ACTION_ADD) { - NS_ASSERT("type" in dialogInfo, "missing type property for add action"); - - if ("title" in dialogInfo) - this._title = dialogInfo.title; - - if ("defaultInsertionPoint" in dialogInfo) { - this._defaultInsertionPoint = dialogInfo.defaultInsertionPoint; - } - else { - this._defaultInsertionPoint = - new InsertionPoint(PlacesUtils.bookmarksMenuFolderId, - PlacesUtils.bookmarks.DEFAULT_INDEX, - Ci.nsITreeView.DROP_ON); - } - - switch (dialogInfo.type) { - case "bookmark": - this._itemType = BOOKMARK_ITEM; - if ("uri" in dialogInfo) { - NS_ASSERT(dialogInfo.uri instanceof Ci.nsIURI, - "uri property should be a uri object"); - this._uri = dialogInfo.uri; - if (typeof(this._title) != "string") { - this._title = this._getURITitleFromHistory(this._uri) || - this._uri.spec; - } - } - else { - this._uri = PlacesUtils._uri("about:blank"); - this._title = this._strings.getString("newBookmarkDefault"); - this._dummyItem = true; - } - - if ("loadBookmarkInSidebar" in dialogInfo) - this._loadInSidebar = dialogInfo.loadBookmarkInSidebar; - - if ("keyword" in dialogInfo) { - this._keyword = dialogInfo.keyword; - this._isAddKeywordDialog = true; - if ("postData" in dialogInfo) - this._postData = dialogInfo.postData; - if ("charSet" in dialogInfo) - this._charSet = dialogInfo.charSet; - } - break; - - case "folder": - this._itemType = BOOKMARK_FOLDER; - if (!this._title) { - if ("URIList" in dialogInfo) { - this._title = this._strings.getString("bookmarkAllTabsDefault"); - this._URIs = dialogInfo.URIList; - } - else - this._title = this._strings.getString("newFolderDefault"); - this._dummyItem = true; - } - break; - - case "livemark": - this._itemType = LIVEMARK_CONTAINER; - if ("feedURI" in dialogInfo) - this._feedURI = dialogInfo.feedURI; - if ("siteURI" in dialogInfo) - this._siteURI = dialogInfo.siteURI; - - if (!this._title) { - if (this._feedURI) { - this._title = this._getURITitleFromHistory(this._feedURI) || - this._feedURI.spec; - } - else - this._title = this._strings.getString("newLivemarkDefault"); - } - } - - if ("description" in dialogInfo) - this._description = dialogInfo.description; - } - else { // edit - this._node = dialogInfo.node; - this._title = this._node.title; - if (PlacesUtils.nodeIsFolder(this._node)) - this._itemType = BOOKMARK_FOLDER; - else if (PlacesUtils.nodeIsURI(this._node)) - this._itemType = BOOKMARK_ITEM; - } - }, - - /** - * This method returns the title string corresponding to a given URI. - * If none is available from the bookmark service (probably because - * the given URI doesn't appear in bookmarks or history), we synthesize - * a title from the first 100 characters of the URI. - * - * @param aURI - * nsIURI object for which we want the title - * - * @returns a title string - */ - _getURITitleFromHistory: function BPP__getURITitleFromHistory(aURI) { - NS_ASSERT(aURI instanceof Ci.nsIURI); - - // get the title from History - return PlacesUtils.history.getPageTitle(aURI); - }, - - /** - * This method should be called by the onload of the Bookmark Properties - * dialog to initialize the state of the panel. - */ - onDialogLoad: Task.async(function* () { - this._determineItemInfo(); - - document.title = this._getDialogTitle(); - var acceptButton = document.documentElement.getButton("accept"); - acceptButton.label = this._getAcceptLabel(); - - // Do not use sizeToContent, otherwise, due to bug 90276, the dialog will - // grow at every opening. - // Since elements can be uncollapsed asynchronously, we must observe their - // mutations and resize the dialog using a cached element size. - this._height = window.outerHeight; - this._mutationObserver = new MutationObserver(mutations => { - for (let mutation of mutations) { - let target = mutation.target; - let id = target.id; - if (!/^editBMPanel_.*(Row|Checkbox)$/.test(id)) - continue; - - let collapsed = target.getAttribute("collapsed") === "true"; - let wasCollapsed = mutation.oldValue === "true"; - if (collapsed == wasCollapsed) - continue; - - if (collapsed) { - this._height -= elementsHeight.get(id); - elementsHeight.delete(id); - } else { - elementsHeight.set(id, target.boxObject.height); - this._height += elementsHeight.get(id); - } - window.resizeTo(window.outerWidth, this._height); - } - }); - - this._mutationObserver.observe(document, - { subtree: true, - attributeOldValue: true, - attributeFilter: ["collapsed"] }); - - // Some controls are flexible and we want to update their cached size when - // the dialog is resized. - window.addEventListener("resize", this); - - this._beginBatch(); - - switch (this._action) { - case ACTION_EDIT: - gEditItemOverlay.initPanel({ node: this._node - , hiddenRows: this._hiddenRows - , focusedElement: "first" }); - acceptButton.disabled = gEditItemOverlay.readOnly; - break; - case ACTION_ADD: - this._node = yield this._promiseNewItem(); - // Edit the new item - gEditItemOverlay.initPanel({ node: this._node - , hiddenRows: this._hiddenRows - , postData: this._postData - , focusedElement: "first" }); - - // Empty location field if the uri is about:blank, this way inserting a new - // url will be easier for the user, Accept button will be automatically - // disabled by the input listener until the user fills the field. - let locationField = this._element("locationField"); - if (locationField.value == "about:blank") - locationField.value = ""; - - // if this is an uri related dialog disable accept button until - // the user fills an uri value. - if (this._itemType == BOOKMARK_ITEM) - acceptButton.disabled = !this._inputIsValid(); - break; - } - - if (!gEditItemOverlay.readOnly) { - // Listen on uri fields to enable accept button if input is valid - if (this._itemType == BOOKMARK_ITEM) { - this._element("locationField") - .addEventListener("input", this, false); - if (this._isAddKeywordDialog) { - this._element("keywordField") - .addEventListener("input", this, false); - } - } - } - }), - - // nsIDOMEventListener - handleEvent: function BPP_handleEvent(aEvent) { - var target = aEvent.target; - switch (aEvent.type) { - case "input": - if (target.id == "editBMPanel_locationField" || - target.id == "editBMPanel_keywordField") { - // Check uri fields to enable accept button if input is valid - document.documentElement - .getButton("accept").disabled = !this._inputIsValid(); - } - break; - case "resize": - for (let [id, oldHeight] of elementsHeight) { - let newHeight = document.getElementById(id).boxObject.height; - this._height += - oldHeight + newHeight; - elementsHeight.set(id, newHeight); - } - break; - } - }, - - // Hack for implementing batched-Undo around the editBookmarkOverlay - // instant-apply code. For all the details see the comment above beginBatch - // in browser-places.js - _batchBlockingDeferred: null, - _beginBatch() { - if (this._batching) - return; - if (PlacesUIUtils.useAsyncTransactions) { - this._batchBlockingDeferred = PromiseUtils.defer(); - PlacesTransactions.batch(function* () { - yield this._batchBlockingDeferred.promise; - }.bind(this)); - } - else { - PlacesUtils.transactionManager.beginBatch(null); - } - this._batching = true; - }, - - _endBatch() { - if (!this._batching) - return; - - if (PlacesUIUtils.useAsyncTransactions) { - this._batchBlockingDeferred.resolve(); - this._batchBlockingDeferred = null; - } - else { - PlacesUtils.transactionManager.endBatch(false); - } - this._batching = false; - }, - - // nsISupports - QueryInterface: function BPP_QueryInterface(aIID) { - if (aIID.equals(Ci.nsIDOMEventListener) || - aIID.equals(Ci.nsISupports)) - return this; - - throw Cr.NS_NOINTERFACE; - }, - - _element: function BPP__element(aID) { - return document.getElementById("editBMPanel_" + aID); - }, - - onDialogUnload() { - // gEditItemOverlay does not exist anymore here, so don't rely on it. - this._mutationObserver.disconnect(); - delete this._mutationObserver; - - window.removeEventListener("resize", this); - - // Calling removeEventListener with arguments which do not identify any - // currently registered EventListener on the EventTarget has no effect. - this._element("locationField") - .removeEventListener("input", this, false); - }, - - onDialogAccept() { - // We must blur current focused element to save its changes correctly - document.commandDispatcher.focusedElement.blur(); - // The order here is important! We have to uninit the panel first, otherwise - // late changes could force it to commit more transactions. - gEditItemOverlay.uninitPanel(true); - this._endBatch(); - window.arguments[0].performed = true; - }, - - onDialogCancel() { - // The order here is important! We have to uninit the panel first, otherwise - // changes done as part of Undo may change the panel contents and by - // that force it to commit more transactions. - gEditItemOverlay.uninitPanel(true); - this._endBatch(); - if (PlacesUIUtils.useAsyncTransactions) - PlacesTransactions.undo().catch(Components.utils.reportError); - else - PlacesUtils.transactionManager.undoTransaction(); - window.arguments[0].performed = false; - }, - - /** - * This method checks to see if the input fields are in a valid state. - * - * @returns true if the input is valid, false otherwise - */ - _inputIsValid: function BPP__inputIsValid() { - if (this._itemType == BOOKMARK_ITEM && - !this._containsValidURI("locationField")) - return false; - if (this._isAddKeywordDialog && !this._element("keywordField").value.length) - return false; - - return true; - }, - - /** - * Determines whether the XUL textbox with the given ID contains a - * string that can be converted into an nsIURI. - * - * @param aTextboxID - * the ID of the textbox element whose contents we'll test - * - * @returns true if the textbox contains a valid URI string, false otherwise - */ - _containsValidURI: function BPP__containsValidURI(aTextboxID) { - try { - var value = this._element(aTextboxID).value; - if (value) { - PlacesUIUtils.createFixedURI(value); - return true; - } - } catch (e) { } - return false; - }, - - /** - * [New Item Mode] Get the insertion point details for the new item, given - * dialog state and opening arguments. - * - * The container-identifier and insertion-index are returned separately in - * the form of [containerIdentifier, insertionIndex] - */ - _getInsertionPointDetails: function BPP__getInsertionPointDetails() { - var containerId = this._defaultInsertionPoint.itemId; - var indexInContainer = this._defaultInsertionPoint.index; - - return [containerId, indexInContainer]; - }, - - /** - * Returns a transaction for creating a new bookmark item representing the - * various fields and opening arguments of the dialog. - */ - _getCreateNewBookmarkTransaction: - function BPP__getCreateNewBookmarkTransaction(aContainer, aIndex) { - var annotations = []; - var childTransactions = []; - - if (this._description) { - let annoObj = { name : PlacesUIUtils.DESCRIPTION_ANNO, - type : Ci.nsIAnnotationService.TYPE_STRING, - flags : 0, - value : this._description, - expires: Ci.nsIAnnotationService.EXPIRE_NEVER }; - let editItemTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj); - childTransactions.push(editItemTxn); - } - - if (this._loadInSidebar) { - let annoObj = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO, - value : true }; - let setLoadTxn = new PlacesSetItemAnnotationTransaction(-1, annoObj); - childTransactions.push(setLoadTxn); - } - - // XXX TODO: this should be in a transaction! - if (this._charSet && !PrivateBrowsingUtils.isWindowPrivate(window)) - PlacesUtils.setCharsetForURI(this._uri, this._charSet); - - let createTxn = new PlacesCreateBookmarkTransaction(this._uri, - aContainer, - aIndex, - this._title, - this._keyword, - annotations, - childTransactions, - this._postData); - - return new PlacesAggregatedTransaction(this._getDialogTitle(), - [createTxn]); - }, - - /** - * Returns a childItems-transactions array representing the URIList with - * which the dialog has been opened. - */ - _getTransactionsForURIList: function BPP__getTransactionsForURIList() { - var transactions = []; - for (let uri of this._URIs) { - // uri should be an object in the form { url, title }. Though add-ons - // could still use the legacy form, where it's an nsIURI. - let [_uri, _title] = uri instanceof Ci.nsIURI ? - [uri, this._getURITitleFromHistory(uri)] : [uri.uri, uri.title]; - - let createTxn = - new PlacesCreateBookmarkTransaction(_uri, -1, - PlacesUtils.bookmarks.DEFAULT_INDEX, - _title); - transactions.push(createTxn); - } - return transactions; - }, - - /** - * Returns a transaction for creating a new folder item representing the - * various fields and opening arguments of the dialog. - */ - _getCreateNewFolderTransaction: - function BPP__getCreateNewFolderTransaction(aContainer, aIndex) { - var annotations = []; - var childItemsTransactions; - if (this._URIs.length) - childItemsTransactions = this._getTransactionsForURIList(); - - if (this._description) - annotations.push(this._getDescriptionAnnotation(this._description)); - - return new PlacesCreateFolderTransaction(this._title, aContainer, - aIndex, annotations, - childItemsTransactions); - }, - - _createNewItem: Task.async(function* () { - let [container, index] = this._getInsertionPointDetails(); - let txn; - switch (this._itemType) { - case BOOKMARK_FOLDER: - txn = this._getCreateNewFolderTransaction(container, index); - break; - case LIVEMARK_CONTAINER: - txn = new PlacesCreateLivemarkTransaction(this._feedURI, this._siteURI, - this._title, container, index); - break; - default: // BOOKMARK_ITEM - txn = this._getCreateNewBookmarkTransaction(container, index); - } - - PlacesUtils.transactionManager.doTransaction(txn); - // This is a temporary hack until we use PlacesTransactions.jsm - if (txn._promise) { - yield txn._promise; - } - - let folderGuid = yield PlacesUtils.promiseItemGuid(container); - let bm = yield PlacesUtils.bookmarks.fetch({ - parentGuid: folderGuid, - index: index - }); - this._itemId = yield PlacesUtils.promiseItemId(bm.guid); - - return Object.freeze({ - itemId: this._itemId, - bookmarkGuid: bm.guid, - title: this._title, - uri: this._uri ? this._uri.spec : "", - type: this._itemType == BOOKMARK_ITEM ? - Ci.nsINavHistoryResultNode.RESULT_TYPE_URI : - Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER - }); - }), - - _promiseNewItem: Task.async(function* () { - if (!PlacesUIUtils.useAsyncTransactions) - return this._createNewItem(); - - let [containerId, index] = this._getInsertionPointDetails(); - let parentGuid = yield PlacesUtils.promiseItemGuid(containerId); - let annotations = []; - if (this._description) { - annotations.push({ name: PlacesUIUtils.DESCRIPTION_ANNO - , value: this._description }); - } - if (this._loadInSidebar) { - annotations.push({ name: PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO - , value: true }); - } - - let itemGuid; - let info = { parentGuid, index, title: this._title, annotations }; - if (this._itemType == BOOKMARK_ITEM) { - info.url = this._uri; - if (this._keyword) - info.keyword = this._keyword; - if (this._postData) - info.postData = this._postData; - - if (this._charSet && !PrivateBrowsingUtils.isWindowPrivate(window)) - PlacesUtils.setCharsetForURI(this._uri, this._charSet); - - itemGuid = yield PlacesTransactions.NewBookmark(info).transact(); - } - else if (this._itemType == LIVEMARK_CONTAINER) { - info.feedUrl = this._feedURI; - if (this._siteURI) - info.siteUrl = this._siteURI; - - itemGuid = yield PlacesTransactions.NewLivemark(info).transact(); - } - else if (this._itemType == BOOKMARK_FOLDER) { - itemGuid = yield PlacesTransactions.NewFolder(info).transact(); - for (let uri of this._URIs) { - let placeInfo = yield PlacesUtils.promisePlaceInfo(uri); - let title = placeInfo ? placeInfo.title : ""; - yield PlacesTransactions.transact({ parentGuid: itemGuid, uri, title }); - } - } - else { - throw new Error(`unexpected value for _itemType: ${this._itemType}`); - } - - this._itemGuid = itemGuid; - this._itemId = yield PlacesUtils.promiseItemId(itemGuid); - return Object.freeze({ - itemId: this._itemId, - bookmarkGuid: this._itemGuid, - title: this._title, - uri: this._uri ? this._uri.spec : "", - type: this._itemType == BOOKMARK_ITEM ? - Ci.nsINavHistoryResultNode.RESULT_TYPE_URI : - Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER - }); - }) -}; diff --git a/browser/components/places/content/bookmarkProperties.xul b/browser/components/places/content/bookmarkProperties.xul deleted file mode 100644 index 2c04f8b05..000000000 --- a/browser/components/places/content/bookmarkProperties.xul +++ /dev/null @@ -1,43 +0,0 @@ -<?xml version="1.0"?> - -<!-- 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/. --> - -<?xml-stylesheet href="chrome://global/skin/"?> -<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?> -<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> -<?xml-stylesheet href="chrome://browser/content/places/places.css"?> - -<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> -<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?> - -<!DOCTYPE dialog [ - <!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd"> - %editBookmarkOverlayDTD; -]> - -<dialog id="bookmarkproperties" - buttons="accept, cancel" - buttoniconaccept="save" - ondialogaccept="BookmarkPropertiesPanel.onDialogAccept();" - ondialogcancel="BookmarkPropertiesPanel.onDialogCancel();" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - onload="BookmarkPropertiesPanel.onDialogLoad();" - onunload="BookmarkPropertiesPanel.onDialogUnload();" - style="min-width: 30em;" - persist="screenX screenY width"> - - <stringbundleset id="stringbundleset"> - <stringbundle id="stringBundle" - src="chrome://browser/locale/places/bookmarkProperties.properties"/> - </stringbundleset> - - <script type="application/javascript" - src="chrome://browser/content/places/editBookmarkOverlay.js"/> - <script type="application/javascript" - src="chrome://browser/content/places/bookmarkProperties.js"/> - -<vbox id="editBookmarkPanelContent"/> - -</dialog> diff --git a/browser/components/places/content/bookmarksPanel.js b/browser/components/places/content/bookmarksPanel.js deleted file mode 100644 index 871d69725..000000000 --- a/browser/components/places/content/bookmarksPanel.js +++ /dev/null @@ -1,24 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -function init() { - document.getElementById("bookmarks-view").place = - "place:queryType=1&folder=" + window.top.PlacesUIUtils.allBookmarksFolderId; -} - -function searchBookmarks(aSearchString) { - var tree = document.getElementById('bookmarks-view'); - if (!aSearchString) - tree.place = tree.place; - else - tree.applyFilter(aSearchString, - [PlacesUtils.bookmarksMenuFolderId, - PlacesUtils.unfiledBookmarksFolderId, - PlacesUtils.toolbarFolderId]); -} - -window.addEventListener("SidebarFocused", - () => document.getElementById("search-box").focus(), - false); diff --git a/browser/components/places/content/bookmarksPanel.xul b/browser/components/places/content/bookmarksPanel.xul deleted file mode 100644 index 332b9e7a9..000000000 --- a/browser/components/places/content/bookmarksPanel.xul +++ /dev/null @@ -1,54 +0,0 @@ -<?xml version="1.0"?> <!-- -*- Mode: SGML; indent-tabs-mode: nil; -*- --> -<!-- 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/. --> - -<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> -<?xml-stylesheet href="chrome://browser/content/places/places.css"?> -<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> -<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?> -<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> - -<!DOCTYPE page SYSTEM "chrome://browser/locale/places/places.dtd"> - -<page id="bookmarksPanel" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - onload="init();" - onunload="SidebarUtils.setMouseoverURL('');"> - - <script type="application/javascript" - src="chrome://browser/content/bookmarks/sidebarUtils.js"/> - <script type="application/javascript" - src="chrome://browser/content/bookmarks/bookmarksPanel.js"/> - - <commandset id="placesCommands"/> - <commandset id="editMenuCommands"/> - <menupopup id="placesContext"/> - - <!-- Bookmarks and history tooltip --> - <tooltip id="bhTooltip"/> - - <hbox id="sidebar-search-container" align="center"> - <label id="sidebar-search-label" - value="&search.label;" accesskey="&search.accesskey;" control="search-box"/> - <textbox id="search-box" flex="1" type="search" class="compact" - aria-controls="bookmarks-view" - oncommand="searchBookmarks(this.value);"/> - </hbox> - - <tree id="bookmarks-view" class="sidebar-placesTree" type="places" - flex="1" - hidecolumnpicker="true" - context="placesContext" - onkeypress="SidebarUtils.handleTreeKeyPress(event);" - onclick="SidebarUtils.handleTreeClick(this, event, true);" - onmousemove="SidebarUtils.handleTreeMouseMove(event);" - onmouseout="SidebarUtils.setMouseoverURL('');"> - <treecols> - <treecol id="title" flex="1" primary="true" hideheader="true"/> - </treecols> - <treechildren id="bookmarks-view-children" view="bookmarks-view" - class="sidebar-placesTreechildren" flex="1" tooltip="bhTooltip"/> - </tree> -</page> diff --git a/browser/components/places/content/browserPlacesViews.js b/browser/components/places/content/browserPlacesViews.js deleted file mode 100644 index c6ee9b6ce..000000000 --- a/browser/components/places/content/browserPlacesViews.js +++ /dev/null @@ -1,1996 +0,0 @@ -/* 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/. */ - -Components.utils.import("resource://gre/modules/AppConstants.jsm"); -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/Services.jsm"); - -/** - * The base view implements everything that's common to the toolbar and - * menu views. - */ -function PlacesViewBase(aPlace, aOptions) { - this.place = aPlace; - this.options = aOptions; - this._controller = new PlacesController(this); - this._viewElt.controllers.appendController(this._controller); -} - -PlacesViewBase.prototype = { - // The xul element that holds the entire view. - _viewElt: null, - get viewElt() { - return this._viewElt; - }, - - get associatedElement() { - return this._viewElt; - }, - - get controllers() { - return this._viewElt.controllers; - }, - - // The xul element that represents the root container. - _rootElt: null, - - // Set to true for views that are represented by native widgets (i.e. - // the native mac menu). - _nativeView: false, - - QueryInterface: XPCOMUtils.generateQI( - [Components.interfaces.nsINavHistoryResultObserver, - Components.interfaces.nsISupportsWeakReference]), - - _place: "", - get place() { - return this._place; - }, - set place(val) { - this._place = val; - - let history = PlacesUtils.history; - let queries = { }, options = { }; - history.queryStringToQueries(val, queries, { }, options); - if (!queries.value.length) - queries.value = [history.getNewQuery()]; - - let result = history.executeQueries(queries.value, queries.value.length, - options.value); - result.addObserver(this, false); - return val; - }, - - _result: null, - get result() { - return this._result; - }, - set result(val) { - if (this._result == val) - return val; - - if (this._result) { - this._result.removeObserver(this); - this._resultNode.containerOpen = false; - } - - if (this._rootElt.localName == "menupopup") - this._rootElt._built = false; - - this._result = val; - if (val) { - this._resultNode = val.root; - this._rootElt._placesNode = this._resultNode; - this._domNodes = new Map(); - this._domNodes.set(this._resultNode, this._rootElt); - - // This calls _rebuild through invalidateContainer. - this._resultNode.containerOpen = true; - } - else { - this._resultNode = null; - delete this._domNodes; - } - - return val; - }, - - _options: null, - get options() { - return this._options; - }, - set options(val) { - if (!val) - val = {}; - - if (!("extraClasses" in val)) - val.extraClasses = {}; - this._options = val; - - return val; - }, - - /** - * Gets the DOM node used for the given places node. - * - * @param aPlacesNode - * a places result node. - * @throws if there is no DOM node set for aPlacesNode. - */ - _getDOMNodeForPlacesNode: - function PVB__getDOMNodeForPlacesNode(aPlacesNode) { - let node = this._domNodes.get(aPlacesNode, null); - if (!node) { - throw new Error("No DOM node set for aPlacesNode.\nnode.type: " + - aPlacesNode.type + ". node.parent: " + aPlacesNode); - } - return node; - }, - - get controller() { - return this._controller; - }, - - get selType() { - return "single"; - }, - selectItems: function() { }, - selectAll: function() { }, - - get selectedNode() { - if (this._contextMenuShown) { - let anchor = this._contextMenuShown.triggerNode; - if (!anchor) - return null; - - if (anchor._placesNode) - return this._rootElt == anchor ? null : anchor._placesNode; - - anchor = anchor.parentNode; - return this._rootElt == anchor ? null : (anchor._placesNode || null); - } - return null; - }, - - get hasSelection() { - return this.selectedNode != null; - }, - - get selectedNodes() { - let selectedNode = this.selectedNode; - return selectedNode ? [selectedNode] : []; - }, - - get removableSelectionRanges() { - // On static content the current selectedNode would be the selection's - // parent node. We don't want to allow removing a node when the - // selection is not explicit. - if (document.popupNode && - (document.popupNode == "menupopup" || !document.popupNode._placesNode)) - return []; - - return [this.selectedNodes]; - }, - - get draggableSelection() { - return [this._draggedElt]; - }, - - get insertionPoint() { - // There is no insertion point for history queries, so bail out now and - // save a lot of work when updating commands. - let resultNode = this._resultNode; - if (PlacesUtils.nodeIsQuery(resultNode) && - PlacesUtils.asQuery(resultNode).queryOptions.queryType == - Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) - return null; - - // By default, the insertion point is at the top level, at the end. - let index = PlacesUtils.bookmarks.DEFAULT_INDEX; - let container = this._resultNode; - let orientation = Ci.nsITreeView.DROP_BEFORE; - let tagName = null; - - let selectedNode = this.selectedNode; - if (selectedNode) { - let popup = document.popupNode; - if (!popup._placesNode || popup._placesNode == this._resultNode || - popup._placesNode.itemId == -1 || !selectedNode.parent) { - // If a static menuitem is selected, or if the root node is selected, - // the insertion point is inside the folder, at the end. - container = selectedNode; - orientation = Ci.nsITreeView.DROP_ON; - } - else { - // In all other cases the insertion point is before that node. - container = selectedNode.parent; - index = container.getChildIndex(selectedNode); - if (PlacesUtils.nodeIsTagQuery(container)) { - tagName = container.title; - // TODO (Bug 1160193): properly support dropping on a tag root. - if (!tagName) - return null; - } - } - } - - if (PlacesControllerDragHelper.disallowInsertion(container)) - return null; - - return new InsertionPoint(PlacesUtils.getConcreteItemId(container), - index, orientation, tagName); - }, - - buildContextMenu: function PVB_buildContextMenu(aPopup) { - this._contextMenuShown = aPopup; - window.updateCommands("places"); - return this.controller.buildContextMenu(aPopup); - }, - - destroyContextMenu: function PVB_destroyContextMenu(aPopup) { - this._contextMenuShown = null; - }, - - _cleanPopup: function PVB_cleanPopup(aPopup, aDelay) { - // Remove Places nodes from the popup. - let child = aPopup._startMarker; - while (child.nextSibling != aPopup._endMarker) { - let sibling = child.nextSibling; - if (sibling._placesNode && !aDelay) { - aPopup.removeChild(sibling); - } - else if (sibling._placesNode && aDelay) { - // HACK (bug 733419): the popups originating from the OS X native - // menubar don't live-update while open, thus we don't clean it - // until the next popupshowing, to avoid zombie menuitems. - if (!aPopup._delayedRemovals) - aPopup._delayedRemovals = []; - aPopup._delayedRemovals.push(sibling); - child = child.nextSibling; - } - else { - child = child.nextSibling; - } - } - }, - - _rebuildPopup: function PVB__rebuildPopup(aPopup) { - let resultNode = aPopup._placesNode; - if (!resultNode.containerOpen) - return; - - if (this.controller.hasCachedLivemarkInfo(resultNode)) { - this._setEmptyPopupStatus(aPopup, false); - aPopup._built = true; - this._populateLivemarkPopup(aPopup); - return; - } - - this._cleanPopup(aPopup); - - let cc = resultNode.childCount; - if (cc > 0) { - this._setEmptyPopupStatus(aPopup, false); - - for (let i = 0; i < cc; ++i) { - let child = resultNode.getChild(i); - this._insertNewItemToPopup(child, aPopup, null); - } - } - else { - this._setEmptyPopupStatus(aPopup, true); - } - aPopup._built = true; - }, - - _removeChild: function PVB__removeChild(aChild) { - // If document.popupNode pointed to this child, null it out, - // otherwise controller's command-updating may rely on the removed - // item still being "selected". - if (document.popupNode == aChild) - document.popupNode = null; - - aChild.parentNode.removeChild(aChild); - }, - - _setEmptyPopupStatus: - function PVB__setEmptyPopupStatus(aPopup, aEmpty) { - if (!aPopup._emptyMenuitem) { - let label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder"); - aPopup._emptyMenuitem = document.createElement("menuitem"); - aPopup._emptyMenuitem.setAttribute("label", label); - aPopup._emptyMenuitem.setAttribute("disabled", true); - aPopup._emptyMenuitem.className = "bookmark-item"; - if (typeof this.options.extraClasses.entry == "string") - aPopup._emptyMenuitem.classList.add(this.options.extraClasses.entry); - } - - if (aEmpty) { - aPopup.setAttribute("emptyplacesresult", "true"); - // Don't add the menuitem if there is static content. - if (!aPopup._startMarker.previousSibling && - !aPopup._endMarker.nextSibling) - aPopup.insertBefore(aPopup._emptyMenuitem, aPopup._endMarker); - } - else { - aPopup.removeAttribute("emptyplacesresult"); - try { - aPopup.removeChild(aPopup._emptyMenuitem); - } catch (ex) {} - } - }, - - _createMenuItemForPlacesNode: - function PVB__createMenuItemForPlacesNode(aPlacesNode) { - this._domNodes.delete(aPlacesNode); - - let element; - let type = aPlacesNode.type; - if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) { - element = document.createElement("menuseparator"); - element.setAttribute("class", "small-separator"); - } - else { - let itemId = aPlacesNode.itemId; - if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI) { - element = document.createElement("menuitem"); - element.className = "menuitem-iconic bookmark-item menuitem-with-favicon"; - element.setAttribute("scheme", - PlacesUIUtils.guessUrlSchemeForUI(aPlacesNode.uri)); - } - else if (PlacesUtils.containerTypes.includes(type)) { - element = document.createElement("menu"); - element.setAttribute("container", "true"); - - if (aPlacesNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) { - element.setAttribute("query", "true"); - if (PlacesUtils.nodeIsTagQuery(aPlacesNode)) - element.setAttribute("tagContainer", "true"); - else if (PlacesUtils.nodeIsDay(aPlacesNode)) - element.setAttribute("dayContainer", "true"); - else if (PlacesUtils.nodeIsHost(aPlacesNode)) - element.setAttribute("hostContainer", "true"); - } - else if (itemId != -1) { - PlacesUtils.livemarks.getLivemark({ id: itemId }) - .then(aLivemark => { - element.setAttribute("livemark", "true"); - if (AppConstants.platform === "macosx") { - // OS X native menubar doesn't track list-style-images since - // it doesn't have a frame (bug 733415). Thus enforce updating. - element.setAttribute("image", ""); - element.removeAttribute("image"); - } - this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark); - }, () => undefined); - } - - let popup = document.createElement("menupopup"); - popup._placesNode = PlacesUtils.asContainer(aPlacesNode); - - if (!this._nativeView) { - popup.setAttribute("placespopup", "true"); - } - - element.appendChild(popup); - element.className = "menu-iconic bookmark-item"; - if (typeof this.options.extraClasses.entry == "string") { - element.classList.add(this.options.extraClasses.entry); - } - - this._domNodes.set(aPlacesNode, popup); - } - else - throw "Unexpected node"; - - element.setAttribute("label", PlacesUIUtils.getBestTitle(aPlacesNode)); - - let icon = aPlacesNode.icon; - if (icon) - element.setAttribute("image", icon); - } - - element._placesNode = aPlacesNode; - if (!this._domNodes.has(aPlacesNode)) - this._domNodes.set(aPlacesNode, element); - - return element; - }, - - _insertNewItemToPopup: - function PVB__insertNewItemToPopup(aNewChild, aPopup, aBefore) { - let element = this._createMenuItemForPlacesNode(aNewChild); - let before = aBefore || aPopup._endMarker; - - if (element.localName == "menuitem" || element.localName == "menu") { - if (typeof this.options.extraClasses.entry == "string") - element.classList.add(this.options.extraClasses.entry); - } - - aPopup.insertBefore(element, before); - return element; - }, - - _setLivemarkSiteURIMenuItem: - function PVB__setLivemarkSiteURIMenuItem(aPopup) { - let livemarkInfo = this.controller.getCachedLivemarkInfo(aPopup._placesNode); - let siteUrl = livemarkInfo && livemarkInfo.siteURI ? - livemarkInfo.siteURI.spec : null; - if (!siteUrl && aPopup._siteURIMenuitem) { - aPopup.removeChild(aPopup._siteURIMenuitem); - aPopup._siteURIMenuitem = null; - aPopup.removeChild(aPopup._siteURIMenuseparator); - aPopup._siteURIMenuseparator = null; - } - else if (siteUrl && !aPopup._siteURIMenuitem) { - // Add "Open (Feed Name)" menuitem. - aPopup._siteURIMenuitem = document.createElement("menuitem"); - aPopup._siteURIMenuitem.className = "openlivemarksite-menuitem"; - if (typeof this.options.extraClasses.entry == "string") { - aPopup._siteURIMenuitem.classList.add(this.options.extraClasses.entry); - } - aPopup._siteURIMenuitem.setAttribute("targetURI", siteUrl); - aPopup._siteURIMenuitem.setAttribute("oncommand", - "openUILink(this.getAttribute('targetURI'), event);"); - - // If a user middle-clicks this item we serve the oncommand event. - // We are using checkForMiddleClick because of Bug 246720. - // Note: stopPropagation is needed to avoid serving middle-click - // with BT_onClick that would open all items in tabs. - aPopup._siteURIMenuitem.setAttribute("onclick", - "checkForMiddleClick(this, event); event.stopPropagation();"); - let label = - PlacesUIUtils.getFormattedString("menuOpenLivemarkOrigin.label", - [aPopup.parentNode.getAttribute("label")]) - aPopup._siteURIMenuitem.setAttribute("label", label); - aPopup.insertBefore(aPopup._siteURIMenuitem, aPopup._startMarker); - - aPopup._siteURIMenuseparator = document.createElement("menuseparator"); - aPopup.insertBefore(aPopup._siteURIMenuseparator, aPopup._startMarker); - } - }, - - /** - * Add, update or remove the livemark status menuitem. - * @param aPopup - * The livemark container popup - * @param aStatus - * The livemark status - */ - _setLivemarkStatusMenuItem: - function PVB_setLivemarkStatusMenuItem(aPopup, aStatus) { - let statusMenuitem = aPopup._statusMenuitem; - if (!statusMenuitem) { - // Create the status menuitem and cache it in the popup object. - statusMenuitem = document.createElement("menuitem"); - statusMenuitem.className = "livemarkstatus-menuitem"; - if (typeof this.options.extraClasses.entry == "string") { - statusMenuitem.classList.add(this.options.extraClasses.entry); - } - statusMenuitem.setAttribute("disabled", true); - aPopup._statusMenuitem = statusMenuitem; - } - - if (aStatus == Ci.mozILivemark.STATUS_LOADING || - aStatus == Ci.mozILivemark.STATUS_FAILED) { - // Status has changed, update the cached status menuitem. - let stringId = aStatus == Ci.mozILivemark.STATUS_LOADING ? - "bookmarksLivemarkLoading" : "bookmarksLivemarkFailed"; - statusMenuitem.setAttribute("label", PlacesUIUtils.getString(stringId)); - if (aPopup._startMarker.nextSibling != statusMenuitem) - aPopup.insertBefore(statusMenuitem, aPopup._startMarker.nextSibling); - } - else if (aPopup._statusMenuitem.parentNode == aPopup) { - // The livemark has finished loading. - aPopup.removeChild(aPopup._statusMenuitem); - } - }, - - toggleCutNode: function PVB_toggleCutNode(aPlacesNode, aValue) { - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - - // We may get the popup for menus, but we need the menu itself. - if (elt.localName == "menupopup") - elt = elt.parentNode; - if (aValue) - elt.setAttribute("cutting", "true"); - else - elt.removeAttribute("cutting"); - }, - - nodeURIChanged: function PVB_nodeURIChanged(aPlacesNode, aURIString) { - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - - // Here we need the <menu>. - if (elt.localName == "menupopup") - elt = elt.parentNode; - - elt.setAttribute("scheme", PlacesUIUtils.guessUrlSchemeForUI(aURIString)); - }, - - nodeIconChanged: function PVB_nodeIconChanged(aPlacesNode) { - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - - // There's no UI representation for the root node, thus there's nothing to - // be done when the icon changes. - if (elt == this._rootElt) - return; - - // Here we need the <menu>. - if (elt.localName == "menupopup") - elt = elt.parentNode; - - let icon = aPlacesNode.icon; - if (!icon) - elt.removeAttribute("image"); - else if (icon != elt.getAttribute("image")) - elt.setAttribute("image", icon); - }, - - nodeAnnotationChanged: - function PVB_nodeAnnotationChanged(aPlacesNode, aAnno) { - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - - // All livemarks have a feedURI, so use it as our indicator of a livemark - // being modified. - if (aAnno == PlacesUtils.LMANNO_FEEDURI) { - let menu = elt.parentNode; - if (!menu.hasAttribute("livemark")) { - menu.setAttribute("livemark", "true"); - if (AppConstants.platform === "macosx") { - // OS X native menubar doesn't track list-style-images since - // it doesn't have a frame (bug 733415). Thus enforce updating. - menu.setAttribute("image", ""); - menu.removeAttribute("image"); - } - } - - PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId }) - .then(aLivemark => { - // Controller will use this to build the meta data for the node. - this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark); - this.invalidateContainer(aPlacesNode); - }, () => undefined); - } - }, - - nodeTitleChanged: - function PVB_nodeTitleChanged(aPlacesNode, aNewTitle) { - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - - // There's no UI representation for the root node, thus there's - // nothing to be done when the title changes. - if (elt == this._rootElt) - return; - - // Here we need the <menu>. - if (elt.localName == "menupopup") - elt = elt.parentNode; - - if (!aNewTitle && elt.localName != "toolbarbutton") { - // Many users consider toolbars as shortcuts containers, so explicitly - // allow empty labels on toolbarbuttons. For any other element try to be - // smarter, guessing a title from the uri. - elt.setAttribute("label", PlacesUIUtils.getBestTitle(aPlacesNode)); - } - else { - elt.setAttribute("label", aNewTitle); - } - }, - - nodeRemoved: - function PVB_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) { - let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode); - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - - // Here we need the <menu>. - if (elt.localName == "menupopup") - elt = elt.parentNode; - - if (parentElt._built) { - parentElt.removeChild(elt); - - // Figure out if we need to show the "<Empty>" menu-item. - // TODO Bug 517701: This doesn't seem to handle the case of an empty - // root. - if (parentElt._startMarker.nextSibling == parentElt._endMarker) - this._setEmptyPopupStatus(parentElt, true); - } - }, - - nodeHistoryDetailsChanged: - function PVB_nodeHistoryDetailsChanged(aPlacesNode, aTime, aCount) { - if (aPlacesNode.parent && - this.controller.hasCachedLivemarkInfo(aPlacesNode.parent)) { - // Find the node in the parent. - let popup = this._getDOMNodeForPlacesNode(aPlacesNode.parent); - for (let child = popup._startMarker.nextSibling; - child != popup._endMarker; - child = child.nextSibling) { - if (child._placesNode && child._placesNode.uri == aPlacesNode.uri) { - if (aCount) - child.setAttribute("visited", "true"); - else - child.removeAttribute("visited"); - break; - } - } - } - }, - - nodeTagsChanged: function() { }, - nodeDateAddedChanged: function() { }, - nodeLastModifiedChanged: function() { }, - nodeKeywordChanged: function() { }, - sortingChanged: function() { }, - batching: function() { }, - - nodeInserted: - function PVB_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) { - let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode); - if (!parentElt._built) - return; - - let index = Array.prototype.indexOf.call(parentElt.childNodes, parentElt._startMarker) + - aIndex + 1; - this._insertNewItemToPopup(aPlacesNode, parentElt, - parentElt.childNodes[index]); - this._setEmptyPopupStatus(parentElt, false); - }, - - nodeMoved: - function PBV_nodeMoved(aPlacesNode, - aOldParentPlacesNode, aOldIndex, - aNewParentPlacesNode, aNewIndex) { - // Note: the current implementation of moveItem does not actually - // use this notification when the item in question is moved from one - // folder to another. Instead, it calls nodeRemoved and nodeInserted - // for the two folders. Thus, we can assume old-parent == new-parent. - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - - // Here we need the <menu>. - if (elt.localName == "menupopup") - elt = elt.parentNode; - - // If our root node is a folder, it might be moved. There's nothing - // we need to do in that case. - if (elt == this._rootElt) - return; - - let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode); - if (parentElt._built) { - // Move the node. - parentElt.removeChild(elt); - let index = Array.prototype.indexOf.call(parentElt.childNodes, parentElt._startMarker) + - aNewIndex + 1; - parentElt.insertBefore(elt, parentElt.childNodes[index]); - } - }, - - containerStateChanged: - function PVB_containerStateChanged(aPlacesNode, aOldState, aNewState) { - if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED || - aNewState == Ci.nsINavHistoryContainerResultNode.STATE_CLOSED) { - this.invalidateContainer(aPlacesNode); - - if (PlacesUtils.nodeIsFolder(aPlacesNode)) { - let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions; - if (queryOptions.excludeItems) { - return; - } - - PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId }) - .then(aLivemark => { - let shouldInvalidate = - !this.controller.hasCachedLivemarkInfo(aPlacesNode); - this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark); - if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) { - aLivemark.registerForUpdates(aPlacesNode, this); - // Prioritize the current livemark. - aLivemark.reload(); - PlacesUtils.livemarks.reloadLivemarks(); - if (shouldInvalidate) - this.invalidateContainer(aPlacesNode); - } - else { - aLivemark.unregisterForUpdates(aPlacesNode); - } - }, () => undefined); - } - } - }, - - _populateLivemarkPopup: function PVB__populateLivemarkPopup(aPopup) - { - this._setLivemarkSiteURIMenuItem(aPopup); - // Show the loading status only if there are no entries yet. - if (aPopup._startMarker.nextSibling == aPopup._endMarker) - this._setLivemarkStatusMenuItem(aPopup, Ci.mozILivemark.STATUS_LOADING); - - PlacesUtils.livemarks.getLivemark({ id: aPopup._placesNode.itemId }) - .then(aLivemark => { - let placesNode = aPopup._placesNode; - if (!placesNode.containerOpen) - return; - - if (aLivemark.status != Ci.mozILivemark.STATUS_LOADING) - this._setLivemarkStatusMenuItem(aPopup, aLivemark.status); - this._cleanPopup(aPopup, - this._nativeView && aPopup.parentNode.hasAttribute("open")); - - let children = aLivemark.getNodesForContainer(placesNode); - for (let i = 0; i < children.length; i++) { - let child = children[i]; - this.nodeInserted(placesNode, child, i); - if (child.accessCount) - this._getDOMNodeForPlacesNode(child).setAttribute("visited", true); - else - this._getDOMNodeForPlacesNode(child).removeAttribute("visited"); - } - }, Components.utils.reportError); - }, - - invalidateContainer: function PVB_invalidateContainer(aPlacesNode) { - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - elt._built = false; - - // If the menupopup is open we should live-update it. - if (elt.parentNode.open) - this._rebuildPopup(elt); - }, - - uninit: function PVB_uninit() { - if (this._result) { - this._result.removeObserver(this); - this._resultNode.containerOpen = false; - this._resultNode = null; - this._result = null; - } - - if (this._controller) { - this._controller.terminate(); - // Removing the controller will fail if it is already no longer there. - // This can happen if the view element was removed/reinserted without - // our knowledge. There is no way to check for that having happened - // without the possibility of an exception. :-( - try { - this._viewElt.controllers.removeController(this._controller); - } catch (ex) { - } finally { - this._controller = null; - } - } - - delete this._viewElt._placesView; - }, - - get isRTL() { - if ("_isRTL" in this) - return this._isRTL; - - return this._isRTL = document.defaultView - .getComputedStyle(this.viewElt, "") - .direction == "rtl"; - }, - - get ownerWindow() { - return window; - }, - - /** - * Adds an "Open All in Tabs" menuitem to the bottom of the popup. - * @param aPopup - * a Places popup. - */ - _mayAddCommandsItems: function PVB__mayAddCommandsItems(aPopup) { - // The command items are never added to the root popup. - if (aPopup == this._rootElt) - return; - - let hasMultipleURIs = false; - - // Check if the popup contains at least 2 menuitems with places nodes. - // We don't currently support opening multiple uri nodes when they are not - // populated by the result. - if (aPopup._placesNode.childCount > 0) { - let currentChild = aPopup.firstChild; - let numURINodes = 0; - while (currentChild) { - if (currentChild.localName == "menuitem" && currentChild._placesNode) { - if (++numURINodes == 2) - break; - } - currentChild = currentChild.nextSibling; - } - hasMultipleURIs = numURINodes > 1; - } - - let isLiveMark = false; - if (this.controller.hasCachedLivemarkInfo(aPopup._placesNode)) { - hasMultipleURIs = true; - isLiveMark = true; - } - - if (!hasMultipleURIs) { - aPopup.setAttribute("singleitempopup", "true"); - } else { - aPopup.removeAttribute("singleitempopup"); - } - - if (!hasMultipleURIs) { - // We don't have to show any option. - if (aPopup._endOptOpenAllInTabs) { - aPopup.removeChild(aPopup._endOptOpenAllInTabs); - aPopup._endOptOpenAllInTabs = null; - - aPopup.removeChild(aPopup._endOptSeparator); - aPopup._endOptSeparator = null; - } - } - else if (!aPopup._endOptOpenAllInTabs) { - // Create a separator before options. - aPopup._endOptSeparator = document.createElement("menuseparator"); - aPopup._endOptSeparator.className = "bookmarks-actions-menuseparator"; - aPopup.appendChild(aPopup._endOptSeparator); - - // Add the "Open All in Tabs" menuitem. - aPopup._endOptOpenAllInTabs = document.createElement("menuitem"); - aPopup._endOptOpenAllInTabs.className = "openintabs-menuitem"; - - if (typeof this.options.extraClasses.entry == "string") - aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.entry); - if (typeof this.options.extraClasses.footer == "string") - aPopup._endOptOpenAllInTabs.classList.add(this.options.extraClasses.footer); - - if (isLiveMark) { - aPopup._endOptOpenAllInTabs.setAttribute("oncommand", - "PlacesUIUtils.openLiveMarkNodesInTabs(this.parentNode._placesNode, event, " + - "PlacesUIUtils.getViewForNode(this));"); - } else { - aPopup._endOptOpenAllInTabs.setAttribute("oncommand", - "PlacesUIUtils.openContainerNodeInTabs(this.parentNode._placesNode, event, " + - "PlacesUIUtils.getViewForNode(this));"); - } - aPopup._endOptOpenAllInTabs.setAttribute("onclick", - "checkForMiddleClick(this, event); event.stopPropagation();"); - aPopup._endOptOpenAllInTabs.setAttribute("label", - gNavigatorBundle.getString("menuOpenAllInTabs.label")); - aPopup.appendChild(aPopup._endOptOpenAllInTabs); - } - }, - - _ensureMarkers: function PVB__ensureMarkers(aPopup) { - if (aPopup._startMarker) - return; - - // _startMarker is an hidden menuseparator that lives before places nodes. - aPopup._startMarker = document.createElement("menuseparator"); - aPopup._startMarker.hidden = true; - aPopup.insertBefore(aPopup._startMarker, aPopup.firstChild); - - // _endMarker is a DOM node that lives after places nodes, specified with - // the 'insertionPoint' option or will be a hidden menuseparator. - let node = ("insertionPoint" in this.options) ? - aPopup.querySelector(this.options.insertionPoint) : null; - if (node) { - aPopup._endMarker = node; - } else { - aPopup._endMarker = document.createElement("menuseparator"); - aPopup._endMarker.hidden = true; - } - aPopup.appendChild(aPopup._endMarker); - - // Move the markers to the right position. - let firstNonStaticNodeFound = false; - for (let i = 0; i < aPopup.childNodes.length; i++) { - let child = aPopup.childNodes[i]; - // Menus that have static content at the end, but are initially empty, - // use a special "builder" attribute to figure out where to start - // inserting places nodes. - if (child.getAttribute("builder") == "end") { - aPopup.insertBefore(aPopup._endMarker, child); - break; - } - - if (child._placesNode && !child.hasAttribute("simulated-places-node") && - !firstNonStaticNodeFound) { - firstNonStaticNodeFound = true; - aPopup.insertBefore(aPopup._startMarker, child); - } - } - if (!firstNonStaticNodeFound) { - aPopup.insertBefore(aPopup._startMarker, aPopup._endMarker); - } - }, - - _onPopupShowing: function PVB__onPopupShowing(aEvent) { - // Avoid handling popupshowing of inner views. - let popup = aEvent.originalTarget; - - this._ensureMarkers(popup); - - // Remove any delayed element, see _cleanPopup for details. - if ("_delayedRemovals" in popup) { - while (popup._delayedRemovals.length > 0) { - popup.removeChild(popup._delayedRemovals.shift()); - } - } - - if (popup._placesNode && PlacesUIUtils.getViewForNode(popup) == this) { - if (!popup._placesNode.containerOpen) - popup._placesNode.containerOpen = true; - if (!popup._built) - this._rebuildPopup(popup); - - this._mayAddCommandsItems(popup); - } - }, - - _addEventListeners: - function PVB__addEventListeners(aObject, aEventNames, aCapturing) { - for (let i = 0; i < aEventNames.length; i++) { - aObject.addEventListener(aEventNames[i], this, aCapturing); - } - }, - - _removeEventListeners: - function PVB__removeEventListeners(aObject, aEventNames, aCapturing) { - for (let i = 0; i < aEventNames.length; i++) { - aObject.removeEventListener(aEventNames[i], this, aCapturing); - } - }, -}; - -function PlacesToolbar(aPlace) { - let startTime = Date.now(); - // Add some smart getters for our elements. - let thisView = this; - [ - ["_viewElt", "PlacesToolbar"], - ["_rootElt", "PlacesToolbarItems"], - ["_dropIndicator", "PlacesToolbarDropIndicator"], - ["_chevron", "PlacesChevron"], - ["_chevronPopup", "PlacesChevronPopup"] - ].forEach(function (elementGlobal) { - let [name, id] = elementGlobal; - thisView.__defineGetter__(name, function () { - let element = document.getElementById(id); - if (!element) - return null; - - delete thisView[name]; - return thisView[name] = element; - }); - }); - - this._viewElt._placesView = this; - - this._addEventListeners(this._viewElt, this._cbEvents, false); - this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true); - this._addEventListeners(this._rootElt, ["overflow", "underflow"], true); - this._addEventListeners(window, ["resize", "unload"], false); - - // If personal-bookmarks has been dragged to the tabs toolbar, - // we have to track addition and removals of tabs, to properly - // recalculate the available space for bookmarks. - // TODO (bug 734730): Use a performant mutation listener when available. - if (this._viewElt.parentNode.parentNode == document.getElementById("TabsToolbar")) { - this._addEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false); - } - - PlacesViewBase.call(this, aPlace); - - Services.telemetry.getHistogramById("FX_BOOKMARKS_TOOLBAR_INIT_MS") - .add(Date.now() - startTime); -} - -PlacesToolbar.prototype = { - __proto__: PlacesViewBase.prototype, - - _cbEvents: ["dragstart", "dragover", "dragexit", "dragend", "drop", - "mousemove", "mouseover", "mouseout"], - - QueryInterface: function PT_QueryInterface(aIID) { - if (aIID.equals(Ci.nsIDOMEventListener) || - aIID.equals(Ci.nsITimerCallback)) - return this; - - return PlacesViewBase.prototype.QueryInterface.apply(this, arguments); - }, - - uninit: function PT_uninit() { - this._removeEventListeners(this._viewElt, this._cbEvents, false); - this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"], - true); - this._removeEventListeners(this._rootElt, ["overflow", "underflow"], true); - this._removeEventListeners(window, ["resize", "unload"], false); - this._removeEventListeners(gBrowser.tabContainer, ["TabOpen", "TabClose"], false); - - if (this._chevron._placesView) { - this._chevron._placesView.uninit(); - } - - PlacesViewBase.prototype.uninit.apply(this, arguments); - }, - - _openedMenuButton: null, - _allowPopupShowing: true, - - _rebuild: function PT__rebuild() { - // Clear out references to existing nodes, since they will be removed - // and re-added. - if (this._overFolder.elt) - this._clearOverFolder(); - - this._openedMenuButton = null; - while (this._rootElt.hasChildNodes()) { - this._rootElt.removeChild(this._rootElt.firstChild); - } - - let cc = this._resultNode.childCount; - for (let i = 0; i < cc; ++i) { - this._insertNewItem(this._resultNode.getChild(i), null); - } - - if (this._chevronPopup.hasAttribute("type")) { - // Chevron has already been initialized, but since we are forcing - // a rebuild of the toolbar, it has to be rebuilt. - // Otherwise, it will be initialized when the toolbar overflows. - this._chevronPopup.place = this.place; - } - }, - - _insertNewItem: - function PT__insertNewItem(aChild, aBefore) { - this._domNodes.delete(aChild); - - let type = aChild.type; - let button; - if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) { - button = document.createElement("toolbarseparator"); - } - else { - button = document.createElement("toolbarbutton"); - button.className = "bookmark-item"; - button.setAttribute("label", aChild.title || ""); - let icon = aChild.icon; - if (icon) - button.setAttribute("image", icon); - - if (PlacesUtils.containerTypes.includes(type)) { - button.setAttribute("type", "menu"); - button.setAttribute("container", "true"); - - if (PlacesUtils.nodeIsQuery(aChild)) { - button.setAttribute("query", "true"); - if (PlacesUtils.nodeIsTagQuery(aChild)) - button.setAttribute("tagContainer", "true"); - } - else if (PlacesUtils.nodeIsFolder(aChild)) { - PlacesUtils.livemarks.getLivemark({ id: aChild.itemId }) - .then(aLivemark => { - button.setAttribute("livemark", "true"); - this.controller.cacheLivemarkInfo(aChild, aLivemark); - }, () => undefined); - } - - let popup = document.createElement("menupopup"); - popup.setAttribute("placespopup", "true"); - button.appendChild(popup); - popup._placesNode = PlacesUtils.asContainer(aChild); - popup.setAttribute("context", "placesContext"); - - this._domNodes.set(aChild, popup); - } - else if (PlacesUtils.nodeIsURI(aChild)) { - button.setAttribute("scheme", - PlacesUIUtils.guessUrlSchemeForUI(aChild.uri)); - } - } - - button._placesNode = aChild; - if (!this._domNodes.has(aChild)) - this._domNodes.set(aChild, button); - - if (aBefore) - this._rootElt.insertBefore(button, aBefore); - else - this._rootElt.appendChild(button); - }, - - _updateChevronPopupNodesVisibility: - function PT__updateChevronPopupNodesVisibility() { - for (let i = 0, node = this._chevronPopup._startMarker.nextSibling; - node != this._chevronPopup._endMarker; - i++, node = node.nextSibling) { - node.hidden = this._rootElt.childNodes[i].style.visibility != "hidden"; - } - }, - - _onChevronPopupShowing: - function PT__onChevronPopupShowing(aEvent) { - // Handle popupshowing only for the chevron popup, not for nested ones. - if (aEvent.target != this._chevronPopup) - return; - - if (!this._chevron._placesView) - this._chevron._placesView = new PlacesMenu(aEvent, this.place); - - this._updateChevronPopupNodesVisibility(); - }, - - handleEvent: function PT_handleEvent(aEvent) { - switch (aEvent.type) { - case "unload": - this.uninit(); - break; - case "resize": - // This handler updates nodes visibility in both the toolbar - // and the chevron popup when a window resize does not change - // the overflow status of the toolbar. - this.updateChevron(); - break; - case "overflow": - if (!this._isOverflowStateEventRelevant(aEvent)) - return; - this._onOverflow(); - break; - case "underflow": - if (!this._isOverflowStateEventRelevant(aEvent)) - return; - this._onUnderflow(); - break; - case "TabOpen": - case "TabClose": - this.updateChevron(); - break; - case "dragstart": - this._onDragStart(aEvent); - break; - case "dragover": - this._onDragOver(aEvent); - break; - case "dragexit": - this._onDragExit(aEvent); - break; - case "dragend": - this._onDragEnd(aEvent); - break; - case "drop": - this._onDrop(aEvent); - break; - case "mouseover": - this._onMouseOver(aEvent); - break; - case "mousemove": - this._onMouseMove(aEvent); - break; - case "mouseout": - this._onMouseOut(aEvent); - break; - case "popupshowing": - this._onPopupShowing(aEvent); - break; - case "popuphidden": - this._onPopupHidden(aEvent); - break; - default: - throw "Trying to handle unexpected event."; - } - }, - - updateOverflowStatus: function() { - if (this._rootElt.scrollLeftMin != this._rootElt.scrollLeftMax) { - this._onOverflow(); - } else { - this._onUnderflow(); - } - }, - - _isOverflowStateEventRelevant: function PT_isOverflowStateEventRelevant(aEvent) { - // Ignore events not aimed at ourselves, as well as purely vertical ones: - return aEvent.target == aEvent.currentTarget && aEvent.detail > 0; - }, - - _onOverflow: function PT_onOverflow() { - // Attach the popup binding to the chevron popup if it has not yet - // been initialized. - if (!this._chevronPopup.hasAttribute("type")) { - this._chevronPopup.setAttribute("place", this.place); - this._chevronPopup.setAttribute("type", "places"); - } - this._chevron.collapsed = false; - this.updateChevron(); - }, - - _onUnderflow: function PT_onUnderflow() { - this.updateChevron(); - this._chevron.collapsed = true; - }, - - updateChevron: function PT_updateChevron() { - // If the chevron is collapsed there's nothing to update. - if (this._chevron.collapsed) - return; - - // Update the chevron on a timer. This will avoid repeated work when - // lot of changes happen in a small timeframe. - if (this._updateChevronTimer) - this._updateChevronTimer.cancel(); - - this._updateChevronTimer = this._setTimer(100); - }, - - _updateChevronTimerCallback: function PT__updateChevronTimerCallback() { - let scrollRect = this._rootElt.getBoundingClientRect(); - let childOverflowed = false; - for (let i = 0; i < this._rootElt.childNodes.length; i++) { - let child = this._rootElt.childNodes[i]; - // Once a child overflows, all the next ones will. - if (!childOverflowed) { - let childRect = child.getBoundingClientRect(); - childOverflowed = this.isRTL ? (childRect.left < scrollRect.left) - : (childRect.right > scrollRect.right); - - } - child.style.visibility = childOverflowed ? "hidden" : "visible"; - } - - // We rebuild the chevron on popupShowing, so if it is open - // we must update it. - if (this._chevron.open) - this._updateChevronPopupNodesVisibility(); - }, - - nodeInserted: - function PT_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) { - let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode); - if (parentElt == this._rootElt) { - let children = this._rootElt.childNodes; - this._insertNewItem(aPlacesNode, - aIndex < children.length ? children[aIndex] : null); - this.updateChevron(); - return; - } - - PlacesViewBase.prototype.nodeInserted.apply(this, arguments); - }, - - nodeRemoved: - function PT_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) { - let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode); - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - - // Here we need the <menu>. - if (elt.localName == "menupopup") - elt = elt.parentNode; - - if (parentElt == this._rootElt) { - this._removeChild(elt); - this.updateChevron(); - return; - } - - PlacesViewBase.prototype.nodeRemoved.apply(this, arguments); - }, - - nodeMoved: - function PT_nodeMoved(aPlacesNode, - aOldParentPlacesNode, aOldIndex, - aNewParentPlacesNode, aNewIndex) { - let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode); - if (parentElt == this._rootElt) { - // Container is on the toolbar. - - // Move the element. - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - - // Here we need the <menu>. - if (elt.localName == "menupopup") - elt = elt.parentNode; - - this._removeChild(elt); - this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]); - - // The chevron view may get nodeMoved after the toolbar. In such a case, - // we should ensure (by manually swapping menuitems) that the actual nodes - // are in the final position before updateChevron tries to updates their - // visibility, or the chevron may go out of sync. - // Luckily updateChevron runs on a timer, so, by the time it updates - // nodes, the menu has already handled the notification. - - this.updateChevron(); - return; - } - - PlacesViewBase.prototype.nodeMoved.apply(this, arguments); - }, - - nodeAnnotationChanged: - function PT_nodeAnnotationChanged(aPlacesNode, aAnno) { - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - if (elt == this._rootElt) - return; - - // We're notified for the menupopup, not the containing toolbarbutton. - if (elt.localName == "menupopup") - elt = elt.parentNode; - - if (elt.parentNode == this._rootElt) { - // Node is on the toolbar. - - // All livemarks have a feedURI, so use it as our indicator. - if (aAnno == PlacesUtils.LMANNO_FEEDURI) { - elt.setAttribute("livemark", true); - - PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId }) - .then(aLivemark => { - this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark); - this.invalidateContainer(aPlacesNode); - }, Components.utils.reportError); - } - } - else { - // Node is in a submenu. - PlacesViewBase.prototype.nodeAnnotationChanged.apply(this, arguments); - } - }, - - nodeTitleChanged: function PT_nodeTitleChanged(aPlacesNode, aNewTitle) { - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - - // There's no UI representation for the root node, thus there's - // nothing to be done when the title changes. - if (elt == this._rootElt) - return; - - PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments); - - // Here we need the <menu>. - if (elt.localName == "menupopup") - elt = elt.parentNode; - - if (elt.parentNode == this._rootElt) { - // Node is on the toolbar - this.updateChevron(); - } - }, - - invalidateContainer: function PT_invalidateContainer(aPlacesNode) { - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - if (elt == this._rootElt) { - // Container is the toolbar itself. - this._rebuild(); - return; - } - - PlacesViewBase.prototype.invalidateContainer.apply(this, arguments); - }, - - _overFolder: { elt: null, - openTimer: null, - hoverTime: 350, - closeTimer: null }, - - _clearOverFolder: function PT__clearOverFolder() { - // The mouse is no longer dragging over the stored menubutton. - // Close the menubutton, clear out drag styles, and clear all - // timers for opening/closing it. - if (this._overFolder.elt && this._overFolder.elt.lastChild) { - if (!this._overFolder.elt.lastChild.hasAttribute("dragover")) { - this._overFolder.elt.lastChild.hidePopup(); - } - this._overFolder.elt.removeAttribute("dragover"); - this._overFolder.elt = null; - } - if (this._overFolder.openTimer) { - this._overFolder.openTimer.cancel(); - this._overFolder.openTimer = null; - } - if (this._overFolder.closeTimer) { - this._overFolder.closeTimer.cancel(); - this._overFolder.closeTimer = null; - } - }, - - /** - * This function returns information about where to drop when dragging over - * the toolbar. The returned object has the following properties: - * - ip: the insertion point for the bookmarks service. - * - beforeIndex: child index to drop before, for the drop indicator. - * - folderElt: the folder to drop into, if applicable. - */ - _getDropPoint: function PT__getDropPoint(aEvent) { - if (!PlacesUtils.nodeIsFolder(this._resultNode)) - return null; - - let dropPoint = { ip: null, beforeIndex: null, folderElt: null }; - let elt = aEvent.target; - if (elt._placesNode && elt != this._rootElt && - elt.localName != "menupopup") { - let eltRect = elt.getBoundingClientRect(); - let eltIndex = Array.prototype.indexOf.call(this._rootElt.childNodes, elt); - if (PlacesUtils.nodeIsFolder(elt._placesNode) && - !PlacesUIUtils.isContentsReadOnly(elt._placesNode)) { - // This is a folder. - // If we are in the middle of it, drop inside it. - // Otherwise, drop before it, with regards to RTL mode. - let threshold = eltRect.width * 0.25; - if (this.isRTL ? (aEvent.clientX > eltRect.right - threshold) - : (aEvent.clientX < eltRect.left + threshold)) { - // Drop before this folder. - dropPoint.ip = - new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode), - eltIndex, Ci.nsITreeView.DROP_BEFORE); - dropPoint.beforeIndex = eltIndex; - } - else if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold) - : (aEvent.clientX < eltRect.right - threshold)) { - // Drop inside this folder. - let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ? - elt._placesNode.title : null; - dropPoint.ip = - new InsertionPoint(PlacesUtils.getConcreteItemId(elt._placesNode), - -1, Ci.nsITreeView.DROP_ON, - tagName); - dropPoint.beforeIndex = eltIndex; - dropPoint.folderElt = elt; - } - else { - // Drop after this folder. - let beforeIndex = - (eltIndex == this._rootElt.childNodes.length - 1) ? - -1 : eltIndex + 1; - - dropPoint.ip = - new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode), - beforeIndex, Ci.nsITreeView.DROP_BEFORE); - dropPoint.beforeIndex = beforeIndex; - } - } - else { - // This is a non-folder node or a read-only folder. - // Drop before it with regards to RTL mode. - let threshold = eltRect.width * 0.5; - if (this.isRTL ? (aEvent.clientX > eltRect.left + threshold) - : (aEvent.clientX < eltRect.left + threshold)) { - // Drop before this bookmark. - dropPoint.ip = - new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode), - eltIndex, Ci.nsITreeView.DROP_BEFORE); - dropPoint.beforeIndex = eltIndex; - } - else { - // Drop after this bookmark. - let beforeIndex = - eltIndex == this._rootElt.childNodes.length - 1 ? - -1 : eltIndex + 1; - dropPoint.ip = - new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode), - beforeIndex, Ci.nsITreeView.DROP_BEFORE); - dropPoint.beforeIndex = beforeIndex; - } - } - } - else { - // We are most likely dragging on the empty area of the - // toolbar, we should drop after the last node. - dropPoint.ip = - new InsertionPoint(PlacesUtils.getConcreteItemId(this._resultNode), - -1, Ci.nsITreeView.DROP_BEFORE); - dropPoint.beforeIndex = -1; - } - - return dropPoint; - }, - - _setTimer: function PT_setTimer(aTime) { - let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT); - return timer; - }, - - notify: function PT_notify(aTimer) { - if (aTimer == this._updateChevronTimer) { - this._updateChevronTimer = null; - this._updateChevronTimerCallback(); - } - - // * Timer to turn off indicator bar. - else if (aTimer == this._ibTimer) { - this._dropIndicator.collapsed = true; - this._ibTimer = null; - } - - // * Timer to open a menubutton that's being dragged over. - else if (aTimer == this._overFolder.openTimer) { - // Set the autoopen attribute on the folder's menupopup so that - // the menu will automatically close when the mouse drags off of it. - this._overFolder.elt.lastChild.setAttribute("autoopened", "true"); - this._overFolder.elt.open = true; - this._overFolder.openTimer = null; - } - - // * Timer to close a menubutton that's been dragged off of. - else if (aTimer == this._overFolder.closeTimer) { - // Close the menubutton if we are not dragging over it or one of - // its children. The autoopened attribute will let the menu know to - // close later if the menu is still being dragged over. - let currentPlacesNode = PlacesControllerDragHelper.currentDropTarget; - let inHierarchy = false; - while (currentPlacesNode) { - if (currentPlacesNode == this._rootElt) { - inHierarchy = true; - break; - } - currentPlacesNode = currentPlacesNode.parentNode; - } - // The _clearOverFolder() function will close the menu for - // _overFolder.elt. So null it out if we don't want to close it. - if (inHierarchy) - this._overFolder.elt = null; - - // Clear out the folder and all associated timers. - this._clearOverFolder(); - } - }, - - _onMouseOver: function PT__onMouseOver(aEvent) { - let button = aEvent.target; - if (button.parentNode == this._rootElt && button._placesNode && - PlacesUtils.nodeIsURI(button._placesNode)) - window.XULBrowserWindow.setOverLink(aEvent.target._placesNode.uri, null); - }, - - _onMouseOut: function PT__onMouseOut(aEvent) { - window.XULBrowserWindow.setOverLink("", null); - }, - - _cleanupDragDetails: function PT__cleanupDragDetails() { - // Called on dragend and drop. - PlacesControllerDragHelper.currentDropTarget = null; - this._draggedElt = null; - if (this._ibTimer) - this._ibTimer.cancel(); - - this._dropIndicator.collapsed = true; - }, - - _onDragStart: function PT__onDragStart(aEvent) { - // Sub menus have their own d&d handlers. - let draggedElt = aEvent.target; - if (draggedElt.parentNode != this._rootElt || !draggedElt._placesNode) - return; - - if (draggedElt.localName == "toolbarbutton" && - draggedElt.getAttribute("type") == "menu") { - // If the drag gesture on a container is toward down we open instead - // of dragging. - let translateY = this._cachedMouseMoveEvent.clientY - aEvent.clientY; - let translateX = this._cachedMouseMoveEvent.clientX - aEvent.clientX; - if ((translateY) >= Math.abs(translateX/2)) { - // Don't start the drag. - aEvent.preventDefault(); - // Open the menu. - draggedElt.open = true; - return; - } - - // If the menu is open, close it. - if (draggedElt.open) { - draggedElt.lastChild.hidePopup(); - draggedElt.open = false; - } - } - - // Activate the view and cache the dragged element. - this._draggedElt = draggedElt._placesNode; - this._rootElt.focus(); - - this._controller.setDataTransfer(aEvent); - aEvent.stopPropagation(); - }, - - _onDragOver: function PT__onDragOver(aEvent) { - // Cache the dataTransfer - PlacesControllerDragHelper.currentDropTarget = aEvent.target; - let dt = aEvent.dataTransfer; - - let dropPoint = this._getDropPoint(aEvent); - if (!dropPoint || !dropPoint.ip || - !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) { - this._dropIndicator.collapsed = true; - aEvent.stopPropagation(); - return; - } - - if (this._ibTimer) { - this._ibTimer.cancel(); - this._ibTimer = null; - } - - if (dropPoint.folderElt || aEvent.originalTarget == this._chevron) { - // Dropping over a menubutton or chevron button. - // Set styles and timer to open relative menupopup. - let overElt = dropPoint.folderElt || this._chevron; - if (this._overFolder.elt != overElt) { - this._clearOverFolder(); - this._overFolder.elt = overElt; - this._overFolder.openTimer = this._setTimer(this._overFolder.hoverTime); - } - if (!this._overFolder.elt.hasAttribute("dragover")) - this._overFolder.elt.setAttribute("dragover", "true"); - - this._dropIndicator.collapsed = true; - } - else { - // Dragging over a normal toolbarbutton, - // show indicator bar and move it to the appropriate drop point. - let ind = this._dropIndicator; - ind.parentNode.collapsed = false; - let halfInd = ind.clientWidth / 2; - let translateX; - if (this.isRTL) { - halfInd = Math.ceil(halfInd); - translateX = 0 - this._rootElt.getBoundingClientRect().right - halfInd; - if (this._rootElt.firstChild) { - if (dropPoint.beforeIndex == -1) - translateX += this._rootElt.lastChild.getBoundingClientRect().left; - else { - translateX += this._rootElt.childNodes[dropPoint.beforeIndex] - .getBoundingClientRect().right; - } - } - } - else { - halfInd = Math.floor(halfInd); - translateX = 0 - this._rootElt.getBoundingClientRect().left + - halfInd; - if (this._rootElt.firstChild) { - if (dropPoint.beforeIndex == -1) - translateX += this._rootElt.lastChild.getBoundingClientRect().right; - else { - translateX += this._rootElt.childNodes[dropPoint.beforeIndex] - .getBoundingClientRect().left; - } - } - } - - ind.style.transform = "translate(" + Math.round(translateX) + "px)"; - ind.style.marginInlineStart = (-ind.clientWidth) + "px"; - ind.collapsed = false; - - // Clear out old folder information. - this._clearOverFolder(); - } - - aEvent.preventDefault(); - aEvent.stopPropagation(); - }, - - _onDrop: function PT__onDrop(aEvent) { - PlacesControllerDragHelper.currentDropTarget = aEvent.target; - - let dropPoint = this._getDropPoint(aEvent); - if (dropPoint && dropPoint.ip) { - PlacesControllerDragHelper.onDrop(dropPoint.ip, aEvent.dataTransfer) - .then(null, Components.utils.reportError); - aEvent.preventDefault(); - } - - this._cleanupDragDetails(); - aEvent.stopPropagation(); - }, - - _onDragExit: function PT__onDragExit(aEvent) { - PlacesControllerDragHelper.currentDropTarget = null; - - // Set timer to turn off indicator bar (if we turn it off - // here, dragenter might be called immediately after, creating - // flicker). - if (this._ibTimer) - this._ibTimer.cancel(); - this._ibTimer = this._setTimer(10); - - // If we hovered over a folder, close it now. - if (this._overFolder.elt) - this._overFolder.closeTimer = this._setTimer(this._overFolder.hoverTime); - }, - - _onDragEnd: function PT_onDragEnd(aEvent) { - this._cleanupDragDetails(); - }, - - _onPopupShowing: function PT__onPopupShowing(aEvent) { - if (!this._allowPopupShowing) { - this._allowPopupShowing = true; - aEvent.preventDefault(); - return; - } - - let parent = aEvent.target.parentNode; - if (parent.localName == "toolbarbutton") - this._openedMenuButton = parent; - - PlacesViewBase.prototype._onPopupShowing.apply(this, arguments); - }, - - _onPopupHidden: function PT__onPopupHidden(aEvent) { - let popup = aEvent.target; - let placesNode = popup._placesNode; - // Avoid handling popuphidden of inner views - if (placesNode && PlacesUIUtils.getViewForNode(popup) == this) { - // UI performance: folder queries are cheap, keep the resultnode open - // so we don't rebuild its contents whenever the popup is reopened. - // Though, we want to always close feed containers so their expiration - // status will be checked at next opening. - if (!PlacesUtils.nodeIsFolder(placesNode) || - this.controller.hasCachedLivemarkInfo(placesNode)) { - placesNode.containerOpen = false; - } - } - - let parent = popup.parentNode; - if (parent.localName == "toolbarbutton") { - this._openedMenuButton = null; - // Clear the dragover attribute if present, if we are dragging into a - // folder in the hierachy of current opened popup we don't clear - // this attribute on clearOverFolder. See Notify for closeTimer. - if (parent.hasAttribute("dragover")) - parent.removeAttribute("dragover"); - } - }, - - _onMouseMove: function PT__onMouseMove(aEvent) { - // Used in dragStart to prevent dragging folders when dragging down. - this._cachedMouseMoveEvent = aEvent; - - if (this._openedMenuButton == null || - PlacesControllerDragHelper.getSession()) - return; - - let target = aEvent.originalTarget; - if (this._openedMenuButton != target && - target.localName == "toolbarbutton" && - target.type == "menu") { - this._openedMenuButton.open = false; - target.open = true; - } - } -}; - -/** - * View for Places menus. This object should be created during the first - * popupshowing that's dispatched on the menu. - */ -function PlacesMenu(aPopupShowingEvent, aPlace, aOptions) { - this._rootElt = aPopupShowingEvent.target; // <menupopup> - this._viewElt = this._rootElt.parentNode; // <menu> - this._viewElt._placesView = this; - this._addEventListeners(this._rootElt, ["popupshowing", "popuphidden"], true); - this._addEventListeners(window, ["unload"], false); - - if (AppConstants.platform === "macosx") { - // Must walk up to support views in sub-menus, like Bookmarks Toolbar menu. - for (let elt = this._viewElt.parentNode; elt; elt = elt.parentNode) { - if (elt.localName == "menubar") { - this._nativeView = true; - break; - } - } - } - - PlacesViewBase.call(this, aPlace, aOptions); - this._onPopupShowing(aPopupShowingEvent); -} - -PlacesMenu.prototype = { - __proto__: PlacesViewBase.prototype, - - QueryInterface: function PM_QueryInterface(aIID) { - if (aIID.equals(Ci.nsIDOMEventListener)) - return this; - - return PlacesViewBase.prototype.QueryInterface.apply(this, arguments); - }, - - _removeChild: function PM_removeChild(aChild) { - PlacesViewBase.prototype._removeChild.apply(this, arguments); - }, - - uninit: function PM_uninit() { - this._removeEventListeners(this._rootElt, ["popupshowing", "popuphidden"], - true); - this._removeEventListeners(window, ["unload"], false); - - PlacesViewBase.prototype.uninit.apply(this, arguments); - }, - - handleEvent: function PM_handleEvent(aEvent) { - switch (aEvent.type) { - case "unload": - this.uninit(); - break; - case "popupshowing": - this._onPopupShowing(aEvent); - break; - case "popuphidden": - this._onPopupHidden(aEvent); - break; - } - }, - - _onPopupHidden: function PM__onPopupHidden(aEvent) { - // Avoid handling popuphidden of inner views. - let popup = aEvent.originalTarget; - let placesNode = popup._placesNode; - if (!placesNode || PlacesUIUtils.getViewForNode(popup) != this) - return; - - // UI performance: folder queries are cheap, keep the resultnode open - // so we don't rebuild its contents whenever the popup is reopened. - // Though, we want to always close feed containers so their expiration - // status will be checked at next opening. - if (!PlacesUtils.nodeIsFolder(placesNode) || - this.controller.hasCachedLivemarkInfo(placesNode)) - placesNode.containerOpen = false; - - // The autoopened attribute is set for folders which have been - // automatically opened when dragged over. Turn off this attribute - // when the folder closes because it is no longer applicable. - popup.removeAttribute("autoopened"); - popup.removeAttribute("dragstart"); - } -}; - -function PlacesPanelMenuView(aPlace, aViewId, aRootId, aOptions) { - this._viewElt = document.getElementById(aViewId); - this._rootElt = document.getElementById(aRootId); - this._viewElt._placesView = this; - this.options = aOptions; - - PlacesViewBase.call(this, aPlace, aOptions); -} - -PlacesPanelMenuView.prototype = { - __proto__: PlacesViewBase.prototype, - - QueryInterface: function PAMV_QueryInterface(aIID) { - return PlacesViewBase.prototype.QueryInterface.apply(this, arguments); - }, - - uninit: function PAMV_uninit() { - PlacesViewBase.prototype.uninit.apply(this, arguments); - }, - - _insertNewItem: - function PAMV__insertNewItem(aChild, aBefore) { - this._domNodes.delete(aChild); - - let type = aChild.type; - let button; - if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) { - button = document.createElement("toolbarseparator"); - button.setAttribute("class", "small-separator"); - } - else { - button = document.createElement("toolbarbutton"); - button.className = "bookmark-item"; - if (typeof this.options.extraClasses.entry == "string") - button.classList.add(this.options.extraClasses.entry); - button.setAttribute("label", aChild.title || ""); - let icon = aChild.icon; - if (icon) - button.setAttribute("image", icon); - - if (PlacesUtils.containerTypes.includes(type)) { - button.setAttribute("container", "true"); - - if (PlacesUtils.nodeIsQuery(aChild)) { - button.setAttribute("query", "true"); - if (PlacesUtils.nodeIsTagQuery(aChild)) - button.setAttribute("tagContainer", "true"); - } - else if (PlacesUtils.nodeIsFolder(aChild)) { - PlacesUtils.livemarks.getLivemark({ id: aChild.itemId }) - .then(aLivemark => { - button.setAttribute("livemark", "true"); - this.controller.cacheLivemarkInfo(aChild, aLivemark); - }, () => undefined); - } - } - else if (PlacesUtils.nodeIsURI(aChild)) { - button.setAttribute("scheme", - PlacesUIUtils.guessUrlSchemeForUI(aChild.uri)); - } - } - - button._placesNode = aChild; - if (!this._domNodes.has(aChild)) - this._domNodes.set(aChild, button); - - this._rootElt.insertBefore(button, aBefore); - }, - - nodeInserted: - function PAMV_nodeInserted(aParentPlacesNode, aPlacesNode, aIndex) { - let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode); - if (parentElt != this._rootElt) - return; - - let children = this._rootElt.childNodes; - this._insertNewItem(aPlacesNode, - aIndex < children.length ? children[aIndex] : null); - }, - - nodeRemoved: - function PAMV_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) { - let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode); - if (parentElt != this._rootElt) - return; - - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - this._removeChild(elt); - }, - - nodeMoved: - function PAMV_nodeMoved(aPlacesNode, - aOldParentPlacesNode, aOldIndex, - aNewParentPlacesNode, aNewIndex) { - let parentElt = this._getDOMNodeForPlacesNode(aNewParentPlacesNode); - if (parentElt != this._rootElt) - return; - - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - this._removeChild(elt); - this._rootElt.insertBefore(elt, this._rootElt.childNodes[aNewIndex]); - }, - - nodeAnnotationChanged: - function PAMV_nodeAnnotationChanged(aPlacesNode, aAnno) { - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - // There's no UI representation for the root node. - if (elt == this._rootElt) - return; - - if (elt.parentNode != this._rootElt) - return; - - // All livemarks have a feedURI, so use it as our indicator. - if (aAnno == PlacesUtils.LMANNO_FEEDURI) { - elt.setAttribute("livemark", true); - - PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId }) - .then(aLivemark => { - this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark); - this.invalidateContainer(aPlacesNode); - }, Components.utils.reportError); - } - }, - - nodeTitleChanged: function PAMV_nodeTitleChanged(aPlacesNode, aNewTitle) { - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - - // There's no UI representation for the root node. - if (elt == this._rootElt) - return; - - PlacesViewBase.prototype.nodeTitleChanged.apply(this, arguments); - }, - - invalidateContainer: function PAMV_invalidateContainer(aPlacesNode) { - let elt = this._getDOMNodeForPlacesNode(aPlacesNode); - if (elt != this._rootElt) - return; - - // Container is the toolbar itself. - while (this._rootElt.hasChildNodes()) { - this._rootElt.removeChild(this._rootElt.firstChild); - } - - for (let i = 0; i < this._resultNode.childCount; ++i) { - this._insertNewItem(this._resultNode.getChild(i), null); - } - } -}; diff --git a/browser/components/places/content/controller.js b/browser/components/places/content/controller.js deleted file mode 100644 index ebdab60f4..000000000 --- a/browser/components/places/content/controller.js +++ /dev/null @@ -1,1743 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -XPCOMUtils.defineLazyModuleGetter(this, "ForgetAboutSite", - "resource://gre/modules/ForgetAboutSite.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", - "resource://gre/modules/NetUtil.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", - "resource://gre/modules/PrivateBrowsingUtils.jsm"); - -// XXXmano: we should move most/all of these constants to PlacesUtils -const ORGANIZER_ROOT_BOOKMARKS = "place:folder=BOOKMARKS_MENU&excludeItems=1&queryType=1"; - -// No change to the view, preserve current selection -const RELOAD_ACTION_NOTHING = 0; -// Inserting items new to the view, select the inserted rows -const RELOAD_ACTION_INSERT = 1; -// Removing items from the view, select the first item after the last selected -const RELOAD_ACTION_REMOVE = 2; -// Moving items within a view, don't treat the dropped items as additional -// rows. -const RELOAD_ACTION_MOVE = 3; - -// When removing a bunch of pages we split them in chunks to give some breath -// to the main-thread. -const REMOVE_PAGES_CHUNKLEN = 300; - -/** - * Represents an insertion point within a container where we can insert - * items. - * @param aItemId - * The identifier of the parent container - * @param aIndex - * The index within the container where we should insert - * @param aOrientation - * The orientation of the insertion. NOTE: the adjustments to the - * insertion point to accommodate the orientation should be done by - * the person who constructs the IP, not the user. The orientation - * is provided for informational purposes only! - * @param [optional] aTag - * The tag name if this IP is set to a tag, null otherwise. - * @param [optional] aDropNearItemId - * When defined we will calculate index based on this itemId - * @constructor - */ -function InsertionPoint(aItemId, aIndex, aOrientation, aTagName = null, - aDropNearItemId = false) { - this.itemId = aItemId; - this._index = aIndex; - this.orientation = aOrientation; - this.tagName = aTagName; - this.dropNearItemId = aDropNearItemId; -} - -InsertionPoint.prototype = { - set index(val) { - return this._index = val; - }, - - promiseGuid: function () { - return PlacesUtils.promiseItemGuid(this.itemId); - }, - - get index() { - if (this.dropNearItemId > 0) { - // If dropNearItemId is set up we must calculate the real index of - // the item near which we will drop. - var index = PlacesUtils.bookmarks.getItemIndex(this.dropNearItemId); - return this.orientation == Ci.nsITreeView.DROP_BEFORE ? index : index + 1; - } - return this._index; - }, - - get isTag() { - return typeof(this.tagName) == "string"; - } -}; - -/** - * Places Controller - */ - -function PlacesController(aView) { - this._view = aView; - XPCOMUtils.defineLazyServiceGetter(this, "clipboard", - "@mozilla.org/widget/clipboard;1", - "nsIClipboard"); - XPCOMUtils.defineLazyGetter(this, "profileName", function () { - return Services.dirsvc.get("ProfD", Ci.nsIFile).leafName; - }); - - this._cachedLivemarkInfoObjects = new Map(); -} - -PlacesController.prototype = { - /** - * The places view. - */ - _view: null, - - QueryInterface: XPCOMUtils.generateQI([ - Ci.nsIClipboardOwner - ]), - - // nsIClipboardOwner - LosingOwnership: function PC_LosingOwnership (aXferable) { - this.cutNodes = []; - }, - - terminate: function PC_terminate() { - this._releaseClipboardOwnership(); - }, - - supportsCommand: function PC_supportsCommand(aCommand) { - // Non-Places specific commands that we also support - switch (aCommand) { - case "cmd_undo": - case "cmd_redo": - case "cmd_cut": - case "cmd_copy": - case "cmd_paste": - case "cmd_delete": - case "cmd_selectAll": - return true; - } - - // All other Places Commands are prefixed with "placesCmd_" ... this - // filters out other commands that we do _not_ support (see 329587). - const CMD_PREFIX = "placesCmd_"; - return (aCommand.substr(0, CMD_PREFIX.length) == CMD_PREFIX); - }, - - isCommandEnabled: function PC_isCommandEnabled(aCommand) { - switch (aCommand) { - case "cmd_undo": - if (!PlacesUIUtils.useAsyncTransactions) - return PlacesUtils.transactionManager.numberOfUndoItems > 0; - - return PlacesTransactions.topUndoEntry != null; - case "cmd_redo": - if (!PlacesUIUtils.useAsyncTransactions) - return PlacesUtils.transactionManager.numberOfRedoItems > 0; - - return PlacesTransactions.topRedoEntry != null; - case "cmd_cut": - case "placesCmd_cut": - case "placesCmd_moveBookmarks": - for (let node of this._view.selectedNodes) { - // If selection includes history nodes or tags-as-bookmark, disallow - // cutting. - if (node.itemId == -1 || - (node.parent && PlacesUtils.nodeIsTagQuery(node.parent))) { - return false; - } - } - // Otherwise fall through the cmd_delete check. - case "cmd_delete": - case "placesCmd_delete": - case "placesCmd_deleteDataHost": - return this._hasRemovableSelection(); - case "cmd_copy": - case "placesCmd_copy": - return this._view.hasSelection; - case "cmd_paste": - case "placesCmd_paste": - return this._canInsert(true) && this._isClipboardDataPasteable(); - case "cmd_selectAll": - if (this._view.selType != "single") { - let rootNode = this._view.result.root; - if (rootNode.containerOpen && rootNode.childCount > 0) - return true; - } - return false; - case "placesCmd_open": - case "placesCmd_open:window": - case "placesCmd_open:privatewindow": - case "placesCmd_open:tab": - var selectedNode = this._view.selectedNode; - return selectedNode && PlacesUtils.nodeIsURI(selectedNode); - case "placesCmd_new:folder": - return this._canInsert(); - case "placesCmd_new:bookmark": - return this._canInsert(); - case "placesCmd_new:separator": - return this._canInsert() && - !PlacesUtils.asQuery(this._view.result.root).queryOptions.excludeItems && - this._view.result.sortingMode == - Ci.nsINavHistoryQueryOptions.SORT_BY_NONE; - case "placesCmd_show:info": { - let selectedNode = this._view.selectedNode; - return selectedNode && PlacesUtils.getConcreteItemId(selectedNode) != -1 - } - case "placesCmd_reload": { - // Livemark containers - let selectedNode = this._view.selectedNode; - return selectedNode && this.hasCachedLivemarkInfo(selectedNode); - } - case "placesCmd_sortBy:name": { - let selectedNode = this._view.selectedNode; - return selectedNode && - PlacesUtils.nodeIsFolder(selectedNode) && - !PlacesUIUtils.isContentsReadOnly(selectedNode) && - this._view.result.sortingMode == - Ci.nsINavHistoryQueryOptions.SORT_BY_NONE; - } - case "placesCmd_createBookmark": - var node = this._view.selectedNode; - return node && PlacesUtils.nodeIsURI(node) && node.itemId == -1; - default: - return false; - } - }, - - doCommand: function PC_doCommand(aCommand) { - switch (aCommand) { - case "cmd_undo": - if (!PlacesUIUtils.useAsyncTransactions) { - PlacesUtils.transactionManager.undoTransaction(); - return; - } - PlacesTransactions.undo().then(null, Components.utils.reportError); - break; - case "cmd_redo": - if (!PlacesUIUtils.useAsyncTransactions) { - PlacesUtils.transactionManager.redoTransaction(); - return; - } - PlacesTransactions.redo().then(null, Components.utils.reportError); - break; - case "cmd_cut": - case "placesCmd_cut": - this.cut(); - break; - case "cmd_copy": - case "placesCmd_copy": - this.copy(); - break; - case "cmd_paste": - case "placesCmd_paste": - this.paste().then(null, Components.utils.reportError); - break; - case "cmd_delete": - case "placesCmd_delete": - this.remove("Remove Selection").then(null, Components.utils.reportError); - break; - case "placesCmd_deleteDataHost": - var host; - if (PlacesUtils.nodeIsHost(this._view.selectedNode)) { - var queries = this._view.selectedNode.getQueries(); - host = queries[0].domain; - } - else - host = NetUtil.newURI(this._view.selectedNode.uri).host; - ForgetAboutSite.removeDataFromDomain(host) - .catch(Components.utils.reportError); - break; - case "cmd_selectAll": - this.selectAll(); - break; - case "placesCmd_open": - PlacesUIUtils.openNodeIn(this._view.selectedNode, "current", this._view); - break; - case "placesCmd_open:window": - PlacesUIUtils.openNodeIn(this._view.selectedNode, "window", this._view); - break; - case "placesCmd_open:privatewindow": - PlacesUIUtils.openNodeIn(this._view.selectedNode, "window", this._view, true); - break; - case "placesCmd_open:tab": - PlacesUIUtils.openNodeIn(this._view.selectedNode, "tab", this._view); - break; - case "placesCmd_new:folder": - this.newItem("folder"); - break; - case "placesCmd_new:bookmark": - this.newItem("bookmark"); - break; - case "placesCmd_new:separator": - this.newSeparator().catch(Components.utils.reportError); - break; - case "placesCmd_show:info": - this.showBookmarkPropertiesForSelection(); - break; - case "placesCmd_moveBookmarks": - this.moveSelectedBookmarks(); - break; - case "placesCmd_reload": - this.reloadSelectedLivemark(); - break; - case "placesCmd_sortBy:name": - this.sortFolderByName().then(null, Components.utils.reportError); - break; - case "placesCmd_createBookmark": - let node = this._view.selectedNode; - PlacesUIUtils.showBookmarkDialog({ action: "add" - , type: "bookmark" - , hiddenRows: [ "description" - , "keyword" - , "location" - , "loadInSidebar" ] - , uri: NetUtil.newURI(node.uri) - , title: node.title - }, window.top); - break; - } - }, - - onEvent: function PC_onEvent(eventName) { }, - - - /** - * Determine whether or not the selection can be removed, either by the - * delete or cut operations based on whether or not any of its contents - * are non-removable. We don't need to worry about recursion here since it - * is a policy decision that a removable item not be placed inside a non- - * removable item. - * - * @return true if all nodes in the selection can be removed, - * false otherwise. - */ - _hasRemovableSelection() { - var ranges = this._view.removableSelectionRanges; - if (!ranges.length) - return false; - - var root = this._view.result.root; - - for (var j = 0; j < ranges.length; j++) { - var nodes = ranges[j]; - for (var i = 0; i < nodes.length; ++i) { - // Disallow removing the view's root node - if (nodes[i] == root) - return false; - - if (!PlacesUIUtils.canUserRemove(nodes[i])) - return false; - } - } - - return true; - }, - - /** - * Determines whether or not nodes can be inserted relative to the selection. - */ - _canInsert: function PC__canInsert(isPaste) { - var ip = this._view.insertionPoint; - return ip != null && (isPaste || ip.isTag != true); - }, - - /** - * Looks at the data on the clipboard to see if it is paste-able. - * Paste-able data is: - * - in a format that the view can receive - * @return true if: - clipboard data is of a TYPE_X_MOZ_PLACE_* flavor, - * - clipboard data is of type TEXT_UNICODE and - * is a valid URI. - */ - _isClipboardDataPasteable: function PC__isClipboardDataPasteable() { - // if the clipboard contains TYPE_X_MOZ_PLACE_* data, it is definitely - // pasteable, with no need to unwrap all the nodes. - - var flavors = PlacesUIUtils.PLACES_FLAVORS; - var clipboard = this.clipboard; - var hasPlacesData = - clipboard.hasDataMatchingFlavors(flavors, flavors.length, - Ci.nsIClipboard.kGlobalClipboard); - if (hasPlacesData) - return this._view.insertionPoint != null; - - // if the clipboard doesn't have TYPE_X_MOZ_PLACE_* data, we also allow - // pasting of valid "text/unicode" and "text/x-moz-url" data - var xferable = Cc["@mozilla.org/widget/transferable;1"]. - createInstance(Ci.nsITransferable); - xferable.init(null); - - xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_URL); - xferable.addDataFlavor(PlacesUtils.TYPE_UNICODE); - clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard); - - try { - // getAnyTransferData will throw if no data is available. - var data = { }, type = { }; - xferable.getAnyTransferData(type, data, { }); - data = data.value.QueryInterface(Ci.nsISupportsString).data; - if (type.value != PlacesUtils.TYPE_X_MOZ_URL && - type.value != PlacesUtils.TYPE_UNICODE) - return false; - - // unwrapNodes() will throw if the data blob is malformed. - PlacesUtils.unwrapNodes(data, type.value); - return this._view.insertionPoint != null; - } - catch (e) { - // getAnyTransferData or unwrapNodes failed - return false; - } - }, - - /** - * Gathers information about the selected nodes according to the following - * rules: - * "link" node is a URI - * "bookmark" node is a bookmark - * "livemarkChild" node is a child of a livemark - * "tagChild" node is a child of a tag - * "folder" node is a folder - * "query" node is a query - * "separator" node is a separator line - * "host" node is a host - * - * @return an array of objects corresponding the selected nodes. Each - * object has each of the properties above set if its corresponding - * node matches the rule. In addition, the annotations names for each - * node are set on its corresponding object as properties. - * Notes: - * 1) This can be slow, so don't call it anywhere performance critical! - */ - _buildSelectionMetadata: function PC__buildSelectionMetadata() { - var metadata = []; - var nodes = this._view.selectedNodes; - - for (var i = 0; i < nodes.length; i++) { - var nodeData = {}; - var node = nodes[i]; - var nodeType = node.type; - var uri = null; - - // We don't use the nodeIs* methods here to avoid going through the type - // property way too often - switch (nodeType) { - case Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY: - nodeData["query"] = true; - if (node.parent) { - switch (PlacesUtils.asQuery(node.parent).queryOptions.resultType) { - case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY: - nodeData["host"] = true; - break; - case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY: - case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY: - nodeData["day"] = true; - break; - } - } - break; - case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER: - case Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT: - nodeData["folder"] = true; - break; - case Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR: - nodeData["separator"] = true; - break; - case Ci.nsINavHistoryResultNode.RESULT_TYPE_URI: - nodeData["link"] = true; - uri = NetUtil.newURI(node.uri); - if (PlacesUtils.nodeIsBookmark(node)) { - nodeData["bookmark"] = true; - var parentNode = node.parent; - if (parentNode) { - if (PlacesUtils.nodeIsTagQuery(parentNode)) - nodeData["tagChild"] = true; - else if (this.hasCachedLivemarkInfo(parentNode)) - nodeData["livemarkChild"] = true; - } - } - break; - } - - // annotations - if (uri) { - let names = PlacesUtils.annotations.getPageAnnotationNames(uri); - for (let j = 0; j < names.length; ++j) - nodeData[names[j]] = true; - } - - // For items also include the item-specific annotations - if (node.itemId != -1) { - let names = PlacesUtils.annotations - .getItemAnnotationNames(node.itemId); - for (let j = 0; j < names.length; ++j) - nodeData[names[j]] = true; - } - metadata.push(nodeData); - } - - return metadata; - }, - - /** - * Determines if a context-menu item should be shown - * @param aMenuItem - * the context menu item - * @param aMetaData - * meta data about the selection - * @return true if the conditions (see buildContextMenu) are satisfied - * and the item can be displayed, false otherwise. - */ - _shouldShowMenuItem: function PC__shouldShowMenuItem(aMenuItem, aMetaData) { - var selectiontype = aMenuItem.getAttribute("selectiontype"); - if (!selectiontype) { - selectiontype = "single|multiple"; - } - var selectionTypes = selectiontype.split("|"); - if (selectionTypes.includes("any")) { - return true; - } - var count = aMetaData.length; - if (count > 1 && !selectionTypes.includes("multiple")) - return false; - if (count == 1 && !selectionTypes.includes("single")) - return false; - // NB: if there is no selection, we show the item if and only if - // the selectiontype includes 'none' - the metadata list will be - // empty so none of the other criteria will apply anyway. - if (count == 0) - return selectionTypes.includes("none"); - - var forceHideAttr = aMenuItem.getAttribute("forcehideselection"); - if (forceHideAttr) { - var forceHideRules = forceHideAttr.split("|"); - for (let i = 0; i < aMetaData.length; ++i) { - for (let j = 0; j < forceHideRules.length; ++j) { - if (forceHideRules[j] in aMetaData[i]) - return false; - } - } - } - - var selectionAttr = aMenuItem.getAttribute("selection"); - if (!selectionAttr) { - return !aMenuItem.hidden; - } - - if (selectionAttr == "any") - return true; - - var showRules = selectionAttr.split("|"); - var anyMatched = false; - function metaDataNodeMatches(metaDataNode, rules) { - for (var i = 0; i < rules.length; i++) { - if (rules[i] in metaDataNode) - return true; - } - return false; - } - - for (var i = 0; i < aMetaData.length; ++i) { - if (metaDataNodeMatches(aMetaData[i], showRules)) - anyMatched = true; - else - return false; - } - return anyMatched; - }, - - /** - * Detects information (meta-data rules) about the current selection in the - * view (see _buildSelectionMetadata) and sets the visibility state for each - * of the menu-items in the given popup with the following rules applied: - * 0) The "ignoreitem" attribute may be set to "true" for this code not to - * handle that menuitem. - * 1) The "selectiontype" attribute may be set on a menu-item to "single" - * if the menu-item should be visible only if there is a single node - * selected, or to "multiple" if the menu-item should be visible only if - * multiple nodes are selected, or to "none" if the menuitems should be - * visible for if there are no selected nodes, or to a |-separated - * combination of these. - * If the attribute is not set or set to an invalid value, the menu-item - * may be visible irrespective of the selection. - * 2) The "selection" attribute may be set on a menu-item to the various - * meta-data rules for which it may be visible. The rules should be - * separated with the | character. - * 3) A menu-item may be visible only if at least one of the rules set in - * its selection attribute apply to each of the selected nodes in the - * view. - * 4) The "forcehideselection" attribute may be set on a menu-item to rules - * for which it should be hidden. This attribute takes priority over the - * selection attribute. A menu-item would be hidden if at least one of the - * given rules apply to one of the selected nodes. The rules should be - * separated with the | character. - * 5) The "hideifnoinsertionpoint" attribute may be set on a menu-item to - * true if it should be hidden when there's no insertion point - * 6) The visibility state of a menu-item is unchanged if none of these - * attribute are set. - * 7) These attributes should not be set on separators for which the - * visibility state is "auto-detected." - * 8) The "hideifprivatebrowsing" attribute may be set on a menu-item to - * true if it should be hidden inside the private browsing mode - * @param aPopup - * The menupopup to build children into. - * @return true if at least one item is visible, false otherwise. - */ - buildContextMenu: function PC_buildContextMenu(aPopup) { - var metadata = this._buildSelectionMetadata(); - var ip = this._view.insertionPoint; - var noIp = !ip || ip.isTag; - - var separator = null; - var visibleItemsBeforeSep = false; - var usableItemCount = 0; - for (var i = 0; i < aPopup.childNodes.length; ++i) { - var item = aPopup.childNodes[i]; - if (item.getAttribute("ignoreitem") == "true") { - continue; - } - if (item.localName != "menuseparator") { - // We allow pasting into tag containers, so special case that. - var hideIfNoIP = item.getAttribute("hideifnoinsertionpoint") == "true" && - noIp && !(ip && ip.isTag && item.id == "placesContext_paste"); - var hideIfPrivate = item.getAttribute("hideifprivatebrowsing") == "true" && - PrivateBrowsingUtils.isWindowPrivate(window); - var shouldHideItem = hideIfNoIP || hideIfPrivate || - !this._shouldShowMenuItem(item, metadata); - item.hidden = item.disabled = shouldHideItem; - - if (!item.hidden) { - visibleItemsBeforeSep = true; - usableItemCount++; - - // Show the separator above the menu-item if any - if (separator) { - separator.hidden = false; - separator = null; - } - } - } - else { // menuseparator - // Initially hide it. It will be unhidden if there will be at least one - // visible menu-item above and below it. - item.hidden = true; - - // We won't show the separator at all if no items are visible above it - if (visibleItemsBeforeSep) - separator = item; - - // New separator, count again: - visibleItemsBeforeSep = false; - } - } - - // Set Open Folder/Links In Tabs items enabled state if they're visible - if (usableItemCount > 0) { - var openContainerInTabsItem = document.getElementById("placesContext_openContainer:tabs"); - if (!openContainerInTabsItem.hidden) { - var containerToUse = this._view.selectedNode || this._view.result.root; - if (PlacesUtils.nodeIsContainer(containerToUse)) { - if (!PlacesUtils.hasChildURIs(containerToUse)) { - openContainerInTabsItem.disabled = true; - // Ensure that we don't display the menu if nothing is enabled: - usableItemCount--; - } - } - } - } - - return usableItemCount > 0; - }, - - /** - * Select all links in the current view. - */ - selectAll: function PC_selectAll() { - this._view.selectAll(); - }, - - /** - * Opens the bookmark properties for the selected URI Node. - */ - showBookmarkPropertiesForSelection() { - let node = this._view.selectedNode; - if (!node) - return; - - PlacesUIUtils.showBookmarkDialog({ action: "edit" - , node - , hiddenRows: [ "folderPicker" ] - }, window.top); - }, - - /** - * This method can be run on a URI parameter to ensure that it didn't - * receive a string instead of an nsIURI object. - */ - _assertURINotString: function PC__assertURINotString(value) { - NS_ASSERT((typeof(value) == "object") && !(value instanceof String), - "This method should be passed a URI as a nsIURI object, not as a string."); - }, - - /** - * Reloads the selected livemark if any. - */ - reloadSelectedLivemark: function PC_reloadSelectedLivemark() { - var selectedNode = this._view.selectedNode; - if (selectedNode) { - let itemId = selectedNode.itemId; - PlacesUtils.livemarks.getLivemark({ id: itemId }) - .then(aLivemark => { - aLivemark.reload(true); - }, Components.utils.reportError); - } - }, - - /** - * Opens the links in the selected folder, or the selected links in new tabs. - */ - openSelectionInTabs: function PC_openLinksInTabs(aEvent) { - var node = this._view.selectedNode; - var nodes = this._view.selectedNodes; - // In the case of no selection, open the root node: - if (!node && !nodes.length) { - node = this._view.result.root; - } - if (node && PlacesUtils.nodeIsContainer(node)) - PlacesUIUtils.openContainerNodeInTabs(node, aEvent, this._view); - else - PlacesUIUtils.openURINodesInTabs(nodes, aEvent, this._view); - }, - - /** - * Shows the Add Bookmark UI for the current insertion point. - * - * @param aType - * the type of the new item (bookmark/livemark/folder) - */ - newItem: function PC_newItem(aType) { - let ip = this._view.insertionPoint; - if (!ip) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - let performed = - PlacesUIUtils.showBookmarkDialog({ action: "add" - , type: aType - , defaultInsertionPoint: ip - , hiddenRows: [ "folderPicker" ] - }, window.top); - if (performed) { - // Select the new item. - let insertedNodeId = PlacesUtils.bookmarks - .getIdForItemAt(ip.itemId, ip.index); - this._view.selectItems([insertedNodeId], false); - } - }, - - /** - * Create a new Bookmark separator somewhere. - */ - newSeparator: Task.async(function* () { - var ip = this._view.insertionPoint; - if (!ip) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - if (!PlacesUIUtils.useAsyncTransactions) { - let txn = new PlacesCreateSeparatorTransaction(ip.itemId, ip.index); - PlacesUtils.transactionManager.doTransaction(txn); - // Select the new item. - let insertedNodeId = PlacesUtils.bookmarks - .getIdForItemAt(ip.itemId, ip.index); - this._view.selectItems([insertedNodeId], false); - return; - } - - let txn = PlacesTransactions.NewSeparator({ parentGuid: yield ip.promiseGuid() - , index: ip.index }); - let guid = yield txn.transact(); - let itemId = yield PlacesUtils.promiseItemId(guid); - // Select the new item. - this._view.selectItems([itemId], false); - }), - - /** - * Opens a dialog for moving the selected nodes. - */ - moveSelectedBookmarks: function PC_moveBookmarks() { - window.openDialog("chrome://browser/content/places/moveBookmarks.xul", - "", "chrome, modal", - this._view.selectedNodes); - }, - - /** - * Sort the selected folder by name - */ - sortFolderByName: Task.async(function* () { - let itemId = PlacesUtils.getConcreteItemId(this._view.selectedNode); - if (!PlacesUIUtils.useAsyncTransactions) { - var txn = new PlacesSortFolderByNameTransaction(itemId); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - let guid = yield PlacesUtils.promiseItemGuid(itemId); - yield PlacesTransactions.SortByName(guid).transact(); - }), - - /** - * Walk the list of folders we're removing in this delete operation, and - * see if the selected node specified is already implicitly being removed - * because it is a child of that folder. - * @param node - * Node to check for containment. - * @param pastFolders - * List of folders the calling function has already traversed - * @return true if the node should be skipped, false otherwise. - */ - _shouldSkipNode: function PC_shouldSkipNode(node, pastFolders) { - /** - * Determines if a node is contained by another node within a resultset. - * @param node - * The node to check for containment for - * @param parent - * The parent container to check for containment in - * @return true if node is a member of parent's children, false otherwise. - */ - function isContainedBy(node, parent) { - var cursor = node.parent; - while (cursor) { - if (cursor == parent) - return true; - cursor = cursor.parent; - } - return false; - } - - for (var j = 0; j < pastFolders.length; ++j) { - if (isContainedBy(node, pastFolders[j])) - return true; - } - return false; - }, - - /** - * Creates a set of transactions for the removal of a range of items. - * A range is an array of adjacent nodes in a view. - * @param [in] range - * An array of nodes to remove. Should all be adjacent. - * @param [out] transactions - * An array of transactions. - * @param [optional] removedFolders - * An array of folder nodes that have already been removed. - */ - _removeRange: function PC__removeRange(range, transactions, removedFolders) { - NS_ASSERT(transactions instanceof Array, "Must pass a transactions array"); - if (!removedFolders) - removedFolders = []; - - for (var i = 0; i < range.length; ++i) { - var node = range[i]; - if (this._shouldSkipNode(node, removedFolders)) - continue; - - if (PlacesUtils.nodeIsTagQuery(node.parent)) { - // This is a uri node inside a tag container. It needs a special - // untag transaction. - var tagItemId = PlacesUtils.getConcreteItemId(node.parent); - var uri = NetUtil.newURI(node.uri); - if (PlacesUIUtils.useAsyncTransactions) { - let tag = node.parent.title; - if (!tag) - tag = PlacesUtils.bookmarks.getItemTitle(tagItemId); - transactions.push(PlacesTransactions.Untag({ uri: uri, tag: tag })); - } - else { - let txn = new PlacesUntagURITransaction(uri, [tagItemId]); - transactions.push(txn); - } - } - else if (PlacesUtils.nodeIsTagQuery(node) && node.parent && - PlacesUtils.nodeIsQuery(node.parent) && - PlacesUtils.asQuery(node.parent).queryOptions.resultType == - Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY) { - // This is a tag container. - // Untag all URIs tagged with this tag only if the tag container is - // child of the "Tags" query in the library, in all other places we - // must only remove the query node. - let tag = node.title; - let URIs = PlacesUtils.tagging.getURIsForTag(tag); - if (PlacesUIUtils.useAsyncTransactions) { - transactions.push(PlacesTransactions.Untag({ tag: tag, uris: URIs })); - } - else { - for (var j = 0; j < URIs.length; j++) { - let txn = new PlacesUntagURITransaction(URIs[j], [tag]); - transactions.push(txn); - } - } - } - else if (PlacesUtils.nodeIsURI(node) && - PlacesUtils.nodeIsQuery(node.parent) && - PlacesUtils.asQuery(node.parent).queryOptions.queryType == - Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) { - // This is a uri node inside an history query. - PlacesUtils.bhistory.removePage(NetUtil.newURI(node.uri)); - // History deletes are not undoable, so we don't have a transaction. - } - else if (node.itemId == -1 && - PlacesUtils.nodeIsQuery(node) && - PlacesUtils.asQuery(node).queryOptions.queryType == - Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) { - // This is a dynamically generated history query, like queries - // grouped by site, time or both. Dynamically generated queries don't - // have an itemId even if they are descendants of a bookmark. - this._removeHistoryContainer(node); - // History deletes are not undoable, so we don't have a transaction. - } - else { - // This is a common bookmark item. - if (PlacesUtils.nodeIsFolder(node)) { - // If this is a folder we add it to our array of folders, used - // to skip nodes that are children of an already removed folder. - removedFolders.push(node); - } - if (PlacesUIUtils.useAsyncTransactions) { - transactions.push( - PlacesTransactions.Remove({ guid: node.bookmarkGuid })); - } - else { - let txn = new PlacesRemoveItemTransaction(node.itemId); - transactions.push(txn); - } - } - } - }, - - /** - * Removes the set of selected ranges from bookmarks. - * @param txnName - * See |remove|. - */ - _removeRowsFromBookmarks: Task.async(function* (txnName) { - var ranges = this._view.removableSelectionRanges; - var transactions = []; - var removedFolders = []; - - for (var i = 0; i < ranges.length; i++) - this._removeRange(ranges[i], transactions, removedFolders); - - if (transactions.length > 0) { - if (PlacesUIUtils.useAsyncTransactions) { - yield PlacesTransactions.batch(transactions); - } - else { - var txn = new PlacesAggregatedTransaction(txnName, transactions); - PlacesUtils.transactionManager.doTransaction(txn); - } - } - }), - - /** - * Removes the set of selected ranges from history. - * - * @note history deletes are not undoable. - */ - _removeRowsFromHistory: function PC__removeRowsFromHistory() { - let nodes = this._view.selectedNodes; - let URIs = []; - for (let i = 0; i < nodes.length; ++i) { - let node = nodes[i]; - if (PlacesUtils.nodeIsURI(node)) { - let uri = NetUtil.newURI(node.uri); - // Avoid duplicates. - if (URIs.indexOf(uri) < 0) { - URIs.push(uri); - } - } - else if (PlacesUtils.nodeIsQuery(node) && - PlacesUtils.asQuery(node).queryOptions.queryType == - Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) { - this._removeHistoryContainer(node); - } - } - - // Do removal in chunks to give some breath to main-thread. - function* pagesChunkGenerator(aURIs) { - while (aURIs.length) { - let URIslice = aURIs.splice(0, REMOVE_PAGES_CHUNKLEN); - PlacesUtils.bhistory.removePages(URIslice, URIslice.length); - Services.tm.mainThread.dispatch(() => gen.next(), - Ci.nsIThread.DISPATCH_NORMAL); - yield undefined; - } - } - let gen = pagesChunkGenerator(URIs); - gen.next(); - }, - - /** - * Removes history visits for an history container node. - * @param [in] aContainerNode - * The container node to remove. - * - * @note history deletes are not undoable. - */ - _removeHistoryContainer: function PC__removeHistoryContainer(aContainerNode) { - if (PlacesUtils.nodeIsHost(aContainerNode)) { - // Site container. - PlacesUtils.bhistory.removePagesFromHost(aContainerNode.title, true); - } - else if (PlacesUtils.nodeIsDay(aContainerNode)) { - // Day container. - let query = aContainerNode.getQueries()[0]; - let beginTime = query.beginTime; - let endTime = query.endTime; - NS_ASSERT(query && beginTime && endTime, - "A valid date container query should exist!"); - // We want to exclude beginTime from the removal because - // removePagesByTimeframe includes both extremes, while date containers - // exclude the lower extreme. So, if we would not exclude it, we would - // end up removing more history than requested. - PlacesUtils.bhistory.removePagesByTimeframe(beginTime + 1, endTime); - } - }, - - /** - * Removes the selection - * @param aTxnName - * A name for the transaction if this is being performed - * as part of another operation. - */ - remove: Task.async(function* (aTxnName) { - if (!this._hasRemovableSelection()) - return; - - NS_ASSERT(aTxnName !== undefined, "Must supply Transaction Name"); - - var root = this._view.result.root; - - if (PlacesUtils.nodeIsFolder(root)) { - if (PlacesUIUtils.useAsyncTransactions) - yield this._removeRowsFromBookmarks(aTxnName); - else - this._removeRowsFromBookmarks(aTxnName); - } - else if (PlacesUtils.nodeIsQuery(root)) { - var queryType = PlacesUtils.asQuery(root).queryOptions.queryType; - if (queryType == Ci.nsINavHistoryQueryOptions.QUERY_TYPE_BOOKMARKS) { - if (PlacesUIUtils.useAsyncTransactions) - yield this._removeRowsFromBookmarks(aTxnName); - else - this._removeRowsFromBookmarks(aTxnName); - } - else if (queryType == Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) { - this._removeRowsFromHistory(); - } - else { - NS_ASSERT(false, "implement support for QUERY_TYPE_UNIFIED"); - } - } - else - NS_ASSERT(false, "unexpected root"); - }), - - /** - * Fills a DataTransfer object with the content of the selection that can be - * dropped elsewhere. - * @param aEvent - * The dragstart event. - */ - setDataTransfer: function PC_setDataTransfer(aEvent) { - let dt = aEvent.dataTransfer; - - let result = this._view.result; - let didSuppressNotifications = result.suppressNotifications; - if (!didSuppressNotifications) - result.suppressNotifications = true; - - function addData(type, index, feedURI) { - let wrapNode = PlacesUtils.wrapNode(node, type, feedURI); - dt.mozSetDataAt(type, wrapNode, index); - } - - function addURIData(index, feedURI) { - addData(PlacesUtils.TYPE_X_MOZ_URL, index, feedURI); - addData(PlacesUtils.TYPE_UNICODE, index, feedURI); - addData(PlacesUtils.TYPE_HTML, index, feedURI); - } - - try { - let nodes = this._view.draggableSelection; - for (let i = 0; i < nodes.length; ++i) { - var node = nodes[i]; - - // This order is _important_! It controls how this and other - // applications select data to be inserted based on type. - addData(PlacesUtils.TYPE_X_MOZ_PLACE, i); - - // Drop the feed uri for livemark containers - let livemarkInfo = this.getCachedLivemarkInfo(node); - if (livemarkInfo) { - addURIData(i, livemarkInfo.feedURI.spec); - } - else if (node.uri) { - addURIData(i); - } - } - } - finally { - if (!didSuppressNotifications) - result.suppressNotifications = false; - } - }, - - get clipboardAction () { - let action = {}; - let actionOwner; - try { - let xferable = Cc["@mozilla.org/widget/transferable;1"]. - createInstance(Ci.nsITransferable); - xferable.init(null); - xferable.addDataFlavor(PlacesUtils.TYPE_X_MOZ_PLACE_ACTION) - this.clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard); - xferable.getTransferData(PlacesUtils.TYPE_X_MOZ_PLACE_ACTION, action, {}); - [action, actionOwner] = - action.value.QueryInterface(Ci.nsISupportsString).data.split(","); - } catch (ex) { - // Paste from external sources don't have any associated action, just - // fallback to a copy action. - return "copy"; - } - // For cuts also check who inited the action, since cuts across different - // instances should instead be handled as copies (The sources are not - // available for this instance). - if (action == "cut" && actionOwner != this.profileName) - action = "copy"; - - return action; - }, - - _releaseClipboardOwnership: function PC__releaseClipboardOwnership() { - if (this.cutNodes.length > 0) { - // This clears the logical clipboard, doesn't remove data. - this.clipboard.emptyClipboard(Ci.nsIClipboard.kGlobalClipboard); - } - }, - - _clearClipboard: function PC__clearClipboard() { - let xferable = Cc["@mozilla.org/widget/transferable;1"]. - createInstance(Ci.nsITransferable); - xferable.init(null); - // Empty transferables may cause crashes, so just add an unknown type. - const TYPE = "text/x-moz-place-empty"; - xferable.addDataFlavor(TYPE); - xferable.setTransferData(TYPE, PlacesUtils.toISupportsString(""), 0); - this.clipboard.setData(xferable, null, Ci.nsIClipboard.kGlobalClipboard); - }, - - _populateClipboard: function PC__populateClipboard(aNodes, aAction) { - // This order is _important_! It controls how this and other applications - // select data to be inserted based on type. - let contents = [ - { type: PlacesUtils.TYPE_X_MOZ_PLACE, entries: [] }, - { type: PlacesUtils.TYPE_X_MOZ_URL, entries: [] }, - { type: PlacesUtils.TYPE_HTML, entries: [] }, - { type: PlacesUtils.TYPE_UNICODE, entries: [] }, - ]; - - // Avoid handling descendants of a copied node, the transactions take care - // of them automatically. - let copiedFolders = []; - aNodes.forEach(function (node) { - if (this._shouldSkipNode(node, copiedFolders)) - return; - if (PlacesUtils.nodeIsFolder(node)) - copiedFolders.push(node); - - let livemarkInfo = this.getCachedLivemarkInfo(node); - let feedURI = livemarkInfo && livemarkInfo.feedURI.spec; - - contents.forEach(function (content) { - content.entries.push( - PlacesUtils.wrapNode(node, content.type, feedURI) - ); - }); - }, this); - - function addData(type, data) { - xferable.addDataFlavor(type); - xferable.setTransferData(type, PlacesUtils.toISupportsString(data), - data.length * 2); - } - - let xferable = Cc["@mozilla.org/widget/transferable;1"]. - createInstance(Ci.nsITransferable); - xferable.init(null); - let hasData = false; - // This order matters here! It controls how this and other applications - // select data to be inserted based on type. - contents.forEach(function (content) { - if (content.entries.length > 0) { - hasData = true; - let glue = - content.type == PlacesUtils.TYPE_X_MOZ_PLACE ? "," : PlacesUtils.endl; - addData(content.type, content.entries.join(glue)); - } - }); - - // Track the exected action in the xferable. This must be the last flavor - // since it's the least preferred one. - // Enqueue a unique instance identifier to distinguish operations across - // concurrent instances of the application. - addData(PlacesUtils.TYPE_X_MOZ_PLACE_ACTION, aAction + "," + this.profileName); - - if (hasData) { - this.clipboard.setData(xferable, - this.cutNodes.length > 0 ? this : null, - Ci.nsIClipboard.kGlobalClipboard); - } - }, - - _cutNodes: [], - get cutNodes() { - return this._cutNodes; - }, - set cutNodes(aNodes) { - let self = this; - function updateCutNodes(aValue) { - self._cutNodes.forEach(function (aNode) { - self._view.toggleCutNode(aNode, aValue); - }); - } - - updateCutNodes(false); - this._cutNodes = aNodes; - updateCutNodes(true); - return aNodes; - }, - - /** - * Copy Bookmarks and Folders to the clipboard - */ - copy: function PC_copy() { - let result = this._view.result; - let didSuppressNotifications = result.suppressNotifications; - if (!didSuppressNotifications) - result.suppressNotifications = true; - try { - this._populateClipboard(this._view.selectedNodes, "copy"); - } - finally { - if (!didSuppressNotifications) - result.suppressNotifications = false; - } - }, - - /** - * Cut Bookmarks and Folders to the clipboard - */ - cut: function PC_cut() { - let result = this._view.result; - let didSuppressNotifications = result.suppressNotifications; - if (!didSuppressNotifications) - result.suppressNotifications = true; - try { - this._populateClipboard(this._view.selectedNodes, "cut"); - this.cutNodes = this._view.selectedNodes; - } - finally { - if (!didSuppressNotifications) - result.suppressNotifications = false; - } - }, - - /** - * Paste Bookmarks and Folders from the clipboard - */ - paste: Task.async(function* () { - // No reason to proceed if there isn't a valid insertion point. - let ip = this._view.insertionPoint; - if (!ip) - throw Cr.NS_ERROR_NOT_AVAILABLE; - - let action = this.clipboardAction; - - let xferable = Cc["@mozilla.org/widget/transferable;1"]. - createInstance(Ci.nsITransferable); - xferable.init(null); - // This order matters here! It controls the preferred flavors for this - // paste operation. - [ PlacesUtils.TYPE_X_MOZ_PLACE, - PlacesUtils.TYPE_X_MOZ_URL, - PlacesUtils.TYPE_UNICODE, - ].forEach(type => xferable.addDataFlavor(type)); - - this.clipboard.getData(xferable, Ci.nsIClipboard.kGlobalClipboard); - - // Now get the clipboard contents, in the best available flavor. - let data = {}, type = {}, items = []; - try { - xferable.getAnyTransferData(type, data, {}); - data = data.value.QueryInterface(Ci.nsISupportsString).data; - type = type.value; - items = PlacesUtils.unwrapNodes(data, type); - } catch (ex) { - // No supported data exists or nodes unwrap failed, just bail out. - return; - } - - let itemsToSelect = []; - if (PlacesUIUtils.useAsyncTransactions) { - if (ip.isTag) { - let uris = items.filter(item => "uri" in item).map(item => NetUtil.newURI(item.uri)); - yield PlacesTransactions.Tag({ uris: uris, tag: ip.tagName }).transact(); - } - else { - yield PlacesTransactions.batch(function* () { - let insertionIndex = ip.index; - let parent = yield ip.promiseGuid(); - - for (let item of items) { - let doCopy = action == "copy"; - - // If this is not a copy, check for safety that we can move the - // source, otherwise report an error and fallback to a copy. - if (!doCopy && - !PlacesControllerDragHelper.canMoveUnwrappedNode(item)) { - Components.utils.reportError("Tried to move an unmovable " + - "Places node, reverting to a copy operation."); - doCopy = true; - } - let guid = yield PlacesUIUtils.getTransactionForData( - item, type, parent, insertionIndex, doCopy).transact(); - itemsToSelect.push(yield PlacesUtils.promiseItemId(guid)); - - // Adjust index to make sure items are pasted in the correct - // position. If index is DEFAULT_INDEX, items are just appended. - if (insertionIndex != PlacesUtils.bookmarks.DEFAULT_INDEX) - insertionIndex++; - } - }); - } - } - else { - let transactions = []; - let insertionIndex = ip.index; - for (let i = 0; i < items.length; ++i) { - if (ip.isTag) { - // Pasting into a tag container means tagging the item, regardless of - // the requested action. - let tagTxn = new PlacesTagURITransaction(NetUtil.newURI(items[i].uri), - [ip.itemId]); - transactions.push(tagTxn); - continue; - } - - // Adjust index to make sure items are pasted in the correct position. - // If index is DEFAULT_INDEX, items are just appended. - if (ip.index != PlacesUtils.bookmarks.DEFAULT_INDEX) - insertionIndex = ip.index + i; - - // If this is not a copy, check for safety that we can move the source, - // otherwise report an error and fallback to a copy. - if (action != "copy" && !PlacesControllerDragHelper.canMoveUnwrappedNode(items[i])) { - Components.utils.reportError("Tried to move an unmovable Places " + - "node, reverting to a copy operation."); - action = "copy"; - } - transactions.push( - PlacesUIUtils.makeTransaction(items[i], type, ip.itemId, - insertionIndex, action == "copy") - ); - } - - let aggregatedTxn = new PlacesAggregatedTransaction("Paste", transactions); - PlacesUtils.transactionManager.doTransaction(aggregatedTxn); - - for (let i = 0; i < transactions.length; ++i) { - itemsToSelect.push( - PlacesUtils.bookmarks.getIdForItemAt(ip.itemId, ip.index + i) - ); - } - } - - // Cut/past operations are not repeatable, so clear the clipboard. - if (action == "cut") { - this._clearClipboard(); - } - - if (itemsToSelect.length > 0) - this._view.selectItems(itemsToSelect, false); - }), - - /** - * Cache the livemark info for a node. This allows the controller and the - * views to treat the given node as a livemark. - * @param aNode - * a places result node. - * @param aLivemarkInfo - * a mozILivemarkInfo object. - */ - cacheLivemarkInfo: function PC_cacheLivemarkInfo(aNode, aLivemarkInfo) { - this._cachedLivemarkInfoObjects.set(aNode, aLivemarkInfo); - }, - - /** - * Returns whether or not there's cached mozILivemarkInfo object for a node. - * @param aNode - * a places result node. - * @return true if there's a cached mozILivemarkInfo object for - * aNode, false otherwise. - */ - hasCachedLivemarkInfo: function PC_hasCachedLivemarkInfo(aNode) { - return this._cachedLivemarkInfoObjects.has(aNode); - }, - - /** - * Returns the cached livemark info for a node, if set by cacheLivemarkInfo, - * null otherwise. - * @param aNode - * a places result node. - * @return the mozILivemarkInfo object for aNode, if set, null otherwise. - */ - getCachedLivemarkInfo: function PC_getCachedLivemarkInfo(aNode) { - return this._cachedLivemarkInfoObjects.get(aNode, null); - } -}; - -/** - * Handles drag and drop operations for views. Note that this is view agnostic! - * You should not use PlacesController._view within these methods, since - * the view that the item(s) have been dropped on was not necessarily active. - * Drop functions are passed the view that is being dropped on. - */ -var PlacesControllerDragHelper = { - /** - * DOM Element currently being dragged over - */ - currentDropTarget: null, - - /** - * Determines if the mouse is currently being dragged over a child node of - * this menu. This is necessary so that the menu doesn't close while the - * mouse is dragging over one of its submenus - * @param node - * The container node - * @return true if the user is dragging over a node within the hierarchy of - * the container, false otherwise. - */ - draggingOverChildNode: function PCDH_draggingOverChildNode(node) { - let currentNode = this.currentDropTarget; - while (currentNode) { - if (currentNode == node) - return true; - currentNode = currentNode.parentNode; - } - return false; - }, - - /** - * @return The current active drag session. Returns null if there is none. - */ - getSession: function PCDH__getSession() { - return this.dragService.getCurrentSession(); - }, - - /** - * Extract the first accepted flavor from a list of flavors. - * @param aFlavors - * The flavors list of type DOMStringList. - */ - getFirstValidFlavor: function PCDH_getFirstValidFlavor(aFlavors) { - for (let i = 0; i < aFlavors.length; i++) { - if (PlacesUIUtils.SUPPORTED_FLAVORS.includes(aFlavors[i])) - return aFlavors[i]; - } - - // If no supported flavor is found, check if data includes text/plain - // contents. If so, request them as text/unicode, a conversion will happen - // automatically. - if (aFlavors.contains("text/plain")) { - return PlacesUtils.TYPE_UNICODE; - } - - return null; - }, - - /** - * Determines whether or not the data currently being dragged can be dropped - * on a places view. - * @param ip - * The insertion point where the items should be dropped. - */ - canDrop: function PCDH_canDrop(ip, dt) { - let dropCount = dt.mozItemCount; - - // Check every dragged item. - for (let i = 0; i < dropCount; i++) { - let flavor = this.getFirstValidFlavor(dt.mozTypesAt(i)); - if (!flavor) - return false; - - // Urls can be dropped on any insertionpoint. - // XXXmano: remember that this method is called for each dragover event! - // Thus we shouldn't use unwrapNodes here at all if possible. - // I think it would be OK to accept bogus data here (e.g. text which was - // somehow wrapped as TAB_DROP_TYPE, this is not in our control, and - // will just case the actual drop to be a no-op), and only rule out valid - // expected cases, which are either unsupported flavors, or items which - // cannot be dropped in the current insertionpoint. The last case will - // likely force us to use unwrapNodes for the private data types of - // places. - if (flavor == TAB_DROP_TYPE) - continue; - - let data = dt.mozGetDataAt(flavor, i); - let dragged; - try { - dragged = PlacesUtils.unwrapNodes(data, flavor)[0]; - } - catch (e) { - return false; - } - - // Only bookmarks and urls can be dropped into tag containers. - if (ip.isTag && - dragged.type != PlacesUtils.TYPE_X_MOZ_URL && - (dragged.type != PlacesUtils.TYPE_X_MOZ_PLACE || - (dragged.uri && dragged.uri.startsWith("place:")) )) - return false; - - // The following loop disallows the dropping of a folder on itself or - // on any of its descendants. - if (dragged.type == PlacesUtils.TYPE_X_MOZ_PLACE_CONTAINER || - (dragged.uri && dragged.uri.startsWith("place:")) ) { - let parentId = ip.itemId; - while (parentId != PlacesUtils.placesRootId) { - if (dragged.concreteId == parentId || dragged.id == parentId) - return false; - parentId = PlacesUtils.bookmarks.getFolderIdForItem(parentId); - } - } - } - return true; - }, - - /** - * Determines if an unwrapped node can be moved. - * - * @param aUnwrappedNode - * A node unwrapped by PlacesUtils.unwrapNodes(). - * @return True if the node can be moved, false otherwise. - */ - canMoveUnwrappedNode: function (aUnwrappedNode) { - return aUnwrappedNode.id > 0 && - !PlacesUtils.isRootItem(aUnwrappedNode.id) && - (!aUnwrappedNode.parent || !PlacesUIUtils.isContentsReadOnly(aUnwrappedNode.parent)) && - aUnwrappedNode.parent != PlacesUtils.tagsFolderId && - aUnwrappedNode.grandParentId != PlacesUtils.tagsFolderId; - }, - - /** - * Determines if a node can be moved. - * - * @param aNode - * A nsINavHistoryResultNode node. - * @param [optional] aDOMNode - * A XUL DOM node. - * @return True if the node can be moved, false otherwise. - */ - canMoveNode(aNode, aDOMNode) { - // Only bookmark items are movable. - if (aNode.itemId == -1) - return false; - - let parentNode = aNode.parent; - if (!parentNode) { - // Normally parentless places nodes can not be moved, - // but simulated bookmarked URI nodes are special. - return !!aDOMNode && - aDOMNode.hasAttribute("simulated-places-node") && - PlacesUtils.nodeIsBookmark(aNode); - } - - // Once tags and bookmarked are divorced, the tag-query check should be - // removed. - return !(PlacesUtils.nodeIsFolder(parentNode) && - PlacesUIUtils.isContentsReadOnly(parentNode)) && - !PlacesUtils.nodeIsTagQuery(parentNode); - }, - - /** - * Handles the drop of one or more items onto a view. - * @param insertionPoint - * The insertion point where the items should be dropped - */ - onDrop: Task.async(function* (insertionPoint, dt) { - let doCopy = ["copy", "link"].includes(dt.dropEffect); - - let transactions = []; - let dropCount = dt.mozItemCount; - let movedCount = 0; - let parentGuid = PlacesUIUtils.useAsyncTransactions ? - (yield insertionPoint.promiseGuid()) : null; - let tagName = insertionPoint.tagName; - for (let i = 0; i < dropCount; ++i) { - let flavor = this.getFirstValidFlavor(dt.mozTypesAt(i)); - if (!flavor) - return; - - let data = dt.mozGetDataAt(flavor, i); - let unwrapped; - if (flavor != TAB_DROP_TYPE) { - // There's only ever one in the D&D case. - unwrapped = PlacesUtils.unwrapNodes(data, flavor)[0]; - } - else if (data instanceof XULElement && data.localName == "tab" && - data.ownerGlobal instanceof ChromeWindow) { - let uri = data.linkedBrowser.currentURI; - let spec = uri ? uri.spec : "about:blank"; - unwrapped = { uri: spec, - title: data.label, - type: PlacesUtils.TYPE_X_MOZ_URL}; - } - else - throw new Error("bogus data was passed as a tab"); - - let index = insertionPoint.index; - - // Adjust insertion index to prevent reversal of dragged items. When you - // drag multiple elts upward: need to increment index or each successive - // elt will be inserted at the same index, each above the previous. - let dragginUp = insertionPoint.itemId == unwrapped.parent && - index < PlacesUtils.bookmarks.getItemIndex(unwrapped.id); - if (index != -1 && dragginUp) - index+= movedCount++; - - // If dragging over a tag container we should tag the item. - if (insertionPoint.isTag) { - let uri = NetUtil.newURI(unwrapped.uri); - let tagItemId = insertionPoint.itemId; - if (PlacesUIUtils.useAsyncTransactions) - transactions.push(PlacesTransactions.Tag({ uri: uri, tag: tagName })); - else - transactions.push(new PlacesTagURITransaction(uri, [tagItemId])); - } - else { - // If this is not a copy, check for safety that we can move the source, - // otherwise report an error and fallback to a copy. - if (!doCopy && !PlacesControllerDragHelper.canMoveUnwrappedNode(unwrapped)) { - Components.utils.reportError("Tried to move an unmovable Places " + - "node, reverting to a copy operation."); - doCopy = true; - } - if (PlacesUIUtils.useAsyncTransactions) { - transactions.push( - PlacesUIUtils.getTransactionForData(unwrapped, - flavor, - parentGuid, - index, - doCopy)); - } - else { - transactions.push(PlacesUIUtils.makeTransaction(unwrapped, - flavor, insertionPoint.itemId, - index, doCopy)); - } - } - } - - if (PlacesUIUtils.useAsyncTransactions) { - yield PlacesTransactions.batch(transactions); - } - else { - let txn = new PlacesAggregatedTransaction("DropItems", transactions); - PlacesUtils.transactionManager.doTransaction(txn); - } - }), - - /** - * Checks if we can insert into a container. - * @param aContainer - * The container were we are want to drop - */ - disallowInsertion: function(aContainer) { - NS_ASSERT(aContainer, "empty container"); - // Allow dropping into Tag containers and editable folders. - return !PlacesUtils.nodeIsTagQuery(aContainer) && - (!PlacesUtils.nodeIsFolder(aContainer) || - PlacesUIUtils.isContentsReadOnly(aContainer)); - } -}; - - -XPCOMUtils.defineLazyServiceGetter(PlacesControllerDragHelper, "dragService", - "@mozilla.org/widget/dragservice;1", - "nsIDragService"); - -function goUpdatePlacesCommands() { - // Get the controller for one of the places commands. - var placesController = doGetPlacesControllerForCommand("placesCmd_open"); - function updatePlacesCommand(aCommand) { - goSetCommandEnabled(aCommand, placesController && - placesController.isCommandEnabled(aCommand)); - } - - updatePlacesCommand("placesCmd_open"); - updatePlacesCommand("placesCmd_open:window"); - updatePlacesCommand("placesCmd_open:privatewindow"); - updatePlacesCommand("placesCmd_open:tab"); - updatePlacesCommand("placesCmd_new:folder"); - updatePlacesCommand("placesCmd_new:bookmark"); - updatePlacesCommand("placesCmd_new:separator"); - updatePlacesCommand("placesCmd_show:info"); - updatePlacesCommand("placesCmd_moveBookmarks"); - updatePlacesCommand("placesCmd_reload"); - updatePlacesCommand("placesCmd_sortBy:name"); - updatePlacesCommand("placesCmd_cut"); - updatePlacesCommand("placesCmd_copy"); - updatePlacesCommand("placesCmd_paste"); - updatePlacesCommand("placesCmd_delete"); -} - -function doGetPlacesControllerForCommand(aCommand) -{ - // A context menu may be built for non-focusable views. Thus, we first try - // to look for a view associated with document.popupNode - let popupNode; - try { - popupNode = document.popupNode; - } catch (e) { - // The document went away (bug 797307). - return null; - } - if (popupNode) { - let view = PlacesUIUtils.getViewForNode(popupNode); - if (view && view._contextMenuShown) - return view.controllers.getControllerForCommand(aCommand); - } - - // When we're not building a context menu, only focusable views - // are possible. Thus, we can safely use the command dispatcher. - let controller = top.document.commandDispatcher - .getControllerForCommand(aCommand); - if (controller) - return controller; - - return null; -} - -function goDoPlacesCommand(aCommand) -{ - let controller = doGetPlacesControllerForCommand(aCommand); - if (controller && controller.isCommandEnabled(aCommand)) - controller.doCommand(aCommand); -} diff --git a/browser/components/places/content/downloadsViewOverlay.xul b/browser/components/places/content/downloadsViewOverlay.xul deleted file mode 100644 index 5706632ba..000000000 --- a/browser/components/places/content/downloadsViewOverlay.xul +++ /dev/null @@ -1,47 +0,0 @@ -<!-- 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/. --> - -<?xul-overlay href="chrome://browser/content/downloads/allDownloadsViewOverlay.xul"?> - -<!DOCTYPE overlay [ -<!ENTITY % downloadsDTD SYSTEM "chrome://browser/locale/downloads/downloads.dtd"> -%downloadsDTD; -]> - -<overlay id="downloadsViewOverlay" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <script type="application/javascript"><![CDATA[ - const DOWNLOADS_QUERY = "place:transition=" + - Components.interfaces.nsINavHistoryService.TRANSITION_DOWNLOAD + - "&sort=" + - Components.interfaces.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING; - - ContentArea.setContentViewForQueryString(DOWNLOADS_QUERY, - () => new DownloadsPlacesView(document.getElementById("downloadsRichListBox"), false), - { showDetailsPane: false, - toolbarSet: "back-button, forward-button, organizeButton, clearDownloadsButton, libraryToolbarSpacer, searchFilter" }); - ]]></script> - - <window id="places"> - <commandset id="downloadCommands"/> - <menupopup id="downloadsContextMenu"/> - </window> - - <deck id="placesViewsDeck"> - <richlistbox id="downloadsRichListBox"/> - </deck> - - <toolbar id="placesToolbar"> - <toolbarbutton id="clearDownloadsButton" -#ifdef XP_MACOSX - class="tabbable" -#endif - insertbefore="libraryToolbarSpacer" - label="&clearDownloadsButton.label;" - command="downloadsCmd_clearDownloads" - tooltiptext="&clearDownloadsButton.tooltip;"/> - </toolbar> - -</overlay> diff --git a/browser/components/places/content/editBookmarkOverlay.js b/browser/components/places/content/editBookmarkOverlay.js deleted file mode 100644 index d59f5c764..000000000 --- a/browser/components/places/content/editBookmarkOverlay.js +++ /dev/null @@ -1,1196 +0,0 @@ -/* 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/. */ - -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); - -const LAST_USED_ANNO = "bookmarkPropertiesDialog/folderLastUsed"; -const MAX_FOLDER_ITEM_IN_MENU_LIST = 5; - -var gEditItemOverlay = { - _observersAdded: false, - _staticFoldersListBuilt: false, - - _paneInfo: null, - _setPaneInfo(aInitInfo) { - if (!aInitInfo) - return this._paneInfo = null; - - if ("uris" in aInitInfo && "node" in aInitInfo) - throw new Error("ambiguous pane info"); - if (!("uris" in aInitInfo) && !("node" in aInitInfo)) - throw new Error("Neither node nor uris set for pane info"); - - let node = "node" in aInitInfo ? aInitInfo.node : null; - - // Since there's no true UI for folder shortcuts (they show up just as their target - // folders), when the pane shows for them it's opened in read-only mode, showing the - // properties of the target folder. - let itemId = node ? node.itemId : -1; - let itemGuid = PlacesUIUtils.useAsyncTransactions && node ? - PlacesUtils.getConcreteItemGuid(node) : null; - let isItem = itemId != -1; - let isFolderShortcut = isItem && - node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT; - let isTag = node && PlacesUtils.nodeIsTagQuery(node); - if (isTag) { - itemId = PlacesUtils.getConcreteItemId(node); - // For now we don't have access to the item guid synchronously for tags, - // so we'll need to fetch it later. - } - let isURI = node && PlacesUtils.nodeIsURI(node); - let uri = isURI ? NetUtil.newURI(node.uri) : null; - let title = node ? node.title : null; - let isBookmark = isItem && isURI; - let bulkTagging = !node; - let uris = bulkTagging ? aInitInfo.uris : null; - let visibleRows = new Set(); - let isParentReadOnly = false; - let postData = aInitInfo.postData; - if (node && "parent" in node) { - let parent = node.parent; - if (parent) { - isParentReadOnly = !PlacesUtils.nodeIsFolder(parent) || - PlacesUIUtils.isContentsReadOnly(parent); - } - } - let focusedElement = aInitInfo.focusedElement; - let onPanelReady = aInitInfo.onPanelReady; - - return this._paneInfo = { itemId, itemGuid, isItem, - isURI, uri, title, - isBookmark, isFolderShortcut, isParentReadOnly, - bulkTagging, uris, - visibleRows, postData, isTag, focusedElement, - onPanelReady }; - }, - - get initialized() { - return this._paneInfo != null; - }, - - // Backwards-compatibility getters - get itemId() { - if (!this.initialized || this._paneInfo.bulkTagging) - return -1; - return this._paneInfo.itemId; - }, - - get uri() { - if (!this.initialized) - return null; - if (this._paneInfo.bulkTagging) - return this._paneInfo.uris[0]; - return this._paneInfo.uri; - }, - - get multiEdit() { - return this.initialized && this._paneInfo.bulkTagging; - }, - - // Check if the pane is initialized to show only read-only fields. - get readOnly() { - // TODO (Bug 1120314): Folder shortcuts are currently read-only due to some - // quirky implementation details (the most important being the "smart" - // semantics of node.title that makes hard to edit the right entry). - // This pane is read-only if: - // * the panel is not initialized - // * the node is a folder shortcut - // * the node is not bookmarked and not a tag container - // * the node is child of a read-only container and is not a bookmarked - // URI nor a tag container - return !this.initialized || - this._paneInfo.isFolderShortcut || - (!this._paneInfo.isItem && !this._paneInfo.isTag) || - (this._paneInfo.isParentReadOnly && !this._paneInfo.isBookmark && !this._paneInfo.isTag); - }, - - // the first field which was edited after this panel was initialized for - // a certain item - _firstEditedField: "", - - _initNamePicker() { - if (this._paneInfo.bulkTagging) - throw new Error("_initNamePicker called unexpectedly"); - - // title may by null, which, for us, is the same as an empty string. - this._initTextField(this._namePicker, this._paneInfo.title || ""); - }, - - _initLocationField() { - if (!this._paneInfo.isURI) - throw new Error("_initLocationField called unexpectedly"); - this._initTextField(this._locationField, this._paneInfo.uri.spec); - }, - - _initDescriptionField() { - if (!this._paneInfo.isItem) - throw new Error("_initDescriptionField called unexpectedly"); - - this._initTextField(this._descriptionField, - PlacesUIUtils.getItemDescription(this._paneInfo.itemId)); - }, - - _initKeywordField: Task.async(function* (newKeyword = "") { - if (!this._paneInfo.isBookmark) { - throw new Error("_initKeywordField called unexpectedly"); - } - - // Reset the field status synchronously now, eventually we'll reinit it - // later if we find an existing keyword. This way we can ensure to be in a - // consistent status when reusing the panel across different bookmarks. - this._keyword = newKeyword; - this._initTextField(this._keywordField, newKeyword); - - if (!newKeyword) { - let entries = []; - yield PlacesUtils.keywords.fetch({ url: this._paneInfo.uri.spec }, - e => entries.push(e)); - if (entries.length > 0) { - // We show an existing keyword if either POST data was not provided, or - // if the POST data is the same. - let existingKeyword = entries[0].keyword; - let postData = this._paneInfo.postData; - if (postData) { - let sameEntry = entries.find(e => e.postData === postData); - existingKeyword = sameEntry ? sameEntry.keyword : ""; - } - if (existingKeyword) { - this._keyword = existingKeyword; - // Update the text field to the existing keyword. - this._initTextField(this._keywordField, this._keyword); - } - } - } - }), - - _initLoadInSidebar: Task.async(function* () { - if (!this._paneInfo.isBookmark) - throw new Error("_initLoadInSidebar called unexpectedly"); - - this._loadInSidebarCheckbox.checked = - PlacesUtils.annotations.itemHasAnnotation( - this._paneInfo.itemId, PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO); - }), - - /** - * Initialize the panel. - * - * @param aInfo - * An object having: - * 1. one of the following properties: - * - node: either a result node or a node-like object representing the - * item to be edited. A node-like object must have the following - * properties (with values that match exactly those a result node - * would have): itemId, bookmarkGuid, uri, title, type. - * - uris: an array of uris for bulk tagging. - * - * 2. any of the following optional properties: - * - hiddenRows (Strings array): list of rows to be hidden regardless - * of the item edited. Possible values: "title", "location", - * "description", "keyword", "loadInSidebar", "feedLocation", - * "siteLocation", folderPicker" - */ - initPanel(aInfo) { - if (typeof(aInfo) != "object" || aInfo === null) - throw new Error("aInfo must be an object."); - if ("node" in aInfo) { - try { - aInfo.node.type; - } catch (e) { - // If the lazy loader for |type| generates an exception, it means that - // this bookmark could not be loaded. This sometimes happens when tests - // create a bookmark by clicking the bookmark star, then try to cleanup - // before the bookmark panel has finished opening. Either way, if we - // cannot retrieve the bookmark information, we cannot open the panel. - return; - } - } - - // For sanity ensure that the implementer has uninited the panel before - // trying to init it again, or we could end up leaking due to observers. - if (this.initialized) - this.uninitPanel(false); - - let { itemId, isItem, isURI, - isBookmark, bulkTagging, uris, - visibleRows, focusedElement, - onPanelReady } = this._setPaneInfo(aInfo); - - let showOrCollapse = - (rowId, isAppropriateForInput, nameInHiddenRows = null) => { - let visible = isAppropriateForInput; - if (visible && "hiddenRows" in aInfo && nameInHiddenRows) - visible &= aInfo.hiddenRows.indexOf(nameInHiddenRows) == -1; - if (visible) - visibleRows.add(rowId); - return !(this._element(rowId).collapsed = !visible); - }; - - if (showOrCollapse("nameRow", !bulkTagging, "name")) { - this._initNamePicker(); - this._namePicker.readOnly = this.readOnly; - } - - // In some cases we want to hide the location field, since it's not - // human-readable, but we still want to initialize it. - showOrCollapse("locationRow", isURI, "location"); - if (isURI) { - this._initLocationField(); - this._locationField.readOnly = this.readOnly; - } - - // hide the description field for - if (showOrCollapse("descriptionRow", isItem && !this.readOnly, - "description")) { - this._initDescriptionField(); - this._descriptionField.readOnly = this.readOnly; - } - - if (showOrCollapse("keywordRow", isBookmark, "keyword")) { - this._initKeywordField().catch(Components.utils.reportError); - this._keywordField.readOnly = this.readOnly; - } - - // Collapse the tag selector if the item does not accept tags. - if (showOrCollapse("tagsRow", isURI || bulkTagging, "tags")) - this._initTagsField().catch(Components.utils.reportError); - else if (!this._element("tagsSelectorRow").collapsed) - this.toggleTagsSelector().catch(Components.utils.reportError); - - // Load in sidebar. - if (showOrCollapse("loadInSidebarCheckbox", isBookmark, "loadInSidebar")) { - this._initLoadInSidebar(); - } - - // Folder picker. - // Technically we should check that the item is not moveable, but that's - // not cheap (we don't always have the parent), and there's no use case for - // this (it's only the Star UI that shows the folderPicker) - if (showOrCollapse("folderRow", isItem, "folderPicker")) { - let containerId = PlacesUtils.bookmarks.getFolderIdForItem(itemId); - this._initFolderMenuList(containerId); - } - - // Selection count. - if (showOrCollapse("selectionCount", bulkTagging)) { - this._element("itemsCountText").value = - PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel", - uris.length, - [uris.length]); - } - - // Observe changes. - if (!this._observersAdded) { - PlacesUtils.bookmarks.addObserver(this, false); - window.addEventListener("unload", this, false); - this._observersAdded = true; - } - - let focusElement = () => { - // The focusedElement possible values are: - // * preferred: focus the field that the user touched first the last - // time the pane was shown (either namePicker or tagsField) - // * first: focus the first non collapsed textbox - // Note: since all controls are collapsed by default, we don't get the - // default XUL dialog behavior, that selects the first control, so we set - // the focus explicitly. - // Note: If focusedElement === "preferred", this file expects gPrefService - // to be defined in the global scope. - let elt; - if (focusedElement === "preferred") { - /* eslint-disable no-undef */ - elt = this._element(gPrefService.getCharPref("browser.bookmarks.editDialog.firstEditField")); - /* eslint-enable no-undef */ - } else if (focusedElement === "first") { - elt = document.querySelector("textbox:not([collapsed=true])"); - } - if (elt) { - elt.focus(); - elt.select(); - } - }; - - if (onPanelReady) { - onPanelReady(focusElement); - } else { - focusElement(); - } - }, - - /** - * Finds tags that are in common among this._currentInfo.uris; - */ - _getCommonTags() { - if ("_cachedCommonTags" in this._paneInfo) - return this._paneInfo._cachedCommonTags; - - let uris = [...this._paneInfo.uris]; - let firstURI = uris.shift(); - let commonTags = new Set(PlacesUtils.tagging.getTagsForURI(firstURI)); - if (commonTags.size == 0) - return this._cachedCommonTags = []; - - for (let uri of uris) { - let curentURITags = PlacesUtils.tagging.getTagsForURI(uri); - for (let tag of commonTags) { - if (!curentURITags.includes(tag)) { - commonTags.delete(tag) - if (commonTags.size == 0) - return this._paneInfo.cachedCommonTags = []; - } - } - } - return this._paneInfo._cachedCommonTags = [...commonTags]; - }, - - _initTextField(aElement, aValue) { - if (aElement.value != aValue) { - aElement.value = aValue; - - // Clear the editor's undo stack - let transactionManager; - try { - transactionManager = aElement.editor.transactionManager; - } catch (e) { - // When retrieving the transaction manager, editor may be null resulting - // in a TypeError. Additionally, the transaction manager may not - // exist yet, which causes access to it to throw NS_ERROR_FAILURE. - // In either event, the transaction manager doesn't exist it, so we - // don't need to worry about clearing it. - if (!(e instanceof TypeError) && e.result != Cr.NS_ERROR_FAILURE) { - throw e; - } - } - if (transactionManager) { - transactionManager.clear(); - } - } - }, - - /** - * Appends a menu-item representing a bookmarks folder to a menu-popup. - * @param aMenupopup - * The popup to which the menu-item should be added. - * @param aFolderId - * The identifier of the bookmarks folder. - * @return the new menu item. - */ - _appendFolderItemToMenupopup(aMenupopup, aFolderId) { - // First make sure the folders-separator is visible - this._element("foldersSeparator").hidden = false; - - var folderMenuItem = document.createElement("menuitem"); - var folderTitle = PlacesUtils.bookmarks.getItemTitle(aFolderId) - folderMenuItem.folderId = aFolderId; - folderMenuItem.setAttribute("label", folderTitle); - folderMenuItem.className = "menuitem-iconic folder-icon"; - aMenupopup.appendChild(folderMenuItem); - return folderMenuItem; - }, - - _initFolderMenuList: function EIO__initFolderMenuList(aSelectedFolder) { - // clean up first - var menupopup = this._folderMenuList.menupopup; - while (menupopup.childNodes.length > 6) - menupopup.removeChild(menupopup.lastChild); - - const bms = PlacesUtils.bookmarks; - const annos = PlacesUtils.annotations; - - // Build the static list - var unfiledItem = this._element("unfiledRootItem"); - if (!this._staticFoldersListBuilt) { - unfiledItem.label = bms.getItemTitle(PlacesUtils.unfiledBookmarksFolderId); - unfiledItem.folderId = PlacesUtils.unfiledBookmarksFolderId; - var bmMenuItem = this._element("bmRootItem"); - bmMenuItem.label = bms.getItemTitle(PlacesUtils.bookmarksMenuFolderId); - bmMenuItem.folderId = PlacesUtils.bookmarksMenuFolderId; - var toolbarItem = this._element("toolbarFolderItem"); - toolbarItem.label = bms.getItemTitle(PlacesUtils.toolbarFolderId); - toolbarItem.folderId = PlacesUtils.toolbarFolderId; - this._staticFoldersListBuilt = true; - } - - // List of recently used folders: - var folderIds = annos.getItemsWithAnnotation(LAST_USED_ANNO); - - /** - * The value of the LAST_USED_ANNO annotation is the time (in the form of - * Date.getTime) at which the folder has been last used. - * - * First we build the annotated folders array, each item has both the - * folder identifier and the time at which it was last-used by this dialog - * set. Then we sort it descendingly based on the time field. - */ - this._recentFolders = []; - for (let i = 0; i < folderIds.length; i++) { - var lastUsed = annos.getItemAnnotation(folderIds[i], LAST_USED_ANNO); - this._recentFolders.push({ folderId: folderIds[i], lastUsed: lastUsed }); - } - this._recentFolders.sort(function(a, b) { - if (b.lastUsed < a.lastUsed) - return -1; - if (b.lastUsed > a.lastUsed) - return 1; - return 0; - }); - - var numberOfItems = Math.min(MAX_FOLDER_ITEM_IN_MENU_LIST, - this._recentFolders.length); - for (let i = 0; i < numberOfItems; i++) { - this._appendFolderItemToMenupopup(menupopup, - this._recentFolders[i].folderId); - } - - var defaultItem = this._getFolderMenuItem(aSelectedFolder); - this._folderMenuList.selectedItem = defaultItem; - - // Set a selectedIndex attribute to show special icons - this._folderMenuList.setAttribute("selectedIndex", - this._folderMenuList.selectedIndex); - - // Hide the folders-separator if no folder is annotated as recently-used - this._element("foldersSeparator").hidden = (menupopup.childNodes.length <= 6); - this._folderMenuList.disabled = this.readOnly; - }, - - QueryInterface: - XPCOMUtils.generateQI([Components.interfaces.nsIDOMEventListener, - Components.interfaces.nsINavBookmarkObserver]), - - _element(aID) { - return document.getElementById("editBMPanel_" + aID); - }, - - uninitPanel(aHideCollapsibleElements) { - if (aHideCollapsibleElements) { - // Hide the folder tree if it was previously visible. - var folderTreeRow = this._element("folderTreeRow"); - if (!folderTreeRow.collapsed) - this.toggleFolderTreeVisibility(); - - // Hide the tag selector if it was previously visible. - var tagsSelectorRow = this._element("tagsSelectorRow"); - if (!tagsSelectorRow.collapsed) - this.toggleTagsSelector(); - } - - if (this._observersAdded) { - PlacesUtils.bookmarks.removeObserver(this); - this._observersAdded = false; - } - - this._setPaneInfo(null); - this._firstEditedField = ""; - }, - - onTagsFieldChange() { - if (this._paneInfo.isURI || this._paneInfo.bulkTagging) { - this._updateTags().then( - anyChanges => { - if (anyChanges) - this._mayUpdateFirstEditField("tagsField"); - }, Components.utils.reportError); - } - }, - - /** - * For a given array of currently-set tags and the tags-input-field - * value, returns which tags should be removed and which should be added in - * the form of { removedTags: [...], newTags: [...] }. - */ - _getTagsChanges(aCurrentTags) { - let inputTags = this._getTagsArrayFromTagsInputField(); - - // Optimize the trivial cases (which are actually the most common). - if (inputTags.length == 0 && aCurrentTags.length == 0) - return { newTags: [], removedTags: [] }; - if (inputTags.length == 0) - return { newTags: [], removedTags: aCurrentTags }; - if (aCurrentTags.length == 0) - return { newTags: inputTags, removedTags: [] }; - - let removedTags = aCurrentTags.filter(t => !inputTags.includes(t)); - let newTags = inputTags.filter(t => !aCurrentTags.includes(t)); - return { removedTags, newTags }; - }, - - // Adds and removes tags for one or more uris. - _setTagsFromTagsInputField: Task.async(function* (aCurrentTags, aURIs) { - let { removedTags, newTags } = this._getTagsChanges(aCurrentTags); - if (removedTags.length + newTags.length == 0) - return false; - - if (!PlacesUIUtils.useAsyncTransactions) { - let txns = []; - for (let uri of aURIs) { - if (removedTags.length > 0) - txns.push(new PlacesUntagURITransaction(uri, removedTags)); - if (newTags.length > 0) - txns.push(new PlacesTagURITransaction(uri, newTags)); - } - - PlacesUtils.transactionManager.doTransaction( - new PlacesAggregatedTransaction("Update tags", txns)); - return true; - } - - let setTags = function* () { - if (newTags.length > 0) { - yield PlacesTransactions.Tag({ urls: aURIs, tags: newTags }) - .transact(); - } - if (removedTags.length > 0) { - yield PlacesTransactions.Untag({ urls: aURIs, tags: removedTags }) - .transact(); - } - }; - - // Only in the library info-pane it's safe (and necessary) to batch these. - // TODO bug 1093030: cleanup this mess when the bookmarksProperties dialog - // and star UI code don't "run a batch in the background". - if (window.document.documentElement.id == "places") - PlacesTransactions.batch(setTags).catch(Components.utils.reportError); - else - Task.spawn(setTags).catch(Components.utils.reportError); - return true; - }), - - _updateTags: Task.async(function*() { - let uris = this._paneInfo.bulkTagging ? - this._paneInfo.uris : [this._paneInfo.uri]; - let currentTags = this._paneInfo.bulkTagging ? - yield this._getCommonTags() : - PlacesUtils.tagging.getTagsForURI(uris[0]); - let anyChanges = yield this._setTagsFromTagsInputField(currentTags, uris); - if (!anyChanges) - return false; - - // The panel could have been closed in the meanwhile. - if (!this._paneInfo) - return false; - - // Ensure the tagsField is in sync, clean it up from empty tags - currentTags = this._paneInfo.bulkTagging ? - this._getCommonTags() : - PlacesUtils.tagging.getTagsForURI(this._paneInfo.uri); - this._initTextField(this._tagsField, currentTags.join(", "), false); - return true; - }), - - /** - * Stores the first-edit field for this dialog, if the passed-in field - * is indeed the first edited field - * @param aNewField - * the id of the field that may be set (without the "editBMPanel_" - * prefix) - */ - _mayUpdateFirstEditField(aNewField) { - // * The first-edit-field behavior is not applied in the multi-edit case - // * if this._firstEditedField is already set, this is not the first field, - // so there's nothing to do - if (this._paneInfo.bulkTagging || this._firstEditedField) - return; - - this._firstEditedField = aNewField; - - // set the pref - var prefs = Cc["@mozilla.org/preferences-service;1"]. - getService(Ci.nsIPrefBranch); - prefs.setCharPref("browser.bookmarks.editDialog.firstEditField", aNewField); - }, - - onNamePickerChange() { - if (this.readOnly || !(this._paneInfo.isItem || this._paneInfo.isTag)) - return; - - // Here we update either the item title or its cached static title - let newTitle = this._namePicker.value; - if (!newTitle && - PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) == PlacesUtils.tagsFolderId) { - // We don't allow setting an empty title for a tag, restore the old one. - this._initNamePicker(); - } - else { - this._mayUpdateFirstEditField("namePicker"); - if (!PlacesUIUtils.useAsyncTransactions) { - let txn = new PlacesEditItemTitleTransaction(this._paneInfo.itemId, - newTitle); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - Task.spawn(function* () { - let guid = this._paneInfo.isTag - ? (yield PlacesUtils.promiseItemGuid(this._paneInfo.itemId)) - : this._paneInfo.itemGuid; - PlacesTransactions.EditTitle({ guid, title: newTitle }) - .transact().catch(Components.utils.reportError); - }).catch(Components.utils.reportError); - } - }, - - onDescriptionFieldChange() { - if (this.readOnly || !this._paneInfo.isItem) - return; - - let itemId = this._paneInfo.itemId; - let description = this._element("descriptionField").value; - if (description != PlacesUIUtils.getItemDescription(this._paneInfo.itemId)) { - let annotation = - { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description }; - if (!PlacesUIUtils.useAsyncTransactions) { - let txn = new PlacesSetItemAnnotationTransaction(itemId, - annotation); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - let guid = this._paneInfo.itemGuid; - PlacesTransactions.Annotate({ guid, annotation }) - .transact().catch(Components.utils.reportError); - } - }, - - onLocationFieldChange() { - if (this.readOnly || !this._paneInfo.isBookmark) - return; - - let newURI; - try { - newURI = PlacesUIUtils.createFixedURI(this._locationField.value); - } - catch (ex) { - // TODO: Bug 1089141 - Provide some feedback about the invalid url. - return; - } - - if (this._paneInfo.uri.equals(newURI)) - return; - - if (!PlacesUIUtils.useAsyncTransactions) { - let txn = new PlacesEditBookmarkURITransaction(this._paneInfo.itemId, newURI); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - let guid = this._paneInfo.itemGuid; - PlacesTransactions.EditUrl({ guid, url: newURI }) - .transact().catch(Components.utils.reportError); - }, - - onKeywordFieldChange() { - if (this.readOnly || !this._paneInfo.isBookmark) - return; - - let itemId = this._paneInfo.itemId; - let oldKeyword = this._keyword; - let keyword = this._keyword = this._keywordField.value; - let postData = this._paneInfo.postData; - if (!PlacesUIUtils.useAsyncTransactions) { - let txn = new PlacesEditBookmarkKeywordTransaction(itemId, - keyword, - postData, - oldKeyword); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - let guid = this._paneInfo.itemGuid; - PlacesTransactions.EditKeyword({ guid, keyword, postData, oldKeyword }) - .transact().catch(Components.utils.reportError); - }, - - onLoadInSidebarCheckboxCommand() { - if (!this.initialized || !this._paneInfo.isBookmark) - return; - - let annotation = { name : PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO }; - if (this._loadInSidebarCheckbox.checked) - annotation.value = true; - - if (!PlacesUIUtils.useAsyncTransactions) { - let itemId = this._paneInfo.itemId; - let txn = new PlacesSetItemAnnotationTransaction(itemId, - annotation); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - let guid = this._paneInfo.itemGuid; - PlacesTransactions.Annotate({ guid, annotation }) - .transact().catch(Components.utils.reportError); - }, - - toggleFolderTreeVisibility() { - var expander = this._element("foldersExpander"); - var folderTreeRow = this._element("folderTreeRow"); - if (!folderTreeRow.collapsed) { - expander.className = "expander-down"; - expander.setAttribute("tooltiptext", - expander.getAttribute("tooltiptextdown")); - folderTreeRow.collapsed = true; - this._element("chooseFolderSeparator").hidden = - this._element("chooseFolderMenuItem").hidden = false; - } - else { - expander.className = "expander-up" - expander.setAttribute("tooltiptext", - expander.getAttribute("tooltiptextup")); - folderTreeRow.collapsed = false; - - // XXXmano: Ideally we would only do this once, but for some odd reason, - // the editable mode set on this tree, together with its collapsed state - // breaks the view. - const FOLDER_TREE_PLACE_URI = - "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" + - PlacesUIUtils.allBookmarksFolderId; - this._folderTree.place = FOLDER_TREE_PLACE_URI; - - this._element("chooseFolderSeparator").hidden = - this._element("chooseFolderMenuItem").hidden = true; - var currentFolder = this._getFolderIdFromMenuList(); - this._folderTree.selectItems([currentFolder]); - this._folderTree.focus(); - } - }, - - _getFolderIdFromMenuList() { - var selectedItem = this._folderMenuList.selectedItem; - NS_ASSERT("folderId" in selectedItem, - "Invalid menuitem in the folders-menulist"); - return selectedItem.folderId; - }, - - /** - * Get the corresponding menu-item in the folder-menu-list for a bookmarks - * folder if such an item exists. Otherwise, this creates a menu-item for the - * folder. If the items-count limit (see MAX_FOLDERS_IN_MENU_LIST) is reached, - * the new item replaces the last menu-item. - * @param aFolderId - * The identifier of the bookmarks folder. - */ - _getFolderMenuItem(aFolderId) { - let menupopup = this._folderMenuList.menupopup; - let menuItem = Array.prototype.find.call( - menupopup.childNodes, menuItem => menuItem.folderId === aFolderId); - if (menuItem !== undefined) - return menuItem; - - // 3 special folders + separator + folder-items-count limit - if (menupopup.childNodes.length == 4 + MAX_FOLDER_ITEM_IN_MENU_LIST) - menupopup.removeChild(menupopup.lastChild); - - return this._appendFolderItemToMenupopup(menupopup, aFolderId); - }, - - onFolderMenuListCommand(aEvent) { - // Set a selectedIndex attribute to show special icons - this._folderMenuList.setAttribute("selectedIndex", - this._folderMenuList.selectedIndex); - - if (aEvent.target.id == "editBMPanel_chooseFolderMenuItem") { - // reset the selection back to where it was and expand the tree - // (this menu-item is hidden when the tree is already visible - let containerId = PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId); - let item = this._getFolderMenuItem(containerId); - this._folderMenuList.selectedItem = item; - // XXXmano HACK: setTimeout 100, otherwise focus goes back to the - // menulist right away - setTimeout(() => this.toggleFolderTreeVisibility(), 100); - return; - } - - // Move the item - let containerId = this._getFolderIdFromMenuList(); - if (PlacesUtils.bookmarks.getFolderIdForItem(this._paneInfo.itemId) != containerId && - this._paneInfo.itemId != containerId) { - if (PlacesUIUtils.useAsyncTransactions) { - Task.spawn(function* () { - let newParentGuid = yield PlacesUtils.promiseItemGuid(containerId); - let guid = this._paneInfo.itemGuid; - yield PlacesTransactions.Move({ guid, newParentGuid }).transact(); - }.bind(this)); - } - else { - let txn = new PlacesMoveItemTransaction(this._paneInfo.itemId, - containerId, - PlacesUtils.bookmarks.DEFAULT_INDEX); - PlacesUtils.transactionManager.doTransaction(txn); - } - - // Mark the containing folder as recently-used if it isn't in the - // static list - if (containerId != PlacesUtils.unfiledBookmarksFolderId && - containerId != PlacesUtils.toolbarFolderId && - containerId != PlacesUtils.bookmarksMenuFolderId) { - this._markFolderAsRecentlyUsed(containerId) - .catch(Components.utils.reportError); - } - - // Auto-show the bookmarks toolbar when adding / moving an item there. - if (containerId == PlacesUtils.toolbarFolderId) { - Services.obs.notifyObservers(null, "autoshow-bookmarks-toolbar", null); - } - } - - // Update folder-tree selection - var folderTreeRow = this._element("folderTreeRow"); - if (!folderTreeRow.collapsed) { - var selectedNode = this._folderTree.selectedNode; - if (!selectedNode || - PlacesUtils.getConcreteItemId(selectedNode) != containerId) - this._folderTree.selectItems([containerId]); - } - }, - - onFolderTreeSelect() { - var selectedNode = this._folderTree.selectedNode; - - // Disable the "New Folder" button if we cannot create a new folder - this._element("newFolderButton") - .disabled = !this._folderTree.insertionPoint || !selectedNode; - - if (!selectedNode) - return; - - var folderId = PlacesUtils.getConcreteItemId(selectedNode); - if (this._getFolderIdFromMenuList() == folderId) - return; - - var folderItem = this._getFolderMenuItem(folderId); - this._folderMenuList.selectedItem = folderItem; - folderItem.doCommand(); - }, - - _markFolderAsRecentlyUsed: Task.async(function* (aFolderId) { - if (!PlacesUIUtils.useAsyncTransactions) { - let txns = []; - - // Expire old unused recent folders. - let annotation = this._getLastUsedAnnotationObject(false); - while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) { - let folderId = this._recentFolders.pop().folderId; - let annoTxn = new PlacesSetItemAnnotationTransaction(folderId, - annotation); - txns.push(annoTxn); - } - - // Mark folder as recently used - annotation = this._getLastUsedAnnotationObject(true); - let annoTxn = new PlacesSetItemAnnotationTransaction(aFolderId, - annotation); - txns.push(annoTxn); - - let aggregate = - new PlacesAggregatedTransaction("Update last used folders", txns); - PlacesUtils.transactionManager.doTransaction(aggregate); - return; - } - - // Expire old unused recent folders. - let guids = []; - while (this._recentFolders.length > MAX_FOLDER_ITEM_IN_MENU_LIST) { - let folderId = this._recentFolders.pop().folderId; - let guid = yield PlacesUtils.promiseItemGuid(folderId); - guids.push(guid); - } - if (guids.length > 0) { - let annotation = this._getLastUsedAnnotationObject(false); - PlacesTransactions.Annotate({ guids, annotation }) - .transact().catch(Components.utils.reportError); - } - - // Mark folder as recently used - let annotation = this._getLastUsedAnnotationObject(true); - let guid = yield PlacesUtils.promiseItemGuid(aFolderId); - PlacesTransactions.Annotate({ guid, annotation }) - .transact().catch(Components.utils.reportError); - }), - - /** - * Returns an object which could then be used to set/unset the - * LAST_USED_ANNO annotation for a folder. - * - * @param aLastUsed - * Whether to set or unset the LAST_USED_ANNO annotation. - * @returns an object representing the annotation which could then be used - * with the transaction manager. - */ - _getLastUsedAnnotationObject(aLastUsed) { - return { name: LAST_USED_ANNO, - value: aLastUsed ? new Date().getTime() : null }; - }, - - _rebuildTagsSelectorList: Task.async(function* () { - let tagsSelector = this._element("tagsSelector"); - let tagsSelectorRow = this._element("tagsSelectorRow"); - if (tagsSelectorRow.collapsed) - return; - - // Save the current scroll position and restore it after the rebuild. - let firstIndex = tagsSelector.getIndexOfFirstVisibleRow(); - let selectedIndex = tagsSelector.selectedIndex; - let selectedTag = selectedIndex >= 0 ? tagsSelector.selectedItem.label - : null; - - while (tagsSelector.hasChildNodes()) { - tagsSelector.removeChild(tagsSelector.lastChild); - } - - let tagsInField = this._getTagsArrayFromTagsInputField(); - let allTags = PlacesUtils.tagging.allTags; - for (tag of allTags) { - let elt = document.createElement("listitem"); - elt.setAttribute("type", "checkbox"); - elt.setAttribute("label", tag); - if (tagsInField.includes(tag)) - elt.setAttribute("checked", "true"); - tagsSelector.appendChild(elt); - if (selectedTag === tag) - selectedIndex = tagsSelector.getIndexOfItem(elt); - } - - // Restore position. - // The listbox allows to scroll only if the required offset doesn't - // overflow its capacity, thus need to adjust the index for removals. - firstIndex = - Math.min(firstIndex, - tagsSelector.itemCount - tagsSelector.getNumberOfVisibleRows()); - tagsSelector.scrollToIndex(firstIndex); - if (selectedIndex >= 0 && tagsSelector.itemCount > 0) { - selectedIndex = Math.min(selectedIndex, tagsSelector.itemCount - 1); - tagsSelector.selectedIndex = selectedIndex; - tagsSelector.ensureIndexIsVisible(selectedIndex); - } - }), - - toggleTagsSelector: Task.async(function* () { - var tagsSelector = this._element("tagsSelector"); - var tagsSelectorRow = this._element("tagsSelectorRow"); - var expander = this._element("tagsSelectorExpander"); - if (tagsSelectorRow.collapsed) { - expander.className = "expander-up"; - expander.setAttribute("tooltiptext", - expander.getAttribute("tooltiptextup")); - tagsSelectorRow.collapsed = false; - yield this._rebuildTagsSelectorList(); - - // This is a no-op if we've added the listener. - tagsSelector.addEventListener("CheckboxStateChange", this, false); - } - else { - expander.className = "expander-down"; - expander.setAttribute("tooltiptext", - expander.getAttribute("tooltiptextdown")); - tagsSelectorRow.collapsed = true; - } - }), - - /** - * Splits "tagsField" element value, returning an array of valid tag strings. - * - * @return Array of tag strings found in the field value. - */ - _getTagsArrayFromTagsInputField() { - let tags = this._element("tagsField").value; - return tags.trim() - .split(/\s*,\s*/) // Split on commas and remove spaces. - .filter(tag => tag.length > 0); // Kill empty tags. - }, - - newFolder: Task.async(function* () { - let ip = this._folderTree.insertionPoint; - - // default to the bookmarks menu folder - if (!ip || ip.itemId == PlacesUIUtils.allBookmarksFolderId) { - ip = new InsertionPoint(PlacesUtils.bookmarksMenuFolderId, - PlacesUtils.bookmarks.DEFAULT_INDEX, - Ci.nsITreeView.DROP_ON); - } - - // XXXmano: add a separate "New Folder" string at some point... - let title = this._element("newFolderButton").label; - if (PlacesUIUtils.useAsyncTransactions) { - let parentGuid = yield ip.promiseGuid(); - yield PlacesTransactions.NewFolder({ parentGuid, title, index: ip.index }) - .transact().catch(Components.utils.reportError); - } - else { - let txn = new PlacesCreateFolderTransaction(title, ip.itemId, ip.index); - PlacesUtils.transactionManager.doTransaction(txn); - } - - this._folderTree.focus(); - this._folderTree.selectItems([ip.itemId]); - PlacesUtils.asContainer(this._folderTree.selectedNode).containerOpen = true; - this._folderTree.selectItems([this._lastNewItem]); - this._folderTree.startEditing(this._folderTree.view.selection.currentIndex, - this._folderTree.columns.getFirstColumn()); - }), - - // nsIDOMEventListener - handleEvent(aEvent) { - switch (aEvent.type) { - case "CheckboxStateChange": - // Update the tags field when items are checked/unchecked in the listbox - let tags = this._getTagsArrayFromTagsInputField(); - let tagCheckbox = aEvent.target; - - let curTagIndex = tags.indexOf(tagCheckbox.label); - let tagsSelector = this._element("tagsSelector"); - tagsSelector.selectedItem = tagCheckbox; - - if (tagCheckbox.checked) { - if (curTagIndex == -1) - tags.push(tagCheckbox.label); - } - else if (curTagIndex != -1) { - tags.splice(curTagIndex, 1); - } - this._element("tagsField").value = tags.join(", "); - this._updateTags(); - break; - case "unload": - this.uninitPanel(false); - break; - } - }, - - _initTagsField: Task.async(function* () { - let tags; - if (this._paneInfo.isURI) - tags = PlacesUtils.tagging.getTagsForURI(this._paneInfo.uri); - else if (this._paneInfo.bulkTagging) - tags = this._getCommonTags(); - else - throw new Error("_promiseTagsStr called unexpectedly"); - - this._initTextField(this._tagsField, tags.join(", ")); - }), - - _onTagsChange(aItemId) { - let paneInfo = this._paneInfo; - let updateTagsField = false; - if (paneInfo.isURI) { - if (paneInfo.isBookmark && aItemId == paneInfo.itemId) { - updateTagsField = true; - } - else if (!paneInfo.isBookmark) { - let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId); - updateTagsField = changedURI.equals(paneInfo.uri); - } - } - else if (paneInfo.bulkTagging) { - let changedURI = PlacesUtils.bookmarks.getBookmarkURI(aItemId); - if (paneInfo.uris.some(uri => uri.equals(changedURI))) { - updateTagsField = true; - delete this._paneInfo._cachedCommonTags; - } - } - else { - throw new Error("_onTagsChange called unexpectedly"); - } - - if (updateTagsField) - this._initTagsField().catch(Components.utils.reportError); - - // Any tags change should be reflected in the tags selector. - if (this._element("tagsSelector")) - this._rebuildTagsSelectorList().catch(Components.utils.reportError); - }, - - _onItemTitleChange(aItemId, aNewTitle) { - if (!this._paneInfo.isBookmark) - return; - if (aItemId == this._paneInfo.itemId) { - this._paneInfo.title = aNewTitle; - this._initTextField(this._namePicker, aNewTitle); - } - else if (this._paneInfo.visibleRows.has("folderRow")) { - // If the title of a folder which is listed within the folders - // menulist has been changed, we need to update the label of its - // representing element. - let menupopup = this._folderMenuList.menupopup; - for (menuitem of menupopup.childNodes) { - if ("folderId" in menuitem && menuitem.folderId == aItemId) { - menuitem.label = aNewTitle; - break; - } - } - } - }, - - // nsINavBookmarkObserver - onItemChanged(aItemId, aProperty, aIsAnnotationProperty, aValue, - aLastModified, aItemType) { - if (aProperty == "tags" && this._paneInfo.visibleRows.has("tagsRow")) { - this._onTagsChange(aItemId); - } - else if (aProperty == "title" && this._paneInfo.isItem) { - // This also updates titles of folders in the folder menu list. - this._onItemTitleChange(aItemId, aValue); - } - else if (!this._paneInfo.isItem || this._paneInfo.itemId != aItemId) { - return; - } - - switch (aProperty) { - case "uri": - let newURI = NetUtil.newURI(aValue); - if (!newURI.equals(this._paneInfo.uri)) { - this._paneInfo.uri = newURI; - if (this._paneInfo.visibleRows.has("locationRow")) - this._initLocationField(); - - if (this._paneInfo.visibleRows.has("tagsRow")) { - delete this._paneInfo._cachedCommonTags; - this._onTagsChange(aItemId); - } - } - break; - case "keyword": - if (this._paneInfo.visibleRows.has("keywordRow")) - this._initKeywordField(aValue).catch(Components.utils.reportError); - break; - case PlacesUIUtils.DESCRIPTION_ANNO: - if (this._paneInfo.visibleRows.has("descriptionRow")) - this._initDescriptionField(); - break; - case PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO: - if (this._paneInfo.visibleRows.has("loadInSidebarCheckbox")) - this._initLoadInSidebar(); - break; - } - }, - - onItemMoved(aItemId, aOldParent, aOldIndex, - aNewParent, aNewIndex, aItemType) { - if (!this._paneInfo.isItem || - !this._paneInfo.visibleRows.has("folderPicker") || - this._paneInfo.itemId != aItemOd || - aNewParent == this._getFolderIdFromMenuList()) { - return; - } - - // Just setting selectItem _does not_ trigger oncommand, so we don't - // recurse. - this._folderMenuList.selectedItem = this._getFolderMenuItem(aNewParent); - }, - - onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI) { - this._lastNewItem = aItemId; - }, - - onItemRemoved() { }, - onBeginUpdateBatch() { }, - onEndUpdateBatch() { }, - onItemVisited() { }, -}; - - -for (let elt of ["folderMenuList", "folderTree", "namePicker", - "locationField", "descriptionField", "keywordField", - "tagsField", "loadInSidebarCheckbox"]) { - let eltScoped = elt; - XPCOMUtils.defineLazyGetter(gEditItemOverlay, `_${eltScoped}`, - () => gEditItemOverlay._element(eltScoped)); -} diff --git a/browser/components/places/content/editBookmarkOverlay.xul b/browser/components/places/content/editBookmarkOverlay.xul deleted file mode 100644 index 140e752c0..000000000 --- a/browser/components/places/content/editBookmarkOverlay.xul +++ /dev/null @@ -1,188 +0,0 @@ -<!-- 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/. --> - -<!DOCTYPE overlay [ -<!ENTITY % editBookmarkOverlayDTD SYSTEM "chrome://browser/locale/places/editBookmarkOverlay.dtd"> -%editBookmarkOverlayDTD; -]> - -<?xml-stylesheet href="chrome://browser/skin/places/editBookmarkOverlay.css"?> -<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> - -<overlay id="editBookmarkOverlay" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <vbox id="editBookmarkPanelContent" flex="1"> - <hbox id="editBMPanel_selectionCount" pack="center"> - <label id="editBMPanel_itemsCountText"/> - </hbox> - - <grid id="editBookmarkPanelGrid" flex="1"> - <columns id="editBMPanel_columns"> - <column id="editBMPanel_labelColumn" /> - <column flex="1" id="editBMPanel_editColumn" /> - </columns> - <rows id="editBMPanel_rows"> - <row id="editBMPanel_nameRow" - align="center" - collapsed="true"> - <label value="&editBookmarkOverlay.name.label;" - class="editBMPanel_rowLabel" - accesskey="&editBookmarkOverlay.name.accesskey;" - control="editBMPanel_namePicker"/> - <textbox id="editBMPanel_namePicker" - onchange="gEditItemOverlay.onNamePickerChange();"/> - </row> - - <row id="editBMPanel_locationRow" - align="center" - collapsed="true"> - <label value="&editBookmarkOverlay.location.label;" - class="editBMPanel_rowLabel" - accesskey="&editBookmarkOverlay.location.accesskey;" - control="editBMPanel_locationField"/> - <textbox id="editBMPanel_locationField" - class="uri-element" - onchange="gEditItemOverlay.onLocationFieldChange();"/> - </row> - - <row id="editBMPanel_folderRow" - align="center" - collapsed="true"> - <label value="&editBookmarkOverlay.folder.label;" - class="editBMPanel_rowLabel" - control="editBMPanel_folderMenuList"/> - <hbox flex="1" align="center"> - <menulist id="editBMPanel_folderMenuList" - class="folder-icon" - flex="1" - oncommand="gEditItemOverlay.onFolderMenuListCommand(event);"> - <menupopup> - <!-- Static item for special folders --> - <menuitem id="editBMPanel_toolbarFolderItem" - class="menuitem-iconic folder-icon"/> - <menuitem id="editBMPanel_bmRootItem" - class="menuitem-iconic folder-icon"/> - <menuitem id="editBMPanel_unfiledRootItem" - class="menuitem-iconic folder-icon"/> - <menuseparator id="editBMPanel_chooseFolderSeparator"/> - <menuitem id="editBMPanel_chooseFolderMenuItem" - label="&editBookmarkOverlay.choose.label;" - class="menuitem-iconic folder-icon"/> - <menuseparator id="editBMPanel_foldersSeparator" hidden="true"/> - </menupopup> - </menulist> - <button id="editBMPanel_foldersExpander" - class="expander-down" - tooltiptext="&editBookmarkOverlay.foldersExpanderDown.tooltip;" - tooltiptextdown="&editBookmarkOverlay.foldersExpanderDown.tooltip;" - tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;" - oncommand="gEditItemOverlay.toggleFolderTreeVisibility();"/> - </hbox> - </row> - - <row id="editBMPanel_folderTreeRow" - collapsed="true" - flex="1"> - <spacer/> - <vbox flex="1"> - <tree id="editBMPanel_folderTree" - flex="1" - class="placesTree" - type="places" - height="150" - minheight="150" - editable="true" - onselect="gEditItemOverlay.onFolderTreeSelect();" - hidecolumnpicker="true"> - <treecols> - <treecol anonid="title" flex="1" primary="true" hideheader="true"/> - </treecols> - <treechildren flex="1"/> - </tree> - - <hbox id="editBMPanel_newFolderBox"> - <button label="&editBookmarkOverlay.newFolderButton.label;" - id="editBMPanel_newFolderButton" - accesskey="&editBookmarkOverlay.newFolderButton.accesskey;" - oncommand="gEditItemOverlay.newFolder().catch(Components.utils.reportError);"/> - </hbox> - </vbox> - </row> - - <row id="editBMPanel_tagsRow" - align="center" - collapsed="true"> - <label value="&editBookmarkOverlay.tags.label;" - class="editBMPanel_rowLabel" - accesskey="&editBookmarkOverlay.tags.accesskey;" - control="editBMPanel_tagsField"/> - <hbox flex="1" align="center"> - <textbox id="editBMPanel_tagsField" - type="autocomplete" - class="padded" - flex="1" - autocompletesearch="places-tag-autocomplete" - completedefaultindex="true" - tabscrolling="true" - showcommentcolumn="true" - placeholder="&editBookmarkOverlay.tagsEmptyDesc.label;" - onchange="gEditItemOverlay.onTagsFieldChange();"/> - <button id="editBMPanel_tagsSelectorExpander" - class="expander-down" - tooltiptext="&editBookmarkOverlay.tagsExpanderDown.tooltip;" - tooltiptextdown="&editBookmarkOverlay.tagsExpanderDown.tooltip;" - tooltiptextup="&editBookmarkOverlay.expanderUp.tooltip;" - oncommand="gEditItemOverlay.toggleTagsSelector();"/> - </hbox> - </row> - - <row id="editBMPanel_tagsSelectorRow" - align="center" - collapsed="true"> - <spacer/> - <listbox id="editBMPanel_tagsSelector" - height="150"/> - </row> - - <row id="editBMPanel_keywordRow" - align="center" - collapsed="true"> - <observes element="additionalInfoBroadcaster" attribute="hidden"/> - <label value="&editBookmarkOverlay.keyword.label;" - class="editBMPanel_rowLabel" - accesskey="&editBookmarkOverlay.keyword.accesskey;" - control="editBMPanel_keywordField"/> - <textbox id="editBMPanel_keywordField" - onchange="gEditItemOverlay.onKeywordFieldChange();"/> - </row> - - <row id="editBMPanel_descriptionRow" - collapsed="true"> - <observes element="additionalInfoBroadcaster" attribute="hidden"/> - <label value="&editBookmarkOverlay.description.label;" - class="editBMPanel_rowLabel" - accesskey="&editBookmarkOverlay.description.accesskey;" - control="editBMPanel_descriptionField"/> - <textbox id="editBMPanel_descriptionField" - multiline="true" - rows="4" - onchange="gEditItemOverlay.onDescriptionFieldChange();"/> - </row> - </rows> - </grid> - - <checkbox id="editBMPanel_loadInSidebarCheckbox" - collapsed="true" - label="&editBookmarkOverlay.loadInSidebar.label;" - accesskey="&editBookmarkOverlay.loadInSidebar.accesskey;" - oncommand="gEditItemOverlay.onLoadInSidebarCheckboxCommand();"> - <observes element="additionalInfoBroadcaster" attribute="hidden"/> - </checkbox> - - <!-- If the ids are changing or additional fields are being added, be sure - to sync the values in places.js --> - <broadcaster id="additionalInfoBroadcaster"/> - </vbox> -</overlay> diff --git a/browser/components/places/content/history-panel.js b/browser/components/places/content/history-panel.js deleted file mode 100644 index 20dbbb5bd..000000000 --- a/browser/components/places/content/history-panel.js +++ /dev/null @@ -1,98 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 4 -*- */ -/* 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/. */ - -Components.utils.import("resource://gre/modules/TelemetryStopwatch.jsm"); - -var gHistoryTree; -var gSearchBox; -var gHistoryGrouping = ""; -var gSearching = false; - -function HistorySidebarInit() -{ - gHistoryTree = document.getElementById("historyTree"); - gSearchBox = document.getElementById("search-box"); - - gHistoryGrouping = document.getElementById("viewButton"). - getAttribute("selectedsort"); - - if (gHistoryGrouping == "site") - document.getElementById("bysite").setAttribute("checked", "true"); - else if (gHistoryGrouping == "visited") - document.getElementById("byvisited").setAttribute("checked", "true"); - else if (gHistoryGrouping == "lastvisited") - document.getElementById("bylastvisited").setAttribute("checked", "true"); - else if (gHistoryGrouping == "dayandsite") - document.getElementById("bydayandsite").setAttribute("checked", "true"); - else - document.getElementById("byday").setAttribute("checked", "true"); - - searchHistory(""); -} - -function GroupBy(groupingType) -{ - gHistoryGrouping = groupingType; - searchHistory(gSearchBox.value); -} - -function searchHistory(aInput) -{ - var query = PlacesUtils.history.getNewQuery(); - var options = PlacesUtils.history.getNewQueryOptions(); - - const NHQO = Ci.nsINavHistoryQueryOptions; - var sortingMode; - var resultType; - - switch (gHistoryGrouping) { - case "visited": - resultType = NHQO.RESULTS_AS_URI; - sortingMode = NHQO.SORT_BY_VISITCOUNT_DESCENDING; - break; - case "lastvisited": - resultType = NHQO.RESULTS_AS_URI; - sortingMode = NHQO.SORT_BY_DATE_DESCENDING; - break; - case "dayandsite": - resultType = NHQO.RESULTS_AS_DATE_SITE_QUERY; - break; - case "site": - resultType = NHQO.RESULTS_AS_SITE_QUERY; - sortingMode = NHQO.SORT_BY_TITLE_ASCENDING; - break; - case "day": - default: - resultType = NHQO.RESULTS_AS_DATE_QUERY; - break; - } - - if (aInput) { - query.searchTerms = aInput; - if (gHistoryGrouping != "visited" && gHistoryGrouping != "lastvisited") { - sortingMode = NHQO.SORT_BY_FRECENCY_DESCENDING; - resultType = NHQO.RESULTS_AS_URI; - } - } - - options.sortingMode = sortingMode; - options.resultType = resultType; - options.includeHidden = !!aInput; - - if (gHistoryGrouping == "lastvisited") - this.TelemetryStopwatch.start("HISTORY_LASTVISITED_TREE_QUERY_TIME_MS"); - - // call load() on the tree manually - // instead of setting the place attribute in history-panel.xul - // otherwise, we will end up calling load() twice - gHistoryTree.load([query], options); - - if (gHistoryGrouping == "lastvisited") - this.TelemetryStopwatch.finish("HISTORY_LASTVISITED_TREE_QUERY_TIME_MS"); -} - -window.addEventListener("SidebarFocused", - () => gSearchBox.focus(), - false); diff --git a/browser/components/places/content/history-panel.xul b/browser/components/places/content/history-panel.xul deleted file mode 100644 index d1c875a63..000000000 --- a/browser/components/places/content/history-panel.xul +++ /dev/null @@ -1,95 +0,0 @@ -<?xml version="1.0"?> <!-- -*- Mode: xml; indent-tabs-mode: nil; -*- --> - -# 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/. - -<?xml-stylesheet href="chrome://browser/content/places/places.css"?> -<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> -<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> - -<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?> -<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> - -<!DOCTYPE page [ -<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd"> -%placesDTD; -]> - -<!-- we need to keep id="history-panel" for upgrade and switching - between versions of the browser --> - -<page id="history-panel" orient="vertical" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - onload="HistorySidebarInit();" - onunload="SidebarUtils.setMouseoverURL('');"> - - <script type="application/javascript" - src="chrome://browser/content/bookmarks/sidebarUtils.js"/> - <script type="application/javascript" - src="chrome://browser/content/places/history-panel.js"/> - - <commandset id="editMenuCommands"/> - <commandset id="placesCommands"/> - - <keyset id="editMenuKeys"> -#ifdef XP_MACOSX - <key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/> -#endif - </keyset> - - <!-- required to overlay the context menu --> - <menupopup id="placesContext"/> - - <!-- Bookmarks and history tooltip --> - <tooltip id="bhTooltip"/> - - <hbox id="sidebar-search-container" align="center"> - <label id="sidebar-search-label" - value="&find.label;" accesskey="&find.accesskey;" - control="search-box"/> - <textbox id="search-box" flex="1" type="search" class="compact" - aria-controls="historyTree" - oncommand="searchHistory(this.value);"/> - <button id="viewButton" style="min-width:0px !important;" type="menu" - label="&view.label;" accesskey="&view.accesskey;" selectedsort="day" - persist="selectedsort"> - <menupopup> - <menuitem id="bydayandsite" label="&byDayAndSite.label;" - accesskey="&byDayAndSite.accesskey;" type="radio" - oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'dayandsite'); GroupBy('dayandsite');"/> - <menuitem id="bysite" label="&bySite.label;" - accesskey="&bySite.accesskey;" type="radio" - oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'site'); GroupBy('site');"/> - <menuitem id="byday" label="&byDate.label;" - accesskey="&byDate.accesskey;" - type="radio" - oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'day'); GroupBy('day');"/> - <menuitem id="byvisited" label="&byMostVisited.label;" - accesskey="&byMostVisited.accesskey;" - type="radio" - oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'visited'); GroupBy('visited');"/> - <menuitem id="bylastvisited" label="&byLastVisited.label;" - accesskey="&byLastVisited.accesskey;" - type="radio" - oncommand="this.parentNode.parentNode.setAttribute('selectedsort', 'lastvisited'); GroupBy('lastvisited');"/> - </menupopup> - </button> - </hbox> - - <tree id="historyTree" - class="sidebar-placesTree" - flex="1" - type="places" - context="placesContext" - hidecolumnpicker="true" - onkeypress="SidebarUtils.handleTreeKeyPress(event);" - onclick="SidebarUtils.handleTreeClick(this, event, true);" - onmousemove="SidebarUtils.handleTreeMouseMove(event);" - onmouseout="SidebarUtils.setMouseoverURL('');"> - <treecols> - <treecol id="title" flex="1" primary="true" hideheader="true"/> - </treecols> - <treechildren class="sidebar-placesTreechildren" flex="1" tooltip="bhTooltip"/> - </tree> -</page> diff --git a/browser/components/places/content/menu.xml b/browser/components/places/content/menu.xml deleted file mode 100644 index f791d76fb..000000000 --- a/browser/components/places/content/menu.xml +++ /dev/null @@ -1,633 +0,0 @@ -<?xml version="1.0"?> - -<!-- 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/. --> - -<bindings id="placesMenuBindings" - xmlns="http://www.mozilla.org/xbl" - xmlns:xbl="http://www.mozilla.org/xbl" - xmlns:html="http://www.w3.org/1999/xhtml" - xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <binding id="places-popup-base" - extends="chrome://global/content/bindings/popup.xml#popup"> - <content> - <xul:hbox flex="1"> - <xul:vbox class="menupopup-drop-indicator-bar" hidden="true"> - <xul:image class="menupopup-drop-indicator" mousethrough="always"/> - </xul:vbox> - <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical" - smoothscroll="false"> - <children/> - </xul:arrowscrollbox> - </xul:hbox> - </content> - - <implementation> - - <field name="AppConstants" readonly="true"> - (Components.utils.import("resource://gre/modules/AppConstants.jsm", {})).AppConstants; - </field> - - <field name="_indicatorBar"> - document.getAnonymousElementByAttribute(this, "class", - "menupopup-drop-indicator-bar"); - </field> - - <field name="_scrollBox"> - document.getAnonymousElementByAttribute(this, "class", - "popup-internal-box"); - </field> - - <!-- This is the view that manage the popup --> - <field name="_rootView">PlacesUIUtils.getViewForNode(this);</field> - - <!-- Check if we should hide the drop indicator for the target --> - <method name="_hideDropIndicator"> - <parameter name="aEvent"/> - <body><![CDATA[ - let target = aEvent.target; - - // Don't draw the drop indicator outside of markers or if current - // node is not a Places node. - let betweenMarkers = - (this._startMarker.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_FOLLOWING) && - (this._endMarker.compareDocumentPosition(target) & Node.DOCUMENT_POSITION_PRECEDING); - - // Hide the dropmarker if current node is not a Places node. - return !(target && target._placesNode && betweenMarkers); - ]]></body> - </method> - - <!-- This function returns information about where to drop when - dragging over this popup insertion point --> - <method name="_getDropPoint"> - <parameter name="aEvent"/> - <body><![CDATA[ - // Can't drop if the menu isn't a folder - let resultNode = this._placesNode; - - if (!PlacesUtils.nodeIsFolder(resultNode) || - PlacesControllerDragHelper.disallowInsertion(resultNode)) { - return null; - } - - var dropPoint = { ip: null, folderElt: null }; - - // The element we are dragging over - let elt = aEvent.target; - if (elt.localName == "menupopup") - elt = elt.parentNode; - - // Calculate positions taking care of arrowscrollbox - let scrollbox = this._scrollBox; - let eventY = aEvent.layerY + (scrollbox.boxObject.y - this.boxObject.y); - let scrollboxOffset = scrollbox.scrollBoxObject.y - - (scrollbox.boxObject.y - this.boxObject.y); - let eltY = elt.boxObject.y - scrollboxOffset; - let eltHeight = elt.boxObject.height; - - if (!elt._placesNode) { - // If we are dragging over a non places node drop at the end. - dropPoint.ip = new InsertionPoint( - PlacesUtils.getConcreteItemId(resultNode), - -1, - Ci.nsITreeView.DROP_ON); - // We can set folderElt if we are dropping over a static menu that - // has an internal placespopup. - let isMenu = elt.localName == "menu" || - (elt.localName == "toolbarbutton" && - elt.getAttribute("type") == "menu"); - if (isMenu && elt.lastChild && - elt.lastChild.hasAttribute("placespopup")) - dropPoint.folderElt = elt; - return dropPoint; - } - - let tagName = PlacesUtils.nodeIsTagQuery(elt._placesNode) ? - elt._placesNode.title : null; - if ((PlacesUtils.nodeIsFolder(elt._placesNode) && - !PlacesUIUtils.isContentsReadOnly(elt._placesNode)) || - PlacesUtils.nodeIsTagQuery(elt._placesNode)) { - // This is a folder or a tag container. - if (eventY - eltY < eltHeight * 0.20) { - // If mouse is in the top part of the element, drop above folder. - dropPoint.ip = new InsertionPoint( - PlacesUtils.getConcreteItemId(resultNode), - -1, - Ci.nsITreeView.DROP_BEFORE, - tagName, - elt._placesNode.itemId); - return dropPoint; - } - else if (eventY - eltY < eltHeight * 0.80) { - // If mouse is in the middle of the element, drop inside folder. - dropPoint.ip = new InsertionPoint( - PlacesUtils.getConcreteItemId(elt._placesNode), - -1, - Ci.nsITreeView.DROP_ON, - tagName); - dropPoint.folderElt = elt; - return dropPoint; - } - } - else if (eventY - eltY <= eltHeight / 2) { - // This is a non-folder node or a readonly folder. - // If the mouse is above the middle, drop above this item. - dropPoint.ip = new InsertionPoint( - PlacesUtils.getConcreteItemId(resultNode), - -1, - Ci.nsITreeView.DROP_BEFORE, - tagName, - elt._placesNode.itemId); - return dropPoint; - } - - // Drop below the item. - dropPoint.ip = new InsertionPoint( - PlacesUtils.getConcreteItemId(resultNode), - -1, - Ci.nsITreeView.DROP_AFTER, - tagName, - elt._placesNode.itemId); - return dropPoint; - ]]></body> - </method> - - <!-- Sub-menus should be opened when the mouse drags over them, and closed - when the mouse drags off. The overFolder object manages opening and - closing of folders when the mouse hovers. --> - <field name="_overFolder"><![CDATA[({ - _self: this, - _folder: {elt: null, - openTimer: null, - hoverTime: 350, - closeTimer: null}, - _closeMenuTimer: null, - - get elt() { - return this._folder.elt; - }, - set elt(val) { - return this._folder.elt = val; - }, - - get openTimer() { - return this._folder.openTimer; - }, - set openTimer(val) { - return this._folder.openTimer = val; - }, - - get hoverTime() { - return this._folder.hoverTime; - }, - set hoverTime(val) { - return this._folder.hoverTime = val; - }, - - get closeTimer() { - return this._folder.closeTimer; - }, - set closeTimer(val) { - return this._folder.closeTimer = val; - }, - - get closeMenuTimer() { - return this._closeMenuTimer; - }, - set closeMenuTimer(val) { - return this._closeMenuTimer = val; - }, - - setTimer: function OF__setTimer(aTime) { - var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); - timer.initWithCallback(this, aTime, timer.TYPE_ONE_SHOT); - return timer; - }, - - notify: function OF__notify(aTimer) { - // Function to process all timer notifications. - - if (aTimer == this._folder.openTimer) { - // Timer to open a submenu that's being dragged over. - this._folder.elt.lastChild.setAttribute("autoopened", "true"); - this._folder.elt.lastChild.showPopup(this._folder.elt); - this._folder.openTimer = null; - } - - else if (aTimer == this._folder.closeTimer) { - // Timer to close a submenu that's been dragged off of. - // Only close the submenu if the mouse isn't being dragged over any - // of its child menus. - var draggingOverChild = PlacesControllerDragHelper - .draggingOverChildNode(this._folder.elt); - if (draggingOverChild) - this._folder.elt = null; - this.clear(); - - // Close any parent folders which aren't being dragged over. - // (This is necessary because of the above code that keeps a folder - // open while its children are being dragged over.) - if (!draggingOverChild) - this.closeParentMenus(); - } - - else if (aTimer == this.closeMenuTimer) { - // Timer to close this menu after the drag exit. - var popup = this._self; - // if we are no more dragging we can leave the menu open to allow - // for better D&D bookmark organization - if (PlacesControllerDragHelper.getSession() && - !PlacesControllerDragHelper.draggingOverChildNode(popup.parentNode)) { - popup.hidePopup(); - // Close any parent menus that aren't being dragged over; - // otherwise they'll stay open because they couldn't close - // while this menu was being dragged over. - this.closeParentMenus(); - } - this._closeMenuTimer = null; - } - }, - - // Helper function to close all parent menus of this menu, - // as long as none of the parent's children are currently being - // dragged over. - closeParentMenus: function OF__closeParentMenus() { - var popup = this._self; - var parent = popup.parentNode; - while (parent) { - if (parent.localName == "menupopup" && parent._placesNode) { - if (PlacesControllerDragHelper.draggingOverChildNode(parent.parentNode)) - break; - parent.hidePopup(); - } - parent = parent.parentNode; - } - }, - - // The mouse is no longer dragging over the stored menubutton. - // Close the menubutton, clear out drag styles, and clear all - // timers for opening/closing it. - clear: function OF__clear() { - if (this._folder.elt && this._folder.elt.lastChild) { - if (!this._folder.elt.lastChild.hasAttribute("dragover")) - this._folder.elt.lastChild.hidePopup(); - // remove menuactive style - this._folder.elt.removeAttribute("_moz-menuactive"); - this._folder.elt = null; - } - if (this._folder.openTimer) { - this._folder.openTimer.cancel(); - this._folder.openTimer = null; - } - if (this._folder.closeTimer) { - this._folder.closeTimer.cancel(); - this._folder.closeTimer = null; - } - } - })]]></field> - - <method name="_cleanupDragDetails"> - <body><![CDATA[ - // Called on dragend and drop. - PlacesControllerDragHelper.currentDropTarget = null; - this._rootView._draggedElt = null; - this.removeAttribute("dragover"); - this.removeAttribute("dragstart"); - this._indicatorBar.hidden = true; - ]]></body> - </method> - - </implementation> - - <handlers> - <handler event="DOMMenuItemActive"><![CDATA[ - let elt = event.target; - if (elt.parentNode != this) - return; - - if (this.AppConstants.platform === "macosx") { - // XXX: The following check is a temporary hack until bug 420033 is - // resolved. - let parentElt = elt.parent; - while (parentElt) { - if (parentElt.id == "bookmarksMenuPopup" || - parentElt.id == "goPopup") - return; - - parentElt = parentElt.parentNode; - } - } - - if (window.XULBrowserWindow) { - let elt = event.target; - let placesNode = elt._placesNode; - - var linkURI; - if (placesNode && PlacesUtils.nodeIsURI(placesNode)) - linkURI = placesNode.uri; - else if (elt.hasAttribute("targetURI")) - linkURI = elt.getAttribute("targetURI"); - - if (linkURI) - window.XULBrowserWindow.setOverLink(linkURI, null); - } - ]]></handler> - - <handler event="DOMMenuItemInactive"><![CDATA[ - let elt = event.target; - if (elt.parentNode != this) - return; - - if (window.XULBrowserWindow) - window.XULBrowserWindow.setOverLink("", null); - ]]></handler> - - <handler event="dragstart"><![CDATA[ - let elt = event.target; - if (!elt._placesNode) - return; - - let draggedElt = elt._placesNode; - - // Force a copy action if parent node is a query or we are dragging a - // not-removable node. - if (!PlacesControllerDragHelper.canMoveNode(draggedElt, elt)) - event.dataTransfer.effectAllowed = "copyLink"; - - // Activate the view and cache the dragged element. - this._rootView._draggedElt = draggedElt; - this._rootView.controller.setDataTransfer(event); - this.setAttribute("dragstart", "true"); - event.stopPropagation(); - ]]></handler> - - <handler event="drop"><![CDATA[ - PlacesControllerDragHelper.currentDropTarget = event.target; - - let dropPoint = this._getDropPoint(event); - if (dropPoint && dropPoint.ip) { - PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer) - .then(null, Components.utils.reportError); - event.preventDefault(); - } - - this._cleanupDragDetails(); - event.stopPropagation(); - ]]></handler> - - <handler event="dragover"><![CDATA[ - PlacesControllerDragHelper.currentDropTarget = event.target; - let dt = event.dataTransfer; - - let dropPoint = this._getDropPoint(event); - if (!dropPoint || !dropPoint.ip || - !PlacesControllerDragHelper.canDrop(dropPoint.ip, dt)) { - this._indicatorBar.hidden = true; - event.stopPropagation(); - return; - } - - // Mark this popup as being dragged over. - this.setAttribute("dragover", "true"); - - if (dropPoint.folderElt) { - // We are dragging over a folder. - // _overFolder should take the care of opening it on a timer. - if (this._overFolder.elt && - this._overFolder.elt != dropPoint.folderElt) { - // We are dragging over a new folder, let's clear old values - this._overFolder.clear(); - } - if (!this._overFolder.elt) { - this._overFolder.elt = dropPoint.folderElt; - // Create the timer to open this folder. - this._overFolder.openTimer = this._overFolder - .setTimer(this._overFolder.hoverTime); - } - // Since we are dropping into a folder set the corresponding style. - dropPoint.folderElt.setAttribute("_moz-menuactive", true); - } - else { - // We are not dragging over a folder. - // Clear out old _overFolder information. - this._overFolder.clear(); - } - - // Autoscroll the popup strip if we drag over the scroll buttons. - let anonid = event.originalTarget.getAttribute('anonid'); - let scrollDir = 0; - if (anonid == "scrollbutton-up") { - scrollDir = -1; - } else if (anonid == "scrollbutton-down") { - scrollDir = 1; - } - if (scrollDir != 0) { - this._scrollBox.scrollByIndex(scrollDir, false); - } - - // Check if we should hide the drop indicator for this target. - if (dropPoint.folderElt || this._hideDropIndicator(event)) { - this._indicatorBar.hidden = true; - event.preventDefault(); - event.stopPropagation(); - return; - } - - // We should display the drop indicator relative to the arrowscrollbox. - let sbo = this._scrollBox.scrollBoxObject; - let newMarginTop = 0; - if (scrollDir == 0) { - let elt = this.firstChild; - while (elt && event.screenY > elt.boxObject.screenY + - elt.boxObject.height / 2) - elt = elt.nextSibling; - newMarginTop = elt ? elt.boxObject.screenY - sbo.screenY : - sbo.height; - } - else if (scrollDir == 1) - newMarginTop = sbo.height; - - // Set the new marginTop based on arrowscrollbox. - newMarginTop += sbo.y - this._scrollBox.boxObject.y; - this._indicatorBar.firstChild.style.marginTop = newMarginTop + "px"; - this._indicatorBar.hidden = false; - - event.preventDefault(); - event.stopPropagation(); - ]]></handler> - - <handler event="dragexit"><![CDATA[ - PlacesControllerDragHelper.currentDropTarget = null; - this.removeAttribute("dragover"); - - // If we have not moved to a valid new target clear the drop indicator - // this happens when moving out of the popup. - let target = event.relatedTarget; - if (!target || !this.contains(target)) - this._indicatorBar.hidden = true; - - // Close any folder being hovered over - if (this._overFolder.elt) { - this._overFolder.closeTimer = this._overFolder - .setTimer(this._overFolder.hoverTime); - } - - // The autoopened attribute is set when this folder was automatically - // opened after the user dragged over it. If this attribute is set, - // auto-close the folder on drag exit. - // We should also try to close this popup if the drag has started - // from here, the timer will check if we are dragging over a child. - if (this.hasAttribute("autoopened") || - this.hasAttribute("dragstart")) { - this._overFolder.closeMenuTimer = this._overFolder - .setTimer(this._overFolder.hoverTime); - } - - event.stopPropagation(); - ]]></handler> - - <handler event="dragend"><![CDATA[ - this._cleanupDragDetails(); - ]]></handler> - - </handlers> - </binding> - - <!-- Most of this is copied from the arrowpanel binding in popup.xml --> - <binding id="places-popup-arrow" - extends="chrome://browser/content/places/menu.xml#places-popup-base"> - <content flip="both" side="top" position="bottomcenter topright"> - <xul:vbox anonid="container" class="panel-arrowcontainer" flex="1" - xbl:inherits="side,panelopen"> - <xul:box anonid="arrowbox" class="panel-arrowbox"> - <xul:image anonid="arrow" class="panel-arrow" xbl:inherits="side"/> - </xul:box> - <xul:box class="panel-arrowcontent" xbl:inherits="side,align,dir,orient,pack" flex="1"> - <xul:vbox class="menupopup-drop-indicator-bar" hidden="true"> - <xul:image class="menupopup-drop-indicator" mousethrough="always"/> - </xul:vbox> - <xul:arrowscrollbox class="popup-internal-box" flex="1" orient="vertical" - smoothscroll="false"> - <children/> - </xul:arrowscrollbox> - </xul:box> - </xul:vbox> - </content> - - <implementation> - <constructor><![CDATA[ - this.style.pointerEvents = 'none'; - ]]></constructor> - <method name="adjustArrowPosition"> - <body><![CDATA[ - var arrow = document.getAnonymousElementByAttribute(this, "anonid", "arrow"); - - var anchor = this.anchorNode; - if (!anchor) { - arrow.hidden = true; - return; - } - - var container = document.getAnonymousElementByAttribute(this, "anonid", "container"); - var arrowbox = document.getAnonymousElementByAttribute(this, "anonid", "arrowbox"); - - var position = this.alignmentPosition; - var offset = this.alignmentOffset; - - this.setAttribute("arrowposition", position); - - // if this panel has a "sliding" arrow, we may have previously set margins... - arrowbox.style.removeProperty("transform"); - if (position.indexOf("start_") == 0 || position.indexOf("end_") == 0) { - container.orient = "horizontal"; - arrowbox.orient = "vertical"; - if (position.indexOf("_after") > 0) { - arrowbox.pack = "end"; - } else { - arrowbox.pack = "start"; - } - arrowbox.style.transform = "translate(0, " + -offset + "px)"; - - // The assigned side stays the same regardless of direction. - var isRTL = (window.getComputedStyle(this).direction == "rtl"); - - if (position.indexOf("start_") == 0) { - container.dir = "reverse"; - this.setAttribute("side", isRTL ? "left" : "right"); - } - else { - container.dir = ""; - this.setAttribute("side", isRTL ? "right" : "left"); - } - } - else if (position.indexOf("before_") == 0 || position.indexOf("after_") == 0) { - container.orient = ""; - arrowbox.orient = ""; - if (position.indexOf("_end") > 0) { - arrowbox.pack = "end"; - } else { - arrowbox.pack = "start"; - } - arrowbox.style.transform = "translate(" + -offset + "px, 0)"; - - if (position.indexOf("before_") == 0) { - container.dir = "reverse"; - this.setAttribute("side", "bottom"); - } - else { - container.dir = ""; - this.setAttribute("side", "top"); - } - } - - arrow.hidden = false; - ]]></body> - </method> - </implementation> - - <handlers> - <handler event="popupshowing" phase="target"><![CDATA[ - this.adjustArrowPosition(); - this.setAttribute("animate", "open"); - ]]></handler> - <handler event="popupshown" phase="target"><![CDATA[ - this.setAttribute("panelopen", "true"); - let disablePointerEvents; - if (!this.hasAttribute("disablepointereventsfortransition")) { - let container = document.getAnonymousElementByAttribute(this, "anonid", "container"); - let cs = getComputedStyle(container); - let transitionProp = cs.transitionProperty; - let transitionTime = parseFloat(cs.transitionDuration); - disablePointerEvents = (transitionProp.includes("transform") || - transitionProp == "all") && - transitionTime > 0; - this.setAttribute("disablepointereventsfortransition", disablePointerEvents); - } else { - disablePointerEvents = this.getAttribute("disablepointereventsfortransition") == "true"; - } - if (!disablePointerEvents) { - this.style.removeProperty("pointer-events"); - } - ]]></handler> - <handler event="transitionend"><![CDATA[ - if (event.originalTarget.getAttribute("anonid") == "container" && - event.propertyName == "transform") { - this.style.removeProperty("pointer-events"); - } - ]]></handler> - <handler event="popuphiding" phase="target"><![CDATA[ - this.setAttribute("animate", "cancel"); - ]]></handler> - <handler event="popuphidden" phase="target"><![CDATA[ - this.removeAttribute("panelopen"); - if (this.getAttribute("disablepointereventsfortransition") == "true") { - this.style.pointerEvents = 'none'; - } - this.removeAttribute("animate"); - ]]></handler> - </handlers> - </binding> -</bindings> diff --git a/browser/components/places/content/moveBookmarks.js b/browser/components/places/content/moveBookmarks.js deleted file mode 100644 index 5bfdce56e..000000000 --- a/browser/components/places/content/moveBookmarks.js +++ /dev/null @@ -1,65 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -var gMoveBookmarksDialog = { - _nodes: null, - - _foldersTree: null, - get foldersTree() { - if (!this._foldersTree) - this._foldersTree = document.getElementById("foldersTree"); - - return this._foldersTree; - }, - - init: function() { - this._nodes = window.arguments[0]; - - this.foldersTree.place = - "place:excludeItems=1&excludeQueries=1&excludeReadOnlyFolders=1&folder=" + - PlacesUIUtils.allBookmarksFolderId; - }, - - onOK: function MBD_onOK(aEvent) { - let selectedNode = this.foldersTree.selectedNode; - let selectedFolderId = PlacesUtils.getConcreteItemId(selectedNode); - - if (!PlacesUIUtils.useAsyncTransactions) { - let transactions = []; - for (var i=0; i < this._nodes.length; i++) { - // Nothing to do if the node is already under the selected folder - if (this._nodes[i].parent.itemId == selectedFolderId) - continue; - - let txn = new PlacesMoveItemTransaction(this._nodes[i].itemId, - selectedFolderId, - PlacesUtils.bookmarks.DEFAULT_INDEX); - transactions.push(txn); - } - if (transactions.length != 0) { - let txn = new PlacesAggregatedTransaction("Move Items", transactions); - PlacesUtils.transactionManager.doTransaction(txn); - } - return; - } - - PlacesTransactions.batch(function* () { - let newParentGuid = yield PlacesUtils.promiseItemGuid(selectedFolderId); - for (let node of this._nodes) { - // Nothing to do if the node is already under the selected folder. - if (node.parent.itemId == selectedFolderId) - continue; - yield PlacesTransactions.Move({ guid: node.bookmarkGuid - , newParentGuid }).transact(); - } - }.bind(this)).then(null, Components.utils.reportError); - }, - - newFolder: function MBD_newFolder() { - // The command is disabled when the tree is not focused - this.foldersTree.focus(); - goDoCommand("placesCmd_new:folder"); - } -}; diff --git a/browser/components/places/content/moveBookmarks.xul b/browser/components/places/content/moveBookmarks.xul deleted file mode 100644 index b6e75f3da..000000000 --- a/browser/components/places/content/moveBookmarks.xul +++ /dev/null @@ -1,53 +0,0 @@ -<?xml version="1.0"?> - -<!-- 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/. --> - -<?xml-stylesheet href="chrome://global/skin/"?> -<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> -<?xml-stylesheet href="chrome://browser/content/places/places.css"?> - -<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> - -<!DOCTYPE window [ - <!ENTITY % moveBookmarksDTD SYSTEM "chrome://browser/locale/places/moveBookmarks.dtd"> - %moveBookmarksDTD; -]> - -<dialog id="moveBookmarkDialog" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - ondialogaccept="return gMoveBookmarksDialog.onOK(event);" - title="&window.title;" - onload="gMoveBookmarksDialog.init();" - style="&window.style;" - screenX="24" - screenY="24" - persist="screenX screenY width height"> - - <script type="application/javascript" - src="chrome://browser/content/places/moveBookmarks.js"/> - - <hbox flex="1"> - <label id="movetolabel" value="&moveTo.label;" control="foldersTree"/> - <hbox flex="1"> - <tree id="foldersTree" - class="placesTree" - flex="1" - type="places" - seltype="single" - hidecolumnpicker="true"> - <treecols> - <treecol id="title" flex="1" primary="true" hideheader="true"/> - </treecols> - <treechildren id="placesListChildren" view="placesList" flex="1"/> - </tree> - <vbox> - <button id="newFolderButton" - label="&newFolderButton.label;" - accesskey="&newFolderButton.accesskey;" - oncommand="gMoveBookmarksDialog.newFolder();"/> - </vbox> - </hbox> - </hbox> -</dialog> diff --git a/browser/components/places/content/organizer.css b/browser/components/places/content/organizer.css deleted file mode 100644 index 47b1832c1..000000000 --- a/browser/components/places/content/organizer.css +++ /dev/null @@ -1,7 +0,0 @@ -/* 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/. */ - -#searchFilter { - width: 23em; -} diff --git a/browser/components/places/content/places.css b/browser/components/places/content/places.css deleted file mode 100644 index de3cc91d8..000000000 --- a/browser/components/places/content/places.css +++ /dev/null @@ -1,25 +0,0 @@ -/* 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/. */ - -tree[type="places"] { - -moz-binding: url("chrome://browser/content/places/tree.xml#places-tree"); -} - -.toolbar-drop-indicator { - position: relative; - z-index: 1; -} - -menupopup[placespopup="true"] { - -moz-binding: url("chrome://browser/content/places/menu.xml#places-popup-base"); -} - -/* Apply crisp rendering for favicons at exactly 2dppx resolution */ -@media (resolution: 2dppx) { - #bookmarksChildren, - .sidebar-placesTreechildren, - .placesTree > treechildren { - image-rendering: -moz-crisp-edges; - } -} diff --git a/browser/components/places/content/places.js b/browser/components/places/content/places.js deleted file mode 100644 index aa43b20e6..000000000 --- a/browser/components/places/content/places.js +++ /dev/null @@ -1,1405 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ -/* 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/. */ - -Components.utils.import("resource://gre/modules/AppConstants.jsm"); -Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); -Components.utils.import("resource://gre/modules/TelemetryStopwatch.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "MigrationUtils", - "resource:///modules/MigrationUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "Task", - "resource://gre/modules/Task.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils", - "resource://gre/modules/BookmarkJSONUtils.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups", - "resource://gre/modules/PlacesBackups.jsm"); -XPCOMUtils.defineLazyModuleGetter(this, "DownloadUtils", - "resource://gre/modules/DownloadUtils.jsm"); - -const RESTORE_FILEPICKER_FILTER_EXT = "*.json;*.jsonlz4"; -const HISTORY_LIBRARY_SEARCH_TELEMETRY = "PLACES_HISTORY_LIBRARY_SEARCH_TIME_MS"; - -var PlacesOrganizer = { - _places: null, - - // IDs of fields from editBookmarkOverlay that should be hidden when infoBox - // is minimal. IDs should be kept in sync with the IDs of the elements - // observing additionalInfoBroadcaster. - _additionalInfoFields: [ - "editBMPanel_descriptionRow", - "editBMPanel_loadInSidebarCheckbox", - "editBMPanel_keywordRow", - ], - - _initFolderTree: function() { - var leftPaneRoot = PlacesUIUtils.leftPaneFolderId; - this._places.place = "place:excludeItems=1&expandQueries=0&folder=" + leftPaneRoot; - }, - - selectLeftPaneQuery: function PO_selectLeftPaneQuery(aQueryName) { - var itemId = PlacesUIUtils.leftPaneQueries[aQueryName]; - this._places.selectItems([itemId]); - // Forcefully expand all-bookmarks - if (aQueryName == "AllBookmarks" || aQueryName == "History") - PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true; - }, - - /** - * Opens a given hierarchy in the left pane, stopping at the last reachable - * container. - * - * @param aHierarchy A single container or an array of containers, sorted from - * the outmost to the innermost in the hierarchy. Each - * container may be either an item id, a Places URI string, - * or a named query. - * @see PlacesUIUtils.leftPaneQueries for supported named queries. - */ - selectLeftPaneContainerByHierarchy: - function PO_selectLeftPaneContainerByHierarchy(aHierarchy) { - if (!aHierarchy) - throw new Error("Invalid containers hierarchy"); - let hierarchy = [].concat(aHierarchy); - let selectWasSuppressed = this._places.view.selection.selectEventsSuppressed; - if (!selectWasSuppressed) - this._places.view.selection.selectEventsSuppressed = true; - try { - for (let container of hierarchy) { - switch (typeof container) { - case "number": - this._places.selectItems([container], false); - break; - case "string": - if (container.substr(0, 6) == "place:") - this._places.selectPlaceURI(container); - else if (container in PlacesUIUtils.leftPaneQueries) - this.selectLeftPaneQuery(container); - else - throw new Error("Invalid container found: " + container); - break; - default: - throw new Error("Invalid container type found: " + container); - } - PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true; - } - } finally { - if (!selectWasSuppressed) - this._places.view.selection.selectEventsSuppressed = false; - } - }, - - init: function PO_init() { - ContentArea.init(); - - this._places = document.getElementById("placesList"); - this._initFolderTree(); - - var leftPaneSelection = "AllBookmarks"; // default to all-bookmarks - if (window.arguments && window.arguments[0]) - leftPaneSelection = window.arguments[0]; - - this.selectLeftPaneContainerByHierarchy(leftPaneSelection); - if (leftPaneSelection === "History") { - let historyNode = this._places.selectedNode; - if (historyNode.childCount > 0) - this._places.selectNode(historyNode.getChild(0)); - } - - // clear the back-stack - this._backHistory.splice(0, this._backHistory.length); - document.getElementById("OrganizerCommand:Back").setAttribute("disabled", true); - - // Set up the search UI. - PlacesSearchBox.init(); - - window.addEventListener("AppCommand", this, true); - - if (AppConstants.platform === "macosx") { - // 1. Map Edit->Find command to OrganizerCommand_find:all. Need to map - // both the menuitem and the Find key. - let findMenuItem = document.getElementById("menu_find"); - findMenuItem.setAttribute("command", "OrganizerCommand_find:all"); - let findKey = document.getElementById("key_find"); - findKey.setAttribute("command", "OrganizerCommand_find:all"); - - // 2. Disable some keybindings from browser.xul - let elements = ["cmd_handleBackspace", "cmd_handleShiftBackspace"]; - for (let i = 0; i < elements.length; i++) { - document.getElementById(elements[i]).setAttribute("disabled", "true"); - } - } - - // remove the "Properties" context-menu item, we've our own details pane - document.getElementById("placesContext") - .removeChild(document.getElementById("placesContext_show:info")); - - ContentArea.focus(); - }, - - QueryInterface: function PO_QueryInterface(aIID) { - if (aIID.equals(Components.interfaces.nsIDOMEventListener) || - aIID.equals(Components.interfaces.nsISupports)) - return this; - - throw Components.results.NS_NOINTERFACE; - }, - - handleEvent: function PO_handleEvent(aEvent) { - if (aEvent.type != "AppCommand") - return; - - aEvent.stopPropagation(); - switch (aEvent.command) { - case "Back": - if (this._backHistory.length > 0) - this.back(); - break; - case "Forward": - if (this._forwardHistory.length > 0) - this.forward(); - break; - case "Search": - PlacesSearchBox.findAll(); - break; - } - }, - - destroy: function PO_destroy() { - }, - - _location: null, - get location() { - return this._location; - }, - - set location(aLocation) { - if (!aLocation || this._location == aLocation) - return aLocation; - - if (this.location) { - this._backHistory.unshift(this.location); - this._forwardHistory.splice(0, this._forwardHistory.length); - } - - this._location = aLocation; - this._places.selectPlaceURI(aLocation); - - if (!this._places.hasSelection) { - // If no node was found for the given place: uri, just load it directly - ContentArea.currentPlace = aLocation; - } - this.updateDetailsPane(); - - // update navigation commands - if (this._backHistory.length == 0) - document.getElementById("OrganizerCommand:Back").setAttribute("disabled", true); - else - document.getElementById("OrganizerCommand:Back").removeAttribute("disabled"); - if (this._forwardHistory.length == 0) - document.getElementById("OrganizerCommand:Forward").setAttribute("disabled", true); - else - document.getElementById("OrganizerCommand:Forward").removeAttribute("disabled"); - - return aLocation; - }, - - _backHistory: [], - _forwardHistory: [], - - back: function PO_back() { - this._forwardHistory.unshift(this.location); - var historyEntry = this._backHistory.shift(); - this._location = null; - this.location = historyEntry; - }, - forward: function PO_forward() { - this._backHistory.unshift(this.location); - var historyEntry = this._forwardHistory.shift(); - this._location = null; - this.location = historyEntry; - }, - - /** - * Called when a place folder is selected in the left pane. - * @param resetSearchBox - * true if the search box should also be reset, false otherwise. - * The search box should be reset when a new folder in the left - * pane is selected; the search scope and text need to be cleared in - * preparation for the new folder. Note that if the user manually - * resets the search box, either by clicking its reset button or by - * deleting its text, this will be false. - */ - _cachedLeftPaneSelectedURI: null, - onPlaceSelected: function PO_onPlaceSelected(resetSearchBox) { - // Don't change the right-hand pane contents when there's no selection. - if (!this._places.hasSelection) - return; - - var node = this._places.selectedNode; - var queries = PlacesUtils.asQuery(node).getQueries(); - - // Items are only excluded on the left pane. - var options = node.queryOptions.clone(); - options.excludeItems = false; - var placeURI = PlacesUtils.history.queriesToQueryString(queries, - queries.length, - options); - - // If either the place of the content tree in the right pane has changed or - // the user cleared the search box, update the place, hide the search UI, - // and update the back/forward buttons by setting location. - if (ContentArea.currentPlace != placeURI || !resetSearchBox) { - ContentArea.currentPlace = placeURI; - this.location = node.uri; - } - - // When we invalidate a container we use suppressSelectionEvent, when it is - // unset a select event is fired, in many cases the selection did not really - // change, so we should check for it, and return early in such a case. Note - // that we cannot return any earlier than this point, because when - // !resetSearchBox, we need to update location and hide the UI as above, - // even though the selection has not changed. - if (node.uri == this._cachedLeftPaneSelectedURI) - return; - this._cachedLeftPaneSelectedURI = node.uri; - - // At this point, resetSearchBox is true, because the left pane selection - // has changed; otherwise we would have returned earlier. - - PlacesSearchBox.searchFilter.reset(); - this._setSearchScopeForNode(node); - this.updateDetailsPane(); - }, - - /** - * Sets the search scope based on aNode's properties. - * @param aNode - * the node to set up scope from - */ - _setSearchScopeForNode: function PO__setScopeForNode(aNode) { - let itemId = aNode.itemId; - - if (PlacesUtils.nodeIsHistoryContainer(aNode) || - itemId == PlacesUIUtils.leftPaneQueries["History"]) { - PlacesQueryBuilder.setScope("history"); - } - else if (itemId == PlacesUIUtils.leftPaneQueries["Downloads"]) { - PlacesQueryBuilder.setScope("downloads"); - } - else { - // Default to All Bookmarks for all other nodes, per bug 469437. - PlacesQueryBuilder.setScope("bookmarks"); - } - }, - - /** - * Handle clicks on the places list. - * Single Left click, right click or modified click do not result in any - * special action, since they're related to selection. - * @param aEvent - * The mouse event. - */ - onPlacesListClick: function PO_onPlacesListClick(aEvent) { - // Only handle clicks on tree children. - if (aEvent.target.localName != "treechildren") - return; - - let node = this._places.selectedNode; - if (node) { - let middleClick = aEvent.button == 1 && aEvent.detail == 1; - if (middleClick && PlacesUtils.nodeIsContainer(node)) { - // The command execution function will take care of seeing if the - // selection is a folder or a different container type, and will - // load its contents in tabs. - PlacesUIUtils.openContainerNodeInTabs(selectedNode, aEvent, this._places); - } - } - }, - - /** - * Handle focus changes on the places list and the current content view. - */ - updateDetailsPane: function PO_updateDetailsPane() { - if (!ContentArea.currentViewOptions.showDetailsPane) - return; - let view = PlacesUIUtils.getViewForNode(document.activeElement); - if (view) { - let selectedNodes = view.selectedNode ? - [view.selectedNode] : view.selectedNodes; - this._fillDetailsPane(selectedNodes); - } - }, - - openFlatContainer: function PO_openFlatContainerFlatContainer(aContainer) { - if (aContainer.itemId != -1) { - PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true; - this._places.selectItems([aContainer.itemId], false); - } - else if (PlacesUtils.nodeIsQuery(aContainer)) { - this._places.selectPlaceURI(aContainer.uri); - } - }, - - /** - * Returns the options associated with the query currently loaded in the - * main places pane. - */ - getCurrentOptions: function PO_getCurrentOptions() { - return PlacesUtils.asQuery(ContentArea.currentView.result.root).queryOptions; - }, - - /** - * Returns the queries associated with the query currently loaded in the - * main places pane. - */ - getCurrentQueries: function PO_getCurrentQueries() { - return PlacesUtils.asQuery(ContentArea.currentView.result.root).getQueries(); - }, - - /** - * Show the migration wizard for importing passwords, - * cookies, history, preferences, and bookmarks. - */ - importFromBrowser: function PO_importFromBrowser() { - // We pass in the type of source we're using for use in telemetry: - MigrationUtils.showMigrationWizard(window, [MigrationUtils.MIGRATION_ENTRYPOINT_PLACES]); - }, - - /** - * Open a file-picker and import the selected file into the bookmarks store - */ - importFromFile: function PO_importFromFile() { - let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - let fpCallback = function fpCallback_done(aResult) { - if (aResult != Ci.nsIFilePicker.returnCancel && fp.fileURL) { - Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); - BookmarkHTMLUtils.importFromURL(fp.fileURL.spec, false) - .then(null, Components.utils.reportError); - } - }; - - fp.init(window, PlacesUIUtils.getString("SelectImport"), - Ci.nsIFilePicker.modeOpen); - fp.appendFilters(Ci.nsIFilePicker.filterHTML); - fp.open(fpCallback); - }, - - /** - * Allows simple exporting of bookmarks. - */ - exportBookmarks: function PO_exportBookmarks() { - let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - let fpCallback = function fpCallback_done(aResult) { - if (aResult != Ci.nsIFilePicker.returnCancel) { - Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm"); - BookmarkHTMLUtils.exportToFile(fp.file.path) - .then(null, Components.utils.reportError); - } - }; - - fp.init(window, PlacesUIUtils.getString("EnterExport"), - Ci.nsIFilePicker.modeSave); - fp.appendFilters(Ci.nsIFilePicker.filterHTML); - fp.defaultString = "bookmarks.html"; - fp.open(fpCallback); - }, - - /** - * Populates the restore menu with the dates of the backups available. - */ - populateRestoreMenu: function PO_populateRestoreMenu() { - let restorePopup = document.getElementById("fileRestorePopup"); - - const locale = Cc["@mozilla.org/chrome/chrome-registry;1"] - .getService(Ci.nsIXULChromeRegistry) - .getSelectedLocale("global", true); - const dtOptions = { year: 'numeric', month: 'long', day: 'numeric' }; - let dateFormatter = new Intl.DateTimeFormat(locale, dtOptions); - - // Remove existing menu items. Last item is the restoreFromFile item. - while (restorePopup.childNodes.length > 1) - restorePopup.removeChild(restorePopup.firstChild); - - Task.spawn(function* () { - let backupFiles = yield PlacesBackups.getBackupFiles(); - if (backupFiles.length == 0) - return; - - // Populate menu with backups. - for (let i = 0; i < backupFiles.length; i++) { - let fileSize = (yield OS.File.stat(backupFiles[i])).size; - let [size, unit] = DownloadUtils.convertByteUnits(fileSize); - let sizeString = PlacesUtils.getFormattedString("backupFileSizeText", - [size, unit]); - let sizeInfo; - let bookmarkCount = PlacesBackups.getBookmarkCountForFile(backupFiles[i]); - if (bookmarkCount != null) { - sizeInfo = " (" + sizeString + " - " + - PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel", - bookmarkCount, - [bookmarkCount]) + - ")"; - } else { - sizeInfo = " (" + sizeString + ")"; - } - - let backupDate = PlacesBackups.getDateForFile(backupFiles[i]); - let m = restorePopup.insertBefore(document.createElement("menuitem"), - document.getElementById("restoreFromFile")); - m.setAttribute("label", dateFormatter.format(backupDate) + sizeInfo); - m.setAttribute("value", OS.Path.basename(backupFiles[i])); - m.setAttribute("oncommand", - "PlacesOrganizer.onRestoreMenuItemClick(this);"); - } - - // Add the restoreFromFile item. - restorePopup.insertBefore(document.createElement("menuseparator"), - document.getElementById("restoreFromFile")); - }); - }, - - /** - * Called when a menuitem is selected from the restore menu. - */ - onRestoreMenuItemClick: Task.async(function* (aMenuItem) { - let backupName = aMenuItem.getAttribute("value"); - let backupFilePaths = yield PlacesBackups.getBackupFiles(); - for (let backupFilePath of backupFilePaths) { - if (OS.Path.basename(backupFilePath) == backupName) { - PlacesOrganizer.restoreBookmarksFromFile(backupFilePath); - break; - } - } - }), - - /** - * Called when 'Choose File...' is selected from the restore menu. - * Prompts for a file and restores bookmarks to those in the file. - */ - onRestoreBookmarksFromFile: function PO_onRestoreBookmarksFromFile() { - let dirSvc = Cc["@mozilla.org/file/directory_service;1"]. - getService(Ci.nsIProperties); - let backupsDir = dirSvc.get("Desk", Ci.nsILocalFile); - let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - let fpCallback = function fpCallback_done(aResult) { - if (aResult != Ci.nsIFilePicker.returnCancel) { - this.restoreBookmarksFromFile(fp.file.path); - } - }.bind(this); - - fp.init(window, PlacesUIUtils.getString("bookmarksRestoreTitle"), - Ci.nsIFilePicker.modeOpen); - fp.appendFilter(PlacesUIUtils.getString("bookmarksRestoreFilterName"), - RESTORE_FILEPICKER_FILTER_EXT); - fp.appendFilters(Ci.nsIFilePicker.filterAll); - fp.displayDirectory = backupsDir; - fp.open(fpCallback); - }, - - /** - * Restores bookmarks from a JSON file. - */ - restoreBookmarksFromFile: function PO_restoreBookmarksFromFile(aFilePath) { - // check file extension - if (!aFilePath.toLowerCase().endsWith("json") && - !aFilePath.toLowerCase().endsWith("jsonlz4")) { - this._showErrorAlert(PlacesUIUtils.getString("bookmarksRestoreFormatError")); - return; - } - - // confirm ok to delete existing bookmarks - var prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"]. - getService(Ci.nsIPromptService); - if (!prompts.confirm(null, - PlacesUIUtils.getString("bookmarksRestoreAlertTitle"), - PlacesUIUtils.getString("bookmarksRestoreAlert"))) - return; - - Task.spawn(function* () { - try { - yield BookmarkJSONUtils.importFromFile(aFilePath, true); - } catch (ex) { - PlacesOrganizer._showErrorAlert(PlacesUIUtils.getString("bookmarksRestoreParseError")); - } - }); - }, - - _showErrorAlert: function PO__showErrorAlert(aMsg) { - var brandShortName = document.getElementById("brandStrings"). - getString("brandShortName"); - - Cc["@mozilla.org/embedcomp/prompt-service;1"]. - getService(Ci.nsIPromptService). - alert(window, brandShortName, aMsg); - }, - - /** - * Backup bookmarks to desktop, auto-generate a filename with a date. - * The file is a JSON serialization of bookmarks, tags and any annotations - * of those items. - */ - backupBookmarks: function PO_backupBookmarks() { - let dirSvc = Cc["@mozilla.org/file/directory_service;1"]. - getService(Ci.nsIProperties); - let backupsDir = dirSvc.get("Desk", Ci.nsILocalFile); - let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); - let fpCallback = function fpCallback_done(aResult) { - if (aResult != Ci.nsIFilePicker.returnCancel) { - // There is no OS.File version of the filepicker yet (Bug 937812). - PlacesBackups.saveBookmarksToJSONFile(fp.file.path); - } - }; - - fp.init(window, PlacesUIUtils.getString("bookmarksBackupTitle"), - Ci.nsIFilePicker.modeSave); - fp.appendFilter(PlacesUIUtils.getString("bookmarksRestoreFilterName"), - RESTORE_FILEPICKER_FILTER_EXT); - fp.defaultString = PlacesBackups.getFilenameForDate(); - fp.defaultExtension = "json"; - fp.displayDirectory = backupsDir; - fp.open(fpCallback); - }, - - _detectAndSetDetailsPaneMinimalState: - function PO__detectAndSetDetailsPaneMinimalState(aNode) { - /** - * The details of simple folder-items (as opposed to livemarks) or the - * of livemark-children are not likely to fill the infoBox anyway, - * thus we remove the "More/Less" button and show all details. - * - * the wasminimal attribute here is used to persist the "more/less" - * state in a bookmark->folder->bookmark scenario. - */ - var infoBox = document.getElementById("infoBox"); - var infoBoxExpanderWrapper = document.getElementById("infoBoxExpanderWrapper"); - var additionalInfoBroadcaster = document.getElementById("additionalInfoBroadcaster"); - - if (!aNode) { - infoBoxExpanderWrapper.hidden = true; - return; - } - if (aNode.itemId != -1 && - PlacesUtils.nodeIsFolder(aNode) && !aNode._feedURI) { - if (infoBox.getAttribute("minimal") == "true") - infoBox.setAttribute("wasminimal", "true"); - infoBox.removeAttribute("minimal"); - infoBoxExpanderWrapper.hidden = true; - } - else { - if (infoBox.getAttribute("wasminimal") == "true") - infoBox.setAttribute("minimal", "true"); - infoBox.removeAttribute("wasminimal"); - infoBoxExpanderWrapper.hidden = - this._additionalInfoFields.every(id => - document.getElementById(id).collapsed); - } - additionalInfoBroadcaster.hidden = infoBox.getAttribute("minimal") == "true"; - }, - - // NOT YET USED - updateThumbnailProportions: function PO_updateThumbnailProportions() { - var previewBox = document.getElementById("previewBox"); - var canvas = document.getElementById("itemThumbnail"); - var height = previewBox.boxObject.height; - var width = height * (screen.width / screen.height); - canvas.width = width; - canvas.height = height; - }, - - _fillDetailsPane: function PO__fillDetailsPane(aNodeList) { - var infoBox = document.getElementById("infoBox"); - var detailsDeck = document.getElementById("detailsDeck"); - - // Make sure the infoBox UI is visible if we need to use it, we hide it - // below when we don't. - infoBox.hidden = false; - let selectedNode = aNodeList.length == 1 ? aNodeList[0] : null; - - // If a textbox within a panel is focused, force-blur it so its contents - // are saved - if (gEditItemOverlay.itemId != -1) { - var focusedElement = document.commandDispatcher.focusedElement; - if ((focusedElement instanceof HTMLInputElement || - focusedElement instanceof HTMLTextAreaElement) && - /^editBMPanel.*/.test(focusedElement.parentNode.parentNode.id)) - focusedElement.blur(); - - // don't update the panel if we are already editing this node unless we're - // in multi-edit mode - if (selectedNode) { - let concreteId = PlacesUtils.getConcreteItemId(selectedNode); - var nodeIsSame = gEditItemOverlay.itemId == selectedNode.itemId || - gEditItemOverlay.itemId == concreteId || - (selectedNode.itemId == -1 && gEditItemOverlay.uri && - gEditItemOverlay.uri == selectedNode.uri); - if (nodeIsSame && detailsDeck.selectedIndex == 1 && - !gEditItemOverlay.multiEdit) - return; - } - } - - // Clean up the panel before initing it again. - gEditItemOverlay.uninitPanel(false); - - if (selectedNode && !PlacesUtils.nodeIsSeparator(selectedNode)) { - detailsDeck.selectedIndex = 1; - - gEditItemOverlay.initPanel({ node: selectedNode - , hiddenRows: ["folderPicker"] }); - - this._detectAndSetDetailsPaneMinimalState(selectedNode); - } - else if (!selectedNode && aNodeList[0]) { - if (aNodeList.every(PlacesUtils.nodeIsURI)) { - let uris = aNodeList.map(node => PlacesUtils._uri(node.uri)); - detailsDeck.selectedIndex = 1; - gEditItemOverlay.initPanel({ uris - , hiddenRows: ["folderPicker", - "loadInSidebar", - "location", - "keyword", - "description", - "name"]}); - this._detectAndSetDetailsPaneMinimalState(selectedNode); - } - else { - detailsDeck.selectedIndex = 0; - let selectItemDesc = document.getElementById("selectItemDescription"); - let itemsCountLabel = document.getElementById("itemsCountText"); - selectItemDesc.hidden = false; - itemsCountLabel.value = - PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel", - aNodeList.length, [aNodeList.length]); - infoBox.hidden = true; - } - } - else { - detailsDeck.selectedIndex = 0; - infoBox.hidden = true; - let selectItemDesc = document.getElementById("selectItemDescription"); - let itemsCountLabel = document.getElementById("itemsCountText"); - let itemsCount = 0; - if (ContentArea.currentView.result) { - let rootNode = ContentArea.currentView.result.root; - if (rootNode.containerOpen) - itemsCount = rootNode.childCount; - } - if (itemsCount == 0) { - selectItemDesc.hidden = true; - itemsCountLabel.value = PlacesUIUtils.getString("detailsPane.noItems"); - } - else { - selectItemDesc.hidden = false; - itemsCountLabel.value = - PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel", - itemsCount, [itemsCount]); - } - } - }, - - // NOT YET USED - _updateThumbnail: function PO__updateThumbnail() { - var bo = document.getElementById("previewBox").boxObject; - var width = bo.width; - var height = bo.height; - - var canvas = document.getElementById("itemThumbnail"); - var ctx = canvas.getContext('2d'); - var notAvailableText = canvas.getAttribute("notavailabletext"); - ctx.save(); - ctx.fillStyle = "-moz-Dialog"; - ctx.fillRect(0, 0, width, height); - ctx.translate(width/2, height/2); - - ctx.fillStyle = "GrayText"; - ctx.mozTextStyle = "12pt sans serif"; - var len = ctx.mozMeasureText(notAvailableText); - ctx.translate(-len/2, 0); - ctx.mozDrawText(notAvailableText); - ctx.restore(); - }, - - toggleAdditionalInfoFields: function PO_toggleAdditionalInfoFields() { - var infoBox = document.getElementById("infoBox"); - var infoBoxExpander = document.getElementById("infoBoxExpander"); - var infoBoxExpanderLabel = document.getElementById("infoBoxExpanderLabel"); - var additionalInfoBroadcaster = document.getElementById("additionalInfoBroadcaster"); - - if (infoBox.getAttribute("minimal") == "true") { - infoBox.removeAttribute("minimal"); - infoBoxExpanderLabel.value = infoBoxExpanderLabel.getAttribute("lesslabel"); - infoBoxExpanderLabel.accessKey = infoBoxExpanderLabel.getAttribute("lessaccesskey"); - infoBoxExpander.className = "expander-up"; - additionalInfoBroadcaster.removeAttribute("hidden"); - } - else { - infoBox.setAttribute("minimal", "true"); - infoBoxExpanderLabel.value = infoBoxExpanderLabel.getAttribute("morelabel"); - infoBoxExpanderLabel.accessKey = infoBoxExpanderLabel.getAttribute("moreaccesskey"); - infoBoxExpander.className = "expander-down"; - additionalInfoBroadcaster.setAttribute("hidden", "true"); - } - }, -}; - -/** - * A set of utilities relating to search within Bookmarks and History. - */ -var PlacesSearchBox = { - - /** - * The Search text field - */ - get searchFilter() { - return document.getElementById("searchFilter"); - }, - - /** - * Folders to include when searching. - */ - _folders: [], - get folders() { - if (this._folders.length == 0) { - this._folders.push(PlacesUtils.bookmarksMenuFolderId, - PlacesUtils.unfiledBookmarksFolderId, - PlacesUtils.toolbarFolderId); - } - return this._folders; - }, - set folders(aFolders) { - this._folders = aFolders; - return aFolders; - }, - - /** - * Run a search for the specified text, over the collection specified by - * the dropdown arrow. The default is all bookmarks, but can be - * localized to the active collection. - * @param filterString - * The text to search for. - */ - search: function PSB_search(filterString) { - var PO = PlacesOrganizer; - // If the user empties the search box manually, reset it and load all - // contents of the current scope. - // XXX this might be to jumpy, maybe should search for "", so results - // are ungrouped, and search box not reset - if (filterString == "") { - PO.onPlaceSelected(false); - return; - } - - let currentView = ContentArea.currentView; - let currentOptions = PO.getCurrentOptions(); - - // Search according to the current scope, which was set by - // PQB_setScope() - switch (PlacesSearchBox.filterCollection) { - case "bookmarks": - currentView.applyFilter(filterString, this.folders); - break; - case "history": - if (currentOptions.queryType != Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) { - var query = PlacesUtils.history.getNewQuery(); - query.searchTerms = filterString; - var options = currentOptions.clone(); - // Make sure we're getting uri results. - options.resultType = currentOptions.RESULTS_AS_URI; - options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY; - options.includeHidden = true; - currentView.load([query], options); - } - else { - TelemetryStopwatch.start(HISTORY_LIBRARY_SEARCH_TELEMETRY); - currentView.applyFilter(filterString, null, true); - TelemetryStopwatch.finish(HISTORY_LIBRARY_SEARCH_TELEMETRY); - } - break; - case "downloads": - if (currentView == ContentTree.view) { - let query = PlacesUtils.history.getNewQuery(); - query.searchTerms = filterString; - query.setTransitions([Ci.nsINavHistoryService.TRANSITION_DOWNLOAD], 1); - let options = currentOptions.clone(); - // Make sure we're getting uri results. - options.resultType = currentOptions.RESULTS_AS_URI; - options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY; - options.includeHidden = true; - currentView.load([query], options); - } - else { - // The new downloads view doesn't use places for searching downloads. - currentView.searchTerm = filterString; - } - break; - default: - throw "Invalid filterCollection on search"; - } - - // Update the details panel - PlacesOrganizer.updateDetailsPane(); - }, - - /** - * Finds across all history, downloads or all bookmarks. - */ - findAll: function PSB_findAll() { - switch (this.filterCollection) { - case "history": - PlacesQueryBuilder.setScope("history"); - break; - case "downloads": - PlacesQueryBuilder.setScope("downloads"); - break; - default: - PlacesQueryBuilder.setScope("bookmarks"); - break; - } - this.focus(); - }, - - /** - * Updates the display with the title of the current collection. - * @param aTitle - * The title of the current collection. - */ - updateCollectionTitle: function PSB_updateCollectionTitle(aTitle) { - let title = ""; - switch (this.filterCollection) { - case "history": - title = PlacesUIUtils.getString("searchHistory"); - break; - case "downloads": - title = PlacesUIUtils.getString("searchDownloads"); - break; - default: - title = PlacesUIUtils.getString("searchBookmarks"); - } - this.searchFilter.placeholder = title; - }, - - /** - * Gets/sets the active collection from the dropdown menu. - */ - get filterCollection() { - return this.searchFilter.getAttribute("collection"); - }, - set filterCollection(collectionName) { - if (collectionName == this.filterCollection) - return collectionName; - - this.searchFilter.setAttribute("collection", collectionName); - this.updateCollectionTitle(); - - return collectionName; - }, - - /** - * Focus the search box - */ - focus: function PSB_focus() { - this.searchFilter.focus(); - }, - - /** - * Set up the gray text in the search bar as the Places View loads. - */ - init: function PSB_init() { - this.updateCollectionTitle(); - }, - - /** - * Gets or sets the text shown in the Places Search Box - */ - get value() { - return this.searchFilter.value; - }, - set value(value) { - return this.searchFilter.value = value; - }, -}; - -/** - * Functions and data for advanced query builder - */ -var PlacesQueryBuilder = { - - queries: [], - queryOptions: null, - - /** - * Sets the search scope. This can be called when no search is active, and - * in that case, when the user does begin a search aScope will be used (see - * PSB_search()). If there is an active search, it's performed again to - * update the content tree. - * @param aScope - * The search scope: "bookmarks", "collection", "downloads" or - * "history". - */ - setScope: function PQB_setScope(aScope) { - // Determine filterCollection, folders, and scopeButtonId based on aScope. - var filterCollection; - var folders = []; - switch (aScope) { - case "history": - filterCollection = "history"; - break; - case "bookmarks": - filterCollection = "bookmarks"; - folders.push(PlacesUtils.bookmarksMenuFolderId, - PlacesUtils.toolbarFolderId, - PlacesUtils.unfiledBookmarksFolderId); - break; - case "downloads": - filterCollection = "downloads"; - break; - default: - throw "Invalid search scope"; - } - - // Update the search box. Re-search if there's an active search. - PlacesSearchBox.filterCollection = filterCollection; - PlacesSearchBox.folders = folders; - var searchStr = PlacesSearchBox.searchFilter.value; - if (searchStr) - PlacesSearchBox.search(searchStr); - } -}; - -/** - * Population and commands for the View Menu. - */ -var ViewMenu = { - /** - * Removes content generated previously from a menupopup. - * @param popup - * The popup that contains the previously generated content. - * @param startID - * The id attribute of an element that is the start of the - * dynamically generated region - remove elements after this - * item only. - * Must be contained by popup. Can be null (in which case the - * contents of popup are removed). - * @param endID - * The id attribute of an element that is the end of the - * dynamically generated region - remove elements up to this - * item only. - * Must be contained by popup. Can be null (in which case all - * items until the end of the popup will be removed). Ignored - * if startID is null. - * @returns The element for the caller to insert new items before, - * null if the caller should just append to the popup. - */ - _clean: function VM__clean(popup, startID, endID) { - if (endID) - NS_ASSERT(startID, "meaningless to have valid endID and null startID"); - if (startID) { - var startElement = document.getElementById(startID); - NS_ASSERT(startElement.parentNode == - popup, "startElement is not in popup"); - NS_ASSERT(startElement, - "startID does not correspond to an existing element"); - var endElement = null; - if (endID) { - endElement = document.getElementById(endID); - NS_ASSERT(endElement.parentNode == popup, - "endElement is not in popup"); - NS_ASSERT(endElement, - "endID does not correspond to an existing element"); - } - while (startElement.nextSibling != endElement) - popup.removeChild(startElement.nextSibling); - return endElement; - } - while (popup.hasChildNodes()) { - popup.removeChild(popup.firstChild); - } - return null; - }, - - /** - * Fills a menupopup with a list of columns - * @param event - * The popupshowing event that invoked this function. - * @param startID - * see _clean - * @param endID - * see _clean - * @param type - * the type of the menuitem, e.g. "radio" or "checkbox". - * Can be null (no-type). - * Checkboxes are checked if the column is visible. - * @param propertyPrefix - * If propertyPrefix is non-null: - * propertyPrefix + column ID + ".label" will be used to get the - * localized label string. - * propertyPrefix + column ID + ".accesskey" will be used to get the - * localized accesskey. - * If propertyPrefix is null, the column label is used as label and - * no accesskey is assigned. - */ - fillWithColumns: function VM_fillWithColumns(event, startID, endID, type, propertyPrefix) { - var popup = event.target; - var pivot = this._clean(popup, startID, endID); - - var content = document.getElementById("placeContent"); - var columns = content.columns; - for (var i = 0; i < columns.count; ++i) { - var column = columns.getColumnAt(i).element; - var menuitem = document.createElement("menuitem"); - menuitem.id = "menucol_" + column.id; - menuitem.column = column; - var label = column.getAttribute("label"); - if (propertyPrefix) { - var menuitemPrefix = propertyPrefix; - // for string properties, use "name" as the id, instead of "title" - // see bug #386287 for details - var columnId = column.getAttribute("anonid"); - menuitemPrefix += columnId == "title" ? "name" : columnId; - label = PlacesUIUtils.getString(menuitemPrefix + ".label"); - var accesskey = PlacesUIUtils.getString(menuitemPrefix + ".accesskey"); - menuitem.setAttribute("accesskey", accesskey); - } - menuitem.setAttribute("label", label); - if (type == "radio") { - menuitem.setAttribute("type", "radio"); - menuitem.setAttribute("name", "columns"); - // This column is the sort key. Its item is checked. - if (column.getAttribute("sortDirection") != "") { - menuitem.setAttribute("checked", "true"); - } - } - else if (type == "checkbox") { - menuitem.setAttribute("type", "checkbox"); - // Cannot uncheck the primary column. - if (column.getAttribute("primary") == "true") - menuitem.setAttribute("disabled", "true"); - // Items for visible columns are checked. - if (!column.hidden) - menuitem.setAttribute("checked", "true"); - } - if (pivot) - popup.insertBefore(menuitem, pivot); - else - popup.appendChild(menuitem); - } - event.stopPropagation(); - }, - - /** - * Set up the content of the view menu. - */ - populateSortMenu: function VM_populateSortMenu(event) { - this.fillWithColumns(event, "viewUnsorted", "directionSeparator", "radio", "view.sortBy.1."); - - var sortColumn = this._getSortColumn(); - var viewSortAscending = document.getElementById("viewSortAscending"); - var viewSortDescending = document.getElementById("viewSortDescending"); - // We need to remove an existing checked attribute because the unsorted - // menu item is not rebuilt every time we open the menu like the others. - var viewUnsorted = document.getElementById("viewUnsorted"); - if (!sortColumn) { - viewSortAscending.removeAttribute("checked"); - viewSortDescending.removeAttribute("checked"); - viewUnsorted.setAttribute("checked", "true"); - } - else if (sortColumn.getAttribute("sortDirection") == "ascending") { - viewSortAscending.setAttribute("checked", "true"); - viewSortDescending.removeAttribute("checked"); - viewUnsorted.removeAttribute("checked"); - } - else if (sortColumn.getAttribute("sortDirection") == "descending") { - viewSortDescending.setAttribute("checked", "true"); - viewSortAscending.removeAttribute("checked"); - viewUnsorted.removeAttribute("checked"); - } - }, - - /** - * Shows/Hides a tree column. - * @param element - * The menuitem element for the column - */ - showHideColumn: function VM_showHideColumn(element) { - var column = element.column; - - var splitter = column.nextSibling; - if (splitter && splitter.localName != "splitter") - splitter = null; - - if (element.getAttribute("checked") == "true") { - column.setAttribute("hidden", "false"); - if (splitter) - splitter.removeAttribute("hidden"); - } - else { - column.setAttribute("hidden", "true"); - if (splitter) - splitter.setAttribute("hidden", "true"); - } - }, - - /** - * Gets the last column that was sorted. - * @returns the currently sorted column, null if there is no sorted column. - */ - _getSortColumn: function VM__getSortColumn() { - var content = document.getElementById("placeContent"); - var cols = content.columns; - for (var i = 0; i < cols.count; ++i) { - var column = cols.getColumnAt(i).element; - var sortDirection = column.getAttribute("sortDirection"); - if (sortDirection == "ascending" || sortDirection == "descending") - return column; - } - return null; - }, - - /** - * Sorts the view by the specified column. - * @param aColumn - * The colum that is the sort key. Can be null - the - * current sort column or the title column will be used. - * @param aDirection - * The direction to sort - "ascending" or "descending". - * Can be null - the last direction or descending will be used. - * - * If both aColumnID and aDirection are null, the view will be unsorted. - */ - setSortColumn: function VM_setSortColumn(aColumn, aDirection) { - var result = document.getElementById("placeContent").result; - if (!aColumn && !aDirection) { - result.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_NONE; - return; - } - - var columnId; - if (aColumn) { - columnId = aColumn.getAttribute("anonid"); - if (!aDirection) { - let sortColumn = this._getSortColumn(); - if (sortColumn) - aDirection = sortColumn.getAttribute("sortDirection"); - } - } - else { - let sortColumn = this._getSortColumn(); - columnId = sortColumn ? sortColumn.getAttribute("anonid") : "title"; - } - - // This maps the possible values of columnId (i.e., anonid's of treecols in - // placeContent) to the default sortingMode and sortingAnnotation values for - // each column. - // key: Sort key in the name of one of the - // nsINavHistoryQueryOptions.SORT_BY_* constants - // dir: Default sort direction to use if none has been specified - // anno: The annotation to sort by, if key is "ANNOTATION" - var colLookupTable = { - title: { key: "TITLE", dir: "ascending" }, - tags: { key: "TAGS", dir: "ascending" }, - url: { key: "URI", dir: "ascending" }, - date: { key: "DATE", dir: "descending" }, - visitCount: { key: "VISITCOUNT", dir: "descending" }, - dateAdded: { key: "DATEADDED", dir: "descending" }, - lastModified: { key: "LASTMODIFIED", dir: "descending" }, - description: { key: "ANNOTATION", - dir: "ascending", - anno: PlacesUIUtils.DESCRIPTION_ANNO } - }; - - // Make sure we have a valid column. - if (!colLookupTable.hasOwnProperty(columnId)) - throw new Error("Invalid column"); - - // Use a default sort direction if none has been specified. If aDirection - // is invalid, result.sortingMode will be undefined, which has the effect - // of unsorting the tree. - aDirection = (aDirection || colLookupTable[columnId].dir).toUpperCase(); - - var sortConst = "SORT_BY_" + colLookupTable[columnId].key + "_" + aDirection; - result.sortingAnnotation = colLookupTable[columnId].anno || ""; - result.sortingMode = Ci.nsINavHistoryQueryOptions[sortConst]; - } -} - -var ContentArea = { - _specialViews: new Map(), - - init: function CA_init() { - this._deck = document.getElementById("placesViewsDeck"); - this._toolbar = document.getElementById("placesToolbar"); - ContentTree.init(); - this._setupView(); - }, - - /** - * Gets the content view to be used for loading the given query. - * If a custom view was set by setContentViewForQueryString, that - * view would be returned, else the default tree view is returned - * - * @param aQueryString - * a query string - * @return the view to be used for loading aQueryString. - */ - getContentViewForQueryString: - function CA_getContentViewForQueryString(aQueryString) { - try { - if (this._specialViews.has(aQueryString)) { - let { view, options } = this._specialViews.get(aQueryString); - if (typeof view == "function") { - view = view(); - this._specialViews.set(aQueryString, { view: view, options: options }); - } - return view; - } - } - catch (ex) { - Components.utils.reportError(ex); - } - return ContentTree.view; - }, - - /** - * Sets a custom view to be used rather than the default places tree - * whenever the given query is selected in the left pane. - * @param aQueryString - * a query string - * @param aView - * Either the custom view or a function that will return the view - * the first (and only) time it's called. - * @param [optional] aOptions - * Object defining special options for the view. - * @see ContentTree.viewOptions for supported options and default values. - */ - setContentViewForQueryString: - function CA_setContentViewForQueryString(aQueryString, aView, aOptions) { - if (!aQueryString || - typeof aView != "object" && typeof aView != "function") - throw new Error("Invalid arguments"); - - this._specialViews.set(aQueryString, { view: aView, - options: aOptions || {} }); - }, - - get currentView() { - return PlacesUIUtils.getViewForNode(this._deck.selectedPanel); - }, - set currentView(aNewView) { - let oldView = this.currentView; - if (oldView != aNewView) { - this._deck.selectedPanel = aNewView.associatedElement; - - // If the content area inactivated view was focused, move focus - // to the new view. - if (document.activeElement == oldView.associatedElement) - aNewView.associatedElement.focus(); - } - return aNewView; - }, - - get currentPlace() { - return this.currentView.place; - }, - set currentPlace(aQueryString) { - let oldView = this.currentView; - let newView = this.getContentViewForQueryString(aQueryString); - newView.place = aQueryString; - if (oldView != newView) { - oldView.active = false; - this.currentView = newView; - this._setupView(); - newView.active = true; - } - return aQueryString; - }, - - /** - * Applies view options. - */ - _setupView: function CA__setupView() { - let options = this.currentViewOptions; - - // showDetailsPane. - let detailsDeck = document.getElementById("detailsDeck"); - detailsDeck.hidden = !options.showDetailsPane; - - // toolbarSet. - for (let elt of this._toolbar.childNodes) { - // On Windows and Linux the menu buttons are menus wrapped in a menubar. - if (elt.id == "placesMenu") { - for (let menuElt of elt.childNodes) { - menuElt.hidden = !options.toolbarSet.includes(menuElt.id); - } - } - else { - elt.hidden = !options.toolbarSet.includes(elt.id); - } - } - }, - - /** - * Options for the current view. - * - * @see ContentTree.viewOptions for supported options and default values. - */ - get currentViewOptions() { - // Use ContentTree options as default. - let viewOptions = ContentTree.viewOptions; - if (this._specialViews.has(this.currentPlace)) { - let { options } = this._specialViews.get(this.currentPlace); - for (let option in options) { - viewOptions[option] = options[option]; - } - } - return viewOptions; - }, - - focus: function() { - this._deck.selectedPanel.focus(); - } -}; - -var ContentTree = { - init: function CT_init() { - this._view = document.getElementById("placeContent"); - }, - - get view() { - return this._view; - }, - - get viewOptions() { - return Object.seal({ - showDetailsPane: true, - toolbarSet: "back-button, forward-button, organizeButton, viewMenu, maintenanceButton, libraryToolbarSpacer, searchFilter" - }); - }, - - openSelectedNode: function CT_openSelectedNode(aEvent) { - let view = this.view; - PlacesUIUtils.openNodeWithEvent(view.selectedNode, aEvent, view); - }, - - onClick: function CT_onClick(aEvent) { - let node = this.view.selectedNode; - if (node) { - let doubleClick = aEvent.button == 0 && aEvent.detail == 2; - let middleClick = aEvent.button == 1 && aEvent.detail == 1; - if (PlacesUtils.nodeIsURI(node) && (doubleClick || middleClick)) { - // Open associated uri in the browser. - this.openSelectedNode(aEvent); - } - else if (middleClick && PlacesUtils.nodeIsContainer(node)) { - // The command execution function will take care of seeing if the - // selection is a folder or a different container type, and will - // load its contents in tabs. - PlacesUIUtils.openContainerNodeInTabs(node, aEvent, this.view); - } - } - }, - - onKeyPress: function CT_onKeyPress(aEvent) { - if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) - this.openSelectedNode(aEvent); - } -}; diff --git a/browser/components/places/content/places.xul b/browser/components/places/content/places.xul deleted file mode 100644 index 16c3385cb..000000000 --- a/browser/components/places/content/places.xul +++ /dev/null @@ -1,438 +0,0 @@ -<?xml version="1.0"?> - -# 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/. - -<?xml-stylesheet href="chrome://browser/content/places/places.css"?> -<?xml-stylesheet href="chrome://browser/content/places/organizer.css"?> - -<?xml-stylesheet href="chrome://global/skin/"?> -<?xml-stylesheet href="chrome://browser/skin/places/places.css"?> -<?xml-stylesheet href="chrome://browser/skin/places/organizer.css"?> - -<?xul-overlay href="chrome://browser/content/places/editBookmarkOverlay.xul"?> - -#ifdef XP_MACOSX -<?xul-overlay href="chrome://browser/content/macBrowserOverlay.xul"?> -#else -<?xul-overlay href="chrome://browser/content/baseMenuOverlay.xul"?> -<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?> -<?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?> -#endif - -<!DOCTYPE window [ -<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd"> -%placesDTD; -<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd"> -%editMenuOverlayDTD; -<!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd"> -%browserDTD; -]> - -<window id="places" - title="&places.library.title;" - windowtype="Places:Organizer" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" - xmlns:html="http://www.w3.org/1999/xhtml" - onload="PlacesOrganizer.init();" - onunload="PlacesOrganizer.destroy();" - width="&places.library.width;" height="&places.library.height;" - screenX="10" screenY="10" - toggletoolbar="true" - persist="width height screenX screenY sizemode"> - - <script type="application/javascript" - src="chrome://browser/content/places/places.js"/> - <script type="application/javascript" - src="chrome://browser/content/utilityOverlay.js"/> - <script type="application/javascript" - src="chrome://browser/content/places/editBookmarkOverlay.js"/> - - <stringbundleset id="placesStringSet"> - <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/> - </stringbundleset> - - -#ifdef XP_MACOSX -#include ../../../base/content/browserMountPoints.inc -#else - <commandset id="editMenuCommands"/> - <commandset id="placesCommands"/> -#endif - - <commandset id="organizerCommandSet"> - <command id="OrganizerCommand_find:all" - oncommand="PlacesSearchBox.findAll();"/> - <command id="OrganizerCommand_export" - oncommand="PlacesOrganizer.exportBookmarks();"/> - <command id="OrganizerCommand_import" - oncommand="PlacesOrganizer.importFromFile();"/> - <command id="OrganizerCommand_browserImport" - oncommand="PlacesOrganizer.importFromBrowser();"/> - <command id="OrganizerCommand_backup" - oncommand="PlacesOrganizer.backupBookmarks();"/> - <command id="OrganizerCommand_restoreFromFile" - oncommand="PlacesOrganizer.onRestoreBookmarksFromFile();"/> - <command id="OrganizerCommand_search:save" - oncommand="PlacesOrganizer.saveSearch();"/> - <command id="OrganizerCommand_search:moreCriteria" - oncommand="PlacesQueryBuilder.addRow();"/> - <command id="OrganizerCommand:Back" - oncommand="PlacesOrganizer.back();"/> - <command id="OrganizerCommand:Forward" - oncommand="PlacesOrganizer.forward();"/> - </commandset> - - - <keyset id="placesOrganizerKeyset"> - <!-- Instantiation Keys --> - <key id="placesKey_close" key="&cmd.close.key;" modifiers="accel" - oncommand="close();"/> - - <!-- Command Keys --> - <key id="placesKey_find:all" - command="OrganizerCommand_find:all" - key="&cmd.find.key;" - modifiers="accel"/> - - <!-- Back/Forward Keys Support --> -#ifndef XP_MACOSX - <key id="placesKey_goBackKb" - keycode="VK_LEFT" - command="OrganizerCommand:Back" - modifiers="alt"/> - <key id="placesKey_goForwardKb" - keycode="VK_RIGHT" - command="OrganizerCommand:Forward" - modifiers="alt"/> -#else - <key id="placesKey_goBackKb" - keycode="VK_LEFT" - command="OrganizerCommand:Back" - modifiers="accel"/> - <key id="placesKey_goForwardKb" - keycode="VK_RIGHT" - command="OrganizerCommand:Forward" - modifiers="accel"/> -#endif -#ifdef XP_UNIX - <key id="placesKey_goBackKb2" - key="&goBackCmd.commandKey;" - command="OrganizerCommand:Back" - modifiers="accel"/> - <key id="placesKey_goForwardKb2" - key="&goForwardCmd.commandKey;" - command="OrganizerCommand:Forward" - modifiers="accel"/> -#endif - </keyset> - - <keyset id="editMenuKeys"> -#ifdef XP_MACOSX - <key id="key_delete2" keycode="VK_BACK" command="cmd_delete"/> -#endif - </keyset> - - <popupset id="placesPopupset"> - <menupopup id="placesContext"/> - <menupopup id="placesColumnsContext" - onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox', null);" - oncommand="ViewMenu.showHideColumn(event.target); event.stopPropagation();"/> - </popupset> - - <toolbox id="placesToolbox"> - <toolbar class="chromeclass-toolbar" id="placesToolbar" align="center"> - <toolbarbutton id="back-button" - command="OrganizerCommand:Back" - tooltiptext="&backButton.tooltip;" - disabled="true"/> - - <toolbarbutton id="forward-button" - command="OrganizerCommand:Forward" - tooltiptext="&forwardButton.tooltip;" - disabled="true"/> - -#ifdef XP_MACOSX - <toolbarbutton type="menu" class="tabbable" - onpopupshowing="document.getElementById('placeContent').focus()" -#else - <menubar id="placesMenu"> - <menu accesskey="&organize.accesskey;" class="menu-iconic" -#endif - id="organizeButton" label="&organize.label;" - tooltiptext="&organize.tooltip;"> - <menupopup id="organizeButtonPopup"> - <menuitem id="newbookmark" - command="placesCmd_new:bookmark" - label="&cmd.new_bookmark.label;" - accesskey="&cmd.new_bookmark.accesskey;"/> - <menuitem id="newfolder" - command="placesCmd_new:folder" - label="&cmd.new_folder.label;" - accesskey="&cmd.new_folder.accesskey;"/> - <menuitem id="newseparator" - command="placesCmd_new:separator" - label="&cmd.new_separator.label;" - accesskey="&cmd.new_separator.accesskey;"/> - -#ifndef XP_MACOSX - <menuseparator id="orgUndoSeparator"/> - - <menuitem id="orgUndo" - command="cmd_undo" - label="&undoCmd.label;" - key="key_undo" - accesskey="&undoCmd.accesskey;"/> - <menuitem id="orgRedo" - command="cmd_redo" - label="&redoCmd.label;" - key="key_redo" - accesskey="&redoCmd.accesskey;"/> - - <menuseparator id="orgCutSeparator"/> - - <menuitem id="orgCut" - command="cmd_cut" - label="&cutCmd.label;" - key="key_cut" - accesskey="&cutCmd.accesskey;" - selection="separator|link|folder|mixed"/> - <menuitem id="orgCopy" - command="cmd_copy" - label="©Cmd.label;" - key="key_copy" - accesskey="©Cmd.accesskey;" - selection="separator|link|folder|mixed"/> - <menuitem id="orgPaste" - command="cmd_paste" - label="&pasteCmd.label;" - key="key_paste" - accesskey="&pasteCmd.accesskey;" - selection="mutable"/> - <menuitem id="orgDelete" - command="cmd_delete" - label="&deleteCmd.label;" - key="key_delete" - accesskey="&deleteCmd.accesskey;"/> - - <menuseparator id="selectAllSeparator"/> - - <menuitem id="orgSelectAll" - command="cmd_selectAll" - label="&selectAllCmd.label;" - key="key_selectAll" - accesskey="&selectAllCmd.accesskey;"/> - -#endif - <menuseparator id="orgMoveSeparator"/> - - <menuitem id="orgMoveBookmarks" - command="placesCmd_moveBookmarks" - label="&cmd.moveBookmarks.label;" - accesskey="&cmd.moveBookmarks.accesskey;"/> -#ifdef XP_MACOSX - <menuitem id="orgDelete" - command="cmd_delete" - label="&deleteCmd.label;" - key="key_delete" - accesskey="&deleteCmd.accesskey;"/> -#else - <menuseparator id="orgCloseSeparator"/> - - <menuitem id="orgClose" - key="placesKey_close" - label="&file.close.label;" - accesskey="&file.close.accesskey;" - oncommand="close();"/> -#endif - </menupopup> -#ifdef XP_MACOSX - </toolbarbutton> - <toolbarbutton type="menu" class="tabbable" -#else - </menu> - <menu accesskey="&views.accesskey;" class="menu-iconic" -#endif - id="viewMenu" label="&views.label;" - tooltiptext="&views.tooltip;"> - <menupopup id="viewMenuPopup"> - - <menu id="viewColumns" - label="&view.columns.label;" accesskey="&view.columns.accesskey;"> - <menupopup onpopupshowing="ViewMenu.fillWithColumns(event, null, null, 'checkbox', null);" - oncommand="ViewMenu.showHideColumn(event.target); event.stopPropagation();"/> - </menu> - - <menu id="viewSort" label="&view.sort.label;" - accesskey="&view.sort.accesskey;"> - <menupopup onpopupshowing="ViewMenu.populateSortMenu(event);" - oncommand="ViewMenu.setSortColumn(event.target.column, null);"> - <menuitem id="viewUnsorted" type="radio" name="columns" - label="&view.unsorted.label;" accesskey="&view.unsorted.accesskey;" - oncommand="ViewMenu.setSortColumn(null, null);"/> - <menuseparator id="directionSeparator"/> - <menuitem id="viewSortAscending" type="radio" name="direction" - label="&view.sortAscending.label;" accesskey="&view.sortAscending.accesskey;" - oncommand="ViewMenu.setSortColumn(null, 'ascending'); event.stopPropagation();"/> - <menuitem id="viewSortDescending" type="radio" name="direction" - label="&view.sortDescending.label;" accesskey="&view.sortDescending.accesskey;" - oncommand="ViewMenu.setSortColumn(null, 'descending'); event.stopPropagation();"/> - </menupopup> - </menu> - </menupopup> -#ifdef XP_MACOSX - </toolbarbutton> - <toolbarbutton type="menu" class="tabbable" -#else - </menu> - <menu accesskey="&maintenance.accesskey;" class="menu-iconic" -#endif - id="maintenanceButton" label="&maintenance.label;" - tooltiptext="&maintenance.tooltip;"> - <menupopup id="maintenanceButtonPopup"> - <menuitem id="backupBookmarks" - command="OrganizerCommand_backup" - label="&cmd.backup.label;" - accesskey="&cmd.backup.accesskey;"/> - <menu id="fileRestoreMenu" label="&cmd.restore2.label;" - accesskey="&cmd.restore2.accesskey;"> - <menupopup id="fileRestorePopup" onpopupshowing="PlacesOrganizer.populateRestoreMenu();"> - <menuitem id="restoreFromFile" - command="OrganizerCommand_restoreFromFile" - label="&cmd.restoreFromFile.label;" - accesskey="&cmd.restoreFromFile.accesskey;"/> - </menupopup> - </menu> - <menuseparator/> - <menuitem id="fileImport" - command="OrganizerCommand_import" - label="&importBookmarksFromHTML.label;" - accesskey="&importBookmarksFromHTML.accesskey;"/> - <menuitem id="fileExport" - command="OrganizerCommand_export" - label="&exportBookmarksToHTML.label;" - accesskey="&exportBookmarksToHTML.accesskey;"/> - <menuseparator/> - <menuitem id="browserImport" - command="OrganizerCommand_browserImport" - label="&importOtherBrowser.label;" - accesskey="&importOtherBrowser.accesskey;"/> - </menupopup> -#ifdef XP_MACOSX - </toolbarbutton> -#else - </menu> - </menubar> -#endif - - <spacer id="libraryToolbarSpacer" flex="1"/> - - <textbox id="searchFilter" - clickSelectsAll="true" - type="search" - aria-controls="placeContent" - oncommand="PlacesSearchBox.search(this.value);" - collection="bookmarks"> - </textbox> - </toolbar> - </toolbox> - - <hbox flex="1" id="placesView"> - <tree id="placesList" - class="plain placesTree" - type="places" - hidecolumnpicker="true" context="placesContext" - onselect="PlacesOrganizer.onPlaceSelected(true);" - onclick="PlacesOrganizer.onPlacesListClick(event);" - onfocus="PlacesOrganizer.updateDetailsPane(event);" - seltype="single" - persist="width" - width="200" - minwidth="100" - maxwidth="400"> - <treecols> - <treecol anonid="title" flex="1" primary="true" hideheader="true"/> - </treecols> - <treechildren flex="1"/> - </tree> - <splitter collapse="none" persist="state"></splitter> - <vbox id="contentView" flex="4"> - <deck id="placesViewsDeck" - selectedIndex="0" - flex="1"> - <tree id="placeContent" - class="plain placesTree" - context="placesContext" - hidecolumnpicker="true" - flex="1" - type="places" - flatList="true" - selectfirstnode="true" - enableColumnDrag="true" - onfocus="PlacesOrganizer.updateDetailsPane(event)" - onselect="PlacesOrganizer.updateDetailsPane(event)" - onkeypress="ContentTree.onKeyPress(event);" - onopenflatcontainer="PlacesOrganizer.openFlatContainer(aContainer);"> - <treecols id="placeContentColumns" context="placesColumnsContext"> - <treecol label="&col.name.label;" id="placesContentTitle" anonid="title" flex="5" primary="true" ordinal="1" - persist="width hidden ordinal sortActive sortDirection"/> - <splitter class="tree-splitter"/> - <treecol label="&col.tags.label;" id="placesContentTags" anonid="tags" flex="2" - persist="width hidden ordinal sortActive sortDirection"/> - <splitter class="tree-splitter"/> - <treecol label="&col.url.label;" id="placesContentUrl" anonid="url" flex="5" - persist="width hidden ordinal sortActive sortDirection"/> - <splitter class="tree-splitter"/> - <treecol label="&col.mostrecentvisit.label;" id="placesContentDate" anonid="date" flex="1" hidden="true" - persist="width hidden ordinal sortActive sortDirection"/> - <splitter class="tree-splitter"/> - <treecol label="&col.visitcount.label;" id="placesContentVisitCount" anonid="visitCount" flex="1" hidden="true" - persist="width hidden ordinal sortActive sortDirection"/> - <splitter class="tree-splitter"/> - <treecol label="&col.description.label;" id="placesContentDescription" anonid="description" flex="1" hidden="true" - persist="width hidden ordinal sortActive sortDirection"/> - <splitter class="tree-splitter"/> - <treecol label="&col.dateadded.label;" id="placesContentDateAdded" anonid="dateAdded" flex="1" hidden="true" - persist="width hidden ordinal sortActive sortDirection"/> - <splitter class="tree-splitter"/> - <treecol label="&col.lastmodified.label;" id="placesContentLastModified" anonid="lastModified" flex="1" hidden="true" - persist="width hidden ordinal sortActive sortDirection"/> - </treecols> - <treechildren flex="1" onclick="ContentTree.onClick(event);"/> - </tree> - </deck> - <deck id="detailsDeck" style="height: 11em;"> - <vbox id="itemsCountBox" align="center"> - <spacer flex="3"/> - <label id="itemsCountText"/> - <spacer flex="1"/> - <description id="selectItemDescription"> - &detailsPane.selectAnItemText.description; - </description> - <spacer flex="3"/> - </vbox> - <vbox id="infoBox" minimal="true"> - <vbox id="editBookmarkPanelContent" flex="1"/> - <hbox id="infoBoxExpanderWrapper" align="center"> - - <button type="image" id="infoBoxExpander" - class="expander-down" - oncommand="PlacesOrganizer.toggleAdditionalInfoFields();" - observes="paneElementsBroadcaster"/> - - <label id="infoBoxExpanderLabel" - lesslabel="&detailsPane.less.label;" - lessaccesskey="&detailsPane.less.accesskey;" - morelabel="&detailsPane.more.label;" - moreaccesskey="&detailsPane.more.accesskey;" - value="&detailsPane.more.label;" - accesskey="&detailsPane.more.accesskey;" - control="infoBoxExpander"/> - - </hbox> - </vbox> - </deck> - </vbox> - </hbox> -</window> diff --git a/browser/components/places/content/placesOverlay.xul b/browser/components/places/content/placesOverlay.xul deleted file mode 100644 index 512eb923e..000000000 --- a/browser/components/places/content/placesOverlay.xul +++ /dev/null @@ -1,233 +0,0 @@ -<!-- 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/. --> - -<!DOCTYPE overlay [ -<!ENTITY % placesDTD SYSTEM "chrome://browser/locale/places/places.dtd"> -%placesDTD; -<!ENTITY % editMenuOverlayDTD SYSTEM "chrome://global/locale/editMenuOverlay.dtd"> -%editMenuOverlayDTD; -]> - -<overlay id="placesOverlay" - xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" - xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <script type="application/javascript" - src="chrome://global/content/globalOverlay.js"/> - <script type="application/javascript" - src="chrome://browser/content/utilityOverlay.js"/> - <script type="application/javascript"><![CDATA[ - // TODO: Bug 406371. - // A bunch of browser code depends on us defining these, sad but true :( - var Cc = Components.classes; - var Ci = Components.interfaces; - var Cr = Components.results; - - Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); - Components.utils.import("resource://gre/modules/Task.jsm"); - Components.utils.import("resource://gre/modules/PlacesUtils.jsm"); - XPCOMUtils.defineLazyModuleGetter(window, - "PlacesUIUtils", "resource:///modules/PlacesUIUtils.jsm"); - XPCOMUtils.defineLazyModuleGetter(window, - "PlacesTransactions", "resource://gre/modules/PlacesTransactions.jsm"); - ]]></script> - <script type="application/javascript" - src="chrome://browser/content/places/controller.js"/> - <script type="application/javascript" - src="chrome://browser/content/places/treeView.js"/> - - <!-- Bookmarks and history tooltip --> - <tooltip id="bhTooltip" noautohide="true" - onpopupshowing="return window.top.BookmarksEventHandler.fillInBHTooltip(document, event)"> - <vbox id="bhTooltipTextBox" flex="1"> - <label id="bhtTitleText" class="tooltip-label" /> - <label id="bhtUrlText" crop="center" class="tooltip-label" /> - </vbox> - </tooltip> - - <commandset id="placesCommands" - commandupdater="true" - events="focus,sort,places" - oncommandupdate="goUpdatePlacesCommands();"> - <command id="placesCmd_open" - oncommand="goDoPlacesCommand('placesCmd_open');"/> - <command id="placesCmd_open:window" - oncommand="goDoPlacesCommand('placesCmd_open:window');"/> - <command id="placesCmd_open:privatewindow" - oncommand="goDoPlacesCommand('placesCmd_open:privatewindow');"/> - <command id="placesCmd_open:tab" - oncommand="goDoPlacesCommand('placesCmd_open:tab');"/> - - <command id="placesCmd_new:bookmark" - oncommand="goDoPlacesCommand('placesCmd_new:bookmark');"/> - <command id="placesCmd_new:folder" - oncommand="goDoPlacesCommand('placesCmd_new:folder');"/> - <command id="placesCmd_new:separator" - oncommand="goDoPlacesCommand('placesCmd_new:separator');"/> - <command id="placesCmd_show:info" - oncommand="goDoPlacesCommand('placesCmd_show:info');"/> - <command id="placesCmd_rename" - oncommand="goDoPlacesCommand('placesCmd_show:info');" - observes="placesCmd_show:info"/> - <command id="placesCmd_reload" - oncommand="goDoPlacesCommand('placesCmd_reload');"/> - <command id="placesCmd_sortBy:name" - oncommand="goDoPlacesCommand('placesCmd_sortBy:name');"/> - <command id="placesCmd_moveBookmarks" - oncommand="goDoPlacesCommand('placesCmd_moveBookmarks');"/> - <command id="placesCmd_deleteDataHost" - oncommand="goDoPlacesCommand('placesCmd_deleteDataHost');"/> - <command id="placesCmd_createBookmark" - oncommand="goDoPlacesCommand('placesCmd_createBookmark');"/> - - <!-- Special versions of cut/copy/paste/delete which check for an open context menu. --> - <command id="placesCmd_cut" - oncommand="goDoPlacesCommand('placesCmd_cut');"/> - <command id="placesCmd_copy" - oncommand="goDoPlacesCommand('placesCmd_copy');"/> - <command id="placesCmd_paste" - oncommand="goDoPlacesCommand('placesCmd_paste');"/> - <command id="placesCmd_delete" - oncommand="goDoPlacesCommand('placesCmd_delete');"/> - </commandset> - - <menupopup id="placesContext" - onpopupshowing="this._view = PlacesUIUtils.getViewForNode(document.popupNode); - return this._view.buildContextMenu(this);" - onpopuphiding="this._view.destroyContextMenu();"> - <menuitem id="placesContext_open" - command="placesCmd_open" - label="&cmd.open.label;" - accesskey="&cmd.open.accesskey;" - default="true" - selectiontype="single" - selection="link"/> - <menuitem id="placesContext_open:newtab" - command="placesCmd_open:tab" - label="&cmd.open_tab.label;" - accesskey="&cmd.open_tab.accesskey;" - selectiontype="single" - selection="link"/> - <menuitem id="placesContext_openContainer:tabs" - oncommand="var view = PlacesUIUtils.getViewForNode(document.popupNode); - view.controller.openSelectionInTabs(event);" - onclick="checkForMiddleClick(this, event);" - label="&cmd.open_all_in_tabs.label;" - accesskey="&cmd.open_all_in_tabs.accesskey;" - selectiontype="single|none" - selection="folder|host|query"/> - <menuitem id="placesContext_openLinks:tabs" - oncommand="var view = PlacesUIUtils.getViewForNode(document.popupNode); - view.controller.openSelectionInTabs(event);" - onclick="checkForMiddleClick(this, event);" - label="&cmd.open_all_in_tabs.label;" - accesskey="&cmd.open_all_in_tabs.accesskey;" - selectiontype="multiple" - selection="link"/> - <menuitem id="placesContext_open:newwindow" - command="placesCmd_open:window" - label="&cmd.open_window.label;" - accesskey="&cmd.open_window.accesskey;" - selectiontype="single" - selection="link"/> - <menuitem id="placesContext_open:newprivatewindow" - command="placesCmd_open:privatewindow" - label="&cmd.open_private_window.label;" - accesskey="&cmd.open_private_window.accesskey;" - selectiontype="single" - selection="link" - hideifprivatebrowsing="true"/> - <menuseparator id="placesContext_openSeparator"/> - <menuitem id="placesContext_new:bookmark" - command="placesCmd_new:bookmark" - label="&cmd.new_bookmark.label;" - accesskey="&cmd.new_bookmark.accesskey;" - selectiontype="any" - hideifnoinsertionpoint="true"/> - <menuitem id="placesContext_new:folder" - command="placesCmd_new:folder" - label="&cmd.new_folder.label;" - accesskey="&cmd.context_new_folder.accesskey;" - selectiontype="any" - hideifnoinsertionpoint="true"/> - <menuitem id="placesContext_new:separator" - command="placesCmd_new:separator" - label="&cmd.new_separator.label;" - accesskey="&cmd.new_separator.accesskey;" - closemenu="single" - selectiontype="any" - hideifnoinsertionpoint="true"/> - <menuseparator id="placesContext_newSeparator"/> - <menuitem id="placesContext_createBookmark" - command="placesCmd_createBookmark" - label="&cmd.bookmarkLink.label;" - accesskey="&cmd.bookmarkLink.accesskey;" - selection="link" - forcehideselection="bookmark|tagChild"/> - <menuitem id="placesContext_cut" - command="placesCmd_cut" - label="&cutCmd.label;" - accesskey="&cutCmd.accesskey;" - closemenu="single" - selection="bookmark|folder|separator|query" - forcehideselection="tagChild|livemarkChild"/> - <menuitem id="placesContext_copy" - command="placesCmd_copy" - label="©Cmd.label;" - closemenu="single" - accesskey="©Cmd.accesskey;" - selection="any"/> - <menuitem id="placesContext_paste" - command="placesCmd_paste" - label="&pasteCmd.label;" - closemenu="single" - accesskey="&pasteCmd.accesskey;" - selectiontype="any" - hideifnoinsertionpoint="true"/> - <menuseparator id="placesContext_editSeparator"/> - <menuitem id="placesContext_delete" - command="placesCmd_delete" - label="&deleteCmd.label;" - accesskey="&deleteCmd.accesskey;" - closemenu="single" - selection="bookmark|tagChild|folder|query|dynamiccontainer|separator|host"/> - <menuitem id="placesContext_delete_history" - command="placesCmd_delete" - label="&cmd.delete.label;" - accesskey="&cmd.delete.accesskey;" - closemenu="single" - selection="link" - forcehideselection="bookmark"/> - <menuitem id="placesContext_deleteHost" - command="placesCmd_deleteDataHost" - label="&cmd.deleteDomainData.label;" - accesskey="&cmd.deleteDomainData.accesskey;" - closemenu="single" - selection="link|host" - selectiontype="single" - hideifprivatebrowsing="true" - forcehideselection="bookmark"/> - <menuseparator id="placesContext_deleteSeparator"/> - <menuitem id="placesContext_sortBy:name" - command="placesCmd_sortBy:name" - label="&cmd.sortby_name.label;" - accesskey="&cmd.context_sortby_name.accesskey;" - closemenu="single" - selection="folder"/> - <menuitem id="placesContext_reload" - command="placesCmd_reload" - label="&cmd.reloadLivebookmark.label;" - accesskey="&cmd.reloadLivebookmark.accesskey;" - closemenu="single" - selection="livemark/feedURI"/> - <menuseparator id="placesContext_sortSeparator"/> - <menuitem id="placesContext_show:info" - command="placesCmd_show:info" - label="&cmd.properties.label;" - accesskey="&cmd.properties.accesskey;" - selection="bookmark|folder|query" - forcehideselection="livemarkChild"/> - </menupopup> - -</overlay> diff --git a/browser/components/places/content/sidebarUtils.js b/browser/components/places/content/sidebarUtils.js deleted file mode 100644 index 96c289741..000000000 --- a/browser/components/places/content/sidebarUtils.js +++ /dev/null @@ -1,106 +0,0 @@ -/* 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/. */ - -Components.utils.import("resource://gre/modules/AppConstants.jsm"); - -var SidebarUtils = { - handleTreeClick: function SU_handleTreeClick(aTree, aEvent, aGutterSelect) { - // right-clicks are not handled here - if (aEvent.button == 2) - return; - - var tbo = aTree.treeBoxObject; - var cell = tbo.getCellAt(aEvent.clientX, aEvent.clientY); - - if (cell.row == -1 || cell.childElt == "twisty") - return; - - var mouseInGutter = false; - if (aGutterSelect) { - var rect = tbo.getCoordsForCellItem(cell.row, cell.col, "image"); - // getCoordsForCellItem returns the x coordinate in logical coordinates - // (i.e., starting from the left and right sides in LTR and RTL modes, - // respectively.) Therefore, we make sure to exclude the blank area - // before the tree item icon (that is, to the left or right of it in - // LTR and RTL modes, respectively) from the click target area. - var isRTL = window.getComputedStyle(aTree, null).direction == "rtl"; - if (isRTL) - mouseInGutter = aEvent.clientX > rect.x; - else - mouseInGutter = aEvent.clientX < rect.x; - } - - var metaKey = AppConstants.platform === "macosx" ? aEvent.metaKey - : aEvent.ctrlKey; - var modifKey = metaKey || aEvent.shiftKey; - var isContainer = tbo.view.isContainer(cell.row); - var openInTabs = isContainer && - (aEvent.button == 1 || - (aEvent.button == 0 && modifKey)) && - PlacesUtils.hasChildURIs(tbo.view.nodeForTreeIndex(cell.row)); - - if (aEvent.button == 0 && isContainer && !openInTabs) { - tbo.view.toggleOpenState(cell.row); - return; - } - else if (!mouseInGutter && openInTabs && - aEvent.originalTarget.localName == "treechildren") { - tbo.view.selection.select(cell.row); - PlacesUIUtils.openContainerNodeInTabs(aTree.selectedNode, aEvent, aTree); - } - else if (!mouseInGutter && !isContainer && - aEvent.originalTarget.localName == "treechildren") { - // Clear all other selection since we're loading a link now. We must - // do this *before* attempting to load the link since openURL uses - // selection as an indication of which link to load. - tbo.view.selection.select(cell.row); - PlacesUIUtils.openNodeWithEvent(aTree.selectedNode, aEvent, aTree); - } - }, - - handleTreeKeyPress: function SU_handleTreeKeyPress(aEvent) { - // XXX Bug 627901: Post Fx4, this method should take a tree parameter. - let tree = aEvent.target; - let node = tree.selectedNode; - if (node) { - if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN) - PlacesUIUtils.openNodeWithEvent(node, aEvent, tree); - } - }, - - /** - * The following function displays the URL of a node that is being - * hovered over. - */ - handleTreeMouseMove: function SU_handleTreeMouseMove(aEvent) { - if (aEvent.target.localName != "treechildren") - return; - - var tree = aEvent.target.parentNode; - var tbo = tree.treeBoxObject; - var cell = tbo.getCellAt(aEvent.clientX, aEvent.clientY); - - // cell.row is -1 when the mouse is hovering an empty area within the tree. - // To avoid showing a URL from a previously hovered node for a currently - // hovered non-url node, we must clear the moused-over URL in these cases. - if (cell.row != -1) { - var node = tree.view.nodeForTreeIndex(cell.row); - if (PlacesUtils.nodeIsURI(node)) - this.setMouseoverURL(node.uri); - else - this.setMouseoverURL(""); - } - else - this.setMouseoverURL(""); - }, - - setMouseoverURL: function SU_setMouseoverURL(aURL) { - // When the browser window is closed with an open sidebar, the sidebar - // unload event happens after the browser's one. In this case - // top.XULBrowserWindow has been nullified already. - if (top.XULBrowserWindow) { - top.XULBrowserWindow.setOverLink(aURL, null); - } - } -}; diff --git a/browser/components/places/content/tree.xml b/browser/components/places/content/tree.xml deleted file mode 100644 index 338ee81f6..000000000 --- a/browser/components/places/content/tree.xml +++ /dev/null @@ -1,801 +0,0 @@ -<?xml version="1.0"?> - -<!-- 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/. --> - -<bindings id="placesTreeBindings" - xmlns="http://www.mozilla.org/xbl" - xmlns:xbl="http://www.mozilla.org/xbl" - xmlns:html="http://www.w3.org/1999/xhtml" - xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - - <binding id="places-tree" extends="chrome://global/content/bindings/tree.xml#tree"> - <implementation> - <constructor><![CDATA[ - // Force an initial build. - if (this.place) - this.place = this.place; - ]]></constructor> - - <destructor><![CDATA[ - // Break the treeviewer->result->treeviewer cycle. - // Note: unsetting the result's viewer also unsets - // the viewer's reference to our treeBoxObject. - var result = this.result; - if (result) { - result.root.containerOpen = false; - } - - // Unregister the controllber before unlinking the view, otherwise it - // may still try to update commands on a view with a null result. - if (this._controller) { - this._controller.terminate(); - this.controllers.removeController(this._controller); - } - - this.view = null; - ]]></destructor> - - <property name="controller" - readonly="true" - onget="return this._controller"/> - - <!-- overriding --> - <property name="view"> - <getter><![CDATA[ - try { - return this.treeBoxObject.view.wrappedJSObject || null; - } - catch (e) { - return null; - } - ]]></getter> - <setter><![CDATA[ - return this.treeBoxObject.view = val; - ]]></setter> - </property> - - <property name="associatedElement" - readonly="true" - onget="return this"/> - - <method name="applyFilter"> - <parameter name="filterString"/> - <parameter name="folderRestrict"/> - <parameter name="includeHidden"/> - <body><![CDATA[ - // preserve grouping - var queryNode = PlacesUtils.asQuery(this.result.root); - var options = queryNode.queryOptions.clone(); - - // Make sure we're getting uri results. - // We do not yet support searching into grouped queries or into - // tag containers, so we must fall to the default case. - if (PlacesUtils.nodeIsHistoryContainer(queryNode) || - options.resultType == options.RESULTS_AS_TAG_QUERY || - options.resultType == options.RESULTS_AS_TAG_CONTENTS) - options.resultType = options.RESULTS_AS_URI; - - var query = PlacesUtils.history.getNewQuery(); - query.searchTerms = filterString; - - if (folderRestrict) { - query.setFolders(folderRestrict, folderRestrict.length); - options.queryType = options.QUERY_TYPE_BOOKMARKS; - } - - options.includeHidden = !!includeHidden; - - this.load([query], options); - ]]></body> - </method> - - <method name="load"> - <parameter name="queries"/> - <parameter name="options"/> - <body><![CDATA[ - let result = PlacesUtils.history - .executeQueries(queries, queries.length, - options); - let callback; - if (this.flatList) { - let onOpenFlatContainer = this.onOpenFlatContainer; - if (onOpenFlatContainer) - callback = new Function("aContainer", onOpenFlatContainer); - } - - if (!this._controller) { - this._controller = new PlacesController(this); - this.controllers.appendController(this._controller); - } - - let treeView = new PlacesTreeView(this.flatList, callback, this._controller); - - // Observer removal is done within the view itself. When the tree - // goes away, treeboxobject calls view.setTree(null), which then - // calls removeObserver. - result.addObserver(treeView, false); - this.view = treeView; - - if (this.getAttribute("selectfirstnode") == "true" && treeView.rowCount > 0) { - treeView.selection.select(0); - } - - this._cachedInsertionPoint = undefined; - ]]></body> - </method> - - <property name="flatList"> - <getter><![CDATA[ - return this.getAttribute("flatList") == "true"; - ]]></getter> - <setter><![CDATA[ - if (this.flatList != val) { - this.setAttribute("flatList", val); - // reload with the last place set - if (this.place) - this.place = this.place; - } - return val; - ]]></setter> - </property> - - <property name="onOpenFlatContainer"> - <getter><![CDATA[ - return this.getAttribute("onopenflatcontainer"); - ]]></getter> - <setter><![CDATA[ - if (this.onOpenFlatContainer != val) { - this.setAttribute("onopenflatcontainer", val); - // reload with the last place set - if (this.place) - this.place = this.place; - } - return val; - ]]></setter> - </property> - - <!-- - Causes a particular node represented by the specified placeURI to be - selected in the tree. All containers above the node in the hierarchy - will be opened, so that the node is visible. - --> - <method name="selectPlaceURI"> - <parameter name="placeURI"/> - <body><![CDATA[ - // Do nothing if a node matching the given uri is already selected - if (this.hasSelection && this.selectedNode.uri == placeURI) - return; - - function findNode(container, placeURI, nodesURIChecked) { - var containerURI = container.uri; - if (containerURI == placeURI) - return container; - if (nodesURIChecked.includes(containerURI)) - return null; - - // never check the contents of the same query - nodesURIChecked.push(containerURI); - - var wasOpen = container.containerOpen; - if (!wasOpen) - container.containerOpen = true; - for (var i = 0; i < container.childCount; ++i) { - var child = container.getChild(i); - var childURI = child.uri; - if (childURI == placeURI) - return child; - else if (PlacesUtils.nodeIsContainer(child)) { - var nested = findNode(PlacesUtils.asContainer(child), placeURI, nodesURIChecked); - if (nested) - return nested; - } - } - - if (!wasOpen) - container.containerOpen = false; - - return null; - } - - var container = this.result.root; - NS_ASSERT(container, "No result, cannot select place URI!"); - if (!container) - return; - - var child = findNode(container, placeURI, []); - if (child) - this.selectNode(child); - else { - // If the specified child could not be located, clear the selection - var selection = this.view.selection; - selection.clearSelection(); - } - ]]></body> - </method> - - <!-- - Causes a particular node to be selected in the tree, resulting in all - containers above the node in the hierarchy to be opened, so that the - node is visible. - --> - <method name="selectNode"> - <parameter name="node"/> - <body><![CDATA[ - var view = this.view; - - var parent = node.parent; - if (parent && !parent.containerOpen) { - // Build a list of all of the nodes that are the parent of this one - // in the result. - var parents = []; - var root = this.result.root; - while (parent && parent != root) { - parents.push(parent); - parent = parent.parent; - } - - // Walk the list backwards (opening from the root of the hierarchy) - // opening each folder as we go. - for (var i = parents.length - 1; i >= 0; --i) { - let index = view.treeIndexForNode(parents[i]); - if (index != Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE && - view.isContainer(index) && !view.isContainerOpen(index)) - view.toggleOpenState(index); - } - // Select the specified node... - } - - let index = view.treeIndexForNode(node); - if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE) - return; - - view.selection.select(index); - // ... and ensure it's visible, not scrolled off somewhere. - this.treeBoxObject.ensureRowIsVisible(index); - ]]></body> - </method> - - <!-- nsIPlacesView --> - <property name="result"> - <getter><![CDATA[ - try { - return this.view.QueryInterface(Ci.nsINavHistoryResultObserver).result; - } - catch (e) { - return null; - } - ]]></getter> - </property> - - <!-- nsIPlacesView --> - <property name="place"> - <getter><![CDATA[ - return this.getAttribute("place"); - ]]></getter> - <setter><![CDATA[ - this.setAttribute("place", val); - - var queriesRef = { }; - var queryCountRef = { }; - var optionsRef = { }; - PlacesUtils.history.queryStringToQueries(val, queriesRef, queryCountRef, optionsRef); - if (queryCountRef.value == 0) - queriesRef.value = [PlacesUtils.history.getNewQuery()]; - if (!optionsRef.value) - optionsRef.value = PlacesUtils.history.getNewQueryOptions(); - - this.load(queriesRef.value, optionsRef.value); - - return val; - ]]></setter> - </property> - - <!-- nsIPlacesView --> - <property name="hasSelection"> - <getter><![CDATA[ - return this.view && this.view.selection.count >= 1; - ]]></getter> - </property> - - <!-- nsIPlacesView --> - <property name="selectedNodes"> - <getter><![CDATA[ - let nodes = []; - if (!this.hasSelection) - return nodes; - - let selection = this.view.selection; - let rc = selection.getRangeCount(); - let resultview = this.view; - for (let i = 0; i < rc; ++i) { - let min = { }, max = { }; - selection.getRangeAt(i, min, max); - for (let j = min.value; j <= max.value; ++j) { - nodes.push(resultview.nodeForTreeIndex(j)); - } - } - return nodes; - ]]></getter> - </property> - - <method name="toggleCutNode"> - <parameter name="aNode"/> - <parameter name="aValue"/> - <body><![CDATA[ - this.view.toggleCutNode(aNode, aValue); - ]]></body> - </method> - - <!-- nsIPlacesView --> - <property name="removableSelectionRanges"> - <getter><![CDATA[ - // This property exists in addition to selectedNodes because it - // encodes selection ranges (which only occur in list views) into - // the return value. For each removed range, the index at which items - // will be re-inserted upon the remove transaction being performed is - // the first index of the range, so that the view updates correctly. - // - // For example, if we remove rows 2,3,4 and 7,8 from a list, when we - // undo that operation, if we insert what was at row 3 at row 3 again, - // it will show up _after_ the item that was at row 5. So we need to - // insert all items at row 2, and the tree view will update correctly. - // - // Also, this function collapses the selection to remove redundant - // data, e.g. when deleting this selection: - // - // http://www.foo.com/ - // (-) Some Folder - // http://www.bar.com/ - // - // ... returning http://www.bar.com/ as part of the selection is - // redundant because it is implied by removing "Some Folder". We - // filter out all such redundancies since some partial amount of - // the folder's children may be selected. - // - let nodes = []; - if (!this.hasSelection) - return nodes; - - var selection = this.view.selection; - var rc = selection.getRangeCount(); - var resultview = this.view; - // This list is kept independently of the range selected (i.e. OUTSIDE - // the for loop) since the row index of a container is unique for the - // entire view, and we could have some really wacky selection and we - // don't want to blow up. - var containers = { }; - for (var i = 0; i < rc; ++i) { - var range = []; - var min = { }, max = { }; - selection.getRangeAt(i, min, max); - - for (var j = min.value; j <= max.value; ++j) { - if (this.view.isContainer(j)) - containers[j] = true; - if (!(this.view.getParentIndex(j) in containers)) - range.push(resultview.nodeForTreeIndex(j)); - } - nodes.push(range); - } - return nodes; - ]]></getter> - </property> - - <!-- nsIPlacesView --> - <property name="draggableSelection" - onget="return this.selectedNodes"/> - - <!-- nsIPlacesView --> - <property name="selectedNode"> - <getter><![CDATA[ - var view = this.view; - if (!view || view.selection.count != 1) - return null; - - var selection = view.selection; - var min = { }, max = { }; - selection.getRangeAt(0, min, max); - - return this.view.nodeForTreeIndex(min.value); - ]]></getter> - </property> - - <!-- nsIPlacesView --> - <property name="insertionPoint"> - <getter><![CDATA[ - // invalidated on selection and focus changes - if (this._cachedInsertionPoint !== undefined) - return this._cachedInsertionPoint; - - // there is no insertion point for history queries - // so bail out now and save a lot of work when updating commands - var resultNode = this.result.root; - if (PlacesUtils.nodeIsQuery(resultNode) && - PlacesUtils.asQuery(resultNode).queryOptions.queryType == - Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) - return this._cachedInsertionPoint = null; - - var orientation = Ci.nsITreeView.DROP_BEFORE; - // If there is no selection, insert at the end of the container. - if (!this.hasSelection) { - var index = this.view.rowCount - 1; - this._cachedInsertionPoint = - this._getInsertionPoint(index, orientation); - return this._cachedInsertionPoint; - } - - // This is a two-part process. The first part is determining the drop - // orientation. - // * The default orientation is to drop _before_ the selected item. - // * If the selected item is a container, the default orientation - // is to drop _into_ that container. - // - // Warning: It may be tempting to use tree indexes in this code, but - // you must not, since the tree is nested and as your tree - // index may change when folders before you are opened and - // closed. You must convert your tree index to a node, and - // then use getChildIndex to find your absolute index in - // the parent container instead. - // - var resultView = this.view; - var selection = resultView.selection; - var rc = selection.getRangeCount(); - var min = { }, max = { }; - selection.getRangeAt(rc - 1, min, max); - - // If the sole selection is a container, and we are not in - // a flatlist, insert into it. - // Note that this only applies to _single_ selections, - // if the last element within a multi-selection is a - // container, insert _adjacent_ to the selection. - // - // If the sole selection is the bookmarks toolbar folder, we insert - // into it even if it is not opened - if (selection.count == 1 && resultView.isContainer(max.value) && - !this.flatList) - orientation = Ci.nsITreeView.DROP_ON; - - this._cachedInsertionPoint = - this._getInsertionPoint(max.value, orientation); - return this._cachedInsertionPoint; - ]]></getter> - </property> - - <method name="_getInsertionPoint"> - <parameter name="index"/> - <parameter name="orientation"/> - <body><![CDATA[ - var result = this.result; - var resultview = this.view; - var container = result.root; - var dropNearItemId = -1; - NS_ASSERT(container, "null container"); - // When there's no selection, assume the container is the container - // the view is populated from (i.e. the result's itemId). - if (index != -1) { - var lastSelected = resultview.nodeForTreeIndex(index); - if (resultview.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) { - // If the last selected item is an open container, append _into_ - // it, rather than insert adjacent to it. - container = lastSelected; - index = -1; - } - else if (lastSelected.containerOpen && - orientation == Ci.nsITreeView.DROP_AFTER && - lastSelected.hasChildren) { - // If the last selected item is an open container and the user is - // trying to drag into it as a first item, really insert into it. - container = lastSelected; - orientation = Ci.nsITreeView.DROP_ON; - index = 0; - } - else { - // Use the last-selected node's container. - container = lastSelected.parent; - - // See comment in the treeView.js's copy of this method - if (!container || !container.containerOpen) - return null; - - // Avoid the potentially expensive call to getChildIndex - // if we know this container doesn't allow insertion - if (PlacesControllerDragHelper.disallowInsertion(container)) - return null; - - var queryOptions = PlacesUtils.asQuery(result.root).queryOptions; - if (queryOptions.sortingMode != - Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) { - // If we are within a sorted view, insert at the end - index = -1; - } - else if (queryOptions.excludeItems || - queryOptions.excludeQueries || - queryOptions.excludeReadOnlyFolders) { - // Some item may be invisible, insert near last selected one. - // We don't replace index here to avoid requests to the db, - // instead it will be calculated later by the controller. - index = -1; - dropNearItemId = lastSelected.itemId; - } - else { - var lsi = container.getChildIndex(lastSelected); - index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1; - } - } - } - - if (PlacesControllerDragHelper.disallowInsertion(container)) - return null; - - // TODO (Bug 1160193): properly support dropping on a tag root. - let tagName = null; - if (PlacesUtils.nodeIsTagQuery(container)) { - tagName = container.title; - if (!tagName) - return null; - } - - return new InsertionPoint(PlacesUtils.getConcreteItemId(container), - index, orientation, - tagName, - dropNearItemId); - ]]></body> - </method> - - <!-- nsIPlacesView --> - <method name="selectAll"> - <body><![CDATA[ - this.view.selection.selectAll(); - ]]></body> - </method> - - <!-- This method will select the first node in the tree that matches - each given item id. It will open any folder nodes that it needs - to in order to show the selected items. - --> - <method name="selectItems"> - <parameter name="aIDs"/> - <parameter name="aOpenContainers"/> - <body><![CDATA[ - // Never open containers in flat lists. - if (this.flatList) - aOpenContainers = false; - // By default, we do search and select within containers which were - // closed (note that containers in which nodes were not found are - // closed). - if (aOpenContainers === undefined) - aOpenContainers = true; - - var ids = aIDs; // don't manipulate the caller's array - - // Array of nodes found by findNodes which are to be selected - var nodes = []; - - // Array of nodes found by findNodes which should be opened - var nodesToOpen = []; - - // A set of GUIDs of container-nodes that were previously searched, - // and thus shouldn't be searched again. This is empty at the initial - // start of the recursion and gets filled in as the recursion - // progresses. - var checkedGuidsSet = new Set(); - - /** - * Recursively search through a node's children for items - * with the given IDs. When a matching item is found, remove its ID - * from the IDs array, and add the found node to the nodes dictionary. - * - * NOTE: This method will leave open any node that had matching items - * in its subtree. - */ - function findNodes(node) { - var foundOne = false; - // See if node matches an ID we wanted; add to results. - // For simple folder queries, check both itemId and the concrete - // item id. - var index = ids.indexOf(node.itemId); - if (index == -1 && - node.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) - index = ids.indexOf(PlacesUtils.asQuery(node).folderItemId); - - if (index != -1) { - nodes.push(node); - foundOne = true; - ids.splice(index, 1); - } - - var concreteGuid = PlacesUtils.getConcreteItemGuid(node); - if (ids.length == 0 || !PlacesUtils.nodeIsContainer(node) || - checkedGuidsSet.has(concreteGuid)) - return foundOne; - - // Only follow a query if it has been been explicitly opened by the caller. - let shouldOpen = aOpenContainers && PlacesUtils.nodeIsFolder(node); - PlacesUtils.asContainer(node); - if (!node.containerOpen && !shouldOpen) - return foundOne; - - checkedGuidsSet.add(concreteGuid); - - // Remember the beginning state so that we can re-close - // this node if we don't find any additional results here. - var previousOpenness = node.containerOpen; - node.containerOpen = true; - for (var child = 0; child < node.childCount && ids.length > 0; - child++) { - var childNode = node.getChild(child); - var found = findNodes(childNode); - if (!foundOne) - foundOne = found; - } - - // If we didn't find any additional matches in this node's - // subtree, revert the node to its previous openness. - if (foundOne) - nodesToOpen.unshift(node); - node.containerOpen = previousOpenness; - return foundOne; - } - - // Disable notifications while looking for nodes. - let result = this.result; - let didSuppressNotifications = result.suppressNotifications; - if (!didSuppressNotifications) - result.suppressNotifications = true - try { - findNodes(this.result.root); - } - finally { - if (!didSuppressNotifications) - result.suppressNotifications = false; - } - - // For all the nodes we've found, highlight the corresponding - // index in the tree. - var resultview = this.view; - var selection = this.view.selection; - selection.selectEventsSuppressed = true; - selection.clearSelection(); - // Open nodes containing found items - for (let i = 0; i < nodesToOpen.length; i++) { - nodesToOpen[i].containerOpen = true; - } - for (let i = 0; i < nodes.length; i++) { - var index = resultview.treeIndexForNode(nodes[i]); - if (index == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE) - continue; - selection.rangedSelect(index, index, true); - } - selection.selectEventsSuppressed = false; - ]]></body> - </method> - - <field name="_contextMenuShown">false</field> - - <method name="buildContextMenu"> - <parameter name="aPopup"/> - <body><![CDATA[ - this._contextMenuShown = true; - return this.controller.buildContextMenu(aPopup); - ]]></body> - </method> - - <method name="destroyContextMenu"> - <parameter name="aPopup"/> - this._contextMenuShown = false; - <body/> - </method> - - <property name="ownerWindow" - readonly="true" - onget="return window;"/> - - <field name="_active">true</field> - <property name="active" - onget="return this._active" - onset="return this._active = val"/> - - </implementation> - <handlers> - <handler event="focus"><![CDATA[ - this._cachedInsertionPoint = undefined; - - // See select handler. We need the sidebar's places commandset to be - // updated as well - document.commandDispatcher.updateCommands("focus"); - ]]></handler> - <handler event="select"><![CDATA[ - this._cachedInsertionPoint = undefined; - - // This additional complexity is here for the sidebars - var win = window; - while (true) { - win.document.commandDispatcher.updateCommands("focus"); - if (win == window.top) - break; - - win = win.parent; - } - ]]></handler> - - <handler event="dragstart"><![CDATA[ - if (event.target.localName != "treechildren") - return; - - let nodes = this.selectedNodes; - for (let i = 0; i < nodes.length; i++) { - let node = nodes[i]; - - // Disallow dragging the root node of a tree. - if (!node.parent) { - event.preventDefault(); - event.stopPropagation(); - return; - } - - // If this node is child of a readonly container (e.g. a livemark) - // or cannot be moved, we must force a copy. - if (!PlacesControllerDragHelper.canMoveNode(node)) { - event.dataTransfer.effectAllowed = "copyLink"; - break; - } - } - - this._controller.setDataTransfer(event); - event.stopPropagation(); - ]]></handler> - - <handler event="dragover"><![CDATA[ - if (event.target.localName != "treechildren") - return; - - let cell = this.treeBoxObject.getCellAt(event.clientX, event.clientY); - let node = cell.row != -1 ? - this.view.nodeForTreeIndex(cell.row) : - this.result.root; - // cache the dropTarget for the view - PlacesControllerDragHelper.currentDropTarget = node; - - // We have to calculate the orientation since view.canDrop will use - // it and we want to be consistent with the dropfeedback. - let tbo = this.treeBoxObject; - let rowHeight = tbo.rowHeight; - let eventY = event.clientY - tbo.treeBody.boxObject.y - - rowHeight * (cell.row - tbo.getFirstVisibleRow()); - - let orientation = Ci.nsITreeView.DROP_BEFORE; - - if (cell.row == -1) { - // If the row is not valid we try to insert inside the resultNode. - orientation = Ci.nsITreeView.DROP_ON; - } - else if (PlacesUtils.nodeIsContainer(node) && - eventY > rowHeight * 0.75) { - // If we are below the 75% of a container the treeview we try - // to drop after the node. - orientation = Ci.nsITreeView.DROP_AFTER; - } - else if (PlacesUtils.nodeIsContainer(node) && - eventY > rowHeight * 0.25) { - // If we are below the 25% of a container the treeview we try - // to drop inside the node. - orientation = Ci.nsITreeView.DROP_ON; - } - - if (!this.view.canDrop(cell.row, orientation, event.dataTransfer)) - return; - - event.preventDefault(); - event.stopPropagation(); - ]]></handler> - - <handler event="dragend"><![CDATA[ - PlacesControllerDragHelper.currentDropTarget = null; - ]]></handler> - - </handlers> - </binding> - -</bindings> diff --git a/browser/components/places/content/treeView.js b/browser/components/places/content/treeView.js deleted file mode 100644 index 181bb5404..000000000 --- a/browser/components/places/content/treeView.js +++ /dev/null @@ -1,1728 +0,0 @@ -/* 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/. */ - -Components.utils.import('resource://gre/modules/XPCOMUtils.jsm'); - -const PTV_interfaces = [Ci.nsITreeView, - Ci.nsINavHistoryResultObserver, - Ci.nsINavHistoryResultTreeViewer, - Ci.nsISupportsWeakReference]; - -function PlacesTreeView(aFlatList, aOnOpenFlatContainer, aController) { - this._tree = null; - this._result = null; - this._selection = null; - this._rootNode = null; - this._rows = []; - this._flatList = aFlatList; - this._openContainerCallback = aOnOpenFlatContainer; - this._controller = aController; -} - -PlacesTreeView.prototype = { - get wrappedJSObject() { - return this; - }, - - __xulStore: null, - get _xulStore() { - if (!this.__xulStore) { - this.__xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore); - } - return this.__xulStore; - }, - - QueryInterface: XPCOMUtils.generateQI(PTV_interfaces), - - // Bug 761494: - // ---------- - // Some addons use methods from nsINavHistoryResultObserver and - // nsINavHistoryResultTreeViewer, without QIing to these interfaces first. - // That's not a problem when the view is retrieved through the - // <tree>.view getter (which returns the wrappedJSObject of this object), - // it raises an issue when the view retrieved through the treeBoxObject.view - // getter. Thus, to avoid breaking addons, the interfaces are prefetched. - classInfo: XPCOMUtils.generateCI({ interfaces: PTV_interfaces }), - - /** - * This is called once both the result and the tree are set. - */ - _finishInit: function PTV__finishInit() { - let selection = this.selection; - if (selection) - selection.selectEventsSuppressed = true; - - if (!this._rootNode.containerOpen) { - // This triggers containerStateChanged which then builds the visible - // section. - this._rootNode.containerOpen = true; - } - else - this.invalidateContainer(this._rootNode); - - // "Activate" the sorting column and update commands. - this.sortingChanged(this._result.sortingMode); - - if (selection) - selection.selectEventsSuppressed = false; - }, - - /** - * Plain Container: container result nodes which may never include sub - * hierarchies. - * - * When the rows array is constructed, we don't set the children of plain - * containers. Instead, we keep placeholders for these children. We then - * build these children lazily as the tree asks us for information about each - * row. Luckily, the tree doesn't ask about rows outside the visible area. - * - * @see _getNodeForRow and _getRowForNode for the actual magic. - * - * @note It's guaranteed that all containers are listed in the rows - * elements array. It's also guaranteed that separators (if they're not - * filtered, see below) are listed in the visible elements array, because - * bookmark folders are never built lazily, as described above. - * - * @param aContainer - * A container result node. - * - * @return true if aContainer is a plain container, false otherwise. - */ - _isPlainContainer: function PTV__isPlainContainer(aContainer) { - // Livemarks are always plain containers. - if (this._controller.hasCachedLivemarkInfo(aContainer)) - return true; - - // We don't know enough about non-query containers. - if (!(aContainer instanceof Ci.nsINavHistoryQueryResultNode)) - return false; - - switch (aContainer.queryOptions.resultType) { - case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_QUERY: - case Ci.nsINavHistoryQueryOptions.RESULTS_AS_SITE_QUERY: - case Ci.nsINavHistoryQueryOptions.RESULTS_AS_DATE_SITE_QUERY: - case Ci.nsINavHistoryQueryOptions.RESULTS_AS_TAG_QUERY: - return false; - } - - // If it's a folder, it's not a plain container. - let nodeType = aContainer.type; - return nodeType != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER && - nodeType != Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT; - }, - - /** - * Gets the row number for a given node. Assumes that the given node is - * visible (i.e. it's not an obsolete node). - * - * @param aNode - * A result node. Do not pass an obsolete node, or any - * node which isn't supposed to be in the tree (e.g. separators in - * sorted trees). - * @param [optional] aForceBuild - * @see _isPlainContainer. - * If true, the row will be computed even if the node still isn't set - * in our rows array. - * @param [optional] aParentRow - * The row of aNode's parent. Ignored for the root node. - * @param [optional] aNodeIndex - * The index of aNode in its parent. Only used if aParentRow is - * set too. - * - * @throws if aNode is invisible. - * @note If aParentRow and aNodeIndex are passed and parent is a plain - * container, this method will just return a calculated row value, without - * making assumptions on existence of the node at that position. - * @return aNode's row if it's in the rows list or if aForceBuild is set, -1 - * otherwise. - */ - _getRowForNode: - function PTV__getRowForNode(aNode, aForceBuild, aParentRow, aNodeIndex) { - if (aNode == this._rootNode) - throw new Error("The root node is never visible"); - - // A node is removed form the view either if it has no parent or if its - // root-ancestor is not the root node (in which case that's the node - // for which nodeRemoved was called). - let ancestors = Array.from(PlacesUtils.nodeAncestors(aNode)); - if (ancestors.length == 0 || - ancestors[ancestors.length - 1] != this._rootNode) { - throw new Error("Removed node passed to _getRowForNode"); - } - - // Ensure that the entire chain is open, otherwise that node is invisible. - for (let ancestor of ancestors) { - if (!ancestor.containerOpen) - throw new Error("Invisible node passed to _getRowForNode"); - } - - // Non-plain containers are initially built with their contents. - let parent = aNode.parent; - let parentIsPlain = this._isPlainContainer(parent); - if (!parentIsPlain) { - if (parent == this._rootNode) - return this._rows.indexOf(aNode); - - return this._rows.indexOf(aNode, aParentRow); - } - - let row = -1; - let useNodeIndex = typeof(aNodeIndex) == "number"; - if (parent == this._rootNode) { - if (aNode instanceof Ci.nsINavHistoryResultNode) { - row = useNodeIndex ? aNodeIndex : this._rootNode.getChildIndex(aNode); - } - } - else if (useNodeIndex && typeof(aParentRow) == "number") { - // If we have both the row of the parent node, and the node's index, we - // can avoid searching the rows array if the parent is a plain container. - row = aParentRow + aNodeIndex + 1; - } - else { - // Look for the node in the nodes array. Start the search at the parent - // row. If the parent row isn't passed, we'll pass undefined to indexOf, - // which is fine. - row = this._rows.indexOf(aNode, aParentRow); - if (row == -1 && aForceBuild) { - let parentRow = typeof(aParentRow) == "number" ? aParentRow - : this._getRowForNode(parent); - row = parentRow + parent.getChildIndex(aNode) + 1; - } - } - - if (row != -1) - this._rows[row] = aNode; - - return row; - }, - - /** - * Given a row, finds and returns the parent details of the associated node. - * - * @param aChildRow - * Row number. - * @return [parentNode, parentRow] - */ - _getParentByChildRow: function PTV__getParentByChildRow(aChildRow) { - let node = this._getNodeForRow(aChildRow); - let parent = (node === null) ? this._rootNode : node.parent; - - // The root node is never visible - if (parent == this._rootNode) - return [this._rootNode, -1]; - - let parentRow = this._rows.lastIndexOf(parent, aChildRow - 1); - return [parent, parentRow]; - }, - - /** - * Gets the node at a given row. - */ - _getNodeForRow: function PTV__getNodeForRow(aRow) { - if (aRow < 0) { - return null; - } - - let node = this._rows[aRow]; - if (node !== undefined) - return node; - - // Find the nearest node. - let rowNode, row; - for (let i = aRow - 1; i >= 0 && rowNode === undefined; i--) { - rowNode = this._rows[i]; - row = i; - } - - // If there's no container prior to the given row, it's a child of - // the root node (remember: all containers are listed in the rows array). - if (!rowNode) - return this._rows[aRow] = this._rootNode.getChild(aRow); - - // Unset elements may exist only in plain containers. Thus, if the nearest - // node is a container, it's the row's parent, otherwise, it's a sibling. - if (rowNode instanceof Ci.nsINavHistoryContainerResultNode) - return this._rows[aRow] = rowNode.getChild(aRow - row - 1); - - let [parent, parentRow] = this._getParentByChildRow(row); - return this._rows[aRow] = parent.getChild(aRow - parentRow - 1); - }, - - /** - * This takes a container and recursively appends our rows array per its - * contents. Assumes that the rows arrays has no rows for the given - * container. - * - * @param [in] aContainer - * A container result node. - * @param [in] aFirstChildRow - * The first row at which nodes may be inserted to the row array. - * In other words, that's aContainer's row + 1. - * @param [out] aToOpen - * An array of containers to open once the build is done. - * - * @return the number of rows which were inserted. - */ - _buildVisibleSection: - function PTV__buildVisibleSection(aContainer, aFirstChildRow, aToOpen) - { - // There's nothing to do if the container is closed. - if (!aContainer.containerOpen) - return 0; - - // Inserting the new elements into the rows array in one shot (by - // Array.prototype.concat) is faster than resizing the array (by splice) on each loop - // iteration. - let cc = aContainer.childCount; - let newElements = new Array(cc); - this._rows = this._rows.splice(0, aFirstChildRow) - .concat(newElements, this._rows); - - if (this._isPlainContainer(aContainer)) - return cc; - - let sortingMode = this._result.sortingMode; - - let rowsInserted = 0; - for (let i = 0; i < cc; i++) { - let curChild = aContainer.getChild(i); - let curChildType = curChild.type; - - let row = aFirstChildRow + rowsInserted; - - // Don't display separators when sorted. - if (curChildType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) { - if (sortingMode != Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) { - // Remove the element for the filtered separator. - // Notice that the rows array was initially resized to include all - // children. - this._rows.splice(row, 1); - continue; - } - } - - this._rows[row] = curChild; - rowsInserted++; - - // Recursively do containers. - if (!this._flatList && - curChild instanceof Ci.nsINavHistoryContainerResultNode && - !this._controller.hasCachedLivemarkInfo(curChild)) { - let uri = curChild.uri; - let isopen = false; - - if (uri) { - let val = this._xulStore.getValue(document.documentURI, uri, "open"); - isopen = (val == "true"); - } - - if (isopen != curChild.containerOpen) - aToOpen.push(curChild); - else if (curChild.containerOpen && curChild.childCount > 0) - rowsInserted += this._buildVisibleSection(curChild, row + 1, aToOpen); - } - } - - return rowsInserted; - }, - - /** - * This counts how many rows a node takes in the tree. For containers it - * will count the node itself plus any child node following it. - */ - _countVisibleRowsForNodeAtRow: - function PTV__countVisibleRowsForNodeAtRow(aNodeRow) { - let node = this._rows[aNodeRow]; - - // If it's not listed yet, we know that it's a leaf node (instanceof also - // null-checks). - if (!(node instanceof Ci.nsINavHistoryContainerResultNode)) - return 1; - - let outerLevel = node.indentLevel; - for (let i = aNodeRow + 1; i < this._rows.length; i++) { - let rowNode = this._rows[i]; - if (rowNode && rowNode.indentLevel <= outerLevel) - return i - aNodeRow; - } - - // This node plus its children take up the bottom of the list. - return this._rows.length - aNodeRow; - }, - - _getSelectedNodesInRange: - function PTV__getSelectedNodesInRange(aFirstRow, aLastRow) { - let selection = this.selection; - let rc = selection.getRangeCount(); - if (rc == 0) - return []; - - // The visible-area borders are needed for checking whether a - // selected row is also visible. - let firstVisibleRow = this._tree.getFirstVisibleRow(); - let lastVisibleRow = this._tree.getLastVisibleRow(); - - let nodesInfo = []; - for (let rangeIndex = 0; rangeIndex < rc; rangeIndex++) { - let min = { }, max = { }; - selection.getRangeAt(rangeIndex, min, max); - - // If this range does not overlap the replaced chunk, we don't need to - // persist the selection. - if (max.value < aFirstRow || min.value > aLastRow) - continue; - - let firstRow = Math.max(min.value, aFirstRow); - let lastRow = Math.min(max.value, aLastRow); - for (let i = firstRow; i <= lastRow; i++) { - nodesInfo.push({ - node: this._rows[i], - oldRow: i, - wasVisible: i >= firstVisibleRow && i <= lastVisibleRow - }); - } - } - - return nodesInfo; - }, - - /** - * Tries to find an equivalent node for a node which was removed. We first - * look for the original node, in case it was just relocated. Then, if we - * that node was not found, we look for a node that has the same itemId, uri - * and time values. - * - * @param aUpdatedContainer - * An ancestor of the node which was removed. It does not have to be - * its direct parent. - * @param aOldNode - * The node which was removed. - * - * @return the row number of an equivalent node for aOldOne, if one was - * found, -1 otherwise. - */ - _getNewRowForRemovedNode: - function PTV__getNewRowForRemovedNode(aUpdatedContainer, aOldNode) { - let parent = aOldNode.parent; - if (parent) { - // If the node's parent is still set, the node is not obsolete - // and we should just find out its new position. - // However, if any of the node's ancestor is closed, the node is - // invisible. - let ancestors = PlacesUtils.nodeAncestors(aOldNode); - for (let ancestor of ancestors) { - if (!ancestor.containerOpen) - return -1; - } - - return this._getRowForNode(aOldNode, true); - } - - // There's a broken edge case here. - // If a visit appears in two queries, and the second one was - // the old node, we'll select the first one after refresh. There's - // nothing we could do about that, because aOldNode.parent is - // gone by the time invalidateContainer is called. - let newNode = aUpdatedContainer.findNodeByDetails(aOldNode.uri, - aOldNode.time, - aOldNode.itemId, - true); - if (!newNode) - return -1; - - return this._getRowForNode(newNode, true); - }, - - /** - * Restores a given selection state as near as possible to the original - * selection state. - * - * @param aNodesInfo - * The persisted selection state as returned by - * _getSelectedNodesInRange. - * @param aUpdatedContainer - * The container which was updated. - */ - _restoreSelection: - function PTV__restoreSelection(aNodesInfo, aUpdatedContainer) { - if (aNodesInfo.length == 0) - return; - - let selection = this.selection; - - // Attempt to ensure that previously-visible selection will be visible - // if it's re-selected. However, we can only ensure that for one row. - let scrollToRow = -1; - for (let i = 0; i < aNodesInfo.length; i++) { - let nodeInfo = aNodesInfo[i]; - let row = this._getNewRowForRemovedNode(aUpdatedContainer, - nodeInfo.node); - // Select the found node, if any. - if (row != -1) { - selection.rangedSelect(row, row, true); - if (nodeInfo.wasVisible && scrollToRow == -1) - scrollToRow = row; - } - } - - // If only one node was previously selected and there's no selection now, - // select the node at its old row, if any. - if (aNodesInfo.length == 1 && selection.count == 0) { - let row = Math.min(aNodesInfo[0].oldRow, this._rows.length - 1); - if (row != -1) { - selection.rangedSelect(row, row, true); - if (aNodesInfo[0].wasVisible && scrollToRow == -1) - scrollToRow = aNodesInfo[0].oldRow; - } - } - - if (scrollToRow != -1) - this._tree.ensureRowIsVisible(scrollToRow); - }, - - _convertPRTimeToString: function PTV__convertPRTimeToString(aTime) { - const MS_PER_MINUTE = 60000; - const MS_PER_DAY = 86400000; - let timeMs = aTime / 1000; // PRTime is in microseconds - - // Date is calculated starting from midnight, so the modulo with a day are - // milliseconds from today's midnight. - // getTimezoneOffset corrects that based on local time, notice midnight - // can have a different offset during DST-change days. - let dateObj = new Date(); - let now = dateObj.getTime() - dateObj.getTimezoneOffset() * MS_PER_MINUTE; - let midnight = now - (now % MS_PER_DAY); - midnight += new Date(midnight).getTimezoneOffset() * MS_PER_MINUTE; - - let timeObj = new Date(timeMs); - return timeMs >= midnight ? this._todayFormatter.format(timeObj) - : this._dateFormatter.format(timeObj); - }, - - // We use a different formatter for times within the current day, - // so we cache both a "today" formatter and a general date formatter. - __todayFormatter: null, - get _todayFormatter() { - if (!this.__todayFormatter) { - const locale = Cc["@mozilla.org/chrome/chrome-registry;1"] - .getService(Ci.nsIXULChromeRegistry) - .getSelectedLocale("global", true); - const dtOptions = { hour: 'numeric', minute: 'numeric' }; - this.__todayFormatter = new Intl.DateTimeFormat(locale, dtOptions); - } - return this.__todayFormatter; - }, - - __dateFormatter: null, - get _dateFormatter() { - if (!this.__dateFormatter) { - const locale = Cc["@mozilla.org/chrome/chrome-registry;1"] - .getService(Ci.nsIXULChromeRegistry) - .getSelectedLocale("global", true); - const dtOptions = { year: 'numeric', month: 'numeric', day: 'numeric', - hour: 'numeric', minute: 'numeric' }; - this.__dateFormatter = new Intl.DateTimeFormat(locale, dtOptions); - } - return this.__dateFormatter; - }, - - COLUMN_TYPE_UNKNOWN: 0, - COLUMN_TYPE_TITLE: 1, - COLUMN_TYPE_URI: 2, - COLUMN_TYPE_DATE: 3, - COLUMN_TYPE_VISITCOUNT: 4, - COLUMN_TYPE_DESCRIPTION: 5, - COLUMN_TYPE_DATEADDED: 6, - COLUMN_TYPE_LASTMODIFIED: 7, - COLUMN_TYPE_TAGS: 8, - - _getColumnType: function PTV__getColumnType(aColumn) { - let columnType = aColumn.element.getAttribute("anonid") || aColumn.id; - - switch (columnType) { - case "title": - return this.COLUMN_TYPE_TITLE; - case "url": - return this.COLUMN_TYPE_URI; - case "date": - return this.COLUMN_TYPE_DATE; - case "visitCount": - return this.COLUMN_TYPE_VISITCOUNT; - case "description": - return this.COLUMN_TYPE_DESCRIPTION; - case "dateAdded": - return this.COLUMN_TYPE_DATEADDED; - case "lastModified": - return this.COLUMN_TYPE_LASTMODIFIED; - case "tags": - return this.COLUMN_TYPE_TAGS; - } - return this.COLUMN_TYPE_UNKNOWN; - }, - - _sortTypeToColumnType: function PTV__sortTypeToColumnType(aSortType) { - switch (aSortType) { - case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_ASCENDING: - return [this.COLUMN_TYPE_TITLE, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_TITLE_DESCENDING: - return [this.COLUMN_TYPE_TITLE, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_ASCENDING: - return [this.COLUMN_TYPE_DATE, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_DATE_DESCENDING: - return [this.COLUMN_TYPE_DATE, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_ASCENDING: - return [this.COLUMN_TYPE_URI, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_URI_DESCENDING: - return [this.COLUMN_TYPE_URI, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_ASCENDING: - return [this.COLUMN_TYPE_VISITCOUNT, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_VISITCOUNT_DESCENDING: - return [this.COLUMN_TYPE_VISITCOUNT, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_ASCENDING: - if (this._result.sortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO) - return [this.COLUMN_TYPE_DESCRIPTION, false]; - break; - case Ci.nsINavHistoryQueryOptions.SORT_BY_ANNOTATION_DESCENDING: - if (this._result.sortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO) - return [this.COLUMN_TYPE_DESCRIPTION, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_ASCENDING: - return [this.COLUMN_TYPE_DATEADDED, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_DATEADDED_DESCENDING: - return [this.COLUMN_TYPE_DATEADDED, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_ASCENDING: - return [this.COLUMN_TYPE_LASTMODIFIED, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_LASTMODIFIED_DESCENDING: - return [this.COLUMN_TYPE_LASTMODIFIED, true]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_ASCENDING: - return [this.COLUMN_TYPE_TAGS, false]; - case Ci.nsINavHistoryQueryOptions.SORT_BY_TAGS_DESCENDING: - return [this.COLUMN_TYPE_TAGS, true]; - } - return [this.COLUMN_TYPE_UNKNOWN, false]; - }, - - // nsINavHistoryResultObserver - nodeInserted: function PTV_nodeInserted(aParentNode, aNode, aNewIndex) { - NS_ASSERT(this._result, "Got a notification but have no result!"); - if (!this._tree || !this._result) - return; - - // Bail out for hidden separators. - if (PlacesUtils.nodeIsSeparator(aNode) && this.isSorted()) - return; - - let parentRow; - if (aParentNode != this._rootNode) { - parentRow = this._getRowForNode(aParentNode); - - // Update parent when inserting the first item, since twisty has changed. - if (aParentNode.childCount == 1) - this._tree.invalidateRow(parentRow); - } - - // Compute the new row number of the node. - let row = -1; - let cc = aParentNode.childCount; - if (aNewIndex == 0 || this._isPlainContainer(aParentNode) || cc == 0) { - // We don't need to worry about sub hierarchies of the parent node - // if it's a plain container, or if the new node is its first child. - if (aParentNode == this._rootNode) - row = aNewIndex; - else - row = parentRow + aNewIndex + 1; - } - else { - // Here, we try to find the next visible element in the child list so we - // can set the new visible index to be right before that. Note that we - // have to search down instead of up, because some siblings could have - // children themselves that would be in the way. - let separatorsAreHidden = PlacesUtils.nodeIsSeparator(aNode) && - this.isSorted(); - for (let i = aNewIndex + 1; i < cc; i++) { - let node = aParentNode.getChild(i); - if (!separatorsAreHidden || PlacesUtils.nodeIsSeparator(node)) { - // The children have not been shifted so the next item will have what - // should be our index. - row = this._getRowForNode(node, false, parentRow, i); - break; - } - } - if (row < 0) { - // At the end of the child list without finding a visible sibling. This - // is a little harder because we don't know how many rows the last item - // in our list takes up (it could be a container with many children). - let prevChild = aParentNode.getChild(aNewIndex - 1); - let prevIndex = this._getRowForNode(prevChild, false, parentRow, - aNewIndex - 1); - row = prevIndex + this._countVisibleRowsForNodeAtRow(prevIndex); - } - } - - this._rows.splice(row, 0, aNode); - this._tree.rowCountChanged(row, 1); - - if (PlacesUtils.nodeIsContainer(aNode) && - PlacesUtils.asContainer(aNode).containerOpen) { - this.invalidateContainer(aNode); - } - }, - - /** - * THIS FUNCTION DOES NOT HANDLE cases where a collapsed node is being - * removed but the node it is collapsed with is not being removed (this then - * just swap out the removee with its collapsing partner). The only time - * when we really remove things is when deleting URIs, which will apply to - * all collapsees. This function is called sometimes when resorting items. - * However, we won't do this when sorted by date because dates will never - * change for visits, and date sorting is the only time things are collapsed. - */ - nodeRemoved: function PTV_nodeRemoved(aParentNode, aNode, aOldIndex) { - NS_ASSERT(this._result, "Got a notification but have no result!"); - if (!this._tree || !this._result) - return; - - // XXX bug 517701: We don't know what to do when the root node is removed. - if (aNode == this._rootNode) - throw Cr.NS_ERROR_NOT_IMPLEMENTED; - - // Bail out for hidden separators. - if (PlacesUtils.nodeIsSeparator(aNode) && this.isSorted()) - return; - - let parentRow = aParentNode == this._rootNode ? - undefined : this._getRowForNode(aParentNode, true); - let oldRow = this._getRowForNode(aNode, true, parentRow, aOldIndex); - if (oldRow < 0) - throw Cr.NS_ERROR_UNEXPECTED; - - // If the node was exclusively selected, the node next to it will be - // selected. - let selectNext = false; - let selection = this.selection; - if (selection.getRangeCount() == 1) { - let min = { }, max = { }; - selection.getRangeAt(0, min, max); - if (min.value == max.value && - this.nodeForTreeIndex(min.value) == aNode) - selectNext = true; - } - - // Remove the node and its children, if any. - let count = this._countVisibleRowsForNodeAtRow(oldRow); - this._rows.splice(oldRow, count); - this._tree.rowCountChanged(oldRow, -count); - - // Redraw the parent if its twisty state has changed. - if (aParentNode != this._rootNode && !aParentNode.hasChildren) { - let parentRow = oldRow - 1; - this._tree.invalidateRow(parentRow); - } - - // Restore selection if the node was exclusively selected. - if (!selectNext) - return; - - // Restore selection. - let rowToSelect = Math.min(oldRow, this._rows.length - 1); - if (rowToSelect != -1) - this.selection.rangedSelect(rowToSelect, rowToSelect, true); - }, - - nodeMoved: - function PTV_nodeMoved(aNode, aOldParent, aOldIndex, aNewParent, aNewIndex) { - NS_ASSERT(this._result, "Got a notification but have no result!"); - if (!this._tree || !this._result) - return; - - // Bail out for hidden separators. - if (PlacesUtils.nodeIsSeparator(aNode) && this.isSorted()) - return; - - // Note that at this point the node has already been moved by the backend, - // so we must give hints to _getRowForNode to get the old row position. - let oldParentRow = aOldParent == this._rootNode ? - undefined : this._getRowForNode(aOldParent, true); - let oldRow = this._getRowForNode(aNode, true, oldParentRow, aOldIndex); - if (oldRow < 0) - throw Cr.NS_ERROR_UNEXPECTED; - - // If this node is a container it could take up more than one row. - let count = this._countVisibleRowsForNodeAtRow(oldRow); - - // Persist selection state. - let nodesToReselect = - this._getSelectedNodesInRange(oldRow, oldRow + count); - if (nodesToReselect.length > 0) - this.selection.selectEventsSuppressed = true; - - // Redraw the parent if its twisty state has changed. - if (aOldParent != this._rootNode && !aOldParent.hasChildren) { - let parentRow = oldRow - 1; - this._tree.invalidateRow(parentRow); - } - - // Remove node and its children, if any, from the old position. - this._rows.splice(oldRow, count); - this._tree.rowCountChanged(oldRow, -count); - - // Insert the node into the new position. - this.nodeInserted(aNewParent, aNode, aNewIndex); - - // Restore selection. - if (nodesToReselect.length > 0) { - this._restoreSelection(nodesToReselect, aNewParent); - this.selection.selectEventsSuppressed = false; - } - }, - - _invalidateCellValue: function PTV__invalidateCellValue(aNode, - aColumnType) { - NS_ASSERT(this._result, "Got a notification but have no result!"); - if (!this._tree || !this._result) - return; - - // Nothing to do for the root node. - if (aNode == this._rootNode) - return; - - let row = this._getRowForNode(aNode); - if (row == -1) - return; - - let column = this._findColumnByType(aColumnType); - if (column && !column.element.hidden) - this._tree.invalidateCell(row, column); - - // Last modified time is altered for almost all node changes. - if (aColumnType != this.COLUMN_TYPE_LASTMODIFIED) { - let lastModifiedColumn = - this._findColumnByType(this.COLUMN_TYPE_LASTMODIFIED); - if (lastModifiedColumn && !lastModifiedColumn.hidden) - this._tree.invalidateCell(row, lastModifiedColumn); - } - }, - - _populateLivemarkContainer: function PTV__populateLivemarkContainer(aNode) { - PlacesUtils.livemarks.getLivemark({ id: aNode.itemId }) - .then(aLivemark => { - let placesNode = aNode; - // Need to check containerOpen since getLivemark is async. - if (!placesNode.containerOpen) - return; - - let children = aLivemark.getNodesForContainer(placesNode); - for (let i = 0; i < children.length; i++) { - let child = children[i]; - this.nodeInserted(placesNode, child, i); - } - }, Components.utils.reportError); - }, - - nodeTitleChanged: function PTV_nodeTitleChanged(aNode, aNewTitle) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE); - }, - - nodeURIChanged: function PTV_nodeURIChanged(aNode, aNewURI) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_URI); - }, - - nodeIconChanged: function PTV_nodeIconChanged(aNode) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE); - }, - - nodeHistoryDetailsChanged: - function PTV_nodeHistoryDetailsChanged(aNode, aUpdatedVisitDate, - aUpdatedVisitCount) { - if (aNode.parent && this._controller.hasCachedLivemarkInfo(aNode.parent)) { - // Find the node in the parent. - let parentRow = this._flatList ? 0 : this._getRowForNode(aNode.parent); - for (let i = parentRow; i < this._rows.length; i++) { - let child = this.nodeForTreeIndex(i); - if (child.uri == aNode.uri) { - this._cellProperties.delete(child); - this._invalidateCellValue(child, this.COLUMN_TYPE_TITLE); - break; - } - } - return; - } - - this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATE); - this._invalidateCellValue(aNode, this.COLUMN_TYPE_VISITCOUNT); - }, - - nodeTagsChanged: function PTV_nodeTagsChanged(aNode) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_TAGS); - }, - - nodeKeywordChanged(aNode, aNewKeyword) {}, - - nodeAnnotationChanged: function PTV_nodeAnnotationChanged(aNode, aAnno) { - if (aAnno == PlacesUIUtils.DESCRIPTION_ANNO) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_DESCRIPTION); - } - else if (aAnno == PlacesUtils.LMANNO_FEEDURI) { - PlacesUtils.livemarks.getLivemark({ id: aNode.itemId }) - .then(aLivemark => { - this._controller.cacheLivemarkInfo(aNode, aLivemark); - let properties = this._cellProperties.get(aNode); - this._cellProperties.set(aNode, properties += " livemark"); - // The livemark attribute is set as a cell property on the title cell. - this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE); - }, Components.utils.reportError); - } - }, - - nodeDateAddedChanged: function PTV_nodeDateAddedChanged(aNode, aNewValue) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_DATEADDED); - }, - - nodeLastModifiedChanged: - function PTV_nodeLastModifiedChanged(aNode, aNewValue) { - this._invalidateCellValue(aNode, this.COLUMN_TYPE_LASTMODIFIED); - }, - - containerStateChanged: - function PTV_containerStateChanged(aNode, aOldState, aNewState) { - this.invalidateContainer(aNode); - - if (PlacesUtils.nodeIsFolder(aNode) || - (this._flatList && aNode == this._rootNode)) { - let queryOptions = PlacesUtils.asQuery(this._rootNode).queryOptions; - if (queryOptions.excludeItems) { - return; - } - if (aNode.itemId != -1) { // run when there's a valid node id - PlacesUtils.livemarks.getLivemark({ id: aNode.itemId }) - .then(aLivemark => { - let shouldInvalidate = - !this._controller.hasCachedLivemarkInfo(aNode); - this._controller.cacheLivemarkInfo(aNode, aLivemark); - if (aNewState == Components.interfaces.nsINavHistoryContainerResultNode.STATE_OPENED) { - aLivemark.registerForUpdates(aNode, this); - // Prioritize the current livemark. - aLivemark.reload(); - PlacesUtils.livemarks.reloadLivemarks(); - if (shouldInvalidate) - this.invalidateContainer(aNode); - } - else { - aLivemark.unregisterForUpdates(aNode); - } - }, () => undefined); - } - } - }, - - invalidateContainer: function PTV_invalidateContainer(aContainer) { - NS_ASSERT(this._result, "Need to have a result to update"); - if (!this._tree) - return; - - let startReplacement, replaceCount; - if (aContainer == this._rootNode) { - startReplacement = 0; - replaceCount = this._rows.length; - - // If the root node is now closed, the tree is empty. - if (!this._rootNode.containerOpen) { - this._rows = []; - if (replaceCount) - this._tree.rowCountChanged(startReplacement, -replaceCount); - - return; - } - } - else { - // Update the twisty state. - let row = this._getRowForNode(aContainer); - this._tree.invalidateRow(row); - - // We don't replace the container node itself, so we should decrease the - // replaceCount by 1. - startReplacement = row + 1; - replaceCount = this._countVisibleRowsForNodeAtRow(row) - 1; - } - - // Persist selection state. - let nodesToReselect = - this._getSelectedNodesInRange(startReplacement, - startReplacement + replaceCount); - - // Now update the number of elements. - this.selection.selectEventsSuppressed = true; - - // First remove the old elements - this._rows.splice(startReplacement, replaceCount); - - // If the container is now closed, we're done. - if (!aContainer.containerOpen) { - let oldSelectionCount = this.selection.count; - if (replaceCount) - this._tree.rowCountChanged(startReplacement, -replaceCount); - - // Select the row next to the closed container if any of its - // children were selected, and nothing else is selected. - if (nodesToReselect.length > 0 && - nodesToReselect.length == oldSelectionCount) { - this.selection.rangedSelect(startReplacement, startReplacement, true); - this._tree.ensureRowIsVisible(startReplacement); - } - - this.selection.selectEventsSuppressed = false; - return; - } - - // Otherwise, start a batch first. - this._tree.beginUpdateBatch(); - if (replaceCount) - this._tree.rowCountChanged(startReplacement, -replaceCount); - - let toOpenElements = []; - let elementsAddedCount = this._buildVisibleSection(aContainer, - startReplacement, - toOpenElements); - if (elementsAddedCount) - this._tree.rowCountChanged(startReplacement, elementsAddedCount); - - if (!this._flatList) { - // Now, open any containers that were persisted. - for (let i = 0; i < toOpenElements.length; i++) { - let item = toOpenElements[i]; - let parent = item.parent; - - // Avoid recursively opening containers. - while (parent) { - if (parent.uri == item.uri) - break; - parent = parent.parent; - } - - // If we don't have a parent, we made it all the way to the root - // and didn't find a match, so we can open our item. - if (!parent && !item.containerOpen) - item.containerOpen = true; - } - } - - if (this._controller.hasCachedLivemarkInfo(aContainer)) { - let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions; - if (!queryOptions.excludeItems) { - this._populateLivemarkContainer(aContainer); - } - } - - this._tree.endUpdateBatch(); - - // Restore selection. - this._restoreSelection(nodesToReselect, aContainer); - this.selection.selectEventsSuppressed = false; - }, - - _columns: [], - _findColumnByType: function PTV__findColumnByType(aColumnType) { - if (this._columns[aColumnType]) - return this._columns[aColumnType]; - - let columns = this._tree.columns; - let colCount = columns.count; - for (let i = 0; i < colCount; i++) { - let column = columns.getColumnAt(i); - let columnType = this._getColumnType(column); - this._columns[columnType] = column; - if (columnType == aColumnType) - return column; - } - - // That's completely valid. Most of our trees actually include just the - // title column. - return null; - }, - - sortingChanged: function PTV__sortingChanged(aSortingMode) { - if (!this._tree || !this._result) - return; - - // Depending on the sort mode, certain commands may be disabled. - window.updateCommands("sort"); - - let columns = this._tree.columns; - - // Clear old sorting indicator. - let sortedColumn = columns.getSortedColumn(); - if (sortedColumn) - sortedColumn.element.removeAttribute("sortDirection"); - - // Set new sorting indicator by looking through all columns for ours. - if (aSortingMode == Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) - return; - - let [desiredColumn, desiredIsDescending] = - this._sortTypeToColumnType(aSortingMode); - let column = this._findColumnByType(desiredColumn); - if (column) { - let sortDir = desiredIsDescending ? "descending" : "ascending"; - column.element.setAttribute("sortDirection", sortDir); - } - }, - - _inBatchMode: false, - batching: function PTV__batching(aToggleMode) { - if (this._inBatchMode != aToggleMode) { - this._inBatchMode = this.selection.selectEventsSuppressed = aToggleMode; - if (this._inBatchMode) { - this._tree.beginUpdateBatch(); - } - else { - this._tree.endUpdateBatch(); - } - } - }, - - get result() { - return this._result; - }, - set result(val) { - if (this._result) { - this._result.removeObserver(this); - this._rootNode.containerOpen = false; - } - - if (val) { - this._result = val; - this._rootNode = this._result.root; - this._cellProperties = new Map(); - this._cuttingNodes = new Set(); - } - else if (this._result) { - delete this._result; - delete this._rootNode; - delete this._cellProperties; - delete this._cuttingNodes; - } - - // If the tree is not set yet, setTree will call finishInit. - if (this._tree && val) - this._finishInit(); - - return val; - }, - - nodeForTreeIndex: function PTV_nodeForTreeIndex(aIndex) { - if (aIndex > this._rows.length) - throw Cr.NS_ERROR_INVALID_ARG; - - return this._getNodeForRow(aIndex); - }, - - treeIndexForNode: function PTV_treeNodeForIndex(aNode) { - // The API allows passing invisible nodes. - try { - return this._getRowForNode(aNode, true); - } - catch (ex) { } - - return Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE; - }, - - // nsITreeView - get rowCount() { - return this._rows.length; - }, - get selection() { - return this._selection; - }, - set selection(val) { - this._selection = val; - }, - - getRowProperties: function() { return ""; }, - - getCellProperties: - function PTV_getCellProperties(aRow, aColumn) { - // for anonid-trees, we need to add the column-type manually - var props = ""; - let columnType = aColumn.element.getAttribute("anonid"); - if (columnType) - props += columnType; - else - columnType = aColumn.id; - - // Set the "ltr" property on url cells - if (columnType == "url") - props += " ltr"; - - if (columnType != "title") - return props; - - let node = this._getNodeForRow(aRow); - - if (this._cuttingNodes.has(node)) { - props += " cutting"; - } - - let properties = this._cellProperties.get(node); - if (properties === undefined) { - properties = ""; - let itemId = node.itemId; - let nodeType = node.type; - if (PlacesUtils.containerTypes.includes(nodeType)) { - if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY) { - properties += " query"; - if (PlacesUtils.nodeIsTagQuery(node)) - properties += " tagContainer"; - else if (PlacesUtils.nodeIsDay(node)) - properties += " dayContainer"; - else if (PlacesUtils.nodeIsHost(node)) - properties += " hostContainer"; - } - else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER || - nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) { - if (this._controller.hasCachedLivemarkInfo(node)) { - properties += " livemark"; - } - else { - PlacesUtils.livemarks.getLivemark({ id: node.itemId }) - .then(aLivemark => { - this._controller.cacheLivemarkInfo(node, aLivemark); - let props = this._cellProperties.get(node); - this._cellProperties.set(node, props += " livemark"); - // The livemark attribute is set as a cell property on the title cell. - this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE); - }, () => undefined); - } - } - - if (itemId != -1) { - let queryName = PlacesUIUtils.getLeftPaneQueryNameFromId(itemId); - if (queryName) - properties += " OrganizerQuery_" + queryName; - } - } - else if (nodeType == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) - properties += " separator"; - else if (PlacesUtils.nodeIsURI(node)) { - properties += " " + PlacesUIUtils.guessUrlSchemeForUI(node.uri); - - if (this._controller.hasCachedLivemarkInfo(node.parent)) { - properties += " livemarkItem"; - if (node.accessCount) { - properties += " visited"; - } - } - } - - this._cellProperties.set(node, properties); - } - - return props + " " + properties; - }, - - getColumnProperties: function(aColumn) { return ""; }, - - isContainer: function PTV_isContainer(aRow) { - // Only leaf nodes aren't listed in the rows array. - let node = this._rows[aRow]; - if (node === undefined) - return false; - - if (PlacesUtils.nodeIsContainer(node)) { - // Flat-lists may ignore expandQueries and other query options when - // they are asked to open a container. - if (this._flatList) - return true; - - // treat non-expandable childless queries as non-containers - if (PlacesUtils.nodeIsQuery(node)) { - let parent = node.parent; - if ((PlacesUtils.nodeIsQuery(parent) || - PlacesUtils.nodeIsFolder(parent)) && - !PlacesUtils.asQuery(node).hasChildren) - return PlacesUtils.asQuery(parent).queryOptions.expandQueries; - } - return true; - } - return false; - }, - - isContainerOpen: function PTV_isContainerOpen(aRow) { - if (this._flatList) - return false; - - // All containers are listed in the rows array. - return this._rows[aRow].containerOpen; - }, - - isContainerEmpty: function PTV_isContainerEmpty(aRow) { - if (this._flatList) - return true; - - let node = this._rows[aRow]; - if (this._controller.hasCachedLivemarkInfo(node)) { - let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions; - return queryOptions.excludeItems; - } - - // All containers are listed in the rows array. - return !node.hasChildren; - }, - - isSeparator: function PTV_isSeparator(aRow) { - // All separators are listed in the rows array. - let node = this._rows[aRow]; - return node && PlacesUtils.nodeIsSeparator(node); - }, - - isSorted: function PTV_isSorted() { - return this._result.sortingMode != - Ci.nsINavHistoryQueryOptions.SORT_BY_NONE; - }, - - canDrop: function PTV_canDrop(aRow, aOrientation, aDataTransfer) { - if (!this._result) - throw Cr.NS_ERROR_UNEXPECTED; - - // Drop position into a sorted treeview would be wrong. - if (this.isSorted()) - return false; - - let ip = this._getInsertionPoint(aRow, aOrientation); - return ip && PlacesControllerDragHelper.canDrop(ip, aDataTransfer); - }, - - _getInsertionPoint: function PTV__getInsertionPoint(index, orientation) { - let container = this._result.root; - let dropNearItemId = -1; - // When there's no selection, assume the container is the container - // the view is populated from (i.e. the result's itemId). - if (index != -1) { - let lastSelected = this.nodeForTreeIndex(index); - if (this.isContainer(index) && orientation == Ci.nsITreeView.DROP_ON) { - // If the last selected item is an open container, append _into_ - // it, rather than insert adjacent to it. - container = lastSelected; - index = -1; - } - else if (lastSelected.containerOpen && - orientation == Ci.nsITreeView.DROP_AFTER && - lastSelected.hasChildren) { - // If the last selected node is an open container and the user is - // trying to drag into it as a first node, really insert into it. - container = lastSelected; - orientation = Ci.nsITreeView.DROP_ON; - index = 0; - } - else { - // Use the last-selected node's container. - container = lastSelected.parent; - - // During its Drag & Drop operation, the tree code closes-and-opens - // containers very often (part of the XUL "spring-loaded folders" - // implementation). And in certain cases, we may reach a closed - // container here. However, we can simply bail out when this happens, - // because we would then be back here in less than a millisecond, when - // the container had been reopened. - if (!container || !container.containerOpen) - return null; - - // Avoid the potentially expensive call to getChildIndex - // if we know this container doesn't allow insertion. - if (PlacesControllerDragHelper.disallowInsertion(container)) - return null; - - let queryOptions = PlacesUtils.asQuery(this._result.root).queryOptions; - if (queryOptions.sortingMode != - Ci.nsINavHistoryQueryOptions.SORT_BY_NONE) { - // If we are within a sorted view, insert at the end. - index = -1; - } - else if (queryOptions.excludeItems || - queryOptions.excludeQueries || - queryOptions.excludeReadOnlyFolders) { - // Some item may be invisible, insert near last selected one. - // We don't replace index here to avoid requests to the db, - // instead it will be calculated later by the controller. - index = -1; - dropNearItemId = lastSelected.itemId; - } - else { - let lsi = container.getChildIndex(lastSelected); - index = orientation == Ci.nsITreeView.DROP_BEFORE ? lsi : lsi + 1; - } - } - } - - if (PlacesControllerDragHelper.disallowInsertion(container)) - return null; - - // TODO (Bug 1160193): properly support dropping on a tag root. - let tagName = null; - if (PlacesUtils.nodeIsTagQuery(container)) { - tagName = container.title; - if (!tagName) - return null; - } - - return new InsertionPoint(PlacesUtils.getConcreteItemId(container), - index, orientation, - tagName, - dropNearItemId); - }, - - drop: function PTV_drop(aRow, aOrientation, aDataTransfer) { - // We are responsible for translating the |index| and |orientation| - // parameters into a container id and index within the container, - // since this information is specific to the tree view. - let ip = this._getInsertionPoint(aRow, aOrientation); - if (ip) { - PlacesControllerDragHelper.onDrop(ip, aDataTransfer) - .then(null, Components.utils.reportError); - } - - PlacesControllerDragHelper.currentDropTarget = null; - }, - - getParentIndex: function PTV_getParentIndex(aRow) { - let [, parentRow] = this._getParentByChildRow(aRow); - return parentRow; - }, - - hasNextSibling: function PTV_hasNextSibling(aRow, aAfterIndex) { - if (aRow == this._rows.length - 1) { - // The last row has no sibling. - return false; - } - - let node = this._rows[aRow]; - if (node === undefined || this._isPlainContainer(node.parent)) { - // The node is a child of a plain container. - // If the next row is either unset or has the same parent, - // it's a sibling. - let nextNode = this._rows[aRow + 1]; - return (nextNode == undefined || nextNode.parent == node.parent); - } - - let thisLevel = node.indentLevel; - for (let i = aAfterIndex + 1; i < this._rows.length; ++i) { - let rowNode = this._getNodeForRow(i); - let nextLevel = rowNode.indentLevel; - if (nextLevel == thisLevel) - return true; - if (nextLevel < thisLevel) - break; - } - - return false; - }, - - getLevel: function(aRow) { - return this._getNodeForRow(aRow).indentLevel; - }, - - getImageSrc: function PTV_getImageSrc(aRow, aColumn) { - // Only the title column has an image. - if (this._getColumnType(aColumn) != this.COLUMN_TYPE_TITLE) - return ""; - - let node = this._getNodeForRow(aRow); - return node.icon; - }, - - getProgressMode: function(aRow, aColumn) { }, - getCellValue: function(aRow, aColumn) { }, - - getCellText: function PTV_getCellText(aRow, aColumn) { - let node = this._getNodeForRow(aRow); - switch (this._getColumnType(aColumn)) { - case this.COLUMN_TYPE_TITLE: - // normally, this is just the title, but we don't want empty items in - // the tree view so return a special string if the title is empty. - // Do it here so that callers can still get at the 0 length title - // if they go through the "result" API. - if (PlacesUtils.nodeIsSeparator(node)) - return ""; - return PlacesUIUtils.getBestTitle(node, true); - case this.COLUMN_TYPE_TAGS: - return node.tags; - case this.COLUMN_TYPE_URI: - if (PlacesUtils.nodeIsURI(node)) - return node.uri; - return ""; - case this.COLUMN_TYPE_DATE: - let nodeTime = node.time; - if (nodeTime == 0 || !PlacesUtils.nodeIsURI(node)) { - // hosts and days shouldn't have a value for the date column. - // Actually, you could argue this point, but looking at the - // results, seeing the most recently visited date is not what - // I expect, and gives me no information I know how to use. - // Only show this for URI-based items. - return ""; - } - - return this._convertPRTimeToString(nodeTime); - case this.COLUMN_TYPE_VISITCOUNT: - return node.accessCount; - case this.COLUMN_TYPE_DESCRIPTION: - if (node.itemId != -1) { - try { - return PlacesUtils.annotations. - getItemAnnotation(node.itemId, PlacesUIUtils.DESCRIPTION_ANNO); - } - catch (ex) { /* has no description */ } - } - return ""; - case this.COLUMN_TYPE_DATEADDED: - if (node.dateAdded) - return this._convertPRTimeToString(node.dateAdded); - return ""; - case this.COLUMN_TYPE_LASTMODIFIED: - if (node.lastModified) - return this._convertPRTimeToString(node.lastModified); - return ""; - } - return ""; - }, - - setTree: function PTV_setTree(aTree) { - // If we are replacing the tree during a batch, there is a concrete risk - // that the treeView goes out of sync, thus it's safer to end the batch now. - // This is a no-op if we are not batching. - this.batching(false); - - let hasOldTree = this._tree != null; - this._tree = aTree; - - if (this._result) { - if (hasOldTree) { - // detach from result when we are detaching from the tree. - // This breaks the reference cycle between us and the result. - if (!aTree) { - this._result.removeObserver(this); - this._rootNode.containerOpen = false; - } - } - if (aTree) - this._finishInit(); - } - }, - - toggleOpenState: function PTV_toggleOpenState(aRow) { - if (!this._result) - throw Cr.NS_ERROR_UNEXPECTED; - - let node = this._rows[aRow]; - if (this._flatList && this._openContainerCallback) { - this._openContainerCallback(node); - return; - } - - // Persist containers open status, but never persist livemarks. - if (!this._controller.hasCachedLivemarkInfo(node)) { - let uri = node.uri; - - if (uri) { - let docURI = document.documentURI; - - if (node.containerOpen) { - this._xulStore.removeValue(docURI, uri, "open"); - } else { - this._xulStore.setValue(docURI, uri, "open", "true"); - } - } - } - - node.containerOpen = !node.containerOpen; - }, - - cycleHeader: function PTV_cycleHeader(aColumn) { - if (!this._result) - throw Cr.NS_ERROR_UNEXPECTED; - - // Sometimes you want a tri-state sorting, and sometimes you don't. This - // rule allows tri-state sorting when the root node is a folder. This will - // catch the most common cases. When you are looking at folders, you want - // the third state to reset the sorting to the natural bookmark order. When - // you are looking at history, that third state has no meaning so we try - // to disallow it. - // - // The problem occurs when you have a query that results in bookmark - // folders. One example of this is the subscriptions view. In these cases, - // this rule doesn't allow you to sort those sub-folders by their natural - // order. - let allowTriState = PlacesUtils.nodeIsFolder(this._result.root); - - let oldSort = this._result.sortingMode; - let oldSortingAnnotation = this._result.sortingAnnotation; - let newSort; - let newSortingAnnotation = ""; - const NHQO = Ci.nsINavHistoryQueryOptions; - switch (this._getColumnType(aColumn)) { - case this.COLUMN_TYPE_TITLE: - if (oldSort == NHQO.SORT_BY_TITLE_ASCENDING) - newSort = NHQO.SORT_BY_TITLE_DESCENDING; - else if (allowTriState && oldSort == NHQO.SORT_BY_TITLE_DESCENDING) - newSort = NHQO.SORT_BY_NONE; - else - newSort = NHQO.SORT_BY_TITLE_ASCENDING; - - break; - case this.COLUMN_TYPE_URI: - if (oldSort == NHQO.SORT_BY_URI_ASCENDING) - newSort = NHQO.SORT_BY_URI_DESCENDING; - else if (allowTriState && oldSort == NHQO.SORT_BY_URI_DESCENDING) - newSort = NHQO.SORT_BY_NONE; - else - newSort = NHQO.SORT_BY_URI_ASCENDING; - - break; - case this.COLUMN_TYPE_DATE: - if (oldSort == NHQO.SORT_BY_DATE_ASCENDING) - newSort = NHQO.SORT_BY_DATE_DESCENDING; - else if (allowTriState && - oldSort == NHQO.SORT_BY_DATE_DESCENDING) - newSort = NHQO.SORT_BY_NONE; - else - newSort = NHQO.SORT_BY_DATE_ASCENDING; - - break; - case this.COLUMN_TYPE_VISITCOUNT: - // visit count default is unusual because we sort by descending - // by default because you are most likely to be looking for - // highly visited sites when you click it - if (oldSort == NHQO.SORT_BY_VISITCOUNT_DESCENDING) - newSort = NHQO.SORT_BY_VISITCOUNT_ASCENDING; - else if (allowTriState && oldSort == NHQO.SORT_BY_VISITCOUNT_ASCENDING) - newSort = NHQO.SORT_BY_NONE; - else - newSort = NHQO.SORT_BY_VISITCOUNT_DESCENDING; - - break; - case this.COLUMN_TYPE_DESCRIPTION: - if (oldSort == NHQO.SORT_BY_ANNOTATION_ASCENDING && - oldSortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO) { - newSort = NHQO.SORT_BY_ANNOTATION_DESCENDING; - newSortingAnnotation = PlacesUIUtils.DESCRIPTION_ANNO; - } - else if (allowTriState && - oldSort == NHQO.SORT_BY_ANNOTATION_DESCENDING && - oldSortingAnnotation == PlacesUIUtils.DESCRIPTION_ANNO) - newSort = NHQO.SORT_BY_NONE; - else { - newSort = NHQO.SORT_BY_ANNOTATION_ASCENDING; - newSortingAnnotation = PlacesUIUtils.DESCRIPTION_ANNO; - } - - break; - case this.COLUMN_TYPE_DATEADDED: - if (oldSort == NHQO.SORT_BY_DATEADDED_ASCENDING) - newSort = NHQO.SORT_BY_DATEADDED_DESCENDING; - else if (allowTriState && - oldSort == NHQO.SORT_BY_DATEADDED_DESCENDING) - newSort = NHQO.SORT_BY_NONE; - else - newSort = NHQO.SORT_BY_DATEADDED_ASCENDING; - - break; - case this.COLUMN_TYPE_LASTMODIFIED: - if (oldSort == NHQO.SORT_BY_LASTMODIFIED_ASCENDING) - newSort = NHQO.SORT_BY_LASTMODIFIED_DESCENDING; - else if (allowTriState && - oldSort == NHQO.SORT_BY_LASTMODIFIED_DESCENDING) - newSort = NHQO.SORT_BY_NONE; - else - newSort = NHQO.SORT_BY_LASTMODIFIED_ASCENDING; - - break; - case this.COLUMN_TYPE_TAGS: - if (oldSort == NHQO.SORT_BY_TAGS_ASCENDING) - newSort = NHQO.SORT_BY_TAGS_DESCENDING; - else if (allowTriState && oldSort == NHQO.SORT_BY_TAGS_DESCENDING) - newSort = NHQO.SORT_BY_NONE; - else - newSort = NHQO.SORT_BY_TAGS_ASCENDING; - - break; - default: - throw Cr.NS_ERROR_INVALID_ARG; - } - this._result.sortingAnnotation = newSortingAnnotation; - this._result.sortingMode = newSort; - }, - - isEditable: function PTV_isEditable(aRow, aColumn) { - // At this point we only support editing the title field. - if (aColumn.index != 0) - return false; - - let node = this._rows[aRow]; - if (!node) { - Cu.reportError("isEditable called for an unbuilt row."); - return false; - } - let itemId = node.itemId; - - // Only bookmark-nodes are editable. Fortunately, this checks also takes - // care of livemark children. - if (itemId == -1) - return false; - - // The following items are also not editable, even though they are bookmark - // items. - // * places-roots - // * the left pane special folders and queries (those are place: uri - // bookmarks) - // * separators - // - // Note that concrete itemIds aren't used intentionally. For example, we - // have no reason to disallow renaming a shortcut to the Bookmarks Toolbar, - // except for the one under All Bookmarks. - if (PlacesUtils.nodeIsSeparator(node) || PlacesUtils.isRootItem(itemId)) - return false; - - let parentId = PlacesUtils.getConcreteItemId(node.parent); - if (parentId == PlacesUIUtils.leftPaneFolderId || - parentId == PlacesUIUtils.allBookmarksFolderId) { - // Note that the for the time being this is the check that actually - // blocks renaming places "roots", and not the isRootItem check above. - // That's because places root are only exposed through folder shortcuts - // descendants of the left pane folder. - return false; - } - - return true; - }, - - setCellText: function PTV_setCellText(aRow, aColumn, aText) { - // We may only get here if the cell is editable. - let node = this._rows[aRow]; - if (node.title != aText) { - if (!PlacesUIUtils.useAsyncTransactions) { - let txn = new PlacesEditItemTitleTransaction(node.itemId, aText); - PlacesUtils.transactionManager.doTransaction(txn); - return; - } - PlacesTransactions.EditTitle({ guid: node.bookmarkGuid, title: aText }) - .transact().catch(Cu.reportError); - } - }, - - toggleCutNode: function PTV_toggleCutNode(aNode, aValue) { - let currentVal = this._cuttingNodes.has(aNode); - if (currentVal != aValue) { - if (aValue) - this._cuttingNodes.add(aNode); - else - this._cuttingNodes.delete(aNode); - - this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE); - } - }, - - selectionChanged: function() { }, - cycleCell: function(aRow, aColumn) { }, - isSelectable: function(aRow, aColumn) { return false; }, - performAction: function(aAction) { }, - performActionOnRow: function(aAction, aRow) { }, - performActionOnCell: function(aAction, aRow, aColumn) { } -}; |