summaryrefslogtreecommitdiffstats
path: root/chrome/browser/search/search.cc
blob: 10cc1ded2faef24b83df341c64762c719e36a20c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
// Copyright 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/search/search.h"

#include "base/command_line.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/rand_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/google/google_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/search/instant_service.h"
#include "chrome/browser/search/instant_service_factory.h"
#include "chrome/browser/search_engines/template_url_service.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_instant_controller.h"
#include "chrome/browser/ui/browser_iterator.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "components/sessions/serialized_navigation_entry.h"
#include "components/user_prefs/pref_registry_syncable.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "grit/generated_resources.h"
#include "ui/base/l10n/l10n_util.h"

namespace chrome {

namespace {

// Configuration options for Embedded Search.
// EmbeddedSearch field trials are named in such a way that we can parse out
// the experiment configuration from the trial's group name in order to give
// us maximum flexability in running experiments.
// Field trial groups should be named things like "Group7 espv:2 instant:1".
// The first token is always GroupN for some integer N, followed by a
// space-delimited list of key:value pairs which correspond to these flags:
const char kEmbeddedPageVersionFlagName[] = "espv";
const uint64 kEmbeddedPageVersionDisabled = 0;
#if defined(OS_IOS) || defined(OS_ANDROID)
const uint64 kEmbeddedPageVersionDefault = 1;
#else
const uint64 kEmbeddedPageVersionDefault = 2;
#endif

// The staleness timeout can be set (in seconds) via this config.
const char kStalePageTimeoutFlagName[] = "stale";
const int kStalePageTimeoutDefault = 3 * 3600;  // 3 hours.

const char kHideVerbatimFlagName[] = "hide_verbatim";
const char kUseRemoteNTPOnStartupFlagName[] = "use_remote_ntp_on_startup";
const char kShowNtpFlagName[] = "show_ntp";
const char kRecentTabsOnNTPFlagName[] = "show_recent_tabs";
const char kUseCacheableNTP[] = "use_cacheable_ntp";
const char kPrefetchSearchResultsOnSRP[] = "prefetch_results_srp";
const char kSuppressInstantExtendedOnSRPFlagName[] = "suppress_on_srp";

// Constants for the field trial name and group prefix.
// Note in M30 and below this field trial was named "InstantExtended" and in
// M31 was renamed to EmbeddedSearch for clarity and cleanliness.  Since we
// can't easilly sync up Finch configs with the pushing of this change to
// Dev & Canary, for now the code accepts both names.
// TODO(dcblack): Remove the InstantExtended name once M31 hits the Beta
// channel.
const char kInstantExtendedFieldTrialName[] = "InstantExtended";
const char kEmbeddedSearchFieldTrialName[] = "EmbeddedSearch";
const char kGroupNumberPrefix[] = "Group";

// If the field trial's group name ends with this string its configuration will
// be ignored and Instant Extended will not be enabled by default.
const char kDisablingSuffix[] = "DISABLED";

// Remember if we reported metrics about opt-in/out state.
bool instant_extended_opt_in_state_gate = false;

// Used to set the Instant support state of the Navigation entry.
const char kInstantSupportStateKey[] = "instant_support_state";

const char kInstantSupportEnabled[] = "Instant support enabled";
const char kInstantSupportDisabled[] = "Instant support disabled";
const char kInstantSupportUnknown[] = "Instant support unknown";

InstantSupportState StringToInstantSupportState(const string16& value) {
  if (value == ASCIIToUTF16(kInstantSupportEnabled))
    return INSTANT_SUPPORT_YES;
  else if (value == ASCIIToUTF16(kInstantSupportDisabled))
    return INSTANT_SUPPORT_NO;
  else
    return INSTANT_SUPPORT_UNKNOWN;
}

string16 InstantSupportStateToString(InstantSupportState state) {
  switch (state) {
    case INSTANT_SUPPORT_NO:
      return ASCIIToUTF16(kInstantSupportDisabled);
    case INSTANT_SUPPORT_YES:
      return ASCIIToUTF16(kInstantSupportEnabled);
    case INSTANT_SUPPORT_UNKNOWN:
      return ASCIIToUTF16(kInstantSupportUnknown);
  }
  return ASCIIToUTF16(kInstantSupportUnknown);
}

TemplateURL* GetDefaultSearchProviderTemplateURL(Profile* profile) {
  TemplateURLService* template_url_service =
      TemplateURLServiceFactory::GetForProfile(profile);
  if (template_url_service)
    return template_url_service->GetDefaultSearchProvider();
  return NULL;
}

GURL TemplateURLRefToGURL(const TemplateURLRef& ref,
                          int start_margin,
                          bool append_extra_query_params) {
  TemplateURLRef::SearchTermsArgs search_terms_args =
      TemplateURLRef::SearchTermsArgs(string16());
  search_terms_args.omnibox_start_margin = start_margin;
  search_terms_args.append_extra_query_params = append_extra_query_params;
  return GURL(ref.ReplaceSearchTerms(search_terms_args));
}

bool MatchesOrigin(const GURL& my_url, const GURL& other_url) {
  return my_url.host() == other_url.host() &&
         my_url.port() == other_url.port() &&
         (my_url.scheme() == other_url.scheme() ||
          (my_url.SchemeIs(content::kHttpsScheme) &&
           other_url.SchemeIs(content::kHttpScheme)));
}

bool MatchesAnySearchURL(const GURL& url, TemplateURL* template_url) {
  GURL search_url =
      TemplateURLRefToGURL(template_url->url_ref(), kDisableStartMargin, false);
  if (search_url.is_valid() && MatchesOriginAndPath(url, search_url))
    return true;

  // "URLCount() - 1" because we already tested url_ref above.
  for (size_t i = 0; i < template_url->URLCount() - 1; ++i) {
    TemplateURLRef ref(template_url, i);
    search_url = TemplateURLRefToGURL(ref, kDisableStartMargin, false);
    if (search_url.is_valid() && MatchesOriginAndPath(url, search_url))
      return true;
  }

  return false;
}

void RecordInstantExtendedOptInState() {
  if (instant_extended_opt_in_state_gate)
    return;

  instant_extended_opt_in_state_gate = true;
  OptInState state = INSTANT_EXTENDED_NOT_SET;
  const CommandLine* command_line = CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(switches::kDisableInstantExtendedAPI))
    state = INSTANT_EXTENDED_OPT_OUT;
  else if (command_line->HasSwitch(switches::kEnableInstantExtendedAPI))
    state = INSTANT_EXTENDED_OPT_IN;

