Bug 708746 - Part 3: Don't display a document until we've changed the browser size to match its viewport. r=kats
authorPatrick Walton <pwalton@mozilla.com>
Fri, 06 Jan 2012 16:42:46 -0800
changeset 85205 18a2b5e395ca86bcba19b0f349df0d41f299387d
parent 85204 ef5ab1377d94943fd3dc7c76ab275c1cb05caf44
child 85206 4539e9ec1e19fb85eaaf4daedb23ef5d55d6bfba
push id805
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 18:17:35 +0000
treeherdermozilla-aurora@6fb3bf232436 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats
bugs708746
milestone12.0a1
Bug 708746 - Part 3: Don't display a document until we've changed the browser size to match its viewport. r=kats
mobile/android/chrome/content/browser.js
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -158,21 +158,40 @@ var Strings = {};
 });
 
 var MetadataProvider = {
   getDrawMetadata: function getDrawMetadata() {
     return BrowserApp.getDrawMetadata();
   },
 
   paintingSuppressed: function paintingSuppressed() {
-    let browser = BrowserApp.selectedBrowser;
-    if (!browser)
+    // Get the current tab. Don't suppress painting if there are no tabs yet.
+    let tab = BrowserApp.selectedTab;
+    if (!tab)
       return false;
-    let cwu = browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                                   .getInterface(Ci.nsIDOMWindowUtils);
+
+    // If the viewport metadata has not yet been updated (and therefore the browser size has not
+    // been changed accordingly), do not draw yet. We'll get an unsightly flash on page transitions
+    // otherwise, because we receive a paint event after the new document is shown but before the
+    // correct browser size for the new document has been set.
+    //
+    // This whole situation exists because the docshell and the browser element are unaware of the
+    // existence of <meta viewport>. Therefore they dispatch paint events without knowledge of the
+    // invariant that the page must not be drawn until the browser size has been appropriately set.
+    // It would be easier if the docshell were made aware of the existence of <meta viewport> so
+    // that this logic could be removed.
+
+    let viewportDocumentId = tab.documentIdForCurrentViewport;
+    let contentDocumentId = ViewportHandler.getIdForDocument(tab.browser.contentDocument);
+    if (viewportDocumentId != null && viewportDocumentId != contentDocumentId)
+      return true;
+
+    // Suppress painting if the current presentation shell is suppressing painting.
+    let cwu = tab.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                                       .getInterface(Ci.nsIDOMWindowUtils);
     return cwu.paintingSuppressed;
   }
 };
 
 var BrowserApp = {
   _tabs: [],
   _selectedTab: null,
 
@@ -1241,16 +1260,17 @@ function Tab(aURL, aParams) {
   this.vbox = null;
   this.id = 0;
   this.agentMode = UA_MODE_MOBILE;
   this.lastHost = null;
   this.create(aURL, aParams);
   this._viewport = { x: 0, y: 0, width: gScreenWidth, height: gScreenHeight, offsetX: 0, offsetY: 0,
                      pageWidth: gScreenWidth, pageHeight: gScreenHeight, zoom: 1.0 };
   this.viewportExcess = { x: 0, y: 0 };
+  this.documentIdForCurrentViewport = null;
   this.userScrollPos = { x: 0, y: 0 };
   this._pluginsToPlay = [];
   this._pluginOverlayShowing = false;
 }
 
 Tab.prototype = {
   create: function(aURL, aParams) {
     if (this.browser)
@@ -1297,18 +1317,20 @@ Tab.prototype = {
 
     this.browser.addEventListener("DOMContentLoaded", this, true);
     this.browser.addEventListener("DOMLinkAdded", this, true);
     this.browser.addEventListener("DOMTitleChanged", this, true);
     this.browser.addEventListener("DOMWindowClose", this, true);
     this.browser.addEventListener("scroll", this, true);
     this.browser.addEventListener("PluginClickToPlay", this, true);
     this.browser.addEventListener("pagehide", this, true);
+    this.browser.addEventListener("pageshow", this, true);
 
     Services.obs.addObserver(this, "http-on-modify-request", false);
+    Services.obs.addObserver(this, "document-shown", false);
 
     if (!aParams.delayLoad) {
       let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
       let postData = ("postData" in aParams && aParams.postData) ? aParams.postData.value : null;
       let referrerURI = "referrerURI" in aParams ? aParams.referrerURI : null;
       let charset = "charset" in aParams ? aParams.charset : null;
 
       try {
@@ -1358,26 +1380,28 @@ Tab.prototype = {
     this.browser.removeProgressListener(this);
     this.browser.removeEventListener("DOMContentLoaded", this, true);
     this.browser.removeEventListener("DOMLinkAdded", this, true);
     this.browser.removeEventListener("DOMTitleChanged", this, true);
     this.browser.removeEventListener("DOMWindowClose", this, true);
     this.browser.removeEventListener("scroll", this, true);
     this.browser.removeEventListener("PluginClickToPlay", this, true);
     this.browser.removeEventListener("pagehide", this, true);
+    this.browser.removeEventListener("pageshow", this, true);
 
     // Make sure the previously selected panel remains selected. The selected panel of a deck is
     // not stable when panels are removed.
     let selectedPanel = BrowserApp.deck.selectedPanel;
     BrowserApp.deck.removeChild(this.vbox);
     BrowserApp.deck.selectedPanel = selectedPanel;
 
     Services.obs.removeObserver(this, "http-on-modify-request", false);
     this.browser = null;
     this.vbox = null;
+    this.documentIdForCurrentViewport = null;
     let message = {
       gecko: {
         type: "Tab:Closed",
         tabID: this.id
       }
     };
 
     sendMessageToJava(message);
@@ -1524,18 +1548,16 @@ Tab.prototype = {
     switch (aEvent.type) {
       case "DOMContentLoaded": {
         let target = aEvent.originalTarget;
 
         // ignore on frames
         if (target.defaultView != this.browser.contentWindow)
           return;
 
-        this.updateViewport(true);
-
         sendMessageToJava({
           gecko: {
             type: "DOMContentLoaded",
             tabID: this.id,
             windowID: 0,
             uri: this.browser.currentURI.spec,
             title: this.browser.contentTitle
           }
@@ -1919,28 +1941,41 @@ Tab.prototype = {
   getWindowForRequest: function(aRequest) {
     let loadContext = this.getRequestLoadContext(aRequest);
     if (loadContext)
       return loadContext.associatedWindow;
     return null;
   },
 
   observe: function(aSubject, aTopic, aData) {
-    if (!(aSubject instanceof Ci.nsIHttpChannel))
-      return;
-
-    let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
-    if (!(channel.loadFlags & Ci.nsIChannel.LOAD_DOCUMENT_URI))
-      return;
-
-    let channelWindow = this.getWindowForRequest(channel);
-    if (channelWindow == this.browser.contentWindow) {
-      this.setHostFromURL(channel.URI.spec);
-      if (this.agentMode == UA_MODE_DESKTOP)
-        channel.setRequestHeader("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1", false);
+    switch (aTopic) {
+      case "http-on-modify-request":
+        if (!(aSubject instanceof Ci.nsIHttpChannel))
+          return;
+
+        let channel = aSubject.QueryInterface(Ci.nsIHttpChannel);
+        if (!(channel.loadFlags & Ci.nsIChannel.LOAD_DOCUMENT_URI))
+          return;
+
+        let channelWindow = this.getWindowForRequest(channel);
+        if (channelWindow == this.browser.contentWindow) {
+          this.setHostFromURL(channel.URI.spec);
+          if (this.agentMode == UA_MODE_DESKTOP)
+            channel.setRequestHeader("User-Agent", "Mozilla/5.0 (X11; Linux x86_64; rv:7.0.1) Gecko/20100101 Firefox/7.0.1", false);
+        }
+        break;
+
+      case "document-shown":
+        // Is it on the top level?
+        let contentDocument = aSubject;
+        if (contentDocument == this.browser.contentDocument) {
+          ViewportHandler.updateMetadata(this);
+          this.documentIdForCurrentViewport = ViewportHandler.getIdForDocument(contentDocument);
+        }
+        break;
     }
   },
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIWebProgressListener,
     Ci.nsISHistoryListener,
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference
@@ -2823,16 +2858,21 @@ const kViewportMinHeight = 223;
 const kViewportMaxHeight = 10000;
 
 var ViewportHandler = {
   // The cached viewport metadata for each document. We tie viewport metadata to each document
   // instead of to each tab so that we don't have to update it when the document changes. Using an
   // ES6 weak map lets us avoid leaks.
   _metadata: new WeakMap(),
 
+  // A list of document IDs, arbitrarily assigned. We use IDs to refer to content documents instead
+  // of strong references to avoid leaking them.
+  _documentIds: new WeakMap(),
+  _nextDocumentId: 0,
+
   init: function init() {
     addEventListener("DOMWindowCreated", this, false);
     addEventListener("DOMMetaAdded", this, false);
     addEventListener("DOMContentLoaded", this, false);
     addEventListener("pageshow", this, false);
     addEventListener("resize", this, false);
   },
 
@@ -2996,16 +3036,29 @@ var ViewportHandler = {
   /** Returns the default viewport metadata for a document. */
   getDefaultMetadata: function getDefaultMetadata() {
     return {
       autoSize: false,
       allowZoom: true,
       autoScale: true,
       scaleRatio: ViewportHandler.getScaleRatio()
     };
+  },
+
+  /**
+   * Returns a globally unique ID for the given content document. Using IDs to refer to documents
+   * allows content documents to be identified without any possibility of leaking them.
+   */
+  getIdForDocument: function getIdForDocument(aDocument) {
+    let id = this._documentIds.get(aDocument, null);
+    if (id == null) {
+      id = this._nextDocumentId++;
+      this._documentIds.set(aDocument, id);
+    }
+    return id;
   }
 };
 
 /**
  * Handler for blocked popups, triggered by DOMUpdatePageReport events in browser.xml
  */
 var PopupBlockerObserver = {
   onUpdatePageReport: function onUpdatePageReport(aEvent) {