Bug 628799 - (1/2) Kill browser.scale and make fuzzy zoom API [r=stechz]
authorMatt Brubeck <mbrubeck@mozilla.com>
Wed, 02 Feb 2011 11:03:46 -0800
changeset 2731 134e1e507ca39845266a21a61114cb0d8c66157c
parent 2730 b7d5e2d0ec015e48699a2aa2e03a2e2aee1a9182
child 2732 744bdd9543c959bb8b81687d0f0185c4360e97e0
push id2294
push usermbrubeck@mozilla.com
push dateWed, 02 Feb 2011 19:05:46 +0000
reviewersstechz
bugs628799
Bug 628799 - (1/2) Kill browser.scale and make fuzzy zoom API [r=stechz]
chrome/content/AnimatedZoom.js
chrome/content/bindings/browser.xml
chrome/content/browser.js
chrome/content/input.js
--- a/chrome/content/AnimatedZoom.js
+++ b/chrome/content/AnimatedZoom.js
@@ -38,78 +38,86 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 /**
  * Responsible for zooming in to a given view rectangle
  */
 const AnimatedZoom = {
+  startScale: null,
+
   /** Starts an animated zoom to zoomRect. */
   animateTo: function(aZoomRect) {
     if (!aZoomRect)
       return;
 
     this.zoomTo = aZoomRect.clone();
 
     if (this.animationDuration === undefined)
       this.animationDuration = Services.prefs.getIntPref("browser.ui.zoom.animationDuration");
 
     Browser.hideSidebars();
     Browser.hideTitlebar();
     Browser.forceChromeReflow();
 
-    this.beginTime = mozAnimationStartTime;
+    this.start();
 
     // Check if zooming animations were occuring before.
-    if (this.zoomRect) {
-      this.zoomFrom = this.zoomRect;
-    } else {
-      this.zoomFrom = this.getStartRect();
+    if (!this.zoomRect) {
       this.updateTo(this.zoomFrom);
 
       mozRequestAnimationFrame(this);
 
       let event = document.createEvent("Events");
       event.initEvent("AnimatedZoomBegin", true, true);
       window.dispatchEvent(event);
     }
   },
 
+  start: function start() {
+    this.browser = getBrowser();
+    this.zoomFrom = this.zoomRect || this.getStartRect();
+    this.startScale = this.browser.scale;
+    this.beginTime = mozAnimationStartTime;
+  },
+
   /** Get the visible rect, in device pixels relative to the content origin. */
   getStartRect: function getStartRect() {
-    let browser = getBrowser();
+    let browser = this.browser;
     let bcr = browser.getBoundingClientRect();
     let scroll = browser.getRootView().getPosition();
     return new Rect(scroll.x, scroll.y, bcr.width, bcr.height);
   },
 
   /** Update the visible rect, in device pixels relative to the content origin. */
   updateTo: function(nextRect) {
-    let browser = getBrowser();
     let zoomRatio = window.innerWidth / nextRect.width;
-    let zoomLevel = browser.scale * zoomRatio;
+    let scale = this.startScale * zoomRatio;
+    let scrollX = nextRect.left * zoomRatio;
+    let scrollY = nextRect.top * zoomRatio;
 
-    // We use _contentView and setScale because we do *not* want the displayport to update.
-    // XXX We need a new API, see bug 628799.
-    let contentView = browser.getRootView();
-    contentView.setScale(zoomLevel);
-    if ("_contentView" in contentView)
-      contentView._contentView.scrollTo(nextRect.left * zoomRatio, nextRect.top * zoomRatio);
+    this.browser.fuzzyZoom(scale, scrollX, scrollY);
 
     this.zoomRect = nextRect;
   },
 
   /** Stop animation, zoom to point, and clean up. */
   finish: function() {
-    Browser.setVisibleRect(this.zoomTo || this.zoomRect);
+    this.updateTo(this.zoomTo || this.zoomRect);
+    this.browser.finishFuzzyZoom();
+
+    Browser.hideSidebars();
+    Browser.hideTitlebar();
+
     this.beginTime = null;
     this.zoomTo = null;
     this.zoomFrom = null;
     this.zoomRect = null;
+    this.startScale = null;
 
     let event = document.createEvent("Events");
     event.initEvent("AnimatedZoomEnd", true, true);
     window.dispatchEvent(event);
   },
 
   isZooming: function isZooming() {
     return this.beginTime != null;
--- a/chrome/content/bindings/browser.xml
+++ b/chrome/content/bindings/browser.xml
@@ -384,42 +384,31 @@
       <field name="_contentDocumentHeight">0</field>
       <property name="contentDocumentWidth"
                 onget="return this._contentDocumentWidth;"
                 readonly="true"/>
       <property name="contentDocumentHeight"
                 onget="return this._contentDocumentHeight;"
                 readonly="true"/>
 
-      <!-- Zoom level is the ratio of device pixels to CSS pixels -->
-      <field name="_scale">1</field>
+      <!-- The ratio of device pixels to CSS pixels -->
       <property name="scale"
-                onget="return this._scale;"
-                onset="return this._setScale(val);"/>
+                onget="return 1;"
+                onset="return 1;"/>
 
       <!-- These counters are used to update the cached viewport after they reach a certain
            threshold when scrolling -->
       <field name="_cacheRatioWidth">1</field>
       <field name="_cacheRatioHeight">1</field>
 
       <!-- Used in remote tabs only. -->
       <method name="_updateCSSViewport">
         <body/>
       </method>
 
-      <!-- Sets the scale of CSS pixels to device pixels. Does not affect page layout. -->
-      <method name="_setScale">
-        <parameter name="scale"/>
-        <body>
-          <![CDATA[
-            // XXX Not implemented for local browsers.
-          ]]>
-        </body>
-      </method>
-
       <!-- Sets size of CSS viewport, which affects how page is layout. -->
       <method name="setWindowSize">
         <parameter name="width"/>
         <parameter name="height"/>
         <body>
           <![CDATA[
             this._contentWindowWidth = width;
             this._contentWindowHeight = height;
@@ -496,19 +485,16 @@
 
           _updateCacheViewport: function() {
           },
 
           isRoot: function() {
             return true;
           },
 
-          setScale: function(aXScale, aYScale) {
-          },
-
           scrollBy: function(x, y) {
             let self = this.self;
             let position = this.getPosition();
             x = Math.floor(Math.max(0, Math.min(self.contentDocumentWidth,  position.x + x)) - position.x);
             y = Math.floor(Math.max(0, Math.min(self.contentDocumentHeight, position.y + y)) - position.y);
             self.contentWindow.scrollBy(x, y);
           },
 
@@ -539,18 +525,18 @@
       <method name="transformClientToBrowser">
         <parameter name="clientX"/>
         <parameter name="clientY"/>
         <body>
           <![CDATA[
             let bcr = this.getBoundingClientRect();
             let view = this.getRootView();
             let scroll = this.getRootView().getPosition();
-            return { x: (clientX + scroll.x - bcr.left) / this._scale,
-                     y: (clientY + scroll.y - bcr.top) / this._scale };
+            return { x: (clientX + scroll.x - bcr.left) / this.scale,
+                     y: (clientY + scroll.y - bcr.top) / this.scale };
           ]]>
         </body>
       </method>
 
       <constructor>
         <![CDATA[
           this._frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
           this._contentViewManager = this._frameLoader.QueryInterface(Components.interfaces.nsIContentViewManager);
@@ -760,19 +746,20 @@
         ({
           _updateCacheViewport: function() {},
           _getViewportSize: function() {},
 
           isRoot: function() {
             return true;
           },
 
+          _scale: 1,
+          _setScale: function(scale) {},
           scrollBy: function(x, y) {},
           scrollTo: function(x, y) {},
-          setScale: function(aXScale, aYScale) {},
           getPosition: function() {
             return { x: 0, y: 0 };
           }
         })
       ]]></field>
 
       <field name="_contentViewPrototype"><![CDATA[
         ({
@@ -899,23 +886,23 @@
 
             // Use our pixels efficiently and don't try to cache things outside of content
             // boundaries.
             let bounds = new Rect(0, 0, contentSize.width, contentSize.height);
             let displayport = new Rect(cacheX, cacheY, cacheSize.width, cacheSize.height);
             displayport.translateInside(bounds);
 
             self.messageManager.sendAsyncMessage("Content:SetCacheViewport", {
-              scrollX: contentView.scrollX / self._scale,
-              scrollY: contentView.scrollY / self._scale,
-              x: displayport.x / self._scale,
-              y: displayport.y / self._scale,
-              w: displayport.width / self._scale,
-              h: displayport.height / self._scale,
-              scale: self._scale,
+              scrollX: contentView.scrollX / this._scale,
+              scrollY: contentView.scrollY / this._scale,
+              x: displayport.x / this._scale,
+              y: displayport.y / this._scale,
+              w: displayport.width / this._scale,
+              h: displayport.height / this._scale,
+              scale: this._scale,
               id: contentView.id
             });
 
             this._pixelsPannedSinceRefresh.x = 0;
             this._pixelsPannedSinceRefresh.y = 0;
           },
 
           _getContentSize: function() {
@@ -989,49 +976,67 @@
               this._updateCacheViewport();
           },
 
           scrollTo: function(x, y) {
             let contentView = this._contentView;
             this.scrollBy(x - contentView.scrollX, y - contentView.scrollY);
           },
 
-          setScale: function(scale) {
-            let contentView = this._contentView;
+          _setScale: function _setScale(scale) {
             this._scale = scale;
-            contentView.setScale(scale, scale);
+            this._contentView.setScale(scale, scale);
           },
 
           getPosition: function() {
             let contentView = this._contentView;
             return { x: contentView.scrollX, y: contentView.scrollY };
           }
         })
         ]]>
       </field>
 
-      <!-- Sets the scale of CSS pixels to device pixels. Does not affect page layout. -->
-      <method name="_setScale">
+      <!-- Transform the viewport without updating the displayport. -->
+      <method name="fuzzyZoom">
         <parameter name="scale"/>
-        <body>
-          <![CDATA[
-            if (scale <= 0 || scale == this._scale)
-              return;
+        <parameter name="x"/>
+        <parameter name="y"/>
+        <body><![CDATA[
+          let rootView = this.getRootView();
+          rootView._setScale(scale);
+          rootView._contentView.scrollTo(x, y);
+        ]]></body>
+      </method>
+
+      <!-- After fuzzy zoom, sync the displayport with the new viewport. -->
+      <method name="finishFuzzyZoom">
+        <body><![CDATA[
+          this.getRootView()._updateCacheViewport();
+        ]]></body>
+      </method>
 
-            this._scale = scale;
-            let rootView = this.getRootView();
-            rootView.setScale(scale);
-            rootView._updateCacheViewport();
+      <!-- The ratio of CSS pixels to device pixels. -->
+      <property name="scale">
+        <getter><![CDATA[
+          return this.getRootView()._scale;
+        ]]></getter>
+        <setter><![CDATA[
+          if (val <= 0 || val == this.scale)
+            return;
 
-            let event = document.createEvent("Events");
-            event.initEvent("ZoomChanged", true, false);
-            this.dispatchEvent(event);
-          ]]>
-        </body>
-      </method>
+          let rootView = this.getRootView();
+          rootView._setScale(val);
+          rootView._updateCacheViewport();
+
+          let event = document.createEvent("Events");
+          event.initEvent("ZoomChanged", true, false);
+          this.dispatchEvent(event);
+          return val;
+        ]]></setter>
+      </property>
 
       <method name="_getView">
         <parameter name="contentView"/>
         <body>
           <![CDATA[
             if (!contentView) return null;
 
             // See if we have cached it.
@@ -1077,18 +1082,18 @@
       </method>
 
       <!-- Synchronize the CSS viewport with the projection viewport. -->
       <method name="_updateCSSViewport">
         <body>
           <![CDATA[
             let rootView = this._contentViewManager.rootContentView;
             this.messageManager.sendAsyncMessage("Content:ScrollTo", {
-              x: rootView.scrollX / this._scale,
-              y: rootView.scrollY / this._scale
+              x: rootView.scrollX / this.scale,
+              y: rootView.scrollY / this.scale
             });
           ]]>
         </body>
       </method>
 
       <property name="active" onget="return this._active;">
         <setter><![CDATA[
             this._active = val;
--- a/chrome/content/browser.js
+++ b/chrome/content/browser.js
@@ -975,17 +975,17 @@ var Browser = {
       zoomLevel = zoomValues[i];
 
     zoomLevel = tab.clampZoomLevel(zoomLevel);
 
     let browserRect = browser.getBoundingClientRect();
     let center = browser.transformClientToBrowser(browserRect.width / 2,
                                                   browserRect.height / 2);
     let rect = this._getZoomRectForPoint(center.x, center.y, zoomLevel);
-    this.animatedZoomTo(rect);
+    AnimatedZoom.animateTo(rect);
   },
 
   /** Rect should be in browser coordinates. */
   _getZoomLevelForRect: function _getZoomLevelForRect(rect) {
     const margin = 15;
     return this.selectedTab.clampZoomLevel(window.innerWidth / (rect.width + margin * 2));
   },
 
@@ -1014,73 +1014,42 @@ var Browser = {
     let newVisW = browserRect.width / zoomRatio, newVisH = browserRect.height / zoomRatio;
     let result = new Rect(x - newVisW / 2, y - newVisH / 2, newVisW, newVisH);
 
     // Make sure rectangle doesn't poke out of viewport
     return result.translateInside(new Rect(0, 0, browser.contentDocumentWidth * oldScale,
                                                  browser.contentDocumentHeight * oldScale));
   },
 
-  animatedZoomTo: function animatedZoomTo(rect) {
-    AnimatedZoom.animateTo(rect);
-  },
-
-  setVisibleRect: function setVisibleRect(rect) {
-    let browser = getBrowser();
-    let zoomRatio = window.innerWidth / rect.width;
-    let zoomLevel = browser.scale * zoomRatio;
-    let scrollX = rect.left * zoomRatio;
-    let scrollY = rect.top * zoomRatio;
-
-    this.hideSidebars();
-    this.hideTitlebar();
-
-    let scale = this.selectedTab.clampZoomLevel(zoomLevel);
-
-    // Use _contentView and setScale so that the displayport does not update.
-    // See bug 628799.
-    let view = browser.getRootView();
-    view.setScale(scale);
-    if ("_contentView" in view)
-      view._contentView.scrollTo(scrollX, scrollY);
-
-    // If the scale level doesn't change ensure the view is well refreshed
-    // otherwise setting the scale level of the browser will do it
-    if (scale == browser.scale)
-      view._updateCacheViewport();
-    else
-      browser.scale = scale;
-  },
-
   zoomToPoint: function zoomToPoint(cX, cY, aRect) {
     let tab = this.selectedTab;
     if (!tab.allowZoom)
       return null;
 
     let zoomRect = null;
     if (aRect)
       zoomRect = this._getZoomRectForRect(aRect, cY);
 
     if (!zoomRect && tab.isDefaultZoomLevel()) {
       let scale = tab.clampZoomLevel(tab.browser.scale * 2);
       zoomRect = this._getZoomRectForPoint(cX, cY, scale);
     }
 
     if (zoomRect)
-      this.animatedZoomTo(zoomRect);
+      AnimatedZoom.animateTo(zoomRect);
 
     return zoomRect;
   },
 
   zoomFromPoint: function zoomFromPoint(cX, cY) {
     let tab = this.selectedTab;
     if (tab.allowZoom && !tab.isDefaultZoomLevel()) {
       let zoomLevel = tab.getDefaultZoomLevel();
       let zoomRect = this._getZoomRectForPoint(cX, cY, zoomLevel);
-      this.animatedZoomTo(zoomRect);
+      AnimatedZoom.animateTo(zoomRect);
     }
   },
 
   // The device-pixel-to-CSS-px ratio used to adjust meta viewport values.
   // This is higher on higher-dpi displays, so pages stay about the same physical size.
   getScaleRatio: function getScaleRatio() {
     let prefValue = Services.prefs.getIntPref("browser.viewport.scaleRatio");
     if (prefValue > 0)
--- a/chrome/content/input.js
+++ b/chrome/content/input.js
@@ -1106,16 +1106,17 @@ GestureModule.prototype = {
     let event = document.createEvent("Events");
     event.initEvent("CancelTouchSequence", true, true);
     let success = aEvent.target.dispatchEvent(event);
 
     if (!success || !Browser.selectedTab.allowZoom)
       return;
 
     // create the AnimatedZoom object for fast arbitrary zooming
+    AnimatedZoom.start();
     this._pinchZoom = AnimatedZoom;
     this._pinchStartRect = AnimatedZoom.getStartRect();
     this._pinchDelta = 0;
 
     let browser = getBrowser();
     this._pinchStartScale = this._pinchScale = browser.scale;
 
     this._ignoreNextUpdate = true; // first update gives useless, huge delta