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

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: Addressing comments. 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
5 cr.define('settings', function() { 14 cr.define('settings', function() {
6 /** @const {string} */ 15 /** @const {string} */
7 var WRAPPER_CSS_CLASS = 'search-highlight-wrapper'; 16 var WRAPPER_CSS_CLASS = 'search-highlight-wrapper';
8 17
9 /** @const {string} */ 18 /** @const {string} */
10 var HIT_CSS_CLASS = 'search-highlight-hit'; 19 var HIT_CSS_CLASS = 'search-highlight-hit';
11 20
12 /** @const {!RegExp} */ 21 /** @const {!RegExp} */
13 var SANITIZE_REGEX = /[-[\]{}()*+?.,\\^$|#\s]/g; 22 var SANITIZE_REGEX = /[-[\]{}()*+?.,\\^$|#\s]/g;
14 23
(...skipping 75 matching lines...) Expand 10 before | Expand all | Expand 10 after
90 var span = document.createElement('span'); 99 var span = document.createElement('span');
91 span.classList.add(HIT_CSS_CLASS); 100 span.classList.add(HIT_CSS_CLASS);
92 span.style.backgroundColor = 'yellow'; 101 span.style.backgroundColor = 'yellow';
93 span.textContent = tokens[i]; 102 span.textContent = tokens[i];
94 wrapper.appendChild(span); 103 wrapper.appendChild(span);
95 } 104 }
96 } 105 }
97 } 106 }
98 107
99 /** 108 /**
109 * Checks whether the given |node| requires force rendering and schedules an
110 * async RenderTask if necessary.
111 *
112 * @param {!SearchContext} context
113 * @param {!Node} node
114 * @return {boolean} Whether a forced rendering task was scheduled.
115 * @private
116 */
117 function forceRenderNeeded_(context, node) {
118 if (node.tagName != 'TEMPLATE' ||
Dan Beam 2016/07/11 21:37:04 if this is truly a node, tagName -> nodeName tagN
dpapad 2016/07/11 23:25:32 Done (here and elsewhere). Wondering why the compi
119 node.getAttribute('name') === null ||
Dan Beam 2016/07/11 21:37:05 !node.hasAttribute('name') ?
dpapad 2016/07/11 23:25:32 Done.
120 node.if) {
121 return false;
122 }
123
124 // TODO(dpapad): Temporarily ignore site-settings because it throws an
125 // assertion error during force-rendering.
126 if (node.getAttribute('name').indexOf('site-') != -1)
Dan Beam 2016/07/11 21:37:05 consider caching the result of getAttribute('name'
dpapad 2016/07/11 23:25:32 This is meant to be temporary. We should be search
127 return false;
128
129 SearchManager.getInstance().queue_.addRenderTask(
130 new RenderTask(context, node));
Dan Beam 2016/07/11 21:37:05 i might make sense to move this scheduling lower
dpapad 2016/07/11 23:25:32 Done.
131 return true;
132 }
133
134 /**
100 * Traverses the entire DOM (including Shadow DOM), finds text nodes that 135 * Traverses the entire DOM (including Shadow DOM), finds text nodes that
101 * match the given regular expression and applies the highlight UI. It also 136 * match the given regular expression and applies the highlight UI. It also
102 * ensures that <settings-section> instances become visible if any matches 137 * ensures that <settings-section> instances become visible if any matches
103 * occurred under their subtree. 138 * occurred under their subtree.
104 * 139 *
105 * @param {!Element} page The page to be searched, should be either 140 * @param {!SearchContext} context
106 * <settings-basic-page> or <settings-advanced-page>. 141 * @param {!Node} root The root of the sub-tree to be searched
107 * @param {!RegExp} regExp The regular expression to detect matches.
108 * @private 142 * @private
109 */ 143 */
110 function findAndHighlightMatches_(page, regExp) { 144 function findAndHighlightMatches_(context, root) {
111 function doSearch(node) { 145 function doSearch(node) {
146 if (forceRenderNeeded_(context, node))
147 return;
148
112 if (IGNORED_ELEMENTS.has(node.tagName)) 149 if (IGNORED_ELEMENTS.has(node.tagName))
113 return; 150 return;
114 151
115 if (node.nodeType == Node.TEXT_NODE) { 152 if (node.nodeType == Node.TEXT_NODE) {
116 var textContent = node.nodeValue.trim(); 153 var textContent = node.nodeValue.trim();
117 if (textContent.length == 0) 154 if (textContent.length == 0)
118 return; 155 return;
119 156
120 if (regExp.test(textContent)) { 157 if (context.regExp.test(textContent)) {
121 revealParentSection_(node); 158 revealParentSection_(node);
122 highlight_(node, textContent.split(regExp)); 159 highlight_(node, textContent.split(context.regExp));
123 } 160 }
124 // Returning early since TEXT_NODE nodes never have children. 161 // Returning early since TEXT_NODE nodes never have children.
125 return; 162 return;
126 } 163 }
127 164
128 var child = node.firstChild; 165 var child = node.firstChild;
129 while (child !== null) { 166 while (child !== null) {
130 // Getting a reference to the |nextSibling| before calling doSearch() 167 // Getting a reference to the |nextSibling| before calling doSearch()
131 // because |child| could be removed from the DOM within doSearch(). 168 // because |child| could be removed from the DOM within doSearch().
132 var nextSibling = child.nextSibling; 169 var nextSibling = child.nextSibling;
133 doSearch(child); 170 doSearch(child);
134 child = nextSibling; 171 child = nextSibling;
135 } 172 }
136 173
137 var shadowRoot = node.shadowRoot; 174 var shadowRoot = node.shadowRoot;
138 if (shadowRoot) 175 if (shadowRoot)
139 doSearch(shadowRoot); 176 doSearch(shadowRoot);
140 } 177 }
141 178
142 doSearch(page); 179 doSearch(root);
143 } 180 }
144 181
145 /** 182 /**
146 * Finds and makes visible the <settings-section> parent of |node|. 183 * Finds and makes visible the <settings-section> parent of |node|.
147 * @param {!Node} node 184 * @param {!Node} node
148 */ 185 */
149 function revealParentSection_(node) { 186 function revealParentSection_(node) {
150 // Find corresponding SETTINGS-SECTION parent and make it visible. 187 // Find corresponding SETTINGS-SECTION parent and make it visible.
151 var parent = node; 188 var parent = node;
152 while (parent && parent.tagName !== 'SETTINGS-SECTION') { 189 while (parent && parent.tagName !== 'SETTINGS-SECTION') {
153 parent = parent.nodeType == Node.DOCUMENT_FRAGMENT_NODE ? 190 parent = parent.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
154 parent.host : parent.parentNode; 191 parent.host : parent.parentNode;
155 } 192 }
156 if (parent) 193 if (parent)
157 parent.hidden = false; 194 parent.hidden = false;
158 } 195 }
159 196
160 /** 197 /**
161 * @param {!Element} page 198 * @param {!Node} page
162 * @param {boolean} visible 199 * @param {boolean} visible
163 * @private 200 * @private
164 */ 201 */
165 function setSectionsVisibility_(page, visible) { 202 function setSectionsVisibility_(page, visible) {
166 var sections = Polymer.dom(page.root).querySelectorAll('settings-section'); 203 var sections = Polymer.dom(page.root).querySelectorAll('settings-section');
167 for (var i = 0; i < sections.length; i++) 204 for (var i = 0; i < sections.length; i++)
168 sections[i].hidden = !visible; 205 sections[i].hidden = !visible;
169 } 206 }
170 207
171 /** 208 /**
172 * Performs hierarchical search, starting at the given page element. 209 * @constructor
210 *
211 * @param {!SearchContext} context
212 * @param {!Node} node
213 */
214 function Task(context, node) {
215 /** @protected {!SearchContext} */
216 this.context = context;
217
218 /** @protected {!Node} */
219 this.node = node;
220 }
221
222 Task.prototype = {
223 /**
224 * @abstract
225 * @return {!Promise}
226 */
227 exec: function() {},
228 };
229
230 /**
231 * @constructor
232 * @extends {Task}
233 *
234 * @param {!SearchContext} context
235 * @param {!Node} node
236 */
237 function RenderTask(context, node) {
238 Task.call(this, context, node);
239 }
240
241 RenderTask.prototype = {
242 /** @override */
243 exec: function() {
244 var subpageTemplate =
245 this.node['_content'].querySelector('settings-subpage');
246 subpageTemplate.id = subpageTemplate.id || this.node.getAttribute('name');
247 assert(!this.node.if);
248 this.node.if = true;
249
250 return this.getRenderedNode_(subpageTemplate.id).then(function(node) {
251 // Register a SearchTask for the part of the DOM that was just
252 // rendered.
253 SearchManager.getInstance().queue_.addSearchTask(
254 new SearchTask(this.context, node));
255 }.bind(this));
256 },
257
258 /**
259 * @param {string} id
260 * @return {!Promise<!Node>}
261 */
262 getRenderedNode_: function(id) {
Dan Beam 2016/07/11 21:37:05 what is the point of making this a separate functi
dpapad 2016/07/11 23:25:32 Inlined. It was more readable IMO.
263 var parent = this.node.parentNode;
264 return new Promise(function(resolve, reject) {
265 parent.async(function() {
266 resolve(parent.querySelector('#' + id));
267 });
268 });
269 },
270 };
271
272 /**
273 * @constructor
274 * @extends {Task}
275 *
276 * @param {!SearchContext} context
277 * @param {!Node} node
278 */
279 function SearchTask(context, node) {
280 Task.call(this, context, node);
281 }
282
283 SearchTask.prototype = {
284 /** @override */
285 exec: function() {
286 findAndHighlightMatches_(this.context, this.node);
287 return Promise.resolve();
288 },
289 };
290
291 /**
292 * @constructor
293 * @extends {Task}
294 *
295 * @param {!SearchContext} context
296 * @param {!Node} page
297 */
298 function TopLevelSearchTask(context, page) {
299 Task.call(this, context, page);
300 }
301
302 TopLevelSearchTask.prototype = {
303 /** @override */
304 exec: function() {
305 findAndRemoveHighlights_(this.node);
306
307 if (this.context.regExp === null) {
308 setSectionsVisibility_(this.node, true);
309 return Promise.resolve();
310 }
311
312 setSectionsVisibility_(this.node, false);
313 findAndHighlightMatches_(this.context, this.node);
Dan Beam 2016/07/11 21:37:05 nit: slightly less duplication var searchActive =
dpapad 2016/07/11 23:25:32 Done.
314 return Promise.resolve();
315 },
316 };
317
318 /**
319 * @constructor
320 */
321 function TaskQueue() {
322 /**
323 * @private {{
324 * high: !Array<!Task>,
325 * middle: !Array<!Task>,
326 * low: !Array<!Task>
327 * }}
328 */
329 this.queues_ = {high: [], middle: [], low: []};
330
331 /** @private {?Task} */
332 this.activeTask_ = null;
333 }
334
335 TaskQueue.prototype = {
336 /** @param {!TopLevelSearchTask} task */
337 addTopLevelSearchTask: function(task) {
338 this.queues_.high.push(task);
339 this.consumePending_();
340 },
341
342 /** @param {!SearchTask} task */
343 addSearchTask: function(task) {
344 this.queues_.middle.push(task);
345 this.consumePending_();
346 },
347
Dan Beam 2016/07/11 21:37:05 nit: @param here as well
dpapad 2016/07/11 23:25:32 Done.
348 addRenderTask: function(task) {
349 this.queues_.low.push(task);
350 this.consumePending_();
351 },
352
353 /**
354 * @return {?Task}
355 * @private
356 */
357 popNextTask_: function() {
358 return this.queues_.high.shift() ||
359 this.queues_.middle.shift() ||
360 this.queues_.low.shift() || null;
Dan Beam 2016/07/11 21:37:05 i don't really see the point of |null vs |undefine
dpapad 2016/07/11 23:25:32 Removed null. But since you asked explaining below
361 },
362
363 /** @private */
364 consumePending_: function() {
365 if (this.activeTask_)
366 return;
367
368 var manager = SearchManager.getInstance();
369 while (1) {
370 this.activeTask_ = this.popNextTask_();
371 if (!this.activeTask_)
372 return;
373
374 if (this.activeTask_.context.id != manager.activeContext_.id) {
Dan Beam 2016/07/11 21:37:05 why can't we drop all tasks when search() is calle
dpapad 2016/07/11 23:25:32 Done, see more detailed comment at line 423.
375 // Dropping this task without ever executing it, since a new search
376 // has been issued since this task was submitted.
Dan Beam 2016/07/11 21:37:04 nit: submitted -> queued
dpapad 2016/07/11 23:25:32 Done.
377 continue;
378 }
379
380 window.requestIdleCallback(function() {
Dan Beam 2016/07/11 21:37:05 so if this is called with nothing to do, we still
Dan Beam 2016/07/11 21:42:13 ignore this if running even 1 task synchronously c
dpapad 2016/07/11 23:25:32 Yes, a single task could potentially freeze the UI
381 this.activeTask_.exec().then(function(result) {
382 this.activeTask_ = null;
383 this.consumePending_();
384 }.bind(this));
385 }.bind(this));
386 break;
387 }
388 },
389 };
390
391 /**
392 * @constructor
393 */
394 var SearchManager = function() {
395 /** @private {!TaskQueue} */
396 this.queue_ = new TaskQueue();
397
398 /** @private {!SearchContext} */
399 this.activeContext_ = {
400 id: 0, rawQuery: null, regExp: null,
401 };
402 };
403 cr.addSingletonGetter(SearchManager);
404
405 SearchManager.prototype = {
406 /**
407 * @param {string} text The text to search for.
408 * @param {!Node} page
409 */
410 search: function(text, page) {
411 if (this.activeContext_.rawQuery != text) {
412 var newId = this.activeContext_.id + 1;
413
414 var regExp = null;
415 // Generate search text by escaping any characters that would be
416 // problematic for regular expressions.
417 var searchText = text.trim().replace(SANITIZE_REGEX, '\\$&');
418 if (searchText.length > 0)
419 regExp = new RegExp('(' + searchText + ')', 'i');
420
421 this.activeContext_ = {
422 id: newId, rawQuery: text, regExp: regExp,
423 };
Dan Beam 2016/07/11 21:37:05 why don't we clear the queue right here?
dpapad 2016/07/11 23:25:32 Done. This made me discover a small bug that I als
424 }
425
426 this.queue_.addTopLevelSearchTask(
427 new TopLevelSearchTask(this.activeContext_, page));
428 },
429 };
430
431 /**
173 * @param {string} text 432 * @param {string} text
174 * @param {!Element} page Must be either <settings-basic-page> or 433 * @param {!Node} page
175 * <settings-advanced-page>.
176 */ 434 */
177 function search(text, page) { 435 function search(text, page) {
178 findAndRemoveHighlights_(page); 436 SearchManager.getInstance().search(text, page);
179
180 // Generate search text by escaping any characters that would be problematic
181 // for regular expressions.
182 var searchText = text.trim().replace(SANITIZE_REGEX, '\\$&');
183 if (searchText.length == 0) {
184 setSectionsVisibility_(page, true);
185 return;
186 }
187
188 setSectionsVisibility_(page, false);
189 findAndHighlightMatches_(page, new RegExp('(' + searchText + ')', 'i'));
190 } 437 }
191 438
192 return { 439 return {
193 search: search, 440 search: search,
194 }; 441 };
195 }); 442 });
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