Bug 707571 (part 2) - Disable zooming for pages with user-scalable=no [r=kats a=akeybl]
authorMatt Brubeck <mbrubeck@mozilla.com>
Fri, 18 May 2012 08:24:27 -0700
changeset 95824 1ecdee8df588f8ff120758557472c93d23727435
parent 95823 68f63ff1743a11bda523b512644637077d619670
child 95825 9f6b81c96aa51db56b9e576a9fc0708292c89ae3
push id886
push userlsblakk@mozilla.com
push dateMon, 04 Jun 2012 19:57:52 +0000
treeherdermozilla-beta@bbd8d5efd6d1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskats, akeybl
bugs707571
milestone14.0a2
Bug 707571 (part 2) - Disable zooming for pages with user-scalable=no [r=kats a=akeybl]
mobile/android/base/GeckoApp.java
mobile/android/base/Tab.java
mobile/android/base/gfx/GeckoLayerClient.java
mobile/android/base/gfx/LayerController.java
mobile/android/base/ui/PanZoomController.java
mobile/android/chrome/content/browser.js
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -665,16 +665,18 @@ abstract public class GeckoApp
             return;
         }
 
         tab.setContentType(contentType);
         tab.updateFavicon(null);
         tab.updateFaviconURL(null);
         tab.updateSecurityMode("unknown");
         tab.removeTransientDoorHangers();
