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.
30static const int kUpdateDelayMS = 200;
31
32// Amount of time we delay before showing pages that have a non-200 status.
33static const int kShowDelayMS = 800;
34
35// static
36InstantController::HostBlacklist* InstantController::host_blacklist_ = NULL;
37
38InstantController::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
55InstantController::~InstantController() {
56}
57
58// static
59void 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
68void 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
89bool InstantController::IsEnabled(Profile* profile) {
90  PrefService* prefs = profile->GetPrefs();
91  return prefs->GetBoolean(prefs::kInstantEnabled);
92}
93
94// static
95void 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
112void 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
130bool 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
138void 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
190void 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
205void 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
219void 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
233bool 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
240void 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
266void InstantController::SetCommitOnMouseUp() {
267  commit_on_mouse_up_ = true;
268}
269
270bool 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)
277void 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
292void 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
350TabContentsWrapper* 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
385void InstantController::CompleteRelease(TabContents* tab) {
386  tab->SetAllContentsBlocked(false);
387}
388
389TabContentsWrapper* InstantController::GetPreviewContents() {
390  return loader_manager_.get() && loader_manager_->current_loader() ?
391      loader_manager_->current_loader()->preview_contents() : NULL;
392}
393
394bool InstantController::IsShowingInstant() {
395  return loader_manager_.get() && loader_manager_->current_loader() &&
396      loader_manager_->current_loader()->is_showing_instant();
397}
398
399bool InstantController::MightSupportInstant() {
400  return loader_manager_.get() && loader_manager_->active_loader() &&
401      loader_manager_->active_loader()->is_showing_instant();
402}
403
404GURL InstantController::GetCurrentURL() {
405  return loader_manager_.get() && loader_manager_->active_loader() ?
406      loader_manager_->active_loader()->url() : GURL();
407}
408
409void 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
424void 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
432gfx::Rect InstantController::GetInstantBounds() {
433  return delegate_->GetInstantBounds();
434}
435
436bool InstantController::ShouldCommitInstantOnMouseUp() {
437  return commit_on_mouse_up_;
438}
439
440void 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
451void 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
474void 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
494void 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
520TabContentsWrapper* InstantController::GetPendingPreviewContents() {
521  return loader_manager_.get() && loader_manager_->pending_loader() ?
522      loader_manager_->pending_loader()->preview_contents() : NULL;
523}
524
525bool 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
554void 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
562void 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
572void 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
586void 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
595void 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
621bool 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
650void InstantController::BlacklistFromInstant(TemplateURLID id) {
651  blacklisted_ids_.insert(id);
652}
653
654bool InstantController::IsBlacklistedFromInstant(TemplateURLID id) {
655  return blacklisted_ids_.count(id) > 0;
656}
657
658void InstantController::ClearBlacklist() {
659  blacklisted_ids_.clear();
660}
661
662void 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
671void InstantController::DestroyLoaders() {
672  loaders_to_destroy_.reset();
673}
674
675const 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