diff options
Diffstat (limited to 'application/basilisk/components/syncedtabs')
12 files changed, 0 insertions, 1522 deletions
diff --git a/application/basilisk/components/syncedtabs/EventEmitter.jsm b/application/basilisk/components/syncedtabs/EventEmitter.jsm deleted file mode 100644 index ec3225f0f..000000000 --- a/application/basilisk/components/syncedtabs/EventEmitter.jsm +++ /dev/null @@ -1,45 +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; - -this.EXPORTED_SYMBOLS = [ - "EventEmitter" -]; - -// Simple event emitter abstraction for storage objects to use. -function EventEmitter () { - this._events = new Map(); -} - -EventEmitter.prototype = { - on(event, listener) { - if (this._events.has(event)) { - this._events.get(event).add(listener); - } else { - this._events.set(event, new Set([listener])); - } - }, - off(event, listener) { - if (!this._events.has(event)) { - return; - } - this._events.get(event).delete(listener); - }, - emit(event, ...args) { - if (!this._events.has(event)) { - return; - } - for (let listener of this._events.get(event).values()) { - try { - listener.apply(this, args); - } catch (e) { - Cu.reportError(e); - } - } - }, -}; - diff --git a/application/basilisk/components/syncedtabs/SyncedTabsDeckComponent.js b/application/basilisk/components/syncedtabs/SyncedTabsDeckComponent.js deleted file mode 100644 index c35277795..000000000 --- a/application/basilisk/components/syncedtabs/SyncedTabsDeckComponent.js +++ /dev/null @@ -1,169 +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/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); - -Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckStore.js"); -Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckView.js"); -Cu.import("resource:///modules/syncedtabs/SyncedTabsListStore.js"); -Cu.import("resource:///modules/syncedtabs/TabListComponent.js"); -Cu.import("resource:///modules/syncedtabs/TabListView.js"); -let { getChromeWindow } = Cu.import("resource:///modules/syncedtabs/util.js", {}); - -XPCOMUtils.defineLazyGetter(this, "FxAccountsCommon", function () { - return Components.utils.import("resource://gre/modules/FxAccountsCommon.js", {}); -}); - -let log = Cu.import("resource://gre/modules/Log.jsm", {}) - .Log.repository.getLogger("Sync.RemoteTabs"); - -this.EXPORTED_SYMBOLS = [ - "SyncedTabsDeckComponent" -]; - -/* SyncedTabsDeckComponent - * This component instantiates views and storage objects as well as defines - * behaviors that will be passed down to the views. This helps keep the views - * isolated and easier to test. - */ - -function SyncedTabsDeckComponent({ - window, SyncedTabs, fxAccounts, deckStore, listStore, listComponent, DeckView, getChromeWindowMock, -}) { - this._window = window; - this._SyncedTabs = SyncedTabs; - this._fxAccounts = fxAccounts; - this._DeckView = DeckView || SyncedTabsDeckView; - // used to stub during tests - this._getChromeWindow = getChromeWindowMock || getChromeWindow; - - this._deckStore = deckStore || new SyncedTabsDeckStore(); - this._syncedTabsListStore = listStore || new SyncedTabsListStore(SyncedTabs); - this.tabListComponent = listComponent || new TabListComponent({ - window: this._window, - store: this._syncedTabsListStore, - View: TabListView, - SyncedTabs: SyncedTabs, - clipboardHelper: Cc["@mozilla.org/widget/clipboardhelper;1"] - .getService(Ci.nsIClipboardHelper), - getChromeWindow: this._getChromeWindow, - }); -} - -SyncedTabsDeckComponent.prototype = { - PANELS: { - TABS_CONTAINER: "tabs-container", - TABS_FETCHING: "tabs-fetching", - NOT_AUTHED_INFO: "notAuthedInfo", - SINGLE_DEVICE_INFO: "singleDeviceInfo", - TABS_DISABLED: "tabs-disabled", - }, - - get container() { - return this._deckView ? this._deckView.container : null; - }, - - init() { - Services.obs.addObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED, false); - Services.obs.addObserver(this, FxAccountsCommon.ONLOGIN_NOTIFICATION, false); - - // Go ahead and trigger sync - this._SyncedTabs.syncTabs() - .catch(Cu.reportError); - - this._deckView = new this._DeckView(this._window, this.tabListComponent, { - onAndroidClick: event => this.openAndroidLink(event), - oniOSClick: event => this.openiOSLink(event), - onSyncPrefClick: event => this.openSyncPrefs(event) - }); - - this._deckStore.on("change", state => this._deckView.render(state)); - // Trigger the initial rendering of the deck view - // Object.values only in nightly - this._deckStore.setPanels(Object.keys(this.PANELS).map(k => this.PANELS[k])); - // Set the initial panel to display - this.updatePanel(); - }, - - uninit() { - Services.obs.removeObserver(this, this._SyncedTabs.TOPIC_TABS_CHANGED); - Services.obs.removeObserver(this, FxAccountsCommon.ONLOGIN_NOTIFICATION); - this._deckView.destroy(); - }, - - observe(subject, topic, data) { - switch (topic) { - case this._SyncedTabs.TOPIC_TABS_CHANGED: - this._syncedTabsListStore.getData(); - this.updatePanel(); - break; - case FxAccountsCommon.ONLOGIN_NOTIFICATION: - this.updatePanel(); - break; - default: - break; - } - }, - - // There's no good way to mock fxAccounts in browser tests where it's already - // been instantiated, so we have this method for stubbing. - _accountStatus() { - return this._fxAccounts.accountStatus(); - }, - - getPanelStatus() { - return this._accountStatus().then(exists => { - if (!exists) { - return this.PANELS.NOT_AUTHED_INFO; - } - if (!this._SyncedTabs.isConfiguredToSyncTabs) { - return this.PANELS.TABS_DISABLED; - } - if (!this._SyncedTabs.hasSyncedThisSession) { - return this.PANELS.TABS_FETCHING; - } - return this._SyncedTabs.getTabClients().then(clients => { - if (clients.length) { - return this.PANELS.TABS_CONTAINER; - } - return this.PANELS.SINGLE_DEVICE_INFO; - }); - }) - .catch(err => { - Cu.reportError(err); - return this.PANELS.NOT_AUTHED_INFO; - }); - }, - - updatePanel() { - // return promise for tests - return this.getPanelStatus() - .then(panelId => this._deckStore.selectPanel(panelId)) - .catch(Cu.reportError); - }, - - openAndroidLink(event) { - let href = Services.prefs.getCharPref("identity.mobilepromo.android") + "synced-tabs-sidebar"; - this._openUrl(href, event); - }, - - openiOSLink(event) { - let href = Services.prefs.getCharPref("identity.mobilepromo.ios") + "synced-tabs-sidebar"; - this._openUrl(href, event); - }, - - _openUrl(url, event) { - this._window.openUILink(url, event); - }, - - openSyncPrefs() { - this._getChromeWindow(this._window).gSyncUI.openSetup(null, "tabs-sidebar"); - } -}; - diff --git a/application/basilisk/components/syncedtabs/SyncedTabsDeckStore.js b/application/basilisk/components/syncedtabs/SyncedTabsDeckStore.js deleted file mode 100644 index ede6914c8..000000000 --- a/application/basilisk/components/syncedtabs/SyncedTabsDeckStore.js +++ /dev/null @@ -1,60 +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; - -let { EventEmitter } = Cu.import("resource:///modules/syncedtabs/EventEmitter.jsm", {}); - -this.EXPORTED_SYMBOLS = [ - "SyncedTabsDeckStore" -]; - -/** - * SyncedTabsDeckStore - * - * This store keeps track of the deck view state, including the panels and which - * one is selected. The view listens for change events on the store, which are - * triggered whenever the state changes. If it's a small change, the state - * will have `isUpdatable` set to true so the view can skip rerendering the whole - * DOM. - */ -function SyncedTabsDeckStore() { - EventEmitter.call(this); - this._panels = []; -} - -Object.assign(SyncedTabsDeckStore.prototype, EventEmitter.prototype, { - _change(isUpdatable = false) { - let panels = this._panels.map(panel => { - return {id: panel, selected: panel === this._selectedPanel}; - }); - this.emit("change", {panels, isUpdatable: isUpdatable}); - }, - - /** - * Sets the selected panelId and triggers a change event. - * @param {String} panelId - ID of the panel to select. - */ - selectPanel(panelId) { - if (this._panels.indexOf(panelId) === -1 || this._selectedPanel === panelId) { - return; - } - this._selectedPanel = panelId; - this._change(true); - }, - - /** - * Update the set of panels in the deck and trigger a change event. - * @param {Array} panels - an array of IDs for each panel in the deck. - */ - setPanels(panels) { - if (panels === this._panels) { - return; - } - this._panels = panels || []; - this._change(); - } -}); diff --git a/application/basilisk/components/syncedtabs/SyncedTabsDeckView.js b/application/basilisk/components/syncedtabs/SyncedTabsDeckView.js deleted file mode 100644 index e9efff323..000000000 --- a/application/basilisk/components/syncedtabs/SyncedTabsDeckView.js +++ /dev/null @@ -1,116 +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; - -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 = [ - "SyncedTabsDeckView" -]; - -/** - * SyncedTabsDeckView - * - * Instances of SyncedTabsDeckView render DOM nodes from a given state. - * No state is kept internaly and the DOM will completely - * rerender unless the state flags `isUpdatable`, which helps - * make small changes without the overhead of a full rerender. - */ -const SyncedTabsDeckView = function (window, tabListComponent, props) { - this.props = props; - - this._window = window; - this._doc = window.document; - - this._tabListComponent = tabListComponent; - this._deckTemplate = this._doc.getElementById("deck-template"); - this.container = this._doc.createElement("div"); -}; - -SyncedTabsDeckView.prototype = { - render(state) { - if (state.isUpdatable) { - this.update(state); - } else { - this.create(state); - } - }, - - create(state) { - let deck = this._doc.importNode(this._deckTemplate.content, true).firstElementChild; - this._clearChilden(); - - let tabListWrapper = this._doc.createElement("div"); - tabListWrapper.className = "tabs-container sync-state"; - this._tabListComponent.init(); - tabListWrapper.appendChild(this._tabListComponent.container); - deck.appendChild(tabListWrapper); - this.container.appendChild(deck); - - this._generateDevicePromo(); - - this._attachListeners(); - this.update(state); - }, - - _getBrowserBundle() { - return getChromeWindow(this._window).document.getElementById("bundle_browser"); - }, - - _generateDevicePromo() { - let bundle = this._getBrowserBundle(); - let formatArgs = ["android", "ios"].map(os => { - let link = this._doc.createElement("a"); - link.textContent = bundle.getString(`appMenuRemoteTabs.mobilePromo.${os}`); - link.className = `${os}-link text-link`; - link.setAttribute("href", "#"); - return link.outerHTML; - }); - // Put it all together... - let contents = bundle.getFormattedString("appMenuRemoteTabs.mobilePromo.text2", formatArgs); - this.container.querySelector(".device-promo").innerHTML = contents; - }, - - destroy() { - this._tabListComponent.uninit(); - this.container.remove(); - }, - - update(state) { - // Note that we may also want to update elements that are outside of the - // deck, so use the document to find the class names rather than our - // container. - for (let panel of state.panels) { - if (panel.selected) { - Array.prototype.map.call(this._doc.getElementsByClassName(panel.id), - item => item.classList.add("selected")); - } else { - Array.prototype.map.call(this._doc.getElementsByClassName(panel.id), - item => item.classList.remove("selected")); - } - } - }, - - _clearChilden() { - while (this.container.firstChild) { - this.container.removeChild(this.container.firstChild); - } - }, - - _attachListeners() { - this.container.querySelector(".android-link").addEventListener("click", this.props.onAndroidClick); - this.container.querySelector(".ios-link").addEventListener("click", this.props.oniOSClick); - let syncPrefLinks = this.container.querySelectorAll(".sync-prefs"); - for (let link of syncPrefLinks) { - link.addEventListener("click", this.props.onSyncPrefClick); - } - }, -}; - diff --git a/application/basilisk/components/syncedtabs/SyncedTabsListStore.js b/application/basilisk/components/syncedtabs/SyncedTabsListStore.js deleted file mode 100644 index 8f03d9a89..000000000 --- a/application/basilisk/components/syncedtabs/SyncedTabsListStore.js +++ /dev/null @@ -1,235 +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; - -let { EventEmitter } = Cu.import("resource:///modules/syncedtabs/EventEmitter.jsm", {}); - -this.EXPORTED_SYMBOLS = [ - "SyncedTabsListStore" -]; - -/** - * SyncedTabsListStore - * - * Instances of this store encapsulate all of the state associated with a synced tabs list view. - * The state includes the clients, their tabs, the row that is currently selected, - * and the filtered query. - */ -function SyncedTabsListStore(SyncedTabs) { - EventEmitter.call(this); - this._SyncedTabs = SyncedTabs; - this.data = []; - this._closedClients = {}; - this._selectedRow = [-1, -1]; - this.filter = ""; - this.inputFocused = false; -} - -Object.assign(SyncedTabsListStore.prototype, EventEmitter.prototype, { - // This internal method triggers the "change" event that views - // listen for. It denormalizes the state so that it's easier for - // the view to deal with. updateType hints to the view what - // actually needs to be rerendered or just updated, and can be - // empty (to (re)render everything), "searchbox" (to rerender just the tab list), - // or "all" (to skip rendering and just update all attributes of existing nodes). - _change(updateType) { - let selectedParent = this._selectedRow[0]; - let selectedChild = this._selectedRow[1]; - let rowSelected = false; - // clone the data so that consumers can't mutate internal storage - let data = Cu.cloneInto(this.data, {}); - let tabCount = 0; - - data.forEach((client, index) => { - client.closed = !!this._closedClients[client.id]; - - if (rowSelected || selectedParent < 0) { - return; - } - if (this.filter) { - if (selectedParent < tabCount + client.tabs.length) { - client.tabs[selectedParent - tabCount].selected = true; - client.tabs[selectedParent - tabCount].focused = !this.inputFocused; - rowSelected = true; - } else { - tabCount += client.tabs.length; - } - return; - } - if (selectedParent === index && selectedChild === -1) { - client.selected = true; - client.focused = !this.inputFocused; - rowSelected = true; - } else if (selectedParent === index) { - client.tabs[selectedChild].selected = true; - client.tabs[selectedChild].focused = !this.inputFocused; - rowSelected = true; - } - }); - - // If this were React the view would be smart enough - // to not re-render the whole list unless necessary. But it's - // not, so updateType is a hint to the view of what actually - // needs to be rerendered. - this.emit("change", { - clients: data, - canUpdateAll: updateType === "all", - canUpdateInput: updateType === "searchbox", - filter: this.filter, - inputFocused: this.inputFocused - }); - }, - - /** - * Moves the row selection from a child to its parent, - * which occurs when the parent of a selected row closes. - */ - _selectParentRow() { - this._selectedRow[1] = -1; - }, - - _toggleBranch(id, closed) { - this._closedClients[id] = closed; - if (this._closedClients[id]) { - this._selectParentRow(); - } - this._change("all"); - }, - - _isOpen(client) { - return !this._closedClients[client.id]; - }, - - moveSelectionDown() { - let branchRow = this._selectedRow[0]; - let childRow = this._selectedRow[1]; - let branch = this.data[branchRow]; - - if (this.filter) { - this.selectRow(branchRow + 1); - return; - } - - if (branchRow < 0) { - this.selectRow(0, -1); - } else if ((!branch.tabs.length || childRow >= branch.tabs.length - 1 || !this._isOpen(branch)) && branchRow < this.data.length) { - this.selectRow(branchRow + 1, -1); - } else if (childRow < branch.tabs.length) { - this.selectRow(branchRow, childRow + 1); - } - }, - - moveSelectionUp() { - let branchRow = this._selectedRow[0]; - let childRow = this._selectedRow[1]; - - if (this.filter) { - this.selectRow(branchRow - 1); - return; - } - - if (branchRow < 0) { - this.selectRow(0, -1); - } else if (childRow < 0 && branchRow > 0) { - let prevBranch = this.data[branchRow - 1]; - let newChildRow = this._isOpen(prevBranch) ? prevBranch.tabs.length - 1 : -1; - this.selectRow(branchRow - 1, newChildRow); - } else if (childRow >= 0) { - this.selectRow(branchRow, childRow - 1); - } - }, - - // Selects a row and makes sure the selection is within bounds - selectRow(parent, child) { - let maxParentRow = this.filter ? this._tabCount() : this.data.length; - let parentRow = parent; - if (parent <= -1) { - parentRow = 0; - } else if (parent >= maxParentRow) { - return; - } - - let childRow = child; - if (parentRow === -1 || this.filter || typeof child === "undefined" || child < -1) { - childRow = -1; - } else if (child >= this.data[parentRow].tabs.length) { - childRow = this.data[parentRow].tabs.length - 1; - } - - if (this._selectedRow[0] === parentRow && this._selectedRow[1] === childRow) { - return; - } - - this._selectedRow = [parentRow, childRow]; - this.inputFocused = false; - this._change("all"); - }, - - _tabCount() { - return this.data.reduce((prev, curr) => curr.tabs.length + prev, 0); - }, - - toggleBranch(id) { - this._toggleBranch(id, !this._closedClients[id]); - }, - - closeBranch(id) { - this._toggleBranch(id, true); - }, - - openBranch(id) { - this._toggleBranch(id, false); - }, - - focusInput() { - this.inputFocused = true; - // A change type of "all" updates rather than rebuilds, which is what we - // want here - only the selection/focus has changed. - this._change("all"); - }, - - blurInput() { - this.inputFocused = false; - // A change type of "all" updates rather than rebuilds, which is what we - // want here - only the selection/focus has changed. - this._change("all"); - }, - - clearFilter() { - this.filter = ""; - this._selectedRow = [-1, -1]; - return this.getData(); - }, - - // Fetches data from the SyncedTabs module and triggers - // and update - getData(filter) { - let updateType; - let hasFilter = typeof filter !== "undefined"; - if (hasFilter) { - this.filter = filter; - this._selectedRow = [-1, -1]; - - // When a filter is specified we tell the view that only the list - // needs to be rerendered so that it doesn't disrupt the input - // field's focus. - updateType = "searchbox"; - } - - // return promise for tests - return this._SyncedTabs.getTabClients(this.filter) - .then(result => { - if (!hasFilter) { - // Only sort clients and tabs if we're rendering the whole list. - this._SyncedTabs.sortTabClientsByLastUsed(result); - } - this.data = result; - this._change(updateType); - }) - .catch(Cu.reportError); - } -}); diff --git a/application/basilisk/components/syncedtabs/TabListComponent.js b/application/basilisk/components/syncedtabs/TabListComponent.js deleted file mode 100644 index aa60e4769..000000000 --- a/application/basilisk/components/syncedtabs/TabListComponent.js +++ /dev/null @@ -1,138 +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/XPCOMUtils.jsm"); - -let log = Cu.import("resource://gre/modules/Log.jsm", {}) - .Log.repository.getLogger("Sync.RemoteTabs"); - -XPCOMUtils.defineLazyModuleGetter(this, "PlacesUIUtils", - "resource:///modules/PlacesUIUtils.jsm"); - -this.EXPORTED_SYMBOLS = [ - "TabListComponent" -]; - -/** - * TabListComponent - * - * The purpose of this component is to compose the view, state, and actions. - * It defines high level actions that act on the state and passes them to the - * view for it to trigger during user interaction. It also subscribes the view - * to state changes so it can rerender. - */ - -function TabListComponent({window, store, View, SyncedTabs, clipboardHelper, - getChromeWindow}) { - this._window = window; - this._store = store; - this._View = View; - this._clipboardHelper = clipboardHelper; - this._getChromeWindow = getChromeWindow; - // used to trigger Sync from context menu - this._SyncedTabs = SyncedTabs; -} - -TabListComponent.prototype = { - get container() { - return this._view.container; - }, - - init() { - log.debug("Initializing TabListComponent"); - - this._view = new this._View(this._window, { - onSelectRow: (...args) => this.onSelectRow(...args), - onOpenTab: (...args) => this.onOpenTab(...args), - onOpenTabs: (...args) => this.onOpenTabs(...args), - onMoveSelectionDown: (...args) => this.onMoveSelectionDown(...args), - onMoveSelectionUp: (...args) => this.onMoveSelectionUp(...args), - onToggleBranch: (...args) => this.onToggleBranch(...args), - onBookmarkTab: (...args) => this.onBookmarkTab(...args), - onCopyTabLocation: (...args) => this.onCopyTabLocation(...args), - onSyncRefresh: (...args) => this.onSyncRefresh(...args), - onFilter: (...args) => this.onFilter(...args), - onClearFilter: (...args) => this.onClearFilter(...args), - onFilterFocus: (...args) => this.onFilterFocus(...args), - onFilterBlur: (...args) => this.onFilterBlur(...args) - }); - - this._store.on("change", state => this._view.render(state)); - this._view.render({clients: []}); - // get what's already available... - this._store.getData(); - this._store.focusInput(); - }, - - uninit() { - this._view.destroy(); - }, - - onFilter(query) { - this._store.getData(query); - }, - - onClearFilter() { - this._store.clearFilter(); - }, - - onFilterFocus() { - this._store.focusInput(); - }, - - onFilterBlur() { - this._store.blurInput(); - }, - - onSelectRow(position) { - this._store.selectRow(position[0], position[1]); - }, - - onMoveSelectionDown() { - this._store.moveSelectionDown(); - }, - - onMoveSelectionUp() { - this._store.moveSelectionUp(); - }, - - onToggleBranch(id) { - this._store.toggleBranch(id); - }, - - onBookmarkTab(uri, title) { - this._window.top.PlacesCommandHook - .bookmarkLink(this._window.top.PlacesUtils.bookmarksMenuFolderId, uri, title) - .catch(Cu.reportError); - }, - - onOpenTab(url, where, params) { - this._window.openUILinkIn(url, where, params); - }, - - onOpenTabs(urls, where) { - if (!PlacesUIUtils.confirmOpenInTabs(urls.length, this._window)) { - return; - } - if (where == "window") { - this._window.openDialog(this._window.getBrowserURL(), "_blank", - "chrome,dialog=no,all", urls.join("|")); - } else { - let loadInBackground = where == "tabshifted" ? true : false; - this._getChromeWindow(this._window).gBrowser.loadTabs(urls, loadInBackground, false); - } - }, - - onCopyTabLocation(url) { - this._clipboardHelper.copyString(url); - }, - - onSyncRefresh() { - this._SyncedTabs.syncTabs(true); - } -}; diff --git a/application/basilisk/components/syncedtabs/TabListView.js b/application/basilisk/components/syncedtabs/TabListView.js deleted file mode 100644 index dab15101b..000000000 --- a/application/basilisk/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"); - } -}; diff --git a/application/basilisk/components/syncedtabs/jar.mn b/application/basilisk/components/syncedtabs/jar.mn deleted file mode 100644 index ba2b105a1..000000000 --- a/application/basilisk/components/syncedtabs/jar.mn +++ /dev/null @@ -1,7 +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/. - -browser.jar: - content/browser/syncedtabs/sidebar.xhtml - content/browser/syncedtabs/sidebar.js diff --git a/application/basilisk/components/syncedtabs/moz.build b/application/basilisk/components/syncedtabs/moz.build deleted file mode 100644 index a6515d6a1..000000000 --- a/application/basilisk/components/syncedtabs/moz.build +++ /dev/null @@ -1,17 +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/. - -JAR_MANIFESTS += ['jar.mn'] - -EXTRA_JS_MODULES.syncedtabs += [ - 'EventEmitter.jsm', - 'SyncedTabsDeckComponent.js', - 'SyncedTabsDeckStore.js', - 'SyncedTabsDeckView.js', - 'SyncedTabsListStore.js', - 'TabListComponent.js', - 'TabListView.js', - 'util.js', -] - diff --git a/application/basilisk/components/syncedtabs/sidebar.js b/application/basilisk/components/syncedtabs/sidebar.js deleted file mode 100644 index 84df95e9d..000000000 --- a/application/basilisk/components/syncedtabs/sidebar.js +++ /dev/null @@ -1,30 +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/XPCOMUtils.jsm"); -Cu.import("resource://services-sync/SyncedTabs.jsm"); -Cu.import("resource:///modules/syncedtabs/SyncedTabsDeckComponent.js"); - -XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts", - "resource://gre/modules/FxAccounts.jsm"); - -this.syncedTabsDeckComponent = new SyncedTabsDeckComponent({window, SyncedTabs, fxAccounts}); - -let onLoaded = () => { - syncedTabsDeckComponent.init(); - document.getElementById("template-container").appendChild(syncedTabsDeckComponent.container); -}; - -let onUnloaded = () => { - removeEventListener("DOMContentLoaded", onLoaded); - removeEventListener("unload", onUnloaded); - syncedTabsDeckComponent.uninit(); -}; - -addEventListener("DOMContentLoaded", onLoaded); -addEventListener("unload", onUnloaded); diff --git a/application/basilisk/components/syncedtabs/sidebar.xhtml b/application/basilisk/components/syncedtabs/sidebar.xhtml deleted file mode 100644 index 3efcbea0e..000000000 --- a/application/basilisk/components/syncedtabs/sidebar.xhtml +++ /dev/null @@ -1,114 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<!-- 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/. --> - -<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" - "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" [ - <!ENTITY % browserDTD SYSTEM "chrome://browser/locale/browser.dtd"> - %browserDTD; - <!ENTITY % globalDTD - SYSTEM "chrome://global/locale/global.dtd"> - %globalDTD; - <!ENTITY % syncBrandDTD - SYSTEM "chrome://browser/locale/syncBrand.dtd"> - %syncBrandDTD; -]> -<html xmlns="http://www.w3.org/1999/xhtml" - xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> - <head> - <script src="chrome://browser/content/syncedtabs/sidebar.js" type="application/javascript;version=1.8"></script> - <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/> - - <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/skin/syncedtabs/sidebar.css"/> - <link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/"/> - <link rel="stylesheet" type="text/css" media="all" href="chrome://global/skin/textbox.css"/> - <link rel="stylesheet" type="text/css" media="all" href="chrome://browser/content/browser.css"/> - <title>&syncedTabs.sidebar.label;</title> - </head> - - <body dir="&locale.dir;" role="application"> - <template id="client-template"> - <div class="item client" role="option" tabindex="-1"> - <div class="item-title-container"> - <div class="item-twisty-container"></div> - <div class="item-icon-container"></div> - <p class="item-title"></p> - </div> - <div class="item-tabs-list"></div> - </div> - </template> - <template id="empty-client-template"> - <div class="item empty client" role="option" tabindex="-1"> - <div class="item-title-container"> - <div class="item-twisty-container"></div> - <div class="item-icon-container"></div> - <p class="item-title"></p> - </div> - <div class="item-tabs-list"> - <div class="item empty" role="option" tabindex="-1"> - <div class="item-title-container"> - <div class="item-icon-container"></div> - <p class="item-title">&syncedTabs.sidebar.notabs.label;</p> - </div> - </div> - </div> - </div> - </template> - <template id="tab-template"> - <div class="item tab" role="option" tabindex="-1"> - <div class="item-title-container"> - <div class="item-icon-container"></div> - <p class="item-title"></p> - </div> - </div> - </template> - - <template id="tabs-container-template"> - <div class="tabs-container"> - <div class="list" role="listbox"></div> - </div> - </template> - - <template id="deck-template"> - <div class="deck"> - <div class="tabs-fetching sync-state"> - <!-- Show intentionally blank panel, see bug 1239845 --> - </div> - <div class="notAuthedInfo sync-state"> - <p>&syncedTabs.sidebar.notsignedin.label;</p> - <p><a href="#" class="sync-prefs text-link">&fxaSignIn.label;</a></p> - </div> - <div class="singleDeviceInfo sync-state"> - <p>&syncedTabs.sidebar.noclients.title;</p> - <p>&syncedTabs.sidebar.noclients.subtitle;</p> - <p class="device-promo" fxAccountsBrand="&syncBrand.fxAccount.label;"></p> - </div> - <div class="tabs-disabled sync-state"> - <p>&syncedTabs.sidebar.tabsnotsyncing.label;</p> - <p><a href="#" class="sync-prefs text-link">&syncedTabs.sidebar.openprefs.label;</a></p> - </div> - </div> - </template> - - <div class="content-container"> - <!-- the non-scrollable header --> - <div class="content-header"> - <div class="sidebar-search-container tabs-container sync-state"> - <div class="search-box compact"> - <div class="textbox-input-box"> - <input type="text" class="tabsFilter textbox-input" tabindex="1"/> - <div class="textbox-search-icons"> - <a class="textbox-search-clear"></a> - <a class="textbox-search-icon"></a> - </div> - </div> - </div> - </div> - </div> - <!-- the scrollable content area where our templates are inserted --> - <div id="template-container" class="content-scrollable" tabindex="-1"> - </div> - </div> - </body> -</html> diff --git a/application/basilisk/components/syncedtabs/util.js b/application/basilisk/components/syncedtabs/util.js deleted file mode 100644 index e09a1a528..000000000 --- a/application/basilisk/components/syncedtabs/util.js +++ /dev/null @@ -1,23 +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; - -this.EXPORTED_SYMBOLS = [ - "getChromeWindow" -]; - -// Get the chrome (ie, browser) window hosting this content. -function getChromeWindow(window) { - return window - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIWebNavigation) - .QueryInterface(Ci.nsIDocShellTreeItem) - .rootTreeItem - .QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindow) - .wrappedJSObject; -} |