blob: 7d936055a590354bd503b6eb64c010fda3be09f5 [file] [log] [blame]
[email protected]f0dc75232012-01-05 01:07:001// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]fd2b9ce2010-08-11 04:03:572// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]6b723f82010-10-05 20:14:275#include "chrome/browser/instant/instant_controller.h"
[email protected]fd2b9ce2010-08-11 04:03:576
[email protected]fd2b9ce2010-08-11 04:03:577#include "base/command_line.h"
[email protected]c55e3b82012-08-09 15:27:058#include "base/i18n/case_conversion.h"
[email protected]7e03e812010-11-15 23:01:019#include "base/metrics/histogram.h"
[email protected]941b260c2012-08-23 23:00:4710#include "base/utf_string_conversions.h"
11#include "chrome/browser/autocomplete/autocomplete_provider.h"
[email protected]f3d2b312012-08-23 22:27:5912#include "chrome/browser/favicon/favicon_service_factory.h"
[email protected]c55e3b82012-08-09 15:27:0513#include "chrome/browser/history/history.h"
14#include "chrome/browser/history/history_service_factory.h"
15#include "chrome/browser/history/history_tab_helper.h"
[email protected]37c917b2012-06-28 15:53:5916#include "chrome/browser/instant/instant_controller_delegate.h"
[email protected]6b723f82010-10-05 20:14:2717#include "chrome/browser/instant/instant_loader.h"
[email protected]ba6680f2010-11-01 20:35:0818#include "chrome/browser/platform_util.h"
[email protected]018cbb22010-10-11 22:32:0919#include "chrome/browser/prefs/pref_service.h"
[email protected]8e5c89a2011-06-07 18:13:3320#include "chrome/browser/search_engines/template_url_service.h"
21#include "chrome/browser/search_engines/template_url_service_factory.h"
[email protected]b67d0a42012-09-04 20:57:3522#include "chrome/browser/ui/search/search.h"
[email protected]a7eef8f2012-06-08 22:19:3923#include "chrome/browser/ui/tab_contents/tab_contents.h"
[email protected]432115822011-07-10 15:52:2724#include "chrome/common/chrome_notification_types.h"
[email protected]fd2b9ce2010-08-11 04:03:5725#include "chrome/common/chrome_switches.h"
[email protected]018cbb22010-10-11 22:32:0926#include "chrome/common/pref_names.h"
[email protected]c55e3b82012-08-09 15:27:0527#include "content/public/browser/favicon_status.h"
28#include "content/public/browser/navigation_entry.h"
[email protected]ad50def52011-10-19 23:17:0729#include "content/public/browser/notification_service.h"
[email protected]5626b0892012-02-20 14:46:5830#include "content/public/browser/render_widget_host_view.h"
[email protected]ef9572e2012-01-04 22:14:1231#include "content/public/browser/web_contents.h"
[email protected]c55e3b82012-08-09 15:27:0532#include "ui/gfx/codec/png_codec.h"
[email protected]fd2b9ce2010-08-11 04:03:5733
[email protected]6eb8ea92011-08-22 21:01:4134#if defined(TOOLKIT_VIEWS)
[email protected]c13be0d2011-11-22 02:09:5835#include "ui/views/widget/widget.h"
[email protected]6eb8ea92011-08-22 21:01:4136#endif
37
[email protected]c55e3b82012-08-09 15:27:0538namespace {
39
40enum PreviewUsageType {
41 PREVIEW_CREATED = 0,
42 PREVIEW_DELETED,
43 PREVIEW_LOADED,
44 PREVIEW_SHOWED,
45 PREVIEW_COMMITTED,
46 PREVIEW_NUM_TYPES,
47};
48
49// An artificial delay (in milliseconds) we introduce before telling the Instant
50// page about the new omnibox bounds, in cases where the bounds shrink. This is
51// to avoid the page jumping up/down very fast in response to bounds changes.
52const int kUpdateBoundsDelayMS = 1000;
53
54// The maximum number of times we'll load a non-Instant-supporting search engine
55// before we give up and blacklist it for the rest of the browsing session.
56const int kMaxInstantSupportFailures = 10;
57
[email protected]08a9f972012-08-27 23:23:1558// If an Instant page has not been used in these many milliseconds, it is
59// reloaded so that the page does not become stale.
60const int kStaleLoaderTimeoutMS = 3 * 3600 * 1000;
61
[email protected]c55e3b82012-08-09 15:27:0562std::string ModeToString(InstantController::Mode mode) {
63 switch (mode) {
[email protected]b67d0a42012-09-04 20:57:3564 case InstantController::EXTENDED: return "_Extended";
[email protected]c55e3b82012-08-09 15:27:0565 case InstantController::INSTANT: return "_Instant";
66 case InstantController::SUGGEST: return "_Suggest";
67 case InstantController::HIDDEN: return "_Hidden";
68 case InstantController::SILENT: return "_Silent";
[email protected]b67d0a42012-09-04 20:57:3569 case InstantController::DISABLED: return "_Disabled";
[email protected]c55e3b82012-08-09 15:27:0570 }
71
72 NOTREACHED();
73 return std::string();
74}
75
76void AddPreviewUsageForHistogram(InstantController::Mode mode,
77 PreviewUsageType usage) {
78 DCHECK(0 <= usage && usage < PREVIEW_NUM_TYPES) << usage;
79 base::Histogram* histogram = base::LinearHistogram::FactoryGet(
80 "Instant.Previews" + ModeToString(mode), 1, PREVIEW_NUM_TYPES,
81 PREVIEW_NUM_TYPES + 1, base::Histogram::kUmaTargetedHistogramFlag);
82 histogram->Add(usage);
83}
84
85void AddSessionStorageHistogram(InstantController::Mode mode,
86 const TabContents* tab1,
87 const TabContents* tab2) {
88 base::Histogram* histogram = base::BooleanHistogram::FactoryGet(
89 "Instant.SessionStorageNamespace" + ModeToString(mode),
90 base::Histogram::kUmaTargetedHistogramFlag);
[email protected]d1198fd2012-08-13 22:50:1991 const content::SessionStorageNamespaceMap& session_storage_map1 =
92 tab1->web_contents()->GetController().GetSessionStorageNamespaceMap();
93 const content::SessionStorageNamespaceMap& session_storage_map2 =
94 tab2->web_contents()->GetController().GetSessionStorageNamespaceMap();
95 bool is_session_storage_the_same =
96 session_storage_map1.size() == session_storage_map2.size();
97 if (is_session_storage_the_same) {
98 // The size is the same, so let's check that all entries match.
99 for (content::SessionStorageNamespaceMap::const_iterator
100 it1 = session_storage_map1.begin(),
101 it2 = session_storage_map2.begin();
102 it1 != session_storage_map1.end() &&
103 it2 != session_storage_map2.end();
104 ++it1, ++it2) {
105 if (it1->first != it2->first || it1->second != it2->second) {
106 is_session_storage_the_same = false;
107 break;
108 }
109 }
110 }
111 histogram->AddBoolean(is_session_storage_the_same);
[email protected]c55e3b82012-08-09 15:27:05112}
113
[email protected]b67d0a42012-09-04 20:57:35114InstantController::Mode GetModeForProfile(Profile* profile) {
115 if (!profile || profile->IsOffTheRecord() || !profile->GetPrefs() ||
116 !profile->GetPrefs()->GetBoolean(prefs::kInstantEnabled))
117 return InstantController::DISABLED;
[email protected]c55e3b82012-08-09 15:27:05118
[email protected]b67d0a42012-09-04 20:57:35119 return chrome::search::IsInstantExtendedAPIEnabled(profile) ?
120 InstantController::EXTENDED : InstantController::INSTANT;
[email protected]03bb953d2010-09-14 21:38:30121}
122
[email protected]b67d0a42012-09-04 20:57:35123} // namespace
124
[email protected]6b723f82010-10-05 20:14:27125InstantController::~InstantController() {
[email protected]c55e3b82012-08-09 15:27:05126 if (GetPreviewContents())
127 AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED);
[email protected]03bb953d2010-09-14 21:38:30128}
129
[email protected]7e03e812010-11-15 23:01:01130// static
[email protected]b67d0a42012-09-04 20:57:35131InstantController* InstantController::CreateInstant(
132 Profile* profile,
133 InstantControllerDelegate* delegate) {
134 const Mode mode = GetModeForProfile(profile);
135 return mode == DISABLED ? NULL : new InstantController(delegate, mode);
136}
137
138// static
139bool InstantController::IsExtendedAPIEnabled(Profile* profile) {
140 return GetModeForProfile(profile) == EXTENDED;
141}
142
143// static
144bool InstantController::IsInstantEnabled(Profile* profile) {
145 const Mode mode = GetModeForProfile(profile);
146 return mode == EXTENDED || mode == INSTANT;
147}
148
149// static
150bool InstantController::IsSuggestEnabled(Profile* profile) {
151 const Mode mode = GetModeForProfile(profile);
152 return mode == EXTENDED || mode == INSTANT || mode == SUGGEST;
153}
154
155// static
[email protected]7e03e812010-11-15 23:01:01156void InstantController::RegisterUserPrefs(PrefService* prefs) {
[email protected]c55e3b82012-08-09 15:27:05157 prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, false,
[email protected]18c2ffac2011-09-16 21:07:29158 PrefService::SYNCABLE_PREF);
[email protected]c55e3b82012-08-09 15:27:05159 prefs->RegisterBooleanPref(prefs::kInstantEnabled, false,
[email protected]18c2ffac2011-09-16 21:07:29160 PrefService::SYNCABLE_PREF);
[email protected]7ab402e62012-06-20 19:49:18161
162 // TODO(jamescook): Move this to search controller.
[email protected]b67d0a42012-09-04 20:57:35163 prefs->RegisterDoublePref(prefs::kInstantAnimationScaleFactor, 1.0,
[email protected]7ab402e62012-06-20 19:49:18164 PrefService::UNSYNCABLE_PREF);
[email protected]7e03e812010-11-15 23:01:01165}
166
[email protected]05cf2fa2012-05-29 20:36:06167bool InstantController::Update(const AutocompleteMatch& match,
[email protected]6b723f82010-10-05 20:14:27168 const string16& user_text,
[email protected]cd223322012-08-26 19:54:42169 const string16& full_text,
170 bool verbatim) {
[email protected]c55e3b82012-08-09 15:27:05171 const TabContents* active_tab = delegate_->GetActiveTabContents();
[email protected]bdf1d862010-11-24 02:46:11172
[email protected]c55e3b82012-08-09 15:27:05173 // We could get here with no active tab if the Browser is closing.
174 if (!active_tab) {
175 Hide();
176 return false;
177 }
178
179 std::string instant_url;
180 Profile* profile = active_tab->profile();
181
182 // If the match's TemplateURL isn't valid, it is likely not a query.
183 if (!GetInstantURL(match.GetTemplateURL(profile), &instant_url)) {
184 Hide();
185 return false;
186 }
187
[email protected]c55e3b82012-08-09 15:27:05188 if (full_text.empty()) {
189 Hide();
190 return false;
191 }
192
[email protected]c55e3b82012-08-09 15:27:05193 ResetLoader(instant_url, active_tab);
194 last_active_tab_ = active_tab;
195
196 // Track the non-Instant search URL for this query.
197 url_for_history_ = match.destination_url;
[email protected]97b6c4f2010-09-27 19:31:26198 last_transition_type_ = match.transition;
[email protected]c55e3b82012-08-09 15:27:05199
[email protected]cd223322012-08-26 19:54:42200 // In EXTENDED mode, we send only |user_text| as the query text. In all other
201 // modes, we use the entire |full_text|.
202 const string16& query_text = mode_ == EXTENDED ? user_text : full_text;
[email protected]b67d0a42012-09-04 20:57:35203 string16 last_query_text = mode_ == EXTENDED ?
204 last_user_text_ : last_full_text_;
[email protected]64f09b82011-10-13 16:17:20205 last_user_text_ = user_text;
[email protected]cd223322012-08-26 19:54:42206 last_full_text_ = full_text;
[email protected]64f09b82011-10-13 16:17:20207
[email protected]c55e3b82012-08-09 15:27:05208 // Don't send an update to the loader if the query text hasn't changed.
[email protected]cd223322012-08-26 19:54:42209 if (query_text == last_query_text && verbatim == last_verbatim_) {
210 // Reuse the last suggestion, as it's still valid.
211 delegate_->SetSuggestedText(last_suggestion_.text,
212 last_suggestion_.behavior);
[email protected]fd2b9ce2010-08-11 04:03:57213
[email protected]c55e3b82012-08-09 15:27:05214 // We need to call Show() here because of this:
215 // 1. User has typed a query (say Q). Instant overlay is showing results.
216 // 2. User arrows-down to a URL entry or erases all omnibox text. Both of
217 // these cause the overlay to Hide().
218 // 3. User arrows-up to Q or types Q again. The last text we processed is
219 // still Q, so we don't Update() the loader, but we do need to Show().
[email protected]941b260c2012-08-23 23:00:47220 if (loader_processed_last_update_ &&
[email protected]b67d0a42012-09-04 20:57:35221 (mode_ == INSTANT || mode_ == EXTENDED))
[email protected]c55e3b82012-08-09 15:27:05222 Show();
[email protected]64f09b82011-10-13 16:17:20223 return true;
224 }
[email protected]36d5e5592010-11-15 20:45:59225
[email protected]c55e3b82012-08-09 15:27:05226 last_verbatim_ = verbatim;
227 loader_processed_last_update_ = false;
[email protected]0e7cb682012-08-15 04:04:38228 last_suggestion_ = InstantSuggestion();
[email protected]c55e3b82012-08-09 15:27:05229
230 if (mode_ != SILENT) {
[email protected]cd223322012-08-26 19:54:42231 loader_->Update(query_text, verbatim);
[email protected]c55e3b82012-08-09 15:27:05232
233 content::NotificationService::current()->Notify(
234 chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED,
235 content::Source<InstantController>(this),
236 content::NotificationService::NoDetails());
237 }
238
[email protected]cd223322012-08-26 19:54:42239 // We don't have suggestions yet, but need to reset any existing "gray text".
240 delegate_->SetSuggestedText(string16(), INSTANT_COMPLETE_NOW);
241
[email protected]65d68da2011-09-08 03:19:33242 return true;
[email protected]fd2b9ce2010-08-11 04:03:57243}
244
[email protected]c55e3b82012-08-09 15:27:05245// TODO(tonyg): This method only fires when the omnibox bounds change. It also
246// needs to fire when the preview bounds change (e.g.: open/close info bar).
[email protected]6b723f82010-10-05 20:14:27247void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) {
[email protected]941b260c2012-08-23 23:00:47248 if (omnibox_bounds_ == bounds || (mode_ != INSTANT && mode_ != EXTENDED))
[email protected]46fe8e92010-09-22 03:32:47249 return;
250
[email protected]6e6b59f42010-12-13 20:20:23251 omnibox_bounds_ = bounds;
[email protected]c55e3b82012-08-09 15:27:05252 if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) {
253 update_bounds_timer_.Stop();
254 SendBoundsToPage();
255 } else if (!update_bounds_timer_.IsRunning()) {
256 update_bounds_timer_.Start(FROM_HERE,
257 base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), this,
258 &InstantController::SendBoundsToPage);
259 }
[email protected]46fe8e92010-09-22 03:32:47260}
261
[email protected]941b260c2012-08-23 23:00:47262void InstantController::HandleAutocompleteResults(
263 const std::vector<AutocompleteProvider*>& providers) {
264 if (mode_ != EXTENDED || !GetPreviewContents())
265 return;
266
267 std::vector<InstantAutocompleteResult> results;
268 for (ACProviders::const_iterator provider = providers.begin();
269 provider != providers.end(); ++provider) {
270 for (ACMatches::const_iterator match = (*provider)->matches().begin();
271 match != (*provider)->matches().end(); ++match) {
272 InstantAutocompleteResult result;
273 result.provider = UTF8ToUTF16((*provider)->name());
274 result.is_search =
275 match->type == AutocompleteMatch::SEARCH_WHAT_YOU_TYPED ||
276 match->type == AutocompleteMatch::SEARCH_HISTORY ||
277 match->type == AutocompleteMatch::SEARCH_SUGGEST ||
278 match->type == AutocompleteMatch::SEARCH_OTHER_ENGINE;
279 result.contents = match->description;
280 result.destination_url = match->destination_url;
281 result.relevance = match->relevance;
282 results.push_back(result);
283 }
284 }
285
286 loader_->SendAutocompleteResults(results);
287}
288
[email protected]c55e3b82012-08-09 15:27:05289TabContents* InstantController::GetPreviewContents() const {
290 return loader_.get() ? loader_->preview_contents() : NULL;
[email protected]fd2b9ce2010-08-11 04:03:57291}
292
[email protected]3e481282011-10-15 15:39:50293void InstantController::Hide() {
[email protected]c55e3b82012-08-09 15:27:05294 last_active_tab_ = NULL;
295 if (is_showing_) {
296 is_showing_ = false;
[email protected]2573b8d2011-03-01 16:20:36297 delegate_->HideInstant();
298 }
[email protected]7cce9f22011-02-28 22:02:47299}
300
[email protected]eadbf9532011-11-03 23:52:16301bool InstantController::IsCurrent() const {
[email protected]c55e3b82012-08-09 15:27:05302 DCHECK(IsOutOfDate() || GetPreviewContents());
303 return !IsOutOfDate() && GetPreviewContents() && loader_->supports_instant();
[email protected]64f09b82011-10-13 16:17:20304}
305
[email protected]a7eef8f2012-06-08 22:19:39306TabContents* InstantController::CommitCurrentPreview(InstantCommitType type) {
[email protected]c55e3b82012-08-09 15:27:05307 const TabContents* active_tab = delegate_->GetActiveTabContents();
308 TabContents* preview = ReleasePreviewContents(type);
309 AddSessionStorageHistogram(mode_, active_tab, preview);
[email protected]05cf2fa2012-05-29 20:36:06310 preview->web_contents()->GetController().CopyStateFromAndPrune(
[email protected]c55e3b82012-08-09 15:27:05311 &active_tab->web_contents()->GetController());
[email protected]05cf2fa2012-05-29 20:36:06312 delegate_->CommitInstant(preview);
[email protected]08a9f972012-08-27 23:23:15313
314 // Try to create another loader immediately so that it is ready for the next
315 // user interaction.
316 CreateDefaultLoader();
317
[email protected]05cf2fa2012-05-29 20:36:06318 return preview;
319}
320
[email protected]c55e3b82012-08-09 15:27:05321TabContents* InstantController::ReleasePreviewContents(InstantCommitType type) {
322 TabContents* preview = loader_->ReleasePreviewContents(type, last_full_text_);
323
324 // If the preview page has navigated since the last Update(), we need to add
325 // the navigation to history ourselves. Else, the page will navigate after
326 // commit, and it will be added to history in the usual manner.
327 scoped_refptr<history::HistoryAddPageArgs> last_navigation =
328 loader_->last_navigation();
329 if (last_navigation != NULL) {
330 content::NavigationEntry* entry =
331 preview->web_contents()->GetController().GetActiveEntry();
332 DCHECK_EQ(last_navigation->url, entry->GetURL());
333
334 // Add the page to history.
335 preview->history_tab_helper()->UpdateHistoryForNavigation(last_navigation);
336
337 // Update the page title.
338 preview->history_tab_helper()->UpdateHistoryPageTitle(*entry);
339
340 // Update the favicon.
[email protected]f3d2b312012-08-23 22:27:59341 FaviconService* favicon_service = FaviconServiceFactory::GetForProfile(
342 preview->profile(), Profile::EXPLICIT_ACCESS);
[email protected]c55e3b82012-08-09 15:27:05343 if (favicon_service && entry->GetFavicon().valid &&
344 entry->GetFavicon().image.IsEmpty()) {
345 std::vector<unsigned char> image_data;
346 // TODO: Add all variants once the history service supports it.
347 gfx::PNGCodec::EncodeBGRASkBitmap(
348 entry->GetFavicon().image.AsBitmap(), false, &image_data);
349 favicon_service->SetFavicon(entry->GetURL(),
350 entry->GetFavicon().url,
351 image_data,
352 history::FAVICON);
353 }
[email protected]05cf2fa2012-05-29 20:36:06354 }
[email protected]c55e3b82012-08-09 15:27:05355
356 // Add a fake history entry with a non-Instant search URL, so that search
357 // terms extraction (for autocomplete history matches) works.
358 HistoryService* history = HistoryServiceFactory::GetForProfile(
359 preview->profile(), Profile::EXPLICIT_ACCESS);
360 if (history) {
361 history->AddPage(url_for_history_, NULL, 0, GURL(), last_transition_type_,
362 history::RedirectList(), history::SOURCE_BROWSED, false);
363 }
364
365 AddPreviewUsageForHistogram(mode_, PREVIEW_COMMITTED);
366
367 // We may have gotten here from CommitInstant(), which means the loader may
368 // still be on the stack. So, schedule a destruction for later.
369 MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release());
370
371 // This call is here to hide the preview and reset view state. It won't
372 // actually delete |loader_| because it was just released to DeleteSoon().
373 DeleteLoader();
374
375 return preview;
[email protected]fd2b9ce2010-08-11 04:03:57376}
377
[email protected]c55e3b82012-08-09 15:27:05378void InstantController::OnAutocompleteLostFocus(
379 gfx::NativeView view_gaining_focus) {
380 DCHECK(!is_showing_ || GetPreviewContents());
[email protected]484ae5912010-09-29 19:16:14381
[email protected]08a9f972012-08-27 23:23:15382 // If there is no preview, nothing to do.
383 if (!GetPreviewContents())
[email protected]c55e3b82012-08-09 15:27:05384 return;
[email protected]484ae5912010-09-29 19:16:14385
[email protected]08a9f972012-08-27 23:23:15386 // If the preview is not showing, only need to check for loader staleness.
387 if (!is_showing_) {
388 MaybeOnStaleLoader();
389 return;
390 }
391
[email protected]20ac3c32011-03-06 17:59:19392#if defined(OS_MACOSX)
[email protected]08a9f972012-08-27 23:23:15393 if (!loader_->IsPointerDownFromActivate()) {
[email protected]c55e3b82012-08-09 15:27:05394 Hide();
[email protected]08a9f972012-08-27 23:23:15395 MaybeOnStaleLoader();
396 }
[email protected]20ac3c32011-03-06 17:59:19397#else
[email protected]5626b0892012-02-20 14:46:58398 content::RenderWidgetHostView* rwhv =
[email protected]ef9572e2012-01-04 22:14:12399 GetPreviewContents()->web_contents()->GetRenderWidgetHostView();
[email protected]5a85751b2010-11-17 01:33:27400 if (!view_gaining_focus || !rwhv) {
[email protected]c55e3b82012-08-09 15:27:05401 Hide();
[email protected]08a9f972012-08-27 23:23:15402 MaybeOnStaleLoader();
[email protected]5a85751b2010-11-17 01:33:27403 return;
404 }
[email protected]ba6680f2010-11-01 20:35:08405
[email protected]6eb8ea92011-08-22 21:01:41406#if defined(TOOLKIT_VIEWS)
407 // For views the top level widget is always focused. If the focus change
408 // originated in views determine the child Widget from the view that is being
409 // focused.
[email protected]09c69432012-03-16 16:23:28410 views::Widget* widget =
411 views::Widget::GetWidgetForNativeView(view_gaining_focus);
412 if (widget) {
413 views::FocusManager* focus_manager = widget->GetFocusManager();
414 if (focus_manager && focus_manager->is_changing_focus() &&
415 focus_manager->GetFocusedView() &&
416 focus_manager->GetFocusedView()->GetWidget()) {
417 view_gaining_focus =
418 focus_manager->GetFocusedView()->GetWidget()->GetNativeView();
[email protected]6eb8ea92011-08-22 21:01:41419 }
420 }
421#endif
422
[email protected]3c9e1872010-11-18 16:17:49423 gfx::NativeView tab_view =
[email protected]ef9572e2012-01-04 22:14:12424 GetPreviewContents()->web_contents()->GetNativeView();
[email protected]c55e3b82012-08-09 15:27:05425
[email protected]ba6680f2010-11-01 20:35:08426 // Focus is going to the renderer.
427 if (rwhv->GetNativeView() == view_gaining_focus ||
428 tab_view == view_gaining_focus) {
[email protected]ba6680f2010-11-01 20:35:08429
[email protected]c55e3b82012-08-09 15:27:05430 // If the mouse is not down, focus is not going to the renderer. Someone
431 // else moved focus and we shouldn't commit.
[email protected]08a9f972012-08-27 23:23:15432 if (!loader_->IsPointerDownFromActivate()) {
[email protected]c55e3b82012-08-09 15:27:05433 Hide();
[email protected]08a9f972012-08-27 23:23:15434 MaybeOnStaleLoader();
435 }
[email protected]c55e3b82012-08-09 15:27:05436
[email protected]5a85751b2010-11-17 01:33:27437 return;
[email protected]ba6680f2010-11-01 20:35:08438 }
439
440 // Walk up the view hierarchy. If the view gaining focus is a subview of the
[email protected]0932b30c2012-04-17 13:25:10441 // WebContents view (such as a windowed plugin or http auth dialog), we want
[email protected]ba6680f2010-11-01 20:35:08442 // to keep the preview contents. Otherwise, focus has gone somewhere else,
443 // such as the JS inspector, and we want to cancel the preview.
444 gfx::NativeView view_gaining_focus_ancestor = view_gaining_focus;
445 while (view_gaining_focus_ancestor &&
446 view_gaining_focus_ancestor != tab_view) {
447 view_gaining_focus_ancestor =
448 platform_util::GetParent(view_gaining_focus_ancestor);
449 }
450
[email protected]5a85751b2010-11-17 01:33:27451 if (view_gaining_focus_ancestor) {
452 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST);
453 return;
454 }
[email protected]ba6680f2010-11-01 20:35:08455
[email protected]c55e3b82012-08-09 15:27:05456 Hide();
[email protected]08a9f972012-08-27 23:23:15457 MaybeOnStaleLoader();
[email protected]20ac3c32011-03-06 17:59:19458#endif
[email protected]c55e3b82012-08-09 15:27:05459}
[email protected]ba6680f2010-11-01 20:35:08460
[email protected]05cf2fa2012-05-29 20:36:06461void InstantController::OnAutocompleteGotFocus() {
[email protected]08a9f972012-08-27 23:23:15462 CreateDefaultLoader();
[email protected]f2557bd2011-06-01 02:33:07463}
464
[email protected]c55e3b82012-08-09 15:27:05465bool InstantController::commit_on_pointer_release() const {
466 return GetPreviewContents() && loader_->IsPointerDownFromActivate();
[email protected]a0b84662010-10-04 23:22:04467}
468
[email protected]c55e3b82012-08-09 15:27:05469void InstantController::SetSuggestions(
[email protected]33b8b8e2011-03-15 14:51:55470 InstantLoader* loader,
[email protected]0e7cb682012-08-15 04:04:38471 const std::vector<InstantSuggestion>& suggestions) {
[email protected]c55e3b82012-08-09 15:27:05472 DCHECK_EQ(loader_.get(), loader);
473 if (loader_ != loader || IsOutOfDate() || mode_ == SILENT || mode_ == HIDDEN)
[email protected]d4a2a6e242012-05-04 04:34:06474 return;
475
[email protected]c55e3b82012-08-09 15:27:05476 loader_processed_last_update_ = true;
[email protected]03bb953d2010-09-14 21:38:30477
[email protected]0e7cb682012-08-15 04:04:38478 InstantSuggestion suggestion;
[email protected]c55e3b82012-08-09 15:27:05479 if (!suggestions.empty())
480 suggestion = suggestions[0];
[email protected]a0b84662010-10-04 23:22:04481
[email protected]0e7cb682012-08-15 04:04:38482 string16 suggestion_lower = base::i18n::ToLower(suggestion.text);
[email protected]c55e3b82012-08-09 15:27:05483 string16 user_text_lower = base::i18n::ToLower(last_user_text_);
484 if (user_text_lower.size() >= suggestion_lower.size() ||
[email protected]b67d0a42012-09-04 20:57:35485 suggestion_lower.compare(0, user_text_lower.size(), user_text_lower))
[email protected]0e7cb682012-08-15 04:04:38486 suggestion.text.clear();
[email protected]b67d0a42012-09-04 20:57:35487 else
[email protected]0e7cb682012-08-15 04:04:38488 suggestion.text.erase(0, last_user_text_.size());
[email protected]c55e3b82012-08-09 15:27:05489
490 last_suggestion_ = suggestion;
[email protected]c55e3b82012-08-09 15:27:05491 if (!last_verbatim_)
[email protected]0e7cb682012-08-15 04:04:38492 delegate_->SetSuggestedText(suggestion.text, suggestion.behavior);
[email protected]c55e3b82012-08-09 15:27:05493
494 if (mode_ != SUGGEST)
495 Show();
[email protected]a0b84662010-10-04 23:22:04496}
497
[email protected]6b723f82010-10-05 20:14:27498void InstantController::CommitInstantLoader(InstantLoader* loader) {
[email protected]c55e3b82012-08-09 15:27:05499 DCHECK_EQ(loader_.get(), loader);
500 DCHECK(is_showing_ && !IsOutOfDate()) << is_showing_;
501 if (loader_ != loader || !is_showing_ || IsOutOfDate())
502 return;
503
504 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST);
505}
506
507void InstantController::InstantLoaderPreviewLoaded(InstantLoader* loader) {
508 DCHECK_EQ(loader_.get(), loader);
509 AddPreviewUsageForHistogram(mode_, PREVIEW_LOADED);
510}
511
512void InstantController::InstantSupportDetermined(InstantLoader* loader,
513 bool supports_instant) {
514 DCHECK_EQ(loader_.get(), loader);
515 if (supports_instant) {
516 blacklisted_urls_.erase(loader->instant_url());
[email protected]a0b84662010-10-04 23:22:04517 } else {
[email protected]c55e3b82012-08-09 15:27:05518 ++blacklisted_urls_[loader->instant_url()];
519 if (loader_ == loader) {
520 if (GetPreviewContents())
521 AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED);
522
523 // Because of the state of the stack, we can't destroy the loader now.
524 MessageLoop::current()->DeleteSoon(FROM_HERE, loader_.release());
525 DeleteLoader();
526 }
[email protected]03bb953d2010-09-14 21:38:30527 }
[email protected]03bb953d2010-09-14 21:38:30528
[email protected]c55e3b82012-08-09 15:27:05529 content::Details<const bool> details(&supports_instant);
530 content::NotificationService::current()->Notify(
531 chrome::NOTIFICATION_INSTANT_SUPPORT_DETERMINED,
532 content::NotificationService::AllSources(),
533 details);
[email protected]2573b8d2011-03-01 16:20:36534}
535
[email protected]00d64212011-05-25 18:18:28536void InstantController::SwappedTabContents(InstantLoader* loader) {
[email protected]c55e3b82012-08-09 15:27:05537 DCHECK_EQ(loader_.get(), loader);
538 if (loader_ == loader && is_showing_)
539 delegate_->ShowInstant();
[email protected]00d64212011-05-25 18:18:28540}
541
[email protected]c55e3b82012-08-09 15:27:05542void InstantController::InstantLoaderContentsFocused(InstantLoader* loader) {
543 DCHECK_EQ(loader_.get(), loader);
544 DCHECK(is_showing_ && !IsOutOfDate()) << is_showing_;
[email protected]09c69432012-03-16 16:23:28545#if defined(USE_AURA)
546 // On aura the omnibox only receives a focus lost if we initiate the focus
547 // change. This does that.
[email protected]c55e3b82012-08-09 15:27:05548 if (is_showing_ && !IsOutOfDate())
[email protected]09c69432012-03-16 16:23:28549 delegate_->InstantPreviewFocused();
550#endif
551}
552
[email protected]b67d0a42012-09-04 20:57:35553InstantController::InstantController(InstantControllerDelegate* delegate,
554 Mode mode)
555 : delegate_(delegate),
556 mode_(mode),
557 last_active_tab_(NULL),
558 last_verbatim_(false),
559 last_transition_type_(content::PAGE_TRANSITION_LINK),
560 is_showing_(false),
561 loader_processed_last_update_(false) {
562}
563
[email protected]c55e3b82012-08-09 15:27:05564void InstantController::ResetLoader(const std::string& instant_url,
565 const TabContents* active_tab) {
566 if (GetPreviewContents() && loader_->instant_url() != instant_url)
567 DeleteLoader();
[email protected]2573b8d2011-03-01 16:20:36568
[email protected]c55e3b82012-08-09 15:27:05569 if (!GetPreviewContents()) {
[email protected]08a9f972012-08-27 23:23:15570 DCHECK(!loader_.get());
[email protected]c55e3b82012-08-09 15:27:05571 loader_.reset(new InstantLoader(this, instant_url, active_tab));
572 loader_->Init();
573 AddPreviewUsageForHistogram(mode_, PREVIEW_CREATED);
[email protected]08a9f972012-08-27 23:23:15574
575 // Reset the loader timer.
576 stale_loader_timer_.Stop();
577 stale_loader_timer_.Start(
578 FROM_HERE,
579 base::TimeDelta::FromMilliseconds(kStaleLoaderTimeoutMS), this,
580 &InstantController::OnStaleLoader);
[email protected]f38adeeb2010-12-08 01:08:11581 }
582}
583
[email protected]08a9f972012-08-27 23:23:15584void InstantController::CreateDefaultLoader() {
585 const TabContents* active_tab = delegate_->GetActiveTabContents();
586
587 // We could get here with no active tab if the Browser is closing.
588 if (!active_tab)
589 return;
590
591 const TemplateURL* template_url =
592 TemplateURLServiceFactory::GetForProfile(active_tab->profile())->
593 GetDefaultSearchProvider();
594 std::string instant_url;
595 if (!GetInstantURL(template_url, &instant_url))
596 return;
597
598 ResetLoader(instant_url, active_tab);
599}
600
601void InstantController::OnStaleLoader() {
602 // If the loader is showing, do not delete it. It will get deleted the next
603 // time the autocomplete loses focus.
604 if (is_showing_)
605 return;
606
607 DeleteLoader();
608 CreateDefaultLoader();
609}
610
611void InstantController::MaybeOnStaleLoader() {
612 if (!stale_loader_timer_.IsRunning())
613 OnStaleLoader();
614}
615
[email protected]c55e3b82012-08-09 15:27:05616void InstantController::DeleteLoader() {
617 Hide();
618 last_full_text_.clear();
619 last_user_text_.clear();
620 last_verbatim_ = false;
[email protected]0e7cb682012-08-15 04:04:38621 last_suggestion_ = InstantSuggestion();
[email protected]c55e3b82012-08-09 15:27:05622 last_transition_type_ = content::PAGE_TRANSITION_LINK;
623 last_omnibox_bounds_ = gfx::Rect();
624 url_for_history_ = GURL();
625 if (GetPreviewContents())
626 AddPreviewUsageForHistogram(mode_, PREVIEW_DELETED);
627 loader_.reset();
[email protected]0a387472010-10-07 00:18:20628}
629
[email protected]c55e3b82012-08-09 15:27:05630void InstantController::Show() {
631 if (!is_showing_) {
632 is_showing_ = true;
633 delegate_->ShowInstant();
634 AddPreviewUsageForHistogram(mode_, PREVIEW_SHOWED);
[email protected]f38adeeb2010-12-08 01:08:11635 }
636}
637
[email protected]c55e3b82012-08-09 15:27:05638void InstantController::SendBoundsToPage() {
639 if (last_omnibox_bounds_ == omnibox_bounds_ || IsOutOfDate() ||
[email protected]b67d0a42012-09-04 20:57:35640 !GetPreviewContents() || loader_->IsPointerDownFromActivate())
[email protected]c55e3b82012-08-09 15:27:05641 return;
[email protected]c55e3b82012-08-09 15:27:05642
643 last_omnibox_bounds_ = omnibox_bounds_;
644 gfx::Rect preview_bounds = delegate_->GetInstantBounds();
645 gfx::Rect intersection = omnibox_bounds_.Intersect(preview_bounds);
646
647 // Translate into window coordinates.
648 if (!intersection.IsEmpty()) {
649 intersection.Offset(-preview_bounds.origin().x(),
650 -preview_bounds.origin().y());
651 }
652
653 // In the current Chrome UI, these must always be true so they sanity check
654 // the above operations. In a future UI, these may be removed or adjusted.
655 // There is no point in sanity-checking |intersection.y()| because the omnibox
656 // can be placed anywhere vertically relative to the preview (for example, in
657 // Mac fullscreen mode, the omnibox is fully enclosed by the preview bounds).
658 DCHECK_LE(0, intersection.x());
659 DCHECK_LE(0, intersection.width());
660 DCHECK_LE(0, intersection.height());
661
662 loader_->SetOmniboxBounds(intersection);
663}
664
665bool InstantController::GetInstantURL(const TemplateURL* template_url,
666 std::string* instant_url) const {
667 CommandLine* command_line = CommandLine::ForCurrentProcess();
668 if (command_line->HasSwitch(switches::kInstantURL)) {
669 *instant_url = command_line->GetSwitchValueASCII(switches::kInstantURL);
670 return true;
671 }
672
673 if (!template_url)
674 return false;
675
676 const TemplateURLRef& instant_url_ref = template_url->instant_url_ref();
[email protected]e01af8b52012-08-30 20:25:16677 if (!instant_url_ref.IsValid())
[email protected]c55e3b82012-08-09 15:27:05678 return false;
679
[email protected]e01af8b52012-08-30 20:25:16680 // Even if the URL template doesn't have search terms, it may have other
681 // components (such as {google:baseURL}) that need to be replaced.
[email protected]c55e3b82012-08-09 15:27:05682 *instant_url = instant_url_ref.ReplaceSearchTerms(
683 TemplateURLRef::SearchTermsArgs(string16()));
684
[email protected]941b260c2012-08-23 23:00:47685 // Extended mode should always use HTTPS. TODO(sreeram): This section can be
686 // removed if TemplateURLs supported "https://{google:host}/..." instead of
687 // only supporting "{google:baseURL}...".
688 if (mode_ == EXTENDED) {
689 GURL url_obj(*instant_url);
690 if (!url_obj.is_valid())
691 return false;
692
693 if (!url_obj.SchemeIsSecure()) {
694 const std::string new_scheme = "https";
695 const std::string new_port = "443";
696 GURL::Replacements secure;
697 secure.SetSchemeStr(new_scheme);
698 secure.SetPortStr(new_port);
699 url_obj = url_obj.ReplaceComponents(secure);
700
701 if (!url_obj.is_valid())
702 return false;
703
704 *instant_url = url_obj.spec();
705 }
706 }
707
[email protected]c55e3b82012-08-09 15:27:05708 std::map<std::string, int>::const_iterator iter =
709 blacklisted_urls_.find(*instant_url);
710 if (iter != blacklisted_urls_.end() &&
[email protected]b67d0a42012-09-04 20:57:35711 iter->second > kMaxInstantSupportFailures)
[email protected]c55e3b82012-08-09 15:27:05712 return false;
[email protected]c55e3b82012-08-09 15:27:05713
714 return true;
715}
716
717bool InstantController::IsOutOfDate() const {
718 return !last_active_tab_ ||
719 last_active_tab_ != delegate_->GetActiveTabContents();
[email protected]f38adeeb2010-12-08 01:08:11720}