Bug 597159 - Double taps sometime cause single taps - part 2 tap highlight [r=mbrubeck]
authorBenjamin Stover <bstover@mozilla.com>
Fri, 17 Sep 2010 11:48:20 -0700
changeset 66670 2e3816c67a0236722cbb22f41354820e7bb0383b
parent 66669 c6b26dced0d0c173ca9f85a0f185199de676e02d
child 66671 5d28f0c0395e069f42026324579c03028938afca
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)
reviewersmbrubeck
bugs597159
Bug 597159 - Double taps sometime cause single taps - part 2 tap highlight [r=mbrubeck]
mobile/chrome/content/browser-ui.js
mobile/chrome/content/browser.js
mobile/chrome/content/content.js
--- a/mobile/chrome/content/browser-ui.js
+++ b/mobile/chrome/content/browser-ui.js
@@ -1080,20 +1080,54 @@ var TapHighlightHelper = {
     overlay.style.top = canvasArea.top + "px";
     ctx.fillStyle = "rgba(0, 145, 255, .5)";
     for (let i = aRects.length - 1; i >= 0; i--) {
       let rect = aRects[i];
       ctx.fillRect(rect.left - scroll.x / browser.scale, rect.top - scroll.y / browser.scale, rect.width, rect.height);
     }
     ctx.restore();
     overlay.style.display = "block";
+
+    addEventListener("MozBeforePaint", this, false);
+    mozRequestAnimationFrame();
   },
 
