|
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 |
|
5 let TabView = { |
|
6 _deck: null, |
|
7 _iframe: null, |
|
8 _window: null, |
|
9 _initialized: false, |
|
10 _browserKeyHandlerInitialized: false, |
|
11 _closedLastVisibleTabBeforeFrameInitialized: false, |
|
12 _isFrameLoading: false, |
|
13 _initFrameCallbacks: [], |
|
14 PREF_BRANCH: "browser.panorama.", |
|
15 PREF_FIRST_RUN: "browser.panorama.experienced_first_run", |
|
16 PREF_STARTUP_PAGE: "browser.startup.page", |
|
17 PREF_RESTORE_ENABLED_ONCE: "browser.panorama.session_restore_enabled_once", |
|
18 GROUPS_IDENTIFIER: "tabview-groups", |
|
19 VISIBILITY_IDENTIFIER: "tabview-visibility", |
|
20 |
|
21 // ---------- |
|
22 get windowTitle() { |
|
23 delete this.windowTitle; |
|
24 let brandBundle = document.getElementById("bundle_brand"); |
|
25 let brandShortName = brandBundle.getString("brandShortName"); |
|
26 let title = gNavigatorBundle.getFormattedString("tabview.title", [brandShortName]); |
|
27 return this.windowTitle = title; |
|
28 }, |
|
29 |
|
30 // ---------- |
|
31 get firstUseExperienced() { |
|
32 let pref = this.PREF_FIRST_RUN; |
|
33 if (Services.prefs.prefHasUserValue(pref)) |
|
34 return Services.prefs.getBoolPref(pref); |
|
35 |
|
36 return false; |
|
37 }, |
|
38 |
|
39 // ---------- |
|
40 set firstUseExperienced(val) { |
|
41 Services.prefs.setBoolPref(this.PREF_FIRST_RUN, val); |
|
42 }, |
|
43 |
|
44 // ---------- |
|
45 get sessionRestoreEnabledOnce() { |
|
46 let pref = this.PREF_RESTORE_ENABLED_ONCE; |
|
47 if (Services.prefs.prefHasUserValue(pref)) |
|
48 return Services.prefs.getBoolPref(pref); |
|
49 |
|
50 return false; |
|
51 }, |
|
52 |
|
53 // ---------- |
|
54 set sessionRestoreEnabledOnce(val) { |
|
55 Services.prefs.setBoolPref(this.PREF_RESTORE_ENABLED_ONCE, val); |
|
56 }, |
|
57 |
|
58 // ---------- |
|
59 init: function TabView_init() { |
|
60 // disable the ToggleTabView command for popup windows |
|
61 goSetCommandEnabled("Browser:ToggleTabView", window.toolbar.visible); |
|
62 if (!window.toolbar.visible) |
|
63 return; |
|
64 |
|
65 if (this._initialized) |
|
66 return; |
|
67 |
|
68 if (this.firstUseExperienced) { |
|
69 // ___ visibility |
|
70 |
|
71 let data = SessionStore.getWindowValue(window, this.VISIBILITY_IDENTIFIER); |
|
72 if (data && data == "true") { |
|
73 this.show(); |
|
74 } else { |
|
75 try { |
|
76 data = SessionStore.getWindowValue(window, this.GROUPS_IDENTIFIER); |
|
77 if (data) { |
|
78 let parsedData = JSON.parse(data); |
|
79 this.updateGroupNumberBroadcaster(parsedData.totalNumber || 1); |
|
80 } |
|
81 } catch (e) { } |
|
82 |
|
83 let self = this; |
|
84 // if a tab is changed from hidden to unhidden and the iframe is not |
|
85 // initialized, load the iframe and setup the tab. |
|
86 this._tabShowEventListener = function(event) { |
|
87 if (!self._window) |
|
88 self._initFrame(function() { |
|
89 self._window.UI.onTabSelect(gBrowser.selectedTab); |
|
90 if (self._closedLastVisibleTabBeforeFrameInitialized) { |
|
91 self._closedLastVisibleTabBeforeFrameInitialized = false; |
|
92 self._window.UI.showTabView(false); |
|
93 } |
|
94 }); |
|
95 }; |
|
96 this._tabCloseEventListener = function(event) { |
|
97 if (!self._window && gBrowser.visibleTabs.length == 0) |
|
98 self._closedLastVisibleTabBeforeFrameInitialized = true; |
|
99 }; |
|
100 gBrowser.tabContainer.addEventListener( |
|
101 "TabShow", this._tabShowEventListener, false); |
|
102 gBrowser.tabContainer.addEventListener( |
|
103 "TabClose", this._tabCloseEventListener, false); |
|
104 |
|
105 if (this._tabBrowserHasHiddenTabs()) { |
|
106 this._setBrowserKeyHandlers(); |
|
107 } else { |
|
108 // for restoring last session and undoing recently closed window |
|
109 this._SSWindowStateReadyListener = function (event) { |
|
110 if (this._tabBrowserHasHiddenTabs()) |
|
111 this._setBrowserKeyHandlers(); |
|
112 }.bind(this); |
|
113 window.addEventListener( |
|
114 "SSWindowStateReady", this._SSWindowStateReadyListener, false); |
|
115 } |
|
116 } |
|
117 } |
|
118 |
|
119 Services.prefs.addObserver(this.PREF_BRANCH, this, false); |
|
120 |
|
121 this._initialized = true; |
|
122 }, |
|
123 |
|
124 // ---------- |
|
125 // Observes topic changes. |
|
126 observe: function TabView_observe(subject, topic, data) { |
|
127 if (data == this.PREF_FIRST_RUN && this.firstUseExperienced) { |
|
128 this._addToolbarButton(); |
|
129 this.enableSessionRestore(); |
|
130 } |
|
131 }, |
|
132 |
|
133 // ---------- |
|
134 // Uninitializes TabView. |
|
135 uninit: function TabView_uninit() { |
|
136 if (!this._initialized) |
|
137 return; |
|
138 |
|
139 Services.prefs.removeObserver(this.PREF_BRANCH, this); |
|
140 |
|
141 if (this._tabShowEventListener) |
|
142 gBrowser.tabContainer.removeEventListener( |
|
143 "TabShow", this._tabShowEventListener, false); |
|
144 |
|
145 if (this._tabCloseEventListener) |
|
146 gBrowser.tabContainer.removeEventListener( |
|
147 "TabClose", this._tabCloseEventListener, false); |
|
148 |
|
149 if (this._SSWindowStateReadyListener) |
|
150 window.removeEventListener( |
|
151 "SSWindowStateReady", this._SSWindowStateReadyListener, false); |
|
152 |
|
153 this._initialized = false; |
|
154 |
|
155 if (this._window) { |
|
156 this._window = null; |
|
157 } |
|
158 |
|
159 if (this._iframe) { |
|
160 this._iframe.remove(); |
|
161 this._iframe = null; |
|
162 } |
|
163 }, |
|
164 |
|
165 // ---------- |
|
166 // Creates the frame and calls the callback once it's loaded. |
|
167 // If the frame already exists, calls the callback immediately. |
|
168 _initFrame: function TabView__initFrame(callback) { |
|
169 let hasCallback = typeof callback == "function"; |
|
170 |
|
171 // prevent frame to be initialized for popup windows |
|
172 if (!window.toolbar.visible) |
|
173 return; |
|
174 |
|
175 if (this._window) { |
|
176 if (hasCallback) |
|
177 callback(); |
|
178 return; |
|
179 } |
|
180 |
|
181 if (hasCallback) |
|
182 this._initFrameCallbacks.push(callback); |
|
183 |
|
184 if (this._isFrameLoading) |
|
185 return; |
|
186 |
|
187 this._isFrameLoading = true; |
|
188 |
|
189 TelemetryStopwatch.start("PANORAMA_INITIALIZATION_TIME_MS"); |
|
190 |
|
191 // ___ find the deck |
|
192 this._deck = document.getElementById("tab-view-deck"); |
|
193 |
|
194 // ___ create the frame |
|
195 this._iframe = document.createElement("iframe"); |
|
196 this._iframe.id = "tab-view"; |
|
197 this._iframe.setAttribute("transparent", "true"); |
|
198 this._iframe.setAttribute("tooltip", "tab-view-tooltip"); |
|
199 this._iframe.flex = 1; |
|
200 |
|
201 let self = this; |
|
202 |
|
203 window.addEventListener("tabviewframeinitialized", function onInit() { |
|
204 window.removeEventListener("tabviewframeinitialized", onInit, false); |
|
205 |
|
206 TelemetryStopwatch.finish("PANORAMA_INITIALIZATION_TIME_MS"); |
|
207 |
|
208 self._isFrameLoading = false; |
|
209 self._window = self._iframe.contentWindow; |
|
210 self._setBrowserKeyHandlers(); |
|
211 |
|
212 if (self._tabShowEventListener) { |
|
213 gBrowser.tabContainer.removeEventListener( |
|
214 "TabShow", self._tabShowEventListener, false); |
|
215 self._tabShowEventListener = null; |
|
216 } |
|
217 if (self._tabCloseEventListener) { |
|
218 gBrowser.tabContainer.removeEventListener( |
|
219 "TabClose", self._tabCloseEventListener, false); |
|
220 self._tabCloseEventListener = null; |
|
221 } |
|
222 if (self._SSWindowStateReadyListener) { |
|
223 window.removeEventListener( |
|
224 "SSWindowStateReady", self._SSWindowStateReadyListener, false); |
|
225 self._SSWindowStateReadyListener = null; |
|
226 } |
|
227 |
|
228 self._initFrameCallbacks.forEach(function (cb) cb()); |
|
229 self._initFrameCallbacks = []; |
|
230 }, false); |
|
231 |
|
232 this._iframe.setAttribute("src", "chrome://browser/content/tabview.html"); |
|
233 this._deck.appendChild(this._iframe); |
|
234 |
|
235 // ___ create tooltip |
|
236 let tooltip = document.createElement("tooltip"); |
|
237 tooltip.id = "tab-view-tooltip"; |
|
238 tooltip.setAttribute("onpopupshowing", "return TabView.fillInTooltip(document.tooltipNode);"); |
|
239 document.getElementById("mainPopupSet").appendChild(tooltip); |
|
240 }, |
|
241 |
|
242 // ---------- |
|
243 getContentWindow: function TabView_getContentWindow() { |
|
244 return this._window; |
|
245 }, |
|
246 |
|
247 // ---------- |
|
248 isVisible: function TabView_isVisible() { |
|
249 return (this._deck ? this._deck.selectedPanel == this._iframe : false); |
|
250 }, |
|
251 |
|
252 // ---------- |
|
253 show: function TabView_show() { |
|
254 if (this.isVisible()) |
|
255 return; |
|
256 |
|
257 let self = this; |
|
258 this._initFrame(function() { |
|
259 self._window.UI.showTabView(true); |
|
260 }); |
|
261 }, |
|
262 |
|
263 // ---------- |
|
264 hide: function TabView_hide() { |
|
265 if (this.isVisible() && this._window) { |
|
266 this._window.UI.exit(); |
|
267 } |
|
268 }, |
|
269 |
|
270 // ---------- |
|
271 toggle: function TabView_toggle() { |
|
272 if (this.isVisible()) |
|
273 this.hide(); |
|
274 else |
|
275 this.show(); |
|
276 }, |
|
277 |
|
278 // ---------- |
|
279 _tabBrowserHasHiddenTabs: function TabView_tabBrowserHasHiddenTabs() { |
|
280 return (gBrowser.tabs.length - gBrowser.visibleTabs.length) > 0; |
|
281 }, |
|
282 |
|
283 // ---------- |
|
284 updateContextMenu: function TabView_updateContextMenu(tab, popup) { |
|
285 let separator = document.getElementById("context_tabViewNamedGroups"); |
|
286 let isEmpty = true; |
|
287 |
|
288 while (popup.firstChild && popup.firstChild != separator) |
|
289 popup.removeChild(popup.firstChild); |
|
290 |
|
291 let self = this; |
|
292 this._initFrame(function() { |
|
293 let activeGroup = tab._tabViewTabItem.parent; |
|
294 let groupItems = self._window.GroupItems.groupItems; |
|
295 |
|
296 groupItems.forEach(function(groupItem) { |
|
297 // if group has title, it's not hidden and there is no active group or |
|
298 // the active group id doesn't match the group id, a group menu item |
|
299 // would be added. |
|
300 if (!groupItem.hidden && |
|
301 (groupItem.getTitle().trim() || groupItem.getChildren().length) && |
|
302 (!activeGroup || activeGroup.id != groupItem.id)) { |
|
303 let menuItem = self._createGroupMenuItem(groupItem); |
|
304 popup.insertBefore(menuItem, separator); |
|
305 isEmpty = false; |
|
306 } |
|
307 }); |
|
308 separator.hidden = isEmpty; |
|
309 }); |
|
310 }, |
|
311 |
|
312 // ---------- |
|
313 _createGroupMenuItem: function TabView__createGroupMenuItem(groupItem) { |
|
314 let menuItem = document.createElement("menuitem"); |
|
315 let title = groupItem.getTitle(); |
|
316 |
|
317 if (!title.trim()) { |
|
318 let topChildLabel = groupItem.getTopChild().tab.label; |
|
319 let childNum = groupItem.getChildren().length; |
|
320 |
|
321 if (childNum > 1) { |
|
322 let num = childNum - 1; |
|
323 title = |
|
324 gNavigatorBundle.getString("tabview.moveToUnnamedGroup.label"); |
|
325 title = PluralForm.get(num, title).replace("#1", topChildLabel).replace("#2", num); |
|
326 } else { |
|
327 title = topChildLabel; |
|
328 } |
|
329 } |
|
330 |
|
331 menuItem.setAttribute("label", title); |
|
332 menuItem.setAttribute("tooltiptext", title); |
|
333 menuItem.setAttribute("crop", "center"); |
|
334 menuItem.setAttribute("class", "tabview-menuitem"); |
|
335 menuItem.setAttribute( |
|
336 "oncommand", |
|
337 "TabView.moveTabTo(TabContextMenu.contextTab,'" + groupItem.id + "')"); |
|
338 |
|
339 return menuItem; |
|
340 }, |
|
341 |
|
342 // ---------- |
|
343 moveTabTo: function TabView_moveTabTo(tab, groupItemId) { |
|
344 if (this._window) { |
|
345 this._window.GroupItems.moveTabToGroupItem(tab, groupItemId); |
|
346 } else { |
|
347 let self = this; |
|
348 this._initFrame(function() { |
|
349 self._window.GroupItems.moveTabToGroupItem(tab, groupItemId); |
|
350 }); |
|
351 } |
|
352 }, |
|
353 |
|
354 // ---------- |
|
355 // Adds new key commands to the browser, for invoking the Tab Candy UI |
|
356 // and for switching between groups of tabs when outside of the Tab Candy UI. |
|
357 _setBrowserKeyHandlers: function TabView__setBrowserKeyHandlers() { |
|
358 if (this._browserKeyHandlerInitialized) |
|
359 return; |
|
360 |
|
361 this._browserKeyHandlerInitialized = true; |
|
362 |
|
363 let self = this; |
|
364 window.addEventListener("keypress", function(event) { |
|
365 if (self.isVisible() || !self._tabBrowserHasHiddenTabs()) |
|
366 return; |
|
367 |
|
368 let charCode = event.charCode; |
|
369 // Control (+ Shift) + ` |
|
370 if (event.ctrlKey && !event.metaKey && !event.altKey && |
|
371 (charCode == 96 || charCode == 126)) { |
|
372 event.stopPropagation(); |
|
373 event.preventDefault(); |
|
374 |
|
375 self._initFrame(function() { |
|
376 let groupItems = self._window.GroupItems; |
|
377 let tabItem = groupItems.getNextGroupItemTab(event.shiftKey); |
|
378 if (!tabItem) |
|
379 return; |
|
380 |
|
381 if (gBrowser.selectedTab.pinned) |
|
382 groupItems.updateActiveGroupItemAndTabBar(tabItem, {dontSetActiveTabInGroup: true}); |
|
383 else |
|
384 gBrowser.selectedTab = tabItem.tab; |
|
385 }); |
|
386 } |
|
387 }, true); |
|
388 }, |
|
389 |
|
390 // ---------- |
|
391 // Prepares the tab view for undo close tab. |
|
392 prepareUndoCloseTab: function TabView_prepareUndoCloseTab(blankTabToRemove) { |
|
393 if (this._window) { |
|
394 this._window.UI.restoredClosedTab = true; |
|
395 |
|
396 if (blankTabToRemove && blankTabToRemove._tabViewTabItem) |
|
397 blankTabToRemove._tabViewTabItem.isRemovedAfterRestore = true; |
|
398 } |
|
399 }, |
|
400 |
|
401 // ---------- |
|
402 // Cleans up the tab view after undo close tab. |
|
403 afterUndoCloseTab: function TabView_afterUndoCloseTab() { |
|
404 if (this._window) |
|
405 this._window.UI.restoredClosedTab = false; |
|
406 }, |
|
407 |
|
408 // ---------- |
|
409 // On move to group pop showing. |
|
410 moveToGroupPopupShowing: function TabView_moveToGroupPopupShowing(event) { |
|
411 // Update the context menu only if Panorama was already initialized or if |
|
412 // there are hidden tabs. |
|
413 let numHiddenTabs = gBrowser.tabs.length - gBrowser.visibleTabs.length; |
|
414 if (this._window || numHiddenTabs > 0) |
|
415 this.updateContextMenu(TabContextMenu.contextTab, event.target); |
|
416 }, |
|
417 |
|
418 // ---------- |
|
419 // Function: _addToolbarButton |
|
420 // Adds the TabView button to the TabsToolbar. |
|
421 _addToolbarButton: function TabView__addToolbarButton() { |
|
422 let buttonId = "tabview-button"; |
|
423 |
|
424 if (CustomizableUI.getPlacementOfWidget(buttonId)) |
|
425 return; |
|
426 |
|
427 let allTabsBtnPlacement = CustomizableUI.getPlacementOfWidget("alltabs-button"); |
|
428 // allTabsBtnPlacement can never be null because the button isn't removable |
|
429 let desiredPosition = allTabsBtnPlacement.position + 1; |
|
430 CustomizableUI.addWidgetToArea(buttonId, "TabsToolbar", desiredPosition); |
|
431 // NB: this is for backwards compatibility, and should be removed by |
|
432 // https://bugzilla.mozilla.org/show_bug.cgi?id=976041 |
|
433 document.persist("TabsToolbar", "currentset"); |
|
434 }, |
|
435 |
|
436 // ---------- |
|
437 // Function: updateGroupNumberBroadcaster |
|
438 // Updates the group number broadcaster. |
|
439 updateGroupNumberBroadcaster: function TabView_updateGroupNumberBroadcaster(number) { |
|
440 let groupsNumber = document.getElementById("tabviewGroupsNumber"); |
|
441 groupsNumber.setAttribute("groups", number); |
|
442 }, |
|
443 |
|
444 // ---------- |
|
445 // Function: enableSessionRestore |
|
446 // Enables automatic session restore when the browser is started. Does |
|
447 // nothing if we already did that once in the past. |
|
448 enableSessionRestore: function TabView_enableSessionRestore() { |
|
449 if (!this._window || !this.firstUseExperienced) |
|
450 return; |
|
451 |
|
452 // do nothing if we already enabled session restore once |
|
453 if (this.sessionRestoreEnabledOnce) |
|
454 return; |
|
455 |
|
456 this.sessionRestoreEnabledOnce = true; |
|
457 |
|
458 // enable session restore if necessary |
|
459 if (Services.prefs.getIntPref(this.PREF_STARTUP_PAGE) != 3) { |
|
460 Services.prefs.setIntPref(this.PREF_STARTUP_PAGE, 3); |
|
461 |
|
462 // show banner |
|
463 this._window.UI.notifySessionRestoreEnabled(); |
|
464 } |
|
465 }, |
|
466 |
|
467 // ---------- |
|
468 // Function: fillInTooltip |
|
469 // Fills in the tooltip text. |
|
470 fillInTooltip: function fillInTooltip(tipElement) { |
|
471 let retVal = false; |
|
472 let titleText = null; |
|
473 let direction = tipElement.ownerDocument.dir; |
|
474 |
|
475 while (!titleText && tipElement) { |
|
476 if (tipElement.nodeType == Node.ELEMENT_NODE) |
|
477 titleText = tipElement.getAttribute("title"); |
|
478 tipElement = tipElement.parentNode; |
|
479 } |
|
480 let tipNode = document.getElementById("tab-view-tooltip"); |
|
481 tipNode.style.direction = direction; |
|
482 |
|
483 if (titleText) { |
|
484 tipNode.setAttribute("label", titleText); |
|
485 retVal = true; |
|
486 } |
|
487 |
|
488 return retVal; |
|
489 } |
|
490 }; |