Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2017 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 Resources.FrameSelector = class extends Common.Object { | |
|
dgozman
2017/03/24 17:06:54
Let's think about reusing this in Console right no
eostroukhov
2017/04/04 23:47:17
Acknowledged.
| |
| 6 constructor() { | |
| 7 super(); | |
| 8 /** @type {!Map<string, !Resources._FrameTreeElement>} */ | |
| 9 this._treeElementForFrameId = new Map(); | |
| 10 | |
| 11 this._button = createElement('button'); | |
| 12 this._button.className = 'frame-selector'; | |
| 13 this._button.appendChild(UI.Icon.create('largeicon-navigator-frame')); | |
| 14 this._nameLabel = this._button.createChild('span', 'frame-name'); | |
| 15 this._button.appendChild(UI.Icon.create('smallicon-dropdown-arrow')); | |
| 16 | |
| 17 this._button.addEventListener('click', () => this._showGlassPane()); | |
| 18 | |
| 19 this._tree = new UI.TreeOutlineInShadow(); | |
| 20 this._tree.element.classList.add('frame-selector-popup'); | |
| 21 this._tree.element.classList.add('filter-all'); | |
| 22 | |
| 23 var eventTarget = this._tree.contentElement; | |
| 24 eventTarget.addEventListener('click', () => this._commitSelection()); | |
| 25 eventTarget.addEventListener('mouseleave', () => this._onmouseleave(), false ); | |
| 26 eventTarget.addEventListener('mousemove', event => this._onmousemove(event), false); | |
| 27 eventTarget.addEventListener('keydown', event => this._keyDown(event)); | |
| 28 | |
| 29 this._button.ownerDocument.defaultView.addEventListener('blur', () => this._ hideGlassPane(null)); | |
| 30 | |
| 31 this._glassPane = new UI.GlassPane(); | |
| 32 this._glassPane.setAnchorBehavior(UI.GlassPane.AnchorBehavior.PreferBottom); | |
| 33 this._glassPane.setSizeBehavior(UI.GlassPane.SizeBehavior.SetExactWidthMaxHe ight); | |
| 34 this._glassPane.setSetOutsideClickCallback(event => this._hideGlassPane(/** @type {!Node} */ (event.target))); | |
| 35 | |
| 36 UI.createShadowRootWithCoreStyles(this._glassPane.contentElement, 'resources /frameSelector.css') | |
| 37 .appendChild(this._tree.element); | |
| 38 | |
| 39 // Suppress the popup once - e.g. because user clicked on the button while t he popup is visible | |
| 40 // which should not instantly reopen popup. | |
| 41 this._suppressNextPopup = false; | |
| 42 | |
| 43 /** @type {?Resources._FrameTreeElement} */ | |
| 44 this._previousHoveredElement = null; | |
| 45 | |
| 46 /** @type {?Element} */ | |
| 47 this._storedFocus = null; | |
| 48 | |
| 49 /** @type {?SDK.ResourceTreeFrame} */ | |
| 50 this._selectedFrame = null; | |
| 51 | |
| 52 /** @type {!Array<?Element>} */ | |
| 53 this._focusedElementsStack = []; | |
| 54 | |
| 55 this._initialize(); | |
| 56 } | |
| 57 | |
| 58 _initialize() { | |
| 59 function addListener(eventType, handler, target) { | |
|
dgozman
2017/03/24 17:06:54
Just spell it out 3 times - makes it easier to sea
eostroukhov
2017/04/04 23:47:17
Done, but I did that lambda so I don't have to rep
| |
| 60 SDK.targetManager.addModelListener(SDK.ResourceTreeModel, eventType, event => handler.call(target, event.data)); | |
| 61 } | |
| 62 addListener(SDK.ResourceTreeModel.Events.FrameAdded, this.frameAdded, this); | |
| 63 addListener(SDK.ResourceTreeModel.Events.FrameNavigated, this.frameNavigated , this); | |
| 64 addListener(SDK.ResourceTreeModel.Events.FrameDetached, this.frameDetached, this); | |
| 65 | |
| 66 var mainTarget = SDK.targetManager.mainTarget(); | |
|
dgozman
2017/03/24 17:06:54
Let's make this multitarget right away.
eostroukhov
2017/04/04 23:47:17
Please take a look.
| |
| 67 var resourceTreeModel = mainTarget && mainTarget.hasDOMCapability() && SDK.R esourceTreeModel.fromTarget(mainTarget); | |
|
dgozman
2017/03/24 17:06:54
You don't have to check capability anymore: mainTa
eostroukhov
2017/04/04 23:47:17
Done.
| |
| 68 var mainFrame = resourceTreeModel.mainFrame; | |
| 69 if (mainFrame) | |
| 70 this.populateFrame(mainFrame); | |
| 71 | |
| 72 this.selectFrame(null); | |
| 73 } | |
| 74 | |
| 75 /** | |
| 76 * @param {!SDK.ResourceTreeFrame} frame | |
| 77 * @returns {?SDK.ResourceTreeFrame} | |
| 78 */ | |
| 79 static _getParentFrame(frame) { | |
| 80 var parentFrame = frame.parentFrame; | |
| 81 if (parentFrame) | |
| 82 return parentFrame; | |
| 83 var parentTarget = frame.target().parentTarget(); | |
| 84 while (parentTarget && !parentTarget.hasDOMCapability()) | |
| 85 parentTarget = parentTarget.parentTarget(); | |
| 86 if (!parentTarget) | |
| 87 return null; | |
| 88 var model = SDK.ResourceTreeModel.fromTarget(parentTarget); | |
| 89 return model.mainFrame; | |
| 90 } | |
| 91 | |
| 92 /** | |
| 93 * @returns {!Element} | |
| 94 */ | |
| 95 get element() { | |
| 96 return this._button; | |
| 97 } | |
| 98 | |
| 99 /** | |
| 100 * @param {!Element} root | |
| 101 * @return {function()} Call to remove the listener | |
| 102 */ | |
| 103 trackFocus(root) { | |
| 104 var listener = (event => { | |
| 105 this._focusedElementsStack = [event.target.ownerDocument.deepActiveElement (), this._focusedElementsStack[0]]; | |
| 106 }); | |
| 107 root.element.addEventListener('focusin', listener); | |
| 108 return function() { | |
| 109 root.removeEventListener('focusin', listener); | |
| 110 }; | |
| 111 } | |
| 112 | |
| 113 /** | |
| 114 * @return {?SDK.ResourceTreeFrame} frame | |
| 115 */ | |
| 116 selectedFrame() { | |
| 117 return this._selectedFrame; | |
| 118 } | |
| 119 | |
| 120 selectRootFrame() { | |
| 121 var firstElement = this._tree.firstChild(); | |
| 122 this.selectFrame(firstElement && firstElement.frame); | |
| 123 } | |
| 124 | |
| 125 _showGlassPane() { | |
| 126 var doc = this._button.ownerDocument; | |
| 127 if (!doc || this._suppressNextPopup) { | |
| 128 this._suppressNextPopup = false; | |
| 129 return; | |
| 130 } | |
| 131 | |
| 132 // Restored focus should not go to button but to previously focused componen t | |
| 133 this._storedFocus = this._focusedElementsStack[0]; | |
| 134 if (this._storedFocus === this._button) | |
| 135 this._storedFocus = this._focusedElementsStack[1]; | |
| 136 | |
| 137 var anchorBox = this._button.boxInWindow(window); | |
| 138 // Account for border | |
| 139 anchorBox.x = 0; | |
| 140 anchorBox.width -= 4; | |
| 141 anchorBox.height -= 8; | |
| 142 var width = Math.max(300, anchorBox.width); | |
| 143 this._glassPane.setMaxContentSize(new UI.Size(width, 300)); | |
| 144 // Position popup relative to arrow dowm and not just below the button | |
| 145 // anchorBox.height = childBox.y + childBox.height - anchorBox.y; | |
|
dgozman
2017/03/24 17:06:54
Commented code.
eostroukhov
2017/04/04 23:47:17
Oops. Fixed!
| |
| 146 this._glassPane.setContentAnchorBox(anchorBox); | |
| 147 this._glassPane.show(doc); | |
| 148 this._tree.firstChild().expand(); | |
| 149 this._tree.focus(); | |
| 150 } | |
| 151 | |
| 152 /** | |
| 153 * @param {?Node} targetNode | |
| 154 */ | |
| 155 _hideGlassPane(targetNode) { | |
| 156 this._suppressNextPopup = this._button.isSelfOrAncestor(targetNode); | |
| 157 this._glassPane.hide(); | |
| 158 if (this._storedFocus) { | |
| 159 this._storedFocus.focus(); | |
| 160 this._storedFocus = null; | |
| 161 } | |
| 162 } | |
| 163 | |
| 164 /** | |
| 165 * @param {!Event} event | |
| 166 */ | |
| 167 _keyDown(event) { | |
| 168 if (!isEscKey(event) && !isEnterKey(event)) | |
| 169 return; | |
| 170 if (isEnterKey(event)) | |
| 171 this._commitSelection(); | |
| 172 this._hideGlassPane(null); | |
| 173 event.consume(); | |
| 174 } | |
| 175 | |
| 176 /** | |
| 177 * @param {!SDK.ResourceTreeFrame} frame | |
| 178 */ | |
| 179 frameAdded(frame) { | |
| 180 var parentFrame = Resources.FrameSelector._getParentFrame(frame); | |
| 181 var parentTreeElement = parentFrame ? this._treeElementForFrameId.get(parent Frame.id) : this._tree; | |
| 182 if (!parentTreeElement) { | |
| 183 console.warn('No frame to route ' + frame.url + ' to.'); | |
| 184 return; | |
| 185 } | |
| 186 | |
| 187 var frameTreeElement = new Resources._FrameTreeElement(frame); | |
| 188 this._treeElementForFrameId.set(frame.id, frameTreeElement); | |
| 189 parentTreeElement.appendChild(frameTreeElement); | |
| 190 } | |
| 191 | |
| 192 /** | |
| 193 * @param {!SDK.ResourceTreeFrame} frame | |
| 194 */ | |
| 195 frameDetached(frame) { | |
| 196 var frameTreeElement = this._treeElementForFrameId.get(frame.id); | |
| 197 if (!frameTreeElement) | |
| 198 return; | |
| 199 | |
| 200 this._treeElementForFrameId.remove(frame.id); | |
| 201 if (frameTreeElement.parent) | |
| 202 frameTreeElement.parent.removeChild(frameTreeElement); | |
| 203 } | |
| 204 | |
| 205 /** | |
| 206 * @param {!SDK.ResourceTreeFrame} frame | |
| 207 */ | |
| 208 frameNavigated(frame) { | |
| 209 if (!Resources.FrameSelector._getParentFrame(frame)) | |
| 210 return; | |
| 211 var frameTreeElement = this._treeElementForFrameId.get(frame.id); | |
| 212 if (frameTreeElement) | |
| 213 frameTreeElement.frameNavigated(frame); | |
| 214 } | |
| 215 | |
| 216 reset() { | |
| 217 this._tree.removeChildren(); | |
| 218 this._treeElementForFrameId.clear(); | |
| 219 } | |
| 220 | |
| 221 /** | |
| 222 * @param {!SDK.ResourceTreeFrame} frame | |
| 223 */ | |
| 224 populateFrame(frame) { | |
| 225 this.frameAdded(frame); | |
| 226 for (var child of frame.childFrames) | |
| 227 this.populateFrame(child); | |
| 228 } | |
| 229 | |
| 230 /** | |
| 231 * @param {?SDK.ResourceTreeFrame} frame | |
| 232 */ | |
| 233 selectFrame(frame) { | |
| 234 this._selectedFrame = frame; | |
| 235 | |
| 236 var displayName = frame ? frame.displayName() : ''; | |
| 237 var frameName = frame ? frame.name : ''; | |
| 238 | |
| 239 this._nameLabel.textContent = frameName || displayName; | |
| 240 this._button.title = displayName; | |
| 241 this._hideGlassPane(null); | |
| 242 } | |
| 243 | |
| 244 _commitSelection() { | |
| 245 var selected = this._tree.selectedTreeElement; | |
| 246 var frame = selected && selected.frame; | |
| 247 this.selectFrame(frame); | |
| 248 this.dispatchEventToListeners(Resources.FrameSelector.Events.FrameSelected, frame); | |
| 249 } | |
| 250 | |
| 251 /** | |
| 252 * @param {!Event} event | |
| 253 */ | |
| 254 _onmousemove(event) { | |
| 255 var nodeUnderMouse = event.target; | |
| 256 if (!nodeUnderMouse) | |
| 257 return; | |
| 258 | |
| 259 var listNode = nodeUnderMouse.enclosingNodeOrSelfWithNodeName('li'); | |
| 260 if (!listNode) | |
| 261 return; | |
| 262 | |
| 263 var element = listNode.treeElement; | |
| 264 if (this._previousHoveredElement === element) | |
| 265 return; | |
| 266 | |
| 267 if (this._previousHoveredElement) { | |
| 268 this._previousHoveredElement.hovered = false; | |
| 269 this._previousHoveredElement = null; | |
| 270 } | |
| 271 | |
| 272 if (element instanceof Resources._FrameTreeElement) { | |
| 273 this._previousHoveredElement = element; | |
| 274 element.select(false, false); | |
| 275 element.hovered = true; | |
| 276 } | |
| 277 } | |
| 278 | |
| 279 _onmouseleave() { | |
| 280 if (this._previousHoveredElement) { | |
| 281 this._previousHoveredElement.hovered = false; | |
| 282 this._previousHoveredElement = null; | |
| 283 } | |
| 284 } | |
| 285 }; | |
| 286 | |
| 287 /** | |
| 288 * @enum {symbol} | |
| 289 */ | |
| 290 Resources.FrameSelector.Events = { | |
| 291 FrameSelected: Symbol('FrameSelected') | |
| 292 }; | |
| 293 | |
| 294 Resources._FrameTreeElement = class extends UI.TreeElement { | |
|
dgozman
2017/03/24 17:06:54
Why do we need a treeoutline? Let's reuse Filtered
eostroukhov
2017/04/04 23:47:17
Done.
| |
| 295 /** | |
| 296 * @param {!SDK.ResourceTreeFrame} frame | |
| 297 */ | |
| 298 constructor(frame) { | |
| 299 super('', false); | |
| 300 this.frame = frame; | |
| 301 this.frameNavigated(frame); | |
| 302 } | |
| 303 | |
| 304 frameNavigated(frame) { | |
| 305 this.frame = frame; | |
| 306 this.title = frame.displayName(); | |
| 307 } | |
| 308 | |
| 309 get itemURL() { | |
|
dgozman
2017/03/24 17:06:54
Unused.
eostroukhov
2017/04/04 23:47:17
Done.
| |
| 310 return 'frame://' + encodeURI(this.titleAsText()); | |
| 311 } | |
| 312 | |
| 313 set hovered(hovered) { | |
| 314 if (hovered) { | |
| 315 this.listItemElement.classList.add('hovered'); | |
| 316 var domModel = SDK.DOMModel.fromTarget(this.frame.target()); | |
| 317 if (domModel) | |
| 318 domModel.highlightFrame(this.frame.id); | |
| 319 } else { | |
| 320 this.listItemElement.classList.remove('hovered'); | |
| 321 SDK.DOMModel.hideDOMNodeHighlight(); | |
| 322 } | |
| 323 } | |
| 324 }; | |
| OLD | NEW |