blob: bd26831f947a343d670f454fd52150cc58e045b7 [file] [log] [blame]
// 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 <stddef.h>
#include <string>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/url_constants.h"
#include "components/google/core/common/google_util.h"
#include "components/search/search.h"
#include "components/search_engines/search_engine_type.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "url/gurl.h"
#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
#include "chrome/browser/supervised_user/supervised_user_service.h"
#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#include "chrome/browser/supervised_user/supervised_user_url_filter.h"
#endif
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/signin/merge_session_throttling_utils.h"
#endif // defined(OS_CHROMEOS)
#if !defined(OS_ANDROID)
#include "chrome/browser/search/instant_service.h"
#include "chrome/browser/search/instant_service_factory.h"
#endif
namespace search {
namespace {
const char kServiceWorkerFileName[] = "newtab-serviceworker.js";
bool MatchesOrigin(const GURL& my_url, const GURL& other_url) {
return my_url.scheme_piece() == other_url.scheme_piece() &&
my_url.host_piece() == other_url.host_piece() &&
my_url.port() == other_url.port();
}
} // namespace
// Returns true if |my_url| matches |other_url| in terms of origin (i.e. host,
// port, and scheme) and path.
// Defined outside of the anonymous namespace so that it's accessible to unit
// tests.
bool MatchesOriginAndPath(const GURL& my_url, const GURL& other_url) {
return MatchesOrigin(my_url, other_url) &&
my_url.path_piece() == other_url.path_piece();
}
namespace {
// Status of the New Tab URL for the default Search provider. NOTE: Used in a
// UMA histogram so values should only be added at the end and not reordered.
enum NewTabURLState {
// Valid URL that should be used.
NEW_TAB_URL_VALID = 0,
// Corrupt state (e.g. no profile or template url).
NEW_TAB_URL_BAD = 1,
// URL should not be used because in incognito window.
NEW_TAB_URL_INCOGNITO = 2,
// No New Tab URL set for provider.
NEW_TAB_URL_NOT_SET = 3,
// URL is not secure.
NEW_TAB_URL_INSECURE = 4,
// URL should not be used because Suggest is disabled.
// Not used anymore, see crbug.com/340424.
// NEW_TAB_URL_SUGGEST_OFF = 5,
// URL should not be used because it is blocked for a supervised user.
NEW_TAB_URL_BLOCKED = 6,
NEW_TAB_URL_MAX
};
const TemplateURL* GetDefaultSearchProviderTemplateURL(Profile* profile) {
if (profile) {
TemplateURLService* template_url_service =
TemplateURLServiceFactory::GetForProfile(profile);
if (template_url_service)
return template_url_service->GetDefaultSearchProvider();
}
return nullptr;
}
bool IsMatchingServiceWorker(const GURL& my_url, const GURL& document_url) {
// The origin should match.
if (!MatchesOrigin(my_url, document_url))
return false;
// The url filename should be the new tab page ServiceWorker.
std::string my_filename = my_url.ExtractFileName();
if (my_filename != kServiceWorkerFileName)
return false;
// The paths up to the filenames should be the same.
std::string my_path_without_filename = my_url.path();
my_path_without_filename = my_path_without_filename.substr(
0, my_path_without_filename.length() - my_filename.length());
std::string document_filename = document_url.ExtractFileName();
std::string document_path_without_filename = document_url.path();
document_path_without_filename = document_path_without_filename.substr(
0, document_path_without_filename.length() - document_filename.length());
return my_path_without_filename == document_path_without_filename;
}
// Returns true if |url| matches the NTP URL or the URL of the NTP's associated
// service worker.
bool IsNTPOrServiceWorkerURL(const GURL& url, Profile* profile) {
if (!url.is_valid())
return false;
const GURL new_tab_url(GetNewTabPageURL(profile));
return new_tab_url.is_valid() && (MatchesOriginAndPath(url, new_tab_url) ||
IsMatchingServiceWorker(url, new_tab_url));
}
bool IsURLAllowedForSupervisedUser(const GURL& url, Profile* profile) {
#if BUILDFLAG(ENABLE_SUPERVISED_USERS)
// If this isn't a supervised user, skip the URL filter check, since it can be
// fairly expensive.
if (!profile->IsSupervised())
return true;
SupervisedUserService* supervised_user_service =
SupervisedUserServiceFactory::GetForProfile(profile);
SupervisedUserURLFilter* url_filter = supervised_user_service->GetURLFilter();
if (url_filter->GetFilteringBehaviorForURL(url) ==
SupervisedUserURLFilter::BLOCK) {
return false;
}
#endif
return true;
}
bool ShouldShowLocalNewTab(Profile* profile) {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
return command_line->HasSwitch(switches::kForceLocalNtp) ||
(base::FeatureList::IsEnabled(features::kUseGoogleLocalNtp) &&
profile && DefaultSearchProviderIsGoogle(profile));
}
bool ShouldDelayRemoteNTP(const GURL& search_provider_url, Profile* profile) {
#if defined(OS_CHROMEOS)
// On Chrome OS, if the session hasn't merged yet, we need to avoid loading
// the remote NTP because that will trigger showing the merge session throttle
// interstitial page, which can show for 5+ seconds. crbug.com/591530.
if (merge_session_throttling_utils::ShouldDelayUrl(search_provider_url) &&
merge_session_throttling_utils::IsSessionRestorePending(profile)) {
return true;
}
#endif // defined(OS_CHROMEOS)
return false;
}
// Used to look up the URL to use for the New Tab page. Also tracks how we
// arrived at that URL so it can be logged with UMA.
struct NewTabURLDetails {
NewTabURLDetails(const GURL& url, NewTabURLState state)
: url(url), state(state) {}
static NewTabURLDetails ForProfile(Profile* profile) {
// Incognito has its own New Tab.
if (profile->IsOffTheRecord())
return NewTabURLDetails(GURL(), NEW_TAB_URL_INCOGNITO);
const GURL local_url(chrome::kChromeSearchLocalNtpUrl);
if (ShouldShowLocalNewTab(profile))
return NewTabURLDetails(local_url, NEW_TAB_URL_VALID);
const TemplateURL* template_url =
GetDefaultSearchProviderTemplateURL(profile);
if (!profile || !template_url)
return NewTabURLDetails(local_url, NEW_TAB_URL_BAD);
GURL search_provider_url(template_url->new_tab_url_ref().ReplaceSearchTerms(
TemplateURLRef::SearchTermsArgs(base::string16()),
UIThreadSearchTermsData(profile)));
if (ShouldDelayRemoteNTP(search_provider_url, profile))
return NewTabURLDetails(local_url, NEW_TAB_URL_VALID);
if (!search_provider_url.is_valid())
return NewTabURLDetails(local_url, NEW_TAB_URL_NOT_SET);
if (!search_provider_url.SchemeIsCryptographic())
return NewTabURLDetails(local_url, NEW_TAB_URL_INSECURE);
if (!IsURLAllowedForSupervisedUser(search_provider_url, profile))
return NewTabURLDetails(local_url, NEW_TAB_URL_BLOCKED);
return NewTabURLDetails(search_provider_url, NEW_TAB_URL_VALID);
}
const GURL url;
const NewTabURLState state;
};
bool IsRenderedInInstantProcess(const content::WebContents* contents,
Profile* profile) {
#if defined(OS_ANDROID)
return false;
#else
const content::RenderProcessHost* process_host =
contents->GetMainFrame()->GetProcess();
if (!process_host)
return false;
const InstantService* instant_service =
InstantServiceFactory::GetForProfile(profile);
if (!instant_service)
return false;
return instant_service->IsInstantProcess(process_host->GetID());
#endif
}
} // namespace
bool DefaultSearchProviderIsGoogle(Profile* profile) {
return DefaultSearchProviderIsGoogle(
TemplateURLServiceFactory::GetForProfile(profile));
}
bool DefaultSearchProviderIsGoogle(
const TemplateURLService* template_url_service) {
if (!template_url_service)
return false;
const TemplateURL* default_provider =
template_url_service->GetDefaultSearchProvider();
if (!default_provider)
return false;
return default_provider->GetEngineType(
template_url_service->search_terms_data()) ==
SearchEngineType::SEARCH_ENGINE_GOOGLE;
}
bool IsNTPURL(const GURL& url, Profile* profile) {
if (!url.is_valid())
return false;
if (!IsInstantExtendedAPIEnabled())
return url == chrome::kChromeUINewTabURL;
// TODO(treib,sfiera): Tolerate query params when detecting local NTPs.
return profile && (IsNTPOrServiceWorkerURL(url, profile) ||
url == chrome::kChromeSearchLocalNtpUrl);
}
bool IsInstantNTP(const content::WebContents* contents) {
if (!contents)
return false;
if (contents->ShowingInterstitialPage())
return false;
const content::NavigationEntry* entry =
contents->GetController().GetLastCommittedEntry();
if (!entry)
entry = contents->GetController().GetVisibleEntry();
return NavEntryIsInstantNTP(contents, entry);
}
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;
return IsInstantNTPURL(entry->GetURL(), profile);
}
bool IsInstantNTPURL(const GURL& url, Profile* profile) {
if (!IsInstantExtendedAPIEnabled())
return false;
// TODO(treib,sfiera): Tolerate query params when detecting local NTPs.
if (url == chrome::kChromeSearchLocalNtpUrl)
return true;
GURL new_tab_url(GetNewTabPageURL(profile));
return new_tab_url.is_valid() && MatchesOriginAndPath(url, new_tab_url);
}
GURL GetNewTabPageURL(Profile* profile) {
return NewTabURLDetails::ForProfile(profile).url;
}
#if !defined(OS_ANDROID)
bool ShouldAssignURLToInstantRenderer(const GURL& url, Profile* profile) {
return url.is_valid() && profile && IsInstantExtendedAPIEnabled() &&
(url.SchemeIs(chrome::kChromeSearchScheme) ||
IsNTPOrServiceWorkerURL(url, profile));
}
bool ShouldUseProcessPerSiteForInstantURL(const GURL& url, Profile* profile) {
return ShouldAssignURLToInstantRenderer(url, profile) &&
(url.host_piece() == chrome::kChromeSearchLocalNtpHost ||
url.host_piece() == chrome::kChromeSearchRemoteNtpHost);
}
GURL GetEffectiveURLForInstant(const GURL& url, Profile* profile) {
CHECK(ShouldAssignURLToInstantRenderer(url, profile))
<< "Error granting Instant access.";
if (url.SchemeIs(chrome::kChromeSearchScheme))
return url;
// Replace the scheme with "chrome-search:", and clear the port, since
// chrome-search is a scheme without port.
url::Replacements<char> replacements;
std::string search_scheme(chrome::kChromeSearchScheme);
replacements.SetScheme(search_scheme.data(),
url::Component(0, search_scheme.length()));
replacements.ClearPort();
// If this is the URL for a server-provided NTP, replace the host with
// "remote-ntp".
std::string remote_ntp_host(chrome::kChromeSearchRemoteNtpHost);
NewTabURLDetails details = NewTabURLDetails::ForProfile(profile);
if (details.state == NEW_TAB_URL_VALID &&
(MatchesOriginAndPath(url, details.url) ||
IsMatchingServiceWorker(url, details.url))) {
replacements.SetHost(remote_ntp_host.c_str(),
url::Component(0, remote_ntp_host.length()));
}
return url.ReplaceComponents(replacements);
}
bool HandleNewTabURLRewrite(GURL* url,
content::BrowserContext* browser_context) {
if (!IsInstantExtendedAPIEnabled())
return false;
if (!url->SchemeIs(content::kChromeUIScheme) ||
url->host() != chrome::kChromeUINewTabHost)
return false;
Profile* profile = Profile::FromBrowserContext(browser_context);
NewTabURLDetails details(NewTabURLDetails::ForProfile(profile));
UMA_HISTOGRAM_ENUMERATION("NewTabPage.URLState",
details.state, NEW_TAB_URL_MAX);
if (details.url.is_valid()) {
*url = details.url;
return true;
}
return false;
}
bool HandleNewTabURLReverseRewrite(GURL* url,
content::BrowserContext* browser_context) {
if (!IsInstantExtendedAPIEnabled())
return false;
// Do nothing in incognito.
Profile* profile = Profile::FromBrowserContext(browser_context);
DCHECK(profile);
if (profile->IsOffTheRecord())
return false;
if (IsInstantNTPURL(*url, profile)) {
*url = GURL(chrome::kChromeUINewTabURL);
return true;
}
return false;
}
#endif // !defined(OS_ANDROID)
} // namespace search