Home | History | Annotate | Download | only in instant
      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 
     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 
     55 InstantController::~InstantController() {
     56 }
     57 
     58 // static
     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
     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
     89 bool InstantController::IsEnabled(Profile* profile) {
     90   PrefService* prefs = profile->GetPrefs();
     91   return prefs->GetBoolean(prefs::kInstantEnabled);
     92 }
     93 
     94 // static
     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
    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
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    266 void InstantController::SetCommitOnMouseUp() {
    267   commit_on_mouse_up_ = true;
    268 }
    269 
    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)
    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
    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 
    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 
    385 void InstantController::CompleteRelease(TabContents* tab) {
    386   tab->SetAllContentsBlocked(false);
    387 }
    388 
    389 TabContentsWrapper* InstantController::GetPreviewContents() {
    390   return loader_manager_.get() && loader_manager_->current_loader() ?
    391       loader_manager_->current_loader()->preview_contents() : NULL;
    392 }
    393 
    394 bool InstantController::IsShowingInstant() {
    395   return loader_manager_.get() && loader_manager_->current_loader() &&
    396       loader_manager_->current_loader()->is_showing_instant();
    397 }
    398 
    399 bool InstantController::MightSupportInstant() {
    400   return loader_manager_.get() && loader_manager_->active_loader() &&
    401       loader_manager_->active_loader()->is_showing_instant();
    402 }
    403 
    404 GURL InstantController::GetCurrentURL() {
    405   return loader_manager_.get() && loader_manager_->active_loader() ?
    406       loader_manager_->active_loader()->url() : GURL();
    407 }
    408 
    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 
    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 
    432 gfx::Rect InstantController::GetInstantBounds() {
    433   return delegate_->GetInstantBounds();
    434 }
    435 
    436 bool InstantController::ShouldCommitInstantOnMouseUp() {
    437   return commit_on_mouse_up_;
    438 }
    439 
    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 
    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 
    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 
    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 
    520 TabContentsWrapper* InstantController::GetPendingPreviewContents() {
    521   return loader_manager_.get() && loader_manager_->pending_loader() ?
    522       loader_manager_->pending_loader()->preview_contents() : NULL;
    523 }
    524 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    650 void InstantController::BlacklistFromInstant(TemplateURLID id) {
    651   blacklisted_ids_.insert(id);
    652 }
    653 
    654 bool InstantController::IsBlacklistedFromInstant(TemplateURLID id) {
    655   return blacklisted_ids_.count(id) > 0;
    656 }
    657 
    658 void InstantController::ClearBlacklist() {
    659   blacklisted_ids_.clear();
    660 }
    661 
    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 
    671 void InstantController::DestroyLoaders() {
    672   loaders_to_destroy_.reset();
    673 }
    674 
    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