|
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 |
|
6 module.metadata = { |
|
7 'stability': 'unstable' |
|
8 }; |
|
9 |
|
10 |
|
11 // NOTE: This file should only deal with xul/native tabs |
|
12 |
|
13 |
|
14 const { Ci } = require('chrome'); |
|
15 const { defer } = require("../lang/functional"); |
|
16 const { windows, isBrowser } = require('../window/utils'); |
|
17 const { isPrivateBrowsingSupported } = require('../self'); |
|
18 const { isGlobalPBSupported } = require('../private-browsing/utils'); |
|
19 |
|
20 // Bug 834961: ignore private windows when they are not supported |
|
21 function getWindows() windows(null, { includePrivate: isPrivateBrowsingSupported || isGlobalPBSupported }); |
|
22 |
|
23 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"; |
|
24 |
|
25 // Define predicate functions that can be used to detech weather |
|
26 // we deal with fennec tabs or firefox tabs. |
|
27 |
|
28 // Predicate to detect whether tab is XUL "Tab" node. |
|
29 const isXULTab = tab => |
|
30 tab instanceof Ci.nsIDOMNode && |
|
31 tab.nodeName === "tab" && |
|
32 tab.namespaceURI === XUL_NS; |
|
33 exports.isXULTab = isXULTab; |
|
34 |
|
35 // Predicate to detecet whether given tab is a fettec tab. |
|
36 // Unfortunately we have to guess via duck typinng of: |
|
37 // http://mxr.mozilla.org/mozilla-central/source/mobile/android/chrome/content/browser.js#2583 |
|
38 const isFennecTab = tab => |
|
39 tab && |
|
40 tab.QueryInterface && |
|
41 Ci.nsIBrowserTab && |
|
42 tab.QueryInterface(Ci.nsIBrowserTab) === tab; |
|
43 exports.isFennecTab = isFennecTab; |
|
44 |
|
45 const isTab = x => isXULTab(x) || isFennecTab(x); |
|
46 exports.isTab = isTab; |
|
47 |
|
48 function activateTab(tab, window) { |
|
49 let gBrowser = getTabBrowserForTab(tab); |
|
50 |
|
51 // normal case |
|
52 if (gBrowser) { |
|
53 gBrowser.selectedTab = tab; |
|
54 } |
|
55 // fennec ? |
|
56 else if (window && window.BrowserApp) { |
|
57 window.BrowserApp.selectTab(tab); |
|
58 } |
|
59 return null; |
|
60 } |
|
61 exports.activateTab = activateTab; |
|
62 |
|
63 function getTabBrowser(window) { |
|
64 return window.gBrowser; |
|
65 } |
|
66 exports.getTabBrowser = getTabBrowser; |
|
67 |
|
68 function getTabContainer(window) { |
|
69 return getTabBrowser(window).tabContainer; |
|
70 } |
|
71 exports.getTabContainer = getTabContainer; |
|
72 |
|
73 /** |
|
74 * Returns the tabs for the `window` if given, or the tabs |
|
75 * across all the browser's windows otherwise. |
|
76 * |
|
77 * @param {nsIWindow} [window] |
|
78 * A reference to a window |
|
79 * |
|
80 * @returns {Array} an array of Tab objects |
|
81 */ |
|
82 function getTabs(window) { |
|
83 if (arguments.length === 0) { |
|
84 return getWindows().filter(isBrowser).reduce(function(tabs, window) { |
|
85 return tabs.concat(getTabs(window)) |
|
86 }, []); |
|
87 } |
|
88 |
|
89 // fennec |
|
90 if (window.BrowserApp) |
|
91 return window.BrowserApp.tabs; |
|
92 |
|
93 // firefox - default |
|
94 return Array.slice(getTabContainer(window).children); |
|
95 } |
|
96 exports.getTabs = getTabs; |
|
97 |
|
98 function getActiveTab(window) { |
|
99 return getSelectedTab(window); |
|
100 } |
|
101 exports.getActiveTab = getActiveTab; |
|
102 |
|
103 function getOwnerWindow(tab) { |
|
104 // normal case |
|
105 if (tab.ownerDocument) |
|
106 return tab.ownerDocument.defaultView; |
|
107 |
|
108 // try fennec case |
|
109 return getWindowHoldingTab(tab); |
|
110 } |
|
111 exports.getOwnerWindow = getOwnerWindow; |
|
112 |
|
113 // fennec |
|
114 function getWindowHoldingTab(rawTab) { |
|
115 for each (let window in getWindows()) { |
|
116 // this function may be called when not using fennec, |
|
117 // but BrowserApp is only defined on Fennec |
|
118 if (!window.BrowserApp) |
|
119 continue; |
|
120 |
|
121 for each (let tab in window.BrowserApp.tabs) { |
|
122 if (tab === rawTab) |
|
123 return window; |
|
124 } |
|
125 } |
|
126 |
|
127 return null; |
|
128 } |
|
129 |
|
130 function openTab(window, url, options) { |
|
131 options = options || {}; |
|
132 |
|
133 // fennec? |
|
134 if (window.BrowserApp) { |
|
135 return window.BrowserApp.addTab(url, { |
|
136 selected: options.inBackground ? false : true, |
|
137 pinned: options.isPinned || false, |
|
138 isPrivate: options.isPrivate || false |
|
139 }); |
|
140 } |
|
141 |
|
142 // firefox |
|
143 let newTab = window.gBrowser.addTab(url); |
|
144 if (!options.inBackground) { |
|
145 activateTab(newTab); |
|
146 } |
|
147 return newTab; |
|
148 }; |
|
149 exports.openTab = openTab; |
|
150 |
|
151 function isTabOpen(tab) { |
|
152 // try normal case then fennec case |
|
153 return !!((tab.linkedBrowser) || getWindowHoldingTab(tab)); |
|
154 } |
|
155 exports.isTabOpen = isTabOpen; |
|
156 |
|
157 function closeTab(tab) { |
|
158 let gBrowser = getTabBrowserForTab(tab); |
|
159 // normal case? |
|
160 if (gBrowser) { |
|
161 // Bug 699450: the tab may already have been detached |
|
162 if (!tab.parentNode) |
|
163 return; |
|
164 return gBrowser.removeTab(tab); |
|
165 } |
|
166 |
|
167 let window = getWindowHoldingTab(tab); |
|
168 // fennec? |
|
169 if (window && window.BrowserApp) { |
|
170 // Bug 699450: the tab may already have been detached |
|
171 if (!tab.browser) |
|
172 return; |
|
173 return window.BrowserApp.closeTab(tab); |
|
174 } |
|
175 return null; |
|
176 } |
|
177 exports.closeTab = closeTab; |
|
178 |
|
179 function getURI(tab) { |
|
180 if (tab.browser) // fennec |
|
181 return tab.browser.currentURI.spec; |
|
182 return tab.linkedBrowser.currentURI.spec; |
|
183 } |
|
184 exports.getURI = getURI; |
|
185 |
|
186 function getTabBrowserForTab(tab) { |
|
187 let outerWin = getOwnerWindow(tab); |
|
188 if (outerWin) |
|
189 return getOwnerWindow(tab).gBrowser; |
|
190 return null; |
|
191 } |
|
192 exports.getTabBrowserForTab = getTabBrowserForTab; |
|
193 |
|
194 function getBrowserForTab(tab) { |
|
195 if (tab.browser) // fennec |
|
196 return tab.browser; |
|
197 |
|
198 return tab.linkedBrowser; |
|
199 } |
|
200 exports.getBrowserForTab = getBrowserForTab; |
|
201 |
|
202 function getTabId(tab) { |
|
203 if (tab.browser) // fennec |
|
204 return tab.id |
|
205 |
|
206 return String.split(tab.linkedPanel, 'panel').pop(); |
|
207 } |
|
208 exports.getTabId = getTabId; |
|
209 |
|
210 function getTabForId(id) { |
|
211 return getTabs().find(tab => getTabId(tab) === id) || null; |
|
212 } |
|
213 exports.getTabForId = getTabForId; |
|
214 |
|
215 function getTabTitle(tab) { |
|
216 return getBrowserForTab(tab).contentDocument.title || tab.label || ""; |
|
217 } |
|
218 exports.getTabTitle = getTabTitle; |
|
219 |
|
220 function setTabTitle(tab, title) { |
|
221 title = String(title); |
|
222 if (tab.browser) |
|
223 tab.browser.contentDocument.title = title; |
|
224 tab.label = String(title); |
|
225 } |
|
226 exports.setTabTitle = setTabTitle; |
|
227 |
|
228 function getTabContentWindow(tab) { |
|
229 return getBrowserForTab(tab).contentWindow; |
|
230 } |
|
231 exports.getTabContentWindow = getTabContentWindow; |
|
232 |
|
233 /** |
|
234 * Returns all tabs' content windows across all the browsers' windows |
|
235 */ |
|
236 function getAllTabContentWindows() { |
|
237 return getTabs().map(getTabContentWindow); |
|
238 } |
|
239 exports.getAllTabContentWindows = getAllTabContentWindows; |
|
240 |
|
241 // gets the tab containing the provided window |
|
242 function getTabForContentWindow(window) { |
|
243 // Retrieve the topmost frame container. It can be either <xul:browser>, |
|
244 // <xul:iframe/> or <html:iframe/>. But in our case, it should be xul:browser. |
|
245 let browser; |
|
246 try { |
|
247 browser = window.QueryInterface(Ci.nsIInterfaceRequestor) |
|
248 .getInterface(Ci.nsIWebNavigation) |
|
249 .QueryInterface(Ci.nsIDocShell) |
|
250 .chromeEventHandler; |
|
251 } catch(e) { |
|
252 // Bug 699450: The tab may already have been detached so that `window` is |
|
253 // in a almost destroyed state and can't be queryinterfaced anymore. |
|
254 } |
|
255 |
|
256 // Is null for toplevel documents |
|
257 if (!browser) { |
|
258 return null; |
|
259 } |
|
260 |
|
261 // Retrieve the owner window, should be browser.xul one |
|
262 let chromeWindow = browser.ownerDocument.defaultView; |
|
263 |
|
264 // Ensure that it is top-level browser window. |
|
265 // We need extra checks because of Mac hidden window that has a broken |
|
266 // `gBrowser` global attribute. |
|
267 if ('gBrowser' in chromeWindow && chromeWindow.gBrowser && |
|
268 'browsers' in chromeWindow.gBrowser) { |
|
269 // Looks like we are on Firefox Desktop |
|
270 // Then search for the position in tabbrowser in order to get the tab object |
|
271 let browsers = chromeWindow.gBrowser.browsers; |
|
272 let i = browsers.indexOf(browser); |
|
273 if (i !== -1) |
|
274 return chromeWindow.gBrowser.tabs[i]; |
|
275 return null; |
|
276 } |
|
277 // Fennec |
|
278 else if ('BrowserApp' in chromeWindow) { |
|
279 return getTabForWindow(window); |
|
280 } |
|
281 |
|
282 return null; |
|
283 } |
|
284 exports.getTabForContentWindow = getTabForContentWindow; |
|
285 |
|
286 // used on fennec |
|
287 function getTabForWindow(window) { |
|
288 for each (let { BrowserApp } in getWindows()) { |
|
289 if (!BrowserApp) |
|
290 continue; |
|
291 |
|
292 for each (let tab in BrowserApp.tabs) { |
|
293 if (tab.browser.contentWindow == window.top) |
|
294 return tab; |
|
295 } |
|
296 } |
|
297 return null; |
|
298 } |
|
299 |
|
300 function getTabURL(tab) { |
|
301 if (tab.browser) // fennec |
|
302 return String(tab.browser.currentURI.spec); |
|
303 return String(getBrowserForTab(tab).currentURI.spec); |
|
304 } |
|
305 exports.getTabURL = getTabURL; |
|
306 |
|
307 function setTabURL(tab, url) { |
|
308 url = String(url); |
|
309 if (tab.browser) |
|
310 return tab.browser.loadURI(url); |
|
311 return getBrowserForTab(tab).loadURI(url); |
|
312 } |
|
313 // "TabOpen" event is fired when it's still "about:blank" is loaded in the |
|
314 // changing `location` property of the `contentDocument` has no effect since |
|
315 // seems to be either ignored or overridden by internal listener, there for |
|
316 // location change is enqueued for the next turn of event loop. |
|
317 exports.setTabURL = defer(setTabURL); |
|
318 |
|
319 function getTabContentType(tab) { |
|
320 return getBrowserForTab(tab).contentDocument.contentType; |
|
321 } |
|
322 exports.getTabContentType = getTabContentType; |
|
323 |
|
324 function getSelectedTab(window) { |
|
325 if (window.BrowserApp) // fennec? |
|
326 return window.BrowserApp.selectedTab; |
|
327 if (window.gBrowser) |
|
328 return window.gBrowser.selectedTab; |
|
329 return null; |
|
330 } |
|
331 exports.getSelectedTab = getSelectedTab; |
|
332 |
|
333 |
|
334 function getTabForBrowser(browser) { |
|
335 for each (let window in getWindows()) { |
|
336 // this function may be called when not using fennec |
|
337 if (!window.BrowserApp) |
|
338 continue; |
|
339 |
|
340 for each (let tab in window.BrowserApp.tabs) { |
|
341 if (tab.browser === browser) |
|
342 return tab; |
|
343 } |
|
344 } |
|
345 return null; |
|
346 } |
|
347 exports.getTabForBrowser = getTabForBrowser; |
|
348 |
|
349 function pin(tab) { |
|
350 let gBrowser = getTabBrowserForTab(tab); |
|
351 // TODO: Implement Fennec support |
|
352 if (gBrowser) gBrowser.pinTab(tab); |
|
353 } |
|
354 exports.pin = pin; |
|
355 |
|
356 function unpin(tab) { |
|
357 let gBrowser = getTabBrowserForTab(tab); |
|
358 // TODO: Implement Fennec support |
|
359 if (gBrowser) gBrowser.unpinTab(tab); |
|
360 } |
|
361 exports.unpin = unpin; |
|
362 |
|
363 function isPinned(tab) !!tab.pinned |
|
364 exports.isPinned = isPinned; |
|
365 |
|
366 function reload(tab) { |
|
367 let gBrowser = getTabBrowserForTab(tab); |
|
368 // Firefox |
|
369 if (gBrowser) gBrowser.unpinTab(tab); |
|
370 // Fennec |
|
371 else if (tab.browser) tab.browser.reload(); |
|
372 } |
|
373 exports.reload = reload |
|
374 |
|
375 function getIndex(tab) { |
|
376 let gBrowser = getTabBrowserForTab(tab); |
|
377 // Firefox |
|
378 if (gBrowser) { |
|
379 let document = getBrowserForTab(tab).contentDocument; |
|
380 return gBrowser.getBrowserIndexForDocument(document); |
|
381 } |
|
382 // Fennec |
|
383 else { |
|
384 let window = getWindowHoldingTab(tab) |
|
385 let tabs = window.BrowserApp.tabs; |
|
386 for (let i = tabs.length; i >= 0; i--) |
|
387 if (tabs[i] === tab) return i; |
|
388 } |
|
389 } |
|
390 exports.getIndex = getIndex; |
|
391 |
|
392 function move(tab, index) { |
|
393 let gBrowser = getTabBrowserForTab(tab); |
|
394 // Firefox |
|
395 if (gBrowser) gBrowser.moveTabTo(tab, index); |
|
396 // TODO: Implement fennec support |
|
397 } |
|
398 exports.move = move; |