• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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