Bumping manifests a=b2g-bump
[gecko.git] / browser / modules / WindowsPreviewPerTab.jsm
blob96a38b89c64339f4abb27102f8b8ae46eed7ab2e
1 /* vim: se cin sw=2 ts=2 et filetype=javascript :
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 /*
6  * This module implements the front end behavior for AeroPeek. Starting in
7  * Windows Vista, the taskbar began showing live thumbnail previews of windows
8  * when the user hovered over the window icon in the taskbar. Starting with
9  * Windows 7, the taskbar allows an application to expose its tabbed interface
10  * in the taskbar by showing thumbnail previews rather than the default window
11  * preview. Additionally, when a user hovers over a thumbnail (tab or window),
12  * they are shown a live preview of the window (or tab + its containing window).
13  *
14  * In Windows 7, a title, icon, close button and optional toolbar are shown for
15  * each preview. This feature does not make use of the toolbar. For window
16  * previews, the title is the window title and the icon the window icon. For
17  * tab previews, the title is the page title and the page's favicon. In both
18  * cases, the close button "does the right thing."
19  *
20  * The primary objects behind this feature are nsITaskbarTabPreview and
21  * nsITaskbarPreviewController. Each preview has a controller. The controller
22  * responds to the user's interactions on the taskbar and provides the required
23  * data to the preview for determining the size of the tab and thumbnail. The
24  * PreviewController class implements this interface. The preview will request
25  * the controller to provide a thumbnail or preview when the user interacts with
26  * the taskbar. To reduce the overhead of drawing the tab area, the controller
27  * implementation caches the tab's contents in a <canvas> element. If no
28  * previews or thumbnails have been requested for some time, the controller will
29  * discard its cached tab contents.
30  *
31  * Screen real estate is limited so when there are too many thumbnails to fit
32  * on the screen, the taskbar stops displaying thumbnails and instead displays
33  * just the title, icon and close button in a similar fashion to previous
34  * versions of the taskbar. If there are still too many previews to fit on the 
35  * screen, the taskbar resorts to a scroll up and scroll down button pair to let
36  * the user scroll through the list of tabs. Since this is undoubtedly
37  * inconvenient for users with many tabs, the AeroPeek objects turns off all of
38  * the tab previews. This tells the taskbar to revert to one preview per window.
39  * If the number of tabs falls below this magic threshold, the preview-per-tab
40  * behavior returns. There is no reliable way to determine when the scroll
41  * buttons appear on the taskbar, so a magic pref-controlled number determines
42  * when this threshold has been crossed.
43  */
44 this.EXPORTED_SYMBOLS = ["AeroPeek"];
46 const Cc = Components.classes;
47 const Ci = Components.interfaces;
48 const Cu = Components.utils;
50 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
51 Cu.import("resource://gre/modules/NetUtil.jsm");
52 Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
54 // Pref to enable/disable preview-per-tab
55 const TOGGLE_PREF_NAME = "browser.taskbar.previews.enable";
56 // Pref to determine the magic auto-disable threshold
57 const DISABLE_THRESHOLD_PREF_NAME = "browser.taskbar.previews.max";
58 // Pref to control the time in seconds that tab contents live in the cache
59 const CACHE_EXPIRATION_TIME_PREF_NAME = "browser.taskbar.previews.cachetime";
61 const WINTASKBAR_CONTRACTID = "@mozilla.org/windows-taskbar;1";
63 ////////////////////////////////////////////////////////////////////////////////
64 //// Various utility properties
65 XPCOMUtils.defineLazyServiceGetter(this, "ioSvc",
66                                    "@mozilla.org/network/io-service;1",
67                                    "nsIIOService");
68 XPCOMUtils.defineLazyServiceGetter(this, "imgTools",
69                                    "@mozilla.org/image/tools;1",
70                                    "imgITools");
71 XPCOMUtils.defineLazyServiceGetter(this, "faviconSvc",
72                                    "@mozilla.org/browser/favicon-service;1",
73                                    "nsIFaviconService");
75 // nsIURI -> imgIContainer
76 function _imageFromURI(uri, privateMode, callback) {
77   let channel = ioSvc.newChannelFromURI(uri);
78   try {
79     channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
80     channel.setPrivate(privateMode);
81   } catch (e) {
82     // Ignore channels which do not support nsIPrivateBrowsingChannel
83   }
84   NetUtil.asyncFetch(channel, function(inputStream, resultCode) {
85     if (!Components.isSuccessCode(resultCode))
86       return;
87     try {
88       let out_img = { value: null };
89       imgTools.decodeImageData(inputStream, channel.contentType, out_img);
90       callback(out_img.value);
91     } catch (e) {
92       // We failed, so use the default favicon (only if this wasn't the default
93       // favicon).
94       let defaultURI = faviconSvc.defaultFavicon;
95       if (!defaultURI.equals(uri))
96         _imageFromURI(defaultURI, callback);
97     }
98   });
101 // string? -> imgIContainer
102 function getFaviconAsImage(iconurl, privateMode, callback) {
103   if (iconurl)
104     _imageFromURI(NetUtil.newURI(iconurl), privateMode, callback);
105   else
106     _imageFromURI(faviconSvc.defaultFavicon, privateMode, callback);
109 // Snaps the given rectangle to be pixel-aligned at the given scale
110 function snapRectAtScale(r, scale) {
111   let x = Math.floor(r.x * scale);
112   let y = Math.floor(r.y * scale);
113   let width = Math.ceil((r.x + r.width) * scale) - x;
114   let height = Math.ceil((r.y + r.height) * scale) - y;
116   r.x = x / scale;
117   r.y = y / scale;
118   r.width = width / scale;
119   r.height = height / scale;
122 ////////////////////////////////////////////////////////////////////////////////
123 //// PreviewController
126  * This class manages the behavior of the preview.
128  * To give greater performance when drawing, the dirty areas of the content
129  * window are tracked and drawn on demand into a canvas of the same size.
130  * This provides a great increase in responsiveness when drawing a preview
131  * for unchanged (or even only slightly changed) tabs.
133  * @param win
134  *        The TabWindow (see below) that owns the preview that this controls
135  * @param tab
136  *        The <tab> that this preview is associated with
137  */
138 function PreviewController(win, tab) {
139   this.win = win;
140   this.tab = tab;
141   this.linkedBrowser = tab.linkedBrowser;
142   this.preview = this.win.createTabPreview(this);
144   this.linkedBrowser.addEventListener("MozAfterPaint", this, false);
145   this.tab.addEventListener("TabAttrModified", this, false);
147   XPCOMUtils.defineLazyGetter(this, "canvasPreview", function () {
148     let canvas = this.win.win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
149     canvas.mozOpaque = true;
150     return canvas;
151   });
153   XPCOMUtils.defineLazyGetter(this, "dirtyRegion",
154     function () {
155       let dirtyRegion = Cc["@mozilla.org/gfx/region;1"]
156                        .createInstance(Ci.nsIScriptableRegion);
157       dirtyRegion.init();
158       return dirtyRegion;
159     });
161   XPCOMUtils.defineLazyGetter(this, "winutils",
162     function () {
163       let win = tab.linkedBrowser.contentWindow;
164       return win.QueryInterface(Ci.nsIInterfaceRequestor)
165                 .getInterface(Ci.nsIDOMWindowUtils);
166   });
169 PreviewController.prototype = {
170   QueryInterface: XPCOMUtils.generateQI([Ci.nsITaskbarPreviewController,
171                                          Ci.nsIDOMEventListener]),
172   destroy: function () {
173     this.tab.removeEventListener("TabAttrModified", this, false);
174     this.linkedBrowser.removeEventListener("MozAfterPaint", this, false);
176     // Break cycles, otherwise we end up leaking the window with everything
177     // attached to it.
178     delete this.win;
179     delete this.preview;
180     delete this.dirtyRegion;
181   },
182   get wrappedJSObject() {
183     return this;
184   },
186   get dirtyRects() {
187     let rectstream = this.dirtyRegion.getRects();
188     if (!rectstream)
189       return [];
190     let rects = [];
191     for (let i = 0; i < rectstream.length; i+= 4) {
192       let r = {x:      rectstream[i],
193                y:      rectstream[i+1],
194                width:  rectstream[i+2],
195                height: rectstream[i+3]};
196       rects.push(r);
197     }
198     return rects;
199   },
201   // Resizes the canvasPreview to 0x0, essentially freeing its memory.
202   // updateCanvasPreview() will detect the size mismatch as a resize event
203   // the next time it is called.
204   resetCanvasPreview: function () {
205     this.canvasPreview.width = 0;
206     this.canvasPreview.height = 0;
207   },
209   get zoom() {
210     // Note that winutils.fullZoom accounts for "quantization" of the zoom factor
211     // from nsIContentViewer due to conversion through appUnits.
212     // We do -not- want screenPixelsPerCSSPixel here, because that would -also-
213     // incorporate any scaling that is applied due to hi-dpi resolution options.
214     return this.winutils.fullZoom;
215   },
217   // Updates the controller's canvas with the parts of the <browser> that need
218   // to be redrawn.
219   updateCanvasPreview: function () {
220     let win = this.linkedBrowser.contentWindow;
221     let bx = this.linkedBrowser.boxObject;
222     // Check for resize
223     if (bx.width != this.canvasPreview.width ||
224         bx.height != this.canvasPreview.height) {
225       // Invalidate the entire area and repaint
226       this.onTabPaint({left:0, top:0, right:win.innerWidth, bottom:win.innerHeight});
227       this.canvasPreview.width = bx.width;
228       this.canvasPreview.height = bx.height;
229     }
231     // Draw dirty regions
232     let ctx = this.canvasPreview.getContext("2d");
233     let scale = this.zoom;
235     let flags = this.canvasPreviewFlags;
236     // The dirty region may include parts that are offscreen so we clip to the
237     // canvas area.
238     this.dirtyRegion.intersectRect(0, 0, win.innerWidth, win.innerHeight);
239     this.dirtyRects.forEach(function (r) {
240       // We need to snap the rectangle to be pixel aligned in the destination
241       // coordinate space. Otherwise natively themed widgets might not draw.
242       snapRectAtScale(r, scale);
243       let x = r.x;
244       let y = r.y;
245       let width = r.width;
246       let height = r.height;
248       ctx.save();
249       ctx.scale(scale, scale);
250       ctx.translate(x, y);
251       ctx.drawWindow(win, x, y, width, height, "white", flags);
252       ctx.restore();
253     });
254     this.dirtyRegion.setToRect(0,0,0,0);
256     // If we're updating the canvas, then we're in the middle of a peek so
257     // don't discard the cache of previews.
258     AeroPeek.resetCacheTimer();
259   },
261   onTabPaint: function (rect) {
262     let x = Math.floor(rect.left),
263         y = Math.floor(rect.top),
264         width = Math.ceil(rect.right) - x,
265         height = Math.ceil(rect.bottom) - y;
266     this.dirtyRegion.unionRect(x, y, width, height);
267   },
269   updateTitleAndTooltip: function () {
270     let title = this.win.tabbrowser.getWindowTitleForBrowser(this.linkedBrowser);
271     this.preview.title = title;
272     this.preview.tooltip = title;
273   },
275   //////////////////////////////////////////////////////////////////////////////
276   //// nsITaskbarPreviewController 
278   get width() {
279     return this.win.width;
280   },
282   get height() {
283     return this.win.height;
284   },
286   get thumbnailAspectRatio() {
287     let boxObject = this.tab.linkedBrowser.boxObject;
288     // Avoid returning 0
289     let tabWidth = boxObject.width || 1;
290     // Avoid divide by 0
291     let tabHeight = boxObject.height || 1;
292     return tabWidth / tabHeight;
293   },
295   drawPreview: function (ctx) {
296     let self = this;
297     this.win.tabbrowser.previewTab(this.tab, function () self.previewTabCallback(ctx));
299     // We must avoid having the frame drawn around the window. See bug 520807
300     return false;
301   },
303   previewTabCallback: function (ctx) {
304     // This will extract the resolution-scale component of the scaling we need,
305     // which should be applied to both chrome and content;
306     // the page zoom component is applied (to content only) within updateCanvasPreview.
307     let scale = this.winutils.screenPixelsPerCSSPixel / this.winutils.fullZoom;
308     ctx.save();
309     ctx.scale(scale, scale);
310     let width = this.win.width;
311     let height = this.win.height;
312     // Draw our toplevel window
313     ctx.drawWindow(this.win.win, 0, 0, width, height, "transparent");
315     // XXX (jfkthame): Pending tabs don't seem to draw with the proper scaling
316     // unless we use this block of code; but doing this for "normal" (loaded) tabs
317     // results in blurry rendering on hidpi systems, so we avoid it if possible.
318     // I don't understand why pending and loaded tabs behave differently here...
319     // (see bug 857061).
320     if (this.tab.hasAttribute("pending")) {
321       // Compositor, where art thou?
322       // Draw the tab content on top of the toplevel window
323       this.updateCanvasPreview();
325       let boxObject = this.linkedBrowser.boxObject;
326       ctx.translate(boxObject.x, boxObject.y);
327       ctx.drawImage(this.canvasPreview, 0, 0);
328     }
330     ctx.restore();
331   },
333   drawThumbnail: function (ctx, width, height) {
334     this.updateCanvasPreview();
336     let scale = width/this.linkedBrowser.boxObject.width;
337     ctx.scale(scale, scale);
338     ctx.drawImage(this.canvasPreview, 0, 0);
340     // Don't draw a frame around the thumbnail
341     return false;
342   },
344   onClose: function () {
345     this.win.tabbrowser.removeTab(this.tab);
346   },
348   onActivate: function () {
349     this.win.tabbrowser.selectedTab = this.tab;
351     // Accept activation - this will restore the browser window
352     // if it's minimized
353     return true;
354   },
356   //// nsIDOMEventListener
357   handleEvent: function (evt) {
358     switch (evt.type) {
359       case "MozAfterPaint":
360         if (evt.originalTarget === this.linkedBrowser.contentWindow) {
361           let clientRects = evt.clientRects;
362           let length = clientRects.length;
363           for (let i = 0; i < length; i++) {
364             let r = clientRects.item(i);
365             this.onTabPaint(r);
366           }
367         }
368         let preview = this.preview;
369         if (preview.visible)
370           preview.invalidate();
371         break;
372       case "TabAttrModified":
373         this.updateTitleAndTooltip();
374         break;
375     }
376   }
379 XPCOMUtils.defineLazyGetter(PreviewController.prototype, "canvasPreviewFlags",
380   function () { let canvasInterface = Ci.nsIDOMCanvasRenderingContext2D;
381                 return canvasInterface.DRAWWINDOW_DRAW_VIEW
382                      | canvasInterface.DRAWWINDOW_DRAW_CARET
383                      | canvasInterface.DRAWWINDOW_ASYNC_DECODE_IMAGES
384                      | canvasInterface.DRAWWINDOW_DO_NOT_FLUSH;
387 ////////////////////////////////////////////////////////////////////////////////
388 //// TabWindow
391  * This class monitors a browser window for changes to its tabs
393  * @param win
394  *        The nsIDOMWindow browser window 
395  */
396 function TabWindow(win) {
397   this.win = win;
398   this.tabbrowser = win.gBrowser;
400   this.previews = new Map();
402   for (let i = 0; i < this.tabEvents.length; i++)
403     this.tabbrowser.tabContainer.addEventListener(this.tabEvents[i], this, false);
404   this.tabbrowser.addTabsProgressListener(this);
406   for (let i = 0; i < this.winEvents.length; i++)
407     this.win.addEventListener(this.winEvents[i], this, false);
409   AeroPeek.windows.push(this);
410   let tabs = this.tabbrowser.tabs;
411   for (let i = 0; i < tabs.length; i++)
412     this.newTab(tabs[i]);
414   this.updateTabOrdering();
415   AeroPeek.checkPreviewCount();
418 TabWindow.prototype = {
419   _enabled: false,
420   tabEvents: ["TabOpen", "TabClose", "TabSelect", "TabMove"],
421   winEvents: ["tabviewshown", "tabviewhidden"],
423   destroy: function () {
424     this._destroying = true;
426     let tabs = this.tabbrowser.tabs;
428     this.tabbrowser.removeTabsProgressListener(this);
429     for (let i = 0; i < this.tabEvents.length; i++)
430       this.tabbrowser.tabContainer.removeEventListener(this.tabEvents[i], this, false);
432     for (let i = 0; i < this.winEvents.length; i++)
433       this.win.removeEventListener(this.winEvents[i], this, false);
435     for (let i = 0; i < tabs.length; i++)
436       this.removeTab(tabs[i]);
438     let idx = AeroPeek.windows.indexOf(this.win.gTaskbarTabGroup);
439     AeroPeek.windows.splice(idx, 1);
440     AeroPeek.checkPreviewCount();
441   },
443   get width () {
444     return this.win.innerWidth;
445   },
446   get height () {
447     return this.win.innerHeight;
448   },
450   // Invoked when the given tab is added to this window
451   newTab: function (tab) {
452     let controller = new PreviewController(this, tab);
453     // It's OK to add the preview now while the favicon still loads.
454     this.previews.set(tab, controller.preview);
455     AeroPeek.addPreview(controller.preview);
456     // updateTitleAndTooltip relies on having controller.preview which is lazily resolved.
457     // Now that we've updated this.previews, it will resolve successfully.
458     controller.updateTitleAndTooltip();
459   },
461   createTabPreview: function (controller) {
462     let docShell = this.win
463                   .QueryInterface(Ci.nsIInterfaceRequestor)
464                   .getInterface(Ci.nsIWebNavigation)
465                   .QueryInterface(Ci.nsIDocShell);
466     let preview = AeroPeek.taskbar.createTaskbarTabPreview(docShell, controller);
467     preview.visible = AeroPeek.enabled;
468     preview.active = this.tabbrowser.selectedTab == controller.tab;
469     // Grab the default favicon
470     getFaviconAsImage(null, PrivateBrowsingUtils.isWindowPrivate(this.win), function (img) {
471       // It is possible that we've already gotten the real favicon, so make sure
472       // we have not set one before setting this default one.
473       if (!preview.icon)
474         preview.icon = img;
475     });
477     return preview;
478   },
480   // Invoked when the given tab is closed
481   removeTab: function (tab) {
482     let preview = this.previewFromTab(tab);
483     preview.active = false;
484     preview.visible = false;
485     preview.move(null);
486     preview.controller.wrappedJSObject.destroy();
488     this.previews.delete(tab);
489     AeroPeek.removePreview(preview);
490   },
492   get enabled () {
493     return this._enabled;
494   },
496   set enabled (enable) {
497     this._enabled = enable;
498     // Because making a tab visible requires that the tab it is next to be
499     // visible, it is far simpler to unset the 'next' tab and recreate them all
500     // at once.
501     for (let [tab, preview] of this.previews) {
502       preview.move(null);
503       preview.visible = enable;
504     }
505     this.updateTabOrdering();
506   },
508   previewFromTab: function (tab) {
509     return this.previews.get(tab);
510   },
512   updateTabOrdering: function () {
513     let previews = this.previews;
514     let tabs = this.tabbrowser.tabs;
516     // Previews are internally stored using a map, so we need to iterate the
517     // tabbrowser's array of tabs to retrieve previews in the same order.
518     let inorder = [previews.get(t) for (t of tabs) if (previews.has(t))];
520     // Since the internal taskbar array has not yet been updated we must force
521     // on it the sorting order of our local array.  To do so we must walk
522     // the local array backwards, otherwise we would send move requests in the
523     // wrong order.  See bug 522610 for details.
524     for (let i = inorder.length - 1; i >= 0; i--) {
525       inorder[i].move(inorder[i + 1] || null);
526     }
527   },
529   //// nsIDOMEventListener
530   handleEvent: function (evt) {
531     let tab = evt.originalTarget;
532     switch (evt.type) {
533       case "TabOpen":
534         this.newTab(tab);
535         this.updateTabOrdering();
536         break;
537       case "TabClose":
538         this.removeTab(tab);
539         this.updateTabOrdering();
540         break;
541       case "TabSelect":
542         this.previewFromTab(tab).active = true;
543         break;
544       case "TabMove":
545         this.updateTabOrdering();
546         break;
547       case "tabviewshown":
548         this.enabled = false;
549         break;
550       case "tabviewhidden":
551         if (!AeroPeek._prefenabled)
552           return;
553         this.enabled = true;
554         break;
555     }
556   },
558   //// Browser progress listener
559   onLinkIconAvailable: function (aBrowser, aIconURL) {
560     let self = this;
561     getFaviconAsImage(aIconURL, PrivateBrowsingUtils.isWindowPrivate(this.win), function (img) {
562       let index = self.tabbrowser.browsers.indexOf(aBrowser);
563       // Only add it if we've found the index.  The tab could have closed!
564       if (index != -1) {
565         let tab = self.tabbrowser.tabs[index];
566         self.previews.get(tab).icon = img;
567       }
568     });
569   }
572 ////////////////////////////////////////////////////////////////////////////////
573 //// AeroPeek
576  * This object acts as global storage and external interface for this feature.
577  * It maintains the values of the prefs.
578  */
579 this.AeroPeek = {
580   available: false,
581   // Does the pref say we're enabled?
582   _prefenabled: true,
584   _enabled: true,
586   // nsITaskbarTabPreview array
587   previews: [],
589   // TabWindow array
590   windows: [],
592   // nsIWinTaskbar service
593   taskbar: null,
595   // Maximum number of previews
596   maxpreviews: 20,
598   // Length of time in seconds that previews are cached
599   cacheLifespan: 20,
601   initialize: function () {
602     if (!(WINTASKBAR_CONTRACTID in Cc))
603       return;
604     this.taskbar = Cc[WINTASKBAR_CONTRACTID].getService(Ci.nsIWinTaskbar);
605     this.available = this.taskbar.available;
606     if (!this.available)
607       return;
609     this.prefs.addObserver(TOGGLE_PREF_NAME, this, false);
610     this.prefs.addObserver(DISABLE_THRESHOLD_PREF_NAME, this, false);
611     this.prefs.addObserver(CACHE_EXPIRATION_TIME_PREF_NAME, this, false);
613     this.cacheLifespan = this.prefs.getIntPref(CACHE_EXPIRATION_TIME_PREF_NAME);
615     this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME);
617     this.enabled = this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME);
618   },
620   destroy: function destroy() {
621     this._enabled = false;
623     this.prefs.removeObserver(TOGGLE_PREF_NAME, this);
624     this.prefs.removeObserver(DISABLE_THRESHOLD_PREF_NAME, this);
625     this.prefs.removeObserver(CACHE_EXPIRATION_TIME_PREF_NAME, this);
627     if (this.cacheTimer)
628       this.cacheTimer.cancel();
629   },
631   get enabled() {
632     return this._enabled;
633   },
635   set enabled(enable) {
636     if (this._enabled == enable)
637       return;
639     this._enabled = enable;
641     this.windows.forEach(function (win) {
642       win.enabled = enable;
643     });
644   },
646   addPreview: function (preview) {
647     this.previews.push(preview);
648     this.checkPreviewCount();
649   },
651   removePreview: function (preview) {
652     let idx = this.previews.indexOf(preview);
653     this.previews.splice(idx, 1);
654     this.checkPreviewCount();
655   },
657   checkPreviewCount: function () {
658     if (this.previews.length > this.maxpreviews)
659       this.enabled = false;
660     else
661       this.enabled = this._prefenabled;
662   },
664   onOpenWindow: function (win) {
665     // This occurs when the taskbar service is not available (xp, vista)
666     if (!this.available)
667       return;
669     win.gTaskbarTabGroup = new TabWindow(win);
670   },
672   onCloseWindow: function (win) {
673     // This occurs when the taskbar service is not available (xp, vista)
674     if (!this.available)
675       return;
677     win.gTaskbarTabGroup.destroy();
678     delete win.gTaskbarTabGroup;
680     if (this.windows.length == 0)
681       this.destroy();
682   },
684   resetCacheTimer: function () {
685     this.cacheTimer.cancel();
686     this.cacheTimer.init(this, 1000*this.cacheLifespan, Ci.nsITimer.TYPE_ONE_SHOT);
687   },
689   //// nsIObserver
690   observe: function (aSubject, aTopic, aData) {
691     switch (aTopic) {
692       case "nsPref:changed":
693         if (aData == CACHE_EXPIRATION_TIME_PREF_NAME)
694           break;
696         if (aData == TOGGLE_PREF_NAME)
697           this._prefenabled = this.prefs.getBoolPref(TOGGLE_PREF_NAME);
698         else if (aData == DISABLE_THRESHOLD_PREF_NAME)
699           this.maxpreviews = this.prefs.getIntPref(DISABLE_THRESHOLD_PREF_NAME);
700         // Might need to enable/disable ourselves
701         this.checkPreviewCount();
702         break;
703       case "timer-callback":
704         this.previews.forEach(function (preview) {
705           let controller = preview.controller.wrappedJSObject;
706           controller.resetCanvasPreview();
707         });
708         break;
709     }
710   }
713 XPCOMUtils.defineLazyGetter(AeroPeek, "cacheTimer", function ()
714   Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer)
717 XPCOMUtils.defineLazyServiceGetter(AeroPeek, "prefs",
718                                    "@mozilla.org/preferences-service;1",
719                                    "nsIPrefBranch");
721 AeroPeek.initialize();