Bug 659594 - Use MozAfterPaint to redraw thumbnails r=ttaubert
authorRaymond Lee <raymond@raysquare.com>
Thu, 19 Apr 2012 00:19:48 +0800
changeset 95224 5d69cf5ce27802364295d27e550b19df09729ffa
parent 95151 3a9629092a5f6f17f83913de422fd41a5c9f71d7
child 95225 d42051ed9a815167b4684ed7965904a9c85e12a5
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersttaubert
bugs659594
milestone14.0a1
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 659594 - Use MozAfterPaint to redraw thumbnails r=ttaubert
browser/components/tabview/content.js
browser/components/tabview/tabitems.js
browser/components/tabview/test/Makefile.in
browser/components/tabview/test/browser_tabview_bug594958.js
browser/components/tabview/test/browser_tabview_bug597248.js
browser/components/tabview/test/browser_tabview_bug610242.js
browser/components/tabview/test/browser_tabview_bug659594.js
browser/components/tabview/test/browser_tabview_expander.js
browser/components/tabview/test/head.js
--- a/browser/components/tabview/content.js
+++ b/browser/components/tabview/content.js
@@ -69,33 +69,42 @@ let WindowEventHandler = {
     // and does not originate from a script.
     if (!event.isTrusted)
       return;
 
     // we're intentionally sending a synchronous message to handle this event
     // as quick as possible, switch the selected tab and hide the tabview
     // before the modal dialog is shown
     sendSyncMessage("Panorama:DOMWillOpenModalDialog");
+  },
+
+  // ----------
+  // Function: onMozAfterPaint
+  // Sends an asynchronous message when the "onMozAfterPaint" event
+  // is fired.
+  onMozAfterPaint: function WEH_onMozAfterPaint(event) {
+    sendAsyncMessage("Panorama:MozAfterPaint");
   }
 };
 
 // add event listeners
 addEventListener("DOMContentLoaded", WindowEventHandler.onDOMContentLoaded, false);
 addEventListener("DOMWillOpenModalDialog", WindowEventHandler.onDOMWillOpenModalDialog, false);
+addEventListener("MozAfterPaint", WindowEventHandler.onMozAfterPaint, false);
 
 // ----------
 // WindowMessageHandler
 //
 // Handles messages sent by the chrome process.
 let WindowMessageHandler = {
   // ----------
   // Function: isDocumentLoaded
   // Checks if the currently active document is loaded.
   isDocumentLoaded: function WMH_isDocumentLoaded(cx) {
-    let isLoaded = (content.document.readyState == "complete" &&
+    let isLoaded = (content.document.readyState != "uninitialized" &&
                     !webProgress.isLoadingDocument);
 
     sendAsyncMessage(cx.name, {isLoaded: isLoaded});
   },
 
   // ----------
   // Function: isImageDocument
   // Checks if the currently active document is an image document or not.
@@ -192,8 +201,9 @@ let WebProgressListener = {
   // Implements progress listener interface.
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
                                          Ci.nsISupportsWeakReference,
                                          Ci.nsISupports])
 };
 
 // add web progress listener
 webProgress.addProgressListener(WebProgressListener, Ci.nsIWebProgress.NOTIFY_STATE_WINDOW);
