1<!DOCTYPE html> 2<!-- 3Copyright (c) 2014 The Chromium Authors. All rights reserved. 4Use of this source code is governed by a BSD-style license that can be 5found in the LICENSE file. 6--> 7<link rel="import" href="/tracing/base/guid.html"> 8<link rel="import" href="/tracing/ui/base/hot_key.html"> 9 10<polymer-element name="tv-ui-b-hotkey-controller"> 11 <script> 12 'use strict'; 13 Polymer({ 14 created: function() { 15 this.isAttached_ = false; 16 this.globalMode_ = false; 17 this.slavedToParentController_ = undefined; 18 this.curHost_ = undefined; 19 this.childControllers_ = []; 20 21 this.bubblingKeyDownHotKeys_ = {}; 22 this.capturingKeyDownHotKeys_ = {}; 23 this.bubblingKeyPressHotKeys_ = {}; 24 this.capturingKeyPressHotKeys_ = {}; 25 26 this.onBubblingKeyDown_ = this.onKey_.bind(this, false); 27 this.onCapturingKeyDown_ = this.onKey_.bind(this, true); 28 this.onBubblingKeyPress_ = this.onKey_.bind(this, false); 29 this.onCapturingKeyPress_ = this.onKey_.bind(this, true); 30 }, 31 32 attached: function() { 33 this.isAttached_ = true; 34 35 var host = this.findHost_(); 36 if (host.__hotkeyController) 37 throw new Error('Multiple hotkey controllers attached to this host'); 38 39 host.__hotkeyController = this; 40 this.curHost_ = host; 41 42 var parentElement; 43 if (host.parentElement) 44 parentElement = host.parentElement; 45 else 46 parentElement = host.parentNode.host; 47 var parentController = tr.b.getHotkeyControllerForElement( 48 parentElement); 49 50 if (parentController) { 51 this.slavedToParentController_ = parentController; 52 parentController.addChildController_(this); 53 return; 54 } 55 56 host.addEventListener('keydown', this.onBubblingKeyDown_, false); 57 host.addEventListener('keydown', this.onCapturingKeyDown_, true); 58 host.addEventListener('keypress', this.onBubblingKeyPress_, false); 59 host.addEventListener('keypress', this.onCapturingKeyPress_, true); 60 }, 61 62 detached: function() { 63 this.isAttached_ = false; 64 65 var host = this.curHost_; 66 if (!host) 67 return; 68 69 delete host.__hotkeyController; 70 this.curHost_ = undefined; 71 72 if (this.slavedToParentController_) { 73 this.slavedToParentController_.removeChildController_(this); 74 this.slavedToParentController_ = undefined; 75 return; 76 } 77 78 host.removeEventListener('keydown', this.onBubblingKeyDown_, false); 79 host.removeEventListener('keydown', this.onCapturingKeyDown_, true); 80 host.removeEventListener('keypress', this.onBubblingKeyPress_, false); 81 host.removeEventListener('keypress', this.onCapturingKeyPress_, true); 82 }, 83 84 addChildController_: function(controller) { 85 var i = this.childControllers_.indexOf(controller); 86 if (i !== -1) 87 throw new Error('Controller already registered'); 88 this.childControllers_.push(controller); 89 }, 90 91 removeChildController_: function(controller) { 92 var i = this.childControllers_.indexOf(controller); 93 if (i === -1) 94 throw new Error('Controller not registered'); 95 this.childControllers_.splice(i, 1); 96 return controller; 97 }, 98 99 getKeyMapForEventType_: function(eventType, useCapture) { 100 if (eventType === 'keydown') { 101 if (!useCapture) 102 return this.bubblingKeyDownHotKeys_; 103 else 104 return this.capturingKeyDownHotKeys_; 105 } else if (eventType === 'keypress') { 106 if (!useCapture) 107 return this.bubblingKeyPressHotKeys_; 108 else 109 return this.capturingKeyPressHotKeys_; 110 } else { 111 throw new Error('Unsupported key event'); 112 } 113 }, 114 115 addHotKey: function(hotKey) { 116 if (!(hotKey instanceof tr.ui.b.HotKey)) 117 throw new Error('hotKey must be a tr.ui.b.HotKey'); 118 119 var keyMap = this.getKeyMapForEventType_( 120 hotKey.eventType, hotKey.useCapture); 121 122 for (var i = 0; i < hotKey.keyCodes.length; i++) { 123 var keyCode = hotKey.keyCodes[i]; 124 if (keyMap[keyCode]) 125 throw new Error('Key is already bound for keyCode=' + keyCode); 126 } 127 128 for (var i = 0; i < hotKey.keyCodes.length; i++) { 129 var keyCode = hotKey.keyCodes[i]; 130 keyMap[keyCode] = hotKey; 131 } 132 return hotKey; 133 }, 134 135 removeHotKey: function(hotKey) { 136 if (!(hotKey instanceof tr.ui.b.HotKey)) 137 throw new Error('hotKey must be a tr.ui.b.HotKey'); 138 139 var keyMap = this.getKeyMapForEventType_( 140 hotKey.eventType, hotKey.useCapture); 141 142 for (var i = 0; i < hotKey.keyCodes.length; i++) { 143 var keyCode = hotKey.keyCodes[i]; 144 if (!keyMap[keyCode]) 145 throw new Error('Key is not bound for keyCode=' + keyCode); 146 keyMap[keyCode] = hotKey; 147 } 148 for (var i = 0; i < hotKey.keyCodes.length; i++) { 149 var keyCode = hotKey.keyCodes[i]; 150 delete keyMap[keyCode]; 151 } 152 return hotKey; 153 }, 154 155 get globalMode() { 156 return this.globalMode_; 157 }, 158 159 set globalMode(globalMode) { 160 var wasAttached = this.isAttached_; 161 if (wasAttached) 162 this.detached(); 163 this.globalMode_ = !!globalMode; 164 if (wasAttached) 165 this.attached(); 166 }, 167 168 get topmostConroller_() { 169 if (this.slavedToParentController_) 170 return this.slavedToParentController_.topmostConroller_; 171 return this; 172 }, 173 174 childRequestsGeneralFocus: function(child) { 175 var topmost = this.topmostConroller_; 176 if (topmost.curHost_) { 177 if (topmost.curHost_.hasAttribute('tabIndex')) { 178 topmost.curHost_.focus(); 179 } else { 180 if (document.activeElement) 181 document.activeElement.blur(); 182 } 183 } else { 184 if (document.activeElement) 185 document.activeElement.blur(); 186 } 187 }, 188 189 childRequestsBlur: function(child) { 190 child.blur(); 191 192 var topmost = this.topmostConroller_; 193 if (topmost.curHost_) { 194 topmost.curHost_.focus(); 195 } 196 }, 197 198 findHost_: function() { 199 if (this.globalMode_) { 200 return document.body; 201 } else { 202 if (this.parentElement) 203 return this.parentElement; 204 205 var node = this; 206 while (node.parentNode) { 207 node = node.parentNode; 208 } 209 return node.host; 210 } 211 }, 212 213 appendMatchingHotKeysTo_: function(matchedHotKeys, 214 useCapture, e) { 215 var localKeyMap = this.getKeyMapForEventType_(e.type, useCapture); 216 var localHotKey = localKeyMap[e.keyCode]; 217 if (localHotKey) 218 matchedHotKeys.push(localHotKey); 219 220 for (var i = 0; i < this.childControllers_.length; i++) { 221 var controller = this.childControllers_[i]; 222 controller.appendMatchingHotKeysTo_(matchedHotKeys, 223 useCapture, e); 224 } 225 }, 226 227 onKey_: function(useCapture, e) { 228 // Keys dispatched to INPUT elements still bubble, even when they're 229 // handled. So, skip any events that targeted the input element. 230 if (useCapture == false && e.path[0].tagName == 'INPUT') 231 return; 232 233 var sortedControllers; 234 235 var matchedHotKeys = []; 236 this.appendMatchingHotKeysTo_(matchedHotKeys, useCapture, e); 237 238 if (matchedHotKeys.length === 0) 239 return false; 240 241 if (matchedHotKeys.length > 1) { 242 // TODO(nduca): To do support for coddling hotKeys, we need to 243 // sort the listeners by their capturing/bubbling order and then pick 244 // the one that would topologically win the tie, per DOM dispatch rules. 245 throw new Error('More than one hotKey is currently unsupported'); 246 } 247 248 249 var hotKey = matchedHotKeys[0]; 250 251 var prevented = 0; 252 prevented |= hotKey.call(e); 253 254 // We want to return false if preventDefaulted, or one of the handlers 255 // return false. But otherwise, we want to return undefiend. 256 return !prevented && e.defaultPrevented; 257 } 258 }); 259 </script> 260</polymer-element> 261<script> 262'use strict'; 263 264tr.exportTo('tr.b', function() { 265 266 function getHotkeyControllerForElement(refElement) { 267 var curElement = refElement; 268 while (curElement) { 269 if (curElement.tagName === 'tv-ui-b-hotkey-controller') 270 return curElement; 271 272 if (curElement.__hotkeyController) 273 return curElement.__hotkeyController; 274 275 if (curElement.parentElement) { 276 curElement = curElement.parentElement; 277 continue; 278 } 279 280 // Probably inside a shadow 281 curElement = findHost(curElement); 282 } 283 return undefined; 284 } 285 286 function findHost(initialNode) { 287 var node = initialNode; 288 while (node.parentNode) { 289 node = node.parentNode; 290 } 291 return node.host; 292 } 293 294 return { 295 getHotkeyControllerForElement: getHotkeyControllerForElement 296 }; 297}); 298</script> 299