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

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: Nits. 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
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.
dpapad 2016/08/04 22:39:49 Tried to address this TODO by casting to SettingsS
Dan Beam 2016/08/05 00:07:59 don't really understand why this would happen, but
dpapad 2016/08/05 01:33:35 ## ^
202 if (!parent.noAssociatedControl) {
203 assert(
204 parent.associatedControl,
205 'An associated control was expected for SETTINGS-SUBPAGE ' +
206 parent.pageTitle + ', but was not found.');
207 associatedControl = parent.associatedControl;
Dan Beam 2016/08/05 00:07:59 nit: associatedControl = assert(parent.associat
dpapad 2016/08/05 01:33:35 Done.
208 }
209 }
162 } 210 }
163 if (parent) 211 if (parent)
164 parent.hidden = false; 212 parent.hidden = false;
213
214 // Need to add the search bubble after the parent SETTINGS-SECTION has
215 // become visible, otherwise |offsetWidth| returns zero.
216 if (associatedControl)
217 highlightAssociatedControl_(associatedControl, rawQuery);
165 } 218 }
166 219
167 /** 220 /**
168 * @constructor 221 * @constructor
169 * 222 *
170 * @param {!SearchRequest} request 223 * @param {!SearchRequest} request
171 * @param {!Node} node 224 * @param {!Node} node
172 */ 225 */
173 function Task(request, node) { 226 function Task(request, node) {
174 /** @protected {!SearchRequest} */ 227 /** @protected {!SearchRequest} */
(...skipping 324 matching lines...) Expand 10 before | Expand all | Expand 10 after
499 552
500 /** @return {!SearchManager} */ 553 /** @return {!SearchManager} */
501 function getSearchManager() { 554 function getSearchManager() {
502 return SearchManager.getInstance(); 555 return SearchManager.getInstance();
503 } 556 }
504 557
505 return { 558 return {
506 getSearchManager: getSearchManager, 559 getSearchManager: getSearchManager,
507 }; 560 };
508 }); 561 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698