summaryrefslogtreecommitdiffstats
path: root/chrome/browser/instant
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/instant')
-rw-r--r--chrome/browser/instant/instant_commit_type.h22
-rw-r--r--chrome/browser/instant/instant_controller.cc197
-rw-r--r--chrome/browser/instant/instant_controller.h143
-rw-r--r--chrome/browser/instant/instant_delegate.h42
-rw-r--r--chrome/browser/instant/instant_loader.cc656
-rw-r--r--chrome/browser/instant/instant_loader.h139
-rw-r--r--chrome/browser/instant/instant_loader_delegate.h40
-rw-r--r--chrome/browser/instant/instant_loader_manager.cc124
-rw-r--r--chrome/browser/instant/instant_loader_manager.h97
-rw-r--r--chrome/browser/instant/instant_loader_manager_unittest.cc209
10 files changed, 1669 insertions, 0 deletions
diff --git a/chrome/browser/instant/instant_commit_type.h b/chrome/browser/instant/instant_commit_type.h
new file mode 100644
index 0000000..f73c08b
--- /dev/null
+++ b/chrome/browser/instant/instant_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_INSTANT_INSTANT_COMMIT_TYPE_H_
+#define CHROME_BROWSER_INSTANT_INSTANT_COMMIT_TYPE_H_
+#pragma once
+
+// Enum describing the ways instant can be committed.
+enum InstantCommitType {
+ // The commit is the result of the user pressing enter.
+ INSTANT_COMMIT_PRESSED_ENTER,
+
+ // The commit is the result of focus being lost. This typically corresponds
+ // to a mouse click event.
+ INSTANT_COMMIT_FOCUS_LOST,
+
+ // Used internally by InstantController.
+ INSTANT_COMMIT_DESTROY
+};
+
+#endif // CHROME_BROWSER_INSTANT_INSTANT_COMMIT_TYPE_H_
diff --git a/chrome/browser/instant/instant_controller.cc b/chrome/browser/instant/instant_controller.cc
new file mode 100644
index 0000000..a095696
--- /dev/null
+++ b/chrome/browser/instant/instant_controller.cc
@@ -0,0 +1,197 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/instant/instant_controller.h"
+
+#include "base/command_line.h"
+#include "chrome/browser/autocomplete/autocomplete.h"
+#include "chrome/browser/instant/instant_delegate.h"
+#include "chrome/browser/instant/instant_loader.h"
+#include "chrome/browser/instant/instant_loader_manager.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/search_engines/template_url_model.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/url_constants.h"
+
+// static
+bool InstantController::IsEnabled() {
+ static bool enabled = false;
+ static bool checked = false;
+ if (!checked) {
+ checked = true;
+ enabled = CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kEnableMatchPreview);
+ }
+ return enabled;
+}
+
+InstantController::InstantController(InstantDelegate* delegate)
+ : delegate_(delegate),
+ tab_contents_(NULL),
+ is_active_(false),
+ commit_on_mouse_up_(false),
+ last_transition_type_(PageTransition::LINK) {
+}
+
+InstantController::~InstantController() {
+}
+
+void InstantController::Update(TabContents* tab_contents,
+ const AutocompleteMatch& match,
+ const string16& user_text,
+ string16* suggested_text) {
+ 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 (loader_manager_.get() && loader_manager_->active_loader()->url() == url)
+ return;
+
+ if (url.is_empty() || !url.is_valid() || !ShouldShowPreviewFor(url)) {
+ DestroyPreviewContents();
+ return;
+ }
+
+ if (!loader_manager_.get())
+ loader_manager_.reset(new InstantLoaderManager(this));
+
+ 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;
+ }
+ // 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;
+
+ InstantLoader* old_loader = loader_manager_->current_loader();
+ scoped_ptr<InstantLoader> owned_loader;
+ InstantLoader* new_loader =
+ loader_manager_->UpdateLoader(template_url_id, &owned_loader);
+
+ 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_->ShowInstant(new_loader->preview_contents());
+}
+
+void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) {
+ if (omnibox_bounds_ == bounds)
+ return;
+
+ 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 InstantController::DestroyPreviewContents() {
+ if (!loader_manager_.get()) {
+ // We're not showing anything, nothing to do.
+ return;
+ }
+
+ delegate_->HideInstant();
+ delete ReleasePreviewContents(INSTANT_COMMIT_DESTROY);
+}
+
+void InstantController::CommitCurrentPreview(InstantCommitType type) {
+ DCHECK(loader_manager_.get());
+ DCHECK(loader_manager_->current_loader());
+ delegate_->CommitInstant(ReleasePreviewContents(type));
+}
+
+void InstantController::SetCommitOnMouseUp() {
+ commit_on_mouse_up_ = true;
+}
+
+bool InstantController::IsMouseDownFromActivate() {
+ DCHECK(loader_manager_.get());
+ DCHECK(loader_manager_->current_loader());
+ return loader_manager_->current_loader()->IsMouseDownFromActivate();
+}
+
+TabContents* InstantController::ReleasePreviewContents(InstantCommitType type) {
+ if (!loader_manager_.get())
+ return NULL;
+
+ scoped_ptr<InstantLoader> loader(loader_manager_->ReleaseCurrentLoader());
+ TabContents* tab = loader->ReleasePreviewContents(type);
+
+ is_active_ = false;
+ omnibox_bounds_ = gfx::Rect();
+ commit_on_mouse_up_ = false;
+ loader_manager_.reset(NULL);
+ return tab;
+}
+
+TabContents* InstantController::GetPreviewContents() {
+ return loader_manager_.get() ?
+ loader_manager_->current_loader()->preview_contents() : NULL;
+}
+
+bool InstantController::IsShowingInstant() {
+ return loader_manager_.get() &&
+ loader_manager_->current_loader()->is_showing_instant();
+}
+
+void InstantController::ShowInstantLoader(InstantLoader* loader) {
+ DCHECK(loader_manager_.get());
+ if (loader_manager_->current_loader() == loader) {
+ is_active_ = true;
+ delegate_->ShowInstant(loader->preview_contents());
+ } else if (loader_manager_->pending_loader() == loader) {
+ scoped_ptr<InstantLoader> old_loader;
+ loader_manager_->MakePendingCurrent(&old_loader);
+ delegate_->ShowInstant(loader->preview_contents());
+ } else {
+ NOTREACHED();
+ }
+}
+
+void InstantController::SetSuggestedTextFor(InstantLoader* loader,
+ const string16& text) {
+ if (loader_manager_->current_loader() == loader)
+ delegate_->SetSuggestedText(text);
+}
+
+gfx::Rect InstantController::GetInstantBounds() {
+ return delegate_->GetInstantBounds();
+}
+
+bool InstantController::ShouldCommitInstantOnMouseUp() {
+ return commit_on_mouse_up_;
+}
+
+void InstantController::CommitInstantLoader(InstantLoader* loader) {
+ if (loader_manager_.get() && loader_manager_->current_loader() == loader) {
+ CommitCurrentPreview(INSTANT_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();
+ }
+}
+
+bool InstantController::ShouldShowPreviewFor(const GURL& url) {
+ return !url.SchemeIs(chrome::kJavaScriptScheme);
+}
diff --git a/chrome/browser/instant/instant_controller.h b/chrome/browser/instant/instant_controller.h
new file mode 100644
index 0000000..678a9e1
--- /dev/null
+++ b/chrome/browser/instant/instant_controller.h
@@ -0,0 +1,143 @@
+// 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_INSTANT_INSTANT_CONTROLLER_H_
+#define CHROME_BROWSER_INSTANT_INSTANT_CONTROLLER_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "base/string16.h"
+#include "chrome/browser/instant/instant_commit_type.h"
+#include "chrome/browser/instant/instant_loader_delegate.h"
+#include "chrome/browser/search_engines/template_url_id.h"
+#include "chrome/common/page_transition_types.h"
+#include "gfx/rect.h"
+#include "googleurl/src/gurl.h"
+
+struct AutocompleteMatch;
+class InstantDelegate;
+class InstantLoaderManager;
+class TabContents;
+
+// InstantController maintains a TabContents that is intended to give a preview
+// of a URL. InstantController is owned by Browser.
+//
+// At any time the TabContents maintained by InstantController may be destroyed
+// by way of |DestroyPreviewContents|, which results in |HideInstant| being
+// invoked on the delegate. Similarly the preview may be committed at any time
+// by invoking |CommitCurrentPreview|, which results in |CommitInstant|
+// being invoked on the delegate.
+class InstantController : public InstantLoaderDelegate {
+ public:
+ explicit InstantController(InstantDelegate* delegate);
+ ~InstantController();
+
+ // Is InstantController enabled?
+ static bool IsEnabled();
+
+ // Invoked as the user types in the omnibox with the url to navigate to. If
+ // the url is empty and there is a preview TabContents it is destroyed. If url
+ // is non-empty and the preview TabContents has not been created it is
+ // created.
+ void Update(TabContents* tab_contents,
+ const AutocompleteMatch& match,
+ const string16& user_text,
+ 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();
+
+ // Invoked when the user does some gesture that should trigger making the
+ // current previewed page the permanent page.
+ void CommitCurrentPreview(InstantCommitType type);
+
+ // Sets InstantController so that when the mouse is released the preview is
+ // committed.
+ void SetCommitOnMouseUp();
+
+ 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
+ // 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(InstantCommitType type);
+
+ // TabContents the match is being shown for.
+ TabContents* tab_contents() const { return tab_contents_; }
+
+ // The preview TabContents; may be null.
+ TabContents* GetPreviewContents();
+
+ // Returns true if the preview TabContents is active. In some situations this
+ // may return false yet preview_contents() returns non-NULL.
+ bool is_active() const { return is_active_; }
+
+ // Returns the transition type of the last AutocompleteMatch passed to Update.
+ PageTransition::Type last_transition_type() const {
+ return last_transition_type_;
+ }
+
+ // Are we showing instant results?
+ bool IsShowingInstant();
+
+ // InstantLoaderDelegate
+ virtual void ShowInstantLoader(InstantLoader* loader);
+ virtual void SetSuggestedTextFor(InstantLoader* loader,
+ const string16& text);
+ virtual gfx::Rect GetInstantBounds();
+ virtual bool ShouldCommitInstantOnMouseUp();
+ virtual void CommitInstantLoader(InstantLoader* 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.
+ void SetCompleteSuggestedText(const string16& suggested_text);
+
+ // 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 true if we should show preview for |url|.
+ bool ShouldShowPreviewFor(const GURL& url);
+
+ InstantDelegate* delegate_;
+
+ // The TabContents last passed to |Update|.
+ TabContents* tab_contents_;
+
+ // Has notification been sent out that the preview TabContents is ready to be
+ // shown?
+ bool is_active_;
+
+ // See description above setter.
+ gfx::Rect omnibox_bounds_;
+
+ // See description above CommitOnMouseUp.
+ bool commit_on_mouse_up_;
+
+ // See description above getter.
+ PageTransition::Type last_transition_type_;
+
+ scoped_ptr<InstantLoaderManager> loader_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstantController);
+};
+
+#endif // CHROME_BROWSER_INSTANT_INSTANT_CONTROLLER_H_
diff --git a/chrome/browser/instant/instant_delegate.h b/chrome/browser/instant/instant_delegate.h
new file mode 100644
index 0000000..a5c9223
--- /dev/null
+++ b/chrome/browser/instant/instant_delegate.h
@@ -0,0 +1,42 @@
+// 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_INSTANT_INSTANT_DELEGATE_H_
+#define CHROME_BROWSER_INSTANT_INSTANT_DELEGATE_H_
+#pragma once
+
+#include "base/string16.h"
+
+class TabContents;
+
+namespace gfx {
+class Rect;
+}
+
+// InstantController's delegate. Normally the Browser implements this. See
+// InstantController for details.
+class InstantDelegate {
+ public:
+ // Invoked when the instant TabContents should be shown.
+ virtual void ShowInstant(TabContents* preview_contents) = 0;
+
+ // Invoked when the instant TabContents should be hidden.
+ virtual void HideInstant() = 0;
+
+ // Invoked when the user does something that should result in the preview
+ // TabContents becoming the active TabContents. The delegate takes ownership
+ // of the supplied TabContents.
+ virtual void CommitInstant(TabContents* preview_contents) = 0;
+
+ // Invoked when the suggested text is to change to |text|.
+ virtual void SetSuggestedText(const string16& text) = 0;
+
+ // Returns the bounds instant will be placed at in screen coordinates.
+ virtual gfx::Rect GetInstantBounds() = 0;
+
+ protected:
+ virtual ~InstantDelegate() {}
+};
+
+#endif // CHROME_BROWSER_INSTANT_INSTANT_DELEGATE_H_
diff --git a/chrome/browser/instant/instant_loader.cc b/chrome/browser/instant/instant_loader.cc
new file mode 100644
index 0000000..adae0b8
--- /dev/null
+++ b/chrome/browser/instant/instant_loader.cc
@@ -0,0 +1,656 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/instant/instant_loader.h"
+
+#include <algorithm>
+
+#include "base/command_line.h"
+#include "base/string_number_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/autocomplete/autocomplete.h"
+#include "chrome/browser/favicon_service.h"
+#include "chrome/browser/history/history_marshaling.h"
+#include "chrome/browser/instant/instant_loader_delegate.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/renderer_host/render_widget_host.h"
+#include "chrome/browser/renderer_host/render_widget_host_view.h"
+#include "chrome/browser/search_engines/template_url.h"
+#include "chrome/browser/tab_contents/navigation_controller.h"
+#include "chrome/browser/tab_contents/navigation_entry.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+#include "chrome/browser/tab_contents/tab_contents_delegate.h"
+#include "chrome/browser/tab_contents/tab_contents_view.h"
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/page_transition_types.h"
+#include "chrome/common/render_messages.h"
+#include "chrome/common/renderer_preferences.h"
+#include "gfx/codec/png_codec.h"
+#include "ipc/ipc_message.h"
+
+namespace {
+
+// Script sent as the user is typing and the provider supports instant.
+// Params:
+// . the text the user typed.
+// TODO: add support for the 2nd and 3rd params.
+const char kUserInputScript[] =
+ "if (window.chrome.userInput) window.chrome.userInput(\"$1\", 0, 0);";
+
+// Script sent when the page is committed and the provider supports instant.
+// Params:
+// . the text the user typed.
+// . boolean indicating if the user pressed enter to accept the text.
+const char kUserDoneScript[] =
+ "if (window.chrome.userWantsQuery) "
+ "window.chrome.userWantsQuery(\"$1\", $2);";
+
+// Script sent when the bounds of the omnibox changes and the provider supports
+// instant. The params are the bounds relative to the origin of the preview
+// (x, y, width, height).
+const char kSetOmniboxBoundsScript[] =
+ "if (window.chrome.setDropdownDimensions) "
+ "window.chrome.setDropdownDimensions($1, $2, $3, $4);";
+
+// Escapes quotes in the |text| so that it be passed to JavaScript as a quoted
+// string.
+string16 EscapeUserText(const string16& text) {
+ string16 escaped_text(text);
+ ReplaceSubstringsAfterOffset(&escaped_text, 0L, ASCIIToUTF16("\""),
+ ASCIIToUTF16("\\\""));
+ return escaped_text;
+}
+
+// Sends the script for when the user commits the preview. |pressed_enter| is
+// true if the user pressed enter to commit.
+void SendDoneScript(TabContents* tab_contents,
+ const string16& text,
+ bool pressed_enter) {
+ std::vector<string16> params;
+ params.push_back(EscapeUserText(text));
+ params.push_back(pressed_enter ? ASCIIToUTF16("true") :
+ ASCIIToUTF16("false"));
+ string16 script = ReplaceStringPlaceholders(ASCIIToUTF16(kUserDoneScript),
+ params,
+ NULL);
+ tab_contents->render_view_host()->ExecuteJavascriptInWebFrame(
+ std::wstring(),
+ UTF16ToWide(script));
+}
+
+// Sends the user input script to |tab_contents|. |text| is the text the user
+// input into the omnibox.
+void SendUserInputScript(TabContents* tab_contents, const string16& text) {
+ string16 script = ReplaceStringPlaceholders(ASCIIToUTF16(kUserInputScript),
+ EscapeUserText(text),
+ NULL);
+ tab_contents->render_view_host()->ExecuteJavascriptInWebFrame(
+ std::wstring(),
+ UTF16ToWide(script));
+}
+
+// Sends the script for setting the bounds of the omnibox to |tab_contents|.
+void SendOmniboxBoundsScript(TabContents* tab_contents,
+ const gfx::Rect& bounds) {
+ std::vector<string16> bounds_vector;
+ bounds_vector.push_back(base::IntToString16(bounds.x()));
+ bounds_vector.push_back(base::IntToString16(bounds.y()));
+ bounds_vector.push_back(base::IntToString16(bounds.width()));
+ bounds_vector.push_back(base::IntToString16(bounds.height()));
+ string16 script = ReplaceStringPlaceholders(
+ ASCIIToUTF16(kSetOmniboxBoundsScript),
+ bounds_vector,
+ NULL);
+ tab_contents->render_view_host()->ExecuteJavascriptInWebFrame(
+ std::wstring(),
+ UTF16ToWide(script));
+}
+
+} // namespace
+
+// FrameLoadObserver is responsible for waiting for the TabContents to finish
+// loading and when done sending the necessary script down to the page.
+class InstantLoader::FrameLoadObserver : public NotificationObserver {
+ public:
+ FrameLoadObserver(InstantLoader* loader, const string16& text)
+ : loader_(loader),
+ tab_contents_(loader->preview_contents()),
+ unique_id_(tab_contents_->controller().pending_entry()->unique_id()),
+ text_(text),
+ pressed_enter_(false) {
+ registrar_.Add(this, NotificationType::LOAD_COMPLETED_MAIN_FRAME,
+ Source<TabContents>(tab_contents_));
+ registrar_.Add(this, NotificationType::TAB_CONTENTS_DESTROYED,
+ Source<TabContents>(tab_contents_));
+ }
+
+ // Sets the text to send to the page.
+ void set_text(const string16& text) { text_ = text; }
+
+ // Invoked when the InstantLoader releases ownership of the TabContents and
+ // the page hasn't finished loading.
+ void DetachFromPreview(bool pressed_enter) {
+ loader_ = NULL;
+ pressed_enter_ = pressed_enter;
+ }
+
+ // NotificationObserver:
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ switch (type.value) {
+ case NotificationType::LOAD_COMPLETED_MAIN_FRAME: {
+ int page_id = *(Details<int>(details).ptr());
+ NavigationEntry* active_entry =
+ tab_contents_->controller().GetActiveEntry();
+ if (!active_entry || active_entry->page_id() != page_id ||
+ active_entry->unique_id() != unique_id_) {
+ return;
+ }
+
+ if (loader_) {
+ gfx::Rect bounds = loader_->GetOmniboxBoundsInTermsOfPreview();
+ if (!bounds.IsEmpty())
+ SendOmniboxBoundsScript(tab_contents_, bounds);
+ }
+
+ SendUserInputScript(tab_contents_, text_);
+
+ if (loader_)
+ loader_->PageFinishedLoading();
+ else
+ SendDoneScript(tab_contents_, text_, pressed_enter_);
+
+ delete this;
+ return;
+ }
+
+ case NotificationType::TAB_CONTENTS_DESTROYED:
+ delete this;
+ return;
+
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+
+ private:
+ // InstantLoader that created us.
+ InstantLoader* loader_;
+
+ // The TabContents we're listening for changes on.
+ TabContents* tab_contents_;
+
+ // unique_id of the NavigationEntry we're waiting on.
+ const int unique_id_;
+
+ // Text to send down to the page.
+ string16 text_;
+
+ // Passed to SendDoneScript.
+ bool pressed_enter_;
+
+ // Registers and unregisters us for notifications.
+ NotificationRegistrar registrar_;
+
+ DISALLOW_COPY_AND_ASSIGN(FrameLoadObserver);
+};
+
+// PaintObserver implementation. When the RenderWidgetHost paints itself this
+// notifies InstantLoader, which makes the TabContents active.
+class InstantLoader::PaintObserverImpl
+ : public RenderWidgetHost::PaintObserver {
+ public:
+ explicit PaintObserverImpl(InstantLoader* loader) : loader_(loader) {
+ }
+
+ virtual void RenderWidgetHostWillPaint(RenderWidgetHost* rwh) {
+ }
+
+ virtual void RenderWidgetHostDidPaint(RenderWidgetHost* rwh) {
+ loader_->PreviewPainted();
+ rwh->set_paint_observer(NULL);
+ // WARNING: we've been deleted.
+ }
+
+ private:
+ InstantLoader* loader_;
+
+ DISALLOW_COPY_AND_ASSIGN(PaintObserverImpl);
+};
+
+class InstantLoader::TabContentsDelegateImpl : public TabContentsDelegate {
+ public:
+ explicit TabContentsDelegateImpl(InstantLoader* loader)
+ : loader_(loader),
+ installed_paint_observer_(false),
+ waiting_for_new_page_(true),
+ is_mouse_down_from_activate_(false) {
+ }
+
+ // Invoked prior to loading a new URL.
+ void PrepareForNewLoad() {
+ waiting_for_new_page_ = true;
+ add_page_vector_.clear();
+ }
+
+ // Invoked when removed as the delegate. Gives a chance to do any necessary
+ // cleanup.
+ void Reset() {
+ installed_paint_observer_ = false;
+ is_mouse_down_from_activate_ = false;
+ }
+
+ bool is_mouse_down_from_activate() const {
+ return is_mouse_down_from_activate_;
+ }
+
+ // Commits the currently buffered history.
+ void CommitHistory() {
+ TabContents* tab = loader_->preview_contents();
+ if (tab->profile()->IsOffTheRecord())
+ return;
+
+ for (size_t i = 0; i < add_page_vector_.size(); ++i)
+ tab->UpdateHistoryForNavigation(add_page_vector_[i].get());
+
+ NavigationEntry* active_entry = tab->controller().GetActiveEntry();
+ DCHECK(active_entry);
+ tab->UpdateHistoryPageTitle(*active_entry);
+
+ FaviconService* favicon_service =
+ tab->profile()->GetFaviconService(Profile::EXPLICIT_ACCESS);
+
+ if (favicon_service && active_entry->favicon().is_valid() &&
+ !active_entry->favicon().bitmap().empty()) {
+ std::vector<unsigned char> image_data;
+ gfx::PNGCodec::EncodeBGRASkBitmap(active_entry->favicon().bitmap(), false,
+ &image_data);
+ favicon_service->SetFavicon(active_entry->url(),
+ active_entry->favicon().url(),
+ image_data);
+ }
+ }
+
+ virtual void OpenURLFromTab(TabContents* source,
+ const GURL& url, const GURL& referrer,
+ WindowOpenDisposition disposition,
+ PageTransition::Type transition) {}
+ virtual void NavigationStateChanged(const TabContents* source,
+ unsigned changed_flags) {
+ if (!installed_paint_observer_ && source->controller().entry_count()) {
+ // The load has been committed. Install an observer that waits for the
+ // first paint then makes the preview active. We wait for the load to be
+ // committed before waiting on paint as there is always an initial paint
+ // when a new renderer is created from the resize so that if we showed the
+ // preview after the first paint we would end up with a white rect.
+ installed_paint_observer_ = true;
+ source->GetRenderWidgetHostView()->GetRenderWidgetHost()->
+ set_paint_observer(new PaintObserverImpl(loader_));
+ }
+ }
+ virtual void AddNewContents(TabContents* source,
+ TabContents* new_contents,
+ WindowOpenDisposition disposition,
+ const gfx::Rect& initial_pos,
+ bool user_gesture) {}
+ virtual void ActivateContents(TabContents* contents) {
+ }
+ virtual void DeactivateContents(TabContents* contents) {}
+ virtual void LoadingStateChanged(TabContents* source) {}
+ virtual void CloseContents(TabContents* source) {}
+ virtual void MoveContents(TabContents* source, const gfx::Rect& pos) {}
+ virtual void DetachContents(TabContents* source) {}
+ virtual bool IsPopup(const TabContents* source) const {
+ return false;
+ }
+ virtual bool ShouldFocusConstrainedWindow(TabContents* source) {
+ // Return false so that constrained windows are not initially focused. If
+ // we did otherwise the preview would prematurely get committed when focus
+ // goes to the constrained window.
+ return false;
+ }
+ virtual void WillShowConstrainedWindow(TabContents* source) {
+ if (!loader_->ready()) {
+ // A constrained window shown for an auth may not paint. Show the preview
+ // contents.
+ if (installed_paint_observer_) {
+ source->GetRenderWidgetHostView()->GetRenderWidgetHost()->
+ set_paint_observer(NULL);
+ }
+ installed_paint_observer_ = true;
+ loader_->ShowPreview();
+ }
+ }
+ virtual TabContents* GetConstrainingContents(TabContents* source) {
+ return NULL;
+ }
+ virtual void ToolbarSizeChanged(TabContents* source, bool is_animating) {}
+ virtual void URLStarredChanged(TabContents* source, bool starred) {}
+ virtual void UpdateTargetURL(TabContents* source, const GURL& url) {}
+ virtual void ContentsMouseEvent(
+ TabContents* source, const gfx::Point& location, bool motion) {}
+ virtual void ContentsZoomChange(bool zoom_in) {}
+ virtual void OnContentSettingsChange(TabContents* source) {}
+ virtual bool IsApplication() const { return false; }
+ virtual void ConvertContentsToApplication(TabContents* source) {}
+ virtual bool CanReloadContents(TabContents* source) const { return true; }
+ virtual gfx::Rect GetRootWindowResizerRect() const {
+ return gfx::Rect();
+ }
+ virtual void ShowHtmlDialog(HtmlDialogUIDelegate* delegate,
+ gfx::NativeWindow parent_window) {}
+ virtual void BeforeUnloadFired(TabContents* tab,
+ bool proceed,
+ bool* proceed_to_fire_unload) {}
+ virtual void ForwardMessageToExternalHost(const std::string& message,
+ const std::string& origin,
+ const std::string& target) {}
+ virtual bool IsExternalTabContainer() const { return false; }
+ virtual void SetFocusToLocationBar(bool select_all) {}
+ virtual bool ShouldFocusPageAfterCrash() { return false; }
+ virtual void RenderWidgetShowing() {}
+ virtual ExtensionFunctionDispatcher* CreateExtensionFunctionDispatcher(
+ RenderViewHost* render_view_host,
+ const std::string& extension_id) {
+ return NULL;
+ }
+ virtual bool TakeFocus(bool reverse) { return false; }
+ virtual void LostCapture() {
+ CommitFromMouseReleaseIfNecessary();
+ }
+ virtual void SetTabContentBlocked(TabContents* contents, bool blocked) {}
+ virtual void TabContentsFocused(TabContents* tab_content) {
+ }
+ virtual int GetExtraRenderViewHeight() const { return 0; }
+ virtual bool CanDownload(int request_id) { return false; }
+ virtual void OnStartDownload(DownloadItem* download, TabContents* tab) {}
+ virtual bool HandleContextMenu(const ContextMenuParams& params) {
+ return false;
+ }
+ virtual bool ExecuteContextMenuCommand(int command) {
+ return false;
+ }
+ virtual void ConfirmAddSearchProvider(const TemplateURL* template_url,
+ Profile* profile) {}
+ virtual void ShowPageInfo(Profile* profile,
+ const GURL& url,
+ const NavigationEntry::SSLStatus& ssl,
+ bool show_history) {}
+ virtual bool PreHandleKeyboardEvent(const NativeWebKeyboardEvent& event,
+ bool* is_keyboard_shortcut) {
+ return false;
+ }
+ virtual void HandleMouseUp() {
+ CommitFromMouseReleaseIfNecessary();
+ }
+ virtual void HandleMouseActivate() {
+ is_mouse_down_from_activate_ = true;
+ }
+ virtual void ShowRepostFormWarningDialog(TabContents* tab_contents) {}
+ virtual void ShowContentSettingsWindow(ContentSettingsType content_type) {}
+ virtual void ShowCollectedCookiesDialog(TabContents* tab_contents) {}
+ virtual bool OnGoToEntryOffset(int offset) { return false; }
+ virtual bool ShouldAddNavigationToHistory(
+ const history::HistoryAddPageArgs& add_page_args,
+ NavigationType::Type navigation_type) {
+ if (waiting_for_new_page_ && navigation_type == NavigationType::NEW_PAGE)
+ waiting_for_new_page_ = false;
+
+ if (!waiting_for_new_page_) {
+ add_page_vector_.push_back(
+ scoped_refptr<history::HistoryAddPageArgs>(add_page_args.Clone()));
+ }
+ return false;
+ }
+ virtual void OnDidGetApplicationInfo(TabContents* tab_contents,
+ int32 page_id) {}
+ virtual gfx::NativeWindow GetFrameNativeWindow() {
+ return NULL;
+ }
+ virtual void TabContentsCreated(TabContents* new_contents) {}
+ virtual bool infobars_enabled() { return false; }
+ virtual bool ShouldEnablePreferredSizeNotifications() { return false; }
+ virtual void UpdatePreferredSize(const gfx::Size& pref_size) {}
+ virtual void ContentTypeChanged(TabContents* source) {}
+
+ virtual void OnSetSuggestResult(int32 page_id, const std::string& result) {
+ TabContents* source = loader_->preview_contents();
+ // TODO: only allow for default search provider.
+ if (source->controller().GetActiveEntry() &&
+ page_id == source->controller().GetActiveEntry()->page_id()) {
+ loader_->SetCompleteSuggestedText(UTF8ToUTF16(result));
+ }
+ }
+
+ private:
+ typedef std::vector<scoped_refptr<history::HistoryAddPageArgs> >
+ AddPageVector;
+
+ void CommitFromMouseReleaseIfNecessary() {
+ bool was_down = is_mouse_down_from_activate_;
+ is_mouse_down_from_activate_ = false;
+ if (was_down && loader_->ShouldCommitInstantOnMouseUp())
+ loader_->CommitInstantLoader();
+ }
+
+ InstantLoader* loader_;
+
+ // Has the paint observer been installed? See comment in
+ // NavigationStateChanged for details on this.
+ bool installed_paint_observer_;
+
+ // Used to cache data that needs to be added to history. Normally entries are
+ // added to history as the user types, but for instant we only want to add the
+ // items to history if the user commits instant. So, we cache them here and if
+ // committed then add the items to history.
+ AddPageVector add_page_vector_;
+
+ // Are we we waiting for a NavigationType of NEW_PAGE? If we're waiting for
+ // NEW_PAGE navigation we don't add history items to add_page_vector_.
+ bool waiting_for_new_page_;
+
+ // Returns true if the mouse is down from an activate.
+ bool is_mouse_down_from_activate_;
+
+ DISALLOW_COPY_AND_ASSIGN(TabContentsDelegateImpl);
+};
+
+InstantLoader::InstantLoader(InstantLoaderDelegate* delegate, TemplateURLID id)
+ : delegate_(delegate),
+ template_url_id_(id),
+ ready_(false) {
+ preview_tab_contents_delegate_.reset(new TabContentsDelegateImpl(this));
+}
+
+InstantLoader::~InstantLoader() {
+ // Delete the TabContents before the delegate as the TabContents holds a
+ // reference to the delegate.
+ preview_contents_.reset(NULL);
+}
+
+void InstantLoader::Update(TabContents* tab_contents,
+ const AutocompleteMatch& match,
+ const string16& user_text,
+ const TemplateURL* template_url,
+ string16* suggested_text) {
+ DCHECK(url_ != match.destination_url);
+
+ url_ = match.destination_url;
+
+ DCHECK(!url_.is_empty() && url_.is_valid());
+
+ user_text_ = user_text;
+
+ bool created_preview_contents;
+ if (preview_contents_.get() == NULL) {
+ preview_contents_.reset(
+ new TabContents(tab_contents->profile(), NULL, MSG_ROUTING_NONE,
+ NULL, NULL));
+ // Propagate the max page id. That way if we end up merging the two
+ // NavigationControllers (which happens if we commit) none of the page ids
+ // will overlap.
+ int32 max_page_id = tab_contents->GetMaxPageID();
+ if (max_page_id != -1)
+ preview_contents_->controller().set_max_restored_page_id(max_page_id + 1);
+
+ preview_contents_->set_delegate(preview_tab_contents_delegate_.get());
+
+ gfx::Rect tab_bounds;
+ tab_contents->view()->GetContainerBounds(&tab_bounds);
+ preview_contents_->view()->SizeContents(tab_bounds.size());
+
+ preview_contents_->ShowContents();
+ created_preview_contents = true;
+ } else {
+ created_preview_contents = false;
+ }
+ preview_tab_contents_delegate_->PrepareForNewLoad();
+
+ if (template_url) {
+ DCHECK(template_url_id_ == template_url->id());
+ if (!created_preview_contents) {
+ if (is_waiting_for_load()) {
+ // The page hasn't loaded yet. We'll send the script down when it does.
+ frame_load_observer_->set_text(user_text_);
+ return;
+ }
+ SendUserInputScript(preview_contents_.get(), user_text_);
+ if (complete_suggested_text_.size() > user_text_.size() &&
+ !complete_suggested_text_.compare(0, user_text_.size(), user_text_)) {
+ *suggested_text = complete_suggested_text_.substr(user_text_.size());
+ }
+ } else {
+ // TODO: should we use a different url for instant?
+ GURL url = GURL(template_url->url()->ReplaceSearchTerms(
+ *template_url, std::wstring(),
+ TemplateURLRef::NO_SUGGESTIONS_AVAILABLE, std::wstring()));
+ // user_text_ is sent once the page finishes loading by FrameLoadObserver.
+ preview_contents_->controller().LoadURL(url, GURL(), match.transition);
+ frame_load_observer_.reset(new FrameLoadObserver(this, user_text_));
+ }
+ } else {
+ DCHECK(template_url_id_ == 0);
+ frame_load_observer_.reset(NULL);
+ preview_contents_->controller().LoadURL(url_, GURL(), match.transition);
+ }
+}
+
+void InstantLoader::SetOmniboxBounds(const gfx::Rect& bounds) {
+ if (omnibox_bounds_ == bounds)
+ return;
+
+ omnibox_bounds_ = bounds;
+ if (preview_contents_.get() && is_showing_instant() &&
+ !is_waiting_for_load()) {
+ SendOmniboxBoundsScript(preview_contents_.get(),
+ GetOmniboxBoundsInTermsOfPreview());
+ }
+}
+
+void InstantLoader::DestroyPreviewContents() {
+ if (!preview_contents_.get()) {
+ // We're not showing anything, nothing to do.
+ return;
+ }
+
+ delete ReleasePreviewContents(INSTANT_COMMIT_DESTROY);
+}
+
+bool InstantLoader::IsMouseDownFromActivate() {
+ return preview_tab_contents_delegate_->is_mouse_down_from_activate();
+}
+
+TabContents* InstantLoader::ReleasePreviewContents(InstantCommitType type) {
+ if (!preview_contents_.get())
+ return NULL;
+
+ if (frame_load_observer_.get()) {
+ frame_load_observer_->DetachFromPreview(
+ type == INSTANT_COMMIT_PRESSED_ENTER);
+ // FrameLoadObserver will delete itself either when the TabContents is
+ // deleted, or when the page finishes loading.
+ FrameLoadObserver* unused ALLOW_UNUSED = frame_load_observer_.release();
+ } else if (type != INSTANT_COMMIT_DESTROY && is_showing_instant()) {
+ SendDoneScript(preview_contents_.get(),
+ user_text_,
+ type == INSTANT_COMMIT_PRESSED_ENTER);
+ }
+ omnibox_bounds_ = gfx::Rect();
+ url_ = GURL();
+ user_text_.clear();
+ complete_suggested_text_.clear();
+ if (preview_contents_.get()) {
+ if (type != INSTANT_COMMIT_DESTROY)
+ preview_tab_contents_delegate_->CommitHistory();
+ // Destroy the paint observer.
+ // RenderWidgetHostView may be null during shutdown.
+ if (preview_contents_->GetRenderWidgetHostView()) {
+ preview_contents_->GetRenderWidgetHostView()->GetRenderWidgetHost()->
+ set_paint_observer(NULL);
+ }
+ preview_contents_->set_delegate(NULL);
+ preview_tab_contents_delegate_->Reset();
+ ready_ = false;
+ }
+ return preview_contents_.release();
+}
+
+bool InstantLoader::ShouldCommitInstantOnMouseUp() {
+ return delegate_->ShouldCommitInstantOnMouseUp();
+}
+
+void InstantLoader::CommitInstantLoader() {
+ delegate_->CommitInstantLoader(this);
+}
+
+void InstantLoader::SetCompleteSuggestedText(
+ const string16& complete_suggested_text) {
+ if (complete_suggested_text == complete_suggested_text_)
+ return;
+
+ if (user_text_.compare(0, user_text_.size(), complete_suggested_text,
+ 0, user_text_.size())) {
+ // The user text no longer contains the suggested text, ignore it.
+ complete_suggested_text_.clear();
+ delegate_->SetSuggestedTextFor(this, string16());
+ return;
+ }
+
+ complete_suggested_text_ = complete_suggested_text;
+ delegate_->SetSuggestedTextFor(
+ this,
+ complete_suggested_text_.substr(user_text_.size()));
+}
+
+void InstantLoader::PreviewPainted() {
+ ShowPreview();
+}
+
+void InstantLoader::ShowPreview() {
+ if (!ready_) {
+ ready_ = true;
+ delegate_->ShowInstantLoader(this);
+ }
+}
+
+void InstantLoader::PageFinishedLoading() {
+ // FrameLoadObserver deletes itself after this call.
+ FrameLoadObserver* unused ALLOW_UNUSED = frame_load_observer_.release();
+}
+
+gfx::Rect InstantLoader::GetOmniboxBoundsInTermsOfPreview() {
+ if (omnibox_bounds_.IsEmpty())
+ return omnibox_bounds_;
+
+ gfx::Rect preview_bounds(delegate_->GetInstantBounds());
+ return gfx::Rect(omnibox_bounds_.x() - preview_bounds.x(),
+ omnibox_bounds_.y() - preview_bounds.y(),
+ omnibox_bounds_.width(),
+ omnibox_bounds_.height());
+}
diff --git a/chrome/browser/instant/instant_loader.h b/chrome/browser/instant/instant_loader.h
new file mode 100644
index 0000000..a5edb35
--- /dev/null
+++ b/chrome/browser/instant/instant_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_INSTANT_INSTANT_LOADER_H_
+#define CHROME_BROWSER_INSTANT_INSTANT_LOADER_H_
+#pragma once
+
+#include "base/basictypes.h"
+#include "base/scoped_ptr.h"
+#include "base/string16.h"
+#include "chrome/browser/instant/instant_commit_type.h"
+#include "chrome/browser/search_engines/template_url_id.h"
+#include "chrome/common/page_transition_types.h"
+#include "gfx/rect.h"
+#include "googleurl/src/gurl.h"
+
+struct AutocompleteMatch;
+class InstantLoaderDelegate;
+class InstantLoaderManagerTest;
+class TabContents;
+class TemplateURL;
+
+// InstantLoader does the loading of a particular URL for InstantController.
+// InstantLoader notifies its delegate, which is typically InstantController, of
+// all interesting events.
+class InstantLoader {
+ public:
+ InstantLoader(InstantLoaderDelegate* delegate, TemplateURLID id);
+ ~InstantLoader();
+
+ // 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(InstantCommitType type);
+
+ // Calls through to method of same name on delegate.
+ bool ShouldCommitInstantOnMouseUp();
+ void CommitInstantLoader();
+
+ // 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 InstantLoaderManagerTest;
+ 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;
+ }
+
+ InstantLoaderDelegate* 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(InstantLoader);
+};
+
+#endif // CHROME_BROWSER_INSTANT_INSTANT_LOADER_H_
diff --git a/chrome/browser/instant/instant_loader_delegate.h b/chrome/browser/instant/instant_loader_delegate.h
new file mode 100644
index 0000000..b77742d
--- /dev/null
+++ b/chrome/browser/instant/instant_loader_delegate.h
@@ -0,0 +1,40 @@
+// 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_INSTANT_INSTANT_LOADER_DELEGATE_H_
+#define CHROME_BROWSER_INSTANT_INSTANT_LOADER_DELEGATE_H_
+#pragma once
+
+#include "base/string16.h"
+
+namespace gfx {
+class Rect;
+}
+
+class InstantLoader;
+
+// InstantLoader's delegate. This interface is implemented by InstantController.
+class InstantLoaderDelegate {
+ public:
+ // Invoked when the loader is ready to be shown.
+ virtual void ShowInstantLoader(InstantLoader* loader) = 0;
+
+ // Invoked when the loader has suggested text.
+ virtual void SetSuggestedTextFor(InstantLoader* loader,
+ const string16& text) = 0;
+
+ // Returns the bounds of instant.
+ virtual gfx::Rect GetInstantBounds() = 0;
+
+ // Returns true if instant should be committed on mouse up.
+ virtual bool ShouldCommitInstantOnMouseUp() = 0;
+
+ // Invoked when the the loader should be committed.
+ virtual void CommitInstantLoader(InstantLoader* loader) = 0;
+
+ protected:
+ virtual ~InstantLoaderDelegate() {}
+};
+
+#endif // CHROME_BROWSER_INSTANT_INSTANT_LOADER_DELEGATE_H_
diff --git a/chrome/browser/instant/instant_loader_manager.cc b/chrome/browser/instant/instant_loader_manager.cc
new file mode 100644
index 0000000..e807bfc
--- /dev/null
+++ b/chrome/browser/instant/instant_loader_manager.cc
@@ -0,0 +1,124 @@
+// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "chrome/browser/instant/instant_loader_manager.h"
+
+#include "base/logging.h"
+#include "chrome/browser/instant/instant_loader.h"
+#include "chrome/browser/instant/instant_loader_delegate.h"
+#include "chrome/browser/tab_contents/tab_contents.h"
+
+InstantLoaderManager::InstantLoaderManager(
+ InstantLoaderDelegate* loader_delegate)
+ : loader_delegate_(loader_delegate),
+ current_loader_(NULL),
+ pending_loader_(NULL) {
+}
+
+InstantLoaderManager::~InstantLoaderManager() {
+ 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_;
+}
+
+InstantLoader* InstantLoaderManager::UpdateLoader(
+ TemplateURLID instant_id,
+ scoped_ptr<InstantLoader>* old_loader) {
+ InstantLoader* old_current_loader = current_loader_;
+ InstantLoader* old_pending_loader = pending_loader_;
+
+ // Determine the new loader.
+ InstantLoader* 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 InstantLoaderManager::MakePendingCurrent(
+ scoped_ptr<InstantLoader>* 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;
+}
+
+InstantLoader* InstantLoaderManager::ReleaseCurrentLoader() {
+ DCHECK(current_loader_);
+ InstantLoader* 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;
+}
+
+InstantLoader* InstantLoaderManager::CreateLoader(TemplateURLID id) {
+ InstantLoader* loader = new InstantLoader(loader_delegate_, id);
+ if (id)
+ instant_loaders_[id] = loader;
+ return loader;
+}
+
+InstantLoader* InstantLoaderManager::GetInstantLoader(TemplateURLID id) {
+ Loaders::iterator i = instant_loaders_.find(id);
+ return i == instant_loaders_.end() ? CreateLoader(id) : i->second;
+}
diff --git a/chrome/browser/instant/instant_loader_manager.h b/chrome/browser/instant/instant_loader_manager.h
new file mode 100644
index 0000000..efcc6ac
--- /dev/null
+++ b/chrome/browser/instant/instant_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_INSTANT_INSTANT_LOADER_MANAGER_H_
+#define CHROME_BROWSER_INSTANT_INSTANT_LOADER_MANAGER_H_
+#pragma once
+
+#include <map>
+
+#include "base/scoped_ptr.h"
+#include "chrome/browser/search_engines/template_url_id.h"
+
+class InstantLoader;
+class InstantLoaderDelegate;
+
+// InstantLoaderManager is responsible for maintaining the InstantLoaders for
+// InstantController. InstantLoaderManager 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 InstantLoaderManager need only concern themselves with the current
+// and pending loaders. The current loader is the loader that if ready is shown
+// by InstantController. 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.
+//
+// InstantLoader owns all the InstantLoaders returned. You can take
+// ownership of the current loader by invoking ReleaseCurrentLoader.
+class InstantLoaderManager {
+ public:
+ explicit InstantLoaderManager(InstantLoaderDelegate* loader_delegate);
+ ~InstantLoaderManager();
+
+ // 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 InstantLoader that should be used.
+ InstantLoader* UpdateLoader(TemplateURLID instant_id,
+ scoped_ptr<InstantLoader>* 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<InstantLoader>* old_loader);
+
+ // Returns the current loader and clears internal references to it. This
+ // should be used prior to destroying the InstantLoaderManager when the owner
+ // of InstantLoaderManager wants to take ownership of the loader.
+ InstantLoader* ReleaseCurrentLoader();
+
+ // Returns the current loader, may be null.
+ InstantLoader* current_loader() const { return current_loader_; }
+
+ // Returns the pending loader, may be null.
+ InstantLoader* 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.
+ InstantLoader* 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, InstantLoader*> Loaders;
+
+ // Creates a loader and if |id| is non-zero registers it in instant_loaders_.
+ InstantLoader* 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.
+ InstantLoader* GetInstantLoader(TemplateURLID id);
+
+ InstantLoaderDelegate* loader_delegate_;
+
+ // The current loader.
+ InstantLoader* 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.
+ InstantLoader* pending_loader_;
+
+ // Maps for template url id to loader used for that template url id.
+ Loaders instant_loaders_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstantLoaderManager);
+};
+
+#endif // CHROME_BROWSER_INSTANT_INSTANT_LOADER_MANAGER_H_
diff --git a/chrome/browser/instant/instant_loader_manager_unittest.cc b/chrome/browser/instant/instant_loader_manager_unittest.cc
new file mode 100644
index 0000000..855bf59
--- /dev/null
+++ b/chrome/browser/instant/instant_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/instant/instant_loader.h"
+#include "chrome/browser/instant/instant_loader_delegate.h"
+#include "chrome/browser/instant/instant_loader_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class InstantLoaderDelegateImpl : public InstantLoaderDelegate {
+ public:
+ InstantLoaderDelegateImpl() {}
+
+ virtual void ShowInstantLoader(InstantLoader* loader) {}
+
+ virtual void SetSuggestedTextFor(InstantLoader* loader,
+ const string16& text) {}
+
+ virtual gfx::Rect GetInstantBounds() {
+ return gfx::Rect();
+ }
+
+ virtual bool ShouldCommitInstantOnMouseUp() {
+ return false;
+ }
+
+ virtual void CommitInstantLoader(InstantLoader* loader) {
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InstantLoaderDelegateImpl);
+};
+
+}
+
+class InstantLoaderManagerTest : public testing::Test {
+ public:
+ InstantLoaderManagerTest() {}
+
+ void MarkReady(InstantLoader* loader) {
+ loader->ready_ = true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(InstantLoaderManagerTest);
+};
+
+// Makes sure UpdateLoader works when invoked once.
+TEST_F(InstantLoaderManagerTest, Basic) {
+ InstantLoaderDelegateImpl delegate;
+ InstantLoaderManager manager(&delegate);
+ scoped_ptr<InstantLoader> 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(InstantLoaderManagerTest, UpdateTwice) {
+ InstantLoaderDelegateImpl delegate;
+ InstantLoaderManager manager(&delegate);
+ scoped_ptr<InstantLoader> loader;
+ manager.UpdateLoader(0, &loader);
+ InstantLoader* 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(InstantLoaderManagerTest, UpdateInstantTwice) {
+ InstantLoaderDelegateImpl delegate;
+ InstantLoaderManager manager(&delegate);
+ scoped_ptr<InstantLoader> loader;
+ manager.UpdateLoader(1, &loader);
+ InstantLoader* 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(InstantLoaderManagerTest, NonInstantToInstant) {
+ InstantLoaderDelegateImpl delegate;
+ InstantLoaderManager manager(&delegate);
+ scoped_ptr<InstantLoader> loader;
+ manager.UpdateLoader(0, &loader);
+ InstantLoader* 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(InstantLoaderManagerTest, DontDeleteInstantLoaders) {
+ InstantLoaderDelegateImpl delegate;
+ InstantLoaderManager manager(&delegate);
+ scoped_ptr<InstantLoader> loader;
+ manager.UpdateLoader(1, &loader);
+ InstantLoader* 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(InstantLoaderManagerTest, CreateSecondaryWhenReady) {
+ InstantLoaderDelegateImpl delegate;
+ InstantLoaderManager manager(&delegate);
+ scoped_ptr<InstantLoader> loader;
+ manager.UpdateLoader(0, &loader);
+ InstantLoader* 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.
+ InstantLoader* 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(InstantLoaderManagerTest, ReleaseInstant) {
+ InstantLoaderDelegateImpl delegate;
+ InstantLoaderManager manager(&delegate);
+ scoped_ptr<InstantLoader> loader;
+ manager.UpdateLoader(1, &loader);
+ scoped_ptr<InstantLoader> 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(InstantLoaderManagerTest, NonInstantToInstantWhenReady) {
+ InstantLoaderDelegateImpl delegate;
+ InstantLoaderManager manager(&delegate);
+ scoped_ptr<InstantLoader> loader;
+ manager.UpdateLoader(1, &loader);
+ ASSERT_TRUE(manager.current_loader());
+ EXPECT_EQ(1, manager.current_loader()->template_url_id());
+ InstantLoader* instant_loader = manager.current_loader();
+
+ manager.UpdateLoader(0, &loader);
+ InstantLoader* 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(InstantLoaderManagerTest, ThreeInstant) {
+ InstantLoaderDelegateImpl delegate;
+ InstantLoaderManager manager(&delegate);
+ scoped_ptr<InstantLoader> loader;
+ manager.UpdateLoader(1, &loader);
+ ASSERT_TRUE(manager.current_loader());
+ EXPECT_EQ(1, manager.current_loader()->template_url_id());
+ InstantLoader* instant_loader1 = manager.current_loader();
+ MarkReady(instant_loader1);
+
+ manager.UpdateLoader(2, &loader);
+ InstantLoader* 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);
+ InstantLoader* 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());
+}