Bug 680126 Reduce checkerboarding during load r=mbrubeck
authorBenjamin Stover <bstover@mozilla.com>
Wed, 24 Aug 2011 14:01:18 -0700
changeset 77160 b1cab441835f70d24ac0c78bde4d24070178971f
parent 77155 427a6b2313db302c4c2d5c726aeddfd9c05b66ba
child 77161 f43d16688b14e8bb0b28f69400e6adbbbdd2e61f
push id78
push userclegnitto@mozilla.com
push dateFri, 16 Dec 2011 17:32:24 +0000
treeherdermozilla-release@79d24e644fdd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmbrubeck
bugs680126
milestone9.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 680126 Reduce checkerboarding during load r=mbrubeck
mobile/app/mobile.js
mobile/chrome/content/bindings/browser.js
mobile/chrome/content/browser.js
--- a/mobile/app/mobile.js
+++ b/mobile/app/mobile.js
@@ -67,16 +67,17 @@ pref("toolkit.screen.lock", false);
 
 // From libpref/src/init/all.js, extended to allow a slightly wider zoom range.
 pref("zoom.minPercent", 20);
 pref("zoom.maxPercent", 400);
 pref("toolkit.zoomManager.zoomValues", ".2,.3,.5,.67,.8,.9,1,1.1,1.2,1.33,1.5,1.7,2,2.4,3,4");
 
 // Device pixel to CSS px ratio, in percent. Set to -1 to calculate based on display density.
 pref("browser.viewport.scaleRatio", -1);
+pref("browser.viewport.desktopWidth", 980);
 
 /* allow scrollbars to float above chrome ui */
 pref("ui.scrollbarsCanOverlapContent", 1);
 
 /* use long press to display a context menu */
 pref("ui.click_hold_context_menus", true);
 
 /* cache prefs */
--- a/mobile/chrome/content/bindings/browser.js
+++ b/mobile/chrome/content/bindings/browser.js
@@ -26,16 +26,18 @@ let WebProgressListener = {
     };
 
     sendAsyncMessage("Content:StateChange", json);
   },
 
   onProgressChange: function onProgressChange(aWebProgress, aRequest, aCurSelf, aMaxSelf, aCurTotal, aMaxTotal) {
   },
 
+  _firstPaint: false,
+
   onLocationChange: function onLocationChange(aWebProgress, aRequest, aLocationURI) {
     if (content != aWebProgress.DOMWindow)
       return;
 
     let spec = aLocationURI ? aLocationURI.spec : "";
     let location = spec.split("#")[0];
 
     let charset = content.document.characterSet;
@@ -46,20 +48,24 @@ let WebProgressListener = {
       location:        spec,
       canGoBack:       docShell.canGoBack,
       canGoForward:    docShell.canGoForward,
       charset:         charset.toString()
     };
 
     sendAsyncMessage("Content:LocationChange", json);
 
+    this._firstPaint = false;
+    let self = this;
+
     // When a new page is loaded fire a message for the first paint
     addEventListener("MozAfterPaint", function(aEvent) {
       removeEventListener("MozAfterPaint", arguments.callee, true);
 
+      self._firstPaint = true;
       let scrollOffset = ContentScroll.getScrollOffset(content);
       sendAsyncMessage("Browser:FirstPaint", scrollOffset);
     }, true);
   },
 
   onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
   },
 