  UMA_HISTOGRAM_ENUMERATION("InstantExtended.NewOptInState", state,
                            INSTANT_EXTENDED_OPT_IN_STATE_ENUM_COUNT);
}

// Returns true if |contents| is rendered inside the Instant process for
// |profile|.
bool IsRenderedInInstantProcess(const content::WebContents* contents,
                                Profile* profile) {
  const content::RenderProcessHost* process_host =
      contents->GetRenderProcessHost();
  if (!process_host)
    return false;

  const InstantService* instant_service =
      InstantServiceFactory::GetForProfile(profile);
  if (!instant_service)
    return false;

  return instant_service->IsInstantProcess(process_host->GetID());
}

// Returns true if |url| passes some basic checks that must succeed for it to be
// usable as an instant URL:
// (1) It contains the search terms replacement key of |template_url|, which is
//     expected to be the TemplateURL* for the default search provider.
// (2) Either it has a secure scheme, or else the user has manually specified a
//     --google-base-url and it uses that base URL.  (This allows testers to use
//     --google-base-url to point at non-HTTPS servers, which eases testing.)
bool IsSuitableURLForInstant(const GURL& url, const TemplateURL* template_url) {
  return template_url->HasSearchTermsReplacementKey(url) &&
      (url.SchemeIsSecure() ||
       google_util::StartsWithCommandLineGoogleBaseURL(url));
}

// Returns true if |url| can be used as an Instant URL for |profile|.
bool IsInstantURL(const GURL& url, Profile* profile) {
  if (!IsInstantExtendedAPIEnabled())
    return false;

  if (!url.is_valid())
    return false;

  const GURL new_tab_url(GetNewTabPageURL(profile));
  if (new_tab_url.is_valid() && MatchesOriginAndPath(url, new_tab_url))
    return true;

  TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
  if (!template_url)
    return false;

  if (!IsSuitableURLForInstant(url, template_url))
    return false;

  const TemplateURLRef& instant_url_ref = template_url->instant_url_ref();
  const GURL instant_url =
      TemplateURLRefToGURL(instant_url_ref, kDisableStartMargin, false);
  if (!instant_url.is_valid())
    return false;

  if (MatchesOriginAndPath(url, instant_url))
    return true;

  return !ShouldSuppressInstantExtendedOnSRP() &&
      MatchesAnySearchURL(url, template_url);
}

string16 GetSearchTermsImpl(const content::WebContents* contents,
                            const content::NavigationEntry* entry) {
  if (!contents || !IsQueryExtractionEnabled())
    return string16();

  // For security reasons, don't extract search terms if the page is not being
  // rendered in the privileged Instant renderer process. This is to protect
  // against a malicious page somehow scripting the search results page and
  // faking search terms in the URL. Random pages can't get into the Instant
  // renderer and scripting doesn't work cross-process, so if the page is in
  // the Instant process, we know it isn't being exploited.
  // Since iOS and Android doesn't use the instant framework, these checks are
  // disabled for the two platforms.
  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
#if !defined(OS_IOS) && !defined(OS_ANDROID)
  if (!IsRenderedInInstantProcess(contents, profile) &&
      ((entry == contents->GetController().GetLastCommittedEntry()) ||
       !ShouldAssignURLToInstantRenderer(entry->GetURL(), profile)))
    return string16();
#endif  // !defined(OS_IOS) && !defined(OS_ANDROID)
  // Check to see if search terms have already been extracted.
  string16 search_terms = GetSearchTermsFromNavigationEntry(entry);
  if (!search_terms.empty())
    return search_terms;

  // Otherwise, extract from the URL.
  return GetSearchTermsFromURL(profile, entry->GetVirtualURL());
}

}  // namespace

