1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/instant/instant_controller.h"
6
7 #include "base/command_line.h"
8 #include "base/message_loop.h"
9 #include "base/metrics/histogram.h"
10 #include "build/build_config.h"
11 #include "chrome/browser/autocomplete/autocomplete_match.h"
12 #include "chrome/browser/instant/instant_delegate.h"
13 #include "chrome/browser/instant/instant_loader.h"
14 #include "chrome/browser/instant/instant_loader_manager.h"
15 #include "chrome/browser/instant/promo_counter.h"
16 #include "chrome/browser/platform_util.h"
17 #include "chrome/browser/prefs/pref_service.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "chrome/browser/search_engines/template_url.h"
20 #include "chrome/browser/search_engines/template_url_model.h"
21 #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h"
22 #include "chrome/common/chrome_switches.h"
23 #include "chrome/common/pref_names.h"
24 #include "chrome/common/url_constants.h"
25 #include "content/browser/renderer_host/render_widget_host_view.h"
26 #include "content/browser/tab_contents/tab_contents.h"
27 #include "content/common/notification_service.h"
28
29 // Number of ms to delay between loading urls.
30 static const int kUpdateDelayMS = 200;
31
32 // Amount of time we delay before showing pages that have a non-200 status.
33 static const int kShowDelayMS = 800;
34
35 // static
36 InstantController::HostBlacklist* InstantController::host_blacklist_ = NULL;
37
InstantController(Profile * profile,InstantDelegate * delegate)38 InstantController::InstantController(Profile* profile,
39 InstantDelegate* delegate)
40 : delegate_(delegate),
41 tab_contents_(NULL),
42 is_active_(false),
43 displayable_loader_(NULL),
44 commit_on_mouse_up_(false),
45 last_transition_type_(PageTransition::LINK),
46 ALLOW_THIS_IN_INITIALIZER_LIST(destroy_factory_(this)) {
47 PrefService* service = profile->GetPrefs();
48 if (service) {
49 // kInstantWasEnabledOnce was added after instant, set it now to make sure
50 // it is correctly set.
51 service->SetBoolean(prefs::kInstantEnabledOnce, true);
52 }
53 }
54
~InstantController()55 InstantController::~InstantController() {
56 }
57
58 // static
RegisterUserPrefs(PrefService * prefs)59 void InstantController::RegisterUserPrefs(PrefService* prefs) {
60 prefs->RegisterBooleanPref(prefs::kInstantConfirmDialogShown, false);
61 prefs->RegisterBooleanPref(prefs::kInstantEnabled, false);
62 prefs->RegisterBooleanPref(prefs::kInstantEnabledOnce, false);
63 prefs->RegisterInt64Pref(prefs::kInstantEnabledTime, false);
64 PromoCounter::RegisterUserPrefs(prefs, prefs::kInstantPromo);
65 }
66
67 // static
RecordMetrics(Profile * profile)68 void InstantController::RecordMetrics(Profile* profile) {
69 if (!IsEnabled(profile))
70 return;
71
72 PrefService* service = profile->GetPrefs();
73 if (service) {
74 int64 enable_time = service->GetInt64(prefs::kInstantEnabledTime);
75 if (!enable_time) {
76 service->SetInt64(prefs::kInstantEnabledTime,
77 base::Time::Now().ToInternalValue());
78 } else {
79 base::TimeDelta delta =
80 base::Time::Now() - base::Time::FromInternalValue(enable_time);
81 // Histogram from 1 hour to 30 days.
82 UMA_HISTOGRAM_CUSTOM_COUNTS("Instant.EnabledTime.Predictive",
83 delta.InHours(), 1, 30 * 24, 50);
84 }
85 }
86 }
87
88 // static
IsEnabled(Profile * profile)89 bool InstantController::IsEnabled(Profile* profile) {
90 PrefService* prefs = profile->GetPrefs();
91 return prefs->GetBoolean(prefs::kInstantEnabled);
92 }
93
94 // static
Enable(Profile * profile)95 void InstantController::Enable(Profile* profile) {
96 PromoCounter* promo_counter = profile->GetInstantPromoCounter();
97 if (promo_counter)
98 promo_counter->Hide();
99
100 PrefService* service = profile->GetPrefs();
101 if (!service)
102 return;
103
104 service->SetBoolean(prefs::kInstantEnabled, true);
105 service->SetBoolean(prefs::kInstantConfirmDialogShown, true);
106 service->SetInt64(prefs::kInstantEnabledTime,
107 base::Time::Now().ToInternalValue());
108 service->SetBoolean(prefs::kInstantEnabledOnce, true);
109 }
110
111 // static
Disable(Profile * profile)112 void InstantController::Disable(Profile* profile) {
113 PrefService* service = profile->GetPrefs();
114 if (!service || !IsEnabled(profile))
115 return;
116
117 int64 enable_time = service->GetInt64(prefs::kInstantEnabledTime);
118 if (enable_time) {
119 base::TimeDelta delta =
120 base::Time::Now() - base::Time::FromInternalValue(enable_time);
121 // Histogram from 1 minute to 10 days.
122 UMA_HISTOGRAM_CUSTOM_COUNTS("Instant.TimeToDisable.Predictive",
123 delta.InMinutes(), 1, 60 * 24 * 10, 50);
124 }
125
126 service->SetBoolean(prefs::kInstantEnabled, false);
127 }
128
129 // static
CommitIfCurrent(InstantController * controller)130 bool InstantController::CommitIfCurrent(InstantController* controller) {
131 if (controller && controller->IsCurrent()) {
132 controller->CommitCurrentPreview(INSTANT_COMMIT_PRESSED_ENTER);
133 return true;
134 }
135 return false;
136 }
137
Update(TabContentsWrapper * tab_contents,const AutocompleteMatch & match,const string16 & user_text,bool verbatim,string16 * suggested_text)138 void InstantController::Update(TabContentsWrapper* tab_contents,
139 const AutocompleteMatch& match,
140 const string16& user_text,
141 bool verbatim,
142 string16* suggested_text) {
143 suggested_text->clear();
144
145 if (tab_contents != tab_contents_)
146 DestroyPreviewContents();
147
148 const GURL& url = match.destination_url;
149 tab_contents_ = tab_contents;
150 commit_on_mouse_up_ = false;
151 last_transition_type_ = match.transition;
152 const TemplateURL* template_url = NULL;
153
154 if (url.is_empty() || !url.is_valid()) {
155 // Assume we were invoked with GURL() and should destroy all.
156 DestroyPreviewContents();
157 return;
158 }
159
160 if (!ShouldShowPreviewFor(match, &template_url)) {
161 DestroyPreviewContentsAndLeaveActive();
162 return;
163 }
164
165 if (!loader_manager_.get())
166 loader_manager_.reset(new InstantLoaderManager(this));
167
168 if (!is_active_) {
169 is_active_ = true;
170 delegate_->PrepareForInstant();
171 }
172
173 TemplateURLID template_url_id = template_url ? template_url->id() : 0;
174 // Verbatim only makes sense if the search engines supports instant.
175 bool real_verbatim = template_url_id ? verbatim : false;
176
177 if (ShouldUpdateNow(template_url_id, match.destination_url)) {
178 UpdateLoader(template_url, match.destination_url, match.transition,
179 user_text, real_verbatim, suggested_text);
180 } else {
181 ScheduleUpdate(match.destination_url);
182 }
183
184 NotificationService::current()->Notify(
185 NotificationType::INSTANT_CONTROLLER_UPDATED,
186 Source<InstantController>(this),
187 NotificationService::NoDetails());
188 }
189
SetOmniboxBounds(const gfx::Rect & bounds)190 void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) {
191 if (omnibox_bounds_ == bounds)
192 return;
193
194 // Always track the omnibox bounds. That way if Update is later invoked the
195 // bounds are in sync.
196 omnibox_bounds_ = bounds;
197 if (loader_manager_.get()) {
198 if (loader_manager_->current_loader())
199 loader_manager_->current_loader()->SetOmniboxBounds(bounds);
200 if (loader_manager_->pending_loader())
201 loader_manager_->pending_loader()->SetOmniboxBounds(bounds);
202 }
203 }
204
DestroyPreviewContents()205 void InstantController::DestroyPreviewContents() {
206 if (!loader_manager_.get()) {
207 // We're not showing anything, nothing to do.
208 return;
209 }
210
211 // ReleasePreviewContents sets is_active_ to false, but we need to set it
212 // before notifying the delegate, otherwise if the delegate asks for the state
213 // we'll still be active.
214 is_active_ = false;
215 delegate_->HideInstant();
216 delete ReleasePreviewContents(INSTANT_COMMIT_DESTROY);
217 }
218
DestroyPreviewContentsAndLeaveActive()219 void InstantController::DestroyPreviewContentsAndLeaveActive() {
220 commit_on_mouse_up_ = false;
221 if (displayable_loader_) {
222 displayable_loader_ = NULL;
223 delegate_->HideInstant();
224 }
225
226 // TODO(sky): this shouldn't nuke the loader. It should just nuke non-instant
227 // loaders and hide instant loaders.
228 loader_manager_.reset(new InstantLoaderManager(this));
229 show_timer_.Stop();
230 update_timer_.Stop();
231 }
232
IsCurrent()233 bool InstantController::IsCurrent() {
234 return loader_manager_.get() && loader_manager_->active_loader() &&
235 loader_manager_->active_loader()->ready() &&
236 !loader_manager_->active_loader()->needs_reload() &&
237 !update_timer_.IsRunning();
238 }
239
CommitCurrentPreview(InstantCommitType type)240 void InstantController::CommitCurrentPreview(InstantCommitType type) {
241 if (type == INSTANT_COMMIT_PRESSED_ENTER && show_timer_.IsRunning()) {
242 // The user pressed enter and the show timer is running. This means the
243 // pending_loader returned an error code and we're not showing it. Force it
244 // to be shown.
245 show_timer_.Stop();
246 ShowTimerFired();
247 }
248 DCHECK(loader_manager_.get());
249 DCHECK(loader_manager_->current_loader());
250 bool showing_instant =
251 loader_manager_->current_loader()->is_showing_instant();
252 TabContentsWrapper* tab = ReleasePreviewContents(type);
253 // If the loader was showing an instant page then it's navigation stack is
254 // something like: search-engine-home-page (eg google.com) search-term1
255 // search-term2 .... Each search-term navigation corresponds to the page
256 // deciding enough time has passed to commit a navigation. We don't want the
257 // searche-engine-home-page navigation in this case so we pass true to
258 // CopyStateFromAndPrune to have the search-engine-home-page navigation
259 // removed.
260 tab->controller().CopyStateFromAndPrune(
261 &tab_contents_->controller(), showing_instant);
262 delegate_->CommitInstant(tab);
263 CompleteRelease(tab->tab_contents());
264 }
265
SetCommitOnMouseUp()266 void InstantController::SetCommitOnMouseUp() {
267 commit_on_mouse_up_ = true;
268 }
269
IsMouseDownFromActivate()270 bool InstantController::IsMouseDownFromActivate() {
271 DCHECK(loader_manager_.get());
272 DCHECK(loader_manager_->current_loader());
273 return loader_manager_->current_loader()->IsMouseDownFromActivate();
274 }
275
276 #if defined(OS_MACOSX)
OnAutocompleteLostFocus(gfx::NativeView view_gaining_focus)277 void InstantController::OnAutocompleteLostFocus(
278 gfx::NativeView view_gaining_focus) {
279 // If |IsMouseDownFromActivate()| returns false, the RenderWidgetHostView did
280 // not receive a mouseDown event. Therefore, we should destroy the preview.
281 // Otherwise, the RWHV was clicked, so we commit the preview.
282 if (!is_displayable() || !GetPreviewContents() ||
283 !IsMouseDownFromActivate()) {
284 DestroyPreviewContents();
285 } else if (IsShowingInstant()) {
286 SetCommitOnMouseUp();
287 } else {
288 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST);
289 }
290 }
291 #else
OnAutocompleteLostFocus(gfx::NativeView view_gaining_focus)292 void InstantController::OnAutocompleteLostFocus(
293 gfx::NativeView view_gaining_focus) {
294 if (!is_active() || !GetPreviewContents()) {
295 DestroyPreviewContents();
296 return;
297 }
298
299 RenderWidgetHostView* rwhv =
300 GetPreviewContents()->tab_contents()->GetRenderWidgetHostView();
301 if (!view_gaining_focus || !rwhv) {
302 DestroyPreviewContents();
303 return;
304 }
305
306 gfx::NativeView tab_view =
307 GetPreviewContents()->tab_contents()->GetNativeView();
308 // Focus is going to the renderer.
309 if (rwhv->GetNativeView() == view_gaining_focus ||
310 tab_view == view_gaining_focus) {
311 if (!IsMouseDownFromActivate()) {
312 // If the mouse is not down, focus is not going to the renderer. Someone
313 // else moved focus and we shouldn't commit.
314 DestroyPreviewContents();
315 return;
316 }
317
318 if (IsShowingInstant()) {
319 // We're showing instant results. As instant results may shift when
320 // committing we commit on the mouse up. This way a slow click still
321 // works fine.
322 SetCommitOnMouseUp();
323 return;
324 }
325
326 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST);
327 return;
328 }
329
330 // Walk up the view hierarchy. If the view gaining focus is a subview of the
331 // TabContents view (such as a windowed plugin or http auth dialog), we want
332 // to keep the preview contents. Otherwise, focus has gone somewhere else,
333 // such as the JS inspector, and we want to cancel the preview.
334 gfx::NativeView view_gaining_focus_ancestor = view_gaining_focus;
335 while (view_gaining_focus_ancestor &&
336 view_gaining_focus_ancestor != tab_view) {
337 view_gaining_focus_ancestor =
338 platform_util::GetParent(view_gaining_focus_ancestor);
339 }
340
341 if (view_gaining_focus_ancestor) {
342 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST);
343 return;
344 }
345
346 DestroyPreviewContents();
347 }
348 #endif
349
ReleasePreviewContents(InstantCommitType type)350 TabContentsWrapper* InstantController::ReleasePreviewContents(
351 InstantCommitType type) {
352 if (!loader_manager_.get())
353 return NULL;
354
355 // Make sure the pending loader is active. Ideally we would call
356 // ShowTimerFired, but if Release is invoked from the browser we don't want to
357 // attempt to show the tab contents (since its being added to a new tab).
358 if (type == INSTANT_COMMIT_PRESSED_ENTER && show_timer_.IsRunning()) {
359 InstantLoader* loader = loader_manager_->active_loader();
360 if (loader && loader->ready() &&
361 loader == loader_manager_->pending_loader()) {
362 scoped_ptr<InstantLoader> old_loader;
363 loader_manager_->MakePendingCurrent(&old_loader);
364 }
365 }
366
367 // Loader may be null if the url blacklisted instant.
368 scoped_ptr<InstantLoader> loader;
369 if (loader_manager_->current_loader())
370 loader.reset(loader_manager_->ReleaseCurrentLoader());
371 TabContentsWrapper* tab = loader.get() ?
372 loader->ReleasePreviewContents(type) : NULL;
373
374 ClearBlacklist();
375 is_active_ = false;
376 displayable_loader_ = NULL;
377 commit_on_mouse_up_ = false;
378 omnibox_bounds_ = gfx::Rect();
379 loader_manager_.reset();
380 update_timer_.Stop();
381 show_timer_.Stop();
382 return tab;
383 }
384
CompleteRelease(TabContents * tab)385 void InstantController::CompleteRelease(TabContents* tab) {
386 tab->SetAllContentsBlocked(false);
387 }
388
GetPreviewContents()389 TabContentsWrapper* InstantController::GetPreviewContents() {
390 return loader_manager_.get() && loader_manager_->current_loader() ?
391 loader_manager_->current_loader()->preview_contents() : NULL;
392 }
393
IsShowingInstant()394 bool InstantController::IsShowingInstant() {
395 return loader_manager_.get() && loader_manager_->current_loader() &&
396 loader_manager_->current_loader()->is_showing_instant();
397 }
398
MightSupportInstant()399 bool InstantController::MightSupportInstant() {
400 return loader_manager_.get() && loader_manager_->active_loader() &&
401 loader_manager_->active_loader()->is_showing_instant();
402 }
403
GetCurrentURL()404 GURL InstantController::GetCurrentURL() {
405 return loader_manager_.get() && loader_manager_->active_loader() ?
406 loader_manager_->active_loader()->url() : GURL();
407 }
408
InstantStatusChanged(InstantLoader * loader)409 void InstantController::InstantStatusChanged(InstantLoader* loader) {
410 if (!loader->http_status_ok()) {
411 // Status isn't ok, start a timer that when fires shows the result. This
412 // delays showing 403 pages and the like.
413 show_timer_.Stop();
414 show_timer_.Start(
415 base::TimeDelta::FromMilliseconds(kShowDelayMS),
416 this, &InstantController::ShowTimerFired);
417 UpdateDisplayableLoader();
418 return;
419 }
420
421 ProcessInstantStatusChanged(loader);
422 }
423
SetSuggestedTextFor(InstantLoader * loader,const string16 & text,InstantCompleteBehavior behavior)424 void InstantController::SetSuggestedTextFor(
425 InstantLoader* loader,
426 const string16& text,
427 InstantCompleteBehavior behavior) {
428 if (loader_manager_->current_loader() == loader)
429 delegate_->SetSuggestedText(text, behavior);
430 }
431
GetInstantBounds()432 gfx::Rect InstantController::GetInstantBounds() {
433 return delegate_->GetInstantBounds();
434 }
435
ShouldCommitInstantOnMouseUp()436 bool InstantController::ShouldCommitInstantOnMouseUp() {
437 return commit_on_mouse_up_;
438 }
439
CommitInstantLoader(InstantLoader * loader)440 void InstantController::CommitInstantLoader(InstantLoader* loader) {
441 if (loader_manager_.get() && loader_manager_->current_loader() == loader) {
442 CommitCurrentPreview(INSTANT_COMMIT_FOCUS_LOST);
443 } else {
444 // This can happen if the mouse was down, we swapped out the preview and
445 // the mouse was released. Generally this shouldn't happen, but if it does
446 // revert.
447 DestroyPreviewContents();
448 }
449 }
450
InstantLoaderDoesntSupportInstant(InstantLoader * loader)451 void InstantController::InstantLoaderDoesntSupportInstant(
452 InstantLoader* loader) {
453 DCHECK(!loader->ready()); // We better not be showing this loader.
454 DCHECK(loader->template_url_id());
455
456 VLOG(1) << "provider does not support instant";
457
458 // Don't attempt to use instant for this search engine again.
459 BlacklistFromInstant(loader->template_url_id());
460
461 // Because of the state of the stack we can't destroy the loader now.
462 bool was_pending = loader_manager_->pending_loader() == loader;
463 ScheduleDestroy(loader_manager_->ReleaseLoader(loader));
464 if (was_pending) {
465 // |loader| was the pending loader. We may be showing another TabContents to
466 // the user (what was current). Destroy it.
467 DestroyPreviewContentsAndLeaveActive();
468 } else {
469 // |loader| wasn't pending, yet it may still be the displayed loader.
470 UpdateDisplayableLoader();
471 }
472 }
473
AddToBlacklist(InstantLoader * loader,const GURL & url)474 void InstantController::AddToBlacklist(InstantLoader* loader, const GURL& url) {
475 std::string host = url.host();
476 if (host.empty())
477 return;
478
479 if (!host_blacklist_)
480 host_blacklist_ = new HostBlacklist;
481 host_blacklist_->insert(host);
482
483 if (!loader_manager_.get())
484 return;
485
486 // Because of the state of the stack we can't destroy the loader now.
487 ScheduleDestroy(loader);
488
489 loader_manager_->ReleaseLoader(loader);
490
491 UpdateDisplayableLoader();
492 }
493
UpdateDisplayableLoader()494 void InstantController::UpdateDisplayableLoader() {
495 InstantLoader* loader = NULL;
496 // As soon as the pending loader is displayable it becomes the current loader,
497 // so we need only concern ourselves with the current loader here.
498 if (loader_manager_.get() && loader_manager_->current_loader() &&
499 loader_manager_->current_loader()->ready() &&
500 (!show_timer_.IsRunning() ||
501 loader_manager_->current_loader()->http_status_ok())) {
502 loader = loader_manager_->current_loader();
503 }
504 if (loader == displayable_loader_)
505 return;
506
507 displayable_loader_ = loader;
508
509 if (!displayable_loader_) {
510 delegate_->HideInstant();
511 } else {
512 delegate_->ShowInstant(displayable_loader_->preview_contents());
513 NotificationService::current()->Notify(
514 NotificationType::INSTANT_CONTROLLER_SHOWN,
515 Source<InstantController>(this),
516 NotificationService::NoDetails());
517 }
518 }
519
GetPendingPreviewContents()520 TabContentsWrapper* InstantController::GetPendingPreviewContents() {
521 return loader_manager_.get() && loader_manager_->pending_loader() ?
522 loader_manager_->pending_loader()->preview_contents() : NULL;
523 }
524
ShouldUpdateNow(TemplateURLID instant_id,const GURL & url)525 bool InstantController::ShouldUpdateNow(TemplateURLID instant_id,
526 const GURL& url) {
527 DCHECK(loader_manager_.get());
528
529 if (instant_id) {
530 // Update sites that support instant immediately, they can do their own
531 // throttling.
532 return true;
533 }
534
535 if (url.SchemeIsFile())
536 return true; // File urls should load quickly, so don't delay loading them.
537
538 if (loader_manager_->WillUpateChangeActiveLoader(instant_id)) {
539 // If Update would change loaders, update now. This indicates transitioning
540 // from an instant to non-instant loader.
541 return true;
542 }
543
544 InstantLoader* active_loader = loader_manager_->active_loader();
545 // WillUpateChangeActiveLoader should return true if no active loader, so
546 // we know there will be an active loader if we get here.
547 DCHECK(active_loader);
548 // Immediately update if the url is the same (which should result in nothing
549 // happening) or the hosts differ, otherwise we'll delay the update.
550 return (active_loader->url() == url) ||
551 (active_loader->url().host() != url.host());
552 }
553
ScheduleUpdate(const GURL & url)554 void InstantController::ScheduleUpdate(const GURL& url) {
555 scheduled_url_ = url;
556
557 update_timer_.Stop();
558 update_timer_.Start(base::TimeDelta::FromMilliseconds(kUpdateDelayMS),
559 this, &InstantController::ProcessScheduledUpdate);
560 }
561
ProcessScheduledUpdate()562 void InstantController::ProcessScheduledUpdate() {
563 DCHECK(loader_manager_.get());
564
565 // We only delay loading of sites that don't support instant, so we can ignore
566 // suggested_text here.
567 string16 suggested_text;
568 UpdateLoader(NULL, scheduled_url_, last_transition_type_, string16(), false,
569 &suggested_text);
570 }
571
ProcessInstantStatusChanged(InstantLoader * loader)572 void InstantController::ProcessInstantStatusChanged(InstantLoader* loader) {
573 DCHECK(loader_manager_.get());
574 scoped_ptr<InstantLoader> old_loader;
575 if (loader == loader_manager_->pending_loader()) {
576 loader_manager_->MakePendingCurrent(&old_loader);
577 } else if (loader != loader_manager_->current_loader()) {
578 // Notification from a loader that is no longer the current (either we have
579 // a pending, or its an instant loader). Ignore it.
580 return;
581 }
582
583 UpdateDisplayableLoader();
584 }
585
ShowTimerFired()586 void InstantController::ShowTimerFired() {
587 if (!loader_manager_.get())
588 return;
589
590 InstantLoader* loader = loader_manager_->active_loader();
591 if (loader && loader->ready())
592 ProcessInstantStatusChanged(loader);
593 }
594
UpdateLoader(const TemplateURL * template_url,const GURL & url,PageTransition::Type transition_type,const string16 & user_text,bool verbatim,string16 * suggested_text)595 void InstantController::UpdateLoader(const TemplateURL* template_url,
596 const GURL& url,
597 PageTransition::Type transition_type,
598 const string16& user_text,
599 bool verbatim,
600 string16* suggested_text) {
601 update_timer_.Stop();
602
603 scoped_ptr<InstantLoader> owned_loader;
604 TemplateURLID template_url_id = template_url ? template_url->id() : 0;
605 InstantLoader* new_loader =
606 loader_manager_->UpdateLoader(template_url_id, &owned_loader);
607
608 new_loader->SetOmniboxBounds(omnibox_bounds_);
609 if (new_loader->Update(tab_contents_, template_url, url, transition_type,
610 user_text, verbatim, suggested_text)) {
611 show_timer_.Stop();
612 if (!new_loader->http_status_ok()) {
613 show_timer_.Start(
614 base::TimeDelta::FromMilliseconds(kShowDelayMS),
615 this, &InstantController::ShowTimerFired);
616 }
617 }
618 UpdateDisplayableLoader();
619 }
620
ShouldShowPreviewFor(const AutocompleteMatch & match,const TemplateURL ** template_url)621 bool InstantController::ShouldShowPreviewFor(const AutocompleteMatch& match,
622 const TemplateURL** template_url) {
623 const TemplateURL* t_url = GetTemplateURL(match);
624 if (t_url) {
625 if (!t_url->id() ||
626 !t_url->instant_url() ||
627 IsBlacklistedFromInstant(t_url->id()) ||
628 !t_url->instant_url()->SupportsReplacement()) {
629 // To avoid extra load on other search engines we only enable previews if
630 // they support the instant API.
631 return false;
632 }
633 }
634 *template_url = t_url;
635
636 if (match.destination_url.SchemeIs(chrome::kJavaScriptScheme))
637 return false;
638
639 // Extension keywords don't have a real destionation URL.
640 if (match.template_url && match.template_url->IsExtensionKeyword())
641 return false;
642
643 // Was the host blacklisted?
644 if (host_blacklist_ && host_blacklist_->count(match.destination_url.host()))
645 return false;
646
647 return true;
648 }
649
BlacklistFromInstant(TemplateURLID id)650 void InstantController::BlacklistFromInstant(TemplateURLID id) {
651 blacklisted_ids_.insert(id);
652 }
653
IsBlacklistedFromInstant(TemplateURLID id)654 bool InstantController::IsBlacklistedFromInstant(TemplateURLID id) {
655 return blacklisted_ids_.count(id) > 0;
656 }
657
ClearBlacklist()658 void InstantController::ClearBlacklist() {
659 blacklisted_ids_.clear();
660 }
661
ScheduleDestroy(InstantLoader * loader)662 void InstantController::ScheduleDestroy(InstantLoader* loader) {
663 loaders_to_destroy_.push_back(loader);
664 if (destroy_factory_.empty()) {
665 MessageLoop::current()->PostTask(
666 FROM_HERE, destroy_factory_.NewRunnableMethod(
667 &InstantController::DestroyLoaders));
668 }
669 }
670
DestroyLoaders()671 void InstantController::DestroyLoaders() {
672 loaders_to_destroy_.reset();
673 }
674
GetTemplateURL(const AutocompleteMatch & match)675 const TemplateURL* InstantController::GetTemplateURL(
676 const AutocompleteMatch& match) {
677 const TemplateURL* template_url = match.template_url;
678 if (match.type == AutocompleteMatch::SEARCH_WHAT_YOU_TYPED ||
679 match.type == AutocompleteMatch::SEARCH_HISTORY ||
680 match.type == AutocompleteMatch::SEARCH_SUGGEST) {
681 TemplateURLModel* model = tab_contents_->profile()->GetTemplateURLModel();
682 template_url = model ? model->GetDefaultSearchProvider() : NULL;
683 }
684 return template_url;
685 }
686