Skip to content
This repository was archived by the owner on Sep 5, 2024. It is now read-only.

Commit 244619a

Browse files
Splaktarandrewseguin
authored andcommitted
fix(gesture): tapping a submit button fires two submit events on mobile (#11729)
this causes the dynamic tabs demo w/ pagination to often add two tabs for each 'Add Tab' button click on iOS and Android when click hijacking is enabled (default) - isKeyClick check did not work on iOS since clientX/Y are set - add an iOS-specific isKeyClick check - revert workaround from PR #10189 use recommended `new MouseEvent()` and `new CustomEvent()` - fallback to deprecated methods `initMouseEvent()` and `initCustomEvent()` Fixes #11725
1 parent 2c226a7 commit 244619a

File tree

2 files changed

+71
-31
lines changed

2 files changed

+71
-31
lines changed

src/components/tabs/demoDynamicTabs/script.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
function AppCtrl ($scope, $log) {
88
var tabs = [
9-
{ title: 'Zero (AKA 0, Cero, One - One, -Nineteen + 19, and so forth and so on and continuing into what seems like infinity.)', content: 'Woah...that is a really long title!' },
9+
{ title: 'Zero (AKA 0, Cero, One - One, -Nineteen + 19, and so forth and so on and continuing into what seems like infinity)', content: 'Whoa...that is a really long title!' },
1010
{ title: 'One', content: "Tabs will become paginated if there isn't enough room for them."},
1111
{ title: 'Two', content: "You can swipe left and right on a mobile device to change tabs."},
1212
{ title: 'Three', content: "You can bind the selected tab via the selected attribute on the md-tabs element."},

src/core/services/gesture/gesture.js

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ var lastLabelClickPos = null;
1717
// Used to attach event listeners once when multiple ng-apps are running.
1818
var isInitialized = false;
1919

20+
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
21+
var isIos = userAgent.match(/ipad|iphone|ipod/i);
22+
var isAndroid = userAgent.match(/android/i);
23+
2024
/**
2125
* @ngdoc module
2226
* @name material.core.gestures
@@ -110,9 +114,6 @@ MdGestureProvider.prototype = {
110114
* @ngInject
111115
*/
112116
function MdGesture($$MdGestureHandler, $$rAF, $timeout) {
113-
var userAgent = navigator.userAgent || navigator.vendor || window.opera;
114-
var isIos = userAgent.match(/ipad|iphone|ipod/i);
115-
var isAndroid = userAgent.match(/android/i);
116117
var touchActionProperty = getTouchAction();
117118
var hasJQuery = (typeof window.jQuery !== 'undefined') && (angular.element === window.jQuery);
118119

@@ -477,7 +478,7 @@ function MdGestureHandler() {
477478

478479
return GestureHandler;
479480

480-
/*
481+
/**
481482
* Dispatch an event with jQuery
482483
* TODO: Make sure this sends bubbling events
483484
*
@@ -519,24 +520,52 @@ function MdGestureHandler() {
519520
var eventObj;
520521

521522
if (eventType === 'click' || eventType === 'mouseup' || eventType === 'mousedown') {
522-
eventObj = document.createEvent('MouseEvents');
523-
eventObj.initMouseEvent(
524-
eventType, true, true, window, srcEvent.detail,
525-
eventPointer.x, eventPointer.y, eventPointer.x, eventPointer.y,
526-
srcEvent.ctrlKey, srcEvent.altKey, srcEvent.shiftKey, srcEvent.metaKey,
527-
srcEvent.button, srcEvent.relatedTarget || null
528-
);
529-
523+
if (typeof window.MouseEvent === "function") {
524+
eventObj = new MouseEvent(eventType, {
525+
bubbles: true,
526+
cancelable: true,
527+
screenX: Number(srcEvent.screenX),
528+
screenY: Number(srcEvent.screenY),
529+
clientX: Number(eventPointer.x),
530+
clientY: Number(eventPointer.y),
531+
ctrlKey: srcEvent.ctrlKey,
532+
altKey: srcEvent.altKey,
533+
shiftKey: srcEvent.shiftKey,
534+
metaKey: srcEvent.metaKey,
535+
button: srcEvent.button,
536+
buttons: srcEvent.buttons,
537+
relatedTarget: srcEvent.relatedTarget || null
538+
});
539+
} else {
540+
eventObj = document.createEvent('MouseEvents');
541+
// This has been deprecated
542+
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/initMouseEvent
543+
eventObj.initMouseEvent(
544+
eventType, true, true, window, srcEvent.detail,
545+
eventPointer.x, eventPointer.y, eventPointer.x, eventPointer.y,
546+
srcEvent.ctrlKey, srcEvent.altKey, srcEvent.shiftKey, srcEvent.metaKey,
547+
srcEvent.button, srcEvent.relatedTarget || null
548+
);
549+
}
530550
} else {
531-
eventObj = document.createEvent('CustomEvent');
532-
eventObj.initCustomEvent(eventType, true, true, {});
551+
if (typeof window.CustomEvent === "function") {
552+
eventObj = new CustomEvent(eventType, {
553+
bubbles: true,
554+
cancelable: true,
555+
detail: {}
556+
});
557+
} else {
558+
// This has been deprecated
559+
// https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/initCustomEvent
560+
eventObj = document.createEvent('CustomEvent');
561+
eventObj.initCustomEvent(eventType, true, true, {});
562+
}
533563
}
534564
eventObj.$material = true;
535565
eventObj.pointer = eventPointer;
536566
eventObj.srcEvent = srcEvent;
537567
eventPointer.target.dispatchEvent(eventObj);
538568
}
539-
540569
}
541570

542571
/**
@@ -589,18 +618,26 @@ function attachToDocument($mdGesture, $$MdGestureHandler) {
589618
}
590619
}
591620

621+
/**
622+
* Ignore click events that don't come from AngularJS Material, Ionic, Input Label clicks,
623+
* or key presses that generate click events. This helps to ignore the ghost tap events on
624+
* older mobile browsers that get sent after a 300-400ms delay.
625+
* @param ev MouseEvent or modified MouseEvent with $material, pointer, and other fields
626+
*/
592627
function clickHijacker(ev) {
593-
var isKeyClick = ev.clientX === 0 && ev.clientY === 0;
594-
var isSubmitEvent = ev.target && ev.target.type === 'submit';
595-
if (!isKeyClick && !ev.$material && !ev.isIonicTap
596-
&& !isInputEventFromLabelClick(ev)
597-
&& !isSubmitEvent) {
628+
var isKeyClick;
629+
if (isIos) {
630+
isKeyClick = angular.isDefined(ev.webkitForce) && ev.webkitForce === 0;
631+
} else {
632+
isKeyClick = ev.clientX === 0 && ev.clientY === 0;
633+
}
634+
if (!isKeyClick && !ev.$material && !ev.isIonicTap && !isInputEventFromLabelClick(ev)) {
598635
ev.preventDefault();
599636
ev.stopPropagation();
600637
lastLabelClickPos = null;
601638
} else {
602639
lastLabelClickPos = null;
603-
if (ev.target.tagName.toLowerCase() == 'label') {
640+
if (ev.target.tagName.toLowerCase() === 'label') {
604641
lastLabelClickPos = {x: ev.x, y: ev.y};
605642
}
606643
}
@@ -621,10 +658,10 @@ function attachToDocument($mdGesture, $$MdGestureHandler) {
621658
lastPointer = pointer = null;
622659
});
623660

624-
/*
661+
/**
625662
* When a DOM event happens, run all registered gesture handlers' lifecycle
626663
* methods which match the DOM event.
627-
* Eg when a 'touchstart' event happens, runHandlers('start') will call and
664+
* Eg. when a 'touchstart' event happens, runHandlers('start') will call and
628665
* run `handler.cancel()` and `handler.start()` on all registered handlers.
629666
*/
630667
function runHandlers(handlerEvent, event) {
@@ -638,7 +675,6 @@ function attachToDocument($mdGesture, $$MdGestureHandler) {
638675
handler.cancel();
639676
}
640677
handler[handlerEvent](event, pointer);
641-
642678
}
643679
}
644680
}
@@ -665,18 +701,22 @@ function attachToDocument($mdGesture, $$MdGestureHandler) {
665701

666702
runHandlers('start', ev);
667703
}
668-
/*
704+
705+
/**
669706
* If a move event happens of the right type, update the pointer and run all the move handlers.
670-
* "of the right type": if a mousemove happens but our pointer started with a touch event, do nothing.
707+
* "of the right type": if a mousemove happens but our pointer started with a touch event, do
708+
* nothing.
671709
*/
672710
function gestureMove(ev) {
673711
if (!pointer || !typesMatch(ev, pointer)) return;
674712

675713
updatePointerState(ev, pointer);
676714
runHandlers('move', ev);
677715
}
678-
/*
679-
* If an end event happens of the right type, update the pointer, run endHandlers, and save the pointer as 'lastPointer'
716+
717+
/**
718+
* If an end event happens of the right type, update the pointer, run endHandlers, and save the
719+
* pointer as 'lastPointer'.
680720
*/
681721
function gestureEnd(ev) {
682722
if (!pointer || !typesMatch(ev, pointer)) return;
@@ -740,8 +780,8 @@ function typesMatch(ev, pointer) {
740780
*/
741781
function isInputEventFromLabelClick(event) {
742782
return lastLabelClickPos
743-
&& lastLabelClickPos.x == event.x
744-
&& lastLabelClickPos.y == event.y;
783+
&& lastLabelClickPos.x === event.x
784+
&& lastLabelClickPos.y === event.y;
745785
}
746786

747787
/*

0 commit comments

Comments
 (0)