]> git.proxmox.com Git - extjs.git/blame - extjs/packages/core/src/event/publisher/Dom.js
add extjs 6.0.1 sources
[extjs.git] / extjs / packages / core / src / event / publisher / Dom.js
CommitLineData
6527f429
DM
1/**\r
2 * @private\r
3 */\r
4Ext.define('Ext.event.publisher.Dom', {\r
5 extend: 'Ext.event.publisher.Publisher',\r
6 requires: [\r
7 'Ext.event.Event'\r
8 ],\r
9\r
10 type: 'dom',\r
11\r
12 /**\r
13 * @property {Array} handledDomEvents\r
14 * An array of DOM events that this publisher handles. Events specified in this array\r
15 * will be added as global listeners on the {@link #target}\r
16 */\r
17 handledDomEvents: [],\r
18\r
19 reEnterCount: 0,\r
20\r
21 // The following events do not bubble, but can still be "captured" at the top of\r
22 // the DOM, For these events, when the delegated event model is used, we attach a\r
23 // single listener on the window object using the "useCapture" option.\r
24 captureEvents: {\r
25 animationstart: 1,\r
26 animationend: 1,\r
27 resize: 1,\r
28 focus: 1,\r
29 blur: 1,\r
30 scroll: 1\r
31 },\r
32\r
33 // The following events do not bubble, and cannot be "captured". The only way to\r
34 // listen for these events is via a listener attached directly to the target element\r
35 directEvents: {\r
36 mouseenter: 1,\r
37 mouseleave: 1,\r
38 pointerenter: 1,\r
39 pointerleave: 1,\r
40 MSPointerEnter: 1,\r
41 MSPointerLeave: 1,\r
42 load: 1,\r
43 unload: 1,\r
44 beforeunload: 1,\r
45 error: 1,\r
46 DOMContentLoaded: 1,\r
47 DOMFrameContentLoaded: 1,\r
48 hashchange: 1\r
49 },\r
50\r
51 /**\r
52 * In browsers that implement pointerevents when a pointerdown is triggered by touching\r
53 * the screen, pointerover and pointerenter events will be fired immmediately before\r
54 * the pointerdown. Also pointerout and pointerleave will be fired immediately after\r
55 * pointerup when triggered using touch input. For a consistent cross-browser\r
56 * experience on touch-screens we block pointerover, pointerout, pointerenter, and\r
57 * pointerleave when triggered by touch input, since in most cases pointerover/pointerenter\r
58 * behavior is not desired when touching the screen. Note: this should only affect\r
59 * events with pointerType === 'touch' or pointerType === 'pen', we do NOT want to\r
60 * block these events when triggered using a mouse.\r
61 * See also:\r
62 * http://www.w3.org/TR/pointerevents/#the-pointerdown-event\r
63 * http://www.w3.org/TR/pointerevents/#the-pointerenter-event\r
64 * @private\r
65 */\r
66 blockedPointerEvents: {\r
67 pointerover: 1,\r
68 pointerout: 1,\r
69 pointerenter: 1,\r
70 pointerleave: 1,\r
71 MSPointerOver: 1,\r
72 MSPointerOut: 1,\r
73 MSPointerEnter: 1,\r
74 MSPointerLeave: 1\r
75 },\r
76\r
77 /**\r
78 * Browsers with pointer events may implement "compatibility" mouse events:\r
79 * http://www.w3.org/TR/pointerevents/#compatibility-mapping-with-mouse-events\r
80 * The behavior implemented in handlers for mouse over/out/enter/leave is not typically\r
81 * desired when touching the screen, so we map all of these events to their pointer\r
82 * counterparts in Ext.Element event translation code, so that they can be blocked\r
83 * via "blockedPointerEvents". The only scenario where this breaks down is in IE10\r
84 * with mouseenter/mouseleave, since MSPointerEnter/MSPointerLeave were not implemented\r
85 * in IE10. For these 2 events we have to resort to a different method - capturing\r
86 * the timestamp of the last pointer event that has pointerType == 'touch', and if the\r
87 * mouse event occurred within a certain threshold we can reasonably assume it occurred\r
88 * because of a touch on the screen (see isEventBlocked)\r
89 * @private\r
90 */\r
91 blockedCompatibilityMouseEvents: {\r
92 mouseenter: 1,\r
93 mouseleave: 1\r
94 },\r
95\r
96 constructor: function() {\r
97 var me = this;\r
98\r
99 me.bubbleSubscribers = {};\r
100 me.captureSubscribers = {};\r
101 me.directSubscribers = {};\r
102 me.directCaptureSubscribers = {};\r
103\r
104 // this map tracks all the names of the events that currently have a delegated\r
105 // event listener attached so that they can be removed from the dom when the\r
106 // publisher is destroyed\r
107 me.delegatedListeners = {};\r
108\r
109 me.initHandlers();\r
110\r
111 Ext.onInternalReady(me.onReady, me);\r
112\r
113 me.callParent();\r
114 },\r
115\r
116 registerEvents: function() {\r
117 var me = this,\r
118 publishersByEvent = Ext.event.publisher.Publisher.publishersByEvent,\r
119 domEvents = me.handledDomEvents,\r
120 ln = domEvents.length,\r
121 i = 0,\r
122 eventName;\r
123\r
124 for (; i < ln; i++) {\r
125 eventName = domEvents[i];\r
126 me.handles[eventName] = 1;\r
127 publishersByEvent[eventName] = me;\r
128 }\r
129\r
130 this.callParent();\r
131 },\r
132\r
133 onReady: function() {\r
134 var me = this,\r
135 domEvents = me.handledDomEvents,\r
136 ln, i;\r
137\r
138 if (domEvents) {\r
139 // If the publisher has handledDomEvents we attach delegated listeners up front\r
140 // for those events. Dom publisher does not have a list of event names, but\r
141 // attaches listeners dynamically as subscribers are subscribed. This allows it\r
142 // to handle all DOM events that are not explicitly handled by another publisher.\r
143 // Subclasses such as Gesture must explicitly list their handledDomEvents.\r
144 for (i = 0, ln = domEvents.length; i < ln; i++) {\r
145 me.addDelegatedListener(domEvents[i]);\r
146 }\r
147 }\r
148\r
149 Ext.getWin().on('unload', me.destroy, me);\r
150 },\r
151\r
152 initHandlers: function() {\r
153 var me = this;\r
154\r
155 me.onDelegatedEvent = Ext.bind(me.onDelegatedEvent, me);\r
156 me.onDirectEvent = Ext.bind(me.onDirectEvent, me);\r
157 me.onDirectCaptureEvent = Ext.bind(me.onDirectCaptureEvent, me);\r
158 },\r
159\r
160 addDelegatedListener: function(eventName) {\r
161 this.delegatedListeners[eventName] = 1;\r
162 this.target.addEventListener(\r
163 eventName, this.onDelegatedEvent, !!this.captureEvents[eventName]\r
164 );\r
165 },\r
166\r
167 removeDelegatedListener: function(eventName) {\r
168 delete this.delegatedListeners[eventName];\r
169 this.target.removeEventListener(\r
170 eventName, this.onDelegatedEvent, !!this.captureEvents[eventName]\r
171 );\r
172 },\r
173\r
174 addDirectListener: function(eventName, element, capture) {\r
175 element.dom.addEventListener(\r
176 eventName,\r
177 capture ? this.onDirectCaptureEvent : this.onDirectEvent,\r
178 capture\r
179 );\r
180 },\r
181\r
182 removeDirectListener: function(eventName, element, capture) {\r
183 element.dom.removeEventListener(\r
184 eventName,\r
185 capture ? this.onDirectCaptureEvent : this.onDirectEvent,\r
186 capture\r
187 );\r
188 },\r
189\r
190 subscribe: function(element, eventName, delegated, capture) {\r
191 var me = this,\r
192 subscribers, id;\r
193\r
194 if (delegated && !me.directEvents[eventName]) {\r
195 // delegated listeners\r
196 subscribers = capture ? me.captureSubscribers : me.bubbleSubscribers;\r
197 if (!me.handles[eventName] && !me.delegatedListeners[eventName]) {\r
198 // First time we've attached a listener for this eventName - need to begin\r
199 // listening at the dom level\r
200 me.addDelegatedListener(eventName);\r
201 }\r
202\r
203 if (subscribers[eventName]) {\r
204 ++subscribers[eventName];\r
205 } else {\r
206 subscribers[eventName] = 1;\r
207 }\r
208 } else {\r
209 subscribers = capture ? me.directCaptureSubscribers : me.directSubscribers;\r
210\r
211 id = element.id;\r
212 // Direct subscribers are tracked by eventName first and by element id second.\r
213 // This allows the element id key to be deleted when there are no more subscribers\r
214 // so that this map does not grow indefinitely (it can only grow to a finite\r
215 // set of event names) - see unsubscribe\r
216 subscribers = subscribers[eventName] || (subscribers[eventName] = {});\r
217 if (subscribers[id]) {\r
218 ++subscribers[id];\r
219 } else {\r
220 subscribers[id] = 1;\r
221 me.addDirectListener(eventName, element, capture);\r
222 }\r
223 }\r
224 },\r
225\r
226 unsubscribe: function(element, eventName, delegated, capture) {\r
227 var me = this,\r
228 captureSubscribers, bubbleSubscribers, subscribers, id;\r
229\r
230 if (delegated && !me.directEvents[eventName]) {\r
231 captureSubscribers = me.captureSubscribers;\r
232 bubbleSubscribers = me.bubbleSubscribers;\r
233 subscribers = capture ? captureSubscribers : bubbleSubscribers;\r
234\r
235 if (subscribers[eventName]) {\r
236 --subscribers[eventName];\r
237 }\r
238\r
239 if (!me.handles[eventName] && !bubbleSubscribers[eventName] && !captureSubscribers[eventName]) {\r
240 // decremented subscribers back to 0 - and the event is not in "handledEvents"\r
241 // no longer need to listen at the dom level\r
242 this.removeDelegatedListener(eventName);\r
243 }\r
244 } else {\r
245 subscribers = capture ? me.directCaptureSubscribers : me.directSubscribers;\r
246\r
247 id = element.id;\r
248 subscribers = subscribers[eventName];\r
249 if (subscribers[id]) {\r
250 --subscribers[id];\r
251 }\r
252\r
253 if (!subscribers[id]) {\r
254 // no more direct subscribers for this element/id/capture, so we can safely\r
255 // remove the dom listener\r
256 delete subscribers[id];\r
257 me.removeDirectListener(eventName, element, capture);\r
258 }\r
259 }\r
260 },\r
261\r
262 getPropagatingTargets: function(target) {\r
263 var currentNode = target,\r
264 targets = [],\r
265 parentNode;\r
266\r
267 while (currentNode) {\r
268 targets.push(currentNode);\r
269 parentNode = currentNode.parentNode;\r
270\r
271 if (!parentNode) {\r
272 // If the node has no parentNode it means one of two things - either it is\r
273 // not in the dom, or we have looped all the way up to the document object.\r
274 // If the latter is the case we need to add the window object to the targets\r
275 // to ensure that our propagation mimics browser propagation where events\r
276 // can bubble from the document to the window.\r
277 parentNode = currentNode.defaultView;\r
278 }\r
279\r
280 currentNode = parentNode;\r
281 }\r
282\r
283 return targets;\r
284 },\r
285\r
286 publish: function(eventName, target, e) {\r
287 var me = this,\r
288 targets, el, i, ln;\r
289\r
290 if (Ext.isArray(target)) {\r
291 // Gesture publisher passes an already created array of propagating targets\r
292 targets = target;\r
293 } else if (me.captureEvents[eventName]) {\r
294 el = Ext.cache[target.id];\r
295 targets = el ? [el] : [];\r
296 } else {\r
297 targets = me.getPropagatingTargets(target);\r
298 }\r
299\r
300 ln = targets.length;\r
301\r
302 // We will now proceed to fire events in both capture and bubble phases. You\r
303 // may notice that we are looping all potential targets both times, and only\r
304 // firing on the target if there is an Ext.Element wrapper in the cache. This is\r
305 // done (vs. eliminating non-cached targets from the array up front) because\r
306 // event handlers can add listeners to other elements during propagation. Looping\r
307 // all the potential targets ensures that these dynamically added listeners\r
308 // are fired. See https://sencha.jira.com/browse/EXTJS-15953\r
309\r
310 // capture phase (top-down event propagation).\r
311 if (me.captureSubscribers[eventName]) {\r
312 for (i = ln; i--;) {\r
313 el = Ext.cache[targets[i].id];\r
314 if (el) {\r
315 me.fire(el, eventName, e, false, true);\r
316 if (e.isStopped) {\r
317 break;\r
318 }\r
319 }\r
320 }\r
321 }\r
322\r
323 // bubble phase (bottom-up event propagation).\r
324 // stopPropagation during capture phase cancels entire bubble phase\r
325 if (!e.isStopped && me.bubbleSubscribers[eventName]) {\r
326 for (i = 0; i < ln; i++) {\r
327 el = Ext.cache[targets[i].id];\r
328 if (el) {\r
329 me.fire(el, eventName, e, false, false);\r
330 if (e.isStopped) {\r
331 break;\r
332 }\r
333 }\r
334 }\r
335 }\r
336 },\r
337\r
338 fire: function(element, eventName, e, direct, capture) {\r
339 var event;\r
340\r
341 if (element.hasListeners[eventName]) {\r
342 event = element.events[eventName];\r
343\r
344 if (event) {\r
345 if (capture && direct) {\r
346 event = event.directCaptures;\r
347 } else if (capture) {\r
348 event = event.captures;\r
349 } else if (direct) {\r
350 event = event.directs;\r
351 }\r
352\r
353 // yes, this second null check for event is necessary - one of the\r
354 // above assignments might have resulted in undefined\r
355 if (event) {\r
356 e.setCurrentTarget(element.dom);\r
357 event.fire(e, e.target);\r
358 }\r
359 }\r
360 }\r
361 },\r
362\r
363 onDelegatedEvent: function(e) {\r
364 if (Ext.elevateFunction) {\r
365 // using [e] is faster than using arguments in most browsers\r
366 // http://jsperf.com/passing-arguments\r
367 Ext.elevateFunction(this.doDelegatedEvent, this, [e]);\r
368 } else {\r
369 this.doDelegatedEvent(e);\r
370 }\r
371 },\r
372\r
373 doDelegatedEvent: function(e, invokeAfter) {\r
374 var me = this,\r
375 timeStamp = e.timeStamp;\r
376\r
377 e = new Ext.event.Event(e);\r
378\r
379 if (me.isEventBlocked(e)) {\r
380 return false;\r
381 }\r
382\r
383 me.beforeEvent(e);\r
384\r
385 Ext.frameStartTime = timeStamp;\r
386\r
387 me.reEnterCount++;\r
388 me.publish(e.type, e.target, e);\r
389 me.reEnterCount--;\r
390\r
391 if (invokeAfter !== false) {\r
392 me.afterEvent(e);\r
393 }\r
394\r
395 return e;\r
396 },\r
397\r
398 /**\r
399 * Handler for directly-attached (non-delegated) dom events\r
400 * @param {Event} e\r
401 * @private\r
402 */\r
403 onDirectEvent: function(e) {\r
404 if (Ext.elevateFunction) {\r
405 // using [e] is faster than using arguments in most browsers\r
406 // http://jsperf.com/passing-arguments\r
407 Ext.elevateFunction(this.doDirectEvent, this, [e, false]);\r
408 } else {\r
409 this.doDirectEvent(e, false);\r
410 }\r
411 },\r
412\r
413 // When eventPhase is AT_TARGET there's no way to know if we are handling a capture\r
414 // or bubble listener, hence the need for this separate handler fn\r
415 onDirectCaptureEvent: function(e) {\r
416 if (Ext.elevateFunction) {\r
417 // using [e] is faster than using arguments in most browsers\r
418 // http://jsperf.com/passing-arguments\r
419 Ext.elevateFunction(this.doDirectEvent, this, [e, true]);\r
420 } else {\r
421 this.doDirectEvent(e, true);\r
422 }\r
423 },\r
424\r
425 doDirectEvent: function(e, capture) {\r
426 var me = this,\r
427 currentTarget = e.currentTarget,\r
428 timeStamp = e.timeStamp,\r
429 el;\r
430\r
431 e = new Ext.event.Event(e);\r
432\r
433 if (me.isEventBlocked(e)) {\r
434 return;\r
435 }\r
436\r
437 me.beforeEvent(e);\r
438\r
439 Ext.frameStartTime = timeStamp;\r
440 \r
441 el = Ext.cache[currentTarget.id];\r
442 \r
443 // Element can be removed from the cache by this time, with the node\r
444 // still lingering for some reason. This can happen for example when\r
445 // load event is fired on an iframe that we constructed when submitting\r
446 // a form for file uploads.\r
447 if (el) {\r
448 // Since natural DOM propagation has occurred, no emulated propagation is needed.\r
449 // Simply dispatch the event on the currentTarget element\r
450 me.reEnterCount++;\r
451 me.fire(el, e.type, e, true, capture);\r
452 me.reEnterCount--;\r
453 }\r
454\r
455 me.afterEvent(e);\r
456 },\r
457\r
458 beforeEvent: function(e) {\r
459 var browserEvent = e.browserEvent,\r
460 // use full class name, not me.self, so that Dom and Gesture publishers will\r
461 // both place flags on the same object.\r
462 self = Ext.event.publisher.Dom,\r
463 touches, touch;\r
464\r
465 if (browserEvent.type === 'touchstart') {\r
466 touches = browserEvent.touches;\r
467\r
468 if (touches.length === 1) {\r
469 // capture the coordinates of the first touchstart event so we can use\r
470 // them to eliminate duplicate mouse events if needed, (see isEventBlocked).\r
471 touch = touches[0];\r
472 self.lastTouchStartX = touch.pageX;\r
473 self.lastTouchStartY = touch.pageY;\r
474 }\r
475 }\r
476 },\r
477\r
478 afterEvent: function(e) {\r
479 var browserEvent = e.browserEvent,\r
480 type = browserEvent.type,\r
481 // use full class name, not me.self, so that Dom and Gesture publishers will\r
482 // both place flags on the same object.\r
483 self = Ext.event.publisher.Dom,\r
484 GlobalEvents = Ext.GlobalEvents;\r
485\r
486 // It is important that the following time stamps are captured after the handlers\r
487 // have been invoked because they need to represent the "exit" time, so that they\r
488 // can be compared against the next "entry" time into onDelegatedEvent or\r
489 // onDirectEvent to detect the time lapse in between the firing of the 2 events.\r
490 // We set these flags on "this.self" so that they can be shared between Dom\r
491 // publisher and subclasses\r
492\r
493 if (e.self.pointerEvents[type] && e.pointerType !== 'mouse') {\r
494 // track the last time a pointer event was fired as a result of interaction\r
495 // with the screen, pointerType === 'touch' most likely but could also be\r
496 // pointerType === 'pen' hence the reason we use !== 'mouse', This is used\r
497 // to eliminate potential duplicate "compatibility" mouse events\r
498 // (see isEventBlocked)\r
499 self.lastScreenPointerEventTime = Ext.now();\r
500 }\r
501\r
502 if (type === 'touchend') {\r
503 // Capture a time stamp so we can use it to eliminate potential duplicate\r
504 // emulated mouse events on multi-input devices that have touch events,\r
505 // e.g. Chrome on Window8 with touch-screen (see isEventBlocked).\r
506 self.lastTouchEndTime = Ext.now();\r
507 }\r
508\r
509 if (!this.reEnterCount && GlobalEvents.hasListeners.idle && !GlobalEvents.idleEventMask[type]) {\r
510 GlobalEvents.fireEvent('idle');\r
511 }\r
512 },\r
513\r
514 /**\r
515 * Detects if the given event should be blocked from firing because it is an emulated\r
516 * "compatibility" mouse event triggered by a touch on the screen.\r
517 * @param {Ext.event.Event} e\r
518 * @return {Boolean}\r
519 * @private\r
520 */\r
521 isEventBlocked: function(e) {\r
522 var me = this,\r
523 type = e.type,\r
524 // use full class name, not me.self, so that Dom and Gesture publishers will\r
525 // both look for flags on the same object.\r
526 self = Ext.event.publisher.Dom,\r
527 now = Ext.now();\r
528\r
529 // Gecko has a bug where right clicking will trigger both a contextmenu\r
530 // and click event. This only occurs when delegating the event onto the window\r
531 // object like we do by default for delegated events.\r
532 // This is not possible to feature detect using synthetic events.\r
533 // Ticket logged: https://bugzilla.mozilla.org/show_bug.cgi?id=1156023\r
534 if (Ext.isGecko && e.type === 'click' && e.button === 2) {\r
535 return true;\r
536 }\r
537\r
538 // prevent emulated pointerover, pointerout, pointerenter, and pointerleave\r
539 // events from firing when triggered by touching the screen.\r
540 return (me.blockedPointerEvents[type] && e.pointerType !== 'mouse') ||\r
541 // prevent compatibility mouse events from firing on devices with pointer\r
542 // events - see comment on blockedCompatibilityMouseEvents for more details\r
543 // The time from when the last pointer event fired until when compatibility\r
544 // events are received varies depending on the browser, device, and application\r
545 // so we use 1 second to be safe\r
546 (me.blockedCompatibilityMouseEvents[type] &&\r
547 (now - self.lastScreenPointerEventTime < 1000)) ||\r
548\r
549 (Ext.supports.TouchEvents && e.self.mouseEvents[e.type] &&\r
550 // some browsers (e.g. webkit on Windows 8 with touch screen) emulate mouse\r
551 // events after touch events have fired. This only seems to happen when there\r
552 // is no movement present, so, for example, a touchstart followed immediately\r
553 // by a touchend would result in the following sequence of events:\r
554 // "touchstart, touchend, mousemove, mousedown, mouseup"\r
555 // yes, you read that right, the emulated mousemove fires before mousedown.\r
556 // However, touch events with movement (touchstart, touchmove, then touchend)\r
557 // do not trigger the emulated mouse events.\r
558 // The side effect of this behavior is that single-touch gestures that expect\r
559 // no movement (e.g. tap) can double-fire - once when the touchstart/touchend\r
560 // occurs, and then again when the emulated mousedown/up occurs.\r
561 // We cannot solve the problem by only listening for touch events and ignoring\r
562 // mouse events, since we may be on a multi-input device that supports both\r
563 // touch and mouse events and we want gestures to respond to both kinds of\r
564 // events. Instead we have to detect if the mouse event is a "dupe" by\r
565 // checking if its coordinates are near the last touchstart's coordinates,\r
566 // and if it's timestamp is within a certain threshold of the last touchend\r
567 // event's timestamp. This is because when dealing with multi-touch events,\r
568 // the emulated mousedown event (when it does fire) will fire with approximately\r
569 // the same coordinates as the first touchstart, but within a short time after\r
570 // the last touchend. We use 15px as the distance threshold, to be on the safe\r
571 // side because the difference in coordinates can sometimes be up to 6px.\r
572 Math.abs(e.pageX - self.lastTouchStartX) < 15 &&\r
573 Math.abs(e.pageY - self.lastTouchStartY) < 15 &&\r
574\r
575 // in the majority of cases, the emulated mousedown is observed within\r
576 // 5ms of touchend, however, to be certain we avoid a situation where a\r
577 // gesture handler gets executed twice we use a threshold of 1000ms. The\r
578 // side effect of this is that if a user touches the screen and then quickly\r
579 // clicks screen in the same spot, the mousedown/mouseup sequence that\r
580 // ensues will not trigger any gesture recognizers.\r
581 (Ext.now() - self.lastTouchEndTime) < 1000);\r
582 },\r
583\r
584 destroy: function() {\r
585 var eventName;\r
586\r
587 for (eventName in this.delegatedListeners) {\r
588 this.removeDelegatedListener(eventName);\r
589 }\r
590 this.callParent();\r
591 },\r
592\r
593 /**\r
594 * Resets the internal state of the Dom publisher. Internally the Dom publisher\r
595 * keeps track of timing and coordinates of events for eliminating browser duplicates\r
596 * (e.g. emulated mousedown after pointerdown etc.). This method resets all this\r
597 * cached data to a state similar to when the publisher was first instantiated.\r
598 *\r
599 * Applications will not typically need to use this method, but it is useful for\r
600 * Unit-testing situations where a clean slate is required for each test.\r
601 */\r
602 reset: function() {\r
603 // use full class name, not me.self, so that Dom and Gesture publishers will\r
604 // both reset flags on the same object.\r
605 var self = Ext.event.publisher.Dom;\r
606\r
607 // set to undefined, not null, because that is the initial state of these vars and\r
608 // undefined/null return different results when used in math operations\r
609 // (see isEventBlocked)\r
610 self.lastScreenPointerEventTime = self.lastTouchEndTime = self.lastTouchStartX =\r
611 self.lastTouchStartY = undefined;\r
612 }\r
613}, function(Dom) {\r
614 var doc = document,\r
615 defaultView = doc.defaultView,\r
616 prototype = Dom.prototype;\r
617\r
618 if ((Ext.os.is.iOS && Ext.os.version.getMajor() < 5) || Ext.browser.is.AndroidStock ||\r
619 !(defaultView && defaultView.addEventListener)) {\r
620 // Delegated listeners will get attached to the document object because\r
621 // attaching to the window object will not work. In IE8 this is needed because\r
622 // events do not bubble up to the window - bubbling stops at the document\r
623 // object. The iOS < 5 check was carried forward from Sencha Touch 2.3 -\r
624 // Not sure why it was needed. The check for (defaultView && defaultView.addEventListener)\r
625 // was carried forward as well - it may be required for older mobile browsers.\r
626 // see also TOUCH-5408\r
627 prototype.target = doc;\r
628 } else {\r
629 /**\r
630 * @property {Object} target the DOM target to which listeners are attached for\r
631 * delegated events.\r
632 * @private\r
633 */\r
634 prototype.target = defaultView;\r
635 }\r
636\r
637 Dom.instance = new Dom();\r
638});\r