forked from WICG/input-device-capabilities
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinputdevicecapabilities-polyfill.js
More file actions
168 lines (146 loc) · 7.2 KB
/
inputdevicecapabilities-polyfill.js
File metadata and controls
168 lines (146 loc) · 7.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
/* inputdevicecapabilities-polyfill.js - https://github.com/WICG/InputDeviceCapabilities
*
* Uses a (not perfectly accurate) heuristic to implement
* UIEvent.sourceCapabilities and InputDeviceCapabilities.firesTouchEvents.
* Assumptions:
* - If sourceCapabilities is consulted on an event, it will be first read within
* one second of the original event being dispatched. We could, instead,
* determine the sourceCapabilities as soon as any UIEvent is dispatched (eg.
* by hooking addEventListener) but that woudln't work for legacy onevent
* style handlers.
* - Touch and non-touch input devices aren't both being used within one
* second of eachother. Eg. if you tap the screen then quickly move the
* mouse, we may incorrectly attribute the mouse movement to the touch
* device.
*
* Browser support:
* - Verified working on:
* - Chrome 43 (Windows, Linux and Android)
* - IE 11 (Windows)
* - Firefox 38 (Linux)
* - Safari 8 (Mac and iOS)
* - Event constructors aren't supported by IE at all.
* - IE on Windows phone isn't supported properly (https://github.com/WICG/InputDeviceCapabilities/issues/13)
* - Won't work in IE prior to version 9 (due to lack of Object.defineProperty)
*/
(function(global) {
'use strict';
if ('InputDeviceCapabilities' in global|| 'sourceCapabilities' in UIEvent.prototype)
return;
function InputDeviceCapabilities(inputDeviceCapabilitiesInit) {
Object.defineProperty(this, '__firesTouchEvents', {
value: (inputDeviceCapabilitiesInit && 'firesTouchEvents' in inputDeviceCapabilitiesInit) ?
inputDeviceCapabilitiesInit.firesTouchEvents : false,
writable: false,
enumerable: false
});
};
// Put the attributes prototype as getter functions to match the IDL.
InputDeviceCapabilities.prototype = {
get firesTouchEvents() {
return this.__firesTouchEvents;
}
};
global.InputDeviceCapabilities = InputDeviceCapabilities;
var touchDevice = new InputDeviceCapabilities({firesTouchEvents:true});
var nonTouchDevice = new InputDeviceCapabilities({firesTouchEvents:false});
// Keep track of the last time we saw a touch event. Note that if you don't
// already have touch handlers on your document, this can have unfortunate
// consequences for scrolling performance. See https://plus.google.com/+RickByers/posts/cmzrtyBYPQc.
var lastTouchTime;
function touchHandler(event) {
lastTouchTime = Date.now();
};
document.addEventListener('touchstart', touchHandler, true);
document.addEventListener('touchmove', touchHandler, true);
document.addEventListener('touchend', touchHandler, true);
document.addEventListener('touchcancel', touchHandler, true);
var specifiedSourceCapabilitiesName = '__inputDeviceCapabilitiesPolyfill_specifiedSourceCapabilities';
// A few UIEvents aren't really input events and so should always have a null
// source capabilities. Arguably we should have a list of opt-in event types instead,
// but that probably depends on ultimately how we want to specify this behavior.
var eventTypesWithNoSourceCapabilities = ['resize', 'error', 'load', 'unload', 'abort'];
// We assume that any UI event that occurs within this many ms from a touch
// event is caused by a touch device. This needs to be a little longer than
// the maximum tap delay on various browsers (350ms in Safari) while remaining
// as short as possible to reduce the risk of confusing other input that happens
// to come shortly after touch input.
var touchTimeConstant = 1000;
Object.defineProperty(UIEvent.prototype, 'sourceCapabilities', {
get: function() {
// Handle script-generated events and events which have already had their
// sourceCapabilities read.
if (specifiedSourceCapabilitiesName in this)
return this[specifiedSourceCapabilitiesName];
// Handle non-input events.
if (eventTypesWithNoSourceCapabilities.indexOf(this.type) >= 0)
return null;
// touch events may not be supported by this browser at all (eg. IE desktop).
if (!('TouchEvent' in global))
return nonTouchDevice;
// Touch events are always generated from devices that fire touch events.
if (this instanceof TouchEvent)
return touchDevice;
// Pointer events are special - they may come before a touch event.
if ('PointerEvent' in global && this instanceof PointerEvent) {
if (this.pointerType == "touch")
return touchDevice;
return nonTouchDevice;
}
// Otherwise use recent touch events to decide if this event is likely due
// to a touch device or not.
var sourceCapabilities = Date.now() < lastTouchTime + touchTimeConstant ? touchDevice : nonTouchDevice;
// Cache the value to ensure it can't change over the life of the event.
Object.defineProperty(this, specifiedSourceCapabilitiesName, {
value: sourceCapabilities,
writable: false
});
return sourceCapabilities;
},
configurable: true,
enumerable: true
});
// Add support for supplying a sourceCapabilities from JS in all UIEvent constructors.
function augmentEventConstructor(constructorName) {
if (!(constructorName in global))
return;
// IE doesn't support event constructors at all.
// In Safari typeof constructor is 'object' while it's 'function' in other browsers.
if (!('length' in global[constructorName]) || global[constructorName].length < 1)
return;
var origCtor = global[constructorName];
global[constructorName] = function(type, initDict) {
var sourceCapabilities = (initDict && initDict.sourceCapabilities) ? initDict.sourceCapabilities : null;
// Need to explicitly remove sourceCapabilities from the dictionary as it would cause
// a type error in blink when InputDeviceCapabilities support is disabled.
if (initDict)
delete initDict.sourceCapabilities;
var evt = new origCtor(type, initDict);
// Stash the sourceCapabilities value away for use by the UIEvent.sourceCapabilities
// getter. We could instead shadow the property on this instance, but
// that would be subtly different than the specified API.
Object.defineProperty(evt, specifiedSourceCapabilitiesName, {
value: sourceCapabilities,
writable: false
});
return evt;
}
global[constructorName].prototype = origCtor.prototype;
};
// Note that SVGZoomEvent desn't yet have constructors defined.
var uiEventConstructors = ['UIEvent', 'MouseEvent', 'TouchEvent', 'InputEvent', 'CompositionEvent', 'FocusEvent', 'KeyboardEvent', 'WheelEvent', 'PointerEvent'];
for (var i = 0; i < uiEventConstructors.length; i++)
augmentEventConstructor(uiEventConstructors[i]);
// Ensure events created with document.createEvent always get a null sourceCapabilities
var origCreateEvent = Document.prototype.createEvent;
Document.prototype.createEvent = function(type) {
var evt = origCreateEvent.call(this, type);
if (evt instanceof UIEvent) {
Object.defineProperty(evt, specifiedSourceCapabilitiesName, {
value: null,
writable: false
});
}
return evt;
};
})(this);