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
|
// 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/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "chrome/browser/profiles/profile.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/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.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"
namespace chrome {
namespace {
// The default value we should assign to the instant_extended.enabled pref. As
// with other prefs, the default is used only when the user hasn't toggled the
// pref explicitly.
enum InstantExtendedDefault {
INSTANT_DEFAULT_ON, // Default the pref to be enabled.
INSTANT_USE_EXISTING, // Use the current value of the instant.enabled pref.
INSTANT_DEFAULT_OFF, // Default the pref to be disabled.
};
// Configuration options for Embedded Search.
// InstantExtended 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
const char kInstantExtendedActivationName[] = "instant";
const InstantExtendedDefault kInstantExtendedActivationDefault =
INSTANT_DEFAULT_ON;
// Constants for the field trial name and group prefix.
const char kInstantExtendedFieldTrialName[] = "InstantExtended";
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";
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) {
TemplateURLRef::SearchTermsArgs search_terms_args =
TemplateURLRef::SearchTermsArgs(string16());
search_terms_args.omnibox_start_margin = start_margin;
return GURL(ref.ReplaceSearchTerms(search_terms_args));
}
bool MatchesOriginAndPath(const GURL& my_url, const GURL& other_url) {
return my_url.host() == other_url.host() &&
my_url.port() == other_url.port() &&
my_url.path() == other_url.path() &&
(my_url.scheme() == other_url.scheme() ||
(my_url.SchemeIs(chrome::kHttpsScheme) &&
other_url.SchemeIs(chrome::kHttpScheme)));
}
bool IsCommandLineInstantURL(const GURL& url) {
const CommandLine* cl = CommandLine::ForCurrentProcess();
const GURL instant_url(cl->GetSwitchValueASCII(switches::kInstantURL));
return instant_url.is_valid() && MatchesOriginAndPath(url, instant_url);
}
bool MatchesAnySearchURL(const GURL& url, TemplateURL* template_url) {
GURL search_url =
TemplateURLRefToGURL(template_url->url_ref(), kDisableStartMargin);
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);
if (search_url.is_valid() && MatchesOriginAndPath(url, search_url))
return true;
}
return false;
}
enum OptInState {
NOT_SET, // The user has not manually opted into or out of InstantExtended.
OPT_IN, // The user has opted-in to InstantExtended.
OPT_OUT, // The user has opted-out of InstantExtended.
OPT_IN_STATE_ENUM_COUNT,
};
void RecordInstantExtendedOptInState(OptInState state) {
static bool recorded = false;
if (!recorded) {
recorded = true;
UMA_HISTOGRAM_ENUMERATION("InstantExtended.OptInState", state,
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| can be used as an Instant URL for |profile|.
bool IsInstantURL(const GURL& url, Profile* profile) {
TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
if (!template_url)
return false;
const TemplateURLRef& instant_url_ref = template_url->instant_url_ref();
const bool extended_api_enabled = IsInstantExtendedAPIEnabled();
GURL effective_url = url;
if (IsCommandLineInstantURL(url))
effective_url = CoerceCommandLineURLToTemplateURL(url, instant_url_ref,
kDisableStartMargin);
if (!effective_url.is_valid())
return false;
if (extended_api_enabled && !effective_url.SchemeIsSecure())
return false;
if (extended_api_enabled &&
!template_url->HasSearchTermsReplacementKey(effective_url))
return false;
const GURL instant_url =
TemplateURLRefToGURL(instant_url_ref, kDisableStartMargin);
if (!instant_url.is_valid())
return false;
if (MatchesOriginAndPath(effective_url, instant_url))
return true;
if (extended_api_enabled && MatchesAnySearchURL(effective_url, template_url))
return true;
return false;
}
string16 GetSearchTermsImpl(const content::WebContents* contents,
const content::NavigationEntry* entry) {
if (!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) &&
(contents->GetController().GetLastCommittedEntry() == entry ||
!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.
TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
if (!template_url)
return string16();
GURL url = entry->GetVirtualURL();
if (IsCommandLineInstantURL(url))
url = CoerceCommandLineURLToTemplateURL(url, template_url->url_ref(),
kDisableStartMargin);
if (url.SchemeIsSecure() && template_url->HasSearchTermsReplacementKey(url))
template_url->ExtractSearchTermsFromURL(url, &search_terms);
return search_terms;
}
} // namespace
const char kInstantExtendedSearchTermsKey[] = "search_terms";
// 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
// On desktop, query extraction is part of Instant extended, so if one is
// enabled, the other is too.
return IsQueryExtractionEnabled();
#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() {
// 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)) {
RecordInstantExtendedOptInState(OPT_OUT);
return kEmbeddedPageVersionDisabled;
}
if (command_line->HasSwitch(switches::kEnableInstantExtendedAPI)) {
// The user has set the about:flags switch to Enabled - give the default
// UI version.
RecordInstantExtendedOptInState(OPT_IN);
return kEmbeddedPageVersionDefault;
}
RecordInstantExtendedOptInState(NOT_SET);
FieldTrialFlags flags;
if (GetFieldTrialInfo(
base::FieldTrialList::FindFullName(kInstantExtendedFieldTrialName),
&flags, NULL)) {
return GetUInt64ValueForFlagWithDefault(kEmbeddedPageVersionFlagName,
kEmbeddedPageVersionDefault,
flags);
}
return kEmbeddedPageVersionDisabled;
}
bool IsQueryExtractionEnabled() {
return EmbeddedSearchPageVersion() != kEmbeddedPageVersionDisabled;
}
string16 GetSearchTermsFromNavigationEntry(
const content::NavigationEntry* entry) {
string16 search_terms;
if (entry)
entry->GetExtraData(kInstantExtendedSearchTermsKey, &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();
return GetSearchTermsImpl(contents, entry);
}
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)
return false;
Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
return IsInstantExtendedAPIEnabled() &&
IsRenderedInInstantProcess(contents, profile) &&
(IsInstantURL(entry->GetVirtualURL(), profile) ||
entry->GetVirtualURL() == GURL(chrome::kChromeSearchLocalNtpUrl)) &&
GetSearchTermsImpl(contents, entry).empty();
}
bool ShouldAssignURLToInstantRenderer(const GURL& url, Profile* profile) {
return url.is_valid() &&
profile &&
(url.SchemeIs(chrome::kChromeSearchScheme) ||
IsInstantURL(url, profile));
}
void RegisterInstantUserPrefs(PrefRegistrySyncable* registry) {
registry->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, false,
PrefRegistrySyncable::SYNCABLE_PREF);
registry->RegisterBooleanPref(prefs::kInstantEnabled, false,
PrefRegistrySyncable::SYNCABLE_PREF);
// This default is overridden by SetInstantExtendedPrefDefault().
registry->RegisterBooleanPref(prefs::kInstantExtendedEnabled, false,
PrefRegistrySyncable::SYNCABLE_PREF);
}
const char* GetInstantPrefName() {
return IsInstantExtendedAPIEnabled() ? prefs::kInstantExtendedEnabled :
prefs::kInstantEnabled;
}
bool IsInstantPrefEnabled(Profile* profile) {
if (!profile || profile->IsOffTheRecord())
return false;
const PrefService* prefs = profile->GetPrefs();
if (!prefs)
return false;
const char* pref_name = GetInstantPrefName();
const bool pref_value = prefs->GetBoolean(pref_name);
if (pref_name == prefs::kInstantExtendedEnabled) {
// Note that this is only recorded for the first profile that calls this
// code (which happens on startup).
static bool recorded = false;
if (!recorded) {
UMA_HISTOGRAM_BOOLEAN("InstantExtended.PrefValue", pref_value);
recorded = true;
}
}
return pref_value;
}
void SetInstantExtendedPrefDefault(Profile* profile) {
PrefService* prefs = profile ? profile->GetPrefs() : NULL;
if (!prefs)
return;
bool pref_default = false;
// 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::kEnableInstantExtendedAPI)) {
pref_default = true;
} else if (!command_line->HasSwitch(switches::kDisableInstantExtendedAPI)) {
uint64 trial_default = kInstantExtendedActivationDefault;
FieldTrialFlags flags;
if (GetFieldTrialInfo(
base::FieldTrialList::FindFullName(kInstantExtendedFieldTrialName),
&flags, NULL)) {
trial_default = GetUInt64ValueForFlagWithDefault(
kInstantExtendedActivationName,
kInstantExtendedActivationDefault,
flags);
}
if (trial_default == INSTANT_DEFAULT_ON) {
pref_default = true;
} else if (trial_default != INSTANT_DEFAULT_OFF) {
pref_default = prefs->GetBoolean(prefs::kInstantEnabled);
}
}
prefs->SetDefaultPrefValue(prefs::kInstantExtendedEnabled,
Value::CreateBooleanValue(pref_default));
}
GURL GetInstantURL(Profile* profile, int start_margin) {
const bool extended_api_enabled = IsInstantExtendedAPIEnabled();
const PrefService* prefs = profile && !profile->IsOffTheRecord() ?
profile->GetPrefs() : NULL;
if (!IsInstantPrefEnabled(profile) &&
!(extended_api_enabled && prefs &&
prefs->GetBoolean(prefs::kSearchSuggestEnabled)))
return GURL();
TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
if (!template_url)
return GURL();
CommandLine* cl = CommandLine::ForCurrentProcess();
if (cl->HasSwitch(switches::kInstantURL)) {
GURL instant_url(cl->GetSwitchValueASCII(switches::kInstantURL));
if (extended_api_enabled) {
// Extended mode won't work if the search terms replacement key is absent.
GURL coerced_url = CoerceCommandLineURLToTemplateURL(
instant_url, template_url->instant_url_ref(), start_margin);
if (!template_url->HasSearchTermsReplacementKey(coerced_url))
return GURL();
}
return instant_url;
}
GURL instant_url =
TemplateURLRefToGURL(template_url->instant_url_ref(), start_margin);
if (extended_api_enabled) {
// Extended mode won't work if the search terms replacement key is absent.
if (!template_url->HasSearchTermsReplacementKey(instant_url))
return GURL();
// Extended mode requires HTTPS. Force it if necessary.
if (!instant_url.SchemeIsSecure()) {
const std::string secure_scheme = chrome::kHttpsScheme;
GURL::Replacements replacements;
replacements.SetSchemeStr(secure_scheme);
instant_url = instant_url.ReplaceComponents(replacements);
}
}
return instant_url;
}
bool IsInstantEnabled(Profile* profile) {
return GetInstantURL(profile, kDisableStartMargin).is_valid();
}
void EnableInstantExtendedAPIForTesting() {
CommandLine* cl = CommandLine::ForCurrentProcess();
cl->AppendSwitch(switches::kEnableInstantExtendedAPI);
}
bool GetFieldTrialInfo(const std::string& group_name,
FieldTrialFlags* flags,
uint64* group_number) {
if (EndsWith(group_name, kDisablingSuffix, true) ||
!StartsWithASCII(group_name, kGroupNumberPrefix, true))
return false;
// We have a valid trial that starts with "Group" and 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;
std::string group_suffix = group_prefix.substr(strlen(kGroupNumberPrefix));
if (!base::StringToUint64(group_suffix, &temp_group_number) ||
temp_group_number == 0)
return false;
if (group_number)
*group_number = temp_group_number;
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, "", 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);
}
// Coerces the commandline Instant URL to look like a template URL, so that we
// can extract search terms from it.
GURL CoerceCommandLineURLToTemplateURL(const GURL& instant_url,
const TemplateURLRef& ref,
int start_margin) {
GURL search_url = TemplateURLRefToGURL(ref, start_margin);
// NOTE(samarth): GURL returns temporaries which we must save because
// GURL::Replacements expects the replacements to live until
// ReplaceComponents is called.
const std::string search_scheme = chrome::kHttpsScheme;
const std::string search_host = search_url.host();
const std::string search_port = search_url.port();
const std::string search_path = search_url.path();
GURL::Replacements replacements;
replacements.SetSchemeStr(search_scheme);
replacements.SetHostStr(search_host);
replacements.SetPortStr(search_port);
replacements.SetPathStr(search_path);
return instant_url.ReplaceComponents(replacements);
}
} // namespace chrome
|