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

Side by Side Diff: chrome/browser/resources/settings/search_settings.js

Issue 2103133007: MD Settings: Second iteration of search within settings. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@cr_search_migration1
Patch Set: Nits. Created 4 years, 5 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
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 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 /**
6 * @typedef {{
7 * id: number,
8 * rawQuery: ?string,
9 * regExp: ?RegExp
10 * }}
11 */
12 var SearchContext;
13
Dan Beam 2016/07/09 01:36:07 why \n\n?
dpapad 2016/07/11 21:18:04 Done.
14
5 cr.define('settings', function() { 15 cr.define('settings', function() {
6 /** @const {string} */ 16 /** @const {string} */
7 var WRAPPER_CSS_CLASS = 'search-highlight-wrapper'; 17 var WRAPPER_CSS_CLASS = 'search-highlight-wrapper';
8 18
9 /** @const {string} */ 19 /** @const {string} */
10 var HIT_CSS_CLASS = 'search-highlight-hit'; 20 var HIT_CSS_CLASS = 'search-highlight-hit';
11 21
12 /** @const {!RegExp} */ 22 /** @const {!RegExp} */
13 var SANITIZE_REGEX = /[-[\]{}()*+?.,\\^$|#\s]/g; 23 var SANITIZE_REGEX = /[-[\]{}()*+?.,\\^$|#\s]/g;
14 24
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
90 var span = document.createElement('span'); 100 var span = document.createElement('span');
91 span.classList.add(HIT_CSS_CLASS); 101 span.classList.add(HIT_CSS_CLASS);
92 span.style.backgroundColor = 'yellow'; 102 span.style.backgroundColor = 'yellow';
93 span.textContent = tokens[i]; 103 span.textContent = tokens[i];
94 wrapper.appendChild(span); 104 wrapper.appendChild(span);
95 } 105 }
96 } 106 }
97 } 107 }
98 108
99 /** 109 /**
110 * Checks whether the given |node| requires force rendering and schedules an
111 * async RenderTask if necessary.
112 *
113 * @param {!SearchContext} searchContext
114 * @param {!Node} node
115 * @return {boolean} Whether a forced rendering task was scheduled.
116 * @private
117 */
118 function forceRenderNeeded_(searchContext, node) {
119 if (node.tagName != 'TEMPLATE' ||
120 node.getAttribute('name') === null ||
121 node.if) {
122 return false;
123 }
124
125 // TODO(dpapad): Temporarily ignore site-settings because it throws an
126 // assertion error during force-rendering.
127 if (node.getAttribute('name').indexOf('site-') != -1)
128 return false;
129
130 SearchManager.getInstance().queue_.addTask(
131 new RenderTask(searchContext, node));
Dan Beam 2016/07/09 01:36:07 see below about "postExec" var queue = SearchMan
dpapad 2016/07/11 21:18:04 Addressed in a slightly different (I think simpler
132 return true;
133 }
134
135 /**
100 * Traverses the entire DOM (including Shadow DOM), finds text nodes that 136 * Traverses the entire DOM (including Shadow DOM), finds text nodes that
101 * match the given regular expression and applies the highlight UI. It also 137 * match the given regular expression and applies the highlight UI. It also
102 * ensures that <settings-section> instances become visible if any matches 138 * ensures that <settings-section> instances become visible if any matches
103 * occurred under their subtree. 139 * occurred under their subtree.
104 * 140 *
105 * @param {!Element} page The page to be searched, should be either 141 * @param {!SearchContext} searchContext
106 * <settings-basic-page> or <settings-advanced-page>. 142 * @param {!Node} root The root of the sub-tree to be searched
107 * @param {!RegExp} regExp The regular expression to detect matches.
108 * @private 143 * @private
109 */ 144 */
110 function findAndHighlightMatches_(page, regExp) { 145 function findAndHighlightMatches_(searchContext, root) {
111 function doSearch(node) { 146 function doSearch(node) {
147 if (forceRenderNeeded_(searchContext, node))
148 return;
149
112 if (IGNORED_ELEMENTS.has(node.tagName)) 150 if (IGNORED_ELEMENTS.has(node.tagName))
113 return; 151 return;
114 152
115 if (node.nodeType == Node.TEXT_NODE) { 153 if (node.nodeType == Node.TEXT_NODE) {
116 var textContent = node.nodeValue.trim(); 154 var textContent = node.nodeValue.trim();
117 if (textContent.length == 0) 155 if (textContent.length == 0)
118 return; 156 return;
119 157
120 if (regExp.test(textContent)) { 158 if (searchContext.regExp.test(textContent)) {
121 revealParentSection_(node); 159 revealParentSection_(node);
122 highlight_(node, textContent.split(regExp)); 160 highlight_(node, textContent.split(searchContext.regExp));
123 } 161 }
124 // Returning early since TEXT_NODE nodes never have children. 162 // Returning early since TEXT_NODE nodes never have children.
125 return; 163 return;
126 } 164 }
127 165
128 var child = node.firstChild; 166 var child = node.firstChild;
129 while (child !== null) { 167 while (child !== null) {
130 // Getting a reference to the |nextSibling| before calling doSearch() 168 // Getting a reference to the |nextSibling| before calling doSearch()
131 // because |child| could be removed from the DOM within doSearch(). 169 // because |child| could be removed from the DOM within doSearch().
132 var nextSibling = child.nextSibling; 170 var nextSibling = child.nextSibling;
133 doSearch(child); 171 doSearch(child);
134 child = nextSibling; 172 child = nextSibling;
135 } 173 }
136 174
137 var shadowRoot = node.shadowRoot; 175 var shadowRoot = node.shadowRoot;
138 if (shadowRoot) 176 if (shadowRoot)
139 doSearch(shadowRoot); 177 doSearch(shadowRoot);
140 } 178 }
141 179
142 doSearch(page); 180 doSearch(root);
143 } 181 }
144 182
145 /** 183 /**
146 * Finds and makes visible the <settings-section> parent of |node|. 184 * Finds and makes visible the <settings-section> parent of |node|.
147 * @param {!Node} node 185 * @param {!Node} node
148 */ 186 */
149 function revealParentSection_(node) { 187 function revealParentSection_(node) {
150 // Find corresponding SETTINGS-SECTION parent and make it visible. 188 // Find corresponding SETTINGS-SECTION parent and make it visible.
151 var parent = node; 189 var parent = node;
152 while (parent && parent.tagName !== 'SETTINGS-SECTION') { 190 while (parent && parent.tagName !== 'SETTINGS-SECTION') {
153 parent = parent.nodeType == Node.DOCUMENT_FRAGMENT_NODE ? 191 parent = parent.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
154 parent.host : parent.parentNode; 192 parent.host : parent.parentNode;
155 } 193 }
156 if (parent) 194 if (parent)
157 parent.hidden = false; 195 parent.hidden = false;
158 } 196 }
159 197
160 /** 198 /**
161 * @param {!Element} page 199 * @param {!Node} page
162 * @param {boolean} visible 200 * @param {boolean} visible
163 * @private 201 * @private
164 */ 202 */
165 function setSectionsVisibility_(page, visible) { 203 function setSectionsVisibility_(page, visible) {
166 var sections = Polymer.dom(page.root).querySelectorAll('settings-section'); 204 var sections = Polymer.dom(page.root).querySelectorAll('settings-section');
167 for (var i = 0; i < sections.length; i++) 205 for (var i = 0; i < sections.length; i++)
168 sections[i].hidden = !visible; 206 sections[i].hidden = !visible;
169 } 207 }
170 208
171 /** 209 /**
172 * Performs hierarchical search, starting at the given page element. 210 * @constructor
173 * @param {string} text 211 *
174 * @param {!Element} page Must be either <settings-basic-page> or 212 * @param {!SearchContext} searchContext
175 * <settings-advanced-page>. 213 * @param {!Node} node
176 */ 214 */
177 function search(text, page) { 215 function Task(searchContext, node) {
178 findAndRemoveHighlights_(page); 216 /** @protected {!SearchContext} */
179 217 this.searchContext = searchContext;
Dan Beam 2016/07/09 01:36:07 nit: this.context while there's no other context?
dpapad 2016/07/11 21:18:05 Renamed, s/searchContext/context s/activeSearchCon
180 // Generate search text by escaping any characters that would be problematic 218
181 // for regular expressions. 219 /** @protected {!Node} */
182 var searchText = text.trim().replace(SANITIZE_REGEX, '\\$&'); 220 this.node = node;
Dan Beam 2016/07/09 01:36:07 /** @type {function(?):void|undefined} */ this.pos
dpapad 2016/07/11 21:18:04 Acknowledged.
183 if (searchText.length == 0) { 221 }
184 setSectionsVisibility_(page, true); 222
185 return; 223 Task.prototype = {
186 } 224 /**
187 225 * @abstract
188 setSectionsVisibility_(page, false); 226 * @return {!Promise}
189 findAndHighlightMatches_(page, new RegExp('(' + searchText + ')', 'i')); 227 */
190 } 228 exec: function() {},
229 };
230
231 /**
232 * @constructor
233 * @extends {Task}
234 *
235 * @param {!SearchContext} searchContext
236 * @param {!Node} node
237 */
238 function RenderTask(searchContext, node) {
239 Task.call(this, searchContext, node);
240 }
241
242 RenderTask.prototype = {
243 /** @override */
244 exec: function() {
245 return new Promise(function(resolve, reject) {
246 var subpageTemplate =
247 this.node._content.querySelector('settings-subpage');
Dan Beam 2016/07/09 01:36:07 _content :(
dpapad 2016/07/11 21:18:04 It gets a bit worse (but don't know of a better al
248 if (!subpageTemplate.id)
249 subpageTemplate.id = this.node.getAttribute('name');
250
251 assert(!this.node.if);
252 this.node.if = true;
253 this.getRenderedNode_(subpageTemplate.id).then(resolve, reject);
254 }.bind(this));
Dan Beam 2016/07/09 01:36:07 var subpageTemplate = this.node._content.querySele
dpapad 2016/07/11 21:18:04 Done. As said earlier, I am registering a new Sear
255 },
256
257 /**
258 * @param {string} id
259 * @return {!Promise<!Node>}
260 */
261 getRenderedNode_: function(id) {
262 var parent = this.node.parentNode;
263 return new Promise(function(resolve, reject) {
264 parent.async(function() {
265 resolve(parent.querySelector('#' + id));
266 });
267 });
268 },
269 };
270
271 /**
272 * @constructor
273 * @extends {Task}
274 *
275 * @param {!SearchContext} searchContext
276 * @param {!Node} node
277 */
278 function SearchTask(searchContext, node) {
279 Task.call(this, searchContext, node);
280 }
281
282 SearchTask.prototype = {
283 /** @override */
284 exec: function() {
285 return new Promise(function(resolve, reject) {
286 findAndHighlightMatches_(this.searchContext, this.node);
287 resolve();
288 }.bind(this));
Dan Beam 2016/07/09 01:36:07 findAndHighlightMatches_(this.searchContext, this.
dpapad 2016/07/11 21:18:05 Done.
289 },
290 };
291
292 /**
293 * @constructor
294 * @extends {Task}
295 *
296 * @param {!SearchContext} searchContext
297 * @param {!Node} page
298 */
299 function TopLevelSearchTask(searchContext, page) {
300 Task.call(this, searchContext, page);
301 }
302
303 TopLevelSearchTask.prototype = {
304 /** @override */
305 exec: function() {
306 return new Promise(function(resolve, reject) {
307 findAndRemoveHighlights_(this.node);
308
309 if (this.searchContext.regExp === null) {
310 setSectionsVisibility_(this.node, true);
311 resolve();
312 return;
313 }
314
315 setSectionsVisibility_(this.node, false);
316 findAndHighlightMatches_(this.searchContext, this.node);
317 resolve();
318 }.bind(this));
319 },
320 };
321
322 /**
323 * @constructor
324 */
325 function TaskQueue() {
326 /** @private {!Array<!Task>} */
327 this.highPriorityQueue_ = [];
Dan Beam 2016/07/09 01:36:07 nit: this.queues_ = {high: [], middle: [], low: []
dpapad 2016/07/11 21:18:04 Done.
328
329 /** @private {!Array<!Task>} */
330 this.middlePriorityQueue_ = [];
331
332 /** @private {!Array<!Task>} */
333 this.lowPriorityQueue_ = [];
334
335 /** @private {?Task} */
336 this.currentActiveTask_ = null;
Dan Beam 2016/07/09 01:36:07 nit: why both current and active? just one or the
dpapad 2016/07/11 21:18:05 Renamed to activeTask_.
337 }
338
339 TaskQueue.prototype = {
340 /** @param {!Task} task */
341 addTask: function(task) {
342 if (task instanceof TopLevelSearchTask)
Dan Beam 2016/07/09 01:36:07 i think it'd be nice not to do these instanceof ch
dpapad 2016/07/11 21:18:04 Removed all instanceof checks in favor of three de
343 this.highPriorityQueue_.push(task);
344 else if (task instanceof SearchTask)
345 this.middlePriorityQueue_.push(task);
346 else
347 this.lowPriorityQueue_.push(task);
348 this.consumePending_();
349 },
350
351 /**
352 * @return {?Task}
353 * @private
354 */
355 popNextTask_: function() {
356 var queue = null;
357 if (this.highPriorityQueue_.length > 0)
358 queue = this.highPriorityQueue_;
359 else if (this.middlePriorityQueue_.length > 0)
360 queue = this.middlePriorityQueue_;
361 else if (this.lowPriorityQueue_.length > 0)
362 queue = this.lowPriorityQueue_;
363
364 if (queue === null)
365 return null;
366
367 return queue.splice(0, 1)[0];
Dan Beam 2016/07/09 01:36:07 return this.highPriorityQueue_.shift() || this.mid
dpapad 2016/07/11 21:18:04 Done.
368 },
369
370 /** @private */
371 consumePending_: function() {
372 if (this.currentActiveTask_ !== null)
373 return;
374
375 if ((this.currentActiveTask_ = this.popNextTask_()) === null)
Dan Beam 2016/07/09 01:36:07 if (this.currentActiveTask_) return; var manage
dpapad 2016/07/11 21:18:05 Done, with some modifications 1) Using while inste
376 return;
377
378 if (this.currentActiveTask_.searchContext.id !==
379 SearchManager.getInstance().activeSearchContext_.id) {
380 // Dropping this task without ever executing it, since a new search has
381 // been issued since this task was submitted.
382 this.currentActiveTask_ = null;
383 this.consumePending_();
384 return;
385 }
386
387 window.requestIdleCallback(function() {
388 this.execTask_(assert(this.currentActiveTask_)).then(function() {
389 this.currentActiveTask_ = null;
390 this.consumePending_();
391 }.bind(this));
392 }.bind(this));
393 },
394
395 /**
396 * @param {!Task} task
397 * @return {!Promise}
398 * @private
399 */
400 execTask_: function(task) {
Dan Beam 2016/07/09 01:36:07 why do we need this if it's only called once? why
dpapad 2016/07/11 21:18:04 Removed. This is a left over from a previous versi
401 return task.exec().then(function(result) {
402 if (task instanceof RenderTask) {
403 // Register a SearchTask for the part of the DOM that was just
404 // rendered.
405 this.addTask(new SearchTask(task.searchContext, result));
Dan Beam 2016/07/09 01:36:07 it'd be nice if the taskqueue didn't need to know
dpapad 2016/07/11 21:18:05 Done, this part has been removed. TaskQueue is una
406 }
407 }.bind(this));
408 },
409 };
410
Dan Beam 2016/07/09 01:36:07 nit: why \n\n?
dpapad 2016/07/11 21:18:04 Removed. Initially added because \n\n makes visual
411
412 /**
413 * @constructor
414 */
415 var SearchManager = function() {
416 /** @private {!TaskQueue} */
417 this.queue_ = new TaskQueue();
418
419 /** @private {!SearchContext} */
420 this.activeSearchContext_ = {
421 id: 0, rawQuery: null, regExp: null,
422 };
423 };
424 cr.addSingletonGetter(SearchManager);
425
426 SearchManager.prototype = {
427 /**
428 * @param {string} text The text to search for.
429 * @param {!Node} page
430 */
431 search: function(text, page) {
432 if (this.activeSearchContext_.rawQuery != text) {
433 var newId = this.activeSearchContext_.id + 1;
434
435 var regExp = null;
436 // Generate search text by escaping any characters that would be
437 // problematic for regular expressions.
438 var searchText = text.trim().replace(SANITIZE_REGEX, '\\$&');
439 if (searchText.length > 0)
440 regExp = new RegExp('(' + searchText + ')', 'i');
441
442 this.activeSearchContext_ = {
443 id: newId, rawQuery: text, regExp: regExp,
444 };
445 }
446
447 this.queue_.addTask(
448 new TopLevelSearchTask(this.activeSearchContext_, page));
449 },
450 };
191 451
192 return { 452 return {
193 search: search, 453 search: function(text, page) {
454 SearchManager.getInstance().search(text, page);
455 },
194 }; 456 };
195 }); 457 });
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698