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 ...@@ -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} active text: console-key-expand.js:112 warning {x: 1}
(anonymous) @ console-key-expand.js:112 (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 @@ ...@@ -145,6 +145,47 @@
next(); 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 @@ ...@@ -169,6 +210,11 @@
eventSender.keyDown(key); eventSender.keyDown(key);
} }
function shiftPress(key) {
TestRunner.addResult(`\nShift+${key}:`);
eventSender.keyDown(key, ['shiftKey']);
}
function dumpFocus(activeElement, messageIndex = 0, skipObjectCheck) { function dumpFocus(activeElement, messageIndex = 0, skipObjectCheck) {
const firstMessage = consoleView._visibleViewMessages[messageIndex]; const firstMessage = consoleView._visibleViewMessages[messageIndex];
const hasTrace = !!firstMessage.element().querySelector('.console-message-stack-trace-toggle'); const hasTrace = !!firstMessage.element().querySelector('.console-message-stack-trace-toggle');
......
...@@ -45,8 +45,8 @@ Console.ConsoleViewMessage = class { ...@@ -45,8 +45,8 @@ Console.ConsoleViewMessage = class {
this._repeatCount = 1; this._repeatCount = 1;
this._closeGroupDecorationCount = 0; this._closeGroupDecorationCount = 0;
this._nestingLevel = nestingLevel; this._nestingLevel = nestingLevel;
/** @type {!Array<!ObjectUI.ObjectPropertiesSection>} */ /** @type {!Array<!UI.TreeOutline>} */
this._focusableChildren = []; this._treeOutlines = [];
/** @type {?DataGrid.DataGrid} */ /** @type {?DataGrid.DataGrid} */
this._dataGrid = null; this._dataGrid = null;
...@@ -613,7 +613,7 @@ Console.ConsoleViewMessage = class { ...@@ -613,7 +613,7 @@ Console.ConsoleViewMessage = class {
const section = new ObjectUI.ObjectPropertiesSection(obj, titleElement, this._linkifier); const section = new ObjectUI.ObjectPropertiesSection(obj, titleElement, this._linkifier);
section.element.classList.add('console-view-object-properties-section'); section.element.classList.add('console-view-object-properties-section');
section.enableContextMenu(); section.enableContextMenu();
this._focusableChildren.push(section); this._treeOutlines.push(section);
return section.element; return section.element;
} }
...@@ -682,8 +682,13 @@ Console.ConsoleViewMessage = class { ...@@ -682,8 +682,13 @@ Console.ConsoleViewMessage = class {
return; return;
} }
const renderResult = await UI.Renderer.render(/** @type {!Object} */ (node)); const renderResult = await UI.Renderer.render(/** @type {!Object} */ (node));
const renderedNode = renderResult ? renderResult.node : null; if (renderResult) {
result.appendChild(renderedNode || this._formatParameterAsObject(remoteObject, false)); if (renderResult.tree)
this._treeOutlines.push(renderResult.tree);
result.appendChild(renderResult.node);
} else {
result.appendChild(this._formatParameterAsObject(remoteObject, false));
}
this._formattedParameterAsNodeForTest(); this._formattedParameterAsNodeForTest();
}); });
...@@ -1041,9 +1046,9 @@ Console.ConsoleViewMessage = class { ...@@ -1041,9 +1046,9 @@ Console.ConsoleViewMessage = class {
* @return {number} * @return {number}
*/ */
_focusedChildIndex() { _focusedChildIndex() {
if (!this._focusableChildren.length) if (!this._treeOutlines.length)
return -1; 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 { ...@@ -1070,7 +1075,7 @@ Console.ConsoleViewMessage = class {
return true; return true;
} }
} }
if (!this._focusableChildren.length) if (!this._treeOutlines.length)
return false; return false;
if (event.key === 'ArrowLeft') { if (event.key === 'ArrowLeft') {
...@@ -1079,7 +1084,7 @@ Console.ConsoleViewMessage = class { ...@@ -1079,7 +1084,7 @@ Console.ConsoleViewMessage = class {
} }
if (event.key === 'ArrowRight') { if (event.key === 'ArrowRight') {
if (isWrapperFocused) { if (isWrapperFocused) {
this._focusChild(0); this._treeOutlines[0].selectFirst();
return true; return true;
} }
} }
...@@ -1088,35 +1093,25 @@ Console.ConsoleViewMessage = class { ...@@ -1088,35 +1093,25 @@ Console.ConsoleViewMessage = class {
this._element.focus(); this._element.focus();
return true; return true;
} else if (focusedChildIndex > 0) { } else if (focusedChildIndex > 0) {
this._focusChild(focusedChildIndex - 1); this._treeOutlines[focusedChildIndex - 1].selectFirst();
return true; return true;
} }
} }
if (event.key === 'ArrowDown') { if (event.key === 'ArrowDown') {
if (isWrapperFocused) { if (isWrapperFocused) {
this._focusChild(0); this._treeOutlines[0].selectFirst();
return true; return true;
} else if (focusedChildIndex < this._focusableChildren.length - 1) { } else if (focusedChildIndex < this._treeOutlines.length - 1) {
this._focusChild(focusedChildIndex + 1); this._treeOutlines[focusedChildIndex + 1].selectFirst();
return true; return true;
} }
} }
return false; return false;
} }
/**
* @param {number} index
*/
_focusChild(index) {
const section = this._focusableChildren[index];
if (!section.objectTreeElement().selected)
section.objectTreeElement().select();
section.focus();
}
focusLastChildOrSelf() { focusLastChildOrSelf() {
if (this._focusableChildren.length) if (this._treeOutlines.length)
this._focusChild(this._focusableChildren.length - 1); this._treeOutlines[this._treeOutlines.length - 1].selectFirst();
else if (this._element) else if (this._element)
this._element.focus(); this._element.focus();
} }
......
...@@ -127,11 +127,14 @@ Console.ConsoleViewport = class { ...@@ -127,11 +127,14 @@ Console.ConsoleViewport = class {
const renderedIndex = this._renderedItems.findIndex(item => item.element().isSelfOrAncestor(event.target)); const renderedIndex = this._renderedItems.findIndex(item => item.element().isSelfOrAncestor(event.target));
if (renderedIndex !== -1) if (renderedIndex !== -1)
this._virtualSelectedIndex = this._firstActiveIndex + renderedIndex; this._virtualSelectedIndex = this._firstActiveIndex + renderedIndex;
let focusLastChild = false;
// Make default selection when moving from external (e.g. prompt) to the container. // Make default selection when moving from external (e.g. prompt) to the container.
if (this._virtualSelectedIndex === -1 && this._isOutsideViewport(/** @type {?Element} */ (event.relatedTarget)) && 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._virtualSelectedIndex = this._itemCount - 1;
this._updateFocusedItem(); }
this._updateFocusedItem(focusLastChild);
} }
/** /**
......
...@@ -1590,12 +1590,13 @@ Elements.ElementsTreeOutline.Renderer = class { ...@@ -1590,12 +1590,13 @@ Elements.ElementsTreeOutline.Renderer = class {
reject(new Error('Could not resolve node.')); reject(new Error('Could not resolve node.'));
return; return;
} }
const treeOutline = new Elements.ElementsTreeOutline(false, false, true /* hideGutter */); const treeOutline = new Elements.ElementsTreeOutline(false, true /* selectEnabled */, true /* hideGutter */);
treeOutline.rootDOMNode = node; treeOutline.rootDOMNode = node;
if (!treeOutline.firstChild().isExpandable()) if (!treeOutline.firstChild().isExpandable())
treeOutline._element.classList.add('single-node'); treeOutline._element.classList.add('single-node');
treeOutline.setVisible(true); treeOutline.setVisible(true);
treeOutline.element.treeElementForTest = treeOutline.firstChild(); treeOutline.element.treeElementForTest = treeOutline.firstChild();
treeOutline.setShowSelectionOnKeyboardFocus(true);
resolve({node: treeOutline.element, tree: treeOutline}); resolve({node: treeOutline.element, tree: treeOutline});
} }
} }
......
...@@ -34,7 +34,7 @@ ...@@ -34,7 +34,7 @@
white-space: pre; 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; opacity: 0.6;
color: var(--selection-inactive-fg-color); color: var(--selection-inactive-fg-color);
} }
...@@ -91,9 +91,17 @@ ...@@ -91,9 +91,17 @@
.elements-disclosure li.selected .selection { .elements-disclosure li.selected .selection {
display: block; display: block;
}
.elements-disclosure .elements-tree-outline:not(.hide-selection-when-blurred) .selection {
background-color: var(--selection-inactive-bg-color); 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 { .elements-disclosure ol {
list-style-type: none; list-style-type: none;
/** Keep it in sync with ElementsTreeElements.updateDecorators **/ /** Keep it in sync with ElementsTreeElements.updateDecorators **/
...@@ -122,19 +130,19 @@ ...@@ -122,19 +130,19 @@
padding-left: 2px; 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); 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); 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; 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); background-color: var(--selection-bg-color);
} }
...@@ -299,10 +307,10 @@ ol:hover > li > .elements-tree-shortcut-link { ...@@ -299,10 +307,10 @@ ol:hover > li > .elements-tree-shortcut-link {
display: none; display: none;
} }
ol li.selected:focus .webkit-html-tag-name, .elements-tree-outline:not(.hide-selection-when-blurred) li.selected:focus .webkit-html-tag-name,
ol li.selected:focus .webkit-html-close-tag-name, .elements-tree-outline:not(.hide-selection-when-blurred) li.selected:focus .webkit-html-close-tag-name,
ol li.selected:focus .webkit-html-attribute-value, .elements-tree-outline:not(.hide-selection-when-blurred) li.selected:focus .webkit-html-attribute-value,
ol li.selected:focus .devtools-link { .elements-tree-outline:not(.hide-selection-when-blurred) li.selected:focus .devtools-link {
color: var(--selection-fg-color); color: var(--selection-fg-color);
} }
......
...@@ -225,7 +225,7 @@ UI.TreeOutline = class extends Common.Object { ...@@ -225,7 +225,7 @@ UI.TreeOutline = class extends Common.Object {
/** /**
* @return {boolean} * @return {boolean}
*/ */
_selectFirst() { selectFirst() {
let first = this.firstChild(); let first = this.firstChild();
while (first && !first.selectable) while (first && !first.selectable)
first = first.traverseNextTreeElement(true); first = first.traverseNextTreeElement(true);
...@@ -276,7 +276,7 @@ UI.TreeOutline = class extends Common.Object { ...@@ -276,7 +276,7 @@ UI.TreeOutline = class extends Common.Object {
} else if (event.keyCode === UI.KeyboardShortcut.Keys.Space.code) { } else if (event.keyCode === UI.KeyboardShortcut.Keys.Space.code) {
handled = this.selectedTreeElement.onspace(); handled = this.selectedTreeElement.onspace();
} else if (event.key === 'Home') { } else if (event.key === 'Home') {
handled = this._selectFirst(); handled = this.selectFirst();
} else if (event.key === 'End') { } else if (event.key === 'End') {
handled = this._selectLast(); 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