// Negative start-margin values prevent the "es_sm" parameter from being used.
const int kDisableStartMargin = -1;

bool IsInstantExtendedAPIEnabled() {
#if defined(OS_IOS) || defined(OS_ANDROID)
  return false;
#else
  RecordInstantExtendedOptInState();
  return EmbeddedSearchPageVersion() != kEmbeddedPageVersionDisabled;
#endif  // defined(OS_IOS) || defined(OS_ANDROID)
}

// Determine what embedded search page version to request from the user's
// default search provider. If 0, the embedded search UI should not be enabled.
uint64 EmbeddedSearchPageVersion() {
  RecordInstantExtendedOptInState();

  // Check the command-line/about:flags setting first, which should have
  // precedence and allows the trial to not be reported (if it's never queried).
  const CommandLine* command_line = CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(switches::kDisableInstantExtendedAPI))
    return kEmbeddedPageVersionDisabled;
  if (command_line->HasSwitch(switches::kEnableInstantExtendedAPI)) {
    // The user has set the about:flags switch to Enabled - give the default
    // UI version.
    return kEmbeddedPageVersionDefault;
  }

  FieldTrialFlags flags;
  uint64 group_num = 0;
  if (GetFieldTrialInfo(&flags, &group_num)) {
    if (group_num == 0)
      return kEmbeddedPageVersionDisabled;
    return GetUInt64ValueForFlagWithDefault(kEmbeddedPageVersionFlagName,
                                            kEmbeddedPageVersionDefault,
                                            flags);
  }
  return kEmbeddedPageVersionDisabled;
}

bool IsQueryExtractionEnabled() {
  return EmbeddedSearchPageVersion() != kEmbeddedPageVersionDisabled &&
      !ShouldSuppressInstantExtendedOnSRP();
}

