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_loader.h" 6 7#include <algorithm> 8#include <string> 9#include <utility> 10#include <vector> 11 12#include "base/command_line.h" 13#include "base/string_number_conversions.h" 14#include "base/timer.h" 15#include "base/utf_string_conversions.h" 16#include "base/values.h" 17#include "chrome/browser/favicon_service.h" 18#include "chrome/browser/history/history_marshaling.h" 19#include "chrome/browser/instant/instant_loader_delegate.h" 20#include "chrome/browser/profiles/profile.h" 21#include "chrome/browser/search_engines/template_url.h" 22#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" 23#include "chrome/common/chrome_switches.h" 24#include "chrome/common/render_messages.h" 25#include "content/browser/renderer_host/render_view_host.h" 26#include "content/browser/renderer_host/render_widget_host.h" 27#include "content/browser/renderer_host/render_widget_host_view.h" 28#include "content/browser/tab_contents/navigation_controller.h" 29#include "content/browser/tab_contents/navigation_entry.h" 30#include "content/browser/tab_contents/provisional_load_details.h" 31#include "content/browser/tab_contents/tab_contents.h" 32#include "content/browser/tab_contents/tab_contents_delegate.h" 33#include "content/browser/tab_contents/tab_contents_view.h" 34#include "content/common/notification_details.h" 35#include "content/common/notification_observer.h" 36#include "content/common/notification_registrar.h" 37#include "content/common/notification_service.h" 38#include "content/common/notification_source.h" 39#include "content/common/notification_type.h" 40#include "content/common/page_transition_types.h" 41#include "content/common/renderer_preferences.h" 42#include "net/http/http_util.h" 43#include "ui/base/l10n/l10n_util.h" 44#include "ui/gfx/codec/png_codec.h" 45 46namespace { 47 48// Number of ms to delay before updating the omnibox bounds. This is only used 49// when the bounds of the omnibox shrinks. If the bounds grows, we update 50// immediately. 51const int kUpdateBoundsDelayMS = 1000; 52 53// If this status code is seen instant is disabled for the specified host. 54const int kHostBlacklistStatusCode = 403; 55 56// Header and value set for all loads. 57const char kPreviewHeader[] = "X-Purpose:"; 58const char kPreviewHeaderValue[] = "preview"; 59 60} // namespace 61 62// FrameLoadObserver is responsible for determining if the page supports 63// instant after it has loaded. 64class InstantLoader::FrameLoadObserver : public NotificationObserver { 65 public: 66 FrameLoadObserver(InstantLoader* loader, 67 TabContents* tab_contents, 68 const string16& text, 69 bool verbatim) 70 : loader_(loader), 71 tab_contents_(tab_contents), 72 text_(text), 73 verbatim_(verbatim), 74 unique_id_(tab_contents_->controller().pending_entry()->unique_id()) { 75 registrar_.Add(this, NotificationType::LOAD_COMPLETED_MAIN_FRAME, 76 Source<TabContents>(tab_contents_)); 77 } 78 79 // Sets the text to send to the page. 80 void set_text(const string16& text) { text_ = text; } 81 82 // Sets whether verbatim results are obtained rather than predictive. 83 void set_verbatim(bool verbatim) { verbatim_ = verbatim; } 84 85 // NotificationObserver: 86 virtual void Observe(NotificationType type, 87 const NotificationSource& source, 88 const NotificationDetails& details) OVERRIDE; 89 90 private: 91 InstantLoader* loader_; 92 93 // The TabContents we're listening for changes on. 94 TabContents* tab_contents_; 95 96 // Text to send down to the page. 97 string16 text_; 98 99 // Whether verbatim results are obtained. 100 bool verbatim_; 101 102 // unique_id of the NavigationEntry we're waiting on. 103 const int unique_id_; 104 105 // Registers and unregisters us for notifications. 106 NotificationRegistrar registrar_; 107 108 DISALLOW_COPY_AND_ASSIGN(FrameLoadObserver); 109}; 110 111void InstantLoader::FrameLoadObserver::Observe( 112 NotificationType type, 113 const NotificationSource& source, 114 const NotificationDetails& details) { 115 switch (type.value) { 116 case NotificationType::LOAD_COMPLETED_MAIN_FRAME: { 117 int page_id = *(Details<int>(details).ptr()); 118 NavigationEntry* active_entry = 119 tab_contents_->controller().GetActiveEntry(); 120 if (!active_entry || active_entry->page_id() != page_id || 121 active_entry->unique_id() != unique_id_) { 122 return; 123 } 124 loader_->SendBoundsToPage(true); 125 // TODO: support real cursor position. 126 int text_length = static_cast<int>(text_.size()); 127 tab_contents_->render_view_host()->DetermineIfPageSupportsInstant( 128 text_, verbatim_, text_length, text_length); 129 break; 130 } 131 default: 132 NOTREACHED(); 133 break; 134 } 135} 136 137// TabContentsDelegateImpl ----------------------------------------------------- 138 139class InstantLoader::TabContentsDelegateImpl 140 : public TabContentsDelegate, 141 public NotificationObserver, 142 public TabContentsObserver { 143 public: 144 explicit TabContentsDelegateImpl(InstantLoader* loader); 145 146 // Invoked prior to loading a new URL. 147 void PrepareForNewLoad(); 148 149 // Invoked when the preview paints. Invokes PreviewPainted on the loader. 150 void PreviewPainted(); 151 152 bool is_mouse_down_from_activate() const { 153 return is_mouse_down_from_activate_; 154 } 155 156 void set_user_typed_before_load() { user_typed_before_load_ = true; } 157 158 // Sets the last URL that will be added to history when CommitHistory is 159 // invoked and removes all but the first navigation. 160 void SetLastHistoryURLAndPrune(const GURL& url); 161 162 // Commits the currently buffered history. 163 void CommitHistory(bool supports_instant); 164 165 void RegisterForPaintNotifications(RenderWidgetHost* render_widget_host); 166 167 void UnregisterForPaintNotifications(); 168 169 // NotificationObserver: 170 virtual void Observe(NotificationType type, 171 const NotificationSource& source, 172 const NotificationDetails& details) OVERRIDE; 173 174 // TabContentsDelegate: 175 virtual void OpenURLFromTab(TabContents* source, 176 const GURL& url, const GURL& referrer, 177 WindowOpenDisposition disposition, 178 PageTransition::Type transition) OVERRIDE; 179 virtual void NavigationStateChanged(const TabContents* source, 180 unsigned changed_flags) OVERRIDE; 181 virtual std::string GetNavigationHeaders(const GURL& url) OVERRIDE; 182 virtual void AddNewContents(TabContents* source, 183 TabContents* new_contents, 184 WindowOpenDisposition disposition, 185 const gfx::Rect& initial_pos, 186 bool user_gesture) OVERRIDE; 187 virtual void ActivateContents(TabContents* contents) OVERRIDE; 188 virtual void DeactivateContents(TabContents* contents) OVERRIDE; 189 virtual void LoadingStateChanged(TabContents* source) OVERRIDE; 190 virtual void CloseContents(TabContents* source) OVERRIDE; 191 virtual void MoveContents(TabContents* source, 192 const gfx::Rect& pos) OVERRIDE; 193 virtual bool ShouldFocusConstrainedWindow() OVERRIDE; 194 virtual void WillShowConstrainedWindow(TabContents* source) OVERRIDE; 195 virtual void UpdateTargetURL(TabContents* source, 196 const GURL& url) OVERRIDE; 197 virtual bool ShouldSuppressDialogs() OVERRIDE; 198 virtual void BeforeUnloadFired(TabContents* tab, 199 bool proceed, 200 bool* proceed_to_fire_unload) OVERRIDE; 201 virtual void SetFocusToLocationBar(bool select_all) OVERRIDE; 202 virtual bool ShouldFocusPageAfterCrash() OVERRIDE; 203 virtual void LostCapture() OVERRIDE; 204 // If the user drags, we won't get a mouse up (at least on Linux). Commit the 205 // instant result when the drag ends, so that during the drag the page won't 206 // move around. 207 virtual void DragEnded() OVERRIDE; 208 virtual bool CanDownload(int request_id) OVERRIDE; 209 virtual void HandleMouseUp() OVERRIDE; 210 virtual void HandleMouseActivate() OVERRIDE; 211 virtual bool OnGoToEntryOffset(int offset) OVERRIDE; 212 virtual bool ShouldAddNavigationToHistory( 213 const history::HistoryAddPageArgs& add_page_args, 214 NavigationType::Type navigation_type) OVERRIDE; 215 virtual bool ShouldShowHungRendererDialog() OVERRIDE; 216 217 // TabContentsObserver: 218 virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; 219 220 private: 221 typedef std::vector<scoped_refptr<history::HistoryAddPageArgs> > 222 AddPageVector; 223 224 // Message from renderer indicating the page has suggestions. 225 void OnSetSuggestions( 226 int32 page_id, 227 const std::vector<std::string>& suggestions, 228 InstantCompleteBehavior behavior); 229 230 // Messages from the renderer when we've determined whether the page supports 231 // instant. 232 void OnInstantSupportDetermined(int32 page_id, bool result); 233 234 void CommitFromMouseReleaseIfNecessary(); 235 236 InstantLoader* loader_; 237 238 NotificationRegistrar registrar_; 239 240 // If we are registered for paint notifications on a RenderWidgetHost this 241 // will contain a pointer to it. 242 RenderWidgetHost* registered_render_widget_host_; 243 244 // Used to cache data that needs to be added to history. Normally entries are 245 // added to history as the user types, but for instant we only want to add the 246 // items to history if the user commits instant. So, we cache them here and if 247 // committed then add the items to history. 248 AddPageVector add_page_vector_; 249 250 // Are we we waiting for a NavigationType of NEW_PAGE? If we're waiting for 251 // NEW_PAGE navigation we don't add history items to add_page_vector_. 252 bool waiting_for_new_page_; 253 254 // True if the mouse is down from an activate. 255 bool is_mouse_down_from_activate_; 256 257 // True if the user typed in the search box before the page loaded. 258 bool user_typed_before_load_; 259 260 DISALLOW_COPY_AND_ASSIGN(TabContentsDelegateImpl); 261}; 262 263InstantLoader::TabContentsDelegateImpl::TabContentsDelegateImpl( 264 InstantLoader* loader) 265 : TabContentsObserver(loader->preview_contents()->tab_contents()), 266 loader_(loader), 267 registered_render_widget_host_(NULL), 268 waiting_for_new_page_(true), 269 is_mouse_down_from_activate_(false), 270 user_typed_before_load_(false) { 271 DCHECK(loader->preview_contents()); 272 registrar_.Add(this, NotificationType::INTERSTITIAL_ATTACHED, 273 Source<TabContents>(loader->preview_contents()->tab_contents())); 274 registrar_.Add(this, NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR, 275 Source<NavigationController>(&loader->preview_contents()->controller())); 276} 277 278void InstantLoader::TabContentsDelegateImpl::PrepareForNewLoad() { 279 user_typed_before_load_ = false; 280 waiting_for_new_page_ = true; 281 add_page_vector_.clear(); 282 UnregisterForPaintNotifications(); 283} 284 285void InstantLoader::TabContentsDelegateImpl::PreviewPainted() { 286 loader_->PreviewPainted(); 287} 288 289void InstantLoader::TabContentsDelegateImpl::SetLastHistoryURLAndPrune( 290 const GURL& url) { 291 if (add_page_vector_.empty()) 292 return; 293 294 history::HistoryAddPageArgs* args = add_page_vector_.front().get(); 295 args->url = url; 296 args->redirects.clear(); 297 args->redirects.push_back(url); 298 299 // Prune all but the first entry. 300 add_page_vector_.erase(add_page_vector_.begin() + 1, 301 add_page_vector_.end()); 302} 303 304void InstantLoader::TabContentsDelegateImpl::CommitHistory( 305 bool supports_instant) { 306 TabContents* tab = loader_->preview_contents()->tab_contents(); 307 if (tab->profile()->IsOffTheRecord()) 308 return; 309 310 for (size_t i = 0; i < add_page_vector_.size(); ++i) 311 tab->UpdateHistoryForNavigation(add_page_vector_[i].get()); 312 313 NavigationEntry* active_entry = tab->controller().GetActiveEntry(); 314 if (!active_entry) { 315 // It appears to be possible to get here with no active entry. This seems 316 // to be possible with an auth dialog, but I can't narrow down the 317 // circumstances. If you hit this, file a bug with the steps you did and 318 // assign it to me (sky). 319 NOTREACHED(); 320 return; 321 } 322 tab->UpdateHistoryPageTitle(*active_entry); 323 324 FaviconService* favicon_service = 325 tab->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS); 326 327 if (favicon_service && active_entry->favicon().is_valid() && 328 !active_entry->favicon().bitmap().empty()) { 329 std::vector<unsigned char> image_data; 330 gfx::PNGCodec::EncodeBGRASkBitmap(active_entry->favicon().bitmap(), false, 331 &image_data); 332 favicon_service->SetFavicon(active_entry->url(), 333 active_entry->favicon().url(), 334 image_data, 335 history::FAVICON); 336 if (supports_instant && !add_page_vector_.empty()) { 337 // If we're using the instant API, then we've tweaked the url that is 338 // going to be added to history. We need to also set the favicon for the 339 // url we're adding to history (see comment in ReleasePreviewContents 340 // for details). 341 favicon_service->SetFavicon(add_page_vector_.back()->url, 342 active_entry->favicon().url(), 343 image_data, 344 history::FAVICON); 345 } 346 } 347} 348 349void InstantLoader::TabContentsDelegateImpl::RegisterForPaintNotifications( 350 RenderWidgetHost* render_widget_host) { 351 DCHECK(registered_render_widget_host_ == NULL); 352 registered_render_widget_host_ = render_widget_host; 353 Source<RenderWidgetHost> source = 354 Source<RenderWidgetHost>(registered_render_widget_host_); 355 registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DID_PAINT, 356 source); 357 registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED, 358 source); 359} 360 361void InstantLoader::TabContentsDelegateImpl::UnregisterForPaintNotifications() { 362 if (registered_render_widget_host_) { 363 Source<RenderWidgetHost> source = 364 Source<RenderWidgetHost>(registered_render_widget_host_); 365 registrar_.Remove(this, NotificationType::RENDER_WIDGET_HOST_DID_PAINT, 366 source); 367 registrar_.Remove(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED, 368 source); 369 registered_render_widget_host_ = NULL; 370 } 371} 372 373void InstantLoader::TabContentsDelegateImpl::Observe( 374 NotificationType type, 375 const NotificationSource& source, 376 const NotificationDetails& details) { 377 switch (type.value) { 378 case NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR: 379 if (Details<ProvisionalLoadDetails>(details)->url() == loader_->url_) { 380 // This typically happens with downloads (which are disabled with 381 // instant active). To ensure the download happens when the user presses 382 // enter we set needs_reload_ to true, which triggers a reload. 383 loader_->needs_reload_ = true; 384 } 385 break; 386 case NotificationType::RENDER_WIDGET_HOST_DID_PAINT: 387 UnregisterForPaintNotifications(); 388 PreviewPainted(); 389 break; 390 case NotificationType::RENDER_WIDGET_HOST_DESTROYED: 391 UnregisterForPaintNotifications(); 392 break; 393 case NotificationType::INTERSTITIAL_ATTACHED: 394 PreviewPainted(); 395 break; 396 default: 397 NOTREACHED() << "Got a notification we didn't register for."; 398 } 399} 400 401void InstantLoader::TabContentsDelegateImpl::OpenURLFromTab( 402 TabContents* source, 403 const GURL& url, const GURL& referrer, 404 WindowOpenDisposition disposition, 405 PageTransition::Type transition) { 406} 407 408void InstantLoader::TabContentsDelegateImpl::NavigationStateChanged( 409 const TabContents* source, 410 unsigned changed_flags) { 411 if (!loader_->ready() && !registered_render_widget_host_ && 412 source->controller().entry_count()) { 413 // The load has been committed. Install an observer that waits for the 414 // first paint then makes the preview active. We wait for the load to be 415 // committed before waiting on paint as there is always an initial paint 416 // when a new renderer is created from the resize so that if we showed the 417 // preview after the first paint we would end up with a white rect. 418 RenderWidgetHostView *rwhv = source->GetRenderWidgetHostView(); 419 if (rwhv) 420 RegisterForPaintNotifications(rwhv->GetRenderWidgetHost()); 421 } else if (source->is_crashed()) { 422 PreviewPainted(); 423 } 424} 425 426std::string InstantLoader::TabContentsDelegateImpl::GetNavigationHeaders( 427 const GURL& url) { 428 std::string header; 429 net::HttpUtil::AppendHeaderIfMissing(kPreviewHeader, kPreviewHeaderValue, 430 &header); 431 return header; 432} 433 434void InstantLoader::TabContentsDelegateImpl::AddNewContents( 435 TabContents* source, 436 TabContents* new_contents, 437 WindowOpenDisposition disposition, 438 const gfx::Rect& initial_pos, 439 bool user_gesture) { 440} 441 442void InstantLoader::TabContentsDelegateImpl::ActivateContents( 443 TabContents* contents) { 444} 445 446void InstantLoader::TabContentsDelegateImpl::DeactivateContents( 447 TabContents* contents) { 448} 449 450void InstantLoader::TabContentsDelegateImpl::LoadingStateChanged( 451 TabContents* source) { 452} 453 454void InstantLoader::TabContentsDelegateImpl::CloseContents( 455 TabContents* source) { 456} 457 458void InstantLoader::TabContentsDelegateImpl::MoveContents( 459 TabContents* source, 460 const gfx::Rect& pos) { 461} 462 463bool InstantLoader::TabContentsDelegateImpl::ShouldFocusConstrainedWindow() { 464 // Return false so that constrained windows are not initially focused. If 465 // we did otherwise the preview would prematurely get committed when focus 466 // goes to the constrained window. 467 return false; 468} 469 470void InstantLoader::TabContentsDelegateImpl::WillShowConstrainedWindow( 471 TabContents* source) { 472 if (!loader_->ready()) { 473 // A constrained window shown for an auth may not paint. Show the preview 474 // contents. 475 UnregisterForPaintNotifications(); 476 loader_->ShowPreview(); 477 } 478} 479 480void InstantLoader::TabContentsDelegateImpl::UpdateTargetURL( 481 TabContents* source, const GURL& url) { 482} 483 484bool InstantLoader::TabContentsDelegateImpl::ShouldSuppressDialogs() { 485 // Any message shown during instant cancels instant, so we suppress them. 486 return true; 487} 488 489void InstantLoader::TabContentsDelegateImpl::BeforeUnloadFired( 490 TabContents* tab, 491 bool proceed, 492 bool* proceed_to_fire_unload) { 493} 494 495void InstantLoader::TabContentsDelegateImpl::SetFocusToLocationBar( 496 bool select_all) { 497} 498 499bool InstantLoader::TabContentsDelegateImpl::ShouldFocusPageAfterCrash() { 500 return false; 501} 502 503void InstantLoader::TabContentsDelegateImpl::LostCapture() { 504 CommitFromMouseReleaseIfNecessary(); 505} 506 507void InstantLoader::TabContentsDelegateImpl::DragEnded() { 508 CommitFromMouseReleaseIfNecessary(); 509} 510 511bool InstantLoader::TabContentsDelegateImpl::CanDownload(int request_id) { 512 // Downloads are disabled. 513 return false; 514} 515 516void InstantLoader::TabContentsDelegateImpl::HandleMouseUp() { 517 CommitFromMouseReleaseIfNecessary(); 518} 519 520void InstantLoader::TabContentsDelegateImpl::HandleMouseActivate() { 521 is_mouse_down_from_activate_ = true; 522} 523 524bool InstantLoader::TabContentsDelegateImpl::OnGoToEntryOffset(int offset) { 525 return false; 526} 527 528bool InstantLoader::TabContentsDelegateImpl::ShouldAddNavigationToHistory( 529 const history::HistoryAddPageArgs& add_page_args, 530 NavigationType::Type navigation_type) { 531 if (waiting_for_new_page_ && navigation_type == NavigationType::NEW_PAGE) 532 waiting_for_new_page_ = false; 533 534 if (!waiting_for_new_page_) { 535 add_page_vector_.push_back( 536 scoped_refptr<history::HistoryAddPageArgs>(add_page_args.Clone())); 537 } 538 return false; 539} 540 541bool InstantLoader::TabContentsDelegateImpl::ShouldShowHungRendererDialog() { 542 // If we allow the hung renderer dialog to be shown it'll gain focus, 543 // stealing focus from the omnibox causing instant to be cancelled. Return 544 // false so that doesn't happen. 545 return false; 546} 547 548bool InstantLoader::TabContentsDelegateImpl::OnMessageReceived( 549 const IPC::Message& message) { 550 bool handled = true; 551 IPC_BEGIN_MESSAGE_MAP(TabContentsDelegateImpl, message) 552 IPC_MESSAGE_HANDLER(ViewHostMsg_SetSuggestions, OnSetSuggestions) 553 IPC_MESSAGE_HANDLER(ViewHostMsg_InstantSupportDetermined, 554 OnInstantSupportDetermined) 555 IPC_MESSAGE_UNHANDLED(handled = false) 556 IPC_END_MESSAGE_MAP() 557 return handled; 558} 559 560void InstantLoader::TabContentsDelegateImpl::OnSetSuggestions( 561 int32 page_id, 562 const std::vector<std::string>& suggestions, 563 InstantCompleteBehavior behavior) { 564 TabContentsWrapper* source = loader_->preview_contents(); 565 if (!source->controller().GetActiveEntry() || 566 page_id != source->controller().GetActiveEntry()->page_id()) 567 return; 568 569 if (suggestions.empty()) 570 loader_->SetCompleteSuggestedText(string16(), behavior); 571 else 572 loader_->SetCompleteSuggestedText(UTF8ToUTF16(suggestions[0]), behavior); 573} 574 575void InstantLoader::TabContentsDelegateImpl::OnInstantSupportDetermined( 576 int32 page_id, 577 bool result) { 578 TabContents* source = loader_->preview_contents()->tab_contents(); 579 if (!source->controller().GetActiveEntry() || 580 page_id != source->controller().GetActiveEntry()->page_id()) 581 return; 582 583 Details<const bool> details(&result); 584 NotificationService::current()->Notify( 585 NotificationType::INSTANT_SUPPORT_DETERMINED, 586 NotificationService::AllSources(), 587 details); 588 589 if (result) 590 loader_->PageFinishedLoading(); 591 else 592 loader_->PageDoesntSupportInstant(user_typed_before_load_); 593} 594 595void InstantLoader::TabContentsDelegateImpl 596 ::CommitFromMouseReleaseIfNecessary() { 597 bool was_down = is_mouse_down_from_activate_; 598 is_mouse_down_from_activate_ = false; 599 if (was_down && loader_->ShouldCommitInstantOnMouseUp()) 600 loader_->CommitInstantLoader(); 601} 602 603// InstantLoader --------------------------------------------------------------- 604 605InstantLoader::InstantLoader(InstantLoaderDelegate* delegate, TemplateURLID id) 606 : delegate_(delegate), 607 template_url_id_(id), 608 ready_(false), 609 http_status_ok_(true), 610 last_transition_type_(PageTransition::LINK), 611 verbatim_(false), 612 needs_reload_(false) { 613} 614 615InstantLoader::~InstantLoader() { 616 registrar_.RemoveAll(); 617 618 // Delete the TabContents before the delegate as the TabContents holds a 619 // reference to the delegate. 620 preview_contents_.reset(); 621} 622 623bool InstantLoader::Update(TabContentsWrapper* tab_contents, 624 const TemplateURL* template_url, 625 const GURL& url, 626 PageTransition::Type transition_type, 627 const string16& user_text, 628 bool verbatim, 629 string16* suggested_text) { 630 DCHECK(!url.is_empty() && url.is_valid()); 631 632 // Strip leading ?. 633 string16 new_user_text = 634 !user_text.empty() && (UTF16ToWide(user_text)[0] == L'?') ? 635 user_text.substr(1) : user_text; 636 637 // We should preserve the transition type regardless of whether we're already 638 // showing the url. 639 last_transition_type_ = transition_type; 640 641 // If state hasn't changed, reuse the last suggestion. There are two cases: 642 // 1. If no template url (not using instant API), then we only care if the url 643 // changes. 644 // 2. Template url (using instant API) then the important part is if the 645 // user_text changes. 646 // We have to be careful in checking user_text as in some situations 647 // InstantController passes in an empty string (when it knows the user_text 648 // won't matter). 649 if ((!template_url_id_ && url_ == url) || 650 (template_url_id_ && 651 (new_user_text.empty() || user_text_ == new_user_text))) { 652 suggested_text->assign(last_suggestion_); 653 // Track the url even if we're not going to update. This is important as 654 // when we get the suggest text we set user_text_ to the new suggest text, 655 // but yet the url is much different. 656 url_ = url; 657 return false; 658 } 659 660 url_ = url; 661 user_text_ = new_user_text; 662 verbatim_ = verbatim; 663 last_suggestion_.clear(); 664 needs_reload_ = false; 665 666 bool created_preview_contents = preview_contents_.get() == NULL; 667 if (created_preview_contents) 668 CreatePreviewContents(tab_contents); 669 670 if (template_url) { 671 DCHECK(template_url_id_ == template_url->id()); 672 if (!created_preview_contents) { 673 if (is_waiting_for_load()) { 674 // The page hasn't loaded yet. We'll send the script down when it does. 675 frame_load_observer_->set_text(user_text_); 676 frame_load_observer_->set_verbatim(verbatim); 677 preview_tab_contents_delegate_->set_user_typed_before_load(); 678 return true; 679 } 680 // TODO: support real cursor position. 681 int text_length = static_cast<int>(user_text_.size()); 682 preview_contents_->render_view_host()->SearchBoxChange( 683 user_text_, verbatim, text_length, text_length); 684 685 string16 complete_suggested_text_lower = l10n_util::ToLower( 686 complete_suggested_text_); 687 string16 user_text_lower = l10n_util::ToLower(user_text_); 688 if (!verbatim && 689 complete_suggested_text_lower.size() > user_text_lower.size() && 690 !complete_suggested_text_lower.compare(0, user_text_lower.size(), 691 user_text_lower)) { 692 *suggested_text = last_suggestion_ = 693 complete_suggested_text_.substr(user_text_.size()); 694 } 695 } else { 696 preview_tab_contents_delegate_->PrepareForNewLoad(); 697 698 // Load the instant URL. We don't reflect the url we load in url() as 699 // callers expect that we're loading the URL they tell us to. 700 // 701 // This uses an empty string for the replacement text as the url doesn't 702 // really have the search params, but we need to use the replace 703 // functionality so that embeded tags (like {google:baseURL}) are escaped 704 // correctly. 705 // TODO(sky): having to use a replaceable url is a bit of a hack here. 706 GURL instant_url( 707 template_url->instant_url()->ReplaceSearchTerms( 708 *template_url, string16(), -1, string16())); 709 CommandLine* cl = CommandLine::ForCurrentProcess(); 710 if (cl->HasSwitch(switches::kInstantURL)) 711 instant_url = GURL(cl->GetSwitchValueASCII(switches::kInstantURL)); 712 preview_contents_->controller().LoadURL( 713 instant_url, GURL(), transition_type); 714 preview_contents_->render_view_host()->SearchBoxChange( 715 user_text_, verbatim, 0, 0); 716 frame_load_observer_.reset( 717 new FrameLoadObserver(this, 718 preview_contents()->tab_contents(), 719 user_text_, 720 verbatim)); 721 } 722 } else { 723 DCHECK(template_url_id_ == 0); 724 preview_tab_contents_delegate_->PrepareForNewLoad(); 725 frame_load_observer_.reset(NULL); 726 preview_contents_->controller().LoadURL(url_, GURL(), transition_type); 727 } 728 return true; 729} 730 731void InstantLoader::SetOmniboxBounds(const gfx::Rect& bounds) { 732 if (omnibox_bounds_ == bounds) 733 return; 734 735 // Don't update the page while the mouse is down. http://crbug.com/71952 736 if (IsMouseDownFromActivate()) 737 return; 738 739 omnibox_bounds_ = bounds; 740 if (preview_contents_.get() && is_showing_instant() && 741 !is_waiting_for_load()) { 742 // Updating the bounds is rather expensive, and because of the async nature 743 // of the omnibox the bounds can dance around a bit. Delay the update in 744 // hopes of things settling down. To avoid hiding results we grow 745 // immediately, but delay shrinking. 746 update_bounds_timer_.Stop(); 747 if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) { 748 SendBoundsToPage(false); 749 } else { 750 update_bounds_timer_.Start( 751 base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), 752 this, &InstantLoader::ProcessBoundsChange); 753 } 754 } 755} 756 757bool InstantLoader::IsMouseDownFromActivate() { 758 return preview_tab_contents_delegate_.get() && 759 preview_tab_contents_delegate_->is_mouse_down_from_activate(); 760} 761 762TabContentsWrapper* InstantLoader::ReleasePreviewContents( 763 InstantCommitType type) { 764 if (!preview_contents_.get()) 765 return NULL; 766 767 // FrameLoadObserver is only used for instant results, and instant results are 768 // only committed if active (when the FrameLoadObserver isn't installed). 769 DCHECK(type == INSTANT_COMMIT_DESTROY || !frame_load_observer_.get()); 770 771 if (type != INSTANT_COMMIT_DESTROY && is_showing_instant()) { 772 if (type == INSTANT_COMMIT_FOCUS_LOST) 773 preview_contents_->render_view_host()->SearchBoxCancel(); 774 else 775 preview_contents_->render_view_host()->SearchBoxSubmit( 776 user_text_, type == INSTANT_COMMIT_PRESSED_ENTER); 777 } 778 omnibox_bounds_ = gfx::Rect(); 779 last_omnibox_bounds_ = gfx::Rect(); 780 GURL url; 781 url.Swap(&url_); 782 user_text_.clear(); 783 complete_suggested_text_.clear(); 784 if (preview_contents_.get()) { 785 if (type != INSTANT_COMMIT_DESTROY) { 786 if (template_url_id_) { 787 // The URL used during instant is mostly gibberish, and not something 788 // we'll parse and match as a past search. Set it to something we can 789 // parse. 790 preview_tab_contents_delegate_->SetLastHistoryURLAndPrune(url); 791 } 792 preview_tab_contents_delegate_->CommitHistory(template_url_id_ != 0); 793 } 794 if (preview_contents_->tab_contents()->GetRenderWidgetHostView()) { 795#if defined(OS_MACOSX) 796 preview_contents_->tab_contents()->GetRenderWidgetHostView()-> 797 SetTakesFocusOnlyOnMouseDown(false); 798 registrar_.Remove( 799 this, 800 NotificationType::RENDER_VIEW_HOST_CHANGED, 801 Source<NavigationController>(&preview_contents_->controller())); 802#endif 803 } 804 preview_contents_->tab_contents()->set_delegate(NULL); 805 ready_ = false; 806 } 807 update_bounds_timer_.Stop(); 808 return preview_contents_.release(); 809} 810 811bool InstantLoader::ShouldCommitInstantOnMouseUp() { 812 return delegate_->ShouldCommitInstantOnMouseUp(); 813} 814 815void InstantLoader::CommitInstantLoader() { 816 delegate_->CommitInstantLoader(this); 817} 818 819void InstantLoader::SetCompleteSuggestedText( 820 const string16& complete_suggested_text, 821 InstantCompleteBehavior behavior) { 822 if (!is_showing_instant()) { 823 // We're not trying to use the instant API with this page. Ignore it. 824 return; 825 } 826 827 ShowPreview(); 828 829 if (complete_suggested_text == complete_suggested_text_) 830 return; 831 832 if (verbatim_) { 833 // Don't show suggest results for verbatim queries. 834 return; 835 } 836 837 string16 user_text_lower = l10n_util::ToLower(user_text_); 838 string16 complete_suggested_text_lower = l10n_util::ToLower( 839 complete_suggested_text); 840 last_suggestion_.clear(); 841 if (user_text_lower.compare(0, user_text_lower.size(), 842 complete_suggested_text_lower, 843 0, user_text_lower.size())) { 844 // The user text no longer contains the suggested text, ignore it. 845 complete_suggested_text_.clear(); 846 delegate_->SetSuggestedTextFor(this, string16(), behavior); 847 return; 848 } 849 850 complete_suggested_text_ = complete_suggested_text; 851 if (behavior == INSTANT_COMPLETE_NOW) { 852 // We are effectively showing complete_suggested_text_ now. Update 853 // user_text_ so we don't notify the page again if Update happens to be 854 // invoked (which is more than likely if this callback completes before the 855 // omnibox is done). 856 string16 suggestion = complete_suggested_text_.substr(user_text_.size()); 857 user_text_ = complete_suggested_text_; 858 delegate_->SetSuggestedTextFor(this, suggestion, behavior); 859 } else { 860 DCHECK((behavior == INSTANT_COMPLETE_DELAYED) || 861 (behavior == INSTANT_COMPLETE_NEVER)); 862 last_suggestion_ = complete_suggested_text_.substr(user_text_.size()); 863 delegate_->SetSuggestedTextFor(this, last_suggestion_, behavior); 864 } 865} 866 867void InstantLoader::PreviewPainted() { 868 // If instant is supported then we wait for the first suggest result before 869 // showing the page. 870 if (!template_url_id_) 871 ShowPreview(); 872} 873 874void InstantLoader::SetHTTPStatusOK(bool is_ok) { 875 if (is_ok == http_status_ok_) 876 return; 877 878 http_status_ok_ = is_ok; 879 if (ready_) 880 delegate_->InstantStatusChanged(this); 881} 882 883void InstantLoader::ShowPreview() { 884 if (!ready_) { 885 ready_ = true; 886 delegate_->InstantStatusChanged(this); 887 } 888} 889 890void InstantLoader::Observe(NotificationType type, 891 const NotificationSource& source, 892 const NotificationDetails& details) { 893#if defined(OS_MACOSX) 894 if (type.value == NotificationType::RENDER_VIEW_HOST_CHANGED) { 895 if (preview_contents_->tab_contents()->GetRenderWidgetHostView()) { 896 preview_contents_->tab_contents()->GetRenderWidgetHostView()-> 897 SetTakesFocusOnlyOnMouseDown(true); 898 } 899 return; 900 } 901#endif 902 if (type.value == NotificationType::NAV_ENTRY_COMMITTED) { 903 NavigationController::LoadCommittedDetails* load_details = 904 Details<NavigationController::LoadCommittedDetails>(details).ptr(); 905 if (load_details->is_main_frame) { 906 if (load_details->http_status_code == kHostBlacklistStatusCode) { 907 delegate_->AddToBlacklist(this, load_details->entry->url()); 908 } else { 909 SetHTTPStatusOK(load_details->http_status_code == 200); 910 } 911 } 912 return; 913 } 914 915 NOTREACHED() << "Got a notification we didn't register for."; 916} 917 918void InstantLoader::PageFinishedLoading() { 919 frame_load_observer_.reset(); 920 921 // Send the bounds of the omnibox down now. 922 SendBoundsToPage(false); 923 924 // Wait for the user input before showing, this way the page should be up to 925 // date by the time we show it. 926} 927 928// TODO(tonyg): This method only fires when the omnibox bounds change. It also 929// needs to fire when the preview bounds change (e.g. open/close info bar). 930gfx::Rect InstantLoader::GetOmniboxBoundsInTermsOfPreview() { 931 gfx::Rect preview_bounds(delegate_->GetInstantBounds()); 932 gfx::Rect intersection(omnibox_bounds_.Intersect(preview_bounds)); 933 934 // Translate into window's coordinates. 935 if (!intersection.IsEmpty()) { 936 intersection.Offset(-preview_bounds.origin().x(), 937 -preview_bounds.origin().y()); 938 } 939 940 // In the current Chrome UI, these must always be true so they sanity check 941 // the above operations. In a future UI, these may be removed or adjusted. 942 DCHECK_EQ(0, intersection.y()); 943 DCHECK_LE(0, intersection.x()); 944 DCHECK_LE(0, intersection.width()); 945 DCHECK_LE(0, intersection.height()); 946 947 return intersection; 948} 949 950void InstantLoader::PageDoesntSupportInstant(bool needs_reload) { 951 frame_load_observer_.reset(NULL); 952 953 delegate_->InstantLoaderDoesntSupportInstant(this); 954} 955 956void InstantLoader::ProcessBoundsChange() { 957 SendBoundsToPage(false); 958} 959 960void InstantLoader::SendBoundsToPage(bool force_if_waiting) { 961 if (last_omnibox_bounds_ == omnibox_bounds_) 962 return; 963 964 if (preview_contents_.get() && is_showing_instant() && 965 (force_if_waiting || !is_waiting_for_load())) { 966 last_omnibox_bounds_ = omnibox_bounds_; 967 preview_contents_->render_view_host()->SearchBoxResize( 968 GetOmniboxBoundsInTermsOfPreview()); 969 } 970} 971 972void InstantLoader::CreatePreviewContents(TabContentsWrapper* tab_contents) { 973 TabContents* new_contents = 974 new TabContents( 975 tab_contents->profile(), NULL, MSG_ROUTING_NONE, NULL, NULL); 976 preview_contents_.reset(new TabContentsWrapper(new_contents)); 977 new_contents->SetAllContentsBlocked(true); 978 // Propagate the max page id. That way if we end up merging the two 979 // NavigationControllers (which happens if we commit) none of the page ids 980 // will overlap. 981 int32 max_page_id = tab_contents->tab_contents()->GetMaxPageID(); 982 if (max_page_id != -1) 983 preview_contents_->controller().set_max_restored_page_id(max_page_id + 1); 984 985 preview_tab_contents_delegate_.reset(new TabContentsDelegateImpl(this)); 986 new_contents->set_delegate(preview_tab_contents_delegate_.get()); 987 988 gfx::Rect tab_bounds; 989 tab_contents->view()->GetContainerBounds(&tab_bounds); 990 preview_contents_->view()->SizeContents(tab_bounds.size()); 991 992#if defined(OS_MACOSX) 993 // If |preview_contents_| does not currently have a RWHV, we will call 994 // SetTakesFocusOnlyOnMouseDown() as a result of the 995 // RENDER_VIEW_HOST_CHANGED notification. 996 if (preview_contents_->tab_contents()->GetRenderWidgetHostView()) { 997 preview_contents_->tab_contents()->GetRenderWidgetHostView()-> 998 SetTakesFocusOnlyOnMouseDown(true); 999 } 1000 registrar_.Add( 1001 this, 1002 NotificationType::RENDER_VIEW_HOST_CHANGED, 1003 Source<NavigationController>(&preview_contents_->controller())); 1004#endif 1005 1006 registrar_.Add( 1007 this, 1008 NotificationType::NAV_ENTRY_COMMITTED, 1009 Source<NavigationController>(&preview_contents_->controller())); 1010 1011 preview_contents_->tab_contents()->ShowContents(); 1012} 1013