From 879309f1a8c7d7c07d9bcaf0954faa8a2e4548ad Mon Sep 17 00:00:00 2001 From: Travis Date: Thu, 10 Dec 2015 18:43:48 -0600 Subject: [PATCH] fix(ngClick): subract processing time of the click action subract the time it takes for the browser to process the click action for preventing the ghost click. long running processes or slower devices where a lot of rendering is happening would get the ghost click every time. --- src/ngTouch/directive/ngClick.js | 12 +++- test/ngTouch/directive/ngClickSpec.js | 79 +++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/ngTouch/directive/ngClick.js b/src/ngTouch/directive/ngClick.js index f352c252f443..0b1af1e3fe66 100644 --- a/src/ngTouch/directive/ngClick.js +++ b/src/ngTouch/directive/ngClick.js @@ -57,6 +57,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement', var ACTIVE_CLASS_NAME = 'ng-click-active'; var lastPreventedTime; + var processingTime; var touchCoordinates; var lastLabelClickCoordinates; @@ -121,7 +122,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement', // Global click handler that prevents the click if it's in a bustable zone and preventGhostClick // was called recently. function onClick(event) { - if (Date.now() - lastPreventedTime > PREVENT_DURATION) { + if (Date.now() - lastPreventedTime - processingTime > PREVENT_DURATION) { return; // Too old. } @@ -197,6 +198,14 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement', checkAllowableRegions(touchCoordinates, x, y); } + function calculateProcessingTime() { + //use the now - last prevented time on a timeout + processingTime = 0; + $timeout(function() { + processingTime = Date.now() - lastPreventedTime; + }, 0); + } + // Actual linking function. return function(scope, element, attr) { var clickHandler = $parse(attr.ngClick), @@ -251,6 +260,7 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement', if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) { // Call preventGhostClick so the clickbuster will catch the corresponding click. preventGhostClick(x, y); + calculateProcessingTime(); // Blur the focused element (the button, probably) before firing the callback. // This doesn't work perfectly on Android Chrome, but seems to work elsewhere. diff --git a/test/ngTouch/directive/ngClickSpec.js b/test/ngTouch/directive/ngClickSpec.js index 179b0248ff21..b1bcca12102c 100644 --- a/test/ngTouch/directive/ngClickSpec.js +++ b/test/ngTouch/directive/ngClickSpec.js @@ -233,6 +233,85 @@ describe('ngClick (touch)', function() { })); + it('should cancel the following click event with long running processes', inject(function($rootScope, $compile, $rootElement, $timeout) { + //we need the real date now function for really getting the time... to really test the timeout... + Date.now = orig_now; + var count = 0; + var done = false; + + $rootScope.slowCount = function() { + var start = Date.now(); + var now = Date.now(); + //must wait longer than the PREVENT_DURATION + while (now - start < 3000) { + now = Date.now(); + } + count++; + }; + + element = $compile('
')($rootScope); + $rootElement.append(element); + + $rootScope.$digest(); + + expect(count).toBe(0); + + // Fire touchstart at 10ms, touchend at 50ms, the click at 300ms. + runs(function() { + setTimeout(function() { + browserTrigger(element, 'touchstart', { + keys: [], + x: 10, + y: 10 + }); + try { + $timeout.verifyNoPendingTasks(); + } catch (e) { + $timeout.flush(); + } + }, 10); + + setTimeout(function() { + browserTrigger(element, 'touchend', { + keys: [], + x: 10, + y: 10 + }); + try { + $timeout.verifyNoPendingTasks(); + } catch (e) { + $timeout.flush(); + } + + expect(count).toBe(1); + }, 50); + + setTimeout(function() { + browserTrigger(element, 'click', { + keys: [], + x: 10, + y: 10 + }); + try { + $timeout.verifyNoPendingTasks(); + } catch (e) { + $timeout.flush(); + } + done = true; + }, 300); + + }); + + waitsFor(function() { + return done; + }, "click event to fire", 500); + + runs(function() { + expect(count).toEqual(1); + }); + })); + + it('should cancel the following click event even when the element has changed', inject( function($rootScope, $compile, $rootElement) { $rootElement.append(