Bug 566500 - e10s: Move viewport metadata support into the browser XBL binding [r=mfinkle]
authorMatt Brubeck <mbrubeck@mozilla.com>
Wed, 09 Jun 2010 19:07:12 -0400
changeset 66271 70e03eb070a95a26b70216db412491a1f454575d
parent 66270 e2d43f22d4b261b24f24ec1fb65b8b96146e9b11
child 66272 3f70fd00c9b0b8e431bc99ccc573cd418ad1f57b
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmfinkle
bugs566500
Bug 566500 - e10s: Move viewport metadata support into the browser XBL binding [r=mfinkle]
mobile/chrome/content/Util.js
mobile/chrome/content/browser.js
mobile/chrome/content/content.js
--- a/mobile/chrome/content/Util.js
+++ b/mobile/chrome/content/Util.js
@@ -80,16 +80,28 @@ let Util = {
   },
 
   /** Like dump, but each arg is handled and there's an automatic newline */
   dumpLn: function dumpLn() {
     for (var i = 0; i < arguments.length; i++) { dump(arguments[i] + " "); }
     dump("\n");
   },
 
+  getWindowUtils: function getWindowUtils(aWindow) {
+    return aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+  },
+
+  getScrollOffset: function getScrollOffset(aWindow) {
+    var cwu = Util.getWindowUtils(aWindow);
+    var scrollX = {};
+    var scrollY = {};
+    cwu.getScrollXY(false, scrollX, scrollY);
+    return new Point(scrollX.value, scrollY.value);
+  },
+
   /** Executes aFunc after other events have been processed. */
   executeSoon: function executeSoon(aFunc) {
     let tm = Cc["@mozilla.org/thread-manager;1"].getService(Ci.nsIThreadManager);
     tm.mainThread.dispatch({
       run: function() {
         aFunc();
       }
     }, Ci.nsIThread.DISPATCH_NORMAL);
@@ -117,79 +129,16 @@ let Util = {
       return null;
   },
 
   makeURLAbsolute: function makeURLAbsolute(base, url) {
     // Note:  makeURI() will throw if url is not a valid URI
     return makeURI(url, null, makeURI(base)).spec;
   },
 
-  getViewportMetadata: function getViewportMetadata(browser) {
-    let dpiScale = gPrefService.getIntPref("zoom.dpiScale") / 100;
-
-    let doctype = browser.contentDocument.doctype;
-    if (doctype && /(WAP|WML|Mobile)/.test(doctype.publicId))
-      return { defaultZoom: dpiScale, autoSize: true };
-
-    let windowUtils = browser.contentWindow
-                             .QueryInterface(Ci.nsIInterfaceRequestor)
-                             .getInterface(Ci.nsIDOMWindowUtils);
-    let handheldFriendly = windowUtils.getDocumentMetadata("HandheldFriendly");
-    if (handheldFriendly == "true")
-      return { defaultZoom: dpiScale, autoSize: true };
-
-    if (browser.contentDocument instanceof XULDocument)
-      return { defaultZoom: 1.0, autoSize: true, allowZoom: false };
-
-    // viewport details found here
-    // http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html
-    // http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html
-    
-    // Note: These values will be NaN if parseFloat or parseInt doesn't find a number.
-    // Remember that NaN is contagious: Math.max(1, NaN) == Math.min(1, NaN) == NaN.
-    let viewportScale = parseFloat(windowUtils.getDocumentMetadata("viewport-initial-scale"));
-    let viewportMinScale = parseFloat(windowUtils.getDocumentMetadata("viewport-minimum-scale"));
-    let viewportMaxScale = parseFloat(windowUtils.getDocumentMetadata("viewport-maximum-scale"));
-    let viewportWidthStr = windowUtils.getDocumentMetadata("viewport-width");
-    let viewportHeightStr = windowUtils.getDocumentMetadata("viewport-height");
-
-    viewportScale = Util.clamp(viewportScale, kViewportMinScale, kViewportMaxScale);
-    viewportMinScale = Util.clamp(viewportMinScale, kViewportMinScale, kViewportMaxScale);
-    viewportMaxScale = Util.clamp(viewportMaxScale, kViewportMinScale, kViewportMaxScale);
-
-    // If initial scale is 1.0 and width is not set, assume width=device-width
-    let autoSize = (viewportWidthStr == "device-width" ||
-                    viewportHeightStr == "device-height" ||
-                    (viewportScale == 1.0 && !viewportWidthStr));
-
-    let viewportWidth = Util.clamp(parseInt(viewportWidthStr), kViewportMinWidth, kViewportMaxWidth);
-    let viewportHeight = Util.clamp(parseInt(viewportHeightStr), kViewportMinHeight, kViewportMaxHeight);
-
-    // Zoom level is the final (device pixel : CSS pixel) ratio for content.
-    // Since web content specifies scale as (reference pixel : CSS pixel) ratio,
-    // multiply the requested scale by a constant (device pixel : reference pixel)
-    // factor to account for high DPI devices.
-    //
-    // See bug 561445 or any of the examples of chrome/tests/browser_viewport_XX.html
-    // for more information and examples.
-    let defaultZoom = viewportScale * dpiScale;
-    let minZoom = viewportMinScale * dpiScale;
-    let maxZoom = viewportMaxScale * dpiScale;
-
-    return {
-      defaultZoom: defaultZoom,
-      minZoom: minZoom,
-      maxZoom: maxZoom,
-      width: viewportWidth,
-      height: viewportHeight,
-      autoSize: autoSize,
-      allowZoom: windowUtils.getDocumentMetadata("viewport-user-scalable") != "no"
-    };
-  },
-
   clamp: function(num, min, max) {
     return Math.max(min, Math.min(max, num));
   },
 
   /**
    * Determines whether a home page override is needed.
    * Returns:
    *  "new profile" if this is the first run with a new profile.
@@ -244,22 +193,92 @@ let Util = {
 
   // Put the Mozilla networking code into a state that will kick the auto-connection
   // process.
   forceOnline: function forceOnline() {
 #ifdef MOZ_ENABLE_LIBCONIC
     gIOService.offline = false;
 #endif
   },
+
+  /** Capitalize first letter of a string. */
+  capitalize: function(str) {
+    return str.charAt(0).toUpperCase() + str.substring(1);
+  },
   
   isPortrait: function isPortrait() {
     return (window.innerWidth < 500);
   }
 };
 
