clean up and get zooming and viewport sort of working
authorBenjamin Stover <bstover@mozilla.com>
Mon, 30 Aug 2010 14:11:49 -0700
changeset 66610 e2747be08305adf705d6b91e1249ef1bdbdb765b
parent 66609 51f1e4c456800bcbd400372608778483eea79674
child 66611 ed0354f2d0d80111b0bd97116b657fd4eba6c87d
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)
clean up and get zooming and viewport sort of working
mobile/chrome/content/AnimatedZoom.js
mobile/chrome/content/bindings/browser.js
mobile/chrome/content/bindings/browser.xml
mobile/chrome/content/browser.js
--- a/mobile/chrome/content/AnimatedZoom.js
+++ b/mobile/chrome/content/AnimatedZoom.js
@@ -200,16 +200,18 @@ AnimatedZoom.prototype.updateTo = functi
   }
 
   ctx.restore();
 };
 
 /** Starts an animated zoom to zoomRect. */
 AnimatedZoom.prototype.animateTo = function(aZoomRect) {
   this.zoomTo = aZoomRect;
+  this.finish();
+  return;
   this.startTimer();
 };
 
 /** Callback for the animation. */
 AnimatedZoom.prototype._callback = function() {
   try {
     if (this.counter < 1) {
       // increase animation position according to elapsed time
@@ -232,16 +234,17 @@ AnimatedZoom.prototype._callback = funct
     Util.dumpLn("Error while zooming. Please report error at:", e);
     this.finish();
     throw e;
   }
 };
 
 /** 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
--- a/mobile/chrome/content/bindings/browser.js
+++ b/mobile/chrome/content/bindings/browser.js
@@ -55,21 +55,16 @@ let WebProgressListener = {
     let json = {
       windowId: aWebProgress.DOMWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID,
       documentURI: aWebProgress.DOMWindow.document.documentURIObject.spec,
       location: location,
       canGoBack: docShell.canGoBack,
       canGoForward: docShell.canGoForward
     };
     sendAsyncMessage("WebProgress:LocationChange", json);
-
-    let cwu = Util.getWindowUtils(content);
-    let scrollOffset = Util.getScrollOffset(content);
-    cwu.setDisplayport(scrollOffset.x - 200, scrollOffset.y - 400,
-                       content.innerWidth + 400, content.innerHeight + 800);
   },
 
   onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
     let json = {
       windowId: aWebProgress.DOMWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID,
       status: aStatus,
       message: aMessage
     };
@@ -257,17 +252,16 @@ let DOMEvents =  {
     addEventListener("DOMTitleChanged", this, false);
     addEventListener("DOMLinkAdded", this, false);
     addEventListener("DOMWillOpenModalDialog", this, false);
     addEventListener("DOMModalDialogClosed", this, true);
     addEventListener("DOMWindowClose", this, false);
     addEventListener("DOMPopupBlocked", this, false);
     addEventListener("pageshow", this, false);
     addEventListener("pagehide", this, false);
-    addEventListener("MozScrolledAreaChanged", this, false);
   },
 
   handleEvent: function(aEvent) {
     let document = content.document;
     switch (aEvent.type) {
       case "DOMContentLoaded":
         if (document.documentURIObject.spec == "about:blank")
           return;
@@ -333,94 +327,110 @@ let DOMEvents =  {
         let retvals = sendSyncMessage(aEvent.type, { });
         for (rv in retvals) {
           if (rv.preventDefault) {
             aEvent.preventDefault();
             break;
           }
         }
         break;
+    }
+  }
+};
+
+DOMEvents.init();
+
+let ContentScroll =  {
+  _contentArea: new Rect(0, 0, 0, 0),
+
+  init: function() {
+    addMessageListener("Content:ScrollTo", this);
+    addMessageListener("Content:ScrollBy", this);
+    addMessageListener("Content:SetResolution", this);
+    addMessageListener("Content:SetDisplayportArea", this);
+    addMessageListener("Content:SetCssViewportSize", this);
+
+    addEventListener("scroll", this, false);
+    addEventListener("MozScrolledAreaChanged", this, false);
+  },
+
+  receiveMessage: function(aMessage) {
+    let json = aMessage.json;
+    switch (aMessage.name) {
+      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:SetDisplayportArea": {
+        let displayport = new Rect(json.x, json.y, json.w, json.h).restrictTo(this._contentArea);
+        if (displayport.isEmpty())
+          break;
+
+        let cwu = Util.getWindowUtils(content);
+        cwu.setDisplayport(displayport.x, displayport.y, displayport.width, displayport.height);
+        sendAsyncMessage("Content:SetDisplayportArea:Return");
+        break;
+      }
+
+      case "Content:SetCssViewportSize": {
+        let cwu = Util.getWindowUtils(content);
+        cwu.setCSSViewport(json.width, json.height);
+        break;
+      }
+    }
+  },
+
+  handleEvent: function(aEvent) {
+    switch (aEvent.type) {
+      case "scroll":
+        Util.dumpLn("XXX stub");
+        break;
 
       case "MozScrolledAreaChanged": {
         let doc = aEvent.originalTarget;
         let win = doc.defaultView;
         // XXX need to make some things in Util as its own module!
         let scrollOffset = Util.getScrollOffset(win);
         if (win.parent != win) // We are only interested in root scroll pane changes
           return;
 
         // Adjust width and height from the incoming event properties so that we
         // ignore changes to width and height contributed by growth in page
         // quadrants other than x > 0 && y > 0.
         let x = aEvent.x + scrollOffset.x;
         let y = aEvent.y + scrollOffset.y;
         let width = aEvent.width + (x < 0 ? x : 0);
         let height = aEvent.height + (y < 0 ? y : 0);
+
+        this._contentArea.width = width;
+        this._contentArea.height = height;
+
         sendAsyncMessage("MozScrolledAreaChanged", {
           width: width,
-          height: height
+          height: height,
+          viewportWidth: content.innerWidth,
+          viewportHeight: content.innerHeight
         });
 
         break;
       }
     }
   }
 };
 
-DOMEvents.init();
-
-let ContentScroll =  {
-  init: function() {
-    addMessageListener("Content:ScrollTo", this);
-    addMessageListener("Content:ScrollBy", this);
-    addMessageListener("Content:ZoomLevel", this);
-    addMessageListener("Content:FastScrollTo", this);
-    addMessageListener("Content:SetCssViewportSize", this);
-  },
-
-  receiveMessage: function(aMessage) {
-    let json = aMessage.json;
-    switch (aMessage.name) {
-      case "Content:ScrollTo":
-//        content.scrollTo(json.x, json.y);
-        let cwu = Util.getWindowUtils(content);
-        cwu.setDisplayport(json.x - 200, json.y - 400,
-                           content.innerWidth + 400, content.innerHeight + 800);
-        break;
-
-      case "Content:ScrollBy":
-        content.scrollBy(json.dx, json.dy);
-        break;
-
-      case "Content:ZoomLevel":
-        content.document.body.style.MozTransformOrigin = "top left";
-        content.document.body.style.MozTransform = "scale(" + json.zoomLevel + ")";
-        break;
-
-      case "Content:FastScrollTo": {
-        try {
-          let cwu = Util.getWindowUtils(content);
-          cwu.setDisplayport(json.x - 200, json.y - 400,
-                             content.innerWidth + 400, content.innerHeight + 800);
-        } catch(e) {
-          Util.dumpLn(e);
-        }
-        sendAsyncMessage("Content:FastScrollTo:Return");
-        break;
-      }
-
-      case "Content:SetCssViewportSize": {
-        let cwu = Util.getWindowUtils(content);
-        cwu.setCSSViewport(json.width, json.height);
-        break;
-      }
-    }
-  }
-};
-
 ContentScroll.init();
 
 
 function PromptRemoter() {
   addEventListener("DOMWindowCreated", this, false);
 }
 
 PromptRemoter.prototype = {
--- a/mobile/chrome/content/bindings/browser.xml
+++ b/mobile/chrome/content/bindings/browser.xml
@@ -141,25 +141,33 @@
 
             case "Prompt:Confirm":
               return confirm(aMessage.json.message);
 
             case "Prompt:Prompt":
               return prompt(aMessage.json.text, aMessage.json.value);
 
             case "MozScrolledAreaChanged":
-              this._widthInDevicePx = aMessage.json.width;
-              this._heightInDevicePx = aMessage.json.height;
+              this._widthInCSSPx = aMessage.json.width;
+              this._heightInCSSPx = aMessage.json.height;
+              this._viewportWidthInCSSPx = aMessage.json.viewportWidth;
+              this._viewportHeightInCSSPx = aMessage.json.viewportHeight;
+              this._updateDisplayport();
               break;
 
-            case "Content:FastScrollTo:Return":
+            case "Content:SetDisplayportArea:Return":
+              // XXX explain behavior
               this._flushingFastScroll = false;
               this._pendingPixelsX = 0;
               this._pendingPixelsY = 0;
               break;
+
+            case "Content:SetResolution:Return":
+              this._zoomLevel = aMessage.json.zoomLevel;
+              break;
          }
         ]]></body>
       </method>
 
       <method name="_getLinkType">
         <parameter name="aLink" />
         <body><![CDATA[
           let type = "";
@@ -262,17 +270,19 @@
                 ];
 
                 if (this._browser.contentWindowId != json.windowId) {
                   this._browser.contentWindowId = json.windowId;
                   this._browser._documentURI = json.documentURI;
                   this._browser._searchEngines = [];
                 }
 
-                this._zoomLevel = 1;
+                this._browser._zoomLevel = 1;
+
+                this._browser._updateDisplayport();
 
                 this._notify(Components.interfaces.nsIWebProgress.NOTIFY_LOCATION,
                              "onLocationChange",
                              args);
 
                 break;
 
               case "WebProgress:StatusChange":
@@ -380,178 +390,160 @@
 
             this.pageReport.push(obj);
             this.pageReport.reported = false;
             this.updatePageReport();
           ]]>
         </body>
       </method>
 
-      <property name="_frameLoader"
-                onget="return this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;"
-                readonly="true"/>
-
-      <field name="_cssViewportWidth">0</field>
+      <field name="_frameLoader">null</field>
+      <field name="_cssViewportWidthInCSSPx">0</field>
       <field name="_flushingFastScroll">false</field>
-      <field name="_widthInDevicePx">0</field>
-      <field name="_heightInDevicePx">0</field>
+      <field name="_widthInCSSPx">0</field>
+      <field name="_heightInCSSPx">0</field>
       <field name="_zoomLevel">1</field>
       <field name="_defaultZoomLevel">1</field>
       <field name="_pendingPixelsX">0</field>
       <field name="_pendingPixelsY">0</field>
 
-      <property name="widthInCssPx"
-                onget="return this._widthInDevicePx / this.zoomLevel;"
+      <property name="widthInCSSPx"
+                onget="return this._widthInCSSPx;"
                 readonly="true"/>
 
-      <property name="heightInCssPx"
-                onget="return this._heightInDevicePx / this.zoomLevel;"
+      <property name="heightInCSSPx"
+                onget="return this._heightInCSSPx;"
                 readonly="true"/>
 
       <property name="widthInDevicePx"
-                onget="return this._widthInDevicePx;"
+                onget="return this._widthInCSSPx * this._zoomLevel;"
                 readonly="true"/>
 
       <property name="heightInDevicePx"
-                onget="return this._heightInDevicePx;"
+                onget="return this._heightInCSSPx * this._zoomLevel;"
                 readonly="true"/>
 
       <property name="zoomLevel"
                 onget="return this._zoomLevel;"
-                onset="return this._setZoomLevel(val);"/>
+                readonly="true"/>
 
       <property name="defaultZoomLevel"
                 onget="return this._defaultZoomLevel;"
                 onset="return this._setDefaultZoomLevel(val);"/>
 
-      <method name="_setZoomLevel">
+      <property name="viewportWidthInCSSPx"
+                onget="return this._viewportWidthInCSSPx;"
+                readonly="true"/>
+
+      <property name="viewportHeightInCSSPx"
+                onget="return this._viewportHeightInCSSPx;"
+                readonly="true"/>
+
+      <method name="_updateDisplayport">
+        <body>
+          <![CDATA[
+            let frameLoader = this._frameLoader;
+            this.messageManager.sendAsyncMessage("Content:SetDisplayportArea", {
+              x: frameLoader.viewportScrollX / this._zoomLevel - 200,
+              y: frameLoader.viewportScrollY / this._zoomLevel - 400,
+              w: this._viewportWidthInCSSPx + 400,
+              h: this._viewportHeightInCSSPx + 800
+            });
+          ]]>
+        </body>
+      </method>
+
+      <method name="setZoomLevel">
         <parameter name="zl"/>
         <body>
           <![CDATA[
-            return;
             if (zl <= 0) throw "Bad zoom level given.";
 
-            this._zoomLevel = zl;
-            this.messageManager.sendAsyncMessage("Content:ZoomLevel", { zoomLevel: zl });
+            this._frameLoader.setViewportScale(zl, zl);
+            this.messageManager.sendAsyncMessage("Content:SetResolution", {
+              zoomLevel: zl
+            });
           ]]>
         </body>
       </method>
 
       <method name="setCssViewportSize">
         <parameter name="width"/>
         <parameter name="height"/>
         <body>
           <![CDATA[
-            return;
             this.messageManager.sendAsyncMessage("Content:SetCssViewportSize", {
               width: width,
               height: height
             });
           ]]>
         </body>
       </method>
 
       <method name="flushScroll">
         <body>
           <![CDATA[
-            let frameLoader = this._frameLoader;
-            this.messageManager.sendAsyncMessage("Content:ScrollTo", {
-              x: frameLoader.viewportScrollX,
-              y: frameLoader.viewportScrollY
-            });
-            this._pendingPixelsX = 0;
-            this._pendingPixelsY = 0;
+            this._updateDisplayport();
           ]]>
         </body>
       </method>
 
       <method name="scrollBy">
         <parameter name="x"/>
         <parameter name="y"/>
         <body>
           <![CDATA[
-            let frameLoader = this._frameLoader;
-
-            // XXX don't assume window size is viewport size
-            let viewportWidth = window.innerWidth;
-            let viewportHeight = window.innerHeight;
-
-            x = Math.max(0, Math.min(this.widthInDevicePx - viewportWidth, frameLoader.viewportScrollX + x)) - frameLoader.viewportScrollX;
-            y = Math.max(0, Math.min(this.heightInDevicePx - viewportHeight, frameLoader.viewportScrollY + y)) - frameLoader.viewportScrollY;
-
-            if (x == 0 && y == 0)
-              return;
-
-            frameLoader.scrollViewportBy(x, y);
-
-            this._pendingPixelsX += x;
-            this._pendingPixelsY += y;
-
-            if (this._flushingFastScroll == false &&
-                (Math.abs(this._pendingPixelsX) >= 150 || Math.abs(this._pendingPixelsY) >= 250)) {
-              this._flushingFastScroll = true;
-              this.messageManager.sendAsyncMessage("Content:FastScrollTo", {
-                x: frameLoader.viewportScrollX + x,
-                y: frameLoader.viewportScrollY + y
-              });
-            }
+            this.contentWindow.scrollBy(x, y);
           ]]>
         </body>
       </method>
 
       <method name="scrollTo">
         <parameter name="x"/>
         <parameter name="y"/>
         <body>
           <![CDATA[
-            let frameLoader = this._frameLoader;
-
-            // XXX don't assume window size is viewport size
-            let viewportWidth = window.innerWidth;
-            let viewportHeight = window.innerHeight;
-
-            x = Math.max(0, Math.min(this.widthInDevicePx - viewportWidth, x));
-            y = Math.max(0, Math.min(this.heightInDevicePx - viewportHeight, y));
-            frameLoader.scrollViewportTo(x, y);
+            this.contentWindow.scrollTo(x, y);
           ]]>
         </body>
       </method>
 
-      <!-- XXX There is no reason this API needs to act like scrollbar's.
-           Should we change it to something less XPCOMy? -->
       <method name="getPosition">
         <parameter name="scrollX"/>
         <parameter name="scrollY"/>
         <body>
           <![CDATA[
-            let frameLoader = this._frameLoader;
-            scrollX.value = frameLoader.viewportScrollX;
-            scrollY.value = frameLoader.viewportScrollY;
+            let cwu = this.contentWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+                      getInterface(Components.interfaces.nsIDOMWindowUtils);
+            cwu.getScrollXY(false, scrollX, scrollY);
           ]]>
         </body>
       </method>
 
       <constructor>
         <![CDATA[
+          this._frameLoader = this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader;
+
           this.messageManager.loadFrameScript("chrome://browser/content/bindings/browser.js", true);
           this.messageManager.addMessageListener("DOMTitleChanged", this);
           this.messageManager.addMessageListener("DOMLinkAdded", this);
 
           // Listen for first load for lazy attachment to form fill controller
           this.messageManager.addMessageListener("pageshow", this);
           this.messageManager.addMessageListener("pagehide", this);
           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:FastScrollTo:Return", this);
+          this.messageManager.addMessageListener("Content:SetDisplayportArea:Return", this);
+          this.messageManager.addMessageListener("Content:SetResolution:Return", this);
 
           this._webProgress._init();
         ]]>
       </constructor>
 
     </implementation>
 
   </binding>
@@ -735,16 +727,18 @@
                 ];
 
                 if (this._browser.contentWindowId != json.windowId) {
                   this._browser.contentWindowId = json.windowId;
                   this._browser._documentURI = json.documentURI;
                   this._browser._searchEngines = [];
                 }
 
+                this._browser._zoomLevel = 1;
+
                 this._notify(Components.interfaces.nsIWebProgress.NOTIFY_LOCATION,
                              "onLocationChange",
                              args);
 
                 break;
 
               case "WebProgress:StatusChange":
                 args = [
@@ -804,13 +798,71 @@
 
       <property name="contentViewerFile"
                 onget="throw 'contentViewerFile: Not Remoteable'"
                 readonly="true"/>
 
       <property name="documentCharsetInfo"
                 onget="throw 'documentCharsetInfo: Not Remoteable'"
                 readonly="true"/>
+
+      <method name="scrollBy">
+        <parameter name="x"/>
+        <parameter name="y"/>
+        <body>
+          <![CDATA[
+            let frameLoader = this._frameLoader;
+
+            let bcr = this.getBoundingClientRect();
+            let viewportWidth = bcr.width;
+            let viewportHeight = bcr.height;
+
+            x = Math.floor(Math.max(0, Math.min(this.widthInDevicePx - viewportWidth, frameLoader.viewportScrollX + x)) - frameLoader.viewportScrollX);
+            y = Math.floor(Math.max(0, Math.min(this.heightInDevicePx - viewportHeight, frameLoader.viewportScrollY + y)) - frameLoader.viewportScrollY);
+
+            if (x == 0 && y == 0)
+              return;
+
+            frameLoader.scrollViewportBy(x, y);
+            Util.dumpLn(frameLoader.viewportScrollY, "+", bcr.height, "=", frameLoader.viewportScrollY + bcr.height);
+            Util.dumpLn("and zoom level is", this._zoomLevel);
+
+            // XXX comment this behavior
+            this._pendingPixelsX += x;
+            this._pendingPixelsY += y;
+
+            if (this._flushingFastScroll == false &&
+                (Math.abs(this._pendingPixelsX) >= 150 || Math.abs(this._pendingPixelsY) >= 250)) {
+              this._flushingFastScroll = true;
+              this._updateDisplayport();
+            }
+          ]]>
+        </body>
+      </method>
+
+      <method name="scrollTo">
+        <parameter name="x"/>
+        <parameter name="y"/>
+        <body>
+          <![CDATA[
+            let frameLoader = this._frameLoader;
+            this.scrollBy(x - frameLoader.viewportScrollX, y - frameLoader.viewportScrollY);
+          ]]>
+        </body>
+      </method>
+
+      <method name="getPosition">
+        <parameter name="scrollX"/>
+        <parameter name="scrollY"/>
+        <body>
+          <![CDATA[
+            let frameLoader = this._frameLoader;
+            scrollX.value = frameLoader.viewportScrollX;
+            scrollY.value = frameLoader.viewportScrollY;
+          ]]>
+        </body>
+      </method>
+
     </implementation>
 
   </binding>
 
 </bindings>
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -161,47 +161,29 @@ var Browser = {
     container.customDragger = new Browser.MainDragger(bv);
 
     // Warning, total hack ahead. All of the real-browser related scrolling code
     // lies in a pretend scrollbox here. Let's not land this as-is. Maybe it's time
     // to redo all the dragging code.
     this.contentScrollbox = container;
     this.contentScrollboxScroller = {
       flush: function() {
-        if (!getBrowser().contentWindow) {
-          getBrowser().flushScroll();
-        }
+        getBrowser().flushScroll();
       },
 
       scrollBy: function(x, y) {
-        if (getBrowser().contentWindow) {
-          getBrowser().contentWindow.scrollBy(x, y);
-        }
-        else {
-          getBrowser().scrollBy(x, y);
-        }
+        getBrowser().scrollBy(x, y);
       },
 
       scrollTo: function(x, y) {
-        if (getBrowser().contentWindow) {
-          getBrowser().contentWindow.scrollTo(x, y);
-        }
-        else {
-          getBrowser().scrollTo(x, y);
-        }
+        getBrowser().scrollTo(x, y);
       },
 
       getPosition: function(scrollX, scrollY) {
-        if (getBrowser().contentWindow) {
-          let cwu = Util.getWindowUtils(getBrowser().contentWindow);
-          cwu.getScrollXY(false, scrollX, scrollY);
-        }
-        else {
-          getBrowser().getPosition(scrollX, scrollY);
-        }
+        getBrowser().getPosition(scrollX, scrollY);
       }
     };
 
     /* horizontally scrolling box that holds the sidebars as well as the contentScrollbox */
     let controlsScrollbox = this.controlsScrollbox = document.getElementById("controls-scrollbox");
     this.controlsScrollboxScroller = controlsScrollbox.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
     controlsScrollbox.customDragger = {
       isDraggable: function isDraggable(target, content) { return {}; },
@@ -844,135 +826,115 @@ var Browser = {
       this.floatedWhileDragging = false;
       return true;
     }
     return false;
   },
 
   /** Zoom one step in (negative) or out (positive). */
   zoom: function zoom(aDirection) {
-    return;
-
-    let bv = this._browserView;
-    if (!bv.allowZoom)
+    let tab = this.selectedTab;
+    if (!tab.allowZoom)
       return;
 
-    let zoomLevel = bv.getZoomLevel();
-
+    let zoomLevel = getBrowser().zoomLevel;
     let zoomValues = ZoomManager.zoomValues;
     let i = zoomValues.indexOf(ZoomManager.snap(zoomLevel)) + (aDirection < 0 ? 1 : -1);
     if (i >= 0 && i < zoomValues.length)
       zoomLevel = zoomValues[i];
 
-    zoomLevel = bv.clampZoomLevel(zoomLevel);
+    zoomLevel = tab.clampZoomLevel(zoomLevel);
 
-    let center = this.getVisibleRect().center().map(bv.viewportToBrowser);
-    this.animatedZoomTo(this._getZoomRectForPoint(center.x, center.y, zoomLevel));
+    let centerX = getBrowser().viewportScrollX + window.innerWidth / 2;
+    let centerY = getBrowser().viewportScrollY + window.innerHeight / 2;
+    this.animatedZoomTo(this._getZoomRectForPoint(centerX, centerY, zoomLevel));
   },
 
   /** Rect should be in browser coordinates. */
   _getZoomLevelForRect: function _getZoomLevelForRect(rect) {
     const margin = 15;
-
-    let bv = this._browserView;
-    let vis = bv.getVisibleRect();
-
-    return bv.clampZoomLevel(vis.width / (rect.width + margin * 2));
+    return this.selectedTab.clampZoomLevel(window.innerWidth / (rect.width + margin * 2));
   },
 
   /**
    * Find an appropriate zoom rect for an element bounding rect, if it exists.
    * @return Rect in viewport coordinates
    * */
   _getZoomRectForRect: function _getZoomRectForRect(rect, y) {
-    let bv = this._browserView;
-    let oldZoomLevel = bv.getZoomLevel();
+    let oldZoomLevel = getBrowser().zoomLevel;
     let zoomLevel = this._getZoomLevelForRect(rect);
     let zoomRatio = oldZoomLevel / zoomLevel;
 
     // Don't zoom in a marginal amount, but be more lenient for the first zoom.
     // > 2/3 means operation increases the zoom level by less than 1.5
     // > 9/10 means operation increases the zoom level by less than 1.1
-    let zoomTolerance = (bv.isDefaultZoom()) ? .9 : .6666;
+    let zoomTolerance = (this.selectedTab.isDefaultZoomLevel()) ? .9 : .6666;
     if (zoomRatio >= zoomTolerance)
       return null;
     else
       return this._getZoomRectForPoint(rect.center().x, y, zoomLevel);
   },
 
   /**
    * Find a good zoom rectangle for point that is specified in browser coordinates.
    * @return Rect in viewport coordinates
    */
   _getZoomRectForPoint: function _getZoomRectForPoint(x, y, zoomLevel) {
-    let bv = this._browserView;
-    let vis = bv.getVisibleRect();
-    x = bv.browserToViewport(x);
-    y = bv.browserToViewport(y);
+    x = x * getBrowser().zoomLevel;
+    y = y * getBrowser().zoomLevel;
 
     zoomLevel = Math.min(ZoomManager.MAX, zoomLevel);
-    let zoomRatio = zoomLevel / bv.getZoomLevel();
-    let newVisW = vis.width / zoomRatio, newVisH = vis.height / zoomRatio;
+    let zoomRatio = zoomLevel / getBrowser().zoomLevel;
+    let newVisW = window.innerWidth / zoomRatio, newVisH = window.innerHeight / 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(bv._browserViewportState.viewportRect);
+    return result.translateInside(new Rect(0, 0, getBrowser().widthInDevicePx, getBrowser().heightInDevicePx));
   },
 
   animatedZoomTo: function animatedZoomTo(rect) {
-    return;
-
-    let zoom = new AnimatedZoom(this._browserView);
+    let zoom = new AnimatedZoom();
     zoom.animateTo(rect);
   },
 
   setVisibleRect: function setVisibleRect(rect) {
-    return;
-
-    let bv = this._browserView;
-    let vis = bv.getVisibleRect();
-    let zoomRatio = vis.width / rect.width;
-    let zoomLevel = bv.getZoomLevel() * zoomRatio;
+    let zoomRatio = window.innerWidth / rect.width;
+    let zoomLevel = getBrowser().zoomLevel * zoomRatio;
     let scrollX = rect.left * zoomRatio;
     let scrollY = rect.top * zoomRatio;
 
     this.hideSidebars();
     this.hideTitlebar();
 
-    bv.setZoomLevel(zoomLevel);
-
-    this.contentScrollboxScroller.scrollTo(scrollX, scrollY);
+    getBrowser().setZoomLevel(zoomLevel);
+    getBrowser().scrollTo(scrollX, scrollY);
   },
 
   zoomToPoint: function zoomToPoint(cX, cY, aRect) {
-    return;
-
-    let bv = this._browserView;
-    if (!bv.allowZoom)
+    let tab = this.selectedTab;
+    if (!tab.allowZoom)
       return null;
 
     let zoomRect = null;
     if (aRect)
       zoomRect = this._getZoomRectForRect(aRect, cY);
 
-    if (!zoomRect && bv.isDefaultZoom())
-      zoomRect = this._getZoomRectForPoint(cX, cY, bv.getZoomLevel() * 2);
+    if (!zoomRect && tab.isDefaultZoomLevel())
+      zoomRect = this._getZoomRectForPoint(cX, cY, getBrowser().zoomLevel * 2);
 
     if (zoomRect)
       this.animatedZoomTo(zoomRect);
 
     return zoomRect;
   },
 
   zoomFromPoint: function zoomFromPoint(cX, cY) {
-    return;
-
-    let bv = this._browserView;
-    if (bv.allowZoom && !bv.isDefaultZoom()) {
-      let zoomLevel = bv.getDefaultZoomLevel();
+    let tab = this.selectedTab;
+    if (tab.allowZoom && !tab.isDefaultZoomLevel()) {
+      let zoomLevel = tab.getDefaultZoomLevel();
       let zoomRect = this._getZoomRectForPoint(cX, cY, zoomLevel);
       this.animatedZoomTo(zoomRect);
     }
   },
 
   /**
    * Transform x and y from client coordinates to BrowserView coordinates.
    */
@@ -1363,21 +1325,19 @@ ContentCustomClicker.prototype = {
     this._dispatchMouseEvent("Browser:MouseCancel");
   },
 
   doubleClick: function doubleClick(aX1, aY1, aX2, aY2) {
     TapHighlightHelper.hide();
 
     this._dispatchMouseEvent("Browser:MouseCancel");
 
-    return;
-
     const kDoubleClickRadius = 32;
 
-    let maxRadius = kDoubleClickRadius * Browser._browserView.getZoomLevel();
+    let maxRadius = kDoubleClickRadius * getBrowser().zoomLevel;
     let isClickInRadius = (Math.abs(aX1 - aX2) < maxRadius && Math.abs(aY1 - aY2) < maxRadius);
     if (isClickInRadius)
       this._dispatchMouseEvent("Browser:ZoomToPoint", aX1, aY1);
   },
 
   toString: function toString() {
     return "[ContentCustomClicker] { }";
   }
@@ -2286,31 +2246,29 @@ Tab.prototype = {
         viewportH = viewportW * (screenH / screenW);
       } else if (!validW && validH) {
         viewportW = viewportH * (screenW / screenH);
       } else {
         viewportW = kDefaultBrowserWidth;
         viewportH = kDefaultBrowserWidth * (screenH / screenW);
       }
 
-      if (!getBrowser().contentWindow)
-        browser.setCssViewportSize(viewportW, viewportH);
+      browser.setCssViewportSize(viewportW, viewportH);
     }
     else {
       let browserBCR = browser.getBoundingClientRect();
       let w = browserBCR.width;
       let h = browserBCR.height;
       if (metaData.defaultZoom != 1.0) {
         let dpiScale = Services.prefs.getIntPref("zoom.dpiScale") / 100;
         w /= dpiScale;
         h /= dpiScale;
       }
 
-      if (!getBrowser().contentWindow)
-        browser.setCssViewportSize(w, h);
+      browser.setCssViewportSize(w, h);
     }
 
     // Local XUL documents are not firing MozScrolledAreaChanged
     /*let contentDocument = browser.contentDocument;
     if (contentDocument && contentDocument instanceof XULDocument) {
       let width = contentDocument.documentElement.scrollWidth;
       let height = contentDocument.documentElement.scrollHeight;
       BrowserView.Util.ensureMozScrolledAreaEvent(browser, width, height);
@@ -2392,53 +2350,51 @@ Tab.prototype = {
       Util.executeSoon(function() {
         document.getElementById("browsers").removeChild(browser);
       });
     }
   },
 
   clampZoomLevel: function clampZoomLevel(zl) {
     let browser = this._browser;
-    if (!browser.contentWindow) {
-      let bounded = Math.min(Math.max(ZoomManager.MIN, zl), ZoomManager.MAX);
+    let bounded = Math.min(Math.max(ZoomManager.MIN, zl), ZoomManager.MAX);
 
-      let md = this.metaData;
-      if (md && md.minZoom)
-        bounded = Math.max(bounded, md.minZoom);
-      if (md && md.maxZoom)
-        bounded = Math.min(bounded, md.maxZoom);
+    let md = this.metaData;
+    if (md && md.minZoom)
+      bounded = Math.max(bounded, md.minZoom);
+    if (md && md.maxZoom)
+      bounded = Math.min(bounded, md.maxZoom);
 
-      bounded = Math.max(bounded, this.getPageZoomLevel());
+    bounded = Math.max(bounded, this.getPageZoomLevel());
 
-      let rounded = Math.round(bounded * kBrowserViewZoomLevelPrecision) / kBrowserViewZoomLevelPrecision;
-      return rounded || 1.0;
-    }
-    return 1;
+    let rounded = Math.round(bounded * kBrowserViewZoomLevelPrecision) / kBrowserViewZoomLevelPrecision;
+    return rounded || 1.0;
   },
 
   /**
    * XXX document me
    */
   resetZoomLevel: function resetZoomLevel() {
     let browser = this._browser;
-    if (!browser.contentWindow)
-      this._defaultZoomLevel = browser.zoomLevel;
+    this._defaultZoomLevel = browser.zoomLevel;
   },
 
   /**
    * XXX document me
    */
   updateDefaultZoomLevel: function updateDefaultZoomLevel() {
     let browser = this._browser;
-    if (!browser.contentWindow) {
-      let isDefault = (browser.zoomLevel == this._defaultZoomLevel);
-      this._defaultZoomLevel = this.getDefaultZoomLevel();
-      if (isDefault)
-        browser.zoomLevel = this._defaultZoomLevel;
-    }
+    let isDefault = (browser.zoomLevel == this._defaultZoomLevel);
+    this._defaultZoomLevel = this.getDefaultZoomLevel();
+    if (isDefault)
+      browser.setZoomLevel(this._defaultZoomLevel);
+  },
+
+  isDefaultZoomLevel: function isDefaultZoomLevel() {
+    return getBrowser().zoomLevel == this._defaultZoomLevel;
   },
 
   getDefaultZoomLevel: function getDefaultZoomLevel() {
     let md = this.metaData;
     if (md && md.defaultZoom)
       return this.clampZoomLevel(md.defaultZoom);
 
     let pageZoom = this.getPageZoomLevel();
@@ -2448,20 +2404,24 @@ Tab.prototype = {
     let threshold = 1 - 1 / granularity;
     if (threshold < pageZoom && pageZoom < 1)
       pageZoom = 1;
 
     return this.clampZoomLevel(pageZoom);
   },
 
   getPageZoomLevel: function getPageZoomLevel() {
-    let browserW = this._browser.widthInCssPx;
+    let browserW = this._browser.viewportWidthInCSSPx;
     return this._browser.getBoundingClientRect().width / browserW;
   },
 
+  get allowZoom() {
+    return this.metaData.allowZoom;
+  },
+
   updateThumbnail: function updateThumbnail() {
     return;
 
     if (!this._browser)
       return;
 
     // XXX: We don't know the size of the browser at this time and we can't fallback
     // to contentWindow.innerWidth and .innerHeight. The viewport meta data is not