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

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
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' ||
119 node.getAttribute('name') === null ||
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)
127 return false;
128
129 SearchManager.getInstance().queue_.addRenderTask(
130 new RenderTask(context, node));
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) {
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);
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;
Dan Beam 2016/07/11 21:48:22 i understand why it's helpful to use a task here,
Dan Beam 2016/07/12 00:08:41 ping
dpapad 2016/07/12 02:20:27 Done.
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
348 /** @param {!RenderTask} task */
349 addRenderTask: function(task) {
350 this.queues_.low.push(task);
351 this.consumePending_();
352 },
353
354 /**
355 * @return {?Task}
356 * @private
357 */
358 popNextTask_: function() {
359 return this.queues_.high.shift() ||
360 this.queues_.middle.shift() ||
361 this.queues_.low.shift() || null;
362 },
363
364 /** @private */
365 consumePending_: function() {
366 if (this.activeTask_)
Dan Beam 2016/07/11 21:48:22 if (this.running_) return; while (1) { var ta
Dan Beam 2016/07/12 00:08:41 that's not to say that we don't need to check its
dpapad 2016/07/12 02:20:27 Done.
367 return;
368
369 var manager = SearchManager.getInstance();
370 while (1) {
371 this.activeTask_ = this.popNextTask_();
372 if (!this.activeTask_)
373 return;
374
375 if (this.activeTask_.context.id != manager.activeContext_.id) {
376 // Dropping this task without ever executing it, since a new search
377 // has been issued since this task was submitted.
378 continue;
379 }
380
381 window.requestIdleCallback(function() {
382 this.activeTask_.exec().then(function(result) {
383 this.activeTask_ = null;
384 this.consumePending_();
385 }.bind(this));
386 }.bind(this));
387 return;
388 }
389 },
390 };
391
392 /**
393 * @constructor
394 */
395 var SearchManager = function() {
396 /** @private {!TaskQueue} */
397 this.queue_ = new TaskQueue();
398
399 /** @private {!SearchContext} */
400 this.activeContext_ = {
401 id: 0, rawQuery: null, regExp: null,
402 };
403 };
404 cr.addSingletonGetter(SearchManager);
405
406 SearchManager.prototype = {
407 /**
408 * @param {string} text The text to search for.
409 * @param {!Node} page
410 */
411 search: function(text, page) {
412 if (this.activeContext_.rawQuery != text) {
413 var newId = this.activeContext_.id + 1;
414
415 var regExp = null;
416 // Generate search text by escaping any characters that would be
417 // problematic for regular expressions.
418 var searchText = text.trim().replace(SANITIZE_REGEX, '\\$&');
419 if (searchText.length > 0)
420 regExp = new RegExp('(' + searchText + ')', 'i');
421
422 this.activeContext_ = {
423 id: newId, rawQuery: text, regExp: regExp,
424 };
425 }
426
427 this.queue_.addTopLevelSearchTask(
428 new TopLevelSearchTask(this.activeContext_, page));
429 },
430 };
431
432 /**
173 * @param {string} text 433 * @param {string} text
174 * @param {!Element} page Must be either <settings-basic-page> or 434 * @param {!Node} page
175 * <settings-advanced-page>.
176 */ 435 */
177 function search(text, page) { 436 function search(text, page) {
178 findAndRemoveHighlights_(page); 437 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 } 438 }
191 439
192 return { 440 return {
193 search: search, 441 search: search,
194 }; 442 };
195 }); 443 });
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