@ -3,12 +3,10 @@
* License , v . 2.0 . If a copy of the MPL was not distributed with this
* file , You can obtain one at http : //mozilla.org/MPL/2.0/. */
/ *
* This module implements the front end behavior for AeroPeek . Starting in
* Windows Vista , the taskbar began showing live thumbnail previews of windows
* when the user hovered over the window icon in the taskbar . Starting with
* Windows 7 , the taskbar allows an application to expose its tabbed interface
* in the taskbar by showing thumbnail previews rather than the default window
* preview . Additionally , when a user hovers over a thumbnail ( tab or window ) ,
* This module implements the front end behavior for AeroPeek . The taskbar
* allows an application to expose its tabbed interface by showing thumbnail
* previews rather than the default window preview .
* Additionally , when a user hovers over a thumbnail ( tab or window ) ,
* they are shown a live preview of the window ( or tab + its containing window ) .
*
* In Windows 7 , a title , icon , close button and optional toolbar are shown for
@ -31,7 +29,7 @@
* Screen real estate is limited so when there are too many thumbnails to fit
* on the screen , the taskbar stops displaying thumbnails and instead displays
* just the title , icon and close button in a similar fashion to previous
* versions of the taskbar . If there are still too many previews to fit on the
* versions of the taskbar . If there are still too many previews to fit on the
* screen , the taskbar resorts to a scroll up and scroll down button pair to let
* the user scroll through the list of tabs . Since this is undoubtedly
* inconvenient for users with many tabs , the AeroPeek objects turns off all of
@ -47,9 +45,11 @@ const Cc = Components.classes;
const Ci = Components . interfaces ;
const Cu = Components . utils ;
Cu . import ( "resource://gre/modules/XPCOMUtils.jsm" ) ;
Cu . import ( "resource://gre/modules/NetUtil.jsm" ) ;
Cu . import ( "resource://gre/modules/PlacesUtils.jsm" ) ;
Cu . import ( "resource://gre/modules/PrivateBrowsingUtils.jsm" ) ;
Cu . import ( "resource://gre/modules/Services.jsm" ) ;
Cu . import ( "resource://gre/modules/XPCOMUtils.jsm" ) ;
// Pref to enable/disable preview-per-tab
const TOGGLE _PREF _NAME = "browser.taskbar.previews.enable" ;
@ -60,17 +60,15 @@ const CACHE_EXPIRATION_TIME_PREF_NAME = "browser.taskbar.previews.cachetime";
const WINTASKBAR _CONTRACTID = "@mozilla.org/windows-taskbar;1" ;
////////////////////////////////////////////////////////////////////////////////
//// Various utility properties
// Various utility properties
XPCOMUtils . defineLazyServiceGetter ( this , "imgTools" ,
"@mozilla.org/image/tools;1" ,
"imgITools" ) ;
XPCOMUtils . defineLazyServiceGetter ( this , "faviconSvc" ,
"@mozilla.org/browser/favicon-service;1" ,
"nsIFaviconService" ) ;
XPCOMUtils . defineLazyModuleGetter ( this , "PageThumbs" ,
"resource://gre/modules/PageThumbs.jsm" ) ;
// nsIURI -> imgIContainer
function _imageFromURI ( doc , uri , privateMode , callback ) {
function _imageFromURI ( uri , privateMode , callback ) {
let channel = NetUtil . newChannel ( {
uri : uri ,
loadUsingSystemPrincipal : true ,
@ -93,19 +91,20 @@ function _imageFromURI(doc, uri, privateMode, callback) {
} catch ( e ) {
// We failed, so use the default favicon (only if this wasn't the default
// favicon).
let defaultURI = faviconSvc . defaultFavicon ;
let defaultURI = PlacesUtils . favicons . defaultFavicon ;
if ( ! defaultURI . equals ( uri ) )
_imageFromURI ( doc , d efaultURI , privateMode , callback ) ;
_imageFromURI ( defaultURI , privateMode , callback ) ;
}
} ) ;
}
// string? -> imgIContainer
function getFaviconAsImage ( doc , iconurl , privateMode , callback ) {
if ( iconurl )
_imageFromURI ( doc , NetUtil . newURI ( iconurl ) , privateMode , callback ) ;
else
_imageFromURI ( doc , faviconSvc . defaultFavicon , privateMode , callback ) ;
function getFaviconAsImage ( iconurl , privateMode , callback ) {
if ( iconurl ) {
_imageFromURI ( NetUtil . newURI ( iconurl ) , privateMode , callback ) ;
} else {
_imageFromURI ( PlacesUtils . favicons . defaultFavicon , privateMode , callback ) ;
}
}
// Snaps the given rectangle to be pixel-aligned at the given scale
@ -121,16 +120,17 @@ function snapRectAtScale(r, scale) {
r . height = height / scale ;
}
////////////////////////////////////////////////////////////////////////////////
//// PreviewController
// PreviewController
/ *
* This class manages the behavior of the preview .
*
* To give greater performance when drawing , the dirty areas of the content
* window are tracked and drawn on demand into a canvas of the same size .
* This provides a great increase in responsiveness when drawing a preview
* for unchanged ( or even only slightly changed ) tabs .
* This class manages the behavior of thumbnails and previews . It has the following
* responsibilities :
* 1 ) Responding to requests from Windows taskbar for a thumbnail or window
* preview .
* 2 ) Listening for DOM events that result in a thumbnail or window preview needing
* to be refreshed , and communicating this to the taskbar .
* 3 ) Handling queryies and returning new thumbnail or window preview images to the
* taskbar through PageThumbs .
*
* @ param win
* The TabWindow ( see below ) that owns the preview that this controls
@ -143,206 +143,182 @@ function PreviewController(win, tab) {
this . linkedBrowser = tab . linkedBrowser ;
this . preview = this . win . createTabPreview ( this ) ;
this . linkedBrowser . addEventListener ( "MozAfterPaint" , this , false ) ;
this . tab . addEventListener ( "TabAttrModified" , this , false ) ;
XPCOMUtils . defineLazyGetter ( this , "canvasPreview" , function ( ) {
let canvas = this . win . win . document . createElementNS ( "http://www.w3.org/1999/xhtml" , "canvas" ) ;
let canvas = PageThumbs . createCanvas ( ) ;
canvas . mozOpaque = true ;
return canvas ;
} ) ;
XPCOMUtils . defineLazyGetter ( this , "dirtyRegion" ,
function ( ) {
let dirtyRegion = Cc [ "@mozilla.org/gfx/region;1" ]
. createInstance ( Ci . nsIScriptableRegion ) ;
dirtyRegion . init ( ) ;
return dirtyRegion ;
} ) ;
XPCOMUtils . defineLazyGetter ( this , "winutils" ,
function ( ) {
let win = tab . linkedBrowser . contentWindow ;
return win . QueryInterface ( Ci . nsIInterfaceRequestor )
. getInterface ( Ci . nsIDOMWindowUtils ) ;
} ) ;
}
PreviewController . prototype = {
QueryInterface : XPCOMUtils . generateQI ( [ Ci . nsITaskbarPreviewController ,
Ci . nsIDOMEventListener ] ) ,
destroy : function ( ) {
this . tab . removeEventListener ( "TabAttrModified" , this , false ) ;
this . linkedBrowser . removeEventListener ( "MozAfterPaint" , this , false ) ;
// Break cycles, otherwise we end up leaking the window with everything
// attached to it.
delete this . win ;
delete this . preview ;
delete this . dirtyRegion ;
} ,
get wrappedJSObject ( ) {
return this ;
} ,
get dirtyRects ( ) {
let rectstream = this . dirtyRegion . getRects ( ) ;
if ( ! rectstream )
return [ ] ;
let rects = [ ] ;
for ( let i = 0 ; i < rectstream . length ; i += 4 ) {
let r = { x : rectstream [ i ] ,
y : rectstream [ i + 1 ] ,
width : rectstream [ i + 2 ] ,
height : rectstream [ i + 3 ] } ;
rects . push ( r ) ;
}
return rects ;
} ,
// Resizes the canvasPreview to 0x0, essentially freeing its memory.
// updateCanvasPreview() will detect the size mismatch as a resize event
// the next time it is called.
resetCanvasPreview : function ( ) {
this . canvasPreview . width = 0 ;
this . canvasPreview . height = 0 ;
} ,
/ * *
* Set the canvas dimensions .
* /
resizeCanvasPreview : function ( aRequestedWidth , aRequestedHeight ) {
this . canvasPreview . width = aRequestedWidth ;
this . canvasPreview . height = aRequestedHeight ;
} ,
get zoom ( ) {
// Note that winutils.fullZoom accounts for "quantization" of the zoom factor
// from nsIMarkupDocumentViewer due to conversion through appUnits.
// from nsICont entViewer due to conversion through appUnits.
// We do -not- want screenPixelsPerCSSPixel here, because that would -also-
// incorporate any scaling that is applied due to hi-dpi resolution options.
return this . winutils . fullZoom ;
} ,
// Updates the controller's canvas with the parts of the <browser> that need
// to be redrawn.
updateCanvasPreview : function ( ) {
let win = this . linkedBrowser . contentWindow ;
let bx = this . linkedBrowser . boxObject ;
// Check for resize
if ( bx . width != this . canvasPreview . width ||
bx . height != this . canvasPreview . height ) {
// Invalidate the entire area and repaint
this . onTabPaint ( { left : 0 , top : 0 , right : win . innerWidth , bottom : win . innerHeight } ) ;
this . canvasPreview . width = bx . width ;
this . canvasPreview . height = bx . height ;
}
return this . tab . linkedBrowser . fullZoom ;
} ,
// Draw dirty regions
let ctx = this . canvasPreview . getContext ( "2d" ) ;
let scale = this . zoom ;
let flags = this . canvasPreviewFlags ;
// The dirty region may include parts that are offscreen so we clip to the
// canvas area.
this . dirtyRegion . intersectRect ( 0 , 0 , win . innerWidth , win . innerHeight ) ;
this . dirtyRects . forEach ( function ( r ) {
// We need to snap the rectangle to be pixel aligned in the destination
// coordinate space. Otherwise natively themed widgets might not draw.
snapRectAtScale ( r , scale ) ;
let x = r . x ;
let y = r . y ;
let width = r . width ;
let height = r . height ;
get screenPixelsPerCSSPixel ( ) {
let chromeWin = this . tab . ownerGlobal ;
let windowUtils = chromeWin . getInterface ( Ci . nsIDOMWindowUtils ) ;
return windowUtils . screenPixelsPerCSSPixel ;
} ,
ctx . save ( ) ;
ctx . scale ( scale , scale ) ;
ctx . translate ( x , y ) ;
ctx . drawWindow ( win , x , y , width , height , "white" , flags ) ;
ctx . restore ( ) ;
} ) ;
this . dirtyRegion . setToRect ( 0 , 0 , 0 , 0 ) ;
get browserDims ( ) {
return this . tab . linkedBrowser . getBoundingClientRect ( ) ;
} ,
cacheBrowserDims : function ( ) {
let dims = this . browserDims ;
this . _cachedWidth = dims . width ;
this . _cachedHeight = dims . height ;
} ,
testCacheBrowserDims : function ( ) {
let dims = this . browserDims ;
return this . _cachedWidth == dims . width &&
this . _cachedHeight == dims . height ;
} ,
/ * *
* Capture a new thumbnail image for this preview . Called by the controller
* in response to a request for a new thumbnail image .
* /
updateCanvasPreview : function ( aFullScale , aCallback ) {
// Update our cached browser dims so that delayed resize
// events don't trigger another invalidation if this tab becomes active.
this . cacheBrowserDims ( ) ;
PageThumbs . captureToCanvas ( this . linkedBrowser , this . canvasPreview ,
aCallback , { fullScale : aFullScale } ) ;
// If we're updating the canvas, then we're in the middle of a peek so
// don't discard the cache of previews.
AeroPeek . resetCacheTimer ( ) ;
} ,
onTabPaint : function ( rect ) {
let x = Math . floor ( rect . left ) ,
y = Math . floor ( rect . top ) ,
width = Math . ceil ( rect . right ) - x ,
height = Math . ceil ( rect . bottom ) - y ;
this . dirtyRegion . unionRect ( x , y , width , height ) ;
} ,
updateTitleAndTooltip : function ( ) {
let title = this . win . tabbrowser . getWindowTitleForBrowser ( this . linkedBrowser ) ;
this . preview . title = title ;
this . preview . tooltip = title ;
} ,
//////////////////////////////////////////////////////////////////////////////
//// nsITaskbarPreviewController
// nsITaskbarPreviewController
// window width and height, not browser
get width ( ) {
return this . win . width ;
} ,
// window width and height, not browser
get height ( ) {
return this . win . height ;
} ,
get thumbnailAspectRatio ( ) {
let boxObject = this . tab . linkedBrowser . boxObject ;
let browserDims = this . browserDims ;
// Avoid returning 0
let tabWidth = boxObject . width || 1 ;
let tabWidth = browserDims . width || 1 ;
// Avoid divide by 0
let tabHeight = boxObject . height || 1 ;
let tabHeight = browserDims . height || 1 ;
return tabWidth / tabHeight ;
} ,
drawPreview : function ( ctx ) {
let self = this ;
this . win . tabbrowser . previewTab ( this . tab , function ( ) self . previewTabCallback ( ctx ) ) ;
// We must avoid having the frame drawn around the window. See bug 520807
return false ;
} ,
previewTabCallback : function ( ctx ) {
// This will extract the resolution-scale component of the scaling we need,
// which should be applied to both chrome and content;
// the page zoom component is applied (to content only) within updateCanvasPreview.
let scale = this . winutils . screenPixelsPerCSSPixel / this . winutils . fullZoom ;
ctx . save ( ) ;
ctx . scale ( scale , scale ) ;
let width = this . win . width ;
let height = this . win . height ;
// Draw our toplevel window
ctx . drawWindow ( this . win . win , 0 , 0 , width , height , "transparent" ) ;
// XXX (jfkthame): Pending tabs don't seem to draw with the proper scaling
// unless we use this block of code; but doing this for "normal" (loaded) tabs
// results in blurry rendering on hidpi systems, so we avoid it if possible.
// I don't understand why pending and loaded tabs behave differently here...
// (see bug 857061).
if ( this . tab . hasAttribute ( "pending" ) ) {
// Compositor, where art thou?
// Draw the tab content on top of the toplevel window
this . updateCanvasPreview ( ) ;
let boxObject = this . linkedBrowser . boxObject ;
ctx . translate ( boxObject . x , boxObject . y ) ;
ctx . drawImage ( this . canvasPreview , 0 , 0 ) ;
}
/ * *
* Responds to taskbar requests for window previews . Returns the results asynchronously
* through updateCanvasPreview .
*
* @ param aTaskbarCallback nsITaskbarPreviewCallback results callback
* /
requestPreview : function ( aTaskbarCallback ) {
// Grab a high res content preview
this . resetCanvasPreview ( ) ;
this . updateCanvasPreview ( true , ( aPreviewCanvas ) => {
let winWidth = this . win . width ;
let winHeight = this . win . height ;
ctx . restore ( ) ;
} ,
let composite = PageThumbs . createCanvas ( ) ;
// Use transparency, Aero glass is drawn black without it.
composite . mozOpaque = false ;
drawThumbnail : function ( ctx , width , height ) {
this . updateCanvasPreview ( ) ;
let ctx = composite . getContext ( '2d' ) ;
let scale = this . screenPixelsPerCSSPixel / this . zoom ;
let scale = width / this . linkedBrowser . boxObject . width ;
ctx . scale ( scale , scale ) ;
ctx . drawImage ( this . canvasPreview , 0 , 0 ) ;
composite . width = winWidth * scale ;
composite . height = winHeight * scale ;
// Don't draw a frame around the thumbnail
return false ;
ctx . save ( ) ;
ctx . scale ( scale , scale ) ;
// Draw chrome. Note we currently do not get scrollbars for remote frames
// in the image above.
ctx . drawWindow ( this . win . win , 0 , 0 , winWidth , winHeight , "rgba(0,0,0,0)" ) ;
// Draw the content are into the composite canvas at the right location.
ctx . drawImage ( aPreviewCanvas , this . browserDims . x , this . browserDims . y ,
aPreviewCanvas . width , aPreviewCanvas . height ) ;
ctx . restore ( ) ;
// Deliver the resulting composite canvas to Windows
this . win . tabbrowser . previewTab ( this . tab , function ( ) {
aTaskbarCallback . done ( composite , false ) ;
} ) ;
} ) ;
} ,
/ * *
* Responds to taskbar requests for tab thumbnails . Returns the results asynchronously
* through updateCanvasPreview .
*
* Note Windows requests a specific width and height here , if the resulting thumbnail
* does not match these dimensions thumbnail display will fail .
*
* @ param aTaskbarCallback nsITaskbarPreviewCallback results callback
* @ param aRequestedWidth width of the requested thumbnail
* @ param aRequestedHeight height of the requested thumbnail
* /
requestThumbnail : function ( aTaskbarCallback , aRequestedWidth , aRequestedHeight ) {
this . resizeCanvasPreview ( aRequestedWidth , aRequestedHeight ) ;
this . updateCanvasPreview ( false , ( aThumbnailCanvas ) => {
aTaskbarCallback . done ( aThumbnailCanvas , false ) ;
} ) ;
} ,
// Event handling
onClose : function ( ) {
this . win . tabbrowser . removeTab ( this . tab ) ;
} ,
@ -355,22 +331,9 @@ PreviewController.prototype = {
return true ;
} ,
//// nsIDOMEventListener
// nsIDOMEventListener
handleEvent : function ( evt ) {
switch ( evt . type ) {
case "MozAfterPaint" :
if ( evt . originalTarget === this . linkedBrowser . contentWindow ) {
let clientRects = evt . clientRects ;
let length = clientRects . length ;
for ( let i = 0 ; i < length ; i ++ ) {
let r = clientRects . item ( i ) ;
this . onTabPaint ( r ) ;
}
}
let preview = this . preview ;
if ( preview . visible )
preview . invalidate ( ) ;
break ;
case "TabAttrModified" :
this . updateTitleAndTooltip ( ) ;
break ;
@ -386,14 +349,13 @@ XPCOMUtils.defineLazyGetter(PreviewController.prototype, "canvasPreviewFlags",
| canvasInterface . DRAWWINDOW _DO _NOT _FLUSH ;
} ) ;
////////////////////////////////////////////////////////////////////////////////
//// TabWindow
// TabWindow
/ *
* This class monitors a browser window for changes to its tabs
*
* @ param win
* The nsIDOMWindow browser window
* The nsIDOMWindow browser window
* /
function TabWindow ( win ) {
this . win = win ;
@ -403,6 +365,10 @@ function TabWindow(win) {
for ( let i = 0 ; i < this . tabEvents . length ; i ++ )
this . tabbrowser . tabContainer . addEventListener ( this . tabEvents [ i ] , this , false ) ;
for ( let i = 0 ; i < this . winEvents . length ; i ++ )
this . win . addEventListener ( this . winEvents [ i ] , this , false ) ;
this . tabbrowser . addTabsProgressListener ( this ) ;
AeroPeek . windows . push ( this ) ;
@ -416,7 +382,10 @@ function TabWindow(win) {
TabWindow . prototype = {
_enabled : false ,
_cachedWidth : 0 ,
_cachedHeight : 0 ,
tabEvents : [ "TabOpen" , "TabClose" , "TabSelect" , "TabMove" ] ,
winEvents : [ "resize" ] ,
destroy : function ( ) {
this . _destroying = true ;
@ -424,6 +393,10 @@ TabWindow.prototype = {
let tabs = this . tabbrowser . tabs ;
this . tabbrowser . removeTabsProgressListener ( this ) ;
for ( let i = 0 ; i < this . winEvents . length ; i ++ )
this . win . removeEventListener ( this . winEvents [ i ] , this , false ) ;
for ( let i = 0 ; i < this . tabEvents . length ; i ++ )
this . tabbrowser . tabContainer . removeEventListener ( this . tabEvents [ i ] , this , false ) ;
@ -442,6 +415,15 @@ TabWindow.prototype = {
return this . win . innerHeight ;
} ,
cacheDims : function ( ) {
this . _cachedWidth = this . width ;
this . _cachedHeight = this . height ;
} ,
testCacheDims : function ( ) {
return this . _cachedWidth == this . width && this . _cachedHeight == this . height ;
} ,
// Invoked when the given tab is added to this window
newTab : function ( tab ) {
let controller = new PreviewController ( this , tab ) ;
@ -461,18 +443,8 @@ TabWindow.prototype = {
let preview = AeroPeek . taskbar . createTaskbarTabPreview ( docShell , controller ) ;
preview . visible = AeroPeek . enabled ;
preview . active = this . tabbrowser . selectedTab == controller . tab ;
// Grab the default favicon
getFaviconAsImage (
controller . linkedBrowser . contentWindow . document ,
null ,
PrivateBrowsingUtils . isWindowPrivate ( this . win ) ,
function ( img ) {
// It is possible that we've already gotten the real favicon, so make sure
// we have not set one before setting this default one.
if ( ! preview . icon )
preview . icon = img ;
} ) ;
this . onLinkIconAvailable ( controller . tab . linkedBrowser ,
controller . tab . getAttribute ( "image" ) ) ;
return preview ;
} ,
@ -484,8 +456,6 @@ TabWindow.prototype = {
preview . move ( null ) ;
preview . controller . wrappedJSObject . destroy ( ) ;
// We don't want to splice from the array if the tabs aren't being removed
// from the tab bar as well (as is the case when the window closes).
this . previews . delete ( tab ) ;
AeroPeek . removePreview ( preview ) ;
} ,
@ -499,7 +469,7 @@ TabWindow.prototype = {
// Because making a tab visible requires that the tab it is next to be
// visible, it is far simpler to unset the 'next' tab and recreate them all
// at once.
for ( let [ tab , preview ] of this . previews ) {
for ( let [ , preview ] of this . previews ) {
preview . move ( null ) ;
preview . visible = enable ;
}
@ -514,27 +484,25 @@ TabWindow.prototype = {
let previews = this . previews ;
let tabs = this . tabbrowser . tabs ;
// Previews are internally stored using a map, so we need to iterate over
// the tabbrowser's array of tabs to retrieve previews in the same order.
// Tycho: let inorder = [previews.get(t) for (t of tabs) if (previews.has(t))];
// Previews are internally stored using a map, so we need to iterate the
// tabbrowser's array of tabs to retrieve previews in the same order.
let inorder = [ ] ;
for ( let t of tabs ) {
if ( previews . has ( t ) ) {
inorder . push ( previews . get ( t ) ) ;
}
}
// Since the internal taskbar array has not yet been updated, we must force
// the sorting order of our local array on it . To do so, we must walk
// the local array backwards, because otherwise we would send move requests
// in the wrong order. See bug 522610 for details.
// Since the internal taskbar array has not yet been updated we must force
// on it the sorting order of our local array. To do so we must walk
// the local array backwards, otherwise we would send move requests in the
// wrong order. See bug 522610 for details.
for ( let i = inorder . length - 1 ; i >= 0 ; i -- ) {
inorder [ i ] . move ( inorder [ i + 1 ] || null ) ;
}
} ,
//// nsIDOMEventListener
// nsIDOMEventListener
handleEvent : function ( evt ) {
let tab = evt . originalTarget ;
switch ( evt . type ) {
@ -552,28 +520,117 @@ TabWindow.prototype = {
case "TabMove" :
this . updateTabOrdering ( ) ;
break ;
case "resize" :
if ( ! AeroPeek . _prefenabled )
return ;
this . onResize ( ) ;
break ;
}
} ,
// Set or reset a timer that will invalidate visible thumbnails soon.
setInvalidationTimer : function ( ) {
if ( ! this . invalidateTimer ) {
this . invalidateTimer = Cc [ "@mozilla.org/timer;1" ] . createInstance ( Ci . nsITimer ) ;
}
this . invalidateTimer . cancel ( ) ;
// delay 1 second before invalidating
this . invalidateTimer . initWithCallback ( ( ) => {
// invalidate every preview. note the internal implementation of
// invalidate ignores thumbnails that aren't visible.
this . previews . forEach ( function ( aPreview ) {
let controller = aPreview . controller . wrappedJSObject ;
if ( ! controller . testCacheBrowserDims ( ) ) {
controller . cacheBrowserDims ( ) ;
aPreview . invalidate ( ) ;
}
} ) ;
} , 1000 , Ci . nsITimer . TYPE _ONE _SHOT ) ;
} ,
onResize : function ( ) {
// Specific to a window.
// Call invalidate on each tab thumbnail so that Windows will request an
// updated image. However don't do this repeatedly across multiple resize
// events triggered during window border drags.
if ( this . testCacheDims ( ) ) {
return ;
}
// update the window dims on our TabWindow object.
this . cacheDims ( ) ;
// invalidate soon
this . setInvalidationTimer ( ) ;
} ,
invalidateTabPreview : function ( aBrowser ) {
for ( let [ tab , preview ] of this . previews ) {
if ( aBrowser == tab . linkedBrowser ) {
preview . invalidate ( ) ;
break ;
}
}
} ,
// Browser progress listener
onLocationChange : function ( aBrowser ) {
// I'm not sure we need this, onStateChange does a really good job
// of picking up page changes.
// this.invalidateTabPreview(aBrowser);
} ,
onStateChange : function ( aBrowser , aWebProgress , aRequest , aStateFlags , aStatus ) {
if ( aStateFlags & Ci . nsIWebProgressListener . STATE _STOP &&
aStateFlags & Ci . nsIWebProgressListener . STATE _IS _NETWORK ) {
this . invalidateTabPreview ( aBrowser ) ;
}
} ,
//// Browser progress listener
directRequestProtocols : new Set ( [
"file" , "chrome" , "resource" , "about"
] ) ,
onLinkIconAvailable : function ( aBrowser , aIconURL ) {
let self = this ;
let requestURL = null ;
if ( aIconURL ) {
let shouldRequestFaviconURL = true ;
try {
let urlObject = NetUtil . newURI ( aIconURL ) ;
shouldRequestFaviconURL =
! this . directRequestProtocols . has ( urlObject . scheme ) ;
} catch ( ex ) { }
requestURL = shouldRequestFaviconURL ?
"moz-anno:favicon:" + aIconURL :
aIconURL ;
}
let isDefaultFavicon = ! requestURL ;
getFaviconAsImage (
aBrowser . contentWindow . document ,
aIconURL , PrivateBrowsingUtils . isWindowPrivate ( this . win ) ,
function ( img ) {
let index = self . tabbrowser . browsers . indexOf ( aBrowser ) ;
// Only add it if we've found the index. The tab could have closed!
requestURL ,
PrivateBrowsingUtils . isWindowPrivate ( this . win ) ,
img => {
let index = this . tabbrowser . browsers . indexOf ( aBrowser ) ;
// Only add it if we've found the index and the URI is still the same.
// The tab could have closed, and there's no guarantee the icons
// will have finished fetching 'in order'.
if ( index != - 1 ) {
let tab = self . tabbrowser . tabs [ index ] ;
self . previews . get ( tab ) . icon = img ;
let tab = this . tabbrowser . tabs [ index ] ;
let preview = this . previews . get ( tab ) ;
if ( tab . getAttribute ( "image" ) == aIconURL ||
( ! preview . icon && isDefaultFavicon ) ) {
preview . icon = img ;
}
}
} ) ;
}
) ;
}
}
////////////////////////////////////////////////////////////////////////////////
//// AeroPeek
// AeroPeek
/ *
* This object acts as global storage and external interface for this feature .
@ -582,10 +639,12 @@ TabWindow.prototype = {
this . AeroPeek = {
available : false ,
// Does the pref say we're enabled?
_prefenabled : tru e,
_ _ prefenabled: fals e,
_enabled : true ,
initialized : false ,
// nsITaskbarTabPreview array
previews : [ ] ,
@ -609,24 +668,14 @@ this.AeroPeek = {
if ( ! this . available )
return ;
this . prefs . addObserver ( TOGGLE _PREF _NAME , this , false ) ;
this . prefs . addObserver ( DISABLE _THRESHOLD _PREF _NAME , this , false ) ;
this . prefs . addObserver ( CACHE _EXPIRATION _TIME _PREF _NAME , this , false ) ;
this . cacheLifespan = this . prefs . getIntPref ( CACHE _EXPIRATION _TIME _PREF _NAME ) ;
this . maxpreviews = this . prefs . getIntPref ( DISABLE _THRESHOLD _PREF _NAME ) ;
this . prefs . addObserver ( TOGGLE _PREF _NAME , this , true ) ;
this . enabled = this . _prefenabled = this . prefs . getBoolPref ( TOGGLE _PREF _NAME ) ;
this . initialized = true ;
} ,
destroy : function destroy ( ) {
this . _enabled = false ;
this . prefs . removeObserver ( TOGGLE _PREF _NAME , this ) ;
this . prefs . removeObserver ( DISABLE _THRESHOLD _PREF _NAME , this ) ;
this . prefs . removeObserver ( CACHE _EXPIRATION _TIME _PREF _NAME , this ) ;
if ( this . cacheTimer )
this . cacheTimer . cancel ( ) ;
} ,
@ -646,6 +695,61 @@ this.AeroPeek = {
} ) ;
} ,
get _prefenabled ( ) {
return this . _ _prefenabled ;
} ,
set _prefenabled ( enable ) {
if ( enable == this . _ _prefenabled ) {
return ;
}
this . _ _prefenabled = enable ;
if ( enable ) {
this . enable ( ) ;
} else {
this . disable ( ) ;
}
} ,
_observersAdded : false ,
enable ( ) {
if ( ! this . _observersAdded ) {
this . prefs . addObserver ( DISABLE _THRESHOLD _PREF _NAME , this , true ) ;
this . prefs . addObserver ( CACHE _EXPIRATION _TIME _PREF _NAME , this , true ) ;
PlacesUtils . history . addObserver ( this , true ) ;
this . _observersAdded = true ;
}
this . cacheLifespan = this . prefs . getIntPref ( CACHE _EXPIRATION _TIME _PREF _NAME ) ;
this . maxpreviews = this . prefs . getIntPref ( DISABLE _THRESHOLD _PREF _NAME ) ;
// If the user toggled us on/off while the browser was already up
// (rather than this code running on startup because the pref was
// already set to true), we must initialize previews for open windows:
if ( this . initialized ) {
let browserWindows = Services . wm . getEnumerator ( "navigator:browser" ) ;
while ( browserWindows . hasMoreElements ( ) ) {
let win = browserWindows . getNext ( ) ;
if ( ! win . closed ) {
this . onOpenWindow ( win ) ;
}
}
}
} ,
disable ( ) {
while ( this . windows . length ) {
// We can't call onCloseWindow here because it'll bail if we're not
// enabled.
let tabWinObject = this . windows [ 0 ] ;
tabWinObject . destroy ( ) ; // This will remove us from the array.
delete tabWinObject . win . gTaskbarTabGroup ; // Tidy up the window.
}
} ,
addPreview : function ( preview ) {
this . previews . push ( preview ) ;
this . checkPreviewCount ( ) ;
@ -658,15 +762,15 @@ this.AeroPeek = {
} ,
checkPreviewCount : function ( ) {
if ( this . previews . length > this . maxpreviews )
this . enabled = false ;
else
this . enabled = this . _prefenabled ;
if ( ! this . _prefenabled ) {
return ;
}
this . enabled = this . previews . length <= this . maxpreviews ;
} ,
onOpenWindow : function ( win ) {
// This occurs when the taskbar service is not available (xp, vista)
if ( ! this . available )
if ( ! this . available || ! this . _prefenabled )
return ;
win . gTaskbarTabGroup = new TabWindow ( win ) ;
@ -674,7 +778,7 @@ this.AeroPeek = {
onCloseWindow : function ( win ) {
// This occurs when the taskbar service is not available (xp, vista)
if ( ! this . available )
if ( ! this . available || ! this . _prefenabled )
return ;
win . gTaskbarTabGroup . destroy ( ) ;
@ -689,16 +793,20 @@ this.AeroPeek = {
this . cacheTimer . init ( this , 1000 * this . cacheLifespan , Ci . nsITimer . TYPE _ONE _SHOT ) ;
} ,
//// nsIObserver
// nsIObserver
observe : function ( aSubject , aTopic , aData ) {
if ( aTopic == "nsPref:changed" && aData == TOGGLE _PREF _NAME ) {
this . _prefenabled = this . prefs . getBoolPref ( TOGGLE _PREF _NAME ) ;
}
if ( ! this . _prefenabled ) {
return ;
}
switch ( aTopic ) {
case "nsPref:changed" :
if ( aData == CACHE _EXPIRATION _TIME _PREF _NAME )
break ;
if ( aData == TOGGLE _PREF _NAME )
this . _prefenabled = this . prefs . getBoolPref ( TOGGLE _PREF _NAME ) ;
else if ( aData == DISABLE _THRESHOLD _PREF _NAME )
if ( aData == DISABLE _THRESHOLD _PREF _NAME )
this . maxpreviews = this . prefs . getIntPref ( DISABLE _THRESHOLD _PREF _NAME ) ;
// Might need to enable/disable ourselves
this . checkPreviewCount ( ) ;
@ -710,10 +818,38 @@ this.AeroPeek = {
} ) ;
break ;
}
}
} ,
/* nsINavHistoryObserver implementation */
onBeginUpdateBatch ( ) { } ,
onEndUpdateBatch ( ) { } ,
onVisit ( ) { } ,
onTitleChanged ( ) { } ,
onFrecencyChanged ( ) { } ,
onManyFrecenciesChanged ( ) { } ,
onDeleteURI ( ) { } ,
onClearHistory ( ) { } ,
onDeleteVisits ( ) { } ,
onPageChanged ( uri , changedConst , newValue ) {
if ( this . enabled && changedConst == Ci . nsINavHistoryObserver . ATTRIBUTE _FAVICON ) {
for ( let win of this . windows ) {
for ( let [ tab , ] of win . previews ) {
if ( tab . getAttribute ( "image" ) == newValue ) {
win . onLinkIconAvailable ( tab . linkedBrowser , newValue ) ;
}
}
}
}
} ,
QueryInterface : XPCOMUtils . generateQI ( [
Ci . nsISupportsWeakReference ,
Ci . nsINavHistoryObserver ,
Ci . nsIObserver
] ) ,
} ;
XPCOMUtils . defineLazyGetter ( AeroPeek , "cacheTimer" , function ( )
XPCOMUtils . defineLazyGetter ( AeroPeek , "cacheTimer" , ( ) =>
Cc [ "@mozilla.org/timer;1" ] . createInstance ( Ci . nsITimer )
) ;