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