Commit de8a9c26 authored by Erik Luo's avatar Erik Luo Committed by Commit Bot

DevTools: navigable ElementsTreeOutline in Console

Introduces a common interface between ElementsTreeOutlines and
ObjectPropertiesSections. This allows both to be keyboard navigable
in Console.

Bug: 865674
Change-Id: I405d161692147374827918cce2369d129620a9fd
Reviewed-on: https://chromium-review.googlesource.com/c/1275225
Commit-Queue: Erik Luo <luoe@chromium.org>
Reviewed-by: default avatarJoel Einbinder <einbinder@chromium.org>
Cr-Commit-Position: refs/heads/master@{#604855}
parent d4e124ec
......@@ -208,3 +208,39 @@ activeElement: DIV.console-message-wrapper.console-from-api.console-warning-leve
active text: console-key-expand.js:112 warning {x: 1}
(anonymous) @ console-key-expand.js:112
Running: testExpandingElement
Evaluating: console.log("before");console.log(el);console.log("after");
Message count: 3
Force selecting index 1
Viewport virtual selection: 1
activeElement: DIV.console-message-wrapper.console-from-api.console-info-level.console-selected
active text: console-key-expand.js:156 <div>​…​</div>​
ArrowDown:
Viewport virtual selection: 1
activeElement: LI.parent.selected
active text: <div>​…​</div>​
ArrowRight:
Viewport virtual selection: 1
activeElement: LI.parent.selected.expanded
active text: <div>​
Running: testShiftTabShouldSelectLastObject
Evaluating: console.log("before");console.log(obj1);
Message count: 2
Setting focus in prompt:
Shift+Tab:
Viewport virtual selection: 1
Has object: collapsed
activeElement: LI.parent.object-properties-section-root-element.selected
active text: {x: 1}
ArrowRight:
Viewport virtual selection: 1
Has object: expanded
activeElement: LI.parent.object-properties-section-root-element.selected.expanded
active text: {x: 1}
......@@ -145,6 +145,47 @@
next();
},
async function testExpandingElement(next) {
await TestRunner.evaluateInPagePromise(`
var el = document.createElement('div');
var child = document.createElement('span');
el.appendChild(child); undefined;
`);
const nodePromise = TestRunner.addSnifferPromise(Console.ConsoleViewMessage.prototype, '_formattedParameterAsNodeForTest');
await clearAndLog(`console.log("before");console.log(el);console.log("after");`, 3);
await nodePromise;
forceSelect(1);
dumpFocus(true, 1);
press('ArrowDown');
dumpFocus(true, 1);
// Expand object.
press('ArrowRight');
await ConsoleTestRunner.waitForRemoteObjectsConsoleMessagesPromise();
dumpFocus(true, 1);
next();
},
async function testShiftTabShouldSelectLastObject(next) {
await clearAndLog(`console.log("before");console.log(obj1);`, 2);
await ConsoleTestRunner.waitForRemoteObjectsConsoleMessagesPromise();
TestRunner.addResult(`Setting focus in prompt:`);
prompt.focus();
shiftPress('Tab');
dumpFocus(true, 1);
// Expand object.
press('ArrowRight');
await ConsoleTestRunner.waitForRemoteObjectsConsoleMessagesPromise();
dumpFocus(true, 1);
next();
},
]);
......@@ -169,6 +210,11 @@
eventSender.keyDown(key);
}
function shiftPress(key) {
TestRunner.addResult(`\nShift+${key}:`);
eventSender.keyDown(key, ['shiftKey']);
}
function dumpFocus(activeElement, messageIndex = 0, skipObjectCheck) {
const firstMessage = consoleView._visibleViewMessages[messageIndex];
const hasTrace = !!firstMessage.element().querySelector('.console-message-stack-trace-toggle');
......
......@@ -45,8 +45,8 @@ Console.ConsoleViewMessage = class {
this._repeatCount = 1;
this._closeGroupDecorationCount = 0;
this._nestingLevel = nestingLevel;
/** @type {!Array<!ObjectUI.ObjectPropertiesSection>} */
this._focusableChildren = [];
/** @type {!Array<!UI.TreeOutline>} */
this._treeOutlines = [];
/** @type {?DataGrid.DataGrid} */
this._dataGrid = null;
......@@ -613,7 +613,7 @@ Console.ConsoleViewMessage = class {
const section = new ObjectUI.ObjectPropertiesSection(obj, titleElement, this._linkifier);
section.element.classList.add('console-view-object-properties-section');
section.enableContextMenu();
this._focusableChildren.push(section);
this._treeOutlines.push(section);
return section.element;
}
......@@ -682,8 +682,13 @@ Console.ConsoleViewMessage = class {
return;
}
const renderResult = await UI.Renderer.render(/** @type {!Object} */ (node));
const renderedNode = renderResult ? renderResult.node : null;
result.appendChild(renderedNode || this._formatParameterAsObject(remoteObject, false));
if (renderResult) {
if (renderResult.tree)
this._treeOutlines.push(renderResult.tree);
result.appendChild(renderResult.node);
} else {
result.appendChild(this._formatParameterAsObject(remoteObject, false));
}
this._formattedParameterAsNodeForTest();
});
......@@ -1041,9 +1046,9 @@ Console.ConsoleViewMessage = class {
* @return {number}
*/
_focusedChildIndex() {
if (!this._focusableChildren.length)
if (!this._treeOutlines.length)
return -1;
return this._focusableChildren.findIndex(child => child.element.hasFocus());
return this._treeOutlines.findIndex(child => child.element.hasFocus());
}
/**
......@@ -1070,7 +1075,7 @@ Console.ConsoleViewMessage = class {
return true;
}
}
if (!this._focusableChildren.length)
if (!this._treeOutlines.length)
return false;
if (event.key === 'ArrowLeft') {
......@@ -1079,7 +1084,7 @@ Console.ConsoleViewMessage = class {
}
if (event.key === 'ArrowRight') {
if (isWrapperFocused) {
this._focusChild(0);
this._treeOutlines[0].selectFirst();
return true;
}
}
......@@ -1088,35 +1093,25 @@ Console.ConsoleViewMessage = class {
this._element.focus();
return true;
} else if (focusedChildIndex > 0) {
this._focusChild(focusedChildIndex - 1);
this._treeOutlines[focusedChildIndex - 1].selectFirst();
return true;
}
}
if (event.key === 'ArrowDown') {
if (isWrapperFocused) {
this._focusChild(0);
this._treeOutlines[0].selectFirst();
return true;
} else if (focusedChildIndex < this._focusableChildren.length - 1) {
this._focusChild(focusedChildIndex + 1);
} else if (focusedChildIndex < this._treeOutlines.length - 1) {
this._treeOutlines[focusedChildIndex + 1].selectFirst();
return true;
}
}
return false;
}
/**
* @param {number} index
*/
_focusChild(index) {
const section = this._focusableChildren[index];
if (!section.objectTreeElement().selected)
section.objectTreeElement().select();
section.focus();
}
focusLastChildOrSelf() {
if (this._focusableChildren.length)
this._focusChild(this._focusableChildren.length - 1);
if (this._treeOutlines.length)
this._treeOutlines[this._treeOutlines.length - 1].selectFirst();
else if (this._element)
this._element.focus();
}
......
......@@ -127,11 +127,14 @@ Console.ConsoleViewport = class {
const renderedIndex = this._renderedItems.findIndex(item => item.element().isSelfOrAncestor(event.target));
if (renderedIndex !== -1)
this._virtualSelectedIndex = this._firstActiveIndex + renderedIndex;
let focusLastChild = false;
// Make default selection when moving from external (e.g. prompt) to the container.
if (this._virtualSelectedIndex === -1 && this._isOutsideViewport(/** @type {?Element} */ (event.relatedTarget)) &&
event.target === this._contentElement)
event.target === this._contentElement) {
focusLastChild = true;
this._virtualSelectedIndex = this._itemCount - 1;
this._updateFocusedItem();
}
this._updateFocusedItem(focusLastChild);
}
/**
......
......@@ -1590,12 +1590,13 @@ Elements.ElementsTreeOutline.Renderer = class {
reject(new Error('Could not resolve node.'));
return;
}
const treeOutline = new Elements.ElementsTreeOutline(false, false, true /* hideGutter */);
const treeOutline = new Elements.ElementsTreeOutline(false, true /* selectEnabled */, true /* hideGutter */);
treeOutline.rootDOMNode = node;
if (!treeOutline.firstChild().isExpandable())
treeOutline._element.classList.add('single-node');
treeOutline.setVisible(true);
treeOutline.element.treeElementForTest = treeOutline.firstChild();
treeOutline.setShowSelectionOnKeyboardFocus(true);
resolve({node: treeOutline.element, tree: treeOutline});
}
}
......
......@@ -34,7 +34,7 @@
white-space: pre;
}
.elements-disclosure li.selected .selected-hint:before {
.elements-disclosure .elements-tree-outline:not(.hide-selection-when-blurred) li.selected .selected-hint:before {
opacity: 0.6;
color: var(--selection-inactive-fg-color);
}
......@@ -91,9 +91,17 @@
.elements-disclosure li.selected .selection {
display: block;
}
.elements-disclosure .elements-tree-outline:not(.hide-selection-when-blurred) .selection {
background-color: var(--selection-inactive-bg-color);
}
.elements-disclosure .elements-tree-outline.hide-selection-when-blurred li.selected:focus[data-keyboard-focus="true"] .selection {
background: rgba(0, 0, 0, 0.08);
border-radius: 2px;
}
.elements-disclosure ol {
list-style-type: none;
/** Keep it in sync with ElementsTreeElements.updateDecorators **/
......@@ -122,19 +130,19 @@
padding-left: 2px;
}
.elements-disclosure ol li.selected:focus {
.elements-disclosure .elements-tree-outline:not(.hide-selection-when-blurred) li.selected:focus {
color: var(--selection-fg-color);
}
.elements-disclosure ol li.parent.selected:focus::before {
.elements-disclosure .elements-tree-outline:not(.hide-selection-when-blurred) li.parent.selected:focus::before {
background-color: var(--selection-fg-color);
}
.elements-disclosure ol li.selected:focus * {
.elements-disclosure .elements-tree-outline:not(.hide-selection-when-blurred) li.selected:focus * {
color: inherit;
}
.elements-disclosure ol li.selected:focus .selection {
.elements-disclosure .elements-tree-outline:not(.hide-selection-when-blurred) li.selected:focus .selection {
background-color: var(--selection-bg-color);
}
......@@ -299,10 +307,10 @@ ol:hover > li > .elements-tree-shortcut-link {
display: none;
}
ol li.selected:focus .webkit-html-tag-name,
ol li.selected:focus .webkit-html-close-tag-name,
ol li.selected:focus .webkit-html-attribute-value,
ol li.selected:focus .devtools-link {
.elements-tree-outline:not(.hide-selection-when-blurred) li.selected:focus .webkit-html-tag-name,
.elements-tree-outline:not(.hide-selection-when-blurred) li.selected:focus .webkit-html-close-tag-name,
.elements-tree-outline:not(.hide-selection-when-blurred) li.selected:focus .webkit-html-attribute-value,
.elements-tree-outline:not(.hide-selection-when-blurred) li.selected:focus .devtools-link {
color: var(--selection-fg-color);
}
......
......@@ -225,7 +225,7 @@ UI.TreeOutline = class extends Common.Object {
/**
* @return {boolean}
*/
_selectFirst() {
selectFirst() {
let first = this.firstChild();
while (first && !first.selectable)
first = first.traverseNextTreeElement(true);
......@@ -276,7 +276,7 @@ UI.TreeOutline = class extends Common.Object {
} else if (event.keyCode === UI.KeyboardShortcut.Keys.Space.code) {
handled = this.selectedTreeElement.onspace();
} else if (event.key === 'Home') {
handled = this._selectFirst();
handled = this.selectFirst();
} else if (event.key === 'End') {
handled = this._selectLast();
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment