summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorfischman@chromium.org <fischman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-03 21:20:42 +0000
committerfischman@chromium.org <fischman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-07-03 21:20:42 +0000
commitf46186424021ba2d70ce2c1626da43572bff43d0 (patch)
tree6f34c140d35ed82858c693049f9215d7ab5cfac9
parent97e6692430f166051246395802a26c6b325b54a4 (diff)
downloadchromium_src-f46186424021ba2d70ce2c1626da43572bff43d0.zip
chromium_src-f46186424021ba2d70ce2c1626da43572bff43d0.tar.gz
chromium_src-f46186424021ba2d70ce2c1626da43572bff43d0.tar.bz2
Revert 210036 "InstantExtended: Remove overlay control code."
Broke the http://build.chromium.org/p/chromium.chrome/builders/Google%20Chrome%20ChromeOS/builds/55501/steps/compile/logs/stdio build, and probably all enable_rlz=1 builds. > InstantExtended: Remove overlay control code. > > This change deletes the browser-side InstantController code pertaining > to old Instant, the HTML popup and search results overlay. A lot of UI > and renderer code is still lingering and doing nothing, but I'll get > that in another CL. > > TEST=Manually. Verify that InstantExtended NTP and searching works, and > that normal Chrome is unaffected. > BUG=251262 > TBR=brettw@chromium.org > > Review URL: https://chromiumcodereview.appspot.com/18223002 TBR=jered@chromium.org Review URL: https://codereview.chromium.org/18654004 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@210042 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/google/google_url_tracker_navigation_helper_impl.cc30
-rw-r--r--chrome/browser/google/google_url_tracker_navigation_helper_impl.h6
-rw-r--r--chrome/browser/history/history_tab_helper.cc8
-rw-r--r--chrome/browser/search/instant_extended_context_menu_observer.cc34
-rw-r--r--chrome/browser/search/instant_extended_context_menu_observer.h35
-rw-r--r--chrome/browser/search/search.cc10
-rw-r--r--chrome/browser/search/search.h3
-rw-r--r--chrome/browser/tab_contents/render_view_context_menu.cc6
-rw-r--r--chrome/browser/tab_contents/render_view_context_menu.h4
-rw-r--r--chrome/browser/task_manager/tab_contents_resource_provider.cc36
-rw-r--r--chrome/browser/task_manager/tab_contents_resource_provider.h1
-rw-r--r--chrome/browser/ui/browser_instant_controller.cc44
-rw-r--r--chrome/browser/ui/browser_instant_controller.h18
-rw-r--r--chrome/browser/ui/cocoa/browser_window_controller.h3
-rw-r--r--chrome/browser/ui/cocoa/browser_window_controller.mm5
-rw-r--r--chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.mm1
-rw-r--r--chrome/browser/ui/cocoa/tab_contents/render_view_context_menu_mac.mm8
-rw-r--r--chrome/browser/ui/omnibox/alternate_nav_url_fetcher.cc15
-rw-r--r--chrome/browser/ui/omnibox/omnibox_controller.cc92
-rw-r--r--chrome/browser/ui/omnibox/omnibox_controller.h21
-rw-r--r--chrome/browser/ui/omnibox/omnibox_edit_model.cc198
-rw-r--r--chrome/browser/ui/omnibox/omnibox_edit_model.h38
-rw-r--r--chrome/browser/ui/search/instant_commit_type.h25
-rw-r--r--chrome/browser/ui/search/instant_controller.cc1297
-rw-r--r--chrome/browser/ui/search/instant_controller.h274
-rw-r--r--chrome/browser/ui/search/instant_controller_unittest.cc88
-rw-r--r--chrome/browser/ui/search/instant_extended_interactive_uitest.cc2
-rw-r--r--chrome/browser/ui/search/instant_extended_manual_interactive_uitest.cc50
-rw-r--r--chrome/browser/ui/search/instant_ipc_sender.cc42
-rw-r--r--chrome/browser/ui/search/instant_ipc_sender.h42
-rw-r--r--chrome/browser/ui/search/instant_overlay.cc148
-rw-r--r--chrome/browser/ui/search/instant_overlay.h105
-rw-r--r--chrome/browser/ui/search/instant_overlay_controller.cc4
-rw-r--r--chrome/browser/ui/search/instant_overlay_model.cc3
-rw-r--r--chrome/browser/ui/search/instant_page.cc38
-rw-r--r--chrome/browser/ui/search/instant_page.h27
-rw-r--r--chrome/browser/ui/search/instant_page_unittest.cc34
-rw-r--r--chrome/browser/ui/search/instant_test_utils.cc70
-rw-r--r--chrome/browser/ui/search/instant_test_utils.h25
-rw-r--r--chrome/chrome_browser.gypi2
-rw-r--r--chrome/chrome_browser_ui.gypi3
-rw-r--r--chrome/common/chrome_notification_types.h17
42 files changed, 2836 insertions, 76 deletions
diff --git a/chrome/browser/google/google_url_tracker_navigation_helper_impl.cc b/chrome/browser/google/google_url_tracker_navigation_helper_impl.cc
index 867a84f..8a1cb14 100644
--- a/chrome/browser/google/google_url_tracker_navigation_helper_impl.cc
+++ b/chrome/browser/google/google_url_tracker_navigation_helper_impl.cc
@@ -31,9 +31,13 @@ void GoogleURLTrackerNavigationHelperImpl::SetListeningForNavigationStart(
if (listen) {
registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this, chrome::NOTIFICATION_INSTANT_COMMITTED,
+ content::NotificationService::AllBrowserContextsAndSources());
} else {
registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Remove(this, chrome::NOTIFICATION_INSTANT_COMMITTED,
+ content::NotificationService::AllBrowserContextsAndSources());
}
}
@@ -144,7 +148,33 @@ void GoogleURLTrackerNavigationHelperImpl::Observe(
break;
}
+ case chrome::NOTIFICATION_INSTANT_COMMITTED: {
+ content::WebContents* web_contents =
+ content::Source<content::WebContents>(source).ptr();
+ content::NavigationController* nav_controller =
+ &web_contents->GetController();
+ const GURL& search_url = web_contents->GetURL();
+ if (!search_url.is_valid()) // Not clear if this can happen.
+ tracker_->OnTabClosed(nav_controller);
+ OnInstantCommitted(nav_controller,
+ InfoBarService::FromWebContents(web_contents),
+ search_url);
+ break;
+ }
+
default:
NOTREACHED() << "Unknown notification received:" << type;
}
}
+
+void GoogleURLTrackerNavigationHelperImpl::OnInstantCommitted(
+ content::NavigationController* nav_controller,
+ InfoBarService* infobar_service,
+ const GURL& search_url) {
+ // Call OnNavigationPending, giving |tracker_| the option to register for
+ // navigation commit messages for this navigation controller. If it does
+ // register for them, simulate the commit as well.
+ tracker_->OnNavigationPending(nav_controller, infobar_service, 0);
+ if (IsListeningForNavigationCommit(nav_controller))
+ tracker_->OnNavigationCommitted(infobar_service, search_url);
+}
diff --git a/chrome/browser/google/google_url_tracker_navigation_helper_impl.h b/chrome/browser/google/google_url_tracker_navigation_helper_impl.h
index 2900590..b8ec45f 100644
--- a/chrome/browser/google/google_url_tracker_navigation_helper_impl.h
+++ b/chrome/browser/google/google_url_tracker_navigation_helper_impl.h
@@ -40,6 +40,12 @@ class GoogleURLTrackerNavigationHelperImpl
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
+ // Handles instant commit notifications by simulating the relevant navigation
+ // callbacks.
+ void OnInstantCommitted(content::NavigationController* nav_controller,
+ InfoBarService* infobar_service,
+ const GURL& search_url);
+
// Returns a WebContents NavigationSource for the WebContents corresponding to
// the given NavigationController NotificationSource.
virtual content::NotificationSource GetWebContentsSource(
diff --git a/chrome/browser/history/history_tab_helper.cc b/chrome/browser/history/history_tab_helper.cc
index f0285ab..5438b97 100644
--- a/chrome/browser/history/history_tab_helper.cc
+++ b/chrome/browser/history/history_tab_helper.cc
@@ -12,6 +12,7 @@
#include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/prerender/prerender_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/search/instant_overlay.h"
#include "chrome/common/render_messages.h"
#include "content/public/browser/navigation_details.h"
#include "content/public/browser/navigation_entry.h"
@@ -127,6 +128,13 @@ void HistoryTabHelper::DidNavigateAnyFrame(
}
}
+ InstantOverlay* instant_overlay =
+ InstantOverlay::FromWebContents(web_contents());
+ if (instant_overlay) {
+ instant_overlay->DidNavigate(add_page_args);
+ return;
+ }
+
#if !defined(OS_ANDROID)
// Don't update history if this web contents isn't associatd with a tab.
Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
diff --git a/chrome/browser/search/instant_extended_context_menu_observer.cc b/chrome/browser/search/instant_extended_context_menu_observer.cc
new file mode 100644
index 0000000..fa42b67
--- /dev/null
+++ b/chrome/browser/search/instant_extended_context_menu_observer.cc
@@ -0,0 +1,34 @@
+// Copyright 2013 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/search/instant_extended_context_menu_observer.h"
+
+#include "base/logging.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/search/search.h"
+
+InstantExtendedContextMenuObserver::InstantExtendedContextMenuObserver(
+ content::WebContents* contents)
+ : is_instant_overlay_(chrome::IsInstantOverlay(contents)) {
+}
+
+InstantExtendedContextMenuObserver::~InstantExtendedContextMenuObserver() {
+}
+
+bool InstantExtendedContextMenuObserver::IsCommandIdSupported(int command_id) {
+ switch (command_id) {
+ case IDC_BACK:
+ case IDC_FORWARD:
+ case IDC_PRINT:
+ case IDC_RELOAD:
+ return is_instant_overlay_;
+ default:
+ return false;
+ }
+}
+
+bool InstantExtendedContextMenuObserver::IsCommandIdEnabled(int command_id) {
+ DCHECK(IsCommandIdSupported(command_id));
+ return false;
+}
diff --git a/chrome/browser/search/instant_extended_context_menu_observer.h b/chrome/browser/search/instant_extended_context_menu_observer.h
new file mode 100644
index 0000000..4685ce6
--- /dev/null
+++ b/chrome/browser/search/instant_extended_context_menu_observer.h
@@ -0,0 +1,35 @@
+// Copyright 2013 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_SEARCH_INSTANT_EXTENDED_CONTEXT_MENU_OBSERVER_H_
+#define CHROME_BROWSER_SEARCH_INSTANT_EXTENDED_CONTEXT_MENU_OBSERVER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "chrome/browser/tab_contents/render_view_context_menu_observer.h"
+
+namespace content {
+class WebContents;
+}
+
+// This class disables menu items which perform poorly in instant extended mode.
+class InstantExtendedContextMenuObserver
+ : public RenderViewContextMenuObserver {
+ public:
+ explicit InstantExtendedContextMenuObserver(content::WebContents* contents);
+ virtual ~InstantExtendedContextMenuObserver();
+
+ // RenderViewContextMenuObserver implementation.
+ virtual bool IsCommandIdSupported(int command_id) OVERRIDE;
+ virtual bool IsCommandIdEnabled(int command_id) OVERRIDE;
+
+ private:
+ // Whether the source web contents of the context menu corresponds to an
+ // Instant overlay.
+ const bool is_instant_overlay_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstantExtendedContextMenuObserver);
+};
+
+#endif // CHROME_BROWSER_SEARCH_INSTANT_EXTENDED_CONTEXT_MENU_OBSERVER_H_
diff --git a/chrome/browser/search/search.cc b/chrome/browser/search/search.cc
index 30cf8be..9d31c26 100644
--- a/chrome/browser/search/search.cc
+++ b/chrome/browser/search/search.cc
@@ -566,6 +566,16 @@ int GetInstantLoaderStalenessTimeoutSec() {
return timeout_sec;
}
+bool IsInstantOverlay(const content::WebContents* contents) {
+ for (chrome::BrowserIterator it; !it.done(); it.Next()) {
+ if (it->instant_controller() &&
+ it->instant_controller()->instant()->GetOverlayContents() == contents) {
+ return true;
+ }
+ }
+ return false;
+}
+
bool IsPreloadedInstantExtendedNTP(const content::WebContents* contents) {
for (chrome::BrowserIterator it; !it.done(); it.Next()) {
if (it->instant_controller() &&
diff --git a/chrome/browser/search/search.h b/chrome/browser/search/search.h
index 313f164..95eb51e 100644
--- a/chrome/browser/search/search.h
+++ b/chrome/browser/search/search.h
@@ -168,6 +168,9 @@ bool IsPrivilegedURLForInstant(const GURL& url);
// InstantLoader.
int GetInstantLoaderStalenessTimeoutSec();
+// Returns true if |contents| corresponds to an Instant overlay.
+bool IsInstantOverlay(const content::WebContents* contents);
+
// Returns true if |contents| corresponds to a preloaded instant extended NTP.
bool IsPreloadedInstantExtendedNTP(const content::WebContents* contents);
diff --git a/chrome/browser/tab_contents/render_view_context_menu.cc b/chrome/browser/tab_contents/render_view_context_menu.cc
index eb4b728..b5b80b7 100644
--- a/chrome/browser/tab_contents/render_view_context_menu.cc
+++ b/chrome/browser/tab_contents/render_view_context_menu.cc
@@ -40,6 +40,7 @@
#include "chrome/browser/printing/print_view_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_io_data.h"
+#include "chrome/browser/search/instant_extended_context_menu_observer.h"
#include "chrome/browser/search/search.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_service.h"
@@ -644,8 +645,13 @@ void RenderViewContextMenu::InitMenu() {
print_preview_menu_observer_.reset(
new PrintPreviewContextMenuObserver(source_web_contents_));
}
+ if (!instant_extended_observer_.get()) {
+ instant_extended_observer_.reset(
+ new InstantExtendedContextMenuObserver(source_web_contents_));
+ }
observers_.AddObserver(print_preview_menu_observer_.get());
+ observers_.AddObserver(instant_extended_observer_.get());
}
}
diff --git a/chrome/browser/tab_contents/render_view_context_menu.h b/chrome/browser/tab_contents/render_view_context_menu.h
index 6f2d0b4..a1179a2 100644
--- a/chrome/browser/tab_contents/render_view_context_menu.h
+++ b/chrome/browser/tab_contents/render_view_context_menu.h
@@ -22,6 +22,7 @@
#include "ui/base/models/simple_menu_model.h"
#include "ui/base/window_open_disposition.h"
+class InstantExtendedContextMenuObserver;
class PrintPreviewContextMenuObserver;
class Profile;
class SpellingMenuObserver;
@@ -276,6 +277,9 @@ class RenderViewContextMenu : public ui::SimpleMenuModel::Delegate,
// An observer that disables menu items when print preview is active.
scoped_ptr<PrintPreviewContextMenuObserver> print_preview_menu_observer_;
+ // An observer that disables menu items for instant extended mode.
+ scoped_ptr<InstantExtendedContextMenuObserver> instant_extended_observer_;
+
// Our observers.
mutable ObserverList<RenderViewContextMenuObserver> observers_;
diff --git a/chrome/browser/task_manager/tab_contents_resource_provider.cc b/chrome/browser/task_manager/tab_contents_resource_provider.cc
index de3a008..6b185ff 100644
--- a/chrome/browser/task_manager/tab_contents_resource_provider.cc
+++ b/chrome/browser/task_manager/tab_contents_resource_provider.cc
@@ -64,6 +64,10 @@ class TabContentsResource : public RendererResource {
explicit TabContentsResource(content::WebContents* web_contents);
virtual ~TabContentsResource();
+ // Called when the underlying web_contents has been committed and is no
+ // longer an Instant overlay.
+ void InstantCommitted();
+
// Resource methods:
virtual Type GetType() const OVERRIDE;
virtual string16 GetTitle() const OVERRIDE;
@@ -79,7 +83,7 @@ class TabContentsResource : public RendererResource {
static gfx::ImageSkia* prerender_icon_;
content::WebContents* web_contents_;
Profile* profile_;
- bool is_instant_ntp_;
+ bool is_instant_overlay_;
DISALLOW_COPY_AND_ASSIGN(TabContentsResource);
};
@@ -92,7 +96,8 @@ TabContentsResource::TabContentsResource(
web_contents->GetRenderViewHost()),
web_contents_(web_contents),
profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())),
- is_instant_ntp_(chrome::IsPreloadedInstantExtendedNTP(web_contents)) {
+ is_instant_overlay_(chrome::IsInstantOverlay(web_contents) ||
+ chrome::IsPreloadedInstantExtendedNTP(web_contents)) {
if (!prerender_icon_) {
ResourceBundle& rb = ResourceBundle::GetSharedInstance();
prerender_icon_ = rb.GetImageSkiaNamed(IDR_PRERENDER);
@@ -102,6 +107,11 @@ TabContentsResource::TabContentsResource(
TabContentsResource::~TabContentsResource() {
}
+void TabContentsResource::InstantCommitted() {
+ DCHECK(is_instant_overlay_);
+ is_instant_overlay_ = false;
+}
+
bool TabContentsResource::HostsExtension() const {
return web_contents_->GetURL().SchemeIs(extensions::kExtensionScheme);
}
@@ -128,7 +138,7 @@ string16 TabContentsResource::GetTitle() const {
HostsExtension(),
profile_->IsOffTheRecord(),
IsContentsPrerendering(web_contents_),
- is_instant_ntp_,
+ is_instant_overlay_,
false); // is_background
return l10n_util::GetStringFUTF16(message_id, tab_title);
}
@@ -210,6 +220,8 @@ void TabContentsResourceProvider::StartUpdating() {
// Add all the Instant pages.
for (chrome::BrowserIterator it; !it.done(); it.Next()) {
if (it->instant_controller()) {
+ if (it->instant_controller()->instant()->GetOverlayContents())
+ Add(it->instant_controller()->instant()->GetOverlayContents());
if (it->instant_controller()->instant()->GetNTPContents())
Add(it->instant_controller()->instant()->GetNTPContents());
}
@@ -245,6 +257,8 @@ void TabContentsResourceProvider::StartUpdating() {
content::NotificationService::AllBrowserContextsAndSources());
registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Add(this, chrome::NOTIFICATION_INSTANT_COMMITTED,
+ content::NotificationService::AllBrowserContextsAndSources());
}
void TabContentsResourceProvider::StopUpdating() {
@@ -258,6 +272,8 @@ void TabContentsResourceProvider::StopUpdating() {
content::NotificationService::AllBrowserContextsAndSources());
registrar_.Remove(this, content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED,
content::NotificationService::AllBrowserContextsAndSources());
+ registrar_.Remove(this, chrome::NOTIFICATION_INSTANT_COMMITTED,
+ content::NotificationService::AllBrowserContextsAndSources());
// Delete all the resources.
STLDeleteContainerPairSecondPointers(resources_.begin(), resources_.end());
@@ -280,6 +296,7 @@ void TabContentsResourceProvider::Add(WebContents* web_contents) {
// pages, prerender pages, and background printed pages.
if (!chrome::FindBrowserWithWebContents(web_contents) &&
!IsContentsPrerendering(web_contents) &&
+ !chrome::IsInstantOverlay(web_contents) &&
!chrome::IsPreloadedInstantExtendedNTP(web_contents) &&
!IsContentsBackgroundPrinted(web_contents)) {
return;
@@ -323,6 +340,16 @@ void TabContentsResourceProvider::Remove(WebContents* web_contents) {
delete resource;
}
+void TabContentsResourceProvider::InstantCommitted(WebContents* web_contents) {
+ if (!updating_)
+ return;
+ std::map<WebContents*, TabContentsResource*>::iterator
+ iter = resources_.find(web_contents);
+ DCHECK(iter != resources_.end());
+ if (iter != resources_.end())
+ iter->second->InstantCommitted();
+}
+
void TabContentsResourceProvider::Observe(
int type,
const content::NotificationSource& source,
@@ -340,6 +367,9 @@ void TabContentsResourceProvider::Observe(
case content::NOTIFICATION_WEB_CONTENTS_DISCONNECTED:
Remove(web_contents);
break;
+ case chrome::NOTIFICATION_INSTANT_COMMITTED:
+ InstantCommitted(web_contents);
+ break;
default:
NOTREACHED() << "Unexpected notification.";
return;
diff --git a/chrome/browser/task_manager/tab_contents_resource_provider.h b/chrome/browser/task_manager/tab_contents_resource_provider.h
index 3795e25..66da6ad 100644
--- a/chrome/browser/task_manager/tab_contents_resource_provider.h
+++ b/chrome/browser/task_manager/tab_contents_resource_provider.h
@@ -47,6 +47,7 @@ class TabContentsResourceProvider : public ResourceProvider,
void Add(content::WebContents* web_contents);
void Remove(content::WebContents* web_contents);
+ void InstantCommitted(content::WebContents* web_contents);
void AddToTaskManager(content::WebContents* web_contents);
diff --git a/chrome/browser/ui/browser_instant_controller.cc b/chrome/browser/ui/browser_instant_controller.cc
index 6203f51..aa8b59d 100644
--- a/chrome/browser/ui/browser_instant_controller.cc
+++ b/chrome/browser/ui/browser_instant_controller.cc
@@ -37,7 +37,8 @@ using content::UserMetricsAction;
BrowserInstantController::BrowserInstantController(Browser* browser)
: browser_(browser),
- instant_(this, chrome::IsInstantExtendedAPIEnabled()),
+ instant_(this,
+ chrome::IsInstantExtendedAPIEnabled()),
instant_unload_handler_(browser) {
// TODO(sreeram): Perhaps this can be removed, if field trial info is
@@ -145,6 +146,29 @@ Profile* BrowserInstantController::profile() const {
return browser_->profile();
}
+void BrowserInstantController::CommitInstant(
+ scoped_ptr<content::WebContents> overlay,
+ bool in_new_tab) {
+ const extensions::Extension* extension =
+ profile()->GetExtensionService()->GetInstalledApp(overlay->GetURL());
+ if (extension) {
+ AppLauncherHandler::RecordAppLaunchType(
+ extension_misc::APP_LAUNCH_OMNIBOX_INSTANT,
+ extension->GetType());
+ }
+ if (in_new_tab) {
+ // TabStripModel takes ownership of |overlay|.
+ browser_->tab_strip_model()->AddWebContents(overlay.release(), -1,
+ instant_.last_transition_type(), TabStripModel::ADD_ACTIVE);
+ } else {
+ content::WebContents* contents = overlay.get();
+ ReplaceWebContentsAt(
+ browser_->tab_strip_model()->active_index(),
+ overlay.Pass());
+ browser_->window()->GetLocationBar()->SaveStateToContents(contents);
+ }
+}
+
void BrowserInstantController::ReplaceWebContentsAt(
int index,
scoped_ptr<content::WebContents> new_contents) {
@@ -155,6 +179,20 @@ void BrowserInstantController::ReplaceWebContentsAt(
index);
}
+void BrowserInstantController::SetInstantSuggestion(
+ const InstantSuggestion& suggestion) {
+ browser_->window()->GetLocationBar()->SetInstantSuggestion(suggestion);
+}
+
+gfx::Rect BrowserInstantController::GetInstantBounds() {
+ return browser_->window()->GetInstantBounds();
+}
+
+void BrowserInstantController::InstantOverlayFocused() {
+ // NOTE: This is only invoked on aura.
+ browser_->window()->WebContentsFocused(instant_.GetOverlayContents());
+}
+
void BrowserInstantController::FocusOmnibox(bool caret_visibility) {
OmniboxView* omnibox_view = browser_->window()->GetLocationBar()->
GetLocationEntry();
@@ -206,7 +244,9 @@ void BrowserInstantController::ToggleVoiceSearch() {
}
void BrowserInstantController::ResetInstant(const std::string& pref_name) {
- instant_.ReloadStaleNTP();
+ bool instant_checkbox_checked = chrome::IsInstantCheckboxChecked(profile());
+ bool use_local_overlay_only = !chrome::IsInstantCheckboxEnabled(profile());
+ instant_.SetInstantEnabled(instant_checkbox_checked, use_local_overlay_only);
}
////////////////////////////////////////////////////////////////////////////////
diff --git a/chrome/browser/ui/browser_instant_controller.h b/chrome/browser/ui/browser_instant_controller.h
index 0f9414f..0ffaf10 100644
--- a/chrome/browser/ui/browser_instant_controller.h
+++ b/chrome/browser/ui/browser_instant_controller.h
@@ -58,11 +58,27 @@ class BrowserInstantController
// this BrowserInstantController.
InstantController* instant() { return &instant_; }
+ // Invoked by |instant_| to commit the |overlay| by merging it into the active
+ // tab or adding it as a new tab.
+ void CommitInstant(scoped_ptr<content::WebContents> overlay, bool in_new_tab);
+
+ // Invoked by |instant_| to autocomplete the |suggestion| into the omnibox.
+ void SetInstantSuggestion(const InstantSuggestion& suggestion);
+
+ // Invoked by |instant_| to get the bounds that the overlay is placed at,
+ // in screen coordinates.
+ gfx::Rect GetInstantBounds();
+
+ // Invoked by |instant_| to notify that the overlay gained focus, usually due
+ // to the user clicking on it.
+ void InstantOverlayFocused();
+
// Invoked by |instant_| to give the omnibox focus, with the option of making
// the caret invisible.
void FocusOmnibox(bool caret_visibility);
- // Invoked by |instant_| to get the currently active tab.
+ // Invoked by |instant_| to get the currently active tab, over which the
+ // overlay would be shown.
content::WebContents* GetActiveWebContents() const;
// Invoked by |browser_| when the active tab changes.
diff --git a/chrome/browser/ui/cocoa/browser_window_controller.h b/chrome/browser/ui/cocoa/browser_window_controller.h
index f50723c..ebf70c7 100644
--- a/chrome/browser/ui/cocoa/browser_window_controller.h
+++ b/chrome/browser/ui/cocoa/browser_window_controller.h
@@ -328,6 +328,9 @@ class WebContents;
// coordinates.
- (NSPoint)bookmarkBubblePoint;
+// Shows or hides the Instant overlay contents.
+- (void)commitInstant;
+
// Returns the frame, in Cocoa (unflipped) screen coordinates, of the area where
// Instant results are. If Instant is not showing, returns the frame of where
// it would be.
diff --git a/chrome/browser/ui/cocoa/browser_window_controller.mm b/chrome/browser/ui/cocoa/browser_window_controller.mm
index f9fb551..c490bf1 100644
--- a/chrome/browser/ui/cocoa/browser_window_controller.mm
+++ b/chrome/browser/ui/cocoa/browser_window_controller.mm
@@ -1966,6 +1966,11 @@ willAnimateFromState:(BookmarkBar::State)oldState
return [[toolbarView superview] convertRect:anchorRect toView:nil];
}
+- (void)commitInstant {
+ if (BrowserInstantController* controller = browser_->instant_controller())
+ controller->instant()->CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST);
+}
+
- (NSRect)instantFrame {
// The view's bounds are in its own coordinate system. Convert that to the
// window base coordinate system, then translate it into the screen's
diff --git a/chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.mm b/chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.mm
index 7a2ce85..4daa324 100644
--- a/chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.mm
+++ b/chrome/browser/ui/cocoa/omnibox/omnibox_view_mac.mm
@@ -900,6 +900,7 @@ void OmniboxViewMac::OnFrameChanged() {
// things even cheaper by refactoring between the popup-placement
// code and the matrix-population code.
popup_view_->UpdatePopupAppearance();
+ model()->OnPopupBoundsChanged(popup_view_->GetTargetBounds());
// Give controller a chance to rearrange decorations.
model()->OnChanged();
diff --git a/chrome/browser/ui/cocoa/tab_contents/render_view_context_menu_mac.mm b/chrome/browser/ui/cocoa/tab_contents/render_view_context_menu_mac.mm
index 3ae3496..0ff7557 100644
--- a/chrome/browser/ui/cocoa/tab_contents/render_view_context_menu_mac.mm
+++ b/chrome/browser/ui/cocoa/tab_contents/render_view_context_menu_mac.mm
@@ -9,6 +9,7 @@
#include "base/message_loop.h"
#include "base/strings/sys_string_conversions.h"
#include "chrome/app/chrome_command_ids.h"
+#import "chrome/browser/ui/cocoa/browser_window_controller.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "grit/generated_resources.h"
@@ -108,6 +109,13 @@ void RenderViewContextMenuMac::PlatformCancel() {
}
void RenderViewContextMenuMac::ExecuteCommand(int command_id, int event_flags) {
+ // Auxiliary windows that do not have address bars (Panels for example)
+ // may not have Instant support.
+ NSWindow* parent_window = [parent_view_ window];
+ BrowserWindowController* controller =
+ [BrowserWindowController browserWindowControllerForWindow:parent_window];
+ [controller commitInstant]; // It's ok if controller is nil.
+
switch (command_id) {
case IDC_CONTENT_CONTEXT_LOOK_UP_IN_DICTIONARY:
LookUpInDictionary();
diff --git a/chrome/browser/ui/omnibox/alternate_nav_url_fetcher.cc b/chrome/browser/ui/omnibox/alternate_nav_url_fetcher.cc
index 2b302c0..c6e6d63 100644
--- a/chrome/browser/ui/omnibox/alternate_nav_url_fetcher.cc
+++ b/chrome/browser/ui/omnibox/alternate_nav_url_fetcher.cc
@@ -29,6 +29,8 @@ AlternateNavURLFetcher::AlternateNavURLFetcher(
navigated_to_entry_(false) {
registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING,
content::NotificationService::AllSources());
+ registrar_.Add(this, chrome::NOTIFICATION_INSTANT_COMMITTED,
+ content::NotificationService::AllSources());
}
AlternateNavURLFetcher::~AlternateNavURLFetcher() {
@@ -58,6 +60,19 @@ void AlternateNavURLFetcher::Observe(
break;
}
+ case chrome::NOTIFICATION_INSTANT_COMMITTED: {
+ // See above.
+ NavigationController* controller =
+ &content::Source<content::WebContents>(source)->GetController();
+ if (controller_ == controller) {
+ delete this;
+ } else if (!controller_) {
+ navigated_to_entry_ = true;
+ StartFetch(controller);
+ }
+ break;
+ }
+
case content::NOTIFICATION_NAV_ENTRY_COMMITTED:
// The page was navigated, we can show the infobar now if necessary.
registrar_.Remove(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
diff --git a/chrome/browser/ui/omnibox/omnibox_controller.cc b/chrome/browser/ui/omnibox/omnibox_controller.cc
index cd75dda..5024ab4 100644
--- a/chrome/browser/ui/omnibox/omnibox_controller.cc
+++ b/chrome/browser/ui/omnibox/omnibox_controller.cc
@@ -50,6 +50,11 @@ void OmniboxController::StartAutocomplete(
ClearPopupKeywordMode();
popup_->SetHoveredLine(OmniboxPopupModel::kNoMatch);
+#if defined(HTML_INSTANT_EXTENDED_POPUP)
+ InstantController* instant_controller = GetInstantController();
+ if (instant_controller)
+ instant_controller->OnAutocompleteStart();
+#endif
if (chrome::IsInstantExtendedAPIEnabled()) {
autocomplete_controller_->search_provider()->
SetOmniboxStartMargin(omnibox_start_margin);
@@ -91,7 +96,7 @@ void OmniboxController::OnResultChanged(bool default_match_changed) {
if (!prerender::IsOmniboxEnabled(profile_))
DoPreconnect(*match);
- omnibox_edit_model_->OnCurrentMatchChanged();
+ omnibox_edit_model_->OnCurrentMatchChanged(false);
} else {
InvalidateCurrentMatch();
popup_->OnResultChanged();
@@ -102,16 +107,91 @@ void OmniboxController::OnResultChanged(bool default_match_changed) {
popup_->OnResultChanged();
}
- if (!popup_->IsOpen() && was_open) {
+ // TODO(beaudoin): This may no longer be needed now that instant classic is
+ // gone.
+ if (popup_->IsOpen()) {
+ // The popup size may have changed, let instant know.
+ OnPopupBoundsChanged(popup_->view()->GetTargetBounds());
+
+#if defined(HTML_INSTANT_EXTENDED_POPUP)
+ InstantController* instant_controller = GetInstantController();
+ if (instant_controller && !omnibox_edit_model_->in_revert()) {
+ instant_controller->HandleAutocompleteResults(
+ *autocomplete_controller_->providers(),
+ autocomplete_controller_->result());
+ }
+#endif
+ } else if (was_open) {
// Accept the temporary text as the user text, because it makes little sense
// to have temporary text when the popup is closed.
omnibox_edit_model_->AcceptTemporaryTextAsUserText();
+ // The popup has been closed, let instant know.
+ OnPopupBoundsChanged(gfx::Rect());
}
}
+bool OmniboxController::DoInstant(const AutocompleteMatch& match,
+ string16 user_text,
+ string16 full_text,
+ size_t selection_start,
+ size_t selection_end,
+ bool user_input_in_progress,
+ bool in_escape_handler,
+ bool just_deleted_text,
+ bool keyword_is_selected) {
+#if defined(HTML_INSTANT_EXTENDED_POPUP)
+ InstantController* instant_controller = GetInstantController();
+ if (!instant_controller)
+ return false;
+
+ // Remove "?" if we're in forced query mode.
+ AutocompleteInput::RemoveForcedQueryStringIfNecessary(
+ autocomplete_controller_->input().type(), &user_text);
+ AutocompleteInput::RemoveForcedQueryStringIfNecessary(
+ autocomplete_controller_->input().type(), &full_text);
+ return instant_controller->Update(
+ match, user_text, full_text, selection_start, selection_end,
+ UseVerbatimInstant(just_deleted_text), user_input_in_progress,
+ popup_->IsOpen(), in_escape_handler, keyword_is_selected);
+#else
+ return false;
+#endif
+}
+
void OmniboxController::SetInstantSuggestion(
const InstantSuggestion& suggestion) {
- // TODO(jered): Delete this.
+// Should only get called for the HTML popup.
+#if defined(HTML_INSTANT_EXTENDED_POPUP)
+ switch (suggestion.behavior) {
+ case INSTANT_COMPLETE_NOW:
+ // Set blue suggestion text.
+ // TODO(beaudoin): Create a valid current_match_ and call
+ // omnibox_edit_model_->OnCurrentMatchChanged.
+ return;
+
+ case INSTANT_COMPLETE_NEVER: {
+ DCHECK_EQ(INSTANT_SUGGESTION_SEARCH, suggestion.type);
+
+ // Set gray suggestion text.
+ // Remove "?" if we're in forced query mode.
+ gray_suggestion_ = suggestion.text;
+
+ omnibox_edit_model_->OnGrayTextChanged();
+ return;
+ }
+
+ case INSTANT_COMPLETE_REPLACE:
+ // Replace the entire omnibox text by the suggestion the user just arrowed
+ // to.
+ CreateAndSetInstantMatch(suggestion.text, suggestion.text,
+ suggestion.type == INSTANT_SUGGESTION_SEARCH ?
+ AutocompleteMatchType::SEARCH_SUGGEST :
+ AutocompleteMatchType::URL_WHAT_YOU_TYPED);
+
+ omnibox_edit_model_->OnCurrentMatchChanged(true);
+ return;
+ }
+#endif
}
void OmniboxController::InvalidateCurrentMatch() {
@@ -140,6 +220,12 @@ void OmniboxController::DoPreconnect(const AutocompleteMatch& match) {
}
}
+void OmniboxController::OnPopupBoundsChanged(const gfx::Rect& bounds) {
+ InstantController* instant_controller = GetInstantController();
+ if (instant_controller)
+ instant_controller->SetPopupBounds(bounds);
+}
+
bool OmniboxController::UseVerbatimInstant(bool just_deleted_text) const {
#if defined(OS_MACOSX)
// TODO(suzhe): Fix Mac port to display Instant suggest in a separated NSView,
diff --git a/chrome/browser/ui/omnibox/omnibox_controller.h b/chrome/browser/ui/omnibox/omnibox_controller.h
index 2259ae7..f877d33 100644
--- a/chrome/browser/ui/omnibox/omnibox_controller.h
+++ b/chrome/browser/ui/omnibox/omnibox_controller.h
@@ -57,6 +57,16 @@ class OmniboxController : public AutocompleteControllerDelegate {
return autocomplete_controller_.get();
}
+ bool DoInstant(const AutocompleteMatch& match,
+ string16 user_text,
+ string16 full_text,
+ size_t selection_start,
+ size_t selection_end,
+ bool user_input_in_progress,
+ bool in_escape_handler,
+ bool just_deleted_text,
+ bool keyword_is_selected);
+
// Sets the suggestion text.
void SetInstantSuggestion(const InstantSuggestion& suggestion);
@@ -74,6 +84,8 @@ class OmniboxController : public AutocompleteControllerDelegate {
const AutocompleteMatch& current_match() const { return current_match_; }
+ const string16& gray_suggestion() const { return gray_suggestion_; }
+
// Turns off keyword mode for the current match.
void ClearPopupKeywordMode() const;
@@ -84,6 +96,11 @@ class OmniboxController : public AutocompleteControllerDelegate {
// TODO(beaudoin): Make private once OmniboxEditModel no longer refers to it.
void DoPreconnect(const AutocompleteMatch& match);
+ // TODO(beaudoin): Make private once OmniboxEditModel no longer refers to it.
+ // Invoked when the popup has changed its bounds to |bounds|. |bounds| here
+ // is in screen coordinates.
+ void OnPopupBoundsChanged(const gfx::Rect& bounds);
+
private:
// Returns true if a verbatim query should be used for Instant. A verbatim
@@ -119,6 +136,10 @@ class OmniboxController : public AutocompleteControllerDelegate {
// some time to extract these fields and use a tighter structure here.
AutocompleteMatch current_match_;
+ // The completion suggested by instant, displayed in gray text besides
+ // |fill_into_edit|.
+ string16 gray_suggestion_;
+
DISALLOW_COPY_AND_ASSIGN(OmniboxController);
};
diff --git a/chrome/browser/ui/omnibox/omnibox_edit_model.cc b/chrome/browser/ui/omnibox/omnibox_edit_model.cc
index 405e407..642ad0c 100644
--- a/chrome/browser/ui/omnibox/omnibox_edit_model.cc
+++ b/chrome/browser/ui/omnibox/omnibox_edit_model.cc
@@ -127,11 +127,15 @@ OmniboxEditModel::OmniboxEditModel(OmniboxView* view,
user_input_in_progress_(false),
just_deleted_text_(false),
has_temporary_text_(false),
+ is_temporary_text_set_by_instant_(false),
+ selected_instant_autocomplete_match_index_(OmniboxPopupModel::kNoMatch),
+ is_instant_temporary_text_a_search_query_(false),
paste_state_(NONE),
control_key_state_(UP),
is_keyword_hint_(false),
profile_(profile),
in_revert_(false),
+ in_escape_handler_(false),
allow_exact_keyword_match_(false) {
omnibox_controller_.reset(new OmniboxController(this, profile));
delegate_.reset(new OmniboxCurrentPageDelegateImpl(controller, profile));
@@ -228,10 +232,17 @@ void OmniboxEditModel::SetUserText(const string16& text) {
omnibox_controller_->InvalidateCurrentMatch();
paste_state_ = NONE;
has_temporary_text_ = false;
+ is_temporary_text_set_by_instant_ = false;
+ selected_instant_autocomplete_match_index_ = OmniboxPopupModel::kNoMatch;
+ is_instant_temporary_text_a_search_query_ = false;
}
void OmniboxEditModel::SetInstantSuggestion(
const InstantSuggestion& suggestion) {
+// Should only get called for the HTML popup.
+#if defined(HTML_INSTANT_EXTENDED_POPUP)
+ omnibox_controller_->SetInstantSuggestion(suggestion);
+#endif
}
bool OmniboxEditModel::CommitSuggestedText() {
@@ -276,8 +287,24 @@ void OmniboxEditModel::OnChanged() {
recommended_action,
AutocompleteActionPredictor::LAST_PREDICT_ACTION);
- // Hide any suggestions we might be showing.
- view_->SetInstantSuggestion(string16());
+ // Do not perform instant if we're currently reverting or the change is the
+ // result of an INSTANT_COMPLETE_REPLACE instant suggestion.
+ bool performed_instant = false;
+ if (!in_revert_ && !is_temporary_text_set_by_instant_) {
+ size_t start, end;
+ view_->GetSelectionBounds(&start, &end);
+ string16 user_text = DisplayTextFromUserText(user_text_);
+ performed_instant = omnibox_controller_->DoInstant(
+ current_match, user_text, view_->GetText(), start, end,
+ user_input_in_progress_, in_escape_handler_,
+ view_->DeleteAtEndPressed() || just_deleted_text_,
+ KeywordIsSelected());
+ }
+
+ if (!performed_instant) {
+ // Hide any suggestions we might be showing.
+ view_->SetInstantSuggestion(string16());
+ }
switch (recommended_action) {
case AutocompleteActionPredictor::ACTION_PRERENDER:
@@ -402,6 +429,9 @@ void OmniboxEditModel::Revert() {
keyword_.clear();
is_keyword_hint_ = false;
has_temporary_text_ = false;
+ is_temporary_text_set_by_instant_ = false;
+ selected_instant_autocomplete_match_index_ = OmniboxPopupModel::kNoMatch;
+ is_instant_temporary_text_a_search_query_ = false;
view_->SetWindowTextAndCaretPos(permanent_text_,
has_focus() ? permanent_text_.length() : 0,
false, true);
@@ -575,6 +605,7 @@ void OmniboxEditModel::OpenMatch(const AutocompleteMatch& match,
elapsed_time_since_last_change_to_default_match =
base::TimeDelta::FromMilliseconds(-1);
}
+ // TODO(sreeram): Handle is_temporary_text_set_by_instant_ correctly.
OmniboxLog log(
autocomplete_controller()->input().text(),
just_deleted_text_,
@@ -680,6 +711,20 @@ void OmniboxEditModel::OpenMatch(const AutocompleteMatch& match,
content::RecordAction(UserMetricsAction(
"OmniboxDestinationURLMatchesDefaultSearchProvider"));
+#if defined(HTML_INSTANT_EXTENDED_POPUP)
+ // If running with instant, notify the instant controller that a navigation
+ // is about to take place if we are navigating to a URL. This can be
+ // determined by inspecting the transition type. To ensure that this is only
+ // done on Enter key press, check that the disposition is CURRENT_TAB. This
+ // is the same heuristic used by BrowserInstantController::OpenInstant
+ if (match.transition == content::PAGE_TRANSITION_TYPED &&
+ disposition == CURRENT_TAB) {
+ InstantController* instant = GetInstantController();
+ if (instant)
+ instant->OmniboxNavigateToURL();
+ }
+#endif
+
// This calls RevertAll again.
base::AutoReset<bool> tmp(&in_revert_, true);
controller_->OnAutocompleteAccept(destination_url, disposition,
@@ -706,6 +751,9 @@ bool OmniboxEditModel::AcceptKeyword(EnteredKeywordModeMethod entered_method) {
// the current state properly.
bool save_original_selection = !has_temporary_text_;
has_temporary_text_ = true;
+ is_temporary_text_set_by_instant_ = false;
+ selected_instant_autocomplete_match_index_ = OmniboxPopupModel::kNoMatch;
+ is_instant_temporary_text_a_search_query_ = false;
view_->OnTemporaryTextMaybeChanged(
DisplayTextFromUserText(CurrentMatch(NULL).fill_into_edit),
save_original_selection, true);
@@ -720,6 +768,10 @@ bool OmniboxEditModel::AcceptKeyword(EnteredKeywordModeMethod entered_method) {
void OmniboxEditModel::AcceptTemporaryTextAsUserText() {
InternalSetUserText(UserTextFromDisplayText(view_->GetText()));
has_temporary_text_ = false;
+ is_temporary_text_set_by_instant_ = false;
+ selected_instant_autocomplete_match_index_ = OmniboxPopupModel::kNoMatch;
+ is_instant_temporary_text_a_search_query_ = false;
+ OnPopupBoundsChanged(gfx::Rect());
delegate_->NotifySearchTabHelper(user_input_in_progress_, !in_revert_,
popup_model()->IsOpen(), user_text_.empty());
}
@@ -825,12 +877,14 @@ bool OmniboxEditModel::OnEscapeKeyPressed() {
if (!user_input_in_progress_ && view_->IsSelectAll())
return false;
+ in_escape_handler_ = true;
if (!user_text_.empty()) {
UMA_HISTOGRAM_ENUMERATION(kOmniboxUserTextClearedHistogram,
OMNIBOX_USER_TEXT_CLEARED_WITH_ESCAPE,
OMNIBOX_USER_TEXT_CLEARED_NUM_OF_ITEMS);
}
view_->RevertAll();
+ in_escape_handler_ = false;
view_->SelectAll(true);
return true;
}
@@ -843,6 +897,18 @@ void OmniboxEditModel::OnControlKeyChanged(bool pressed) {
void OmniboxEditModel::OnUpOrDownKeyPressed(int count) {
// NOTE: This purposefully doesn't trigger any code that resets paste_state_.
if (popup_model()->IsOpen()) {
+#if defined(HTML_INSTANT_EXTENDED_POPUP)
+ InstantController* instant = GetInstantController();
+ if (instant && instant->OnUpOrDownKeyPressed(count)) {
+ // If Instant handles the key press, it's showing a list of suggestions
+ // that it's stepping through. In that case, our popup model is
+ // irrelevant, so don't process the key press ourselves. However, do stop
+ // the autocomplete system from changing the results.
+ autocomplete_controller()->Stop(false);
+ return;
+ }
+#endif
+
// The popup is open, so the user should be able to interact with it
// normally.
popup_model()->Move(count);
@@ -893,6 +959,9 @@ void OmniboxEditModel::OnPopupDataChanged(
if (save_original_selection) {
// Save the original selection and URL so it can be reverted later.
has_temporary_text_ = true;
+ is_temporary_text_set_by_instant_ = false;
+ selected_instant_autocomplete_match_index_ = OmniboxPopupModel::kNoMatch;
+ is_instant_temporary_text_a_search_query_ = false;
original_url_ = *destination_for_temporary_text_change;
inline_autocomplete_text_.clear();
}
@@ -998,6 +1067,9 @@ bool OmniboxEditModel::OnAfterPossibleChange(const string16& old_text,
if (user_text_changed) {
InternalSetUserText(UserTextFromDisplayText(new_text));
has_temporary_text_ = false;
+ is_temporary_text_set_by_instant_ = false;
+ selected_instant_autocomplete_match_index_ = OmniboxPopupModel::kNoMatch;
+ is_instant_temporary_text_a_search_query_ = false;
// Track when the user has deleted text so we won't allow inline
// autocomplete.
@@ -1049,27 +1121,38 @@ bool OmniboxEditModel::OnAfterPossibleChange(const string16& old_text,
MaybeAcceptKeywordBySpace(user_text_));
}
-void OmniboxEditModel::OnCurrentMatchChanged() {
- has_temporary_text_ = false;
+void OmniboxEditModel::OnCurrentMatchChanged(bool is_temporary_set_by_instant) {
+ has_temporary_text_ = is_temporary_set_by_instant;
+ is_temporary_text_set_by_instant_ = is_temporary_set_by_instant;
const AutocompleteMatch& match = omnibox_controller_->current_match();
- // We store |keyword| and |is_keyword_hint| in temporary variables since
- // OnPopupDataChanged use their previous state to detect changes.
- string16 keyword;
- bool is_keyword_hint;
- match.GetKeywordUIState(profile_, &keyword, &is_keyword_hint);
- string16 inline_autocomplete_text;
- if (match.inline_autocomplete_offset < match.fill_into_edit.length()) {
- // We have blue text, go through OnPopupDataChanged.
- // TODO(beaudoin): Merge OnPopupDataChanged with this method once the
- // popup handling has completely migrated to omnibox_controller.
- inline_autocomplete_text =
- match.fill_into_edit.substr(match.inline_autocomplete_offset);
+ if (is_temporary_set_by_instant) {
+ view_->OnTemporaryTextMaybeChanged(
+ DisplayTextFromUserText(match.fill_into_edit), !has_temporary_text_,
+ false);
+ } else {
+ // We store |keyword| and |is_keyword_hint| in temporary variables since
+ // OnPopupDataChanged use their previous state to detect changes.
+ string16 keyword;
+ bool is_keyword_hint;
+ match.GetKeywordUIState(profile_, &keyword, &is_keyword_hint);
+ string16 inline_autocomplete_text;
+ if (match.inline_autocomplete_offset < match.fill_into_edit.length()) {
+ // We have blue text, go through OnPopupDataChanged.
+ // TODO(beaudoin): Merge OnPopupDataChanged with this method once the
+ // popup handling has completely migrated to omnibox_controller.
+ inline_autocomplete_text =
+ match.fill_into_edit.substr(match.inline_autocomplete_offset);
+ }
+ popup_model()->OnResultChanged();
+ OnPopupDataChanged(inline_autocomplete_text, NULL, keyword,
+ is_keyword_hint);
}
- popup_model()->OnResultChanged();
- OnPopupDataChanged(inline_autocomplete_text, NULL, keyword,
- is_keyword_hint);
+}
+
+void OmniboxEditModel::OnGrayTextChanged() {
+ view_->SetInstantSuggestion(omnibox_controller_->gray_suggestion());
}
string16 OmniboxEditModel::GetViewText() const {
@@ -1127,6 +1210,56 @@ void OmniboxEditModel::GetInfoForCurrentText(AutocompleteMatch* match,
match->destination_url =
delegate_->GetNavigationController().GetVisibleEntry()->GetURL();
match->transition = content::PAGE_TRANSITION_RELOAD;
+#if defined(HTML_INSTANT_EXTENDED_POPUP)
+ } else if (is_temporary_text_set_by_instant_) {
+ // If there's temporary text and it has been set by Instant, we won't find
+ // it in the popup model, so create the match based on the type Instant told
+ // us (SWYT for queries and UWYT for URLs). We do this instead of
+ // classifying the text ourselves because the text may look like a URL, but
+ // Instant may expect it to be a search (e.g.: a query for "amazon.com").
+ if (selected_instant_autocomplete_match_index_ !=
+ OmniboxPopupModel::kNoMatch) {
+ // Great, we know the exact match struct. Just use that.
+ const AutocompleteResult& result = this->result();
+ *match = result.match_at(selected_instant_autocomplete_match_index_);
+ } else {
+ const string16& text = view_->GetText();
+ AutocompleteInput input(text, string16::npos, string16(), GURL(), false,
+ false, false, AutocompleteInput::BEST_MATCH);
+ // Only the destination_url and the transition of the match will be be
+ // used (to either navigate to the URL or let Instant commit its preview).
+ // The match won't be used for logging, displaying in the dropdown, etc.
+ // So, it's okay to pass in mostly bogus params (such as relevance = 0).
+ // TODO(sreeram): Always using NO_SUGGESTIONS_AVAILABLE is wrong when
+ // Instant is using the local fallback overlay. Fix.
+ if (is_instant_temporary_text_a_search_query_) {
+ const TemplateURL* default_provider =
+ TemplateURLServiceFactory::GetForProfile(profile_)->
+ GetDefaultSearchProvider();
+ if (default_provider && default_provider->SupportsReplacement()) {
+ *match = SearchProvider::CreateSearchSuggestion(
+ autocomplete_controller()->search_provider(), 0,
+ AutocompleteMatchType::SEARCH_WHAT_YOU_TYPED, default_provider,
+ text, text, input, false,
+ TemplateURLRef::NO_SUGGESTIONS_AVAILABLE,
+ controller_->GetOmniboxBounds().x(), true);
+ } else {
+ // Can't create a new search match. Leave |match| as is, with an
+ // invalid destination_url. This shouldn't ever happen. For example,
+ // even if a group policy update in the midst of interacting with
+ // Instant leaves us without a valid search provider, Instant
+ // should've observed the update and reset
+ // |is_temporary_text_set_by_instant_|, so we still shouldn't get
+ // here. However, as protection against the unknowns and Instant
+ // regressions, we simply return an invalid match instead of crashing
+ // (hence no DCHECK).
+ }
+ } else {
+ *match = HistoryURLProvider::SuggestExactInput(
+ autocomplete_controller()->history_url_provider(), input, false);
+ }
+ }
+#endif
} else if (popup_model()->IsOpen() || query_in_progress()) {
if (query_in_progress()) {
// It's technically possible for |result| to be empty if no provider
@@ -1157,9 +1290,36 @@ void OmniboxEditModel::RevertTemporaryText(bool revert_popup) {
// The user typed something, then selected a different item. Restore the
// text they typed and change back to the default item.
// NOTE: This purposefully does not reset paste_state_.
+#if defined(HTML_INSTANT_EXTENDED_POPUP)
+ bool notify_instant = is_temporary_text_set_by_instant_;
+#endif
just_deleted_text_ = false;
has_temporary_text_ = false;
+ is_temporary_text_set_by_instant_ = false;
+ selected_instant_autocomplete_match_index_ = OmniboxPopupModel::kNoMatch;
+ is_instant_temporary_text_a_search_query_ = false;
+#if defined(HTML_INSTANT_EXTENDED_POPUP)
+ InstantController* instant = GetInstantController();
+ if (instant && notify_instant) {
+ // Normally, popup_model()->ResetToDefaultMatch() will cause the view text
+ // to be updated. In Instant Extended mode however, the popup_model() is
+ // not used, so it won't do anything. So, update the view ourselves. Even
+ // if Instant is not in extended mode (i.e., it's enabled in non-extended
+ // mode, or disabled altogether), this is okay to do, since the call to
+ // popup_model()->ResetToDefaultMatch() will just override whatever we do
+ // here.
+ //
+ // The two "false" arguments make sure that our shenanigans don't cause any
+ // previously saved selection to be erased nor OnChanged() to be called.
+ view_->OnTemporaryTextMaybeChanged(user_text_ + inline_autocomplete_text_,
+ false, false);
+ AutocompleteResult::const_iterator match(result().default_match());
+ instant->OnCancel(match != result().end() ? *match : AutocompleteMatch(),
+ user_text_,
+ user_text_ + inline_autocomplete_text_);
+ }
+#endif
if (revert_popup)
popup_model()->ResetToDefaultMatch();
view_->OnRevertTemporaryText();
diff --git a/chrome/browser/ui/omnibox/omnibox_edit_model.h b/chrome/browser/ui/omnibox/omnibox_edit_model.h
index d9f40c8..f4a8aa6 100644
--- a/chrome/browser/ui/omnibox/omnibox_edit_model.h
+++ b/chrome/browser/ui/omnibox/omnibox_edit_model.h
@@ -297,8 +297,19 @@ class OmniboxEditModel {
bool just_deleted_text,
bool allow_keyword_ui_change);
+ // TODO(beaudoin): Mac code still calls this here. We should try to untangle
+ // this.
+ // Invoked when the popup has changed its bounds to |bounds|. |bounds| here
+ // is in screen coordinates.
+ void OnPopupBoundsChanged(const gfx::Rect& bounds) {
+ omnibox_controller_->OnPopupBoundsChanged(bounds);
+ }
+
// Called when the current match has changed in the OmniboxController.
- void OnCurrentMatchChanged();
+ void OnCurrentMatchChanged(bool is_temporary_set_by_instant);
+
+ // Callend when the gray text suggestion has changed in the OmniboxController.
+ void OnGrayTextChanged();
// Access the current view text.
string16 GetViewText() const;
@@ -481,6 +492,26 @@ class OmniboxEditModel {
bool has_temporary_text_;
GURL original_url_;
+ // True if Instant set the current temporary text, as opposed to it being set
+ // due to the user arrowing up/down through the popup. This can only be true
+ // if |has_temporary_text_| is true.
+ // TODO(sreeram): This is a temporary hack. Remove it once the omnibox edit
+ // model/view code is decoupled from Instant (among other things).
+ bool is_temporary_text_set_by_instant_;
+
+ // The index of the selected AutocompleteMatch in AutocompleteResult. This is
+ // needed to get the metadata details of the temporary text set by instant on
+ // the Local NTP. If the Instant extended is disabled or an Instant NTP is
+ // used, this is set to OmniboxPopupModel::kNoMatch.
+ size_t selected_instant_autocomplete_match_index_;
+
+ // True if the current temporary text set by Instant is a search query; false
+ // if it is a URL that can be directly navigated to. This is only valid if
+ // |is_temporary_text_set_by_instant_| is true. This field is needed because
+ // Instant's temporary text doesn't come from the popup model, so we can't
+ // lookup its type from the current match.
+ bool is_instant_temporary_text_a_search_query_;
+
// When the user's last action was to paste, we disallow inline autocomplete
// (on the theory that the user is trying to paste in a new URL or part of
// one, and in either case inline autocomplete would get in the way).
@@ -521,6 +552,11 @@ class OmniboxEditModel {
// "foo", which is wrong.
bool in_revert_;
+ // InstantController needs this in extended mode to distinguish the case in
+ // which it should instruct a committed search results page to revert to
+ // showing results for the original query.
+ bool in_escape_handler_;
+
// Indicates if the upcoming autocomplete search is allowed to be treated as
// an exact keyword match. If this is true then keyword mode will be
// triggered automatically if the input is "<keyword> <search string>". We
diff --git a/chrome/browser/ui/search/instant_commit_type.h b/chrome/browser/ui/search/instant_commit_type.h
new file mode 100644
index 0000000..3ead857
--- /dev/null
+++ b/chrome/browser/ui/search/instant_commit_type.h
@@ -0,0 +1,25 @@
+// Copyright 2012 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_UI_SEARCH_INSTANT_COMMIT_TYPE_H_
+#define CHROME_BROWSER_UI_SEARCH_INSTANT_COMMIT_TYPE_H_
+
+// Reason why the Instant overlay is committed (merged into a tab).
+enum InstantCommitType {
+ // The commit is due to the user pressing Enter from the omnibox.
+ INSTANT_COMMIT_PRESSED_ENTER,
+
+ // The commit is due to the user pressing Alt-Enter from the omnibox (which
+ // causes the overlay to be committed onto a new tab).
+ INSTANT_COMMIT_PRESSED_ALT_ENTER,
+
+ // The commit is due to the omnibox losing focus, usually due to the user
+ // clicking on the overlay.
+ INSTANT_COMMIT_FOCUS_LOST,
+
+ // The commit is due to the Instant overlay navigating to a non-Instant URL.
+ INSTANT_COMMIT_NAVIGATED,
+};
+
+#endif // CHROME_BROWSER_UI_SEARCH_INSTANT_COMMIT_TYPE_H_
diff --git a/chrome/browser/ui/search/instant_controller.cc b/chrome/browser/ui/search/instant_controller.cc
index a3f465b..f80fbf9 100644
--- a/chrome/browser/ui/search/instant_controller.cc
+++ b/chrome/browser/ui/search/instant_controller.cc
@@ -4,13 +4,22 @@
#include "chrome/browser/ui/search/instant_controller.h"
+#include <iterator>
+
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
+#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "chrome/browser/autocomplete/autocomplete_provider.h"
+#include "chrome/browser/autocomplete/autocomplete_result.h"
+#include "chrome/browser/autocomplete/search_provider.h"
#include "chrome/browser/content_settings/content_settings_provider.h"
#include "chrome/browser/content_settings/host_content_settings_map.h"
+#include "chrome/browser/history/history_service.h"
+#include "chrome/browser/history/history_service_factory.h"
+#include "chrome/browser/history/history_tab_helper.h"
#include "chrome/browser/platform_util.h"
-#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search/instant_service.h"
#include "chrome/browser/search/instant_service_factory.h"
#include "chrome/browser/search/search.h"
@@ -19,6 +28,7 @@
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/browser_instant_controller.h"
#include "chrome/browser/ui/search/instant_ntp.h"
+#include "chrome/browser/ui/search/instant_overlay.h"
#include "chrome/browser/ui/search/instant_tab.h"
#include "chrome/browser/ui/search/search_tab_helper.h"
#include "chrome/common/chrome_notification_types.h"
@@ -36,6 +46,7 @@
#include "content/public/browser/web_contents_view.h"
#include "net/base/escape.h"
#include "net/base/network_change_notifier.h"
+#include "third_party/icu/public/common/unicode/normalizer2.h"
#if defined(TOOLKIT_VIEWS)
#include "ui/views/widget/widget.h"
@@ -43,6 +54,11 @@
namespace {
+// An artificial delay (in milliseconds) we introduce before telling the Instant
+// page about the new omnibox bounds, in cases where the bounds shrink. This is
+// to avoid the page jumping up/down very fast in response to bounds changes.
+const int kUpdateBoundsDelayMS = 1000;
+
// For reporting Instant navigations.
enum InstantNavigation {
INSTANT_NAVIGATION_LOCAL_CLICK = 0,
@@ -69,6 +85,128 @@ void RecordNavigationHistogram(bool is_local, bool is_click, bool is_extended) {
INSTANT_NAVIGATION_MAX);
}
+void RecordFallbackReasonHistogram(
+ const InstantController::InstantFallbackReason fallback_reason) {
+ UMA_HISTOGRAM_ENUMERATION("InstantExtended.FallbackToLocalOverlay",
+ fallback_reason,
+ InstantController::INSTANT_FALLBACK_MAX);
+}
+
+InstantController::InstantFallbackReason DetermineFallbackReason(
+ const InstantPage* page, std::string instant_url) {
+ InstantController::InstantFallbackReason fallback_reason;
+ if (!page) {
+ fallback_reason = InstantController::INSTANT_FALLBACK_NO_OVERLAY;
+ } else if (instant_url.empty()) {
+ fallback_reason = InstantController::INSTANT_FALLBACK_INSTANT_URL_EMPTY;
+ } else if (!chrome::MatchesOriginAndPath(GURL(page->instant_url()),
+ GURL(instant_url))) {
+ fallback_reason = InstantController::INSTANT_FALLBACK_ORIGIN_PATH_MISMATCH;
+ } else if (!page->supports_instant()) {
+ fallback_reason = InstantController::INSTANT_FALLBACK_INSTANT_NOT_SUPPORTED;
+ } else {
+ fallback_reason = InstantController::INSTANT_FALLBACK_UNKNOWN;
+ }
+ return fallback_reason;
+}
+
+void AddSessionStorageHistogram(bool extended_enabled,
+ const content::WebContents* tab1,
+ const content::WebContents* tab2) {
+ base::HistogramBase* histogram = base::BooleanHistogram::FactoryGet(
+ std::string("Instant.SessionStorageNamespace") +
+ (extended_enabled ? "_Extended" : "_Instant"),
+ base::HistogramBase::kUmaTargetedHistogramFlag);
+ const content::SessionStorageNamespaceMap& session_storage_map1 =
+ tab1->GetController().GetSessionStorageNamespaceMap();
+ const content::SessionStorageNamespaceMap& session_storage_map2 =
+ tab2->GetController().GetSessionStorageNamespaceMap();
+ bool is_session_storage_the_same =
+ session_storage_map1.size() == session_storage_map2.size();
+ if (is_session_storage_the_same) {
+ // The size is the same, so let's check that all entries match.
+ for (content::SessionStorageNamespaceMap::const_iterator
+ it1 = session_storage_map1.begin(),
+ it2 = session_storage_map2.begin();
+ it1 != session_storage_map1.end() && it2 != session_storage_map2.end();
+ ++it1, ++it2) {
+ if (it1->first != it2->first || it1->second.get() != it2->second.get()) {
+ is_session_storage_the_same = false;
+ break;
+ }
+ }
+ }
+ histogram->AddBoolean(is_session_storage_the_same);
+}
+
+string16 Normalize(const string16& str) {
+ UErrorCode status = U_ZERO_ERROR;
+ const icu::Normalizer2* normalizer =
+ icu::Normalizer2::getInstance(NULL, "nfkc_cf", UNORM2_COMPOSE, status);
+ if (normalizer == NULL || U_FAILURE(status))
+ return str;
+ icu::UnicodeString norm_str(normalizer->normalize(
+ icu::UnicodeString(FALSE, str.c_str(), str.size()), status));
+ if (U_FAILURE(status))
+ return str;
+ return string16(norm_str.getBuffer(), norm_str.length());
+}
+
+bool NormalizeAndStripPrefix(string16* text, const string16& prefix) {
+ string16 norm_prefix = Normalize(prefix);
+ string16 norm_text = Normalize(*text);
+ if (norm_prefix.size() <= norm_text.size() &&
+ norm_text.compare(0, norm_prefix.size(), norm_prefix) == 0) {
+ *text = norm_text.erase(0, norm_prefix.size());
+ return true;
+ }
+ return false;
+}
+
+// For TOOLKIT_VIEWS, the top level widget is always focused. If the focus
+// change originated in views determine the child Widget from the view that is
+// being focused.
+gfx::NativeView GetViewGainingFocus(gfx::NativeView view_gaining_focus) {
+#if defined(TOOLKIT_VIEWS)
+ views::Widget* widget = view_gaining_focus ?
+ views::Widget::GetWidgetForNativeView(view_gaining_focus) : NULL;
+ if (widget) {
+ views::FocusManager* focus_manager = widget->GetFocusManager();
+ if (focus_manager && focus_manager->is_changing_focus() &&
+ focus_manager->GetFocusedView() &&
+ focus_manager->GetFocusedView()->GetWidget())
+ return focus_manager->GetFocusedView()->GetWidget()->GetNativeView();
+ }
+#endif
+ return view_gaining_focus;
+}
+
+// Returns true if |view| is the top-level contents view or a child view in the
+// view hierarchy of |contents|.
+bool IsViewInContents(gfx::NativeView view, content::WebContents* contents) {
+ content::RenderWidgetHostView* rwhv = contents->GetRenderWidgetHostView();
+ if (!view || !rwhv)
+ return false;
+
+ gfx::NativeView tab_view = contents->GetView()->GetNativeView();
+ if (view == rwhv->GetNativeView() || view == tab_view)
+ return true;
+
+ // Walk up the view hierarchy to determine if the view is a subview of the
+ // WebContents view (such as a windowed plugin or http auth dialog).
+ while (view) {
+ view = platform_util::GetParent(view);
+ if (view == tab_view)
+ return true;
+ }
+
+ return false;
+}
+
+bool IsFullHeight(const InstantOverlayModel& model) {
+ return model.height() == 100 && model.height_units() == INSTANT_SIZE_PERCENT;
+}
+
bool IsContentsFrom(const InstantPage* page,
const content::WebContents* contents) {
return page && (page->contents() == contents);
@@ -118,9 +256,19 @@ InstantController::InstantController(BrowserInstantController* browser,
bool extended_enabled)
: browser_(browser),
extended_enabled_(extended_enabled),
+ instant_enabled_(false),
+ use_local_page_only_(true),
+ preload_ntp_(true),
+ model_(this),
+ use_tab_for_suggestions_(false),
+ last_omnibox_text_has_inline_autocompletion_(false),
+ last_verbatim_(false),
+ last_transition_type_(content::PAGE_TRANSITION_LINK),
+ last_match_was_search_(false),
omnibox_focus_state_(OMNIBOX_FOCUS_NONE),
omnibox_focus_change_reason_(OMNIBOX_FOCUS_CHANGE_EXPLICIT),
- omnibox_bounds_(-1, -1, 0, 0) {
+ omnibox_bounds_(-1, -1, 0, 0),
+ allow_overlay_to_show_search_suggestions_(false) {
// When the InstantController lives, the InstantService should live.
// InstantService sets up profile-level facilities such as the ThemeSource for
@@ -139,6 +287,267 @@ InstantController::~InstantController() {
}
}
+void InstantController::OnAutocompleteStart() {
+ if (UseTabForSuggestions() && instant_tab_->supports_instant()) {
+ LOG_INSTANT_DEBUG_EVENT(
+ this, "OnAutocompleteStart: using InstantTab");
+ return;
+ }
+
+ // Not using |instant_tab_|. Check if overlay is OK to use.
+ InstantFallbackReason fallback_reason = ShouldSwitchToLocalOverlay();
+ if (fallback_reason != INSTANT_FALLBACK_NONE) {
+ ResetOverlay(GetLocalInstantURL());
+ RecordFallbackReasonHistogram(fallback_reason);
+ LOG_INSTANT_DEBUG_EVENT(
+ this, "OnAutocompleteStart: switching to local overlay");
+ } else {
+ LOG_INSTANT_DEBUG_EVENT(
+ this, "OnAutocompleteStart: using existing overlay");
+ }
+ use_tab_for_suggestions_ = false;
+}
+
+bool InstantController::Update(const AutocompleteMatch& match,
+ const string16& user_text,
+ const string16& full_text,
+ size_t selection_start,
+ size_t selection_end,
+ bool verbatim,
+ bool user_input_in_progress,
+ bool omnibox_popup_is_open,
+ bool escape_pressed,
+ bool is_keyword_search) {
+ if (!extended_enabled() && !instant_enabled_)
+ return false;
+
+ LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
+ "Update: %s user_text='%s' full_text='%s' selection_start=%d "
+ "selection_end=%d verbatim=%d typing=%d popup=%d escape_pressed=%d "
+ "is_keyword_search=%d",
+ AutocompleteMatchType::ToString(match.type).c_str(),
+ UTF16ToUTF8(user_text).c_str(), UTF16ToUTF8(full_text).c_str(),
+ static_cast<int>(selection_start), static_cast<int>(selection_end),
+ verbatim, user_input_in_progress, omnibox_popup_is_open, escape_pressed,
+ is_keyword_search));
+
+ // Store the current |last_omnibox_text_| and update |last_omnibox_text_|
+ // upfront with the contents of |full_text|. Even if we do an early return,
+ // |last_omnibox_text_| will be updated.
+ string16 previous_omnibox_text = last_omnibox_text_;
+ last_omnibox_text_ = full_text;
+ last_match_was_search_ = AutocompleteMatch::IsSearchType(match.type) &&
+ !user_text.empty();
+
+ // TODO(dhollowa): Complete keyword match UI. For now just hide suggestions.
+ // http://crbug.com/153932. Note, this early escape is happens prior to the
+ // DCHECKs below because |user_text| and |full_text| have different semantics
+ // when keyword search is in effect.
+ if (is_keyword_search) {
+ if (UseTabForSuggestions())
+ instant_tab_->sender()->Update(string16(), 0, 0, true);
+ else
+ HideOverlay();
+ last_match_was_search_ = false;
+ last_suggestion_ = InstantSuggestion();
+ return false;
+ }
+
+ // Ignore spurious updates when the omnibox is blurred; otherwise click
+ // targets on the page may vanish before a click event arrives.
+ if (omnibox_focus_state_ == OMNIBOX_FOCUS_NONE)
+ return false;
+
+ // If the popup is open, the user has to be typing.
+ DCHECK(!omnibox_popup_is_open || user_input_in_progress);
+
+ // If the popup is closed, there should be no inline autocompletion.
+ DCHECK(omnibox_popup_is_open || user_text.empty() || user_text == full_text)
+ << user_text << "|" << full_text;
+
+ // If there's no text in the omnibox, the user can't have typed any.
+ DCHECK(!full_text.empty() || user_text.empty()) << user_text;
+
+ // If the user isn't typing, and the popup is closed, there can't be any
+ // user-typed text.
+ DCHECK(user_input_in_progress || omnibox_popup_is_open || user_text.empty())
+ << user_text;
+
+ // The overlay is being clicked and will commit soon. Don't change anything.
+ // TODO(sreeram): Add a browser test for this.
+ if (overlay_ && overlay_->is_pointer_down_from_activate())
+ return false;
+
+ // In non-extended mode, SearchModeChanged() is never called, so fake it. The
+ // mode is set to "disallow suggestions" here, so that if one of the early
+ // "return false" conditions is hit, suggestions will be disallowed. If the
+ // query is sent to the overlay, the mode is set to "allow" further below.
+ if (!extended_enabled())
+ search_mode_.mode = SearchMode::MODE_DEFAULT;
+
+ // In non extended mode, Instant is disabled for URLs and keyword mode.
+ if (!extended_enabled() &&
+ (!last_match_was_search_ ||
+ match.type == AutocompleteMatchType::SEARCH_OTHER_ENGINE)) {
+ HideOverlay();
+ return false;
+ }
+
+ if (!UseTabForSuggestions() && !overlay_) {
+ HideOverlay();
+ return false;
+ }
+
+ if (extended_enabled()) {
+ if (!omnibox_popup_is_open) {
+ if (!user_input_in_progress) {
+ // If the user isn't typing and the omnibox popup is closed, it means a
+ // regular navigation, tab-switch or the user hitting Escape.
+ if (UseTabForSuggestions()) {
+ // The user is on a search results page. It may be showing results for
+ // a partial query the user typed before they hit Escape. Send the
+ // omnibox text to the page to restore the original results.
+ //
+ // In a tab switch, |instant_tab_| won't have updated yet, so it may
+ // be pointing to the previous tab (which was a search results page).
+ // Ensure we don't send the omnibox text to a random webpage (the new
+ // tab), by comparing the old and new WebContents.
+ if (escape_pressed &&
+ instant_tab_->contents() == browser_->GetActiveWebContents()) {
+ // TODO(kmadhusu): If the |full_text| is not empty, send an
+ // onkeypress(esc) to the Instant page. Do not call
+ // onsubmit(full_text). Fix.
+ if (full_text.empty()) {
+ // Call onchange("") to clear the query for the page.
+ instant_tab_->sender()->Update(string16(), 0, 0, true);
+ instant_tab_->sender()->EscKeyPressed();
+ } else {
+ instant_tab_->sender()->Submit(full_text);
+ }
+ }
+ } else if (!full_text.empty()) {
+ // If |full_text| is empty, the user is on the NTP. The overlay may
+ // be showing custom NTP content; hide only if that's not the case.
+ HideOverlay();
+ }
+ } else if (full_text.empty()) {
+ // The user is typing, and backspaced away all omnibox text. Clear
+ // |last_omnibox_text_| so that we don't attempt to set suggestions.
+ last_omnibox_text_.clear();
+ last_user_text_.clear();
+ last_suggestion_ = InstantSuggestion();
+ if (UseTabForSuggestions()) {
+ // On a search results page, tell it to clear old results.
+ instant_tab_->sender()->Update(string16(), 0, 0, true);
+ } else if (overlay_ && search_mode_.is_origin_ntp()) {
+ // On the NTP, tell the overlay to clear old results. Don't hide the
+ // overlay so it can show a blank page or logo if it wants.
+ overlay_->Update(string16(), 0, 0, true);
+ } else {
+ HideOverlay();
+ }
+ } else {
+ // The user switched to a tab with partial text already in the omnibox.
+ HideOverlay();
+
+ // The new tab may or may not be a search results page; we don't know
+ // since SearchModeChanged() hasn't been called yet. If it later turns
+ // out to be, we should store |full_text| now, so that if the user hits
+ // Enter, we'll send the correct query to
+ // instant_tab_->sender()->Submit(). If the partial text is not a query
+ // (|last_match_was_search_| is false), we won't Submit(), so no need to
+ // worry about that.
+ last_user_text_ = user_text;
+ last_suggestion_ = InstantSuggestion();
+ }
+ return false;
+ } else if (full_text.empty()) {
+ // The user typed a solitary "?". Same as the backspace case above.
+ last_omnibox_text_.clear();
+ last_user_text_.clear();
+ last_suggestion_ = InstantSuggestion();
+ if (UseTabForSuggestions())
+ instant_tab_->sender()->Update(string16(), 0, 0, true);
+ else if (overlay_ && search_mode_.is_origin_ntp())
+ overlay_->Update(string16(), 0, 0, true);
+ else
+ HideOverlay();
+ return false;
+ }
+ } else if (!omnibox_popup_is_open || full_text.empty()) {
+ // In the non-extended case, hide the overlay as long as the user isn't
+ // actively typing a non-empty query.
+ HideOverlay();
+ return false;
+ }
+
+ last_omnibox_text_has_inline_autocompletion_ = user_text != full_text;
+
+ // If the user continues typing the same query as the suggested text is
+ // showing, reuse the suggestion (but only for INSTANT_COMPLETE_NEVER).
+ bool reused_suggestion = false;
+ if (last_suggestion_.behavior == INSTANT_COMPLETE_NEVER &&
+ !last_omnibox_text_has_inline_autocompletion_) {
+ if (StartsWith(previous_omnibox_text, full_text, false)) {
+ // The user is backspacing away characters.
+ last_suggestion_.text.insert(0, previous_omnibox_text, full_text.size(),
+ previous_omnibox_text.size() - full_text.size());
+ reused_suggestion = true;
+ } else if (StartsWith(full_text, previous_omnibox_text, false)) {
+ // The user is typing forward. Normalize any added characters.
+ reused_suggestion = NormalizeAndStripPrefix(&last_suggestion_.text,
+ string16(full_text, previous_omnibox_text.size()));
+ }
+ }
+ if (!reused_suggestion)
+ last_suggestion_ = InstantSuggestion();
+
+ // TODO(kmadhusu): Investigate whether it's possible to update
+ // |last_user_text_| at the beginning of this function.
+ last_user_text_ = user_text;
+
+ if (!extended_enabled()) {
+ // In non-extended mode, the query is verbatim if there's any selection
+ // (including inline autocompletion) or if the cursor is not at the end.
+ verbatim = verbatim || selection_start != selection_end ||
+ selection_start != full_text.size();
+ }
+ last_verbatim_ = verbatim;
+
+ last_transition_type_ = match.transition;
+ url_for_history_ = match.destination_url;
+
+ // Allow search suggestions. In extended mode, SearchModeChanged() will set
+ // this, but it's not called in non-extended mode, so fake it.
+ if (!extended_enabled())
+ search_mode_.mode = SearchMode::MODE_SEARCH_SUGGESTIONS;
+
+ if (UseTabForSuggestions()) {
+ instant_tab_->sender()->Update(user_text, selection_start,
+ selection_end, verbatim);
+ } else if (overlay_) {
+ allow_overlay_to_show_search_suggestions_ = true;
+
+ overlay_->Update(extended_enabled() ? user_text : full_text,
+ selection_start, selection_end, verbatim);
+ }
+
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_INSTANT_CONTROLLER_UPDATED,
+ content::Source<InstantController>(this),
+ content::NotificationService::NoDetails());
+
+ // We don't have new suggestions yet, but we can either reuse the existing
+ // suggestion or reset the existing "gray text".
+ browser_->SetInstantSuggestion(last_suggestion_);
+
+ // Record the time of the first keypress for logging histograms.
+ if (!first_interaction_time_recorded_ && first_interaction_time_.is_null())
+ first_interaction_time_ = base::Time::Now();
+
+ return true;
+}
+
scoped_ptr<content::WebContents> InstantController::ReleaseNTPContents() {
if (!extended_enabled() || !browser_->profile() ||
browser_->profile()->IsOffTheRecord() ||
@@ -153,27 +562,178 @@ scoped_ptr<content::WebContents> InstantController::ReleaseNTPContents() {
scoped_ptr<content::WebContents> ntp_contents = ntp_->ReleaseContents();
// Preload a new Instant NTP.
- ResetNTP(GetInstantURL());
+ if (preload_ntp_)
+ ResetNTP(GetInstantURL());
+ else
+ ntp_.reset();
return ntp_contents.Pass();
}
+// TODO(tonyg): This method only fires when the omnibox bounds change. It also
+// needs to fire when the overlay bounds change (e.g.: open/close info bar).
+void InstantController::SetPopupBounds(const gfx::Rect& bounds) {
+ if (!extended_enabled() && !instant_enabled_)
+ return;
+
+ if (popup_bounds_ == bounds)
+ return;
+
+ popup_bounds_ = bounds;
+ if (popup_bounds_.height() > last_popup_bounds_.height()) {
+ update_bounds_timer_.Stop();
+ SendPopupBoundsToPage();
+ } else if (!update_bounds_timer_.IsRunning()) {
+ update_bounds_timer_.Start(FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kUpdateBoundsDelayMS), this,
+ &InstantController::SendPopupBoundsToPage);
+ }
+}
+
void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) {
if (!extended_enabled() || omnibox_bounds_ == bounds)
return;
omnibox_bounds_ = bounds;
+ if (overlay_)
+ overlay_->sender()->SetOmniboxBounds(omnibox_bounds_);
if (ntp_)
ntp_->sender()->SetOmniboxBounds(omnibox_bounds_);
if (instant_tab_)
instant_tab_->sender()->SetOmniboxBounds(omnibox_bounds_);
}
+void InstantController::HandleAutocompleteResults(
+ const std::vector<AutocompleteProvider*>& providers,
+ const AutocompleteResult& autocomplete_result) {
+ if (!extended_enabled())
+ return;
+
+ if (!UseTabForSuggestions() && !overlay_)
+ return;
+
+ // The omnibox sends suggestions when its possibly imaginary popup closes
+ // as it stops autocomplete. Ignore these.
+ if (omnibox_focus_state_ == OMNIBOX_FOCUS_NONE)
+ return;
+
+ DVLOG(1) << "AutocompleteResults:";
+ std::vector<InstantAutocompleteResult> results;
+ if (UsingLocalPage()) {
+ for (AutocompleteResult::const_iterator match(autocomplete_result.begin());
+ match != autocomplete_result.end(); ++match) {
+ InstantAutocompleteResult result;
+ PopulateInstantAutocompleteResultFromMatch(
+ *match, std::distance(autocomplete_result.begin(), match), &result);
+ results.push_back(result);
+ }
+ } else {
+ for (ACProviders::const_iterator provider = providers.begin();
+ provider != providers.end(); ++provider) {
+ for (ACMatches::const_iterator match = (*provider)->matches().begin();
+ match != (*provider)->matches().end(); ++match) {
+ // When the top match is an inline history URL, the page calls
+ // SetSuggestions(url) which calls FinalizeInstantQuery() in
+ // SearchProvider creating a NAVSUGGEST match for the URL. If we sent
+ // this NAVSUGGEST match back to the page, it would be deduped against
+ // the original history match and replace it. But since the page ignores
+ // SearchProvider suggestions, the match would then disappear. Yuck.
+ // TODO(jered): Remove this when FinalizeInstantQuery() is ripped out.
+ if ((*provider)->type() == AutocompleteProvider::TYPE_SEARCH &&
+ match->type == AutocompleteMatchType::NAVSUGGEST) {
+ continue;
+ }
+ InstantAutocompleteResult result;
+ PopulateInstantAutocompleteResultFromMatch(*match, kNoMatchIndex,
+ &result);
+ results.push_back(result);
+ }
+ }
+ }
+ LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
+ "HandleAutocompleteResults: total_results=%d",
+ static_cast<int>(results.size())));
+
+ if (UseTabForSuggestions())
+ instant_tab_->sender()->SendAutocompleteResults(results);
+ else if (overlay_)
+ overlay_->sender()->SendAutocompleteResults(results);
+
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_INSTANT_SENT_AUTOCOMPLETE_RESULTS,
+ content::Source<InstantController>(this),
+ content::NotificationService::NoDetails());
+}
+
void InstantController::OnDefaultSearchProviderChanged() {
if (ntp_ && extended_enabled()) {
ntp_.reset();
- ResetNTP(GetInstantURL());
+ if (preload_ntp_)
+ ResetNTP(GetInstantURL());
}
+
+ // Do not reload the overlay if it's actually the local overlay.
+ if (overlay_ && !overlay_->IsLocal()) {
+ overlay_.reset();
+ if (extended_enabled() || instant_enabled_) {
+ // Try to create another overlay immediately so that it is ready for the
+ // next user interaction.
+ ResetOverlay(GetInstantURL());
+ }
+ }
+}
+
+bool InstantController::OnUpOrDownKeyPressed(int count) {
+ if (!extended_enabled())
+ return false;
+
+ if (!UseTabForSuggestions() && !overlay_)
+ return false;
+
+ if (UseTabForSuggestions())
+ instant_tab_->sender()->UpOrDownKeyPressed(count);
+ else if (overlay_)
+ overlay_->sender()->UpOrDownKeyPressed(count);
+
+ return true;
+}
+
+void InstantController::OnCancel(const AutocompleteMatch& match,
+ const string16& user_text,
+ const string16& full_text) {
+ if (!extended_enabled())
+ return;
+
+ if (!UseTabForSuggestions() && !overlay_)
+ return;
+
+ // We manually reset the state here since the JS is not expected to do it.
+ // TODO(sreeram): Handle the case where user_text is now a URL
+ last_match_was_search_ = AutocompleteMatch::IsSearchType(match.type) &&
+ !full_text.empty();
+ last_omnibox_text_ = full_text;
+ last_user_text_ = user_text;
+ last_suggestion_ = InstantSuggestion();
+
+ // Say |full_text| is "amazon.com" and |user_text| is "ama". This means the
+ // inline autocompletion is "zon.com"; so the selection should span from
+ // user_text.size() to full_text.size(). The selection bounds are inverted
+ // because the caret is at the end of |user_text|, not |full_text|.
+ if (UseTabForSuggestions()) {
+ instant_tab_->sender()->CancelSelection(user_text, full_text.size(),
+ user_text.size(), last_verbatim_);
+ } else if (overlay_) {
+ overlay_->sender()->CancelSelection(user_text, full_text.size(),
+ user_text.size(), last_verbatim_);
+ }
+}
+
+void InstantController::OmniboxNavigateToURL() {
+ RecordNavigationHistogram(UsingLocalPage(), false, extended_enabled());
+ if (!extended_enabled())
+ return;
+ if (UseTabForSuggestions())
+ instant_tab_->sender()->Submit(string16());
}
void InstantController::ToggleVoiceSearch() {
@@ -213,15 +773,29 @@ void InstantController::InstantPageLoadFailed(content::WebContents* contents) {
DeletePageSoon(ntp_.Pass());
if (!is_local)
ResetNTP(GetLocalInstantURL());
- } else {
- NOTREACHED();
+ } else if (IsContentsFrom(overlay(), contents)) {
+ LOG_INSTANT_DEBUG_EVENT(this, "InstantPageLoadFailed: overlay");
+ bool is_local = overlay_->IsLocal();
+ DeletePageSoon(overlay_.Pass());
+ if (!is_local)
+ ResetOverlay(GetLocalInstantURL());
}
}
+content::WebContents* InstantController::GetOverlayContents() const {
+ return overlay_ ? overlay_->contents() : NULL;
+}
+
content::WebContents* InstantController::GetNTPContents() const {
return ntp_ ? ntp_->contents() : NULL;
}
+bool InstantController::IsOverlayingSearchResults() const {
+ return model_.mode().is_search_suggestions() && IsFullHeight(model_) &&
+ (last_match_was_search_ ||
+ last_suggestion_.behavior == INSTANT_COMPLETE_NEVER);
+}
+
bool InstantController::SubmitQuery(const string16& search_terms) {
if (extended_enabled() && instant_tab_ && instant_tab_->supports_instant() &&
search_mode_.is_origin_search()) {
@@ -235,6 +809,156 @@ bool InstantController::SubmitQuery(const string16& search_terms) {
return false;
}
+bool InstantController::CommitIfPossible(InstantCommitType type) {
+ if (!extended_enabled() && !instant_enabled_)
+ return false;
+
+ LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
+ "CommitIfPossible: type=%d last_omnibox_text_='%s' "
+ "last_match_was_search_=%d use_tab_for_suggestions=%d", type,
+ UTF16ToUTF8(last_omnibox_text_).c_str(), last_match_was_search_,
+ UseTabForSuggestions()));
+
+ // If we are on an already committed search results page, send a submit event
+ // to the page, but otherwise, nothing else to do.
+ if (UseTabForSuggestions()) {
+ if (type == INSTANT_COMMIT_PRESSED_ENTER &&
+ !instant_tab_->IsLocal() &&
+ (last_match_was_search_ ||
+ last_suggestion_.behavior == INSTANT_COMPLETE_NEVER)) {
+ last_suggestion_.text.clear();
+ instant_tab_->sender()->Submit(last_omnibox_text_);
+ instant_tab_->contents()->GetView()->Focus();
+ EnsureSearchTermsAreSet(instant_tab_->contents(), last_omnibox_text_);
+ return true;
+ }
+ return false;
+ }
+
+ if (!overlay_)
+ return false;
+
+ // If the overlay is not showing at all, don't commit it.
+ if (!model_.mode().is_search_suggestions())
+ return false;
+
+ // If the overlay is showing at full height (with results), commit it.
+ // If it's showing at parial height, commit if it's navigating.
+ if (!IsOverlayingSearchResults() && type != INSTANT_COMMIT_NAVIGATED)
+ return false;
+
+ // There may re-entrance here, from the call to browser_->CommitInstant below,
+ // which can cause a TabDeactivated notification which gets back here.
+ // In this case, overlay_->ReleaseContents() was called already.
+ if (!GetOverlayContents())
+ return false;
+
+ // Never commit the local overlay.
+ if (overlay_->IsLocal())
+ return false;
+
+ if (type == INSTANT_COMMIT_FOCUS_LOST) {
+ // Extended mode doesn't need or use the Cancel message.
+ if (!extended_enabled())
+ overlay_->sender()->Cancel(last_omnibox_text_);
+ } else if (type != INSTANT_COMMIT_NAVIGATED) {
+ overlay_->sender()->Submit(last_omnibox_text_);
+ }
+
+ // We expect the WebContents to be in a valid state (i.e., has a last
+ // committed entry, no transient entry, and no existing pending entry).
+ scoped_ptr<content::WebContents> overlay = overlay_->ReleaseContents();
+ CHECK(overlay->GetController().CanPruneAllButVisible());
+
+ // If the overlay page has navigated since the last Update(), we need to add
+ // the navigation to history ourselves. Else, the page will navigate after
+ // commit, and it will be added to history in the usual manner.
+ const history::HistoryAddPageArgs& last_navigation =
+ overlay_->last_navigation();
+ if (!last_navigation.url.is_empty()) {
+ content::NavigationEntry* entry = overlay->GetController().GetActiveEntry();
+
+ // The last navigation should be the same as the active entry if the overlay
+ // is in search mode. During navigation, the active entry could have
+ // changed since DidCommitProvisionalLoadForFrame is called after the entry
+ // is changed.
+ // TODO(shishir): Should we commit the last navigation for
+ // INSTANT_COMMIT_NAVIGATED.
+ DCHECK(type == INSTANT_COMMIT_NAVIGATED ||
+ last_navigation.url == entry->GetURL());
+
+ // Add the page to history.
+ HistoryTabHelper* history_tab_helper =
+ HistoryTabHelper::FromWebContents(overlay.get());
+ history_tab_helper->UpdateHistoryForNavigation(last_navigation);
+
+ // Update the page title.
+ history_tab_helper->UpdateHistoryPageTitle(*entry);
+ }
+
+ // Add a fake history entry with a non-Instant search URL, so that search
+ // terms extraction (for autocomplete history matches) works.
+ HistoryService* history = HistoryServiceFactory::GetForProfile(
+ Profile::FromBrowserContext(overlay->GetBrowserContext()),
+ Profile::EXPLICIT_ACCESS);
+ if (history) {
+ history->AddPage(url_for_history_, base::Time::Now(), NULL, 0, GURL(),
+ history::RedirectList(), last_transition_type_,
+ history::SOURCE_BROWSED, false);
+ }
+
+ if (type == INSTANT_COMMIT_PRESSED_ALT_ENTER) {
+ overlay->GetController().PruneAllButVisible();
+ } else {
+ content::WebContents* active_tab = browser_->GetActiveWebContents();
+ AddSessionStorageHistogram(extended_enabled(), active_tab, overlay.get());
+ overlay->GetController().CopyStateFromAndPrune(
+ &active_tab->GetController());
+ }
+
+ if (extended_enabled()) {
+ // Adjust the search terms shown in the omnibox for this query. Hitting
+ // ENTER searches for what the user typed, so use last_omnibox_text_.
+ // Clicking on the overlay commits what is currently showing, so add in the
+ // gray text in that case.
+ if (type == INSTANT_COMMIT_FOCUS_LOST &&
+ last_suggestion_.behavior == INSTANT_COMPLETE_NEVER) {
+ // Update |last_omnibox_text_| so that the controller commits the proper
+ // query if the user focuses the omnibox and presses Enter.
+ last_omnibox_text_ += last_suggestion_.text;
+ }
+
+ EnsureSearchTermsAreSet(overlay.get(), last_omnibox_text_);
+ }
+
+ // Save notification source before we release the overlay.
+ content::Source<content::WebContents> notification_source(overlay.get());
+
+ browser_->CommitInstant(overlay.Pass(),
+ type == INSTANT_COMMIT_PRESSED_ALT_ENTER);
+
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_INSTANT_COMMITTED,
+ notification_source,
+ content::NotificationService::NoDetails());
+
+ // Hide explicitly. See comments in HideOverlay() for why.
+ model_.SetOverlayState(SearchMode(), 0, INSTANT_SIZE_PERCENT);
+
+ // Delay deletion as we could've gotten here from an InstantOverlay method.
+ DeletePageSoon(overlay_.Pass());
+
+ // Try to create another overlay immediately so that it is ready for the next
+ // user interaction.
+ ResetOverlay(GetInstantURL());
+
+ if (instant_tab_)
+ use_tab_for_suggestions_ = true;
+
+ LOG_INSTANT_DEBUG_EVENT(this, "Committed");
+ return true;
+}
+
void InstantController::OmniboxFocusChanged(
OmniboxFocusState state,
OmniboxFocusChangeReason reason,
@@ -243,8 +967,9 @@ void InstantController::OmniboxFocusChanged(
"OmniboxFocusChanged: %d to %d for reason %d", omnibox_focus_state_,
state, reason));
+ OmniboxFocusState old_focus_state = omnibox_focus_state_;
omnibox_focus_state_ = state;
- if (!extended_enabled() || !instant_tab_)
+ if (!extended_enabled() && !instant_enabled_)
return;
content::NotificationService::current()->Notify(
@@ -252,12 +977,32 @@ void InstantController::OmniboxFocusChanged(
content::Source<InstantController>(this),
content::NotificationService::NoDetails());
- instant_tab_->sender()->FocusChanged(omnibox_focus_state_, reason);
- // Don't send oninputstart/oninputend updates in response to focus changes
- // if there's a navigation in progress. This prevents Chrome from sending
- // a spurious oninputend when the user accepts a match in the omnibox.
- if (instant_tab_->contents()->GetController().GetPendingEntry() == NULL)
- instant_tab_->sender()->SetInputInProgress(IsInputInProgress());
+ if (extended_enabled()) {
+ if (overlay_)
+ overlay_->sender()->FocusChanged(omnibox_focus_state_, reason);
+
+ if (instant_tab_) {
+ instant_tab_->sender()->FocusChanged(omnibox_focus_state_, reason);
+ // Don't send oninputstart/oninputend updates in response to focus changes
+ // if there's a navigation in progress. This prevents Chrome from sending
+ // a spurious oninputend when the user accepts a match in the omnibox.
+ if (instant_tab_->contents()->GetController().GetPendingEntry() == NULL)
+ instant_tab_->sender()->SetInputInProgress(IsInputInProgress());
+ }
+ }
+
+ if (state == OMNIBOX_FOCUS_VISIBLE && old_focus_state == OMNIBOX_FOCUS_NONE) {
+ // If the user explicitly focused the omnibox, then create the overlay if
+ // it doesn't exist. If we're using a fallback overlay, try loading the
+ // remote overlay again.
+ if (!overlay_ || (overlay_->IsLocal() && !use_local_page_only_))
+ ResetOverlay(GetInstantURL());
+ } else if (state == OMNIBOX_FOCUS_NONE &&
+ old_focus_state != OMNIBOX_FOCUS_NONE) {
+ // If the focus went from the omnibox to outside the omnibox, commit or
+ // discard the overlay.
+ OmniboxLostFocus(view_gaining_focus);
+ }
}
void InstantController::SearchModeChanged(const SearchMode& old_mode,
@@ -270,6 +1015,9 @@ void InstantController::SearchModeChanged(const SearchMode& old_mode,
old_mode.mode, new_mode.origin, new_mode.mode));
search_mode_ = new_mode;
+ if (!new_mode.is_search_suggestions())
+ HideOverlay();
+
ResetInstantTab();
if (instant_tab_ && old_mode.is_ntp() != new_mode.is_ntp())
@@ -277,14 +1025,57 @@ void InstantController::SearchModeChanged(const SearchMode& old_mode,
}
void InstantController::ActiveTabChanged() {
- if (!extended_enabled())
+ if (!extended_enabled() && !instant_enabled_)
return;
LOG_INSTANT_DEBUG_EVENT(this, "ActiveTabChanged");
- ResetInstantTab();
+
+ // When switching tabs, always hide the overlay.
+ HideOverlay();
+
+ if (extended_enabled())
+ ResetInstantTab();
}
void InstantController::TabDeactivated(content::WebContents* contents) {
+ LOG_INSTANT_DEBUG_EVENT(this, "TabDeactivated");
+ if (extended_enabled() && !contents->IsBeingDestroyed())
+ CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST);
+
+ if (GetOverlayContents())
+ HideOverlay();
+}
+
+void InstantController::SetInstantEnabled(bool instant_enabled,
+ bool use_local_page_only) {
+ LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
+ "SetInstantEnabled: instant_enabled=%d, use_local_page_only=%d",
+ instant_enabled, use_local_page_only));
+
+ // Non extended mode does not care about |use_local_page_only|.
+ if (instant_enabled == instant_enabled_ &&
+ (!extended_enabled() ||
+ use_local_page_only == use_local_page_only_)) {
+ return;
+ }
+
+ instant_enabled_ = instant_enabled;
+ use_local_page_only_ = use_local_page_only;
+ preload_ntp_ = !use_local_page_only_;
+
+ // Preload the overlay.
+ HideInternal();
+ overlay_.reset();
+ if (extended_enabled() || instant_enabled_)
+ ResetOverlay(GetInstantURL());
+
+ // Preload the Instant NTP.
+ ntp_.reset();
+ if (extended_enabled() && preload_ntp_)
+ ResetNTP(GetInstantURL());
+
+ if (instant_tab_)
+ instant_tab_->sender()->SetDisplayInstantResults(instant_enabled_);
}
void InstantController::ThemeInfoChanged(
@@ -292,12 +1083,52 @@ void InstantController::ThemeInfoChanged(
if (!extended_enabled())
return;
+ if (overlay_)
+ overlay_->sender()->SendThemeBackgroundInfo(theme_info);
if (ntp_)
ntp_->sender()->SendThemeBackgroundInfo(theme_info);
if (instant_tab_)
instant_tab_->sender()->SendThemeBackgroundInfo(theme_info);
}
+void InstantController::SwappedOverlayContents() {
+ model_.SetOverlayContents(GetOverlayContents());
+}
+
+void InstantController::FocusedOverlayContents() {
+#if defined(USE_AURA)
+ // On aura the omnibox only receives a focus lost if we initiate the focus
+ // change. This does that.
+ if (!model_.mode().is_default())
+ browser_->InstantOverlayFocused();
+#endif
+}
+
+void InstantController::ReloadOverlayIfStale() {
+ // The local overlay is never stale.
+ if (overlay_ && (overlay_->IsLocal() || !overlay_->is_stale()))
+ return;
+
+ // If the overlay is showing or the omnibox has focus, don't refresh the
+ // overlay. It will get refreshed the next time the overlay is hidden or the
+ // omnibox loses focus.
+ if (omnibox_focus_state_ == OMNIBOX_FOCUS_NONE && model_.mode().is_default())
+ ResetOverlay(GetInstantURL());
+}
+
+void InstantController::OverlayLoadCompletedMainFrame() {
+ if (!overlay_ || overlay_->supports_instant())
+ return;
+ InstantService* instant_service = GetInstantService();
+ content::WebContents* contents = overlay_->contents();
+ DCHECK(contents);
+ if (instant_service->IsInstantProcess(
+ contents->GetRenderProcessHost()->GetID())) {
+ return;
+ }
+ InstantSupportDetermined(contents, false);
+}
+
void InstantController::LogDebugEvent(const std::string& info) const {
DVLOG(1) << info;
@@ -314,8 +1145,12 @@ void InstantController::ClearDebugEvents() {
void InstantController::MostVisitedItemsChanged(
const std::vector<InstantMostVisitedItem>& items) {
+ if (overlay_)
+ overlay_->sender()->SendMostVisitedItems(items);
+
if (ntp_)
ntp_->sender()->SendMostVisitedItems(items);
+
if (instant_tab_)
instant_tab_->sender()->SendMostVisitedItems(items);
@@ -355,6 +1190,10 @@ Profile* InstantController::profile() const {
return browser_->profile();
}
+InstantOverlay* InstantController::overlay() const {
+ return overlay_.get();
+}
+
InstantTab* InstantController::instant_tab() const {
return instant_tab_.get();
}
@@ -368,7 +1207,7 @@ void InstantController::OnNetworkChanged(
// Not interested in events conveying change to offline
if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
return;
- if (!extended_enabled_)
+ if (!extended_enabled_ || use_local_page_only_)
return;
if (!ntp_ || ntp_->IsLocal())
ResetNTP(GetInstantURL());
@@ -389,7 +1228,14 @@ void InstantController::InstantPageRenderViewCreated(
}
// Ensure the searchbox API has the correct initial state.
- if (IsContentsFrom(ntp(), contents)) {
+ if (IsContentsFrom(overlay(), contents)) {
+ overlay_->sender()->SetDisplayInstantResults(instant_enabled_);
+ overlay_->sender()->FocusChanged(omnibox_focus_state_,
+ omnibox_focus_change_reason_);
+ overlay_->sender()->SetOmniboxBounds(omnibox_bounds_);
+ overlay_->InitializeFonts();
+ } else if (IsContentsFrom(ntp(), contents)) {
+ ntp_->sender()->SetDisplayInstantResults(instant_enabled_);
ntp_->sender()->SetOmniboxBounds(omnibox_bounds_);
ntp_->InitializeFonts();
ntp_->InitializePromos();
@@ -440,14 +1286,29 @@ void InstantController::InstantSupportDetermined(
content::Source<InstantController>(this),
content::NotificationService::NoDetails());
- } else {
- NOTREACHED();
+ } else if (IsContentsFrom(overlay(), contents)) {
+ if (!supports_instant) {
+ HideInternal();
+ bool is_local = overlay_->IsLocal();
+ DeletePageSoon(overlay_.Pass());
+ // Preload a local overlay in place of the broken online one.
+ if (!is_local && extended_enabled())
+ ResetOverlay(GetLocalInstantURL());
+ }
+
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_INSTANT_OVERLAY_SUPPORT_DETERMINED,
+ content::Source<InstantController>(this),
+ content::NotificationService::NoDetails());
}
}
void InstantController::InstantPageRenderViewGone(
const content::WebContents* contents) {
- if (IsContentsFrom(ntp(), contents)) {
+ if (IsContentsFrom(overlay(), contents)) {
+ HideInternal();
+ DeletePageSoon(overlay_.Pass());
+ } else if (IsContentsFrom(ntp(), contents)) {
DeletePageSoon(ntp_.Pass());
} else {
NOTREACHED();
@@ -457,7 +1318,31 @@ void InstantController::InstantPageRenderViewGone(
void InstantController::InstantPageAboutToNavigateMainFrame(
const content::WebContents* contents,
const GURL& url) {
- if (IsContentsFrom(instant_tab(), contents)) {
+ if (IsContentsFrom(overlay(), contents)) {
+ // If the page does not yet support Instant, we allow redirects and other
+ // navigations to go through since the Instant URL can redirect - e.g. to
+ // country specific pages.
+ if (!overlay_->supports_instant())
+ return;
+
+ GURL instant_url(overlay_->instant_url());
+
+ // If we are navigating to the Instant URL, do nothing.
+ if (url == instant_url)
+ return;
+
+ // Commit the navigation if either:
+ // - The page is in NTP mode (so it could only navigate on a user click) or
+ // - The page is not in NTP mode and we are navigating to a URL with a
+ // different host or path than the Instant URL. This enables the instant
+ // page when it is showing search results to change the query parameters
+ // and fragments of the URL without it navigating.
+ if (model_.mode().is_ntp() ||
+ (url.host() != instant_url.host() ||
+ url.path() != instant_url.path())) {
+ CommitIfPossible(INSTANT_COMMIT_NAVIGATED);
+ }
+ } else if (IsContentsFrom(instant_tab(), contents)) {
// The Instant tab navigated. Send it the data it needs to display
// properly.
UpdateInfoForInstantTab();
@@ -466,6 +1351,118 @@ void InstantController::InstantPageAboutToNavigateMainFrame(
}
}
+void InstantController::SetSuggestions(
+ const content::WebContents* contents,
+ const std::vector<InstantSuggestion>& suggestions) {
+ LOG_INSTANT_DEBUG_EVENT(this, "SetSuggestions");
+
+ // Ignore if the message is from an unexpected source.
+ if (IsContentsFrom(ntp(), contents))
+ return;
+ if (UseTabForSuggestions() && !IsContentsFrom(instant_tab(), contents))
+ return;
+ if (IsContentsFrom(overlay(), contents) &&
+ !allow_overlay_to_show_search_suggestions_)
+ return;
+
+ InstantSuggestion suggestion;
+ if (!suggestions.empty())
+ suggestion = suggestions[0];
+
+ // TODO(samarth): allow InstantTabs to call SetSuggestions() from the NTP once
+ // that is better supported.
+ bool can_use_instant_tab = UseTabForSuggestions() &&
+ search_mode_.is_search();
+ bool can_use_overlay = search_mode_.is_search_suggestions() &&
+ !last_omnibox_text_.empty();
+ if (!can_use_instant_tab && !can_use_overlay)
+ return;
+
+ if (suggestion.behavior == INSTANT_COMPLETE_REPLACE) {
+ if (omnibox_focus_state_ == OMNIBOX_FOCUS_NONE) {
+ // TODO(samarth,skanuj): setValue() needs to be handled differently when
+ // the omnibox doesn't have focus. Instead of setting temporary text, we
+ // should be setting search terms on the appropriate NavigationEntry.
+ // (Among other things, this ensures that URL-shaped values will get the
+ // additional security token.)
+ //
+ // Note that this also breaks clicking on a suggestion corresponding to
+ // gray-text completion: we can't distinguish between the user
+ // clicking on white space (where we don't accept the gray text) and the
+ // user clicking on the suggestion (when we do accept the gray text).
+ // This needs to be fixed before we can turn on Instant again.
+ return;
+ }
+
+ // We don't get an Update() when changing the omnibox due to a REPLACE
+ // suggestion (so that we don't inadvertently cause the overlay to change
+ // what it's showing, as the user arrows up/down through the page-provided
+ // suggestions). So, update these state variables here.
+ last_omnibox_text_ = suggestion.text;
+ last_user_text_.clear();
+ last_suggestion_ = InstantSuggestion();
+ last_match_was_search_ = suggestion.type == INSTANT_SUGGESTION_SEARCH;
+ LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
+ "ReplaceSuggestion text='%s' type=%d",
+ UTF16ToUTF8(suggestion.text).c_str(), suggestion.type));
+ browser_->SetInstantSuggestion(suggestion);
+ } else {
+ if (FixSuggestion(&suggestion)) {
+ last_suggestion_ = suggestion;
+ if (suggestion.type == INSTANT_SUGGESTION_SEARCH &&
+ suggestion.behavior == INSTANT_COMPLETE_NEVER)
+ last_omnibox_text_ = last_user_text_;
+ LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
+ "SetInstantSuggestion: text='%s' behavior=%d",
+ UTF16ToUTF8(suggestion.text).c_str(),
+ suggestion.behavior));
+ browser_->SetInstantSuggestion(suggestion);
+ content::NotificationService::current()->Notify(
+ chrome::NOTIFICATION_INSTANT_SET_SUGGESTION,
+ content::Source<InstantController>(this),
+ content::NotificationService::NoDetails());
+ } else {
+ last_suggestion_ = InstantSuggestion();
+ }
+ }
+
+ // Extended mode pages will call ShowOverlay() when they are ready.
+ if (!extended_enabled())
+ ShowOverlay(100, INSTANT_SIZE_PERCENT);
+}
+
+void InstantController::ShowInstantOverlay(const content::WebContents* contents,
+ int height,
+ InstantSizeUnits units) {
+ if (extended_enabled() && IsContentsFrom(overlay(), contents))
+ ShowOverlay(height, units);
+}
+
+void InstantController::LogDropdownShown() {
+ // If suggestions are being shown for the first time since the user started
+ // typing, record a histogram value.
+ if (!first_interaction_time_.is_null() && !first_interaction_time_recorded_) {
+ base::TimeDelta delta = base::Time::Now() - first_interaction_time_;
+ first_interaction_time_recorded_ = true;
+ if (search_mode_.is_origin_ntp()) {
+ UMA_HISTOGRAM_TIMES("Instant.TimeToFirstShowFromNTP", delta);
+ LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
+ "LogShowInstantOverlay: TimeToFirstShowFromNTP=%d",
+ static_cast<int>(delta.InMilliseconds())));
+ } else if (search_mode_.is_origin_search()) {
+ UMA_HISTOGRAM_TIMES("Instant.TimeToFirstShowFromSERP", delta);
+ LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
+ "LogShowInstantOverlay: TimeToFirstShowFromSERP=%d",
+ static_cast<int>(delta.InMilliseconds())));
+ } else {
+ UMA_HISTOGRAM_TIMES("Instant.TimeToFirstShowFromWeb", delta);
+ LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
+ "LogShowInstantOverlay: TimeToFirstShowFromWeb=%d",
+ static_cast<int>(delta.InMilliseconds())));
+ }
+ }
+}
+
void InstantController::FocusOmnibox(const content::WebContents* contents,
OmniboxFocusState state) {
if (!extended_enabled())
@@ -508,6 +1505,9 @@ void InstantController::NavigateToURL(const content::WebContents* contents,
// has switched tabs).
if (!extended_enabled())
return;
+ if (overlay_) {
+ HideOverlay();
+ }
if (transition == content::PAGE_TRANSITION_AUTO_BOOKMARK) {
content::RecordAction(
@@ -520,12 +1520,43 @@ void InstantController::NavigateToURL(const content::WebContents* contents,
browser_->OpenURL(url, transition, disposition);
}
+void InstantController::OmniboxLostFocus(gfx::NativeView view_gaining_focus) {
+ // If the overlay is showing custom NTP content, don't hide it, commit it
+ // (no matter where the user clicked) or try to recreate it.
+ if (model_.mode().is_ntp())
+ return;
+
+ if (model_.mode().is_default()) {
+ // If the overlay is not showing at all, recreate it if it's stale.
+ ReloadOverlayIfStale();
+ return;
+ }
+
+ // The overlay is showing search suggestions. If GetOverlayContents() is NULL,
+ // we are in the commit path. Don't do anything.
+ if (!GetOverlayContents())
+ return;
+
+#if defined(OS_MACOSX)
+ // TODO(sreeram): See if Mac really needs this special treatment.
+ if (!overlay_->is_pointer_down_from_activate())
+ HideOverlay();
+#else
+ if (IsFullHeight(model_))
+ CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST);
+ else if (!IsViewInContents(GetViewGainingFocus(view_gaining_focus),
+ overlay_->contents()))
+ HideOverlay();
+#endif
+}
+
std::string InstantController::GetLocalInstantURL() const {
return chrome::GetLocalInstantURL(profile()).spec();
}
std::string InstantController::GetInstantURL() const {
- if (extended_enabled() && net::NetworkChangeNotifier::IsOffline())
+ if (extended_enabled() &&
+ (use_local_page_only_ || net::NetworkChangeNotifier::IsOffline()))
return GetLocalInstantURL();
const GURL instant_url = chrome::GetInstantURL(profile(),
@@ -570,8 +1601,7 @@ void InstantController::ResetNTP(const std::string& instant_url) {
}
void InstantController::ReloadStaleNTP() {
- if (extended_enabled())
- ResetNTP(GetInstantURL());
+ ResetNTP(GetInstantURL());
}
bool InstantController::ShouldSwitchToLocalNTP() const {
@@ -596,6 +1626,33 @@ bool InstantController::ShouldSwitchToLocalNTP() const {
return !(InStartup() && chrome::ShouldPreferRemoteNTPOnStartup());
}
+void InstantController::ResetOverlay(const std::string& instant_url) {
+ HideInternal();
+ overlay_.reset();
+}
+
+InstantController::InstantFallbackReason
+InstantController::ShouldSwitchToLocalOverlay() const {
+ if (!extended_enabled())
+ return INSTANT_FALLBACK_NONE;
+
+ if (!overlay())
+ return DetermineFallbackReason(NULL, std::string());
+
+ // Assume users with Javascript disabled do not want the online experience.
+ if (!IsJavascriptEnabled())
+ return INSTANT_FALLBACK_JAVASCRIPT_DISABLED;
+
+ if (overlay()->IsLocal())
+ return INSTANT_FALLBACK_NONE;
+
+ bool page_is_current = PageIsCurrent(overlay());
+ if (!page_is_current)
+ return DetermineFallbackReason(overlay(), GetInstantURL());
+
+ return INSTANT_FALLBACK_NONE;
+}
+
void InstantController::ResetInstantTab() {
if (!search_mode_.is_origin_default()) {
content::WebContents* active_tab = browser_->GetActiveWebContents();
@@ -604,7 +1661,11 @@ void InstantController::ResetInstantTab() {
new InstantTab(this, browser_->profile()->IsOffTheRecord()));
instant_tab_->Init(active_tab);
UpdateInfoForInstantTab();
+ use_tab_for_suggestions_ = true;
}
+
+ // Hide the |overlay_| since we are now using |instant_tab_| instead.
+ HideOverlay();
} else {
instant_tab_.reset();
}
@@ -612,6 +1673,7 @@ void InstantController::ResetInstantTab() {
void InstantController::UpdateInfoForInstantTab() {
if (instant_tab_) {
+ instant_tab_->sender()->SetDisplayInstantResults(instant_enabled_);
instant_tab_->sender()->SetOmniboxBounds(omnibox_bounds_);
// Update theme details.
@@ -634,8 +1696,168 @@ bool InstantController::IsInputInProgress() const {
omnibox_focus_state_ == OMNIBOX_FOCUS_VISIBLE;
}
+void InstantController::HideOverlay() {
+ HideInternal();
+ ReloadOverlayIfStale();
+}
+
+void InstantController::HideInternal() {
+ LOG_INSTANT_DEBUG_EVENT(this, "Hide");
+
+ // If GetOverlayContents() returns NULL, either we're already in the desired
+ // MODE_DEFAULT state, or we're in the commit path. For the latter, don't
+ // change the state just yet; else we may hide the overlay unnecessarily.
+ // Instead, the state will be set correctly after the commit is done.
+ if (GetOverlayContents()) {
+ model_.SetOverlayState(SearchMode(), 0, INSTANT_SIZE_PERCENT);
+ allow_overlay_to_show_search_suggestions_ = false;
+
+ // Send a message asking the overlay to clear out old results.
+ overlay_->Update(string16(), 0, 0, true);
+ }
+
+ // Clear the first interaction timestamp for later use.
+ first_interaction_time_ = base::Time();
+ first_interaction_time_recorded_ = false;
+
+ if (instant_tab_)
+ use_tab_for_suggestions_ = true;
+}
+
+void InstantController::ShowOverlay(int height, InstantSizeUnits units) {
+ // Nothing to see here.
+ if (!overlay_)
+ return;
+
+ // If we are on a committed search results page, the |overlay_| is not in use.
+ if (UseTabForSuggestions())
+ return;
+
+ LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
+ "Show: height=%d units=%d", height, units));
+
+ // Must have updated omnibox after the last HideOverlay() to show suggestions.
+ if (!allow_overlay_to_show_search_suggestions_)
+ return;
+
+ // The page is trying to hide itself. Hide explicitly (i.e., don't use
+ // HideOverlay()) so that it can change its mind.
+ if (height == 0) {
+ model_.SetOverlayState(SearchMode(), 0, INSTANT_SIZE_PERCENT);
+ if (instant_tab_)
+ use_tab_for_suggestions_ = true;
+ return;
+ }
+
+ // Show at 100% height except in the following cases:
+ // - The local overlay (omnibox popup) is being loaded.
+ // - Instant is disabled. The page needs to be able to show only a dropdown.
+ // - The page is over a website other than search or an NTP, and is not
+ // already showing at 100% height.
+ if (overlay_->IsLocal() || !instant_enabled_ ||
+ (search_mode_.is_origin_default() && !IsFullHeight(model_)))
+ model_.SetOverlayState(search_mode_, height, units);
+ else
+ model_.SetOverlayState(search_mode_, 100, INSTANT_SIZE_PERCENT);
+
+ // If the overlay is being shown at full height and the omnibox is not
+ // focused, commit right away.
+ if (IsFullHeight(model_) && omnibox_focus_state_ == OMNIBOX_FOCUS_NONE)
+ CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST);
+}
+
+void InstantController::SendPopupBoundsToPage() {
+ if (last_popup_bounds_ == popup_bounds_ || !overlay_ ||
+ overlay_->is_pointer_down_from_activate())
+ return;
+
+ last_popup_bounds_ = popup_bounds_;
+ gfx::Rect overlay_bounds = browser_->GetInstantBounds();
+ gfx::Rect intersection = gfx::IntersectRects(popup_bounds_, overlay_bounds);
+
+ // Translate into window coordinates.
+ if (!intersection.IsEmpty()) {
+ intersection.Offset(-overlay_bounds.origin().x(),
+ -overlay_bounds.origin().y());
+ }
+
+ // In the current Chrome UI, these must always be true so they sanity check
+ // the above operations. In a future UI, these may be removed or adjusted.
+ // There is no point in sanity-checking |intersection.y()| because the omnibox
+ // can be placed anywhere vertically relative to the overlay (for example, in
+ // Mac fullscreen mode, the omnibox is fully enclosed by the overlay bounds).
+ DCHECK_LE(0, intersection.x());
+ DCHECK_LE(0, intersection.width());
+ DCHECK_LE(0, intersection.height());
+
+ overlay_->sender()->SetPopupBounds(intersection);
+}
+
+
+
+bool InstantController::FixSuggestion(InstantSuggestion* suggestion) const {
+ // We only accept suggestions if the user has typed text. If the user is
+ // arrowing up/down (|last_user_text_| is empty), we reject suggestions.
+ if (last_user_text_.empty())
+ return false;
+
+ // If the page is trying to set inline autocompletion in verbatim mode,
+ // instead try suggesting the exact omnibox text. This makes the omnibox
+ // interpret user text as an URL if possible while preventing unwanted
+ // autocompletion during backspacing.
+ if (suggestion->behavior == INSTANT_COMPLETE_NOW && last_verbatim_)
+ suggestion->text = last_omnibox_text_;
+
+ // Suggestion text should be a full URL for URL suggestions, or the
+ // completion of a query for query suggestions.
+ if (suggestion->type == INSTANT_SUGGESTION_URL) {
+ // If the suggestion is not a valid URL, perhaps it's something like
+ // "foo.com". Try prefixing "http://". If it still isn't valid, drop it.
+ if (!GURL(suggestion->text).is_valid()) {
+ suggestion->text.insert(0, ASCIIToUTF16("http://"));
+ if (!GURL(suggestion->text).is_valid())
+ return false;
+ }
+
+ // URL suggestions are only accepted if the query for which the suggestion
+ // was generated is the same as |last_user_text_|.
+ //
+ // Any other URL suggestions--in particular suggestions for old user_text
+ // lagging behind a slow IPC--are ignored. See crbug.com/181589.
+ //
+ // TODO(samarth): Accept stale suggestions if they would be accepted by
+ // SearchProvider as an inlinable suggestion. http://crbug.com/191656.
+ return suggestion->query == last_user_text_;
+ }
+
+ // We use |last_user_text_| because |last_omnibox_text| may contain text from
+ // a previous URL suggestion at this point.
+ if (suggestion->type == INSTANT_SUGGESTION_SEARCH) {
+ if (StartsWith(suggestion->text, last_user_text_, true)) {
+ // The user typed an exact prefix of the suggestion.
+ suggestion->text.erase(0, last_user_text_.size());
+ return true;
+ } else if (NormalizeAndStripPrefix(&suggestion->text, last_user_text_)) {
+ // Unicode normalize and case-fold the user text and suggestion. If the
+ // user text is a prefix, suggest the normalized, case-folded completion
+ // for instance, if the user types 'i' and the suggestion is 'INSTANT',
+ // suggest 'nstant'. Otherwise, the user text really isn't a prefix, so
+ // suggest nothing.
+ // TODO(samarth|jered): revisit this logic. http://crbug.com/196572.
+ return true;
+ }
+ }
+
+ return false;
+}
+
bool InstantController::UsingLocalPage() const {
- return instant_tab_ && instant_tab_->IsLocal();
+ return (UseTabForSuggestions() && instant_tab_->IsLocal()) ||
+ (!UseTabForSuggestions() && overlay_ && overlay_->IsLocal());
+}
+
+bool InstantController::UseTabForSuggestions() const {
+ return instant_tab_ && use_tab_for_suggestions_;
}
void InstantController::RedirectToLocalNTP(content::WebContents* contents) {
@@ -649,6 +1871,31 @@ void InstantController::RedirectToLocalNTP(content::WebContents* contents) {
// entry.
}
+void InstantController::PopulateInstantAutocompleteResultFromMatch(
+ const AutocompleteMatch& match, size_t autocomplete_match_index,
+ InstantAutocompleteResult* result) {
+ DCHECK(result);
+ result->provider = UTF8ToUTF16(match.provider->GetName());
+ result->type = match.type;
+ result->description = match.description;
+ result->destination_url = UTF8ToUTF16(match.destination_url.spec());
+
+ // Setting the search_query field tells the Instant page to treat the
+ // suggestion as a query.
+ if (AutocompleteMatch::IsSearchType(match.type))
+ result->search_query = match.contents;
+
+ result->transition = match.transition;
+ result->relevance = match.relevance;
+ result->autocomplete_match_index = autocomplete_match_index;
+
+ DVLOG(1) << " " << result->relevance << " "
+ << UTF8ToUTF16(AutocompleteMatchType::ToString(result->type)) << " "
+ << result->provider << " " << result->destination_url << " '"
+ << result->description << "' '" << result->search_query << "' "
+ << result->transition << " " << result->autocomplete_match_index;
+}
+
bool InstantController::IsJavascriptEnabled() const {
GURL instant_url(GetInstantURL());
GURL origin(instant_url.GetOrigin());
diff --git a/chrome/browser/ui/search/instant_controller.h b/chrome/browser/ui/search/instant_controller.h
index 8dd8bb9..4806d73 100644
--- a/chrome/browser/ui/search/instant_controller.h
+++ b/chrome/browser/ui/search/instant_controller.h
@@ -14,22 +14,34 @@
#include "base/gtest_prod_util.h"
#include "base/memory/scoped_ptr.h"
#include "base/strings/string16.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
#include "chrome/browser/search/instant_service_observer.h"
+#include "chrome/browser/ui/omnibox/omnibox_edit_model.h"
+#include "chrome/browser/ui/search/instant_commit_type.h"
+#include "chrome/browser/ui/search/instant_overlay_model.h"
#include "chrome/browser/ui/search/instant_page.h"
#include "chrome/common/instant_types.h"
#include "chrome/common/omnibox_focus_state.h"
#include "chrome/common/search_types.h"
+#include "content/public/common/page_transition_types.h"
#include "googleurl/src/gurl.h"
#include "net/base/network_change_notifier.h"
#include "ui/base/window_open_disposition.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/rect.h"
+struct AutocompleteMatch;
+struct InstantAutocompleteResult;
+
+class AutocompleteProvider;
+class AutocompleteResult;
class BrowserInstantController;
class InstantNTP;
+class InstantOverlay;
class InstantService;
class InstantTab;
-class Profile;
+class TemplateURL;
namespace content {
class WebContents;
@@ -42,46 +54,121 @@ class WebContents;
// InstantController drives Chrome Instant, i.e., the browser implementation of
// the Embedded Search API (see http://dev.chromium.org/embeddedsearch).
//
-// In extended mode, InstantController maintains and coordinates two
+// In extended mode, InstantController maintains and coordinates three
// instances of InstantPage:
-// (1) An InstantNTP instance which is a preloaded search page that will be
+// (1) An InstantOverlay instance that is used to show search suggestions and
+// results in an overlay over a non-search page.
+// (2) An InstantNTP instance which is a preloaded search page that will be
// swapped-in the next time the user navigates to the New Tab Page. It is
// never shown to the user in an uncommitted state.
-// (2) An InstantTab instance which points to the currently active tab, if it
+// (3) An InstantTab instance which points to the currently active tab, if it
// supports the Embedded Search API.
//
-// Both are backed by a WebContents. InstantNTP owns its WebContents and
-// InstantTab does not.
+// All three are backed by a WebContents. InstantOverlay and InstantNTP own
+// their corresponding WebContents; InstantTab does not. In non-extended mode,
+// only an InstantOverlay instance is kept.
//
// InstantController is owned by Browser via BrowserInstantController.
class InstantController : public InstantPage::Delegate,
public InstantServiceObserver {
public:
+ // For reporting fallbacks to local overlay.
+ enum InstantFallbackReason {
+ INSTANT_FALLBACK_NONE = 0,
+ INSTANT_FALLBACK_UNKNOWN = 1,
+ INSTANT_FALLBACK_INSTANT_URL_EMPTY = 2,
+ INSTANT_FALLBACK_ORIGIN_PATH_MISMATCH = 3,
+ INSTANT_FALLBACK_INSTANT_NOT_SUPPORTED = 4,
+ INSTANT_FALLBACK_NO_OVERLAY = 5,
+ INSTANT_FALLBACK_JAVASCRIPT_DISABLED = 6,
+ INSTANT_FALLBACK_MAX = 7,
+ };
+
InstantController(BrowserInstantController* browser,
bool extended_enabled);
virtual ~InstantController();
+ // Called when the Autocomplete flow is about to start. Sets up the
+ // appropriate page to send user updates to. May reset |instant_tab_| or
+ // switch to a local fallback |overlay_| as necessary.
+ void OnAutocompleteStart();
+
+ // Invoked as the user types into the omnibox. |user_text| is what the user
+ // has typed. |full_text| is what the omnibox is showing. These may differ if
+ // the user typed only some text, and the rest was inline autocompleted. If
+ // |verbatim| is true, search results are shown for the exact omnibox text,
+ // rather than the best guess as to what the user means. Returns true if the
+ // update is accepted (i.e., if |match| is a search rather than a URL).
+ // |is_keyword_search| is true if keyword searching is in effect.
+ bool Update(const AutocompleteMatch& match,
+ const string16& user_text,
+ const string16& full_text,
+ size_t selection_start,
+ size_t selection_end,
+ bool verbatim,
+ bool user_input_in_progress,
+ bool omnibox_popup_is_open,
+ bool escape_pressed,
+ bool is_keyword_search);
+
// Releases and returns the NTP WebContents. May be NULL. Loads a new
// WebContents for the NTP.
scoped_ptr<content::WebContents> ReleaseNTPContents() WARN_UNUSED_RESULT;
+ // Sets the bounds of the omnibox popup, in screen coordinates.
+ void SetPopupBounds(const gfx::Rect& bounds);
+
// Sets the stored start-edge margin and width of the omnibox.
void SetOmniboxBounds(const gfx::Rect& bounds);
- // Called when the default search provider changes. Resets InstantNTP.
+ // Send autocomplete results from |providers| to the overlay page.
+ void HandleAutocompleteResults(
+ const std::vector<AutocompleteProvider*>& providers,
+ const AutocompleteResult& result);
+
+ // Called when the default search provider changes. Resets InstantNTP and
+ // InstantOverlay.
void OnDefaultSearchProviderChanged();
+ // Called when the user presses up or down. |count| is a repeat count,
+ // negative for moving up, positive for moving down. Returns true if Instant
+ // handled the key press.
+ bool OnUpOrDownKeyPressed(int count);
+
+ // Called when the user has arrowed into the suggestions but wants to cancel,
+ // typically by pressing ESC. The omnibox text is expected to have been
+ // reverted to |full_text| by the OmniboxEditModel prior to calling this.
+ // |match| is the match reverted to.
+ void OnCancel(const AutocompleteMatch& match,
+ const string16& user_text,
+ const string16& full_text);
+
+ // Called when the user navigates to a URL from the omnibox. This will send
+ // an onsubmit notification to the instant page.
+ void OmniboxNavigateToURL();
+
// Notifies |instant_Tab_| to toggle voice search.
void ToggleVoiceSearch();
+ // The overlay WebContents. May be NULL. InstantController retains ownership.
+ content::WebContents* GetOverlayContents() const;
+
// The ntp WebContents. May be NULL. InstantController retains ownership.
content::WebContents* GetNTPContents() const;
+ // Returns true if Instant is showing a search results overlay. Returns false
+ // if the overlay is not showing, or if it's showing only suggestions.
+ bool IsOverlayingSearchResults() const;
+
// Called if the browser is navigating to a search URL for |search_terms| with
// search-term-replacement enabled. If |instant_tab_| can be used to process
// the search, this does so and returns true. Else, returns false.
bool SubmitQuery(const string16& search_terms);
+ // If the overlay is showing search results, commits the overlay, calling
+ // CommitInstant() on the browser, and returns true. Else, returns false.
+ bool CommitIfPossible(InstantCommitType type);
+
// If the network status changes, try to reset NTP and Overlay.
void OnNetworkChanged(net::NetworkChangeNotifier::ConnectionType type);
@@ -92,18 +179,38 @@ class InstantController : public InstantPage::Delegate,
OmniboxFocusChangeReason reason,
gfx::NativeView view_gaining_focus);
- // The search mode in the active tab has changed. Bind |instant_tab_| if the
+ // The search mode in the active tab has changed. Pass the message down to
+ // the overlay which will notify the renderer. Create |instant_tab_| if the
// |new_mode| reflects an Instant search results page.
void SearchModeChanged(const SearchMode& old_mode,
const SearchMode& new_mode);
- // The user switched tabs. Bind |instant_tab_| if the newly active tab is an
- // Instant search results page.
+ // The user switched tabs. Hide the overlay. Create |instant_tab_| if the
+ // newly active tab is an Instant search results page.
void ActiveTabChanged();
- // The user is about to switch tabs.
+ // The user is about to switch tabs. Commit the overlay if needed.
void TabDeactivated(content::WebContents* contents);
+ // Sets whether Instant should show result overlays. |use_local_page_only|
+ // will force the use of baked-in page as the Instant URL and is only
+ // applicable if |extended_enabled_| is true.
+ void SetInstantEnabled(bool instant_enabled, bool use_local_page_only);
+
+ // Called when someone else swapped in a different contents in the |overlay_|.
+ void SwappedOverlayContents();
+
+ // Called when contents for |overlay_| received focus.
+ void FocusedOverlayContents();
+
+ // Called when the |overlay_| might be stale. If it's actually stale, and the
+ // omnibox doesn't have focus, and the overlay isn't showing, the |overlay_|
+ // is deleted and recreated. Else the refresh is skipped.
+ void ReloadOverlayIfStale();
+
+ // Called when the |overlay_|'s main frame has finished loading.
+ void OverlayLoadCompletedMainFrame();
+
// Adds a new event to |debug_events_| and also DVLOG's it. Ensures that
// |debug_events_| doesn't get too large.
void LogDebugEvent(const std::string& info) const;
@@ -111,9 +218,6 @@ class InstantController : public InstantPage::Delegate,
// Resets list of debug events.
void ClearDebugEvents();
- // Loads a new NTP to replace |ntp_|.
- void ReloadStaleNTP();
-
// Returns the correct Instant URL to use from the following possibilities:
// o The default search engine's Instant URL
// o The local page (see GetLocalInstantURL())
@@ -126,6 +230,15 @@ class InstantController : public InstantPage::Delegate,
return debug_events_;
}
+ // Returns the transition type of the last AutocompleteMatch passed to Update.
+ content::PageTransition last_transition_type() const {
+ return last_transition_type_;
+ }
+
+ // Non-const for Add/RemoveObserver only. Other model changes should only
+ // happen through the InstantController interface.
+ InstantOverlayModel* model() { return &model_; }
+
// Used by BrowserInstantController to notify InstantController about the
// instant support change event for the active web contents.
void InstantSupportChanged(InstantSupportState instant_support);
@@ -134,6 +247,7 @@ class InstantController : public InstantPage::Delegate,
// Accessors are made protected for testing purposes.
virtual bool extended_enabled() const;
+ virtual InstantOverlay* overlay() const;
virtual InstantTab* instant_tab() const;
virtual InstantNTP* ntp() const;
@@ -155,6 +269,7 @@ class InstantController : public InstantPage::Delegate,
UNIT_F(IsJavascriptEnabledChecksContentSettings);
UNIT_F(IsJavascriptEnabledChecksPrefs);
UNIT_F(PrefersRemoteNTPOnStartup);
+ UNIT_F(ShouldSwitchToLocalOverlay);
UNIT_F(SwitchesToLocalNTPIfJSDisabled);
UNIT_F(SwitchesToLocalNTPIfNoInstantSupport);
UNIT_F(SwitchesToLocalNTPIfNoNTPReady);
@@ -204,6 +319,14 @@ class InstantController : public InstantPage::Delegate,
virtual void InstantPageAboutToNavigateMainFrame(
const content::WebContents* contents,
const GURL& url) OVERRIDE;
+ virtual void SetSuggestions(
+ const content::WebContents* contents,
+ const std::vector<InstantSuggestion>& suggestions) OVERRIDE;
+ virtual void ShowInstantOverlay(
+ const content::WebContents* contents,
+ int height,
+ InstantSizeUnits units) OVERRIDE;
+ virtual void LogDropdownShown() OVERRIDE;
virtual void FocusOmnibox(const content::WebContents* contents,
OmniboxFocusState state) OVERRIDE;
virtual void NavigateToURL(
@@ -249,9 +372,19 @@ class InstantController : public InstantPage::Delegate,
// Recreates |ntp_| using |instant_url|.
void ResetNTP(const std::string& instant_url);
+ // Reloads a new InstantNTP. Called when |ntp_| is stale.
+ void ReloadStaleNTP();
+
// Returns true if we should switch to using the local NTP.
bool ShouldSwitchToLocalNTP() const;
+ // Recreates |overlay_| using |instant_url|. |overlay_| will be NULL if
+ // |instant_url| is empty or if there is no active tab.
+ void ResetOverlay(const std::string& instant_url);
+
+ // Returns an enum value indicating the reason to fallback.
+ InstantFallbackReason ShouldSwitchToLocalOverlay() const;
+
// If the active tab is an Instant search results page, sets |instant_tab_| to
// point to it. Else, deletes any existing |instant_tab_|.
void ResetInstantTab();
@@ -263,9 +396,42 @@ class InstantController : public InstantPage::Delegate,
// active tab is in mode SEARCH_SUGGESTIONS.
bool IsInputInProgress() const;
+ // Hide the overlay. Also sends an onchange event (with blank query) to the
+ // overlay, telling it to clear out results for any old queries.
+ void HideOverlay();
+
+ // Like HideOverlay(), but doesn't call OnStaleOverlay(). Use HideOverlay()
+ // unless you are going to call overlay_.reset() yourself subsequently.
+ void HideInternal();
+
+ // Counterpart to HideOverlay(). Asks the |browser_| to display the overlay
+ // with the given |height| in |units|.
+ void ShowOverlay(int height, InstantSizeUnits units);
+
+ // Send the omnibox popup bounds to the page.
+ void SendPopupBoundsToPage();
+
+ // If possible, tries to mutate |suggestion| to a valid suggestion. Returns
+ // true if successful. (Note that |suggestion| may be modified even if this
+ // returns false.)
+ bool FixSuggestion(InstantSuggestion* suggestion) const;
+
// Returns true if the local page is being used.
bool UsingLocalPage() const;
+ // Returns true iff |use_tab_for_suggestions_| is true and |instant_tab_|
+ // exists.
+ bool UseTabForSuggestions() const;
+
+ // Populates InstantAutocompleteResult with AutocompleteMatch details.
+ // |autocomplete_match_index| specifies the index of |match| in the
+ // AutocompleteResult. If the |match| is obtained from auto complete
+ // providers, then the |autocomplete_match_index| is set to kNoMatchIndex.
+ void PopulateInstantAutocompleteResultFromMatch(
+ const AutocompleteMatch& match,
+ size_t autocomplete_match_index,
+ InstantAutocompleteResult* result);
+
// Returns the InstantService for the browser profile.
InstantService* GetInstantService() const;
@@ -274,11 +440,61 @@ class InstantController : public InstantPage::Delegate,
// Whether the extended API and regular API are enabled. If both are false,
// Instant is effectively disabled.
const bool extended_enabled_;
+ bool instant_enabled_;
+
+ // If true, the Instant URL is set to kChromeSearchLocalNtpUrl.
+ bool use_local_page_only_;
+
+ // If true, preload an NTP into |ntp_|.
+ bool preload_ntp_;
- // The instances of InstantPage maintained by InstantController.
+ // The state of the overlay page, i.e., the page owned by |overlay_|. Ignored
+ // if |instant_tab_| is in use.
+ InstantOverlayModel model_;
+
+ // The three instances of InstantPage maintained by InstantController as
+ // described above. All three may be non-NULL in extended mode. If
+ // |instant_tab_| is not NULL, then |overlay_| is guaranteed to be hidden and
+ // messages will be sent to |instant_tab_| instead.
+ //
+ // In non-extended mode, only |overlay_| is ever non-NULL.
+ scoped_ptr<InstantOverlay> overlay_;
scoped_ptr<InstantNTP> ntp_;
scoped_ptr<InstantTab> instant_tab_;
+ // If true, send suggestion-related events (such as user key strokes, auto
+ // complete results, etc.) to |instant_tab_| instead of |overlay_|. Once set
+ // to false, will stay false until the overlay is hidden or committed.
+ bool use_tab_for_suggestions_;
+
+ // The most recent full_text passed to Update(). If empty, we'll not accept
+ // search suggestions from |overlay_| or |instant_tab_|.
+ string16 last_omnibox_text_;
+
+ // The most recent user_text passed to Update(). Used to filter out-of-date
+ // URL suggestions from the Instant page. Set in Update() and cleared when
+ // the page sets temporary text (SetSuggestions() with REPLACE behavior).
+ string16 last_user_text_;
+
+ // True if the last Update() had an inline autocompletion. Used only to make
+ // sure that we don't accidentally suggest gray text suggestion in that case.
+ bool last_omnibox_text_has_inline_autocompletion_;
+
+ // The most recent verbatim passed to Update(). Used only to ensure that we
+ // don't accidentally suggest an inline autocompletion.
+ bool last_verbatim_;
+
+ // The most recent suggestion received from the page, minus any prefix that
+ // the user has typed.
+ InstantSuggestion last_suggestion_;
+
+ // See comments on the getter above.
+ content::PageTransition last_transition_type_;
+
+ // True if the last match passed to Update() was a search (versus a URL).
+ // Used to ensure that the overlay page is committable.
+ bool last_match_was_search_;
+
// Omnibox focus state.
OmniboxFocusState omnibox_focus_state_;
@@ -288,10 +504,38 @@ class InstantController : public InstantPage::Delegate,
// The search model mode for the active tab.
SearchMode search_mode_;
+ // Current omnibox popup bounds.
+ gfx::Rect popup_bounds_;
+
+ // Last popup bounds passed to the page.
+ gfx::Rect last_popup_bounds_;
+
// The start-edge margin and width of the omnibox, used by the page to align
// its suggestions with the omnibox.
gfx::Rect omnibox_bounds_;
+ // Timer used to update the bounds of the omnibox popup.
+ base::OneShotTimer<InstantController> update_bounds_timer_;
+
+ // Search terms extraction (for autocomplete history matches) doesn't work
+ // on Instant URLs. So, whenever the user commits an Instant search, we add
+ // an equivalent non-Instant search URL to history, so that the search shows
+ // up in autocomplete history matches.
+ // TODO(sreeram): Remove when http://crbug.com/155373 is fixed.
+ GURL url_for_history_;
+
+ // The timestamp at which query editing began. This value is used when the
+ // overlay is showed and cleared when the overlay is hidden.
+ base::Time first_interaction_time_;
+
+ // Indicates that the first interaction time has already been logged.
+ bool first_interaction_time_recorded_;
+
+ // Whether to allow the overlay to show search suggestions. In general, the
+ // overlay is allowed to show search suggestions whenever |search_mode_| is
+ // MODE_SEARCH_SUGGESTIONS, except in those cases where this is false.
+ bool allow_overlay_to_show_search_suggestions_;
+
// List of events and their timestamps, useful in debugging Instant behaviour.
mutable std::list<std::pair<int64, std::string> > debug_events_;
diff --git a/chrome/browser/ui/search/instant_controller_unittest.cc b/chrome/browser/ui/search/instant_controller_unittest.cc
index b96540b..c3c09ac 100644
--- a/chrome/browser/ui/search/instant_controller_unittest.cc
+++ b/chrome/browser/ui/search/instant_controller_unittest.cc
@@ -11,6 +11,7 @@
#include "chrome/browser/search/search.h"
#include "chrome/browser/ui/search/instant_controller.h"
#include "chrome/browser/ui/search/instant_ntp.h"
+#include "chrome/browser/ui/search/instant_overlay.h"
#include "chrome/common/content_settings.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/testing_profile.h"
@@ -21,6 +22,36 @@ using base::HistogramBase;
using base::HistogramSamples;
using base::StatisticsRecorder;
+class TestableInstantOverlay : public InstantOverlay {
+ public:
+ TestableInstantOverlay(InstantController* controller,
+ const std::string& instant_url)
+ : InstantOverlay(controller, instant_url, false) {
+ }
+
+ // Overrides from InstantPage
+ virtual bool supports_instant() const OVERRIDE {
+ return test_supports_instant_;
+ }
+
+ virtual bool IsLocal() const OVERRIDE {
+ return test_is_local_;
+ };
+
+ void set_supports_instant(bool supports_instant) {
+ test_supports_instant_ = supports_instant;
+ }
+
+ void set_is_local(bool is_local) {
+ test_is_local_ = is_local;
+ }
+
+ private:
+ std::string test_instant_url_;
+ bool test_supports_instant_;
+ bool test_is_local_;
+};
+
class TestableInstantNTP : public InstantNTP {
public:
TestableInstantNTP(InstantController* controller,
@@ -68,6 +99,7 @@ class TestableInstantController : public InstantController {
override_javascript_enabled_(true),
test_javascript_enabled_(true),
test_in_startup_(false),
+ test_overlay_(NULL),
test_ntp_(NULL) {}
// Overrides from InstantController
@@ -83,6 +115,10 @@ class TestableInstantController : public InstantController {
return test_extended_enabled_;
}
+ virtual InstantOverlay* overlay() const OVERRIDE {
+ return test_overlay_;
+ }
+
virtual InstantNTP* ntp() const OVERRIDE {
return test_ntp_;
}
@@ -95,6 +131,10 @@ class TestableInstantController : public InstantController {
test_extended_enabled_ = extended_enabled;
}
+ void set_overlay(InstantOverlay* overlay) {
+ test_overlay_ = overlay;
+ }
+
void set_ntp(InstantNTP* ntp) {
test_ntp_ = ntp;
}
@@ -133,6 +173,7 @@ private:
bool override_javascript_enabled_;
bool test_javascript_enabled_;
bool test_in_startup_;
+ InstantOverlay* test_overlay_;
InstantNTP* test_ntp_;
mutable TestingProfile profile_;
};
@@ -157,6 +198,53 @@ class InstantControllerTest : public testing::Test {
scoped_ptr<TestableInstantController> instant_controller_;
};
+TEST_F(InstantControllerTest, ShouldSwitchToLocalOverlay) {
+ InstantController::InstantFallbackReason fallback_reason;
+
+ instant_controller()->set_extended_enabled(false);
+ fallback_reason = instant_controller()->ShouldSwitchToLocalOverlay();
+ ASSERT_EQ(fallback_reason, InstantController::INSTANT_FALLBACK_NONE);
+
+ instant_controller()->set_extended_enabled(true);
+ fallback_reason = instant_controller()->ShouldSwitchToLocalOverlay();
+ ASSERT_EQ(fallback_reason, InstantController::INSTANT_FALLBACK_NO_OVERLAY);
+
+ std::string instant_url("http://test_url");
+ scoped_ptr<TestableInstantOverlay> test_overlay(
+ new TestableInstantOverlay(instant_controller(), instant_url));
+ test_overlay->set_is_local(true);
+ instant_controller()->set_overlay(test_overlay.get());
+ fallback_reason = instant_controller()->ShouldSwitchToLocalOverlay();
+ ASSERT_EQ(fallback_reason, InstantController::INSTANT_FALLBACK_NONE);
+
+ instant_controller()->set_javascript_enabled(false);
+ fallback_reason = instant_controller()->ShouldSwitchToLocalOverlay();
+ ASSERT_EQ(fallback_reason,
+ InstantController::INSTANT_FALLBACK_JAVASCRIPT_DISABLED);
+ instant_controller()->set_javascript_enabled(true);
+
+ test_overlay->set_is_local(false);
+ instant_controller()->set_instant_url("");
+ fallback_reason = instant_controller()->ShouldSwitchToLocalOverlay();
+ ASSERT_EQ(fallback_reason,
+ InstantController::INSTANT_FALLBACK_INSTANT_URL_EMPTY);
+
+ instant_controller()->set_instant_url("http://instant_url");
+ fallback_reason = instant_controller()->ShouldSwitchToLocalOverlay();
+ ASSERT_EQ(fallback_reason,
+ InstantController::INSTANT_FALLBACK_ORIGIN_PATH_MISMATCH);
+
+ instant_controller()->set_instant_url(instant_url);
+ test_overlay->set_supports_instant(false);
+ fallback_reason = instant_controller()->ShouldSwitchToLocalOverlay();
+ ASSERT_EQ(fallback_reason,
+ InstantController::INSTANT_FALLBACK_INSTANT_NOT_SUPPORTED);
+
+ test_overlay->set_supports_instant(true);
+ fallback_reason = instant_controller()->ShouldSwitchToLocalOverlay();
+ ASSERT_EQ(fallback_reason, InstantController::INSTANT_FALLBACK_NONE);
+}
+
TEST_F(InstantControllerTest, PrefersRemoteNTPOnStartup) {
std::string instant_url("http://instant_url");
scoped_ptr<TestableInstantNTP> ntp(new TestableInstantNTP(
diff --git a/chrome/browser/ui/search/instant_extended_interactive_uitest.cc b/chrome/browser/ui/search/instant_extended_interactive_uitest.cc
index 344b7e4..92194397 100644
--- a/chrome/browser/ui/search/instant_extended_interactive_uitest.cc
+++ b/chrome/browser/ui/search/instant_extended_interactive_uitest.cc
@@ -43,7 +43,9 @@
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/omnibox/omnibox_view.h"
+#include "chrome/browser/ui/search/instant_commit_type.h"
#include "chrome/browser/ui/search/instant_ntp.h"
+#include "chrome/browser/ui/search/instant_overlay.h"
#include "chrome/browser/ui/search/instant_tab.h"
#include "chrome/browser/ui/search/instant_test_utils.h"
#include "chrome/browser/ui/search/search_tab_helper.h"
diff --git a/chrome/browser/ui/search/instant_extended_manual_interactive_uitest.cc b/chrome/browser/ui/search/instant_extended_manual_interactive_uitest.cc
index 6ee7e94..00c701c 100644
--- a/chrome/browser/ui/search/instant_extended_manual_interactive_uitest.cc
+++ b/chrome/browser/ui/search/instant_extended_manual_interactive_uitest.cc
@@ -11,6 +11,7 @@
#include "chrome/browser/ui/host_desktop.h"
#include "chrome/browser/ui/omnibox/omnibox_view.h"
#include "chrome/browser/ui/search/instant_ntp.h"
+#include "chrome/browser/ui/search/instant_overlay.h"
#include "chrome/browser/ui/search/instant_test_utils.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_notification_types.h"
@@ -59,7 +60,7 @@ class InstantExtendedManualTest : public InProcessBrowserTest,
testing::UnitTest::GetInstance()->current_test_info();
ASSERT_TRUE(StartsWithASCII(test_info->name(), "MANUAL_", true) ||
StartsWithASCII(test_info->name(), "DISABLED_", true));
- // Make IsOffline() return false so we don't try to use the local NTP.
+ // Make IsOffline() return false so we don't try to use the local overlay.
disable_network_change_notifier_.reset(
new net::NetworkChangeNotifier::DisableForTest());
}
@@ -77,6 +78,47 @@ class InstantExtendedManualTest : public InProcessBrowserTest,
return browser()->tab_strip_model()->GetActiveWebContents();
}
+ bool PressBackspace() {
+ return ui_test_utils::SendKeyPressSync(browser(), ui::VKEY_BACK,
+ false, false, false, false);
+ }
+
+ bool PressBackspaceAndWaitForSuggestions() {
+ content::WindowedNotificationObserver observer(
+ chrome::NOTIFICATION_INSTANT_SET_SUGGESTION,
+ content::NotificationService::AllSources());
+ bool result = PressBackspace();
+ observer.Wait();
+ return result;
+ }
+
+ bool PressBackspaceAndWaitForOverlayToShow() {
+ InstantTestModelObserver observer(
+ instant()->model(), SearchMode::MODE_SEARCH_SUGGESTIONS);
+ return PressBackspace() && observer.WaitForExpectedOverlayState() ==
+ SearchMode::MODE_SEARCH_SUGGESTIONS;
+ }
+
+ bool PressEnterAndWaitForNavigationWithTitle(content::WebContents* contents,
+ const string16& title) {
+ content::TitleWatcher title_watcher(contents, title);
+ content::WindowedNotificationObserver nav_observer(
+ content::NOTIFICATION_NAV_ENTRY_COMMITTED,
+ content::NotificationService::AllSources());
+ browser()->window()->GetLocationBar()->AcceptInput();
+ nav_observer.Wait();
+ return title_watcher.WaitAndGetTitle() == title;
+ }
+
+ GURL GetActiveTabURL() {
+ return active_tab()->GetController().GetActiveEntry()->GetURL();
+ }
+
+ bool GetSelectionState(bool* selected) {
+ return GetBoolFromJS(instant()->GetOverlayContents(),
+ "google.ac.gs().api.i()", selected);
+ }
+
bool IsGooglePage(content::WebContents* contents) {
bool is_google = false;
if (!GetBoolFromJS(contents, "!!window.google", &is_google))
@@ -93,7 +135,8 @@ class InstantExtendedManualTest : public InProcessBrowserTest,
IN_PROC_BROWSER_TEST_F(InstantExtendedManualTest, MANUAL_ShowsGoogleNTP) {
set_browser(browser());
- instant()->ReloadStaleNTP();
+ instant()->SetInstantEnabled(false, true);
+ instant()->SetInstantEnabled(true, false);
FocusOmniboxAndWaitForInstantNTPSupport();
content::WindowedNotificationObserver observer(
@@ -112,7 +155,8 @@ IN_PROC_BROWSER_TEST_F(InstantExtendedManualTest, MANUAL_ShowsGoogleNTP) {
IN_PROC_BROWSER_TEST_F(InstantExtendedManualTest, MANUAL_SearchesFromFakebox) {
set_browser(browser());
- instant()->ReloadStaleNTP();
+ instant()->SetInstantEnabled(false, true);
+ instant()->SetInstantEnabled(true, false);
FocusOmniboxAndWaitForInstantNTPSupport();
// Open a new tab page.
diff --git a/chrome/browser/ui/search/instant_ipc_sender.cc b/chrome/browser/ui/search/instant_ipc_sender.cc
index a457534..4150259 100644
--- a/chrome/browser/ui/search/instant_ipc_sender.cc
+++ b/chrome/browser/ui/search/instant_ipc_sender.cc
@@ -15,10 +15,26 @@ class InstantIPCSenderImpl : public InstantIPCSender {
virtual ~InstantIPCSenderImpl() {}
private:
+ virtual void Update(const string16& text,
+ size_t selection_start,
+ size_t selection_end,
+ bool verbatim) OVERRIDE {
+ Send(new ChromeViewMsg_SearchBoxChange(routing_id(), text, verbatim,
+ selection_start, selection_end));
+ }
+
virtual void Submit(const string16& text) OVERRIDE {
Send(new ChromeViewMsg_SearchBoxSubmit(routing_id(), text));
}
+ virtual void Cancel(const string16& text) OVERRIDE {
+ Send(new ChromeViewMsg_SearchBoxCancel(routing_id(), text));
+ }
+
+ virtual void SetPopupBounds(const gfx::Rect& bounds) OVERRIDE {
+ Send(new ChromeViewMsg_SearchBoxPopupResize(routing_id(), bounds));
+ }
+
virtual void SetOmniboxBounds(const gfx::Rect& bounds) OVERRIDE {
Send(new ChromeViewMsg_SearchBoxMarginChange(
routing_id(), bounds.x(), bounds.width()));
@@ -35,11 +51,37 @@ class InstantIPCSenderImpl : public InstantIPCSender {
routing_id(), is_app_launcher_enabled));
}
+ virtual void SendAutocompleteResults(
+ const std::vector<InstantAutocompleteResult>& results) OVERRIDE {
+ Send(new ChromeViewMsg_SearchBoxAutocompleteResults(routing_id(), results));
+ }
+
+ virtual void UpOrDownKeyPressed(int count) OVERRIDE {
+ Send(new ChromeViewMsg_SearchBoxUpOrDownKeyPressed(routing_id(), count));
+ }
+
+ virtual void EscKeyPressed() OVERRIDE {
+ Send(new ChromeViewMsg_SearchBoxEscKeyPressed(routing_id()));
+ }
+
+ virtual void CancelSelection(const string16& user_text,
+ size_t selection_start,
+ size_t selection_end,
+ bool verbatim) OVERRIDE {
+ Send(new ChromeViewMsg_SearchBoxCancelSelection(
+ routing_id(), user_text, verbatim, selection_start, selection_end));
+ }
+
virtual void SendThemeBackgroundInfo(
const ThemeBackgroundInfo& theme_info) OVERRIDE {
Send(new ChromeViewMsg_SearchBoxThemeChanged(routing_id(), theme_info));
}
+ virtual void SetDisplayInstantResults(bool display_instant_results) OVERRIDE {
+ Send(new ChromeViewMsg_SearchBoxSetDisplayInstantResults(
+ routing_id(), display_instant_results));
+ }
+
virtual void FocusChanged(OmniboxFocusState state,
OmniboxFocusChangeReason reason) OVERRIDE {
Send(new ChromeViewMsg_SearchBoxFocusChanged(routing_id(), state, reason));
diff --git a/chrome/browser/ui/search/instant_ipc_sender.h b/chrome/browser/ui/search/instant_ipc_sender.h
index 8319f2c..3da734d 100644
--- a/chrome/browser/ui/search/instant_ipc_sender.h
+++ b/chrome/browser/ui/search/instant_ipc_sender.h
@@ -31,9 +31,29 @@ class InstantIPCSender : public content::WebContentsObserver {
// Sets |web_contents| as the receiver of IPCs.
void SetContents(content::WebContents* web_contents);
+ // Tells the page that the user typed |text| into the omnibox. If |verbatim|
+ // is false, the page predicts the query the user means to type and fetches
+ // results for the prediction. If |verbatim| is true, |text| is taken as the
+ // exact query (no prediction is made). |selection_start| and |selection_end|
+ // mark the inline autocompleted portion (i.e., blue highlighted text). The
+ // omnibox caret (cursor) is at |selection_end|.
+ virtual void Update(const string16& text,
+ size_t selection_start,
+ size_t selection_end,
+ bool verbatim) {}
+
// Tells the page that the user pressed Enter in the omnibox.
virtual void Submit(const string16& text) {}
+ // Tells the page that the user clicked on it. Nothing is being cancelled; the
+ // poor choice of name merely reflects the IPC of the same (poor) name.
+ virtual void Cancel(const string16& text) {}
+
+ // Tells the page the bounds of the omnibox dropdown (in screen coordinates).
+ // This is used by the page to offset the results to avoid them being covered
+ // by the omnibox dropdown.
+ virtual void SetPopupBounds(const gfx::Rect& bounds) {}
+
// Tells the page the bounds of the omnibox (in screen coordinates). This is
// used by the page to align text or assets properly with the omnibox.
virtual void SetOmniboxBounds(const gfx::Rect& bounds) {}
@@ -45,10 +65,32 @@ class InstantIPCSender : public content::WebContentsObserver {
// Tells the page information it needs to display promos.
virtual void SetPromoInformation(bool is_app_launcher_enabled) {}
+ // Tells the page about the available autocomplete results.
+ virtual void SendAutocompleteResults(
+ const std::vector<InstantAutocompleteResult>& results) {}
+
+ // Tells the page that the user pressed Up or Down in the omnibox. |count| is
+ // a repeat count, negative for moving up, positive for moving down.
+ virtual void UpOrDownKeyPressed(int count) {}
+
+ // Tells the page that the user pressed Esc key in the omnibox.
+ virtual void EscKeyPressed() {}
+
+ // Tells the page that the user pressed Esc in the omnibox after having
+ // arrowed down in the suggestions. The page should reset the selection to
+ // the first suggestion. Arguments are the same as those for Update().
+ virtual void CancelSelection(const string16& user_text,
+ size_t selection_start,
+ size_t selection_end,
+ bool verbatim) {}
+
// Tells the page about the current theme background.
virtual void SendThemeBackgroundInfo(
const ThemeBackgroundInfo& theme_info) {}
+ // Tells the page whether it is allowed to display Instant results.
+ virtual void SetDisplayInstantResults(bool display_instant_results) {}
+
// Tells the page that the omnibox focus has changed.
virtual void FocusChanged(OmniboxFocusState state,
OmniboxFocusChangeReason reason) {}
diff --git a/chrome/browser/ui/search/instant_overlay.cc b/chrome/browser/ui/search/instant_overlay.cc
new file mode 100644
index 0000000..54cca1d
--- /dev/null
+++ b/chrome/browser/ui/search/instant_overlay.cc
@@ -0,0 +1,148 @@
+// Copyright 2013 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/ui/search/instant_overlay.h"
+
+#include "base/auto_reset.h"
+#include "base/supports_user_data.h"
+#include "chrome/browser/search/search.h"
+#include "chrome/common/url_constants.h"
+#include "content/public/browser/web_contents.h"
+
+namespace {
+
+int kUserDataKey;
+
+class InstantOverlayUserData : public base::SupportsUserData::Data {
+ public:
+ explicit InstantOverlayUserData(InstantOverlay* overlay)
+ : overlay_(overlay) {}
+
+ virtual InstantOverlay* overlay() const { return overlay_; }
+
+ private:
+ virtual ~InstantOverlayUserData() {}
+
+ InstantOverlay* const overlay_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstantOverlayUserData);
+};
+
+} // namespace
+
+// static
+InstantOverlay* InstantOverlay::FromWebContents(
+ const content::WebContents* web_contents) {
+ InstantOverlayUserData* data = static_cast<InstantOverlayUserData*>(
+ web_contents->GetUserData(&kUserDataKey));
+ return data ? data->overlay() : NULL;
+}
+
+InstantOverlay::InstantOverlay(InstantController* controller,
+ const std::string& instant_url,
+ bool is_incognito)
+ : InstantPage(controller, instant_url, is_incognito),
+ loader_(this),
+ is_stale_(false),
+ is_pointer_down_from_activate_(false) {
+}
+
+InstantOverlay::~InstantOverlay() {
+}
+
+void InstantOverlay::InitContents(Profile* profile,
+ const content::WebContents* active_tab) {
+ loader_.Init(GURL(instant_url()), profile, active_tab,
+ base::Bind(&InstantOverlay::HandleStalePage,
+ base::Unretained(this)));
+ SetContents(loader_.contents());
+ contents()->SetUserData(&kUserDataKey, new InstantOverlayUserData(this));
+ loader_.Load();
+}
+
+scoped_ptr<content::WebContents> InstantOverlay::ReleaseContents() {
+ contents()->RemoveUserData(&kUserDataKey);
+ SetContents(NULL);
+ return loader_.ReleaseContents();
+}
+
+void InstantOverlay::DidNavigate(
+ const history::HistoryAddPageArgs& add_page_args) {
+ last_navigation_ = add_page_args;
+}
+
+void InstantOverlay::Update(const string16& text,
+ size_t selection_start,
+ size_t selection_end,
+ bool verbatim) {
+ last_navigation_ = history::HistoryAddPageArgs();
+ sender()->Update(text, selection_start, selection_end, verbatim);
+}
+
+void InstantOverlay::OnSwappedContents() {
+ contents()->RemoveUserData(&kUserDataKey);
+ SetContents(loader_.contents());
+ contents()->SetUserData(&kUserDataKey, new InstantOverlayUserData(this));
+ instant_controller()->SwappedOverlayContents();
+}
+
+void InstantOverlay::OnFocus() {
+ // The overlay is getting focus. Equivalent to it being clicked.
+ base::AutoReset<bool> reset(&is_pointer_down_from_activate_, true);
+ instant_controller()->FocusedOverlayContents();
+}
+
+void InstantOverlay::OnMouseDown() {
+ is_pointer_down_from_activate_ = true;
+}
+
+void InstantOverlay::OnMouseUp() {
+ if (is_pointer_down_from_activate_) {
+ is_pointer_down_from_activate_ = false;
+ instant_controller()->CommitIfPossible(INSTANT_COMMIT_FOCUS_LOST);
+ }
+}
+
+content::WebContents* InstantOverlay::OpenURLFromTab(
+ content::WebContents* source,
+ const content::OpenURLParams& params) {
+ if (!supports_instant()) {
+ // If the page doesn't yet support Instant, it hasn't fully loaded.
+ // This is a redirect that we should allow. http://crbug.com/177948
+ content::NavigationController::LoadURLParams load_params(params.url);
+ load_params.transition_type = params.transition;
+ load_params.referrer = params.referrer;
+ load_params.extra_headers = params.extra_headers;
+ load_params.is_renderer_initiated = params.is_renderer_initiated;
+ load_params.transferred_global_request_id =
+ params.transferred_global_request_id;
+ load_params.is_cross_site_redirect = params.is_cross_site_redirect;
+
+ contents()->GetController().LoadURLWithParams(load_params);
+ return contents();
+ }
+
+ // We will allow the navigate to continue if we are able to commit the
+ // overlay.
+ //
+ // First, cache the overlay contents since committing it will cause the
+ // contents to be released (and be set to NULL).
+ content::WebContents* overlay = contents();
+ if (instant_controller()->CommitIfPossible(INSTANT_COMMIT_NAVIGATED)) {
+ // If the commit was successful, the overlay's delegate should be the tab
+ // strip, which will be able to handle the navigation.
+ DCHECK_NE(&loader_, overlay->GetDelegate());
+ return overlay->GetDelegate()->OpenURLFromTab(source, params);
+ }
+ return NULL;
+}
+
+void InstantOverlay::LoadCompletedMainFrame() {
+ instant_controller()->OverlayLoadCompletedMainFrame();
+}
+
+void InstantOverlay::HandleStalePage() {
+ is_stale_ = true;
+ instant_controller()->ReloadOverlayIfStale();
+}
diff --git a/chrome/browser/ui/search/instant_overlay.h b/chrome/browser/ui/search/instant_overlay.h
new file mode 100644
index 0000000..59c1284
--- /dev/null
+++ b/chrome/browser/ui/search/instant_overlay.h
@@ -0,0 +1,105 @@
+// Copyright 2013 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_UI_SEARCH_INSTANT_OVERLAY_H_
+#define CHROME_BROWSER_UI_SEARCH_INSTANT_OVERLAY_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/gtest_prod_util.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/history/history_types.h"
+#include "chrome/browser/ui/search/instant_controller.h"
+#include "chrome/browser/ui/search/instant_loader.h"
+#include "chrome/browser/ui/search/instant_page.h"
+
+class Profile;
+
+namespace content {
+class WebContents;
+}
+
+// InstantOverlay is used to communicate with an overlay WebContents that it
+// owns and loads the "Instant URL" into. This overlay can appear and disappear
+// at will as the user types in the omnibox.
+class InstantOverlay : public InstantPage,
+ public InstantLoader::Delegate {
+ public:
+ // Returns the InstantOverlay for |contents| if it's used for Instant.
+ static InstantOverlay* FromWebContents(const content::WebContents* contents);
+
+ InstantOverlay(InstantController* controller,
+ const std::string& instant_url,
+ bool is_incognito);
+ virtual ~InstantOverlay();
+
+ // Creates a new WebContents and loads |instant_url_| into it. Uses
+ // |active_tab|, if non-NULL, to initialize the size of the WebContents.
+ void InitContents(Profile* profile,
+ const content::WebContents* active_tab);
+
+ // Releases the overlay WebContents. This should be called when the overlay
+ // is committed.
+ scoped_ptr<content::WebContents> ReleaseContents();
+
+ // Returns whether the underlying contents is stale (i.e. was loaded too long
+ // ago).
+ bool is_stale() const { return is_stale_; }
+
+ // Returns true if the mouse or a touch pointer is down due to activating the
+ // overlay contents.
+ bool is_pointer_down_from_activate() const {
+ return is_pointer_down_from_activate_;
+ }
+
+ // Returns info about the last navigation by the Instant page. If the page
+ // hasn't navigated since the last Update(), the URL is empty.
+ const history::HistoryAddPageArgs& last_navigation() const {
+ return last_navigation_;
+ }
+
+ // Called by the history tab helper with information that it would have added
+ // to the history service had this WebContents not been used for Instant.
+ void DidNavigate(const history::HistoryAddPageArgs& add_page_args);
+
+ // Wrapper around InstantIPCSender::Update that also clears
+ // |last_navigation_|.
+ void Update(const string16& text,
+ size_t selection_start,
+ size_t selection_end,
+ bool verbatim);
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(InstantTest, InstantOverlayRefresh);
+ FRIEND_TEST_ALL_PREFIXES(InstantTest, InstantOverlayRefreshDifferentOrder);
+
+ // Helper to access delegate() as an InstantController object.
+ InstantController* instant_controller() const {
+ return static_cast<InstantController*>(delegate());
+ }
+
+ // Overriden from InstantLoader::Delegate:
+ virtual void OnSwappedContents() OVERRIDE;
+ virtual void OnFocus() OVERRIDE;
+ virtual void OnMouseDown() OVERRIDE;
+ virtual void OnMouseUp() OVERRIDE;
+ virtual content::WebContents* OpenURLFromTab(
+ content::WebContents* source,
+ const content::OpenURLParams& params) OVERRIDE;
+ virtual void LoadCompletedMainFrame() OVERRIDE;
+
+ // Called when the underlying page becomes stale.
+ void HandleStalePage();
+
+ InstantLoader loader_;
+ bool is_stale_;
+ bool is_pointer_down_from_activate_;
+ history::HistoryAddPageArgs last_navigation_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstantOverlay);
+};
+
+#endif // CHROME_BROWSER_UI_SEARCH_INSTANT_OVERLAY_H_
diff --git a/chrome/browser/ui/search/instant_overlay_controller.cc b/chrome/browser/ui/search/instant_overlay_controller.cc
index 64fda78..547ccab 100644
--- a/chrome/browser/ui/search/instant_overlay_controller.cc
+++ b/chrome/browser/ui/search/instant_overlay_controller.cc
@@ -9,7 +9,11 @@
InstantOverlayController::InstantOverlayController(Browser* browser)
: browser_(browser) {
+ if (browser_->instant_controller())
+ browser_->instant_controller()->instant()->model()->AddObserver(this);
}
InstantOverlayController::~InstantOverlayController() {
+ if (browser_->instant_controller())
+ browser_->instant_controller()->instant()->model()->RemoveObserver(this);
}
diff --git a/chrome/browser/ui/search/instant_overlay_model.cc b/chrome/browser/ui/search/instant_overlay_model.cc
index bc6213b..09a668b 100644
--- a/chrome/browser/ui/search/instant_overlay_model.cc
+++ b/chrome/browser/ui/search/instant_overlay_model.cc
@@ -50,6 +50,9 @@ void InstantOverlayModel::SetOverlayContents(
}
content::WebContents* InstantOverlayModel::GetOverlayContents() const {
+ // |controller_| maybe NULL durning tests.
+ if (controller_)
+ return controller_->GetOverlayContents();
return overlay_contents_;
}
diff --git a/chrome/browser/ui/search/instant_page.cc b/chrome/browser/ui/search/instant_page.cc
index 121300a..c8aa8c6 100644
--- a/chrome/browser/ui/search/instant_page.cc
+++ b/chrome/browser/ui/search/instant_page.cc
@@ -89,6 +89,14 @@ bool InstantPage::ShouldProcessAboutToNavigateMainFrame() {
return false;
}
+bool InstantPage::ShouldProcessSetSuggestions() {
+ return false;
+}
+
+bool InstantPage::ShouldProcessShowInstantOverlay() {
+ return false;
+}
+
bool InstantPage::ShouldProcessFocusOmnibox() {
return false;
}
@@ -115,6 +123,9 @@ bool InstantPage::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(InstantPage, message)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_SetSuggestions, OnSetSuggestions)
+ IPC_MESSAGE_HANDLER(ChromeViewHostMsg_ShowInstantOverlay,
+ OnShowInstantOverlay)
IPC_MESSAGE_HANDLER(ChromeViewHostMsg_FocusOmnibox, OnFocusOmnibox)
IPC_MESSAGE_HANDLER(ChromeViewHostMsg_SearchBoxNavigate,
OnSearchBoxNavigate);
@@ -174,6 +185,33 @@ void InstantPage::InstantSupportDetermined(bool supports_instant) {
ClearContents();
}
+void InstantPage::OnSetSuggestions(
+ int page_id,
+ const std::vector<InstantSuggestion>& suggestions) {
+ if (!contents()->IsActiveEntry(page_id))
+ return;
+
+ SearchTabHelper::FromWebContents(contents())->InstantSupportChanged(true);
+ if (!ShouldProcessSetSuggestions())
+ return;
+
+ delegate_->SetSuggestions(contents(), suggestions);
+}
+
+void InstantPage::OnShowInstantOverlay(int page_id,
+ int height,
+ InstantSizeUnits units) {
+ if (!contents()->IsActiveEntry(page_id))
+ return;
+
+ SearchTabHelper::FromWebContents(contents())->InstantSupportChanged(true);
+ delegate_->LogDropdownShown();
+ if (!ShouldProcessShowInstantOverlay())
+ return;
+
+ delegate_->ShowInstantOverlay(contents(), height, units);
+}
+
void InstantPage::OnFocusOmnibox(int page_id, OmniboxFocusState state) {
if (!contents()->IsActiveEntry(page_id))
return;
diff --git a/chrome/browser/ui/search/instant_page.h b/chrome/browser/ui/search/instant_page.h
index bf60ccb..010a5ca 100644
--- a/chrome/browser/ui/search/instant_page.h
+++ b/chrome/browser/ui/search/instant_page.h
@@ -33,8 +33,8 @@ class Rect;
// InstantPage is used to exchange messages with a page that implements the
// Instant/Embedded Search API (http://dev.chromium.org/embeddedsearch).
-// InstantPage is not used directly but via one of its derived classes,
-// InstantNTP and InstantTab.
+// InstantPage is not used directly but via one of its derived classes:
+// InstantOverlay, InstantNTP and InstantTab.
class InstantPage : public content::WebContentsObserver,
public SearchModelObserver {
public:
@@ -61,6 +61,22 @@ class InstantPage : public content::WebContentsObserver,
const content::WebContents* contents,
const GURL& url) = 0;
+ // Called when the page has suggestions. Usually in response to Update(),
+ // SendAutocompleteResults() or UpOrDownKeyPressed().
+ virtual void SetSuggestions(
+ const content::WebContents* contents,
+ const std::vector<InstantSuggestion>& suggestions) = 0;
+
+ // Called when the page wants to be shown. Usually in response to Update()
+ // or SendAutocompleteResults().
+ virtual void ShowInstantOverlay(const content::WebContents* contents,
+ int height,
+ InstantSizeUnits units) = 0;
+
+ // Called when the page shows suggestions for logging purposes, regardless
+ // of whether the page is processing the call.
+ virtual void LogDropdownShown() = 0;
+
// Called when the page wants the omnibox to be focused. |state| specifies
// the omnibox focus state.
virtual void FocusOmnibox(const content::WebContents* contents,
@@ -133,6 +149,8 @@ class InstantPage : public content::WebContentsObserver,
// choose to ignore some or all of the received messages by overriding these
// methods.
virtual bool ShouldProcessAboutToNavigateMainFrame();
+ virtual bool ShouldProcessSetSuggestions();
+ virtual bool ShouldProcessShowInstantOverlay();
virtual bool ShouldProcessFocusOmnibox();
virtual bool ShouldProcessNavigateToURL();
virtual bool ShouldProcessDeleteMostVisitedItem();
@@ -179,6 +197,11 @@ class InstantPage : public content::WebContentsObserver,
// Update the status of Instant support.
void InstantSupportDetermined(bool supports_instant);
+ void OnSetSuggestions(int page_id,
+ const std::vector<InstantSuggestion>& suggestions);
+ void OnShowInstantOverlay(int page_id,
+ int height,
+ InstantSizeUnits units);
void OnFocusOmnibox(int page_id, OmniboxFocusState state);
void OnSearchBoxNavigate(int page_id,
const GURL& url,
diff --git a/chrome/browser/ui/search/instant_page_unittest.cc b/chrome/browser/ui/search/instant_page_unittest.cc
index a73ffc9..eafd976 100644
--- a/chrome/browser/ui/search/instant_page_unittest.cc
+++ b/chrome/browser/ui/search/instant_page_unittest.cc
@@ -37,6 +37,14 @@ class FakePageDelegate : public InstantPage::Delegate {
MOCK_METHOD2(InstantPageAboutToNavigateMainFrame,
void(const content::WebContents* contents,
const GURL& url));
+ MOCK_METHOD2(SetSuggestions,
+ void(const content::WebContents* contents,
+ const std::vector<InstantSuggestion>& suggestions));
+ MOCK_METHOD3(ShowInstantOverlay,
+ void(const content::WebContents* contents,
+ int height,
+ InstantSizeUnits units));
+ MOCK_METHOD0(LogDropdownShown, void());
MOCK_METHOD2(FocusOmnibox,
void(const content::WebContents* contents,
OmniboxFocusState state));
@@ -336,12 +344,38 @@ TEST_F(InstantPageTest, AppropriateMessagesSentToIncognitoPages) {
EXPECT_TRUE(MessageWasSent(ChromeViewMsg_SearchBoxMarginChange::ID));
// Incognito pages should not get any others.
+ page->sender()->Update(string16(), 0, 0, false);
+ EXPECT_FALSE(MessageWasSent(ChromeViewMsg_SearchBoxChange::ID));
+
+ page->sender()->Cancel(string16());
+ EXPECT_FALSE(MessageWasSent(ChromeViewMsg_SearchBoxCancel::ID));
+
+ page->sender()->SetPopupBounds(gfx::Rect());
+ EXPECT_FALSE(MessageWasSent(ChromeViewMsg_SearchBoxPopupResize::ID));
+
page->sender()->SetFontInformation(string16(), 0);
EXPECT_FALSE(MessageWasSent(ChromeViewMsg_SearchBoxFontInformation::ID));
+ page->sender()->SendAutocompleteResults(
+ std::vector<InstantAutocompleteResult>());
+ EXPECT_FALSE(MessageWasSent(ChromeViewMsg_SearchBoxAutocompleteResults::ID));
+
+ page->sender()->UpOrDownKeyPressed(0);
+ EXPECT_FALSE(MessageWasSent(ChromeViewMsg_SearchBoxUpOrDownKeyPressed::ID));
+
+ page->sender()->EscKeyPressed();
+ EXPECT_FALSE(MessageWasSent(ChromeViewMsg_SearchBoxEscKeyPressed::ID));
+
+ page->sender()->CancelSelection(string16(), 0, 0, false);
+ EXPECT_FALSE(MessageWasSent(ChromeViewMsg_SearchBoxCancelSelection::ID));
+
page->sender()->SendThemeBackgroundInfo(ThemeBackgroundInfo());
EXPECT_FALSE(MessageWasSent(ChromeViewMsg_SearchBoxThemeChanged::ID));
+ page->sender()->SetDisplayInstantResults(false);
+ EXPECT_FALSE(MessageWasSent(
+ ChromeViewMsg_SearchBoxSetDisplayInstantResults::ID));
+
page->sender()->FocusChanged(
OMNIBOX_FOCUS_NONE, OMNIBOX_FOCUS_CHANGE_EXPLICIT);
EXPECT_FALSE(MessageWasSent(ChromeViewMsg_SearchBoxFocusChanged::ID));
diff --git a/chrome/browser/ui/search/instant_test_utils.cc b/chrome/browser/ui/search/instant_test_utils.cc
index 1ccbd03..e55c2e2 100644
--- a/chrome/browser/ui/search/instant_test_utils.cc
+++ b/chrome/browser/ui/search/instant_test_utils.cc
@@ -12,6 +12,7 @@
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/ui/omnibox/omnibox_view.h"
#include "chrome/browser/ui/search/instant_ntp.h"
+#include "chrome/browser/ui/search/instant_overlay.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
@@ -31,6 +32,32 @@ std::string WrapScript(const std::string& script) {
} // namespace
+// InstantTestModelObserver --------------------------------------------------
+
+InstantTestModelObserver::InstantTestModelObserver(
+ InstantOverlayModel* model,
+ SearchMode::Type expected_mode_type)
+ : model_(model),
+ expected_mode_type_(expected_mode_type),
+ observed_mode_type_(static_cast<SearchMode::Type>(-1)) {
+ model_->AddObserver(this);
+}
+
+InstantTestModelObserver::~InstantTestModelObserver() {
+ model_->RemoveObserver(this);
+}
+
+SearchMode::Type InstantTestModelObserver::WaitForExpectedOverlayState() {
+ run_loop_.Run();
+ return observed_mode_type_;
+}
+
+void InstantTestModelObserver::OverlayStateChanged(
+ const InstantOverlayModel& model) {
+ observed_mode_type_ = model.mode().mode;
+ run_loop_.Quit();
+}
+
// InstantTestBase -----------------------------------------------------------
void InstantTestBase::SetupInstant(Browser* browser) {
@@ -52,7 +79,9 @@ void InstantTestBase::SetupInstant(Browser* browser) {
service->Add(template_url); // Takes ownership of |template_url|.
service->SetDefaultSearchProvider(template_url);
- instant()->ReloadStaleNTP();
+ // TODO(shishir): Fix this ugly hack.
+ instant()->SetInstantEnabled(false, true);
+ instant()->SetInstantEnabled(true, false);
}
void InstantTestBase::SetInstantURL(const std::string& url) {
@@ -73,6 +102,13 @@ void InstantTestBase::Init(const GURL& instant_url) {
instant_url_ = instant_url;
}
+void InstantTestBase::KillInstantRenderView() {
+ base::KillProcess(
+ instant()->GetOverlayContents()->GetRenderProcessHost()->GetHandle(),
+ content::RESULT_CODE_KILLED,
+ false);
+}
+
void InstantTestBase::FocusOmnibox() {
// If the omnibox already has focus, just notify Instant.
if (omnibox()->model()->has_focus()) {
@@ -97,6 +133,36 @@ void InstantTestBase::SetOmniboxText(const std::string& text) {
omnibox()->SetUserText(UTF8ToUTF16(text));
}
+bool InstantTestBase::SetOmniboxTextAndWaitForOverlayToShow(
+ const std::string& text) {
+ // The order of events may be:
+ // { hide, show } or just { show } depending on the order things
+ // flow in from GWS and Chrome's response to hiding the infobar and/or
+ // bookmark bar. Note, the GWS response is relevant because of the
+ // Instant "MANUAL_*" tests.
+ InstantTestModelObserver first_observer(
+ instant()->model(), SearchMode::MODE_DEFAULT);
+ SetOmniboxText(text);
+
+ SearchMode::Type observed = first_observer.WaitForExpectedOverlayState();
+ if (observed == SearchMode::MODE_DEFAULT) {
+ InstantTestModelObserver second_observer(
+ instant()->model(), SearchMode::MODE_SEARCH_SUGGESTIONS);
+ observed = second_observer.WaitForExpectedOverlayState();
+ }
+ EXPECT_EQ(SearchMode::MODE_SEARCH_SUGGESTIONS, observed);
+ return observed == SearchMode::MODE_SEARCH_SUGGESTIONS;
+}
+
+void InstantTestBase::SetOmniboxTextAndWaitForSuggestion(
+ const std::string& text) {
+ content::WindowedNotificationObserver observer(
+ chrome::NOTIFICATION_INSTANT_SET_SUGGESTION,
+ content::NotificationService::AllSources());
+ SetOmniboxText(text);
+ observer.Wait();
+}
+
void InstantTestBase::PressEnterAndWaitForNavigation() {
content::WindowedNotificationObserver nav_observer(
content::NOTIFICATION_NAV_ENTRY_COMMITTED,
@@ -127,7 +193,7 @@ bool InstantTestBase::GetStringFromJS(content::WebContents* contents,
}
bool InstantTestBase::ExecuteScript(const std::string& script) {
- return content::ExecuteScript(instant()->GetNTPContents(), script);
+ return content::ExecuteScript(instant()->GetOverlayContents(), script);
}
bool InstantTestBase::CheckVisibilityIs(content::WebContents* contents,
diff --git a/chrome/browser/ui/search/instant_test_utils.h b/chrome/browser/ui/search/instant_test_utils.h
index 5072443..9ab18b8 100644
--- a/chrome/browser/ui/search/instant_test_utils.h
+++ b/chrome/browser/ui/search/instant_test_utils.h
@@ -16,6 +16,7 @@
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/omnibox/location_bar.h"
#include "chrome/browser/ui/search/instant_controller.h"
+#include "chrome/browser/ui/search/instant_overlay_model_observer.h"
#include "chrome/common/search_types.h"
#include "googleurl/src/gurl.h"
#include "net/test/spawned_test_server/spawned_test_server.h"
@@ -29,6 +30,28 @@ namespace content {
class WebContents;
};
+class InstantTestModelObserver : public InstantOverlayModelObserver {
+ public:
+ InstantTestModelObserver(InstantOverlayModel* model,
+ SearchMode::Type expected_mode_type);
+ virtual ~InstantTestModelObserver();
+
+ // Returns the observed mode type, may be different than the
+ // |expected_mode_type_| that was observed in OverlayStateChanged.
+ SearchMode::Type WaitForExpectedOverlayState();
+
+ // Overridden from InstantOverlayModelObserver:
+ virtual void OverlayStateChanged(const InstantOverlayModel& model) OVERRIDE;
+
+ private:
+ InstantOverlayModel* const model_;
+ const SearchMode::Type expected_mode_type_;
+ SearchMode::Type observed_mode_type_;
+ base::RunLoop run_loop_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstantTestModelObserver);
+};
+
// This utility class is meant to be used in a "mix-in" fashion, giving the
// derived test class additional Instant-related functionality.
class InstantTestBase {
@@ -73,6 +96,8 @@ class InstantTestBase {
void FocusOmniboxAndWaitForInstantNTPSupport();
void SetOmniboxText(const std::string& text);
+ bool SetOmniboxTextAndWaitForOverlayToShow(const std::string& text);
+ void SetOmniboxTextAndWaitForSuggestion(const std::string& text);
void PressEnterAndWaitForNavigation();
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index eccd7f7..901387f 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -1738,6 +1738,8 @@
'browser/screensaver_window_finder_x11.h',
'browser/search/iframe_source.cc',
'browser/search/iframe_source.h',
+ 'browser/search/instant_extended_context_menu_observer.cc',
+ 'browser/search/instant_extended_context_menu_observer.h',
'browser/search/instant_io_context.cc',
'browser/search/instant_io_context.h',
'browser/search/instant_service.cc',
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index 90049a97..97b5316 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -1360,6 +1360,7 @@
'browser/ui/sad_tab_types.h',
'browser/ui/screen_capture_notification_ui.h',
'browser/ui/screen_capture_notification_ui_stub.cc',
+ 'browser/ui/search/instant_commit_type.h',
'browser/ui/search/instant_controller.cc',
'browser/ui/search/instant_controller.h',
'browser/ui/search/instant_ipc_sender.cc',
@@ -1368,6 +1369,8 @@
'browser/ui/search/instant_loader.h',
'browser/ui/search/instant_ntp.cc',
'browser/ui/search/instant_ntp.h',
+ 'browser/ui/search/instant_overlay.cc',
+ 'browser/ui/search/instant_overlay.h',
'browser/ui/search/instant_overlay_controller.cc',
'browser/ui/search/instant_overlay_controller.h',
'browser/ui/search/instant_overlay_model.cc',
diff --git a/chrome/common/chrome_notification_types.h b/chrome/common/chrome_notification_types.h
index 3000dea..5c7b8cf 100644
--- a/chrome/common/chrome_notification_types.h
+++ b/chrome/common/chrome_notification_types.h
@@ -1082,6 +1082,17 @@ enum NotificationType {
// Send when a context menu is closed.
NOTIFICATION_RENDER_VIEW_CONTEXT_MENU_CLOSED,
+ // Sent each time the InstantController is updated.
+ NOTIFICATION_INSTANT_CONTROLLER_UPDATED,
+
+ // Sent when an Instant overlay is committed. The Source is the WebContents
+ // containing the committed overlay.
+ NOTIFICATION_INSTANT_COMMITTED,
+
+ // Sent when the Instant Controller determines whether the overlay supports
+ // the Instant API or not.
+ NOTIFICATION_INSTANT_OVERLAY_SUPPORT_DETERMINED,
+
// Sent when the Instant Controller determines whether an Instant tab supports
// the Instant API or not.
NOTIFICATION_INSTANT_TAB_SUPPORT_DETERMINED,
@@ -1094,6 +1105,12 @@ enum NotificationType {
// renderer.
NOTIFICATION_INSTANT_SENT_MOST_VISITED_ITEMS,
+ // Sent when the Instant Controller sets an omnibox suggestion.
+ NOTIFICATION_INSTANT_SET_SUGGESTION,
+
+ // Sent when the Instant Controller has sent autocomplete results.
+ NOTIFICATION_INSTANT_SENT_AUTOCOMPLETE_RESULTS,
+
// Sent when the CaptivePortalService checks if we're behind a captive portal.
// The Source is the Profile the CaptivePortalService belongs to, and the
// Details are a Details<CaptivePortalService::CheckResults>.