forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			2470 lines
		
	
	
	
		
			74 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			2470 lines
		
	
	
	
		
			74 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* 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/. */
 | 
						|
 | 
						|
/* eslint-env mozilla/browser-window */
 | 
						|
 | 
						|
/**
 | 
						|
 * The base view implements everything that's common to the toolbar and
 | 
						|
 * menu views.
 | 
						|
 *
 | 
						|
 * @param {string} aPlace
 | 
						|
 *   The query string associated with the view.
 | 
						|
 * @param {object} aOptions
 | 
						|
 *   Associated options for the view.
 | 
						|
 */
 | 
						|
function PlacesViewBase(aPlace, aOptions = {}) {
 | 
						|
  if ("rootElt" in aOptions) {
 | 
						|
    this._rootElt = aOptions.rootElt;
 | 
						|
  }
 | 
						|
  if ("viewElt" in aOptions) {
 | 
						|
    this._viewElt = aOptions.viewElt;
 | 
						|
  }
 | 
						|
  this.options = aOptions;
 | 
						|
  this._controller = new PlacesController(this);
 | 
						|
  this.place = aPlace;
 | 
						|
  this._viewElt.controllers.appendController(this._controller);
 | 
						|
}
 | 
						|
 | 
						|
PlacesViewBase.interfaces = [
 | 
						|
  Ci.nsINavHistoryResultObserver,
 | 
						|
  Ci.nsISupportsWeakReference,
 | 
						|
];
 | 
						|
 | 
						|
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: ChromeUtils.generateQI(PlacesViewBase.interfaces),
 | 
						|
 | 
						|
  _place: "",
 | 
						|
  get place() {
 | 
						|
    return this._place;
 | 
						|
  },
 | 
						|
  set place(val) {
 | 
						|
    this._place = val;
 | 
						|
 | 
						|
    let history = PlacesUtils.history;
 | 
						|
    let query = {},
 | 
						|
      options = {};
 | 
						|
    history.queryStringToQuery(val, query, options);
 | 
						|
    let result = history.executeQuery(query.value, options.value);
 | 
						|
    result.addObserver(this);
 | 
						|
  },
 | 
						|
 | 
						|
  _result: null,
 | 
						|
  get result() {
 | 
						|
    return this._result;
 | 
						|
  },
 | 
						|
  set result(val) {
 | 
						|
    if (this._result == val) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    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;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _options: null,
 | 
						|
  get options() {
 | 
						|
    return this._options;
 | 
						|
  },
 | 
						|
  set options(val) {
 | 
						|
    if (!val) {
 | 
						|
      val = {};
 | 
						|
    }
 | 
						|
 | 
						|
    if (!("extraClasses" in val)) {
 | 
						|
      val.extraClasses = {};
 | 
						|
    }
 | 
						|
    this._options = val;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the DOM node used for the given places node.
 | 
						|
   *
 | 
						|
   * @param {object} aPlacesNode
 | 
						|
   *        a places result node.
 | 
						|
   * @param {boolean} aAllowMissing
 | 
						|
   *        whether the node may be missing
 | 
						|
   * @returns {object|null} The associated DOM node.
 | 
						|
   * @throws if there is no DOM node set for aPlacesNode.
 | 
						|
   */
 | 
						|
  _getDOMNodeForPlacesNode: function PVB__getDOMNodeForPlacesNode(
 | 
						|
    aPlacesNode,
 | 
						|
    aAllowMissing = false
 | 
						|
  ) {
 | 
						|
    let node = this._domNodes.get(aPlacesNode, null);
 | 
						|
    if (!node && !aAllowMissing) {
 | 
						|
      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() {},
 | 
						|
  selectAll() {},
 | 
						|
 | 
						|
  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 singleClickOpens() {
 | 
						|
    return true;
 | 
						|
  },
 | 
						|
 | 
						|
  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 = PlacesUtils.asQuery(container).query.tags[0];
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (this.controller.disallowInsertion(container)) {
 | 
						|
      return null;
 | 
						|
    }
 | 
						|
 | 
						|
    return new PlacesInsertionPoint({
 | 
						|
      parentId: PlacesUtils.getConcreteItemId(container),
 | 
						|
      parentGuid: PlacesUtils.getConcreteItemGuid(container),
 | 
						|
      index,
 | 
						|
      orientation,
 | 
						|
      tagName,
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  buildContextMenu: function PVB_buildContextMenu(aPopup) {
 | 
						|
    this._contextMenuShown = aPopup;
 | 
						|
    window.updateCommands("places");
 | 
						|
 | 
						|
    // Ensure that an existing "Show Other Bookmarks" item is removed before adding it
 | 
						|
    // again. This item should only be added when gBookmarksToolbar2h2020 is true, but
 | 
						|
    // its possible the pref could be toggled off in the same window. This results in
 | 
						|
    // the "Show Other Bookmarks" menu item still being visible even when the pref is
 | 
						|
    // set to false.
 | 
						|
    let existingOtherBookmarksItem = aPopup.querySelector(
 | 
						|
      "#show-other-bookmarks_PersonalToolbar"
 | 
						|
    );
 | 
						|
    existingOtherBookmarksItem?.remove();
 | 
						|
 | 
						|
    let manageBookmarksMenu = aPopup.querySelector(
 | 
						|
      "#placesContext_showAllBookmarks"
 | 
						|
    );
 | 
						|
    // Add the View menu for the Bookmarks Toolbar and "Show Other Bookmarks" menu item
 | 
						|
    // if the click originated from the Bookmarks Toolbar.
 | 
						|
    if (gBookmarksToolbar2h2020) {
 | 
						|
      let existingSubmenu = aPopup.querySelector("#toggle_PersonalToolbar");
 | 
						|
      existingSubmenu?.remove();
 | 
						|
      let bookmarksToolbar = document.getElementById("PersonalToolbar");
 | 
						|
      if (bookmarksToolbar?.contains(aPopup.triggerNode)) {
 | 
						|
        manageBookmarksMenu.removeAttribute("hidden");
 | 
						|
 | 
						|
        let menu = BookmarkingUI.buildBookmarksToolbarSubmenu(bookmarksToolbar);
 | 
						|
        aPopup.insertBefore(menu, manageBookmarksMenu);
 | 
						|
 | 
						|
        if (
 | 
						|
          aPopup.triggerNode.id === "OtherBookmarks" ||
 | 
						|
          aPopup.triggerNode.id === "PlacesChevron" ||
 | 
						|
          aPopup.triggerNode.id === "PlacesToolbarItems" ||
 | 
						|
          aPopup.triggerNode.parentNode.id === "PlacesToolbarItems"
 | 
						|
        ) {
 | 
						|
          let otherBookmarksMenuItem = BookmarkingUI.buildShowOtherBookmarksMenuItem();
 | 
						|
 | 
						|
          if (otherBookmarksMenuItem) {
 | 
						|
            aPopup.insertBefore(
 | 
						|
              otherBookmarksMenuItem,
 | 
						|
              menu.nextElementSibling
 | 
						|
            );
 | 
						|
          }
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        manageBookmarksMenu.setAttribute("hidden", "true");
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      manageBookmarksMenu.setAttribute("hidden", "true");
 | 
						|
    }
 | 
						|
 | 
						|
    return this.controller.buildContextMenu(aPopup);
 | 
						|
  },
 | 
						|
 | 
						|
  destroyContextMenu: function PVB_destroyContextMenu(aPopup) {
 | 
						|
    this._contextMenuShown = null;
 | 
						|
  },
 | 
						|
 | 
						|
  clearAllContents(aPopup) {
 | 
						|
    let kid = aPopup.firstElementChild;
 | 
						|
    while (kid) {
 | 
						|
      let next = kid.nextElementSibling;
 | 
						|
      if (!kid.classList.contains("panel-header")) {
 | 
						|
        kid.remove();
 | 
						|
      }
 | 
						|
      kid = next;
 | 
						|
    }
 | 
						|
    aPopup._emptyMenuitem = aPopup._startMarker = aPopup._endMarker = null;
 | 
						|
  },
 | 
						|
 | 
						|
  _cleanPopup: function PVB_cleanPopup(aPopup, aDelay) {
 | 
						|
    // Ensure markers are here when `invalidateContainer` is called before the
 | 
						|
    // popup is shown, which may the case for panelviews, for example.
 | 
						|
    this._ensureMarkers(aPopup);
 | 
						|
    // Remove Places nodes from the popup.
 | 
						|
    let child = aPopup._startMarker;
 | 
						|
    while (child.nextElementSibling != aPopup._endMarker) {
 | 
						|
      let sibling = child.nextElementSibling;
 | 
						|
      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.nextElementSibling;
 | 
						|
      } else {
 | 
						|
        child = child.nextElementSibling;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _rebuildPopup: function PVB__rebuildPopup(aPopup) {
 | 
						|
    let resultNode = aPopup._placesNode;
 | 
						|
    if (!resultNode.containerOpen) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    this._cleanPopup(aPopup);
 | 
						|
 | 
						|
    let cc = resultNode.childCount;
 | 
						|
    if (cc > 0) {
 | 
						|
      this._setEmptyPopupStatus(aPopup, false);
 | 
						|
      let fragment = document.createDocumentFragment();
 | 
						|
      for (let i = 0; i < cc; ++i) {
 | 
						|
        let child = resultNode.getChild(i);
 | 
						|
        this._insertNewItemToPopup(child, fragment);
 | 
						|
      }
 | 
						|
      aPopup.insertBefore(fragment, aPopup._endMarker);
 | 
						|
    } 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.remove();
 | 
						|
  },
 | 
						|
 | 
						|
  _setEmptyPopupStatus: function PVB__setEmptyPopupStatus(aPopup, aEmpty) {
 | 
						|
    if (!aPopup._emptyMenuitem) {
 | 
						|
      let label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder");
 | 
						|
      aPopup._emptyMenuitem = document.createXULElement("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.previousElementSibling &&
 | 
						|
        !aPopup._endMarker.nextElementSibling
 | 
						|
      ) {
 | 
						|
        aPopup.insertBefore(aPopup._emptyMenuitem, aPopup._endMarker);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      aPopup.removeAttribute("emptyplacesresult");
 | 
						|
      try {
 | 
						|
        aPopup.removeChild(aPopup._emptyMenuitem);
 | 
						|
      } catch (ex) {}
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _createDOMNodeForPlacesNode: function PVB__createDOMNodeForPlacesNode(
 | 
						|
    aPlacesNode
 | 
						|
  ) {
 | 
						|
    this._domNodes.delete(aPlacesNode);
 | 
						|
 | 
						|
    let element;
 | 
						|
    let type = aPlacesNode.type;
 | 
						|
    if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
 | 
						|
      element = document.createXULElement("menuseparator");
 | 
						|
      element.setAttribute("class", "small-separator");
 | 
						|
    } else {
 | 
						|
      if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_URI) {
 | 
						|
        element = document.createXULElement("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.createXULElement("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");
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        let popup = document.createXULElement("menupopup", {
 | 
						|
          is: "places-popup",
 | 
						|
        });
 | 
						|
        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 new Error("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,
 | 
						|
    aInsertionNode,
 | 
						|
    aBefore = null
 | 
						|
  ) {
 | 
						|
    let element = this._createDOMNodeForPlacesNode(aNewChild);
 | 
						|
 | 
						|
    if (element.localName == "menuitem" || element.localName == "menu") {
 | 
						|
      if (typeof this.options.extraClasses.entry == "string") {
 | 
						|
        element.classList.add(this.options.extraClasses.entry);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    aInsertionNode.insertBefore(element, aBefore);
 | 
						|
    return element;
 | 
						|
  },
 | 
						|
 | 
						|
  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(aPlacesNode.uri)
 | 
						|
    );
 | 
						|
  },
 | 
						|
 | 
						|
  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;
 | 
						|
    }
 | 
						|
    // We must remove and reset the attribute to force an update.
 | 
						|
    elt.removeAttribute("image");
 | 
						|
    elt.setAttribute("image", aPlacesNode.icon);
 | 
						|
  },
 | 
						|
 | 
						|
  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.nextElementSibling == parentElt._endMarker) {
 | 
						|
        this._setEmptyPopupStatus(parentElt, true);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  // Opt-out of history details updates, since all the views derived from this
 | 
						|
  // are not showing them.
 | 
						|
  observeHistoryDetails: false,
 | 
						|
  nodeHistoryDetailsChanged() {},
 | 
						|
  nodeTagsChanged() {},
 | 
						|
  nodeDateAddedChanged() {},
 | 
						|
  nodeLastModifiedChanged() {},
 | 
						|
  nodeKeywordChanged() {},
 | 
						|
  sortingChanged() {},
 | 
						|
  batching() {},
 | 
						|
 | 
						|
  nodeInserted: function PVB_nodeInserted(
 | 
						|
    aParentPlacesNode,
 | 
						|
    aPlacesNode,
 | 
						|
    aIndex
 | 
						|
  ) {
 | 
						|
    let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
 | 
						|
    if (!parentElt._built) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let index =
 | 
						|
      Array.prototype.indexOf.call(parentElt.children, parentElt._startMarker) +
 | 
						|
      aIndex +
 | 
						|
      1;
 | 
						|
    this._insertNewItemToPopup(
 | 
						|
      aPlacesNode,
 | 
						|
      parentElt,
 | 
						|
      parentElt.children[index] || parentElt._endMarker
 | 
						|
    );
 | 
						|
    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.children,
 | 
						|
          parentElt._startMarker
 | 
						|
        ) +
 | 
						|
        aNewIndex +
 | 
						|
        1;
 | 
						|
      parentElt.insertBefore(elt, parentElt.children[index]);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  containerStateChanged: function PVB_containerStateChanged(
 | 
						|
    aPlacesNode,
 | 
						|
    aOldState,
 | 
						|
    aNewState
 | 
						|
  ) {
 | 
						|
    if (
 | 
						|
      aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED ||
 | 
						|
      aNewState == Ci.nsINavHistoryContainerResultNode.STATE_CLOSED
 | 
						|
    ) {
 | 
						|
      this.invalidateContainer(aPlacesNode);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Checks whether the popup associated with the provided element is open.
 | 
						|
   * This method may be overridden by classes that extend this base class.
 | 
						|
   *
 | 
						|
   * @param  {Element} elt
 | 
						|
   * @returns {boolean}
 | 
						|
   */
 | 
						|
  _isPopupOpen(elt) {
 | 
						|
    return !!elt.parentNode.open;
 | 
						|
  },
 | 
						|
 | 
						|
  invalidateContainer: function PVB_invalidateContainer(aPlacesNode) {
 | 
						|
    let elt = this._getDOMNodeForPlacesNode(aPlacesNode);
 | 
						|
    elt._built = false;
 | 
						|
 | 
						|
    // If the menupopup is open we should live-update it.
 | 
						|
    if (this._isPopupOpen(elt)) {
 | 
						|
      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 {object} 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.firstElementChild;
 | 
						|
      let numURINodes = 0;
 | 
						|
      while (currentChild) {
 | 
						|
        if (currentChild.localName == "menuitem" && currentChild._placesNode) {
 | 
						|
          if (++numURINodes == 2) {
 | 
						|
            break;
 | 
						|
          }
 | 
						|
        }
 | 
						|
        currentChild = currentChild.nextElementSibling;
 | 
						|
      }
 | 
						|
      hasMultipleURIs = numURINodes > 1;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!hasMultipleURIs) {
 | 
						|
      aPopup.setAttribute("nofooterpopup", "true");
 | 
						|
    } else {
 | 
						|
      aPopup.removeAttribute("nofooterpopup");
 | 
						|
    }
 | 
						|
 | 
						|
    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.createXULElement("menuseparator");
 | 
						|
      aPopup._endOptSeparator.className = "bookmarks-actions-menuseparator";
 | 
						|
      aPopup.appendChild(aPopup._endOptSeparator);
 | 
						|
 | 
						|
      // Add the "Open All in Tabs" menuitem.
 | 
						|
      aPopup._endOptOpenAllInTabs = document.createXULElement("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
 | 
						|
        );
 | 
						|
      }
 | 
						|
 | 
						|
      aPopup._endOptOpenAllInTabs.setAttribute(
 | 
						|
        "oncommand",
 | 
						|
        "PlacesUIUtils.openMultipleLinksInTabs(this.parentNode._placesNode, event, " +
 | 
						|
          "PlacesUIUtils.getViewForNode(this));"
 | 
						|
      );
 | 
						|
      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.createXULElement("menuseparator");
 | 
						|
    aPopup._startMarker.hidden = true;
 | 
						|
    aPopup.insertBefore(aPopup._startMarker, aPopup.firstElementChild);
 | 
						|
 | 
						|
    // _endMarker is a DOM node that lives after places nodes, specified with
 | 
						|
    // the 'insertionPoint' option or will be a hidden menuseparator.
 | 
						|
    let node = this.options.insertionPoint
 | 
						|
      ? aPopup.querySelector(this.options.insertionPoint)
 | 
						|
      : null;
 | 
						|
    if (node) {
 | 
						|
      aPopup._endMarker = node;
 | 
						|
    } else {
 | 
						|
      aPopup._endMarker = document.createXULElement("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.children.length; i++) {
 | 
						|
      let child = aPopup.children[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 && !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) {
 | 
						|
        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 = false
 | 
						|
  ) {
 | 
						|
    for (let i = 0; i < aEventNames.length; i++) {
 | 
						|
      aObject.addEventListener(aEventNames[i], this, aCapturing);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _removeEventListeners: function PVB__removeEventListeners(
 | 
						|
    aObject,
 | 
						|
    aEventNames,
 | 
						|
    aCapturing = false
 | 
						|
  ) {
 | 
						|
    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._dragRoot = BookmarkingUI.toolbar.contains(this._viewElt)
 | 
						|
    ? BookmarkingUI.toolbar
 | 
						|
    : this._viewElt;
 | 
						|
  this._addEventListeners(this._dragRoot, 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
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  this._updatingNodesVisibility = 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",
 | 
						|
    "mousedown",
 | 
						|
  ],
 | 
						|
 | 
						|
  QueryInterface: ChromeUtils.generateQI([
 | 
						|
    "nsITimerCallback",
 | 
						|
    ...PlacesViewBase.interfaces,
 | 
						|
  ]),
 | 
						|
 | 
						|
  uninit: function PT_uninit() {
 | 
						|
    if (this._dragRoot) {
 | 
						|
      this._removeEventListeners(this._dragRoot, 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();
 | 
						|
    }
 | 
						|
 | 
						|
    if (this._otherBookmarks?._placesView) {
 | 
						|
      this._otherBookmarks._placesView.uninit();
 | 
						|
    }
 | 
						|
 | 
						|
    PlacesViewBase.prototype.uninit.apply(this, arguments);
 | 
						|
  },
 | 
						|
 | 
						|
  _openedMenuButton: null,
 | 
						|
  _allowPopupShowing: true,
 | 
						|
 | 
						|
  get _isAlive() {
 | 
						|
    return this._resultNode && this._rootElt;
 | 
						|
  },
 | 
						|
 | 
						|
  _runBeforeFrameRender(callback) {
 | 
						|
    return new Promise((resolve, reject) => {
 | 
						|
      window.requestAnimationFrame(() => {
 | 
						|
        try {
 | 
						|
          resolve(callback());
 | 
						|
        } catch (err) {
 | 
						|
          reject(err);
 | 
						|
        }
 | 
						|
      });
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  async _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.firstChild.remove();
 | 
						|
    }
 | 
						|
 | 
						|
    let cc = this._resultNode.childCount;
 | 
						|
    if (cc > 0) {
 | 
						|
      // There could be a lot of nodes, but we only want to build the ones that
 | 
						|
      // are more likely to be shown, not all of them.
 | 
						|
      // We also don't want to wait for reflows at every node insertion, to
 | 
						|
      // calculate a precise number of visible items, thus we guess a size from
 | 
						|
      // the first non-separator node (because separators have flexible size).
 | 
						|
      let startIndex = 0;
 | 
						|
      let limit = await this._runBeforeFrameRender(() => {
 | 
						|
        if (!this._isAlive) {
 | 
						|
          return cc;
 | 
						|
        }
 | 
						|
 | 
						|
        // Look for the first non-separator node.
 | 
						|
        let elt;
 | 
						|
        while (startIndex < cc) {
 | 
						|
          elt = this._insertNewItem(
 | 
						|
            this._resultNode.getChild(startIndex),
 | 
						|
            this._rootElt
 | 
						|
          );
 | 
						|
          ++startIndex;
 | 
						|
          if (elt.localName != "toolbarseparator") {
 | 
						|
            break;
 | 
						|
          }
 | 
						|
        }
 | 
						|
        if (!elt) {
 | 
						|
          return cc;
 | 
						|
        }
 | 
						|
 | 
						|
        return window.promiseDocumentFlushed(() => {
 | 
						|
          // We assume a button with just the icon will be more or less a square,
 | 
						|
          // then compensate the measurement error by considering a larger screen
 | 
						|
          // width. Moreover the window could be bigger than the screen.
 | 
						|
          let size = elt.clientHeight || 1; // Sanity fallback.
 | 
						|
          return Math.min(cc, parseInt((window.screen.width * 1.5) / size));
 | 
						|
        });
 | 
						|
      });
 | 
						|
 | 
						|
      if (!this._isAlive) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      let fragment = document.createDocumentFragment();
 | 
						|
      for (let i = startIndex; i < limit; ++i) {
 | 
						|
        this._insertNewItem(this._resultNode.getChild(i), fragment);
 | 
						|
      }
 | 
						|
      window.requestAnimationFrame(() => {
 | 
						|
        if (this._isAlive) {
 | 
						|
          this._rootElt.appendChild(fragment);
 | 
						|
          this.updateNodesVisibility();
 | 
						|
        }
 | 
						|
      });
 | 
						|
    }
 | 
						|
 | 
						|
    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;
 | 
						|
    }
 | 
						|
 | 
						|
    // Rebuild the "Other Bookmarks" folder if it already exists.
 | 
						|
    let otherBookmarks = document.getElementById("OtherBookmarks");
 | 
						|
    otherBookmarks?.remove();
 | 
						|
 | 
						|
    BookmarkingUI.maybeShowOtherBookmarksFolder();
 | 
						|
  },
 | 
						|
 | 
						|
  _insertNewItem: function PT__insertNewItem(
 | 
						|
    aChild,
 | 
						|
    aInsertionNode,
 | 
						|
    aBefore = null
 | 
						|
  ) {
 | 
						|
    this._domNodes.delete(aChild);
 | 
						|
 | 
						|
    let type = aChild.type;
 | 
						|
    let button;
 | 
						|
    if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
 | 
						|
      button = document.createXULElement("toolbarseparator");
 | 
						|
    } else {
 | 
						|
      button = document.createXULElement("toolbarbutton");
 | 
						|
      button.className = "bookmark-item";
 | 
						|
      button.setAttribute("label", aChild.title || "");
 | 
						|
 | 
						|
      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");
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        let popup = document.createXULElement("menupopup", {
 | 
						|
          is: "places-popup",
 | 
						|
        });
 | 
						|
        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;
 | 
						|
    let { icon } = button._placesNode;
 | 
						|
    if (icon) {
 | 
						|
      button.setAttribute("image", icon);
 | 
						|
    }
 | 
						|
    if (!this._domNodes.has(aChild)) {
 | 
						|
      this._domNodes.set(aChild, button);
 | 
						|
    }
 | 
						|
 | 
						|
    if (aBefore) {
 | 
						|
      aInsertionNode.insertBefore(button, aBefore);
 | 
						|
    } else {
 | 
						|
      aInsertionNode.appendChild(button);
 | 
						|
    }
 | 
						|
    return button;
 | 
						|
  },
 | 
						|
 | 
						|
  _updateChevronPopupNodesVisibility: function PT__updateChevronPopupNodesVisibility() {
 | 
						|
    // Note the toolbar by default builds less nodes than the chevron popup.
 | 
						|
    for (
 | 
						|
      let toolbarNode = this._rootElt.firstElementChild,
 | 
						|
        node = this._chevronPopup._startMarker.nextElementSibling;
 | 
						|
      toolbarNode && node;
 | 
						|
      toolbarNode = toolbarNode.nextElementSibling,
 | 
						|
        node = node.nextElementSibling
 | 
						|
    ) {
 | 
						|
      node.hidden = toolbarNode.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();
 | 
						|
  },
 | 
						|
 | 
						|
  _onOtherBookmarksPopupShowing: function PT__onOtherBookmarksPopupShowing(
 | 
						|
    aEvent
 | 
						|
  ) {
 | 
						|
    if (aEvent.target != this._otherBookmarksPopup) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!this._otherBookmarks._placesView) {
 | 
						|
      this._otherBookmarks._placesView = new PlacesMenu(
 | 
						|
        aEvent,
 | 
						|
        "place:parent=" + PlacesUtils.bookmarks.unfiledGuid
 | 
						|
      );
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  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.
 | 
						|
        if (aEvent.target == aEvent.currentTarget) {
 | 
						|
          this.updateNodesVisibility();
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      case "overflow":
 | 
						|
        if (!this._isOverflowStateEventRelevant(aEvent)) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        // Avoid triggering overflow in containers if possible
 | 
						|
        aEvent.stopPropagation();
 | 
						|
        this._onOverflow();
 | 
						|
        break;
 | 
						|
      case "underflow":
 | 
						|
        if (!this._isOverflowStateEventRelevant(aEvent)) {
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        // Avoid triggering underflow in containers if possible
 | 
						|
        aEvent.stopPropagation();
 | 
						|
        this._onUnderflow();
 | 
						|
        break;
 | 
						|
      case "TabOpen":
 | 
						|
      case "TabClose":
 | 
						|
        this.updateNodesVisibility();
 | 
						|
        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 "mousedown":
 | 
						|
        this._onMouseDown(aEvent);
 | 
						|
        break;
 | 
						|
      case "popupshowing":
 | 
						|
        this._onPopupShowing(aEvent);
 | 
						|
        break;
 | 
						|
      case "popuphidden":
 | 
						|
        this._onPopupHidden(aEvent);
 | 
						|
        break;
 | 
						|
      default:
 | 
						|
        throw new Error("Trying to handle unexpected event.");
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _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.updateNodesVisibility();
 | 
						|
  },
 | 
						|
 | 
						|
  _onUnderflow: function PT_onUnderflow() {
 | 
						|
    this.updateNodesVisibility();
 | 
						|
    this._chevron.collapsed = true;
 | 
						|
  },
 | 
						|
 | 
						|
  updateNodesVisibility: function PT_updateNodesVisibility() {
 | 
						|
    // Update the chevron on a timer.  This will avoid repeated work when
 | 
						|
    // lot of changes happen in a small timeframe.
 | 
						|
    if (this._updateNodesVisibilityTimer) {
 | 
						|
      this._updateNodesVisibilityTimer.cancel();
 | 
						|
    }
 | 
						|
 | 
						|
    this._updateNodesVisibilityTimer = this._setTimer(100);
 | 
						|
  },
 | 
						|
 | 
						|
  async _updateNodesVisibilityTimerCallback() {
 | 
						|
    if (this._updatingNodesVisibility || window.closed) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    this._updatingNodesVisibility = true;
 | 
						|
 | 
						|
    let dwu = window.windowUtils;
 | 
						|
 | 
						|
    let scrollRect = await window.promiseDocumentFlushed(() =>
 | 
						|
      dwu.getBoundsWithoutFlushing(this._rootElt)
 | 
						|
    );
 | 
						|
 | 
						|
    let childOverflowed = false;
 | 
						|
 | 
						|
    // We're about to potentially update a bunch of nodes, so we do it
 | 
						|
    // in a requestAnimationFrame so that other JS that's might execute
 | 
						|
    // in the same tick can avoid flushing styles and layout for these
 | 
						|
    // changes.
 | 
						|
    window.requestAnimationFrame(() => {
 | 
						|
      for (let child of this._rootElt.children) {
 | 
						|
        // Once a child overflows, all the next ones will.
 | 
						|
        if (!childOverflowed) {
 | 
						|
          let childRect = dwu.getBoundsWithoutFlushing(child);
 | 
						|
          childOverflowed = this.isRTL
 | 
						|
            ? childRect.left < scrollRect.left
 | 
						|
            : childRect.right > scrollRect.right;
 | 
						|
        }
 | 
						|
 | 
						|
        if (childOverflowed) {
 | 
						|
          child.removeAttribute("image");
 | 
						|
          child.style.visibility = "hidden";
 | 
						|
        } else {
 | 
						|
          let icon = child._placesNode.icon;
 | 
						|
          if (icon) {
 | 
						|
            child.setAttribute("image", icon);
 | 
						|
          }
 | 
						|
          child.style.visibility = "visible";
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // We rebuild the chevron on popupShowing, so if it is open
 | 
						|
      // we must update it.
 | 
						|
      if (!this._chevron.collapsed && this._chevron.open) {
 | 
						|
        this._updateChevronPopupNodesVisibility();
 | 
						|
      }
 | 
						|
 | 
						|
      let event = new CustomEvent("BookmarksToolbarVisibilityUpdated", {
 | 
						|
        bubbles: true,
 | 
						|
      });
 | 
						|
      this._viewElt.dispatchEvent(event);
 | 
						|
      this._updatingNodesVisibility = false;
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  nodeInserted: function PT_nodeInserted(
 | 
						|
    aParentPlacesNode,
 | 
						|
    aPlacesNode,
 | 
						|
    aIndex
 | 
						|
  ) {
 | 
						|
    let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
 | 
						|
    if (parentElt == this._rootElt) {
 | 
						|
      // Node is on the toolbar.
 | 
						|
      let children = this._rootElt.children;
 | 
						|
      // Nothing to do if it's a never-visible node, but note it's possible
 | 
						|
      // we are appending.
 | 
						|
      if (aIndex > children.length) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      // Note that childCount is already accounting for the node being added,
 | 
						|
      // thus we must subtract one node from it.
 | 
						|
      if (this._resultNode.childCount - 1 > children.length) {
 | 
						|
        if (aIndex == children.length) {
 | 
						|
          // If we didn't build all the nodes and new node is being appended,
 | 
						|
          // we can skip it as well.
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        // Keep the number of built nodes consistent.
 | 
						|
        this._rootElt.removeChild(this._rootElt.lastElementChild);
 | 
						|
      }
 | 
						|
 | 
						|
      let button = this._insertNewItem(
 | 
						|
        aPlacesNode,
 | 
						|
        this._rootElt,
 | 
						|
        children[aIndex] || null
 | 
						|
      );
 | 
						|
      let prevSiblingOverflowed =
 | 
						|
        aIndex > 0 &&
 | 
						|
        aIndex <= children.length &&
 | 
						|
        children[aIndex - 1].style.visibility == "hidden";
 | 
						|
      if (prevSiblingOverflowed) {
 | 
						|
        button.style.visibility = "hidden";
 | 
						|
      } else {
 | 
						|
        let icon = aPlacesNode.icon;
 | 
						|
        if (icon) {
 | 
						|
          button.setAttribute("image", icon);
 | 
						|
        }
 | 
						|
        this.updateNodesVisibility();
 | 
						|
      }
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    PlacesViewBase.prototype.nodeInserted.apply(this, arguments);
 | 
						|
  },
 | 
						|
 | 
						|
  nodeRemoved: function PT_nodeRemoved(aParentPlacesNode, aPlacesNode, aIndex) {
 | 
						|
    let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
 | 
						|
    if (parentElt == this._rootElt) {
 | 
						|
      // Node is on the toolbar.
 | 
						|
      let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
 | 
						|
      // Nothing to do if it's a never-visible node.
 | 
						|
      if (!elt) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      // Here we need the <menu>.
 | 
						|
      if (elt.localName == "menupopup") {
 | 
						|
        elt = elt.parentNode;
 | 
						|
      }
 | 
						|
 | 
						|
      let overflowed = elt.style.visibility == "hidden";
 | 
						|
      this._removeChild(elt);
 | 
						|
      if (this._resultNode.childCount > this._rootElt.children.length) {
 | 
						|
        // A new node should be built to keep a coherent number of children.
 | 
						|
        this._insertNewItem(
 | 
						|
          this._resultNode.getChild(this._rootElt.children.length),
 | 
						|
          this._rootElt
 | 
						|
        );
 | 
						|
      }
 | 
						|
      if (!overflowed) {
 | 
						|
        this.updateNodesVisibility();
 | 
						|
      }
 | 
						|
      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) {
 | 
						|
      // Node is on the toolbar.
 | 
						|
      // Do nothing if the node will never be visible.
 | 
						|
      let lastBuiltIndex = this._rootElt.children.length - 1;
 | 
						|
      if (aOldIndex > lastBuiltIndex && aNewIndex > lastBuiltIndex + 1) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
 | 
						|
      if (elt) {
 | 
						|
        // Here we need the <menu>.
 | 
						|
        if (elt.localName == "menupopup") {
 | 
						|
          elt = elt.parentNode;
 | 
						|
        }
 | 
						|
        this._removeChild(elt);
 | 
						|
      }
 | 
						|
 | 
						|
      if (aNewIndex > lastBuiltIndex + 1) {
 | 
						|
        if (this._resultNode.childCount > this._rootElt.children.length) {
 | 
						|
          // If the element was built and becomes non built, another node should
 | 
						|
          // be built to keep a coherent number of children.
 | 
						|
          this._insertNewItem(
 | 
						|
            this._resultNode.getChild(this._rootElt.children.length),
 | 
						|
            this._rootElt
 | 
						|
          );
 | 
						|
        }
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (!elt) {
 | 
						|
        // The node has not been inserted yet, so we must create it.
 | 
						|
        elt = this._insertNewItem(
 | 
						|
          aPlacesNode,
 | 
						|
          this._rootElt,
 | 
						|
          this._rootElt.children[aNewIndex]
 | 
						|
        );
 | 
						|
        let icon = aPlacesNode.icon;
 | 
						|
        if (icon) {
 | 
						|
          elt.setAttribute("image", icon);
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        this._rootElt.insertBefore(elt, this._rootElt.children[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 updateNodesVisibility tries to update
 | 
						|
      // their visibility, or the chevron may go out of sync.
 | 
						|
      // Luckily updateNodesVisibility runs on a timer, so, by the time it updates
 | 
						|
      // nodes, the menu has already handled the notification.
 | 
						|
 | 
						|
      this.updateNodesVisibility();
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    PlacesViewBase.prototype.nodeMoved.apply(this, arguments);
 | 
						|
  },
 | 
						|
 | 
						|
  nodeTitleChanged: function PT_nodeTitleChanged(aPlacesNode, aNewTitle) {
 | 
						|
    let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
 | 
						|
 | 
						|
    // Nothing to do if it's a never-visible node.
 | 
						|
    if (!elt || 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.
 | 
						|
      if (elt.style.visibility != "hidden") {
 | 
						|
        this.updateNodesVisibility();
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  invalidateContainer: function PT_invalidateContainer(aPlacesNode) {
 | 
						|
    let elt = this._getDOMNodeForPlacesNode(aPlacesNode, true);
 | 
						|
    // Nothing to do if it's a never-visible node.
 | 
						|
    if (!elt) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (elt == this._rootElt) {
 | 
						|
      // Container is the toolbar itself.
 | 
						|
      this._rebuild().catch(Cu.reportError);
 | 
						|
      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.menupopup) {
 | 
						|
      if (!this._overFolder.elt.menupopup.hasAttribute("dragover")) {
 | 
						|
        this._overFolder.elt.menupopup.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.
 | 
						|
   *
 | 
						|
   * @param {object} aEvent
 | 
						|
   *   The associated event.
 | 
						|
   * @returns {object}
 | 
						|
   *   - 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.children, elt);
 | 
						|
      if (
 | 
						|
        PlacesUtils.nodeIsFolder(elt._placesNode) &&
 | 
						|
        !PlacesUIUtils.isFolderReadOnly(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 PlacesInsertionPoint({
 | 
						|
            parentId: PlacesUtils.getConcreteItemId(this._resultNode),
 | 
						|
            parentGuid: PlacesUtils.getConcreteItemGuid(this._resultNode),
 | 
						|
            index: eltIndex,
 | 
						|
            orientation: 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 PlacesInsertionPoint({
 | 
						|
            parentId: PlacesUtils.getConcreteItemId(elt._placesNode),
 | 
						|
            parentGuid: PlacesUtils.getConcreteItemGuid(elt._placesNode),
 | 
						|
            tagName,
 | 
						|
          });
 | 
						|
          dropPoint.beforeIndex = eltIndex;
 | 
						|
          dropPoint.folderElt = elt;
 | 
						|
        } else {
 | 
						|
          // Drop after this folder.
 | 
						|
          let beforeIndex =
 | 
						|
            eltIndex == this._rootElt.children.length - 1 ? -1 : eltIndex + 1;
 | 
						|
 | 
						|
          dropPoint.ip = new PlacesInsertionPoint({
 | 
						|
            parentId: PlacesUtils.getConcreteItemId(this._resultNode),
 | 
						|
            parentGuid: PlacesUtils.getConcreteItemGuid(this._resultNode),
 | 
						|
            index: beforeIndex,
 | 
						|
            orientation: 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 PlacesInsertionPoint({
 | 
						|
            parentId: PlacesUtils.getConcreteItemId(this._resultNode),
 | 
						|
            parentGuid: PlacesUtils.getConcreteItemGuid(this._resultNode),
 | 
						|
            index: eltIndex,
 | 
						|
            orientation: Ci.nsITreeView.DROP_BEFORE,
 | 
						|
          });
 | 
						|
          dropPoint.beforeIndex = eltIndex;
 | 
						|
        } else {
 | 
						|
          // Drop after this bookmark.
 | 
						|
          let beforeIndex =
 | 
						|
            eltIndex == this._rootElt.children.length - 1 ? -1 : eltIndex + 1;
 | 
						|
          dropPoint.ip = new PlacesInsertionPoint({
 | 
						|
            parentId: PlacesUtils.getConcreteItemId(this._resultNode),
 | 
						|
            parentGuid: PlacesUtils.getConcreteItemGuid(this._resultNode),
 | 
						|
            index: beforeIndex,
 | 
						|
            orientation: 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 PlacesInsertionPoint({
 | 
						|
        parentId: PlacesUtils.getConcreteItemId(this._resultNode),
 | 
						|
        parentGuid: PlacesUtils.getConcreteItemGuid(this._resultNode),
 | 
						|
        orientation: 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._updateNodesVisibilityTimer) {
 | 
						|
      this._updateNodesVisibilityTimer = null;
 | 
						|
      this._updateNodesVisibilityTimerCallback();
 | 
						|
    } else if (aTimer == this._ibTimer) {
 | 
						|
      // * Timer to turn off indicator bar.
 | 
						|
      this._dropIndicator.collapsed = true;
 | 
						|
      this._ibTimer = null;
 | 
						|
    } else if (aTimer == this._overFolder.openTimer) {
 | 
						|
      // * Timer to open a menubutton that's being dragged over.
 | 
						|
      // 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.menupopup.setAttribute("autoopened", "true");
 | 
						|
      this._overFolder.elt.open = true;
 | 
						|
      this._overFolder.openTimer = null;
 | 
						|
    } else if (aTimer == this._overFolder.closeTimer) {
 | 
						|
      // * Timer to close a menubutton that's been dragged off of.
 | 
						|
      // 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);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _onMouseOut: function PT__onMouseOut(aEvent) {
 | 
						|
    window.XULBrowserWindow.setOverLink("");
 | 
						|
  },
 | 
						|
 | 
						|
  _onMouseDown: function PT__onMouseDown(aEvent) {
 | 
						|
    let target = aEvent.target;
 | 
						|
    if (
 | 
						|
      aEvent.button == 0 &&
 | 
						|
      target.localName == "toolbarbutton" &&
 | 
						|
      target.getAttribute("type") == "menu"
 | 
						|
    ) {
 | 
						|
      let modifKey = aEvent.shiftKey || aEvent.getModifierState("Accel");
 | 
						|
      if (modifKey) {
 | 
						|
        // Do not open the popup since BEH_onClick is about to
 | 
						|
        // open all child uri nodes in tabs.
 | 
						|
        this._allowPopupShowing = false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _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.menupopup.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.firstElementChild) {
 | 
						|
          if (dropPoint.beforeIndex == -1) {
 | 
						|
            translateX += this._rootElt.lastElementChild.getBoundingClientRect()
 | 
						|
              .left;
 | 
						|
          } else {
 | 
						|
            translateX += this._rootElt.children[
 | 
						|
              dropPoint.beforeIndex
 | 
						|
            ].getBoundingClientRect().right;
 | 
						|
          }
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        halfInd = Math.floor(halfInd);
 | 
						|
        translateX = 0 - this._rootElt.getBoundingClientRect().left + halfInd;
 | 
						|
        if (this._rootElt.firstElementChild) {
 | 
						|
          if (dropPoint.beforeIndex == -1) {
 | 
						|
            translateX += this._rootElt.lastElementChild.getBoundingClientRect()
 | 
						|
              .right;
 | 
						|
          } else {
 | 
						|
            translateX += this._rootElt.children[
 | 
						|
              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
 | 
						|
      ).catch(Cu.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.
 | 
						|
      !PlacesUtils.nodeIsFolder(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.
 | 
						|
 *
 | 
						|
 * @param {object} aPopupShowingEvent
 | 
						|
 *   The event associated with opening the menu.
 | 
						|
 * @param {string} aPlace
 | 
						|
 *   The query associated with the view on the menu.
 | 
						|
 * @param {object} aOptions
 | 
						|
 *   Options associated with the view.
 | 
						|
 */
 | 
						|
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,
 | 
						|
 | 
						|
  _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.
 | 
						|
    if (!PlacesUtils.nodeIsFolder(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,
 | 
						|
 | 
						|
  uninit: function PAMV_uninit() {
 | 
						|
    PlacesViewBase.prototype.uninit.apply(this, arguments);
 | 
						|
  },
 | 
						|
 | 
						|
  _insertNewItem: function PAMV__insertNewItem(
 | 
						|
    aChild,
 | 
						|
    aInsertionNode,
 | 
						|
    aBefore = null
 | 
						|
  ) {
 | 
						|
    this._domNodes.delete(aChild);
 | 
						|
 | 
						|
    let type = aChild.type;
 | 
						|
    let button;
 | 
						|
    if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
 | 
						|
      button = document.createXULElement("toolbarseparator");
 | 
						|
      button.setAttribute("class", "small-separator");
 | 
						|
    } else {
 | 
						|
      button = document.createXULElement("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.nodeIsURI(aChild)) {
 | 
						|
        button.setAttribute(
 | 
						|
          "scheme",
 | 
						|
          PlacesUIUtils.guessUrlSchemeForUI(aChild.uri)
 | 
						|
        );
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    button._placesNode = aChild;
 | 
						|
    if (!this._domNodes.has(aChild)) {
 | 
						|
      this._domNodes.set(aChild, button);
 | 
						|
    }
 | 
						|
 | 
						|
    aInsertionNode.insertBefore(button, aBefore);
 | 
						|
    return button;
 | 
						|
  },
 | 
						|
 | 
						|
  nodeInserted: function PAMV_nodeInserted(
 | 
						|
    aParentPlacesNode,
 | 
						|
    aPlacesNode,
 | 
						|
    aIndex
 | 
						|
  ) {
 | 
						|
    let parentElt = this._getDOMNodeForPlacesNode(aParentPlacesNode);
 | 
						|
    if (parentElt != this._rootElt) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let children = this._rootElt.children;
 | 
						|
    this._insertNewItem(
 | 
						|
      aPlacesNode,
 | 
						|
      this._rootElt,
 | 
						|
      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.children[aNewIndex]);
 | 
						|
  },
 | 
						|
 | 
						|
  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.firstChild.remove();
 | 
						|
    }
 | 
						|
 | 
						|
    let fragment = document.createDocumentFragment();
 | 
						|
    for (let i = 0; i < this._resultNode.childCount; ++i) {
 | 
						|
      this._insertNewItem(this._resultNode.getChild(i), fragment);
 | 
						|
    }
 | 
						|
    this._rootElt.appendChild(fragment);
 | 
						|
  },
 | 
						|
};
 | 
						|
 | 
						|
this.PlacesPanelview = class extends PlacesViewBase {
 | 
						|
  constructor(container, panelview, place, options = {}) {
 | 
						|
    options.rootElt = container;
 | 
						|
    options.viewElt = panelview;
 | 
						|
    super(place, options);
 | 
						|
    this._viewElt._placesView = this;
 | 
						|
    // We're simulating a popup show, because a panelview may only be shown when
 | 
						|
    // its containing popup is already shown.
 | 
						|
    this._onPopupShowing({ originalTarget: this._rootElt });
 | 
						|
    this._addEventListeners(window, ["unload"]);
 | 
						|
    this._rootElt.setAttribute("context", "placesContext");
 | 
						|
  }
 | 
						|
 | 
						|
  get events() {
 | 
						|
    if (this._events) {
 | 
						|
      return this._events;
 | 
						|
    }
 | 
						|
    return (this._events = [
 | 
						|
      "click",
 | 
						|
      "command",
 | 
						|
      "dragend",
 | 
						|
      "dragstart",
 | 
						|
      "ViewHiding",
 | 
						|
      "ViewShown",
 | 
						|
    ]);
 | 
						|
  }
 | 
						|
 | 
						|
  handleEvent(event) {
 | 
						|
    switch (event.type) {
 | 
						|
      case "click":
 | 
						|
        // For middle clicks, fall through to the command handler.
 | 
						|
        if (event.button != 1) {
 | 
						|
          break;
 | 
						|
        }
 | 
						|
      // fall through
 | 
						|
      case "command":
 | 
						|
        this._onCommand(event);
 | 
						|
        break;
 | 
						|
      case "dragend":
 | 
						|
        this._onDragEnd(event);
 | 
						|
        break;
 | 
						|
      case "dragstart":
 | 
						|
        this._onDragStart(event);
 | 
						|
        break;
 | 
						|
      case "unload":
 | 
						|
        this.uninit(event);
 | 
						|
        break;
 | 
						|
      case "ViewHiding":
 | 
						|
        this._onPopupHidden(event);
 | 
						|
        break;
 | 
						|
      case "ViewShown":
 | 
						|
        this._onViewShown(event);
 | 
						|
        break;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  _onCommand(event) {
 | 
						|
    event = getRootEvent(event);
 | 
						|
    let button = event.originalTarget;
 | 
						|
    if (!button._placesNode) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let modifKey =
 | 
						|
      AppConstants.platform === "macosx" ? event.metaKey : event.ctrlKey;
 | 
						|
    if (!PlacesUIUtils.openInTabClosesMenu && modifKey) {
 | 
						|
      // If 'Recent Bookmarks' in Bookmarks Panel.
 | 
						|
      if (button.parentNode.id == "panelMenu_bookmarksMenu") {
 | 
						|
        button.setAttribute("closemenu", "none");
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      button.removeAttribute("closemenu");
 | 
						|
    }
 | 
						|
    PlacesUIUtils.openNodeWithEvent(button._placesNode, event);
 | 
						|
    // Unlike left-click, middle-click requires manual menu closing.
 | 
						|
    if (
 | 
						|
      button.parentNode.id != "panelMenu_bookmarksMenu" ||
 | 
						|
      (event.type == "click" &&
 | 
						|
        event.button == 1 &&
 | 
						|
        PlacesUIUtils.openInTabClosesMenu)
 | 
						|
    ) {
 | 
						|
      this.panelMultiView.closest("panel").hidePopup();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  _onDragEnd() {
 | 
						|
    this._draggedElt = null;
 | 
						|
  }
 | 
						|
 | 
						|
  _onDragStart(event) {
 | 
						|
    let draggedElt = event.originalTarget;
 | 
						|
    if (draggedElt.parentNode != this._rootElt || !draggedElt._placesNode) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Activate the view and cache the dragged element.
 | 
						|
    this._draggedElt = draggedElt._placesNode;
 | 
						|
    this._rootElt.focus();
 | 
						|
 | 
						|
    this._controller.setDataTransfer(event);
 | 
						|
    event.stopPropagation();
 | 
						|
  }
 | 
						|
 | 
						|
  uninit(event) {
 | 
						|
    this._removeEventListeners(this.panelMultiView, this.events);
 | 
						|
    this._removeEventListeners(window, ["unload"]);
 | 
						|
    delete this.panelMultiView;
 | 
						|
    super.uninit(event);
 | 
						|
  }
 | 
						|
 | 
						|
  _createDOMNodeForPlacesNode(placesNode) {
 | 
						|
    this._domNodes.delete(placesNode);
 | 
						|
 | 
						|
    let element;
 | 
						|
    let type = placesNode.type;
 | 
						|
    if (type == Ci.nsINavHistoryResultNode.RESULT_TYPE_SEPARATOR) {
 | 
						|
      element = document.createXULElement("toolbarseparator");
 | 
						|
    } else {
 | 
						|
      if (type != Ci.nsINavHistoryResultNode.RESULT_TYPE_URI) {
 | 
						|
        throw new Error("Unexpected node");
 | 
						|
      }
 | 
						|
 | 
						|
      element = document.createXULElement("toolbarbutton");
 | 
						|
      element.classList.add(
 | 
						|
        "subviewbutton",
 | 
						|
        "subviewbutton-iconic",
 | 
						|
        "bookmark-item"
 | 
						|
      );
 | 
						|
      element.setAttribute(
 | 
						|
        "scheme",
 | 
						|
        PlacesUIUtils.guessUrlSchemeForUI(placesNode.uri)
 | 
						|
      );
 | 
						|
      element.setAttribute("label", PlacesUIUtils.getBestTitle(placesNode));
 | 
						|
 | 
						|
      let icon = placesNode.icon;
 | 
						|
      if (icon) {
 | 
						|
        element.setAttribute("image", icon);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    element._placesNode = placesNode;
 | 
						|
    if (!this._domNodes.has(placesNode)) {
 | 
						|
      this._domNodes.set(placesNode, element);
 | 
						|
    }
 | 
						|
 | 
						|
    return element;
 | 
						|
  }
 | 
						|
 | 
						|
  _setEmptyPopupStatus(panelview, empty = false) {
 | 
						|
    if (!panelview._emptyMenuitem) {
 | 
						|
      let label = PlacesUIUtils.getString("bookmarksMenuEmptyFolder");
 | 
						|
      panelview._emptyMenuitem = document.createXULElement("toolbarbutton");
 | 
						|
      panelview._emptyMenuitem.setAttribute("label", label);
 | 
						|
      panelview._emptyMenuitem.setAttribute("disabled", true);
 | 
						|
      panelview._emptyMenuitem.className = "subviewbutton";
 | 
						|
      if (typeof this.options.extraClasses.entry == "string") {
 | 
						|
        panelview._emptyMenuitem.classList.add(this.options.extraClasses.entry);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (empty) {
 | 
						|
      panelview.setAttribute("emptyplacesresult", "true");
 | 
						|
      // Don't add the menuitem if there is static content.
 | 
						|
      // We also support external usage for custom crafted panels - which'll have
 | 
						|
      // no markers present.
 | 
						|
      if (
 | 
						|
        !panelview._startMarker ||
 | 
						|
        (!panelview._startMarker.previousElementSibling &&
 | 
						|
          !panelview._endMarker.nextElementSibling)
 | 
						|
      ) {
 | 
						|
        panelview.insertBefore(panelview._emptyMenuitem, panelview._endMarker);
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      panelview.removeAttribute("emptyplacesresult");
 | 
						|
      try {
 | 
						|
        panelview.removeChild(panelview._emptyMenuitem);
 | 
						|
      } catch (ex) {}
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  _isPopupOpen() {
 | 
						|
    return PanelView.forNode(this._viewElt).active;
 | 
						|
  }
 | 
						|
 | 
						|
  _onPopupHidden(event) {
 | 
						|
    let panelview = event.originalTarget;
 | 
						|
    let placesNode = panelview._placesNode;
 | 
						|
    // Avoid handling ViewHiding of inner views
 | 
						|
    if (
 | 
						|
      placesNode &&
 | 
						|
      PlacesUIUtils.getViewForNode(panelview) == this &&
 | 
						|
      // UI performance: folder queries are cheap, keep the resultnode open
 | 
						|
      // so we don't rebuild its contents whenever the popup is reopened.
 | 
						|
      !PlacesUtils.nodeIsFolder(placesNode)
 | 
						|
    ) {
 | 
						|
      placesNode.containerOpen = false;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  _onPopupShowing(event) {
 | 
						|
    // If the event came from the root element, this is the first time
 | 
						|
    // we ever get here.
 | 
						|
    if (event.originalTarget == this._rootElt) {
 | 
						|
      // Start listening for events from all panels inside the panelmultiview.
 | 
						|
      this.panelMultiView = this._viewElt.panelMultiView;
 | 
						|
      this._addEventListeners(this.panelMultiView, this.events);
 | 
						|
    }
 | 
						|
    super._onPopupShowing(event);
 | 
						|
  }
 | 
						|
 | 
						|
  _onViewShown(event) {
 | 
						|
    if (event.originalTarget != this._viewElt) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    // Because PanelMultiView reparents the panelview internally, the controller
 | 
						|
    // may get lost. In that case we'll append it again, because we certainly
 | 
						|
    // need it later!
 | 
						|
    if (!this.controllers.getControllerCount() && this._controller) {
 | 
						|
      this.controllers.appendController(this._controller);
 | 
						|
    }
 | 
						|
  }
 | 
						|
};
 |