forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			653 lines
		
	
	
	
		
			18 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			653 lines
		
	
	
	
		
			18 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/. */
 | 
						|
 | 
						|
const lazy = {};
 | 
						|
 | 
						|
ChromeUtils.defineESModuleGetters(lazy, {
 | 
						|
  PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
 | 
						|
});
 | 
						|
 | 
						|
import { getChromeWindow } from "resource:///modules/syncedtabs/util.sys.mjs";
 | 
						|
 | 
						|
function getContextMenu(window) {
 | 
						|
  return getChromeWindow(window).document.getElementById(
 | 
						|
    "SyncedTabsSidebarContext"
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
function getTabsFilterContextMenu(window) {
 | 
						|
  return getChromeWindow(window).document.getElementById(
 | 
						|
    "SyncedTabsSidebarTabsFilterContext"
 | 
						|
  );
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * TabListView
 | 
						|
 *
 | 
						|
 * Given a state, this object will render the corresponding DOM.
 | 
						|
 * It maintains no state of it's own. It listens for DOM events
 | 
						|
 * and triggers actions that may cause the state to change and
 | 
						|
 * ultimately the view to rerender.
 | 
						|
 */
 | 
						|
export function TabListView(window, props) {
 | 
						|
  this.props = props;
 | 
						|
 | 
						|
  this._window = window;
 | 
						|
  this._doc = this._window.document;
 | 
						|
 | 
						|
  this._tabsContainerTemplate = this._doc.getElementById(
 | 
						|
    "tabs-container-template"
 | 
						|
  );
 | 
						|
  this._clientTemplate = this._doc.getElementById("client-template");
 | 
						|
  this._emptyClientTemplate = this._doc.getElementById("empty-client-template");
 | 
						|
  this._tabTemplate = this._doc.getElementById("tab-template");
 | 
						|
  this.tabsFilter = this._doc.querySelector(".tabsFilter");
 | 
						|
 | 
						|
  this.container = this._doc.createElement("div");
 | 
						|
 | 
						|
  this._attachFixedListeners();
 | 
						|
 | 
						|
  this._setupContextMenu();
 | 
						|
}
 | 
						|
 | 
						|
TabListView.prototype = {
 | 
						|
  render(state) {
 | 
						|
    // Don't rerender anything; just update attributes, e.g. selection
 | 
						|
    if (state.canUpdateAll) {
 | 
						|
      this._update(state);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // Rerender the tab list
 | 
						|
    if (state.canUpdateInput) {
 | 
						|
      this._updateSearchBox(state);
 | 
						|
      this._createList(state);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    // Create the world anew
 | 
						|
    this._create(state);
 | 
						|
  },
 | 
						|
 | 
						|
  // Create the initial DOM from templates
 | 
						|
  _create(state) {
 | 
						|
    let wrapper = this._doc.importNode(
 | 
						|
      this._tabsContainerTemplate.content,
 | 
						|
      true
 | 
						|
    ).firstElementChild;
 | 
						|
    this._clearChilden();
 | 
						|
    this.container.appendChild(wrapper);
 | 
						|
 | 
						|
    this.list = this.container.querySelector(".list");
 | 
						|
 | 
						|
    this._createList(state);
 | 
						|
    this._updateSearchBox(state);
 | 
						|
 | 
						|
    this._attachListListeners();
 | 
						|
  },
 | 
						|
 | 
						|
  _createList(state) {
 | 
						|
    this._clearChilden(this.list);
 | 
						|
    for (let client of state.clients) {
 | 
						|
      if (state.filter) {
 | 
						|
        this._renderFilteredClient(client);
 | 
						|
      } else {
 | 
						|
        this._renderClient(client);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (this.list.firstElementChild) {
 | 
						|
      const firstTab = this.list.firstElementChild.querySelector(
 | 
						|
        ".item.tab:first-child .item-title"
 | 
						|
      );
 | 
						|
      if (firstTab) {
 | 
						|
        firstTab.setAttribute("tabindex", 2);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  destroy() {
 | 
						|
    this._teardownContextMenu();
 | 
						|
    this.container.remove();
 | 
						|
  },
 | 
						|
 | 
						|
  _update(state) {
 | 
						|
    this._updateSearchBox(state);
 | 
						|
    for (let client of state.clients) {
 | 
						|
      let clientNode = this._doc.getElementById("item-" + client.id);
 | 
						|
      if (clientNode) {
 | 
						|
        this._updateClient(client, clientNode);
 | 
						|
      }
 | 
						|
 | 
						|
      client.tabs.forEach((tab, index) => {
 | 
						|
        let tabNode = this._doc.getElementById(
 | 
						|
          "tab-" + client.id + "-" + index
 | 
						|
        );
 | 
						|
        this._updateTab(tab, tabNode, index);
 | 
						|
      });
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  // Client rows are hidden when the list is filtered
 | 
						|
  _renderFilteredClient(client, filter) {
 | 
						|
    client.tabs.forEach((tab, index) => {
 | 
						|
      let node = this._renderTab(client, tab, index);
 | 
						|
      this.list.appendChild(node);
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  _updateLastSyncTitle(lastModified, itemNode) {
 | 
						|
    let lastSync = new Date(lastModified);
 | 
						|
    let lastSyncTitle = getChromeWindow(this._window).gSync.formatLastSyncDate(
 | 
						|
      lastSync
 | 
						|
    );
 | 
						|
    itemNode.setAttribute("title", lastSyncTitle);
 | 
						|
  },
 | 
						|
 | 
						|
  _renderClient(client) {
 | 
						|
    let itemNode = client.tabs.length
 | 
						|
      ? this._createClient(client)
 | 
						|
      : this._createEmptyClient(client);
 | 
						|
 | 
						|
    itemNode.addEventListener("mouseover", () =>
 | 
						|
      this._updateLastSyncTitle(client.lastModified, itemNode)
 | 
						|
    );
 | 
						|
 | 
						|
    this._updateClient(client, itemNode);
 | 
						|
 | 
						|
    let tabsList = itemNode.querySelector(".item-tabs-list");
 | 
						|
    client.tabs.forEach((tab, index) => {
 | 
						|
      let node = this._renderTab(client, tab, index);
 | 
						|
      tabsList.appendChild(node);
 | 
						|
    });
 | 
						|
 | 
						|
    this.list.appendChild(itemNode);
 | 
						|
    return itemNode;
 | 
						|
  },
 | 
						|
 | 
						|
  _renderTab(client, tab, index) {
 | 
						|
    let itemNode = this._createTab(tab);
 | 
						|
    this._updateTab(tab, itemNode, index);
 | 
						|
    return itemNode;
 | 
						|
  },
 | 
						|
 | 
						|
  _createClient() {
 | 
						|
    return this._doc.importNode(this._clientTemplate.content, true)
 | 
						|
      .firstElementChild;
 | 
						|
  },
 | 
						|
 | 
						|
  _createEmptyClient() {
 | 
						|
    return this._doc.importNode(this._emptyClientTemplate.content, true)
 | 
						|
      .firstElementChild;
 | 
						|
  },
 | 
						|
 | 
						|
  _createTab() {
 | 
						|
    return this._doc.importNode(this._tabTemplate.content, true)
 | 
						|
      .firstElementChild;
 | 
						|
  },
 | 
						|
 | 
						|
  _clearChilden(node) {
 | 
						|
    let parent = node || this.container;
 | 
						|
    while (parent.firstChild) {
 | 
						|
      parent.firstChild.remove();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  // These listeners are attached only once, when we initialize the view
 | 
						|
  _attachFixedListeners() {
 | 
						|
    this.tabsFilter.addEventListener("command", this.onFilter.bind(this));
 | 
						|
    this.tabsFilter.addEventListener("focus", this.onFilterFocus.bind(this));
 | 
						|
    this.tabsFilter.addEventListener("blur", this.onFilterBlur.bind(this));
 | 
						|
  },
 | 
						|
 | 
						|
  // These listeners have to be re-created every time since we re-create the list
 | 
						|
  _attachListListeners() {
 | 
						|
    this.list.addEventListener("click", this.onClick.bind(this));
 | 
						|
    this.list.addEventListener("mouseup", this.onMouseUp.bind(this));
 | 
						|
    this.list.addEventListener("keydown", this.onKeyDown.bind(this));
 | 
						|
  },
 | 
						|
 | 
						|
  _updateSearchBox(state) {
 | 
						|
    this.tabsFilter.value = state.filter;
 | 
						|
    if (state.inputFocused) {
 | 
						|
      this.tabsFilter.focus();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Update the element representing an item, ensuring it's in sync with the
 | 
						|
   * underlying data.
 | 
						|
   * @param {client} item - Item to use as a source.
 | 
						|
   * @param {Element} itemNode - Element to update.
 | 
						|
   */
 | 
						|
  _updateClient(item, itemNode) {
 | 
						|
    itemNode.setAttribute("id", "item-" + item.id);
 | 
						|
    this._updateLastSyncTitle(item.lastModified, itemNode);
 | 
						|
    if (item.closed) {
 | 
						|
      itemNode.classList.add("closed");
 | 
						|
    } else {
 | 
						|
      itemNode.classList.remove("closed");
 | 
						|
    }
 | 
						|
    if (item.selected) {
 | 
						|
      itemNode.classList.add("selected");
 | 
						|
    } else {
 | 
						|
      itemNode.classList.remove("selected");
 | 
						|
    }
 | 
						|
    if (item.focused) {
 | 
						|
      itemNode.focus();
 | 
						|
    }
 | 
						|
    itemNode.setAttribute("clientType", item.clientType);
 | 
						|
    itemNode.dataset.id = item.id;
 | 
						|
    itemNode.querySelector(".item-title").textContent = item.name;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Update the element representing a tab, ensuring it's in sync with the
 | 
						|
   * underlying data.
 | 
						|
   * @param {tab} item - Item to use as a source.
 | 
						|
   * @param {Element} itemNode - Element to update.
 | 
						|
   */
 | 
						|
  _updateTab(item, itemNode, index) {
 | 
						|
    itemNode.setAttribute("title", `${item.title}\n${item.url}`);
 | 
						|
    itemNode.setAttribute("id", "tab-" + item.client + "-" + index);
 | 
						|
    if (item.selected) {
 | 
						|
      itemNode.classList.add("selected");
 | 
						|
    } else {
 | 
						|
      itemNode.classList.remove("selected");
 | 
						|
    }
 | 
						|
    if (item.focused) {
 | 
						|
      itemNode.focus();
 | 
						|
    }
 | 
						|
    itemNode.dataset.url = item.url;
 | 
						|
 | 
						|
    itemNode.querySelector(".item-title").textContent = item.title;
 | 
						|
 | 
						|
    if (item.icon) {
 | 
						|
      let icon = itemNode.querySelector(".item-icon-container");
 | 
						|
      icon.style.backgroundImage = "url(" + item.icon + ")";
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onMouseUp(event) {
 | 
						|
    if (event.which == 2) {
 | 
						|
      // Middle click
 | 
						|
      this.onClick(event);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onClick(event) {
 | 
						|
    let itemNode = this._findParentItemNode(event.target);
 | 
						|
    if (!itemNode) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (itemNode.classList.contains("tab")) {
 | 
						|
      let url = itemNode.dataset.url;
 | 
						|
      if (url) {
 | 
						|
        this.onOpenSelected(url, event);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // Middle click on a client
 | 
						|
    if (itemNode.classList.contains("client")) {
 | 
						|
      let where = getChromeWindow(this._window).whereToOpenLink(event);
 | 
						|
      if (where != "current") {
 | 
						|
        this._openAllClientTabs(itemNode, where);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (
 | 
						|
      event.target.classList.contains("item-twisty-container") &&
 | 
						|
      event.which != 2
 | 
						|
    ) {
 | 
						|
      this.props.onToggleBranch(itemNode.dataset.id);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    let position = this._getSelectionPosition(itemNode);
 | 
						|
    this.props.onSelectRow(position);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Handle a keydown event on the list box.
 | 
						|
   * @param {Event} event - Triggering event.
 | 
						|
   */
 | 
						|
  onKeyDown(event) {
 | 
						|
    if (event.keyCode == this._window.KeyEvent.DOM_VK_DOWN) {
 | 
						|
      event.preventDefault();
 | 
						|
      this.props.onMoveSelectionDown();
 | 
						|
    } else if (event.keyCode == this._window.KeyEvent.DOM_VK_UP) {
 | 
						|
      event.preventDefault();
 | 
						|
      this.props.onMoveSelectionUp();
 | 
						|
    } else if (event.keyCode == this._window.KeyEvent.DOM_VK_RETURN) {
 | 
						|
      let selectedNode = this.container.querySelector(".item.selected");
 | 
						|
      if (selectedNode.dataset.url) {
 | 
						|
        this.onOpenSelected(selectedNode.dataset.url, event);
 | 
						|
      } else if (selectedNode) {
 | 
						|
        this.props.onToggleBranch(selectedNode.dataset.id);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onBookmarkTab() {
 | 
						|
    let item = this._getSelectedTabNode();
 | 
						|
    if (item) {
 | 
						|
      let title = item.querySelector(".item-title").textContent;
 | 
						|
      this.props.onBookmarkTab(item.dataset.url, title);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onCopyTabLocation() {
 | 
						|
    let item = this._getSelectedTabNode();
 | 
						|
    if (item) {
 | 
						|
      this.props.onCopyTabLocation(item.dataset.url);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onOpenSelected(url, event) {
 | 
						|
    let where = getChromeWindow(this._window).whereToOpenLink(event);
 | 
						|
    this.props.onOpenTab(url, where, {});
 | 
						|
  },
 | 
						|
 | 
						|
  onOpenSelectedFromContextMenu(event) {
 | 
						|
    let item = this._getSelectedTabNode();
 | 
						|
    if (item) {
 | 
						|
      let where = event.target.getAttribute("where");
 | 
						|
      let params = {
 | 
						|
        private: event.target.hasAttribute("private"),
 | 
						|
      };
 | 
						|
      this.props.onOpenTab(item.dataset.url, where, params);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onOpenSelectedInContainerTab(event) {
 | 
						|
    let item = this._getSelectedTabNode();
 | 
						|
    if (item) {
 | 
						|
      this.props.onOpenTab(item.dataset.url, "tab", {
 | 
						|
        userContextId: parseInt(event.target?.dataset.usercontextid),
 | 
						|
      });
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onOpenAllInTabs() {
 | 
						|
    let item = this._getSelectedClientNode();
 | 
						|
    if (item) {
 | 
						|
      this._openAllClientTabs(item, "tab");
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onFilter(event) {
 | 
						|
    let query = event.target.value;
 | 
						|
    if (query) {
 | 
						|
      this.props.onFilter(query);
 | 
						|
    } else {
 | 
						|
      this.props.onClearFilter();
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  onFilterFocus() {
 | 
						|
    this.props.onFilterFocus();
 | 
						|
  },
 | 
						|
  onFilterBlur() {
 | 
						|
    this.props.onFilterBlur();
 | 
						|
  },
 | 
						|
 | 
						|
  _getSelectedTabNode() {
 | 
						|
    let item = this.container.querySelector(".item.selected");
 | 
						|
    if (this._isTab(item) && item.dataset.url) {
 | 
						|
      return item;
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
  },
 | 
						|
 | 
						|
  _getSelectedClientNode() {
 | 
						|
    let item = this.container.querySelector(".item.selected");
 | 
						|
    if (this._isClient(item)) {
 | 
						|
      return item;
 | 
						|
    }
 | 
						|
    return null;
 | 
						|
  },
 | 
						|
 | 
						|
  // Set up the custom context menu
 | 
						|
  _setupContextMenu() {
 | 
						|
    Services.els.addSystemEventListener(
 | 
						|
      this._window,
 | 
						|
      "contextmenu",
 | 
						|
      this,
 | 
						|
      false
 | 
						|
    );
 | 
						|
    for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
 | 
						|
      let menu = getMenu(this._window);
 | 
						|
      menu.addEventListener("popupshowing", this, true);
 | 
						|
      menu.addEventListener("command", this, true);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  _teardownContextMenu() {
 | 
						|
    // Tear down context menu
 | 
						|
    Services.els.removeSystemEventListener(
 | 
						|
      this._window,
 | 
						|
      "contextmenu",
 | 
						|
      this,
 | 
						|
      false
 | 
						|
    );
 | 
						|
    for (let getMenu of [getContextMenu, getTabsFilterContextMenu]) {
 | 
						|
      let menu = getMenu(this._window);
 | 
						|
      menu.removeEventListener("popupshowing", this, true);
 | 
						|
      menu.removeEventListener("command", this, true);
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  handleEvent(event) {
 | 
						|
    switch (event.type) {
 | 
						|
      case "contextmenu":
 | 
						|
        this.handleContextMenu(event);
 | 
						|
        break;
 | 
						|
 | 
						|
      case "popupshowing": {
 | 
						|
        if (
 | 
						|
          event.target.getAttribute("id") ==
 | 
						|
          "SyncedTabsSidebarTabsFilterContext"
 | 
						|
        ) {
 | 
						|
          this.handleTabsFilterContextMenuShown(event);
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      }
 | 
						|
 | 
						|
      case "command": {
 | 
						|
        let menu = event.target.closest("menupopup");
 | 
						|
        switch (menu.getAttribute("id")) {
 | 
						|
          case "SyncedTabsSidebarContext":
 | 
						|
            this.handleContentContextMenuCommand(event);
 | 
						|
            break;
 | 
						|
 | 
						|
          case "SyncedTabsOpenSelectedInContainerTabMenu":
 | 
						|
            this.onOpenSelectedInContainerTab(event);
 | 
						|
            break;
 | 
						|
 | 
						|
          case "SyncedTabsSidebarTabsFilterContext":
 | 
						|
            this.handleTabsFilterContextMenuCommand(event);
 | 
						|
            break;
 | 
						|
        }
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  handleTabsFilterContextMenuShown(event) {
 | 
						|
    let document = event.target.ownerDocument;
 | 
						|
    let focusedElement = document.commandDispatcher.focusedElement;
 | 
						|
    if (focusedElement != this.tabsFilter.inputField) {
 | 
						|
      this.tabsFilter.focus();
 | 
						|
    }
 | 
						|
    for (let item of event.target.children) {
 | 
						|
      if (!item.hasAttribute("cmd")) {
 | 
						|
        continue;
 | 
						|
      }
 | 
						|
      let command = item.getAttribute("cmd");
 | 
						|
      let controller =
 | 
						|
        document.commandDispatcher.getControllerForCommand(command);
 | 
						|
      if (controller.isCommandEnabled(command)) {
 | 
						|
        item.removeAttribute("disabled");
 | 
						|
      } else {
 | 
						|
        item.setAttribute("disabled", "true");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  handleContentContextMenuCommand(event) {
 | 
						|
    let id = event.target.getAttribute("id");
 | 
						|
    switch (id) {
 | 
						|
      case "syncedTabsOpenSelected":
 | 
						|
      case "syncedTabsOpenSelectedInTab":
 | 
						|
      case "syncedTabsOpenSelectedInWindow":
 | 
						|
      case "syncedTabsOpenSelectedInPrivateWindow":
 | 
						|
        this.onOpenSelectedFromContextMenu(event);
 | 
						|
        break;
 | 
						|
      case "syncedTabsOpenAllInTabs":
 | 
						|
        this.onOpenAllInTabs();
 | 
						|
        break;
 | 
						|
      case "syncedTabsBookmarkSelected":
 | 
						|
        this.onBookmarkTab();
 | 
						|
        break;
 | 
						|
      case "syncedTabsCopySelected":
 | 
						|
        this.onCopyTabLocation();
 | 
						|
        break;
 | 
						|
      case "syncedTabsRefresh":
 | 
						|
      case "syncedTabsRefreshFilter":
 | 
						|
        this.props.onSyncRefresh();
 | 
						|
        break;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  handleTabsFilterContextMenuCommand(event) {
 | 
						|
    let command = event.target.getAttribute("cmd");
 | 
						|
    let dispatcher = getChromeWindow(this._window).document.commandDispatcher;
 | 
						|
    let controller =
 | 
						|
      dispatcher.focusedElement.controllers.getControllerForCommand(command);
 | 
						|
    controller.doCommand(command);
 | 
						|
  },
 | 
						|
 | 
						|
  handleContextMenu(event) {
 | 
						|
    let menu;
 | 
						|
 | 
						|
    if (event.target == this.tabsFilter) {
 | 
						|
      menu = getTabsFilterContextMenu(this._window);
 | 
						|
    } else {
 | 
						|
      let itemNode = this._findParentItemNode(event.target);
 | 
						|
      if (itemNode) {
 | 
						|
        let position = this._getSelectionPosition(itemNode);
 | 
						|
        this.props.onSelectRow(position);
 | 
						|
      }
 | 
						|
      menu = getContextMenu(this._window);
 | 
						|
      this.adjustContextMenu(menu);
 | 
						|
    }
 | 
						|
 | 
						|
    menu.openPopupAtScreen(event.screenX, event.screenY, true, event);
 | 
						|
  },
 | 
						|
 | 
						|
  adjustContextMenu(menu) {
 | 
						|
    let item = this.container.querySelector(".item.selected");
 | 
						|
    let showTabOptions = this._isTab(item);
 | 
						|
 | 
						|
    let el = menu.firstElementChild;
 | 
						|
 | 
						|
    while (el) {
 | 
						|
      let show = false;
 | 
						|
      if (showTabOptions) {
 | 
						|
        if (el.getAttribute("id") == "syncedTabsOpenSelectedInPrivateWindow") {
 | 
						|
          show = lazy.PrivateBrowsingUtils.enabled;
 | 
						|
        } else if (
 | 
						|
          el.getAttribute("id") === "syncedTabsOpenSelectedInContainerTab"
 | 
						|
        ) {
 | 
						|
          show =
 | 
						|
            Services.prefs.getBoolPref("privacy.userContext.enabled", false) &&
 | 
						|
            !lazy.PrivateBrowsingUtils.isWindowPrivate(
 | 
						|
              getChromeWindow(this._window)
 | 
						|
            );
 | 
						|
        } else if (
 | 
						|
          el.getAttribute("id") != "syncedTabsOpenAllInTabs" &&
 | 
						|
          el.getAttribute("id") != "syncedTabsManageDevices"
 | 
						|
        ) {
 | 
						|
          show = true;
 | 
						|
        }
 | 
						|
      } else if (el.getAttribute("id") == "syncedTabsOpenAllInTabs") {
 | 
						|
        const tabs = item.querySelectorAll(".item-tabs-list > .item.tab");
 | 
						|
        show = !!tabs.length;
 | 
						|
      } else if (el.getAttribute("id") == "syncedTabsRefresh") {
 | 
						|
        show = true;
 | 
						|
      } else if (el.getAttribute("id") == "syncedTabsManageDevices") {
 | 
						|
        show = true;
 | 
						|
      }
 | 
						|
      el.hidden = !show;
 | 
						|
 | 
						|
      el = el.nextElementSibling;
 | 
						|
    }
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Find the parent item element, from a given child element.
 | 
						|
   * @param {Element} node - Child element.
 | 
						|
   * @return {Element} Element for the item, or null if not found.
 | 
						|
   */
 | 
						|
  _findParentItemNode(node) {
 | 
						|
    while (
 | 
						|
      node &&
 | 
						|
      node !== this.list &&
 | 
						|
      node !== this._doc.documentElement &&
 | 
						|
      !node.classList.contains("item")
 | 
						|
    ) {
 | 
						|
      node = node.parentNode;
 | 
						|
    }
 | 
						|
 | 
						|
    if (node !== this.list && node !== this._doc.documentElement) {
 | 
						|
      return node;
 | 
						|
    }
 | 
						|
 | 
						|
    return null;
 | 
						|
  },
 | 
						|
 | 
						|
  _findParentBranchNode(node) {
 | 
						|
    while (
 | 
						|
      node &&
 | 
						|
      !node.classList.contains("list") &&
 | 
						|
      node !== this._doc.documentElement &&
 | 
						|
      !node.parentNode.classList.contains("list")
 | 
						|
    ) {
 | 
						|
      node = node.parentNode;
 | 
						|
    }
 | 
						|
 | 
						|
    if (node !== this.list && node !== this._doc.documentElement) {
 | 
						|
      return node;
 | 
						|
    }
 | 
						|
 | 
						|
    return null;
 | 
						|
  },
 | 
						|
 | 
						|
  _getSelectionPosition(itemNode) {
 | 
						|
    let parent = this._findParentBranchNode(itemNode);
 | 
						|
    let parentPosition = this._indexOfNode(parent.parentNode, parent);
 | 
						|
    let childPosition = -1;
 | 
						|
    // if the node is not a client, find its position within the parent
 | 
						|
    if (parent !== itemNode) {
 | 
						|
      childPosition = this._indexOfNode(itemNode.parentNode, itemNode);
 | 
						|
    }
 | 
						|
    return [parentPosition, childPosition];
 | 
						|
  },
 | 
						|
 | 
						|
  _indexOfNode(parent, child) {
 | 
						|
    return Array.prototype.indexOf.call(parent.children, child);
 | 
						|
  },
 | 
						|
 | 
						|
  _isTab(item) {
 | 
						|
    return item && item.classList.contains("tab");
 | 
						|
  },
 | 
						|
 | 
						|
  _isClient(item) {
 | 
						|
    return item && item.classList.contains("client");
 | 
						|
  },
 | 
						|
 | 
						|
  _openAllClientTabs(clientNode, where) {
 | 
						|
    const tabs = clientNode.querySelector(".item-tabs-list").children;
 | 
						|
    const urls = [...tabs].map(tab => tab.dataset.url);
 | 
						|
    this.props.onOpenTabs(urls, where);
 | 
						|
  },
 | 
						|
};
 |