Upstream version 11.39.266.0
[platform/framework/web/crosswalk.git] / src / third_party / trace-viewer / third_party / tvcm / src / tvcm / key_event_manager.html
1 <!DOCTYPE html>
2 <!--
3 Copyright (c) 2014 The Chromium Authors. All rights reserved.
4 Use of this source code is governed by a BSD-style license that can be
5 found in the LICENSE file.
6 -->
7 <link rel="import" href="/tvcm/guid.html">
8 <script>
9 'use strict';
10
11 tvcm.exportTo('tvcm', function() {
12
13   /**
14    * KeyEventManager avoids leaks when listening for keys.
15    *
16    * A common but leaky pattern is:
17    *   document.addEventListener('key*', function().bind(this))
18    * This leaks.
19    *
20    * Instead do this:
21    *   KeyEventManager.instance.addListener('keyDown', func, this);
22    *
23    * This will not leak. BUT, note, if "this" is not attached to the document,
24    * it will NOT receive input events.
25    *
26    * Conceptually, KeyEventManager works by making the this refrence "weak",
27    * which is actually accomplished by putting a guid on the thisArg. When keys
28    * are received, we look for elements with that guid and dispatch the keys to
29    * them.
30    */
31   function KeyEventManager(opt_document) {
32     this.document_ = opt_document || document;
33     if (KeyEventManager.instance)
34       throw new Error('KeyEventManager is a singleton.');
35     this.onEvent_ = this.onEvent_.bind(this);
36     this.document_.addEventListener('keydown', this.onEvent_);
37     this.document_.addEventListener('keypress', this.onEvent_);
38     this.document_.addEventListener('keyup', this.onEvent_);
39     this.listeners_ = [];
40   }
41   KeyEventManager.instance = undefined;
42
43   document.head.addEventListener('tvcm-unittest-will-run', function() {
44     if (KeyEventManager.instance) {
45       KeyEventManager.instance.destroy();
46       KeyEventManager.instance = undefined;
47     }
48     KeyEventManager.instance = new KeyEventManager();
49   });
50
51   KeyEventManager.prototype = {
52     addListener: function(type, handler, thisArg) {
53       if (!thisArg.keyEventManagerGuid_) {
54         thisArg.keyEventManagerGuid_ = tvcm.GUID.allocate();
55         thisArg.keyEventManagerRefCount_ = 0;
56       }
57       thisArg.classList.add('key-event-manager-target');
58       thisArg.keyEventManagerRefCount_++;
59
60       var guid = thisArg.keyEventManagerGuid_;
61       this.listeners_.push({
62         guid: guid,
63         type: type,
64         handler: handler
65       });
66     },
67
68     onEvent_: function(event) {
69       // This does standard DOM event propagation of the given event, but using
70       // guids to locate the thisArg for each listener. See event_target.js for
71       // notes on how this works.
72       var preventDefaultState = undefined;
73       var stopPropagationCalled = false;
74
75       var oldPreventDefault = event.preventDefault;
76       event.preventDefault = function() {
77         preventDefaultState = false;
78         oldPreventDefault.call(this);
79       };
80
81       var oldStopPropagation = event.stopPropagation;
82       event.stopPropagation = function() {
83         stopPropagationCalled = true;
84         oldStopPropagation.call(this);
85       };
86
87       event.stopImmediatePropagation = function() {
88         throw new Error('Not implemented');
89       };
90
91       var possibleThisArgs = this.document_.querySelectorAll(
92           '.key-event-manager-target');
93       var possibleThisArgsByGUID = {};
94       for (var i = 0; i < possibleThisArgs.length; i++) {
95         possibleThisArgsByGUID[possibleThisArgs[i].keyEventManagerGuid_] =
96             possibleThisArgs[i];
97       }
98
99       // We need to copy listeners_ and verify the thisArgs exists on each loop
100       // iteration because the event callbacks can change the DOM and listener
101       // list.
102       var listeners = this.listeners_.concat();
103       var type = event.type;
104       var prevented = 0;
105       for (var i = 0; i < listeners.length; i++) {
106         var listener = listeners[i];
107         if (listener.type !== type)
108           continue;
109         // thisArg went away.
110         var thisArg = possibleThisArgsByGUID[listener.guid];
111         if (!thisArg)
112           continue;
113
114         var handler = listener.handler;
115         if (handler.handleEvent)
116           prevented |= handler.handleEvent.call(handler, event) === false;
117         else
118           prevented |= handler.call(thisArg, event) === false;
119         if (stopPropagationCalled)
120           break;
121       }
122
123       // We want to return false if preventDefaulted, or one of the handlers
124       // return false. But otherwise, we want to return undefiend.
125       return !prevented && preventDefaultState;
126     },
127
128     removeListener: function(type, handler, thisArg) {
129       if (thisArg.keyEventManagerGuid_ === undefined)
130         throw new Error('Was not registered with KeyEventManager');
131       if (thisArg.keyEventManagerRefCount_ === 0)
132         throw new Error('No events were registered on the provided thisArg');
133       for (var i = 0; i < this.listeners_.length; i++) {
134         var listener = this.listeners_[i];
135         if (listener.type == type &&
136             listener.handler == handler &&
137             listener.guid == thisArg.keyEventManagerGuid_) {
138           thisArg.keyEventManagerRefCount_--;
139           if (thisArg.keyEventManagerRefCount_ === 0)
140             thisArg.classList.remove('key-event-manager-target');
141           this.listeners_.splice(i, 1);
142           return;
143         }
144       }
145       throw new Error('Listener not found');
146     },
147
148     destroy: function() {
149       this.listeners_.splice(0);
150       this.document_.removeEventListener('keydown', this.onEvent_);
151       this.document_.removeEventListener('keypress', this.onEvent_);
152       this.document_.removeEventListener('keyup', this.onEvent_);
153     },
154
155     dispatchFakeEvent: function(type, args) {
156       var e = new KeyboardEvent(type, args);
157       return KeyEventManager.instance.onEvent_.call(undefined, e);
158     }
159   };
160
161   KeyEventManager.instance = new KeyEventManager();
162
163   return {
164     KeyEventManager: KeyEventManager
165   };
166 });
167 </script>