browser-tabPreviews.js
Go to the documentation of this file.
1 /*
2 #ifdef 0
3  * ***** BEGIN LICENSE BLOCK *****
4  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5  *
6  * The contents of this file are subject to the Mozilla Public License Version
7  * 1.1 (the "License"); you may not use this file except in compliance with
8  * the License. You may obtain a copy of the License at
9  * http://www.mozilla.org/MPL/
10  *
11  * Software distributed under the License is distributed on an "AS IS" basis,
12  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13  * for the specific language governing rights and limitations under the
14  * License.
15  *
16  * The Original Code is Tab Previews.
17  *
18  * The Initial Developer of the Original Code is Mozilla.
19  * Portions created by the Initial Developer are Copyright (C) 2008
20  * the Initial Developer. All Rights Reserved.
21  *
22  * Contributor(s):
23  * Dão Gottwald <dao@mozilla.com>
24  *
25  * Alternatively, the contents of this file may be used under the terms of
26  * either the GNU General Public License Version 2 or later (the "GPL"), or
27  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28  * in which case the provisions of the GPL or the LGPL are applicable instead
29  * of those above. If you wish to allow use of your version of this file only
30  * under the terms of either the GPL or the LGPL, and not to allow others to
31  * use your version of this file under the terms of the MPL, indicate your
32  * decision by deleting the provisions above and replace them with the notice
33  * and other provisions required by the GPL or the LGPL. If you do not delete
34  * the provisions above, a recipient may use your version of this file under
35  * the terms of any one of the MPL, the GPL or the LGPL.
36  *
37  * ***** END LICENSE BLOCK *****
38 #endif
39  */
40 
44 var tabPreviews = {
45  aspectRatio: 0.5625, // 16:9
46  init: function tabPreviews_init() {
47  this.width = Math.ceil(screen.availWidth / 5.75);
48  this.height = Math.round(this.width * this.aspectRatio);
49 
50  gBrowser.tabContainer.addEventListener("TabSelect", this, false);
51  gBrowser.tabContainer.addEventListener("SSTabRestored", this, false);
52  },
53  uninit: function tabPreviews_uninit() {
54  gBrowser.tabContainer.removeEventListener("TabSelect", this, false);
55  gBrowser.tabContainer.removeEventListener("SSTabRestored", this, false);
56  this._selectedTab = null;
57  },
58  get: function tabPreviews_get(aTab) {
59  if (aTab.__thumbnail_lastURI &&
60  aTab.__thumbnail_lastURI != aTab.linkedBrowser.currentURI.spec) {
61  aTab.__thumbnail = null;
62  aTab.__thumbnail_lastURI = null;
63  }
64  return aTab.__thumbnail || this.capture(aTab, !aTab.hasAttribute("busy"));
65  },
66  capture: function tabPreviews_capture(aTab, aStore) {
67  var thumbnail = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
68  thumbnail.mozOpaque = true;
69  thumbnail.height = this.height;
70  thumbnail.width = this.width;
71 
72  var ctx = thumbnail.getContext("2d");
73  var win = aTab.linkedBrowser.contentWindow;
74  var snippetWidth = win.innerWidth * .6;
75  var scale = this.width / snippetWidth;
76  ctx.scale(scale, scale);
77  ctx.drawWindow(win, win.scrollX, win.scrollY,
78  snippetWidth, snippetWidth * this.aspectRatio, "rgb(255,255,255)");
79 
80  if (aStore) {
81  aTab.__thumbnail = thumbnail;
82  aTab.__thumbnail_lastURI = aTab.linkedBrowser.currentURI.spec;
83  }
84  return thumbnail;
85  },
86  handleEvent: function tabPreviews_handleEvent(event) {
87  switch (event.type) {
88  case "TabSelect":
89  if (this._selectedTab &&
90  this._selectedTab.parentNode &&
91  !this._pendingUpdate) {
92  // Generate a thumbnail for the tab that was selected.
93  // The timeout keeps the UI snappy and prevents us from generating thumbnails
94  // for tabs that will be closed. During that timeout, don't generate other
95  // thumbnails in case multiple TabSelect events occur fast in succession.
96  this._pendingUpdate = true;
97  setTimeout(function (self, aTab) {
98  self._pendingUpdate = false;
99  if (aTab.parentNode && !aTab.hasAttribute("busy"))
100  self.capture(aTab, true);
101  }, 2000, this, this._selectedTab);
102  }
103  this._selectedTab = event.target;
104  break;
105  case "SSTabRestored":
106  this.capture(event.target, true);
107  break;
108  }
109  }
110 };
111 
113  opening: function (host) {
114  host.panel.hidden = false;
115 
116  var handler = this._generateHandler(host);
117  host.panel.addEventListener("popupshown", handler, false);
118  host.panel.addEventListener("popuphiding", handler, false);
119  host.panel.addEventListener("popuphidden", handler, false);
120 
121  host._prevFocus = document.commandDispatcher.focusedElement;
122  },
123  _generateHandler: function (host) {
124  var self = this;
125  return function (event) {
126  if (event.target == host.panel) {
127  host.panel.removeEventListener(event.type, arguments.callee, false);
128  self["_" + event.type](host);
129  }
130  };
131  },
132  _popupshown: function (host) {
133  if ("setupGUI" in host)
134  host.setupGUI();
135  },
136  _popuphiding: function (host) {
137  if ("suspendGUI" in host)
138  host.suspendGUI();
139 
140  if (host._prevFocus) {
141  Cc["@mozilla.org/focus-manager;1"]
142  .getService(Ci.nsIFocusManager)
143  .setFocus(host._prevFocus, Ci.nsIFocusManager.FLAG_NOSCROLL);
144  host._prevFocus = null;
145  } else
146  gBrowser.selectedBrowser.focus();
147 
148  if (host.tabToSelect) {
149  gBrowser.selectedTab = host.tabToSelect;
150  host.tabToSelect = null;
151  }
152  },
153  _popuphidden: function (host) {
154  // Destroy the widget in order to prevent outdated content
155  // when re-opening the panel.
156  host.panel.hidden = true;
157  }
158 };
159 
163 var ctrlTab = {
164  get panel () {
165  delete this.panel;
166  return this.panel = document.getElementById("ctrlTab-panel");
167  },
168  get showAllButton () {
169  delete this.showAllButton;
170  return this.showAllButton = document.getElementById("ctrlTab-showAll");
171  },
172  get previews () {
173  delete this.previews;
174  return this.previews = this.panel.getElementsByClassName("ctrlTab-preview");
175  },
176  get recentlyUsedLimit () {
177  delete this.recentlyUsedLimit;
178  return this.recentlyUsedLimit = gPrefService.getIntPref("browser.ctrlTab.recentlyUsedLimit");
179  },
180  get keys () {
181  var keys = {};
182  ["close", "find", "selectAll"].forEach(function (key) {
183  keys[key] = document.getElementById("key_" + key)
184  .getAttribute("key")
185  .toLocaleLowerCase().charCodeAt(0);
186  });
187  delete this.keys;
188  return this.keys = keys;
189  },
190  _selectedIndex: 0,
191  get selected () this._selectedIndex < 0 ?
192  document.activeElement :
193  this.previews.item(this._selectedIndex),
194  get isOpen () this.panel.state == "open" || this.panel.state == "showing" || this._timer,
195  get tabCount () this.tabList.length,
196  get tabPreviewCount () Math.min(this.previews.length - 1, this.tabCount),
197  get canvasWidth () Math.min(tabPreviews.width,
198  Math.ceil(screen.availWidth * .85 / this.tabPreviewCount)),
199  get canvasHeight () Math.round(this.canvasWidth * tabPreviews.aspectRatio),
200 
201  get tabList () {
202  if (this._tabList)
203  return this._tabList;
204 
205  var list = Array.slice(gBrowser.mTabs);
206 
207  if (this._closing)
208  this.detachTab(this._closing, list);
209 
210  for (let i = 0; i < gBrowser.tabContainer.selectedIndex; i++)
211  list.push(list.shift());
212 
213  if (this.recentlyUsedLimit != 0) {
214  let recentlyUsedTabs = this._recentlyUsedTabs;
215  if (this.recentlyUsedLimit > 0)
216  recentlyUsedTabs = this._recentlyUsedTabs.slice(0, this.recentlyUsedLimit);
217  for (let i = recentlyUsedTabs.length - 1; i >= 0; i--) {
218  list.splice(list.indexOf(recentlyUsedTabs[i]), 1);
219  list.unshift(recentlyUsedTabs[i]);
220  }
221  }
222 
223  return this._tabList = list;
224  },
225 
226  init: function ctrlTab_init() {
227  if (!this._recentlyUsedTabs) {
228  this._recentlyUsedTabs = [gBrowser.selectedTab];
229  this._init(true);
230  }
231  },
232 
233  uninit: function ctrlTab_uninit() {
234  this._recentlyUsedTabs = null;
235  this._init(false);
236  },
237 
238  prefName: "browser.ctrlTab.previews",
239  readPref: function ctrlTab_readPref() {
240  var enable =
241  gPrefService.getBoolPref(this.prefName) &&
242  (!gPrefService.prefHasUserValue("browser.ctrlTab.disallowForScreenReaders") ||
243  !gPrefService.getBoolPref("browser.ctrlTab.disallowForScreenReaders"));
244 
245  if (enable)
246  this.init();
247  else
248  this.uninit();
249  },
250  observe: function (aSubject, aTopic, aPrefName) {
251  this.readPref();
252  },
253 
254  updatePreviews: function ctrlTab_updatePreviews() {
255  for (let i = 0; i < this.previews.length; i++)
256  this.updatePreview(this.previews[i], this.tabList[i]);
257 
258  var showAllLabel = gNavigatorBundle.getString("ctrlTab.showAll.label");
259  this.showAllButton.label =
260  PluralForm.get(this.tabCount, showAllLabel).replace("#1", this.tabCount);
261  },
262 
263  updatePreview: function ctrlTab_updatePreview(aPreview, aTab) {
264  if (aPreview == this.showAllButton)
265  return;
266 
267  if ((aPreview._tab || null) != aTab) {
268  if (aPreview._tab)
269  aPreview._tab.removeEventListener("DOMAttrModified", this, false);
270  aPreview._tab = aTab;
271  if (aTab)
272  aTab.addEventListener("DOMAttrModified", this, false);
273  }
274 
275  if (aPreview.firstChild)
276  aPreview.removeChild(aPreview.firstChild);
277  if (aTab) {
278  let canvasWidth = this.canvasWidth;
279  let canvasHeight = this.canvasHeight;
280  aPreview.appendChild(tabPreviews.get(aTab));
281  aPreview.setAttribute("label", aTab.label);
282  aPreview.setAttribute("tooltiptext", aTab.label);
283  aPreview.setAttribute("crop", aTab.crop);
284  aPreview.setAttribute("canvaswidth", canvasWidth);
285  aPreview.setAttribute("canvasstyle",
286  "max-width:" + canvasWidth + "px;" +
287  "min-width:" + canvasWidth + "px;" +
288  "max-height:" + canvasHeight + "px;" +
289  "min-height:" + canvasHeight + "px;");
290  if (aTab.image)
291  aPreview.setAttribute("image", aTab.image);
292  else
293  aPreview.removeAttribute("image");
294  aPreview.hidden = false;
295  } else {
296  aPreview.hidden = true;
297  aPreview.removeAttribute("label");
298  aPreview.removeAttribute("tooltiptext");
299  aPreview.removeAttribute("image");
300  }
301  },
302 
303  advanceFocus: function ctrlTab_advanceFocus(aForward) {
304  if (this.panel.state == "open") {
305  if (aForward)
306  document.commandDispatcher.advanceFocus();
307  else
308  document.commandDispatcher.rewindFocus();
309  } else {
310  do {
311  this._selectedIndex += aForward ? 1 : -1;
312  if (this._selectedIndex < 0)
313  this._selectedIndex = this.previews.length - 1;
314  else if (this._selectedIndex >= this.previews.length)
315  this._selectedIndex = 0;
316  } while (this.selected.hidden);
317  }
318 
319  if (this._timer) {
320  clearTimeout(this._timer);
321  this._timer = null;
322  this._openPanel();
323  }
324  },
325 
326  _mouseOverFocus: function ctrlTab_mouseOverFocus(aPreview) {
327  if (this._trackMouseOver)
328  aPreview.focus();
329  },
330 
331  pick: function ctrlTab_pick(aPreview) {
332  if (!this.tabCount)
333  return;
334 
335  var select = (aPreview || this.selected);
336 
337  if (select == this.showAllButton)
338  this.showAllTabs();
339  else
340  this.close(select._tab);
341  },
342 
343  showAllTabs: function ctrlTab_showAllTabs(aPreview) {
344  this.close();
345  document.getElementById("Browser:ShowAllTabs").doCommand();
346  },
347 
348  remove: function ctrlTab_remove(aPreview) {
349  if (aPreview._tab)
350  gBrowser.removeTab(aPreview._tab);
351  },
352 
353  attachTab: function ctrlTab_attachTab(aTab, aPos) {
354  if (aPos == 0)
355  this._recentlyUsedTabs.unshift(aTab);
356  else if (aPos)
357  this._recentlyUsedTabs.splice(aPos, 0, aTab);
358  else
359  this._recentlyUsedTabs.push(aTab);
360  },
361  detachTab: function ctrlTab_detachTab(aTab, aTabs) {
362  var tabs = aTabs || this._recentlyUsedTabs;
363  var i = tabs.indexOf(aTab);
364  if (i >= 0)
365  tabs.splice(i, 1);
366  },
367 
368  open: function ctrlTab_open() {
369  if (this.isOpen)
370  return;
371 
372  allTabs.close();
373 
374  document.addEventListener("keyup", this, true);
375 
376  this.updatePreviews();
377  this._selectedIndex = 1;
378 
379  // Add a slight delay before showing the UI, so that a quick
380  // "ctrl-tab" keypress just flips back to the MRU tab.
381  this._timer = setTimeout(function (self) {
382  self._timer = null;
383  self._openPanel();
384  }, 200, this);
385  },
386 
387  _openPanel: function ctrlTab_openPanel() {
388  tabPreviewPanelHelper.opening(this);
389 
390  this.panel.width = Math.min(screen.availWidth * .99,
391  this.canvasWidth * 1.25 * this.tabPreviewCount);
392  var estimateHeight = this.canvasHeight * 1.25 + 75;
393  this.panel.openPopupAtScreen(screen.availLeft + (screen.availWidth - this.panel.width) / 2,
394  screen.availTop + (screen.availHeight - estimateHeight) / 2,
395  false);
396  },
397 
398  close: function ctrlTab_close(aTabToSelect) {
399  if (!this.isOpen)
400  return;
401 
402  if (this._timer) {
403  clearTimeout(this._timer);
404  this._timer = null;
405  this.suspendGUI();
406  if (aTabToSelect)
407  gBrowser.selectedTab = aTabToSelect;
408  return;
409  }
410 
411  this.tabToSelect = aTabToSelect;
412  this.panel.hidePopup();
413  },
414 
415  setupGUI: function ctrlTab_setupGUI() {
416  this.selected.focus();
417  this._selectedIndex = -1;
418 
419  // Track mouse movement after a brief delay so that the item that happens
420  // to be under the mouse pointer initially won't be selected unintentionally.
421  this._trackMouseOver = false;
422  setTimeout(function (self) {
423  if (self.isOpen)
424  self._trackMouseOver = true;
425  }, 0, this);
426  },
427 
428  suspendGUI: function ctrlTab_suspendGUI() {
429  document.removeEventListener("keyup", this, true);
430 
431  Array.forEach(this.previews, function (preview) {
432  this.updatePreview(preview, null);
433  }, this);
434 
435  this._tabList = null;
436  },
437 
438  onKeyPress: function ctrlTab_onKeyPress(event) {
439  var isOpen = this.isOpen;
440 
441  if (isOpen) {
442  event.preventDefault();
443  event.stopPropagation();
444  }
445 
446  switch (event.keyCode) {
447  case event.DOM_VK_TAB:
448  if (event.ctrlKey && !event.altKey && !event.metaKey) {
449  if (isOpen) {
450  this.advanceFocus(!event.shiftKey);
451  } else if (!event.shiftKey) {
452  event.preventDefault();
453  event.stopPropagation();
454  if (gBrowser.mTabs.length > 2) {
455  this.open();
456  } else if (gBrowser.mTabs.length == 2) {
457  gBrowser.selectedTab = gBrowser.selectedTab.nextSibling ||
458  gBrowser.selectedTab.previousSibling;
459  }
460  }
461  }
462  break;
463  default:
464  if (isOpen && event.ctrlKey) {
465  if (event.keyCode == event.DOM_VK_DELETE) {
466  this.remove(this.selected);
467  break;
468  }
469  switch (event.charCode) {
470  case this.keys.close:
471  this.remove(this.selected);
472  break;
473  case this.keys.find:
474  case this.keys.selectAll:
475  this.showAllTabs();
476  break;
477  }
478  }
479  }
480  },
481 
482  removeClosingTabFromUI: function ctrlTab_removeClosingTabFromUI(aTab) {
483  if (this.tabCount == 2) {
484  this.close();
485  return;
486  }
487 
488  this._closing = aTab;
489  this._tabList = null;
490  this.updatePreviews();
491  this._closing = null;
492 
493  if (this.selected.hidden)
494  this.advanceFocus(false);
495  if (this.selected == this.showAllButton)
496  this.advanceFocus(false);
497 
498  // If the current tab is removed, another tab can steal our focus.
499  if (aTab == gBrowser.selectedTab && this.panel.state == "open") {
500  setTimeout(function (selected) {
501  selected.focus();
502  }, 0, this.selected);
503  }
504  },
505 
506  handleEvent: function ctrlTab_handleEvent(event) {
507  switch (event.type) {
508  case "DOMAttrModified":
509  // tab attribute modified (e.g. label, crop, busy, image)
510  for (let i = this.previews.length - 1; i >= 0; i--) {
511  if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
512  this.updatePreview(this.previews[i], event.target);
513  break;
514  }
515  }
516  break;
517  case "TabSelect":
518  this.detachTab(event.target);
519  this.attachTab(event.target, 0);
520  break;
521  case "TabOpen":
522  this.attachTab(event.target, 1);
523  break;
524  case "TabClose":
525  this.detachTab(event.target);
526  if (this.isOpen)
527  this.removeClosingTabFromUI(event.target);
528  break;
529  case "keypress":
530  this.onKeyPress(event);
531  break;
532  case "keyup":
533  if (event.keyCode == event.DOM_VK_CONTROL)
534  this.pick();
535  break;
536  }
537  },
538 
539  _init: function ctrlTab__init(enable) {
540  var toggleEventListener = enable ? "addEventListener" : "removeEventListener";
541 
542  var tabContainer = gBrowser.tabContainer;
543  tabContainer[toggleEventListener]("TabOpen", this, false);
544  tabContainer[toggleEventListener]("TabSelect", this, false);
545  tabContainer[toggleEventListener]("TabClose", this, false);
546 
547  document[toggleEventListener]("keypress", this, false);
548  gBrowser.mTabBox.handleCtrlTab = !enable;
549 
550  // If we're not running, hide the "Show All Tabs" menu item,
551  // as Shift+Ctrl+Tab will be handled by the tab bar.
552  document.getElementById("menu_showAllTabs").hidden = !enable;
553  }
554 };
555 
556 
560 var allTabs = {
561  get panel () {
562  delete this.panel;
563  return this.panel = document.getElementById("allTabs-panel");
564  },
565  get filterField () {
566  delete this.filterField;
567  return this.filterField = document.getElementById("allTabs-filter");
568  },
569  get container () {
570  delete this.container;
571  return this.container = document.getElementById("allTabs-container");
572  },
573  get tabCloseButton () {
574  delete this.tabCloseButton;
575  return this.tabCloseButton = document.getElementById("allTabs-tab-close-button");
576  },
577  get _browserCommandSet () {
578  delete this._browserCommandSet;
579  return this._browserCommandSet = document.getElementById("mainCommandSet");
580  },
581  get isOpen () this.panel.state == "open" || this.panel.state == "showing",
582 
583  init: function allTabs_init() {
584  if (this._initiated)
585  return;
586  this._initiated = true;
587 
588  Array.forEach(gBrowser.mTabs, function (tab) {
589  this._addPreview(tab);
590  }, this);
591 
592  gBrowser.tabContainer.addEventListener("TabOpen", this, false);
593  gBrowser.tabContainer.addEventListener("TabMove", this, false);
594  gBrowser.tabContainer.addEventListener("TabClose", this, false);
595  },
596 
597  uninit: function allTabs_uninit() {
598  if (!this._initiated)
599  return;
600 
601  gBrowser.tabContainer.removeEventListener("TabOpen", this, false);
602  gBrowser.tabContainer.removeEventListener("TabMove", this, false);
603  gBrowser.tabContainer.removeEventListener("TabClose", this, false);
604 
605  while (this.container.hasChildNodes())
606  this.container.removeChild(this.container.firstChild);
607 
608  this._initiated = false;
609  },
610 
611  prefName: "browser.allTabs.previews",
612  readPref: function allTabs_readPref() {
613  var allTabsButton = document.getAnonymousElementByAttribute(
614  gBrowser.tabContainer, "anonid", "alltabs-button");
615  if (gPrefService.getBoolPref(this.prefName)) {
616  allTabsButton.removeAttribute("type");
617  allTabsButton.setAttribute("command", "Browser:ShowAllTabs");
618  } else {
619  allTabsButton.setAttribute("type", "menu");
620  allTabsButton.removeAttribute("command");
621  allTabsButton.removeAttribute("oncommand");
622  }
623  },
624  observe: function (aSubject, aTopic, aPrefName) {
625  this.readPref();
626  },
627 
628  pick: function allTabs_pick(aPreview) {
629  if (!aPreview)
630  aPreview = this._firstVisiblePreview;
631  if (aPreview)
632  this.tabToSelect = aPreview._tab;
633 
634  this.close();
635  },
636 
637  closeTab: function allTabs_closeTab(event) {
638  this.filterField.focus();
639  gBrowser.removeTab(event.currentTarget._targetPreview._tab);
640  },
641 
642  filter: function allTabs_filter() {
643  if (this._currentFilter == this.filterField.value)
644  return;
645 
646  this._currentFilter = this.filterField.value;
647 
648  var filter = this._currentFilter.split(/\s+/g);
649  this._visible = 0;
650  Array.forEach(this.container.childNodes, function (preview) {
651  var tab = preview._tab;
652  var matches = 0;
653  if (filter.length) {
654  let tabstring = tab.linkedBrowser.currentURI.spec;
655  try {
656  tabstring = decodeURI(tabstring);
657  } catch (e) {}
658  tabstring = tab.label + " " + tab.label.toLocaleLowerCase() + " " + tabstring;
659  for (let i = 0; i < filter.length; i++)
660  matches += tabstring.indexOf(filter[i]) > -1;
661  }
662  if (matches < filter.length) {
663  preview.hidden = true;
664  tab.removeEventListener("DOMAttrModified", this, false);
665  }
666  else {
667  this._visible++;
668  this._updatePreview(preview);
669  preview.hidden = false;
670  tab.addEventListener("DOMAttrModified", this, false);
671  }
672  }, this);
673 
674  this._reflow();
675  },
676 
677  open: function allTabs_open() {
678  this.init();
679 
680  if (this.isOpen)
681  return;
682 
683  this.filter();
684 
685  tabPreviewPanelHelper.opening(this);
686 
687  this.panel.popupBoxObject.setConsumeRollupEvent(Ci.nsIPopupBoxObject.ROLLUP_CONSUME);
688  var estimateHeight = (this._maxHeight + parseInt(this.container.maxHeight) + 50) / 2;
689  this.panel.openPopupAtScreen(screen.availLeft + (screen.availWidth - this._maxWidth) / 2,
690  screen.availTop + (screen.availHeight - estimateHeight) / 2,
691  false);
692  },
693 
694  close: function allTabs_close() {
695  this.panel.hidePopup();
696  },
697 
698  setupGUI: function allTabs_setupGUI() {
699  this.filterField.focus();
700  this.filterField.setAttribute("emptytext", this.filterField.tooltipText);
701 
702  this.panel.addEventListener("keypress", this, false);
703  this.panel.addEventListener("keypress", this, true);
704  this._browserCommandSet.addEventListener("command", this, false);
705  },
706 
707  suspendGUI: function allTabs_suspendGUI() {
708  Array.forEach(this.container.childNodes, function (preview) {
709  preview._tab.removeEventListener("DOMAttrModified", this, false);
710  }, this);
711 
712  this.filterField.removeAttribute("emptytext");
713  this.filterField.value = "";
714  this._currentFilter = null;
715 
716  this._updateTabCloseButton();
717 
718  this.panel.removeEventListener("keypress", this, false);
719  this.panel.removeEventListener("keypress", this, true);
720  this._browserCommandSet.removeEventListener("command", this, false);
721  },
722 
723  handleEvent: function allTabs_handleEvent(event) {
724  switch (event.type) {
725  case "DOMAttrModified":
726  // tab attribute modified (e.g. label, crop, busy, image)
727  this._updatePreview(this._getPreview(event.target));
728  break;
729  case "TabOpen":
730  if (this.isOpen)
731  this.close();
732  this._addPreview(event.target);
733  break;
734  case "TabMove":
735  if (event.target.nextSibling)
736  this.container.insertBefore(this._getPreview(event.target),
737  this._getPreview(event.target.nextSibling));
738  else
739  this.container.appendChild(this._getPreview(event.target));
740  break;
741  case "TabClose":
742  this._removePreview(this._getPreview(event.target));
743  break;
744  case "keypress":
745  this._onKeyPress(event);
746  break;
747  case "command":
748  if (event.target.id != "Browser:ShowAllTabs") {
749  // Close the panel when there's a browser command executing in the background.
750  this.close();
751  }
752  break;
753  }
754  },
755 
756  _visible: 0,
757  _currentFilter: null,
758  get _maxWidth () screen.availWidth * .9,
759  get _maxHeight () screen.availHeight * .75,
760  get _stack () {
761  delete this._stack;
762  return this._stack = document.getElementById("allTabs-stack");
763  },
764  get _previewLabelHeight () {
765  delete this._previewLabelHeight;
766  return this._previewLabelHeight = parseInt(getComputedStyle(this.container.firstChild, "").lineHeight);
767  },
768 
769  get _visiblePreviews ()
770  Array.filter(this.container.childNodes, function (preview) !preview.hidden),
771 
772  get _firstVisiblePreview () {
773  if (this._visible == 0)
774  return null;
775  var previews = this.container.childNodes;
776  for (let i = 0; i < previews.length; i++) {
777  if (!previews[i].hidden)
778  return previews[i];
779  }
780  return null;
781  },
782 
783  _reflow: function allTabs_reflow() {
784  this._updateTabCloseButton();
785 
786  // the size of the whole preview relative to the thumbnail
787  const REL_PREVIEW_THUMBNAIL = 1.2;
788 
789  var maxHeight = this._maxHeight;
790  var maxWidth = this._maxWidth;
791  var rel = tabPreviews.height / tabPreviews.width;
792  var rows, previewHeight, previewWidth, outerHeight;
793  var previewMaxWidth = tabPreviews.width * REL_PREVIEW_THUMBNAIL;
794  this._columns = Math.floor(maxWidth / previewMaxWidth);
795  do {
796  rows = Math.ceil(this._visible / this._columns);
797  previewWidth = Math.min(previewMaxWidth, Math.round(maxWidth / this._columns));
798  previewHeight = Math.round(previewWidth * rel);
799  outerHeight = previewHeight + this._previewLabelHeight;
800  } while (rows * outerHeight > maxHeight && ++this._columns);
801 
802  var outerWidth = previewWidth;
803  {
804  let innerWidth = Math.ceil(previewWidth / REL_PREVIEW_THUMBNAIL);
805  let innerHeight = Math.ceil(previewHeight / REL_PREVIEW_THUMBNAIL);
806  var canvasStyle = "max-width:" + innerWidth + "px;" +
807  "min-width:" + innerWidth + "px;" +
808  "max-height:" + innerHeight + "px;" +
809  "min-height:" + innerHeight + "px;";
810  }
811 
812  Array.forEach(this.container.childNodes, function (preview) {
813  preview.setAttribute("minwidth", outerWidth);
814  preview.setAttribute("height", outerHeight);
815  preview.setAttribute("canvasstyle", canvasStyle);
816  preview.removeAttribute("closebuttonhover");
817  }, this);
818 
819  this._stack.width = maxWidth;
820  this.container.width = Math.ceil(outerWidth * Math.min(this._columns, this._visible));
821  this.container.left = Math.round((maxWidth - this.container.width) / 2);
822  this.container.maxWidth = maxWidth - this.container.left;
823  this.container.maxHeight = rows * outerHeight;
824  },
825 
826  _addPreview: function allTabs_addPreview(aTab) {
827  var preview = document.createElement("button");
828  preview.className = "allTabs-preview";
829  preview._tab = aTab;
830  return this.container.appendChild(preview);
831  },
832 
833  _removePreview: function allTabs_removePreview(aPreview) {
834  var updateUI = (this.isOpen && !aPreview.hidden);
835  aPreview._tab.removeEventListener("DOMAttrModified", this, false);
836  aPreview._tab = null;
837  this.container.removeChild(aPreview);
838  if (updateUI) {
839  this._visible--;
840  this._reflow();
841  this.filterField.focus();
842  }
843  },
844 
845  _getPreview: function allTabs_getPreview(aTab) {
846  var previews = this.container.childNodes;
847  for (let i = 0; i < previews.length; i++)
848  if (previews[i]._tab == aTab)
849  return previews[i];
850  return null;
851  },
852 
853  _updateTabCloseButton: function allTabs_updateTabCloseButton(event) {
854  if (event && event.target == this.tabCloseButton)
855  return;
856 
857  if (this.tabCloseButton._targetPreview) {
858  if (event && event.target == this.tabCloseButton._targetPreview)
859  return;
860 
861  this.tabCloseButton._targetPreview.removeAttribute("closebuttonhover");
862  }
863 
864  if (event &&
865  event.target.parentNode == this.container &&
866  (event.target._tab.previousSibling || event.target._tab.nextSibling)) {
867  let preview = event.target.getBoundingClientRect();
868  let container = this.container.getBoundingClientRect();
869  let tabCloseButton = this.tabCloseButton.getBoundingClientRect();
870  let alignLeft = getComputedStyle(this.panel, "").direction == "rtl";
871 #ifdef XP_MACOSX
872  alignLeft = !alignLeft;
873 #endif
874  this.tabCloseButton.left = preview.left -
875  container.left +
876  parseInt(this.container.left) +
877  (alignLeft ? 0 :
878  preview.width - tabCloseButton.width);
879  this.tabCloseButton.top = preview.top - container.top;
880  this.tabCloseButton._targetPreview = event.target;
881  this.tabCloseButton.style.visibility = "visible";
882  event.target.setAttribute("closebuttonhover", "true");
883  } else {
884  this.tabCloseButton.style.visibility = "hidden";
885  this.tabCloseButton.left = this.tabCloseButton.top = 0;
886  this.tabCloseButton._targetPreview = null;
887  }
888  },
889 
890  _updatePreview: function allTabs_updatePreview(aPreview) {
891  aPreview.setAttribute("label", aPreview._tab.label);
892  aPreview.setAttribute("tooltiptext", aPreview._tab.label);
893  aPreview.setAttribute("crop", aPreview._tab.crop);
894  if (aPreview._tab.image)
895  aPreview.setAttribute("image", aPreview._tab.image);
896  else
897  aPreview.removeAttribute("image");
898 
899  var thumbnail = tabPreviews.get(aPreview._tab);
900  if (aPreview.firstChild) {
901  if (aPreview.firstChild == thumbnail)
902  return;
903  aPreview.removeChild(aPreview.firstChild);
904  }
905  aPreview.appendChild(thumbnail);
906  },
907 
908  _onKeyPress: function allTabs_onKeyPress(event) {
909  if (event.eventPhase == event.CAPTURING_PHASE) {
910  this._onCapturingKeyPress(event);
911  return;
912  }
913 
914  if (event.keyCode == event.DOM_VK_ESCAPE) {
915  this.close();
916  event.preventDefault();
917  event.stopPropagation();
918  return;
919  }
920 
921  if (event.target == this.filterField) {
922  switch (event.keyCode) {
923  case event.DOM_VK_UP:
924  if (this._visible) {
925  let previews = this._visiblePreviews;
926  let columns = Math.min(previews.length, this._columns);
927  previews[Math.floor(previews.length / columns) * columns - 1].focus();
928  event.preventDefault();
929  event.stopPropagation();
930  }
931  break;
932  case event.DOM_VK_DOWN:
933  if (this._visible) {
934  this._firstVisiblePreview.focus();
935  event.preventDefault();
936  event.stopPropagation();
937  }
938  break;
939  }
940  }
941  },
942 
943  _onCapturingKeyPress: function allTabs_onCapturingKeyPress(event) {
944  switch (event.keyCode) {
945  case event.DOM_VK_UP:
946  case event.DOM_VK_DOWN:
947  if (event.target != this.filterField)
948  this._advanceFocusVertically(event);
949  break;
950  case event.DOM_VK_RETURN:
951  if (event.target == this.filterField) {
952  this.filter();
953  this.pick();
954  event.preventDefault();
955  event.stopPropagation();
956  }
957  break;
958  }
959  },
960 
961  _advanceFocusVertically: function allTabs_advanceFocusVertically(event) {
962  var preview = document.activeElement;
963  if (!preview || preview.parentNode != this.container)
964  return;
965 
966  event.stopPropagation();
967 
968  var up = (event.keyCode == event.DOM_VK_UP);
969  var previews = this._visiblePreviews;
970 
971  if (up && preview == previews[0]) {
972  this.filterField.focus();
973  return;
974  }
975 
976  var i = previews.indexOf(preview);
977  var columns = Math.min(previews.length, this._columns);
978  var column = i % columns;
979  var row = Math.floor(i / columns);
980 
981  function newIndex() row * columns + column;
982  function outOfBounds() newIndex() >= previews.length;
983 
984  if (up) {
985  row--;
986  if (row < 0) {
987  let rows = Math.ceil(previews.length / columns);
988  row = rows - 1;
989  column--;
990  if (outOfBounds())
991  row--;
992  }
993  } else {
994  row++;
995  if (outOfBounds()) {
996  if (column == columns - 1) {
997  this.filterField.focus();
998  return;
999  }
1000  row = 0;
1001  column++;
1002  }
1003  }
1004  previews[newIndex()].focus();
1005  }
1006 };
this _columns
const Cc
_selectMonthYear select
var tabPreviewPanelHelper
var event
var tab
function width(ele) rect(ele).width
sbDeviceFirmwareAutoCheckForUpdate prototype _timer
_window init
Definition: FeedWriter.js:1144
var ctrlTab
BogusChannel prototype open
var tabs
aWindow setTimeout(function(){_this.restoreHistory(aWindow, aTabs, aTabData, aIdMap);}, 0)
this _document this
Definition: FeedWriter.js:1085
var columns
var tabContainer
return null
Definition: FeedWriter.js:1143
_updateDatepicker height
return!aWindow arguments!aWindow arguments[0]
var gPrefService
Definition: overlay.js:34
var tabPreviews
if(DEBUG_DATAREMOTES)
const Ci
_replaceLoadingTitle aTab
var gNavigatorBundle
var hidden
var allTabs
function uninit(aEvent)
Definition: aboutDialog.js:89
_getSelectedPageStyle s i
Array filter(tab.attributes, function(aAttr){return(_this.xulAttributes.indexOf(aAttr.name) >-1);}).forEach(tab.removeAttribute
GstMessage gpointer data sbGStreamerMessageHandler * handler
restoreHistoryPrecursor aTabs
sbDeviceFirmwareAutoCheckForUpdate prototype observe