1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 sc_require('system/ready');
  9 sc_require('system/platform');
 10 sc_require('system/touch');
 11 
 12 /** Set to NO to leave the backspace key under the control of the browser.*/
 13 SC.CAPTURE_BACKSPACE_KEY = NO ;
 14 
 15 /** @class
 16 
 17   The RootResponder captures events coming from a web browser and routes them
 18   to the correct view in the view hierarchy.  Usually you do not work with a
 19   RootResponder directly.  Instead you will work with Pane objects, which
 20   register themselves with the RootResponder as needed to receive events.
 21 
 22   RootResponder and Platforms
 23   ---
 24 
 25   RootResponder contains core functionality common among the different web
 26   platforms. You will likely be working with a subclass of RootResponder that
 27   implements functionality unique to that platform.
 28 
 29   The correct instance of RootResponder is detected at runtime and loaded
 30   transparently.
 31 
 32   Event Types
 33   ---
 34 
 35   RootResponders can route four types of events:
 36 
 37    - Direct events, such as mouse and touch events.  These are routed to the
 38      nearest view managing the target DOM elment. RootResponder also handles
 39      multitouch events so that they are delegated to the correct views.
 40    - Keyboard events. These are sent to the keyPane, which will then send the
 41      event to the current firstResponder and up the responder chain.
 42    - Resize events. When the viewport resizes, these events will be sent to all
 43      panes.
 44    - Keyboard shortcuts. Shortcuts are sent to the keyPane first, which
 45      will go down its view hierarchy. Then they go to the mainPane, which will
 46      go down its view hierarchy.
 47    - Actions. Actions are generic messages that your application can send in
 48      response to user action or other events. You can either specify an
 49      explicit target, or allow the action to traverse the hierarchy until a
 50      view is found that handles it.
 51 */
 52 SC.RootResponder = SC.Object.extend(
 53   /** @scope SC.RootResponder.prototype */{
 54 
 55   /**
 56     Contains a list of all panes currently visible on screen.  Every time a
 57     pane attaches or detaches, it will update itself in this array.
 58   */
 59   panes: null,
 60 
 61   init: function() {
 62     sc_super();
 63     this.panes = SC.Set.create();
 64   },
 65 
 66   // .......................................................
 67   // MAIN PANE
 68   //
 69 
 70   /**
 71     The main pane.  This pane receives shortcuts and actions if the
 72     focusedPane does not respond to them.  There can be only one main pane.
 73     You can swap main panes by calling makeMainPane() here.
 74 
 75     Usually you will not need to edit the main pane directly.  Instead, you
 76     should use a MainPane subclass, which will automatically make itself main
 77     when you append it to the document.
 78 
 79     @type SC.MainPane
 80   */
 81   mainPane: null,
 82 
 83   /**
 84     Swaps the main pane.  If the current main pane is also the key pane, then
 85     the new main pane will also be made key view automatically.  In addition
 86     to simply updating the mainPane property, this method will also notify the
 87     panes themselves that they will lose/gain their mainView status.
 88 
 89     Note that this method does not actually change the Pane's place in the
 90     document body.  That will be handled by the Pane itself.
 91 
 92     @param {SC.Pane} pane
 93     @returns {SC.RootResponder}
 94   */
 95   makeMainPane: function(pane) {
 96     var currentMain = this.get('mainPane') ;
 97     if (currentMain === pane) return this ; // nothing to do
 98 
 99     this.beginPropertyChanges() ;
100 
101     // change key focus if needed.
102     if (this.get('keyPane') === currentMain) this.makeKeyPane(pane) ;
103 
104     // change setting
105     this.set('mainPane', pane) ;
106 
107     // notify panes.  This will allow them to remove themselves.
108     if (currentMain) currentMain.blurMainTo(pane) ;
109     if (pane) pane.focusMainFrom(currentMain) ;
110 
111     this.endPropertyChanges() ;
112     return this ;
113   },
114 
115   // ..........................................................
116   // MENU PANE
117   //
118 
119   /**
120     The current menu pane. This pane receives keyboard events before all other
121     panes, but tends to be transient, as it is only set when a pane is open.
122 
123     @type SC.MenuPane
124   */
125   menuPane: null,
126 
127   /**
128     Sets a pane as the menu pane. All key events will be directed to this
129     pane, but the current key pane will not lose focus.
130 
131     Usually you would not call this method directly, but allow instances of
132     SC.MenuPane to manage the menu pane for you. If your pane does need to
133     become menu pane, you should relinquish control by calling this method
134     with a null parameter. Otherwise, key events will always be delivered to
135     that pane.
136 
137     @param {SC.MenuPane} pane
138     @returns {SC.RootResponder} receiver
139   */
140   makeMenuPane: function(pane) {
141     // Does the specified pane accept being the menu pane?  If not, there's
142     // nothing to do.
143     if (pane  &&  !pane.get('acceptsMenuPane')) {
144       return this;
145     } else {
146       var currentMenu = this.get('menuPane');
147       if (currentMenu === pane) return this; // nothing to do
148 
149       this.set('menuPane', pane);
150     }
151 
152     return this;
153   },
154 
155   // .......................................................
156   // KEY PANE
157   //
158 
159   /**
160     The current key pane. This pane receives keyboard events, shortcuts, and
161     actions first, unless a menu is open. This pane is usually the highest
162     ordered pane or the mainPane.
163 
164     @type SC.Pane
165   */
166   keyPane: null,
167 
168   /** @private
169     A stack of previous key panes. Used to allow panes to resign key pane
170     status without having to know who had it before them.
171 
172     NOTE: This property is not observable.
173   */
174   previousKeyPanes: [],
175 
176   /**
177     Makes the passed pane the new key pane.  If you pass null or if the pane
178     does not accept key focus, then key focus will transfer to the previous
179     key pane (if it is still attached), and so on down the stack.  This will
180     notify both the old pane and the new root View that key focus has changed.
181 
182     @param {SC.Pane} pane
183     @returns {SC.RootResponder} receiver
184   */
185   makeKeyPane: function(pane) {
186     // Quick note about previousKeyPanes: if a pane is destroyed while in the
187     // previous panes stack, it will retain a reference to it here, causing a
188     // brief leak. The reference will be removed as soon as the panes above it
189     // in the stack resign, so it's rarely an issue, and fixing it would require
190     // a dedicated method and some extra coordination that's probably not worth
191     // it.
192 
193     // Was a pane specified?
194     var newKeyPane, previousKeyPane, previousKeyPanes ;
195 
196     if (pane) {
197       // Does the specified pane accept being the key pane?  If not, there's
198       // nothing to do.
199       if (!pane.get('acceptsKeyPane')) {
200         return this ;
201       }
202       else {
203         // It does accept key pane status?  Then push the current keyPane to
204         // the top of the stack and make the specified pane the new keyPane.
205         // First, though, do a sanity-check to make sure it's not already the
206         // key pane, in which case we have nothing to do.
207         previousKeyPane = this.get('keyPane') ;
208         if (previousKeyPane === pane) {
209           return this ;
210         }
211         else {
212           if (previousKeyPane) {
213             previousKeyPanes = this.get('previousKeyPanes') ;
214             previousKeyPanes.push(previousKeyPane) ;
215           }
216 
217           newKeyPane = pane ;
218         }
219       }
220     } else {
221       // No pane was specified?  Then pop the previous key pane off the top of
222       // the stack and make it the new key pane, assuming that it's still
223       // attached and accepts key pane (its value for acceptsKeyPane might
224       // have changed in the meantime).  Otherwise, we'll keep going up the
225       // stack.
226       previousKeyPane = this.get('keyPane') ;
227       previousKeyPanes = this.get('previousKeyPanes') ;
228 
229       newKeyPane = null ;
230       var candidate;
231       while (previousKeyPanes.length > 0) {
232         candidate = previousKeyPanes.pop();
233         if (candidate.get('isVisibleInWindow') && candidate.get('acceptsKeyPane')) {
234           newKeyPane = candidate ;
235           break ;
236         }
237       }
238     }
239 
240 
241     // If we found an appropriate candidate, make it the new key pane.
242     // Otherwise, make the main pane the key pane (if it accepts it).
243     if (!newKeyPane) {
244       var mainPane = this.get('mainPane') ;
245       if (mainPane && mainPane.get('acceptsKeyPane')) newKeyPane = mainPane ;
246     }
247 
248     // now notify old and new key views of change after edit
249     if (previousKeyPane) previousKeyPane.willLoseKeyPaneTo(newKeyPane) ;
250     if (newKeyPane) newKeyPane.willBecomeKeyPaneFrom(previousKeyPane) ;
251 
252     this.set('keyPane', newKeyPane) ;
253 
254     if (newKeyPane) newKeyPane.didBecomeKeyPaneFrom(previousKeyPane) ;
255     if (previousKeyPane) previousKeyPane.didLoseKeyPaneTo(newKeyPane) ;
256 
257     return this ;
258   },
259 
260   // ..........................................................
261   // VIEWPORT STATE
262   //
263 
264   /**
265     The last known window size.
266     @type Rect
267     @isReadOnly
268   */
269   currentWindowSize: null,
270 
271   /**
272     Computes the window size from the DOM.
273 
274     @returns Rect
275   */
276   computeWindowSize: function() {
277     var size, bod, docElement;
278     if(!this._bod || !this._docElement){
279       bod = document.body;
280       docElement = document.documentElement;
281       this._bod=bod;
282       this._docElement=docElement;
283     }else{
284       bod = this._bod;
285       docElement = this._docElement;
286     }
287 
288     if (window.innerHeight) {
289       size = {
290         width: window.innerWidth,
291         height: window.innerHeight
292       } ;
293     } else if (docElement && docElement.clientHeight) {
294       size = {
295         width: docElement.clientWidth,
296         height: docElement.clientHeight
297       };
298     } else if (bod) {
299       size = {
300         width: bod.clientWidth,
301         height: bod.clientHeight
302       } ;
303     }
304     return size;
305   },
306 
307   /**
308     On window resize, notifies panes of the change.
309 
310     @returns {Boolean}
311   */
312   resize: function() {
313     this._resize();
314     this._assignDesignMode();
315 
316     return YES; //always allow normal processing to continue.
317   },
318 
319   /** @private */
320   _resize: function() {
321     // calculate new window size...
322     var newSize = this.computeWindowSize(), oldSize = this.get('currentWindowSize');
323     this.set('currentWindowSize', newSize); // update size
324 
325     if (!SC.rectsEqual(newSize, oldSize)) {
326       SC.run(function() {
327         //Notify orientation change. This is faster than waiting for the orientation
328         //change event.
329         SC.device.windowSizeDidChange(newSize);
330 
331         // notify panes
332         if (this.panes) {
333             if (oldSize !== newSize) {
334               this.panes.invoke('windowSizeDidChange', oldSize, newSize);
335             }
336         }
337       }, this);
338     }
339   },
340 
341   /** @private */
342   _assignDesignMode: function () {
343     var newDesignMode = this.computeDesignMode(),
344       oldDesignMode = this.get('currentDesignMode');
345 
346     if (oldDesignMode !== newDesignMode) {
347       this.set('currentDesignMode', newDesignMode);
348 
349       if (this.panes) {
350         SC.run(function() {
351           this.panes.invoke('updateDesignMode', oldDesignMode, newDesignMode);
352         }, this);
353       }
354     }
355   },
356 
357   /**
358     Indicates whether or not the window currently has focus.  If you need
359     to do something based on whether or not the window is in focus, you can
360     setup a binding or observer to this property.  Note that the SproutCore
361     automatically adds an sc-focus or sc-blur CSS class to the body tag as
362     appropriate.  If you only care about changing the appearance of your
363     controls, you should use those classes in your CSS rules instead.
364   */
365   hasFocus: NO,
366 
367   /**
368     Handle window focus.  Change hasFocus and add sc-focus CSS class
369     (removing sc-blur).  Also notify panes.
370   */
371   focus: function(evt) {
372     if (!this.get('hasFocus')) {
373       SC.$('body').addClass('sc-focus').removeClass('sc-blur');
374 
375       SC.run(function () {
376       // If the app is getting focus again set the first responder to the first
377       // valid firstResponder view in the view's tree
378       if(!SC.TABBING_ONLY_INSIDE_DOCUMENT && !SC.browser.isIE8OrLower){
379         var keyPane = SC.RootResponder.responder.get('keyPane');
380         if (keyPane) {
381           var nextValidKeyView = keyPane.get('nextValidKeyView');
382           if (nextValidKeyView) keyPane.makeFirstResponder(nextValidKeyView);
383         }
384       }
385 
386         this.set('hasFocus', YES);
387       }, this);
388     }
389 
390     return YES ; // allow default
391   },
392 
393   /**
394     Handle window focus event for IE. Listening to the focus event is not
395     reliable as per every focus event you receive you immediately get a blur
396     event (Only on IE of course ;)
397   */
398   focusin: function(evt) {
399     if(this._focusTimeout) clearTimeout(this._focusTimeout);
400     this.focus(evt);
401   },
402 
403   /**
404     Handle window blur event for IE. Listening to the focus event is not
405     reliable as per every focus event you receive you immediately get a blur
406     event (Only on IE of course ;)
407   */
408   focusout: function(evt) {
409     var that = this;
410     this._focusTimeout = setTimeout(function(){that.blur(evt);}, 300);
411   },
412 
413 
414   /**
415     Handle window focus.  Change hasFocus and add sc-focus CSS class (removing
416     sc-blur).  Also notify panes.
417   */
418   blur: function(evt) {
419     if (this.get('hasFocus')) {
420       SC.$('body').addClass('sc-blur').removeClass('sc-focus');
421 
422       SC.run(function() {
423         this.set('hasFocus', NO);
424       }, this);
425     }
426     return YES ; // allow default
427   },
428 
429   dragDidStart: function(drag) {
430     this._mouseDownView = drag ;
431     this._drag = drag ;
432   },
433 
434   // ------------------------------------------------------------------------
435   // Design Modes
436   //
437 
438   /** @private */
439   currentDesignMode: null,
440 
441   /** @private Managed by SC.Application. */
442   designModes: function (key, value) {
443     if (SC.none(value)) {
444       // Clear previous values.
445       if (this._designModeNames) {
446         delete this._designModeNames;
447         delete this._designModeThresholds;
448       }
449 
450       value = null;
451     } else {
452       this._prepOrderedArrays(value);
453     }
454 
455     this._assignDesignMode();
456 
457     return value;
458   }.property().cacheable(),
459 
460   /** @private Determine the design mode based on area and pixel density. */
461   computeDesignMode: function () {
462     var designMode = null,
463       designModeNames = this._designModeNames,
464       designModeThresholds = this._designModeThresholds,
465       currentWindowSize,
466       area;
467 
468     // Fast path!
469     if (!designModeNames) { return null; }
470 
471     currentWindowSize = this.get('currentWindowSize');
472     area = (currentWindowSize.width * currentWindowSize.height);
473     var i, len;
474     for (i = 0, len = designModeThresholds.get('length'); i < len; i++) {
475       var layoutWidthThreshold = designModeThresholds.objectAt(i);
476       if (area < layoutWidthThreshold) {
477         designMode = designModeNames.objectAt(i);
478         break;
479       }
480     }
481 
482     // If no smaller designMode was found, use the biggest designMode.
483     if (SC.none(designMode) && designModeNames && designModeNames.get('length') > 0) {
484       designMode = designModeNames.objectAt(i);
485     }
486 
487     return SC.device.orientation === SC.PORTRAIT_ORIENTATION ? designMode + '_p' : designMode + '_l';
488   },
489 
490   /** @private (semi-private)
491     Returns the fallback design mode for the given design mode.  This is
492     primarily used by SC.View for the case where an adjustment isn't found
493     for the current design mode and we want to apply the next best design
494     mode as a fallback.
495   */
496   fallbackDesignMode: function (designMode) {
497     var designModeNames = this._designModeNames,
498       index,
499       ret = null;
500 
501     index = designModeNames.indexOf(designMode);
502     if (index >= 0) {
503       ret = designModeNames[index - 1];
504     }
505 
506     return ret;
507   },
508 
509   /** @private Prepares ordered design modes & widths arrays when designModes changes. */
510   _prepOrderedArrays: function (designModes) {
511     var designModeNames,
512       designModeThresholds;
513 
514     // Order the design modes for easier access later.
515     if (designModes) {
516       designModeNames = this._designModeNames = [];
517       designModeThresholds = this._designModeThresholds = [];
518 
519       var key;
520 
521       outer:
522         for (key in designModes) {
523           var i, value;
524 
525           // Assume that the keys will be ordered smallest to largest so run backwards.
526           value = designModes[key];
527           inner:
528             for (i = designModeThresholds.length - 1; i >= 0; i--) {
529               if (designModeThresholds[i] < value) {
530                 // Exit early!
531                 break inner;
532               }
533             }
534 
535           i += 1;
536           designModeNames.splice(i, 0, key);
537           designModeThresholds.splice(i, 0, value);
538         }
539     }
540   },
541 
542   // .......................................................
543   // ACTIONS
544   //
545 
546   /**
547     Set this to a delegate object that can respond to actions as they are sent
548     down the responder chain.
549 
550     @type SC.Object
551   */
552   defaultResponder: null,
553 
554   /**
555     Route an action message to the appropriate responder.  This method will
556     walk the responder chain, attempting to find a responder that implements
557     the action name you pass to this method.  Set 'target' to null to search
558     the responder chain.
559 
560     **IMPORTANT**: This method's API and implementation will likely change
561     significantly after SproutCore 1.0 to match the version found in
562     SC.ResponderContext.
563 
564     You generally should not call or override this method in your own
565     applications.
566 
567     @param {String} action The action to perform - this is a method name.
568     @param {SC.Responder} target object to set method to (can be null)
569     @param {Object} sender The sender of the action
570     @param {SC.Pane} pane optional pane to start search with
571     @param {Object} context optional. only passed to ResponderContexts
572     @returns {Boolean} YES if action was performed, NO otherwise
573     @test in targetForAction
574   */
575   sendAction: function( action, target, sender, pane, context, firstResponder) {
576     target = this.targetForAction(action, target, sender, pane, firstResponder) ;
577 
578     if (target) {
579       // HACK: If the target is a ResponderContext, forward the action.
580       if (target.isResponderContext) {
581         return !!target.sendAction(action, sender, context, firstResponder);
582       } else {
583         return target.tryToPerform(action, sender, context);
584       }
585     }
586   },
587 
588   _responderFor: function(target, methodName, firstResponder) {
589     var defaultResponder = target ? target.get('defaultResponder') : null;
590 
591     if (target) {
592       target = firstResponder || target.get('firstResponder') || target;
593       do {
594         if (target.respondsTo(methodName)) return target ;
595       } while ((target = target.get('nextResponder'))) ;
596     }
597 
598     // HACK: Eventually we need to normalize the sendAction() method between
599     // this and the ResponderContext, but for the moment just look for a
600     // ResponderContext as the defaultResponder and return it if present.
601     if (typeof defaultResponder === SC.T_STRING) {
602       defaultResponder = SC.objectForPropertyPath(defaultResponder);
603     }
604 
605     if (!defaultResponder) return null;
606     else if (defaultResponder.isResponderContext) return defaultResponder;
607     else if (defaultResponder.respondsTo(methodName)) return defaultResponder;
608     else return null;
609   },
610 
611   /**
612     Attempts to determine the initial target for a given action/target/sender
613     tuple.  This is the method used by sendAction() to try to determine the
614     correct target starting point for an action before trickling up the
615     responder chain.
616 
617     You send actions for user interface events and for menu actions.
618 
619     This method returns an object if a starting target was found or null if no
620     object could be found that responds to the target action.
621 
622     Passing an explicit target or pane constrains the target lookup to just
623     them; the defaultResponder and other panes are *not* searched.
624 
625     @param {Object|String} target or null if no target is specified
626     @param {String} method name for target
627     @param {Object} sender optional sender
628     @param {SC.Pane} optional pane
629     @param {firstResponder} a first responder to use
630     @returns {Object} target object or null if none found
631   */
632   targetForAction: function(methodName, target, sender, pane, firstResponder) {
633 
634     // 1. no action, no target...
635     if (!methodName || (SC.typeOf(methodName) !== SC.T_STRING)) {
636       return null ;
637     }
638 
639     // 2. an explicit target was passed...
640     if (target) {
641       // Normalize String targets to Objects
642       if (SC.typeOf(target) === SC.T_STRING) {
643         target = SC.objectForPropertyPath(target) ||
644                  SC.objectForPropertyPath(target, sender);
645       }
646 
647       // Ensure that the target responds to the method.
648       if (target && !target.isResponderContext) {
649         if (target.respondsTo && !target.respondsTo(methodName)) {
650           target = null ;
651         } else if (SC.typeOf(target[methodName]) !== SC.T_FUNCTION) {
652           target = null ;
653         }
654       }
655 
656       return target ;
657     }
658 
659     // 3. an explicit pane was passed...
660     if (pane) {
661       target = this._responderFor(pane, methodName, firstResponder);
662       if (target) return target;
663     }
664 
665     // 4. no target or pane passed... try to find target in the active panes
666     // and the defaultResponder
667     var keyPane = this.get('keyPane'), mainPane = this.get('mainPane') ;
668 
669     // ...check key and main panes first
670     if (keyPane && (keyPane !== pane)) {
671       target = this._responderFor(keyPane, methodName) ;
672     }
673     if (!target && mainPane && (mainPane !== keyPane)) {
674       target = this._responderFor(mainPane, methodName) ;
675     }
676 
677     // ...still no target? check the defaultResponder...
678     if (!target && (target = this.get('defaultResponder'))) {
679       if (SC.typeOf(target) === SC.T_STRING) {
680         target = SC.objectForPropertyPath(target) ;
681         if (target) this.set('defaultResponder', target) ; // cache if found
682       }
683       if (target && !target.isResponderContext) {
684         if (target.respondsTo && !target.respondsTo(methodName)) {
685           target = null ;
686         } else if (SC.typeOf(target[methodName]) !== SC.T_FUNCTION) {
687           target = null ;
688         }
689       }
690     }
691 
692     return target ;
693   },
694 
695   /**
696     Finds the view that appears to be targeted by the passed event.  This only
697     works on events with a valid target property.
698 
699     @param {SC.Event} evt
700     @returns {SC.View} view instance or null
701   */
702   targetViewForEvent: function (evt) {
703     var ret = null;
704     if (evt.target) { ret = SC.viewFor(evt.target); }
705 
706     return ret;
707   },
708 
709   /**
710     Attempts to send an event down the responder chain.  This method will
711     invoke the sendEvent() method on either the keyPane or on the pane owning
712     the target view you pass in.  It will also automatically begin and end
713     a new run loop.
714 
715     If you want to trap additional events, you should use this method to
716     send the event down the responder chain.
717 
718     @param {String} action
719     @param {SC.Event} evt
720     @param {Object} target
721     @returns {Object} object that handled the event or null if not handled
722   */
723   sendEvent: function(action, evt, target) {
724     var pane, ret ;
725 
726     SC.run(function send_event() {
727       // get the target pane
728       if (target) pane = target.get('pane') ;
729       else pane = this.get('menuPane') || this.get('keyPane') || this.get('mainPane') ;
730 
731       // if we found a valid pane, send the event to it
732       ret = (pane) ? pane.sendEvent(action, evt, target) : null ;
733     }, this);
734 
735     return ret ;
736   },
737 
738   // .......................................................
739   // EVENT LISTENER SETUP
740   //
741 
742   /**
743     Default method to add an event listener for the named event.  If you simply
744     need to add listeners for a type of event, you can use this method as
745     shorthand.  Pass an array of event types to listen for and the element to
746     listen in.  A listener will only be added if a handler is actually installed
747     on the RootResponder (or receiver) of the same name.
748 
749     @param {Array} keyNames
750     @param {Element} target
751     @param {Object} receiver - optional if you don't want 'this'
752     @param {Boolean} useCapture
753     @returns {SC.RootResponder} receiver
754   */
755   listenFor: function(keyNames, target, receiver, useCapture) {
756     receiver = receiver ? receiver : this;
757     keyNames.forEach( function(keyName) {
758       var method = receiver[keyName] ;
759       if (method) SC.Event.add(target, keyName, receiver, method, null, useCapture) ;
760     },this) ;
761 
762     target = null ;
763 
764     return receiver ;
765   },
766 
767   /**
768     Called when the document is ready to begin handling events.  Setup event
769     listeners in this method that you are interested in observing for your
770     particular platform.  Be sure to call sc_super().
771 
772     @returns {void}
773   */
774   setup: function() {
775     // handle basic events
776     this.listenFor(['touchstart', 'touchmove', 'touchend', 'touchcancel', 'keydown', 'keyup', 'beforedeactivate', 'mousedown', 'mouseup', 'dragenter', 'dragover', 'dragleave', 'drop', 'click', 'dblclick', 'mousemove', 'contextmenu'], document)
777         .listenFor(['resize'], window);
778 
779     if(SC.browser.isIE8OrLower) this.listenFor(['focusin', 'focusout'], document);
780     else this.listenFor(['focus', 'blur'], window);
781 
782     // handle special case for keypress- you can't use normal listener to block
783     // the backspace key on Mozilla
784     if (this.keypress) {
785       if (SC.CAPTURE_BACKSPACE_KEY && SC.browser.isMozilla) {
786         var responder = this ;
787         document.onkeypress = function(e) {
788           e = SC.Event.normalizeEvent(e);
789           return responder.keypress.call(responder, e);
790         };
791 
792       // Otherwise, just add a normal event handler.
793       } else {
794         SC.Event.add(document, 'keypress', this, this.keypress);
795       }
796     }
797 
798     // Add an array of transition listeners for immediate use (these will be cleaned up when actual testing completes).
799     // Because the transition test happens asynchronously and because we don't want to
800     // delay the launch of the application in order to a transition test (the app won't
801     // load if the browser tab is not visible), we start off by listening to everything
802     // and when the test is completed, we remove the extras to avoid double callbacks.
803     if (SC.platform.supportsCSSTransitions) {
804       var domPrefix = SC.browser.domPrefix,
805         lowerDomPrefix = domPrefix.toLowerCase(),
806         variation1 = lowerDomPrefix + 'transitionend',
807         variation2 = lowerDomPrefix + 'TransitionEnd',
808         variation3 = domPrefix + 'TransitionEnd';
809 
810       // Ensure that the callback name used maps to our implemented function name.
811       this[variation1] = this[variation2] = this[variation3] = this.transitionend;
812 
813       // ex. transitionend, webkittransitionend, webkitTransitionEnd, WebkitTransitionEnd
814       this.listenFor(['transitionend', variation1, variation2, variation3], document);
815 
816       if (SC.platform.supportsCSSAnimations) {
817         variation1 = lowerDomPrefix + 'animationstart';
818         variation2 = lowerDomPrefix + 'AnimationStart';
819         variation3 = domPrefix + 'AnimationStart';
820 
821         // Ensure that the callback name used maps to our implemented function name.
822         this[variation1] = this[variation2] = this[variation3] = this.animationstart;
823 
824         // ex. animationstart, webkitanimationstart, webkitAnimationStart, WebkitAnimationStart
825         this.listenFor(['animationstart', variation1, variation2, variation3], document);
826 
827         variation1 = lowerDomPrefix + 'animationiteration';
828         variation2 = lowerDomPrefix + 'AnimationIteration';
829         variation3 = domPrefix + 'AnimationIteration';
830 
831         // Ensure that the callback name used maps to our implemented function name.
832         this[variation1] = this[variation2] = this[variation3] = this.animationiteration;
833 
834         // ex. animationiteration, webkitanimationiteration, webkitAnimationIteration, WebkitAnimationIteration
835         this.listenFor(['animationiteration', variation1, variation2, variation3], document);
836 
837         variation1 = lowerDomPrefix + 'animationend';
838         variation2 = lowerDomPrefix + 'AnimationEnd';
839         variation3 = domPrefix + 'AnimationEnd';
840 
841         // Ensure that the callback name used maps to our implemented function name.
842         this[variation1] = this[variation2] = this[variation3] = this.animationend;
843 
844         // ex. animationend, webkitanimationend, webkitAnimationEnd, WebkitAnimationEnd
845         this.listenFor(['animationend', variation1, variation2, variation3], document);
846       }
847     }
848 
849     // handle these two events specially in IE
850     ['drag', 'selectstart'].forEach(function(keyName) {
851       var method = this[keyName] ;
852       if (method) {
853         if (SC.browser.isIE) {
854           var responder = this ;
855 
856           document.body['on' + keyName] = function(e) {
857             return method.call(responder, SC.Event.normalizeEvent(event || window.event)); // this is IE :(
858           };
859 
860           // be sure to cleanup memory leaks
861            SC.Event.add(window, 'unload', this, function() {
862             document.body['on' + keyName] = null;
863           });
864 
865         } else {
866           SC.Event.add(document, keyName, this, method);
867         }
868       }
869     }, this);
870 
871     var mousewheel = 'mousewheel';
872 
873     // Firefox emits different mousewheel events than other browsers
874     if (SC.browser.isMozilla) {
875       // For Firefox < 3.5, subscribe to DOMMouseScroll events
876       if (SC.browser.compare(SC.browser.engineVersion, '1.9.1') < 0) {
877         mousewheel = 'DOMMouseScroll';
878 
879       // For Firefox 3.5 and greater, we can listen for MozMousePixelScroll,
880       // which supports pixel-precision scrolling devices, like MacBook
881       // trackpads.
882       } else {
883         mousewheel = 'MozMousePixelScroll';
884       }
885     }
886     SC.Event.add(document, mousewheel, this, this.mousewheel);
887 
888     // Do some initial set up.
889     this.set('currentWindowSize', this.computeWindowSize()) ;
890 
891     // TODO: Is this workaround still valid?
892     if (SC.browser.os === SC.OS.ios && SC.browser.name === SC.BROWSER.safari) {
893 
894       // If the browser is identifying itself as a touch-enabled browser, but
895       // touch events are not present, assume this is a desktop browser doing
896       // user agent spoofing and simulate touch events automatically.
897       if (SC.platform && !SC.platform.touch) {
898         SC.platform.simulateTouchEvents();
899       }
900 
901       // Monkey patch RunLoop if we're in MobileSafari
902       var f = SC.RunLoop.prototype.endRunLoop, patch;
903 
904       patch = function() {
905         // Call original endRunLoop implementation.
906         if (f) f.apply(this, arguments);
907 
908         // This is a workaround for a bug in MobileSafari.
909         // Specifically, if the target of a touchstart event is removed from the DOM,
910         // you will not receive future touchmove or touchend events. What we do is, at the
911         // end of every runloop, check to see if the target of any touches has been removed
912         // from the DOM. If so, we re-append it to the DOM and hide it. We then mark the target
913         // as having been moved, and it is de-allocated in the corresponding touchend event.
914         var touches = SC.RootResponder.responder._touches, touch, elem, target, found = NO;
915         if (touches) {
916           // Iterate through the touches we're currently tracking
917           for (touch in touches) {
918             if (touches[touch]._rescuedElement) continue; // only do once
919 
920             target = elem = touches[touch].target;
921 
922             // Travel up the hierarchy looking for the document body
923             while (elem && (elem = elem.parentNode) && !found) {
924               found = (elem === document.body);
925             }
926 
927             // If we aren't part of the body, move the element back
928             // but make sure we hide it from display.
929             if (!found && target) {
930 
931               // Actually clone this node and replace it in the original
932               // layer if needed
933               if (target.parentNode && target.cloneNode) {
934                 var clone = target.cloneNode(true);
935                 target.parentNode.replaceChild(clone, target);
936                 target.swapNode = clone; // save for restore later
937               }
938 
939               // Create a holding pen if needed for these views...
940               var pen = SC.touchHoldingPen;
941               if (!pen) {
942                 pen = SC.touchHoldingPen = document.createElement('div');
943                 pen.style.display = 'none';
944                 document.body.appendChild(pen);
945               }
946 
947               // move element back into document...
948               pen.appendChild(target);
949 
950               // ...and save the element to be garbage collected on touchEnd.
951               touches[touch]._rescuedElement = target;
952             }
953           }
954         }
955       };
956       SC.RunLoop.prototype.endRunLoop = patch;
957     }
958   },
959 
960   /**
961     Cleans up the additional transition event listeners.
962 
963     NOTE: requires that SC.RootResponser.responder.transitionendEventName
964     has been determined.
965 
966     @returns {void}
967   */
968   cleanUpTransitionListeners: function () {
969     var actualEventName = SC.platform.transitionendEventName,
970       domPrefix = SC.browser.domPrefix,
971       lowerDomPrefix = domPrefix.toLowerCase(),
972       variation1 = lowerDomPrefix + 'transitionend',
973       variation2 = lowerDomPrefix + 'TransitionEnd',
974       variation3 = domPrefix + 'TransitionEnd';
975 
976     // Once the actual event name is determined, simply remove all the extras.
977     // This should prevent any problems with browsers that fire multiple events.
978     ['transitionend', variation1, variation2, variation3].forEach(function (keyName) {
979       if (keyName !== actualEventName) {
980         SC.Event.remove(document, keyName, this, this[keyName]);
981         this[keyName] = null;
982     }
983     });
984   },
985 
986   /**
987     Cleans up the additional animation event listeners.
988 
989     NOTE: requires that SC.RootResponser.responder.animationstartEventName,
990     SC.RootResponser.responder.animationendEventName and
991     SC.RootResponser.responder.animationiterationEventName have been
992     determined.
993 
994     @returns {void}
995   */
996   cleanUpAnimationListeners: function () {
997     var domPrefix = SC.browser.domPrefix,
998       lowerDomPrefix = domPrefix.toLowerCase(),
999       actualEventName = SC.platform.animationendEventName,
1000       variation1 = lowerDomPrefix + 'animationend',
1001       variation2 = lowerDomPrefix + 'AnimationEnd',
1002       variation3 = domPrefix + 'AnimationEnd';
1003 
1004     // Once the actual event name is determined, simply remove all the extras.
1005     // This should prevent any problems with browsers that fire multiple events.
1006     ['animationend', variation1, variation2, variation3].forEach(function (keyName) {
1007       if (keyName !== actualEventName) {
1008         SC.Event.remove(document, keyName, this, this[keyName]);
1009         this[keyName] = null;
1010     }
1011     });
1012 
1013     actualEventName = SC.platform.animationiterationEventName;
1014     variation1 = lowerDomPrefix + 'animationiteration';
1015     variation2 = lowerDomPrefix + 'AnimationIteration';
1016     variation3 = domPrefix + 'AnimationIteration';
1017     ['animationiteration', variation1, variation2, variation3].forEach(function (keyName) {
1018       if (keyName !== actualEventName) {
1019         SC.Event.remove(document, keyName, this, this[keyName]);
1020         this[keyName] = null;
1021       }
1022     });
1023 
1024     actualEventName = SC.platform.animationstartEventName;
1025     variation1 = lowerDomPrefix + 'animationstart';
1026     variation2 = lowerDomPrefix + 'AnimationStart';
1027     variation3 = domPrefix + 'AnimationStart';
1028     ['animationstart', variation1, variation2, variation3].forEach(function (keyName) {
1029       if (keyName !== actualEventName) {
1030         SC.Event.remove(document, keyName, this, this[keyName]);
1031         this[keyName] = null;
1032       }
1033     });
1034   },
1035 
1036   // ...........................................................................
1037   // TOUCH SUPPORT
1038   //
1039 
1040   /**
1041     @private
1042     A map from views to internal touch entries.
1043 
1044     Note: the touch entries themselves also reference the views.
1045   */
1046   _touchedViews: {},
1047 
1048   /**
1049     @private
1050     A map from internal touch ids to the touch entries themselves.
1051 
1052     The touch entry ids currently come from the touch event's identifier.
1053   */
1054   _touches: {},
1055 
1056   /**
1057     Returns the touches that are registered to the specified view or responder; undefined if none.
1058 
1059     When views receive a touch event, they have the option to subscribe to it.
1060     They are then mapped to touch events and vice-versa. This returns touches mapped to the view.
1061 
1062     This method is also available on SC.Touch objects, and you will usually call it from there.
1063   */
1064   touchesForView: function(view) {
1065     if (this._touchedViews[SC.guidFor(view)]) {
1066       return this._touchedViews[SC.guidFor(view)].touches;
1067     }
1068   },
1069 
1070   /**
1071     Computes a hash with x, y, and d (distance) properties, containing the average position
1072     of all touches, and the average distance of all touches from that average. This is useful
1073     for implementing scaling.
1074 
1075     This method is also available on SC.Touch objects, and you will usually call it from there.
1076 
1077     @param {SC.View} view The view whose touches should be averaged.
1078     @param {SC.Touch} additionalTouch This method uses touchesForView; if you call it from
1079         touchStart, that touch will not yet be included in touchesForView. To accommodate this,
1080         you should pass the view to this method (or pass YES to SC.Touch#averagedTouchesForView's
1081         `addSelf` argument).
1082   */
1083   averagedTouchesForView: function(view, additionalTouch) {
1084     var t = this.touchesForView(view),
1085       len, averaged, additionalTouchIsDuplicate;
1086 
1087     // Each view gets its own cached average touches object for performance.
1088     averaged = view._scrr_averagedTouches || (view._scrr_averagedTouches = {});
1089 
1090     // FAST PATH: no touches to track.
1091     if ((!t || t.length === 0) && !additionalTouch) {
1092       averaged.x = 0;
1093       averaged.y = 0;
1094       averaged.d = 0;
1095       averaged.velocityX = 0;
1096       averaged.velocityY = 0;
1097 
1098     // Otherwise, average the touches.
1099     } else {
1100       // Cache the array object used by this method. (Cleared at the end to prevent memory leaks.)
1101       var touches = this._averagedTouches_touches || (this._averagedTouches_touches = []);
1102 
1103       // copy touches into array
1104       if (t) {
1105         var i;
1106         len = t.length;
1107         for(i = 0; i < len; i++) {
1108           touches.push(t[i]);
1109           if (additionalTouch && t[i] === additionalTouch) additionalTouchIsDuplicate = YES;
1110         }
1111       }
1112 
1113       // Add additionalTouch if present and not duplicated.
1114       if (additionalTouch && !additionalTouchIsDuplicate) touches.push(additionalTouch);
1115 
1116       // Calculate the average.
1117       SC.Touch.averagedTouch(touches, averaged);
1118 
1119       // Clear the touches array to prevent touch object leaks.
1120       touches.length = 0;
1121     }
1122 
1123     return averaged;
1124   },
1125 
1126   assignTouch: function(touch, view) {
1127     // sanity-check
1128     if (touch.hasEnded) throw new Error("Attempt to assign a touch that is already finished.");
1129 
1130     // Fast path, the touch is already assigned to the view.
1131     if (touch.view === view) return;
1132 
1133     // unassign from old view if necessary
1134     if (touch.view) {
1135       this.unassignTouch(touch);
1136     }
1137 
1138     // create view entry if needed
1139     if (!this._touchedViews[SC.guidFor(view)]) {
1140       this._touchedViews[SC.guidFor(view)] = {
1141         view: view,
1142         touches: SC.CoreSet.create([]),
1143         touchCount: 0
1144       };
1145       view.set("hasTouch", YES);
1146     }
1147 
1148     // add touch
1149     touch.view = view;
1150     this._touchedViews[SC.guidFor(view)].touches.add(touch);
1151     this._touchedViews[SC.guidFor(view)].touchCount++;
1152   },
1153 
1154   unassignTouch: function(touch) {
1155     // find view entry
1156     var view, viewEntry;
1157 
1158     // Fast path, the touch is not assigned to a view.
1159     if (!touch.view) return; // touch.view should===touch.touchResponder eventually :)
1160 
1161     // get view
1162     view = touch.view;
1163 
1164     // get view entry
1165     viewEntry = this._touchedViews[SC.guidFor(view)];
1166     viewEntry.touches.remove(touch);
1167     viewEntry.touchCount--;
1168 
1169     // remove view entry if needed
1170     if (viewEntry.touchCount < 1) {
1171       view.set("hasTouch", NO);
1172       viewEntry.view = null;
1173       delete this._touchedViews[SC.guidFor(view)];
1174     }
1175 
1176     // clear view
1177     touch.view = undefined;
1178   },
1179 
1180   _flushQueuedTouchResponder: function(){
1181     if (this._queuedTouchResponder) {
1182       var queued = this._queuedTouchResponder;
1183       this._queuedTouchResponder = null;
1184       this.makeTouchResponder.apply(this, queued);
1185     }
1186   },
1187 
1188   /**
1189     This method attempts to change the responder for a particular touch. The touch's responder is the
1190     view which will receive touch events for that touch.
1191 
1192     You will usually not call this method directly, instead calling one of the convenience methods on
1193     the touch itself. See documentation for SC.Touch for more.
1194 
1195     Possible gotchas:
1196 
1197     - Because this method must search for a view which implements touchStart (without returning NO),
1198       touchStart is called on the new responder before touchCancelled is called on the old one.
1199     - While a touch exposes its current responder at `touchResponder` and any previous stacked one at
1200       `nextTouchResponder`, their relationship is ad hoc and arbitrary, and so are not chained by
1201       `nextResponder` like in a standard responder chain. To query the touch's current responder stack
1202       (or, though it's not recommended, change it), check touch.touchResponders.
1203 
1204     @param {SC.Touch} touch
1205     @param {SC.Responder} responder The view to assign to the touch. (It, or if bubbling then an ancestor,
1206       must implement touchStart.)
1207     @param {Boolean} shouldStack Whether the new responder should replace the old one, or stack with it.
1208       Stacked responders are easy to revert via `SC.Touch#restoreLastTouchResponder`.
1209     @param {Boolean|SC.Responder} bubblesTo If YES, will attempt to find a `touchStart` responder up the
1210       responder chain. If NO or undefined, will only check the passed responder. If you pass a responder
1211       for this argument, the attempt will bubble until it reaches the passed responder, allowing you to
1212       restrict the bubbling to a portion of the responder chain. ((Note that this responder will not be
1213       given an opportunity to respond to the event.)
1214     @returns {Boolean} Whether a valid touch responder was found and assigned.
1215   */
1216   makeTouchResponder: function(touch, responder, shouldStack, bubblesTo) {
1217     // In certain cases (SC.Gesture being one), we have to call makeTouchResponder
1218     // from inside makeTouchResponder so we queue it up here.
1219     if (this._isMakingTouchResponder) {
1220       this._queuedTouchResponder = [touch, responder, shouldStack, bubblesTo];
1221       return YES; // um?
1222     }
1223     this._isMakingTouchResponder = YES;
1224 
1225     var stack = touch.touchResponders, touchesForView;
1226 
1227     // find the actual responder (if any, I suppose)
1228     // note that the pane's sendEvent function is slightly clever:
1229     // if the target is already touch responder, it will just return it without calling touchStart
1230     // we must do the same.
1231     if (touch.touchResponder === responder) {
1232       this._isMakingTouchResponder = NO;
1233       this._flushQueuedTouchResponder();
1234       return YES; // more um
1235     }
1236 
1237     // send touchStart
1238     // get the target pane
1239     var pane;
1240     if (responder) pane = responder.get('pane') ;
1241     else pane = this.get('keyPane') || this.get('mainPane') ;
1242 
1243     // if the responder is not already in the stack...
1244     if (stack.indexOf(responder) < 0) {
1245 
1246       // if we need to go up the view chain, do so via SC.Pane#sendEvent.
1247       if (bubblesTo) {
1248         // if we found a valid pane, send the event to it
1249         try {
1250           responder = pane ? pane.sendEvent("touchStart", touch, responder, bubblesTo) : null ;
1251         } catch (e) {
1252           SC.Logger.error("Error in touchStart: " + e);
1253           responder = null;
1254         }
1255       } else {
1256         // If the responder doesn't currently have a touch, or it does but it accepts multitouch, test it. Otherwise it's cool.
1257         if (responder && ((responder.get ? responder.get("acceptsMultitouch") : responder.acceptsMultitouch) || !responder.hasTouch)) {
1258           // If it doesn't respond to touchStart, it's no good.
1259           if (!responder.respondsTo("touchStart")) {
1260             responder = null;
1261           }
1262           // If it returns NO from touchStart, it's no good. Otherwise it's cool.
1263           else if (responder.touchStart(touch) === NO) {
1264             responder = null;
1265           }
1266         }
1267       }
1268     }
1269 
1270     // if the item is in the stack, we will go to it (whether shouldStack is true or not)
1271     // as it is already stacked
1272     if (!shouldStack || (stack.indexOf(responder) > -1 && stack[stack.length - 1] !== responder)) {
1273       // first, we should unassign the touch. Note that we only do this IF WE ARE removing
1274       // the current touch responder. Otherwise we cause all sorts of headaches; why? Because,
1275       // if we are not (suppose, for instance, that it is stacked), then the touch does not
1276       // get passed back to the touch responder-- even while it continues to get events because
1277       // the touchResponder is still set!
1278       this.unassignTouch(touch);
1279 
1280       // pop all other items
1281       var idx = stack.length - 1, last = stack[idx];
1282       while (last && last !== responder) {
1283         // unassign the touch
1284         touchesForView = this.touchesForView(last); // won't even exist if there are no touches
1285 
1286         // send touchCancelled (or, don't, if the view doesn't accept multitouch and it is not the last touch)
1287         if ((last.get ? last.get("acceptsMultitouch") : last.acceptsMultitouch) || !touchesForView) {
1288           if (last.touchCancelled) last.touchCancelled(touch);
1289         }
1290 
1291         // go to next (if < 0, it will be undefined, so lovely)
1292         idx--;
1293         last = stack[idx];
1294 
1295         // update responders (for consistency)
1296         stack.pop();
1297 
1298         touch.touchResponder = stack[idx];
1299         touch.nextTouchResponder = stack[idx - 1];
1300       }
1301 
1302     }
1303 
1304     // now that we've popped off, we can push on
1305     if (responder) {
1306       this.assignTouch(touch, responder);
1307 
1308       // keep in mind, it could be one we popped off _to_ above...
1309       if (responder !== touch.touchResponder) {
1310         stack.push(responder);
1311 
1312         // update responder helpers
1313         touch.touchResponder = responder;
1314         touch.nextTouchResponder = stack[stack.length - 2];
1315       }
1316     }
1317 
1318     // Unflag that this method is running, and flush the queue if any.
1319     this._isMakingTouchResponder = NO;
1320     this._flushQueuedTouchResponder(); // this may need to be &&'ed with the responder to give the correct return value...
1321 
1322     return !!responder;
1323   },
1324 
1325   /**
1326     Before the touchStart event is sent up the usual responder chain, the views along that same responder chain
1327     are given the opportunity to capture the touch event, preventing child views (including the target) from
1328     hearing about it. This of course proceeds in the opposite direction from a usual event bubbling, starting at
1329     the target's first ancestor and proceeding towards the target. This method implements the capture phase.
1330 
1331     If no view captures the touch, this method will return NO, and makeTouchResponder is then called for the
1332     target, proceeding with standard target-to-pane event bubbling for `touchStart`.
1333 
1334     For an example of captureTouch in action, see SC.ScrollView's touch handling, which by default captures the
1335     touch and holds it for 150ms to allow it to determine whether the user is tapping or scrolling.
1336 
1337     You will usually not call this method yourself, and if you do, you should call the corresponding convenience
1338     method on the touch itself.
1339 
1340     @param {SC.Touch} touch The touch to offer up for capture.
1341     @param {?SC.Responder} startingPoint The view whose children should be given an opportunity to capture
1342       the event. (The starting point itself is not asked.)
1343     @param {Boolean} shouldStack Whether any capturing responder should stack with existing responders.
1344       Stacked responders are easy to revert via `SC.Touch#restoreLastTouchResponder`.
1345 
1346     @returns {Boolean} Whether or not the touch was captured. If it was not, you should pass it to
1347       `makeTouchResponder` for standard event bubbling.
1348   */
1349   captureTouch: function(touch, startingPoint, shouldStack) {
1350     if (!startingPoint) startingPoint = this;
1351 
1352     var target = touch.targetView, view = target,
1353         chain = [], idx, len;
1354 
1355     //@if (debug)
1356     if (SC.LOG_TOUCH_EVENTS) {
1357       SC.Logger.info('  -- Received one touch on %@'.fmt(target.toString()));
1358     }
1359     //@endif
1360     // Generate the captureTouch responder chain by working backwards from the target
1361     // to the starting point. (Don't include the starting point.)
1362     while (view && (view !== startingPoint)) {
1363       chain.unshift(view);
1364       view = view.get('nextResponder');
1365     }
1366 
1367     // work down the chain
1368     for (len = chain.length, idx = 0; idx < len; idx++) {
1369       view = chain[idx];
1370       //@if (debug)
1371       if (SC.LOG_TOUCH_EVENTS) SC.Logger.info('  -- Checking %@ for captureTouch response…'.fmt(view.toString()));
1372       //@endif
1373 
1374       // see if it captured the touch
1375       if (view.tryToPerform('captureTouch', touch)) {
1376         //@if (debug)
1377         if (SC.LOG_TOUCH_EVENTS) SC.Logger.info('   -- Making %@ touch responder because it returns YES to captureTouch'.fmt(view.toString()));
1378         //@endif
1379 
1380         // if so, make it the touch's responder
1381         this.makeTouchResponder(touch, view, shouldStack, startingPoint); // (touch, target, should stack, bubbles back to startingPoint, or all the way up.)
1382         return YES; // and that's all we need
1383       }
1384     }
1385 
1386     //@if (debug)
1387     if (SC.LOG_TOUCH_EVENTS) SC.Logger.info("   -- Didn't find a view that returned YES to captureTouch.");
1388     //@endif
1389 
1390     return NO;
1391   },
1392 
1393   //@if(debug)
1394   /** @private
1395     Artificially calls endTouch for any touch which is no longer present. This is necessary because
1396     _sometimes_, WebKit ends up not sending endtouch.
1397   */
1398   endMissingTouches: function(presentTouches) {
1399     var idx, len = presentTouches.length, map = {}, end = [];
1400 
1401     // make a map of what touches _are_ present
1402     for (idx = 0; idx < len; idx++) {
1403       map[presentTouches[idx].identifier] = YES;
1404     }
1405 
1406     // check if any of the touches we have recorded are NOT present
1407     for (idx in this._touches) {
1408       var id = this._touches[idx].identifier;
1409       if (!map[id]) end.push(this._touches[idx]);
1410     }
1411 
1412     // end said touches
1413     if (end.length) {
1414       console.warn('Ending missing touches: ' + end.toString());
1415     }
1416     for (idx = 0, len = end.length; idx < len; idx++) {
1417       this.endTouch(end[idx]);
1418       this.finishTouch(end[idx]);
1419     }
1420   },
1421   //@endif
1422 
1423   _touchCount: 0,
1424 
1425   /** @private
1426     Ends a specific touch (for a bit, at least). This does not "finish" a touch; it merely calls
1427     touchEnd, touchCancelled, etc. A re-dispatch (through recapture or makeTouchResponder) will terminate
1428     the process; it would have to be restarted separately, through touch.end().
1429   */
1430   endTouch: function(touchEntry, action, evt) {
1431     if (!action) { action = "touchEnd"; }
1432 
1433     var responderIdx, responders, responder, originalResponder;
1434 
1435     // unassign
1436     this.unassignTouch(touchEntry);
1437 
1438     // call end for all items in chain
1439     if (touchEntry.touchResponder) {
1440       originalResponder = touchEntry.touchResponder;
1441 
1442       responders = touchEntry.touchResponders;
1443       responderIdx = responders.length - 1;
1444       responder = responders[responderIdx];
1445       while (responder) {
1446         if (responder[action]) { responder[action](touchEntry, evt); }
1447 
1448         // check to see if the responder changed, and stop immediately if so.
1449         if (touchEntry.touchResponder !== originalResponder) { break; }
1450 
1451         // next
1452         responderIdx--;
1453         responder = responders[responderIdx];
1454         action = "touchCancelled"; // any further ones receive cancelled
1455       }
1456     }
1457   },
1458 
1459   /**
1460     @private
1461     "Finishes" a touch. That is, it eradicates it from our touch entries and removes all responder, etc. properties.
1462   */
1463   finishTouch: function(touch) {
1464     // ensure the touch is indeed unassigned.
1465     this.unassignTouch(touch);
1466 
1467     // If we rescued this touch's initial element, we should remove it
1468     // from the DOM and garbage collect now. See setup() for an
1469     // explanation of this bug/workaround.
1470     var elem = touch._rescuedElement;
1471     if (elem) {
1472       if (elem.swapNode && elem.swapNode.parentNode) {
1473         elem.swapNode.parentNode.replaceChild(elem, elem.swapNode);
1474       } else if (elem.parentNode === SC.touchHoldingPen) {
1475         SC.touchHoldingPen.removeChild(elem);
1476       }
1477       delete touch._rescuedElement;
1478       elem.swapNode = null;
1479       elem = null;
1480     }
1481 
1482     // clear responders (just to be thorough)
1483     touch.touchResponders = null;
1484     touch.touchResponder = null;
1485     touch.nextTouchResponder = null;
1486     touch.hasEnded = YES;
1487 
1488     // and remove from our set
1489     if (this._touches[touch.identifier]) delete this._touches[touch.identifier];
1490   },
1491 
1492   /** @private
1493     Called when the user touches their finger to the screen. This method
1494     dispatches the touchstart event to the appropriate view.
1495 
1496     We may receive a touchstart event for each touch, or we may receive a
1497     single touchstart event with multiple touches, so we may have to dispatch
1498     events to multiple views.
1499 
1500     @param {Event} evt the event
1501     @returns {Boolean}
1502   */
1503   touchstart: function(evt) {
1504     // Starting iOS5 touch events are handled by textfields.
1505     // As a workaround just let the browser to use the default behavior.
1506     if(this.ignoreTouchHandle(evt)) return YES;
1507 
1508     var hidingTouchIntercept = NO;
1509 
1510     SC.run(function() {
1511       //@if(debug)
1512       // When using breakpoints on touch start, we will lose the end touch event.
1513       this.endMissingTouches(evt.touches);
1514       //@endif
1515 
1516       // loop through changed touches, calling touchStart, etc.
1517       var changedTouches = evt.changedTouches,
1518           len = changedTouches.length,
1519           idx,
1520           touch, touchEntry;
1521 
1522       // prepare event for touch mapping.
1523       evt.touchContext = this;
1524 
1525       // Loop through each touch we received in this event
1526       for (idx = 0; idx < len; idx++) {
1527         touch = changedTouches[idx];
1528 
1529         // Create an SC.Touch instance for every touch.
1530         touchEntry = SC.Touch.create(touch, this);
1531 
1532         // skip the touch if there was no target
1533         if (!touchEntry.targetView) continue;
1534 
1535         // account for hidden touch intercept (passing through touches, etc.)
1536         if (touchEntry.hidesTouchIntercept) hidingTouchIntercept = YES;
1537 
1538         // set timestamp
1539         touchEntry.timeStamp = evt.timeStamp;
1540 
1541         // Store the SC.Touch object. We use the identifier property (provided
1542         // by the browser) to disambiguate between touches. These will be used
1543         // later to determine if the touches have changed.
1544         this._touches[touch.identifier] = touchEntry;
1545 
1546         // set the event (so default action, etc. can be stopped)
1547         touchEntry.event = evt; // will be unset momentarily
1548 
1549         // First we allow any view in the responder chain to capture the touch, before triggering the standard touchStart
1550         // handler chain.
1551         var captured = this.captureTouch(touchEntry, this);
1552         if (!captured) this.makeTouchResponder(touchEntry, touchEntry.targetView, NO, YES); // (touch, target, shouldn't stack, bubbles all the way)
1553 
1554         // Unset the reference to the original event so we can garbage collect.
1555         touchEntry.event = null;
1556       }
1557 
1558       evt.touchContext = null;
1559     }, this);
1560 
1561     // hack for text fields
1562     if (hidingTouchIntercept) {
1563       return YES;
1564     }
1565 
1566     return evt.hasCustomEventHandling;
1567   },
1568 
1569   /**
1570     @private
1571     used to keep track of when a specific type of touch event was last handled, to see if it needs to be re-handled
1572   */
1573   touchmove: function(evt) {
1574     // Starting iOS5 touch events are handled by textfields.
1575     // As a workaround just let the browser to use the default behavior.
1576     if(this.ignoreTouchHandle(evt)) return YES;
1577 
1578     SC.run(function() {
1579       // pretty much all we gotta do is update touches, and figure out which views need updating.
1580       var touches = evt.changedTouches, touch, touchEntry,
1581           idx, len = touches.length, view, changedTouches, viewTouches, firstTouch,
1582           changedViews = {}, guid, hidingTouchIntercept = NO;
1583 
1584       if (this._drag) {
1585         touch = SC.Touch.create(evt.changedTouches[0], this);
1586         this._drag.tryToPerform('mouseDragged', touch);
1587       }
1588 
1589       // figure out what views had touches changed, and update our internal touch objects
1590       for (idx = 0; idx < len; idx++) {
1591         touch = touches[idx];
1592 
1593         // get our touch
1594         touchEntry = this._touches[touch.identifier];
1595 
1596         // we may have no touch entry; this can happen if somehow the touch came to a non-SC area.
1597         if (!touchEntry) {
1598           continue;
1599         }
1600 
1601         if (touchEntry.hidesTouchIntercept) hidingTouchIntercept = YES;
1602 
1603         // update touch velocity (moving average)
1604         var duration = evt.timeStamp - touchEntry.timeStamp,
1605           velocityLambda, latestXVelocity, latestYVelocity;
1606         // Given uneven timing between events, we should give less weight to shorter (less accurate)
1607         // events, with no consideration at all given zero-time events.
1608         if (duration !== 0) {
1609           // Lambda (how heavily we're weighting the latest number)
1610           velocityLambda = Math.min(1, duration / 80);
1611           // X
1612           latestXVelocity = (touch.pageX - touchEntry.pageX) / duration;
1613           touchEntry.velocityX = (1.0 - velocityLambda) * touchEntry.velocityX + velocityLambda * (latestXVelocity);
1614           // Y
1615           latestYVelocity = (touch.pageY - touchEntry.pageY) / duration;
1616           touchEntry.velocityY = (1.0 - velocityLambda) * touchEntry.velocityY + velocityLambda * (latestYVelocity);
1617         }
1618 
1619         // update touch position et al.
1620         touchEntry.pageX = touch.pageX;
1621         touchEntry.pageY = touch.pageY;
1622         touchEntry.clientX = touch.clientX;
1623         touchEntry.clientY = touch.clientY;
1624         touchEntry.screenX = touch.screenX;
1625         touchEntry.screenY = touch.screenY;
1626         touchEntry.timeStamp = evt.timeStamp;
1627         touchEntry.type = evt.type;
1628         touchEntry.event = evt;
1629 
1630         // if the touch entry has a view
1631         if (touchEntry.touchResponder) {
1632           view = touchEntry.touchResponder;
1633 
1634           guid = SC.guidFor(view);
1635           // create a view entry
1636           if (!changedViews[guid]) changedViews[guid] = { "view": view, "touches": [] };
1637 
1638           // add touch
1639           changedViews[guid].touches.push(touchEntry);
1640         }
1641       }
1642 
1643       // HACK: DISABLE OTHER TOUCH DRAGS WHILE MESSING WITH TEXT FIELDS
1644       if (hidingTouchIntercept) {
1645         evt.allowDefault();
1646         return YES;
1647       }
1648 
1649       // loop through changed views and send events
1650       for (idx in changedViews) {
1651         // get info
1652         view = changedViews[idx].view;
1653         changedTouches = changedViews[idx].touches;
1654 
1655         // prepare event; note that views often won't use this method anyway (they'll call touchesForView instead)
1656         evt.viewChangedTouches = changedTouches;
1657 
1658         // the first VIEW touch should be the touch info sent
1659         viewTouches = this.touchesForView(view);
1660         firstTouch = viewTouches.firstObject();
1661 
1662         // Load the event up with data from the first touch. THIS IS FOR CONVENIENCE ONLY in cases where the developer
1663         // only cares about one touch.
1664         evt.pageX = firstTouch.pageX;
1665         evt.pageY = firstTouch.pageY;
1666         evt.clientX = firstTouch.clientX;
1667         evt.clientY = firstTouch.clientY;
1668         evt.screenX = firstTouch.screenX;
1669         evt.screenY = firstTouch.screenY;
1670         evt.startX = firstTouch.startX;
1671         evt.startY = firstTouch.startY;
1672         evt.velocityX = firstTouch.velocityX;
1673         evt.velocityY = firstTouch.velocityY;
1674         evt.touchContext = this; // Injects the root responder so it can call e.g. `touchesForView`.
1675 
1676         // Give the view a chance to handle touchesDragged. (Don't bubble; viewTouches is view-specific.)
1677         view.tryToPerform("touchesDragged", evt, viewTouches);
1678       }
1679 
1680       // clear references to event
1681       touches = evt.changedTouches;
1682       len = touches.length;
1683       for (idx = 0; idx < len; idx++) {
1684         touch = touches[idx];
1685         touchEntry = this._touches[touch.identifier];
1686         if (touchEntry) touchEntry.event = null;
1687       }
1688       evt.touchContext = null;
1689       evt.viewChangedTouches = null;
1690     }, this);
1691 
1692     return evt.hasCustomEventHandling;
1693   },
1694 
1695   touchend: function(evt) {
1696     var hidesTouchIntercept = NO;
1697 
1698     // Starting iOS5 touch events are handled by textfields.
1699     // As a workaround just let the browser to use the default behavior.
1700     if(this.ignoreTouchHandle(evt)) return YES;
1701 
1702     SC.run(function() {
1703       var touches = evt.changedTouches, touch, touchEntry,
1704           idx, len = touches.length,
1705           action = evt.isCancel ? "touchCancelled" : "touchEnd";
1706 
1707       for (idx = 0; idx < len; idx++) {
1708         //get touch+entry
1709         touch = touches[idx];
1710         touch.type = 'touchend';
1711         touchEntry = this._touches[touch.identifier];
1712 
1713         // check if there is an entry
1714         if (!touchEntry) continue;
1715 
1716         // update touch velocity (moving average)
1717         var duration = evt.timeStamp - touchEntry.timeStamp,
1718           velocityLambda, latestXVelocity, latestYVelocity;
1719         // Given uneven timing between events, we should give less weight to shorter (less accurate)
1720         // events, with no consideration at all given zero-time events.
1721         if (duration !== 0) {
1722           // Lambda (how heavily we're weighting the latest number)
1723           velocityLambda = Math.min(1, duration / 80);
1724           // X
1725           latestXVelocity = (touch.pageX - touchEntry.pageX) / duration;
1726           touchEntry.velocityX = (1.0 - velocityLambda) * touchEntry.velocityX + velocityLambda * (latestXVelocity);
1727           // Y
1728           latestYVelocity = (touch.pageY - touchEntry.pageY) / duration;
1729           touchEntry.velocityY = (1.0 - velocityLambda) * touchEntry.velocityY + velocityLambda * (latestYVelocity);
1730         }
1731 
1732         // update touch position et al.
1733         touchEntry.timeStamp = evt.timeStamp;
1734         touchEntry.pageX = touch.pageX;
1735         touchEntry.pageY = touch.pageY;
1736         touchEntry.clientX = touch.clientX;
1737         touchEntry.clientY = touch.clientY;
1738         touchEntry.screenX = touch.screenX;
1739         touchEntry.screenY = touch.screenY;
1740         touchEntry.type = 'touchend';
1741         touchEntry.event = evt;
1742 
1743         //@if (debug)
1744         if (SC.LOG_TOUCH_EVENTS) SC.Logger.info('-- Received touch end');
1745         //@endif
1746         if (touchEntry.hidesTouchIntercept) {
1747           touchEntry.unhideTouchIntercept();
1748           hidesTouchIntercept = YES;
1749         }
1750 
1751         if (this._drag) {
1752           this._drag.tryToPerform('mouseUp', touch) ;
1753           this._drag = null ;
1754         }
1755 
1756         // unassign
1757         this.endTouch(touchEntry, action, evt);
1758         this.finishTouch(touchEntry);
1759       }
1760     }, this);
1761 
1762 
1763     // for text fields
1764     if (hidesTouchIntercept) {
1765       return YES;
1766     }
1767 
1768     return evt.hasCustomEventHandling;
1769   },
1770 
1771   /** @private
1772     Handle touch cancel event.  Works just like cancelling a touch for any other reason.
1773     touchend handles it as a special case (sending cancel instead of end if needed).
1774   */
1775   touchcancel: function(evt) {
1776     evt.isCancel = YES;
1777     this.touchend(evt);
1778   },
1779 
1780   /** @private
1781      Ignore Touch events on textfields and links. starting iOS 5 textfields
1782      get touch events. Textfields just need to get the default focus action.
1783   */
1784   ignoreTouchHandle: function(evt) {
1785     if(SC.browser.isMobileSafari){
1786       var tag = evt.target.tagName;
1787       if(tag==="INPUT" || tag==="TEXTAREA" || tag==="A" || tag==="SELECT"){
1788         evt.allowDefault();
1789         return YES;
1790       }
1791     }
1792     return NO;
1793   },
1794 
1795   // ..........................................................
1796   // KEYBOARD HANDLING
1797   //
1798 
1799 
1800   /**
1801     Invoked on a keyDown event that is not handled by any actual value.  This
1802     will get the key equivalent string and then walk down the keyPane, then
1803     the focusedPane, then the mainPane, looking for someone to handle it.
1804     Note that this will walk DOWN the view hierarchy, not up it like most.
1805 
1806     @returns {Object} Object that handled evet or null
1807   */
1808   attemptKeyEquivalent: function(evt) {
1809     var ret = null ;
1810 
1811     // keystring is a method name representing the keys pressed (i.e
1812     // 'alt_shift_escape')
1813     var keystring = evt.commandCodes()[0];
1814 
1815     // couldn't build a keystring for this key event, nothing to do
1816     if (!keystring) return NO;
1817 
1818     var menuPane = this.get('menuPane'),
1819         keyPane  = this.get('keyPane'),
1820         mainPane = this.get('mainPane');
1821 
1822     if (menuPane) {
1823       ret = menuPane.performKeyEquivalent(keystring, evt) ;
1824       if (ret) return ret;
1825     }
1826 
1827     // Try the keyPane.  If it's modal, then try the equivalent there but on
1828     // nobody else.
1829     if (keyPane) {
1830       ret = keyPane.performKeyEquivalent(keystring, evt) ;
1831       if (ret || keyPane.get('isModal')) return ret ;
1832     }
1833 
1834     // if not, then try the main pane
1835     if (!ret && mainPane && (mainPane!==keyPane)) {
1836       ret = mainPane.performKeyEquivalent(keystring, evt);
1837       if (ret || mainPane.get('isModal')) return ret ;
1838     }
1839 
1840     return ret ;
1841   },
1842 
1843   _lastModifiers: null,
1844 
1845   /** @private
1846     Modifier key changes are notified with a keydown event in most browsers.
1847     We turn this into a flagsChanged keyboard event.  Normally this does not
1848     stop the normal browser behavior.
1849   */
1850   _handleModifierChanges: function(evt) {
1851     // if the modifier keys have changed, then notify the first responder.
1852     var m;
1853     m = this._lastModifiers = (this._lastModifiers || { alt: false, ctrl: false, shift: false });
1854 
1855     var changed = false;
1856     if (evt.altKey !== m.alt) {
1857       m.alt = evt.altKey;
1858       changed = true;
1859     }
1860 
1861     if (evt.ctrlKey !== m.ctrl) {
1862       m.ctrl = evt.ctrlKey;
1863       changed = true;
1864     }
1865 
1866     if (evt.shiftKey !== m.shift) {
1867       m.shift = evt.shiftKey;
1868       changed = true;
1869     }
1870     evt.modifiers = m; // save on event
1871 
1872     return (changed) ? (this.sendEvent('flagsChanged', evt) ? evt.hasCustomEventHandling : YES) : YES ;
1873   },
1874 
1875   /** @private
1876     Determines if the keyDown event is a nonprintable or function key. These
1877     kinds of events are processed as keyboard shortcuts.  If no shortcut
1878     handles the event, then it will be sent as a regular keyDown event.
1879     This function is only valid when called with a keydown event.
1880   */
1881   _isFunctionOrNonPrintableKey: function(evt) {
1882     return !!(evt.altKey || evt.ctrlKey || evt.metaKey || SC.FUNCTION_KEYS[evt.which]);
1883   },
1884 
1885   /** @private
1886     Determines if the event simply reflects a modifier key change.  These
1887     events may generate a flagsChanged event, but are otherwise ignored.
1888   */
1889   _isModifierKey: function(evt) {
1890     return !!SC.MODIFIER_KEYS[evt.charCode];
1891   },
1892 
1893    /**
1894      @private
1895      Determines if the key is printable (and therefore should be dispatched from keypress).
1896      Some browsers send backspace, tab, enter, and escape on keypress, so we want to
1897      explicitly ignore those here.
1898 
1899      @param {KeyboardEvent} evt keypress event
1900      @returns {Boolean}
1901    */
1902   _isPrintableKey: function (evt) {
1903     return ((evt.originalEvent.which === undefined || evt.originalEvent.which > 0) &&
1904       !(evt.which === 8 || evt.which === 9 || evt.which === 13 || evt.which === 27));
1905   },
1906 
1907   /** @private
1908     The keydown event occurs whenever the physically depressed key changes.
1909     This event is used to deliver the flagsChanged event and to with function
1910     keys and keyboard shortcuts.
1911 
1912     All actions that might cause an actual insertion of text are handled in
1913     the keypress event.
1914 
1915     References:
1916         http://www.quirksmode.org/js/keys.html
1917         https://developer.mozilla.org/en/DOM/KeyboardEvent
1918         http://msdn.microsoft.com/library/ff974342.aspx
1919   */
1920   keydown: function(evt) {
1921     if (SC.none(evt)) return YES;
1922     var keyCode = evt.keyCode;
1923     if (SC.browser.isMozilla && evt.keyCode===9) {
1924       this.keydownCounter = 1;
1925     }
1926     // Fix for IME input (japanese, mandarin).
1927     // If the KeyCode is 229 wait for the keyup and
1928     // trigger a keyDown if it is is enter onKeyup.
1929     if (keyCode===229){
1930       this._IMEInputON = YES;
1931       return this.sendEvent('keyDown', evt);
1932     }
1933 
1934     // If user presses the escape key while we are in the middle of a
1935     // drag operation, cancel the drag operation and handle the event.
1936     if (keyCode === 27 && this._drag) {
1937       this._drag.cancelDrag(evt);
1938       this._drag = null;
1939       this._mouseDownView = null;
1940       return YES;
1941     }
1942 
1943     // Firefox does NOT handle delete here...
1944     if (SC.browser.isMozilla && (evt.which === 8)) return true ;
1945 
1946     // modifier keys are handled separately by the 'flagsChanged' event
1947     // send event for modifier key changes, but only stop processing if this
1948     // is only a modifier change
1949     var ret = this._handleModifierChanges(evt),
1950         target = evt.target || evt.srcElement,
1951         forceBlock = (evt.which === 8) && !SC.allowsBackspaceToPreviousPage && (target === document.body);
1952 
1953     if (this._isModifierKey(evt)) return (forceBlock ? NO : ret);
1954 
1955     // if this is a function or non-printable key, try to use this as a key
1956     // equivalent.  Otherwise, send as a keyDown event so that the focused
1957     // responder can do something useful with the event.
1958     ret = YES ;
1959     if (this._isFunctionOrNonPrintableKey(evt)) {
1960       // otherwise, send as keyDown event.  If no one was interested in this
1961       // keyDown event (probably the case), just let the browser do its own
1962       // processing.
1963 
1964       // Arrow keys are handled in keypress for firefox
1965       if (keyCode>=37 && keyCode<=40 && SC.browser.isMozilla) return YES;
1966 
1967       ret = this.sendEvent('keyDown', evt) ;
1968 
1969       // attempt key equivalent if key not handled
1970       if (!ret) {
1971         SC.run(function () {
1972         ret = !this.attemptKeyEquivalent(evt) ;
1973         }, this);
1974       } else {
1975         ret = evt.hasCustomEventHandling ;
1976         if (ret) forceBlock = NO ; // code asked explicitly to let delete go
1977       }
1978     }
1979 
1980     return forceBlock ? NO : ret ;
1981   },
1982 
1983   /** @private
1984     The keypress event occurs after the user has typed something useful that
1985     the browser would like to insert.  Unlike keydown, the input codes here
1986     have been processed to reflect that actual text you might want to insert.
1987 
1988     Normally ignore any function or non-printable key events.  Otherwise, just
1989     trigger a keyDown.
1990   */
1991   keypress: function(evt) {
1992     var ret,
1993         keyCode   = evt.keyCode,
1994         isFirefox = SC.browser.isMozilla;
1995 
1996     if (isFirefox && evt.keyCode===9) {
1997       this.keydownCounter++;
1998       if (this.keydownCounter === 2) return YES;
1999     }
2000 
2001     // delete is handled in keydown() for most browsers
2002     if (isFirefox && (evt.which === 8)) {
2003       //get the keycode and set it for which.
2004       evt.which = keyCode;
2005       ret = this.sendEvent('keyDown', evt);
2006       return ret ? (SC.allowsBackspaceToPreviousPage || evt.hasCustomEventHandling) : YES ;
2007 
2008     // normal processing.  send keyDown for printable keys...
2009     //there is a special case for arrow key repeating of events in FF.
2010     } else {
2011       var isFirefoxArrowKeys = (keyCode >= 37 && keyCode <= 40 && isFirefox),
2012           charCode           = evt.charCode;
2013 
2014       if ((charCode !== undefined && charCode === 0 && evt.keyCode!==9) && !isFirefoxArrowKeys) return YES;
2015       if (isFirefoxArrowKeys) evt.which = keyCode;
2016       // we only want to rethrow if this is a printable key so that we don't
2017       // duplicate the event sent in keydown when a modifier key is pressed.
2018       if (isFirefoxArrowKeys || this._isPrintableKey(evt)) {
2019         return this.sendEvent('keyDown', evt) ? evt.hasCustomEventHandling : YES;
2020     }
2021     }
2022   },
2023 
2024   keyup: function(evt) {
2025     // to end the simulation of keypress in firefox set the _ffevt to null
2026     if(this._ffevt) this._ffevt=null;
2027 
2028     // modifier keys are handled separately by the 'flagsChanged' event
2029     // send event for modifier key changes, but only stop processing if this is only a modifier change
2030     var ret = this._handleModifierChanges(evt);
2031     if (this._isModifierKey(evt)) return ret;
2032     // Fix for IME input (japanese, mandarin).
2033     // If the KeyCode is 229 wait for the keyup and
2034     // trigger a keyDown if it is is enter onKeyup.
2035     if (this._IMEInputON && evt.keyCode===13){
2036       evt.isIMEInput = YES;
2037       this.sendEvent('keyDown', evt);
2038       this._IMEInputON = NO;
2039     }
2040     return this.sendEvent('keyUp', evt) ? evt.hasCustomEventHandling:YES;
2041   },
2042 
2043   /**
2044     IE's default behavior to blur textfields and other controls can only be
2045     blocked by returning NO to this event. However we don't want to block
2046     its default behavior otherwise textfields won't lose focus by clicking on
2047     an empty area as it's expected. If you want to block IE from blurring another
2048     control set blockIEDeactivate to true on the specific view in which you
2049     want to avoid this. Think of an autocomplete menu, you want to click on
2050     the menu but don't loose focus.
2051   */
2052   beforedeactivate: function(evt) {
2053     var toElement = evt.toElement;
2054     if (toElement && toElement.tagName && toElement.tagName!=="IFRAME") {
2055       var view = SC.viewFor(toElement);
2056       //The following line is necessary to allow/block text selection for IE,
2057       // in combination with the selectstart event.
2058       if (view && view.get('blocksIEDeactivate')) return NO;
2059     }
2060     return YES;
2061   },
2062 
2063   // ..........................................................
2064   // MOUSE HANDLING
2065   //
2066 
2067   mousedown: function(evt) {
2068     // First, save the click count. The click count resets if the mouse down
2069     // event occurs more than 250 ms later than the mouse up event or more
2070     // than 8 pixels away from the mouse down event or if the button used is different.
2071     this._clickCount += 1;
2072 
2073     var view = this.targetViewForEvent(evt);
2074 
2075     view = this._mouseDownView = this.sendEvent('mouseDown', evt, view) ;
2076     if (view && view.respondsTo('mouseDragged')) this._mouseCanDrag = YES ;
2077 
2078     // Determine if any views took responsibility for the event. If so, save that information so we
2079     // can prevent the next click event we receive from propagating to the browser.
2080     var ret = view ? evt.hasCustomEventHandling : YES;
2081     this._lastMouseDownCustomHandling = ret;
2082 
2083     // If it has been too long since the last click, the handler has changed or the mouse has moved
2084     // too far, reset the click count.
2085     if (!this._lastMouseUpAt || this._lastMouseDownView !== this._mouseDownView || ((Date.now() - this._lastMouseUpAt) > 250)) {
2086       this._clickCount = 1;
2087     } else {
2088       var deltaX = this._lastMouseDownX - evt.clientX,
2089           deltaY = this._lastMouseDownY - evt.clientY,
2090           distance = Math.sqrt(deltaX*deltaX + deltaY*deltaY) ;
2091 
2092       if (distance > 8.0) this._clickCount = 1;
2093     }
2094     evt.clickCount = this._clickCount;
2095 
2096     // Cache the handler and point of the last mouse down in order to determine whether a successive mouse down should
2097     // still increment the click count.
2098     this._lastMouseDownView = this._mouseDownView;
2099     this._lastMouseDownX = evt.clientX;
2100     this._lastMouseDownY = evt.clientY;
2101 
2102     return ret;
2103   },
2104 
2105   /**
2106     mouseUp only gets delivered to the view that handled the mouseDown evt.
2107     we also handle click and double click notifications through here to
2108     ensure consistant delivery.  Note that if mouseDownView is not
2109     implemented, then no mouseUp event will be sent, but a click will be
2110     sent.
2111   */
2112   mouseup: function(evt) {
2113     var clickOrDoubleClickDidTrigger = NO,
2114       dragView = this._drag,
2115       handler = null;
2116 
2117     if (dragView) {
2118       SC.run(function () {
2119         dragView.tryToPerform('mouseUp', evt);
2120       });
2121     } else {
2122       var view = this._mouseDownView,
2123         targetView = this.targetViewForEvent(evt);
2124 
2125       // record click count.
2126       evt.clickCount = this._clickCount ;
2127 
2128       // attempt the mouseup call only if there's a target.
2129       // don't want a mouseup going to anyone unless they handled the mousedown...
2130       if (view) {
2131         handler = this.sendEvent('mouseUp', evt, view) ;
2132 
2133         // try doubleClick
2134         if (!handler && this._clickCount === 2) {
2135           handler = this.sendEvent('doubleClick', evt, view) ;
2136           clickOrDoubleClickDidTrigger = YES;
2137         }
2138 
2139         // try single click
2140         if (!handler) {
2141           handler = this.sendEvent('click', evt, view) ;
2142           clickOrDoubleClickDidTrigger = YES;
2143         }
2144       }
2145 
2146       // try whoever's under the mouse if we haven't handle the mouse up yet
2147       if (!handler && !clickOrDoubleClickDidTrigger) {
2148 
2149         // try doubleClick
2150         if (this._clickCount === 2) {
2151           handler = this.sendEvent('doubleClick', evt, targetView);
2152         }
2153 
2154         // try singleClick
2155         if (!handler) {
2156           handler = this.sendEvent('click', evt, targetView) ;
2157         }
2158       }
2159     }
2160 
2161     // cleanup
2162     this._mouseCanDrag = NO;
2163     this._mouseDownView = this._drag = null;
2164 
2165     // Save timestamp of mouseup at last possible moment.
2166     // (This is used to calculate double click events)
2167     this._lastMouseUpAt = Date.now() ;
2168 
2169     // Determine if any views took responsibility for the
2170     // event. If so, save that information so we can prevent
2171     // the next click event we receive from propagating to the browser.
2172     var ret = handler ? evt.hasCustomEventHandling : YES;
2173     this._lastMouseUpCustomHandling = ret;
2174 
2175     return ret;
2176   },
2177 
2178   /**
2179     Certain browsers ignore us overriding mouseup and mousedown events and
2180     still allow default behavior (such as navigating away when the user clicks
2181     on a link). To block default behavior, we store whether or not the last
2182     mouseup or mousedown events resulted in us calling preventDefault() or
2183     stopPropagation(), in which case we make the same calls on the click event.
2184 
2185     @param {Event} evt the click event
2186     @returns {Boolean} whether the event should be propagated to the browser
2187   */
2188   click: function(evt) {
2189     if (!this._lastMouseUpCustomHandling || !this._lastMouseDownCustomHandling) {
2190       evt.preventDefault();
2191       evt.stopPropagation();
2192       return NO;
2193     }
2194 
2195     return YES;
2196   },
2197 
2198   dblclick: function(evt){
2199     if (SC.browser.isIE8OrLower) {
2200       this._clickCount = 2;
2201       // this._onmouseup(evt);
2202       this.mouseup(evt);
2203     }
2204   },
2205 
2206   mousewheel: function(evt) {
2207     var view = this.targetViewForEvent(evt) ,
2208         handler = this.sendEvent('mouseWheel', evt, view) ;
2209 
2210     return (handler) ? evt.hasCustomEventHandling : YES ;
2211   },
2212 
2213   _lastHovered: null,
2214 
2215   /**
2216    This will send mouseEntered, mouseExited, mousedDragged and mouseMoved
2217    to the views you hover over.  To receive these events, you must implement
2218    the method. If any subviews implement them and return true, then you won't
2219    receive any notices.
2220 
2221    If there is a target mouseDown view, then mouse moved events will also
2222    trigger calls to mouseDragged.
2223   */
2224   mousemove: function(evt) {
2225 
2226     if (SC.browser.isIE) {
2227       if (this._lastMoveX === evt.clientX && this._lastMoveY === evt.clientY) return;
2228     }
2229 
2230     // We'll record the last positions in all browsers, in case a special pane
2231     // or some such UI absolutely needs this information.
2232     this._lastMoveX = evt.clientX;
2233     this._lastMoveY = evt.clientY;
2234 
2235     SC.run(function() {
2236       var dragView = this._drag;
2237 
2238        // make sure the view gets focus no matter what.  FF is inconsistent
2239        // about this.
2240       // this.focus();
2241        // only do mouse[Moved|Entered|Exited|Dragged] if not in a drag session
2242        // drags send their own events, e.g. drag[Moved|Entered|Exited]
2243       if (dragView) {
2244          //IE triggers mousemove at the same time as mousedown
2245          if(SC.browser.isIE){
2246            if (this._lastMouseDownX !== evt.clientX || this._lastMouseDownY !== evt.clientY) {
2247             dragView.tryToPerform('mouseDragged', evt);
2248            }
2249         } else {
2250           dragView.tryToPerform('mouseDragged', evt);
2251          }
2252        } else {
2253         var lh = this._lastHovered || [], nh = [], loc, len,
2254              view = this.targetViewForEvent(evt) ;
2255 
2256          // first collect all the responding view starting with the
2257          // target view from the given mouse move event
2258          while (view && (view !== this)) {
2259            nh.push(view);
2260            view = view.get('nextResponder');
2261          }
2262          // next exit views that are no longer part of the
2263          // responding chain
2264          for (loc=0, len=lh.length; loc < len; loc++) {
2265            view = lh[loc] ;
2266           if (nh.indexOf(view) === -1 && !view.isDestroyed) { // Usually we don't want to have to manually check isDestroyed, but in this case we're explicitly checking an out-of-date cache.
2267              view.tryToPerform('mouseExited', evt);
2268            }
2269          }
2270          // finally, either perform mouse moved or mouse entered depending on
2271          // whether a responding view was or was not part of the last
2272          // hovered views
2273          for (loc=0, len=nh.length; loc < len; loc++) {
2274            view = nh[loc];
2275            if (lh.indexOf(view) !== -1) {
2276              view.tryToPerform('mouseMoved', evt);
2277            } else {
2278              view.tryToPerform('mouseEntered', evt);
2279            }
2280          }
2281          // Keep track of the view that were last hovered
2282          this._lastHovered = nh;
2283          // also, if a mouseDownView exists, call the mouseDragged action, if
2284          // it exists.
2285          if (this._mouseDownView) {
2286            if(SC.browser.isIE){
2287              if (this._lastMouseDownX !== evt.clientX && this._lastMouseDownY !== evt.clientY) {
2288                this._mouseDownView.tryToPerform('mouseDragged', evt);
2289              }
2290            }
2291            else {
2292              this._mouseDownView.tryToPerform('mouseDragged', evt);
2293            }
2294          }
2295        }
2296     }, this);
2297   },
2298 
2299   // These event handlers prevent default file handling, and enable the dataDrag API.
2300   /** @private The dragenter event comes from the browser when a data-ful drag enters any element. */
2301   dragenter: function(evt) {
2302     SC.run(function() { this._dragenter(evt); }, this);
2303   },
2304 
2305   /** @private */
2306   _dragenter: function(evt) {
2307     if (!this._dragCounter) {
2308       this._dragCounter = 1;
2309     }
2310     else this._dragCounter++;
2311     return this._dragover(evt);
2312   },
2313 
2314   /** @private The dragleave event comes from the browser when a data-ful drag leaves any element. */
2315   dragleave: function(evt) {
2316     SC.run(function() { this._dragleave(evt); }, this);
2317   },
2318 
2319   /** @private */
2320   _dragleave: function(evt) {
2321     this._dragCounter--;
2322     var ret = this._dragover(evt);
2323     return ret;
2324   },
2325   /** @private
2326     Dragleave doesn't fire reliably in all browsers, so this method forces it (scheduled below). Note
2327     that, being scheduled via SC.Timer, this method is already in a run loop.
2328   */
2329   _forceDragLeave: function() {
2330     // Give it another runloop to ensure that we're not in the middle of a drag.
2331     this.invokeLast(function() {
2332       if (this._dragCounter === 0) return;
2333       this._dragCounter = 0;
2334       var evt = this._lastDraggedEvt;
2335       this._dragover(evt);
2336     });
2337   },
2338 
2339   /** @private This event fires continuously while the dataful drag is over the document. */
2340   dragover: function(evt) {
2341     SC.run(function() { this._dragover(evt); }, this);
2342   },
2343 
2344   /** @private */
2345   _dragover: function(evt) {
2346     // If it's a file being dragged, prevent the default (leaving the app and opening the file).
2347     if (evt.dataTransfer.types && (evt.dataTransfer.types.contains('Files') || evt.dataTransfer.types.contains('text/uri-list'))) {
2348       evt.preventDefault();
2349       evt.stopPropagation();
2350       // Set the default drag effect to 'none'. Views may reverse this if they wish.
2351       evt.dataTransfer.dropEffect = 'none';
2352     }
2353 
2354     // Walk the responder chain, alerting anyone that would like to know.
2355     var ld = this._lastDraggedOver || [], nd = [], loc, len,
2356         view = this.targetViewForEvent(evt);
2357 
2358     // Build the responder chain, starting with the view's target and (presumably) moving
2359     // up through parentViews to the pane.
2360     while (view && (view !== this)) {
2361       nd.push(view);
2362       view = view.get('nextResponder');
2363     }
2364 
2365     // Invalidate the force-drag-leave timer, if we have one set up.
2366     if (this._dragLeaveTimer) this._dragLeaveTimer.invalidate();
2367 
2368     // If this is our final drag event then we've left the document and everybody gets a
2369     // dataDragExited.
2370     if (this._dragCounter === 0) {
2371       for (loc = 0, len = nd.length; loc < len; loc++) {
2372         view = nd[loc];
2373         view.tryToPerform('dataDragExited', evt);
2374       }
2375       this._lastDraggedOver = this._lastDraggedEvt = this._dragLeaveTimer = null;
2376     }
2377     // Otherwise, we process the responder chain normally, ignoring dragleaves.
2378     // (We skip dragleave events because they are sent after the adjacent dragenter event; checking
2379     // through both stacks would result in views being exited, re-entered and re-exited each time.
2380     // As a consequence, views are left ignorant of a very small number of dragleave events; those
2381     // shouldn't end up being the crucial just-before-drop events, though, so they should be of no
2382     // consequence.)
2383     else if (evt.type !== 'dragleave') {
2384       // First, exit views that are no longer part of the responder chain, child to parent.
2385       for (loc = 0, len = ld.length; loc < len; loc++) {
2386         view = ld[loc];
2387         if (nd.indexOf(view) === -1) {
2388           view.tryToPerform('dataDragExited', evt);
2389         }
2390       }
2391       // Next, enter views that have just joined the responder chain, parent to child.
2392       for (loc = nd.length - 1; loc >= 0; loc--) {
2393         view = nd[loc];
2394         if (ld.indexOf(view) === -1) {
2395           view.tryToPerform('dataDragEntered', evt);
2396         }
2397       }
2398       // Finally, send hover events to everybody.
2399       for (loc = 0, len = nd.length; loc < len; loc++) {
2400         view = nd[loc];
2401         view.tryToPerform('dataDragHovered', evt);
2402       }
2403       this._lastDraggedOver = nd;
2404       this._lastDraggedEvt = evt;
2405       // For browsers that don't reliably call a dragleave for every dragenter, we have a timer fallback.
2406       this._dragLeaveTimer = SC.Timer.schedule({ target: this, action: '_forceDragLeave', interval: 300 });
2407     }
2408   },
2409 
2410   /** @private This event is called if the most recent dragover event returned with a non-"none" dropEffect. */
2411   drop: function(evt) {
2412     SC.run(function() { this._drop(evt); }, this);
2413   },
2414 
2415   /** @private */
2416   _drop: function(evt) {
2417     // If it's a file being dragged, prevent the default (leaving the app and opening the file).
2418     if (evt.dataTransfer.types && (evt.dataTransfer.types.contains('Files') || evt.dataTransfer.types.contains('text/uri-list'))) {
2419       evt.preventDefault();
2420       evt.stopPropagation();
2421       // Set the default drag effect to 'none'. Views may reverse this if they wish.
2422       evt.dataTransfer.dropEffect = 'none';
2423     }
2424 
2425     // Bubble up the responder chain until we have a successful responder.
2426     var ld = this._lastDraggedOver || [], nd = [], loc, len,
2427         view = this.targetViewForEvent(evt);
2428 
2429     // First collect all the responding view starting with the target view from the given drag event.
2430     while (view && (view !== this)) {
2431       nd.push(view);
2432       view = view.get('nextResponder');
2433     }
2434     // Next, exit views that are no longer part of the responding chain. (This avoids the pixel-wide
2435     // edge case where a drop event fires on a new view without a final dragover event.)
2436     for (loc = 0, len = ld.length; loc < len; loc++) {
2437       view = ld[loc];
2438       if (nd.indexOf(view) === -1) {
2439         view.tryToPerform('dataDragExited', evt);
2440       }
2441     }
2442     // Next, bubble the drop event itself until we find someone that successfully responds.
2443     for (loc = 0, len = nd.length; loc < len; loc++) {
2444       view = nd[loc];
2445       if (view.tryToPerform('dataDragDropped', evt)) break;
2446     }
2447     // Finally, notify all interested views that the drag is dead and gone.
2448     for (loc = 0, len = nd.length; loc < len; loc++) {
2449       view = nd[loc];
2450       view.tryToPerform('dataDragExited', evt);
2451     }
2452 
2453     // Reset caches and counters.
2454     this._lastDraggedOver = null;
2455     this._lastDraggedAt = null;
2456     this._dragCounter = 0;
2457     if (this._dragLeaveTimer) this._dragLeaveTimer.invalidate();
2458     this._dragLeaveTimer = null;
2459   },
2460 
2461 
2462   // these methods are used to prevent unnecessary text-selection in IE,
2463   // there could be some more work to improve this behavior and make it
2464   // a bit more useful; right now it's just to prevent bugs when dragging
2465   // and dropping.
2466 
2467   _mouseCanDrag: YES,
2468 
2469   selectstart: function(evt) {
2470     var targetView = this.targetViewForEvent(evt),
2471         result = this.sendEvent('selectStart', evt, targetView);
2472 
2473     // If the target view implements mouseDragged, then we want to ignore the
2474     // 'selectstart' event.
2475     if (targetView && targetView.respondsTo('mouseDragged')) {
2476       return (result !==null ? YES: NO) && !this._mouseCanDrag;
2477     } else {
2478       return (result !==null ? YES: NO);
2479     }
2480   },
2481 
2482   drag: function() { return false; },
2483 
2484   contextmenu: function(evt) {
2485     var view = this.targetViewForEvent(evt),
2486       ret;
2487 
2488     // Determine if any views took responsibility for the event.
2489     view = this.sendEvent('contextMenu', evt, view);
2490     ret = view ? evt.hasCustomEventHandling : YES;
2491 
2492     return ret;
2493   },
2494 
2495   // ..........................................................
2496   // ANIMATION HANDLING
2497   //
2498 
2499   /* @private Handler for animationstart events. */
2500   animationstart: function (evt) {
2501     var view = this.targetViewForEvent(evt);
2502     this.sendEvent('animationDidStart', evt, view);
2503 
2504     return view ? evt.hasCustomEventHandling : YES;
2505   },
2506 
2507   /* @private Handler for animationiteration events. */
2508   animationiteration: function (evt) {
2509     var view = this.targetViewForEvent(evt);
2510     this.sendEvent('animationDidIterate', evt, view);
2511 
2512     return view ? evt.hasCustomEventHandling : YES;
2513   },
2514 
2515   /* @private Handler for animationend events. */
2516   animationend: function (evt) {
2517     var view = this.targetViewForEvent(evt);
2518     this.sendEvent('animationDidEnd', evt, view);
2519 
2520     return view ? evt.hasCustomEventHandling : YES;
2521   },
2522 
2523   /* @private Handler for transitionend events. */
2524   transitionend: function (evt) {
2525     var view = this.targetViewForEvent(evt);
2526     this.sendEvent('transitionDidEnd', evt, view);
2527 
2528     return view ? evt.hasCustomEventHandling : YES;
2529   }
2530 
2531 });
2532 
2533 /*
2534   Invoked when the document is ready, but before main is called.  Creates
2535   an instance and sets up event listeners as needed.
2536 */
2537 SC.ready(SC.RootResponder, SC.RootResponder.ready = function () {
2538   var r;
2539 
2540   r = SC.RootResponder.responder = SC.RootResponder.create();
2541   r.setup();
2542 });
2543