browser/base/content/browser-tabPreviews.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /*
     2 #ifdef 0
     3  * This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
     6 #endif
     7  */
     9 /**
    10  * Tab previews utility, produces thumbnails
    11  */
    12 var tabPreviews = {
    13   aspectRatio: 0.5625, // 16:9
    15   get width() {
    16     delete this.width;
    17     return this.width = Math.ceil(screen.availWidth / 5.75);
    18   },
    20   get height() {
    21     delete this.height;
    22     return this.height = Math.round(this.width * this.aspectRatio);
    23   },
    25   init: function tabPreviews_init() {
    26     if (this._selectedTab)
    27       return;
    28     this._selectedTab = gBrowser.selectedTab;
    30     gBrowser.tabContainer.addEventListener("TabSelect", this, false);
    31     gBrowser.tabContainer.addEventListener("SSTabRestored", this, false);
    32   },
    34   get: function tabPreviews_get(aTab) {
    35     let uri = aTab.linkedBrowser.currentURI.spec;
    37     if (aTab.__thumbnail_lastURI &&
    38         aTab.__thumbnail_lastURI != uri) {
    39       aTab.__thumbnail = null;
    40       aTab.__thumbnail_lastURI = null;
    41     }
    43     if (aTab.__thumbnail)
    44       return aTab.__thumbnail;
    46     if (aTab.getAttribute("pending") == "true") {
    47       let img = new Image;
    48       img.src = PageThumbs.getThumbnailURL(uri);
    49       return img;
    50     }
    52     return this.capture(aTab, !aTab.hasAttribute("busy"));
    53   },
    55   capture: function tabPreviews_capture(aTab, aStore) {
    56     var thumbnail = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
    57     thumbnail.mozOpaque = true;
    58     thumbnail.height = this.height;
    59     thumbnail.width = this.width;
    61     var ctx = thumbnail.getContext("2d");
    62     var win = aTab.linkedBrowser.contentWindow;
    63     var snippetWidth = win.innerWidth * .6;
    64     var scale = this.width / snippetWidth;
    65     ctx.scale(scale, scale);
    66     ctx.drawWindow(win, win.scrollX, win.scrollY,
    67                    snippetWidth, snippetWidth * this.aspectRatio, "rgb(255,255,255)");
    69     if (aStore &&
    70         aTab.linkedBrowser /* bug 795608: the tab may got removed while drawing the thumbnail */) {
    71       aTab.__thumbnail = thumbnail;
    72       aTab.__thumbnail_lastURI = aTab.linkedBrowser.currentURI.spec;
    73     }
    75     return thumbnail;
    76   },
    78   handleEvent: function tabPreviews_handleEvent(event) {
    79     switch (event.type) {
    80       case "TabSelect":
    81         if (this._selectedTab &&
    82             this._selectedTab.parentNode &&
    83             !this._pendingUpdate) {
    84           // Generate a thumbnail for the tab that was selected.
    85           // The timeout keeps the UI snappy and prevents us from generating thumbnails
    86           // for tabs that will be closed. During that timeout, don't generate other
    87           // thumbnails in case multiple TabSelect events occur fast in succession.
    88           this._pendingUpdate = true;
    89           setTimeout(function (self, aTab) {
    90             self._pendingUpdate = false;
    91             if (aTab.parentNode &&
    92                 !aTab.hasAttribute("busy") &&
    93                 !aTab.hasAttribute("pending"))
    94               self.capture(aTab, true);
    95           }, 2000, this, this._selectedTab);
    96         }
    97         this._selectedTab = event.target;
    98         break;
    99       case "SSTabRestored":
   100         this.capture(event.target, true);
   101         break;
   102     }
   103   }
   104 };
   106 var tabPreviewPanelHelper = {
   107   opening: function (host) {
   108     host.panel.hidden = false;
   110     var handler = this._generateHandler(host);
   111     host.panel.addEventListener("popupshown", handler, false);
   112     host.panel.addEventListener("popuphiding", handler, false);
   114     host._prevFocus = document.commandDispatcher.focusedElement;
   115   },
   116   _generateHandler: function (host) {
   117     var self = this;
   118     return function (event) {
   119       if (event.target == host.panel) {
   120         host.panel.removeEventListener(event.type, arguments.callee, false);
   121         self["_" + event.type](host);
   122       }
   123     };
   124   },
   125   _popupshown: function (host) {
   126     if ("setupGUI" in host)
   127       host.setupGUI();
   128   },
   129   _popuphiding: function (host) {
   130     if ("suspendGUI" in host)
   131       host.suspendGUI();
   133     if (host._prevFocus) {
   134       Cc["@mozilla.org/focus-manager;1"]
   135         .getService(Ci.nsIFocusManager)
   136         .setFocus(host._prevFocus, Ci.nsIFocusManager.FLAG_NOSCROLL);
   137       host._prevFocus = null;
   138     } else
   139       gBrowser.selectedBrowser.focus();
   141     if (host.tabToSelect) {
   142       gBrowser.selectedTab = host.tabToSelect;
   143       host.tabToSelect = null;
   144     }
   145   }
   146 };
   148 /**
   149  * Ctrl-Tab panel
   150  */
   151 var ctrlTab = {
   152   get panel () {
   153     delete this.panel;
   154     return this.panel = document.getElementById("ctrlTab-panel");
   155   },
   156   get showAllButton () {
   157     delete this.showAllButton;
   158     return this.showAllButton = document.getElementById("ctrlTab-showAll");
   159   },
   160   get previews () {
   161     delete this.previews;
   162     return this.previews = this.panel.getElementsByClassName("ctrlTab-preview");
   163   },
   164   get keys () {
   165     var keys = {};
   166     ["close", "find", "selectAll"].forEach(function (key) {
   167       keys[key] = document.getElementById("key_" + key)
   168                           .getAttribute("key")
   169                           .toLocaleLowerCase().charCodeAt(0);
   170     });
   171     delete this.keys;
   172     return this.keys = keys;
   173   },
   174   _selectedIndex: 0,
   175   get selected () this._selectedIndex < 0 ?
   176                     document.activeElement :
   177                     this.previews.item(this._selectedIndex),
   178   get isOpen   () this.panel.state == "open" || this.panel.state == "showing" || this._timer,
   179   get tabCount () this.tabList.length,
   180   get tabPreviewCount () Math.min(this.previews.length - 1, this.tabCount),
   181   get canvasWidth () Math.min(tabPreviews.width,
   182                               Math.ceil(screen.availWidth * .85 / this.tabPreviewCount)),
   183   get canvasHeight () Math.round(this.canvasWidth * tabPreviews.aspectRatio),
   185   get tabList () {
   186     return this._recentlyUsedTabs;
   187   },
   189   init: function ctrlTab_init() {
   190     if (!this._recentlyUsedTabs) {
   191       tabPreviews.init();
   193       this._initRecentlyUsedTabs();
   194       this._init(true);
   195     }
   196   },
   198   uninit: function ctrlTab_uninit() {
   199     this._recentlyUsedTabs = null;
   200     this._init(false);
   201   },
   203   prefName: "browser.ctrlTab.previews",
   204   readPref: function ctrlTab_readPref() {
   205     var enable =
   206       gPrefService.getBoolPref(this.prefName) &&
   207       (!gPrefService.prefHasUserValue("browser.ctrlTab.disallowForScreenReaders") ||
   208        !gPrefService.getBoolPref("browser.ctrlTab.disallowForScreenReaders"));
   210     if (enable)
   211       this.init();
   212     else
   213       this.uninit();
   214   },
   215   observe: function (aSubject, aTopic, aPrefName) {
   216     this.readPref();
   217   },
   219   updatePreviews: function ctrlTab_updatePreviews() {
   220     for (let i = 0; i < this.previews.length; i++)
   221       this.updatePreview(this.previews[i], this.tabList[i]);
   223     var showAllLabel = gNavigatorBundle.getString("ctrlTab.showAll.label");
   224     this.showAllButton.label =
   225       PluralForm.get(this.tabCount, showAllLabel).replace("#1", this.tabCount);
   226     this.showAllButton.hidden = !allTabs.canOpen;
   227   },
   229   updatePreview: function ctrlTab_updatePreview(aPreview, aTab) {
   230     if (aPreview == this.showAllButton)
   231       return;
   233     aPreview._tab = aTab;
   235     if (aPreview.firstChild)
   236       aPreview.removeChild(aPreview.firstChild);
   237     if (aTab) {
   238       let canvasWidth = this.canvasWidth;
   239       let canvasHeight = this.canvasHeight;
   240       aPreview.appendChild(tabPreviews.get(aTab));
   241       aPreview.setAttribute("label", aTab.label);
   242       aPreview.setAttribute("tooltiptext", aTab.label);
   243       aPreview.setAttribute("crop", aTab.crop);
   244       aPreview.setAttribute("canvaswidth", canvasWidth);
   245       aPreview.setAttribute("canvasstyle",
   246                             "max-width:" + canvasWidth + "px;" +
   247                             "min-width:" + canvasWidth + "px;" +
   248                             "max-height:" + canvasHeight + "px;" +
   249                             "min-height:" + canvasHeight + "px;");
   250       if (aTab.image)
   251         aPreview.setAttribute("image", aTab.image);
   252       else
   253         aPreview.removeAttribute("image");
   254       aPreview.hidden = false;
   255     } else {
   256       aPreview.hidden = true;
   257       aPreview.removeAttribute("label");
   258       aPreview.removeAttribute("tooltiptext");
   259       aPreview.removeAttribute("image");
   260     }
   261   },
   263   advanceFocus: function ctrlTab_advanceFocus(aForward) {
   264     let selectedIndex = Array.indexOf(this.previews, this.selected);
   265     do {
   266       selectedIndex += aForward ? 1 : -1;
   267       if (selectedIndex < 0)
   268         selectedIndex = this.previews.length - 1;
   269       else if (selectedIndex >= this.previews.length)
   270         selectedIndex = 0;
   271     } while (this.previews[selectedIndex].hidden);
   273     if (this._selectedIndex == -1) {
   274       // Focus is already in the panel.
   275       this.previews[selectedIndex].focus();
   276     } else {
   277       this._selectedIndex = selectedIndex;
   278     }
   280     if (this._timer) {
   281       clearTimeout(this._timer);
   282       this._timer = null;
   283       this._openPanel();
   284     }
   285   },
   287   _mouseOverFocus: function ctrlTab_mouseOverFocus(aPreview) {
   288     if (this._trackMouseOver)
   289       aPreview.focus();
   290   },
   292   pick: function ctrlTab_pick(aPreview) {
   293     if (!this.tabCount)
   294       return;
   296     var select = (aPreview || this.selected);
   298     if (select == this.showAllButton)
   299       this.showAllTabs();
   300     else
   301       this.close(select._tab);
   302   },
   304   showAllTabs: function ctrlTab_showAllTabs(aPreview) {
   305     this.close();
   306     document.getElementById("Browser:ShowAllTabs").doCommand();
   307   },
   309   remove: function ctrlTab_remove(aPreview) {
   310     if (aPreview._tab)
   311       gBrowser.removeTab(aPreview._tab);
   312   },
   314   attachTab: function ctrlTab_attachTab(aTab, aPos) {
   315     if (aTab.closing)
   316       return;
   318     if (aPos == 0)
   319       this._recentlyUsedTabs.unshift(aTab);
   320     else if (aPos)
   321       this._recentlyUsedTabs.splice(aPos, 0, aTab);
   322     else
   323       this._recentlyUsedTabs.push(aTab);
   324   },
   326   detachTab: function ctrlTab_detachTab(aTab) {
   327     var i = this._recentlyUsedTabs.indexOf(aTab);
   328     if (i >= 0)
   329       this._recentlyUsedTabs.splice(i, 1);
   330   },
   332   open: function ctrlTab_open() {
   333     if (this.isOpen)
   334       return;
   336     document.addEventListener("keyup", this, true);
   338     this.updatePreviews();
   339     this._selectedIndex = 1;
   341     // Add a slight delay before showing the UI, so that a quick
   342     // "ctrl-tab" keypress just flips back to the MRU tab.
   343     this._timer = setTimeout(function (self) {
   344       self._timer = null;
   345       self._openPanel();
   346     }, 200, this);
   347   },
   349   _openPanel: function ctrlTab_openPanel() {
   350     tabPreviewPanelHelper.opening(this);
   352     this.panel.width = Math.min(screen.availWidth * .99,
   353                                 this.canvasWidth * 1.25 * this.tabPreviewCount);
   354     var estimateHeight = this.canvasHeight * 1.25 + 75;
   355     this.panel.openPopupAtScreen(screen.availLeft + (screen.availWidth - this.panel.width) / 2,
   356                                  screen.availTop + (screen.availHeight - estimateHeight) / 2,
   357                                  false);
   358   },
   360   close: function ctrlTab_close(aTabToSelect) {
   361     if (!this.isOpen)
   362       return;
   364     if (this._timer) {
   365       clearTimeout(this._timer);
   366       this._timer = null;
   367       this.suspendGUI();
   368       if (aTabToSelect)
   369         gBrowser.selectedTab = aTabToSelect;
   370       return;
   371     }
   373     this.tabToSelect = aTabToSelect;
   374     this.panel.hidePopup();
   375   },
   377   setupGUI: function ctrlTab_setupGUI() {
   378     this.selected.focus();
   379     this._selectedIndex = -1;
   381     // Track mouse movement after a brief delay so that the item that happens
   382     // to be under the mouse pointer initially won't be selected unintentionally.
   383     this._trackMouseOver = false;
   384     setTimeout(function (self) {
   385       if (self.isOpen)
   386         self._trackMouseOver = true;
   387     }, 0, this);
   388   },
   390   suspendGUI: function ctrlTab_suspendGUI() {
   391     document.removeEventListener("keyup", this, true);
   393     Array.forEach(this.previews, function (preview) {
   394       this.updatePreview(preview, null);
   395     }, this);
   396   },
   398   onKeyPress: function ctrlTab_onKeyPress(event) {
   399     var isOpen = this.isOpen;
   401     if (isOpen) {
   402       event.preventDefault();
   403       event.stopPropagation();
   404     }
   406     switch (event.keyCode) {
   407       case event.DOM_VK_TAB:
   408         if (event.ctrlKey && !event.altKey && !event.metaKey) {
   409           if (isOpen) {
   410             this.advanceFocus(!event.shiftKey);
   411           } else if (!event.shiftKey) {
   412             event.preventDefault();
   413             event.stopPropagation();
   414             let tabs = gBrowser.visibleTabs;
   415             if (tabs.length > 2) {
   416               this.open();
   417             } else if (tabs.length == 2) {
   418               let index = tabs[0].selected ? 1 : 0;
   419               gBrowser.selectedTab = tabs[index];
   420             }
   421           }
   422         }
   423         break;
   424       default:
   425         if (isOpen && event.ctrlKey) {
   426           if (event.keyCode == event.DOM_VK_DELETE) {
   427             this.remove(this.selected);
   428             break;
   429           }
   430           switch (event.charCode) {
   431             case this.keys.close:
   432               this.remove(this.selected);
   433               break;
   434             case this.keys.find:
   435             case this.keys.selectAll:
   436               this.showAllTabs();
   437               break;
   438           }
   439         }
   440     }
   441   },
   443   removeClosingTabFromUI: function ctrlTab_removeClosingTabFromUI(aTab) {
   444     if (this.tabCount == 2) {
   445       this.close();
   446       return;
   447     }
   449     this.updatePreviews();
   451     if (this.selected.hidden)
   452       this.advanceFocus(false);
   453     if (this.selected == this.showAllButton)
   454       this.advanceFocus(false);
   456     // If the current tab is removed, another tab can steal our focus.
   457     if (aTab.selected && this.panel.state == "open") {
   458       setTimeout(function (selected) {
   459         selected.focus();
   460       }, 0, this.selected);
   461     }
   462   },
   464   handleEvent: function ctrlTab_handleEvent(event) {
   465     switch (event.type) {
   466       case "SSWindowStateReady":
   467         this._initRecentlyUsedTabs();
   468         break;
   469       case "TabAttrModified":
   470         // tab attribute modified (e.g. label, crop, busy, image, selected)
   471         for (let i = this.previews.length - 1; i >= 0; i--) {
   472           if (this.previews[i]._tab && this.previews[i]._tab == event.target) {
   473             this.updatePreview(this.previews[i], event.target);
   474             break;
   475           }
   476         }
   477         break;
   478       case "TabSelect":
   479         this.detachTab(event.target);
   480         this.attachTab(event.target, 0);
   481         break;
   482       case "TabOpen":
   483         this.attachTab(event.target, 1);
   484         break;
   485       case "TabClose":
   486         this.detachTab(event.target);
   487         if (this.isOpen)
   488           this.removeClosingTabFromUI(event.target);
   489         break;
   490       case "keypress":
   491         this.onKeyPress(event);
   492         break;
   493       case "keyup":
   494         if (event.keyCode == event.DOM_VK_CONTROL)
   495           this.pick();
   496         break;
   497       case "popupshowing":
   498         if (event.target.id == "menu_viewPopup")
   499           document.getElementById("menu_showAllTabs").hidden = !allTabs.canOpen;
   500         break;
   501     }
   502   },
   504   _initRecentlyUsedTabs: function () {
   505     this._recentlyUsedTabs =
   506       Array.filter(gBrowser.tabs, tab => !tab.closing)
   507            .sort((tab1, tab2) => tab2.lastAccessed - tab1.lastAccessed);
   508   },
   510   _init: function ctrlTab__init(enable) {
   511     var toggleEventListener = enable ? "addEventListener" : "removeEventListener";
   513     window[toggleEventListener]("SSWindowStateReady", this, false);
   515     var tabContainer = gBrowser.tabContainer;
   516     tabContainer[toggleEventListener]("TabOpen", this, false);
   517     tabContainer[toggleEventListener]("TabAttrModified", this, false);
   518     tabContainer[toggleEventListener]("TabSelect", this, false);
   519     tabContainer[toggleEventListener]("TabClose", this, false);
   521     document[toggleEventListener]("keypress", this, false);
   522     gBrowser.mTabBox.handleCtrlTab = !enable;
   524     // If we're not running, hide the "Show All Tabs" menu item,
   525     // as Shift+Ctrl+Tab will be handled by the tab bar.
   526     document.getElementById("menu_showAllTabs").hidden = !enable;
   527     document.getElementById("menu_viewPopup")[toggleEventListener]("popupshowing", this);
   529     // Also disable the <key> to ensure Shift+Ctrl+Tab never triggers
   530     // Show All Tabs.
   531     var key_showAllTabs = document.getElementById("key_showAllTabs");
   532     if (enable)
   533       key_showAllTabs.removeAttribute("disabled");
   534     else
   535       key_showAllTabs.setAttribute("disabled", "true");
   536   }
   537 };
   540 /**
   541  * All Tabs menu
   542  */
   543 var allTabs = {
   544   get toolbarButton() document.getElementById("alltabs-button"),
   545   get canOpen() isElementVisible(this.toolbarButton),
   547   open: function allTabs_open() {
   548     if (this.canOpen) {
   549       // Without setTimeout, the menupopup won't stay open when invoking
   550       // "View > Show All Tabs" and the menu bar auto-hides.
   551       setTimeout(function () {
   552         allTabs.toolbarButton.open = true;
   553       }, 0);
   554     }
   555   }
   556 };

mercurial