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

Side by Side Diff: third_party/WebKit/Source/devtools/front_end/sources/SourcesSearchScope.js

Issue 2466123002: DevTools: reformat front-end code to match chromium style. (Closed)
Patch Set: all done Created 4 years, 1 month 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 /* 1 /*
2 * Copyright (C) 2011 Google Inc. All rights reserved. 2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 * 3 *
4 * Redistribution and use in source and binary forms, with or without 4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are 5 * modification, are permitted provided that the following conditions are
6 * met: 6 * met:
7 * 7 *
8 * 1. Redistributions of source code must retain the above copyright 8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer. 9 * notice, this list of conditions and the following disclaimer.
10 * 10 *
11 * 2. Redistributions in binary form must reproduce the above 11 * 2. Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer 12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the 13 * in the documentation and/or other materials provided with the
14 * distribution. 14 * distribution.
15 * 15 *
16 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS 16 * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 17 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 18 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC. 19 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
20 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 20 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */ 27 */
28
29 /** 28 /**
30 * @constructor
31 * @implements {WebInspector.SearchScope} 29 * @implements {WebInspector.SearchScope}
30 * @unrestricted
32 */ 31 */
33 WebInspector.SourcesSearchScope = function() 32 WebInspector.SourcesSearchScope = class {
34 { 33 constructor() {
35 // FIXME: Add title once it is used by search controller. 34 // FIXME: Add title once it is used by search controller.
36 this._searchId = 0; 35 this._searchId = 0;
37 }; 36 }
38 37
39 /** 38 /**
40 * @param {!WebInspector.UISourceCode} uiSourceCode1 39 * @param {!WebInspector.UISourceCode} uiSourceCode1
41 * @param {!WebInspector.UISourceCode} uiSourceCode2 40 * @param {!WebInspector.UISourceCode} uiSourceCode2
42 * @return {number} 41 * @return {number}
43 */ 42 */
44 WebInspector.SourcesSearchScope._filesComparator = function(uiSourceCode1, uiSou rceCode2) 43 static _filesComparator(uiSourceCode1, uiSourceCode2) {
45 {
46 if (uiSourceCode1.isDirty() && !uiSourceCode2.isDirty()) 44 if (uiSourceCode1.isDirty() && !uiSourceCode2.isDirty())
47 return -1; 45 return -1;
48 if (!uiSourceCode1.isDirty() && uiSourceCode2.isDirty()) 46 if (!uiSourceCode1.isDirty() && uiSourceCode2.isDirty())
49 return 1; 47 return 1;
50 var url1 = uiSourceCode1.url(); 48 var url1 = uiSourceCode1.url();
51 var url2 = uiSourceCode2.url(); 49 var url2 = uiSourceCode2.url();
52 if (url1 && !url2) 50 if (url1 && !url2)
53 return -1; 51 return -1;
54 if (!url1 && url2) 52 if (!url1 && url2)
55 return 1; 53 return 1;
56 return String.naturalOrderComparator(uiSourceCode1.fullDisplayName(), uiSour ceCode2.fullDisplayName()); 54 return String.naturalOrderComparator(uiSourceCode1.fullDisplayName(), uiSour ceCode2.fullDisplayName());
55 }
56
57 /**
58 * @override
59 * @param {!WebInspector.Progress} progress
60 */
61 performIndexing(progress) {
62 this.stopSearch();
63
64 var projects = this._projects();
65 var compositeProgress = new WebInspector.CompositeProgress(progress);
66 for (var i = 0; i < projects.length; ++i) {
67 var project = projects[i];
68 var projectProgress = compositeProgress.createSubProgress(project.uiSource Codes().length);
69 project.indexContent(projectProgress);
70 }
71 }
72
73 /**
74 * @return {!Array.<!WebInspector.Project>}
75 */
76 _projects() {
77 /**
78 * @param {!WebInspector.Project} project
79 * @return {boolean}
80 */
81 function filterOutServiceProjects(project) {
82 return project.type() !== WebInspector.projectTypes.Service;
83 }
84
85 /**
86 * @param {!WebInspector.Project} project
87 * @return {boolean}
88 */
89 function filterOutContentScriptsIfNeeded(project) {
90 return WebInspector.moduleSetting('searchInContentScripts').get() ||
91 project.type() !== WebInspector.projectTypes.ContentScripts;
92 }
93
94 return WebInspector.workspace.projects().filter(filterOutServiceProjects).fi lter(filterOutContentScriptsIfNeeded);
95 }
96
97 /**
98 * @override
99 * @param {!WebInspector.ProjectSearchConfig} searchConfig
100 * @param {!WebInspector.Progress} progress
101 * @param {function(!WebInspector.FileBasedSearchResult)} searchResultCallback
102 * @param {function(boolean)} searchFinishedCallback
103 */
104 performSearch(searchConfig, progress, searchResultCallback, searchFinishedCall back) {
105 this.stopSearch();
106 this._searchResultCandidates = [];
107 this._searchResultCallback = searchResultCallback;
108 this._searchFinishedCallback = searchFinishedCallback;
109 this._searchConfig = searchConfig;
110
111 var projects = this._projects();
112 var barrier = new CallbackBarrier();
113 var compositeProgress = new WebInspector.CompositeProgress(progress);
114 var searchContentProgress = compositeProgress.createSubProgress();
115 var findMatchingFilesProgress = new WebInspector.CompositeProgress(composite Progress.createSubProgress());
116 for (var i = 0; i < projects.length; ++i) {
117 var project = projects[i];
118 var weight = project.uiSourceCodes().length;
119 var findMatchingFilesInProjectProgress = findMatchingFilesProgress.createS ubProgress(weight);
120 var barrierCallback = barrier.createCallback();
121 var filesMathingFileQuery = this._projectFilesMatchingFileQuery(project, s earchConfig);
122 var callback = this._processMatchingFilesForProject.bind(
123 this, this._searchId, project, filesMathingFileQuery, barrierCallback) ;
124 project.findFilesMatchingSearchRequest(
125 searchConfig, filesMathingFileQuery, findMatchingFilesInProjectProgres s, callback);
126 }
127 barrier.callWhenDone(this._processMatchingFiles.bind(
128 this, this._searchId, searchContentProgress, this._searchFinishedCallbac k.bind(this, true)));
129 }
130
131 /**
132 * @param {!WebInspector.Project} project
133 * @param {!WebInspector.ProjectSearchConfig} searchConfig
134 * @param {boolean=} dirtyOnly
135 * @return {!Array.<string>}
136 */
137 _projectFilesMatchingFileQuery(project, searchConfig, dirtyOnly) {
138 var result = [];
139 var uiSourceCodes = project.uiSourceCodes();
140 for (var i = 0; i < uiSourceCodes.length; ++i) {
141 var uiSourceCode = uiSourceCodes[i];
142 var binding = WebInspector.persistence.binding(uiSourceCode);
143 if (binding && binding.fileSystem === uiSourceCode)
144 continue;
145 if (dirtyOnly && !uiSourceCode.isDirty())
146 continue;
147 if (this._searchConfig.filePathMatchesFileQuery(uiSourceCode.fullDisplayNa me()))
148 result.push(uiSourceCode.url());
149 }
150 result.sort(String.naturalOrderComparator);
151 return result;
152 }
153
154 /**
155 * @param {number} searchId
156 * @param {!WebInspector.Project} project
157 * @param {!Array.<string>} filesMathingFileQuery
158 * @param {function()} callback
159 * @param {!Array.<string>} files
160 */
161 _processMatchingFilesForProject(searchId, project, filesMathingFileQuery, call back, files) {
162 if (searchId !== this._searchId) {
163 this._searchFinishedCallback(false);
164 return;
165 }
166
167 files.sort(String.naturalOrderComparator);
168 files = files.intersectOrdered(filesMathingFileQuery, String.naturalOrderCom parator);
169 var dirtyFiles = this._projectFilesMatchingFileQuery(project, this._searchCo nfig, true);
170 files = files.mergeOrdered(dirtyFiles, String.naturalOrderComparator);
171
172 var uiSourceCodes = [];
173 for (var i = 0; i < files.length; ++i) {
174 var uiSourceCode = project.uiSourceCodeForURL(files[i]);
175 if (uiSourceCode) {
176 var script = WebInspector.DefaultScriptMapping.scriptForUISourceCode(uiS ourceCode);
177 if (script && !script.isAnonymousScript())
178 continue;
179 uiSourceCodes.push(uiSourceCode);
180 }
181 }
182 uiSourceCodes.sort(WebInspector.SourcesSearchScope._filesComparator);
183 this._searchResultCandidates =
184 this._searchResultCandidates.mergeOrdered(uiSourceCodes, WebInspector.So urcesSearchScope._filesComparator);
185 callback();
186 }
187
188 /**
189 * @param {number} searchId
190 * @param {!WebInspector.Progress} progress
191 * @param {function()} callback
192 */
193 _processMatchingFiles(searchId, progress, callback) {
194 if (searchId !== this._searchId) {
195 this._searchFinishedCallback(false);
196 return;
197 }
198
199 var files = this._searchResultCandidates;
200 if (!files.length) {
201 progress.done();
202 callback();
203 return;
204 }
205
206 progress.setTotalWork(files.length);
207
208 var fileIndex = 0;
209 var maxFileContentRequests = 20;
210 var callbacksLeft = 0;
211
212 for (var i = 0; i < maxFileContentRequests && i < files.length; ++i)
213 scheduleSearchInNextFileOrFinish.call(this);
214
215 /**
216 * @param {!WebInspector.UISourceCode} uiSourceCode
217 * @this {WebInspector.SourcesSearchScope}
218 */
219 function searchInNextFile(uiSourceCode) {
220 if (uiSourceCode.isDirty())
221 contentLoaded.call(this, uiSourceCode, uiSourceCode.workingCopy());
222 else
223 uiSourceCode.checkContentUpdated(true, contentUpdated.bind(this, uiSourc eCode));
224 }
225
226 /**
227 * @param {!WebInspector.UISourceCode} uiSourceCode
228 * @this {WebInspector.SourcesSearchScope}
229 */
230 function contentUpdated(uiSourceCode) {
231 uiSourceCode.requestContent().then(contentLoaded.bind(this, uiSourceCode)) ;
232 }
233
234 /**
235 * @this {WebInspector.SourcesSearchScope}
236 */
237 function scheduleSearchInNextFileOrFinish() {
238 if (fileIndex >= files.length) {
239 if (!callbacksLeft) {
240 progress.done();
241 callback();
242 return;
243 }
244 return;
245 }
246
247 ++callbacksLeft;
248 var uiSourceCode = files[fileIndex++];
249 setTimeout(searchInNextFile.bind(this, uiSourceCode), 0);
250 }
251
252 /**
253 * @param {!WebInspector.UISourceCode} uiSourceCode
254 * @param {?string} content
255 * @this {WebInspector.SourcesSearchScope}
256 */
257 function contentLoaded(uiSourceCode, content) {
258 /**
259 * @param {!WebInspector.ContentProvider.SearchMatch} a
260 * @param {!WebInspector.ContentProvider.SearchMatch} b
261 */
262 function matchesComparator(a, b) {
263 return a.lineNumber - b.lineNumber;
264 }
265
266 progress.worked(1);
267 var matches = [];
268 var queries = this._searchConfig.queries();
269 if (content !== null) {
270 for (var i = 0; i < queries.length; ++i) {
271 var nextMatches = WebInspector.ContentProvider.performSearchInContent(
272 content, queries[i], !this._searchConfig.ignoreCase(), this._searc hConfig.isRegex());
273 matches = matches.mergeOrdered(nextMatches, matchesComparator);
274 }
275 }
276 if (matches) {
277 var searchResult = new WebInspector.FileBasedSearchResult(uiSourceCode, matches);
278 this._searchResultCallback(searchResult);
279 }
280
281 --callbacksLeft;
282 scheduleSearchInNextFileOrFinish.call(this);
283 }
284 }
285
286 /**
287 * @override
288 */
289 stopSearch() {
290 ++this._searchId;
291 }
292
293 /**
294 * @override
295 * @param {!WebInspector.ProjectSearchConfig} searchConfig
296 * @return {!WebInspector.FileBasedSearchResultsPane}
297 */
298 createSearchResultsPane(searchConfig) {
299 return new WebInspector.FileBasedSearchResultsPane(searchConfig);
300 }
57 }; 301 };
58 302
59 303
60 WebInspector.SourcesSearchScope.prototype = {
61 /**
62 * @override
63 * @param {!WebInspector.Progress} progress
64 */
65 performIndexing: function(progress)
66 {
67 this.stopSearch();
68
69 var projects = this._projects();
70 var compositeProgress = new WebInspector.CompositeProgress(progress);
71 for (var i = 0; i < projects.length; ++i) {
72 var project = projects[i];
73 var projectProgress = compositeProgress.createSubProgress(project.ui SourceCodes().length);
74 project.indexContent(projectProgress);
75 }
76 },
77
78 /**
79 * @return {!Array.<!WebInspector.Project>}
80 */
81 _projects: function()
82 {
83 /**
84 * @param {!WebInspector.Project} project
85 * @return {boolean}
86 */
87 function filterOutServiceProjects(project)
88 {
89 return project.type() !== WebInspector.projectTypes.Service;
90 }
91
92 /**
93 * @param {!WebInspector.Project} project
94 * @return {boolean}
95 */
96 function filterOutContentScriptsIfNeeded(project)
97 {
98 return WebInspector.moduleSetting("searchInContentScripts").get() || project.type() !== WebInspector.projectTypes.ContentScripts;
99 }
100
101 return WebInspector.workspace.projects().filter(filterOutServiceProjects ).filter(filterOutContentScriptsIfNeeded);
102 },
103
104 /**
105 * @override
106 * @param {!WebInspector.ProjectSearchConfig} searchConfig
107 * @param {!WebInspector.Progress} progress
108 * @param {function(!WebInspector.FileBasedSearchResult)} searchResultCallba ck
109 * @param {function(boolean)} searchFinishedCallback
110 */
111 performSearch: function(searchConfig, progress, searchResultCallback, search FinishedCallback)
112 {
113 this.stopSearch();
114 this._searchResultCandidates = [];
115 this._searchResultCallback = searchResultCallback;
116 this._searchFinishedCallback = searchFinishedCallback;
117 this._searchConfig = searchConfig;
118
119 var projects = this._projects();
120 var barrier = new CallbackBarrier();
121 var compositeProgress = new WebInspector.CompositeProgress(progress);
122 var searchContentProgress = compositeProgress.createSubProgress();
123 var findMatchingFilesProgress = new WebInspector.CompositeProgress(compo siteProgress.createSubProgress());
124 for (var i = 0; i < projects.length; ++i) {
125 var project = projects[i];
126 var weight = project.uiSourceCodes().length;
127 var findMatchingFilesInProjectProgress = findMatchingFilesProgress.c reateSubProgress(weight);
128 var barrierCallback = barrier.createCallback();
129 var filesMathingFileQuery = this._projectFilesMatchingFileQuery(proj ect, searchConfig);
130 var callback = this._processMatchingFilesForProject.bind(this, this. _searchId, project, filesMathingFileQuery, barrierCallback);
131 project.findFilesMatchingSearchRequest(searchConfig, filesMathingFil eQuery, findMatchingFilesInProjectProgress, callback);
132 }
133 barrier.callWhenDone(this._processMatchingFiles.bind(this, this._searchI d, searchContentProgress, this._searchFinishedCallback.bind(this, true)));
134 },
135
136 /**
137 * @param {!WebInspector.Project} project
138 * @param {!WebInspector.ProjectSearchConfig} searchConfig
139 * @param {boolean=} dirtyOnly
140 * @return {!Array.<string>}
141 */
142 _projectFilesMatchingFileQuery: function(project, searchConfig, dirtyOnly)
143 {
144 var result = [];
145 var uiSourceCodes = project.uiSourceCodes();
146 for (var i = 0; i < uiSourceCodes.length; ++i) {
147 var uiSourceCode = uiSourceCodes[i];
148 var binding = WebInspector.persistence.binding(uiSourceCode);
149 if (binding && binding.fileSystem === uiSourceCode)
150 continue;
151 if (dirtyOnly && !uiSourceCode.isDirty())
152 continue;
153 if (this._searchConfig.filePathMatchesFileQuery(uiSourceCode.fullDis playName()))
154 result.push(uiSourceCode.url());
155 }
156 result.sort(String.naturalOrderComparator);
157 return result;
158 },
159
160 /**
161 * @param {number} searchId
162 * @param {!WebInspector.Project} project
163 * @param {!Array.<string>} filesMathingFileQuery
164 * @param {function()} callback
165 * @param {!Array.<string>} files
166 */
167 _processMatchingFilesForProject: function(searchId, project, filesMathingFil eQuery, callback, files)
168 {
169 if (searchId !== this._searchId) {
170 this._searchFinishedCallback(false);
171 return;
172 }
173
174 files.sort(String.naturalOrderComparator);
175 files = files.intersectOrdered(filesMathingFileQuery, String.naturalOrde rComparator);
176 var dirtyFiles = this._projectFilesMatchingFileQuery(project, this._sear chConfig, true);
177 files = files.mergeOrdered(dirtyFiles, String.naturalOrderComparator);
178
179 var uiSourceCodes = [];
180 for (var i = 0; i < files.length; ++i) {
181 var uiSourceCode = project.uiSourceCodeForURL(files[i]);
182 if (uiSourceCode) {
183 var script = WebInspector.DefaultScriptMapping.scriptForUISource Code(uiSourceCode);
184 if (script && !script.isAnonymousScript())
185 continue;
186 uiSourceCodes.push(uiSourceCode);
187 }
188 }
189 uiSourceCodes.sort(WebInspector.SourcesSearchScope._filesComparator);
190 this._searchResultCandidates = this._searchResultCandidates.mergeOrdered (uiSourceCodes, WebInspector.SourcesSearchScope._filesComparator);
191 callback();
192 },
193
194 /**
195 * @param {number} searchId
196 * @param {!WebInspector.Progress} progress
197 * @param {function()} callback
198 */
199 _processMatchingFiles: function(searchId, progress, callback)
200 {
201 if (searchId !== this._searchId) {
202 this._searchFinishedCallback(false);
203 return;
204 }
205
206 var files = this._searchResultCandidates;
207 if (!files.length) {
208 progress.done();
209 callback();
210 return;
211 }
212
213 progress.setTotalWork(files.length);
214
215 var fileIndex = 0;
216 var maxFileContentRequests = 20;
217 var callbacksLeft = 0;
218
219 for (var i = 0; i < maxFileContentRequests && i < files.length; ++i)
220 scheduleSearchInNextFileOrFinish.call(this);
221
222 /**
223 * @param {!WebInspector.UISourceCode} uiSourceCode
224 * @this {WebInspector.SourcesSearchScope}
225 */
226 function searchInNextFile(uiSourceCode)
227 {
228 if (uiSourceCode.isDirty())
229 contentLoaded.call(this, uiSourceCode, uiSourceCode.workingCopy( ));
230 else
231 uiSourceCode.checkContentUpdated(true, contentUpdated.bind(this, uiSourceCode));
232 }
233
234 /**
235 * @param {!WebInspector.UISourceCode} uiSourceCode
236 * @this {WebInspector.SourcesSearchScope}
237 */
238 function contentUpdated(uiSourceCode)
239 {
240 uiSourceCode.requestContent().then(contentLoaded.bind(this, uiSource Code));
241 }
242
243 /**
244 * @this {WebInspector.SourcesSearchScope}
245 */
246 function scheduleSearchInNextFileOrFinish()
247 {
248 if (fileIndex >= files.length) {
249 if (!callbacksLeft) {
250 progress.done();
251 callback();
252 return;
253 }
254 return;
255 }
256
257 ++callbacksLeft;
258 var uiSourceCode = files[fileIndex++];
259 setTimeout(searchInNextFile.bind(this, uiSourceCode), 0);
260 }
261
262 /**
263 * @param {!WebInspector.UISourceCode} uiSourceCode
264 * @param {?string} content
265 * @this {WebInspector.SourcesSearchScope}
266 */
267 function contentLoaded(uiSourceCode, content)
268 {
269 /**
270 * @param {!WebInspector.ContentProvider.SearchMatch} a
271 * @param {!WebInspector.ContentProvider.SearchMatch} b
272 */
273 function matchesComparator(a, b)
274 {
275 return a.lineNumber - b.lineNumber;
276 }
277
278 progress.worked(1);
279 var matches = [];
280 var queries = this._searchConfig.queries();
281 if (content !== null) {
282 for (var i = 0; i < queries.length; ++i) {
283 var nextMatches = WebInspector.ContentProvider.performSearch InContent(content, queries[i], !this._searchConfig.ignoreCase(), this._searchCon fig.isRegex());
284 matches = matches.mergeOrdered(nextMatches, matchesComparato r);
285 }
286 }
287 if (matches) {
288 var searchResult = new WebInspector.FileBasedSearchResult(uiSour ceCode, matches);
289 this._searchResultCallback(searchResult);
290 }
291
292 --callbacksLeft;
293 scheduleSearchInNextFileOrFinish.call(this);
294 }
295 },
296
297 /**
298 * @override
299 */
300 stopSearch: function()
301 {
302 ++this._searchId;
303 },
304
305 /**
306 * @override
307 * @param {!WebInspector.ProjectSearchConfig} searchConfig
308 * @return {!WebInspector.FileBasedSearchResultsPane}
309 */
310 createSearchResultsPane: function(searchConfig)
311 {
312 return new WebInspector.FileBasedSearchResultsPane(searchConfig);
313 }
314 };
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698