| Index: chrome/browser/tab_contents/match_preview.cc
|
| diff --git a/chrome/browser/tab_contents/match_preview.cc b/chrome/browser/tab_contents/match_preview.cc
|
| index 709a58281413be36851fe7834313da060a795e44..d167ceb315b3b61f3cdbe9f03d4c8a156ed58b36 100644
|
| --- a/chrome/browser/tab_contents/match_preview.cc
|
| +++ b/chrome/browser/tab_contents/match_preview.cc
|
| @@ -7,20 +7,204 @@
|
| #include <algorithm>
|
|
|
| #include "base/command_line.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/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/search_engines/template_url_model.h"
|
| +#include "chrome/browser/tab_contents/match_preview_delegate.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/chrome_switches.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 {
|
| +
|
| +const char kUserInputScript[] =
|
| + "if (window.chrome.userInput) window.chrome.userInput(\"$1\");";
|
| +
|
| +// 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,
|
| + bool done) {
|
| + // TODO: support done.
|
| + string16 escaped_text(text);
|
| + ReplaceSubstringsAfterOffset(&escaped_text, 0L, ASCIIToUTF16("\""),
|
| + ASCIIToUTF16("\\\""));
|
| + string16 script = ReplaceStringPlaceholders(ASCIIToUTF16(kUserInputScript),
|
| + escaped_text, 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 MatchPreview::FrameLoadObserver : public NotificationObserver {
|
| + public:
|
| + FrameLoadObserver(MatchPreview* match_preview, const string16& text)
|
| + : match_preview_(match_preview),
|
| + tab_contents_(match_preview->preview_contents()),
|
| + unique_id_(tab_contents_->controller().pending_entry()->unique_id()),
|
| + text_(text),
|
| + send_done_(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 MatchPreview releases ownership of the TabContents and
|
| + // the page hasn't finished loading.
|
| + void DetachFromPreview() {
|
| + match_preview_ = NULL;
|
| + send_done_ = true;
|
| + }
|
| +
|
| + // 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;
|
| + }
|
| +
|
| + SendUserInputScript(tab_contents_, text_, send_done_);
|
| +
|
| + if (match_preview_)
|
| + match_preview_->PageFinishedLoading();
|
| +
|
| + delete this;
|
| + return;
|
| + }
|
| +
|
| + case NotificationType::TAB_CONTENTS_DESTROYED:
|
| + delete this;
|
| + return;
|
| +
|
| + default:
|
| + NOTREACHED();
|
| + break;
|
| + }
|
| + }
|
| +
|
| + private:
|
| + // MatchPreview that created us.
|
| + MatchPreview* match_preview_;
|
| +
|
| + // 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 SendScript.
|
| + bool send_done_;
|
| +
|
| + // Registers and unregisters us for notifications.
|
| + NotificationRegistrar registrar_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(FrameLoadObserver);
|
| +};
|
| +
|
| +// PaintObserver implementation. When the RenderWidgetHost paints itself this
|
| +// notifies MatchPreview, which makes the TabContents active.
|
| +class MatchPreview::PaintObserverImpl : public RenderWidgetHost::PaintObserver {
|
| + public:
|
| + explicit PaintObserverImpl(MatchPreview* preview)
|
| + : match_preview_(preview) {
|
| + }
|
| +
|
| + virtual void RenderWidgetHostWillPaint(RenderWidgetHost* rwh) {
|
| + }
|
| +
|
| + virtual void RenderWidgetHostDidPaint(RenderWidgetHost* rwh) {
|
| + match_preview_->PreviewDidPaint();
|
| + rwh->set_paint_observer(NULL);
|
| + // WARNING: we've been deleted.
|
| + }
|
| +
|
| + private:
|
| + MatchPreview* match_preview_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(PaintObserverImpl);
|
| +};
|
| +
|
| class MatchPreview::TabContentsDelegateImpl : public TabContentsDelegate {
|
| public:
|
| explicit TabContentsDelegateImpl(MatchPreview* match_preview)
|
| - : match_preview_(match_preview) {
|
| + : match_preview_(match_preview),
|
| + installed_paint_observer_(false),
|
| + waiting_for_new_page_(true) {
|
| + }
|
| +
|
| + // 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;
|
| + }
|
| +
|
| + // Commits the currently buffered history.
|
| + void CommitHistory() {
|
| + TabContents* tab = match_preview_->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,
|
| @@ -28,14 +212,24 @@ class MatchPreview::TabContentsDelegateImpl : public TabContentsDelegate {
|
| WindowOpenDisposition disposition,
|
| PageTransition::Type transition) {}
|
| virtual void NavigationStateChanged(const TabContents* source,
|
| - unsigned changed_flags) {}
|
| + 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(match_preview_));
|
| + }
|
| + }
|
| virtual void AddNewContents(TabContents* source,
|
| TabContents* new_contents,
|
| WindowOpenDisposition disposition,
|
| const gfx::Rect& initial_pos,
|
| bool user_gesture) {}
|
| virtual void ActivateContents(TabContents* contents) {
|
| - match_preview_->CommitCurrentPreview();
|
| }
|
| virtual void DeactivateContents(TabContents* contents) {}
|
| virtual void LoadingStateChanged(TabContents* source) {}
|
| @@ -59,9 +253,7 @@ class MatchPreview::TabContentsDelegateImpl : public TabContentsDelegate {
|
| virtual void ConvertContentsToApplication(TabContents* source) {}
|
| virtual bool CanReloadContents(TabContents* source) const { return true; }
|
| virtual gfx::Rect GetRootWindowResizerRect() const {
|
| - return match_preview_->host_->delegate() ?
|
| - match_preview_->host_->delegate()->GetRootWindowResizerRect() :
|
| - gfx::Rect();
|
| + return gfx::Rect();
|
| }
|
| virtual void ShowHtmlDialog(HtmlDialogUIDelegate* delegate,
|
| gfx::NativeWindow parent_window) {}
|
| @@ -82,7 +274,6 @@ class MatchPreview::TabContentsDelegateImpl : public TabContentsDelegate {
|
| virtual bool TakeFocus(bool reverse) { return false; }
|
| virtual void SetTabContentBlocked(TabContents* contents, bool blocked) {}
|
| virtual void TabContentsFocused(TabContents* tab_content) {
|
| - match_preview_->CommitCurrentPreview();
|
| }
|
| virtual int GetExtraRenderViewHeight() const { return 0; }
|
| virtual bool CanDownload(int request_id) { return false; }
|
| @@ -108,16 +299,22 @@ class MatchPreview::TabContentsDelegateImpl : public TabContentsDelegate {
|
| virtual void ShowContentSettingsWindow(ContentSettingsType content_type) {}
|
| virtual void ShowCollectedCookiesDialog(TabContents* tab_contents) {}
|
| virtual bool OnGoToEntryOffset(int offset) { return false; }
|
| - virtual bool ShouldAddNavigationToHistory(
|
| + virtual bool ShouldAddNavigationsToHistory(
|
| 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 match_preview_->host_->delegate() ?
|
| - match_preview_->host_->delegate()->GetFrameNativeWindow() : NULL;
|
| + return NULL;
|
| }
|
| virtual void TabContentsCreated(TabContents* new_contents) {}
|
| virtual bool infobars_enabled() { return false; }
|
| @@ -125,21 +322,37 @@ class MatchPreview::TabContentsDelegateImpl : public TabContentsDelegate {
|
| 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 = match_preview_->preview_contents();
|
| + // TODO: only allow for default search provider.
|
| + if (source->controller().GetActiveEntry() &&
|
| + page_id == source->controller().GetActiveEntry()->page_id()) {
|
| + match_preview_->SetCompleteSuggestedText(UTF8ToUTF16(result));
|
| + }
|
| + }
|
| +
|
| private:
|
| + typedef std::vector<scoped_refptr<history::HistoryAddPageArgs> >
|
| + AddPageVector;
|
| +
|
| MatchPreview* match_preview_;
|
|
|
| - DISALLOW_COPY_AND_ASSIGN(TabContentsDelegateImpl);
|
| -};
|
| + // Has the paint observer been installed? See comment in
|
| + // NavigationStateChanged for details on this.
|
| + bool installed_paint_observer_;
|
|
|
| -MatchPreview::MatchPreview(TabContents* host) : host_(host) {
|
| - delegate_.reset(new TabContentsDelegateImpl(this));
|
| -}
|
| + // Used to cache data that needs to be added to history. Normally entries are
|
| + // added to history as the user types, but for match preview we only want to
|
| + // add the items to history if the user commits the match preview. So, we
|
| + // cache them here and if committed then add the items to history.
|
| + AddPageVector add_page_vector_;
|
|
|
| -MatchPreview::~MatchPreview() {
|
| - // Delete the TabContents before the delegate as the TabContents holds a
|
| - // reference to the delegate.
|
| - preview_contents_.reset(NULL);
|
| -}
|
| + // 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_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(TabContentsDelegateImpl);
|
| +};
|
|
|
| // static
|
| bool MatchPreview::IsEnabled() {
|
| @@ -153,44 +366,163 @@ bool MatchPreview::IsEnabled() {
|
| return enabled;
|
| }
|
|
|
| -void MatchPreview::Update(const GURL& url) {
|
| - if (url_ == url)
|
| +MatchPreview::MatchPreview(MatchPreviewDelegate* delegate)
|
| + : delegate_(delegate),
|
| + tab_contents_(NULL),
|
| + is_active_(false),
|
| + template_url_id_(0) {
|
| + preview_tab_contents_delegate_.reset(new TabContentsDelegateImpl(this));
|
| +}
|
| +
|
| +MatchPreview::~MatchPreview() {
|
| + // Delete the TabContents before the delegate as the TabContents holds a
|
| + // reference to the delegate.
|
| + preview_contents_.reset(NULL);
|
| +}
|
| +
|
| +void MatchPreview::Update(TabContents* tab_contents,
|
| + const AutocompleteMatch& match,
|
| + const string16& user_text,
|
| + string16* suggested_text) {
|
| + if (tab_contents != tab_contents_)
|
| + DestroyPreviewContents();
|
| +
|
| + tab_contents_ = tab_contents;
|
| +
|
| + if (url_ == match.destination_url)
|
| return;
|
|
|
| - url_ = url;
|
| + url_ = match.destination_url;
|
|
|
| if (url_.is_empty() || !url_.is_valid()) {
|
| DestroyPreviewContents();
|
| return;
|
| }
|
|
|
| - if (!preview_contents_.get()) {
|
| + user_text_ = user_text;
|
| +
|
| + if (preview_contents_.get() == NULL) {
|
| preview_contents_.reset(
|
| - new TabContents(host_->profile(), NULL, MSG_ROUTING_NONE, NULL, NULL));
|
| - preview_contents_->set_delegate(delegate_.get());
|
| - NotificationService::current()->Notify(
|
| - NotificationType::MATCH_PREVIEW_TAB_CONTENTS_CREATED,
|
| - Source<TabContents>(host_),
|
| - NotificationService::NoDetails());
|
| + 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();
|
| }
|
| + preview_tab_contents_delegate_->PrepareForNewLoad();
|
|
|
| - // TODO: figure out transition type.
|
| - preview_contents_->controller().LoadURL(url, GURL(),
|
| - PageTransition::GENERATED);
|
| + const TemplateURL* template_url = match.template_url;
|
| + if (match.type == AutocompleteMatch::SEARCH_WHAT_YOU_TYPED ||
|
| + match.type == AutocompleteMatch::SEARCH_HISTORY ||
|
| + match.type == AutocompleteMatch::SEARCH_SUGGEST) {
|
| + TemplateURLModel* model = tab_contents->profile()->GetTemplateURLModel();
|
| + template_url = model ? model->GetDefaultSearchProvider() : NULL;
|
| + }
|
| + TemplateURLID template_url_id = template_url ? template_url->id() : 0;
|
| +
|
| + if (template_url && template_url->supports_instant() &&
|
| + TemplateURL::SupportsReplacement(template_url)) {
|
| + if (template_url_id == template_url_id_) {
|
| + if (frame_load_observer_.get()) {
|
| + // 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_, false);
|
| + 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 {
|
| + frame_load_observer_.reset(NULL);
|
| + preview_contents_->controller().LoadURL(url_, GURL(), match.transition);
|
| + }
|
| +
|
| + template_url_id_ = template_url_id;
|
| }
|
|
|
| void MatchPreview::DestroyPreviewContents() {
|
| - url_ = GURL();
|
| - preview_contents_.reset(NULL);
|
| + delegate_->HideMatchPreview();
|
| + delete ReleasePreviewContents(false);
|
| }
|
|
|
| void MatchPreview::CommitCurrentPreview() {
|
| DCHECK(preview_contents_.get());
|
| - if (host_->delegate())
|
| - host_->delegate()->CommitMatchPreview(host_);
|
| + delegate_->CommitMatchPreview();
|
| }
|
|
|
| -TabContents* MatchPreview::ReleasePreviewContents() {
|
| +TabContents* MatchPreview::ReleasePreviewContents(bool commit_history) {
|
| + template_url_id_ = 0;
|
| url_ = GURL();
|
| + user_text_.clear();
|
| + complete_suggested_text_.clear();
|
| + if (frame_load_observer_.get()) {
|
| + frame_load_observer_->DetachFromPreview();
|
| + // FrameLoadObserver will delete itself either when the TabContents is
|
| + // deleted, or when the page finishes loading.
|
| + FrameLoadObserver* unused ALLOW_UNUSED = frame_load_observer_.release();
|
| + }
|
| + if (preview_contents_.get()) {
|
| + if (commit_history)
|
| + preview_tab_contents_delegate_->CommitHistory();
|
| + // Destroy the paint observer.
|
| + if (preview_contents_->GetRenderWidgetHostView()) {
|
| + // RenderWidgetHostView may be null during shutdown.
|
| + preview_contents_->GetRenderWidgetHostView()->GetRenderWidgetHost()->
|
| + set_paint_observer(NULL);
|
| + }
|
| + preview_contents_->set_delegate(NULL);
|
| + preview_tab_contents_delegate_->Reset();
|
| + is_active_ = false;
|
| + }
|
| return preview_contents_.release();
|
| }
|
| +
|
| +void MatchPreview::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_->SetSuggestedText(string16());
|
| + return;
|
| + }
|
| +
|
| + complete_suggested_text_ = complete_suggested_text;
|
| + delegate_->SetSuggestedText(
|
| + complete_suggested_text_.substr(user_text_.size()));
|
| +}
|
| +
|
| +void MatchPreview::PreviewDidPaint() {
|
| + DCHECK(!is_active_);
|
| + is_active_ = true;
|
| + delegate_->ShowMatchPreview();
|
| +}
|
| +
|
| +void MatchPreview::PageFinishedLoading() {
|
| + // FrameLoadObserver deletes itself after this call.
|
| + FrameLoadObserver* unused ALLOW_UNUSED = frame_load_observer_.release();
|
| +}
|
|
|