Index: third_party/WebKit/Source/devtools/front_end/resources/FrameSelector.js |
diff --git a/third_party/WebKit/Source/devtools/front_end/resources/FrameSelector.js b/third_party/WebKit/Source/devtools/front_end/resources/FrameSelector.js |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f68b8a38108c3d15030d97c8e15a86f4811a255c |
--- /dev/null |
+++ b/third_party/WebKit/Source/devtools/front_end/resources/FrameSelector.js |
@@ -0,0 +1,324 @@ |
+// Copyright 2017 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+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.
|
+ constructor() { |
+ super(); |
+ /** @type {!Map<string, !Resources._FrameTreeElement>} */ |
+ this._treeElementForFrameId = new Map(); |
+ |
+ this._button = createElement('button'); |
+ this._button.className = 'frame-selector'; |
+ this._button.appendChild(UI.Icon.create('largeicon-navigator-frame')); |
+ this._nameLabel = this._button.createChild('span', 'frame-name'); |
+ this._button.appendChild(UI.Icon.create('smallicon-dropdown-arrow')); |
+ |
+ this._button.addEventListener('click', () => this._showGlassPane()); |
+ |
+ this._tree = new UI.TreeOutlineInShadow(); |
+ this._tree.element.classList.add('frame-selector-popup'); |
+ this._tree.element.classList.add('filter-all'); |
+ |
+ var eventTarget = this._tree.contentElement; |
+ eventTarget.addEventListener('click', () => this._commitSelection()); |
+ eventTarget.addEventListener('mouseleave', () => this._onmouseleave(), false); |
+ eventTarget.addEventListener('mousemove', event => this._onmousemove(event), false); |
+ eventTarget.addEventListener('keydown', event => this._keyDown(event)); |
+ |
+ this._button.ownerDocument.defaultView.addEventListener('blur', () => this._hideGlassPane(null)); |
+ |
+ this._glassPane = new UI.GlassPane(); |
+ this._glassPane.setAnchorBehavior(UI.GlassPane.AnchorBehavior.PreferBottom); |
+ this._glassPane.setSizeBehavior(UI.GlassPane.SizeBehavior.SetExactWidthMaxHeight); |
+ this._glassPane.setSetOutsideClickCallback(event => this._hideGlassPane(/** @type {!Node} */ (event.target))); |
+ |
+ UI.createShadowRootWithCoreStyles(this._glassPane.contentElement, 'resources/frameSelector.css') |
+ .appendChild(this._tree.element); |
+ |
+ // Suppress the popup once - e.g. because user clicked on the button while the popup is visible |
+ // which should not instantly reopen popup. |
+ this._suppressNextPopup = false; |
+ |
+ /** @type {?Resources._FrameTreeElement} */ |
+ this._previousHoveredElement = null; |
+ |
+ /** @type {?Element} */ |
+ this._storedFocus = null; |
+ |
+ /** @type {?SDK.ResourceTreeFrame} */ |
+ this._selectedFrame = null; |
+ |
+ /** @type {!Array<?Element>} */ |
+ this._focusedElementsStack = []; |
+ |
+ this._initialize(); |
+ } |
+ |
+ _initialize() { |
+ 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
|
+ SDK.targetManager.addModelListener(SDK.ResourceTreeModel, eventType, event => handler.call(target, event.data)); |
+ } |
+ addListener(SDK.ResourceTreeModel.Events.FrameAdded, this.frameAdded, this); |
+ addListener(SDK.ResourceTreeModel.Events.FrameNavigated, this.frameNavigated, this); |
+ addListener(SDK.ResourceTreeModel.Events.FrameDetached, this.frameDetached, this); |
+ |
+ 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.
|
+ var resourceTreeModel = mainTarget && mainTarget.hasDOMCapability() && SDK.ResourceTreeModel.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.
|
+ var mainFrame = resourceTreeModel.mainFrame; |
+ if (mainFrame) |
+ this.populateFrame(mainFrame); |
+ |
+ this.selectFrame(null); |
+ } |
+ |
+ /** |
+ * @param {!SDK.ResourceTreeFrame} frame |
+ * @returns {?SDK.ResourceTreeFrame} |
+ */ |
+ static _getParentFrame(frame) { |
+ var parentFrame = frame.parentFrame; |
+ if (parentFrame) |
+ return parentFrame; |
+ var parentTarget = frame.target().parentTarget(); |
+ while (parentTarget && !parentTarget.hasDOMCapability()) |
+ parentTarget = parentTarget.parentTarget(); |
+ if (!parentTarget) |
+ return null; |
+ var model = SDK.ResourceTreeModel.fromTarget(parentTarget); |
+ return model.mainFrame; |
+ } |
+ |
+ /** |
+ * @returns {!Element} |
+ */ |
+ get element() { |
+ return this._button; |
+ } |
+ |
+ /** |
+ * @param {!Element} root |
+ * @return {function()} Call to remove the listener |
+ */ |
+ trackFocus(root) { |
+ var listener = (event => { |
+ this._focusedElementsStack = [event.target.ownerDocument.deepActiveElement(), this._focusedElementsStack[0]]; |
+ }); |
+ root.element.addEventListener('focusin', listener); |
+ return function() { |
+ root.removeEventListener('focusin', listener); |
+ }; |
+ } |
+ |
+ /** |
+ * @return {?SDK.ResourceTreeFrame} frame |
+ */ |
+ selectedFrame() { |
+ return this._selectedFrame; |
+ } |
+ |
+ selectRootFrame() { |
+ var firstElement = this._tree.firstChild(); |
+ this.selectFrame(firstElement && firstElement.frame); |
+ } |
+ |
+ _showGlassPane() { |
+ var doc = this._button.ownerDocument; |
+ if (!doc || this._suppressNextPopup) { |
+ this._suppressNextPopup = false; |
+ return; |
+ } |
+ |
+ // Restored focus should not go to button but to previously focused component |
+ this._storedFocus = this._focusedElementsStack[0]; |
+ if (this._storedFocus === this._button) |
+ this._storedFocus = this._focusedElementsStack[1]; |
+ |
+ var anchorBox = this._button.boxInWindow(window); |
+ // Account for border |
+ anchorBox.x = 0; |
+ anchorBox.width -= 4; |
+ anchorBox.height -= 8; |
+ var width = Math.max(300, anchorBox.width); |
+ this._glassPane.setMaxContentSize(new UI.Size(width, 300)); |
+ // Position popup relative to arrow dowm and not just below the button |
+ // 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!
|
+ this._glassPane.setContentAnchorBox(anchorBox); |
+ this._glassPane.show(doc); |
+ this._tree.firstChild().expand(); |
+ this._tree.focus(); |
+ } |
+ |
+ /** |
+ * @param {?Node} targetNode |
+ */ |
+ _hideGlassPane(targetNode) { |
+ this._suppressNextPopup = this._button.isSelfOrAncestor(targetNode); |
+ this._glassPane.hide(); |
+ if (this._storedFocus) { |
+ this._storedFocus.focus(); |
+ this._storedFocus = null; |
+ } |
+ } |
+ |
+ /** |
+ * @param {!Event} event |
+ */ |
+ _keyDown(event) { |
+ if (!isEscKey(event) && !isEnterKey(event)) |
+ return; |
+ if (isEnterKey(event)) |
+ this._commitSelection(); |
+ this._hideGlassPane(null); |
+ event.consume(); |
+ } |
+ |
+ /** |
+ * @param {!SDK.ResourceTreeFrame} frame |
+ */ |
+ frameAdded(frame) { |
+ var parentFrame = Resources.FrameSelector._getParentFrame(frame); |
+ var parentTreeElement = parentFrame ? this._treeElementForFrameId.get(parentFrame.id) : this._tree; |
+ if (!parentTreeElement) { |
+ console.warn('No frame to route ' + frame.url + ' to.'); |
+ return; |
+ } |
+ |
+ var frameTreeElement = new Resources._FrameTreeElement(frame); |
+ this._treeElementForFrameId.set(frame.id, frameTreeElement); |
+ parentTreeElement.appendChild(frameTreeElement); |
+ } |
+ |
+ /** |
+ * @param {!SDK.ResourceTreeFrame} frame |
+ */ |
+ frameDetached(frame) { |
+ var frameTreeElement = this._treeElementForFrameId.get(frame.id); |
+ if (!frameTreeElement) |
+ return; |
+ |
+ this._treeElementForFrameId.remove(frame.id); |
+ if (frameTreeElement.parent) |
+ frameTreeElement.parent.removeChild(frameTreeElement); |
+ } |
+ |
+ /** |
+ * @param {!SDK.ResourceTreeFrame} frame |
+ */ |
+ frameNavigated(frame) { |
+ if (!Resources.FrameSelector._getParentFrame(frame)) |
+ return; |
+ var frameTreeElement = this._treeElementForFrameId.get(frame.id); |
+ if (frameTreeElement) |
+ frameTreeElement.frameNavigated(frame); |
+ } |
+ |
+ reset() { |
+ this._tree.removeChildren(); |
+ this._treeElementForFrameId.clear(); |
+ } |
+ |
+ /** |
+ * @param {!SDK.ResourceTreeFrame} frame |
+ */ |
+ populateFrame(frame) { |
+ this.frameAdded(frame); |
+ for (var child of frame.childFrames) |
+ this.populateFrame(child); |
+ } |
+ |
+ /** |
+ * @param {?SDK.ResourceTreeFrame} frame |
+ */ |
+ selectFrame(frame) { |
+ this._selectedFrame = frame; |
+ |
+ var displayName = frame ? frame.displayName() : ''; |
+ var frameName = frame ? frame.name : ''; |
+ |
+ this._nameLabel.textContent = frameName || displayName; |
+ this._button.title = displayName; |
+ this._hideGlassPane(null); |
+ } |
+ |
+ _commitSelection() { |
+ var selected = this._tree.selectedTreeElement; |
+ var frame = selected && selected.frame; |
+ this.selectFrame(frame); |
+ this.dispatchEventToListeners(Resources.FrameSelector.Events.FrameSelected, frame); |
+ } |
+ |
+ /** |
+ * @param {!Event} event |
+ */ |
+ _onmousemove(event) { |
+ var nodeUnderMouse = event.target; |
+ if (!nodeUnderMouse) |
+ return; |
+ |
+ var listNode = nodeUnderMouse.enclosingNodeOrSelfWithNodeName('li'); |
+ if (!listNode) |
+ return; |
+ |
+ var element = listNode.treeElement; |
+ if (this._previousHoveredElement === element) |
+ return; |
+ |
+ if (this._previousHoveredElement) { |
+ this._previousHoveredElement.hovered = false; |
+ this._previousHoveredElement = null; |
+ } |
+ |
+ if (element instanceof Resources._FrameTreeElement) { |
+ this._previousHoveredElement = element; |
+ element.select(false, false); |
+ element.hovered = true; |
+ } |
+ } |
+ |
+ _onmouseleave() { |
+ if (this._previousHoveredElement) { |
+ this._previousHoveredElement.hovered = false; |
+ this._previousHoveredElement = null; |
+ } |
+ } |
+}; |
+ |
+/** |
+ * @enum {symbol} |
+ */ |
+Resources.FrameSelector.Events = { |
+ FrameSelected: Symbol('FrameSelected') |
+}; |
+ |
+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.
|
+ /** |
+ * @param {!SDK.ResourceTreeFrame} frame |
+ */ |
+ constructor(frame) { |
+ super('', false); |
+ this.frame = frame; |
+ this.frameNavigated(frame); |
+ } |
+ |
+ frameNavigated(frame) { |
+ this.frame = frame; |
+ this.title = frame.displayName(); |
+ } |
+ |
+ get itemURL() { |
dgozman
2017/03/24 17:06:54
Unused.
eostroukhov
2017/04/04 23:47:17
Done.
|
+ return 'frame://' + encodeURI(this.titleAsText()); |
+ } |
+ |
+ set hovered(hovered) { |
+ if (hovered) { |
+ this.listItemElement.classList.add('hovered'); |
+ var domModel = SDK.DOMModel.fromTarget(this.frame.target()); |
+ if (domModel) |
+ domModel.highlightFrame(this.frame.id); |
+ } else { |
+ this.listItemElement.classList.remove('hovered'); |
+ SDK.DOMModel.hideDOMNodeHighlight(); |
+ } |
+ } |
+}; |