+
+/**
+ * Helper class to nsITimer that adds a little more pizazz.  Callback can be an
+ * object with a notify method or a function.
+ */
+Util.Timeout = function(aCallback) {
+  this._callback = aCallback;
+  this._timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+  this._active = false;
+}
+
+Util.Timeout.prototype = {
+  /** Timer callback. Don't call this manually. */
+  notify: function notify() {
+    this._active = false;
+    if (this._callback.notify)
+      this._callback.notify();
+    else
+      this._callback.apply(null);
+  },
+
+  /** Do the callback once.  Cancels other timeouts on this object. */
+  once: function once(aDelay, aCallback) {
+    if (aCallback)
+      this._callback = aCallback;
+    this.clear();
+    this._timer.initWithCallback(this, aDelay, this._timer.TYPE_ONE_SHOT);
+    this._active = true;
+    return this;
+  },
+
+  /** Do the callback every aDelay msecs. Cancels other timeouts on this object. */
+  interval: function interval(aDelay, aCallback) {
+    if (aCallback)
+      this._callback = aCallback;
+    this.clear();
+    this._timer.initWithCallback(this, aDelay, this._timer.TYPE_REPEATING_SLACK);
+    this._active = true;
+    return this;
+  },
+
+  /** Clear any pending timeouts. */
+  clear: function clear() {
+    if (this._active) {
+      this._timer.cancel();
+      this._active = false;
+    }
+    return this;
+  },
+
+  /** If there is a pending timeout, call it and cancel the timeout. */
+  flush: function flush() {
+    if (this._active) {
+      this.clear();
+      this.notify();
+    }
+    return this;
+  },
+
+  /** Return true iff we are waiting for a callback. */
+  isPending: function isPending() {
+    return this._active;
+  }
+};
+
 /**
  * Cache of commonly used elements.
  */
 let Elements = {};
 
 [
   ["browserBundle",      "bundle_browser"],
   ["contentShowing",     "bcast_contentShowing"],
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -354,16 +354,17 @@ var Browser = {
   pageScrollbox: null,
   pageScrollboxScroller: null,
   styles: {},
 
   startup: function startup() {
     var self = this;
 
     try {
+      messageManager.loadFrameScript("chrome://browser/content/Util.js", true);
       messageManager.loadFrameScript("chrome://browser/content/content.js", true);
     } catch (e) {
       // XXX whatever is calling startup needs to dump errors!
       dump("###########" + e + "\n");
     }
 
     let needOverride = Util.needHomepageOverride();
     if (needOverride == "new profile")
@@ -568,16 +569,17 @@ var Browser = {
                                      label, false, "", null);
       }
       gPrefService.clearUserPref("extensions.disabledAddons");
     }
 
     // Force commonly used border-images into the image cache
     ImagePreloader.cache();
 
+    messageManager.addMessageListener("FennecViewportMetadata", this);
     messageManager.addMessageListener("MozApplicationManifest", OfflineApps);
 
     this._pluginObserver = new PluginObserver(bv);
 
     // broadcast a UIReady message so add-ons know we are finished with startup
     let event = document.createEvent("Events");
     event.initEvent("UIReady", true, false);
     window.dispatchEvent(event);
@@ -732,23 +734,32 @@ var Browser = {
     let tabs = this._tabs;
     for (let i = 0; i < tabs.length; i++) {
       if (tabs[i].browser.contentDocument == aDocument)
         return tabs[i];
     }
     return null;
   },
 
-  getTabAtIndex: function(index) {
+  getTabForBrowser: function getTabForBrowser(aBrowser) {
+    let tabs = this._tabs;
+    for (let i = 0; i < tabs.length; i++) {
+      if (tabs[i].browser == aBrowser)
+        return tabs[i];
+    }
+    return null;
+  },
+
+  getTabAtIndex: function getTabAtIndex(index) {
     if (index > this._tabs.length || index < 0)
       return null;
     return this._tabs[index];
   },
 
-  getTabFromChrome: function(chromeTab) {
+  getTabFromChrome: function getTabFromChrome(chromeTab) {
     for (var t = 0; t < this._tabs.length; t++) {
       if (this._tabs[t].chromeTab == chromeTab)
         return this._tabs[t];
     }
     return null;
   },
 
   addTab: function(uri, bringFront) {
@@ -1377,16 +1388,25 @@ var Browser = {
     let x = {};
     let y = {};
     scroller.getPosition(x, y);
     return new Point(x.value, y.value);
   },
 
   forceChromeReflow: function forceChromeReflow() {
     let dummy = getComputedStyle(document.documentElement, "").width;
+  },
+
+  receiveMessage: function receiveMessage(aMessage) {
+    switch (aMessage.name) {
+      case "FennecViewportMetadata":
+        let tab = Browser.getTabForBrowser(aMessage.target);
+        tab.updateViewportMetadata(aMessage.json);
+        break;
+    }
   }
 };
 
 Browser.MainDragger = function MainDragger(browserView) {
   this.bv = browserView;
   this.draggedFrame = null;
   this.contentScrollbox = null;
 };
@@ -3004,22 +3024,21 @@ Tab.prototype = {
       throw "No loading timeout!";
 
     clearTimeout(this._loadingTimeout);
     delete this._loadingTimeout;
     Browser._browserView.commitBatchOperation();
   },
 
   /** Update browser styles when the viewport metadata changes. */
-  updateViewportMetadata: function updateViewportMetadata() {
+  updateViewportMetadata: function updateViewportMetadata(metaData) {
     let browser = this._browser;
     if (!browser)
       return;
 
-    let metaData = Util.getViewportMetadata(this._browser);
     this._browserViewportState.metaData = metaData;
 
     // Remove any previous styles.
     browser.className = "";
     browser.style.removeProperty("width");
     browser.style.removeProperty("height");
 
     // Add classes for auto-sizing viewports.
@@ -3096,27 +3115,21 @@ Tab.prototype = {
         bv.invalidateEntireView();
         bv.setAggressive(false);
         // Sync up browser so previous and forward scroll positions are set. This is a good time to do
         // this because the resulting invalidation is irrelevant.
         bv.ignorePageScroll(true);
         Browser.scrollBrowserToContent();
       }
     }
-
-    this._browserViewportState.metaData = null;
-    this.updateViewportSize();
   },
 
   endLoading: function endLoading() {
     if (!this._loading) throw "Not Loading!";
 
-    if (!this._browserViewportState.metaData)
-      this.updateViewportMetadata();
-
     this.setIcon(this._browser.mIconURL);
     this._loading = false;
 
     if (this == Browser.selectedTab) {
       let bv = Browser._browserView;
       bv.ignorePageScroll(false);
       bv.setAggressive(true);
     }
--- a/mobile/chrome/content/content.js
+++ b/mobile/chrome/content/content.js
@@ -755,18 +755,122 @@ Content.prototype = {
     this._iconURI = null;
     this._coalescer.emptyPage();
     this._coalescer.startCoalescing();
   },
 
   stopLoading: function stopLoading() {
     this._loading = false;
     this._coalescer.stopCoalescing();
-    sendMessage("FennecMetadata", Util.getViewportMetadata(content));
   },
 
   isSelected: function isSelected() {
     return this._selected;
   }
 };
 
+let contentObject = new Content();
 
-let contentObject = new Content();
+let ViewportHandler = {
+  metadata: null,
+
+  init: function init() {
+    addEventListener("DOMContentLoaded", this, false);
+    addEventListener("DOMMetaAdded", this, false);
+    addEventListener("pageshow", this, false);
+
+    this.progresscontroller = new ProgressController(this)
+    this.progresscontroller.start();
+  },
+
+  handleEvent: function handleEvent(aEvent) {
+    switch (aEvent.type) {
+      case "DOMMetaAdded":
+        let target = aEvent.originalTarget;
+        let isRootDocument = (target.ownerDocument == content.document);
+        if (isRootDocument && target.name == "viewport")
+          this.updateMetadata();
+        break;
+
+      case "DOMContentLoaded":
+      case "pageshow":
+        if (!this.metadata)
+          this.updateMetadata();
+        break;
+    }
+  },
+
+  startLoading: function() {
+    this.metadata = null;
+    sendAsyncMessage("FennecViewportMetadata", {});
+  },
+
+  stopLoading: function() {
+  },
+
+  updateMetadata: function notify() {
+    this.metadata = this.getViewportMetadata();
+    sendAsyncMessage("FennecViewportMetadata", this.metadata);
+  },
+
+  getViewportMetadata: function getViewportMetadata() {
+    let dpiScale = gPrefService.getIntPref("zoom.dpiScale") / 100;
+
+    let doctype = content.document.doctype;
+    if (doctype && /(WAP|WML|Mobile)/.test(doctype.publicId))
+      return { defaultZoom: dpiScale, autoSize: true };
+
+    let windowUtils = Util.getWindowUtils(content);
+    let handheldFriendly = windowUtils.getDocumentMetadata("HandheldFriendly");
+    if (handheldFriendly == "true")
+      return { defaultZoom: dpiScale, autoSize: true };
+
+    if (content.document instanceof XULDocument)
+      return { defaultZoom: 1.0, autoSize: true, allowZoom: false };
+
+    // viewport details found here
+    // http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html
+    // http://developer.apple.com/safari/library/documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html
+
+    // Note: These values will be NaN if parseFloat or parseInt doesn't find a number.
+    // Remember that NaN is contagious: Math.max(1, NaN) == Math.min(1, NaN) == NaN.
+    let viewportScale = parseFloat(windowUtils.getDocumentMetadata("viewport-initial-scale"));
+    let viewportMinScale = parseFloat(windowUtils.getDocumentMetadata("viewport-minimum-scale"));
+    let viewportMaxScale = parseFloat(windowUtils.getDocumentMetadata("viewport-maximum-scale"));
+    let viewportWidthStr = windowUtils.getDocumentMetadata("viewport-width");
+    let viewportHeightStr = windowUtils.getDocumentMetadata("viewport-height");
+
+    viewportScale = Util.clamp(viewportScale, kViewportMinScale, kViewportMaxScale);
+    viewportMinScale = Util.clamp(viewportMinScale, kViewportMinScale, kViewportMaxScale);
+    viewportMaxScale = Util.clamp(viewportMaxScale, kViewportMinScale, kViewportMaxScale);
+
+    // If initial scale is 1.0 and width is not set, assume width=device-width
+    let autoSize = (viewportWidthStr == "device-width" ||
+                    viewportHeightStr == "device-height" ||
+                    (viewportScale == 1.0 && !viewportWidthStr));
+
+    let viewportWidth = Util.clamp(parseInt(viewportWidthStr), kViewportMinWidth, kViewportMaxWidth);
+    let viewportHeight = Util.clamp(parseInt(viewportHeightStr), kViewportMinHeight, kViewportMaxHeight);
+
+    // Zoom level is the final (device pixel : CSS pixel) ratio for content.
+    // Since web content specifies scale as (reference pixel : CSS pixel) ratio,
+    // multiply the requested scale by a constant (device pixel : reference pixel)
+    // factor to account for high DPI devices.
+    //
+    // See bug 561445 or any of the examples of chrome/tests/browser_viewport_XX.html
+    // for more information and examples.
+    let defaultZoom = viewportScale * dpiScale;
+    let minZoom = viewportMinScale * dpiScale;
+    let maxZoom = viewportMaxScale * dpiScale;
+
+    return {
+      defaultZoom: defaultZoom,
+      minZoom: minZoom,
+      maxZoom: maxZoom,
+      width: viewportWidth,
+      height: viewportHeight,
+      autoSize: autoSize,
+      allowZoom: windowUtils.getDocumentMetadata("viewport-user-scalable") != "no"
+    };
+  },
+};
+
+ViewportHandler.init();