@@ -537,18 +543,21 @@ let ContentScroll =  {
   },
 
   receiveMessage: function(aMessage) {
     let json = aMessage.json;
     switch (aMessage.name) {
       case "Content:SetCacheViewport": {
         // Set resolution for root view
         let rootCwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
-        if (json.id == 1)
+        if (json.id == 1) {
           rootCwu.setResolution(json.scale, json.scale);
+          if (!WebProgressListener._firstPaint)
+            break;
+        }
 
         let displayport = new Rect(json.x, json.y, json.w, json.h);
         if (displayport.isEmpty())
           break;
 
         // Map ID to element
         let element = null;
         try {
@@ -581,17 +590,16 @@ let ContentScroll =  {
         if (json.id == 1) {
           x = Math.round(x * json.scale) / json.scale;
           y = Math.round(y * json.scale) / json.scale;
         }
 
         let win = element.ownerDocument.defaultView;
         let winCwu = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
         winCwu.setDisplayPortForElement(x, y, displayport.width, displayport.height, element);
-
         break;
       }
 
       case "Content:SetWindowSize": {
         let cwu = content.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
         cwu.setCSSViewport(json.width, json.height);
         break;
       }
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -54,19 +54,16 @@ let Cu = Components.utils;
 let Cr = Components.results;
 
 function getBrowser() {
   return Browser.selectedBrowser;
 }
 
 const kBrowserViewZoomLevelPrecision = 10000;
 
-const kDefaultBrowserWidth = 800;
-const kFallbackBrowserWidth = 980;
-
 // allow panning after this timeout on pages with registered touch listeners
 const kTouchTimeout = 300;
 
 const kDefaultMetadata = { autoSize: false, allowZoom: true, autoScale: true };
 
 // Override sizeToContent in the main window. It breaks things (bug 565887)
 window.sizeToContent = function() {
   Cu.reportError("window.sizeToContent is not allowed in this window");
@@ -153,16 +150,23 @@ var Browser = {
                      .getInterface(Ci.nsIDOMWindowUtils),
   controlsScrollbox: null,
   controlsScrollboxScroller: null,
   controlsPosition: null,
   pageScrollbox: null,
   pageScrollboxScroller: null,
   styles: {},
 
+  get defaultBrowserWidth() {
+    delete this.defaultBrowserWidth;
+    var width = Services.prefs.getIntPref("browser.viewport.desktopWidth");
+    this.defaultBrowserWidth = width;
+    return width;
+  },
+
   startup: function startup() {
     var self = this;
 
     try {
       messageManager.loadFrameScript("chrome://browser/content/Util.js", true);
       messageManager.loadFrameScript("chrome://browser/content/forms.js", true);
       messageManager.loadFrameScript("chrome://browser/content/content.js", true);
     } catch (e) {
@@ -373,16 +377,17 @@ var Browser = {
         };
         Services.obs.addObserver(dummyCleanup, "sessionstore-windows-restored", false);
       }
       ss.restoreLastSession(bringFront);
     } else {
       this.addTab(commandURL || this.getHomePage(), true);
     }
 
+    messageManager.addMessageListener("MozScrolledAreaChanged", this);
     messageManager.addMessageListener("Browser:ViewportMetadata", this);
     messageManager.addMessageListener("Browser:CanCaptureMouse:Return", this);
     messageManager.addMessageListener("Browser:FormSubmit", this);
     messageManager.addMessageListener("Browser:KeyPress", this);
     messageManager.addMessageListener("Browser:ZoomToPoint:Return", this);
     messageManager.addMessageListener("Browser:CanUnload:Return", this);
     messageManager.addMessageListener("scroll", this);
     messageManager.addMessageListener("Browser:CertException", this);
@@ -472,16 +477,17 @@ var Browser = {
 
     Services.obs.notifyObservers(null, "browser-lastwindow-close-granted", null);
     return true;
   },
 
   shutdown: function shutdown() {
     BrowserUI.uninit();
 
+    messageManager.removeMessageListener("MozScrolledAreaChanged", this);
     messageManager.removeMessageListener("Browser:ViewportMetadata", this);
     messageManager.removeMessageListener("Browser:FormSubmit", this);
     messageManager.removeMessageListener("Browser:KeyPress", this);
     messageManager.removeMessageListener("Browser:ZoomToPoint:Return", this);
     messageManager.removeMessageListener("scroll", this);
     messageManager.removeMessageListener("Browser:CertException", this);
     messageManager.removeMessageListener("Browser:BlockedSite", this);
     messageManager.removeMessageListener("Browser:ErrorPage", this);
@@ -1174,16 +1180,22 @@ var Browser = {
     let dummy = getComputedStyle(document.documentElement, "").width;
   },
 
   receiveMessage: function receiveMessage(aMessage) {
     let json = aMessage.json;
     let browser = aMessage.target;
 
     switch (aMessage.name) {
+      case "MozScrolledAreaChanged": {
+        let tab = this.getTabForBrowser(browser);
+        if (tab)
+          tab.scrolledAreaChanged();
+        break;
+      }
       case "Browser:ViewportMetadata": {
         let tab = this.getTabForBrowser(browser);
         // Some browser such as iframes loaded dynamically into the chrome UI
         // does not have any assigned tab
         if (tab)
           tab.updateViewportMetadata(json);
         break;
       }
@@ -1524,17 +1536,16 @@ Browser.WebProgress.prototype = {
           tab.browser.userTypedValue = "";
           tab.browser.appIcon = { href: null, size:-1 };
 
 #ifdef MOZ_CRASH_REPORTER
           if (CrashReporter.enabled)
             CrashReporter.annotateCrashReport("URL", spec);
 #endif
           this._waitForLoad(tab);
-          tab.useFallbackWidth = false;
         }
 
         let event = document.createEvent("UIEvents");
         event.initUIEvent("URLChanged", true, false, window, locationHasChanged);
         tab.browser.dispatchEvent(event);
         break;
       }
 
@@ -1576,36 +1587,39 @@ Browser.WebProgress.prototype = {
   _documentStop: function _documentStop(aTab) {
     // Make sure the URLbar is in view. If this were the selected tab,
     // _waitForLoad would scroll to top.
     aTab.pageScrollOffset = { x: 0, y: 0 };
   },
 
   _waitForLoad: function _waitForLoad(aTab) {
     let browser = aTab.browser;
-    browser.messageManager.removeMessageListener("MozScrolledAreaChanged", aTab.scrolledAreaChanged);
+
+    aTab._firstPaint = false;
 
     browser.messageManager.addMessageListener("Browser:FirstPaint", function firstPaintListener(aMessage) {
       browser.messageManager.removeMessageListener(aMessage.name, arguments.callee);
+      aTab._firstPaint = true;
 
       // We're about to have new page content, so scroll the content area
       // so the new paints will draw correctly.
       // Background tabs are delayed scrolled to top in _documentStop
       if (getBrowser() == browser) {
         let json = aMessage.json;
         browser.getRootView().scrollTo(Math.floor(json.x * browser.scale),
                                        Math.floor(json.y * browser.scale));
         if (json.x == 0 && json.y == 0)
           Browser.pageScrollboxScroller.scrollTo(0, 0);
+        else
+          Browser.pageScrollboxScroller.scrollTo(0, Number.MAX_VALUE);
       }
 
-      aTab.scrolledAreaChanged();
+      aTab.scrolledAreaChanged(true);
       aTab.updateThumbnail();
 
-      browser.messageManager.addMessageListener("MozScrolledAreaChanged", aTab.scrolledAreaChanged);
       aTab.updateContentCapture();
     });
   }
 };
 
 
 const OPEN_APPTAB = 100; // Hack until we get a real API
 
@@ -2681,34 +2695,31 @@ function Tab(aURI, aParams) {
   this._id = null;
   this._browser = null;
   this._notification = null;
   this._loading = false;
   this._chromeTab = null;
   this._metadata = null;
 
   this.contentMightCaptureMouse = false;
-  this.useFallbackWidth = false;
   this.owner = null;
 
   this.hostChanged = false;
   this.state = null;
 
   // Set to 0 since new tabs that have not been viewed yet are good tabs to
   // toss if app needs more memory.
   this.lastSelected = 0;
 
   // aParams is an object that contains some properties for the initial tab
   // loading like flags, a referrerURI, a charset or even a postData.
   this.create(aURI, aParams || {});
 
   // default tabs to inactive (i.e. no display port)
   this.active = false;
-
-  this.scrolledAreaChanged = this.scrolledAreaChanged.bind(this);
 }
 
 Tab.prototype = {
   get browser() {
     return this._browser;
   },
 
   get notification() {
@@ -2778,18 +2789,18 @@ Tab.prototype = {
       let validW = viewportW > 0;
       let validH = viewportH > 0;
 
       if (validW && !validH) {
         viewportH = viewportW * (screenH / screenW);
       } else if (!validW && validH) {
         viewportW = viewportH * (screenW / screenH);
       } else if (!validW && !validH) {
-        viewportW = this.useFallbackWidth ? kFallbackBrowserWidth : kDefaultBrowserWidth;
-        viewportH = kDefaultBrowserWidth * (screenH / screenW);
+        viewportW = Browser.defaultBrowserWidth;
+        viewportH = Browser.defaultBrowserWidth * (screenH / screenW);
       }
     }
 
     // Make sure the viewport height is not shorter than the window when
     // the page is zoomed out to show its full width.
     if (viewportH * this.clampZoomLevel(this.getPageZoomLevel()) < screenH)
       viewportH = Math.max(viewportH, screenH * (browser.contentDocumentWidth / screenW));
 
@@ -2939,35 +2950,35 @@ Tab.prototype = {
     return rounded || 1.0;
   },
 
   /** Record the initial zoom level when a page first loads. */
   resetZoomLevel: function resetZoomLevel() {
     this._defaultZoomLevel = this._browser.scale;
   },
 
-  scrolledAreaChanged: function scrolledAreaChanged() {
+  scrolledAreaChanged: function scrolledAreaChanged(firstPaint) {
     if (!this._browser)
       return;
 
+    if (firstPaint) {
+      // You only get one shot, do not miss your chance to reflow.
+      this.updateViewportSize();
+    }
+
     this.updateDefaultZoomLevel();
-
-    if (!this.useFallbackWidth && this._browser.contentDocumentWidth > kDefaultBrowserWidth)
-      this.useFallbackWidth = true;
-
-    this.updateViewportSize();
   },
 
   /**
    * Recalculate default zoom level when page size changes, and update zoom
    * level if we are at default.
    */
   updateDefaultZoomLevel: function updateDefaultZoomLevel() {
     let browser = this._browser;
-    if (!browser)
+    if (!browser || !this._firstPaint)
       return;
 
     let isDefault = this.isDefaultZoomLevel();
     this._defaultZoomLevel = this.getDefaultZoomLevel();
     if (isDefault) {
       if (browser.scale != this._defaultZoomLevel) {
         browser.scale = this._defaultZoomLevel;
       } else {
@@ -3189,39 +3200,41 @@ var ViewableAreaObserver = {
     let oldWidth = parseInt(Browser.styles["viewable-width"].width);
 
     let newWidth = this.width;
     let newHeight = this.height;
     if (newHeight == oldHeight && newWidth == oldWidth)
       return;
 
     // Guess if the window has been resize to handle a virtual keyboard
-    let isDueToKeyboard = (newHeight != oldHeight && newWidth == oldWidth);
     this.isKeyboardOpened = (newHeight < oldHeight && newWidth == oldWidth);
 
     Browser.styles["viewable-height"].height = newHeight + "px";
     Browser.styles["viewable-height"].maxHeight = newHeight + "px";
 
     Browser.styles["viewable-width"].width = newWidth + "px";
     Browser.styles["viewable-width"].maxWidth = newWidth + "px";
 
-    let startup = !oldHeight && !oldWidth;
-    if (!isDueToKeyboard) {
+    // Don't update the viewport if screen height is shrinking, which typically
+    // means the keyboard appeared. This helps smooth out the experience of our
+    // form filler.
+    if (newHeight > oldHeight || newWidth != oldWidth) {
+      let startup = !oldHeight && !oldWidth;
       for (let i = Browser.tabs.length - 1; i >= 0; i--) {
         let tab = Browser.tabs[i];
         let oldContentWindowWidth = tab.browser.contentWindowWidth;
         tab.updateViewportSize(); // contentWindowWidth may change here.
-  
+
         // Don't bother updating the zoom level on startup
         if (!startup) {
           // If the viewport width is still the same, the page layout has not
           // changed, so we can keep keep the same content on-screen.
           if (tab.browser.contentWindowWidth == oldContentWindowWidth)
             tab.restoreViewportPosition(oldWidth, newWidth);
-  
+
           tab.updateDefaultZoomLevel();
         }
       }
     }
 
     // setTimeout(callback, 0) to ensure the resize event handler dispatch is finished
     setTimeout(function() {
       let event = document.createEvent("Events");