string16 GetSearchTermsFromURL(Profile* profile, const GURL& url) {
  string16 search_terms;
  TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
  if (template_url && IsSuitableURLForInstant(url, template_url))
    template_url->ExtractSearchTermsFromURL(url, &search_terms);
  return search_terms;
}

string16 GetSearchTermsFromNavigationEntry(
    const content::NavigationEntry* entry) {
  string16 search_terms;
  if (entry)
    entry->GetExtraData(sessions::kSearchTermsKey, &search_terms);
  return search_terms;
}

string16 GetSearchTerms(const content::WebContents* contents) {
  if (!contents)
    return string16();

  const content::NavigationEntry* entry =
      contents->GetController().GetVisibleEntry();
  if (!entry)
    return string16();

#if !defined(OS_IOS) && !defined(OS_ANDROID)
  // iOS and Android doesn't use the Instant framework, disable this check for
  // the two platforms.
  InstantSupportState state = GetInstantSupportStateFromNavigationEntry(*entry);
  if (state == INSTANT_SUPPORT_NO)
    return string16();
#endif  // !defined(OS_IOS) && !defined(OS_ANDROID)

  return GetSearchTermsImpl(contents, entry);
}

bool ShouldAssignURLToInstantRenderer(const GURL& url, Profile* profile) {
  return url.is_valid() &&
         profile &&
         IsInstantExtendedAPIEnabled() &&
         (url.SchemeIs(chrome::kChromeSearchScheme) ||
          IsInstantURL(url, profile));
}

bool ShouldUseProcessPerSiteForInstantURL(const GURL& url, Profile* profile) {
  return ShouldAssignURLToInstantRenderer(url, profile) &&
      (url.host() == chrome::kChromeSearchLocalNtpHost ||
       url.host() == chrome::kChromeSearchOnlineNtpHost);
}

bool IsNTPURL(const GURL& url, Profile* profile) {
  if (!url.is_valid())
    return false;

  if (!IsInstantExtendedAPIEnabled())
    return url == GURL(chrome::kChromeUINewTabURL);

  return profile &&
      ((IsInstantURL(url, profile) &&
        GetSearchTermsFromURL(profile, url).empty()) ||
       url == GURL(chrome::kChromeSearchLocalNtpUrl));
}

bool IsInstantNTP(const content::WebContents* contents) {
  if (!contents)
    return false;

  return NavEntryIsInstantNTP(contents,
                              contents->GetController().GetVisibleEntry());
}

bool NavEntryIsInstantNTP(const content::WebContents* contents,
                          const content::NavigationEntry* entry) {
  if (!contents || !entry || !IsInstantExtendedAPIEnabled())
    return false;

  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
  if (!IsRenderedInInstantProcess(contents, profile))
    return false;

  if (entry->GetVirtualURL() == GetLocalInstantURL(profile))
    return true;

  if (ShouldUseCacheableNTP()) {
    GURL new_tab_url(GetNewTabPageURL(profile));
    return new_tab_url.is_valid() &&
        MatchesOriginAndPath(entry->GetURL(), new_tab_url);
  }

  return IsInstantURL(entry->GetVirtualURL(), profile) &&
      GetSearchTermsImpl(contents, entry).empty();
}

bool IsSuggestPrefEnabled(Profile* profile) {
  return profile && !profile->IsOffTheRecord() && profile->GetPrefs() &&
         profile->GetPrefs()->GetBoolean(prefs::kSearchSuggestEnabled);
}

