summaryrefslogtreecommitdiffstats
path: root/browser/components/places/content/browserPlacesViews.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/places/content/browserPlacesViews.js')
-rw-r--r--browser/components/places/content/browserPlacesViews.js1996
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);
- }
- }
-};