Index: client/touch/ClickBuster.dart |
=================================================================== |
--- client/touch/ClickBuster.dart (revision 4144) |
+++ client/touch/ClickBuster.dart (working copy) |
@@ -1,215 +0,0 @@ |
-// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
-// for details. All rights reserved. Use of this source code is governed by a |
-// BSD-style license that can be found in the LICENSE file. |
- |
-/** |
- * Click buster implementation, which is a behavior that prevents native clicks |
- * from firing at undesirable times. There are two scenarios where we may want |
- * to 'bust' a click. |
- * |
- * Buttons implemented with touch events usually have click handlers as well. |
- * This is because sometimes touch events stop working, and the click handler |
- * serves as a fallback. Here we use a click buster to prevent the native click |
- * from firing if the touchend event was succesfully handled. |
- * |
- * When native scrolling behavior is disabled (see Scroller), click events will |
- * fire after the touchend event when the drag sequence is complete. The click |
- * event also happens to fire at the location of the touchstart event which can |
- * lead to some very strange behavior. |
- * |
- * This class puts a single click handler on the body, and calls preventDefault |
- * on the click event if we detect that there was a touchend event that already |
- * fired in the same spot recently. |
- */ |
-class ClickBuster { |
- /** |
- * The threshold for how long we allow a click to occur after a touchstart. |
- */ |
- static final _TIME_THRESHOLD = 2500; |
- |
- /** |
- * The threshold for how close a click has to be to the saved coordinate for |
- * us to allow it. |
- */ |
- static final _DISTANCE_THRESHOLD = 25; |
- |
- /** |
- * The list of coordinates that we use to measure the distance of clicks from. |
- * If a click is within the distance threshold of any of these coordinates |
- * then we allow the click. |
- * TODO(ngeoffray): Should be DoubleLinkedQueue<num> |
- */ |
- static var _coordinates; |
- |
- /** The last time preventGhostClick was called. */ |
- static int _lastPreventedTime; |
- |
- /** |
- * This handler will prevent the default behavior for any clicks unless the |
- * click is within the distance threshold of one of the temporary allowed |
- * coordinates. |
- */ |
- static void _onClick(Event e) { |
- if (TimeUtil.now() - _lastPreventedTime > _TIME_THRESHOLD) { |
- return; |
- } |
- final coord = new Coordinate.fromClient(e); |
- // TODO(rnystrom): On Android, we get spurious click events at (0, 0). We |
- // *do* want those clicks to be busted, so commenting this out fixes it. |
- // Leaving it commented out instead of just deleting it because I'm not sure |
- // what this code was intended to do to begin with. |
- /* |
- if (coord.x < 1 && coord.y < 1) { |
- // TODO(jacobr): implement a configurable logging framework. |
- // _logger.warning( |
- // "Not busting click on label elem at(${coord.x}, ${coord.y})"); |
- return; |
- } |
- */ |
- var entry = _coordinates.firstEntry(); |
- while (entry != null) { |
- if (_hitTest(entry.element, |
- entry.nextEntry().element, |
- coord.x, |
- coord.y)) { |
- entry.nextEntry().remove(); |
- entry.remove(); |
- return; |
- } else { |
- entry = entry.nextEntry().nextEntry(); |
- } |
- } |
- |
- // TODO(jacobr): implement a configurable logging framework. |
- // _logger.warning("busting click at ${coord.x}, ${coord.y}"); |
- e.stopPropagation(); |
- e.preventDefault(); |
- } |
- |
- /** |
- * This handler will temporarily allow a click to occur near the touch event's |
- * coordinates. |
- */ |
- static void _onTouchStart(Event e) { |
- TouchEvent te = e; |
- final coord = new Coordinate.fromClient(te.touches[0]); |
- _coordinates.add(coord.x); |
- _coordinates.add(coord.y); |
- window.setTimeout(() { |
- _removeCoordinate(coord.x, coord.y); |
- }, _TIME_THRESHOLD); |
- _toggleTapHighlights(true); |
- } |
- |
- /** |
- * Hit test for whether a coordinate is within the distance threshold of an |
- * event. |
- */ |
- static bool _hitTest(num x, num y, num eventX, num eventY) { |
- return (eventX - x).abs() < _DISTANCE_THRESHOLD && |
- (eventY - y).abs() < _DISTANCE_THRESHOLD; |
- } |
- |
- /** |
- * Remove one specified coordinate from the coordinates list. |
- */ |
- static void _removeCoordinate(num x, num y) { |
- var entry = _coordinates.firstEntry(); |
- while (entry != null) { |
- if (entry.element == x && entry.nextEntry().element == y) { |
- entry.nextEntry().remove(); |
- entry.remove(); |
- return; |
- } else { |
- entry = entry.nextEntry().nextEntry(); |
- } |
- } |
- } |
- |
- /** |
- * Enable or disable tap highlights. They are disabled when preventGhostClick |
- * is called so that the flicker on links is not invoked when the ghost click |
- * does fire. This is due to a bug: links get highlighted even if the click |
- * event has preventDefault called on it. |
- */ |
- static void _toggleTapHighlights(bool enable) { |
- document.body.style.setProperty( |
- "-webkit-tap-highlight-color", enable ? "" : "rgba(0,0,0,0)", ""); |
- } |
- |
- /** |
- * Registers new touches to create temporary "allowable zones" and registers |
- * new clicks to be prevented unless they fall in one of the current |
- * "allowable zones". Note that if the touchstart and touchend locations are |
- * different, it is still possible for a ghost click to be fired if you |
- * called preventDefault on all touchmove events. In this case the ghost |
- * click will be fired at the location of the touchstart event, so the |
- * coordinate you pass in should be the coordinate of the touchstart. |
- */ |
- static void preventGhostClick(num x, num y) { |
- // First time this is called the following occurs: |
- // 1) Attaches a handler to touchstart events so that each touch will |
- // temporarily create an "allowable zone" for clicks to occur in. |
- // 2) Attaches a handler to click events so that each click will be |
- // prevented unless it is in an "allowable zone". |
- // |
- // Every time this is called (including the first) the following occurs: |
- // 1) Removes an allowable zone that contains the specified coordinate. |
- // |
- // How this enables click busting: |
- // 1) User performs first click. |
- // - No attached touchstart handler yet. |
- // - preventGhostClick is called before the click event occurs, it |
- // attaches the touchstart and click handlers. |
- // - The click handler captures the user's click event and prevents it |
- // from propagating since there is no "allowable zone". |
- // |
- // 2) User performs subsequent, to-be-busted click. |
- // - touchstart event triggers the attached handler and creates a |
- // temporary "allowable zone". |
- // - preventGhostClick is called and removes the "allowable zone". |
- // - The click handler captures the user's click event and prevents it |
- // from propagating since there is no "allowable zone". |
- // |
- // 3) User performs a should-not-be-busted click. |
- // - touchstart event triggers the attached handler and creates a |
- // temporary "allowable zone". |
- // - The click handler captures the user's click event and allows it to |
- // propagate since the click falls in the "allowable zone". |
- if (_coordinates === null) { |
- // Listen to clicks on capture phase so they can be busted before anything |
- // else gets a chance to handle them. |
- document.on.click.add((e) { _onClick(e); }, true); |
- document.on.focus.add((e) { _lastPreventedTime = 0; }, true); |
- |
- // Listen to touchstart on capture phase since it must be called prior to |
- // every click or else we will accidentally prevent the click even if we |
- // don't call preventGhostClick. |
- Function startFn = (e) { _onTouchStart(e); }; |
- if (!Device.supportsTouch) { |
- startFn = mouseToTouchCallback(startFn); |
- } |
- EventUtil.observe(document, |
- Device.supportsTouch ? document.on.touchStart : document.on.mouseDown, |
- startFn, true, true); |
- _coordinates = new Queue<num>(); |
- } |
- |
- // Turn tap highlights off until we know the ghost click has fired. |
- _toggleTapHighlights(false); |
- |
- // Above all other rules, we won't bust any clicks if there wasn't some call |
- // to preventGhostClick in the last time threshold. |
- _lastPreventedTime = TimeUtil.now(); |
- var entry = _coordinates.firstEntry(); |
- while (entry != null) { |
- if (_hitTest(entry.element, entry.nextEntry().element, x, y)) { |
- entry.nextEntry().remove(); |
- entry.remove(); |
- return; |
- } else { |
- entry = entry.nextEntry().nextEntry(); |
- } |
- } |
- } |
-} |