diff options
Diffstat (limited to 'browser/components/places/content/browserPlacesViews.js')
-rw-r--r-- | browser/components/places/content/browserPlacesViews.js | 1996 |
1 files changed, 0 insertions, 1996 deletions
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); - } - } -}; |