+
--- a/browser/components/tabview/tabitems.js
+++ b/browser/components/tabview/tabitems.js
@@ -796,16 +796,43 @@ TabItem.prototype = Utils.extend(new Ite
 
     let xOrigin = (left - zoomLeft) / ((left - zoomLeft) + (zoomLeft + zoomWidth - right)) * 100;
     let yOrigin = (top - zoomTop) / ((top - zoomTop) + (zoomTop + zoomHeight - bottom)) * 100;
 
     return {
       transformOrigin: xOrigin + "% " + yOrigin + "%",
       transform: "scale(" + zoomScaleFactor + ")"
     };
+  },
+
+  // ----------
+  // Function: updateCanvas
+  // Updates the tabitem's canvas.
+  updateCanvas: function TabItem_updateCanvas() {
+    // ___ thumbnail
+    let $canvas = this.$canvas;
+    if (!this.canvasSizeForced) {
+      let w = $canvas.width();
+      let h = $canvas.height();
+      if (w != $canvas[0].width || h != $canvas[0].height) {
+        $canvas[0].width = w;
+        $canvas[0].height = h;
+      }
+    }
+
+    TabItems._lastUpdateTime = Date.now();
+    this._lastTabUpdateTime = TabItems._lastUpdateTime;
+
+    if (this.tabCanvas)
+      this.tabCanvas.paint();
+    this.saveThumbnail();
+
+    // ___ cache
+    if (this.isShowingCachedData())
+      this.hideCachedData();
   }
 });
 
 // ##########
 // Class: TabItems
 // Singleton for managing <TabItem>s
 let TabItems = {
   minTabWidth: 40,
@@ -823,16 +850,17 @@ let TabItems = {
   _heartbeatTiming: 200, // milliseconds between calls
   _maxTimeForUpdating: 200, // milliseconds that consecutive updates can take
   _lastUpdateTime: Date.now(),
   _eventListeners: [],
   _pauseUpdateForTest: false,
   tempCanvas: null,
   _reconnectingPaused: false,
   tabItemPadding: {},
+  _mozAfterPaintHandler: null,
 
   // ----------
   // Function: toString
   // Prints [TabItems count=count] for debug use
   toString: function TabItems_toString() {
     return "[TabItems count=" + this.items.length + "]";
   },
 
@@ -854,16 +882,20 @@ let TabItems = {
     $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 = 112;
 
+    let mm = gWindow.messageManager;
+    this._mozAfterPaintHandler = this.onMozAfterPaint.bind(this);
+    mm.addMessageListener("Panorama:MozAfterPaint", this._mozAfterPaintHandler);
+
     // When a tab is opened, create the TabItem
     this._eventListeners.open = function (event) {
       let tab = event.target;
 
       if (!tab.pinned)
         self.link(tab);
     }
     // When a tab's content is loaded, show the canvas and hide the cached data
@@ -903,16 +935,19 @@ let TabItems = {
       self.link(tab, options);
       self.update(tab);
     });
   },
 
   // ----------
   // Function: uninit
   uninit: function TabItems_uninit() {
+    let mm = gWindow.messageManager;
+    mm.removeMessageListener("Panorama:MozAfterPaint", this._mozAfterPaintHandler);
+
     for (let name in this._eventListeners) {
       AllTabs.unregister(name, this._eventListeners[name]);
     }
     this.items.forEach(function(tabItem) {
       for (let x in tabItem) {
         if (typeof tabItem[x] == "object")
           tabItem[x] = null;
       }
@@ -941,30 +976,43 @@ let TabItems = {
             "<span class='tab-title'>&nbsp;</span>" +
             "<div class='close'></div>";
     this._fragment = document.createDocumentFragment();
     this._fragment.appendChild(div);
 
     return this._fragment;
   },
 
+  // Function: _isComplete
+  // Checks whether the xul:tab has fully loaded and calls a callback with a 
+  // boolean indicates whether the tab is loaded or not.
+  _isComplete: function TabItems__isComplete(tab, callback) {
+    Utils.assertThrow(tab, "tab");
+
+    let mm = tab.linkedBrowser.messageManager;
+    let message = "Panorama:isDocumentLoaded";
+
+    mm.addMessageListener(message, function onMessage(cx) {
+      mm.removeMessageListener(cx.name, onMessage);
+      callback(cx.json.isLoaded);
+    });
+    mm.sendAsyncMessage(message);
+  },
+
   // ----------
-  // Function: isComplete
-  // Return whether the xul:tab has fully loaded.
-  isComplete: function TabItems_isComplete(tab) {
-    // If our readyState is complete, but we're showing about:blank,
-    // and we're not loading about:blank, it means we haven't really
-    // started loading. This can happen to the first few tabs in a
-    // page.
-    Utils.assertThrow(tab, "tab");
-    return (
-      tab.linkedBrowser.contentDocument.readyState == 'complete' &&
-      !(tab.linkedBrowser.contentDocument.URL == 'about:blank' &&
-        tab._tabViewTabItem.getTabState().url != 'about:blank')
-    );
+  // Function: onMozAfterPaint
+  // Called when a web page is painted.
+  onMozAfterPaint: function TabItems_onMozAfterPaint(cx) {
+    let index = gBrowser.browsers.indexOf(cx.target);
+    if (index == -1)
+      return;
+
+    let tab = gBrowser.tabs[index];
+    if (!tab.pinned)
+      this.update(tab);
   },
 
   // ----------
   // Function: update
   // Takes in a xul:tab.
   update: function TabItems_update(tab) {
     try {
       Utils.assertThrow(tab, "tab");
@@ -1035,45 +1083,32 @@ let TabItems = {
       // early returns
       this._tabsWaitingForUpdate.remove(tab);
 
       // ___ URL
       let tabUrl = tab.linkedBrowser.currentURI.spec;
       tabItem.$container.attr("title", label + "\n" + tabUrl);
 
       // ___ Make sure the tab is complete and ready for updating.
-      if (!this.isComplete(tab) && (!options || !options.force)) {
-        // If it's incomplete, stick it on the end of the queue
-        this._tabsWaitingForUpdate.push(tab);
-        return;
-      }
+      if (options && options.force) {
+        tabItem.updateCanvas();
+        tabItem._sendToSubscribers("updated");
+      } else {
+        this._isComplete(tab, function TabItems__update_isComplete(isComplete) {
+          if (!Utils.isValidXULTab(tab) || tab.pinned)
+            return;
 
-      // ___ thumbnail
-      let $canvas = tabItem.$canvas;
-      if (!tabItem.canvasSizeForced) {
-        let w = $canvas.width();
-        let h = $canvas.height();
-        if (w != tabItem.$canvas[0].width || h != tabItem.$canvas[0].height) {
-          tabItem.$canvas[0].width = w;
-          tabItem.$canvas[0].height = h;
-        }
+          if (isComplete) {
+            tabItem.updateCanvas();
+            tabItem._sendToSubscribers("updated");
+          } else {
+            this._tabsWaitingForUpdate.push(tab);
+          }
+        }.bind(this));
       }
-
-      this._lastUpdateTime = Date.now();
-      tabItem._lastTabUpdateTime = this._lastUpdateTime;
-
-      tabItem.tabCanvas.paint();
-      tabItem.saveThumbnail();
-
-      // ___ cache
-      if (tabItem.isShowingCachedData())
-        tabItem.hideCachedData();
-
-      // ___ notify subscribers that a full update has completed.
-      tabItem._sendToSubscribers("updated");
     } catch(e) {
       Utils.log(e);
     }
   },
 
   // ----------
   // Function: link
   // Takes in a xul:tab, creates a TabItem for it and adds it to the scene. 
--- a/browser/components/tabview/test/Makefile.in
+++ b/browser/components/tabview/test/Makefile.in
@@ -146,16 +146,17 @@ include $(topsrcdir)/config/rules.mk
                  browser_tabview_bug650573.js \
                  browser_tabview_bug651311.js \
                  browser_tabview_bug654295.js \
                  browser_tabview_bug654721.js \
                  browser_tabview_bug654941.js \
                  browser_tabview_bug655269.js \
                  browser_tabview_bug656778.js \
                  browser_tabview_bug656913.js \
+                 browser_tabview_bug659594.js \
                  browser_tabview_bug662266.js \
                  browser_tabview_bug663421.js \
                  browser_tabview_bug665502.js \
                  browser_tabview_bug669694.js \
                  browser_tabview_bug673196.js \
                  browser_tabview_bug673729.js \
                  browser_tabview_bug677310.js \
                  browser_tabview_bug678374.js \
--- a/browser/components/tabview/test/browser_tabview_bug594958.js
+++ b/browser/components/tabview/test/browser_tabview_bug594958.js
@@ -49,22 +49,27 @@ function test() {
     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);
+    let browser = tab.linkedBrowser;
+    browser.addEventListener("load", function onLoad(event) {
+       browser.removeEventListener("load", onLoad, true);
+       
+       let tabItem = tab._tabViewTabItem;
+       tabItem.addSubscriber("updated", function onUpdated() {
+         tabItem.removeSubscriber("updated", onUpdated);
+         checkUrl(test);
+       });
     }, true);
-
-    tab.linkedBrowser.loadURI(test.url);
+    browser.loadURI(test.url);
   }
 
   let checkUrl = function (test) {
     let sizes = [[-400, 0], [800, -200], [-400, 200]];
 
     let nextSize = function () {
       if (!sizes.length) {
         next();
--- a/browser/components/tabview/test/browser_tabview_bug597248.js
+++ b/browser/components/tabview/test/browser_tabview_bug597248.js
@@ -129,18 +129,21 @@ let gTabsProgressListener = {
 function updateAndCheck() {
   // force all canvas to update
   let contentWindow = restoredWin.TabView.getContentWindow();
 
   contentWindow.TabItems._pauseUpdateForTest = false;
 
   let tabItems = contentWindow.TabItems.getItems();
   tabItems.forEach(function(tabItem) {
-    contentWindow.TabItems._update(tabItem.tab);
-    ok(!tabItem.isShowingCachedData(),
-      "Tab item is not showing cached data anymore. " +
-      tabItem.tab.linkedBrowser.currentURI.spec);
+    tabItem.addSubscriber("updated", function onUpdated() {
+      tabItem.removeSubscriber("updated", onUpdated);
+      ok(!tabItem.isShowingCachedData(),
+         "Tab item is not showing cached data anymore. " +
+         tabItem.tab.linkedBrowser.currentURI.spec);
+    });
+    contentWindow.TabItems.update(tabItem.tab);
   });
 
   // clean up and finish
   restoredWin.close();
   finish();
 }
--- a/browser/components/tabview/test/browser_tabview_bug610242.js
+++ b/browser/components/tabview/test/browser_tabview_bug610242.js
@@ -62,39 +62,35 @@ function onTabViewWindowLoaded(win) {
     if (visible) {
       is(display, "block", label + " has favicon");
     } else {
       is(display, "none", label + " has no favicon");
     }
   }
 
   afterAllTabsLoaded(function() {
-    afterAllTabItemsUpdated(function() {
-      let children = group.getChildren();
-      let len = children.length;
-      let iconUpdateCounter = 0;
+    let children = group.getChildren();
+    let len = children.length;
+    let iconUpdateCounter = 0;
 
-      children.forEach(function(tabItem) {
-        tabItem.addSubscriber("iconUpdated", function onIconUpdated() {
-          // the tab is not loaded completely so ignore it.
-          if (tabItem.tab.linkedBrowser.currentURI.spec == "about:blank")
-            return;
-
-          tabItem.removeSubscriber("iconUpdated", onIconUpdated);
+    children.forEach(function(tabItem) {
+      tabItem.addSubscriber("iconUpdated", function onIconUpdated() {
+        tabItem.removeSubscriber("iconUpdated", onIconUpdated);
 
-          if (++iconUpdateCounter == len) {
-            check(datatext, "datatext", false);
-            check(datahtml, "datahtml", false);
-            check(mozilla, "about:mozilla", false);
-            check(robots, "about:robots", true);
-            check(html, "html", true);
-            check(png, "png", false);
-            check(svg, "svg", true);
+        if (++iconUpdateCounter == len) {
+          check(datatext, "datatext", false);
+          check(datahtml, "datahtml", false);
+          check(mozilla, "about:mozilla", false);
+          check(robots, "about:robots", true);
+          check(html, "html", true);
+          check(png, "png", false);
+          check(svg, "svg", true);
 
-            // Get rid of the group and its children
-            // The group close will trigger a finish().
-            closeGroupItem(group);
-          }
-        });
+          // Get rid of the group and its children
+          // The group close will trigger a finish().
+          closeGroupItem(group);
+        }
       });
-    }, win);
+    });
+
+    afterAllTabItemsUpdated(function () {}, win);
   }, win);
 }
new file mode 100644
--- /dev/null
+++ b/browser/components/tabview/test/browser_tabview_bug659594.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+  waitForExplicitFinish();
+
+  newWindowWithTabView(function(win) {
+    let numTabsToUpdate = 2;
+
+    showTabView(function() {
+      let contentWindow = win.TabView.getContentWindow();
+      let groupItem = contentWindow.GroupItems.groupItems[0];
+
+      groupItem.getChildren().forEach(function(tabItem) {
+        tabItem.addSubscriber("updated", function onUpdated() {
+          tabItem.removeSubscriber("updated", onUpdated);
+
+          if (--numTabsToUpdate == 0)
+            finish();
+        });
+        contentWindow.TabItems.update(tabItem.tab);
+      });
+    }, win);
+  }, function(win) {
+    BrowserOffline.toggleOfflineStatus();
+    ok(Services.io.offline, "It is now offline");
+
+    let originalTab = win.gBrowser.tabs[0];
+    originalTab.linkedBrowser.loadURI("http://www.example.com/foo");
+    win.gBrowser.addTab("http://www.example.com/bar");
+
+    registerCleanupFunction(function () {
+      if (Services.io.offline)
+        BrowserOffline.toggleOfflineStatus();
+      win.close();
+    });
+  });
+}
+
--- a/browser/components/tabview/test/browser_tabview_expander.js
+++ b/browser/components/tabview/test/browser_tabview_expander.js
@@ -21,24 +21,23 @@ function onTabViewWindowLoaded(win) {
   });
 
   let expander = contentWindow.iQ(group.container).find(".stackExpander");
   ok("length" in expander && expander.length == 1, "The group has an expander.");
 
   // procreate!
   contentWindow.UI.setActive(group);
   for (var i=0; i<7; i++) {
-    win.gBrowser.loadOneTab('about:blank#' + i, {inBackground: true});
+    win.gBrowser.loadOneTab('http://example.com#' + i, {inBackground: true});
   }
   let children = group.getChildren();
   
   // Wait until they all update because, once updated, they will notice that they
   // don't have favicons and this will change their styling at some unknown time.
   afterAllTabItemsUpdated(function() {
-    
     ok(!group.shouldStack(group._children.length), "The group should not stack.");
     is(expander[0].style.display, "none", "The expander is hidden.");
     
     // now resize the group down.
     group.setSize(100, 100, true);
   
     ok(group.shouldStack(group._children.length), "The group should stack.");
     isnot(expander[0].style.display, "none", "The expander is now visible!");
--- a/browser/components/tabview/test/head.js
+++ b/browser/components/tabview/test/head.js
@@ -73,23 +73,33 @@ function closeGroupItem(groupItem, callb
 
   groupItem.closeAll();
 }
 
 // ----------
 function afterAllTabItemsUpdated(callback, win) {
   win = win || window;
   let tabItems = win.document.getElementById("tab-view").contentWindow.TabItems;
+  let counter = 0;
 
   for (let a = 0; a < win.gBrowser.tabs.length; a++) {
     let tabItem = win.gBrowser.tabs[a]._tabViewTabItem;
-    if (tabItem)
-      tabItems._update(win.gBrowser.tabs[a]);
+    if (tabItem) {
+      let tab = win.gBrowser.tabs[a];
+      counter++;
+      tabItem.addSubscriber("updated", function onUpdated() {
+        tabItem.removeSubscriber("updated", onUpdated);
+        if (--counter == 0)
+          callback();
+      });
+      tabItems.update(tab);
+    }
   }
-  callback();
+  if (counter == 0)
+    callback();
 }
 
 // ---------
 function newWindowWithTabView(shownCallback, loadCallback, width, height) {
   let winWidth = width || 800;
   let winHeight = height || 800;
   let win = window.openDialog(getBrowserURL(), "_blank",
                               "chrome,all,dialog=no,height=" + winHeight +