+        tab.setAllowZoom(true);
+        tab.setDefaultZoom(0);
         tab.setHasTouchListeners(false);
         tab.setCheckerboardColor(Color.WHITE);
 
         maybeCancelFaviconLoad(tab);
 
         mMainHandler.post(new Runnable() {
             public void run() {
                 if (Tabs.getInstance().isSelectedTab(tab)) {
@@ -991,16 +993,29 @@ abstract public class GeckoApp
                     mMainHandler.post(new Runnable() {
                         public void run() {
                             sMenu.findItem(R.id.char_encoding).setVisible(visible);
                         }
                     });
                 }
             } else if (event.equals("Update:Restart")) {
                 doRestart("org.mozilla.gecko.restart_update");
+            } else if (event.equals("Tab:ViewportMetadata")) {
+                int tabId = message.getInt("tabID");
+                Tab tab = Tabs.getInstance().getTab(tabId);
+                if (tab == null)
+                    return;
+                tab.setAllowZoom(message.getBoolean("allowZoom"));
+                tab.setDefaultZoom((float) message.getDouble("defaultZoom"));
+                // Sync up the LayerController and the tab if the tab's currently displayed.
+                LayerController controller = getLayerController();
+                if (controller != null && Tabs.getInstance().isSelectedTab(tab)) {
+                    controller.setAllowZoom(tab.getAllowZoom());
+                    controller.setDefaultZoom(tab.getDefaultZoom());
+                }
             } else if (event.equals("Tab:HasTouchListener")) {
                 int tabId = message.getInt("tabID");
                 Tab tab = Tabs.getInstance().getTab(tabId);
                 tab.setHasTouchListeners(true);
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     mMainHandler.post(new Runnable() {
                         public void run() {
                             mLayerController.getView().getTouchEventHandler().setWaitForTouchListeners(true);
@@ -1674,16 +1689,17 @@ abstract public class GeckoApp
         GeckoAppShell.registerGeckoEventListener("ToggleChrome:Hide", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("ToggleChrome:Show", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("ToggleChrome:Focus", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Permissions:Data", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("CharEncoding:Data", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("CharEncoding:State", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Update:Restart", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Tab:HasTouchListener", GeckoApp.mAppContext);
+        GeckoAppShell.registerGeckoEventListener("Tab:ViewportMetadata", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Session:StatePurged", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Bookmark:Insert", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Accessibility:Event", GeckoApp.mAppContext);
 
         if (SmsManager.getInstance() != null) {
           SmsManager.getInstance().start();
         }
 
--- a/mobile/android/base/Tab.java
+++ b/mobile/android/base/Tab.java
@@ -81,16 +81,18 @@ public final class Tab {
     private int mParentId;
     private boolean mExternal;
     private boolean mBookmark;
     private HashMap<String, DoorHanger> mDoorHangers;
     private long mFaviconLoadId;
     private String mDocumentURI;
     private String mContentType;
     private boolean mHasTouchListeners;
+    private boolean mAllowZoom;
+    private float mDefaultZoom;
     private ArrayList<View> mPluginViews;
     private HashMap<Object, Layer> mPluginLayers;
     private ContentResolver mContentResolver;
     private ContentObserver mContentObserver;
     private int mCheckerboardColor = Color.WHITE;
     private int mState;
 
     public static final int STATE_DELAYED = 0;
@@ -287,16 +289,32 @@ public final class Tab {
     public void setState(int state) {
         mState = state;
     }
 
     public int getState() {
         return mState;
     }
 
+    public void setAllowZoom(boolean aValue) {
+        mAllowZoom = aValue;
+    }
+
+    public boolean getAllowZoom() {
+        return mAllowZoom;
+    }
+
+    public void setDefaultZoom(float aValue) {
+        mDefaultZoom = aValue;
+    }
+
+    public float getDefaultZoom() {
+        return mDefaultZoom;
+    }
+
     public void setHasTouchListeners(boolean aValue) {
         mHasTouchListeners = aValue;
     }
 
     public boolean getHasTouchListeners() {
         return mHasTouchListeners;
     }
 
--- a/mobile/android/base/gfx/GeckoLayerClient.java
+++ b/mobile/android/base/gfx/GeckoLayerClient.java
@@ -38,33 +38,33 @@
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.FloatUtils;
 import org.mozilla.gecko.GeckoApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoEventResponder;
+import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.Tabs;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import android.content.Context;
 import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.os.SystemClock;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.View;
 import java.util.Map;
 import java.util.HashMap;
-import org.mozilla.gecko.Tabs;
 
 public class GeckoLayerClient implements GeckoEventResponder,
                                          LayerView.Listener {
     private static final String LOGTAG = "GeckoLayerClient";
 
     private LayerController mLayerController;
     private LayerRenderer mLayerRenderer;
     private boolean mLayerRendererInitialized;
@@ -345,17 +345,22 @@ public class GeckoLayerClient implements
             // viewport in mLayerController. Usually this information is updated via handleViewportMessage
             // while we remain on the same document.
             mLayerController.post(new Runnable() {
                 public void run() {
                     mGeckoViewport = currentMetrics;
                 }
             });
             mLayerController.setViewportMetrics(currentMetrics);
-            mLayerController.setCheckerboardColor(Tabs.getInstance().getSelectedTab().getCheckerboardColor());
+
+            Tab tab = Tabs.getInstance().getSelectedTab();
+            mLayerController.setCheckerboardColor(tab.getCheckerboardColor());
+            mLayerController.setAllowZoom(tab.getAllowZoom());
+            mLayerController.setDefaultZoom(tab.getDefaultZoom());
+
             // At this point, we have just switched to displaying a different document than we
             // we previously displaying. This means we need to abort any panning/zooming animations
             // that are in progress and send an updated display port request to browser.js as soon
             // as possible. We accomplish this by passing true to abortPanZoomAnimation, which
             // sends the request after aborting the animation. The display port request is actually
             // a full viewport update, which is fine because if browser.js has somehow moved to
             // be out of sync with this first-paint viewport, then we force them back in sync.
             mLayerController.abortPanZoomAnimation();
--- a/mobile/android/base/gfx/LayerController.java
+++ b/mobile/android/base/gfx/LayerController.java
@@ -36,17 +36,16 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 package org.mozilla.gecko.gfx;
 
 import org.mozilla.gecko.gfx.Layer;
 import org.mozilla.gecko.ui.PanZoomController;
 import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
-import org.mozilla.gecko.GeckoApp;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.graphics.PointF;
 import android.graphics.RectF;
 import android.util.Log;
@@ -87,16 +86,19 @@ public class LayerController {
     private PanZoomController mPanZoomController;
 
     private GeckoLayerClient mLayerClient;          /* The layer client. */
 
     /* The new color for the checkerboard. */
     private int mCheckerboardColor = Color.WHITE;
     private boolean mCheckerboardShouldShowChecks;
 
+    private boolean mAllowZoom;
+    private float mDefaultZoom;
+
     private boolean mForceRedraw;
 
     public LayerController(Context context) {
         mContext = context;
 
         mForceRedraw = true;
         mViewportMetrics = new ImmutableViewportMetrics(new ViewportMetrics());
         mPanZoomController = new PanZoomController(this);
@@ -341,9 +343,25 @@ public class LayerController {
         mView.requestRender();
     }
 
     /** Sets a new color for the checkerboard. */
     public void setCheckerboardColor(int newColor) {
         mCheckerboardColor = newColor;
         mView.requestRender();
     }
+
+    public void setAllowZoom(boolean aValue) {
+        mAllowZoom = aValue;
+    }
+
+    public boolean getAllowZoom() {
+        return mAllowZoom;
+    }
+
+    public void setDefaultZoom(float aValue) {
+        mDefaultZoom = aValue;
+    }
+
+    public float getDefaultZoom() {
+        return mDefaultZoom;
+    }
 }
--- a/mobile/android/base/ui/PanZoomController.java
+++ b/mobile/android/base/ui/PanZoomController.java
@@ -811,39 +811,52 @@ public class PanZoomController
 
         /* First, we adjust the zoom factor so that we can make no overscrolled area visible. */
         float zoomFactor = viewportMetrics.getZoomFactor();
         FloatSize pageSize = viewportMetrics.getPageSize();
         RectF viewport = viewportMetrics.getViewport();
 
         float focusX = viewport.width() / 2.0f;
         float focusY = viewport.height() / 2.0f;
+
         float minZoomFactor = 0.0f;
-        if (viewport.width() > pageSize.width && pageSize.width > 0) {
+        float maxZoomFactor = MAX_ZOOM;
+
+        if (!mController.getAllowZoom()) {
+            // If allowZoom is false, clamp to the default zoom level.
+            maxZoomFactor = minZoomFactor = mController.getDefaultZoom();
+        }
+
+        // Ensure minZoomFactor keeps the page at least as big as the viewport.
+        if (pageSize.width > 0) {
             float scaleFactor = viewport.width() / pageSize.width;
             minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
-            focusX = 0.0f;
+            if (viewport.width() > pageSize.width)
+                focusX = 0.0f;
         }
-        if (viewport.height() > pageSize.height && pageSize.height > 0) {
+        if (pageSize.height > 0) {
             float scaleFactor = viewport.height() / pageSize.height;
             minZoomFactor = Math.max(minZoomFactor, zoomFactor * scaleFactor);
-            focusY = 0.0f;
+            if (viewport.height() > pageSize.height)
+                focusY = 0.0f;
         }
 
-        if (!FloatUtils.fuzzyEquals(minZoomFactor, 0.0f)) {
+        maxZoomFactor = Math.max(maxZoomFactor, minZoomFactor);
+
+        if (zoomFactor < minZoomFactor) {
             // if one (or both) of the page dimensions is smaller than the viewport,
             // zoom using the top/left as the focus on that axis. this prevents the
             // scenario where, if both dimensions are smaller than the viewport, but
             // by different scale factors, we end up scrolled to the end on one axis
             // after applying the scale
             PointF center = new PointF(focusX, focusY);
             viewportMetrics.scaleTo(minZoomFactor, center);
-        } else if (zoomFactor > MAX_ZOOM) {
+        } else if (zoomFactor > maxZoomFactor) {
             PointF center = new PointF(viewport.width() / 2.0f, viewport.height() / 2.0f);
-            viewportMetrics.scaleTo(MAX_ZOOM, center);
+            viewportMetrics.scaleTo(maxZoomFactor, center);
         }
 
         /* Now we pan to the right origin. */
         viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
         Log.d(LOGTAG, "generated valid viewport as " + viewportMetrics);
 
         return viewportMetrics;
     }
@@ -873,16 +886,19 @@ public class PanZoomController
      */
     @Override
     public boolean onScaleBegin(SimpleScaleGestureDetector detector) {
         Log.d(LOGTAG, "onScaleBegin in " + mState);
 
         if (mState == PanZoomState.ANIMATED_ZOOM)
             return false;
 
+        if (!mController.getAllowZoom())
+            return false;
+
         mState = PanZoomState.PINCHING;
         mLastZoomFocus = new PointF(detector.getFocusX(), detector.getFocusY());
         GeckoApp.mFormAssistPopup.hide();
         cancelTouch();
 
         return true;
     }
 
@@ -992,16 +1008,18 @@ public class PanZoomController
     public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
         GeckoApp.mFormAssistPopup.hide();
         sendPointToGecko("Gesture:SingleTap", motionEvent);
         return true;
     }
 
     @Override
     public boolean onDoubleTap(MotionEvent motionEvent) {
+        if (!mController.getAllowZoom())
+            return false;
         sendPointToGecko("Gesture:DoubleTap", motionEvent);
         return true;
     }
 
     private void cancelTouch() {
         GeckoEvent e = GeckoEvent.createBroadcastEvent("Gesture:CancelTouch", "");
         GeckoAppShell.sendEventToGecko(e);
     }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -2350,16 +2350,17 @@ Tab.prototype = {
         aMetadata.defaultZoom *= scaleRatio;
       if ("minZoom" in aMetadata && aMetadata.minZoom > 0)
         aMetadata.minZoom *= scaleRatio;
       if ("maxZoom" in aMetadata && aMetadata.maxZoom > 0)
         aMetadata.maxZoom *= scaleRatio;
     }
     ViewportHandler.setMetadataForDocument(this.browser.contentDocument, aMetadata);
     this.updateViewportSize(gScreenWidth);
+    this.sendViewportMetadata();
   },
 
   /** Update viewport when the metadata or the window size changes. */
   updateViewportSize: function updateViewportSize(aOldScreenWidth) {
     // When this function gets called on window resize, we must execute
     // this.sendViewportUpdate() so that refreshDisplayPort is called.
     // Ensure that when making changes to this function that code path
     // is not accidentally removed (the call to sendViewportUpdate() is
@@ -2437,16 +2438,25 @@ Tab.prototype = {
     // within the screen width. Note that "actual content" may be different
     // with respect to CSS pixels because of the CSS viewport size changing.
     let zoomScale = (screenW * oldBrowserWidth) / (aOldScreenWidth * viewportW);
     let zoom = this.clampZoom(this._zoom * zoomScale);
     this.setResolution(zoom, false);
     this.sendViewportUpdate();
   },
 
+  sendViewportMetadata: function sendViewportMetadata() {
+    sendMessageToJava({ gecko: {
+      type: "Tab:ViewportMetadata",
+      allowZoom: this.metadata.allowZoom,
+      defaultZoom: this.metadata.defaultZoom || 0,
+      tabID: this.id
+    }});
+  },
+
   setBrowserSize: function(aWidth, aHeight) {
     this.browserWidth = aWidth;
 
     if (!this.browser.contentWindow)
       return;
     let cwu = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
     cwu.setCSSViewport(aWidth, aHeight);
   },