1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/modules/WindowsPreviewPerTab.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,721 @@ 1.4 +/* vim: se cin sw=2 ts=2 et filetype=javascript : 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 +/* 1.9 + * This module implements the front end behavior for AeroPeek. Starting in 1.10 + * Windows Vista, the taskbar began showing live thumbnail previews of windows 1.11 + * when the user hovered over the window icon in the taskbar. Starting with 1.12 + * Windows 7, the taskbar allows an application to expose its tabbed interface 1.13 + * in the taskbar by showing thumbnail previews rather than the default window 1.14 + * preview. Additionally, when a user hovers over a thumbnail (tab or window), 1.15 + * they are shown a live preview of the window (or tab + its containing window). 1.16 + * 1.17 + * In Windows 7, a title, icon, close button and optional toolbar are shown for 1.18 + * each preview. This feature does not make use of the toolbar. For window 1.19 + * previews, the title is the window title and the icon the window icon. For 1.20 + * tab previews, the title is the page title and the page's favicon. In both 1.21 + * cases, the close button "does the right thing." 1.22 + * 1.23 + * The primary objects behind this feature are nsITaskbarTabPreview and 1.24 + * nsITaskbarPreviewController. Each preview has a controller. The controller 1.25 + * responds to the user's interactions on the taskbar and provides the required 1.26 + * data to the preview for determining the size of the tab and thumbnail. The 1.27 + * PreviewController class implements this interface. The preview will request 1.28 + * the controller to provide a thumbnail or preview when the user interacts with 1.29 + * the taskbar. To reduce the overhead of drawing the tab area, the controller 1.30 + * implementation caches the tab's contents in a <canvas> element. If no 1.31 + * previews or thumbnails have been requested for some time, the controller will 1.32 + * discard its cached tab contents. 1.33 + * 1.34 + * Screen real estate is limited so when there are too many thumbnails to fit 1.35 + * on the screen, the taskbar stops displaying thumbnails and instead displays 1.36 + * just the title, icon and close button in a similar fashion to previous 1.37 + * versions of the taskbar. If there are still too many previews to fit on the 1.38 + * screen, the taskbar resorts to a scroll up and scroll down button pair to let 1.39 + * the user scroll through the list of tabs. Since this is undoubtedly 1.40 + * inconvenient for users with many tabs, the AeroPeek objects turns off all of 1.41 + * the tab previews. This tells the taskbar to revert to one preview per window. 1.42 + * If the number of tabs falls below this magic threshold, the preview-per-tab 1.43 + * behavior returns. There is no reliable way to determine when the scroll 1.44 + * buttons appear on the taskbar, so a magic pref-controlled number determines 1.45 + * when this threshold has been crossed. 1.46 + */ 1.47 +this.EXPORTED_SYMBOLS = ["AeroPeek"]; 1.48 + 1.49 +const Cc = Components.classes; 1.50 +const Ci = Components.interfaces; 1.51 +const Cu = Components.utils; 1.52 + 1.53 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.54 +Cu.import("resource://gre/modules/NetUtil.jsm"); 1.55 +Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); 1.56 + 1.57 +// Pref to enable/disable preview-per-tab 1.58 +const TOGGLE_PREF_NAME = "browser.taskbar.previews.enable"; 1.59 +// Pref to determine the magic auto-disable threshold 1.60 +const DISABLE_THRESHOLD_PREF_NAME = "browser.taskbar.previews.max"; 1.61 +// Pref to control the time in seconds that tab contents live in the cache 1.62 +const CACHE_EXPIRATION_TIME_PREF_NAME = "browser.taskbar.previews.cachetime"; 1.63 + 1.64 +const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1"; 1.65 + 1.66 +//////////////////////////////////////////////////////////////////////////////// 1.67 +//// Various utility properties 1.68 +XPCOMUtils.defineLazyServiceGetter(this, "ioSvc", 1.69 + "@mozilla.org/network/io-service;1", 1.70 + "nsIIOService"); 1.71 +XPCOMUtils.defineLazyServiceGetter(this, "imgTools", 1.72 + "@mozilla.org/image/tools;1", 1.73 + "imgITools"); 1.74 +XPCOMUtils.defineLazyServiceGetter(this, "faviconSvc", 1.75 + "@mozilla.org/browser/favicon-service;1", 1.76 + "nsIFaviconService"); 1.77 + 1.78 +// nsIURI -> imgIContainer 1.79 +function _imageFromURI(uri, privateMode, callback) { 1.80 + let channel = ioSvc.newChannelFromURI(uri); 1.81 + try { 1.82 + channel.QueryInterface(Ci.nsIPrivateBrowsingChannel); 1.83 + channel.setPrivate(privateMode); 1.84 + } catch (e) { 1.85 + // Ignore channels which do not support nsIPrivateBrowsingChannel 1.86 + } 1.87 + NetUtil.asyncFetch(channel, function(inputStream, resultCode) { 1.88 + if (!Components.isSuccessCode(resultCode)) 1.89 + return; 1.90 + try { 1.91 + let out_img = { value: null }; 1.92 + imgTools.decodeImageData(inputStream, channel.contentType, out_img); 1.93 + callback(out_img.value); 1.94 + } catch (e) { 1.95 + // We failed, so use the default favicon (only if this wasn't the default 1.96 + // favicon). 1.97 + let defaultURI = faviconSvc.defaultFavicon; 1.98 + if (!defaultURI.equals(uri)) 1.99 + _imageFromURI(defaultURI, callback); 1.100 + } 1.101 + }); 1.102 +} 1.103 + 1.104 +// string? -> imgIContainer 1.105 +function getFaviconAsImage(iconurl, privateMode, callback) { 1.106 + if (iconurl) 1.107 + _imageFromURI(NetUtil.newURI(iconurl), privateMode, callback); 1.108 + else 1.109 + _imageFromURI(faviconSvc.defaultFavicon, privateMode, callback); 1.110 +} 1.111 + 1.112 +// Snaps the given rectangle to be pixel-aligned at the given scale 1.113 +function snapRectAtScale(r, scale) { 1.114 + let x = Math.floor(r.x * scale); 1.115 + let y = Math.floor(r.y * scale); 1.116 + let width = Math.ceil((r.x + r.width) * scale) - x; 1.117 + let height = Math.ceil((r.y + r.height) * scale) - y; 1.118 + 1.119 + r.x = x / scale; 1.120 + r.y = y / scale; 1.121 + r.width = width / scale; 1.122 + r.height = height / scale; 1.123 +} 1.124 + 1.125 +//////////////////////////////////////////////////////////////////////////////// 1.126 +//// PreviewController 1.127 + 1.128 +/* 1.129 + * This class manages the behavior of the preview. 1.130 + * 1.131 + * To give greater performance when drawing, the dirty areas of the content 1.132 + * window are tracked and drawn on demand into a canvas of the same size. 1.133 + * This provides a great increase in responsiveness when drawing a preview 1.134 + * for unchanged (or even only slightly changed) tabs. 1.135 + * 1.136 + * @param win 1.137 + * The TabWindow (see below) that owns the preview that this controls 1.138 + * @param tab 1.139 + * The <tab> that this preview is associated with 1.140 + */ 1.141 +function PreviewController(win, tab) { 1.142 + this.win = win; 1.143 + this.tab = tab; 1.144 + this.linkedBrowser = tab.linkedBrowser; 1.145 + this.preview = this.win.createTabPreview(this); 1.146 + 1.147 + this.linkedBrowser.addEventListener("MozAfterPaint", this, false); 1.148 + this.tab.addEventListener("TabAttrModified", this, false); 1.149 + 1.150 + XPCOMUtils.defineLazyGetter(this, "canvasPreview", function () { 1.151 + let canvas = this.win.win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); 1.152 + canvas.mozOpaque = true; 1.153 + return canvas; 1.154 + }); 1.155 + 1.156 + XPCOMUtils.defineLazyGetter(this, "dirtyRegion", 1.157 + function () { 1.158 + let dirtyRegion = Cc["@mozilla.org/gfx/region;1"] 1.159 + .createInstance(Ci.nsIScriptableRegion); 1.160 + dirtyRegion.init(); 1.161 + return dirtyRegion; 1.162 + }); 1.163 + 1.164 + XPCOMUtils.defineLazyGetter(this, "winutils", 1.165 + function () { 1.166 + let win = tab.linkedBrowser.contentWindow; 1.167 + return win.QueryInterface(Ci.nsIInterfaceRequestor) 1.168 + .getInterface(Ci.nsIDOMWindowUtils); 1.169 + }); 1.170 +} 1.171 + 1.172 +PreviewController.prototype = { 1.173 + QueryInterface: XPCOMUtils.generateQI([Ci.nsITaskbarPreviewController, 1.174 + Ci.nsIDOMEventListener]), 1.175 + destroy: function () { 1.176 + this.tab.removeEventListener("TabAttrModified", this, false); 1.177 + this.linkedBrowser.removeEventListener("MozAfterPaint", this, false); 1.178 + 1.179 + // Break cycles, otherwise we end up leaking the window with everything 1.180 + // attached to it. 1.181 + delete this.win; 1.182 + delete this.preview; 1.183 + delete this.dirtyRegion; 1.184 + }, 1.185 + get wrappedJSObject() { 1.186 + return this; 1.187 + }, 1.188 + 1.189 + get dirtyRects() { 1.190 + let rectstream = this.dirtyRegion.getRects(); 1.191 + if (!rectstream) 1.192 + return []; 1.193 + let rects = []; 1.194 + for (let i = 0; i < rectstream.length; i+= 4) { 1.195 + let r = {x: rectstream[i], 1.196 + y: rectstream[i+1], 1.197 + width: rectstream[i+2], 1.198 + height: rectstream[i+3]}; 1.199 + rects.push(r); 1.200 + } 1.201 + return rects; 1.202 + }, 1.203 + 1.204 + // Resizes the canvasPreview to 0x0, essentially freeing its memory. 1.205 + // updateCanvasPreview() will detect the size mismatch as a resize event 1.206 + // the next time it is called. 1.207 + resetCanvasPreview: function () { 1.208 + this.canvasPreview.width = 0; 1.209 + this.canvasPreview.height = 0; 1.210 + }, 1.211 + 1.212 + get zoom() { 1.213 + // Note that winutils.fullZoom accounts for "quantization" of the zoom factor 1.214 + // from nsIMarkupDocumentViewer due to conversion through appUnits. 1.215 + // We do -not- want screenPixelsPerCSSPixel here, because that would -also- 1.216 + // incorporate any scaling that is applied due to hi-dpi resolution options. 1.217 + return this.winutils.fullZoom; 1.218 + }, 1.219 + 1.220 + // Updates the controller's canvas with the parts of the <browser> that need 1.221 + // to be redrawn. 1.222 + updateCanvasPreview: function () { 1.223 + let win = this.linkedBrowser.contentWindow; 1.224 + let bx = this.linkedBrowser.boxObject; 1.225 + // Check for resize 1.226 + if (bx.width != this.canvasPreview.width || 1.227 + bx.height != this.canvasPreview.height) { 1.228 + // Invalidate the entire area and repaint 1.229 + this.onTabPaint({left:0, top:0, right:win.innerWidth, bottom:win.innerHeight}); 1.230 + this.canvasPreview.width = bx.width; 1.231 + this.canvasPreview.height = bx.height; 1.232 + } 1.233 + 1.234 + // Draw dirty regions 1.235 + let ctx = this.canvasPreview.getContext("2d"); 1.236 + let scale = this.zoom; 1.237 + 1.238 + let flags = this.canvasPreviewFlags; 1.239 + // The dirty region may include parts that are offscreen so we clip to the 1.240 + // canvas area. 1.241 + this.dirtyRegion.intersectRect(0, 0, win.innerWidth, win.innerHeight); 1.242 + this.dirtyRects.forEach(function (r) { 1.243 + // We need to snap the rectangle to be pixel aligned in the destination 1.244 + // coordinate space. Otherwise natively themed widgets might not draw. 1.245 + snapRectAtScale(r, scale); 1.246 + let x = r.x; 1.247 + let y = r.y; 1.248 + let width = r.width; 1.249 + let height = r.height; 1.250 + 1.251 + ctx.save(); 1.252 + ctx.scale(scale, scale); 1.253 + ctx.translate(x, y); 1.254 + ctx.drawWindow(win, x, y, width, height, "white", flags); 1.255 + ctx.restore(); 1.256 + }); 1.257 + this.dirtyRegion.setToRect(0,0,0,0); 1.258 + 1.259 + // If we're updating the canvas, then we're in the middle of a peek so 1.260 + // don't discard the cache of previews. 1.261 + AeroPeek.resetCacheTimer(); 1.262 + }, 1.263 + 1.264 + onTabPaint: function (rect) { 1.265 + let x = Math.floor(rect.left), 1.266 + y = Math.floor(rect.top), 1.267 + width = Math.ceil(rect.right) - x, 1.268 + height = Math.ceil(rect.bottom) - y; 1.269 + this.dirtyRegion.unionRect(x, y, width, height); 1.270 + }, 1.271 + 1.272 + updateTitleAndTooltip: function () { 1.273 + let title = this.win.tabbrowser.getWindowTitleForBrowser(this.linkedBrowser); 1.274 + this.preview.title = title; 1.275 + this.preview.tooltip = title; 1.276 + }, 1.277 + 1.278 + ////////////////////////////////////////////////////////////////////////////// 1.279 + //// nsITaskbarPreviewController 1.280 + 1.281 + get width() { 1.282 + return this.win.width; 1.283 + }, 1.284 + 1.285 + get height() { 1.286 + return this.win.height; 1.287 + }, 1.288 + 1.289 + get thumbnailAspectRatio() { 1.290 + let boxObject = this.tab.linkedBrowser.boxObject; 1.291 + // Avoid returning 0 1.292 + let tabWidth = boxObject.width || 1; 1.293 + // Avoid divide by 0 1.294 + let tabHeight = boxObject.height || 1; 1.295 + return tabWidth / tabHeight; 1.296 + }, 1.297 + 1.298 + drawPreview: function (ctx) { 1.299 + let self = this; 1.300 + this.win.tabbrowser.previewTab(this.tab, function () self.previewTabCallback(ctx)); 1.301 + 1.302 + // We must avoid having the frame drawn around the window. See bug 520807 1.303 + return false; 1.304 + }, 1.305 + 1.306 + previewTabCallback: function (ctx) { 1.307 + // This will extract the resolution-scale component of the scaling we need, 1.308 + // which should be applied to both chrome and content; 1.309 + // the page zoom component is applied (to content only) within updateCanvasPreview. 1.310 + let scale = this.winutils.screenPixelsPerCSSPixel / this.winutils.fullZoom; 1.311 + ctx.save(); 1.312 + ctx.scale(scale, scale); 1.313 + let width = this.win.width; 1.314 + let height = this.win.height; 1.315 + // Draw our toplevel window 1.316 + ctx.drawWindow(this.win.win, 0, 0, width, height, "transparent"); 1.317 + 1.318 + // XXX (jfkthame): Pending tabs don't seem to draw with the proper scaling 1.319 + // unless we use this block of code; but doing this for "normal" (loaded) tabs 1.320 + // results in blurry rendering on hidpi systems, so we avoid it if possible. 1.321 + // I don't understand why pending and loaded tabs behave differently here... 1.322 + // (see bug 857061). 1.323 + if (this.tab.hasAttribute("pending")) { 1.324 + // Compositor, where art thou? 1.325 + // Draw the tab content on top of the toplevel window 1.326 + this.updateCanvasPreview(); 1.327 + 1.328 + let boxObject = this.linkedBrowser.boxObject; 1.329 + ctx.translate(boxObject.x, boxObject.y); 1.330 + ctx.drawImage(this.canvasPreview, 0, 0); 1.331 + } 1.332 + 1.333 + ctx.restore(); 1.334 + }, 1.335 + 1.336 + drawThumbnail: function (ctx, width, height) { 1.337 + this.updateCanvasPreview(); 1.338 + 1.339 + let scale = width/this.linkedBrowser.boxObject.width; 1.340 + ctx.scale(scale, scale); 1.341 + ctx.drawImage(this.canvasPreview, 0, 0); 1.342 + 1.343 + // Don't draw a frame around the thumbnail 1.344 + return false; 1.345 + }, 1.346 + 1.347 + onClose: function () { 1.348 + this.win.tabbrowser.removeTab(this.tab); 1.349 + }, 1.350 + 1.351 + onActivate: function () { 1.352 + this.win.tabbrowser.selectedTab = this.tab; 1.353 + 1.354 + // Accept activation - this will restore the browser window 1.355 + // if it's minimized 1.356 + return true; 1.357 + }, 1.358 + 1.359 + //// nsIDOMEventListener 1.360 + handleEvent: function (evt) { 1.361 + switch (evt.type) { 1.362 + case "MozAfterPaint": 1.363 + if (evt.originalTarget === this.linkedBrowser.contentWindow) { 1.364 + let clientRects = evt.clientRects; 1.365 + let length = clientRects.length; 1.366 + for (let i = 0; i < length; i++) { 1.367 + let r = clientRects.item(i); 1.368 + this.onTabPaint(r); 1.369 + } 1.370 + } 1.371 + let preview = this.preview; 1.372 + if (preview.visible) 1.373 + preview.invalidate(); 1.374 + break; 1.375 + case "TabAttrModified": 1.376 + this.updateTitleAndTooltip(); 1.377 + break; 1.378 + } 1.379 + } 1.380 +}; 1.381 + 1.382 +XPCOMUtils.defineLazyGetter(PreviewController.prototype, "canvasPreviewFlags", 1.383 + function () { let canvasInterface = Ci.nsIDOMCanvasRenderingContext2D; 1.384 + return canvasInterface.DRAWWINDOW_DRAW_VIEW 1.385 + | canvasInterface.DRAWWINDOW_DRAW_CARET 1.386 + | canvasInterface.DRAWWINDOW_ASYNC_DECODE_IMAGES 1.387 + | canvasInterface.DRAWWINDOW_DO_NOT_FLUSH; 1.388 +}); 1.389 + 1.390 +//////////////////////////////////////////////////////////////////////////////// 1.391 +//// TabWindow 1.392 + 1.393 +/* 1.394 + * This class monitors a browser window for changes to its tabs 1.395 + * 1.396 + * @param win 1.397 + * The nsIDOMWindow browser window 1.398 + */ 1.399 +function TabWindow(win) { 1.400 + this.win = win; 1.401 + this.tabbrowser = win.gBrowser; 1.402 + 1.403 + this.previews = new Map(); 1.404 + 1.405 + for (let i = 0; i < this.tabEvents.length; i++) 1.406 + this.tabbrowser.tabContainer.addEventListener(this.tabEvents[i], this, false); 1.407 + this.tabbrowser.addTabsProgressListener(this); 1.408 + 1.409 + for (let i = 0; i < this.winEvents.length; i++) 1.410 + this.win.addEventListener(this.winEvents[i], this, false); 1.411 + 1.412 + AeroPeek.windows.push(this); 1.413 + let tabs = this.tabbrowser.tabs; 1.414 + for (let i = 0; i < tabs.length; i++) 1.415 + this.newTab(tabs[i]); 1.416 + 1.417 + this.updateTabOrdering(); 1.418 + AeroPeek.checkPreviewCount(); 1.419 +} 1.420 + 1.421 +TabWindow.prototype = { 1.422 + _enabled: false, 1.423 + tabEvents: ["TabOpen", "TabClose", "TabSelect", "TabMove"], 1.424 + winEvents: ["tabviewshown", "tabviewhidden"], 1.425 + 1.426 + destroy: function () { 1.427 + this._destroying = true; 1.428 + 1.429 + let tabs = this.tabbrowser.tabs; 1.430 + 1.431 + this.tabbrowser.removeTabsProgressListener(this); 1.432 + for (let i = 0; i < this.tabEvents.length; i++) 1.433 + this.tabbrowser.tabContainer.removeEventListener(this.tabEvents[i], this, false); 1.434 + 1.435 + for (let i = 0; i < this.winEvents.length; i++) 1.436 + this.win.removeEventListener(this.winEvents[i], this, false); 1.437 + 1.438 + for (let i = 0; i < tabs.length; i++) 1.439 + this.removeTab(tabs[i]); 1.440 + 1.441 + let idx = AeroPeek.windows.indexOf(this.win.gTaskbarTabGroup); 1.442 + AeroPeek.windows.splice(idx, 1); 1.443 + AeroPeek.checkPreviewCount(); 1.444 + }, 1.445 + 1.446 + get width () { 1.447 + return this.win.innerWidth; 1.448 + }, 1.449 + get height () { 1.450 + return this.win.innerHeight; 1.451 + }, 1.452 + 1.453 + // Invoked when the given tab is added to this window 1.454 + newTab: function (tab) { 1.455 + let controller = new PreviewController(this, tab); 1.456 + // It's OK to add the preview now while the favicon still loads. 1.457 + this.previews.set(tab, controller.preview); 1.458 + AeroPeek.addPreview(controller.preview); 1.459 + // updateTitleAndTooltip relies on having controller.preview which is lazily resolved. 1.460 + // Now that we've updated this.previews, it will resolve successfully. 1.461 + controller.updateTitleAndTooltip(); 1.462 + }, 1.463 + 1.464 + createTabPreview: function (controller) { 1.465 + let docShell = this.win 1.466 + .QueryInterface(Ci.nsIInterfaceRequestor) 1.467 + .getInterface(Ci.nsIWebNavigation) 1.468 + .QueryInterface(Ci.nsIDocShell); 1.469 + let preview = AeroPeek.taskbar.createTaskbarTabPreview(docShell, controller); 1.470 + preview.visible = AeroPeek.enabled; 1.471 + preview.active = this.tabbrowser.selectedTab == controller.tab; 1.472 + // Grab the default favicon 1.473 + getFaviconAsImage(null, PrivateBrowsingUtils.isWindowPrivate(this.win), function (img) { 1.474 + // It is possible that we've already gotten the real favicon, so make sure 1.475 + // we have not set one before setting this default one. 1.476 + if (!preview.icon) 1.477 + preview.icon = img; 1.478 + }); 1.479 + 1.480 + return preview; 1.481 + }, 1.482 + 1.483 + // Invoked when the given tab is closed 1.484 + removeTab: function (tab) { 1.485 + let preview = this.previewFromTab(tab); 1.486 + preview.active = false; 1.487 + preview.visible = false; 1.488 + preview.move(null); 1.489 + preview.controller.wrappedJSObject.destroy(); 1.490 + 1.491 + this.previews.delete(tab); 1.492 + AeroPeek.removePreview(preview); 1.493 + }, 1.494 + 1.495 + get enabled () { 1.496 + return this._enabled; 1.497 + }, 1.498 + 1.499 + set enabled (enable) { 1.500 + this._enabled = enable; 1.501 + // Because making a tab visible requires that the tab it is next to be 1.502 + // visible, it is far simpler to unset the 'next' tab and recreate them all 1.503 + // at once. 1.504 + for (let [tab, preview] of this.previews) { 1.505 + preview.move(null); 1.506 + preview.visible = enable; 1.507 + } 1.508 + this.updateTabOrdering(); 1.509 + }, 1.510 + 1.511 + previewFromTab: function (tab) { 1.512 + return this.previews.get(tab); 1.513 + }, 1.514 + 1.515 + updateTabOrdering: function () { 1.516 + let previews = this.previews; 1.517 + let tabs = this.tabbrowser.tabs; 1.518 + 1.519 + // Previews are internally stored using a map, so we need to iterate the 1.520 + // tabbrowser's array of tabs to retrieve previews in the same order. 1.521 + let inorder = [previews.get(t) for (t of tabs) if (previews.has(t))]; 1.522 + 1.523 + // Since the internal taskbar array has not yet been updated we must force 1.524 + // on it the sorting order of our local array. To do so we must walk 1.525 + // the local array backwards, otherwise we would send move requests in the 1.526 + // wrong order. See bug 522610 for details. 1.527 + for (let i = inorder.length - 1; i >= 0; i--) { 1.528 + inorder[i].move(inorder[i + 1] || null); 1.529 + } 1.530 + }, 1.531 + 1.532 + //// nsIDOMEventListener 1.533 + handleEvent: function (evt) { 1.534 + let tab = evt.originalTarget; 1.535 + switch (evt.type) { 1.536 + case "TabOpen": 1.537 + this.newTab(tab); 1.538 + this.updateTabOrdering(); 1.539 + break; 1.540 + case "TabClose": 1.541 + this.removeTab(tab); 1.542 + this.updateTabOrdering(); 1.543 + break; 1.544 + case "TabSelect": 1.545 + this.previewFromTab(tab).active = true; 1.546 + break; 1.547 + case "TabMove": 1.548 + this.updateTabOrdering(); 1.549 + break; 1.550 + case "tabviewshown": 1.551 + this.enabled = false; 1.552 + break; 1.553 + case "tabviewhidden": 1.554 + if (!AeroPeek._prefenabled) 1.555 + return; 1.556 + this.enabled = true; 1.557 + break; 1.558 + } 1.559 + }, 1.560 + 1.561 + //// Browser progress listener 1.562 + onLinkIconAvailable: function (aBrowser, aIconURL) { 1.563 + let self = this; 1.564 + getFaviconAsImage(aIconURL, PrivateBrowsingUtils.isWindowPrivate(this.win), function (img) { 1.565 + let index = self.tabbrowser.browsers.indexOf(aBrowser); 1.566 + // Only add it if we've found the index. The tab could have closed! 1.567 + if (index != -1) { 1.568 + let tab = self.tabbrowser.tabs[index]; 1.569 + self.previews.get(tab).icon = img; 1.570 + } 1.571 + }); 1.572 + } 1.573 +} 1.574 + 1.575 +//////////////////////////////////////////////////////////////////////////////// 1.576 +//// AeroPeek 1.577 + 1.578 +/* 1.579 + * This object acts as global storage and external interface for this feature. 1.580 + * It maintains the values of the prefs. 1.581 + */ 1.582 +this.AeroPeek = { 1.583 + available: false, 1.584 + // Does the pref say we're enabled? 1.585 + _prefenabled: true, 1.586 + 1.587 + _enabled: true, 1.588 + 1.589 + // nsITaskbarTabPreview array 1.590 + previews: [], 1.591 + 1.592 + // TabWindow array 1.593 + windows: [], 1.594 + 1.595 + // nsIWinTaskbar service 1.596 + taskbar: null, 1.597 + 1.598 + // Maximum number of previews 1.599 + maxpreviews: 20, 1.600 + 1.601 + // Length of time in seconds that previews are cached 1.602 + cacheLifespan: 20, 1.603 + 1.604 + initialize: function () { 1.605 + if (!(WINTASKBAR_CONTRACTID in Cc)) 1.606 + return; 1.607 + this.taskbar = Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar); 1.608 + this.available = this.taskbar.available; 1.609 + if (!this.available) 1.610 + return; 1.611 + 1.612 + this.prefs.addObserver(TOGGLE_PREF_NAME, this, false); 1.613 + this.prefs.addObserver(DISABLE_THRESHOLD_PREF_NAME, this, false); 1.614 + this.prefs.addObserver(CACHE_EXPIRATION_TIME_PREF_NAME, this, false); 1.615 + 1.616 + this.cacheLifespan = this.prefs.getIntPref(CACHE_EXPIRATION_TIME_PREF_NAME); 1.617 + 1.618 + this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME); 1.619 + 1.620 + this.enabled = this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME); 1.621 + }, 1.622 + 1.623 + destroy: function destroy() { 1.624 + this._enabled = false; 1.625 + 1.626 + this.prefs.removeObserver(TOGGLE_PREF_NAME, this); 1.627 + this.prefs.removeObserver(DISABLE_THRESHOLD_PREF_NAME, this); 1.628 + this.prefs.removeObserver(CACHE_EXPIRATION_TIME_PREF_NAME, this); 1.629 + 1.630 + if (this.cacheTimer) 1.631 + this.cacheTimer.cancel(); 1.632 + }, 1.633 + 1.634 + get enabled() { 1.635 + return this._enabled; 1.636 + }, 1.637 + 1.638 + set enabled(enable) { 1.639 + if (this._enabled == enable) 1.640 + return; 1.641 + 1.642 + this._enabled = enable; 1.643 + 1.644 + this.windows.forEach(function (win) { 1.645 + win.enabled = enable; 1.646 + }); 1.647 + }, 1.648 + 1.649 + addPreview: function (preview) { 1.650 + this.previews.push(preview); 1.651 + this.checkPreviewCount(); 1.652 + }, 1.653 + 1.654 + removePreview: function (preview) { 1.655 + let idx = this.previews.indexOf(preview); 1.656 + this.previews.splice(idx, 1); 1.657 + this.checkPreviewCount(); 1.658 + }, 1.659 + 1.660 + checkPreviewCount: function () { 1.661 + if (this.previews.length > this.maxpreviews) 1.662 + this.enabled = false; 1.663 + else 1.664 + this.enabled = this._prefenabled; 1.665 + }, 1.666 + 1.667 + onOpenWindow: function (win) { 1.668 + // This occurs when the taskbar service is not available (xp, vista) 1.669 + if (!this.available) 1.670 + return; 1.671 + 1.672 + win.gTaskbarTabGroup = new TabWindow(win); 1.673 + }, 1.674 + 1.675 + onCloseWindow: function (win) { 1.676 + // This occurs when the taskbar service is not available (xp, vista) 1.677 + if (!this.available) 1.678 + return; 1.679 + 1.680 + win.gTaskbarTabGroup.destroy(); 1.681 + delete win.gTaskbarTabGroup; 1.682 + 1.683 + if (this.windows.length == 0) 1.684 + this.destroy(); 1.685 + }, 1.686 + 1.687 + resetCacheTimer: function () { 1.688 + this.cacheTimer.cancel(); 1.689 + this.cacheTimer.init(this, 1000*this.cacheLifespan, Ci.nsITimer.TYPE_ONE_SHOT); 1.690 + }, 1.691 + 1.692 + //// nsIObserver 1.693 + observe: function (aSubject, aTopic, aData) { 1.694 + switch (aTopic) { 1.695 + case "nsPref:changed": 1.696 + if (aData == CACHE_EXPIRATION_TIME_PREF_NAME) 1.697 + break; 1.698 + 1.699 + if (aData == TOGGLE_PREF_NAME) 1.700 + this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME); 1.701 + else if (aData == DISABLE_THRESHOLD_PREF_NAME) 1.702 + this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME); 1.703 + // Might need to enable/disable ourselves 1.704 + this.checkPreviewCount(); 1.705 + break; 1.706 + case "timer-callback": 1.707 + this.previews.forEach(function (preview) { 1.708 + let controller = preview.controller.wrappedJSObject; 1.709 + controller.resetCanvasPreview(); 1.710 + }); 1.711 + break; 1.712 + } 1.713 + } 1.714 +}; 1.715 + 1.716 +XPCOMUtils.defineLazyGetter(AeroPeek, "cacheTimer", function () 1.717 + Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer) 1.718 +); 1.719 + 1.720 +XPCOMUtils.defineLazyServiceGetter(AeroPeek, "prefs", 1.721 + "@mozilla.org/preferences-service;1", 1.722 + "nsIPrefBranch"); 1.723 + 1.724 +AeroPeek.initialize();