summaryrefslogtreecommitdiffstats
path: root/browser/components/syncedtabs/TabListView.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/syncedtabs/TabListView.js')
-rw-r--r--browser/components/syncedtabs/TabListView.js568
1 files changed, 0 insertions, 568 deletions
diff --git a/browser/components/syncedtabs/TabListView.js b/browser/components/syncedtabs/TabListView.js
deleted file mode 100644
index dab15101b..000000000
--- a/browser/components/syncedtabs/TabListView.js
+++ /dev/null
@@ -1,568 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
-
-Cu.import("resource://gre/modules/Services.jsm");
-
-let { getChromeWindow } = Cu.import("resource:///modules/syncedtabs/util.js", {});
-
-let log = Cu.import("resource://gre/modules/Log.jsm", {})
- .Log.repository.getLogger("Sync.RemoteTabs");
-
-this.EXPORTED_SYMBOLS = [
- "TabListView"
-];
-
-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.
- */
-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.clearFilter = this._doc.querySelector(".textbox-search-clear");
- this.searchBox = this._doc.querySelector(".search-box");
- this.searchIcon = this._doc.querySelector(".textbox-search-icon");
-
- 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.firstChild) {
- const firstTab = this.list.firstChild.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);
- });
- },
-
- _renderClient(client) {
- let itemNode = client.tabs.length ?
- this._createClient(client) :
- this._createEmptyClient(client);
-
- 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(item) {
- return this._doc.importNode(this._clientTemplate.content, true).firstElementChild;
- },
-
- _createEmptyClient(item) {
- return this._doc.importNode(this._emptyClientTemplate.content, true).firstElementChild;
- },
-
- _createTab(item) {
- return this._doc.importNode(this._tabTemplate.content, true).firstElementChild;
- },
-
- _clearChilden(node) {
- let parent = node || this.container;
- while (parent.firstChild) {
- parent.removeChild(parent.firstChild);
- }
- },
-
- // These listeners are attached only once, when we initialize the view
- _attachFixedListeners() {
- this.tabsFilter.addEventListener("input", this.onFilter.bind(this));
- this.tabsFilter.addEventListener("focus", this.onFilterFocus.bind(this));
- this.tabsFilter.addEventListener("blur", this.onFilterBlur.bind(this));
- this.clearFilter.addEventListener("click", this.onClearFilter.bind(this));
- this.searchIcon.addEventListener("click", this.onFilterFocus.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) {
- if (state.filter) {
- this.searchBox.classList.add("filtered");
- } else {
- this.searchBox.classList.remove("filtered");
- }
- this.tabsFilter.value = state.filter;
- if (state.inputFocused) {
- this.searchBox.setAttribute("focused", true);
- this.tabsFilter.focus();
- } else {
- this.searchBox.removeAttribute("focused");
- }
- },
-
- /**
- * 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);
- let lastSync = new Date(item.lastModified);
- let lastSyncTitle = getChromeWindow(this._window).gSyncUI.formatLastSyncDate(lastSync);
- itemNode.setAttribute("title", lastSyncTitle);
- 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.isMobile) {
- itemNode.classList.add("device-image-mobile");
- } else {
- itemNode.classList.add("device-image-desktop");
- }
- if (item.focused) {
- itemNode.focus();
- }
- 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") {
- const tabs = itemNode.querySelector(".item-tabs-list").childNodes;
- const urls = [...tabs].map(tab => tab.dataset.url);
- this.props.onOpenTabs(urls, 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);
- }
- },
-
- onFilter(event) {
- let query = event.target.value;
- if (query) {
- this.props.onFilter(query);
- } else {
- this.props.onClearFilter();
- }
- },
-
- onClearFilter() {
- 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;
- },
-
- // 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 "SyncedTabsSidebarTabsFilterContext":
- this.handleTabsFilterContextMenuCommand(event);
- break;
- }
- break;
- }
- }
- },
-
- handleTabsFilterContextMenuShown(event) {
- let document = event.target.ownerDocument;
- let focusedElement = document.commandDispatcher.focusedElement;
- if (focusedElement != this.tabsFilter) {
- 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 "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.firstChild;
-
- while (el) {
- if (showTabOptions || el.getAttribute("id") === "syncedTabsRefresh") {
- el.hidden = false;
- } else {
- el.hidden = true;
- }
-
- el = el.nextSibling;
- }
- },
-
- /**
- * 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.childNodes, child);
- },
-
- _isTab(item) {
- return item && item.classList.contains("tab");
- }
-};