GURL GetInstantURL(Profile* profile, int start_margin) {
  if (!IsInstantExtendedAPIEnabled() || !IsSuggestPrefEnabled(profile))
    return GURL();

  TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
  if (!template_url)
    return GURL();

  GURL instant_url =
      TemplateURLRefToGURL(template_url->instant_url_ref(), start_margin, true);
  if (!instant_url.is_valid() ||
      !template_url->HasSearchTermsReplacementKey(instant_url))
    return GURL();

  // Extended mode requires HTTPS.  Force it unless the base URL was overridden
  // on the command line, in which case we allow HTTP (see comments on
  // IsSuitableURLForInstant()).
  if (instant_url.SchemeIsSecure() ||
      google_util::StartsWithCommandLineGoogleBaseURL(instant_url))
    return instant_url;
  GURL::Replacements replacements;
  const std::string secure_scheme(content::kHttpsScheme);
  replacements.SetSchemeStr(secure_scheme);
  return instant_url.ReplaceComponents(replacements);
}

GURL GetLocalInstantURL(Profile* profile) {
  return GURL(chrome::kChromeSearchLocalNtpUrl);
}

bool ShouldPreferRemoteNTPOnStartup() {
  // Check the command-line/about:flags setting first, which should have
  // precedence and allows the trial to not be reported (if it's never queried).
  const CommandLine* command_line = CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(switches::kDisableInstantExtendedAPI) ||
      command_line->HasSwitch(switches::kEnableLocalFirstLoadNTP)) {
    return false;
  }
  if (command_line->HasSwitch(switches::kDisableLocalFirstLoadNTP))
    return true;

  FieldTrialFlags flags;
  if (GetFieldTrialInfo(&flags, NULL)) {
    return GetBoolValueForFlagWithDefault(kUseRemoteNTPOnStartupFlagName, true,
                                          flags);
  }
  return false;
}

bool ShouldHideTopVerbatimMatch() {
  FieldTrialFlags flags;
  if (GetFieldTrialInfo(&flags, NULL)) {
    return GetBoolValueForFlagWithDefault(kHideVerbatimFlagName, false, flags);
  }
  return false;
}

bool ShouldUseCacheableNTP() {
  const CommandLine* command_line = CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(switches::kUseCacheableNewTabPage))
    return true;

  FieldTrialFlags flags;
  if (GetFieldTrialInfo(&flags, NULL)) {
    return GetBoolValueForFlagWithDefault(kUseCacheableNTP, false, flags);
  }
  return false;
}

bool ShouldShowInstantNTP() {
  // If using the cacheable NTP, load the NTP directly instead of preloading its
  // contents using InstantNTP.
  if (ShouldUseCacheableNTP())
    return false;

  FieldTrialFlags flags;
  if (GetFieldTrialInfo(&flags, NULL)) {
    return GetBoolValueForFlagWithDefault(kShowNtpFlagName, true, flags);
  }
  return true;
}

bool ShouldShowRecentTabsOnNTP() {
  FieldTrialFlags flags;
  if (GetFieldTrialInfo(&flags, NULL)) {
    return GetBoolValueForFlagWithDefault(
        kRecentTabsOnNTPFlagName, false, flags);
  }

  return false;
}

bool ShouldSuppressInstantExtendedOnSRP() {
  FieldTrialFlags flags;
  if (GetFieldTrialInfo(&flags, NULL)) {
    return GetBoolValueForFlagWithDefault(
        kSuppressInstantExtendedOnSRPFlagName, false, flags);
  }

  return false;
}

bool MatchesOriginAndPath(const GURL& my_url, const GURL& other_url) {
  return MatchesOrigin(my_url, other_url) && my_url.path() == other_url.path();
}

GURL GetEffectiveURLForInstant(const GURL& url, Profile* profile) {
  CHECK(ShouldAssignURLToInstantRenderer(url, profile))
      << "Error granting Instant access.";

  if (url.SchemeIs(chrome::kChromeSearchScheme))
    return url;

  GURL effective_url(url);

  // Replace the scheme with "chrome-search:".
  url_canon::Replacements<char> replacements;
  std::string search_scheme(chrome::kChromeSearchScheme);
  replacements.SetScheme(search_scheme.data(),
                         url_parse::Component(0, search_scheme.length()));

  // If the URL corresponds to an online NTP, replace the host with
  // "online-ntp".
  std::string online_ntp_host(chrome::kChromeSearchOnlineNtpHost);
  TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
  if (template_url) {
    const GURL instant_url = TemplateURLRefToGURL(
        template_url->instant_url_ref(), kDisableStartMargin, false);
    if (instant_url.is_valid() && MatchesOriginAndPath(url, instant_url)) {
      replacements.SetHost(online_ntp_host.c_str(),
                           url_parse::Component(0, online_ntp_host.length()));
    }
  }

  effective_url = effective_url.ReplaceComponents(replacements);
  return effective_url;
}

