- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / search / search.cc
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
3 // found in the LICENSE file.
4
5 #include "chrome/browser/search/search.h"
6
7 #include "base/command_line.h"
8 #include "base/metrics/field_trial.h"
9 #include "base/metrics/histogram.h"
10 #include "base/prefs/pref_service.h"
11 #include "base/rand_util.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_split.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/browser_process.h"
16 #include "chrome/browser/google/google_util.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/browser/profiles/profile_manager.h"
19 #include "chrome/browser/search/instant_service.h"
20 #include "chrome/browser/search/instant_service_factory.h"
21 #include "chrome/browser/search_engines/template_url_service.h"
22 #include "chrome/browser/search_engines/template_url_service_factory.h"
23 #include "chrome/browser/ui/browser.h"
24 #include "chrome/browser/ui/browser_instant_controller.h"
25 #include "chrome/browser/ui/browser_iterator.h"
26 #include "chrome/common/chrome_switches.h"
27 #include "chrome/common/pref_names.h"
28 #include "chrome/common/search_urls.h"
29 #include "chrome/common/url_constants.h"
30 #include "components/sessions/serialized_navigation_entry.h"
31 #include "components/user_prefs/pref_registry_syncable.h"
32 #include "content/public/browser/navigation_entry.h"
33 #include "content/public/browser/render_process_host.h"
34 #include "content/public/browser/web_contents.h"
35 #include "grit/generated_resources.h"
36 #include "ui/base/l10n/l10n_util.h"
37
38 #if defined(ENABLE_MANAGED_USERS)
39 #include "chrome/browser/managed_mode/managed_mode_url_filter.h"
40 #include "chrome/browser/managed_mode/managed_user_service.h"
41 #include "chrome/browser/managed_mode/managed_user_service_factory.h"
42 #endif
43
44 namespace chrome {
45
46 namespace {
47
48 // Configuration options for Embedded Search.
49 // EmbeddedSearch field trials are named in such a way that we can parse out
50 // the experiment configuration from the trial's group name in order to give
51 // us maximum flexability in running experiments.
52 // Field trial groups should be named things like "Group7 espv:2 instant:1".
53 // The first token is always GroupN for some integer N, followed by a
54 // space-delimited list of key:value pairs which correspond to these flags:
55 const char kEmbeddedPageVersionFlagName[] = "espv";
56 const uint64 kEmbeddedPageVersionDisabled = 0;
57 #if defined(OS_IOS) || defined(OS_ANDROID)
58 const uint64 kEmbeddedPageVersionDefault = 1;
59 #else
60 const uint64 kEmbeddedPageVersionDefault = 2;
61 #endif
62
63 // The staleness timeout can be set (in seconds) via this config.
64 const char kStalePageTimeoutFlagName[] = "stale";
65 const int kStalePageTimeoutDefault = 3 * 3600;  // 3 hours.
66
67 const char kHideVerbatimFlagName[] = "hide_verbatim";
68 const char kUseRemoteNTPOnStartupFlagName[] = "use_remote_ntp_on_startup";
69 const char kShowNtpFlagName[] = "show_ntp";
70 const char kRecentTabsOnNTPFlagName[] = "show_recent_tabs";
71 const char kUseCacheableNTP[] = "use_cacheable_ntp";
72 const char kPrefetchSearchResultsOnSRP[] = "prefetch_results_srp";
73 const char kSuppressInstantExtendedOnSRPFlagName[] = "suppress_on_srp";
74
75 // Constants for the field trial name and group prefix.
76 // Note in M30 and below this field trial was named "InstantExtended" and in
77 // M31 was renamed to EmbeddedSearch for clarity and cleanliness.  Since we
78 // can't easilly sync up Finch configs with the pushing of this change to
79 // Dev & Canary, for now the code accepts both names.
80 // TODO(dcblack): Remove the InstantExtended name once M31 hits the Beta
81 // channel.
82 const char kInstantExtendedFieldTrialName[] = "InstantExtended";
83 const char kEmbeddedSearchFieldTrialName[] = "EmbeddedSearch";
84 const char kGroupNumberPrefix[] = "Group";
85
86 // If the field trial's group name ends with this string its configuration will
87 // be ignored and Instant Extended will not be enabled by default.
88 const char kDisablingSuffix[] = "DISABLED";
89
90 // Remember if we reported metrics about opt-in/out state.
91 bool instant_extended_opt_in_state_gate = false;
92
93 // Used to set the Instant support state of the Navigation entry.
94 const char kInstantSupportStateKey[] = "instant_support_state";
95
96 const char kInstantSupportEnabled[] = "Instant support enabled";
97 const char kInstantSupportDisabled[] = "Instant support disabled";
98 const char kInstantSupportUnknown[] = "Instant support unknown";
99
100 InstantSupportState StringToInstantSupportState(const string16& value) {
101   if (value == ASCIIToUTF16(kInstantSupportEnabled))
102     return INSTANT_SUPPORT_YES;
103   else if (value == ASCIIToUTF16(kInstantSupportDisabled))
104     return INSTANT_SUPPORT_NO;
105   else
106     return INSTANT_SUPPORT_UNKNOWN;
107 }
108
109 string16 InstantSupportStateToString(InstantSupportState state) {
110   switch (state) {
111     case INSTANT_SUPPORT_NO:
112       return ASCIIToUTF16(kInstantSupportDisabled);
113     case INSTANT_SUPPORT_YES:
114       return ASCIIToUTF16(kInstantSupportEnabled);
115     case INSTANT_SUPPORT_UNKNOWN:
116       return ASCIIToUTF16(kInstantSupportUnknown);
117   }
118   return ASCIIToUTF16(kInstantSupportUnknown);
119 }
120
121 TemplateURL* GetDefaultSearchProviderTemplateURL(Profile* profile) {
122   TemplateURLService* template_url_service =
123       TemplateURLServiceFactory::GetForProfile(profile);
124   if (template_url_service)
125     return template_url_service->GetDefaultSearchProvider();
126   return NULL;
127 }
128
129 GURL TemplateURLRefToGURL(const TemplateURLRef& ref,
130                           int start_margin,
131                           bool append_extra_query_params,
132                           bool force_instant_results) {
133   TemplateURLRef::SearchTermsArgs search_terms_args =
134       TemplateURLRef::SearchTermsArgs(string16());
135   search_terms_args.omnibox_start_margin = start_margin;
136   search_terms_args.append_extra_query_params = append_extra_query_params;
137   search_terms_args.force_instant_results = force_instant_results;
138   return GURL(ref.ReplaceSearchTerms(search_terms_args));
139 }
140
141 bool MatchesAnySearchURL(const GURL& url, TemplateURL* template_url) {
142   GURL search_url =
143       TemplateURLRefToGURL(template_url->url_ref(), kDisableStartMargin, false,
144                            false);
145   if (search_url.is_valid() &&
146       search::MatchesOriginAndPath(url, search_url))
147     return true;
148
149   // "URLCount() - 1" because we already tested url_ref above.
150   for (size_t i = 0; i < template_url->URLCount() - 1; ++i) {
151     TemplateURLRef ref(template_url, i);
152     search_url = TemplateURLRefToGURL(ref, kDisableStartMargin, false, false);
153     if (search_url.is_valid() &&
154         search::MatchesOriginAndPath(url, search_url))
155       return true;
156   }
157
158   return false;
159 }
160
161 void RecordInstantExtendedOptInState() {
162   if (instant_extended_opt_in_state_gate)
163     return;
164
165   instant_extended_opt_in_state_gate = true;
166   OptInState state = INSTANT_EXTENDED_NOT_SET;
167   const CommandLine* command_line = CommandLine::ForCurrentProcess();
168   if (command_line->HasSwitch(switches::kDisableInstantExtendedAPI))
169     state = INSTANT_EXTENDED_OPT_OUT;
170   else if (command_line->HasSwitch(switches::kEnableInstantExtendedAPI))
171     state = INSTANT_EXTENDED_OPT_IN;
172
173   UMA_HISTOGRAM_ENUMERATION("InstantExtended.NewOptInState", state,
174                             INSTANT_EXTENDED_OPT_IN_STATE_ENUM_COUNT);
175 }
176
177 // Returns true if |contents| is rendered inside the Instant process for
178 // |profile|.
179 bool IsRenderedInInstantProcess(const content::WebContents* contents,
180                                 Profile* profile) {
181   const content::RenderProcessHost* process_host =
182       contents->GetRenderProcessHost();
183   if (!process_host)
184     return false;
185
186   const InstantService* instant_service =
187       InstantServiceFactory::GetForProfile(profile);
188   if (!instant_service)
189     return false;
190
191   return instant_service->IsInstantProcess(process_host->GetID());
192 }
193
194 // Returns true if |url| passes some basic checks that must succeed for it to be
195 // usable as an instant URL:
196 // (1) It contains the search terms replacement key of |template_url|, which is
197 //     expected to be the TemplateURL* for the default search provider.
198 // (2) Either it has a secure scheme, or else the user has manually specified a
199 //     --google-base-url and it uses that base URL.  (This allows testers to use
200 //     --google-base-url to point at non-HTTPS servers, which eases testing.)
201 bool IsSuitableURLForInstant(const GURL& url, const TemplateURL* template_url) {
202   return template_url->HasSearchTermsReplacementKey(url) &&
203       (url.SchemeIsSecure() ||
204        google_util::StartsWithCommandLineGoogleBaseURL(url));
205 }
206
207 // Returns true if |url| can be used as an Instant URL for |profile|.
208 bool IsInstantURL(const GURL& url, Profile* profile) {
209   if (!IsInstantExtendedAPIEnabled())
210     return false;
211
212   if (!url.is_valid())
213     return false;
214
215   const GURL new_tab_url(GetNewTabPageURL(profile));
216   if (new_tab_url.is_valid() &&
217       search::MatchesOriginAndPath(url, new_tab_url))
218     return true;
219
220   TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
221   if (!template_url)
222     return false;
223
224   if (!IsSuitableURLForInstant(url, template_url))
225     return false;
226
227   const TemplateURLRef& instant_url_ref = template_url->instant_url_ref();
228   const GURL instant_url =
229       TemplateURLRefToGURL(instant_url_ref, kDisableStartMargin, false, false);
230   if (!instant_url.is_valid())
231     return false;
232
233   if (search::MatchesOriginAndPath(url, instant_url))
234     return true;
235
236   return !ShouldSuppressInstantExtendedOnSRP() &&
237       MatchesAnySearchURL(url, template_url);
238 }
239
240 string16 GetSearchTermsImpl(const content::WebContents* contents,
241                             const content::NavigationEntry* entry) {
242   if (!contents || !IsQueryExtractionEnabled())
243     return string16();
244
245   // For security reasons, don't extract search terms if the page is not being
246   // rendered in the privileged Instant renderer process. This is to protect
247   // against a malicious page somehow scripting the search results page and
248   // faking search terms in the URL. Random pages can't get into the Instant
249   // renderer and scripting doesn't work cross-process, so if the page is in
250   // the Instant process, we know it isn't being exploited.
251   // Since iOS and Android doesn't use the instant framework, these checks are
252   // disabled for the two platforms.
253   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
254 #if !defined(OS_IOS) && !defined(OS_ANDROID)
255   if (!IsRenderedInInstantProcess(contents, profile) &&
256       ((entry == contents->GetController().GetLastCommittedEntry()) ||
257        !ShouldAssignURLToInstantRenderer(entry->GetURL(), profile)))
258     return string16();
259 #endif  // !defined(OS_IOS) && !defined(OS_ANDROID)
260   // Check to see if search terms have already been extracted.
261   string16 search_terms = GetSearchTermsFromNavigationEntry(entry);
262   if (!search_terms.empty())
263     return search_terms;
264
265   // Otherwise, extract from the URL.
266   return GetSearchTermsFromURL(profile, entry->GetVirtualURL());
267 }
268
269 bool IsURLAllowedForSupervisedUser(const GURL& url, Profile* profile) {
270 #if defined(ENABLE_MANAGED_USERS)
271   ManagedUserService* managed_user_service =
272       ManagedUserServiceFactory::GetForProfile(profile);
273   ManagedModeURLFilter* url_filter =
274       managed_user_service->GetURLFilterForUIThread();
275   if (url_filter->GetFilteringBehaviorForURL(url) ==
276           ManagedModeURLFilter::BLOCK) {
277     return false;
278   }
279 #endif
280   return true;
281 }
282
283 }  // namespace
284
285 // Negative start-margin values prevent the "es_sm" parameter from being used.
286 const int kDisableStartMargin = -1;
287
288 bool IsInstantExtendedAPIEnabled() {
289 #if defined(OS_IOS) || defined(OS_ANDROID)
290   return false;
291 #else
292   RecordInstantExtendedOptInState();
293   return EmbeddedSearchPageVersion() != kEmbeddedPageVersionDisabled;
294 #endif  // defined(OS_IOS) || defined(OS_ANDROID)
295 }
296
297 // Determine what embedded search page version to request from the user's
298 // default search provider. If 0, the embedded search UI should not be enabled.
299 uint64 EmbeddedSearchPageVersion() {
300   RecordInstantExtendedOptInState();
301
302   // Check the command-line/about:flags setting first, which should have
303   // precedence and allows the trial to not be reported (if it's never queried).
304   const CommandLine* command_line = CommandLine::ForCurrentProcess();
305   if (command_line->HasSwitch(switches::kDisableInstantExtendedAPI))
306     return kEmbeddedPageVersionDisabled;
307   if (command_line->HasSwitch(switches::kEnableInstantExtendedAPI)) {
308     // The user has set the about:flags switch to Enabled - give the default
309     // UI version.
310     return kEmbeddedPageVersionDefault;
311   }
312
313   FieldTrialFlags flags;
314   uint64 group_num = 0;
315   if (GetFieldTrialInfo(&flags, &group_num)) {
316     if (group_num == 0)
317       return kEmbeddedPageVersionDisabled;
318     return GetUInt64ValueForFlagWithDefault(kEmbeddedPageVersionFlagName,
319                                             kEmbeddedPageVersionDefault,
320                                             flags);
321   }
322   return kEmbeddedPageVersionDisabled;
323 }
324
325 bool IsQueryExtractionEnabled() {
326   return EmbeddedSearchPageVersion() != kEmbeddedPageVersionDisabled &&
327       !ShouldSuppressInstantExtendedOnSRP();
328 }
329
330 string16 GetSearchTermsFromURL(Profile* profile, const GURL& url) {
331   string16 search_terms;
332   TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
333   if (template_url && IsSuitableURLForInstant(url, template_url))
334     template_url->ExtractSearchTermsFromURL(url, &search_terms);
335   return search_terms;
336 }
337
338 string16 GetSearchTermsFromNavigationEntry(
339     const content::NavigationEntry* entry) {
340   string16 search_terms;
341   if (entry)
342     entry->GetExtraData(sessions::kSearchTermsKey, &search_terms);
343   return search_terms;
344 }
345
346 string16 GetSearchTerms(const content::WebContents* contents) {
347   if (!contents)
348     return string16();
349
350   const content::NavigationEntry* entry =
351       contents->GetController().GetVisibleEntry();
352   if (!entry)
353     return string16();
354
355 #if !defined(OS_IOS) && !defined(OS_ANDROID)
356   // iOS and Android doesn't use the Instant framework, disable this check for
357   // the two platforms.
358   InstantSupportState state = GetInstantSupportStateFromNavigationEntry(*entry);
359   if (state == INSTANT_SUPPORT_NO)
360     return string16();
361 #endif  // !defined(OS_IOS) && !defined(OS_ANDROID)
362
363   return GetSearchTermsImpl(contents, entry);
364 }
365
366 bool ShouldAssignURLToInstantRenderer(const GURL& url, Profile* profile) {
367   return url.is_valid() &&
368          profile &&
369          IsInstantExtendedAPIEnabled() &&
370          (url.SchemeIs(chrome::kChromeSearchScheme) ||
371           IsInstantURL(url, profile));
372 }
373
374 bool ShouldUseProcessPerSiteForInstantURL(const GURL& url, Profile* profile) {
375   return ShouldAssignURLToInstantRenderer(url, profile) &&
376       (url.host() == chrome::kChromeSearchLocalNtpHost ||
377        url.host() == chrome::kChromeSearchOnlineNtpHost);
378 }
379
380 bool IsNTPURL(const GURL& url, Profile* profile) {
381   if (!url.is_valid())
382     return false;
383
384   if (!IsInstantExtendedAPIEnabled())
385     return url == GURL(chrome::kChromeUINewTabURL);
386
387   return profile &&
388       ((IsInstantURL(url, profile) &&
389         GetSearchTermsFromURL(profile, url).empty()) ||
390        url == GURL(chrome::kChromeSearchLocalNtpUrl));
391 }
392
393 bool IsInstantNTP(const content::WebContents* contents) {
394   if (!contents)
395     return false;
396
397   return NavEntryIsInstantNTP(contents,
398                               contents->GetController().GetVisibleEntry());
399 }
400
401 bool NavEntryIsInstantNTP(const content::WebContents* contents,
402                           const content::NavigationEntry* entry) {
403   if (!contents || !entry || !IsInstantExtendedAPIEnabled())
404     return false;
405
406   Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
407   if (!IsRenderedInInstantProcess(contents, profile))
408     return false;
409
410   if (entry->GetVirtualURL() == GetLocalInstantURL(profile))
411     return true;
412
413   if (ShouldUseCacheableNTP()) {
414     GURL new_tab_url(GetNewTabPageURL(profile));
415     return new_tab_url.is_valid() &&
416         search::MatchesOriginAndPath(entry->GetURL(), new_tab_url);
417   }
418
419   return IsInstantURL(entry->GetVirtualURL(), profile) &&
420       GetSearchTermsImpl(contents, entry).empty();
421 }
422
423 bool IsSuggestPrefEnabled(Profile* profile) {
424   return profile && !profile->IsOffTheRecord() && profile->GetPrefs() &&
425          profile->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled);
426 }
427
428 GURL GetInstantURL(Profile* profile, int start_margin,
429                    bool force_instant_results) {
430   if (!IsInstantExtendedAPIEnabled() || !IsSuggestPrefEnabled(profile))
431     return GURL();
432
433   TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
434   if (!template_url)
435     return GURL();
436
437   GURL instant_url =
438       TemplateURLRefToGURL(template_url->instant_url_ref(), start_margin, true,
439                            force_instant_results);
440   if (!instant_url.is_valid() ||
441       !template_url->HasSearchTermsReplacementKey(instant_url))
442     return GURL();
443
444   // Extended mode requires HTTPS.  Force it unless the base URL was overridden
445   // on the command line, in which case we allow HTTP (see comments on
446   // IsSuitableURLForInstant()).
447   if (!instant_url.SchemeIsSecure() &&
448       !google_util::StartsWithCommandLineGoogleBaseURL(instant_url)) {
449     GURL::Replacements replacements;
450     const std::string secure_scheme(content::kHttpsScheme);
451     replacements.SetSchemeStr(secure_scheme);
452     instant_url = instant_url.ReplaceComponents(replacements);
453   }
454
455   if (!IsURLAllowedForSupervisedUser(instant_url, profile))
456     return GURL();
457
458   return instant_url;
459 }
460
461 // Returns URLs associated with the default search engine for |profile|.
462 std::vector<GURL> GetSearchURLs(Profile* profile) {
463   std::vector<GURL> result;
464   TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
465   if (!template_url)
466     return result;
467   for (size_t i = 0; i < template_url->URLCount(); ++i) {
468     TemplateURLRef ref(template_url, i);
469     result.push_back(TemplateURLRefToGURL(ref, kDisableStartMargin, false,
470                                           false));
471   }
472   return result;
473 }
474
475 GURL GetNewTabPageURL(Profile* profile) {
476   if (!ShouldUseCacheableNTP())
477     return GURL();
478
479   if (!profile || profile->IsOffTheRecord())
480     return GURL();
481
482   if (!IsSuggestPrefEnabled(profile))
483     return GURL(chrome::kChromeSearchLocalNtpUrl);
484
485   TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
486   if (!template_url)
487     return GURL(chrome::kChromeSearchLocalNtpUrl);
488
489   GURL url(TemplateURLRefToGURL(template_url->new_tab_url_ref(),
490                                 kDisableStartMargin, false, false));
491   if (!url.is_valid() || !url.SchemeIsSecure())
492     return GURL(chrome::kChromeSearchLocalNtpUrl);
493
494   if (!IsURLAllowedForSupervisedUser(url, profile))
495     return GURL(chrome::kChromeSearchLocalNtpUrl);
496
497   return url;
498 }
499
500 GURL GetLocalInstantURL(Profile* profile) {
501   return GURL(chrome::kChromeSearchLocalNtpUrl);
502 }
503
504 bool ShouldPreferRemoteNTPOnStartup() {
505   // Check the command-line/about:flags setting first, which should have
506   // precedence and allows the trial to not be reported (if it's never queried).
507   const CommandLine* command_line = CommandLine::ForCurrentProcess();
508   if (command_line->HasSwitch(switches::kDisableInstantExtendedAPI) ||
509       command_line->HasSwitch(switches::kEnableLocalFirstLoadNTP)) {
510     return false;
511   }
512   if (command_line->HasSwitch(switches::kDisableLocalFirstLoadNTP))
513     return true;
514
515   FieldTrialFlags flags;
516   if (GetFieldTrialInfo(&flags, NULL)) {
517     return GetBoolValueForFlagWithDefault(kUseRemoteNTPOnStartupFlagName, true,
518                                           flags);
519   }
520   return false;
521 }
522
523 bool ShouldHideTopVerbatimMatch() {
524   FieldTrialFlags flags;
525   if (GetFieldTrialInfo(&flags, NULL)) {
526     return GetBoolValueForFlagWithDefault(kHideVerbatimFlagName, false, flags);
527   }
528   return false;
529 }
530
531 bool ShouldUseCacheableNTP() {
532   const CommandLine* command_line = CommandLine::ForCurrentProcess();
533   if (command_line->HasSwitch(switches::kUseCacheableNewTabPage))
534     return true;
535
536   FieldTrialFlags flags;
537   if (GetFieldTrialInfo(&flags, NULL)) {
538     return GetBoolValueForFlagWithDefault(kUseCacheableNTP, false, flags);
539   }
540   return false;
541 }
542
543 bool ShouldShowInstantNTP() {
544   // If using the cacheable NTP, load the NTP directly instead of preloading its
545   // contents using InstantNTP.
546   if (ShouldUseCacheableNTP())
547     return false;
548
549   FieldTrialFlags flags;
550   if (GetFieldTrialInfo(&flags, NULL)) {
551     return GetBoolValueForFlagWithDefault(kShowNtpFlagName, true, flags);
552   }
553   return true;
554 }
555
556 bool ShouldShowRecentTabsOnNTP() {
557   FieldTrialFlags flags;
558   if (GetFieldTrialInfo(&flags, NULL)) {
559     return GetBoolValueForFlagWithDefault(
560         kRecentTabsOnNTPFlagName, false, flags);
561   }
562
563   return false;
564 }
565
566 bool ShouldSuppressInstantExtendedOnSRP() {
567   FieldTrialFlags flags;
568   if (GetFieldTrialInfo(&flags, NULL)) {
569     return GetBoolValueForFlagWithDefault(
570         kSuppressInstantExtendedOnSRPFlagName, false, flags);
571   }
572
573   return false;
574 }
575
576 GURL GetEffectiveURLForInstant(const GURL& url, Profile* profile) {
577   CHECK(ShouldAssignURLToInstantRenderer(url, profile))
578       << "Error granting Instant access.";
579
580   if (url.SchemeIs(chrome::kChromeSearchScheme))
581     return url;
582
583   GURL effective_url(url);
584
585   // Replace the scheme with "chrome-search:".
586   url_canon::Replacements<char> replacements;
587   std::string search_scheme(chrome::kChromeSearchScheme);
588   replacements.SetScheme(search_scheme.data(),
589                          url_parse::Component(0, search_scheme.length()));
590
591   // If the URL corresponds to an online NTP, replace the host with
592   // "online-ntp".
593   std::string online_ntp_host(chrome::kChromeSearchOnlineNtpHost);
594   TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
595   if (template_url) {
596     const GURL instant_url = TemplateURLRefToGURL(
597         template_url->instant_url_ref(), kDisableStartMargin, false, false);
598     if (instant_url.is_valid() &&
599         search::MatchesOriginAndPath(url, instant_url)) {
600       replacements.SetHost(online_ntp_host.c_str(),
601                            url_parse::Component(0, online_ntp_host.length()));
602     }
603   }
604
605   effective_url = effective_url.ReplaceComponents(replacements);
606   return effective_url;
607 }
608
609 int GetInstantLoaderStalenessTimeoutSec() {
610   int timeout_sec = kStalePageTimeoutDefault;
611   FieldTrialFlags flags;
612   if (GetFieldTrialInfo(&flags, NULL)) {
613     timeout_sec = GetUInt64ValueForFlagWithDefault(kStalePageTimeoutFlagName,
614                                                    kStalePageTimeoutDefault,
615                                                    flags);
616   }
617
618   // Require a minimum 5 minute timeout.
619   if (timeout_sec < 0 || (timeout_sec > 0 && timeout_sec < 300))
620     timeout_sec = kStalePageTimeoutDefault;
621
622   // Randomize by upto 15% either side.
623   timeout_sec = base::RandInt(timeout_sec * 0.85, timeout_sec * 1.15);
624
625   return timeout_sec;
626 }
627
628 bool IsPreloadedInstantExtendedNTP(const content::WebContents* contents) {
629   if (!IsInstantExtendedAPIEnabled())
630     return false;
631
632   ProfileManager* profile_manager = g_browser_process->profile_manager();
633   if (!profile_manager)
634     return false;  // The profile manager can be NULL while testing.
635
636   const std::vector<Profile*>& profiles = profile_manager->GetLoadedProfiles();
637   for (size_t i = 0; i < profiles.size(); ++i) {
638     const InstantService* instant_service =
639         InstantServiceFactory::GetForProfile(profiles[i]);
640     if (instant_service && instant_service->GetNTPContents() == contents)
641       return true;
642   }
643   return false;
644 }
645
646 bool HandleNewTabURLRewrite(GURL* url,
647                             content::BrowserContext* browser_context) {
648   if (!IsInstantExtendedAPIEnabled())
649     return false;
650
651   if (!url->SchemeIs(chrome::kChromeUIScheme) ||
652       url->host() != chrome::kChromeUINewTabHost)
653     return false;
654
655   Profile* profile = Profile::FromBrowserContext(browser_context);
656   GURL new_tab_url(GetNewTabPageURL(profile));
657   if (!new_tab_url.is_valid())
658     return false;
659
660   *url = new_tab_url;
661   return true;
662 }
663
664 bool HandleNewTabURLReverseRewrite(GURL* url,
665                                    content::BrowserContext* browser_context) {
666   if (!IsInstantExtendedAPIEnabled())
667     return false;
668
669   Profile* profile = Profile::FromBrowserContext(browser_context);
670   GURL new_tab_url(GetNewTabPageURL(profile));
671   if (!new_tab_url.is_valid() ||
672       !search::MatchesOriginAndPath(new_tab_url, *url))
673     return false;
674
675   *url = GURL(chrome::kChromeUINewTabURL);
676   return true;
677 }
678
679 void SetInstantSupportStateInNavigationEntry(InstantSupportState state,
680                                              content::NavigationEntry* entry) {
681   if (!entry)
682     return;
683
684   entry->SetExtraData(kInstantSupportStateKey,
685                       InstantSupportStateToString(state));
686 }
687
688 InstantSupportState GetInstantSupportStateFromNavigationEntry(
689     const content::NavigationEntry& entry) {
690   string16 value;
691   if (!entry.GetExtraData(kInstantSupportStateKey, &value))
692     return INSTANT_SUPPORT_UNKNOWN;
693
694   return StringToInstantSupportState(value);
695 }
696
697 bool ShouldPrefetchSearchResultsOnSRP() {
698   // Check the command-line/about:flags setting first, which should have
699   // precedence and allows the trial to not be reported (if it's never queried).
700   const CommandLine* command_line = CommandLine::ForCurrentProcess();
701   if (command_line->HasSwitch(switches::kDisableInstantExtendedAPI) ||
702       command_line->HasSwitch(switches::kEnableInstantExtendedAPI)) {
703     return false;
704   }
705
706   FieldTrialFlags flags;
707   if (GetFieldTrialInfo(&flags, NULL)) {
708     return GetBoolValueForFlagWithDefault(kPrefetchSearchResultsOnSRP, false,
709                                           flags);
710   }
711   return false;
712 }
713
714 void EnableInstantExtendedAPIForTesting() {
715   CommandLine* cl = CommandLine::ForCurrentProcess();
716   cl->AppendSwitch(switches::kEnableInstantExtendedAPI);
717 }
718
719 void DisableInstantExtendedAPIForTesting() {
720   CommandLine* cl = CommandLine::ForCurrentProcess();
721   cl->AppendSwitch(switches::kDisableInstantExtendedAPI);
722 }
723
724 bool GetFieldTrialInfo(FieldTrialFlags* flags,
725                        uint64* group_number) {
726   // Get the group name.  If the EmbeddedSearch trial doesn't exist, look for
727   // the older InstantExtended name.
728   std::string group_name = base::FieldTrialList::FindFullName(
729       kEmbeddedSearchFieldTrialName);
730   if (group_name.empty()) {
731     group_name = base::FieldTrialList::FindFullName(
732         kInstantExtendedFieldTrialName);
733   }
734
735   if (EndsWith(group_name, kDisablingSuffix, true))
736     return false;
737
738   // We have a valid trial that isn't disabled.
739   // First extract the flags.
740   std::string group_prefix(group_name);
741
742   size_t first_space = group_name.find(" ");
743   if (first_space != std::string::npos) {
744     // There is a flags section of the group name. Split that out and parse it.
745     group_prefix = group_name.substr(0, first_space);
746     if (!base::SplitStringIntoKeyValuePairs(group_name.substr(first_space),
747                                             ':', ' ', flags)) {
748       // Failed to parse the flags section. Assume the whole group name is
749       // invalid.
750       return false;
751     }
752   }
753
754   // Now extract the group number, making sure we get a non-zero value.
755   uint64 temp_group_number = 0;
756   if (StartsWithASCII(group_name, kGroupNumberPrefix, true)) {
757     std::string group_suffix = group_prefix.substr(strlen(kGroupNumberPrefix));
758     if (!base::StringToUint64(group_suffix, &temp_group_number))
759       return false;
760     if (group_number)
761       *group_number = temp_group_number;
762   } else {
763     // Instant Extended is not enabled.
764     if (group_number)
765       *group_number = 0;
766   }
767
768   return true;
769 }
770
771 // Given a FieldTrialFlags object, returns the string value of the provided
772 // flag.
773 std::string GetStringValueForFlagWithDefault(const std::string& flag,
774                                              const std::string& default_value,
775                                              const FieldTrialFlags& flags) {
776   FieldTrialFlags::const_iterator i;
777   for (i = flags.begin(); i != flags.end(); i++) {
778     if (i->first == flag)
779       return i->second;
780   }
781   return default_value;
782 }
783
784 // Given a FieldTrialFlags object, returns the uint64 value of the provided
785 // flag.
786 uint64 GetUInt64ValueForFlagWithDefault(const std::string& flag,
787                                         uint64 default_value,
788                                         const FieldTrialFlags& flags) {
789   uint64 value;
790   std::string str_value =
791       GetStringValueForFlagWithDefault(flag, std::string(), flags);
792   if (base::StringToUint64(str_value, &value))
793     return value;
794   return default_value;
795 }
796
797 // Given a FieldTrialFlags object, returns the boolean value of the provided
798 // flag.
799 bool GetBoolValueForFlagWithDefault(const std::string& flag,
800                                     bool default_value,
801                                     const FieldTrialFlags& flags) {
802   return !!GetUInt64ValueForFlagWithDefault(flag, default_value ? 1 : 0, flags);
803 }
804
805 void ResetInstantExtendedOptInStateGateForTest() {
806   instant_extended_opt_in_state_gate = false;
807 }
808
809 }  // namespace chrome