 Chromium Code Reviews
 Chromium Code Reviews Issue 1803813002:
  [DevTools] Added keyboard search while in sources  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master
    
  
    Issue 1803813002:
  [DevTools] Added keyboard search while in sources  (Closed) 
  Base URL: https://chromium.googlesource.com/chromium/src.git@master| Index: third_party/WebKit/Source/devtools/front_end/ui/treeoutline.js | 
| diff --git a/third_party/WebKit/Source/devtools/front_end/ui/treeoutline.js b/third_party/WebKit/Source/devtools/front_end/ui/treeoutline.js | 
| index 7755b1cdc7fc565c0af853c00ba39addfbd23400..f15c29c9fc68e5d0257db86096ce10edddf5995a 100644 | 
| --- a/third_party/WebKit/Source/devtools/front_end/ui/treeoutline.js | 
| +++ b/third_party/WebKit/Source/devtools/front_end/ui/treeoutline.js | 
| @@ -42,10 +42,14 @@ function TreeOutline(nonFocusable) | 
| this._contentElement = this._rootElement._childrenListNode; | 
| this._contentElement.addEventListener("keydown", this._treeKeyDown.bind(this), true); | 
| + this._contentElement.addEventListener("keypress", this._treeKeyPress.bind(this), true); | 
| this.setFocusable(!nonFocusable); | 
| this.element = this._contentElement; | 
| + | 
| + this.element.addEventListener("blur", this._handleBlur.bind(this), true); | 
| + this.element.addEventListener("click", this._handleClick.bind(this), true); | 
| } | 
| TreeOutline.Events = { | 
| @@ -64,6 +68,11 @@ TreeOutline.prototype = { | 
| this._rootElement.selectable = false; | 
| this._rootElement.expanded = true; | 
| this._rootElement._childrenListNode.classList.remove("children"); | 
| + | 
| + this._currentSelectionFilterString = ''; | 
| 
pfeldman
2016/03/14 23:51:35
Please only use double quotes.
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + this._currentSelectionFilter = null; | 
| 
pfeldman
2016/03/14 23:51:34
You should declare a type here using JSDoc.
 
Allada-Google
2016/03/15 17:13:07
Done.
 | 
| + this._highlightChanges = []; | 
| + this._interactiveFilterEnabled = false; | 
| }, | 
| /** | 
| @@ -75,6 +84,79 @@ TreeOutline.prototype = { | 
| }, | 
| /** | 
| + * This will also set/override the RegExp to filter on (ie: setCurrentSelectionFilter()) | 
| 
pfeldman
2016/03/14 23:51:34
We don't explain why (in Blink)...
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + * @param {string} filterString String to filter text on. | 
| 
pfeldman
2016/03/14 23:51:35
or what... Just the types.
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + */ | 
| + setCurrentSelectionFilterString: function (filterString) | 
| 
pfeldman
2016/03/14 23:51:35
Seems to be private (starts with _)
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + { | 
| + this._currentSelectionFilterString = filterString; | 
| + if (this._currentSelectionFilterString === '') | 
| 
pfeldman
2016/03/14 23:51:34
""
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + this.setCurrentSelectionFilter(null); | 
| + else | 
| + this.setCurrentSelectionFilter(new RegExp(['(', this._currentSelectionFilterString.escapeForRegExp(), ')'].join(''), 'i')); | 
| + }, | 
| + | 
| + /** | 
| + * @return {string} | 
| + */ | 
| + currentSelectionFilterString: function () | 
| 
pfeldman
2016/03/14 23:51:34
lets not expose until required.
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + { | 
| + | 
| + return this._currentSelectionFilterString; | 
| + }, | 
| + | 
| + setInteractiveFilterable: function (enable) | 
| 
pfeldman
2016/03/14 23:51:34
JSDoc for enable type.
 
Allada-Google
2016/03/15 17:13:07
Done.
 | 
| + { | 
| + this._interactiveFilterEnabled = !!enable; | 
| 
pfeldman
2016/03/14 23:51:34
Declare it as a boolean and there would be no need
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + }, | 
| + | 
| + /** | 
| + * @param {?RegExp} filterRegExp Regular Expression to use to filter selectable items | 
| + */ | 
| + setCurrentSelectionFilter: function (filterRegExp) | 
| + { | 
| + var currentFilter = this._currentSelectionFilter; | 
| + this._currentSelectionFilter = filterRegExp; | 
| + | 
| + if (this._highlightChanges && this._highlightChanges.length > 0) | 
| 
pfeldman
2016/03/14 23:51:34
(this._highlightChanges && this._highlightChanges.
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + WebInspector.revertDomChanges(this._highlightChanges); | 
| 
pfeldman
2016/03/14 23:51:34
We are now vulnerable to the changes made to the e
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + this._highlightChanges = []; | 
| + | 
| + if (filterRegExp && filterRegExp !== currentFilter) { | 
| 
pfeldman
2016/03/14 23:51:34
We prefer early returns to nested conditions.
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + // If the list is not fully rendered don't try and continue | 
| + if (this._rootElement) { | 
| 
pfeldman
2016/03/14 23:51:34
ditto
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + var textNode = this._rootElement.firstChild(); | 
| 
pfeldman
2016/03/14 23:51:35
textNode is not really a text node, it is a first
 | 
| + var childTextNodes = this.element.childTextNodes(); | 
| 
pfeldman
2016/03/14 23:51:34
unused?
 | 
| + var newExp = new RegExp(filterRegExp.source, 'gi'); | 
| 
pfeldman
2016/03/14 23:51:34
"gi", also, why new regex?
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + | 
| + if (this.selectedTreeElement && !this.selectedTreeElement.selectable) | 
| + this.selectNext() || this.selectPrevious(); | 
| + | 
| + do { | 
| + var match; | 
| + var ranges = []; | 
| + var textContent = textNode._listItemNode.textContent; | 
| + while ((match = newExp.exec(textContent)) !== null) { | 
| 
pfeldman
2016/03/14 23:51:35
drop !== null, extract match from comparison into
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + ranges.push(new WebInspector.SourceRange(match.index, match[0].length)); | 
| + } | 
| + if (ranges.length > 0) | 
| + WebInspector.highlightRangesWithStyleClass(textNode._listItemNode, ranges, "tree-text-interactive-highlight", this._highlightChanges); | 
| + | 
| + textNode = textNode.traverseNextTreeElement(true, null, true); | 
| + } while(textNode); | 
| + } | 
| + } | 
| + }, | 
| + | 
| + /** | 
| + * @return {?RegExp} | 
| + */ | 
| + currentSelectionFilter: function () | 
| 
pfeldman
2016/03/14 23:51:34
do not expose
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + { | 
| + return this._currentSelectionFilter; | 
| + }, | 
| + | 
| + /** | 
| * @return {?TreeElement} | 
| */ | 
| firstChild: function() | 
| @@ -187,6 +269,15 @@ TreeOutline.prototype = { | 
| }, | 
| /** | 
| + * @param {!TreeElement} treeElement | 
| + * @return {boolean} | 
| + */ | 
| + checkFilter: function (treeElement) | 
| 
pfeldman
2016/03/14 23:51:34
ditto
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + { | 
| + return this.currentSelectionFilter() ? this.currentSelectionFilter().test(treeElement.titleText) : true; | 
| + }, | 
| + | 
| + /** | 
| * @return {boolean} | 
| */ | 
| selectPrevious: function() | 
| @@ -221,6 +312,24 @@ TreeOutline.prototype = { | 
| /** | 
| * @param {!Event} event | 
| */ | 
| + _handleClick: function (event) | 
| + { | 
| + if (this._interactiveFilterEnabled) | 
| + this.setCurrentSelectionFilterString(''); | 
| 
pfeldman
2016/03/14 23:51:34
""
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + }, | 
| + | 
| + /** | 
| + * @param {!Event} event | 
| + */ | 
| + _handleBlur: function (event) | 
| + { | 
| + if (this._interactiveFilterEnabled) | 
| + this.setCurrentSelectionFilterString(''); | 
| + }, | 
| + | 
| + /** | 
| + * @param {!Event} event | 
| + */ | 
| _treeKeyDown: function(event) | 
| { | 
| if (event.target !== this._contentElement) | 
| @@ -229,30 +338,33 @@ TreeOutline.prototype = { | 
| if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey) | 
| return; | 
| + var currentFilterString = this.currentSelectionFilterString(); | 
| var handled = false; | 
| + var key = event.keyCode; | 
| var nextSelectedElement; | 
| - if (event.keyIdentifier === "Up" && !event.altKey) { | 
| - handled = this.selectPrevious(); | 
| - } else if (event.keyIdentifier === "Down" && !event.altKey) { | 
| - handled = this.selectNext(); | 
| - } else if (event.keyIdentifier === "Left") { | 
| - if (this.selectedTreeElement.expanded) { | 
| - if (event.altKey) | 
| - this.selectedTreeElement.collapseRecursively(); | 
| - else | 
| - this.selectedTreeElement.collapse(); | 
| - handled = true; | 
| - } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) { | 
| - handled = true; | 
| - if (this.selectedTreeElement.parent.selectable) { | 
| - nextSelectedElement = this.selectedTreeElement.parent; | 
| - while (nextSelectedElement && !nextSelectedElement.selectable) | 
| - nextSelectedElement = nextSelectedElement.parent; | 
| - handled = nextSelectedElement ? true : false; | 
| - } else if (this.selectedTreeElement.parent) | 
| - this.selectedTreeElement.parent.collapse(); | 
| + | 
| + switch (key) { | 
| + case WebInspector.KeyboardShortcut.Keys.Esc.code: | 
| + if (this._interactiveFilterEnabled) { | 
| + if (currentFilterString.length !== 0) | 
| 
pfeldman
2016/03/14 23:51:35
drop !== 0
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + // Consider the item handled if the filter string is already set (this will keep the console from triggering) | 
| + handled = true; | 
| + this.setCurrentSelectionFilterString(''); | 
| } | 
| - } else if (event.keyIdentifier === "Right") { | 
| + break; | 
| + case WebInspector.KeyboardShortcut.Keys.Delete.code: | 
| + if (this._interactiveFilterEnabled) | 
| + this.setCurrentSelectionFilterString(''); | 
| + case WebInspector.KeyboardShortcut.Keys.Backspace.code: | 
| + if (this._interactiveFilterEnabled === false || currentFilterString.length === 0) | 
| + handled = this.selectedTreeElement.ondelete(); | 
| + else if (this._interactiveFilterEnabled) | 
| + this.setCurrentSelectionFilterString(currentFilterString.substr(0, currentFilterString.length - 1)); | 
| + break; | 
| + case WebInspector.KeyboardShortcut.Keys.Right.code: | 
| 
pfeldman
2016/03/14 23:51:34
Group these and use case fallthrough to handle the
 
Allada-Google
2016/03/15 17:13:08
I optimized the code I added here as much as I fou
 | 
| + if (this._interactiveFilterEnabled) | 
| + this.setCurrentSelectionFilterString(''); | 
| + | 
| if (!this.selectedTreeElement.revealed()) { | 
| this.selectedTreeElement.reveal(); | 
| handled = true; | 
| @@ -270,12 +382,49 @@ TreeOutline.prototype = { | 
| this.selectedTreeElement.expand(); | 
| } | 
| } | 
| - } else if (event.keyCode === 8 /* Backspace */ || event.keyCode === 46 /* Delete */) | 
| - handled = this.selectedTreeElement.ondelete(); | 
| - else if (isEnterKey(event)) | 
| - handled = this.selectedTreeElement.onenter(); | 
| - else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Space.code) | 
| - handled = this.selectedTreeElement.onspace(); | 
| + break; | 
| + case WebInspector.KeyboardShortcut.Keys.Left.code: | 
| + if (this._interactiveFilterEnabled) | 
| + this.setCurrentSelectionFilterString(''); | 
| + | 
| + if (this.selectedTreeElement.expanded) { | 
| + if (event.altKey) | 
| + this.selectedTreeElement.collapseRecursively(); | 
| + else | 
| + this.selectedTreeElement.collapse(); | 
| + handled = true; | 
| + } else if (this.selectedTreeElement.parent && !this.selectedTreeElement.parent.root) { | 
| + handled = true; | 
| + if (this.selectedTreeElement.parent.selectable) { | 
| + nextSelectedElement = this.selectedTreeElement.parent; | 
| + while (nextSelectedElement && !nextSelectedElement.selectable) | 
| + nextSelectedElement = nextSelectedElement.parent; | 
| + handled = nextSelectedElement ? true : false; | 
| + } else if (this.selectedTreeElement.parent) | 
| + this.selectedTreeElement.parent.collapse(); | 
| + } | 
| + break; | 
| + case WebInspector.KeyboardShortcut.Keys.Down.code: | 
| + if (!event.altKey) | 
| + handled = this.selectNext(); | 
| + break; | 
| + case WebInspector.KeyboardShortcut.Keys.Up.code: | 
| + if (!event.altKey) | 
| + handled = this.selectPrevious(); | 
| + break; | 
| + case WebInspector.KeyboardShortcut.Keys.Space.code: | 
| + // Do not send space key event if the search filter has stuff in buffer | 
| + if (currentFilterString.length === 0) | 
| + handled = this.selectedTreeElement.onspace(); | 
| + break; | 
| + default: | 
| + if (isEnterKey(event)) { | 
| + if (this._interactiveFilterEnabled) | 
| + this.setCurrentSelectionFilterString(''); | 
| + | 
| + handled = this.selectedTreeElement.onenter(); | 
| + } | 
| + } | 
| if (nextSelectedElement) { | 
| nextSelectedElement.reveal(); | 
| @@ -287,6 +436,35 @@ TreeOutline.prototype = { | 
| }, | 
| /** | 
| + * @param {!Event} event | 
| + */ | 
| + _treeKeyPress: function (event) | 
| + { | 
| + if (this._interactiveFilterEnabled === false) | 
| 
pfeldman
2016/03/14 23:51:34
!this._interactiveFilterEnabled
 
Allada-Google
2016/03/15 17:13:08
Done.
 | 
| + return; | 
| + | 
| + if (event.target !== this._contentElement) | 
| + return; | 
| + | 
| + if (!this.selectedTreeElement || event.shiftKey || event.metaKey || event.ctrlKey) | 
| + return; | 
| + | 
| + var currentFilterString = this.currentSelectionFilterString(); | 
| + | 
| + switch (event.data) { | 
| + case "\r": | 
| + case "\n": | 
| + break; | 
| + case " ": | 
| + if (currentFilterString.length === 0) { | 
| + break; | 
| + } | 
| + default: | 
| + this.setCurrentSelectionFilterString(currentFilterString + event.data); | 
| + } | 
| + }, | 
| + | 
| + /** | 
| * @param {!TreeElement} treeElement | 
| * @param {boolean} center | 
| */ | 
| @@ -360,6 +538,7 @@ function TreeElement(title, expandable) | 
| this._listItemNode = createElement("li"); | 
| this._listItemNode.treeElement = this; | 
| + this.titleText = null; | 
| 
pfeldman
2016/03/14 23:51:34
Lets compute this dynamically off element's text c
 | 
| if (title) | 
| this.title = title; | 
| this._listItemNode.addEventListener("mousedown", this._handleMouseDown.bind(this), false); | 
| @@ -603,7 +782,7 @@ TreeElement.prototype = { | 
| get selectable() | 
| { | 
| - if (this._hidden) | 
| + if (this._hidden || !this.treeOutline.checkFilter(this)) | 
| return false; | 
| return this._selectable; | 
| }, | 
| @@ -641,6 +820,7 @@ TreeElement.prototype = { | 
| this._titleElement = createElementWithClass("span", "tree-element-title"); | 
| this._titleElement.textContent = x; | 
| this.tooltip = x; | 
| + this.titleText = x; | 
| } else { | 
| this._titleElement = x; | 
| this.tooltip = ""; |