int GetInstantLoaderStalenessTimeoutSec() {
  int timeout_sec = kStalePageTimeoutDefault;
  FieldTrialFlags flags;
  if (GetFieldTrialInfo(&flags, NULL)) {
    timeout_sec = GetUInt64ValueForFlagWithDefault(kStalePageTimeoutFlagName,
                                                   kStalePageTimeoutDefault,
                                                   flags);
  }

  // Require a minimum 5 minute timeout.
  if (timeout_sec < 0 || (timeout_sec > 0 && timeout_sec < 300))
    timeout_sec = kStalePageTimeoutDefault;

  // Randomize by upto 15% either side.
  timeout_sec = base::RandInt(timeout_sec * 0.85, timeout_sec * 1.15);

  return timeout_sec;
}

bool IsPreloadedInstantExtendedNTP(const content::WebContents* contents) {
  if (!IsInstantExtendedAPIEnabled())
    return false;

  ProfileManager* profile_manager = g_browser_process->profile_manager();
  if (!profile_manager)
    return false;  // The profile manager can be NULL while testing.

  const std::vector<Profile*>& profiles = profile_manager->GetLoadedProfiles();
  for (size_t i = 0; i < profiles.size(); ++i) {
    const InstantService* instant_service =
        InstantServiceFactory::GetForProfile(profiles[i]);
    if (instant_service && instant_service->GetNTPContents() == contents)
      return true;
  }
  return false;
}

bool HandleNewTabURLRewrite(GURL* url,
                            content::BrowserContext* browser_context) {
  if (!IsInstantExtendedAPIEnabled())
    return false;

  if (!url->SchemeIs(chrome::kChromeUIScheme) ||
      url->host() != chrome::kChromeUINewTabHost)
    return false;

  Profile* profile = Profile::FromBrowserContext(browser_context);
  GURL new_tab_url(GetNewTabPageURL(profile));
  if (!new_tab_url.is_valid())
    return false;

  *url = new_tab_url;
  return true;
}

bool HandleNewTabURLReverseRewrite(GURL* url,
                                   content::BrowserContext* browser_context) {
  if (!IsInstantExtendedAPIEnabled())
    return false;

  Profile* profile = Profile::FromBrowserContext(browser_context);
  GURL new_tab_url(GetNewTabPageURL(profile));
  if (!new_tab_url.is_valid() || !MatchesOriginAndPath(new_tab_url, *url))
    return false;

  *url = GURL(chrome::kChromeUINewTabURL);
  return true;
}

void SetInstantSupportStateInNavigationEntry(InstantSupportState state,
                                             content::NavigationEntry* entry) {
  if (!entry)
    return;

  entry->SetExtraData(kInstantSupportStateKey,
                      InstantSupportStateToString(state));
}

InstantSupportState GetInstantSupportStateFromNavigationEntry(
    const content::NavigationEntry& entry) {
  string16 value;
  if (!entry.GetExtraData(kInstantSupportStateKey, &value))
    return INSTANT_SUPPORT_UNKNOWN;

  return StringToInstantSupportState(value);
}

bool ShouldPrefetchSearchResultsOnSRP() {
  // Check the command-line/about:flags setting first, which should have
  // precedence and allows the trial to not be reported (if it's never queried).
  const CommandLine* command_line = CommandLine::ForCurrentProcess();
  if (command_line->HasSwitch(switches::kDisableInstantExtendedAPI) ||
      command_line->HasSwitch(switches::kEnableInstantExtendedAPI)) {
    return false;
  }

  FieldTrialFlags flags;
  if (GetFieldTrialInfo(&flags, NULL)) {
    return GetBoolValueForFlagWithDefault(kPrefetchSearchResultsOnSRP, false,
                                          flags);
  }
  return false;
}

