Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(307)

Unified Diff: third_party/WebKit/Source/devtools/front_end/resources/FrameSelector.js

Issue 2773583002: [DevTools] Introduce a sidebar with a drop-down
Patch Set: [DevTools] Introduce a sidebar with a drop-down Created 3 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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();
+ }
+ }
+};

Powered by Google App Engine
This is Rietveld 408576698