diff options
Diffstat (limited to 'chrome/browser/tab_contents')
-rw-r--r-- | chrome/browser/tab_contents/loader_manager.cc | 123 | ||||
-rw-r--r-- | chrome/browser/tab_contents/loader_manager.h | 97 | ||||
-rw-r--r-- | chrome/browser/tab_contents/loader_manager_unittest.cc | 209 | ||||
-rw-r--r-- | chrome/browser/tab_contents/match_preview.cc | 666 | ||||
-rw-r--r-- | chrome/browser/tab_contents/match_preview.h | 74 | ||||
-rw-r--r-- | chrome/browser/tab_contents/match_preview_commit_type.h | 22 | ||||
-rw-r--r-- | chrome/browser/tab_contents/match_preview_delegate.h | 2 | ||||
-rw-r--r-- | chrome/browser/tab_contents/match_preview_loader.cc | 659 | ||||
-rw-r--r-- | chrome/browser/tab_contents/match_preview_loader.h | 139 | ||||
-rw-r--r-- | chrome/browser/tab_contents/match_preview_loader_delegate.h | 38 |
10 files changed, 1397 insertions, 632 deletions
diff --git a/chrome/browser/tab_contents/loader_manager.cc b/chrome/browser/tab_contents/loader_manager.cc new file mode 100644 index 0000000..962154f --- /dev/null +++ b/chrome/browser/tab_contents/loader_manager.cc @@ -0,0 +1,123 @@ +// 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/tab_contents/loader_manager.h" + +#include "base/logging.h" +#include "chrome/browser/tab_contents/match_preview_loader.h" +#include "chrome/browser/tab_contents/match_preview_loader_delegate.h" +#include "chrome/browser/tab_contents/tab_contents.h" + +LoaderManager::LoaderManager(MatchPreviewLoaderDelegate* loader_delegate) + : loader_delegate_(loader_delegate), + current_loader_(NULL), + pending_loader_(NULL) { +} + +LoaderManager::~LoaderManager() { + for (Loaders::iterator i = instant_loaders_.begin(); + i != instant_loaders_.end(); ++i) { + if (i->second == current_loader_) + current_loader_ = NULL; + if (i->second == pending_loader_) + pending_loader_ = NULL; + delete i->second; + } + instant_loaders_.clear(); + + if (current_loader_) + delete current_loader_; + if (pending_loader_) + delete pending_loader_; +} + +MatchPreviewLoader* LoaderManager::UpdateLoader( + TemplateURLID instant_id, + scoped_ptr<MatchPreviewLoader>* old_loader) { + MatchPreviewLoader* old_current_loader = current_loader_; + MatchPreviewLoader* old_pending_loader = pending_loader_; + + // Determine the new loader. + MatchPreviewLoader* loader = NULL; + if (instant_id) { + loader = GetInstantLoader(instant_id); + } else { + if (current_loader_ && !current_loader_->template_url_id()) + loader = current_loader_; + else if (pending_loader_ && !pending_loader_->template_url_id()) + loader = pending_loader_; + else + loader = CreateLoader(0); + } + + if (loader->ready()) { + // The loader is ready, make it the current loader no matter what. + current_loader_ = loader; + pending_loader_ = NULL; + } else { + // The loader isn't ready make it the current only if the current isn't + // ready. If the current is ready, then stop the current and make the new + // loader pending. + if (!current_loader_ || !current_loader_->ready()) { + current_loader_ = loader; + DCHECK(!pending_loader_); + } else { + // preview_contents() may be null for tests. + if (!current_loader_->template_url_id() && + current_loader_->preview_contents()) { + current_loader_->preview_contents()->Stop(); + } + pending_loader_ = loader; + } + } + + if (current_loader_ != old_current_loader && old_current_loader && + !old_current_loader->template_url_id()) { + old_loader->reset(old_current_loader); + } + if (pending_loader_ != old_pending_loader && old_pending_loader && + !old_pending_loader->template_url_id()) { + DCHECK(!old_loader->get()); + old_loader->reset(old_pending_loader); + } + + return active_loader(); +} + +void LoaderManager::MakePendingCurrent( + scoped_ptr<MatchPreviewLoader>* old_loader) { + DCHECK(current_loader_); + DCHECK(pending_loader_); + + if (!current_loader_->template_url_id()) + old_loader->reset(current_loader_); + + current_loader_ = pending_loader_; + pending_loader_ = NULL; +} + +MatchPreviewLoader* LoaderManager::ReleaseCurrentLoader() { + DCHECK(current_loader_); + MatchPreviewLoader* loader = current_loader_; + if (current_loader_->template_url_id()) { + Loaders::iterator i = + instant_loaders_.find(current_loader_->template_url_id()); + DCHECK(i != instant_loaders_.end()); + instant_loaders_.erase(i); + } + current_loader_ = NULL; + return loader; +} + +MatchPreviewLoader* LoaderManager::CreateLoader(TemplateURLID id) { + MatchPreviewLoader* loader = new MatchPreviewLoader(loader_delegate_, id); + if (id) + instant_loaders_[id] = loader; + return loader; +} + +MatchPreviewLoader* LoaderManager::GetInstantLoader(TemplateURLID id) { + Loaders::iterator i = instant_loaders_.find(id); + return i == instant_loaders_.end() ? CreateLoader(id) : i->second; +} diff --git a/chrome/browser/tab_contents/loader_manager.h b/chrome/browser/tab_contents/loader_manager.h new file mode 100644 index 0000000..a72defc --- /dev/null +++ b/chrome/browser/tab_contents/loader_manager.h @@ -0,0 +1,97 @@ +// 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. + +#ifndef CHROME_BROWSER_TAB_CONTENTS_LOADER_MANAGER_H_ +#define CHROME_BROWSER_TAB_CONTENTS_LOADER_MANAGER_H_ +#pragma once + +#include <map> + +#include "base/scoped_ptr.h" +#include "chrome/browser/search_engines/template_url_id.h" + +class MatchPreviewLoader; +class MatchPreviewLoaderDelegate; + +// LoaderManager is responsible for maintaining the MatchPreviewLoaders for +// MatchPreview. LoaderManager keeps track of one loader for loading non-instant +// urls, and a loader per TemplateURLID for loading instant urls. A loader per +// TemplateURLID is necessitated due to not knowing in advance if a site +// really supports instant (for example, the user might have opted out even +// though it's supported). +// +// Users of LoaderManager need only concern themselves with the current and +// pending loaders. The current loader is the loader that if ready is shown by +// MatchPreview. The pending loader is used if the current loader is ready and +// update is invoked with a different id. In this case the current loader is +// left as current (and it's preview contents stopped) and the newly created +// loader is set to pending. Once the pending loader is ready +// MakePendingCurrent should be invoked to make the pending the current loader. +// +// MatchPreviewLoader owns all the MatchPreviewLoaders returned. You can take +// ownership of the current loader by invoking ReleaseCurrentLoader. +class LoaderManager { + public: + explicit LoaderManager(MatchPreviewLoaderDelegate* loader_delegate); + ~LoaderManager(); + + // Updates the current loader. If the current loader is replaced and should be + // deleted it is set in |old_loader|. This is done to allow the caller to + // notify delegates before the old loader is destroyed. This returns the + // active MatchPreviewLoader that should be used. + MatchPreviewLoader* UpdateLoader(TemplateURLID instant_id, + scoped_ptr<MatchPreviewLoader>* old_loader); + + // Makes the pending loader the current loader. If ownership of the old + // loader is to pass to the caller |old_loader| is set appropriately. + void MakePendingCurrent(scoped_ptr<MatchPreviewLoader>* old_loader); + + // Returns the current loader and clears internal references to it. This + // should be used prior to destroying the LoaderManager when the owner of + // LoaderManager wants to take ownership of the loader. + MatchPreviewLoader* ReleaseCurrentLoader(); + + // Returns the current loader, may be null. + MatchPreviewLoader* current_loader() const { return current_loader_; } + + // Returns the pending loader, may be null. + MatchPreviewLoader* pending_loader() const { return pending_loader_; } + + // The active loader is the loader that should be used for new loads. It is + // either the pending loader or the current loader. + MatchPreviewLoader* active_loader() const { + return pending_loader_ ? pending_loader_ : current_loader_; + } + + // Returns the number of instant loaders. + // This is exposed for tests. + size_t num_instant_loaders() const { return instant_loaders_.size(); } + + private: + typedef std::map<TemplateURLID, MatchPreviewLoader*> Loaders; + + // Creates a loader and if |id| is non-zero registers it in instant_loaders_. + MatchPreviewLoader* CreateLoader(TemplateURLID id); + + // Returns the loader for loading instant results with the specified id. If + // there is no loader for the specified id a new one is created. + MatchPreviewLoader* GetInstantLoader(TemplateURLID id); + + MatchPreviewLoaderDelegate* loader_delegate_; + + // The current loader. + MatchPreviewLoader* current_loader_; + + // Loader we want to use as soon as ready. This is only non-null if + // current_loader_ is ready and Update is invoked with a different template + // url id. + MatchPreviewLoader* pending_loader_; + + // Maps for template url id to loader used for that template url id. + Loaders instant_loaders_; + + DISALLOW_COPY_AND_ASSIGN(LoaderManager); +}; + +#endif // CHROME_BROWSER_TAB_CONTENTS_LOADER_MANAGER_H_ diff --git a/chrome/browser/tab_contents/loader_manager_unittest.cc b/chrome/browser/tab_contents/loader_manager_unittest.cc new file mode 100644 index 0000000..4c9c5de --- /dev/null +++ b/chrome/browser/tab_contents/loader_manager_unittest.cc @@ -0,0 +1,209 @@ +// 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 "base/scoped_ptr.h" +#include "chrome/browser/tab_contents/loader_manager.h" +#include "chrome/browser/tab_contents/match_preview_loader.h" +#include "chrome/browser/tab_contents/match_preview_loader_delegate.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class MatchPreviewLoaderDelegateImpl : public MatchPreviewLoaderDelegate { + public: + MatchPreviewLoaderDelegateImpl() {} + + virtual void ShowMatchPreviewLoader(MatchPreviewLoader* loader) {} + + virtual void SetSuggestedTextFor(MatchPreviewLoader* loader, + const string16& text) {} + + virtual gfx::Rect GetMatchPreviewBounds() { + return gfx::Rect(); + } + + virtual bool ShouldCommitPreviewOnMouseUp() { + return false; + } + + virtual void CommitPreview(MatchPreviewLoader* loader) { + } + + private: + DISALLOW_COPY_AND_ASSIGN(MatchPreviewLoaderDelegateImpl); +}; + +} + +class LoaderManagerTest : public testing::Test { + public: + LoaderManagerTest() {} + + void MarkReady(MatchPreviewLoader* loader) { + loader->ready_ = true; + } + + private: + DISALLOW_COPY_AND_ASSIGN(LoaderManagerTest); +}; + +// Makes sure UpdateLoader works when invoked once. +TEST_F(LoaderManagerTest, Basic) { + MatchPreviewLoaderDelegateImpl delegate; + LoaderManager manager(&delegate); + scoped_ptr<MatchPreviewLoader> loader; + manager.UpdateLoader(0, &loader); + EXPECT_EQ(NULL, loader.get()); + EXPECT_TRUE(manager.current_loader()); + EXPECT_EQ(NULL, manager.pending_loader()); + EXPECT_EQ(0, manager.current_loader()->template_url_id()); +} + +// Make sure invoking update twice for non-instant results keeps the same +// loader. +TEST_F(LoaderManagerTest, UpdateTwice) { + MatchPreviewLoaderDelegateImpl delegate; + LoaderManager manager(&delegate); + scoped_ptr<MatchPreviewLoader> loader; + manager.UpdateLoader(0, &loader); + MatchPreviewLoader* current_loader = manager.current_loader(); + manager.UpdateLoader(0, &loader); + EXPECT_EQ(NULL, loader.get()); + EXPECT_EQ(current_loader, manager.current_loader()); + EXPECT_EQ(NULL, manager.pending_loader()); +} + +// Make sure invoking update twice for instant results keeps the same loader. +TEST_F(LoaderManagerTest, UpdateInstantTwice) { + MatchPreviewLoaderDelegateImpl delegate; + LoaderManager manager(&delegate); + scoped_ptr<MatchPreviewLoader> loader; + manager.UpdateLoader(1, &loader); + MatchPreviewLoader* current_loader = manager.current_loader(); + manager.UpdateLoader(1, &loader); + EXPECT_EQ(NULL, loader.get()); + EXPECT_EQ(current_loader, manager.current_loader()); + EXPECT_EQ(NULL, manager.pending_loader()); + EXPECT_EQ(1u, manager.num_instant_loaders()); +} + +// Makes sure transitioning from non-instant to instant works. +TEST_F(LoaderManagerTest, NonInstantToInstant) { + MatchPreviewLoaderDelegateImpl delegate; + LoaderManager manager(&delegate); + scoped_ptr<MatchPreviewLoader> loader; + manager.UpdateLoader(0, &loader); + MatchPreviewLoader* current_loader = manager.current_loader(); + manager.UpdateLoader(1, &loader); + EXPECT_TRUE(loader.get() != NULL); + EXPECT_NE(current_loader, manager.current_loader()); + EXPECT_EQ(NULL, manager.pending_loader()); + EXPECT_EQ(1u, manager.num_instant_loaders()); +} + +// Makes sure instant loaders aren't deleted when invoking update with different +// ids. +TEST_F(LoaderManagerTest, DontDeleteInstantLoaders) { + MatchPreviewLoaderDelegateImpl delegate; + LoaderManager manager(&delegate); + scoped_ptr<MatchPreviewLoader> loader; + manager.UpdateLoader(1, &loader); + MatchPreviewLoader* current_loader = manager.current_loader(); + manager.UpdateLoader(2, &loader); + EXPECT_EQ(NULL, loader.get()); + EXPECT_NE(current_loader, manager.current_loader()); + EXPECT_EQ(NULL, manager.pending_loader()); + EXPECT_EQ(2u, manager.num_instant_loaders()); +} + +// Makes sure a new loader is created and assigned to secondary when +// transitioning from a ready non-instant to instant. +TEST_F(LoaderManagerTest, CreateSecondaryWhenReady) { + MatchPreviewLoaderDelegateImpl delegate; + LoaderManager manager(&delegate); + scoped_ptr<MatchPreviewLoader> loader; + manager.UpdateLoader(0, &loader); + MatchPreviewLoader* current_loader = manager.current_loader(); + ASSERT_TRUE(current_loader); + MarkReady(current_loader); + + manager.UpdateLoader(1, &loader); + EXPECT_EQ(NULL, loader.get()); + EXPECT_EQ(current_loader, manager.current_loader()); + EXPECT_TRUE(manager.pending_loader()); + EXPECT_NE(current_loader, manager.pending_loader()); + EXPECT_EQ(1u, manager.num_instant_loaders()); + + // Make the pending loader current. + MatchPreviewLoader* pending_loader = manager.pending_loader(); + manager.MakePendingCurrent(&loader); + EXPECT_TRUE(loader.get()); + EXPECT_EQ(pending_loader, manager.current_loader()); + EXPECT_EQ(NULL, manager.pending_loader()); + EXPECT_EQ(1u, manager.num_instant_loaders()); +} + +// Makes sure releasing an instant updates maps currectly. +TEST_F(LoaderManagerTest, ReleaseInstant) { + MatchPreviewLoaderDelegateImpl delegate; + LoaderManager manager(&delegate); + scoped_ptr<MatchPreviewLoader> loader; + manager.UpdateLoader(1, &loader); + scoped_ptr<MatchPreviewLoader> current_loader(manager.ReleaseCurrentLoader()); + EXPECT_TRUE(current_loader.get()); + EXPECT_EQ(NULL, manager.current_loader()); + EXPECT_EQ(0u, manager.num_instant_loaders()); +} + +// Tests transitioning from a non-instant ready loader to an instant ready +// loader is immediate. +TEST_F(LoaderManagerTest, NonInstantToInstantWhenReady) { + MatchPreviewLoaderDelegateImpl delegate; + LoaderManager manager(&delegate); + scoped_ptr<MatchPreviewLoader> loader; + manager.UpdateLoader(1, &loader); + ASSERT_TRUE(manager.current_loader()); + EXPECT_EQ(1, manager.current_loader()->template_url_id()); + MatchPreviewLoader* instant_loader = manager.current_loader(); + + manager.UpdateLoader(0, &loader); + MatchPreviewLoader* non_instant_loader = manager.current_loader(); + ASSERT_TRUE(non_instant_loader); + MarkReady(non_instant_loader); + EXPECT_NE(non_instant_loader, instant_loader); + + MarkReady(instant_loader); + manager.UpdateLoader(1, &loader); + EXPECT_EQ(non_instant_loader, loader.get()); + EXPECT_EQ(instant_loader, manager.current_loader()); + EXPECT_EQ(NULL, manager.pending_loader()); + EXPECT_EQ(1u, manager.num_instant_loaders()); +} + +// Tests transitioning between 3 instant loaders, all ready. +TEST_F(LoaderManagerTest, ThreeInstant) { + MatchPreviewLoaderDelegateImpl delegate; + LoaderManager manager(&delegate); + scoped_ptr<MatchPreviewLoader> loader; + manager.UpdateLoader(1, &loader); + ASSERT_TRUE(manager.current_loader()); + EXPECT_EQ(1, manager.current_loader()->template_url_id()); + MatchPreviewLoader* instant_loader1 = manager.current_loader(); + MarkReady(instant_loader1); + + manager.UpdateLoader(2, &loader); + MatchPreviewLoader* instant_loader2 = manager.pending_loader(); + ASSERT_TRUE(instant_loader2); + EXPECT_EQ(2, instant_loader2->template_url_id()); + EXPECT_NE(instant_loader1, instant_loader2); + EXPECT_EQ(instant_loader1, manager.current_loader()); + + manager.UpdateLoader(3, &loader); + MatchPreviewLoader* instant_loader3 = manager.pending_loader(); + ASSERT_TRUE(instant_loader3); + EXPECT_EQ(3, instant_loader3->template_url_id()); + EXPECT_NE(instant_loader1, instant_loader3); + EXPECT_NE(instant_loader2, instant_loader3); + EXPECT_EQ(instant_loader1, manager.current_loader()); +} diff --git a/chrome/browser/tab_contents/match_preview.cc b/chrome/browser/tab_contents/match_preview.cc index 7e8e63f..ef8baf2 100644 --- a/chrome/browser/tab_contents/match_preview.cc +++ b/chrome/browser/tab_contents/match_preview.cc @@ -4,463 +4,17 @@ #include "chrome/browser/tab_contents/match_preview.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/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/loader_manager.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/match_preview_loader.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 { - -// 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 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), - 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 MatchPreview releases ownership of the TabContents and - // the page hasn't finished loading. - void DetachFromPreview(bool pressed_enter) { - match_preview_ = 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 (match_preview_) { - gfx::Rect bounds = match_preview_->GetOmniboxBoundsInTermsOfPreview(); - if (!bounds.IsEmpty()) - SendOmniboxBoundsScript(tab_contents_, bounds); - } - - SendUserInputScript(tab_contents_, text_); - - if (match_preview_) - match_preview_->PageFinishedLoading(); - else - SendDoneScript(tab_contents_, text_, pressed_enter_); - - 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 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 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_->ShowPreview(); - 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), - 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 = 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, - 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(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) { - } - 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 (!match_preview_->is_active()) { - // 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; - match_preview_->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 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; - - void CommitFromMouseReleaseIfNecessary() { - bool was_down = is_mouse_down_from_activate_; - is_mouse_down_from_activate_ = false; - if (was_down && match_preview_->commit_on_mouse_up_) - match_preview_->CommitCurrentPreview(MatchPreview::COMMIT_FOCUS_LOST); - } - - MatchPreview* match_preview_; - - // 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 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_; - - // 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); -}; +#include "chrome/common/url_constants.h" // static bool MatchPreview::IsEnabled() { @@ -478,16 +32,11 @@ MatchPreview::MatchPreview(MatchPreviewDelegate* delegate) : delegate_(delegate), tab_contents_(NULL), is_active_(false), - template_url_id_(0), commit_on_mouse_up_(false), last_transition_type_(PageTransition::LINK) { - 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, @@ -497,42 +46,22 @@ void MatchPreview::Update(TabContents* tab_contents, if (tab_contents != tab_contents_) DestroyPreviewContents(); + const GURL& url = match.destination_url; + tab_contents_ = tab_contents; commit_on_mouse_up_ = false; last_transition_type_ = match.transition; - if (url_ == match.destination_url) + if (loader_manager_.get() && loader_manager_->active_loader()->url() == url) return; - url_ = match.destination_url; - - if (url_.is_empty() || !url_.is_valid()) { + if (url.is_empty() || !url.is_valid() || !ShouldShowPreviewFor(url)) { DestroyPreviewContents(); return; } - user_text_ = user_text; - - 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(); - } - preview_tab_contents_delegate_->PrepareForNewLoad(); + if (!loader_manager_.get()) + loader_manager_.reset(new LoaderManager(this)); const TemplateURL* template_url = match.template_url; if (match.type == AutocompleteMatch::SEARCH_WHAT_YOU_TYPED || @@ -541,63 +70,52 @@ void MatchPreview::Update(TabContents* tab_contents, TemplateURLModel* model = tab_contents->profile()->GetTemplateURLModel(); template_url = model ? model->GetDefaultSearchProvider() : NULL; } + // TODO(sky): remove the id check. It's only necessary because the search + // engine saved to prefs doesn't have an id. Jean-luc is fixing separately. + if (template_url && (!template_url->supports_instant() || + !template_url->id() || + !TemplateURL::SupportsReplacement(template_url))) { + template_url = 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 (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 { - template_url_id_ = 0; - frame_load_observer_.reset(NULL); - preview_contents_->controller().LoadURL(url_, GURL(), match.transition); - } + MatchPreviewLoader* old_loader = loader_manager_->current_loader(); + scoped_ptr<MatchPreviewLoader> owned_loader; + MatchPreviewLoader* new_loader = + loader_manager_->UpdateLoader(template_url_id, &owned_loader); - template_url_id_ = template_url_id; + new_loader->SetOmniboxBounds(omnibox_bounds_); + new_loader->Update(tab_contents, match, user_text, template_url, + suggested_text); + if (old_loader != new_loader && new_loader->ready()) + delegate_->ShowMatchPreview(new_loader->preview_contents()); } void MatchPreview::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()); + if (loader_manager_.get()) { + if (loader_manager_->current_loader()) + loader_manager_->current_loader()->SetOmniboxBounds(bounds); + if (loader_manager_->pending_loader()) + loader_manager_->pending_loader()->SetOmniboxBounds(bounds); } } void MatchPreview::DestroyPreviewContents() { - if (!preview_contents_.get()) { + if (!loader_manager_.get()) { // We're not showing anything, nothing to do. return; } delegate_->HideMatchPreview(); - delete ReleasePreviewContents(COMMIT_DESTROY); + delete ReleasePreviewContents(MATCH_PREVIEW_COMMIT_DESTROY); } -void MatchPreview::CommitCurrentPreview(CommitType type) { - DCHECK(preview_contents_.get()); +void MatchPreview::CommitCurrentPreview(MatchPreviewCommitType type) { + DCHECK(loader_manager_.get()); + DCHECK(loader_manager_->current_loader()); delegate_->CommitMatchPreview(ReleasePreviewContents(type)); } @@ -606,81 +124,75 @@ void MatchPreview::SetCommitOnMouseUp() { } bool MatchPreview::IsMouseDownFromActivate() { - return preview_tab_contents_delegate_->is_mouse_down_from_activate(); + DCHECK(loader_manager_.get()); + DCHECK(loader_manager_->current_loader()); + return loader_manager_->current_loader()->IsMouseDownFromActivate(); } -TabContents* MatchPreview::ReleasePreviewContents(CommitType type) { - if (!preview_contents_.get()) +TabContents* MatchPreview::ReleasePreviewContents(MatchPreviewCommitType type) { + if (!loader_manager_.get()) return NULL; - if (frame_load_observer_.get()) { - frame_load_observer_->DetachFromPreview(type == 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 != COMMIT_DESTROY && is_showing_instant()) { - SendDoneScript(preview_contents_.get(), - user_text_, - type == COMMIT_PRESSED_ENTER); - } - commit_on_mouse_up_ = false; + scoped_ptr<MatchPreviewLoader> loader( + loader_manager_->ReleaseCurrentLoader()); + TabContents* tab = loader->ReleasePreviewContents(type); + + is_active_ = false; omnibox_bounds_ = gfx::Rect(); - template_url_id_ = 0; - url_ = GURL(); - user_text_.clear(); - complete_suggested_text_.clear(); - if (preview_contents_.get()) { - if (type != 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(); - is_active_ = false; - } - return preview_contents_.release(); + commit_on_mouse_up_ = false; + loader_manager_.reset(NULL); + return tab; } -void MatchPreview::SetCompleteSuggestedText( - const string16& complete_suggested_text) { - if (complete_suggested_text == complete_suggested_text_) - return; +TabContents* MatchPreview::GetPreviewContents() { + return loader_manager_.get() ? + loader_manager_->current_loader()->preview_contents() : NULL; +} - 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; +bool MatchPreview::IsShowingInstant() { + return loader_manager_.get() && + loader_manager_->current_loader()->is_showing_instant(); +} + +void MatchPreview::ShowMatchPreviewLoader(MatchPreviewLoader* loader) { + DCHECK(loader_manager_.get()); + if (loader_manager_->current_loader() == loader) { + is_active_ = true; + delegate_->ShowMatchPreview(loader->preview_contents()); + } else if (loader_manager_->pending_loader() == loader) { + scoped_ptr<MatchPreviewLoader> old_loader; + loader_manager_->MakePendingCurrent(&old_loader); + delegate_->ShowMatchPreview(loader->preview_contents()); + } else { + NOTREACHED(); } +} - complete_suggested_text_ = complete_suggested_text; - delegate_->SetSuggestedText( - complete_suggested_text_.substr(user_text_.size())); +void MatchPreview::SetSuggestedTextFor(MatchPreviewLoader* loader, + const string16& text) { + if (loader_manager_->current_loader() == loader) + delegate_->SetSuggestedText(text); } -void MatchPreview::ShowPreview() { - DCHECK(!is_active_); - is_active_ = true; - delegate_->ShowMatchPreview(); +gfx::Rect MatchPreview::GetMatchPreviewBounds() { + return delegate_->GetMatchPreviewBounds(); } -void MatchPreview::PageFinishedLoading() { - // FrameLoadObserver deletes itself after this call. - FrameLoadObserver* unused ALLOW_UNUSED = frame_load_observer_.release(); +bool MatchPreview::ShouldCommitPreviewOnMouseUp() { + return commit_on_mouse_up_; } -gfx::Rect MatchPreview::GetOmniboxBoundsInTermsOfPreview() { - if (omnibox_bounds_.IsEmpty()) - return omnibox_bounds_; +void MatchPreview::CommitPreview(MatchPreviewLoader* loader) { + if (loader_manager_.get() && loader_manager_->current_loader() == loader) { + CommitCurrentPreview(MATCH_PREVIEW_COMMIT_FOCUS_LOST); + } else { + // This can happen if the mouse was down, we swapped out the preview and + // the mouse was released. Generally this shouldn't happen, but if it does + // revert. + DestroyPreviewContents(); + } +} - gfx::Rect preview_bounds(delegate_->GetMatchPreviewBounds()); - return gfx::Rect(omnibox_bounds_.x() - preview_bounds.x(), - omnibox_bounds_.y() - preview_bounds.y(), - omnibox_bounds_.width(), - omnibox_bounds_.height()); +bool MatchPreview::ShouldShowPreviewFor(const GURL& url) { + return !url.SchemeIs(chrome::kJavaScriptScheme); } diff --git a/chrome/browser/tab_contents/match_preview.h b/chrome/browser/tab_contents/match_preview.h index 5b59528f..7d2d7f0 100644 --- a/chrome/browser/tab_contents/match_preview.h +++ b/chrome/browser/tab_contents/match_preview.h @@ -9,13 +9,15 @@ #include "base/basictypes.h" #include "base/scoped_ptr.h" #include "base/string16.h" -#include "base/timer.h" #include "chrome/browser/search_engines/template_url_id.h" +#include "chrome/browser/tab_contents/match_preview_commit_type.h" +#include "chrome/browser/tab_contents/match_preview_loader_delegate.h" #include "chrome/common/page_transition_types.h" #include "gfx/rect.h" #include "googleurl/src/gurl.h" struct AutocompleteMatch; +class LoaderManager; class MatchPreviewDelegate; class TabContents; @@ -27,20 +29,8 @@ class TabContents; // invoked on the delegate. Similarly the preview may be committed at any time // by invoking |CommitCurrentPreview|, which results in |CommitMatchPreview| // being invoked on the delegate. -class MatchPreview { +class MatchPreview : public MatchPreviewLoaderDelegate { public: - enum CommitType { - // The commit is the result of the user pressing enter. - COMMIT_PRESSED_ENTER, - - // The commit is the result of focus being lost. This typically corresponds - // to a mouse click event. - COMMIT_FOCUS_LOST, - - // Used internally. - COMMIT_DESTROY - }; - explicit MatchPreview(MatchPreviewDelegate* delegate); ~MatchPreview(); @@ -67,13 +57,12 @@ class MatchPreview { // Invoked when the user does some gesture that should trigger making the // current previewed page the permanent page. - void CommitCurrentPreview(CommitType type); + void CommitCurrentPreview(MatchPreviewCommitType type); // Sets MatchPreview so that when the mouse is released the preview is // committed. void SetCommitOnMouseUp(); - // Returns true if the preview will be committed on mouse up. bool commit_on_mouse_up() const { return commit_on_mouse_up_; } // Returns true if the mouse is down as the result of activating the preview @@ -83,13 +72,13 @@ class MatchPreview { // Releases the preview TabContents passing ownership to the caller. This is // intended to be called when the preview TabContents is committed. This does // not notify the delegate. - TabContents* ReleasePreviewContents(CommitType type); + TabContents* ReleasePreviewContents(MatchPreviewCommitType type); // TabContents the match is being shown for. TabContents* tab_contents() const { return tab_contents_; } // The preview TabContents; may be null. - TabContents* preview_contents() const { return preview_contents_.get(); } + TabContents* GetPreviewContents(); // Returns true if the preview TabContents is active. In some situations this // may return false yet preview_contents() returns non-NULL. @@ -100,16 +89,18 @@ class MatchPreview { return last_transition_type_; } - const GURL& url() const { return url_; } - // Are we showing instant results? - bool is_showing_instant() const { return template_url_id_ != 0; } + bool IsShowingInstant(); - private: - class FrameLoadObserver; - class PaintObserverImpl; - class TabContentsDelegateImpl; + // MatchPreviewLoaderDelegate + virtual void ShowMatchPreviewLoader(MatchPreviewLoader* loader); + virtual void SetSuggestedTextFor(MatchPreviewLoader* loader, + const string16& text); + virtual gfx::Rect GetMatchPreviewBounds(); + virtual bool ShouldCommitPreviewOnMouseUp(); + virtual void CommitPreview(MatchPreviewLoader* loader); + private: // Invoked when the page wants to update the suggested text. If |user_text_| // starts with |suggested_text|, then the delegate is notified of the change, // which results in updating the omnibox. @@ -123,54 +114,29 @@ class MatchPreview { // Invoked once the page has finished loading and the script has been sent. void PageFinishedLoading(); - // Returns the bounds of the omnibox in terms of the preview tab contents. - gfx::Rect GetOmniboxBoundsInTermsOfPreview(); - - // Are we waiting for the preview page to finish loading? - bool is_waiting_for_load() const { - return frame_load_observer_.get() != NULL; - } + // Returns true if we should show preview for |url|. + bool ShouldShowPreviewFor(const GURL& url); MatchPreviewDelegate* delegate_; // The TabContents last passed to |Update|. TabContents* tab_contents_; - // The url we're displaying. - GURL url_; - - // Delegate of the preview TabContents. Used to detect when the user does some - // gesture on the TabContents and the preview needs to be activated. - scoped_ptr<TabContentsDelegateImpl> preview_tab_contents_delegate_; - - // The preview TabContents; may be null. - scoped_ptr<TabContents> preview_contents_; - // Has notification been sent out that the preview TabContents is ready to be // shown? bool is_active_; - // The text the user typed in the omnibox. - string16 user_text_; - - // The latest suggestion from the page. - string16 complete_suggested_text_; - - // If we're showing instant results this is the ID of the TemplateURL driving - // the results. A value of 0 means there is no TemplateURL. - TemplateURLID template_url_id_; - // See description above setter. gfx::Rect omnibox_bounds_; - scoped_ptr<FrameLoadObserver> frame_load_observer_; - // See description above CommitOnMouseUp. bool commit_on_mouse_up_; // See description above getter. PageTransition::Type last_transition_type_; + scoped_ptr<LoaderManager> loader_manager_; + DISALLOW_COPY_AND_ASSIGN(MatchPreview); }; diff --git a/chrome/browser/tab_contents/match_preview_commit_type.h b/chrome/browser/tab_contents/match_preview_commit_type.h new file mode 100644 index 0000000..a4bbd3f --- /dev/null +++ b/chrome/browser/tab_contents/match_preview_commit_type.h @@ -0,0 +1,22 @@ +// 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. + +#ifndef CHROME_BROWSER_TAB_CONTENTS_MATCH_PREVIEW_COMMIT_TYPE_H_ +#define CHROME_BROWSER_TAB_CONTENTS_MATCH_PREVIEW_COMMIT_TYPE_H_ +#pragma once + +// Enum describing the ways the MatchPreview can be committed. +enum MatchPreviewCommitType { + // The commit is the result of the user pressing enter. + MATCH_PREVIEW_COMMIT_PRESSED_ENTER, + + // The commit is the result of focus being lost. This typically corresponds + // to a mouse click event. + MATCH_PREVIEW_COMMIT_FOCUS_LOST, + + // Used internally by MatchPreview. + MATCH_PREVIEW_COMMIT_DESTROY +}; + +#endif // CHROME_BROWSER_TAB_CONTENTS_MATCH_PREVIEW_COMMIT_TYPE_H_ diff --git a/chrome/browser/tab_contents/match_preview_delegate.h b/chrome/browser/tab_contents/match_preview_delegate.h index 8506cef..fed4d89 100644 --- a/chrome/browser/tab_contents/match_preview_delegate.h +++ b/chrome/browser/tab_contents/match_preview_delegate.h @@ -19,7 +19,7 @@ class Rect; class MatchPreviewDelegate { public: // Invoked when the preview TabContents should be shown. - virtual void ShowMatchPreview() = 0; + virtual void ShowMatchPreview(TabContents* preview_contents) = 0; // Invoked when the preview TabContents should be hidden. virtual void HideMatchPreview() = 0; diff --git a/chrome/browser/tab_contents/match_preview_loader.cc b/chrome/browser/tab_contents/match_preview_loader.cc new file mode 100644 index 0000000..f7ff7ed --- /dev/null +++ b/chrome/browser/tab_contents/match_preview_loader.cc @@ -0,0 +1,659 @@ +// 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/tab_contents/match_preview_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/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/match_preview_loader_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/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 MatchPreviewLoader::FrameLoadObserver : public NotificationObserver { + public: + FrameLoadObserver(MatchPreviewLoader* 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), + 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 MatchPreviewLoader releases ownership of the TabContents + // and the page hasn't finished loading. + void DetachFromPreview(bool pressed_enter) { + match_preview_ = 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 (match_preview_) { + gfx::Rect bounds = match_preview_->GetOmniboxBoundsInTermsOfPreview(); + if (!bounds.IsEmpty()) + SendOmniboxBoundsScript(tab_contents_, bounds); + } + + SendUserInputScript(tab_contents_, text_); + + if (match_preview_) + match_preview_->PageFinishedLoading(); + else + SendDoneScript(tab_contents_, text_, pressed_enter_); + + delete this; + return; + } + + case NotificationType::TAB_CONTENTS_DESTROYED: + delete this; + return; + + default: + NOTREACHED(); + break; + } + } + + private: + // MatchPreviewLoader that created us. + MatchPreviewLoader* 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 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 MatchPreviewLoader, which makes the TabContents active. +class MatchPreviewLoader::PaintObserverImpl + : public RenderWidgetHost::PaintObserver { + public: + explicit PaintObserverImpl(MatchPreviewLoader* preview) + : match_preview_(preview) { + } + + virtual void RenderWidgetHostWillPaint(RenderWidgetHost* rwh) { + } + + virtual void RenderWidgetHostDidPaint(RenderWidgetHost* rwh) { + match_preview_->PreviewPainted(); + rwh->set_paint_observer(NULL); + // WARNING: we've been deleted. + } + + private: + MatchPreviewLoader* match_preview_; + + DISALLOW_COPY_AND_ASSIGN(PaintObserverImpl); +}; + +class MatchPreviewLoader::TabContentsDelegateImpl : public TabContentsDelegate { + public: + explicit TabContentsDelegateImpl(MatchPreviewLoader* match_preview) + : match_preview_(match_preview), + 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 = 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, + 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(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) { + } + 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 (!match_preview_->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; + match_preview_->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 = 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; + + void CommitFromMouseReleaseIfNecessary() { + bool was_down = is_mouse_down_from_activate_; + is_mouse_down_from_activate_ = false; + if (was_down && match_preview_->ShouldCommitPreviewOnMouseUp()) + match_preview_->CommitPreview(); + } + + MatchPreviewLoader* match_preview_; + + // 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 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_; + + // 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); +}; + +MatchPreviewLoader::MatchPreviewLoader(MatchPreviewLoaderDelegate* delegate, + TemplateURLID id) + : delegate_(delegate), + template_url_id_(id), + ready_(false) { + preview_tab_contents_delegate_.reset(new TabContentsDelegateImpl(this)); +} + +MatchPreviewLoader::~MatchPreviewLoader() { + // Delete the TabContents before the delegate as the TabContents holds a + // reference to the delegate. + preview_contents_.reset(NULL); +} + +void MatchPreviewLoader::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 MatchPreviewLoader::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 MatchPreviewLoader::DestroyPreviewContents() { + if (!preview_contents_.get()) { + // We're not showing anything, nothing to do. + return; + } + + delete ReleasePreviewContents(MATCH_PREVIEW_COMMIT_DESTROY); +} + +bool MatchPreviewLoader::IsMouseDownFromActivate() { + return preview_tab_contents_delegate_->is_mouse_down_from_activate(); +} + +TabContents* MatchPreviewLoader::ReleasePreviewContents( + MatchPreviewCommitType type) { + if (!preview_contents_.get()) + return NULL; + + if (frame_load_observer_.get()) { + frame_load_observer_->DetachFromPreview( + type == MATCH_PREVIEW_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 != MATCH_PREVIEW_COMMIT_DESTROY && is_showing_instant()) { + SendDoneScript(preview_contents_.get(), + user_text_, + type == MATCH_PREVIEW_COMMIT_PRESSED_ENTER); + } + omnibox_bounds_ = gfx::Rect(); + url_ = GURL(); + user_text_.clear(); + complete_suggested_text_.clear(); + if (preview_contents_.get()) { + if (type != MATCH_PREVIEW_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 MatchPreviewLoader::ShouldCommitPreviewOnMouseUp() { + return delegate_->ShouldCommitPreviewOnMouseUp(); +} + +void MatchPreviewLoader::CommitPreview() { + delegate_->CommitPreview(this); +} + +void MatchPreviewLoader::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 MatchPreviewLoader::PreviewPainted() { + ShowPreview(); +} + +void MatchPreviewLoader::ShowPreview() { + if (!ready_) { + ready_ = true; + delegate_->ShowMatchPreviewLoader(this); + } +} + +void MatchPreviewLoader::PageFinishedLoading() { + // FrameLoadObserver deletes itself after this call. + FrameLoadObserver* unused ALLOW_UNUSED = frame_load_observer_.release(); +} + +gfx::Rect MatchPreviewLoader::GetOmniboxBoundsInTermsOfPreview() { + if (omnibox_bounds_.IsEmpty()) + return omnibox_bounds_; + + gfx::Rect preview_bounds(delegate_->GetMatchPreviewBounds()); + return gfx::Rect(omnibox_bounds_.x() - preview_bounds.x(), + omnibox_bounds_.y() - preview_bounds.y(), + omnibox_bounds_.width(), + omnibox_bounds_.height()); +} diff --git a/chrome/browser/tab_contents/match_preview_loader.h b/chrome/browser/tab_contents/match_preview_loader.h new file mode 100644 index 0000000..be96378 --- /dev/null +++ b/chrome/browser/tab_contents/match_preview_loader.h @@ -0,0 +1,139 @@ +// 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. + +#ifndef CHROME_BROWSER_TAB_CONTENTS_MATCH_PREVIEW_LOADER_H_ +#define CHROME_BROWSER_TAB_CONTENTS_MATCH_PREVIEW_LOADER_H_ +#pragma once + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "base/string16.h" +#include "chrome/browser/search_engines/template_url_id.h" +#include "chrome/browser/tab_contents/match_preview_commit_type.h" +#include "chrome/common/page_transition_types.h" +#include "gfx/rect.h" +#include "googleurl/src/gurl.h" + +struct AutocompleteMatch; +class LoaderManagerTest; +class MatchPreviewLoaderDelegate; +class TabContents; +class TemplateURL; + +// MatchPreviewLoader does the loading of a particular URL for MatchPreview. +// MatchPreviewLoader notifies its delegate, which is typically MatchPreview, +// of all interesting events. +class MatchPreviewLoader { + public: + MatchPreviewLoader(MatchPreviewLoaderDelegate* delegate, TemplateURLID id); + ~MatchPreviewLoader(); + + // Invoked to load a URL. |tab_contents| is the TabContents the preview is + // going to be shown on top of and potentially replace. + void Update(TabContents* tab_contents, + const AutocompleteMatch& match, + const string16& user_text, + const TemplateURL* template_url, + string16* suggested_text); + + // Sets the bounds of the omnibox (in screen coordinates). The bounds are + // remembered until the preview is committed or destroyed. This is only used + // when showing results for a search provider that supports instant. + void SetOmniboxBounds(const gfx::Rect& bounds); + + // Destroys the preview TabContents. Does nothing if the preview TabContents + // has not been created. + void DestroyPreviewContents(); + + // Returns true if the mouse is down as the result of activating the preview + // content. + bool IsMouseDownFromActivate(); + + // Releases the preview TabContents passing ownership to the caller. This is + // intended to be called when the preview TabContents is committed. This does + // not notify the delegate. + TabContents* ReleasePreviewContents(MatchPreviewCommitType type); + + // Calls through to method of same name on delegate. + bool ShouldCommitPreviewOnMouseUp(); + void CommitPreview(); + + // The preview TabContents; may be null. + TabContents* preview_contents() const { return preview_contents_.get(); } + + // Returns true if the preview TabContents is ready to be shown. + bool ready() const { return ready_; } + + const GURL& url() const { return url_; } + + // Are we showing instant results? + bool is_showing_instant() const { return template_url_id_ != 0; } + + // If we're showing instant this returns non-zero. + TemplateURLID template_url_id() const { return template_url_id_; } + + private: + friend class LoaderManagerTest; + class FrameLoadObserver; + class PaintObserverImpl; + class TabContentsDelegateImpl; + + // Invoked when the page wants to update the suggested text. If |user_text_| + // starts with |suggested_text|, then the delegate is notified of the change, + // which results in updating the omnibox. + void SetCompleteSuggestedText(const string16& suggested_text); + + // Invoked when the page paints. + void PreviewPainted(); + + // Invoked to show the preview. This is invoked in two possible cases: when + // the renderer paints, or when an auth dialog is shown. This notifies the + // delegate the preview is ready to be shown. + void ShowPreview(); + + // Invoked once the page has finished loading and the script has been sent. + void PageFinishedLoading(); + + // Returns the bounds of the omnibox in terms of the preview tab contents. + gfx::Rect GetOmniboxBoundsInTermsOfPreview(); + + // Are we waiting for the preview page to finish loading? + bool is_waiting_for_load() const { + return frame_load_observer_.get() != NULL; + } + + MatchPreviewLoaderDelegate* delegate_; + + // If we're showing instant results this is the ID of the TemplateURL driving + // the results. A value of 0 means there is no TemplateURL. + const TemplateURLID template_url_id_; + + // The url we're displaying. + GURL url_; + + // Delegate of the preview TabContents. Used to detect when the user does some + // gesture on the TabContents and the preview needs to be activated. + scoped_ptr<TabContentsDelegateImpl> preview_tab_contents_delegate_; + + // The preview TabContents; may be null. + scoped_ptr<TabContents> preview_contents_; + + // Is the preview_contents ready to be shown? + bool ready_; + + // The text the user typed in the omnibox. + string16 user_text_; + + // The latest suggestion from the page. + string16 complete_suggested_text_; + + // See description above setter. + gfx::Rect omnibox_bounds_; + + scoped_ptr<FrameLoadObserver> frame_load_observer_; + + DISALLOW_COPY_AND_ASSIGN(MatchPreviewLoader); +}; + +#endif // CHROME_BROWSER_TAB_CONTENTS_MATCH_PREVIEW_LOADER_H_ diff --git a/chrome/browser/tab_contents/match_preview_loader_delegate.h b/chrome/browser/tab_contents/match_preview_loader_delegate.h new file mode 100644 index 0000000..3384b0c --- /dev/null +++ b/chrome/browser/tab_contents/match_preview_loader_delegate.h @@ -0,0 +1,38 @@ +// 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. + +#ifndef CHROME_BROWSER_TAB_CONTENTS_MATCH_PREVIEW_LOADER_DELEGATE_H_ +#define CHROME_BROWSER_TAB_CONTENTS_MATCH_PREVIEW_LOADER_DELEGATE_H_ +#pragma once + +#include "base/string16.h" + +namespace gfx { +class Rect; +} + +class MatchPreviewLoader; + +// MatchPreviewLoader's delegate. This interface is implemented by +// MatchPreview. +class MatchPreviewLoaderDelegate { + public: + // Invoked when the loader is ready to be shown. + virtual void ShowMatchPreviewLoader(MatchPreviewLoader* loader) = 0; + + // Invoked when the renderer has suggested text. + virtual void SetSuggestedTextFor(MatchPreviewLoader* loader, + const string16& text) = 0; + + // Returns the bounds of the match preview. + virtual gfx::Rect GetMatchPreviewBounds() = 0; + + // Returns true if preview should be committed on mouse up. + virtual bool ShouldCommitPreviewOnMouseUp() = 0; + + // Invoked when the preview should be committed. + virtual void CommitPreview(MatchPreviewLoader* loader) = 0; +}; + +#endif // CHROME_BROWSER_TAB_CONTENTS_MATCH_PREVIEW_LOADER_DELEGATE_H_ |