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