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

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

Issue 2202723004: MD Settings: Highlight sub-page search hits. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@search_no_results_attempt_test
Patch Set: Nit. Created 4 years, 4 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 | chrome/browser/resources/settings/settings_page/settings_animated_pages.html » ('j') | 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 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 HIT_CSS_CLASS = 'search-highlight-hit'; 10 var HIT_CSS_CLASS = 'search-highlight-hit';
11 11
12 /** @const {string} */
13 var SEARCH_BUBBLE_CSS_CLASS = 'search-bubble';
14
12 /** 15 /**
13 * List of elements types that should not be searched at all. 16 * List of elements types that should not be searched at all.
14 * The only DOM-MODULE node is in <body> which is not searched, therefore 17 * The only DOM-MODULE node is in <body> which is not searched, therefore
15 * DOM-MODULE is not needed in this set. 18 * DOM-MODULE is not needed in this set.
16 * @const {!Set<string>} 19 * @const {!Set<string>}
17 */ 20 */
18 var IGNORED_ELEMENTS = new Set([ 21 var IGNORED_ELEMENTS = new Set([
19 'CONTENT', 22 'CONTENT',
20 'CR-EVENTS', 23 'CR-EVENTS',
21 'IMG', 24 'IMG',
22 'IRON-ICON', 25 'IRON-ICON',
23 'IRON-LIST', 26 'IRON-LIST',
24 'PAPER-ICON-BUTTON', 27 'PAPER-ICON-BUTTON',
25 /* TODO(dpapad): paper-item is used for dynamically populated dropdown 28 /* TODO(dpapad): paper-item is used for dynamically populated dropdown
26 * menus. Perhaps a better approach is to mark the entire dropdown menu such 29 * menus. Perhaps a better approach is to mark the entire dropdown menu such
27 * that search algorithm can skip it as a whole instead. 30 * that search algorithm can skip it as a whole instead.
28 */ 31 */
29 'PAPER-ITEM', 32 'PAPER-ITEM',
30 'PAPER-RIPPLE', 33 'PAPER-RIPPLE',
31 'PAPER-SLIDER', 34 'PAPER-SLIDER',
32 'PAPER-SPINNER', 35 'PAPER-SPINNER',
33 'STYLE', 36 'STYLE',
34 'TEMPLATE', 37 'TEMPLATE',
35 ]); 38 ]);
36 39
37 /** 40 /**
38 * Finds all previous highlighted nodes under |node| (both within self and 41 * Finds all previous highlighted nodes under |node| (both within self and
39 * children's Shadow DOM) and removes the highlight (yellow rectangle). 42 * children's Shadow DOM) and removes the highlights (yellow rectangle and
43 * search bubbles).
40 * TODO(dpapad): Consider making this a private method of TopLevelSearchTask. 44 * TODO(dpapad): Consider making this a private method of TopLevelSearchTask.
41 * @param {!Node} node 45 * @param {!Node} node
42 * @private 46 * @private
43 */ 47 */
44 function findAndRemoveHighlights_(node) { 48 function findAndRemoveHighlights_(node) {
45 var wrappers = node.querySelectorAll('* /deep/ .' + WRAPPER_CSS_CLASS); 49 var wrappers = node.querySelectorAll('* /deep/ .' + WRAPPER_CSS_CLASS);
46 50
47 for (var wrapper of wrappers) { 51 for (var wrapper of wrappers) {
48 var hitElements = wrapper.querySelectorAll('.' + HIT_CSS_CLASS); 52 var hitElements = wrapper.querySelectorAll('.' + HIT_CSS_CLASS);
49 // For each hit element, remove the highlighting. 53 // For each hit element, remove the highlighting.
50 for (var hitElement of hitElements) { 54 for (var hitElement of hitElements) {
51 wrapper.replaceChild(hitElement.firstChild, hitElement); 55 wrapper.replaceChild(hitElement.firstChild, hitElement);
52 } 56 }
53 57
54 // Normalize so that adjacent text nodes will be combined. 58 // Normalize so that adjacent text nodes will be combined.
55 wrapper.normalize(); 59 wrapper.normalize();
56 // Restore the DOM structure as it was before the search occurred. 60 // Restore the DOM structure as it was before the search occurred.
57 if (wrapper.previousSibling) 61 if (wrapper.previousSibling)
58 wrapper.textContent = ' ' + wrapper.textContent; 62 wrapper.textContent = ' ' + wrapper.textContent;
59 if (wrapper.nextSibling) 63 if (wrapper.nextSibling)
60 wrapper.textContent = wrapper.textContent + ' '; 64 wrapper.textContent = wrapper.textContent + ' ';
61 65
62 wrapper.parentElement.replaceChild(wrapper.firstChild, wrapper); 66 wrapper.parentElement.replaceChild(wrapper.firstChild, wrapper);
63 } 67 }
68
69 var searchBubbles = node.querySelectorAll(
70 '* /deep/ .' + SEARCH_BUBBLE_CSS_CLASS);
71 for (var searchBubble of searchBubbles)
72 searchBubble.remove();
64 } 73 }
65 74
66 /** 75 /**
67 * Applies the highlight UI (yellow rectangle) around all matches in |node|. 76 * Applies the highlight UI (yellow rectangle) around all matches in |node|.
68 * @param {!Node} node The text node to be highlighted. |node| ends up 77 * @param {!Node} node The text node to be highlighted. |node| ends up
69 * being removed from the DOM tree. 78 * being removed from the DOM tree.
70 * @param {!Array<string>} tokens The string tokens after splitting on the 79 * @param {!Array<string>} tokens The string tokens after splitting on the
71 * relevant regExp. Even indices hold text that doesn't need highlighting, 80 * relevant regExp. Even indices hold text that doesn't need highlighting,
72 * odd indices hold the text to be highlighted. For example: 81 * odd indices hold the text to be highlighted. For example:
73 * var r = new RegExp('(foo)', 'i'); 82 * var r = new RegExp('(foo)', 'i');
74 * 'barfoobar foo bar'.split(r) => ['bar', 'foo', 'bar ', 'foo', ' bar'] 83 * 'barfoobar foo bar'.split(r) => ['bar', 'foo', 'bar ', 'foo', ' bar']
75 * @private 84 * @private
76 */ 85 */
77 function highlight_(node, tokens) { 86 function highlight_(node, tokens) {
78 var wrapper = document.createElement('span'); 87 var wrapper = document.createElement('span');
79 wrapper.classList.add(WRAPPER_CSS_CLASS); 88 wrapper.classList.add(WRAPPER_CSS_CLASS);
80 // Use existing node as placeholder to determine where to insert the 89 // Use existing node as placeholder to determine where to insert the
81 // replacement content. 90 // replacement content.
82 node.parentNode.replaceChild(wrapper, node); 91 node.parentNode.replaceChild(wrapper, node);
83 92
84 for (var i = 0; i < tokens.length; ++i) { 93 for (var i = 0; i < tokens.length; ++i) {
85 if (i % 2 == 0) { 94 if (i % 2 == 0) {
86 wrapper.appendChild(document.createTextNode(tokens[i])); 95 wrapper.appendChild(document.createTextNode(tokens[i]));
87 } else { 96 } else {
88 var span = document.createElement('span'); 97 var span = document.createElement('span');
89 span.classList.add(HIT_CSS_CLASS); 98 span.classList.add(HIT_CSS_CLASS);
90 span.style.backgroundColor = 'yellow';
91 span.textContent = tokens[i]; 99 span.textContent = tokens[i];
92 wrapper.appendChild(span); 100 wrapper.appendChild(span);
93 } 101 }
94 } 102 }
95 } 103 }
96 104
97 /** 105 /**
98 * Traverses the entire DOM (including Shadow DOM), finds text nodes that 106 * Traverses the entire DOM (including Shadow DOM), finds text nodes that
99 * match the given regular expression and applies the highlight UI. It also 107 * match the given regular expression and applies the highlight UI. It also
100 * ensures that <settings-section> instances become visible if any matches 108 * ensures that <settings-section> instances become visible if any matches
(...skipping 16 matching lines...) Expand all
117 if (IGNORED_ELEMENTS.has(node.nodeName)) 125 if (IGNORED_ELEMENTS.has(node.nodeName))
118 return; 126 return;
119 127
120 if (node.nodeType == Node.TEXT_NODE) { 128 if (node.nodeType == Node.TEXT_NODE) {
121 var textContent = node.nodeValue.trim(); 129 var textContent = node.nodeValue.trim();
122 if (textContent.length == 0) 130 if (textContent.length == 0)
123 return; 131 return;
124 132
125 if (request.regExp.test(textContent)) { 133 if (request.regExp.test(textContent)) {
126 foundMatches = true; 134 foundMatches = true;
127 revealParentSection_(node); 135 revealParentSection_(node, request.rawQuery_);
128 highlight_(node, textContent.split(request.regExp)); 136 highlight_(node, textContent.split(request.regExp));
129 } 137 }
130 // Returning early since TEXT_NODE nodes never have children. 138 // Returning early since TEXT_NODE nodes never have children.
131 return; 139 return;
132 } 140 }
133 141
134 var child = node.firstChild; 142 var child = node.firstChild;
135 while (child !== null) { 143 while (child !== null) {
136 // Getting a reference to the |nextSibling| before calling doSearch() 144 // Getting a reference to the |nextSibling| before calling doSearch()
137 // because |child| could be removed from the DOM within doSearch(). 145 // because |child| could be removed from the DOM within doSearch().
138 var nextSibling = child.nextSibling; 146 var nextSibling = child.nextSibling;
139 doSearch(child); 147 doSearch(child);
140 child = nextSibling; 148 child = nextSibling;
141 } 149 }
142 150
143 var shadowRoot = node.shadowRoot; 151 var shadowRoot = node.shadowRoot;
144 if (shadowRoot) 152 if (shadowRoot)
145 doSearch(shadowRoot); 153 doSearch(shadowRoot);
146 } 154 }
147 155
148 doSearch(root); 156 doSearch(root);
149 return foundMatches; 157 return foundMatches;
150 } 158 }
151 159
152 /** 160 /**
161 * Highlights the HTML control that triggers a subpage, by displaying a search
162 * bubble.
163 * @param {!HTMLElement} element The element to be highlighted.
164 * @param {string} rawQuery The search query.
165 * @private
166 */
167 function highlightAssociatedControl_(element, rawQuery) {
168 var searchBubble = element.querySelector('.' + SEARCH_BUBBLE_CSS_CLASS);
169 // If the associated control has already been highlighted due to another
170 // match on the same subpage, there is no need to do anything.
171 if (searchBubble)
172 return;
173
174 searchBubble = document.createElement('div');
175 searchBubble.classList.add(SEARCH_BUBBLE_CSS_CLASS);
176 var innards = document.createElement('div');
177 innards.classList.add('search-bubble-innards');
178 innards.textContent = rawQuery;
179 searchBubble.appendChild(innards);
180 element.appendChild(searchBubble);
181
182 // Dynamically position the bubble at the edge the associated controle
183 // elemnt.
184 searchBubble.style.left = '-' + searchBubble.offsetWidth + 'px';
185 }
186
187 /**
153 * Finds and makes visible the <settings-section> parent of |node|. 188 * Finds and makes visible the <settings-section> parent of |node|.
154 * @param {!Node} node 189 * @param {!Node} node
190 * @param {string} rawQuery
191 * @private
155 */ 192 */
156 function revealParentSection_(node) { 193 function revealParentSection_(node, rawQuery) {
194 var associatedControl = null;
157 // Find corresponding SETTINGS-SECTION parent and make it visible. 195 // Find corresponding SETTINGS-SECTION parent and make it visible.
158 var parent = node; 196 var parent = node;
159 while (parent && parent.nodeName !== 'SETTINGS-SECTION') { 197 while (parent && parent.nodeName !== 'SETTINGS-SECTION') {
160 parent = parent.nodeType == Node.DOCUMENT_FRAGMENT_NODE ? 198 parent = parent.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
161 parent.host : parent.parentNode; 199 parent.host : parent.parentNode;
200 if (parent.nodeName == 'SETTINGS-SUBPAGE') {
201 // TODO(dpapad): Cast to SettingsSubpageElement here.
202 if (!parent.noAssociatedControl) {
203 associatedControl = assert(
204 parent.associatedControl,
205 'An associated control was expected for SETTINGS-SUBPAGE ' +
206 parent.pageTitle + ', but was not found.');
207 }
208 }
162 } 209 }
163 if (parent) 210 if (parent)
164 parent.hidden = false; 211 parent.hidden = false;
212
213 // Need to add the search bubble after the parent SETTINGS-SECTION has
214 // become visible, otherwise |offsetWidth| returns zero.
215 if (associatedControl)
216 highlightAssociatedControl_(associatedControl, rawQuery);
165 } 217 }
166 218
167 /** 219 /**
168 * @constructor 220 * @constructor
169 * 221 *
170 * @param {!settings.SearchRequest} request 222 * @param {!settings.SearchRequest} request
171 * @param {!Node} node 223 * @param {!Node} node
172 */ 224 */
173 function Task(request, node) { 225 function Task(request, node) {
174 /** @protected {!settings.SearchRequest} */ 226 /** @protected {!settings.SearchRequest} */
(...skipping 343 matching lines...) Expand 10 before | Expand all | Expand 10 after
518 function setSearchManagerForTesting(searchManager) { 570 function setSearchManagerForTesting(searchManager) {
519 SearchManagerImpl.instance_ = searchManager; 571 SearchManagerImpl.instance_ = searchManager;
520 } 572 }
521 573
522 return { 574 return {
523 getSearchManager: getSearchManager, 575 getSearchManager: getSearchManager,
524 setSearchManagerForTesting: setSearchManagerForTesting, 576 setSearchManagerForTesting: setSearchManagerForTesting,
525 SearchRequest: SearchRequest, 577 SearchRequest: SearchRequest,
526 }; 578 };
527 }); 579 });
OLDNEW
« no previous file with comments | « no previous file | chrome/browser/resources/settings/settings_page/settings_animated_pages.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698