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 |