// Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "chrome/browser/instant/instant_loader.h" #include <algorithm> #include <string> #include <utility> #include <vector> #include "base/command_line.h" #include "base/string_number_conversions.h" #include "base/timer.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/browser/favicon_service.h" #include "chrome/browser/history/history_marshaling.h" #include "chrome/browser/instant/instant_loader_delegate.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/search_engines/template_url.h" #include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/render_messages.h" #include "content/browser/renderer_host/render_view_host.h" #include "content/browser/renderer_host/render_widget_host.h" #include "content/browser/renderer_host/render_widget_host_view.h" #include "content/browser/tab_contents/navigation_controller.h" #include "content/browser/tab_contents/navigation_entry.h" #include "content/browser/tab_contents/provisional_load_details.h" #include "content/browser/tab_contents/tab_contents.h" #include "content/browser/tab_contents/tab_contents_delegate.h" #include "content/browser/tab_contents/tab_contents_view.h" #include "content/common/notification_details.h" #include "content/common/notification_observer.h" #include "content/common/notification_registrar.h" #include "content/common/notification_service.h" #include "content/common/notification_source.h" #include "content/common/notification_type.h" #include "content/common/page_transition_types.h" #include "content/common/renderer_preferences.h" #include "net/http/http_util.h" #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/codec/png_codec.h" namespace { // Number of ms to delay before updating the omnibox bounds. This is only used // when the bounds of the omnibox shrinks. If the bounds grows, we update // immediately. const int kUpdateBoundsDelayMS = 1000; // If this status code is seen instant is disabled for the specified host. const int kHostBlacklistStatusCode = 403; // Header and value set for all loads. const char kPreviewHeader[] = "X-Purpose:"; const char kPreviewHeaderValue[] = "preview"; } // namespace // FrameLoadObserver is responsible for determining if the page supports // instant after it has loaded. class InstantLoader::FrameLoadObserver : public NotificationObserver { public: FrameLoadObserver(InstantLoader* loader, TabContents* tab_contents, const string16& text, bool verbatim) : loader_(loader), tab_contents_(tab_contents), text_(text), verbatim_(verbatim), unique_id_(tab_contents_->controller().pending_entry()->unique_id()) { registrar_.Add(this, NotificationType::LOAD_COMPLETED_MAIN_FRAME, Source<TabContents>(tab_contents_)); } // Sets the text to send to the page. void set_text(const string16& text) { text_ = text; } // Sets whether verbatim results are obtained rather than predictive. void set_verbatim(bool verbatim) { verbatim_ = verbatim; } // NotificationObserver: virtual void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) OVERRIDE; private: InstantLoader* loader_; // The TabContents we're listening for changes on. TabContents* tab_contents_; // Text to send down to the page. string16 text_; // Whether verbatim results are obtained. bool verbatim_; // unique_id of the NavigationEntry we're waiting on. const int unique_id_; // Registers and unregisters us for notifications. NotificationRegistrar registrar_; DISALLOW_COPY_AND_ASSIGN(FrameLoadObserver); }; void InstantLoader::FrameLoadObserver::Observe( NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::LOAD_COMPLETED_MAIN_FRAME: { int page_id = *(Details<int>(details).ptr()); NavigationEntry* active_entry = tab_contents_->controller().GetActiveEntry(); if (!active_entry || active_entry->page_id() != page_id || active_entry->unique_id() != unique_id_) { return; } loader_->SendBoundsToPage(true); // TODO: support real cursor position. int text_length = static_cast<int>(text_.size()); tab_contents_->render_view_host()->DetermineIfPageSupportsInstant( text_, verbatim_, text_length, text_length); break; } default: NOTREACHED(); break; } } // TabContentsDelegateImpl ----------------------------------------------------- class InstantLoader::TabContentsDelegateImpl : public TabContentsDelegate, public NotificationObserver, public TabContentsObserver { public: explicit TabContentsDelegateImpl(InstantLoader* loader); // Invoked prior to loading a new URL. void PrepareForNewLoad(); // Invoked when the preview paints. Invokes PreviewPainted on the loader. void PreviewPainted(); bool is_mouse_down_from_activate() const { return is_mouse_down_from_activate_; } void set_user_typed_before_load() { user_typed_before_load_ = true; } // Sets the last URL that will be added to history when CommitHistory is // invoked and removes all but the first navigation. void SetLastHistoryURLAndPrune(const GURL& url); // Commits the currently buffered history. void CommitHistory(bool supports_instant); void RegisterForPaintNotifications(RenderWidgetHost* render_widget_host); void UnregisterForPaintNotifications(); // NotificationObserver: virtual void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) OVERRIDE; // TabContentsDelegate: virtual void OpenURLFromTab(TabContents* source, const GURL& url, const GURL& referrer, WindowOpenDisposition disposition, PageTransition::Type transition) OVERRIDE; virtual void NavigationStateChanged(const TabContents* source, unsigned changed_flags) OVERRIDE; virtual std::string GetNavigationHeaders(const GURL& url) OVERRIDE; virtual void AddNewContents(TabContents* source, TabContents* new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture) OVERRIDE; virtual void ActivateContents(TabContents* contents) OVERRIDE; virtual void DeactivateContents(TabContents* contents) OVERRIDE; virtual void LoadingStateChanged(TabContents* source) OVERRIDE; virtual void CloseContents(TabContents* source) OVERRIDE; virtual void MoveContents(TabContents* source, const gfx::Rect& pos) OVERRIDE; virtual bool ShouldFocusConstrainedWindow() OVERRIDE; virtual void WillShowConstrainedWindow(TabContents* source) OVERRIDE; virtual void UpdateTargetURL(TabContents* source, const GURL& url) OVERRIDE; virtual bool ShouldSuppressDialogs() OVERRIDE; virtual void BeforeUnloadFired(TabContents* tab, bool proceed, bool* proceed_to_fire_unload) OVERRIDE; virtual void SetFocusToLocationBar(bool select_all) OVERRIDE; virtual bool ShouldFocusPageAfterCrash() OVERRIDE; virtual void LostCapture() OVERRIDE; // If the user drags, we won't get a mouse up (at least on Linux). Commit the // instant result when the drag ends, so that during the drag the page won't // move around. virtual void DragEnded() OVERRIDE; virtual bool CanDownload(int request_id) OVERRIDE; virtual void HandleMouseUp() OVERRIDE; virtual void HandleMouseActivate() OVERRIDE; virtual bool OnGoToEntryOffset(int offset) OVERRIDE; virtual bool ShouldAddNavigationToHistory( const history::HistoryAddPageArgs& add_page_args, NavigationType::Type navigation_type) OVERRIDE; virtual bool ShouldShowHungRendererDialog() OVERRIDE; // TabContentsObserver: virtual bool OnMessageReceived(const IPC::Message& message) OVERRIDE; private: typedef std::vector<scoped_refptr<history::HistoryAddPageArgs> > AddPageVector; // Message from renderer indicating the page has suggestions. void OnSetSuggestions( int32 page_id, const std::vector<std::string>& suggestions, InstantCompleteBehavior behavior); // Messages from the renderer when we've determined whether the page supports // instant. void OnInstantSupportDetermined(int32 page_id, bool result); void CommitFromMouseReleaseIfNecessary(); InstantLoader* loader_; NotificationRegistrar registrar_; // If we are registered for paint notifications on a RenderWidgetHost this // will contain a pointer to it. RenderWidgetHost* registered_render_widget_host_; // Used to cache data that needs to be added to history. Normally entries are // added to history as the user types, but for instant we only want to add the // items to history if the user commits instant. So, we cache them here and if // committed then add the items to history. AddPageVector add_page_vector_; // Are we we waiting for a NavigationType of NEW_PAGE? If we're waiting for // NEW_PAGE navigation we don't add history items to add_page_vector_. bool waiting_for_new_page_; // True if the mouse is down from an activate. bool is_mouse_down_from_activate_; // True if the user typed in the search box before the page loaded. bool user_typed_before_load_; DISALLOW_COPY_AND_ASSIGN(TabContentsDelegateImpl); }; InstantLoader::TabContentsDelegateImpl::TabContentsDelegateImpl( InstantLoader* loader) : TabContentsObserver(loader->preview_contents()->tab_contents()), loader_(loader), registered_render_widget_host_(NULL), waiting_for_new_page_(true), is_mouse_down_from_activate_(false), user_typed_before_load_(false) { DCHECK(loader->preview_contents()); registrar_.Add(this, NotificationType::INTERSTITIAL_ATTACHED, Source<TabContents>(loader->preview_contents()->tab_contents())); registrar_.Add(this, NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR, Source<NavigationController>(&loader->preview_contents()->controller())); } void InstantLoader::TabContentsDelegateImpl::PrepareForNewLoad() { user_typed_before_load_ = false; waiting_for_new_page_ = true; add_page_vector_.clear(); UnregisterForPaintNotifications(); } void InstantLoader::TabContentsDelegateImpl::PreviewPainted() { loader_->PreviewPainted(); } void InstantLoader::TabContentsDelegateImpl::SetLastHistoryURLAndPrune( const GURL& url) { if (add_page_vector_.empty()) return; history::HistoryAddPageArgs* args = add_page_vector_.front().get(); args->url = url; args->redirects.clear(); args->redirects.push_back(url); // Prune all but the first entry. add_page_vector_.erase(add_page_vector_.begin() + 1, add_page_vector_.end()); } void InstantLoader::TabContentsDelegateImpl::CommitHistory( bool supports_instant) { TabContents* tab = loader_->preview_contents()->tab_contents(); if (tab->profile()->IsOffTheRecord()) return; for (size_t i = 0; i < add_page_vector_.size(); ++i) tab->UpdateHistoryForNavigation(add_page_vector_[i].get()); NavigationEntry* active_entry = tab->controller().GetActiveEntry(); if (!active_entry) { // It appears to be possible to get here with no active entry. This seems // to be possible with an auth dialog, but I can't narrow down the // circumstances. If you hit this, file a bug with the steps you did and // assign it to me (sky). NOTREACHED(); return; } tab->UpdateHistoryPageTitle(*active_entry); FaviconService* favicon_service = tab->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS); if (favicon_service && active_entry->favicon().is_valid() && !active_entry->favicon().bitmap().empty()) { std::vector<unsigned char> image_data; gfx::PNGCodec::EncodeBGRASkBitmap(active_entry->favicon().bitmap(), false, &image_data); favicon_service->SetFavicon(active_entry->url(), active_entry->favicon().url(), image_data, history::FAVICON); if (supports_instant && !add_page_vector_.empty()) { // If we're using the instant API, then we've tweaked the url that is // going to be added to history. We need to also set the favicon for the // url we're adding to history (see comment in ReleasePreviewContents // for details). favicon_service->SetFavicon(add_page_vector_.back()->url, active_entry->favicon().url(), image_data, history::FAVICON); } } } void InstantLoader::TabContentsDelegateImpl::RegisterForPaintNotifications( RenderWidgetHost* render_widget_host) { DCHECK(registered_render_widget_host_ == NULL); registered_render_widget_host_ = render_widget_host; Source<RenderWidgetHost> source = Source<RenderWidgetHost>(registered_render_widget_host_); registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DID_PAINT, source); registrar_.Add(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED, source); } void InstantLoader::TabContentsDelegateImpl::UnregisterForPaintNotifications() { if (registered_render_widget_host_) { Source<RenderWidgetHost> source = Source<RenderWidgetHost>(registered_render_widget_host_); registrar_.Remove(this, NotificationType::RENDER_WIDGET_HOST_DID_PAINT, source); registrar_.Remove(this, NotificationType::RENDER_WIDGET_HOST_DESTROYED, source); registered_render_widget_host_ = NULL; } } void InstantLoader::TabContentsDelegateImpl::Observe( NotificationType type, const NotificationSource& source, const NotificationDetails& details) { switch (type.value) { case NotificationType::FAIL_PROVISIONAL_LOAD_WITH_ERROR: if (Details<ProvisionalLoadDetails>(details)->url() == loader_->url_) { // This typically happens with downloads (which are disabled with // instant active). To ensure the download happens when the user presses // enter we set needs_reload_ to true, which triggers a reload. loader_->needs_reload_ = true; } break; case NotificationType::RENDER_WIDGET_HOST_DID_PAINT: UnregisterForPaintNotifications(); PreviewPainted(); break; case NotificationType::RENDER_WIDGET_HOST_DESTROYED: UnregisterForPaintNotifications(); break; case NotificationType::INTERSTITIAL_ATTACHED: PreviewPainted(); break; default: NOTREACHED() << "Got a notification we didn't register for."; } } void InstantLoader::TabContentsDelegateImpl::OpenURLFromTab( TabContents* source, const GURL& url, const GURL& referrer, WindowOpenDisposition disposition, PageTransition::Type transition) { } void InstantLoader::TabContentsDelegateImpl::NavigationStateChanged( const TabContents* source, unsigned changed_flags) { if (!loader_->ready() && !registered_render_widget_host_ && source->controller().entry_count()) { // The load has been committed. Install an observer that waits for the // first paint then makes the preview active. We wait for the load to be // committed before waiting on paint as there is always an initial paint // when a new renderer is created from the resize so that if we showed the // preview after the first paint we would end up with a white rect. RenderWidgetHostView *rwhv = source->GetRenderWidgetHostView(); if (rwhv) RegisterForPaintNotifications(rwhv->GetRenderWidgetHost()); } else if (source->is_crashed()) { PreviewPainted(); } } std::string InstantLoader::TabContentsDelegateImpl::GetNavigationHeaders( const GURL& url) { std::string header; net::HttpUtil::AppendHeaderIfMissing(kPreviewHeader, kPreviewHeaderValue, &header); return header; } void InstantLoader::TabContentsDelegateImpl::AddNewContents( TabContents* source, TabContents* new_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, bool user_gesture) { } void InstantLoader::TabContentsDelegateImpl::ActivateContents( TabContents* contents) { } void InstantLoader::TabContentsDelegateImpl::DeactivateContents( TabContents* contents) { } void InstantLoader::TabContentsDelegateImpl::LoadingStateChanged( TabContents* source) { } void InstantLoader::TabContentsDelegateImpl::CloseContents( TabContents* source) { } void InstantLoader::TabContentsDelegateImpl::MoveContents( TabContents* source, const gfx::Rect& pos) { } bool InstantLoader::TabContentsDelegateImpl::ShouldFocusConstrainedWindow() { // Return false so that constrained windows are not initially focused. If // we did otherwise the preview would prematurely get committed when focus // goes to the constrained window. return false; } void InstantLoader::TabContentsDelegateImpl::WillShowConstrainedWindow( TabContents* source) { if (!loader_->ready()) { // A constrained window shown for an auth may not paint. Show the preview // contents. UnregisterForPaintNotifications(); loader_->ShowPreview(); } } void InstantLoader::TabContentsDelegateImpl::UpdateTargetURL( TabContents* source, const GURL& url) { } bool InstantLoader::TabContentsDelegateImpl::ShouldSuppressDialogs() { // Any message shown during instant cancels instant, so we suppress them. return true; } void InstantLoader::TabContentsDelegateImpl::BeforeUnloadFired( TabContents* tab, bool proceed, bool* proceed_to_fire_unload) { } void InstantLoader::TabContentsDelegateImpl::SetFocusToLocationBar( bool select_all) { } bool InstantLoader::TabContentsDelegateImpl::ShouldFocusPageAfterCrash() { return false; } void InstantLoader::TabContentsDelegateImpl::LostCapture() { CommitFromMouseReleaseIfNecessary(); } void InstantLoader::TabContentsDelegateImpl::DragEnded() { CommitFromMouseReleaseIfNecessary(); } bool InstantLoader::TabContentsDelegateImpl::CanDownload(int request_id) { // Downloads are disabled. return false; } void InstantLoader::TabContentsDelegateImpl::HandleMouseUp() { CommitFromMouseReleaseIfNecessary(); } void InstantLoader::TabContentsDelegateImpl::HandleMouseActivate() { is_mouse_down_from_activate_ = true; } bool InstantLoader::TabContentsDelegateImpl::OnGoToEntryOffset(int offset) { return false; } bool InstantLoader::TabContentsDelegateImpl::ShouldAddNavigationToHistory( const history::HistoryAddPageArgs& add_page_args, NavigationType::Type navigation_type) { if (waiting_for_new_page_ && navigation_type == NavigationType::NEW_PAGE) waiting_for_new_page_ = false; if (!waiting_for_new_page_) { add_page_vector_.push_back( scoped_refptr<history::HistoryAddPageArgs>(add_page_args.Clone())); } return false; } bool InstantLoader::TabContentsDelegateImpl::ShouldShowHungRendererDialog() { // If we allow the hung renderer dialog to be shown it'll gain focus, // stealing focus from the omnibox causing instant to be cancelled. Return // false so that doesn't happen. return false; } bool InstantLoader::TabContentsDelegateImpl::OnMessageReceived( const IPC::Message& message) { bool handled = true; IPC_BEGIN_MESSAGE_MAP(TabContentsDelegateImpl, message) IPC_MESSAGE_HANDLER(ViewHostMsg_SetSuggestions, OnSetSuggestions) IPC_MESSAGE_HANDLER(ViewHostMsg_InstantSupportDetermined, OnInstantSupportDetermined) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() return handled; } void InstantLoader::TabContentsDelegateImpl::OnSetSuggestions( int32 page_id, const std::vector<std::string>& suggestions, InstantCompleteBehavior behavior) { TabContentsWrapper* source = loader_->preview_contents(); if (!source->controller().GetActiveEntry() || page_id != source->controller().GetActiveEntry()->page_id()) return; if (suggestions.empty()) loader_->SetCompleteSuggestedText(string16(), behavior); else loader_->SetCompleteSuggestedText(UTF8ToUTF16(suggestions[0]), behavior); } void InstantLoader::TabContentsDelegateImpl::OnInstantSupportDetermined( int32 page_id, bool result) { TabContents* source = loader_->preview_contents()->tab_contents(); if (!source->controller().GetActiveEntry() || page_id != source->controller().GetActiveEntry()->page_id()) return; Details<const bool> details(&result); NotificationService::current()->Notify( NotificationType::INSTANT_SUPPORT_DETERMINED, NotificationService::AllSources(), details); if (result) loader_->PageFinishedLoading(); else loader_->PageDoesntSupportInstant(user_typed_before_load_); } void InstantLoader::TabContentsDelegateImpl ::CommitFromMouseReleaseIfNecessary() { bool was_down = is_mouse_down_from_activate_; is_mouse_down_from_activate_ = false; if (was_down && loader_->ShouldCommitInstantOnMouseUp()) loader_->CommitInstantLoader(); } // InstantLoader --------------------------------------------------------------- InstantLoader::InstantLoader(InstantLoaderDelegate* delegate, TemplateURLID id) : delegate_(delegate), template_url_id_(id), ready_(false), http_status_ok_(true), last_transition_type_(PageTransition::LINK), verbatim_(false), needs_reload_(false) { } InstantLoader::~InstantLoader() { registrar_.RemoveAll(); // Delete the TabContents before the delegate as the TabContents holds a // reference to the delegate. preview_contents_.reset(); } bool InstantLoader::Update(TabContentsWrapper* tab_contents, const TemplateURL* template_url, const GURL& url, PageTransition::Type transition_type, const string16& user_text, bool verbatim, string16* suggested_text) { DCHECK(!url.is_empty() && url.is_valid()); // Strip leading ?. string16 new_user_text = !user_text.empty() && (UTF16ToWide(user_text)[0] == L'?') ? user_text.substr(1) : user_text; // We should preserve the transition type regardless of whether we're already // showing the url. last_transition_type_ = transition_type; // If state hasn't changed, reuse the last suggestion. There are two cases: // 1. If no template url (not using instant API), then we only care if the url // changes. // 2. Template url (using instant API) then the important part is if the // user_text changes. // We have to be careful in checking user_text as in some situations // InstantController passes in an empty string (when it knows the user_text // won't matter). if ((!template_url_id_ && url_ == url) || (template_url_id_ && (new_user_text.empty() || user_text_ == new_user_text))) { suggested_text->assign(last_suggestion_); // Track the url even if we're not going to update. This is important as // when we get the suggest text we set user_text_ to the new suggest text, // but yet the url is much different. url_ = url; return false; } url_ = url; user_text_ = new_user_text; verbatim_ = verbatim; last_suggestion_.clear(); needs_reload_ = false; bool created_preview_contents = preview_contents_.get() == NULL; if (created_preview_contents) CreatePreviewContents(tab_contents); if (template_url) { DCHECK(template_url_id_ == template_url->id()); if (!created_preview_contents) { if (is_waiting_for_load()) { // The page hasn't loaded yet. We'll send the script down when it does. frame_load_observer_->set_text(user_text_); frame_load_observer_->set_verbatim(verbatim); preview_tab_contents_delegate_->set_user_typed_before_load(); return true; } // TODO: support real cursor position. int text_length = static_cast<int>(user_text_.size()); preview_contents_->render_view_host()->SearchBoxChange( user_text_, verbatim, text_length, text_length); string16 complete_suggested_text_lower = l10n_util::ToLower( complete_suggested_text_); string16 user_text_lower = l10n_util::ToLower(user_text_); if (!verbatim && complete_suggested_text_lower.size() > user_text_lower.size() && !complete_suggested_text_lower.compare(0, user_text_lower.size(), user_text_lower)) { *suggested_text = last_suggestion_ = complete_suggested_text_.substr(user_text_.size()); } } else { preview_tab_contents_delegate_->PrepareForNewLoad(); // Load the instant URL. We don't reflect the url we load in url() as // callers expect that we're loading the URL they tell us to. // // This uses an empty string for the replacement text as the url doesn't // really have the search params, but we need to use the replace // functionality so that embeded tags (like {google:baseURL}) are escaped // correctly. // TODO(sky): having to use a replaceable url is a bit of a hack here. GURL instant_url( template_url->instant_url()->ReplaceSearchTerms( *template_url, string16(), -1, string16())); CommandLine* cl = CommandLine::ForCurrentProcess(); if (cl->HasSwitch(switches::kInstantURL)) instant_url = GURL(cl->GetSwitchValueASCII(switches::kInstantURL)); preview_contents_->controller().LoadURL( instant_url, GURL(), transition_type); preview_contents_->render_view_host()->SearchBoxChange( user_text_, verbatim, 0, 0); frame_load_observer_.reset( new FrameLoadObserver(this, preview_contents()->tab_contents(), user_text_, verbatim)); } } else { DCHECK(template_url_id_ == 0); preview_tab_contents_delegate_->PrepareForNewLoad(); frame_load_observer_.reset(NULL); preview_contents_->controller().LoadURL(url_, GURL(), transition_type); } return true; } void InstantLoader::SetOmniboxBounds(const gfx::Rect& bounds) { if (omnibox_bounds_ == bounds) return; // Don't update the page while the mouse is down. http://crbug.com/71952 if (IsMouseDownFromActivate()) return; omnibox_bounds_ = bounds; if (preview_contents_.get() && is_showing_instant() && !is_waiting_for_load()) { // Updating the bounds is rather expensive, and because of the async nature // of the omnibox the bounds can dance around a bit. Delay the update in // hopes of things settling down. To avoid hiding results we grow // immediately, but delay shrinking. update_bounds_timer_.Stop(); if (omnibox_bounds_.height() > last_omnibox_bounds_.height()) { SendBoundsToPage(false); } else { update_bounds_timer_.Start( base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), this, &InstantLoader::ProcessBoundsChange); } } } bool InstantLoader::IsMouseDownFromActivate() { return preview_tab_contents_delegate_.get() && preview_tab_contents_delegate_->is_mouse_down_from_activate(); } TabContentsWrapper* InstantLoader::ReleasePreviewContents( InstantCommitType type) { if (!preview_contents_.get()) return NULL; // FrameLoadObserver is only used for instant results, and instant results are // only committed if active (when the FrameLoadObserver isn't installed). DCHECK(type == INSTANT_COMMIT_DESTROY || !frame_load_observer_.get()); if (type != INSTANT_COMMIT_DESTROY && is_showing_instant()) { if (type == INSTANT_COMMIT_FOCUS_LOST) preview_contents_->render_view_host()->SearchBoxCancel(); else preview_contents_->render_view_host()->SearchBoxSubmit( user_text_, type == INSTANT_COMMIT_PRESSED_ENTER); } omnibox_bounds_ = gfx::Rect(); last_omnibox_bounds_ = gfx::Rect(); GURL url; url.Swap(&url_); user_text_.clear(); complete_suggested_text_.clear(); if (preview_contents_.get()) { if (type != INSTANT_COMMIT_DESTROY) { if (template_url_id_) { // The URL used during instant is mostly gibberish, and not something // we'll parse and match as a past search. Set it to something we can // parse. preview_tab_contents_delegate_->SetLastHistoryURLAndPrune(url); } preview_tab_contents_delegate_->CommitHistory(template_url_id_ != 0); } if (preview_contents_->tab_contents()->GetRenderWidgetHostView()) { #if defined(OS_MACOSX) preview_contents_->tab_contents()->GetRenderWidgetHostView()-> SetTakesFocusOnlyOnMouseDown(false); registrar_.Remove( this, NotificationType::RENDER_VIEW_HOST_CHANGED, Source<NavigationController>(&preview_contents_->controller())); #endif } preview_contents_->tab_contents()->set_delegate(NULL); ready_ = false; } update_bounds_timer_.Stop(); return preview_contents_.release(); } bool InstantLoader::ShouldCommitInstantOnMouseUp() { return delegate_->ShouldCommitInstantOnMouseUp(); } void InstantLoader::CommitInstantLoader() { delegate_->CommitInstantLoader(this); } void InstantLoader::SetCompleteSuggestedText( const string16& complete_suggested_text, InstantCompleteBehavior behavior) { if (!is_showing_instant()) { // We're not trying to use the instant API with this page. Ignore it. return; } ShowPreview(); if (complete_suggested_text == complete_suggested_text_) return; if (verbatim_) { // Don't show suggest results for verbatim queries. return; } string16 user_text_lower = l10n_util::ToLower(user_text_); string16 complete_suggested_text_lower = l10n_util::ToLower( complete_suggested_text); last_suggestion_.clear(); if (user_text_lower.compare(0, user_text_lower.size(), complete_suggested_text_lower, 0, user_text_lower.size())) { // The user text no longer contains the suggested text, ignore it. complete_suggested_text_.clear(); delegate_->SetSuggestedTextFor(this, string16(), behavior); return; } complete_suggested_text_ = complete_suggested_text; if (behavior == INSTANT_COMPLETE_NOW) { // We are effectively showing complete_suggested_text_ now. Update // user_text_ so we don't notify the page again if Update happens to be // invoked (which is more than likely if this callback completes before the // omnibox is done). string16 suggestion = complete_suggested_text_.substr(user_text_.size()); user_text_ = complete_suggested_text_; delegate_->SetSuggestedTextFor(this, suggestion, behavior); } else { DCHECK((behavior == INSTANT_COMPLETE_DELAYED) || (behavior == INSTANT_COMPLETE_NEVER)); last_suggestion_ = complete_suggested_text_.substr(user_text_.size()); delegate_->SetSuggestedTextFor(this, last_suggestion_, behavior); } } void InstantLoader::PreviewPainted() { // If instant is supported then we wait for the first suggest result before // showing the page. if (!template_url_id_) ShowPreview(); } void InstantLoader::SetHTTPStatusOK(bool is_ok) { if (is_ok == http_status_ok_) return; http_status_ok_ = is_ok; if (ready_) delegate_->InstantStatusChanged(this); } void InstantLoader::ShowPreview() { if (!ready_) { ready_ = true; delegate_->InstantStatusChanged(this); } } void InstantLoader::Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details) { #if defined(OS_MACOSX) if (type.value == NotificationType::RENDER_VIEW_HOST_CHANGED) { if (preview_contents_->tab_contents()->GetRenderWidgetHostView()) { preview_contents_->tab_contents()->GetRenderWidgetHostView()-> SetTakesFocusOnlyOnMouseDown(true); } return; } #endif if (type.value == NotificationType::NAV_ENTRY_COMMITTED) { NavigationController::LoadCommittedDetails* load_details = Details<NavigationController::LoadCommittedDetails>(details).ptr(); if (load_details->is_main_frame) { if (load_details->http_status_code == kHostBlacklistStatusCode) { delegate_->AddToBlacklist(this, load_details->entry->url()); } else { SetHTTPStatusOK(load_details->http_status_code == 200); } } return; } NOTREACHED() << "Got a notification we didn't register for."; } void InstantLoader::PageFinishedLoading() { frame_load_observer_.reset(); // Send the bounds of the omnibox down now. SendBoundsToPage(false); // Wait for the user input before showing, this way the page should be up to // date by the time we show it. } // TODO(tonyg): This method only fires when the omnibox bounds change. It also // needs to fire when the preview bounds change (e.g. open/close info bar). gfx::Rect InstantLoader::GetOmniboxBoundsInTermsOfPreview() { gfx::Rect preview_bounds(delegate_->GetInstantBounds()); gfx::Rect intersection(omnibox_bounds_.Intersect(preview_bounds)); // Translate into window's coordinates. if (!intersection.IsEmpty()) { intersection.Offset(-preview_bounds.origin().x(), -preview_bounds.origin().y()); } // In the current Chrome UI, these must always be true so they sanity check // the above operations. In a future UI, these may be removed or adjusted. DCHECK_EQ(0, intersection.y()); DCHECK_LE(0, intersection.x()); DCHECK_LE(0, intersection.width()); DCHECK_LE(0, intersection.height()); return intersection; } void InstantLoader::PageDoesntSupportInstant(bool needs_reload) { frame_load_observer_.reset(NULL); delegate_->InstantLoaderDoesntSupportInstant(this); } void InstantLoader::ProcessBoundsChange() { SendBoundsToPage(false); } void InstantLoader::SendBoundsToPage(bool force_if_waiting) { if (last_omnibox_bounds_ == omnibox_bounds_) return; if (preview_contents_.get() && is_showing_instant() && (force_if_waiting || !is_waiting_for_load())) { last_omnibox_bounds_ = omnibox_bounds_; preview_contents_->render_view_host()->SearchBoxResize( GetOmniboxBoundsInTermsOfPreview()); } } void InstantLoader::CreatePreviewContents(TabContentsWrapper* tab_contents) { TabContents* new_contents = new TabContents( tab_contents->profile(), NULL, MSG_ROUTING_NONE, NULL, NULL); preview_contents_.reset(new TabContentsWrapper(new_contents)); new_contents->SetAllContentsBlocked(true); // Propagate the max page id. That way if we end up merging the two // NavigationControllers (which happens if we commit) none of the page ids // will overlap. int32 max_page_id = tab_contents->tab_contents()->GetMaxPageID(); if (max_page_id != -1) preview_contents_->controller().set_max_restored_page_id(max_page_id + 1); preview_tab_contents_delegate_.reset(new TabContentsDelegateImpl(this)); new_contents->set_delegate(preview_tab_contents_delegate_.get()); gfx::Rect tab_bounds; tab_contents->view()->GetContainerBounds(&tab_bounds); preview_contents_->view()->SizeContents(tab_bounds.size()); #if defined(OS_MACOSX) // If |preview_contents_| does not currently have a RWHV, we will call // SetTakesFocusOnlyOnMouseDown() as a result of the // RENDER_VIEW_HOST_CHANGED notification. if (preview_contents_->tab_contents()->GetRenderWidgetHostView()) { preview_contents_->tab_contents()->GetRenderWidgetHostView()-> SetTakesFocusOnlyOnMouseDown(true); } registrar_.Add( this, NotificationType::RENDER_VIEW_HOST_CHANGED, Source<NavigationController>(&preview_contents_->controller())); #endif registrar_.Add( this, NotificationType::NAV_ENTRY_COMMITTED, Source<NavigationController>(&preview_contents_->controller())); preview_contents_->tab_contents()->ShowContents(); }