diff --git a/front_end/console/ConsoleViewMessage.js b/front_end/console/ConsoleViewMessage.js index 22236b1c59..7ab961e9b9 100644 --- a/front_end/console/ConsoleViewMessage.js +++ b/front_end/console/ConsoleViewMessage.js @@ -45,6 +45,8 @@ Console.ConsoleViewMessage = class { this._repeatCount = 1; this._closeGroupDecorationCount = 0; this._nestingLevel = nestingLevel; + /** @type {!Array} */ + this._focusableChildren = []; /** @type {?DataGrid.DataGrid} */ this._dataGrid = null; @@ -608,6 +610,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); return section.element; } @@ -1034,6 +1037,15 @@ Console.ConsoleViewMessage = class { } } + /** + * @return {number} + */ + _focusedChildIndex() { + if (!this._focusableChildren.length) + return -1; + return this._focusableChildren.findIndex(child => child.element.hasFocus()); + } + /** * @param {!Event} event */ @@ -1050,15 +1062,65 @@ Console.ConsoleViewMessage = class { */ maybeHandleOnKeyDown(event) { // Handle trace expansion. - if (this._expandTrace) { + const focusedChildIndex = this._focusedChildIndex(); + const isWrapperFocused = focusedChildIndex === -1; + if (this._expandTrace && isWrapperFocused) { if ((event.key === 'ArrowLeft' && this._traceExpanded) || (event.key === 'ArrowRight' && !this._traceExpanded)) { this._expandTrace(!this._traceExpanded); return true; } } + if (!this._focusableChildren.length) + return false; + + if (event.key === 'ArrowLeft') { + this._element.focus(); + return true; + } + if (event.key === 'ArrowRight') { + if (isWrapperFocused) { + this._focusChild(0); + return true; + } + } + if (event.key === 'ArrowUp') { + if (focusedChildIndex === 0) { + this._element.focus(); + return true; + } else if (focusedChildIndex > 0) { + this._focusChild(focusedChildIndex - 1); + return true; + } + } + if (event.key === 'ArrowDown') { + if (isWrapperFocused) { + this._focusChild(0); + return true; + } else if (focusedChildIndex < this._focusableChildren.length - 1) { + this._focusChild(focusedChildIndex + 1); + 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); + else if (this._element) + this._element.focus(); + } + /** * @return {!Element} */ @@ -1557,9 +1619,12 @@ Console.ConsoleGroupViewMessage = class extends Console.ConsoleViewMessage { * @param {!Event} event */ maybeHandleOnKeyDown(event) { - if ((event.key === 'ArrowLeft' && !this._collapsed) || (event.key === 'ArrowRight' && this._collapsed)) { - this._setCollapsed(!this._collapsed); - return true; + const focusedChildIndex = this._focusedChildIndex(); + if (focusedChildIndex === -1) { + if ((event.key === 'ArrowLeft' && !this._collapsed) || (event.key === 'ArrowRight' && this._collapsed)) { + this._setCollapsed(!this._collapsed); + return true; + } } return super.maybeHandleOnKeyDown(event); } diff --git a/front_end/console/ConsoleViewport.js b/front_end/console/ConsoleViewport.js index d0378a03d2..f1ea217ef0 100644 --- a/front_end/console/ConsoleViewport.js +++ b/front_end/console/ConsoleViewport.js @@ -171,16 +171,21 @@ Console.ConsoleViewport = class { _onKeyDown(event) { if (UI.isEditing() || !this._itemCount || event.shiftKey) return; + let isArrowUp = false; switch (event.key) { case 'ArrowUp': - this._virtualSelectedIndex--; - if (this._virtualSelectedIndex < 0) - this._virtualSelectedIndex = this._itemCount - 1; + if (this._virtualSelectedIndex > 0) { + isArrowUp = true; + this._virtualSelectedIndex--; + } else { + return; + } break; case 'ArrowDown': - this._virtualSelectedIndex++; - if (this._virtualSelectedIndex >= this._itemCount) - this._virtualSelectedIndex = 0; + if (this._virtualSelectedIndex < this._itemCount - 1) + this._virtualSelectedIndex++; + else + return; break; case 'Home': this._virtualSelectedIndex = 0; @@ -193,20 +198,27 @@ Console.ConsoleViewport = class { } event.consume(true); this.scrollItemIntoView(this._virtualSelectedIndex); - this._updateFocusedItem(); + this._updateFocusedItem(isArrowUp); } - _updateFocusedItem() { + /** + * @param {boolean=} focusLastChild + */ + _updateFocusedItem(focusLastChild) { const selectedElement = this.renderedElementAt(this._virtualSelectedIndex); const changed = this._lastSelectedElement !== selectedElement; const containerHasFocus = this._contentElement === this.element.ownerDocument.deepActiveElement(); if (this._lastSelectedElement && changed) this._lastSelectedElement.classList.remove('console-selected'); - if (selectedElement && (changed || containerHasFocus)) { + if (selectedElement && (changed || containerHasFocus) && this.element.hasFocus()) { selectedElement.classList.add('console-selected'); // Do not focus the message if something within holds focus (e.g. object). - if (!selectedElement.hasFocus()) - focusWithoutScroll(selectedElement); + if (!selectedElement.hasFocus()) { + if (focusLastChild) + this._renderedItems[this._virtualSelectedIndex - this._firstActiveIndex].focusLastChildOrSelf(); + else + focusWithoutScroll(selectedElement); + } } if (this._itemCount && !this._contentElement.hasFocus()) this._contentElement.tabIndex = 0; diff --git a/front_end/console_test_runner/ConsoleTestRunner.js b/front_end/console_test_runner/ConsoleTestRunner.js index dc28c73c70..aea6002d6e 100644 --- a/front_end/console_test_runner/ConsoleTestRunner.js +++ b/front_end/console_test_runner/ConsoleTestRunner.js @@ -441,6 +441,13 @@ ConsoleTestRunner.waitForRemoteObjectsConsoleMessages = function(callback) { TestRunner.deprecatedRunAfterPendingDispatches(callback); }; +/** + * @return {!Promise} + */ +ConsoleTestRunner.waitForRemoteObjectsConsoleMessagesPromise = function() { + return new Promise(resolve => ConsoleTestRunner.waitForRemoteObjectsConsoleMessages(resolve)); +}; + /** * @return {!Promise} */