Chromium Code Reviews| 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 |