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

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/network/XMLView.js

Issue 1942683006: [Devtools] XMLView now searchable (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@FIND_IN_JSON_FINAL
Patch Set: Created 4 years, 7 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 unified diff | Download patch
OLDNEW
1 // Copyright 2014 The Chromium Authors. All rights reserved. 1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @constructor 6 * @constructor
7 * @extends {WebInspector.Widget} 7 * @extends {WebInspector.Widget}
8 * @implements {WebInspector.Searchable}
8 * @param {!Document} parsedXML 9 * @param {!Document} parsedXML
9 */ 10 */
10 WebInspector.XMLView = function(parsedXML) 11 WebInspector.XMLView = function(parsedXML)
11 { 12 {
12 WebInspector.Widget.call(this, true); 13 WebInspector.Widget.call(this, true);
13 this.registerRequiredCSS("network/xmlView.css"); 14 this.registerRequiredCSS("network/xmlView.css");
14 this.contentElement.classList.add("shadow-xml-view", "source-code"); 15 this.contentElement.classList.add("shadow-xml-view", "source-code");
15 var treeOutline = new TreeOutline(); 16 this._treeOutline = new TreeOutline();
16 this.contentElement.appendChild(treeOutline.element); 17 this.contentElement.appendChild(this._treeOutline.element);
17 WebInspector.XMLView.Node.populate(treeOutline, parsedXML); 18 WebInspector.XMLView.Node.populate(this._treeOutline, parsedXML, this);
19
20 /** @type {?WebInspector.SearchableView} */
21 this._searchableView;
22 /** @type {number} */
23 this._currentSearchFocusIndex = 0;
24 /** @type {!Array.<!TreeElement>} */
25 this._currentSearchTreeElements = [];
26 /** @type {?WebInspector.SearchableView.SearchConfig} */
27 this._lastSearchConfig;
18 } 28 }
19 29
20 /** 30 /**
31 * @param {!Document} parsedXML
32 * @return {!WebInspector.SearchableView}
33 */
34 WebInspector.XMLView.createSearchableView = function(parsedXML)
35 {
36 var xmlView = new WebInspector.XMLView(parsedXML);
37 var searchableView = new WebInspector.SearchableView(xmlView);
38 searchableView.setPlaceholder(WebInspector.UIString("Find"));
39 xmlView.setSearchableView(searchableView);
40 xmlView.show(searchableView.element);
41 xmlView.contentElement.setAttribute("tabIndex", 0);
42 return searchableView;
43 }
44
45 /**
21 * @param {string} text 46 * @param {string} text
22 * @param {string} mimeType 47 * @param {string} mimeType
23 * @return {?Document} 48 * @return {?Document}
24 */ 49 */
25 WebInspector.XMLView.parseXML = function(text, mimeType) 50 WebInspector.XMLView.parseXML = function(text, mimeType)
26 { 51 {
27 var parsedXML; 52 var parsedXML;
28 try { 53 try {
29 parsedXML = (new DOMParser()).parseFromString(text, mimeType); 54 parsedXML = (new DOMParser()).parseFromString(text, mimeType);
30 } catch (e) { 55 } catch (e) {
31 return null; 56 return null;
32 } 57 }
33 if (parsedXML.body) 58 if (parsedXML.body)
34 return null; 59 return null;
35 return parsedXML; 60 return parsedXML;
36 } 61 }
37 62
38 WebInspector.XMLView.prototype = { 63 WebInspector.XMLView.prototype = {
64 /**
65 * @param {?WebInspector.SearchableView} view
66 */
67 setSearchableView: function(view)
68 {
69 this._searchableView = view;
70 },
lushnikov 2016/05/04 17:00:47 style: new line
allada 2016/05/04 21:22:35 Done.
71 /**
72 * @param {number} index
73 * @param {boolean} shouldJump
74 */
75 _goToMatch: function(index, shouldJump)
lushnikov 2016/05/04 17:00:47 _jumpToMatch: function... to correlate with Sear
allada 2016/05/04 21:22:35 Done.
76 {
77 if (!this._lastSearchConfig)
78 return;
79 var regEx = this._lastSearchConfig.toSearchRegex(true);
lushnikov 2016/05/04 17:00:47 regex
allada 2016/05/04 21:22:35 Done.
80 var newFocusElement = this._currentSearchTreeElements[index];
81 var previousFocusElement = this._currentSearchTreeElements[this._current SearchFocusIndex];
82
83 if (!newFocusElement)
84 index = 0;
85
86 if (previousFocusElement)
87 previousFocusElement.setSearchRegex(regEx);
88
89 this._updateSearchIndex(index);
90 if (newFocusElement) {
91 if (shouldJump)
92 newFocusElement.reveal(true);
93 newFocusElement.setSearchRegex(regEx, WebInspector.highlightedCurren tSearchResultClassName);
94 }
95 },
96
97 /**
98 * @param {number} count
99 */
100 _updateSearchCount: function(count)
101 {
102 if (!this._searchableView)
103 return;
104 this._searchableView.updateSearchMatchesCount(count);
105 },
106
107 /**
108 * @param {number} index
109 */
110 _updateSearchIndex: function(index)
111 {
112 this._currentSearchFocusIndex = index;
113 if (!this._searchableView)
114 return;
115 this._searchableView.updateCurrentMatchIndex(index);
116 },
117
118 _rerunSearch: function()
lushnikov 2016/05/04 17:00:47 It's usually good to avoid calling public API from
allada 2016/05/04 21:22:35 Done.
119 {
120 if (this._lastSearchConfig)
121 this.performSearch(this._lastSearchConfig, false, false);
122 },
123
124 /**
125 * @override
126 */
127 searchCanceled: function()
128 {
129 this._lastSearchConfig = null;
130 this._currentSearchTreeElements = [];
131 var currentElement = this._treeOutline.rootElement();
132
133 while (currentElement) {
134 if (currentElement instanceof WebInspector.XMLView.Node)
135 currentElement.revertHighlightChanges();
136 currentElement = currentElement.traverseNextTreeElement(false);
137 }
138 this._updateSearchCount(0);
139 this._updateSearchIndex(0);
140 },
141
142 /**
143 * @override
144 * @param {!WebInspector.SearchableView.SearchConfig} searchConfig
145 * @param {boolean} shouldJump
146 * @param {boolean=} jumpBackwards
147 */
148 performSearch: function(searchConfig, shouldJump, jumpBackwards)
149 {
150 var newIndex = this._currentSearchFocusIndex;
151 var previousSearchFocusElement = this._currentSearchTreeElements[newInde x];
152 this.searchCanceled();
153 var regEx = searchConfig.toSearchRegex(true);
154 var currentElement = this._treeOutline.rootElement();
155
156 while (currentElement) {
157 if (currentElement instanceof WebInspector.XMLView.Node) {
lushnikov 2016/05/04 17:00:48 let's use fast-returns as much as possible; the le
allada 2016/05/04 21:22:35 Done.
158 var hasMatch = currentElement.setSearchRegex(regEx);
159 if (hasMatch)
160 this._currentSearchTreeElements.push(currentElement);
161 var currentIndex = this._currentSearchTreeElements.length - 1;
162 if (previousSearchFocusElement === currentElement) {
163 if(hasMatch)
lushnikov 2016/05/04 17:00:48 style: space after if
allada 2016/05/04 21:22:36 Done.
164 newIndex = currentIndex;
165 else if (jumpBackwards)
166 newIndex = currentIndex - 1;
167 else
168 newIndex = currentIndex + 1;
169 }
170 }
171 currentElement = currentElement.traverseNextTreeElement(false);
172 }
173 this._updateSearchCount(this._currentSearchTreeElements.length);
174
175 if (!this._currentSearchTreeElements.length) {
176 this._goToMatch(0, shouldJump);
lushnikov 2016/05/04 17:00:48 why do we call _goToMatch if there are no matches?
allada 2016/05/04 21:22:36 Done.
177 return;
178 }
179 newIndex = mod(newIndex, this._currentSearchTreeElements.length);
180
181 this._lastSearchConfig = /** @type {?WebInspector.SearchableView.SearchC onfig} */ (searchConfig);
182 this._goToMatch(newIndex, shouldJump);
183 },
184
185 /**
186 * @override
187 */
188 jumpToNextSearchResult: function()
189 {
190 if (!this._currentSearchTreeElements.length)
191 return;
192
193 var newIndex = mod(this._currentSearchFocusIndex + 1, this._currentSearc hTreeElements.length);
194 this._goToMatch(newIndex, true);
195 },
196
197 /**
198 * @override
199 */
200 jumpToPreviousSearchResult: function()
201 {
202 if (!this._currentSearchTreeElements.length)
203 return;
204
205 var newIndex = mod(this._currentSearchFocusIndex - 1, this._currentSearc hTreeElements.length);
206 this._goToMatch(newIndex, true);
207 },
208
209 /**
210 * @override
211 * @return {boolean}
212 */
213 supportsCaseSensitiveSearch: function()
214 {
215 return true;
216 },
217
218 /**
219 * @override
220 * @return {boolean}
221 */
222 supportsRegexSearch: function()
223 {
224 return true;
225 },
226
39 __proto__: WebInspector.Widget.prototype 227 __proto__: WebInspector.Widget.prototype
40 } 228 }
41 229
42 /** 230 /**
43 * @constructor 231 * @constructor
44 * @extends {TreeElement} 232 * @extends {TreeElement}
45 * @param {!Node} node 233 * @param {!Node} node
46 * @param {boolean} closeTag 234 * @param {boolean} closeTag
235 * @param {!WebInspector.XMLView} xmlView
47 */ 236 */
48 WebInspector.XMLView.Node = function(node, closeTag) 237 WebInspector.XMLView.Node = function(node, closeTag, xmlView)
49 { 238 {
50 TreeElement.call(this, "", !closeTag && !!node.childElementCount); 239 TreeElement.call(this, "", !closeTag && !!node.childNodes.length);
51 this._node = node; 240 this._node = node;
52 this._closeTag = closeTag; 241 this._closeTag = closeTag;
53 this.selectable = false; 242 this.selectable = false;
243 /** @type {!Array.<!Object>} */
244 this._highlightChanges = [];
54 this._updateTitle(); 245 this._updateTitle();
246 this._xmlView = xmlView;
55 } 247 }
56 248
57 /** 249 /**
58 * @param {!TreeOutline|!TreeElement} root 250 * @param {!TreeOutline|!TreeElement} root
59 * @param {!Node} xmlNode 251 * @param {!Node} xmlNode
252 * @param {!WebInspector.XMLView} xmlView
60 */ 253 */
61 WebInspector.XMLView.Node.populate = function(root, xmlNode) 254 WebInspector.XMLView.Node.populate = function(root, xmlNode, xmlView)
62 { 255 {
63 var node = xmlNode.firstChild; 256 var node = xmlNode.firstChild;
64 while (node) { 257 while (node) {
65 var currentNode = node; 258 var currentNode = node;
66 node = node.nextSibling; 259 node = node.nextSibling;
67 var nodeType = currentNode.nodeType; 260 var nodeType = currentNode.nodeType;
68 // ignore empty TEXT 261 // ignore empty TEXT
69 if (nodeType === 3 && currentNode.nodeValue.match(/\s+/)) 262 if (nodeType === 3 && currentNode.nodeValue.match(/\s+/))
70 continue; 263 continue;
71 // ignore ATTRIBUTE, ENTITY_REFERENCE, ENTITY, DOCUMENT, DOCUMENT_TYPE, DOCUMENT_FRAGMENT, NOTATION 264 // ignore ATTRIBUTE, ENTITY_REFERENCE, ENTITY, DOCUMENT, DOCUMENT_TYPE, DOCUMENT_FRAGMENT, NOTATION
72 if ((nodeType !== 1) && (nodeType !== 3) && (nodeType !== 4) && (nodeTyp e !== 7) && (nodeType !== 8)) 265 if ((nodeType !== 1) && (nodeType !== 3) && (nodeType !== 4) && (nodeTyp e !== 7) && (nodeType !== 8))
73 continue; 266 continue;
74 root.appendChild(new WebInspector.XMLView.Node(currentNode, false)); 267 root.appendChild(new WebInspector.XMLView.Node(currentNode, false, xmlVi ew));
75 } 268 }
76 } 269 }
77 270
78 WebInspector.XMLView.Node.prototype = { 271 WebInspector.XMLView.Node.prototype = {
272 /**
273 * @param {?RegExp} regex
274 * @param {string=} additionalCssClassName
275 * @return {boolean}
276 */
277 setSearchRegex: function(regex, additionalCssClassName) {
278 var cssClasses = WebInspector.highlightedSearchResultClassName;
lushnikov 2016/05/04 17:00:48 let's structure this with fast-returns in the begi
allada 2016/05/04 21:22:36 Done.
279 if (additionalCssClassName)
280 cssClasses += " " + additionalCssClassName;
281 this.revertHighlightChanges();
282 if (regex)
283 this._applySearch(regex, this.listItemElement, cssClasses);
284 if (this._closeTag && this.parent && !this.parent.expanded) {
285 return false;
286 }
287 return !!this._highlightChanges.length;
288 },
289
290 /**
291 * @param {!RegExp} regex
292 * @param {!Element} element
293 * @param {string} cssClassName
294 */
295 _applySearch: function(regex, element, cssClassName)
lushnikov 2016/05/04 17:00:47 let's inline this method
allada 2016/05/04 21:22:35 Done.
296 {
297 var ranges = [];
298 var content = element.textContent.replace(/\xA0/g, " ");
allada 2016/05/03 18:22:55 This is because &nbsp; is treated as character 160
allada 2016/05/04 21:22:35 Done.
299 regex.lastIndex = 0;
300 var match = regex.exec(content);
301 while (match) {
302 ranges.push(new WebInspector.SourceRange(match.index, match[0].lengt h));
303 match = regex.exec(content);
304 }
305 if (ranges.length)
306 WebInspector.highlightRangesWithStyleClass(element, ranges, cssClass Name, this._highlightChanges);
307 },
308
309 revertHighlightChanges: function()
310 {
311 WebInspector.revertDomChanges(this._highlightChanges);
312 this._highlightChanges = [];
313 },
314
79 _updateTitle: function() 315 _updateTitle: function()
80 { 316 {
81 var node = this._node; 317 var node = this._node;
82 switch (node.nodeType) { 318 switch (node.nodeType) {
83 case 1: // ELEMENT 319 case 1: // ELEMENT
84 var tag = node.tagName; 320 var tag = node.tagName;
85 if (this._closeTag) { 321 if (this._closeTag) {
86 this._setTitle(["</" + tag + ">", "shadow-xml-view-tag"]); 322 this._setTitle(["</" + tag + ">", "shadow-xml-view-tag"]);
87 return; 323 return;
88 } 324 }
89 var titleItems = ["<" + tag, "shadow-xml-view-tag"]; 325 var titleItems = ["<" + tag, "shadow-xml-view-tag"];
90 var attributes = node.attributes; 326 var attributes = node.attributes;
91 for (var i = 0; i < attributes.length; ++i) { 327 for (var i = 0; i < attributes.length; ++i) {
92 var attributeNode = attributes.item(i); 328 var attributeNode = attributes.item(i);
93 titleItems.push( 329 titleItems.push(
94 "\u00a0", "shadow-xml-view-tag", 330 "\u00a0", "shadow-xml-view-tag",
95 attributeNode.name, "shadow-xml-view-attribute-name", 331 attributeNode.name, "shadow-xml-view-attribute-name",
96 "=\"", "shadow-xml-view-tag", 332 "=\"", "shadow-xml-view-tag",
97 attributeNode.value, "shadow-xml-view-attribute-value", 333 attributeNode.value, "shadow-xml-view-attribute-value",
98 "\"", "shadow-xml-view-tag") 334 "\"", "shadow-xml-view-tag")
99 } 335 }
100 if (!this.expanded) { 336 if (!this.expanded) {
101 if (node.childElementCount) { 337 if (node.childNodes.length) {
allada 2016/05/03 18:22:55 Found this bug in the tests I wrote. It was not co
lushnikov 2016/05/04 17:00:48 sg!
allada 2016/05/04 21:22:35 Reverted for now because of a few minor side affec
102 titleItems.push( 338 titleItems.push(
103 ">", "shadow-xml-view-tag", 339 ">", "shadow-xml-view-tag",
104 "\u2026", "shadow-xml-view-comment", 340 "\u2026", "shadow-xml-view-comment",
105 "</" + tag, "shadow-xml-view-tag"); 341 "</" + tag, "shadow-xml-view-tag");
106 } else if (this._node.textContent) { 342 } else if (this._node.textContent) {
107 titleItems.push( 343 titleItems.push(
108 ">", "shadow-xml-view-tag", 344 ">", "shadow-xml-view-tag",
109 node.textContent, "shadow-xml-view-text", 345 node.textContent, "shadow-xml-view-text",
110 "</" + tag, "shadow-xml-view-tag"); 346 "</" + tag, "shadow-xml-view-tag");
111 } else { 347 } else {
(...skipping 23 matching lines...) Expand all
135 371
136 /** 372 /**
137 * @param {!Array.<string>} items 373 * @param {!Array.<string>} items
138 */ 374 */
139 _setTitle: function(items) 375 _setTitle: function(items)
140 { 376 {
141 var titleFragment = createDocumentFragment(); 377 var titleFragment = createDocumentFragment();
142 for (var i = 0; i < items.length; i += 2) 378 for (var i = 0; i < items.length; i += 2)
143 titleFragment.createChild("span", items[i + 1]).textContent = items[ i]; 379 titleFragment.createChild("span", items[i + 1]).textContent = items[ i];
144 this.title = titleFragment; 380 this.title = titleFragment;
381 if (this._xmlView)
lushnikov 2016/05/04 17:00:47 AFAIU this if always evaluates to "true"
allada 2016/05/04 21:22:35 Done.
382 this._xmlView._rerunSearch();
145 }, 383 },
146 384
147 onattach: function() 385 onattach: function()
148 { 386 {
149 this.listItemElement.classList.toggle("shadow-xml-view-close-tag", this. _closeTag); 387 this.listItemElement.classList.toggle("shadow-xml-view-close-tag", this. _closeTag);
150 }, 388 },
151 389
152 onexpand: function() 390 onexpand: function()
153 { 391 {
154 this._updateTitle(); 392 this._updateTitle();
155 }, 393 },
156 394
157 oncollapse: function() 395 oncollapse: function()
158 { 396 {
159 this._updateTitle(); 397 this._updateTitle();
160 }, 398 },
161 399
162 onpopulate: function() 400 onpopulate: function()
163 { 401 {
164 WebInspector.XMLView.Node.populate(this, this._node); 402 WebInspector.XMLView.Node.populate(this, this._node, this._xmlView);
165 this.appendChild(new WebInspector.XMLView.Node(this._node, true)); 403 this.appendChild(new WebInspector.XMLView.Node(this._node, true, this._x mlView));
166 }, 404 },
167 405
168 __proto__: TreeElement.prototype 406 __proto__: TreeElement.prototype
169 } 407 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698