summaryrefslogtreecommitdiffstats
path: root/chrome/browser/ui/search/search.cc
blob: 3594ebdbf35e2e4e9eeb20b687ba7fd6e59bd561 (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
// Copyright (c) 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/ui/search/search.h"

#include "base/command_line.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "chrome/browser/instant/instant_service.h"
#include "chrome/browser/instant/instant_service_factory.h"
#include "chrome/browser/profiles/profile.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/url_constants.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"

namespace {

// 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;
const uint64 kEmbeddedPageVersionDefault = 2;

const char kInstantExtendedActivationName[] = "instant";
const chrome::search::InstantExtendedDefault kInstantExtendedActivationDefault =
    chrome::search::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";

chrome::search::InstantExtendedDefault InstantExtendedDefaultFromInt64(
    int64 default_value) {
  switch (default_value) {
    case 0: return chrome::search::INSTANT_DEFAULT_ON;
    case 1: return chrome::search::INSTANT_USE_EXISTING;
    case 2: return chrome::search::INSTANT_DEFAULT_OFF;
    default: return chrome::search::INSTANT_USE_EXISTING;
  }
}

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) {
  return GURL(
      ref.ReplaceSearchTerms(TemplateURLRef::SearchTermsArgs(string16())));
}

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();
  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());
  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);
    if (search_url.is_valid() && MatchesOriginAndPath(url, search_url))
      return true;
  }

  return false;
}

enum OptInState {
  // The user has not manually opted-in to or opted-out of InstantExtended.
  NOT_SET,
  // The user has opted-in to InstantExtended.
  OPT_IN,
  // The user has opted-out of InstantExtended.
  OPT_OUT,
  // Number of enum entries, used for UMA histogram reporting macros.
  OPT_IN_STATE_ENUM_COUNT,
};

void RecordInstantExtendedOptInState(OptInState state) {
  static bool recorded = false;
  if (!recorded) {
    UMA_HISTOGRAM_ENUMERATION("InstantExtended.OptInState", state,
                              OPT_IN_STATE_ENUM_COUNT);
    recorded = true;
  }
}

}  // namespace

namespace chrome {
namespace search {

const char kInstantExtendedSearchTermsKey[] = "search_terms";

const char kLocalOmniboxPopupURL[] =
    "chrome://local-omnibox-popup/local-omnibox-popup.html";

InstantExtendedDefault GetInstantExtendedDefaultSetting() {
  // 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 chrome::search::INSTANT_DEFAULT_OFF;
  if (command_line->HasSwitch(switches::kEnableInstantExtendedAPI))
    return chrome::search::INSTANT_DEFAULT_ON;

  FieldTrialFlags flags;
  if (GetFieldTrialInfo(
          base::FieldTrialList::FindFullName(kInstantExtendedFieldTrialName),
          &flags, NULL)) {
    uint64 trial_default = GetUInt64ValueForFlagWithDefault(
                               kInstantExtendedActivationName,
                               kInstantExtendedActivationDefault,
                               flags);
    return InstantExtendedDefaultFromInt64(trial_default);
  }

  return kInstantExtendedActivationDefault;
}

bool IsInstantExtendedAPIEnabled(const Profile* profile) {
  return EmbeddedSearchPageVersion(profile) != kEmbeddedPageVersionDisabled;
}

// 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(const Profile* profile) {
  if (!profile || profile->IsOffTheRecord())
    return kEmbeddedPageVersionDisabled;

  // 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(const Profile* profile) {
#if defined(OS_IOS)
  const CommandLine* cl = CommandLine::ForCurrentProcess();
  return cl->HasSwitch(switches::kEnableQueryExtraction);
#else
  // On desktop, query extraction is controlled by the instant-extended-api
  // flag.
  return IsInstantExtendedAPIEnabled(profile);
#endif
}

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();

  Profile* profile = Profile::FromBrowserContext(contents->GetBrowserContext());
  if (!IsQueryExtractionEnabled(profile))
    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.
  const content::RenderProcessHost* process_host =
      contents->GetRenderProcessHost();
  if (!process_host)
    return string16();

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

  if (!instant_service->IsInstantProcess(process_host->GetID()))
    return string16();

  // Check to see if search terms have already been extracted.
  const content::NavigationEntry* entry =
      contents->GetController().GetVisibleEntry();
  if (!entry)
    return string16();

  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());

  if (url.SchemeIsSecure() && template_url->HasSearchTermsReplacementKey(url))
    template_url->ExtractSearchTermsFromURL(url, &search_terms);

  return search_terms;
}

bool ShouldAssignURLToInstantRenderer(const GURL& url, Profile* profile) {
  TemplateURL* template_url = GetDefaultSearchProviderTemplateURL(profile);
  if (!template_url)
    return false;

  GURL effective_url = url;

  if (IsCommandLineInstantURL(url)) {
    const TemplateURLRef& instant_url_ref = template_url->instant_url_ref();
    effective_url = CoerceCommandLineURLToTemplateURL(url, instant_url_ref);
  }

  return ShouldAssignURLToInstantRendererImpl(
             effective_url,
             IsInstantExtendedAPIEnabled(profile),
             template_url);
}

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

void EnableQueryExtractionForTesting() {
#if defined(OS_IOS)
  CommandLine* cl = CommandLine::ForCurrentProcess();
  cl->AppendSwitch(switches::kEnableQueryExtraction);
#else
  EnableInstantExtendedAPIForTesting();
#endif
}

bool ShouldAssignURLToInstantRendererImpl(const GURL& url,
                                          bool extended_api_enabled,
                                          TemplateURL* template_url) {
  if (!url.is_valid())
    return false;

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

  if (extended_api_enabled && url == GURL(kLocalOmniboxPopupURL))
    return true;

  if (extended_api_enabled && !url.SchemeIsSecure())
    return false;

  if (extended_api_enabled && !template_url->HasSearchTermsReplacementKey(url))
    return false;

  GURL instant_url = TemplateURLRefToGURL(template_url->instant_url_ref());
  if (!instant_url.is_valid())
    return false;

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

  if (extended_api_enabled && MatchesAnySearchURL(url, template_url))
    return true;

  return false;
}

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) {
  GURL search_url = TemplateURLRefToGURL(ref);
  // 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 search
}  // namespace chrome