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

Side by Side Diff: chrome/browser/search/search.cc

Issue 1260033003: Partially componentize //chrome/browser/search/search.{h,cc} (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Rebase Created 5 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 2012 The Chromium Authors. All rights reserved. 1 // Copyright 2012 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 #include "chrome/browser/search/search.h" 5 #include "chrome/browser/search/search.h"
6 6
7 #include "base/command_line.h" 7 #include "base/command_line.h"
8 #include "base/metrics/field_trial.h" 8 #include "base/metrics/field_trial.h"
9 #include "base/metrics/histogram.h" 9 #include "base/metrics/histogram.h"
10 #include "base/prefs/pref_service.h" 10 #include "base/prefs/pref_service.h"
(...skipping 23 matching lines...) Expand all
34 #include "content/public/browser/navigation_entry.h" 34 #include "content/public/browser/navigation_entry.h"
35 #include "content/public/browser/render_process_host.h" 35 #include "content/public/browser/render_process_host.h"
36 #include "content/public/browser/web_contents.h" 36 #include "content/public/browser/web_contents.h"
37 37
38 #if defined(ENABLE_SUPERVISED_USERS) 38 #if defined(ENABLE_SUPERVISED_USERS)
39 #include "chrome/browser/supervised_user/supervised_user_service.h" 39 #include "chrome/browser/supervised_user/supervised_user_service.h"
40 #include "chrome/browser/supervised_user/supervised_user_service_factory.h" 40 #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
41 #include "chrome/browser/supervised_user/supervised_user_url_filter.h" 41 #include "chrome/browser/supervised_user/supervised_user_url_filter.h"
42 #endif 42 #endif
43 43
44 namespace chrome { 44 namespace search {
45 45
46 namespace { 46 namespace {
47 47
48 const char kPrefetchSearchResultsOnSRP[] = "prefetch_results_srp"; 48 const char kPrefetchSearchResultsOnSRP[] = "prefetch_results_srp";
49 const char kAllowPrefetchNonDefaultMatch[] = "allow_prefetch_non_default_match";
50 const char kPrerenderInstantUrlOnOmniboxFocus[] = 49 const char kPrerenderInstantUrlOnOmniboxFocus[] =
51 "prerender_instant_url_on_omnibox_focus"; 50 "prerender_instant_url_on_omnibox_focus";
52 51
53 #if defined(OS_ANDROID)
54 const char kPrefetchSearchResultsFlagName[] = "prefetch_results";
55
56 // Controls whether to reuse prerendered Instant Search base page to commit any
57 // search query.
58 const char kReuseInstantSearchBasePage[] = "reuse_instant_search_base_page";
59 #endif
60
61 // Controls whether to use the alternate Instant search base URL. This allows 52 // Controls whether to use the alternate Instant search base URL. This allows
62 // experimentation of Instant search. 53 // experimentation of Instant search.
63 const char kUseAltInstantURL[] = "use_alternate_instant_url"; 54 const char kUseAltInstantURL[] = "use_alternate_instant_url";
64 const char kUseSearchPathForInstant[] = "use_search_path_for_instant"; 55 const char kUseSearchPathForInstant[] = "use_search_path_for_instant";
65 const char kAltInstantURLPath[] = "search"; 56 const char kAltInstantURLPath[] = "search";
66 const char kAltInstantURLQueryParams[] = "&qbp=1"; 57 const char kAltInstantURLQueryParams[] = "&qbp=1";
67 58
68 #if !defined(OS_IOS) && !defined(OS_ANDROID)
69 const char kEnableQueryExtractionFlagName[] = "query_extraction";
70 #endif
71 const char kShouldShowGoogleLocalNTPFlagName[] = "google_local_ntp"; 59 const char kShouldShowGoogleLocalNTPFlagName[] = "google_local_ntp";
72 60
73 // Status of the New Tab URL for the default Search provider. NOTE: Used in a 61 // Status of the New Tab URL for the default Search provider. NOTE: Used in a
74 // UMA histogram so values should only be added at the end and not reordered. 62 // UMA histogram so values should only be added at the end and not reordered.
75 enum NewTabURLState { 63 enum NewTabURLState {
76 // Valid URL that should be used. 64 // Valid URL that should be used.
77 NEW_TAB_URL_VALID = 0, 65 NEW_TAB_URL_VALID = 0,
78 66
79 // Corrupt state (e.g. no profile or template url). 67 // Corrupt state (e.g. no profile or template url).
80 NEW_TAB_URL_BAD = 1, 68 NEW_TAB_URL_BAD = 1,
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
145 search_terms_args.append_extra_query_params = append_extra_query_params; 133 search_terms_args.append_extra_query_params = append_extra_query_params;
146 search_terms_args.force_instant_results = force_instant_results; 134 search_terms_args.force_instant_results = force_instant_results;
147 return GURL(ref.ReplaceSearchTerms(search_terms_args, search_terms_data)); 135 return GURL(ref.ReplaceSearchTerms(search_terms_args, search_terms_data));
148 } 136 }
149 137
150 bool MatchesAnySearchURL(const GURL& url, 138 bool MatchesAnySearchURL(const GURL& url,
151 TemplateURL* template_url, 139 TemplateURL* template_url,
152 const SearchTermsData& search_terms_data) { 140 const SearchTermsData& search_terms_data) {
153 GURL search_url = TemplateURLRefToGURL(template_url->url_ref(), 141 GURL search_url = TemplateURLRefToGURL(template_url->url_ref(),
154 search_terms_data, false, false); 142 search_terms_data, false, false);
155 if (search_url.is_valid() && 143 if (search_url.is_valid() && MatchesOriginAndPath(url, search_url))
156 search::MatchesOriginAndPath(url, search_url))
157 return true; 144 return true;
158 145
159 // "URLCount() - 1" because we already tested url_ref above. 146 // "URLCount() - 1" because we already tested url_ref above.
160 for (size_t i = 0; i < template_url->URLCount() - 1; ++i) { 147 for (size_t i = 0; i < template_url->URLCount() - 1; ++i) {
161 TemplateURLRef ref(template_url, i); 148 TemplateURLRef ref(template_url, i);
162 search_url = TemplateURLRefToGURL(ref, search_terms_data, false, false); 149 search_url = TemplateURLRefToGURL(ref, search_terms_data, false, false);
163 if (search_url.is_valid() && 150 if (search_url.is_valid() && MatchesOriginAndPath(url, search_url))
164 search::MatchesOriginAndPath(url, search_url))
165 return true; 151 return true;
166 } 152 }
167 153
168 return false; 154 return false;
169 } 155 }
170 156
171
172
173 // |url| should either have a secure scheme or have a non-HTTPS base URL that
174 // the user specified using --google-base-url. (This allows testers to use
175 // --google-base-url to point at non-HTTPS servers, which eases testing.)
176 bool IsSuitableURLForInstant(const GURL& url, const TemplateURL* template_url) {
177 return template_url->HasSearchTermsReplacementKey(url) &&
178 (url.SchemeIsCryptographic() ||
179 google_util::StartsWithCommandLineGoogleBaseURL(url));
180 }
181
182 // Returns true if |url| can be used as an Instant URL for |profile|. 157 // Returns true if |url| can be used as an Instant URL for |profile|.
183 bool IsInstantURL(const GURL& url, Profile* profile) { 158 bool IsInstantURL(const GURL& url, Profile* profile) {
184 if (!IsInstantExtendedAPIEnabled()) 159 if (!IsInstantExtendedAPIEnabled())
185 return false; 160 return false;
186 161
187 if (!url.is_valid()) 162 if (!url.is_valid())
188 return false; 163 return false;
189 164
190 const GURL new_tab_url(GetNewTabPageURL(profile)); 165 const GURL new_tab_url(GetNewTabPageURL(profile));
191 if (new_tab_url.is_valid() && 166 if (new_tab_url.is_valid() && MatchesOriginAndPath(url, new_tab_url))
192 search::MatchesOriginAndPath(url, new_tab_url))
193 return true; 167 return true;
194 168
195 TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile); 169 TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
196 if (!template_url) 170 if (!template_url)
197 return false; 171 return false;
198 172
199 if (!IsSuitableURLForInstant(url, template_url)) 173 if (!IsSuitableURLForInstant(url, template_url))
200 return false; 174 return false;
201 175
202 const TemplateURLRef& instant_url_ref = template_url->instant_url_ref(); 176 const TemplateURLRef& instant_url_ref = template_url->instant_url_ref();
203 UIThreadSearchTermsData search_terms_data(profile); 177 UIThreadSearchTermsData search_terms_data(profile);
204 const GURL instant_url = TemplateURLRefToGURL( 178 const GURL instant_url = TemplateURLRefToGURL(
205 instant_url_ref, search_terms_data, false, false); 179 instant_url_ref, search_terms_data, false, false);
206 if (!instant_url.is_valid()) 180 if (!instant_url.is_valid())
207 return false; 181 return false;
208 182
209 if (search::MatchesOriginAndPath(url, instant_url)) 183 if (MatchesOriginAndPath(url, instant_url))
210 return true; 184 return true;
211 185
212 return IsQueryExtractionEnabled() && 186 return IsQueryExtractionEnabled() &&
213 MatchesAnySearchURL(url, template_url, search_terms_data); 187 MatchesAnySearchURL(url, template_url, search_terms_data);
214 } 188 }
215 189
216 base::string16 GetSearchTermsImpl(const content::WebContents* contents, 190 base::string16 GetSearchTermsImpl(const content::WebContents* contents,
217 const content::NavigationEntry* entry) { 191 const content::NavigationEntry* entry) {
218 if (!contents || !IsQueryExtractionEnabled()) 192 if (!contents || !IsQueryExtractionEnabled())
219 return base::string16(); 193 return base::string16();
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after
299 return NewTabURLDetails(local_url, state); 273 return NewTabURLDetails(local_url, state);
300 } 274 }
301 } 275 }
302 276
303 GURL url; 277 GURL url;
304 NewTabURLState state; 278 NewTabURLState state;
305 }; 279 };
306 280
307 } // namespace 281 } // namespace
308 282
309 // Negative start-margin values prevent the "es_sm" parameter from being used.
310 const int kDisableStartMargin = -1;
311
312 std::string InstantExtendedEnabledParam(bool for_search) {
313 if (for_search && !chrome::IsQueryExtractionEnabled())
314 return std::string();
315 return std::string(google_util::kInstantExtendedAPIParam) + "=" +
316 base::Uint64ToString(EmbeddedSearchPageVersion()) + "&";
317 }
318
319 std::string ForceInstantResultsParam(bool for_prerender) {
320 return (for_prerender || !IsInstantExtendedAPIEnabled()) ?
321 "ion=1&" : std::string();
322 }
323
324 bool IsQueryExtractionEnabled() {
325 #if defined(OS_IOS) || defined(OS_ANDROID)
326 return true;
327 #else
328 if (!IsInstantExtendedAPIEnabled())
329 return false;
330
331 const base::CommandLine* command_line =
332 base::CommandLine::ForCurrentProcess();
333 if (command_line->HasSwitch(switches::kEnableQueryExtraction))
334 return true;
335
336 FieldTrialFlags flags;
337 return GetFieldTrialInfo(&flags) && GetBoolValueForFlagWithDefault(
338 kEnableQueryExtractionFlagName, false, flags);
339 #endif // defined(OS_IOS) || defined(OS_ANDROID)
340 }
341
342 base::string16 ExtractSearchTermsFromURL(Profile* profile, const GURL& url) { 283 base::string16 ExtractSearchTermsFromURL(Profile* profile, const GURL& url) {
343 if (url.is_valid() && url == GetSearchResultPrefetchBaseURL(profile)) { 284 if (url.is_valid() && url == GetSearchResultPrefetchBaseURL(profile)) {
344 // InstantSearchPrerenderer has the search query for the Instant search base 285 // InstantSearchPrerenderer has the search query for the Instant search base
345 // page. 286 // page.
346 InstantSearchPrerenderer* prerenderer = 287 InstantSearchPrerenderer* prerenderer =
347 InstantSearchPrerenderer::GetForProfile(profile); 288 InstantSearchPrerenderer::GetForProfile(profile);
348 // TODO(kmadhusu): Remove this CHECK after the investigation of 289 // TODO(kmadhusu): Remove this CHECK after the investigation of
349 // crbug.com/367204. 290 // crbug.com/367204.
350 CHECK(prerenderer); 291 CHECK(prerenderer);
351 return prerenderer->get_last_query(); 292 return prerenderer->get_last_query();
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after
448 389
449 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext()); 390 Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
450 if (!IsRenderedInInstantProcess(contents, profile)) 391 if (!IsRenderedInInstantProcess(contents, profile))
451 return false; 392 return false;
452 393
453 if (entry->GetURL() == GetLocalInstantURL(profile)) 394 if (entry->GetURL() == GetLocalInstantURL(profile))
454 return true; 395 return true;
455 396
456 GURL new_tab_url(GetNewTabPageURL(profile)); 397 GURL new_tab_url(GetNewTabPageURL(profile));
457 return new_tab_url.is_valid() && 398 return new_tab_url.is_valid() &&
458 search::MatchesOriginAndPath(entry->GetURL(), new_tab_url); 399 MatchesOriginAndPath(entry->GetURL(), new_tab_url);
459 } 400 }
460 401
461 bool IsSuggestPrefEnabled(Profile* profile) { 402 bool IsSuggestPrefEnabled(Profile* profile) {
462 return profile && !profile->IsOffTheRecord() && profile->GetPrefs() && 403 return profile && !profile->IsOffTheRecord() && profile->GetPrefs() &&
463 profile->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled); 404 profile->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled);
464 } 405 }
465 406
466 GURL GetInstantURL(Profile* profile, bool force_instant_results) { 407 GURL GetInstantURL(Profile* profile, bool force_instant_results) {
467 if (!IsInstantExtendedAPIEnabled() || !IsSuggestPrefEnabled(profile)) 408 if (!IsInstantExtendedAPIEnabled() || !IsSuggestPrefEnabled(profile))
468 return GURL(); 409 return GURL();
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after
520 } 461 }
521 462
522 GURL GetNewTabPageURL(Profile* profile) { 463 GURL GetNewTabPageURL(Profile* profile) {
523 return NewTabURLDetails::ForProfile(profile).url; 464 return NewTabURLDetails::ForProfile(profile).url;
524 } 465 }
525 466
526 GURL GetSearchResultPrefetchBaseURL(Profile* profile) { 467 GURL GetSearchResultPrefetchBaseURL(Profile* profile) {
527 return ShouldPrefetchSearchResults() ? GetInstantURL(profile, true) : GURL(); 468 return ShouldPrefetchSearchResults() ? GetInstantURL(profile, true) : GURL();
528 } 469 }
529 470
530 bool ShouldPrefetchSearchResults() {
531 if (!IsInstantExtendedAPIEnabled())
532 return false;
533
534 #if defined(OS_ANDROID)
535 if (base::CommandLine::ForCurrentProcess()->HasSwitch(
536 switches::kPrefetchSearchResults)) {
537 return true;
538 }
539
540 FieldTrialFlags flags;
541 return GetFieldTrialInfo(&flags) && GetBoolValueForFlagWithDefault(
542 kPrefetchSearchResultsFlagName, false, flags);
543 #else
544 return true;
545 #endif
546 }
547
548 bool ShouldAllowPrefetchNonDefaultMatch() {
549 if (!ShouldPrefetchSearchResults())
550 return false;
551
552 FieldTrialFlags flags;
553 return GetFieldTrialInfo(&flags) && GetBoolValueForFlagWithDefault(
554 kAllowPrefetchNonDefaultMatch, false, flags);
555 }
556
557 bool ShouldPrerenderInstantUrlOnOmniboxFocus() { 471 bool ShouldPrerenderInstantUrlOnOmniboxFocus() {
558 if (!ShouldPrefetchSearchResults()) 472 if (!ShouldPrefetchSearchResults())
559 return false; 473 return false;
560 474
561 FieldTrialFlags flags; 475 FieldTrialFlags flags;
562 return GetFieldTrialInfo(&flags) && GetBoolValueForFlagWithDefault( 476 return GetFieldTrialInfo(&flags) && GetBoolValueForFlagWithDefault(
563 kPrerenderInstantUrlOnOmniboxFocus, false, flags); 477 kPrerenderInstantUrlOnOmniboxFocus, false, flags);
564 } 478 }
565 479
566 bool ShouldReuseInstantSearchBasePage() {
567 if (!ShouldPrefetchSearchResults())
568 return false;
569
570 #if defined(OS_ANDROID)
571 FieldTrialFlags flags;
572 return GetFieldTrialInfo(&flags) && GetBoolValueForFlagWithDefault(
573 kReuseInstantSearchBasePage, false, flags);
574 #else
575 return true;
576 #endif
577 }
578
579 GURL GetLocalInstantURL(Profile* profile) { 480 GURL GetLocalInstantURL(Profile* profile) {
580 return GURL(chrome::kChromeSearchLocalNtpUrl); 481 return GURL(chrome::kChromeSearchLocalNtpUrl);
581 } 482 }
582 483
583 bool ShouldShowGoogleLocalNTP() { 484 bool ShouldShowGoogleLocalNTP() {
584 FieldTrialFlags flags; 485 FieldTrialFlags flags;
585 return !GetFieldTrialInfo(&flags) || GetBoolValueForFlagWithDefault( 486 return !GetFieldTrialInfo(&flags) || GetBoolValueForFlagWithDefault(
586 kShouldShowGoogleLocalNTPFlagName, true, flags); 487 kShouldShowGoogleLocalNTPFlagName, true, flags);
587 } 488 }
588 489
(...skipping 10 matching lines...) Expand all
599 url::Replacements<char> replacements; 500 url::Replacements<char> replacements;
600 std::string search_scheme(chrome::kChromeSearchScheme); 501 std::string search_scheme(chrome::kChromeSearchScheme);
601 replacements.SetScheme(search_scheme.data(), 502 replacements.SetScheme(search_scheme.data(),
602 url::Component(0, search_scheme.length())); 503 url::Component(0, search_scheme.length()));
603 504
604 // If this is the URL for a server-provided NTP, replace the host with 505 // If this is the URL for a server-provided NTP, replace the host with
605 // "remote-ntp". 506 // "remote-ntp".
606 std::string remote_ntp_host(chrome::kChromeSearchRemoteNtpHost); 507 std::string remote_ntp_host(chrome::kChromeSearchRemoteNtpHost);
607 NewTabURLDetails details = NewTabURLDetails::ForProfile(profile); 508 NewTabURLDetails details = NewTabURLDetails::ForProfile(profile);
608 if (details.state == NEW_TAB_URL_VALID && 509 if (details.state == NEW_TAB_URL_VALID &&
609 search::MatchesOriginAndPath(url, details.url)) { 510 MatchesOriginAndPath(url, details.url)) {
610 replacements.SetHost(remote_ntp_host.c_str(), 511 replacements.SetHost(remote_ntp_host.c_str(),
611 url::Component(0, remote_ntp_host.length())); 512 url::Component(0, remote_ntp_host.length()));
612 } 513 }
613 514
614 effective_url = effective_url.ReplaceComponents(replacements); 515 effective_url = effective_url.ReplaceComponents(replacements);
615 return effective_url; 516 return effective_url;
616 } 517 }
617 518
618 bool HandleNewTabURLRewrite(GURL* url, 519 bool HandleNewTabURLRewrite(GURL* url,
619 content::BrowserContext* browser_context) { 520 content::BrowserContext* browser_context) {
(...skipping 18 matching lines...) Expand all
638 bool HandleNewTabURLReverseRewrite(GURL* url, 539 bool HandleNewTabURLReverseRewrite(GURL* url,
639 content::BrowserContext* browser_context) { 540 content::BrowserContext* browser_context) {
640 if (!IsInstantExtendedAPIEnabled()) 541 if (!IsInstantExtendedAPIEnabled())
641 return false; 542 return false;
642 543
643 // Do nothing in incognito. 544 // Do nothing in incognito.
644 Profile* profile = Profile::FromBrowserContext(browser_context); 545 Profile* profile = Profile::FromBrowserContext(browser_context);
645 if (profile && profile->IsOffTheRecord()) 546 if (profile && profile->IsOffTheRecord())
646 return false; 547 return false;
647 548
648 if (search::MatchesOriginAndPath( 549 if (MatchesOriginAndPath(GURL(chrome::kChromeSearchLocalNtpUrl), *url)) {
649 GURL(chrome::kChromeSearchLocalNtpUrl), *url)) {
650 *url = GURL(chrome::kChromeUINewTabURL); 550 *url = GURL(chrome::kChromeUINewTabURL);
651 return true; 551 return true;
652 } 552 }
653 553
654 GURL new_tab_url(GetNewTabPageURL(profile)); 554 GURL new_tab_url(GetNewTabPageURL(profile));
655 if (new_tab_url.is_valid() && 555 if (new_tab_url.is_valid() && MatchesOriginAndPath(new_tab_url, *url)) {
656 search::MatchesOriginAndPath(new_tab_url, *url)) {
657 *url = GURL(chrome::kChromeUINewTabURL); 556 *url = GURL(chrome::kChromeUINewTabURL);
658 return true; 557 return true;
659 } 558 }
660 559
661 return false; 560 return false;
662 } 561 }
663 562
664 void SetInstantSupportStateInNavigationEntry(InstantSupportState state, 563 void SetInstantSupportStateInNavigationEntry(InstantSupportState state,
665 content::NavigationEntry* entry) { 564 content::NavigationEntry* entry) {
666 if (!entry) 565 if (!entry)
(...skipping 11 matching lines...) Expand all
678 577
679 return StringToInstantSupportState(value); 578 return StringToInstantSupportState(value);
680 } 579 }
681 580
682 bool ShouldPrefetchSearchResultsOnSRP() { 581 bool ShouldPrefetchSearchResultsOnSRP() {
683 FieldTrialFlags flags; 582 FieldTrialFlags flags;
684 return GetFieldTrialInfo(&flags) && GetBoolValueForFlagWithDefault( 583 return GetFieldTrialInfo(&flags) && GetBoolValueForFlagWithDefault(
685 kPrefetchSearchResultsOnSRP, false, flags); 584 kPrefetchSearchResultsOnSRP, false, flags);
686 } 585 }
687 586
688 void EnableQueryExtractionForTesting() {
689 base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
690 cl->AppendSwitch(switches::kEnableQueryExtraction);
691 }
692
693 bool ShouldUseAltInstantURL() { 587 bool ShouldUseAltInstantURL() {
694 FieldTrialFlags flags; 588 FieldTrialFlags flags;
695 return GetFieldTrialInfo(&flags) && GetBoolValueForFlagWithDefault( 589 return GetFieldTrialInfo(&flags) && GetBoolValueForFlagWithDefault(
696 kUseAltInstantURL, false, flags); 590 kUseAltInstantURL, false, flags);
697 } 591 }
698 592
699 bool ShouldUseSearchPathForInstant() { 593 bool ShouldUseSearchPathForInstant() {
700 FieldTrialFlags flags; 594 FieldTrialFlags flags;
701 return GetFieldTrialInfo(&flags) && GetBoolValueForFlagWithDefault( 595 return GetFieldTrialInfo(&flags) && GetBoolValueForFlagWithDefault(
702 kUseSearchPathForInstant, false, flags); 596 kUseSearchPathForInstant, false, flags);
703 } 597 }
704 598
705 } // namespace chrome 599 } // namespace search
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698