michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: 'use strict'; michael@0: michael@0: module.metadata = { michael@0: 'stability': 'unstable' michael@0: }; michael@0: michael@0: michael@0: // NOTE: This file should only deal with xul/native tabs michael@0: michael@0: michael@0: const { Ci } = require('chrome'); michael@0: const { defer } = require("../lang/functional"); michael@0: const { windows, isBrowser } = require('../window/utils'); michael@0: const { isPrivateBrowsingSupported } = require('../self'); michael@0: const { isGlobalPBSupported } = require('../private-browsing/utils'); michael@0: michael@0: // Bug 834961: ignore private windows when they are not supported michael@0: function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported }); michael@0: michael@0: const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; michael@0: michael@0: // Define predicate functions that can be used to detech weather michael@0: // we deal with fennec tabs or firefox tabs. michael@0: michael@0: // Predicate to detect whether tab is XUL "Tab" node. michael@0: const isXULTab = tab => michael@0: tab instanceof Ci.nsIDOMNode && michael@0: tab.nodeName === "tab" && michael@0: tab.namespaceURI === XUL_NS; michael@0: exports.isXULTab = isXULTab; michael@0: michael@0: // Predicate to detecet whether given tab is a fettec tab. michael@0: // Unfortunately we have to guess via duck typinng of: michael@0: // http://mxr.mozilla.org/mozilla-central/source/mobile/android/chrome/content/browser.js#2583 michael@0: const isFennecTab = tab => michael@0: tab && michael@0: tab.QueryInterface && michael@0: Ci.nsIBrowserTab && michael@0: tab.QueryInterface(Ci.nsIBrowserTab) === tab; michael@0: exports.isFennecTab = isFennecTab; michael@0: michael@0: const isTab = x => isXULTab(x) || isFennecTab(x); michael@0: exports.isTab = isTab; michael@0: michael@0: function activateTab(tab, window) { michael@0: let gBrowser = getTabBrowserForTab(tab); michael@0: michael@0: // normal case michael@0: if (gBrowser) { michael@0: gBrowser.selectedTab = tab; michael@0: } michael@0: // fennec ? michael@0: else if (window && window.BrowserApp) { michael@0: window.BrowserApp.selectTab(tab); michael@0: } michael@0: return null; michael@0: } michael@0: exports.activateTab = activateTab; michael@0: michael@0: function getTabBrowser(window) { michael@0: return window.gBrowser; michael@0: } michael@0: exports.getTabBrowser = getTabBrowser; michael@0: michael@0: function getTabContainer(window) { michael@0: return getTabBrowser(window).tabContainer; michael@0: } michael@0: exports.getTabContainer = getTabContainer; michael@0: michael@0: /** michael@0: * Returns the tabs for the `window` if given, or the tabs michael@0: * across all the browser's windows otherwise. michael@0: * michael@0: * @param {nsIWindow} [window] michael@0: * A reference to a window michael@0: * michael@0: * @returns {Array} an array of Tab objects michael@0: */ michael@0: function getTabs(window) { michael@0: if (arguments.length === 0) { michael@0: return getWindows().filter(isBrowser).reduce(function(tabs, window) { michael@0: return tabs.concat(getTabs(window)) michael@0: }, []); michael@0: } michael@0: michael@0: // fennec michael@0: if (window.BrowserApp) michael@0: return window.BrowserApp.tabs; michael@0: michael@0: // firefox - default michael@0: return Array.slice(getTabContainer(window).children); michael@0: } michael@0: exports.getTabs = getTabs; michael@0: michael@0: function getActiveTab(window) { michael@0: return getSelectedTab(window); michael@0: } michael@0: exports.getActiveTab = getActiveTab; michael@0: michael@0: function getOwnerWindow(tab) { michael@0: // normal case michael@0: if (tab.ownerDocument) michael@0: return tab.ownerDocument.defaultView; michael@0: michael@0: // try fennec case michael@0: return getWindowHoldingTab(tab); michael@0: } michael@0: exports.getOwnerWindow = getOwnerWindow; michael@0: michael@0: // fennec michael@0: function getWindowHoldingTab(rawTab) { michael@0: for each (let window in getWindows()) { michael@0: // this function may be called when not using fennec, michael@0: // but BrowserApp is only defined on Fennec michael@0: if (!window.BrowserApp) michael@0: continue; michael@0: michael@0: for each (let tab in window.BrowserApp.tabs) { michael@0: if (tab === rawTab) michael@0: return window; michael@0: } michael@0: } michael@0: michael@0: return null; michael@0: } michael@0: michael@0: function openTab(window, url, options) { michael@0: options = options || {}; michael@0: michael@0: // fennec? michael@0: if (window.BrowserApp) { michael@0: return window.BrowserApp.addTab(url, { michael@0: selected: options.inBackground ? false : true, michael@0: pinned: options.isPinned || false, michael@0: isPrivate: options.isPrivate || false michael@0: }); michael@0: } michael@0: michael@0: // firefox michael@0: let newTab = window.gBrowser.addTab(url); michael@0: if (!options.inBackground) { michael@0: activateTab(newTab); michael@0: } michael@0: return newTab; michael@0: }; michael@0: exports.openTab = openTab; michael@0: michael@0: function isTabOpen(tab) { michael@0: // try normal case then fennec case michael@0: return !!((tab.linkedBrowser) || getWindowHoldingTab(tab)); michael@0: } michael@0: exports.isTabOpen = isTabOpen; michael@0: michael@0: function closeTab(tab) { michael@0: let gBrowser = getTabBrowserForTab(tab); michael@0: // normal case? michael@0: if (gBrowser) { michael@0: // Bug 699450: the tab may already have been detached michael@0: if (!tab.parentNode) michael@0: return; michael@0: return gBrowser.removeTab(tab); michael@0: } michael@0: michael@0: let window = getWindowHoldingTab(tab); michael@0: // fennec? michael@0: if (window && window.BrowserApp) { michael@0: // Bug 699450: the tab may already have been detached michael@0: if (!tab.browser) michael@0: return; michael@0: return window.BrowserApp.closeTab(tab); michael@0: } michael@0: return null; michael@0: } michael@0: exports.closeTab = closeTab; michael@0: michael@0: function getURI(tab) { michael@0: if (tab.browser) // fennec michael@0: return tab.browser.currentURI.spec; michael@0: return tab.linkedBrowser.currentURI.spec; michael@0: } michael@0: exports.getURI = getURI; michael@0: michael@0: function getTabBrowserForTab(tab) { michael@0: let outerWin = getOwnerWindow(tab); michael@0: if (outerWin) michael@0: return getOwnerWindow(tab).gBrowser; michael@0: return null; michael@0: } michael@0: exports.getTabBrowserForTab = getTabBrowserForTab; michael@0: michael@0: function getBrowserForTab(tab) { michael@0: if (tab.browser) // fennec michael@0: return tab.browser; michael@0: michael@0: return tab.linkedBrowser; michael@0: } michael@0: exports.getBrowserForTab = getBrowserForTab; michael@0: michael@0: function getTabId(tab) { michael@0: if (tab.browser) // fennec michael@0: return tab.id michael@0: michael@0: return String.split(tab.linkedPanel, 'panel').pop(); michael@0: } michael@0: exports.getTabId = getTabId; michael@0: michael@0: function getTabForId(id) { michael@0: return getTabs().find(tab => getTabId(tab) === id) || null; michael@0: } michael@0: exports.getTabForId = getTabForId; michael@0: michael@0: function getTabTitle(tab) { michael@0: return getBrowserForTab(tab).contentDocument.title || tab.label || ""; michael@0: } michael@0: exports.getTabTitle = getTabTitle; michael@0: michael@0: function setTabTitle(tab, title) { michael@0: title = String(title); michael@0: if (tab.browser) michael@0: tab.browser.contentDocument.title = title; michael@0: tab.label = String(title); michael@0: } michael@0: exports.setTabTitle = setTabTitle; michael@0: michael@0: function getTabContentWindow(tab) { michael@0: return getBrowserForTab(tab).contentWindow; michael@0: } michael@0: exports.getTabContentWindow = getTabContentWindow; michael@0: michael@0: /** michael@0: * Returns all tabs' content windows across all the browsers' windows michael@0: */ michael@0: function getAllTabContentWindows() { michael@0: return getTabs().map(getTabContentWindow); michael@0: } michael@0: exports.getAllTabContentWindows = getAllTabContentWindows; michael@0: michael@0: // gets the tab containing the provided window michael@0: function getTabForContentWindow(window) { michael@0: // Retrieve the topmost frame container. It can be either , michael@0: // or . But in our case, it should be xul:browser. michael@0: let browser; michael@0: try { michael@0: browser = window.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIWebNavigation) michael@0: .QueryInterface(Ci.nsIDocShell) michael@0: .chromeEventHandler; michael@0: } catch(e) { michael@0: // Bug 699450: The tab may already have been detached so that `window` is michael@0: // in a almost destroyed state and can't be queryinterfaced anymore. michael@0: } michael@0: michael@0: // Is null for toplevel documents michael@0: if (!browser) { michael@0: return null; michael@0: } michael@0: michael@0: // Retrieve the owner window, should be browser.xul one michael@0: let chromeWindow = browser.ownerDocument.defaultView; michael@0: michael@0: // Ensure that it is top-level browser window. michael@0: // We need extra checks because of Mac hidden window that has a broken michael@0: // `gBrowser` global attribute. michael@0: if ('gBrowser' in chromeWindow && chromeWindow.gBrowser && michael@0: 'browsers' in chromeWindow.gBrowser) { michael@0: // Looks like we are on Firefox Desktop michael@0: // Then search for the position in tabbrowser in order to get the tab object michael@0: let browsers = chromeWindow.gBrowser.browsers; michael@0: let i = browsers.indexOf(browser); michael@0: if (i !== -1) michael@0: return chromeWindow.gBrowser.tabs[i]; michael@0: return null; michael@0: } michael@0: // Fennec michael@0: else if ('BrowserApp' in chromeWindow) { michael@0: return getTabForWindow(window); michael@0: } michael@0: michael@0: return null; michael@0: } michael@0: exports.getTabForContentWindow = getTabForContentWindow; michael@0: michael@0: // used on fennec michael@0: function getTabForWindow(window) { michael@0: for each (let { BrowserApp } in getWindows()) { michael@0: if (!BrowserApp) michael@0: continue; michael@0: michael@0: for each (let tab in BrowserApp.tabs) { michael@0: if (tab.browser.contentWindow == window.top) michael@0: return tab; michael@0: } michael@0: } michael@0: return null; michael@0: } michael@0: michael@0: function getTabURL(tab) { michael@0: if (tab.browser) // fennec michael@0: return String(tab.browser.currentURI.spec); michael@0: return String(getBrowserForTab(tab).currentURI.spec); michael@0: } michael@0: exports.getTabURL = getTabURL; michael@0: michael@0: function setTabURL(tab, url) { michael@0: url = String(url); michael@0: if (tab.browser) michael@0: return tab.browser.loadURI(url); michael@0: return getBrowserForTab(tab).loadURI(url); michael@0: } michael@0: // "TabOpen" event is fired when it's still "about:blank" is loaded in the michael@0: // changing `location` property of the `contentDocument` has no effect since michael@0: // seems to be either ignored or overridden by internal listener, there for michael@0: // location change is enqueued for the next turn of event loop. michael@0: exports.setTabURL = defer(setTabURL); michael@0: michael@0: function getTabContentType(tab) { michael@0: return getBrowserForTab(tab).contentDocument.contentType; michael@0: } michael@0: exports.getTabContentType = getTabContentType; michael@0: michael@0: function getSelectedTab(window) { michael@0: if (window.BrowserApp) // fennec? michael@0: return window.BrowserApp.selectedTab; michael@0: if (window.gBrowser) michael@0: return window.gBrowser.selectedTab; michael@0: return null; michael@0: } michael@0: exports.getSelectedTab = getSelectedTab; michael@0: michael@0: michael@0: function getTabForBrowser(browser) { michael@0: for each (let window in getWindows()) { michael@0: // this function may be called when not using fennec michael@0: if (!window.BrowserApp) michael@0: continue; michael@0: michael@0: for each (let tab in window.BrowserApp.tabs) { michael@0: if (tab.browser === browser) michael@0: return tab; michael@0: } michael@0: } michael@0: return null; michael@0: } michael@0: exports.getTabForBrowser = getTabForBrowser; michael@0: michael@0: function pin(tab) { michael@0: let gBrowser = getTabBrowserForTab(tab); michael@0: // TODO: Implement Fennec support michael@0: if (gBrowser) gBrowser.pinTab(tab); michael@0: } michael@0: exports.pin = pin; michael@0: michael@0: function unpin(tab) { michael@0: let gBrowser = getTabBrowserForTab(tab); michael@0: // TODO: Implement Fennec support michael@0: if (gBrowser) gBrowser.unpinTab(tab); michael@0: } michael@0: exports.unpin = unpin; michael@0: michael@0: function isPinned(tab) !!tab.pinned michael@0: exports.isPinned = isPinned; michael@0: michael@0: function reload(tab) { michael@0: let gBrowser = getTabBrowserForTab(tab); michael@0: // Firefox michael@0: if (gBrowser) gBrowser.unpinTab(tab); michael@0: // Fennec michael@0: else if (tab.browser) tab.browser.reload(); michael@0: } michael@0: exports.reload = reload michael@0: michael@0: function getIndex(tab) { michael@0: let gBrowser = getTabBrowserForTab(tab); michael@0: // Firefox michael@0: if (gBrowser) { michael@0: let document = getBrowserForTab(tab).contentDocument; michael@0: return gBrowser.getBrowserIndexForDocument(document); michael@0: } michael@0: // Fennec michael@0: else { michael@0: let window = getWindowHoldingTab(tab) michael@0: let tabs = window.BrowserApp.tabs; michael@0: for (let i = tabs.length; i >= 0; i--) michael@0: if (tabs[i] === tab) return i; michael@0: } michael@0: } michael@0: exports.getIndex = getIndex; michael@0: michael@0: function move(tab, index) { michael@0: let gBrowser = getTabBrowserForTab(tab); michael@0: // Firefox michael@0: if (gBrowser) gBrowser.moveTabTo(tab, index); michael@0: // TODO: Implement fennec support michael@0: } michael@0: exports.move = move;