/* 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'; module.metadata = { 'stability': 'unstable' }; // NOTE: This file should only deal with xul/native tabs const { Ci, Cu } = require('chrome'); const { defer } = require("../lang/functional"); const { windows, isBrowser } = require('../window/utils'); const { isPrivateBrowsingSupported } = require('../self'); const { ShimWaiver } = Cu.import("resource://gre/modules/ShimWaiver.jsm"); // Bug 834961: ignore private windows when they are not supported function getWindows() { return windows(null, { includePrivate: isPrivateBrowsingSupported }); } const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; // Define predicate functions that can be used to detech weather // we deal with fennec tabs or firefox tabs. // Predicate to detect whether tab is XUL "Tab" node. const isXULTab = tab => tab instanceof Ci.nsIDOMNode && tab.nodeName === "tab" && tab.namespaceURI === XUL_NS; exports.isXULTab = isXULTab; // Predicate to detecet whether given tab is a fettec tab. // Unfortunately we have to guess via duck typinng of: // http://mxr.mozilla.org/mozilla-central/source/mobile/android/chrome/content/browser.js#2583 const isFennecTab = tab => tab && tab.QueryInterface && Ci.nsIBrowserTab && tab.QueryInterface(Ci.nsIBrowserTab) === tab; exports.isFennecTab = isFennecTab; const isTab = x => isXULTab(x) || isFennecTab(x); exports.isTab = isTab; function activateTab(tab, window) { let gBrowser = getTabBrowserForTab(tab); // normal case if (gBrowser) { gBrowser.selectedTab = tab; } // fennec ? else if (window && window.BrowserApp) { window.BrowserApp.selectTab(tab); } return null; } exports.activateTab = activateTab; function getTabBrowser(window) { // bug 1009938 - may be null in SeaMonkey return window.gBrowser || window.getBrowser(); } exports.getTabBrowser = getTabBrowser; function getTabContainer(window) { return getTabBrowser(window).tabContainer; } exports.getTabContainer = getTabContainer; /** * Returns the tabs for the `window` if given, or the tabs * across all the browser's windows otherwise. * * @param {nsIWindow} [window] * A reference to a window * * @returns {Array} an array of Tab objects */ function getTabs(window) { if (arguments.length === 0) { return getWindows(). filter(isBrowser). reduce((tabs, window) => tabs.concat(getTabs(window)), []); } // fennec if (window.BrowserApp) return window.BrowserApp.tabs; // firefox - default return Array.filter(getTabContainer(window).children, t => !t.closing); } exports.getTabs = getTabs; function getActiveTab(window) { return getSelectedTab(window); } exports.getActiveTab = getActiveTab; function getOwnerWindow(tab) { // normal case if (tab.ownerDocument) return tab.ownerDocument.defaultView; // try fennec case return getWindowHoldingTab(tab); } exports.getOwnerWindow = getOwnerWindow; // fennec function getWindowHoldingTab(rawTab) { for (let window of getWindows()) { // this function may be called when not using fennec, // but BrowserApp is only defined on Fennec if (!window.BrowserApp) continue; for (let tab of window.BrowserApp.tabs) { if (tab === rawTab) return window; } } return null; } function openTab(window, url, options) { options = options || {}; // fennec? if (window.BrowserApp) { return window.BrowserApp.addTab(url, { selected: options.inBackground ? false : true, pinned: options.isPinned || false, isPrivate: options.isPrivate || false, parentId: window.BrowserApp.selectedTab.id }); } // firefox let newTab = window.gBrowser.addTab(url); if (!options.inBackground) { activateTab(newTab); } return newTab; }; exports.openTab = openTab; function isTabOpen(tab) { // try normal case then fennec case return !!((tab.linkedBrowser) || getWindowHoldingTab(tab)); } exports.isTabOpen = isTabOpen; function closeTab(tab) { let gBrowser = getTabBrowserForTab(tab); // normal case? if (gBrowser) { // Bug 699450: the tab may already have been detached if (!tab.parentNode) return; return gBrowser.removeTab(tab); } let window = getWindowHoldingTab(tab); // fennec? if (window && window.BrowserApp) { // Bug 699450: the tab may already have been detached if (!tab.browser) return; return window.BrowserApp.closeTab(tab); } return null; } exports.closeTab = closeTab; function getURI(tab) { if (tab.browser) // fennec return tab.browser.currentURI.spec; return tab.linkedBrowser.currentURI.spec; } exports.getURI = getURI; function getTabBrowserForTab(tab) { let outerWin = getOwnerWindow(tab); if (outerWin) return getOwnerWindow(tab).gBrowser; return null; } exports.getTabBrowserForTab = getTabBrowserForTab; function getBrowserForTab(tab) { if (tab.browser) // fennec return tab.browser; return tab.linkedBrowser; } exports.getBrowserForTab = getBrowserForTab; function getTabId(tab) { if (tab.browser) // fennec return tab.id return String.split(tab.linkedPanel, 'panel').pop(); } exports.getTabId = getTabId; function getTabForId(id) { return getTabs().find(tab => getTabId(tab) === id) || null; } exports.getTabForId = getTabForId; function getTabTitle(tab) { return getBrowserForTab(tab).contentTitle || tab.label || ""; } exports.getTabTitle = getTabTitle; function setTabTitle(tab, title) { title = String(title); if (tab.browser) { // Fennec tab.browser.contentDocument.title = title; } else { let browser = getBrowserForTab(tab); // Note that we aren't actually setting the document title in e10s, just // the title the browser thinks the content has if (browser.isRemoteBrowser) browser._contentTitle = title; else browser.contentDocument.title = title; } tab.label = String(title); } exports.setTabTitle = setTabTitle; function getTabContentDocument(tab) { return getBrowserForTab(tab).contentDocument; } exports.getTabContentDocument = getTabContentDocument; function getTabContentWindow(tab) { return getBrowserForTab(tab).contentWindow; } exports.getTabContentWindow = getTabContentWindow; /** * Returns all tabs' content windows across all the browsers' windows */ function getAllTabContentWindows() { return getTabs().map(getTabContentWindow); } exports.getAllTabContentWindows = getAllTabContentWindows; // gets the tab containing the provided window function getTabForContentWindow(window) { return getTabs().find(tab => getTabContentWindow(tab) === window.top) || null; } exports.getTabForContentWindow = getTabForContentWindow; // only sdk/selection.js is relying on shims function getTabForContentWindowNoShim(window) { function getTabContentWindowNoShim(tab) { let browser = getBrowserForTab(tab); return ShimWaiver.getProperty(browser, "contentWindow"); } return getTabs().find(tab => getTabContentWindowNoShim(tab) === window.top) || null; } exports.getTabForContentWindowNoShim = getTabForContentWindowNoShim; function getTabURL(tab) { return String(getBrowserForTab(tab).currentURI.spec); } exports.getTabURL = getTabURL; function setTabURL(tab, url) { let browser = getBrowserForTab(tab); browser.loadURI(String(url)); } // "TabOpen" event is fired when it's still "about:blank" is loaded in the // changing `location` property of the `contentDocument` has no effect since // seems to be either ignored or overridden by internal listener, there for // location change is enqueued for the next turn of event loop. exports.setTabURL = defer(setTabURL); function getTabContentType(tab) { return getBrowserForTab(tab).contentDocument.contentType; } exports.getTabContentType = getTabContentType; function getSelectedTab(window) { if (window.BrowserApp) // fennec? return window.BrowserApp.selectedTab; if (window.gBrowser) return window.gBrowser.selectedTab; return null; } exports.getSelectedTab = getSelectedTab; function getTabForBrowser(browser) { for (let window of getWindows()) { // this function may be called when not using fennec if (!window.BrowserApp) continue; for (let tab of window.BrowserApp.tabs) { if (tab.browser === browser) return tab; } } let tabbrowser = browser.getTabBrowser && browser.getTabBrowser() return !!tabbrowser && tabbrowser.getTabForBrowser(browser); } exports.getTabForBrowser = getTabForBrowser; function pin(tab) { let gBrowser = getTabBrowserForTab(tab); // TODO: Implement Fennec support if (gBrowser) gBrowser.pinTab(tab); } exports.pin = pin; function unpin(tab) { let gBrowser = getTabBrowserForTab(tab); // TODO: Implement Fennec support if (gBrowser) gBrowser.unpinTab(tab); } exports.unpin = unpin; function isPinned(tab) { return !!tab.pinned; } exports.isPinned = isPinned; function reload(tab) { getBrowserForTab(tab).reload(); } exports.reload = reload function getIndex(tab) { let gBrowser = getTabBrowserForTab(tab); // Firefox if (gBrowser) { return tab._tPos; } // Fennec else { let window = getWindowHoldingTab(tab) let tabs = window.BrowserApp.tabs; for (let i = tabs.length; i >= 0; i--) if (tabs[i] === tab) return i; } } exports.getIndex = getIndex; function move(tab, index) { let gBrowser = getTabBrowserForTab(tab); // Firefox if (gBrowser) gBrowser.moveTabTo(tab, index); // TODO: Implement fennec support } exports.move = move;