Bug 594958 - Tab thumbnails sometimes have gray (now black) bars at the bottom [r=ian, a=blocking2.0:final+]
authorTim Taubert <tim.taubert@gmx.de>
Thu, 03 Feb 2011 02:53:13 +0100
changeset 61858 54da8f75df7f9d2a91d9a16019c850f25994c6a3
parent 61857 490e39e1d9569f606e3ff5675c9569b4c7b8529d
child 61859 fd7962a458688e92cc5edd857d30da928438ad03
push id18522
push usereakhgari@mozilla.com
push dateThu, 03 Feb 2011 17:29:54 +0000
treeherdermozilla-central@3c87074d5f50 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersian, blocking2.0
bugs594958
milestone2.0b12pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 594958 - Tab thumbnails sometimes have gray (now black) bars at the bottom [r=ian, a=blocking2.0:final+]
browser/base/content/tabview/tabitems.js
browser/base/content/test/tabview/Makefile.in
browser/base/content/test/tabview/browser_tabview_bug594958.js
--- a/browser/base/content/tabview/tabitems.js
+++ b/browser/base/content/tabview/tabitems.js
@@ -803,17 +803,17 @@ let TabItems = {
     let $canvas = iQ("<canvas>")
       .attr('moz-opaque', '');
     $canvas.appendTo(iQ("body"));
     $canvas.hide();
     this.tempCanvas = $canvas[0];
     // 150 pixels is an empirical size, below which FF's drawWindow()
     // algorithm breaks down
     this.tempCanvas.width = 150;
-    this.tempCanvas.height = 150;
+    this.tempCanvas.height = 112;
 
     this.tabsProgressListener = {
       onStateChange: function(browser, webProgress, request, stateFlags, status) {
         if ((stateFlags & Ci.nsIWebProgressListener.STATE_STOP) &&
             (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)) {
           // browser would only has _tabViewTabItemWithCachedData if 
           // it's showing cached data.
           if (browser._tabViewTabItemWithCachedData)
@@ -1276,77 +1276,109 @@ TabCanvas.prototype = {
   // ----------
   // Function: paint
   paint: function TabCanvas_paint(evt) {
     var w = this.canvas.width;
     var h = this.canvas.height;
     if (!w || !h)
       return;
 
-    let fromWin = this.tab.linkedBrowser.contentWindow;
-    if (fromWin == null) {
-      Utils.log('null fromWin in paint');
+    if (!this.tab.linkedBrowser.contentWindow) {
+      Utils.log('no tab.linkedBrowser.contentWindow in TabCanvas.paint()');
       return;
     }
 
+    let ctx = this.canvas.getContext("2d");
     let tempCanvas = TabItems.tempCanvas;
+    let bgColor = '#fff';
+
     if (w < tempCanvas.width) {
       // Small draw case where nearest-neighbor algorithm breaks down in Windows
       // First draw to a larger canvas (150px wide), and then draw that image
       // to the destination canvas.
-      
-      var tempCtx = tempCanvas.getContext("2d");
-      
-      let canvW = tempCanvas.width;
-      let canvH = (h/w) * canvW;
-      
-      var scaler = canvW/fromWin.innerWidth;
-  
-      tempCtx.save();
-      tempCtx.clearRect(0,0,tempCanvas.width,tempCanvas.height);
-      tempCtx.scale(scaler, scaler);
-      try{
-        tempCtx.drawWindow(fromWin, fromWin.scrollX, fromWin.scrollY, 
-          canvW/scaler, canvH/scaler, "#fff");
-      } catch(e) {
+      let tempCtx = tempCanvas.getContext("2d");
+      this._drawWindow(tempCtx, tempCanvas.width, tempCanvas.height, bgColor);
+
+      // Now copy to tabitem canvas.
+      try {
+        this._fillCanvasBackground(ctx, w, h, bgColor);
+        ctx.drawImage(tempCanvas, 0, 0, w, h);
+      } catch (e) {
         Utils.error('paint', e);
-      }  
-      tempCtx.restore();
-      
-      // Now copy to tabitem canvas. No save/restore necessary.      
-      var destCtx = this.canvas.getContext("2d");      
-      try{
-        // the tempcanvas is square, so draw it as a square.
-        destCtx.drawImage(tempCanvas, 0, 0, w, w);
-      } catch(e) {
-        Utils.error('paint', e);
-      }  
-      
+      }
     } else {
       // General case where nearest neighbor algorithm looks good
       // Draw directly to the destination canvas
-      
-      var ctx = this.canvas.getContext("2d");
-      
-      var scaler = w/fromWin.innerWidth;
-  
-      // TODO: Potentially only redraw the dirty rect? (Is it worth it?)
-  
-      ctx.save();
-      ctx.scale(scaler, scaler);
-      try{
-        ctx.drawWindow(fromWin, fromWin.scrollX, fromWin.scrollY, 
-          w/scaler, h/scaler, "#fff",
-          Ci.nsIDOMCanvasRenderingContext2D.DRAWWINDOW_DO_NOT_FLUSH);
-      } catch(e) {
-        Utils.error('paint', e);
-      }
-  
-      ctx.restore();
+      this._drawWindow(ctx, w, h, bgColor);
+    }
+  },
+
+  // ----------
+  // Function: _fillCanvasBackground
+  // Draws a rectangle of <width>x<height> with color <bgColor> to the given
+  // canvas context.
+  _fillCanvasBackground: function TabCanvas__fillCanvasBackground(ctx, width, height, bgColor) {
+    ctx.fillStyle = bgColor;
+    ctx.fillRect(0, 0, width, height);
+  },
+
+  // ----------
+  // Function: _drawWindow
+  // Draws contents of the tabs' browser window to the given canvas context.
+  _drawWindow: function TabCanvas__drawWindow(ctx, width, height, bgColor) {
+    this._fillCanvasBackground(ctx, width, height, bgColor);
+
+    let rect = this._calculateClippingRect(width, height);
+    let scaler = width / rect.width;
+
+    ctx.save();
+    ctx.scale(scaler, scaler);
+
+    try {
+      let win = this.tab.linkedBrowser.contentWindow;
+      ctx.drawWindow(win, rect.left, rect.top, rect.width, rect.height,
+                     bgColor, ctx.DRAWWINDOW_DO_NOT_FLUSH);
+    } catch (e) {
+      Utils.error('paint', e);
     }
+
+    ctx.restore();
+  },
+
+  // ----------
+  // Function: _calculateClippingRect
+  // Calculate the clipping rect that will be projected to the tab's
+  // thumbnail canvas.
+  _calculateClippingRect: function TabCanvas__calculateClippingRect(origWidth, origHeight) {
+    let win = this.tab.linkedBrowser.contentWindow;
+    let body = win.document.body;
+
+    let maxWidth = win.innerWidth - 25;
+    let maxHeight = win.innerHeight;
+
+    if (body) {
+      maxWidth = Math.max(maxWidth, body.scrollWidth - win.scrollX);
+      maxHeight = Math.max(maxHeight, body.scrollHeight - win.scrollY);
+    }
+
+    let height = Math.min(maxHeight, Math.floor(origHeight * maxWidth / origWidth));
+    let width = Math.floor(origWidth * height / origHeight);
+
+    // very short pages in combination with a very wide browser window force us
+    // to extend the clipping rect and add some empty space around the thumb
+    let factor = 0.7;
+    if (width < maxWidth * factor) {
+      width = maxWidth * factor;
+      height = Math.floor(origHeight * width / origWidth);
+    }
+
+    let left = win.scrollX + Math.max(0, Math.round((maxWidth - width) / 2));
+    let top = win.scrollY;
+
+    return new Rect(left, top, width, height);
   },
 
   // ----------
   // Function: toImageData
   toImageData: function TabCanvas_toImageData() {
     return this.canvas.toDataURL("image/png", "");
   }
 };
--- a/browser/base/content/test/tabview/Makefile.in
+++ b/browser/base/content/test/tabview/Makefile.in
@@ -52,16 +52,17 @@ include $(topsrcdir)/config/rules.mk
                  browser_tabview_bug587231.js \
                  browser_tabview_bug587351.js \
                  browser_tabview_bug587503.js \
                  browser_tabview_bug587990.js \
                  browser_tabview_bug588265.js \
                  browser_tabview_bug589324.js \
                  browser_tabview_bug590606.js \
                  browser_tabview_bug591706.js \
+                 browser_tabview_bug594958.js \
                  browser_tabview_bug595191.js \
                  browser_tabview_bug595436.js \
                  browser_tabview_bug595518.js \
                  browser_tabview_bug595521.js \
                  browser_tabview_bug595560.js \
                  browser_tabview_bug595804.js \
                  browser_tabview_bug595930.js \
                  browser_tabview_bug595943.js \
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug594958.js
@@ -0,0 +1,149 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+  let win;
+  let cw;
+
+  let getGroupItem = function (index) {
+    return cw.GroupItems.groupItems[index];
+  }
+
+  let newWindow = function (callback) {
+    newWindowWithTabView(function (tvwin) {
+      registerCleanupFunction(function () {
+        if (!tvwin.closed)
+          tvwin.close();
+      });
+
+      win = tvwin;
+      cw = win.TabView.getContentWindow();
+
+      callback();
+    });
+  }
+
+  let finishTest = function () {
+    win.close();
+    finish();
+  }
+
+  // very long page that produces black bars at the right
+  let html1 = '<html><body style="background-color: #00f;">' +
+              '<div style="background: #fff; width: 95%; height: 10000px; ' +
+              ' margin: 0 auto;"></div></body></html>';
+
+  // very short page that produces black bars at the bottom
+  let html2 = '<html><body style="background-color: #00f;"></body></html>';
+
+  let tests = [{
+    url: 'data:text/html,' + html1,
+    colors: [
+      {right: [0, 0, 255], bottom: [255, 255, 255]},
+      {right: [0, 0, 255], bottom: [255, 255, 255]},
+      {right: [0, 0, 255], bottom: [255, 255, 255]}
+    ]
+  }, {
+    url: 'about:blank',
+    colors: [
+      {right: [255, 255, 255], bottom: [255, 255, 255]},
+      {right: [255, 255, 255], bottom: [255, 255, 255]},
+      {right: [255, 255, 255], bottom: [255, 255, 255]}
+    ]
+  }, {
+    url: 'data:text/html,' + html2,
+    colors: [
+      {right: [0, 0, 255], bottom: [0, 0, 255]},
+      {right: [0, 0, 255], bottom: [255, 255, 255]},
+      {right: [0, 0, 255], bottom: [0, 0, 255]}
+    ]
+  }];
+
+  let next = function () {
+    if (!tests.length) {
+      finishTest();
+      return;
+    }
+
+    let test = tests.shift();
+    let tab = win.gBrowser.tabs[0];
+
+    tab.linkedBrowser.addEventListener('load', function onLoad() {
+      tab.linkedBrowser.removeEventListener('load', onLoad, true);
+      checkUrl(test);
+    }, true);
+
+    tab.linkedBrowser.loadURI(test.url);
+  }
+
+  let checkUrl = function (test) {
+    let sizes = [[-400, 0], [800, -200], [-400, 200]];
+
+    let nextSize = function () {
+      if (!sizes.length) {
+        next();
+        return;
+      }
+
+      let size = sizes.shift();
+      let colors = test.colors.shift();
+      let groupItem = getGroupItem(0);
+
+      let checkWithSmallItemSize = function () {
+        groupItem.setSize(150, 150, true);
+        groupItem.setUserSize();
+
+        afterAllTabItemsUpdated(function () {
+          checkPixelColors(test.url, colors, nextSize);
+        }, win);
+      }
+
+      let checkWithNormalItemSize = function () {
+        groupItem.setSize(300, 300, true);
+        groupItem.setUserSize();
+
+        afterAllTabItemsUpdated(function () {
+          checkPixelColors(test.url, colors, checkWithSmallItemSize);
+        }, win);
+      }
+
+      win.resizeBy.apply(win, size);
+      checkWithNormalItemSize();
+    }
+
+    nextSize();
+  }
+
+  let checkPixelColors = function (url, colors, callback) {
+    let tab = win.gBrowser.tabs[0];
+    let $canvas = tab._tabViewTabItem.$canvas;
+    let width = $canvas.width();
+    let height = $canvas.height();
+    let ctx = $canvas[0].getContext("2d");
+
+    afterAllTabItemsUpdated(function () {
+      checkPixelColor(ctx, url, colors.bottom, Math.floor(width / 4), height - 1, 'bottom');
+      checkPixelColor(ctx, url, colors.right, width - 1, Math.floor(height / 4), 'right');
+      callback();
+    }, win);
+  }
+
+  let checkPixelColor = function (ctx, url, color, x, y, edge) {
+    let data = ctx.getImageData(x, y, 1, 1).data;
+    let check = (data[0] == color[0] && data[1] == color[1] && data[2] == color[2]);
+    ok(check, url + ': ' + edge + ' edge color matches pixel value');
+  }
+
+  waitForExplicitFinish();
+  newWindow(next);
+}
+
+// ---------
+function newWindowWithTabView(callback) {
+  let win = window.openDialog(getBrowserURL(), "_blank", 
+                              "chrome,all,dialog=no,height=600,width=800");
+  win.addEventListener("load", function onLoad() {
+    win.removeEventListener("load", onLoad, false);
+    showTabView(function () callback(win), win);
+  }, false);
+}