blob: 0bd32009a588af504c3803c98a552a46358e7639 [file] [log] [blame]
// 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/instant/instant_controller.h"
#include "base/command_line.h"
#include "base/i18n/case_conversion.h"
#include "base/metrics/histogram.h"
#include "base/time.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/autocomplete/autocomplete_provider.h"
#include "chrome/browser/favicon/favicon_service_factory.h"
#include "chrome/browser/history/history.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/history/history_tab_helper.h"
#include "chrome/browser/instant/instant_controller_delegate.h"
#include "chrome/browser/instant/instant_loader.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/search_engines/template_url_service.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/search/search.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "content/public/browser/favicon_status.h"
#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#if defined(TOOLKIT_VIEWS)
#include "ui/views/widget/widget.h"
#endif
namespace {
enum PreviewUsageType {
PREVIEW_CREATED = 0,
PREVIEW_DELETED,
PREVIEW_LOADED,
PREVIEW_SHOWED,
PREVIEW_COMMITTED,
PREVIEW_NUM_TYPES,
};
// An artificial delay (in milliseconds) we introduce before telling the Instant
// page about the new omnibox bounds, in cases where the bounds shrink. This is
// to avoid the page jumping up/down very fast in response to bounds changes.
const int kUpdateBoundsDelayMS = 1000;
// The maximum number of times we'll load a non-Instant-supporting search engine
// before we give up and blacklist it for the rest of the browsing session.
const int kMaxInstantSupportFailures = 10;
// If an Instant page has not been used in these many milliseconds, it is
// reloaded so that the page does not become stale.
const int kStaleLoaderTimeoutMS = 3 * 3600 * 1000;
std::string ModeToString(InstantController::Mode mode) {
switch (mode) {
case InstantController::EXTENDED: return "_Extended";
case InstantController::INSTANT: return "_Instant";
case InstantController::SUGGEST: return "_Suggest";
case InstantController::HIDDEN: return "_Hidden";
case InstantController::SILENT: return "_Silent";
case InstantController::DISABLED: return "_Disabled";
}
NOTREACHED();
return std::string();
}
void AddPreviewUsageForHistogram(InstantController::Mode mode,
PreviewUsageType usage) {
DCHECK(0 <= usage && usage < PREVIEW_NUM_TYPES) << usage;
base::Histogram* histogram = base::LinearHistogram::FactoryGet(
"Instant.Previews" + ModeToString(mode), 1, PREVIEW_NUM_TYPES,
PREVIEW_NUM_TYPES + 1, base::Histogram::kUmaTargetedHistogramFlag);
histogram->Add(usage);
}
void AddSessionStorageHistogram(InstantController::Mode mode,
const TabContents* tab1,
const TabContents* tab2) {
base::Histogram* histogram = base::BooleanHistogram::FactoryGet(
"Instant.SessionStorageNamespace" + ModeToString(mode),
base::Histogram::kUmaTargetedHistogramFlag);
const content::SessionStorageNamespaceMap& session_storage_map1 =
tab1->web_contents()->GetController().GetSessionStorageNamespaceMap();
const content::SessionStorageNamespaceMap& session_storage_map2 =
tab2->web_contents()->GetController().GetSessionStorageNamespaceMap();
bool is_session_storage_the_same =
session_storage_map1.size() == session_storage_map2.size();
if (is_session_storage_the_same) {
// The size is the same, so let's check that all entries match.
for (content::SessionStorageNamespaceMap::const_iterator
it1 = session_storage_map1.begin(),
it2 = session_storage_map2.begin();
it1 != session_storage_map1.end() &&
it2 != session_storage_map2.end();
++it1, ++it2) {
if (it1->first != it2->first || it1->second != it2->second) {
is_session_storage_the_same = false;
break;
}
}
}
histogram->AddBoolean(is_session_storage_the_same);
}
InstantController::Mode GetModeForProfile(Profile* profile) {
if (!profile || profile->IsOffTheRecord() || !profile->GetPrefs() ||
!profile->GetPrefs()->GetBoolean(prefs::kInstantEnabled))
return InstantController::DISABLED;
return chrome::search::IsInstantExtendedAPIEnabled(profile) ?
InstantController::EXTENDED : InstantController::INSTANT;
}
} // namespace
InstantController::~InstantController() {
if (GetPreviewContents())
AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED);
}
// static
InstantController* InstantController::CreateInstant(
Profile* profile,
InstantControllerDelegate* delegate) {
const Mode mode = GetModeForProfile(profile);
return mode == DISABLED ? NULL : new InstantController(delegate, mode);
}
// static
bool InstantController::IsExtendedAPIEnabled(Profile* profile) {
return GetModeForProfile(profile) == EXTENDED;
}
// static
bool InstantController::IsInstantEnabled(Profile* profile) {
const Mode mode = GetModeForProfile(profile);
return mode == EXTENDED || mode == INSTANT;
}
// static
bool InstantController::IsSuggestEnabled(Profile* profile) {
const Mode mode = GetModeForProfile(profile);
return mode == EXTENDED || mode == INSTANT || mode == SUGGEST;
}
// static
void InstantController::RegisterUserPrefs(PrefService* prefs) {
prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, false,
PrefService::SYNCABLE_PREF);
prefs->RegisterBooleanPref(prefs::kInstantEnabled, false,
PrefService::SYNCABLE_PREF);
// TODO(jamescook): Move this to search controller.
prefs->RegisterDoublePref(prefs::kInstantAnimationScaleFactor, 1.0,
PrefService::UNSYNCABLE_PREF);
}
bool InstantController::Update(const AutocompleteMatch& match,
const string16& user_text,
const string16& full_text,
bool verbatim) {
const TabContents* active_tab = delegate_->GetActiveTabContents();
// We could get here with no active tab if the Browser is closing.
if (!active_tab) {
Hide();
return false;
}
std::string instant_url;
Profile* profile = active_tab->profile();
// If the match's TemplateURL is valid, it's a search query; use it. If it's
// not valid, it's likely a URL; in EXTENDED mode, try using the default
// search engine's TemplateURL instead.
const GURL& tab_url = active_tab->web_contents()->GetURL();
if (GetInstantURL(match.GetTemplateURL(profile), tab_url, &instant_url)) {
ResetLoader(instant_url, active_tab);
} else if (mode_ != EXTENDED || !CreateDefaultLoader()) {
Hide();
return false;
}
if (full_text.empty()) {
Hide();
return false;
}
// Track the non-Instant search URL for this query.
url_for_history_ = match.destination_url;
last_transition_type_ = match.transition;
last_active_tab_ = active_tab;
last_match_was_search_ = AutocompleteMatch::IsSearchType(match.type);
// In EXTENDED mode, we send only |user_text| as the query text. In all other
// modes, we use the entire |full_text|.
const string16& query_text = mode_ == EXTENDED ? user_text : full_text;
string16 last_query_text = mode_ == EXTENDED ?
last_user_text_ : last_full_text_;
last_user_text_ = user_text;
last_full_text_ = full_text;
// Don't send an update to the loader if the query text hasn't changed.
if (query_text == last_query_text && verbatim == last_verbatim_) {
// Reuse the last suggestion, as it's still valid.
delegate_->SetSuggestedText(last_suggestion_.text,
last_suggestion_.behavior);
// We need to call Show() here because of this:
// 1. User has typed a query (say Q). Instant overlay is showing results.
// 2. User arrows-down to a URL entry or erases all omnibox text. Both of
// these cause the overlay to Hide().
// 3. User arrows-up to Q or types Q again. The last text we processed is
// still Q, so we don't Update() the loader, but we do need to Show().
if (loader_processed_last_update_ &&
(mode_ == INSTANT || mode_ == EXTENDED))
Show(100, INSTANT_SIZE_PERCENT);
return true;
}
last_verbatim_ = verbatim;
loader_processed_last_update_ = false;
last_suggestion_ = InstantSuggestion();
if (mode_ != SILENT) {
loader_->Update(query_text, verbatim);
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED,
content::Source<InstantController>(this),
content::NotificationService::NoDetails());
}
// We don't have suggestions yet, but need to reset any existing "gray text".
delegate_->SetSuggestedText(string16(), INSTANT_COMPLETE_NOW);
// Though we may have handled a URL match above, we return false here, so that
// omnibox prerendering can kick in. TODO(sreeram): Remove this (and always
// return true) once we are able to commit URLs as well.
return last_match_was_search_;
}
// TODO(tonyg): This method only fires when the omnibox bounds change. It also
// needs to fire when the preview bounds change (e.g.: open/close info bar).
void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) {
if (omnibox_bounds_ == bounds || (mode_ != INSTANT && mode_ != EXTENDED))
return;
omnibox_bounds_ = bounds;
if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) {
update_bounds_timer_.Stop();
SendBoundsToPage();
} else if (!update_bounds_timer_.IsRunning()) {
update_bounds_timer_.Start(FROM_HERE,
base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), this,
&InstantController::SendBoundsToPage);
}
}
void InstantController::HandleAutocompleteResults(
const std::vector<AutocompleteProvider*>& providers) {
if (mode_ != EXTENDED || !GetPreviewContents())
return;
std::vector<InstantAutocompleteResult> results;
for (ACProviders::const_iterator provider = providers.begin();
provider != providers.end(); ++provider) {
for (ACMatches::const_iterator match = (*provider)->matches().begin();
match != (*provider)->matches().end(); ++match) {
InstantAutocompleteResult result;
result.provider = UTF8ToUTF16((*provider)->GetName());
result.is_search = AutocompleteMatch::IsSearchType(match->type);
result.contents = match->description;
result.destination_url = match->destination_url;
result.relevance = match->relevance;
results.push_back(result);
}
}
loader_->SendAutocompleteResults(results);
}
bool InstantController::OnUpOrDownKeyPressed(int count) {
if (mode_ != EXTENDED || !GetPreviewContents())
return false;
loader_->OnUpOrDownKeyPressed(count);
return true;
}
TabContents* InstantController::GetPreviewContents() const {
return loader_.get() ? loader_->preview_contents() : NULL;
}
void InstantController::Hide() {
last_active_tab_ = NULL;
if (is_showing_) {
is_showing_ = false;
delegate_->HideInstant();
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_INSTANT_CONTROLLER_HIDDEN,
content::Source<InstantController>(this),
content::NotificationService::NoDetails());
}
if (GetPreviewContents() && !last_full_text_.empty()) {
// Send a blank query to ask the preview to clear out old results.
last_full_text_.clear();
last_user_text_.clear();
loader_->Update(last_full_text_, true);
}
}
bool InstantController::IsCurrent() const {
DCHECK(IsOutOfDate() || GetPreviewContents());
return !IsOutOfDate() && GetPreviewContents() &&
loader_->supports_instant() && last_match_was_search_;
}
TabContents* InstantController::CommitCurrentPreview(InstantCommitType type) {
const TabContents* active_tab = delegate_->GetActiveTabContents();
TabContents* preview = ReleasePreviewContents(type);
AddSessionStorageHistogram(mode_, active_tab, preview);
preview->web_contents()->GetController().CopyStateFromAndPrune(
&active_tab->web_contents()->GetController());
delegate_->CommitInstant(preview);
// Try to create another loader immediately so that it is ready for the next
// user interaction.
CreateDefaultLoader();
return preview;
}
TabContents* InstantController::ReleasePreviewContents(InstantCommitType type) {
TabContents* preview = loader_->ReleasePreviewContents(type, last_full_text_);
// If the preview page has navigated since the last Update(), we need to add
// the navigation to history ourselves. Else, the page will navigate after
// commit, and it will be added to history in the usual manner.
const history::HistoryAddPageArgs& last_navigation =
loader_->last_navigation();
if (!last_navigation.url.is_empty()) {
content::NavigationEntry* entry =
preview->web_contents()->GetController().GetActiveEntry();
DCHECK_EQ(last_navigation.url, entry->GetURL());
// Add the page to history.
preview->history_tab_helper()->UpdateHistoryForNavigation(last_navigation);
// Update the page title.
preview->history_tab_helper()->UpdateHistoryPageTitle(*entry);
// Update the favicon.
FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
preview->profile(), Profile::EXPLICIT_ACCESS);
if (favicon_service && entry->GetFavicon().valid &&
entry->GetFavicon().image.IsEmpty()) {
favicon_service->SetFavicons(entry->GetURL(), entry->GetFavicon().url,
history::FAVICON, entry->GetFavicon().image);
}
}
// Add a fake history entry with a non-Instant search URL, so that search
// terms extraction (for autocomplete history matches) works.
HistoryService* history = HistoryServiceFactory::GetForProfile(
preview->profile(), Profile::EXPLICIT_ACCESS);
if (history) {
history->AddPage(url_for_history_, base::Time::Now(), NULL, 0, GURL(),
history::RedirectList(), last_transition_type_,
history::SOURCE_BROWSED, false);
}
AddPreviewUsageForHistogram(mode_, PREVIEW_COMMITTED);
// We may have gotten here from CommitInstant(), which means the loader may
// still be on the stack. So, schedule a destruction for later.
MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release());
// This call is here to reset view state. It won't actually delete |loader_|
// because it was just released to DeleteSoon().
DeleteLoader();
return preview;
}
void InstantController::OnAutocompleteLostFocus(
gfx::NativeView view_gaining_focus) {
DCHECK(!is_showing_ || GetPreviewContents());
// If there is no preview, nothing to do.
if (!GetPreviewContents())
return;
// If the preview is not showing, only need to check for loader staleness.
if (!is_showing_) {
MaybeOnStaleLoader();
return;
}
#if defined(OS_MACOSX)
if (!loader_->IsPointerDownFromActivate()) {
Hide();
MaybeOnStaleLoader();
}
#else
content::RenderWidgetHostView* rwhv =
GetPreviewContents()->web_contents()->GetRenderWidgetHostView();
if (!view_gaining_focus || !rwhv) {
Hide();
MaybeOnStaleLoader();
return;
}
#if defined(TOOLKIT_VIEWS)
// For views the top level widget is always focused. If the focus change
// originated in views determine the child Widget from the view that is being
// focused.
views::Widget* widget =
views::Widget::GetWidgetForNativeView(view_gaining_focus);
if (widget) {
views::FocusManager* focus_manager = widget->GetFocusManager();
if (focus_manager && focus_manager->is_changing_focus() &&
focus_manager->GetFocusedView() &&
focus_manager->GetFocusedView()->GetWidget()) {
view_gaining_focus =
focus_manager->GetFocusedView()->GetWidget()->GetNativeView();
}
}
#endif
gfx::NativeView tab_view =
GetPreviewContents()->web_contents()->GetNativeView();
// Focus is going to the renderer.
if (rwhv->GetNativeView() == view_gaining_focus ||
tab_view == view_gaining_focus) {
// If the mouse is not down, focus is not going to the renderer. Someone
// else moved focus and we shouldn't commit.
if (!loader_->IsPointerDownFromActivate()) {
Hide();
MaybeOnStaleLoader();
}
return;
}
// Walk up the view hierarchy. If the view gaining focus is a subview of the
// WebContents view (such as a windowed plugin or http auth dialog), we want
// to keep the preview contents. Otherwise, focus has gone somewhere else,
// such as the JS inspector, and we want to cancel the preview.
gfx::NativeView view_gaining_focus_ancestor = view_gaining_focus;
while (view_gaining_focus_ancestor &&
view_gaining_focus_ancestor != tab_view) {
view_gaining_focus_ancestor =
platform_util::GetParent(view_gaining_focus_ancestor);
}
if (view_gaining_focus_ancestor) {
CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST);
return;
}
Hide();
MaybeOnStaleLoader();
#endif
}
void InstantController::OnAutocompleteGotFocus() {
CreateDefaultLoader();
}
bool InstantController::commit_on_pointer_release() const {
return GetPreviewContents() && loader_->IsPointerDownFromActivate();
}
void InstantController::SetSuggestions(
InstantLoader* loader,
const std::vector<InstantSuggestion>& suggestions) {
DCHECK_EQ(loader_.get(), loader);
if (loader_ != loader || IsOutOfDate() || mode_ == SILENT || mode_ == HIDDEN)
return;
loader_processed_last_update_ = true;
InstantSuggestion suggestion;
if (!suggestions.empty())
suggestion = suggestions[0];
if (suggestion.behavior == INSTANT_COMPLETE_REPLACE) {
// We don't get an Update() when changing the omnibox due to a REPLACE
// suggestion (so that we don't inadvertently cause the preview to change
// what it's showing, as the user arrows up/down through the page-provided
// suggestions). So, update these state variables here.
last_full_text_ = suggestion.text;
last_user_text_.clear();
last_verbatim_ = true;
last_suggestion_ = InstantSuggestion();
last_match_was_search_ = suggestion.type == INSTANT_SUGGESTION_SEARCH;
delegate_->SetSuggestedText(suggestion.text, suggestion.behavior);
} else {
// Match case-sensitively first, to preserve suggestion case if possible.
// If that fails, match case-insensitively. http://crbug.com/150728
if (last_user_text_.size() < suggestion.text.size() &&
!suggestion.text.compare(0, last_user_text_.size(), last_user_text_)) {
suggestion.text.erase(0, last_user_text_.size());
} else {
string16 suggestion_lower = base::i18n::ToLower(suggestion.text);
string16 user_text_lower = base::i18n::ToLower(last_user_text_);
if (user_text_lower.size() < suggestion_lower.size() &&
!suggestion_lower.compare(0, user_text_lower.size(),
user_text_lower)) {
suggestion.text.assign(suggestion_lower, user_text_lower.size(),
suggestion_lower.size() - user_text_lower.size());
} else {
suggestion.text.clear();
}
}
last_suggestion_ = suggestion;
if (!last_verbatim_)
delegate_->SetSuggestedText(suggestion.text, suggestion.behavior);
}
if (mode_ != SUGGEST)
Show(100, INSTANT_SIZE_PERCENT);
}
void InstantController::CommitInstantLoader(InstantLoader* loader) {
DCHECK_EQ(loader_.get(), loader);
DCHECK(is_showing_ && !IsOutOfDate()) << is_showing_;
if (loader_ != loader || !is_showing_ || IsOutOfDate())
return;
CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST);
}
void InstantController::SetInstantPreviewHeight(InstantLoader* loader,
int height,
InstantSizeUnits units) {
DCHECK_EQ(loader_.get(), loader);
if (loader_ != loader || mode_ != EXTENDED)
return;
Show(height, units);
}
void InstantController::InstantLoaderPreviewLoaded(InstantLoader* loader) {
DCHECK_EQ(loader_.get(), loader);
AddPreviewUsageForHistogram(mode_, PREVIEW_LOADED);
}
void InstantController::InstantSupportDetermined(InstantLoader* loader,
bool supports_instant) {
DCHECK_EQ(loader_.get(), loader);
if (supports_instant) {
blacklisted_urls_.erase(loader->instant_url());
} else {
++blacklisted_urls_[loader->instant_url()];
if (loader_ == loader) {
if (GetPreviewContents())
AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED);
// Because of the state of the stack, we can't destroy the loader now.
MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release());
DeleteLoader();
}
}
content::Details<const bool> details(&supports_instant);
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED,
content::NotificationService::AllSources(),
details);
}
void InstantController::SwappedTabContents(InstantLoader* loader) {
DCHECK_EQ(loader_.get(), loader);
if (loader_ == loader && is_showing_)
delegate_->ShowInstant(100, INSTANT_SIZE_PERCENT);
}
void InstantController::InstantLoaderContentsFocused(InstantLoader* loader) {
DCHECK_EQ(loader_.get(), loader);
DCHECK(is_showing_ && !IsOutOfDate()) << is_showing_;
#if defined(USE_AURA)
// On aura the omnibox only receives a focus lost if we initiate the focus
// change. This does that.
if (is_showing_ && !IsOutOfDate())
delegate_->InstantPreviewFocused();
#endif
}
InstantController::InstantController(InstantControllerDelegate* delegate,
Mode mode)
: delegate_(delegate),
mode_(mode),
last_active_tab_(NULL),
last_verbatim_(false),
last_transition_type_(content::PAGE_TRANSITION_LINK),
last_match_was_search_(false),
is_showing_(false),
loader_processed_last_update_(false) {
}
void InstantController::ResetLoader(const std::string& instant_url,
const TabContents* active_tab) {
if (GetPreviewContents() && loader_->instant_url() != instant_url)
DeleteLoader();
if (!GetPreviewContents()) {
DCHECK(!loader_.get());
loader_.reset(new InstantLoader(this, instant_url, active_tab));
loader_->Init();
AddPreviewUsageForHistogram(mode_, PREVIEW_CREATED);
// Reset the loader timer.
stale_loader_timer_.Stop();
stale_loader_timer_.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kStaleLoaderTimeoutMS), this,
&InstantController::OnStaleLoader);
}
}
bool InstantController::CreateDefaultLoader() {
const TabContents* active_tab = delegate_->GetActiveTabContents();
// We could get here with no active tab if the Browser is closing.
if (!active_tab)
return false;
const TemplateURL* template_url =
TemplateURLServiceFactory::GetForProfile(active_tab->profile())->
GetDefaultSearchProvider();
const GURL& tab_url = active_tab->web_contents()->GetURL();
std::string instant_url;
if (!GetInstantURL(template_url, tab_url, &instant_url))
return false;
ResetLoader(instant_url, active_tab);
return true;
}
void InstantController::OnStaleLoader() {
// If the loader is showing, do not delete it. It will get deleted the next
// time the autocomplete loses focus.
if (is_showing_)
return;
DeleteLoader();
CreateDefaultLoader();
}
void InstantController::MaybeOnStaleLoader() {
if (!stale_loader_timer_.IsRunning())
OnStaleLoader();
}
void InstantController::DeleteLoader() {
last_active_tab_ = NULL;
last_full_text_.clear();
last_user_text_.clear();
last_verbatim_ = false;
last_suggestion_ = InstantSuggestion();
last_transition_type_ = content::PAGE_TRANSITION_LINK;
last_match_was_search_ = false;
is_showing_ = false;
loader_processed_last_update_ = false;
last_omnibox_bounds_ = gfx::Rect();
url_for_history_ = GURL();
if (GetPreviewContents())
AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED);
loader_.reset();
}
void InstantController::Show(int height, InstantSizeUnits units) {
// Call even if showing in case height changed.
delegate_->ShowInstant(height, units);
if (!is_showing_) {
is_showing_ = true;
AddPreviewUsageForHistogram(mode_, PREVIEW_SHOWED);
content::NotificationService::current()->Notify(
chrome::NOTIFICATION_INSTANT_CONTROLLER_SHOWN,
content::Source<InstantController>(this),
content::NotificationService::NoDetails());
}
}
void InstantController::SendBoundsToPage() {
if (last_omnibox_bounds_ == omnibox_bounds_ || IsOutOfDate() ||
!GetPreviewContents() || loader_->IsPointerDownFromActivate())
return;
last_omnibox_bounds_ = omnibox_bounds_;
gfx::Rect preview_bounds = delegate_->GetInstantBounds();
gfx::Rect intersection = omnibox_bounds_.Intersect(preview_bounds);
// Translate into window coordinates.
if (!intersection.IsEmpty()) {
intersection.Offset(-preview_bounds.origin().x(),
-preview_bounds.origin().y());
}
// In the current Chrome UI, these must always be true so they sanity check
// the above operations. In a future UI, these may be removed or adjusted.
// There is no point in sanity-checking |intersection.y()| because the omnibox
// can be placed anywhere vertically relative to the preview (for example, in
// Mac fullscreen mode, the omnibox is fully enclosed by the preview bounds).
DCHECK_LE(0, intersection.x());
DCHECK_LE(0, intersection.width());
DCHECK_LE(0, intersection.height());
loader_->SetOmniboxBounds(intersection);
}
bool InstantController::GetInstantURL(const TemplateURL* template_url,
const GURL& tab_url,
std::string* instant_url) const {
CommandLine* command_line = CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kInstantURL)) {
*instant_url = command_line->GetSwitchValueASCII(switches::kInstantURL);
MaybeSetRefFromURL(tab_url, instant_url);
return template_url != NULL;
}
if (!template_url)
return false;
const TemplateURLRef& instant_url_ref = template_url->instant_url_ref();
if (!instant_url_ref.IsValid())
return false;
// Even if the URL template doesn't have search terms, it may have other
// components (such as {google:baseURL}) that need to be replaced.
*instant_url = instant_url_ref.ReplaceSearchTerms(
TemplateURLRef::SearchTermsArgs(string16()));
// Extended mode should always use HTTPS. TODO(sreeram): This section can be
// removed if TemplateURLs supported "https://{google:host}/..." instead of
// only supporting "{google:baseURL}...".
if (mode_ == EXTENDED) {
GURL url_obj(*instant_url);
if (!url_obj.is_valid())
return false;
if (!url_obj.SchemeIsSecure()) {
const std::string new_scheme = "https";
const std::string new_port = "443";
GURL::Replacements secure;
secure.SetSchemeStr(new_scheme);
secure.SetPortStr(new_port);
url_obj = url_obj.ReplaceComponents(secure);
if (!url_obj.is_valid())
return false;
*instant_url = url_obj.spec();
}
}
std::map<std::string, int>::const_iterator iter =
blacklisted_urls_.find(*instant_url);
if (iter != blacklisted_urls_.end() &&
iter->second > kMaxInstantSupportFailures)
return false;
MaybeSetRefFromURL(tab_url, instant_url);
return true;
}
void InstantController::MaybeSetRefFromURL(const GURL& tab_url,
std::string* instant_url) const {
if (mode_ == EXTENDED) {
GURL url_obj(*instant_url);
if (!url_obj.is_valid())
return;
// Copy hash state so that search modes persist for query refinements.
if (tab_url.has_ref() &&
tab_url.host() == url_obj.host() &&
tab_url.path() == url_obj.path()) {
const std::string new_ref = tab_url.ref();
GURL::Replacements hash;
hash.SetRefStr(new_ref);
url_obj = url_obj.ReplaceComponents(hash);
DCHECK(url_obj.is_valid());
*instant_url = url_obj.spec();
}
}
}
bool InstantController::IsOutOfDate() const {
return !last_active_tab_ ||
last_active_tab_ != delegate_->GetActiveTabContents();
}