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

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

Issue 2082793003: MD Settings: First iteration of searching within settings. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@cr_search_migration0
Patch Set: Fix error when searching from About page. 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
OLDNEW
(Empty)
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
3 // found in the LICENSE file.
4
5 cr.define('settings', function() {
6 /** @const {string} */
7 var WRAPPER_CSS_CLASS = 'search-highlight-wrapper';
8
9 /** @const {string} */
10 var HIT_CSS_CLASS = 'search-highlight-hit';
11
12 /** @const {!RegExp} */
13 var SANITIZE_REGEX = /[-[\]{}()*+?.,\\^$|#\s]/g;
14
15 /**
16 * Finds all previous highlighted nodes under |node| (both within self and
17 * children's Shadow DOM) and removes the highlight.
18 * @param {!Node} node
19 * @private
20 */
21 function findAndRemoveHighligts_(node) {
22 removeHighlightUi_(node.shadowRoot);
23
24 // Finds all descendants of |node| that have their own Shadow DOM. It
25 // does NOT pierce through the Shadow DOM boundary (which is why
26 // |findAndRemoveHighligts_| has to be called recursively.
27 var children = [];
28 var walker = document.createTreeWalker(
29 node.shadowRoot, NodeFilter.SHOW_ELEMENT, null, false);
30 var currentNode = null;
31 while ((currentNode = walker.nextNode()) !== null) {
32 if (!!currentNode.shadowRoot)
33 children.push(currentNode);
34 }
35
36 children.forEach(findAndRemoveHighligts_);
37 }
38
39 /**
40 * Applies the highlight UI (yellow rectangle) around all matches in |node|.
41 * param {!Node} node The text node to be highlighted. |node| ends up being
42 * removed from the DOM tree.
43 * @param {!Array<string>} tokens The string tokens that did not match.
44 * @private
45 */
46 function applyHighlightUi_(node, tokens) {
47 var wrapper = document.createElement('span');
48 wrapper.classList.add(WRAPPER_CSS_CLASS);
49 // Use existing node as placeholder to determine where to insert the
50 // replacement content.
51 node.parentNode.insertBefore(wrapper, node);
52
53 for (var i = 0; i < tokens.length; ++i) {
54 if (i % 2 == 0) {
55 wrapper.appendChild(document.createTextNode(tokens[i]));
56 } else {
57 var span = document.createElement('span');
58 span.classList.add(HIT_CSS_CLASS);
59 span.style['background-color'] = 'yellow';
60 span.textContent = tokens[i];
61 wrapper.appendChild(span);
62 }
63 }
64
65 node.remove();
66 }
67
68 /**
69 * Removes highlight UI (yellow rectangle) from previous matches in the given
70 * element and descendants. It does NOT pierce through Shadow DOM.
71 * @param {!Element} element
72 * @private
73 */
74 function removeHighlightUi_(element) {
75 var wrappers = element.querySelectorAll('.' + WRAPPER_CSS_CLASS);
76 for (var i = 0; i < wrappers.length; i++) {
77 var wrapper = wrappers[i];
78 var hitElements = wrapper.querySelectorAll('.' + HIT_CSS_CLASS);
79 // For each hit element, remove the highlighting.
80 for (var j = 0; j < hitElements.length; j++) {
81 var hitElement = hitElements[j];
82 wrapper.replaceChild(hitElement.firstChild, hitElement);
83 }
84
85 // Normalize so that adjacent text nodes will be combined.
86 wrapper.normalize();
87 // Restore the DOM structure as it was before the search occurred.
88 if (wrapper.previousSibling)
89 wrapper.textContent = ' ' + wrapper.textContent;
90 if (wrapper.nextSibling)
91 wrapper.textContent = wrapper.textContent + ' ';
92
93 wrapper.parentElement.insertBefore(
94 wrapper.firstChild, wrapper.nextSibling);
95
96 wrapper.remove();
97 }
98 }
99
100 /**
101 * Traverses the entire DOM (including Shadow DOM), finds text nodes that
102 * match the giver regular expression and applies the highlight UI. It also
103 * ensures that <settings-section> instances become visible if any matches
104 * occurred under their subtree.
105 *
106 * @param {!Element} page The page to be searched, should be either
107 * <settings-basic-page> or <settings-advanced-page>.
108 * @param {!RegExp} regExp The regular expression to detect matches.
109 * @private
110 */
111 function findAndHighlightMatches_(page, regExp) {
112 var walker = new ShadowDomTreeWalker(page);
113 var node = walker.nextNode();
114 while (node !== null) {
115 var textContent = node.nodeValue.trim();
116 if (!regExp.test(textContent)) {
117 node = walker.nextNode();
118 continue;
119 }
120
121 // Find corresponding SETTINGS-SECTION parent and make it visible.
122 var parent = node;
123 while (!!parent && parent.tagName !== 'SETTINGS-SECTION') {
124 parent = parent.nodeType == Node.DOCUMENT_FRAGMENT_NODE ?
125 parent.host : parent.parentNode;
126 }
127 if (!!parent)
128 parent.style.display = '';
129
130 var nextNode = walker.nextNode();
131 applyHighlightUi_(node, textContent.split(regExp));
132 node = nextNode;
133 }
134 }
135
136 /**
137 * @param {!Element} page
138 * @param {boolean} visible
139 * @private
140 */
141 function setSectionsVisibility_(page, visible) {
142 var sections = Polymer.dom(page.root).querySelectorAll('settings-section');
143 for (var i = 0; i < sections.length; i++)
144 sections[i].style.display = visible ? '' : 'none';
145 }
146
147 /**
148 * Performs hierarchical search, starting at the given page element.
149 * @param {string} text
150 * @param {!Element} page Must be either <settings-basic-page> or
151 * <settings-advanced-page>.
152 */
153 function search(text, page) {
154 findAndRemoveHighligts_(page);
155
156 // Generate search text by applying lowercase and escaping any characters
157 // that would be problematic for regular expressions.
158 var searchText = text.trim().toLowerCase().replace(SANITIZE_REGEX, '\\$&');
159 if (searchText.length == 0) {
160 setSectionsVisibility_(page, true);
161 return;
162 }
163
164 setSectionsVisibility_(page, false);
165 findAndHighlightMatches_(page, new RegExp('(' + searchText + ')', 'i'));
166 }
167
168 /**
169 * A utility class for navigating all text nodes in a DOM tree taking into
170 * account Shadow DOM nodes. The native TreeWalker does not traverse Shadow
171 * DOM.
172 * @constructor
173 *
174 * Example usage:
175 *
176 * var walker = new ShadowDomTreeWalker(myElememt);
177 * var node = null;
178 * while ((node = walker.nextNode()) !== null) {
179 * // |node| is of type Node.TEXT_NODE.
180 * // Do something with |node| here.
181 * }
182 *
183 * @param {!Node} node The root of the DOM tree to be traversed.
184 */
185 function ShadowDomTreeWalker(node) {
186 /** @private {!Array<!TreeWalker>} */
187 this.walkers_ = [];
188
189 /** @private {!NodeFilter} */
190 this.nodeFilter_ = /** @type {!NodeFilter} */ (
191 {acceptNode: this.filterFn_.bind(this)});
192
193 if (!!node.shadowRoot)
194 this.addWalker_(node.shadowRoot);
195 this.addWalker_(node);
196 }
197
198 /**
199 * List of elements types that should not be searched at all.
200 * @const {!Set<string>}
201 * @private
202 */
203 ShadowDomTreeWalker.IGNORED_ELEMENTS_ = new Set([
204 'CONTENT',
205 'CR-EVENTS',
206 'IMG',
207 'IRON-ICON',
208 'PAPER-ICON-BUTTON',
209 /* TODO(dpapad): paper-item is used for dynamically populated dropdown
210 * menus. Perhaps a better approach is to mark the entire dropdown menu such
211 * that search algorithm can skip it as a whole instead.
212 */
213 'PAPER-ITEM',
214 'PAPER-RIPPLE',
215 'STYLE',
216 'TEMPLATE',
217 ]);
218
219 ShadowDomTreeWalker.prototype = {
220 /**
221 * Adds a TreeWalker instance to the queue for the given |node|.
222 * @param {!Node} node
223 * @private
224 */
225 addWalker_: function(node) {
226 this.walkers_.push(
227 document.createTreeWalker(
228 node, NodeFilter.SHOW_ALL, this.nodeFilter_, false));
Dan Beam 2016/06/30 16:33:17 according to esprehn@, if we're doing SHOW_ALL or
dpapad 2016/06/30 17:32:57 Whether it simplifies things, is questionable, unt
dpapad 2016/06/30 21:27:44 I've updated this CL to traverse the DOM in a pure
229 },
230
231 /**
232 * @return {?Node} The next text node in the DOM tree, or null if the end of
233 * the traversal is reached.
234 */
235 nextNode: function() {
236 if (this.walkers_.length == 0)
237 return null;
238
239 var node = null;
240 do {
241 // Processing walkers in a FIFO order because it simplifies the code.
242 // The order of traversal is not important (unlike native TreeWalker),
243 // the only important invariant is to ensure that all nodes in the DOM
244 // tree are traversed.
245 var activeWalker = this.walkers_[0];
246 node = activeWalker.nextNode();
247 if (node === null) {
248 this.walkers_.splice(0, 1);
249 } else if (!!node.shadowRoot) {
250 // Adding TreeWalker for shadow root, if it exists, because shadowRoot
251 // is invisible from the normal root TreeWalker.
252 this.addWalker_(node.shadowRoot);
253 }
254 } while (node === null && this.walkers_.length > 0);
255
256 if (node !== null && node.nodeType != Node.TEXT_NODE) {
257 return this.nextNode();
258 }
259
260 return node;
261 },
262
263 /**
264 * @param {!Node} node
265 * @return {number}
266 */
267 filterFn_: function(node) {
268 if (ShadowDomTreeWalker.IGNORED_ELEMENTS_.has(node.tagName))
269 return NodeFilter.FILTER_REJECT;
270
271 if (node.nodeType == Node.TEXT_NODE && node.nodeValue.trim() == 0)
272 return NodeFilter.FILTER_REJECT;
273
274 return NodeFilter.FILTER_ACCEPT;
275 },
276 };
277
278 return {
279 search: search,
280 };
281 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698