Bug 941995 - Disable double-tapping and click delay on pages that are device-width or narrower. r=mbrubeck,wesj
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 24 Feb 2014 19:21:02 -0500
changeset 170648 c0f13ab0740e3552744c1727e8c15cbebd182a39
parent 170647 a19e4c6dc2a95920c0a0a80a86833dbb7630e308
child 170649 ff2a1d3d39f2e74712b28460c6cd6f8d11007102
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersmbrubeck, wesj
bugs941995
milestone30.0a1
Bug 941995 - Disable double-tapping and click delay on pages that are device-width or narrower. r=mbrubeck,wesj
mobile/android/base/ZoomConstraints.java
mobile/android/base/gfx/JavaPanZoomController.java
mobile/android/chrome/content/browser.js
--- a/mobile/android/base/ZoomConstraints.java
+++ b/mobile/android/base/ZoomConstraints.java
@@ -5,38 +5,45 @@
 
 package org.mozilla.gecko;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
 public final class ZoomConstraints {
     private final boolean mAllowZoom;
+    private final boolean mAllowDoubleTapZoom;
     private final float mDefaultZoom;
     private final float mMinZoom;
     private final float mMaxZoom;
 
     public ZoomConstraints(boolean allowZoom) {
         mAllowZoom = allowZoom;
+        mAllowDoubleTapZoom = allowZoom;
         mDefaultZoom = 0.0f;
         mMinZoom = 0.0f;
         mMaxZoom = 0.0f;
     }
 
     ZoomConstraints(JSONObject message) throws JSONException {
         mAllowZoom = message.getBoolean("allowZoom");
+        mAllowDoubleTapZoom = message.getBoolean("allowDoubleTapZoom");
         mDefaultZoom = (float)message.getDouble("defaultZoom");
         mMinZoom = (float)message.getDouble("minZoom");
         mMaxZoom = (float)message.getDouble("maxZoom");
     }
 
     public final boolean getAllowZoom() {
         return mAllowZoom;
     }
 
+    public final boolean getAllowDoubleTapZoom() {
+        return mAllowDoubleTapZoom;
+    }
+
     public final float getDefaultZoom() {
         return mDefaultZoom;
     }
 
     public final float getMinZoom() {
         return mMinZoom;
     }
 
--- a/mobile/android/base/gfx/JavaPanZoomController.java
+++ b/mobile/android/base/gfx/JavaPanZoomController.java
@@ -1349,38 +1349,39 @@ class JavaPanZoomController
 
     @Override
     public void onLongPress(MotionEvent motionEvent) {
         sendPointToGecko("Gesture:LongPress", motionEvent);
     }
 
     @Override
     public boolean onSingleTapUp(MotionEvent motionEvent) {
-        // When zooming is enabled, we wait to see if there's a double-tap.
+        // When double-tapping is allowed, we have to wait to see if this is
+        // going to be a double-tap.
         // However, if mMediumPress is true then we know there will be no
         // double-tap so we treat this as a click.
-        if (mMediumPress || !mTarget.getZoomConstraints().getAllowZoom()) {
+        if (mMediumPress || !mTarget.getZoomConstraints().getAllowDoubleTapZoom()) {
             sendPointToGecko("Gesture:SingleTap", motionEvent);
         }
         // return false because we still want to get the ACTION_UP event that triggers this
         return false;
     }
 
     @Override
     public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
         // When zooming is disabled, we handle this in onSingleTapUp.
-        if (mTarget.getZoomConstraints().getAllowZoom()) {
+        if (mTarget.getZoomConstraints().getAllowDoubleTapZoom()) {
             sendPointToGecko("Gesture:SingleTap", motionEvent);
         }
         return true;
     }
 
     @Override
     public boolean onDoubleTap(MotionEvent motionEvent) {
-        if (mTarget.getZoomConstraints().getAllowZoom()) {
+        if (mTarget.getZoomConstraints().getAllowDoubleTapZoom()) {
             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
@@ -4036,33 +4036,35 @@ Tab.prototype = {
   get metadata() {
     return ViewportHandler.getMetadataForDocument(this.browser.contentDocument);
   },
 
   /** Update viewport when the metadata changes. */
   updateViewportMetadata: function updateViewportMetadata(aMetadata, aInitialLoad) {
     if (Services.prefs.getBoolPref("browser.ui.zoom.force-user-scalable")) {
       aMetadata.allowZoom = true;
+      aMetadata.allowDoubleTapZoom = true;
       aMetadata.minZoom = aMetadata.maxZoom = NaN;
     }
 
     let scaleRatio = window.devicePixelRatio;
 
     if (aMetadata.defaultZoom > 0)
       aMetadata.defaultZoom *= scaleRatio;
     if (aMetadata.minZoom > 0)
       aMetadata.minZoom *= scaleRatio;
     if (aMetadata.maxZoom > 0)
       aMetadata.maxZoom *= scaleRatio;
 
     aMetadata.isRTL = this.browser.contentDocument.documentElement.dir == "rtl";
 
     ViewportHandler.setMetadataForDocument(this.browser.contentDocument, aMetadata);
+    this.sendViewportMetadata();
+
     this.updateViewportSize(gScreenWidth, aInitialLoad);
-    this.sendViewportMetadata();
   },
 
   /** Update viewport when the metadata or the window size changes. */
   updateViewportSize: function updateViewportSize(aOldScreenWidth, aInitialLoad) {
     // 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
@@ -4177,30 +4179,42 @@ Tab.prototype = {
 
     // Avoid having the scroll position jump around after device rotation.
     let win = this.browser.contentWindow;
     this.userScrollPos.x = win.scrollX;
     this.userScrollPos.y = win.scrollY;
 
     this.sendViewportUpdate();
 
+    if (metadata.allowZoom && !Services.prefs.getBoolPref("browser.ui.zoom.force-user-scalable")) {
+      // If the CSS viewport is narrower than the screen (i.e. width <= device-width)
+      // then we disable double-tap-to-zoom behaviour.
+      var oldAllowDoubleTapZoom = metadata.allowDoubleTapZoom;
+      var newAllowDoubleTapZoom = (viewportW > screenW / window.devicePixelRatio);
+      if (oldAllowDoubleTapZoom !== newAllowDoubleTapZoom) {
+        metadata.allowDoubleTapZoom = newAllowDoubleTapZoom;
+        this.sendViewportMetadata();
+      }
+    }
+
     // Store the page size that was used to calculate the viewport so that we
     // can verify it's changed when we consider remeasuring in updateViewportForPageSize
     let viewport = this.getViewport();
     this.lastPageSizeAfterViewportRemeasure = {
       width: viewport.pageRight - viewport.pageLeft,
       height: viewport.pageBottom - viewport.pageTop
     };
   },
 
   sendViewportMetadata: function sendViewportMetadata() {
     let metadata = this.metadata;
     sendMessageToJava({
       type: "Tab:ViewportMetadata",
       allowZoom: metadata.allowZoom,
+      allowDoubleTapZoom: metadata.allowDoubleTapZoom,
       defaultZoom: metadata.defaultZoom || window.devicePixelRatio,
       minZoom: metadata.minZoom || 0,
       maxZoom: metadata.maxZoom || 0,
       isRTL: metadata.isRTL,
       tabID: this.id
     });
   },
 
@@ -5873,35 +5887,42 @@ var ViewportHandler = {
     let height = this.clamp(parseInt(heightStr), kViewportMinHeight, kViewportMaxHeight) || 0;
 
     // Allow zoom unless explicity disabled or minScale and maxScale are equal.
     // WebKit allows 0, "no", or "false" for viewport-user-scalable.
     // Note: NaN != NaN. Therefore if minScale and maxScale are undefined the clause has no effect.
     let allowZoomStr = windowUtils.getDocumentMetadata("viewport-user-scalable");
     let allowZoom = !/^(0|no|false)$/.test(allowZoomStr) && (minScale != maxScale);
 
+    // Double-tap should always be disabled if allowZoom is disabled. So we initialize
+    // allowDoubleTapZoom to the same value as allowZoom and have additional conditions to
+    // disable it in updateViewportSize.
+    let allowDoubleTapZoom = allowZoom;
+
     let autoSize = true;
 
     if (isNaN(scale) && isNaN(minScale) && isNaN(maxScale) && allowZoomStr == "" && widthStr == "" && heightStr == "") {
       // Only check for HandheldFriendly if we don't have a viewport meta tag
       let handheldFriendly = windowUtils.getDocumentMetadata("HandheldFriendly");
       if (handheldFriendly == "true") {
         return new ViewportMetadata({
           defaultZoom: 1,
           autoSize: true,
-          allowZoom: true
+          allowZoom: true,
+          allowDoubleTapZoom: false
         });
       }
 
       let doctype = aWindow.document.doctype;
       if (doctype && /(WAP|WML|Mobile)/.test(doctype.publicId)) {
         return new ViewportMetadata({
           defaultZoom: 1,
           autoSize: true,
-          allowZoom: true
+          allowZoom: true,
+          allowDoubleTapZoom: false
         });
       }
 
       hasMetaViewport = false;
       let defaultZoom = Services.prefs.getIntPref("browser.viewport.defaultZoom");
       if (defaultZoom >= 0) {
         scale = defaultZoom / 1000;
         autoSize = false;
@@ -5923,16 +5944,17 @@ var ViewportHandler = {
     return new ViewportMetadata({
       defaultZoom: scale,
       minZoom: minScale,
       maxZoom: maxScale,
       width: width,
       height: height,
       autoSize: autoSize,
       allowZoom: allowZoom,
+      allowDoubleTapZoom: allowDoubleTapZoom,
       isSpecified: hasMetaViewport,
       isRTL: isRTL
     });
   },
 
   clamp: function(num, min, max) {
     return Math.max(min, Math.min(max, num));
   },
@@ -5966,39 +5988,42 @@ var ViewportHandler = {
  * An object which represents the page's preferred viewport properties:
  *   width (int): The CSS viewport width in px.
  *   height (int): The CSS viewport height in px.
  *   defaultZoom (float): The initial scale when the page is loaded.
  *   minZoom (float): The minimum zoom level.
  *   maxZoom (float): The maximum zoom level.
  *   autoSize (boolean): Resize the CSS viewport when the window resizes.
  *   allowZoom (boolean): Let the user zoom in or out.
+ *   allowDoubleTapZoom (boolean): Allow double-tap to zoom in.
  *   isSpecified (boolean): Whether the page viewport is specified or not.
  */
 function ViewportMetadata(aMetadata = {}) {
   this.width = ("width" in aMetadata) ? aMetadata.width : 0;
   this.height = ("height" in aMetadata) ? aMetadata.height : 0;
   this.defaultZoom = ("defaultZoom" in aMetadata) ? aMetadata.defaultZoom : 0;
   this.minZoom = ("minZoom" in aMetadata) ? aMetadata.minZoom : 0;
   this.maxZoom = ("maxZoom" in aMetadata) ? aMetadata.maxZoom : 0;
   this.autoSize = ("autoSize" in aMetadata) ? aMetadata.autoSize : false;
   this.allowZoom = ("allowZoom" in aMetadata) ? aMetadata.allowZoom : true;
+  this.allowDoubleTapZoom = ("allowDoubleTapZoom" in aMetadata) ? aMetadata.allowDoubleTapZoom : true;
   this.isSpecified = ("isSpecified" in aMetadata) ? aMetadata.isSpecified : false;
   this.isRTL = ("isRTL" in aMetadata) ? aMetadata.isRTL : false;
   Object.seal(this);
 }
 
 ViewportMetadata.prototype = {
   width: null,
   height: null,
   defaultZoom: null,
   minZoom: null,
   maxZoom: null,
   autoSize: null,
   allowZoom: null,
+  allowDoubleTapZoom: null,
   isSpecified: null,
   isRTL: null,
 };
 
 
 /**
  * Handler for blocked popups, triggered by DOMUpdatePageReport events in browser.xml
  */