tab-firefox.js 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297
  1. /*This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. 'use strict';
  5. const { Trait } = require("../deprecated/traits");
  6. const { EventEmitter } = require("../deprecated/events");
  7. const { defer } = require("../lang/functional");
  8. const { has } = require("../util/array");
  9. const { EVENTS } = require("./events");
  10. const { getThumbnailURIForWindow } = require("../content/thumbnail");
  11. const { getFaviconURIForLocation } = require("../io/data");
  12. const { activateTab, getOwnerWindow, getBrowserForTab, getTabTitle, setTabTitle,
  13. getTabURL, setTabURL, getTabContentType, getTabId } = require('./utils');
  14. const { getOwnerWindow: getPBOwnerWindow } = require('../private-browsing/window/utils');
  15. const viewNS = require('../core/namespace').ns();
  16. const { deprecateUsage } = require('../util/deprecate');
  17. const { getURL } = require('../url/utils');
  18. const { viewFor } = require('../view/core');
  19. // Array of the inner instances of all the wrapped tabs.
  20. const TABS = [];
  21. /**
  22. * Trait used to create tab wrappers.
  23. */
  24. const TabTrait = Trait.compose(EventEmitter, {
  25. on: Trait.required,
  26. _emit: Trait.required,
  27. /**
  28. * Tab DOM element that is being wrapped.
  29. */
  30. _tab: null,
  31. /**
  32. * Window wrapper whose tab this object represents.
  33. */
  34. window: null,
  35. constructor: function Tab(options) {
  36. this._onReady = this._onReady.bind(this);
  37. this._onLoad = this._onLoad.bind(this);
  38. this._onPageShow = this._onPageShow.bind(this);
  39. this._tab = options.tab;
  40. // TODO: Remove this dependency
  41. let window = this.window = options.window || require('../windows').BrowserWindow({ window: getOwnerWindow(this._tab) });
  42. // Setting event listener if was passed.
  43. for each (let type in EVENTS) {
  44. let listener = options[type.listener];
  45. if (listener) {
  46. this.on(type.name, options[type.listener]);
  47. }
  48. // window spreads this event.
  49. if (!has(['ready', 'load', 'pageshow'], (type.name)))
  50. window.tabs.on(type.name, this._onEvent.bind(this, type.name));
  51. }
  52. this.on(EVENTS.close.name, this.destroy.bind(this));
  53. this._browser.addEventListener(EVENTS.ready.dom, this._onReady, true);
  54. this._browser.addEventListener(EVENTS.load.dom, this._onLoad, true);
  55. this._browser.addEventListener(EVENTS.pageshow.dom, this._onPageShow, true);
  56. if (options.isPinned)
  57. this.pin();
  58. viewNS(this._public).tab = this._tab;
  59. getPBOwnerWindow.implement(this._public, getChromeTab);
  60. viewFor.implement(this._public, getTabView);
  61. // Add tabs to getURL method
  62. getURL.implement(this._public, (function (obj) this._public.url).bind(this));
  63. // Since we will have to identify tabs by a DOM elements facade function
  64. // is used as constructor that collects all the instances and makes sure
  65. // that they more then one wrapper is not created per tab.
  66. return this;
  67. },
  68. destroy: function destroy() {
  69. this._removeAllListeners();
  70. if (this._tab) {
  71. let browser = this._browser;
  72. // The tab may already be removed from DOM -or- not yet added
  73. if (browser) {
  74. browser.removeEventListener(EVENTS.ready.dom, this._onReady, true);
  75. browser.removeEventListener(EVENTS.load.dom, this._onLoad, true);
  76. browser.removeEventListener(EVENTS.pageshow.dom, this._onPageShow, true);
  77. }
  78. this._tab = null;
  79. TABS.splice(TABS.indexOf(this), 1);
  80. }
  81. },
  82. /**
  83. * Internal listener that emits public event 'ready' when the page of this
  84. * tab is loaded, from DOMContentLoaded
  85. */
  86. _onReady: function _onReady(event) {
  87. // IFrames events will bubble so we need to ignore those.
  88. if (event.target == this._contentDocument)
  89. this._emit(EVENTS.ready.name, this._public);
  90. },
  91. /**
  92. * Internal listener that emits public event 'load' when the page of this
  93. * tab is loaded, for triggering on non-HTML content, bug #671305
  94. */
  95. _onLoad: function _onLoad(event) {
  96. // IFrames events will bubble so we need to ignore those.
  97. if (event.target == this._contentDocument) {
  98. this._emit(EVENTS.load.name, this._public);
  99. }
  100. },
  101. /**
  102. * Internal listener that emits public event 'pageshow' when the page of this
  103. * tab is loaded from cache, bug #671305
  104. */
  105. _onPageShow: function _onPageShow(event) {
  106. // IFrames events will bubble so we need to ignore those.
  107. if (event.target == this._contentDocument) {
  108. this._emit(EVENTS.pageshow.name, this._public, event.persisted);
  109. }
  110. },
  111. /**
  112. * Internal tab event router. Window will emit tab related events for all it's
  113. * tabs, this listener will propagate all the events for this tab to it's
  114. * listeners.
  115. */
  116. _onEvent: function _onEvent(type, tab) {
  117. if (viewNS(tab).tab == this._tab)
  118. this._emit(type, tab);
  119. },
  120. /**
  121. * Browser DOM element where page of this tab is currently loaded.
  122. */
  123. get _browser() getBrowserForTab(this._tab),
  124. /**
  125. * Window DOM element containing this tab.
  126. */
  127. get _window() getOwnerWindow(this._tab),
  128. /**
  129. * Document object of the page that is currently loaded in this tab.
  130. */
  131. get _contentDocument() this._browser.contentDocument,
  132. /**
  133. * Window object of the page that is currently loaded in this tab.
  134. */
  135. get _contentWindow() this._browser.contentWindow,
  136. /**
  137. * Unique id for the tab, actually maps to tab.linkedPanel but with some munging.
  138. */
  139. get id() this._tab ? getTabId(this._tab) : undefined,
  140. /**
  141. * The title of the page currently loaded in the tab.
  142. * Changing this property changes an actual title.
  143. * @type {String}
  144. */
  145. get title() this._tab ? getTabTitle(this._tab) : undefined,
  146. set title(title) this._tab && setTabTitle(this._tab, title),
  147. /**
  148. * Returns the MIME type that the document loaded in the tab is being
  149. * rendered as.
  150. * @type {String}
  151. */
  152. get contentType() this._tab ? getTabContentType(this._tab) : undefined,
  153. /**
  154. * Location of the page currently loaded in this tab.
  155. * Changing this property will loads page under under the specified location.
  156. * @type {String}
  157. */
  158. get url() this._tab ? getTabURL(this._tab) : undefined,
  159. set url(url) this._tab && setTabURL(this._tab, url),
  160. /**
  161. * URI of the favicon for the page currently loaded in this tab.
  162. * @type {String}
  163. */
  164. get favicon() {
  165. deprecateUsage(
  166. 'tab.favicon is deprecated, ' +
  167. 'please use require("sdk/places/favicon").getFavicon instead.'
  168. );
  169. return this._tab ? getFaviconURIForLocation(this.url) : undefined
  170. },
  171. /**
  172. * The CSS style for the tab
  173. */
  174. get style() null, // TODO
  175. /**
  176. * The index of the tab relative to other tabs in the application window.
  177. * Changing this property will change order of the actual position of the tab.
  178. * @type {Number}
  179. */
  180. get index()
  181. this._tab ?
  182. this._window.gBrowser.getBrowserIndexForDocument(this._contentDocument) :
  183. undefined,
  184. set index(value)
  185. this._tab && this._window.gBrowser.moveTabTo(this._tab, value),
  186. /**
  187. * Thumbnail data URI of the page currently loaded in this tab.
  188. * @type {String}
  189. */
  190. getThumbnail: function getThumbnail()
  191. this._tab ? getThumbnailURIForWindow(this._contentWindow) : undefined,
  192. /**
  193. * Whether or not tab is pinned (Is an app-tab).
  194. * @type {Boolean}
  195. */
  196. get isPinned() this._tab ? this._tab.pinned : undefined,
  197. pin: function pin() {
  198. if (!this._tab)
  199. return;
  200. this._window.gBrowser.pinTab(this._tab);
  201. },
  202. unpin: function unpin() {
  203. if (!this._tab)
  204. return;
  205. this._window.gBrowser.unpinTab(this._tab);
  206. },
  207. /**
  208. * Create a worker for this tab, first argument is options given to Worker.
  209. * @type {Worker}
  210. */
  211. attach: function attach(options) {
  212. if (!this._tab)
  213. return;
  214. // BUG 792946 https://bugzilla.mozilla.org/show_bug.cgi?id=792946
  215. // TODO: fix this circular dependency
  216. let { Worker } = require('./worker');
  217. return Worker(options, this._contentWindow);
  218. },
  219. /**
  220. * Make this tab active.
  221. * Please note: That this function is called asynchronous since in E10S that
  222. * will be the case. Besides this function is called from a constructor where
  223. * we would like to return instance before firing a 'TabActivated' event.
  224. */
  225. activate: defer(function activate() {
  226. if (!this._tab)
  227. return;
  228. activateTab(this._tab);
  229. }),
  230. /**
  231. * Close the tab
  232. */
  233. close: function close(callback) {
  234. // Bug 699450: the tab may already have been detached
  235. if (!this._tab || !this._tab.parentNode) {
  236. if (callback)
  237. callback();
  238. return;
  239. }
  240. if (callback)
  241. this.once(EVENTS.close.name, callback);
  242. this._window.gBrowser.removeTab(this._tab);
  243. },
  244. /**
  245. * Reload the tab
  246. */
  247. reload: function reload() {
  248. if (!this._tab)
  249. return;
  250. this._window.gBrowser.reloadTab(this._tab);
  251. }
  252. });
  253. function getChromeTab(tab) {
  254. return getOwnerWindow(viewNS(tab).tab);
  255. }
  256. // Implement `viewFor` polymorphic function for the Tab
  257. // instances.
  258. const getTabView = tab => viewNS(tab).tab;
  259. function Tab(options, existingOnly) {
  260. let chromeTab = options.tab;
  261. for each (let tab in TABS) {
  262. if (chromeTab == tab._tab)
  263. return tab._public;
  264. }
  265. // If called asked to return only existing wrapper,
  266. // we should return null here as no matching Tab object has been found
  267. if (existingOnly)
  268. return null;
  269. let tab = TabTrait(options);
  270. TABS.push(tab);
  271. return tab._public;
  272. }
  273. Tab.prototype = TabTrait.prototype;
  274. exports.Tab = Tab;