mozilla-inbound @ c0f13ab0740e

7 min read Original article ↗

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 170286 c0f13ab0740e3552744c1727e8c15cbebd182a39
parent 170285 a19e4c6dc2a95920c0a0a80a86833dbb7630e308
child 170287 ff2a1d3d39f2e74712b28460c6cd6f8d11007102
push id40184
push userkgupta@mozilla.com
push dateTue, 25 Feb 2014 00:23:08 +0000
treeherdermozilla-inbound@ff2a1d3d39f2 [default view] [failures only]
reviewersmbrubeck, wesj
bugs941995
milestone30.0a1
first release with

nightly linux32

nightly linux64

nightly mac

nightly win32

nightly win64

last release without

nightly linux32

nightly linux64

nightly mac

nightly win32

nightly win64

Bug 941995 - Disable double-tapping and click delay on pages that are device-width or narrower. r=mbrubeck,wesj

--- 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
  */