void EnableInstantExtendedAPIForTesting() {
  CommandLine* cl = CommandLine::ForCurrentProcess();
  cl->AppendSwitch(switches::kEnableInstantExtendedAPI);
}

void DisableInstantExtendedAPIForTesting() {
  CommandLine* cl = CommandLine::ForCurrentProcess();
  cl->AppendSwitch(switches::kDisableInstantExtendedAPI);
}

bool GetFieldTrialInfo(FieldTrialFlags* flags,
                       uint64* group_number) {
  // Get the group name.  If the EmbeddedSearch trial doesn't exist, look for
  // the older InstantExtended name.
  std::string group_name = base::FieldTrialList::FindFullName(
      kEmbeddedSearchFieldTrialName);
  if (group_name.empty()) {
    group_name = base::FieldTrialList::FindFullName(
        kInstantExtendedFieldTrialName);
  }

  if (EndsWith(group_name, kDisablingSuffix, true))
    return false;

  // We have a valid trial that isn't disabled.
  // First extract the flags.
  std::string group_prefix(group_name);

  size_t first_space = group_name.find(" ");
  if (first_space != std::string::npos) {
    // There is a flags section of the group name. Split that out and parse it.
    group_prefix = group_name.substr(0, first_space);
    if (!base::SplitStringIntoKeyValuePairs(group_name.substr(first_space),
                                            ':', ' ', flags)) {
      // Failed to parse the flags section. Assume the whole group name is
      // invalid.
      return false;
    }
  }

  // Now extract the group number, making sure we get a non-zero value.
  uint64 temp_group_number = 0;
  if (StartsWithASCII(group_name, kGroupNumberPrefix, true)) {
    std::string group_suffix = group_prefix.substr(strlen(kGroupNumberPrefix));
    if (!base::StringToUint64(group_suffix, &temp_group_number))
      return false;
    if (group_number)
      *group_number = temp_group_number;
  } else {
    // Instant Extended is not enabled.
    if (group_number)
      *group_number = 0;
  }

  return true;
}

// Given a FieldTrialFlags object, returns the string value of the provided
// flag.
std::string GetStringValueForFlagWithDefault(const std::string& flag,
                                             const std::string& default_value,
                                             const FieldTrialFlags& flags) {
  FieldTrialFlags::const_iterator i;
  for (i = flags.begin(); i != flags.end(); i++) {
    if (i->first == flag)
      return i->second;
  }
  return default_value;
}

// Given a FieldTrialFlags object, returns the uint64 value of the provided
// flag.
uint64 GetUInt64ValueForFlagWithDefault(const std::string& flag,
                                        uint64 default_value,
                                        const FieldTrialFlags& flags) {
  uint64 value;
  std::string str_value =
      GetStringValueForFlagWithDefault(flag, std::string(), flags);
  if (base::StringToUint64(str_value, &value))
    return value;
  return default_value;
}

// Given a FieldTrialFlags object, returns the boolean value of the provided
// flag.
bool GetBoolValueForFlagWithDefault(const std::string& flag,
                                    bool default_value,
                                    const FieldTrialFlags& flags) {
  return !!GetUInt64ValueForFlagWithDefault(flag, default_value ? 1 : 0, flags);
}

GURL GetNewTabPageURL(Profile* profile) {
  if (!ShouldUseCacheableNTP())
    return GURL();

  if (!profile || !IsSuggestPrefEnabled(profile))
    return GURL();

  TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
  if (!template_url)
    return GURL();

  return TemplateURLRefToGURL(template_url->new_tab_url_ref(),
                              kDisableStartMargin, false);
}

void ResetInstantExtendedOptInStateGateForTest() {
  instant_extended_opt_in_state_gate = false;
}

}  // namespace chrome