OLD | NEW |
---|---|
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 cr.define('settings', function() { | 5 cr.define('settings', function() { |
6 /** @const {string} */ | 6 /** @const {string} */ |
7 var WRAPPER_CSS_CLASS = 'search-highlight-wrapper'; | 7 var WRAPPER_CSS_CLASS = 'search-highlight-wrapper'; |
8 | 8 |
9 /** @const {string} */ | 9 /** @const {string} */ |
10 var ORIGINAL_CONTENT_CSS_CLASS = 'search-highlight-original-content'; | 10 var ORIGINAL_CONTENT_CSS_CLASS = 'search-highlight-original-content'; |
(...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
118 * | 118 * |
119 * @param {!settings.SearchRequest} request | 119 * @param {!settings.SearchRequest} request |
120 * @param {!Node} root The root of the sub-tree to be searched | 120 * @param {!Node} root The root of the sub-tree to be searched |
121 * @private | 121 * @private |
122 */ | 122 */ |
123 function findAndHighlightMatches_(request, root) { | 123 function findAndHighlightMatches_(request, root) { |
124 var foundMatches = false; | 124 var foundMatches = false; |
125 function doSearch(node) { | 125 function doSearch(node) { |
126 if (node.nodeName == 'TEMPLATE' && node.hasAttribute('route-path') && | 126 if (node.nodeName == 'TEMPLATE' && node.hasAttribute('route-path') && |
127 !node.if && !node.hasAttribute(SKIP_SEARCH_CSS_ATTRIBUTE)) { | 127 !node.if && !node.hasAttribute(SKIP_SEARCH_CSS_ATTRIBUTE)) { |
128 getSearchManager().queue_.addRenderTask( | 128 request.queue_.addRenderTask(new RenderTask(request, node)); |
129 new RenderTask(request, node)); | |
130 return; | 129 return; |
131 } | 130 } |
132 | 131 |
133 if (IGNORED_ELEMENTS.has(node.nodeName)) | 132 if (IGNORED_ELEMENTS.has(node.nodeName)) |
134 return; | 133 return; |
135 | 134 |
136 if (node instanceof HTMLElement) { | 135 if (node instanceof HTMLElement) { |
137 var element = /** @type {HTMLElement} */(node); | 136 var element = /** @type {HTMLElement} */(node); |
138 if (element.hasAttribute(SKIP_SEARCH_CSS_ATTRIBUTE) || | 137 if (element.hasAttribute(SKIP_SEARCH_CSS_ATTRIBUTE) || |
139 element.hasAttribute('hidden') || | 138 element.hasAttribute('hidden') || |
(...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
297 assert(!this.node.if); | 296 assert(!this.node.if); |
298 this.node.if = true; | 297 this.node.if = true; |
299 | 298 |
300 return new Promise(function(resolve, reject) { | 299 return new Promise(function(resolve, reject) { |
301 var parent = this.node.parentNode; | 300 var parent = this.node.parentNode; |
302 parent.async(function() { | 301 parent.async(function() { |
303 var renderedNode = | 302 var renderedNode = |
304 parent.querySelector('[route-path="' + routePath + '"]'); | 303 parent.querySelector('[route-path="' + routePath + '"]'); |
305 // Register a SearchAndHighlightTask for the part of the DOM that was | 304 // Register a SearchAndHighlightTask for the part of the DOM that was |
306 // just rendered. | 305 // just rendered. |
307 getSearchManager().queue_.addSearchAndHighlightTask( | 306 this.request.queue_.addSearchAndHighlightTask( |
Dan Beam
2017/03/14 00:44:27
should this be this.request_?
dpapad
2017/03/14 03:28:11
This is a protected member variable from the super
| |
308 new SearchAndHighlightTask(this.request, assert(renderedNode))); | 307 new SearchAndHighlightTask(this.request, assert(renderedNode))); |
309 resolve(); | 308 resolve(); |
310 }.bind(this)); | 309 }.bind(this)); |
311 }.bind(this)); | 310 }.bind(this)); |
312 }, | 311 }, |
313 }; | 312 }; |
314 | 313 |
315 /** | 314 /** |
316 * @constructor | 315 * @constructor |
317 * @extends {Task} | 316 * @extends {Task} |
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
365 setSectionsVisibility_: function(visible) { | 364 setSectionsVisibility_: function(visible) { |
366 var sections = this.node.querySelectorAll('settings-section'); | 365 var sections = this.node.querySelectorAll('settings-section'); |
367 | 366 |
368 for (var i = 0; i < sections.length; i++) | 367 for (var i = 0; i < sections.length; i++) |
369 sections[i].hiddenBySearch = !visible; | 368 sections[i].hiddenBySearch = !visible; |
370 }, | 369 }, |
371 }; | 370 }; |
372 | 371 |
373 /** | 372 /** |
374 * @constructor | 373 * @constructor |
374 * @param {!settings.SearchRequest} request | |
375 */ | 375 */ |
376 function TaskQueue() { | 376 function TaskQueue(request) { |
377 /** @private {!settings.SearchRequest} */ | |
378 this.request_ = request; | |
379 | |
377 /** | 380 /** |
378 * @private {{ | 381 * @private {{ |
379 * high: !Array<!Task>, | 382 * high: !Array<!Task>, |
380 * middle: !Array<!Task>, | 383 * middle: !Array<!Task>, |
381 * low: !Array<!Task> | 384 * low: !Array<!Task> |
382 * }} | 385 * }} |
383 */ | 386 */ |
384 this.queues_; | 387 this.queues_; |
385 this.reset(); | 388 this.reset(); |
386 | 389 |
(...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
445 var task = this.popNextTask_(); | 448 var task = this.popNextTask_(); |
446 if (!task) { | 449 if (!task) { |
447 this.running_ = false; | 450 this.running_ = false; |
448 if (this.onEmptyCallback_) | 451 if (this.onEmptyCallback_) |
449 this.onEmptyCallback_(); | 452 this.onEmptyCallback_(); |
450 return; | 453 return; |
451 } | 454 } |
452 | 455 |
453 this.running_ = true; | 456 this.running_ = true; |
454 window.requestIdleCallback(function() { | 457 window.requestIdleCallback(function() { |
455 function startNextTask() { | 458 if (!this.request_.canceled) { |
456 this.running_ = false; | 459 task.exec().then(function() { |
457 this.consumePending_(); | 460 this.running_ = false; |
461 this.consumePending_(); | |
462 }.bind(this)); | |
458 } | 463 } |
459 if (task.request.id == | 464 // Nothing to do otherwise. Since the request corresponding to this |
460 getSearchManager().activeRequest_.id) { | 465 // queue was canceled, the queue is disposed along with the request. |
461 task.exec().then(startNextTask.bind(this)); | |
462 } else { | |
463 // Dropping this task without ever executing it, since a new search | |
464 // has been issued since this task was queued. | |
465 startNextTask.call(this); | |
466 } | |
467 }.bind(this)); | 466 }.bind(this)); |
468 return; | 467 return; |
469 } | 468 } |
470 }, | 469 }, |
471 }; | 470 }; |
472 | 471 |
473 /** | 472 /** |
474 * @constructor | 473 * @constructor |
474 * | |
475 * @param {string} rawQuery | |
476 * @param {!HTMLElement} root | |
475 */ | 477 */ |
476 var SearchRequest = function(rawQuery) { | 478 var SearchRequest = function(rawQuery, root) { |
477 /** @type {number} */ | |
478 this.id = SearchRequest.nextId_++; | |
479 | |
480 /** @private {string} */ | 479 /** @private {string} */ |
481 this.rawQuery_ = rawQuery; | 480 this.rawQuery_ = rawQuery; |
482 | 481 |
482 /** @private {!HTMLElement} */ | |
483 this.root = root; | |
Dan Beam
2017/03/14 00:44:27
if this is private, root_
dpapad
2017/03/14 03:28:11
Done.
| |
484 | |
483 /** @type {?RegExp} */ | 485 /** @type {?RegExp} */ |
484 this.regExp = this.generateRegExp_(); | 486 this.regExp = this.generateRegExp_(); |
485 | 487 |
486 /** | 488 /** |
487 * Whether this request was fully processed. | 489 * Whether this request was canceled before completing. |
488 * @type {boolean} | 490 * @type {boolean} |
489 */ | 491 */ |
490 this.finished = false; | 492 this.canceled = false; |
491 | 493 |
492 /** @private {boolean} */ | 494 /** @private {boolean} */ |
493 this.foundMatches_ = false; | 495 this.foundMatches_ = false; |
494 | 496 |
495 /** @type {!PromiseResolver} */ | 497 /** @type {!PromiseResolver} */ |
496 this.resolver = new PromiseResolver(); | 498 this.resolver = new PromiseResolver(); |
499 | |
500 /** @private {!TaskQueue} */ | |
501 this.queue_ = new TaskQueue(this); | |
502 this.queue_.onEmpty(function() { | |
503 this.resolver.resolve(this); | |
504 }.bind(this)); | |
497 }; | 505 }; |
498 | 506 |
499 /** @private {number} */ | |
500 SearchRequest.nextId_ = 0; | |
501 | |
502 /** @private {!RegExp} */ | 507 /** @private {!RegExp} */ |
503 SearchRequest.SANITIZE_REGEX_ = /[-[\]{}()*+?.,\\^$|#\s]/g; | 508 SearchRequest.SANITIZE_REGEX_ = /[-[\]{}()*+?.,\\^$|#\s]/g; |
504 | 509 |
505 SearchRequest.prototype = { | 510 SearchRequest.prototype = { |
506 /** | 511 /** |
512 * Fires this search request. | |
513 */ | |
514 start: function() { | |
515 this.queue_.addTopLevelSearchTask( | |
516 new TopLevelSearchTask(this, this.root)); | |
517 }, | |
518 | |
519 /** | |
507 * @return {?RegExp} | 520 * @return {?RegExp} |
508 * @private | 521 * @private |
509 */ | 522 */ |
510 generateRegExp_: function() { | 523 generateRegExp_: function() { |
511 var regExp = null; | 524 var regExp = null; |
512 | 525 |
513 // Generate search text by escaping any characters that would be | 526 // Generate search text by escaping any characters that would be |
514 // problematic for regular expressions. | 527 // problematic for regular expressions. |
515 var searchText = this.rawQuery_.trim().replace( | 528 var searchText = this.rawQuery_.trim().replace( |
516 SearchRequest.SANITIZE_REGEX_, '\\$&'); | 529 SearchRequest.SANITIZE_REGEX_, '\\$&'); |
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
554 * searching finished. | 567 * searching finished. |
555 */ | 568 */ |
556 search: function(text, page) {} | 569 search: function(text, page) {} |
557 }; | 570 }; |
558 | 571 |
559 /** | 572 /** |
560 * @constructor | 573 * @constructor |
561 * @implements {SearchManager} | 574 * @implements {SearchManager} |
562 */ | 575 */ |
563 var SearchManagerImpl = function() { | 576 var SearchManagerImpl = function() { |
564 /** @private {?settings.SearchRequest} */ | 577 /** @private {!Set<!settings.SearchRequest>} */ |
565 this.activeRequest_ = null; | 578 this.activeRequests_ = new Set(); |
566 | 579 |
567 /** @private {!TaskQueue} */ | 580 /** @private {?string} */ |
568 this.queue_ = new TaskQueue(); | 581 this.lastSearchedText_ = null; |
569 this.queue_.onEmpty(function() { | |
570 this.activeRequest_.finished = true; | |
571 this.activeRequest_.resolver.resolve(this.activeRequest_); | |
572 this.activeRequest_ = null; | |
573 }.bind(this)); | |
574 }; | 582 }; |
575 cr.addSingletonGetter(SearchManagerImpl); | 583 cr.addSingletonGetter(SearchManagerImpl); |
576 | 584 |
577 SearchManagerImpl.prototype = { | 585 SearchManagerImpl.prototype = { |
578 /** @override */ | 586 /** @override */ |
579 search: function(text, page) { | 587 search: function(text, page) { |
580 // Creating a new request only if the |text| changed. | 588 // Cancel any pending requests if a request with different text is |
581 if (!this.activeRequest_ || !this.activeRequest_.isSame(text)) { | 589 // submitted. |
582 // Resolving previous search request without marking it as | 590 if (text != this.lastSearchedText_) { |
583 // 'finished', if any, and dropping all pending tasks. | 591 this.activeRequests_.forEach(function(request) { |
584 this.queue_.reset(); | 592 request.canceled = true; |
585 if (this.activeRequest_) | 593 request.resolver.resolve(request); |
586 this.activeRequest_.resolver.resolve(this.activeRequest_); | 594 }); |
587 | 595 this.activeRequests_.clear(); |
588 this.activeRequest_ = new SearchRequest(text); | |
589 } | 596 } |
590 | 597 |
591 this.queue_.addTopLevelSearchTask( | 598 this.lastSearchedText_ = text; |
592 new TopLevelSearchTask(this.activeRequest_, page)); | 599 var request = new SearchRequest(text, page); |
593 | 600 this.activeRequests_.add(request); |
594 return this.activeRequest_.resolver.promise; | 601 request.start(); |
602 return request.resolver.promise.then(function() { | |
603 // Stop tracking requests that finished. | |
604 this.activeRequests_.delete(request); | |
605 return request; | |
606 }.bind(this)); | |
595 }, | 607 }, |
596 }; | 608 }; |
597 | 609 |
598 /** @return {!SearchManager} */ | 610 /** @return {!SearchManager} */ |
599 function getSearchManager() { | 611 function getSearchManager() { |
600 return SearchManagerImpl.getInstance(); | 612 return SearchManagerImpl.getInstance(); |
601 } | 613 } |
602 | 614 |
603 /** | 615 /** |
604 * Sets the SearchManager singleton instance, useful for testing. | 616 * Sets the SearchManager singleton instance, useful for testing. |
605 * @param {!SearchManager} searchManager | 617 * @param {!SearchManager} searchManager |
606 */ | 618 */ |
607 function setSearchManagerForTesting(searchManager) { | 619 function setSearchManagerForTesting(searchManager) { |
608 SearchManagerImpl.instance_ = searchManager; | 620 SearchManagerImpl.instance_ = searchManager; |
609 } | 621 } |
610 | 622 |
611 return { | 623 return { |
612 getSearchManager: getSearchManager, | 624 getSearchManager: getSearchManager, |
613 setSearchManagerForTesting: setSearchManagerForTesting, | 625 setSearchManagerForTesting: setSearchManagerForTesting, |
614 SearchRequest: SearchRequest, | 626 SearchRequest: SearchRequest, |
615 }; | 627 }; |
616 }); | 628 }); |
OLD | NEW |