(half-baked) animated zoom
authorBenjamin Stover <bstover@mozilla.com>
Thu, 02 Sep 2010 09:46:07 -0700
changeset 66623 a4b4efbab276fbb48d408aedcbcb87cecb89b24a
parent 66622 5fc0e9516ad22a9ba8778d975dfdd0659c1a5fff
child 66624 49ba66dce510ded491a99613352c69d65d34c5a0
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)
(half-baked) animated zoom
mobile/app/mobile.js
mobile/chrome/content/AnimatedZoom.js
mobile/chrome/content/bindings/browser.js
mobile/chrome/content/bindings/browser.xml
mobile/chrome/content/browser.js
--- a/mobile/app/mobile.js
+++ b/mobile/app/mobile.js
@@ -368,17 +368,16 @@ pref("browser.console.showInPanel", fals
 // kinetic tweakables
 pref("browser.ui.kinetic.updateInterval", 30);
 pref("browser.ui.kinetic.decelerationRate", 20);
 pref("browser.ui.kinetic.speedSensitivity", 80);
 pref("browser.ui.kinetic.swipeLength", 160);
 
 // zooming
 pref("browser.ui.zoom.pageFitGranularity", 9); // don't zoom to fit by less than 1/9
-pref("browser.ui.zoom.animationFps", 60);
 pref("browser.ui.zoom.animationDuration", 350); // ms duration of double-tap zoom animation
 
 // pinch gesture
 pref("browser.ui.pinch.maxGrowth", 150);     // max pinch distance growth
 pref("browser.ui.pinch.maxShrink", 200);     // max pinch distance shrinkage
 pref("browser.ui.pinch.scalingFactor", 500); // scaling factor for above pinch limits
 
 // Touch radius (area around the touch location to look for target elements):
--- a/mobile/chrome/content/AnimatedZoom.js
+++ b/mobile/chrome/content/AnimatedZoom.js
@@ -40,224 +40,74 @@
  * ***** END LICENSE BLOCK ***** */
 
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 /**
  * Responsible for zooming in to a given view rectangle
- * @param aBrowserView BrowserView instance
- * @param aZoomRect Optional. Zoom rectangle to be configured
  */
-function AnimatedZoom(aBrowserView) {
-  return;
-  this.bv = aBrowserView;
-
-  this.snapshot = AnimatedZoom.createCanvas();
-  if (this.snapshot.pending_render)
-    return;
-
+function AnimatedZoom() {
   // Render a snapshot of the viewport contents around the visible rect
-  let [w, h] = this.bv.getViewportDimensions();
-  let viewportRect = new Rect(0, 0, w, h);
-  this.zoomFrom = this.bv.getVisibleRect().translateInside(viewportRect);
-
-  // try to cover twice the size of the current visible rect
-  this.snapshotRect = this.bv.getVisibleRect().inflate(2);
-
-  // sanitize the snapshot rectangle to fit inside viewport
-  this.snapshotRect.translateInside(viewportRect).restrictTo(viewportRect).expandToIntegers();
-  this.snapshotRect.width = Math.min(this.snapshotRect.width, this.snapshot.width);
-  this.snapshotRect.height = Math.min(this.snapshotRect.height, this.snapshot.height);
-
-  let snapshotCtx = this.snapshot.MozGetIPCContext("2d")
-  snapshotCtx.clearRect(0, 0, this.snapshotRect.width, this.snapshotRect.height);
-  this.bv.renderToCanvas(this.snapshot, this.snapshotRect.width, this.snapshotRect.height, this.snapshotRect.clone());
-
-  let remote = !this.bv.getBrowser().contentWindow;
-  if (remote) {
-    this.snapshot.addEventListener("MozAsyncCanvasRender", this, false);
-    this.snapshot.pending_render = true;
-  } else {
-    this.setupCanvas();
-  }
+  this.animationDuration = Services.prefs.getIntPref("browser.ui.zoom.animationDuration");
 }
 
-AnimatedZoom.prototype.handleEvent = function(aEvent) {
-  if (aEvent.type == "MozAsyncCanvasRender") {
-    let snapshot = aEvent.originalTarget;
-    snapshot.pending_render = false;
-    snapshot.removeEventListener("MozAsyncCanvasRender", this, false);
-
-    if (this.snapshot == snapshot) {
-      this.setupCanvas();
-      this.startTimer();
-    }
-  }
-};
-
-/** Creating a canvas element of width and height. */
-AnimatedZoom.createCanvas = function(aRemote) {
-  if (!this._canvas) {
-    let canvas = document.createElementNS(kXHTMLNamespaceURI, "canvas");
-    canvas.width = Math.max(window.innerWidth, window.innerHeight) * 2;
-    canvas.height = Math.max(window.innerWidth, window.innerHeight) * 2;
-    canvas.mozOpaque = true;
-    this._canvas = canvas;
-  }
-  return this._canvas;
-};
-
-AnimatedZoom.prototype.setupCanvas = function() {
-  return;
-
-  // stop live rendering during zooming
-  this.bv.pauseRendering();
-
-  // hide ui elements to avoid undefined states after zoom
-  Browser.hideTitlebar();
-  Browser.hideSidebars();
-
-  let clientVis = Browser.browserViewToClientRect(this.bv.getCriticalRect());
-  let viewBuffer = Elements.viewBuffer;
-  viewBuffer.left = clientVis.left;
-  viewBuffer.top = clientVis.top;
-  viewBuffer.width = this.zoomFrom.width;
-  viewBuffer.height = this.zoomFrom.height;
-  viewBuffer.style.display = "block";
+AnimatedZoom.prototype = {
+  startTimer: function() {
+    if (this.zoomTo && !this.beginTime) {
+      Browser.hideSidebars();
+      Browser.hideTitlebar();
+      Browser.forceChromeReflow();
 
-  // configure defaults for the canvas' drawing context
-  let ctx = viewBuffer.getContext("2d");
-
-  // disable smoothing and use the fastest composition operation
-  ctx.mozImageSmoothingEnabled = false;
-  ctx.globalCompositeOperation = "copy";
-
-  // set background fill pattern
-  let backgroundImage = new Image();
-  backgroundImage.src = "chrome://browser/content/checkerboard.png";
-  ctx.fillStyle = ctx.createPattern(backgroundImage, "repeat");
-
-  this.canvasReady = true;
-};
-
-AnimatedZoom.prototype.startTimer = function() {
-  if (this.zoomTo && this.canvasReady && !this.timer) {
-    this.updateTo(this.zoomFrom);
+      let browserRect = Rect.fromRect(getBrowser().getBoundingClientRect());
+      this.zoomFrom = browserRect.translate(getBrowser()._frameLoader.viewportScrollX, getBrowser()._frameLoader.viewportScrollY);
 
-    // start animation timer
-    this.counter = 0;
-    this.inc = 1.0 / Services.prefs.getIntPref("browser.ui.zoom.animationDuration");
-    this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-    this.interval = 1000 / Services.prefs.getIntPref("browser.ui.zoom.animationFps");
-    this.timer.initWithCallback(Util.bind(this._callback, this), this.interval, this.timer.TYPE_REPEATING_PRECISE);
-
-    // force first update to be according to FPS even though first callback would take longer
-    this.lastTime = 0;
-  }
-};
-
-/** Updates the zoom to new rect. */
-AnimatedZoom.prototype.updateTo = function(nextRect) {
-  this.zoomRect = nextRect;
-
-  if (this.snapshot.pending_render)
-    return;
-
-  // prepare to draw to the zoom canvas
-  let canvasRect = new Rect(0, 0, Elements.viewBuffer.width, Elements.viewBuffer.height);
-  let ctx = Elements.viewBuffer.getContext("2d");
-  ctx.save();
+      this.updateTo(this.zoomFrom);
+      this.beginTime = Date.now();
+      window.addEventListener("MozBeforePaint", this, false);
+      mozRequestAnimationFrame();
+    }
+  },
 
-  // srcRect = area inside this.snapshot to copy from
-  let srcRect = nextRect.intersect(this.snapshotRect);
-  if (srcRect.isEmpty())
-    return;
-
-  // destRect = respective area inside canvas to paint to. The dimensions of
-  // destRect within canvas equals those of srcRect within nextRect.
-  let s = canvasRect.width / nextRect.width;
-  let destRect = srcRect.clone().translate(-nextRect.x, -nextRect.y).scale(s, s);
-
-  // adjust from viewport coordinates to snapshot canvas coordinates
-  srcRect.translate(-this.snapshotRect.left, -this.snapshotRect.top);
-
-  // fill background and draw the (possibly scaled) image
-  destRect.restrictTo(canvasRect).expandToIntegers();
-
-  ctx.drawImage(this.snapshot,
-                Math.floor(srcRect.left), Math.floor(srcRect.top),
-                Math.floor(srcRect.width), Math.floor(srcRect.height),
-                Math.floor(destRect.left), Math.floor(destRect.top),
-                Math.floor(destRect.width), Math.floor(destRect.height));
-
-  // clip the over leftover area and fill it with checkerboard
-  let unknowns = canvasRect.subtract(destRect);
-  if (unknowns.length > 0) {
-    ctx.beginPath();
-    unknowns.forEach(function(r) { ctx.rect(r.x, r.y, r.width, r.height); });
-    ctx.clip();
-    ctx.fill();
-  }
-
-  ctx.restore();
-};
-
-/** Starts an animated zoom to zoomRect. */
-AnimatedZoom.prototype.animateTo = function(aZoomRect) {
-  this.zoomTo = aZoomRect;
-  this.finish();
-  return;
-  this.startTimer();
-};
+  handleEvent: function(aEvent) {
+    try {
+      let tdiff = aEvent.timeStamp - this.beginTime;
+      let counter = tdiff / this.animationDuration;
+      if (counter < 1) {
+        // update browser to interpolated rectangle
+        let rect = this.zoomFrom.blend(this.zoomTo, Math.min(counter, 1));
+        this.updateTo(rect);
+        mozRequestAnimationFrame();
+      }
+      else {
+        // last cycle already rendered final scaled image, now clean up
+        this.finish();
+      }
+    }
+    catch(e) {
+      this.finish();
+      throw e;
+    }
+  },
 
-/** Callback for the animation. */
-AnimatedZoom.prototype._callback = function() {
-  try {
-    if (this.counter < 1) {
-      // increase animation position according to elapsed time
-      let now = Date.now();
-      if (this.lastTime == 0)
-        this.lastTime = now - this.interval; // fix lastTime if not yet set (first frame)
-      this.counter += this.inc * (now - this.lastTime);
-      this.lastTime = now;
+  /** Updates the zoom to new rect. */
+  updateTo: function(nextRect) {
+    let zoomRatio = window.innerWidth / nextRect.width;
+    let zoomLevel = getBrowser().scale * zoomRatio;
+    getBrowser()._frameLoader.setViewportScale(zoomLevel, zoomLevel);
+    getBrowser()._frameLoader.scrollViewportTo(nextRect.left * zoomRatio, nextRect.top * zoomRatio);
+  },
 
-      // update scaled image to interpolated rectangle
-      let rect = this.zoomFrom.blend(this.zoomTo, Math.min(this.counter, 1));
-      this.updateTo(rect);
-    }
-    else {
-      // last cycle already rendered final scaled image, now clean up
-      this.finish();
-    }
-  }
-  catch(e) {
-    Util.dumpLn("Error while zooming. Please report error at:", e);
-    this.finish();
-    throw e;
-  }
-};
+  /** Starts an animated zoom to zoomRect. */
+  animateTo: function(aZoomRect) {
+    this.zoomTo = aZoomRect.clone();
+    this.startTimer();
+  },
 
-/** Stop animation, zoom to point, and clean up. */
-AnimatedZoom.prototype.finish = function() {
-  Browser.setVisibleRect(this.zoomTo);
-  return;
-  try {
-    Elements.viewBuffer.style.display = "none";
-
-    // resume live rendering
-    this.bv.resumeRendering(true);
-
-    // if we actually zoomed somewhere, clean up the UI to normal
-    if (this.zoomRect)
-      Browser.setVisibleRect(this.zoomRect);
-  }
-  finally {
-    if (this.timer) {
-      this.timer.cancel();
-      this.timer = null;
-    }
-    this.snapshot = null;
+  /** Stop animation, zoom to point, and clean up. */
+  finish: function() {
+    window.removeEventListener("MozBeforePaint", this, false);
+    Browser.setVisibleRect(this.zoomTo);
+    this.beginTime = null;
     this.zoomTo = null;
   }
 };
-
--- a/mobile/chrome/content/bindings/browser.js
+++ b/mobile/chrome/content/bindings/browser.js
@@ -356,30 +356,24 @@ let ContentScroll =  {
       case "Content:ScrollTo":
         content.scrollTo(json.x, json.y);
         break;
 
       case "Content:ScrollBy":
         content.scrollBy(json.dx, json.dy);
         break;
 
-      case "Content:SetResolution": {
-        let cwu = Util.getWindowUtils(content);
-        cwu.setResolution(json.zoomLevel, json.zoomLevel);
-        sendAsyncMessage("Content:SetResolution:Return", { zoomLevel: json.zoomLevel });
-        break;
-      }
-
       case "Content:SetCacheViewport": {
         let displayport = new Rect(json.x, json.y, json.w, json.h);
         if (displayport.isEmpty())
           break;
 
         let cwu = Util.getWindowUtils(content);
         cwu.setDisplayPort(displayport.x, displayport.y, displayport.width, displayport.height);
+        cwu.setResolution(json.zoomLevel, json.zoomLevel);
         break;
       }
 
       case "Content:SetCssViewportSize": {
         let cwu = Util.getWindowUtils(content);
         cwu.setCSSViewport(json.width, json.height);
         break;
       }
--- a/mobile/chrome/content/bindings/browser.xml
+++ b/mobile/chrome/content/bindings/browser.xml
@@ -158,21 +158,16 @@
 
             case "MozScrolledAreaChanged":
               this._widthInCSSPx = aMessage.json.width;
               this._heightInCSSPx = aMessage.json.height;
               this._viewportWidthInCSSPx = aMessage.json.viewportWidth;
               this._viewportHeightInCSSPx = aMessage.json.viewportHeight;
               this._updateCacheViewport();
               break;
-
-            case "Content:SetResolution:Return":
-              this._zoomLevel = aMessage.json.zoomLevel;
-              this._updateCacheViewport();
-              break;
          }
         ]]></body>
       </method>
 
       <method name="_getLinkType">
         <parameter name="aLink" />
         <body><![CDATA[
           let type = "";
@@ -445,17 +440,18 @@
             let offscreenX = this._pendingThresholdX / this._recacheRatio;
             let offscreenY = this._pendingThresholdY / this._recacheRatio;
 
             let frameLoader = this._frameLoader;
             this.messageManager.sendAsyncMessage("Content:SetCacheViewport", {
               x: (frameLoader.viewportScrollX - offscreenX) / this._zoomLevel,
               y: (frameLoader.viewportScrollY - offscreenY) / this._zoomLevel,
               w: (this._viewportWidthInCSSPx + offscreenX * 2) / this._zoomLevel,
-              h: (this._viewportHeightInCSSPx + offscreenY * 2) / this._zoomLevel
+              h: (this._viewportHeightInCSSPx + offscreenY * 2) / this._zoomLevel,
+              zoomLevel: this._zoomLevel
             });
 
             this._pendingPixelsX = 0;
             this._pendingPixelsY = 0;
           ]]>
         </body>
       </method>
 
@@ -474,18 +470,19 @@
 
       <!-- Sets the scale of CSS pixels to device pixels. Does not affect page layout. -->
       <method name="setScale">
         <parameter name="zl"/>
         <body>
           <![CDATA[
             if (zl <= 0) throw "Bad zoom level given.";
 
+            this._zoomLevel = zl;
             this._frameLoader.setViewportScale(zl, zl);
-            this.messageManager.sendAsyncMessage("Content:SetResolution", { zoomLevel: zl });
+            this._updateCacheViewport();
           ]]>
         </body>
       </method>
 
       <!-- Sets size of CSS viewport, which affects how page is layout. -->
       <method name="setCssViewportSize">
         <parameter name="width"/>
         <parameter name="height"/>
@@ -556,18 +553,16 @@
           this.messageManager.addMessageListener("DOMPopupBlocked", this);
 
           // Prompt remoting
           ["Alert", "Confirm", "Prompt"].forEach(function(name) {
             this.messageManager.addMessageListener("Prompt:" + name, this);
           }, this);
 
           this.messageManager.addMessageListener("MozScrolledAreaChanged", this);
-          this.messageManager.addMessageListener("Content:SetDisplayportArea:Return", this);
-          this.messageManager.addMessageListener("Content:SetResolution:Return", this);
 
           this._webProgress._init();
         ]]>
       </constructor>
 
     </implementation>
 
   </binding>
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -912,21 +912,18 @@ var Browser = {
     let zoomRatio = window.innerWidth / rect.width;
     let zoomLevel = getBrowser().scale * zoomRatio;
     let scrollX = rect.left * zoomRatio;
     let scrollY = rect.top * zoomRatio;
 
     this.hideSidebars();
     this.hideTitlebar();
 
-    getBrowser().setScale(zoomLevel);
-    // XXX
-    setTimeout(function() {
-      getBrowser().scrollTo(scrollX, scrollY);
-    }, 500);
+    getBrowser().setScale(this.selectedTab.clampZoomLevel(zoomLevel));
+    getBrowser().scrollTo(scrollX, scrollY);
   },
 
   zoomToPoint: function zoomToPoint(cX, cY, aRect) {
     let tab = this.selectedTab;
     if (!tab.allowZoom)
       return null;
 
     let zoomRect = null;