diff options
Diffstat (limited to 'chrome/browser/instant/instant_loader.cc')
-rw-r--r-- | chrome/browser/instant/instant_loader.cc | 656 |
1 files changed, 656 insertions, 0 deletions
diff --git a/chrome/browser/instant/instant_loader.cc b/chrome/browser/instant/instant_loader.cc new file mode 100644 index 0000000..adae0b8 --- /dev/null +++ b/chrome/browser/instant/instant_loader.cc @@ -0,0 +1,656 @@ +// Copyright (c) 2010 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 "base/command_line.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/autocomplete/autocomplete.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/profile.h" +#include "chrome/browser/renderer_host/render_view_host.h" +#include "chrome/browser/renderer_host/render_widget_host.h" +#include "chrome/browser/renderer_host/render_widget_host_view.h" +#include "chrome/browser/search_engines/template_url.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tab_contents/tab_contents.h" +#include "chrome/browser/tab_contents/tab_contents_delegate.h" +#include "chrome/browser/tab_contents/tab_contents_view.h" +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/page_transition_types.h" +#include "chrome/common/render_messages.h" +#include "chrome/common/renderer_preferences.h" +#include "gfx/codec/png_codec.h" +#include "ipc/ipc_message.h" + +namespace { + +// Script sent as the user is typing and the provider supports instant. +// Params: +// . the text the user typed. +// TODO: add support for the 2nd and 3rd params. +const char kUserInputScript[] = + "if (window.chrome.userInput) window.chrome.userInput(\"$1\", 0, 0);"; + +// Script sent when the page is committed and the provider supports instant. +// Params: +// . the text the user typed. +// . boolean indicating if the user pressed enter to accept the text. +const char kUserDoneScript[] = + "if (window.chrome.userWantsQuery) " + "window.chrome.userWantsQuery(\"$1\", $2);"; + +// Script sent when the bounds of the omnibox changes and the provider supports +// instant. The params are the bounds relative to the origin of the preview +// (x, y, width, height). +const char kSetOmniboxBoundsScript[] = + "if (window.chrome.setDropdownDimensions) " + "window.chrome.setDropdownDimensions($1, $2, $3, $4);"; + +// Escapes quotes in the |text| so that it be passed to JavaScript as a quoted +// string. +string16 EscapeUserText(const string16& text) { + string16 escaped_text(text); + ReplaceSubstringsAfterOffset(&escaped_text, 0L, ASCIIToUTF16("\""), + ASCIIToUTF16("\\\"")); + return escaped_text; +} + +// Sends the script for when the user commits the preview. |pressed_enter| is +// true if the user pressed enter to commit. +void SendDoneScript(TabContents* tab_contents, + const string16& text, + bool pressed_enter) { + std::vector<string16> params; + params.push_back(EscapeUserText(text)); + params.push_back(pressed_enter ? ASCIIToUTF16("true") : + ASCIIToUTF16("false")); + string16 script = ReplaceStringPlaceholders(ASCIIToUTF16(kUserDoneScript), + params, + NULL); + tab_contents->render_view_host()->ExecuteJavascriptInWebFrame( + std::wstring(), + UTF16ToWide(script)); +} + +// Sends the user input script to |tab_contents|. |text| is the text the user +// input into the omnibox. +void SendUserInputScript(TabContents* tab_contents, const string16& text) { + string16 script = ReplaceStringPlaceholders(ASCIIToUTF16(kUserInputScript), + EscapeUserText(text), + NULL); + tab_contents->render_view_host()->ExecuteJavascriptInWebFrame( + std::wstring(), + UTF16ToWide(script)); +} + +// Sends the script for setting the bounds of the omnibox to |tab_contents|. +void SendOmniboxBoundsScript(TabContents* tab_contents, + const gfx::Rect& bounds) { + std::vector<string16> bounds_vector; + bounds_vector.push_back(base::IntToString16(bounds.x())); + bounds_vector.push_back(base::IntToString16(bounds.y())); + bounds_vector.push_back(base::IntToString16(bounds.width())); + bounds_vector.push_back(base::IntToString16(bounds.height())); + string16 script = ReplaceStringPlaceholders( + ASCIIToUTF16(kSetOmniboxBoundsScript), + bounds_vector, + NULL); + tab_contents->render_view_host()->ExecuteJavascriptInWebFrame( + std::wstring(), + UTF16ToWide(script)); +} + +} // namespace + +// FrameLoadObserver is responsible for waiting for the TabContents to finish +// loading and when done sending the necessary script down to the page. +class InstantLoader::FrameLoadObserver : public NotificationObserver { + public: + FrameLoadObserver(InstantLoader* loader, const string16& text) + : loader_(loader), + tab_contents_(loader->preview_contents()), + unique_id_(tab_contents_->controller().pending_entry()->unique_id()), + text_(text), + pressed_enter_(false) { + registrar_.Add(this, NotificationType::LOAD_COMPLETED_MAIN_FRAME, + Source<TabContents>(tab_contents_)); + registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED, + Source<TabContents>(tab_contents_)); + } + + // Sets the text to send to the page. + void set_text(const string16& text) { text_ = text; } + + // Invoked when the InstantLoader releases ownership of the TabContents and + // the page hasn't finished loading. + void DetachFromPreview(bool pressed_enter) { + loader_ = NULL; + pressed_enter_ = pressed_enter; + } + + // NotificationObserver: + virtual void 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; + } + + if (loader_) { + gfx::Rect bounds = loader_->GetOmniboxBoundsInTermsOfPreview(); + if (!bounds.IsEmpty()) + SendOmniboxBoundsScript(tab_contents_, bounds); + } + + SendUserInputScript(tab_contents_, text_); + + if (loader_) + loader_->PageFinishedLoading(); + else + SendDoneScript(tab_contents_, text_, pressed_enter_); + + delete this; + return; + } + + case NotificationType::TAB_CONTENTS_DESTROYED: + delete this; + return; + + default: + NOTREACHED(); + break; + } + } + + private: + // InstantLoader that created us. + InstantLoader* loader_; + + // The TabContents we're listening for changes on. + TabContents* tab_contents_; + + // unique_id of the NavigationEntry we're waiting on. + const int unique_id_; + + // Text to send down to the page. + string16 text_; + + // Passed to SendDoneScript. + bool pressed_enter_; + + // Registers and unregisters us for notifications. + NotificationRegistrar registrar_; + + DISALLOW_COPY_AND_ASSIGN(FrameLoadObserver); +}; + +// PaintObserver implementation. When the RenderWidgetHost paints itself this +// notifies InstantLoader, which makes the TabContents active. +class InstantLoader::PaintObserverImpl + : public RenderWidgetHost::PaintObserver { + public: + explicit PaintObserverImpl(InstantLoader* loader) : loader_(loader) { + } + + virtual void RenderWidgetHostWillPaint(RenderWidgetHost* rwh) { + } + + virtual void RenderWidgetHostDidPaint(RenderWidgetHost* rwh) { + loader_->PreviewPainted(); + rwh->set_paint_observer(NULL); + // WARNING: we've been deleted. + } + + private: + InstantLoader* loader_; + + DISALLOW_COPY_AND_ASSIGN(PaintObserverImpl); +}; + +class InstantLoader::TabContentsDelegateImpl : public TabContentsDelegate { + public: + explicit TabContentsDelegateImpl(InstantLoader* loader) + : loader_(loader), + installed_paint_observer_(false), + waiting_for_new_page_(true), + is_mouse_down_from_activate_(false) { + } + + // Invoked prior to loading a new URL. + void PrepareForNewLoad() { + waiting_for_new_page_ = true; + add_page_vector_.clear(); + } + + // Invoked when removed as the delegate. Gives a chance to do any necessary + // cleanup. + void Reset() { + installed_paint_observer_ = false; + is_mouse_down_from_activate_ = false; + } + + bool is_mouse_down_from_activate() const { + return is_mouse_down_from_activate_; + } + + // Commits the currently buffered history. + void CommitHistory() { + TabContents* tab = loader_->preview_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(); + DCHECK(active_entry); + 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); + } + } + + virtual void OpenURLFromTab(TabContents* source, + const GURL& url, const GURL& referrer, + WindowOpenDisposition disposition, + PageTransition::Type transition) {} + virtual void NavigationStateChanged(const TabContents* source, + unsigned changed_flags) { + if (!installed_paint_observer_ && 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. + installed_paint_observer_ = true; + source->GetRenderWidgetHostView()->GetRenderWidgetHost()-> + set_paint_observer(new PaintObserverImpl(loader_)); + } + } + virtual void AddNewContents(TabContents* source, + TabContents* new_contents, + WindowOpenDisposition disposition, + const gfx::Rect& initial_pos, + bool user_gesture) {} + virtual void ActivateContents(TabContents* contents) { + } + virtual void DeactivateContents(TabContents* contents) {} + virtual void LoadingStateChanged(TabContents* source) {} + virtual void CloseContents(TabContents* source) {} + virtual void MoveContents(TabContents* source, const gfx::Rect& pos) {} + virtual void DetachContents(TabContents* source) {} + virtual bool IsPopup(const TabContents* source) const { + return false; + } + virtual bool ShouldFocusConstrainedWindow(TabContents* source) { + // 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; + } + virtual void WillShowConstrainedWindow(TabContents* source) { + if (!loader_->ready()) { + // A constrained window shown for an auth may not paint. Show the preview + // contents. + if (installed_paint_observer_) { + source->GetRenderWidgetHostView()->GetRenderWidgetHost()-> + set_paint_observer(NULL); + } + installed_paint_observer_ = true; + loader_->ShowPreview(); + } + } + virtual TabContents* GetConstrainingContents(TabContents* source) { + return NULL; + } + virtual void ToolbarSizeChanged(TabContents* source, bool is_animating) {} + virtual void URLStarredChanged(TabContents* source, bool starred) {} + virtual void UpdateTargetURL(TabContents* source, const GURL& url) {} + virtual void ContentsMouseEvent( + TabContents* source, const gfx::Point& location, bool motion) {} + virtual void ContentsZoomChange(bool zoom_in) {} + virtual void OnContentSettingsChange(TabContents* source) {} + virtual bool IsApplication() const { return false; } + virtual void ConvertContentsToApplication(TabContents* source) {} + virtual bool CanReloadContents(TabContents* source) const { return true; } + virtual gfx::Rect GetRootWindowResizerRect() const { + return gfx::Rect(); + } + virtual void ShowHtmlDialog(HtmlDialogUIDelegate* delegate, + gfx::NativeWindow parent_window) {} + virtual void BeforeUnloadFired(TabContents* tab, + bool proceed, + bool* proceed_to_fire_unload) {} + virtual void ForwardMessageToExternalHost(const std::string& message, + const std::string& origin, + const std::string& target) {} + virtual bool IsExternalTabContainer() const { return false; } + virtual void SetFocusToLocationBar(bool select_all) {} + virtual bool ShouldFocusPageAfterCrash() { return false; } + virtual void RenderWidgetShowing() {} + virtual ExtensionFunctionDispatcher* CreateExtensionFunctionDispatcher( + RenderViewHost* render_view_host, + const std::string& extension_id) { + return NULL; + } + virtual bool TakeFocus(bool reverse) { return false; } + virtual void LostCapture() { + CommitFromMouseReleaseIfNecessary(); + } + virtual void SetTabContentBlocked(TabContents* contents, bool blocked) {} + virtual void TabContentsFocused(TabContents* tab_content) { + } + virtual int GetExtraRenderViewHeight() const { return 0; } + virtual bool CanDownload(int request_id) { return false; } + virtual void OnStartDownload(DownloadItem* download, TabContents* tab) {} + virtual bool HandleContextMenu(const ContextMenuParams& params) { + return false; + } + virtual bool ExecuteContextMenuCommand(int command) { + return false; + } + virtual void ConfirmAddSearchProvider(const TemplateURL* template_url, + Profile* profile) {} + virtual void ShowPageInfo(Profile* profile, + const GURL& url, + const NavigationEntry::SSLStatus& ssl, + bool show_history) {} + virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event, + bool* is_keyboard_shortcut) { + return false; + } + virtual void HandleMouseUp() { + CommitFromMouseReleaseIfNecessary(); + } + virtual void HandleMouseActivate() { + is_mouse_down_from_activate_ = true; + } + virtual void ShowRepostFormWarningDialog(TabContents* tab_contents) {} + virtual void ShowContentSettingsWindow(ContentSettingsType content_type) {} + virtual void ShowCollectedCookiesDialog(TabContents* tab_contents) {} + virtual bool OnGoToEntryOffset(int offset) { return false; } + virtual bool 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; + } + virtual void OnDidGetApplicationInfo(TabContents* tab_contents, + int32 page_id) {} + virtual gfx::NativeWindow GetFrameNativeWindow() { + return NULL; + } + virtual void TabContentsCreated(TabContents* new_contents) {} + virtual bool infobars_enabled() { return false; } + virtual bool ShouldEnablePreferredSizeNotifications() { return false; } + virtual void UpdatePreferredSize(const gfx::Size& pref_size) {} + virtual void ContentTypeChanged(TabContents* source) {} + + virtual void OnSetSuggestResult(int32 page_id, const std::string& result) { + TabContents* source = loader_->preview_contents(); + // TODO: only allow for default search provider. + if (source->controller().GetActiveEntry() && + page_id == source->controller().GetActiveEntry()->page_id()) { + loader_->SetCompleteSuggestedText(UTF8ToUTF16(result)); + } + } + + private: + typedef std::vector<scoped_refptr<history::HistoryAddPageArgs> > + AddPageVector; + + void CommitFromMouseReleaseIfNecessary() { + bool was_down = is_mouse_down_from_activate_; + is_mouse_down_from_activate_ = false; + if (was_down && loader_->ShouldCommitInstantOnMouseUp()) + loader_->CommitInstantLoader(); + } + + InstantLoader* loader_; + + // Has the paint observer been installed? See comment in + // NavigationStateChanged for details on this. + bool installed_paint_observer_; + + // 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_; + + // Returns true if the mouse is down from an activate. + bool is_mouse_down_from_activate_; + + DISALLOW_COPY_AND_ASSIGN(TabContentsDelegateImpl); +}; + +InstantLoader::InstantLoader(InstantLoaderDelegate* delegate, TemplateURLID id) + : delegate_(delegate), + template_url_id_(id), + ready_(false) { + preview_tab_contents_delegate_.reset(new TabContentsDelegateImpl(this)); +} + +InstantLoader::~InstantLoader() { + // Delete the TabContents before the delegate as the TabContents holds a + // reference to the delegate. + preview_contents_.reset(NULL); +} + +void InstantLoader::Update(TabContents* tab_contents, + const AutocompleteMatch& match, + const string16& user_text, + const TemplateURL* template_url, + string16* suggested_text) { + DCHECK(url_ != match.destination_url); + + url_ = match.destination_url; + + DCHECK(!url_.is_empty() && url_.is_valid()); + + user_text_ = user_text; + + bool created_preview_contents; + if (preview_contents_.get() == NULL) { + preview_contents_.reset( + new TabContents(tab_contents->profile(), NULL, MSG_ROUTING_NONE, + NULL, NULL)); + // 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->GetMaxPageID(); + if (max_page_id != -1) + preview_contents_->controller().set_max_restored_page_id(max_page_id + 1); + + preview_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()); + + preview_contents_->ShowContents(); + created_preview_contents = true; + } else { + created_preview_contents = false; + } + preview_tab_contents_delegate_->PrepareForNewLoad(); + + 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_); + return; + } + SendUserInputScript(preview_contents_.get(), user_text_); + if (complete_suggested_text_.size() > user_text_.size() && + !complete_suggested_text_.compare(0, user_text_.size(), user_text_)) { + *suggested_text = complete_suggested_text_.substr(user_text_.size()); + } + } else { + // TODO: should we use a different url for instant? + GURL url = GURL(template_url->url()->ReplaceSearchTerms( + *template_url, std::wstring(), + TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, std::wstring())); + // user_text_ is sent once the page finishes loading by FrameLoadObserver. + preview_contents_->controller().LoadURL(url, GURL(), match.transition); + frame_load_observer_.reset(new FrameLoadObserver(this, user_text_)); + } + } else { + DCHECK(template_url_id_ == 0); + frame_load_observer_.reset(NULL); + preview_contents_->controller().LoadURL(url_, GURL(), match.transition); + } +} + +void InstantLoader::SetOmniboxBounds(const gfx::Rect& bounds) { + if (omnibox_bounds_ == bounds) + return; + + omnibox_bounds_ = bounds; + if (preview_contents_.get() && is_showing_instant() && + !is_waiting_for_load()) { + SendOmniboxBoundsScript(preview_contents_.get(), + GetOmniboxBoundsInTermsOfPreview()); + } +} + +void InstantLoader::DestroyPreviewContents() { + if (!preview_contents_.get()) { + // We're not showing anything, nothing to do. + return; + } + + delete ReleasePreviewContents(INSTANT_COMMIT_DESTROY); +} + +bool InstantLoader::IsMouseDownFromActivate() { + return preview_tab_contents_delegate_->is_mouse_down_from_activate(); +} + +TabContents* InstantLoader::ReleasePreviewContents(InstantCommitType type) { + if (!preview_contents_.get()) + return NULL; + + if (frame_load_observer_.get()) { + frame_load_observer_->DetachFromPreview( + type == INSTANT_COMMIT_PRESSED_ENTER); + // FrameLoadObserver will delete itself either when the TabContents is + // deleted, or when the page finishes loading. + FrameLoadObserver* unused ALLOW_UNUSED = frame_load_observer_.release(); + } else if (type != INSTANT_COMMIT_DESTROY && is_showing_instant()) { + SendDoneScript(preview_contents_.get(), + user_text_, + type == INSTANT_COMMIT_PRESSED_ENTER); + } + omnibox_bounds_ = gfx::Rect(); + url_ = GURL(); + user_text_.clear(); + complete_suggested_text_.clear(); + if (preview_contents_.get()) { + if (type != INSTANT_COMMIT_DESTROY) + preview_tab_contents_delegate_->CommitHistory(); + // Destroy the paint observer. + // RenderWidgetHostView may be null during shutdown. + if (preview_contents_->GetRenderWidgetHostView()) { + preview_contents_->GetRenderWidgetHostView()->GetRenderWidgetHost()-> + set_paint_observer(NULL); + } + preview_contents_->set_delegate(NULL); + preview_tab_contents_delegate_->Reset(); + ready_ = false; + } + return preview_contents_.release(); +} + +bool InstantLoader::ShouldCommitInstantOnMouseUp() { + return delegate_->ShouldCommitInstantOnMouseUp(); +} + +void InstantLoader::CommitInstantLoader() { + delegate_->CommitInstantLoader(this); +} + +void InstantLoader::SetCompleteSuggestedText( + const string16& complete_suggested_text) { + if (complete_suggested_text == complete_suggested_text_) + return; + + if (user_text_.compare(0, user_text_.size(), complete_suggested_text, + 0, user_text_.size())) { + // The user text no longer contains the suggested text, ignore it. + complete_suggested_text_.clear(); + delegate_->SetSuggestedTextFor(this, string16()); + return; + } + + complete_suggested_text_ = complete_suggested_text; + delegate_->SetSuggestedTextFor( + this, + complete_suggested_text_.substr(user_text_.size())); +} + +void InstantLoader::PreviewPainted() { + ShowPreview(); +} + +void InstantLoader::ShowPreview() { + if (!ready_) { + ready_ = true; + delegate_->ShowInstantLoader(this); + } +} + +void InstantLoader::PageFinishedLoading() { + // FrameLoadObserver deletes itself after this call. + FrameLoadObserver* unused ALLOW_UNUSED = frame_load_observer_.release(); +} + +gfx::Rect InstantLoader::GetOmniboxBoundsInTermsOfPreview() { + if (omnibox_bounds_.IsEmpty()) + return omnibox_bounds_; + + gfx::Rect preview_bounds(delegate_->GetInstantBounds()); + return gfx::Rect(omnibox_bounds_.x() - preview_bounds.x(), + omnibox_bounds_.y() - preview_bounds.y(), + omnibox_bounds_.width(), + omnibox_bounds_.height()); +} |