-  hide: function hide() {
+  /**
+   * Hide the highlight. aGuaranteeShowMsecs specifies how many milliseconds the
+   * highlight should be shown before it disappears.
+   */
+  hide: function hide(aGuaranteeShowMsecs) {
+    if (this._overlay.style.display == "none")
+      return;
+
+    this._guaranteeShow = Math.max(0, aGuaranteeShowMsecs);
+    if (this._guaranteeShow) {
+      // _shownAt is set once highlight has been painted
+      if (this._shownAt)
+        setTimeout(this._hide.bind(this),
+                   Math.max(0, this._guaranteeShow - (mozAnimationStartTime - this._shownAt)));
+    } else {
+      this._hide();
+    }
+  },
+
+  /** Helper function that hides popup immediately. */
+  _hide: function _hide() {
+    this._shownAt = 0;
+    this._guaranteeShow = 0;
     this._overlay.style.display = "none";
+  },
+
+  handleEvent: function handleEvent(ev) {
+    removeEventListener("MozBeforePaint", this, false);
+    this._shownAt = ev.timeStamp;
+    // hide has been called, so hide the tap highlight after it has
+    // been shown for a moment.
+    if (this._guaranteeShow)
+      this.hide(this._guaranteeShow);
   }
 };
 
 var PageActions = {
   init: function init() {
     this.register("pageaction-reset", this.updatePagePermissions, this);
     this.register("pageaction-password", this.updateForgetPassword, this);
 #ifdef NS_PRINTING
--- a/mobile/chrome/content/browser.js
+++ b/mobile/chrome/content/browser.js
@@ -574,16 +574,18 @@ var Browser = {
 
   set selectedTab(tab) {
     if (tab instanceof XULElement)
       tab = this.getTabFromChrome(tab);
 
     if (!tab || this._selectedTab == tab)
       return;
 
+    TapHighlightHelper.hide();
+
     if (this._selectedTab) {
       this._selectedTab.pageScrollOffset = this.getScrollboxPosition(this.pageScrollboxScroller);
 
       // Make sure we leave the toolbar in an unlocked state
       if (this._selectedTab.isLoading())
         BrowserUI.unlockToolbar();
     }
 
@@ -1270,26 +1272,27 @@ ContentCustomClicker.prototype = {
     try {
       fl.activateRemoteFrame();
     } catch (e) {
     }
     this._dispatchMouseEvent("Browser:MouseDown", aX, aY);
   },
 
   mouseUp: function mouseUp(aX, aY) {
+    TapHighlightHelper.hide(200);
   },
 
   panBegin: function panBegin() {
-    TapHighlightHelper.hide();
+    TapHighlightHelper.hide(0);
 
     this._dispatchMouseEvent("Browser:MouseCancel");
   },
 
   singleClick: function singleClick(aX, aY, aModifiers) {
-    TapHighlightHelper.hide();
+    TapHighlightHelper.hide(200);
 
     // Cancel the mouse click if we are showing a context menu
     if (!ContextHelper.popupState)
       this._dispatchMouseEvent("Browser:MouseUp", aX, aY, aModifiers);
     this._dispatchMouseEvent("Browser:MouseCancel");
   },
 
   doubleClick: function doubleClick(aX1, aY1, aX2, aY2) {
@@ -2004,16 +2007,18 @@ ProgressController.prototype = {
       return;
 
     let spec = aLocationURI ? aLocationURI.spec : "";
     let location = spec.split("#")[0]; // Ignore fragment identifier changes.
 
     this._hostChanged = true;
 
     if (location != this.browser.lastLocation) {
+      TapHighlightHelper.hide();
+
       this.browser.lastLocation = location;
       Browser.removeTransientNotificationsForTab(this._tab);
       this._tab.resetZoomLevel();
 
       if (this._tab == Browser.selectedTab) {
         BrowserUI.updateURI();
 
         // We're about to have new page content, so scroll the content area
--- a/mobile/chrome/content/content.js
+++ b/mobile/chrome/content/content.js
@@ -1,14 +1,11 @@
 // This stays here because otherwise it's hard to tell if there's a parsing error
 dump("###################################### content loaded\n");
 
-// how many milliseconds before the mousedown and the overlay of an element
-const kTapOverlayTimeout = 200;
-
 let Cc = Components.classes;
 let Ci = Components.interfaces;
 let Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 let gFocusManager = Cc["@mozilla.org/focus-manager;1"]
   .getService(Ci.nsIFocusManager);
@@ -313,17 +310,16 @@ function Content() {
     addEventListener("DOMActivate", this, true);
 
   addEventListener("MozApplicationManifest", this, false);
 
   this._progressController = new ProgressController(this);
   this._progressController.start();
 
   this._formAssistant = new FormAssistant();
-  this._overlayTimeout = new Util.Timeout();
   this._contextTimeout = new Util.Timeout();
 }
 
 Content.prototype = {
   handleEvent: function handleEvent(aEvent) {
     switch (aEvent.type) {
       case "DOMActivate": {
         // In a local tab, open remote links in new tabs.
@@ -375,33 +371,28 @@ Content.prototype = {
             metaKey: json.modifiers & masks.META_MASK,
             keyCode: json.keyCode,
             charCode: json.charCode
           });
         }
         break;
 
       case "Browser:MouseDown":
-        this._overlayTimeout.clear();
         this._contextTimeout.clear();
 
         let element = elementFromPoint(x, y);
         if (!element)
           return;
 
         if (element.mozMatchesSelector("*:link,*:visited,*:link *,*:visited *,*[role=button],button,input,option,select,textarea,label")) {
-          this._overlayTimeout.once(kTapOverlayTimeout, function() {
-            let rects = getContentClientRects(element);
-            sendAsyncMessage("Browser:Highlight", { rects: rects });
-          });
+          let rects = getContentClientRects(element);
+          sendAsyncMessage("Browser:Highlight", { rects: rects });
         }
 
-        // We add a few milliseconds because of how the InputHandler wait before
-        // dispatching a single click (default: 500)
-        this._contextTimeout.once(500 + kTapOverlayTimeout, function() {
+        this._contextTimeout.once(500, function() {
           let event = content.document.createEvent("PopupEvents");
           event.initEvent("contextmenu", true, true);
           element.dispatchEvent(event);
         });
         break;
 
       case "Browser:MouseUp": {
         let element = elementFromPoint(x, y);
@@ -414,17 +405,16 @@ Content.prototype = {
           this._sendMouseEvent("mousemove", element, x, y);
           this._sendMouseEvent("mousedown", element, x, y);
           this._sendMouseEvent("mouseup", element, x, y);
         }
         break;
       }
 
       case "Browser:MouseCancel":
-        this._overlayTimeout.clear();
         this._contextTimeout.clear();
         break;
 
       case "Browser:SaveAs":
         if (json.type != Ci.nsIPrintSettings.kOutputFormatPDF)
           return;
 
         let printSettings = Cc["@mozilla.org/gfx/printsettings-service;1"]
@@ -503,17 +493,16 @@ Content.prototype = {
 
     let scrollOffset = Util.getScrollOffset(content);
     let windowUtils = Util.getWindowUtils(content);
     windowUtils.sendMouseEventToWindow(aName, aX - scrollOffset.x, aY - scrollOffset.y, 0, 1, 0, true);
   },
 
   startLoading: function startLoading() {
     this._contextTimeout.clear();
-    this._overlayTimeout.clear();
     this._loading = true;
   },
 
   stopLoading: function stopLoading() {
     this._loading = false;
   },
 
   isSelected: function isSelected() {