diff options
author | fischman@chromium.org <fischman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-03 21:20:42 +0000 |
---|---|---|
committer | fischman@chromium.org <fischman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-03 21:20:42 +0000 |
commit | f46186424021ba2d70ce2c1626da43572bff43d0 (patch) | |
tree | 6f34c140d35ed82858c693049f9215d7ab5cfac9 | |
parent | 97e6692430f166051246395802a26c6b325b54a4 (diff) | |
download | chromium_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
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>. |