diff options
author | jcampan@chromium.org <jcampan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-05 19:28:08 +0000 |
---|---|---|
committer | jcampan@chromium.org <jcampan@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-11-05 19:28:08 +0000 |
commit | 89ac46c97e6a3b2f2726bd11d52824d27ee26a24 (patch) | |
tree | c976f9689ff7fabd3c3e54e06ed051d1bd4544c7 | |
parent | 9ac105a1023bc696e619e5714c524e1a3ffdc2ef (diff) | |
download | chromium_src-89ac46c97e6a3b2f2726bd11d52824d27ee26a24.zip chromium_src-89ac46c97e6a3b2f2726bd11d52824d27ee26a24.tar.gz chromium_src-89ac46c97e6a3b2f2726bd11d52824d27ee26a24.tar.bz2 |
This CL adds the autofill UI in forms.
When the user types text in a text field in a form, the renderer queries the browser for suggestion based on the entered text and displays the suggestions in a popup.
Listeners are set on the form text field in a similar fashion than for password save.
The popup showing the suggestion uses the same mechanism as the select popup. Note that a difference between the select and the autofill popup is that the autofill should not take focus, so the page still has focus and the user can still type in while it shows.
The creation of the render widget was modified for that purpose so we can specify the popup should not be focused when shown.
Review URL: http://codereview.chromium.org/8885
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@4804 0039d316-1c4b-4281-b951-d872f2087c98
41 files changed, 769 insertions, 134 deletions
diff --git a/base/string_util.cc b/base/string_util.cc index 5fa2f75..4c27693 100644 --- a/base/string_util.cc +++ b/base/string_util.cc @@ -659,6 +659,19 @@ bool StartsWithASCII(const std::string& str, return base::strncasecmp(str.c_str(), search.c_str(), search.length()) == 0; } +bool StartsWith(const std::wstring& str, + const std::wstring& search, + bool case_sensitive) { + if (case_sensitive) + return str.compare(0, search.length(), search) == 0; + else { + if (search.size() > str.size()) + return false; + return std::equal(search.begin(), search.end(), str.begin(), + CaseInsensitiveCompare<wchar_t>()); + } +} + DataUnits GetByteDisplayUnits(int64 bytes) { // The byte thresholds at which we display amounts. A byte count is displayed // in unit U when kUnitThresholds[U] <= bytes < kUnitThresholds[U+1]. diff --git a/base/string_util.h b/base/string_util.h index 981cd30..d62e9b0 100644 --- a/base/string_util.h +++ b/base/string_util.h @@ -274,10 +274,12 @@ bool LowerCaseEqualsASCII(const wchar_t* a_begin, const char* b); // Returns true if str starts with search, or false otherwise. -// This only works on ASCII strings. bool StartsWithASCII(const std::string& str, const std::string& search, bool case_sensitive); +bool StartsWith(const std::wstring& str, + const std::wstring& search, + bool case_sensitive); // Determines the type of ASCII character, independent of locale (the C // library versions will change based on locale). diff --git a/base/string_util_unittest.cc b/base/string_util_unittest.cc index 25f43c6..9a7f40f 100644 --- a/base/string_util_unittest.cc +++ b/base/string_util_unittest.cc @@ -1219,11 +1219,27 @@ TEST(StringUtilTest, SplitString) { } TEST(StringUtilTest, StartsWith) { - EXPECT_EQ(true, StartsWithASCII("javascript:url", "javascript", true)); - EXPECT_EQ(true, StartsWithASCII("javascript:url", "javascript", false)); - EXPECT_EQ(true, StartsWithASCII("JavaScript:url", "javascript", false)); - EXPECT_EQ(false, StartsWithASCII("java", "javascript", true)); - EXPECT_EQ(false, StartsWithASCII("java", "javascript", false)); + EXPECT_TRUE(StartsWithASCII("javascript:url", "javascript", true)); + EXPECT_FALSE(StartsWithASCII("JavaScript:url", "javascript", true)); + EXPECT_TRUE(StartsWithASCII("javascript:url", "javascript", false)); + EXPECT_TRUE(StartsWithASCII("JavaScript:url", "javascript", false)); + EXPECT_FALSE(StartsWithASCII("java", "javascript", true)); + EXPECT_FALSE(StartsWithASCII("java", "javascript", false)); + EXPECT_FALSE(StartsWithASCII("", "javascript", false)); + EXPECT_FALSE(StartsWithASCII("", "javascript", true)); + EXPECT_TRUE(StartsWithASCII("java", "", false)); + EXPECT_TRUE(StartsWithASCII("java", "", true)); + + EXPECT_TRUE(StartsWith(L"javascript:url", L"javascript", true)); + EXPECT_FALSE(StartsWith(L"JavaScript:url", L"javascript", true)); + EXPECT_TRUE(StartsWith(L"javascript:url", L"javascript", false)); + EXPECT_TRUE(StartsWith(L"JavaScript:url", L"javascript", false)); + EXPECT_FALSE(StartsWith(L"java", L"javascript", true)); + EXPECT_FALSE(StartsWith(L"java", L"javascript", false)); + EXPECT_FALSE(StartsWith(L"", L"javascript", false)); + EXPECT_FALSE(StartsWith(L"", L"javascript", true)); + EXPECT_TRUE(StartsWith(L"java", L"", false)); + EXPECT_TRUE(StartsWith(L"java", L"", true)); } TEST(StringUtilTest, GetStringFWithOffsets) { diff --git a/chrome/browser/render_view_host.cc b/chrome/browser/render_view_host.cc index 08addb5..963acc9 100644 --- a/chrome/browser/render_view_host.cc +++ b/chrome/browser/render_view_host.cc @@ -680,6 +680,8 @@ void RenderViewHost::OnMessageReceived(const IPC::Message& msg) { IPC_MESSAGE_HANDLER(ViewHostMsg_ShouldClose_ACK, OnMsgShouldCloseACK); IPC_MESSAGE_HANDLER(ViewHostMsg_UnloadListenerChanged, OnUnloadListenerChanged); + IPC_MESSAGE_HANDLER(ViewHostMsg_QueryFormFieldAutofill, + OnQueryFormFieldAutofill) // Have the super handle all other messages. IPC_MESSAGE_UNHANDLED(RenderWidgetHost::OnMessageReceived(msg)) IPC_END_MESSAGE_MAP_EX() @@ -709,10 +711,11 @@ void RenderViewHost::OnMsgCreateWindow(int route_id, view->CreateNewWindow(route_id, modal_dialog_event); } -void RenderViewHost::OnMsgCreateWidget(int route_id) { +void RenderViewHost::OnMsgCreateWidget(int route_id, + bool focus_on_show) { RenderViewHostDelegate::View* view = delegate_->GetViewDelegate(); if (view) - view->CreateNewWidget(route_id); + view->CreateNewWidget(route_id, focus_on_show); } void RenderViewHost::OnMsgShowView(int route_id, @@ -1203,6 +1206,43 @@ void RenderViewHost::OnUnloadListenerChanged(bool has_listener) { has_unload_listener_ = has_listener; } +void RenderViewHost::OnQueryFormFieldAutofill(const std::wstring& field_name, + const std::wstring& user_text, + int64 node_id, + int request_id) { + // TODO(jcampan): this is where the suggestions should be queried from the + // database. The sample code commented below is left here in the meantime for + // testing purpose. +#ifndef TEST_AUTOFILL + static std::vector<std::wstring>* suggestions = NULL; + if (!suggestions) { + suggestions = new std::vector<std::wstring>(); + suggestions->push_back(L"Alice"); + suggestions->push_back(L"Jay"); + suggestions->push_back(L"Jason"); + suggestions->push_back(L"Jasmine"); + suggestions->push_back(L"Jamel"); + suggestions->push_back(L"Jamelo"); + suggestions->push_back(L"Volvo"); + suggestions->push_back(L"Volswagen"); + } + + + std::vector<std::wstring> result; + for (std::vector<std::wstring>::iterator iter = suggestions->begin(); + iter != suggestions->end(); ++iter) { + if (StartsWith(*iter, user_text, false)) + result.push_back(*iter); + } + Send(new ViewMsg_AutofillSuggestions(routing_id_, + node_id, request_id, result, 0)); +#else + Send(new ViewMsg_AutofillSuggestions(routing_id_, + node_id, request_id, + std::vector<std::wstring>(), 0)); +#endif +} + void RenderViewHost::NotifyRendererUnresponsive() { if (is_waiting_for_unload_ack_ && !Singleton<CrossSiteRequestManager>()->HasPendingCrossSiteRequest( diff --git a/chrome/browser/render_view_host.h b/chrome/browser/render_view_host.h index e387171..551c5b8 100644 --- a/chrome/browser/render_view_host.h +++ b/chrome/browser/render_view_host.h @@ -393,7 +393,7 @@ class RenderViewHost : public RenderWidgetHost { // IPC message handlers: void OnMsgCreateWindow(int route_id, HANDLE modal_dialog_event); - void OnMsgCreateWidget(int route_id); + void OnMsgCreateWidget(int route_id, bool focus_on_show); void OnMsgShowView(int route_id, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, @@ -491,7 +491,10 @@ class RenderViewHost : public RenderWidgetHost { const webkit_glue::WebApplicationInfo& info); void OnMsgShouldCloseACK(bool proceed); void OnUnloadListenerChanged(bool has_handler); - + void OnQueryFormFieldAutofill(const std::wstring& field_name, + const std::wstring& user_text, + int64 node_id, + int request_id); virtual void NotifyRendererUnresponsive(); virtual void NotifyRendererResponsive(); diff --git a/chrome/browser/render_view_host_delegate.h b/chrome/browser/render_view_host_delegate.h index 44aa1c99..eea3113 100644 --- a/chrome/browser/render_view_host_delegate.h +++ b/chrome/browser/render_view_host_delegate.h @@ -60,7 +60,10 @@ class RenderViewHostDelegate { // The page is trying to open a new widget (e.g. a select popup). The // widget should be created associated with the given route, but it should // not be shown yet. That should happen in response to ShowCreatedWidget. - virtual void CreateNewWidget(int route_id) = 0; + // If |focus_on_show| is true, the focus is given to the widget when shown, + // otherwise the focus is not changed. + virtual void CreateNewWidget(int route_id, + bool focus_on_show) = 0; // Show a previously created page with the specified disposition and bounds. // The window is identified by the route_id passed to CreateNewWindow. diff --git a/chrome/browser/render_widget_helper.cc b/chrome/browser/render_widget_helper.cc index d6e2bd7..4b87080 100644 --- a/chrome/browser/render_widget_helper.cc +++ b/chrome/browser/render_widget_helper.cc @@ -218,9 +218,11 @@ void RenderWidgetHelper::CreateNewWindow(int opener_id, this, &RenderWidgetHelper::OnSimulateReceivedMessage, msg)); } -void RenderWidgetHelper::CreateNewWidget(int opener_id, int* route_id) { +void RenderWidgetHelper::CreateNewWidget(int opener_id, + bool focus_on_show, + int* route_id) { *route_id = GetNextRoutingID(); - ViewHostMsg_CreateWidgetWithRoute msg(opener_id, *route_id); + ViewHostMsg_CreateWidgetWithRoute msg(opener_id, *route_id, focus_on_show); ui_loop_->PostTask(FROM_HERE, NewRunnableMethod( this, &RenderWidgetHelper::OnSimulateReceivedMessage, msg)); } diff --git a/chrome/browser/render_widget_helper.h b/chrome/browser/render_widget_helper.h index 7b6295f..c09462a 100644 --- a/chrome/browser/render_widget_helper.h +++ b/chrome/browser/render_widget_helper.h @@ -113,7 +113,7 @@ class RenderWidgetHelper : void CreateNewWindow(int opener_id, bool user_gesture, int* route_id, HANDLE* modal_dialog_event, HANDLE render_process); - void CreateNewWidget(int opener_id, int* route_id); + void CreateNewWidget(int opener_id, bool focus_on_show, int* route_id); private: // A class used to proxy a paint message. PaintMsgProxy objects are created diff --git a/chrome/browser/render_widget_host_view_win.cc b/chrome/browser/render_widget_host_view_win.cc index 05eb2a2..ab9275a 100644 --- a/chrome/browser/render_widget_host_view_win.cc +++ b/chrome/browser/render_widget_host_view_win.cc @@ -65,10 +65,8 @@ RenderWidgetHostView* RenderWidgetHostView::CreateViewForWidget( /////////////////////////////////////////////////////////////////////////////// // RenderWidgetHostViewWin, public: -RenderWidgetHostViewWin::RenderWidgetHostViewWin( - RenderWidgetHost* widget) - : RenderWidgetHostView(), - render_widget_host_(widget), +RenderWidgetHostViewWin::RenderWidgetHostViewWin(RenderWidgetHost* widget) + : render_widget_host_(widget), real_cursor_(LoadCursor(NULL, IDC_ARROW)), real_cursor_type_(WebCursor::ARROW), track_mouse_leave_(false), @@ -79,7 +77,8 @@ RenderWidgetHostViewWin::RenderWidgetHostViewWin( tooltip_showing_(false), shutdown_factory_(this), parent_hwnd_(NULL), - is_loading_(false) { + is_loading_(false), + focus_on_show_(true) { render_widget_host_->set_view(this); renderer_accessible_ = CommandLine().HasSwitch(switches::kEnableRendererAccessibility); diff --git a/chrome/browser/render_widget_host_view_win.h b/chrome/browser/render_widget_host_view_win.h index 348e21c..1d15674 100644 --- a/chrome/browser/render_widget_host_view_win.h +++ b/chrome/browser/render_widget_host_view_win.h @@ -65,6 +65,11 @@ class RenderWidgetHostViewWin : close_on_deactivate_ = close_on_deactivate; } + void set_focus_on_show(bool focus) { + focus_on_show_ = focus; + } + bool focus_on_show() const { return focus_on_show_; } + void set_parent_hwnd(HWND parent) { parent_hwnd_ = parent; } DECLARE_WND_CLASS_EX(kRenderWidgetHostHWNDClass, CS_DBLCLKS, 0); @@ -259,6 +264,9 @@ class RenderWidgetHostViewWin : // value returns true for is_null() if we are not recording whiteout times. base::TimeTicks whiteout_start_time_; + // Whether the window should get focus when shown. Default is true. + bool focus_on_show_; + // Whether the renderer is made accessible. // TODO(jcampan): http://b/issue?id=1432077 This is a temporary work-around // until that bug is fixed. diff --git a/chrome/browser/resource_message_filter.cc b/chrome/browser/resource_message_filter.cc index 2e25e92..24eb964 100644 --- a/chrome/browser/resource_message_filter.cc +++ b/chrome/browser/resource_message_filter.cc @@ -234,8 +234,10 @@ void ResourceMessageFilter::OnMsgCreateWindow(int opener_id, modal_dialog_event, render_handle_); } -void ResourceMessageFilter::OnMsgCreateWidget(int opener_id, int* route_id) { - render_widget_helper_->CreateNewWidget(opener_id, route_id); +void ResourceMessageFilter::OnMsgCreateWidget(int opener_id, + bool focus_on_show, + int* route_id) { + render_widget_helper_->CreateNewWidget(opener_id, focus_on_show, route_id); } void ResourceMessageFilter::OnRequestResource( diff --git a/chrome/browser/resource_message_filter.h b/chrome/browser/resource_message_filter.h index 7dd3513..1b068e4 100644 --- a/chrome/browser/resource_message_filter.h +++ b/chrome/browser/resource_message_filter.h @@ -77,7 +77,7 @@ class ResourceMessageFilter : public IPC::ChannelProxy::MessageFilter, private: void OnMsgCreateWindow(int opener_id, bool user_gesture, int* route_id, HANDLE* modal_dialog_event); - void OnMsgCreateWidget(int opener_id, int* route_id); + void OnMsgCreateWidget(int opener_id, bool focus_on_show, int* route_id); void OnRequestResource(const IPC::Message& msg, int request_id, const ViewHostMsg_Resource_Request& request); void OnCancelRequest(int request_id); diff --git a/chrome/browser/web_contents_view.cc b/chrome/browser/web_contents_view.cc index 28dd88b..931e36c 100644 --- a/chrome/browser/web_contents_view.cc +++ b/chrome/browser/web_contents_view.cc @@ -10,9 +10,11 @@ void WebContentsView::CreateNewWindow(int route_id, HANDLE modal_dialog_event) { modal_dialog_event); } -void WebContentsView::CreateNewWidget(int route_id) { +void WebContentsView::CreateNewWidget(int route_id, + bool focus_on_show) { // Save the created widget associated with the route so we can show it later. - pending_widget_views_[route_id] = CreateNewWidgetInternal(route_id); + pending_widget_views_[route_id] = CreateNewWidgetInternal(route_id, + focus_on_show); } void WebContentsView::ShowCreatedWindow(int route_id, diff --git a/chrome/browser/web_contents_view.h b/chrome/browser/web_contents_view.h index 6ebf84c..e6b24c2 100644 --- a/chrome/browser/web_contents_view.h +++ b/chrome/browser/web_contents_view.h @@ -172,7 +172,8 @@ class WebContentsView : public RenderViewHostDelegate::View { // the Show functions rather than the route ID. virtual WebContents* CreateNewWindowInternal(int route_id, HANDLE modal_dialog_event) = 0; - virtual RenderWidgetHostView* CreateNewWidgetInternal(int route_id) = 0; + virtual RenderWidgetHostView* CreateNewWidgetInternal(int route_id, + bool focus_on_show) = 0; virtual void ShowCreatedWindowInternal(WebContents* new_web_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, @@ -185,7 +186,8 @@ class WebContentsView : public RenderViewHostDelegate::View { // do some book-keeping associated with the request. The request is then // forwarded to *Internal which does platform-specific work. virtual void CreateNewWindow(int route_id, HANDLE modal_dialog_event); - virtual void CreateNewWidget(int route_id); + virtual void CreateNewWidget(int route_id, + bool focus_on_show); virtual void ShowCreatedWindow(int route_id, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, diff --git a/chrome/browser/web_contents_view_win.cc b/chrome/browser/web_contents_view_win.cc index 082fb89..3ba7173 100644 --- a/chrome/browser/web_contents_view_win.cc +++ b/chrome/browser/web_contents_view_win.cc @@ -378,7 +378,8 @@ WebContents* WebContentsViewWin::CreateNewWindowInternal( } RenderWidgetHostView* WebContentsViewWin::CreateNewWidgetInternal( - int route_id) { + int route_id, + bool focus_on_show) { // Create the widget and its associated view. // TODO(brettw) can widget creation be cross-platform? RenderWidgetHost* widget_host = @@ -394,6 +395,7 @@ RenderWidgetHostView* WebContentsViewWin::CreateNewWidgetInternal( widget_view->set_parent_hwnd( web_contents_->render_widget_host_view()->GetPluginHWND()); widget_view->set_close_on_deactivate(true); + widget_view->set_focus_on_show(focus_on_show); return widget_view; } @@ -438,7 +440,8 @@ void WebContentsViewWin::ShowCreatedWidgetInternal( widget_host_view_win->MoveWindow(initial_pos.x(), initial_pos.y(), initial_pos.width(), initial_pos.height(), TRUE); - widget_host_view_win->ShowWindow(SW_SHOW); + widget_host_view_win->ShowWindow(widget_host_view_win->focus_on_show() ? + SW_SHOW : SW_SHOWNOACTIVATE); widget_host->Init(); } diff --git a/chrome/browser/web_contents_view_win.h b/chrome/browser/web_contents_view_win.h index d1ea193..ca03829 100644 --- a/chrome/browser/web_contents_view_win.h +++ b/chrome/browser/web_contents_view_win.h @@ -56,7 +56,8 @@ class WebContentsViewWin : public WebContentsView, // Backend implementation of RenderViewHostDelegate::View. virtual WebContents* CreateNewWindowInternal( int route_id, HANDLE modal_dialog_event); - virtual RenderWidgetHostView* CreateNewWidgetInternal(int route_id); + virtual RenderWidgetHostView* CreateNewWidgetInternal(int route_id, + bool focus_on_show); virtual void ShowCreatedWindowInternal(WebContents* new_web_contents, WindowOpenDisposition disposition, const gfx::Rect& initial_pos, diff --git a/chrome/common/render_messages_internal.h b/chrome/common/render_messages_internal.h index ea97bc7..4e7e3dc 100644 --- a/chrome/common/render_messages_internal.h +++ b/chrome/common/render_messages_internal.h @@ -462,6 +462,14 @@ IPC_BEGIN_MESSAGES(View, 1) // into a full window). IPC_MESSAGE_ROUTED0(ViewMsg_DisassociateFromPopupCount) + // Reply to the ViewHostMsg_QueryFormFieldAutofill message with the autofill + // suggestions. + IPC_MESSAGE_ROUTED4(ViewMsg_AutofillSuggestions, + int64 /* id of the text input field */, + int /* id of the request message */, + std::vector<std::wstring> /* suggestions */, + int /* index of default suggestion */) + IPC_END_MESSAGES(View) @@ -483,8 +491,9 @@ IPC_BEGIN_MESSAGES(ViewHost, 2) // Similar to ViewHostMsg_CreateView, except used for sub-widgets, like // <select> dropdowns. This message is sent to the WebContents that // contains the widget being created. - IPC_SYNC_MESSAGE_CONTROL1_1(ViewHostMsg_CreateWidget, + IPC_SYNC_MESSAGE_CONTROL2_1(ViewHostMsg_CreateWidget, int /* opener_id */, + bool /* focus on show */, int /* route_id */) // These two messages are sent as a result of the above two, in the browser @@ -493,8 +502,9 @@ IPC_BEGIN_MESSAGES(ViewHost, 2) int /* route_id */, HANDLE /* modal_dialog_event */) - IPC_MESSAGE_ROUTED1(ViewHostMsg_CreateWidgetWithRoute, - int /* route_id */) + IPC_MESSAGE_ROUTED2(ViewHostMsg_CreateWidgetWithRoute, + int /* route_id */, + bool /* focus on show */) // These two messages are sent to the parent RenderViewHost to display the // page/widget that was created by CreateView/CreateWidget. routing_id @@ -1061,4 +1071,11 @@ IPC_BEGIN_MESSAGES(ViewHost, 2) HWND /* window */, gfx::Rect /* Out: Window location */) + // Queries the browser for suggestion for autofill in a form input field. + IPC_MESSAGE_ROUTED4(ViewHostMsg_QueryFormFieldAutofill, + std::wstring /* field name */, + std::wstring /* user entered text */, + int64 /* id of the text input field */, + int /* id of this message */) + IPC_END_MESSAGES(ViewHost) diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index 7cebc16..cbae9d0 100644 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -142,7 +142,7 @@ class RenderViewExtraRequestData : public WebRequest::ExtraData { /////////////////////////////////////////////////////////////////////////////// RenderView::RenderView() - : RenderWidget(RenderThread::current()), + : RenderWidget(RenderThread::current(), true), is_loading_(false), page_id_(-1), last_page_id_sent_to_browser_(-1), @@ -161,7 +161,8 @@ RenderView::RenderView() has_unload_listener_(false), decrement_shared_popup_at_destruction_(false), greasemonkey_enabled_(false), - waiting_for_create_window_ack_(false) { + waiting_for_create_window_ack_(false), + form_field_autofill_request_id_(0) { resource_dispatcher_ = new ResourceDispatcher(this); #ifdef CHROME_PERSONALIZATION personalization_ = Personalization::CreateRendererPersonalization(); @@ -384,6 +385,8 @@ void RenderView::OnMessageReceived(const IPC::Message& message) { OnMessageFromExternalHost) IPC_MESSAGE_HANDLER(ViewMsg_DisassociateFromPopupCount, OnDisassociateFromPopupCount) + IPC_MESSAGE_HANDLER(ViewMsg_AutofillSuggestions, + OnReceivedAutofillSuggestions) // Have the super handle all other messages. IPC_MESSAGE_UNHANDLED(RenderWidget::OnMessageReceived(message)) IPC_END_MESSAGE_MAP() @@ -1664,6 +1667,29 @@ void RenderView::OnUnloadListenerChanged(WebView* webview, WebFrame* webframe) { } } +void RenderView::QueryFormFieldAutofill(const std::wstring& field_name, + const std::wstring& text, + int64 node_id) { + static int message_id_counter = 0; + form_field_autofill_request_id_ = message_id_counter++; + Send(new ViewHostMsg_QueryFormFieldAutofill(routing_id_, + field_name, text, + node_id, + form_field_autofill_request_id_)); +} + +void RenderView::OnReceivedAutofillSuggestions( + int64 node_id, + int request_id, + const std::vector<std::wstring> suggestions, + int default_suggestion_index) { + if (!webview() || request_id != form_field_autofill_request_id_) + return; + + webview()->AutofillSuggestionsForNode(node_id, suggestions, + default_suggestion_index); +} + void RenderView::ShowModalHTMLDialog(const GURL& url, int width, int height, const std::string& json_arguments, std::string* json_retval) { @@ -1762,9 +1788,11 @@ WebView* RenderView::CreateWebView(WebView* webview, bool user_gesture) { return view->webview(); } -WebWidget* RenderView::CreatePopupWidget(WebView* webview) { +WebWidget* RenderView::CreatePopupWidget(WebView* webview, + bool focus_on_show) { RenderWidget* widget = RenderWidget::Create(routing_id_, - RenderThread::current()); + RenderThread::current(), + focus_on_show); return widget->webwidget(); } diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h index eee08e1..8484afd 100644 --- a/chrome/renderer/render_view.h +++ b/chrome/renderer/render_view.h @@ -127,6 +127,9 @@ class RenderView : public RenderWidget, public WebViewDelegate, virtual bool RunBeforeUnloadConfirm(WebView* webview, const std::wstring& message); virtual void OnUnloadListenerChanged(WebView* webview, WebFrame* webframe); + virtual void QueryFormFieldAutofill(const std::wstring& field_name, + const std::wstring& text, + int64 node_id); virtual void UpdateTargetURL(WebView* webview, const GURL& url); virtual void RunFileChooser(const std::wstring& default_filename, @@ -197,7 +200,8 @@ class RenderView : public RenderWidget, public WebViewDelegate, bool is_redirect); virtual WebView* CreateWebView(WebView* webview, bool user_gesture); - virtual WebWidget* CreatePopupWidget(WebView* webview); + virtual WebWidget* CreatePopupWidget(WebView* webview, + bool focus_on_show); virtual WebPluginDelegate* CreatePluginDelegate( WebView* webview, const GURL& url, @@ -457,6 +461,13 @@ class RenderView : public RenderWidget, public WebViewDelegate, // Notification about ui theme changes. void OnThemeChanged(); + // Notification that we have received autofill suggestion. + void OnReceivedAutofillSuggestions( + int64 node_id, + int request_id, + const std::vector<std::wstring> suggestions, + int default_suggestion_index); + #ifdef CHROME_PERSONALIZATION void OnPersonalizationEvent(std::string event_name, std::string event_args); #endif @@ -673,6 +684,10 @@ class RenderView : public RenderWidget, public WebViewDelegate, // Set if we are waiting for an ack for ViewHostMsg_CreateWindow bool waiting_for_create_window_ack_; + // The id of the last request sent for form field autofill. Used to ignore + // out of date responses. + int form_field_autofill_request_id_; + // A cached WebHistoryItem used for back/forward navigations initiated by // WebCore (via the window.history.go API). We only have one such navigation // pending at a time. diff --git a/chrome/renderer/render_widget.cc b/chrome/renderer/render_widget.cc index 874768b..c8d3070 100644 --- a/chrome/renderer/render_widget.cc +++ b/chrome/renderer/render_widget.cc @@ -69,7 +69,8 @@ DeferredCloses* DeferredCloses::current_ = NULL; /////////////////////////////////////////////////////////////////////////////// -RenderWidget::RenderWidget(RenderThreadBase* render_thread) +RenderWidget::RenderWidget(RenderThreadBase* render_thread, + bool focus_on_show) : routing_id_(MSG_ROUTING_NONE), opener_id_(MSG_ROUTING_NONE), render_thread_(render_thread), @@ -88,7 +89,8 @@ RenderWidget::RenderWidget(RenderThreadBase* render_thread) ime_control_x_(-1), ime_control_y_(-1), ime_control_new_state_(false), - ime_control_updated_(false) { + ime_control_updated_(false), + focus_on_show_(focus_on_show) { RenderProcess::AddRefProcess(); DCHECK(render_thread_); } @@ -107,9 +109,11 @@ RenderWidget::~RenderWidget() { /*static*/ RenderWidget* RenderWidget::Create(int32 opener_id, - RenderThreadBase* render_thread) { + RenderThreadBase* render_thread, + bool focus_on_show) { DCHECK(opener_id != MSG_ROUTING_NONE); - scoped_refptr<RenderWidget> widget = new RenderWidget(render_thread); + scoped_refptr<RenderWidget> widget = new RenderWidget(render_thread, + focus_on_show); widget->Init(opener_id); // adds reference return widget; } @@ -125,7 +129,7 @@ void RenderWidget::Init(int32 opener_id) { webwidget_.swap(&webwidget); bool result = render_thread_->Send( - new ViewHostMsg_CreateWidget(opener_id, &routing_id_)); + new ViewHostMsg_CreateWidget(opener_id, focus_on_show_, &routing_id_)); if (result) { render_thread_->AddRoute(routing_id_, this); // Take a reference on behalf of the RenderThread. This will be balanced diff --git a/chrome/renderer/render_widget.h b/chrome/renderer/render_widget.h index 98d0377..f597dff 100644 --- a/chrome/renderer/render_widget.h +++ b/chrome/renderer/render_widget.h @@ -30,7 +30,9 @@ class RenderWidget : public IPC::Channel::Listener, // Creates a new RenderWidget. The opener_id is the routing ID of the // RenderView that this widget lives inside. The render_thread is any // RenderThreadBase implementation, mostly commonly RenderThread::current(). - static RenderWidget* Create(int32 opener_id, RenderThreadBase* render_thread); + static RenderWidget* Create(int32 opener_id, + RenderThreadBase* render_thread, + bool focus_on_show); // The routing ID assigned by the RenderProcess. Will be MSG_ROUTING_NONE if // not yet assigned a view ID, in which case, the process MUST NOT send @@ -86,7 +88,7 @@ class RenderWidget : public IPC::Channel::Listener, // without ref-counting is an error. friend class base::RefCounted<RenderWidget>; - RenderWidget(RenderThreadBase* render_thread); + RenderWidget(RenderThreadBase* render_thread, bool focus_on_show); virtual ~RenderWidget(); // Initializes this view with the given opener. CompleteInit must be called @@ -261,6 +263,9 @@ class RenderWidget : public IPC::Channel::Listener, bool ime_control_new_state_; bool ime_control_updated_; + // Whether the window for this RenderWidget should be focused when shown. + bool focus_on_show_; + // Holds all the needed plugin window moves for a scroll. std::vector<WebPluginGeometry> plugin_window_moves_; diff --git a/chrome/renderer/render_widget_unittest.cc b/chrome/renderer/render_widget_unittest.cc index 086f8bf..f280aa7 100644 --- a/chrome/renderer/render_widget_unittest.cc +++ b/chrome/renderer/render_widget_unittest.cc @@ -114,7 +114,9 @@ class MockRenderThread : public RenderThreadBase { } // The Widget expects to be returned valid route_id. - void OnMsgCreateWidget(int opener_id, int* route_id) { + void OnMsgCreateWidget(int opener_id, + bool focus_on_show, + int* route_id) { opener_id_ = opener_id; *route_id = routing_id_; } @@ -141,7 +143,7 @@ TEST(RenderWidgetTest, CreateAndCloseWidget) { { scoped_refptr<RenderWidget> rw = - RenderWidget::Create(kOpenerId, &render_thread); + RenderWidget::Create(kOpenerId, &render_thread, true); ASSERT_TRUE(rw != NULL); // After the RenderWidget it must have sent a message to the render thread diff --git a/webkit/build/glue/glue.vcproj b/webkit/build/glue/glue.vcproj index 28fe820..3ce56f3 100644 --- a/webkit/build/glue/glue.vcproj +++ b/webkit/build/glue/glue.vcproj @@ -381,6 +381,14 @@ > </File> <File + RelativePath="..\..\glue\form_autocomplete_listener.cc" + > + </File> + <File + RelativePath="..\..\glue\form_autocomplete_listener.h" + > + </File> + <File RelativePath="..\..\glue\glue_accessibility.cc" > </File> diff --git a/webkit/build/port/port.vcproj b/webkit/build/port/port.vcproj index c91d3ba..7464c87 100644 --- a/webkit/build/port/port.vcproj +++ b/webkit/build/port/port.vcproj @@ -807,6 +807,10 @@ > </File> <File + RelativePath="..\..\port\platform\chromium\PopupMenuChromium.h" + > + </File> + <File RelativePath="..\..\port\platform\chromium\SearchPopupMenuChromium.cpp" > </File> diff --git a/webkit/glue/SConscript b/webkit/glue/SConscript index 42dfec6..61d3aa0 100644 --- a/webkit/glue/SConscript +++ b/webkit/glue/SConscript @@ -35,6 +35,7 @@ input_files = [ 'editor_client_impl.cc', 'entity_map.cc', 'event_conversion.cc', + 'form_autocomplete_listener.cc', 'feed_preview.cc', 'glue_util.cc', 'glue_serialize.cc', diff --git a/webkit/glue/autocomplete_input_listener.h b/webkit/glue/autocomplete_input_listener.h index 56233a3..45ad993 100644 --- a/webkit/glue/autocomplete_input_listener.h +++ b/webkit/glue/autocomplete_input_listener.h @@ -16,7 +16,6 @@ MSVC_PUSH_WARNING_LEVEL(0); #include "EventListener.h" MSVC_POP_WARNING(); -#undef LOG #include "base/basictypes.h" #include "base/scoped_ptr.h" diff --git a/webkit/glue/chrome_client_impl.cc b/webkit/glue/chrome_client_impl.cc index 618869d..b8cb4dd 100644 --- a/webkit/glue/chrome_client_impl.cc +++ b/webkit/glue/chrome_client_impl.cc @@ -449,12 +449,14 @@ void ChromeClientImpl::runFileChooser(const WebCore::String& default_path, delegate->RunFileChooser(suggestion, chooser); } -void ChromeClientImpl::popupOpened( - WebCore::FramelessScrollView* popup_view, const WebCore::IntRect& bounds) { +void ChromeClientImpl::popupOpened(WebCore::FramelessScrollView* popup_view, + const WebCore::IntRect& bounds, + bool focus_on_show) { WebViewDelegate* d = webview_->delegate(); if (d) { WebWidgetImpl* webwidget = - static_cast<WebWidgetImpl*>(d->CreatePopupWidget(webview_)); + static_cast<WebWidgetImpl*>(d->CreatePopupWidget(webview_, + focus_on_show)); webwidget->Init(popup_view, webkit_glue::FromIntRect(bounds)); } } diff --git a/webkit/glue/chrome_client_impl.h b/webkit/glue/chrome_client_impl.h index acecb44..7da89d3 100644 --- a/webkit/glue/chrome_client_impl.h +++ b/webkit/glue/chrome_client_impl.h @@ -111,7 +111,8 @@ public: virtual void runFileChooser(const WebCore::String&, PassRefPtr<WebCore::FileChooser>); virtual void popupOpened(WebCore::FramelessScrollView* popup_view, - const WebCore::IntRect& bounds); + const WebCore::IntRect& bounds, + bool focus_on_show); virtual void setCursor(const WebCore::Cursor&); private: diff --git a/webkit/glue/form_autocomplete_listener.cc b/webkit/glue/form_autocomplete_listener.cc new file mode 100644 index 0000000..4c17527 --- /dev/null +++ b/webkit/glue/form_autocomplete_listener.cc @@ -0,0 +1,35 @@ +// Copyright (c) 2006-2008 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 "webkit/glue/form_autocomplete_listener.h" + +MSVC_PUSH_WARNING_LEVEL(0); +#include "HTMLInputElement.h" +MSVC_POP_WARNING(); + +#undef LOG + +#include "webkit/glue/autocomplete_input_listener.h" +#include "webkit/glue/glue_util.h" +#include "webkit/glue/webview_delegate.h" + +namespace webkit_glue { + +FormAutocompleteListener::FormAutocompleteListener( + WebViewDelegate* webview_delegate, + WebCore::HTMLInputElement* input_element) + : AutocompleteInputListener(new HTMLInputDelegate(input_element)), + webview_delegate_(webview_delegate), + name_(webkit_glue::StringToStdWString(input_element->name().string())), + node_id_(reinterpret_cast<int64>(input_element)) { + DCHECK(input_element->isTextField() && !input_element->isPasswordField() && + input_element->autoComplete()); +} + +void FormAutocompleteListener::OnInlineAutocompleteNeeded( + const std::wstring& user_input) { + webview_delegate_->QueryFormFieldAutofill(name_, user_input, node_id_); +} + +} // namespace diff --git a/webkit/glue/form_autocomplete_listener.h b/webkit/glue/form_autocomplete_listener.h new file mode 100644 index 0000000..e55522e --- /dev/null +++ b/webkit/glue/form_autocomplete_listener.h @@ -0,0 +1,46 @@ +// Copyright (c) 2006-2008 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 WEBKIT_GLUE_FORM_AUTOCOMPLETE_LISTENER_ +#define WEBKIT_GLUE_FORM_AUTOCOMPLETE_LISTENER_ + +#include <string> + +#include "webkit/glue/autocomplete_input_listener.h" + +class WebViewDelegate; + +namespace webkit_glue { + +// This class listens for the user typing in a text input in a form and queries +// the browser for autofill information. + +class FormAutocompleteListener : public AutocompleteInputListener { + public: + FormAutocompleteListener(WebViewDelegate* webview_delegate, + WebCore::HTMLInputElement* input_element); + virtual ~FormAutocompleteListener() { } + + // AutocompleteInputListener implementation. + virtual void OnBlur(const std::wstring& user_input) { } + virtual void OnInlineAutocompleteNeeded(const std::wstring& user_input); + + private: + // The delegate associated with the WebView that contains thhe input we are + // listening to. + WebViewDelegate* webview_delegate_; + + // The name of the input node we are listening to. + std::wstring name_; + + // An id to represent the input element. That ID is passed to the call that + // queries for suggestions. + int64 node_id_; + + DISALLOW_COPY_AND_ASSIGN(FormAutocompleteListener); +}; + +} // webkit_glue + +#endif // WEBKIT_GLUE_FORM_AUTOCOMPLETE_LISTENER_ diff --git a/webkit/glue/password_autocomplete_listener.cc b/webkit/glue/password_autocomplete_listener.cc index aaf6125..0a88ab3 100644 --- a/webkit/glue/password_autocomplete_listener.cc +++ b/webkit/glue/password_autocomplete_listener.cc @@ -6,6 +6,7 @@ // component. #include "webkit/glue/password_autocomplete_listener.h" +#undef LOG #include "base/logging.h" namespace webkit_glue { diff --git a/webkit/glue/webframeloaderclient_impl.cc b/webkit/glue/webframeloaderclient_impl.cc index f178554..2a46af8 100644 --- a/webkit/glue/webframeloaderclient_impl.cc +++ b/webkit/glue/webframeloaderclient_impl.cc @@ -16,6 +16,9 @@ MSVC_PUSH_WARNING_LEVEL(0); #include "Element.h" #include "HistoryItem.h" #include "HTMLFormElement.h" // needed by FormState.h +#include "HTMLFormControlElement.h" +#include "HTMLInputElement.h" +#include "HTMLNames.h" #include "FormState.h" #include "FrameLoader.h" #include "FrameLoadRequest.h" @@ -39,8 +42,9 @@ MSVC_POP_WARNING(); #if defined(OS_WIN) #include "webkit/activex_shim/activex_shared.h" #endif -#include "webkit/glue/webframeloaderclient_impl.h" #include "webkit/glue/alt_404_page_resource_fetcher.h" +#include "webkit/glue/autocomplete_input_listener.h" +#include "webkit/glue/form_autocomplete_listener.h" #include "webkit/glue/glue_util.h" #include "webkit/glue/password_form_dom_manager.h" #include "webkit/glue/plugins/plugin_list.h" @@ -48,6 +52,7 @@ MSVC_POP_WARNING(); #include "webkit/glue/webdatasource_impl.h" #include "webkit/glue/webdocumentloader_impl.h" #include "webkit/glue/weberror_impl.h" +#include "webkit/glue/webframeloaderclient_impl.h" #include "webkit/glue/webhistoryitem_impl.h" #include "webkit/glue/webkit_glue.h" #include "webkit/glue/webplugin_impl.h" @@ -351,12 +356,28 @@ void WebFrameLoaderClient::dispatchDidFinishDocumentLoad() { if (!form->autoComplete()) continue; + std::set<std::wstring> password_related_fields; scoped_ptr<PasswordForm> data( PasswordFormDomManager::CreatePasswordForm(form)); - if (data.get()) + if (data.get()) { actions.push_back(*data); + // Let's remember the names of password related fields so we do not + // autofill them with the regular form autofill. + DCHECK(!data->username_element.empty()); + DCHECK(!data->password_element.empty()); + password_related_fields.insert(data->username_element); + password_related_fields.insert(data->password_element); + if (!data->old_password_element.empty()) + password_related_fields.insert(data->old_password_element); + } + + // Now let's register for any text input. + // TODO(jcampan): bug #3847 merge password and form autofill so we + // traverse the form elements only once. + RegisterAutofillListeners(form, password_related_fields); } } + if (d && (actions.size() > 0)) d->OnPasswordFormsSeen(webview, actions); if (d) @@ -685,6 +706,40 @@ NavigationGesture WebFrameLoaderClient::NavigationGestureForLastLoad() { NavigationGestureAuto; } +void WebFrameLoaderClient::RegisterAutofillListeners( + WebCore::HTMLFormElement* form, + const std::set<std::wstring>& excluded_fields) { + + WebViewDelegate* webview_delegate = webframe_->webview_impl()->delegate(); + if (!webview_delegate) + return; + + for (size_t i = 0; i < form->formElements.size(); i++) { + WebCore::HTMLFormControlElement* form_element = form->formElements[i]; + if (!form_element->hasLocalName(WebCore::HTMLNames::inputTag)) + continue; + + WebCore::HTMLInputElement* input_element = + static_cast<WebCore::HTMLInputElement*>(form_element); + if (!input_element->isEnabled() || !input_element->isTextField() || + input_element->isPasswordField() || !input_element->autoComplete()) { + continue; + } + + std::wstring name = webkit_glue::StringToStdWString(input_element->name()); + if (excluded_fields.find(name) != excluded_fields.end()) + continue; + +#if !defined(OS_MACOSX) + // FIXME on Mac + webkit_glue::FormAutocompleteListener* listener = + new webkit_glue::FormAutocompleteListener(webview_delegate, + input_element); + webkit_glue::AttachForInlineAutocomplete(input_element, listener); +#endif + } +} + void WebFrameLoaderClient::dispatchDidReceiveTitle(const String& title) { WebViewImpl* webview = webframe_->webview_impl(); WebViewDelegate* d = webview->delegate(); @@ -1477,4 +1532,3 @@ bool WebFrameLoaderClient::ActionSpecifiesDisposition( *disposition = shift ? NEW_WINDOW : SAVE_TO_DISK; return true; } - diff --git a/webkit/glue/webframeloaderclient_impl.h b/webkit/glue/webframeloaderclient_impl.h index 20b12ee..6010ba8 100644 --- a/webkit/glue/webframeloaderclient_impl.h +++ b/webkit/glue/webframeloaderclient_impl.h @@ -5,6 +5,8 @@ #ifndef WEBKIT_GLUE_WEBFRAMELOADERCLIENT_IMPL_H__ #define WEBKIT_GLUE_WEBFRAMELOADERCLIENT_IMPL_H__ +#include <set> + #include "base/compiler_specific.h" MSVC_PUSH_WARNING_LEVEL(0); @@ -18,6 +20,7 @@ MSVC_POP_WARNING(); namespace WebCore { class Frame; +class HTMLFormElement; class Widget; } @@ -209,6 +212,11 @@ class WebFrameLoaderClient : public WebCore::FrameLoaderClient { // otherwise returns NavigationGestureUnknown. NavigationGesture NavigationGestureForLastLoad(); + // Registers the text input fields in the passed form for autofill, with the + // exclusion of any field whose name is contained in |excluded_fields|. + void RegisterAutofillListeners(WebCore::HTMLFormElement* form, + const std::set<std::wstring>& excluded_fields); + // The WebFrame that owns this object and manages its lifetime. Therefore, // the web frame object is guaranteed to exist. WebFrameImpl* webframe_; diff --git a/webkit/glue/webplugin_impl_mac.mm b/webkit/glue/webplugin_impl_mac.mm index 191ebb7..1a420ea 100644 --- a/webkit/glue/webplugin_impl_mac.mm +++ b/webkit/glue/webplugin_impl_mac.mm @@ -4,6 +4,9 @@ #include "config.h" +#include "wtf/ASCIICType.h" + +#undef LOG #include "webkit/glue/webplugin_impl.h" // TODO(pinkerton): all of this needs to be filled in. webplugin_impl.cc has diff --git a/webkit/glue/webview.h b/webkit/glue/webview.h index e47ad0e..1c67c4f4 100644 --- a/webkit/glue/webview.h +++ b/webkit/glue/webview.h @@ -6,6 +6,7 @@ #define WEBKIT_GLUE_WEBVIEW_H__ #include <string> +#include <vector> #include "base/basictypes.h" #include "base/ref_counted.h" @@ -195,6 +196,12 @@ class WebView : public WebWidget { virtual void DragTargetDrop( int client_x, int client_y, int screen_x, int screen_y) = 0; + // Notifies the webview that autofill suggestions are available for a node. + virtual void AutofillSuggestionsForNode( + int64 node_id, + const std::vector<std::wstring>& suggestions, + int default_suggestion_index) = 0; + private: DISALLOW_EVIL_CONSTRUCTORS(WebView); }; diff --git a/webkit/glue/webview_delegate.h b/webkit/glue/webview_delegate.h index b9f2b9e..415510e 100644 --- a/webkit/glue/webview_delegate.h +++ b/webkit/glue/webview_delegate.h @@ -107,7 +107,8 @@ class WebViewDelegate : virtual public WebWidgetDelegate { // This method is called to create a new WebWidget to act as a popup // (like a drop-down menu). - virtual WebWidget* CreatePopupWidget(WebView* webview) { + virtual WebWidget* CreatePopupWidget(WebView* webview, + bool focus_on_show) { return NULL; } @@ -450,6 +451,14 @@ class WebViewDelegate : virtual public WebWidgetDelegate { virtual void OnUnloadListenerChanged(WebView* webview, WebFrame* webframe) { } + // Queries the browser for suggestions to be shown for the form text field + // named |field_name|. |text| is the text entered by the user so far and + // |node_id| is the id of the node of the input field. + virtual void QueryFormFieldAutofill(const std::wstring& field_name, + const std::wstring& text, + int64 node_id) { + } + // UIDelegate -------------------------------------------------------------- // Asks the browser to show a modal HTML dialog. The dialog is passed the diff --git a/webkit/glue/webview_impl.cc b/webkit/glue/webview_impl.cc index 814d168..5d3101c 100644 --- a/webkit/glue/webview_impl.cc +++ b/webkit/glue/webview_impl.cc @@ -35,6 +35,7 @@ #include "base/compiler_specific.h" MSVC_PUSH_WARNING_LEVEL(0); +#include "CSSStyleSelector.h" #if defined(OS_WIN) #include "Cursor.h" #endif @@ -50,6 +51,8 @@ MSVC_PUSH_WARNING_LEVEL(0); #include "FrameTree.h" #include "FrameView.h" #include "GraphicsContext.h" +#include "HTMLNames.h" +#include "HTMLInputElement.h" #include "HitTestResult.h" #include "Image.h" #include "InspectorController.h" @@ -61,9 +64,11 @@ MSVC_PUSH_WARNING_LEVEL(0); #include "PlatformMouseEvent.h" #include "PlatformWheelEvent.h" #include "PluginInfoStore.h" +#include "PopupMenuClient.h" #if defined(OS_WIN) #include "RenderThemeWin.h" #endif +#include "RenderView.h" #include "ResourceHandle.h" #include "SelectionController.h" #include "Settings.h" @@ -94,6 +99,7 @@ MSVC_POP_WARNING(); #include "webkit/glue/webview_delegate.h" #include "webkit/glue/webview_impl.h" #include "webkit/glue/webwidget_impl.h" +#include "webkit/port/platform/chromium/PopupMenuChromium.h" #include "webkit/port/platform/graphics/PlatformContextSkia.h" // Get rid of WTF's pow define so we can use std::pow. @@ -115,6 +121,126 @@ static const double kMaxTextSizeMultiplier = 3.0; static const WebCore::DragOperation kDropTargetOperation = static_cast<WebCore::DragOperation>(DragOperationCopy | DragOperationLink); +// AutocompletePopupMenuClient +class AutocompletePopupMenuClient + : public RefCounted<AutocompletePopupMenuClient>, + public WebCore::PopupMenuClient { + public: + AutocompletePopupMenuClient(WebViewImpl* webview, + WebCore::HTMLInputElement* text_field, + const std::vector<std::wstring>& suggestions, + int default_suggestion_index) + : text_field_(text_field), + selected_index_(default_suggestion_index), + webview_(webview) { + for (std::vector<std::wstring>::const_iterator iter = suggestions.begin(); + iter != suggestions.end(); ++iter) { + suggestions_.push_back(webkit_glue::StdWStringToString(*iter)); + } + } + virtual ~AutocompletePopupMenuClient() { + } + + virtual void valueChanged(unsigned listIndex, bool fireEvents = true) { + text_field_->setValue(suggestions_[listIndex]); + } + + virtual WebCore::String itemText(unsigned list_index) const { + return suggestions_[list_index]; + } + + virtual bool itemIsEnabled(unsigned listIndex) const { + return true; + } + + virtual PopupMenuStyle itemStyle(unsigned listIndex) const { + return menuStyle(); + } + + virtual PopupMenuStyle menuStyle() const { + RenderStyle* style = text_field_->renderStyle() ? + text_field_->renderStyle() : + text_field_->computedStyle(); + return PopupMenuStyle(style->color(), Color::white, style->font(), + style->visibility() == VISIBLE); + } + + virtual int clientInsetLeft() const { + return 0; + } + virtual int clientInsetRight() const { + return 0; + } + virtual int clientPaddingLeft() const { +#if defined(OS_WIN) + return theme()->popupInternalPaddingLeft(text_field_->computedStyle()); +#else + NOTIMPLEMENTED(); + return 0; +#endif + } + virtual int clientPaddingRight() const { +#if defined(OS_WIN) + return theme()->popupInternalPaddingRight(text_field_->computedStyle()); +#else + NOTIMPLEMENTED(); + return 0; +#endif + } + virtual int listSize() const { + return suggestions_.size(); + } + virtual int selectedIndex() const { + return selected_index_; + } + virtual void hidePopup() { + webview_->HideAutoCompletePopup(); + } + virtual bool itemIsSeparator(unsigned listIndex) const { + return false; + } + virtual bool itemIsLabel(unsigned listIndex) const { + return false; + } + virtual bool itemIsSelected(unsigned listIndex) const { + return false; + } + virtual bool shouldPopOver() const { + return false; + } + virtual bool valueShouldChangeOnHotTrack() const { + return false; + } + + virtual FontSelector* fontSelector() const { + return text_field_->document()->styleSelector()->fontSelector(); + } + + virtual void setTextFromItem(unsigned listIndex) { + text_field_->setValue(suggestions_[listIndex]); + } + + virtual HostWindow* hostWindow() const { + return text_field_->document()->view()->hostWindow(); + } + + virtual PassRefPtr<Scrollbar> createScrollbar( + ScrollbarClient* client, + ScrollbarOrientation orientation, + ScrollbarControlSize size) { + RefPtr<Scrollbar> widget = Scrollbar::createNativeScrollbar(client, + orientation, + size); + return widget.release(); + } + + private: + RefPtr<WebCore::HTMLInputElement> text_field_; + std::vector<WebCore::String> suggestions_; + int selected_index_; + WebViewImpl* webview_; +}; + // WebView ---------------------------------------------------------------- /*static*/ @@ -292,6 +418,18 @@ bool WebViewImpl::KeyEvent(const WebKeyboardEvent& event) { // event. suppress_next_keypress_event_ = false; + // Give autocomplete a chance to consume the key events it is interested in. + if (autocomplete_popup_ && + autocomplete_popup_->isInterestedInEventForKey(event.key_code)) { + if (autocomplete_popup_->handleKeyEvent(MakePlatformKeyboardEvent(event))) + return true; + return false; + } + + // A new key being pressed should hide the popup. + if (event.type == WebInputEvent::KEY_DOWN) + HideAutoCompletePopup(); + Frame* frame = GetFocusedWebCoreFrame(); if (!frame) return false; @@ -714,7 +852,6 @@ bool WebViewImpl::HandleInputEvent(const WebInputEvent* input_event) { // we're done. if (doing_drag_and_drop_) return true; - // TODO(eseidel): Remove g_current_input_event. // This only exists to allow ChromeClient::show() to know which mouse button // triggered a window.open event. @@ -782,6 +919,14 @@ void WebViewImpl::SetBackForwardListSize(int size) { void WebViewImpl::SetFocus(bool enable) { if (enable) { + // Hide the popup menu if any. + // TODO(jcampan): bug #3844: we should do that when we lose focus. The + // reason we are not doing it is because when clicking on the autofill + // popup, the page first loses focus before the mouse click is sent to the + // popup. So if we close when the focus is lost, the mouse click does not + // do anything. + HideAutoCompletePopup(); + // Getting the focused frame will have the side-effect of setting the main // frame as the focused frame if it is not already focused. Otherwise, if // there is already a focused frame, then this does nothing. @@ -803,8 +948,6 @@ void WebViewImpl::SetFocus(bool enable) { // updated below. ReleaseFocusReferences(); - // Clear focus on the currently focused frame if any. - if (!main_frame_) return; @@ -1020,7 +1163,8 @@ void WebViewImpl::SetInitialFocus(bool reverse) { // We have to set the key type explicitly to avoid an assert in the // KeyboardEvent constructor. platform_event.SetKeyType(PlatformKeyboardEvent::RawKeyDown); - RefPtr<KeyboardEvent> webkit_event = KeyboardEvent::create(platform_event, NULL); + RefPtr<KeyboardEvent> webkit_event = KeyboardEvent::create(platform_event, + NULL); page()->focusController()->setInitialFocus( reverse ? WebCore::FocusDirectionBackward : WebCore::FocusDirectionForward, @@ -1333,6 +1477,59 @@ SearchableFormData* WebViewImpl::CreateSearchableFormDataForFocusedNode() { return NULL; } +void WebViewImpl::AutofillSuggestionsForNode( + int64 node_id, + const std::vector<std::wstring>& suggestions, + int default_suggestion_index) { + if (!main_frame_ || suggestions.empty()) + return; + + DCHECK(default_suggestion_index < static_cast<int>(suggestions.size())); + + Frame* frame = main_frame_->frame(); + if (!frame) + return; + + if (RefPtr<Frame> focused = + frame->page()->focusController()->focusedFrame()) { + RefPtr<Document> document = focused->document(); + if (!document.get()) + return; + + RefPtr<Node> focused_node = document->focusedNode(); + // If the node for which we queried the autofill suggestions is not the + // focused node, then we have nothing to do. + // TODO(jcampan): also check the carret is at the end and that the text has + // not changed. + if (!focused_node.get() || + reinterpret_cast<int64>(focused_node.get()) != node_id) + return; + + if (!focused_node->hasTagName(WebCore::HTMLNames::inputTag)) { + NOTREACHED(); + return; + } + + WebCore::HTMLInputElement* input_elem = + static_cast<WebCore::HTMLInputElement*>(focused_node.get()); + // Hide any current autocomplete popup. + HideAutoCompletePopup(); + + if (suggestions.size() > 0) { + autocomplete_popup_client_ = + adoptRef(new AutocompletePopupMenuClient(this, input_elem, + suggestions, + default_suggestion_index)); + // Autocomplete popup does not get focused. We need the page to still + // have focus so the user can keep typing when the popup is showing. + autocomplete_popup_ = + WebCore::PopupContainer::create(autocomplete_popup_client_.get(), + false); + autocomplete_popup_->show(focused_node->getRect(), frame->view(), 0); + } + } +} + void WebViewImpl::DidCommitLoad(bool* is_new_navigation) { if (is_new_navigation) *is_new_navigation = observed_new_navigation_; @@ -1481,3 +1678,11 @@ void WebViewImpl::DeleteImageResourceFetcher(ImageResourceFetcher* fetcher) { // deletion. MessageLoop::current()->DeleteSoon(FROM_HERE, fetcher); } + +void WebViewImpl::HideAutoCompletePopup() { + if (autocomplete_popup_) { + autocomplete_popup_->hidePopup(); + autocomplete_popup_.clear(); + autocomplete_popup_client_.clear(); + } +} diff --git a/webkit/glue/webview_impl.h b/webkit/glue/webview_impl.h index 9bc6c4b..5d66def 100644 --- a/webkit/glue/webview_impl.h +++ b/webkit/glue/webview_impl.h @@ -27,10 +27,12 @@ class HistoryItem; class KeyboardEvent; class Page; class PlatformKeyboardEvent; +class PopupContainer; class Range; class Widget; } +class AutocompletePopupMenuClient; class ImageResourceFetcher; class SearchableFormData; struct WebDropData; @@ -96,6 +98,10 @@ class WebViewImpl : public WebView, public WebCore::BackForwardListClient { virtual void DragTargetDragLeave(); virtual void DragTargetDrop( int client_x, int client_y, int screen_x, int screen_y); + virtual void AutofillSuggestionsForNode( + int64 node_id, + const std::vector<std::wstring>& suggestions, + int default_suggestion_index); // WebViewImpl @@ -176,6 +182,9 @@ class WebViewImpl : public WebView, public WebCore::BackForwardListClient { bool errored, const SkBitmap& image); + // Hides the autocomplete popup if it is showing. + void HideAutoCompletePopup(); + protected: friend class WebView; // So WebView::Create can call our constructor @@ -285,6 +294,13 @@ class WebViewImpl : public WebView, public WebCore::BackForwardListClient { // Represents whether or not this object should process incoming IME events. bool ime_accept_events_; + // The currently shown autocomplete popup. + RefPtr<WebCore::PopupContainer> autocomplete_popup_; + + // The popup client of the currently shown autocomplete popup. Necessary for + // managing the life of the client. + RefPtr<AutocompletePopupMenuClient> autocomplete_popup_client_; + // HACK: current_input_event is for ChromeClientImpl::show(), until we can fix // WebKit to pass enough information up into ChromeClient::show() so we can // decide if the window.open event was caused by a middle-mouse click diff --git a/webkit/port/page/chromium/ChromeClientChromium.h b/webkit/port/page/chromium/ChromeClientChromium.h index 66e08b0..31633ae 100644 --- a/webkit/port/page/chromium/ChromeClientChromium.h +++ b/webkit/port/page/chromium/ChromeClientChromium.h @@ -26,7 +26,9 @@ namespace WebCore { // Notifies the client of a new popup widget. The client should place // and size the widget with the given bounds, relative to the screen. - virtual void popupOpened(FramelessScrollView* popupView, const IntRect& bounds) = 0; + virtual void popupOpened(FramelessScrollView* popupView, + const IntRect& bounds, + bool focus_on_show) = 0; // Set the current cursor. virtual void setCursor(const Cursor& cursor) = 0; diff --git a/webkit/port/platform/chromium/PopupMenuChromium.cpp b/webkit/port/platform/chromium/PopupMenuChromium.cpp index d30df68..ae9f164 100644 --- a/webkit/port/platform/chromium/PopupMenuChromium.cpp +++ b/webkit/port/platform/chromium/PopupMenuChromium.cpp @@ -36,9 +36,9 @@ #include "ChromeClientChromium.h" #include "Document.h" #include "Font.h" -#include "Frame.h" #include "FrameView.h" #include "FontSelector.h" +#include "Frame.h" #include "FramelessScrollView.h" #include "FramelessScrollViewClient.h" #include "GraphicsContext.h" @@ -57,6 +57,8 @@ #include "Widget.h" #pragma warning(pop) +#include "webkit/port/platform/chromium/PopupMenuChromium.h" + using namespace WTF; using namespace Unicode; @@ -72,51 +74,6 @@ static const int kMaxHeight = 500; static const int kBorderSize = 1; static const TimeStamp kTypeAheadTimeoutMs = 1000; -class PopupListBox; - -// TODO(darin): Our FramelessScrollView classes need to implement HostWindow! - -// This class holds a PopupListBox. Its sole purpose is to be able to draw -// a border around its child. All its paint/event handling is just forwarded -// to the child listBox (with the appropriate transforms). -class PopupContainer : public FramelessScrollView, public RefCounted<PopupContainer> { -public: - static PassRefPtr<PopupContainer> create(PopupMenuClient* client); - - // FramelessScrollView - virtual void paint(GraphicsContext* gc, const IntRect& rect); - virtual void hide(); - virtual bool handleMouseDownEvent(const PlatformMouseEvent& event); - virtual bool handleMouseMoveEvent(const PlatformMouseEvent& event); - virtual bool handleMouseReleaseEvent(const PlatformMouseEvent& event); - virtual bool handleWheelEvent(const PlatformWheelEvent& event); - virtual bool handleKeyEvent(const PlatformKeyboardEvent& event); - - // PopupContainer methods - - // Show the popup - void showPopup(FrameView* view); - - // Hide the popup. Do not call this directly: use client->hidePopup(). - void hidePopup(); - - // Compute size of widget and children. - void layout(); - - PopupListBox* listBox() const { return m_listBox.get(); } - -private: - friend class RefCounted<PopupContainer>; - - PopupContainer(PopupMenuClient* client); - ~PopupContainer(); - - // Paint the border. - void paintBorder(GraphicsContext* gc, const IntRect& rect); - - RefPtr<PopupListBox> m_listBox; -}; - // This class uses WebCore code to paint and handle events for a drop-down list // box ("combobox" on Windows). class PopupListBox : public FramelessScrollView, public RefCounted<PopupListBox> { @@ -170,6 +127,9 @@ public: // Compute size of widget and children. void layout(); + // Returns whether the popup wants to process events for the passed key. + bool isInterestedInEventForKey(int key_code); + private: friend class PopupContainer; friend class RefCounted<PopupListBox>; @@ -327,13 +287,15 @@ static PlatformWheelEvent constructRelativeWheelEvent(const PlatformWheelEvent& // PopupContainer implementation // static -PassRefPtr<PopupContainer> PopupContainer::create(PopupMenuClient* client) +PassRefPtr<PopupContainer> PopupContainer::create(PopupMenuClient* client, + bool focusOnShow) { - return adoptRef(new PopupContainer(client)); + return adoptRef(new PopupContainer(client, focusOnShow)); } -PopupContainer::PopupContainer(PopupMenuClient* client) - : m_listBox(new PopupListBox(client)) +PopupContainer::PopupContainer(PopupMenuClient* client, bool focusOnShow) + : m_listBox(new PopupListBox(client)), + m_focusOnShow(focusOnShow) { // FrameViews are created with a refcount of 1 so it needs releasing after we // assign it to a RefPtr. @@ -367,7 +329,7 @@ void PopupContainer::showPopup(FrameView* view) if (widgetRect.bottom() > static_cast<int>(screen.bottom())) widgetRect.move(0, -(widgetRect.height() + selectHeight)); - chromeClient->popupOpened(this, widgetRect); + chromeClient->popupOpened(this, widgetRect, m_focusOnShow); } // Must get called after we have a client and containingWindow. @@ -474,6 +436,33 @@ void PopupContainer::paintBorder(GraphicsContext* gc, const IntRect& rect) gc->drawRect(IntRect(tx + width() - kBorderSize, ty, kBorderSize, height())); } +bool PopupContainer::isInterestedInEventForKey(int key_code) { + return m_listBox->isInterestedInEventForKey(key_code); +} + +void PopupContainer::show(const IntRect& r, FrameView* v, int index) { + // The rect is the size of the select box. It's usually larger than we need. + // subtract border size so that usually the container will be displayed + // exactly the same width as the select box. + listBox()->setBaseWidth(max(r.width() - kBorderSize * 2, 0)); + + listBox()->updateFromElement(); + + // We set the selected item in updateFromElement(), and disregard the + // index passed into this function (same as Webkit's PopupMenuWin.cpp) + // TODO(ericroman): make sure this is correct, and add an assertion. + // DCHECK(popupWindow(popup)->listBox()->selectedIndex() == index); + + // Convert point to main window coords. + IntPoint location = v->contentsToWindow(r.location()); + + // Move it below the select widget. + location.move(0, r.height()); + + IntRect popupRect(location, r.size()); + setFrameRect(popupRect); + showPopup(v); +} /////////////////////////////////////////////////////////////////////////////// // PopupListBox implementation @@ -548,6 +537,23 @@ bool PopupListBox::handleWheelEvent(const PlatformWheelEvent& event) return true; } +// Should be kept in sync with handleKeyEvent(). +bool PopupListBox::isInterestedInEventForKey(int key_code) { + switch (key_code) { + case VKEY_ESCAPE: + case VKEY_RETURN: + case VKEY_UP: + case VKEY_DOWN: + case VKEY_PRIOR: + case VKEY_NEXT: + case VKEY_HOME: + case VKEY_END: + return true; + default: + return false; + } +} + bool PopupListBox::handleKeyEvent(const PlatformKeyboardEvent& event) { if (event.type() == PlatformKeyboardEvent::KeyUp) @@ -1036,29 +1042,8 @@ PopupMenu::~PopupMenu() void PopupMenu::show(const IntRect& r, FrameView* v, int index) { - p.m_popup = PopupContainer::create(client()); - - // The rect is the size of the select box. It's usually larger than we need. - // subtract border size so that usually the container will be displayed - // exactly the same width as the select box. - p.m_popup->listBox()->setBaseWidth(max(r.width() - kBorderSize * 2, 0)); - - updateFromElement(); - - // We set the selected item in updateFromElement(), and disregard the - // index passed into this function (same as Webkit's PopupMenuWin.cpp) - // TODO(ericroman): make sure this is correct, and add an assertion. - // DCHECK(popupWindow(m_popup)->listBox()->selectedIndex() == index); - - // Convert point to main window coords. - IntPoint location = v->contentsToWindow(r.location()); - - // Move it below the select widget. - location.move(0, r.height()); - - IntRect popupRect(location, r.size()); - p.m_popup->setFrameRect(popupRect); - p.m_popup->showPopup(v); + p.m_popup = PopupContainer::create(client(), true); + p.m_popup->show(r, v, index); } void PopupMenu::hide() diff --git a/webkit/port/platform/chromium/PopupMenuChromium.h b/webkit/port/platform/chromium/PopupMenuChromium.h new file mode 100644 index 0000000..d419468 --- /dev/null +++ b/webkit/port/platform/chromium/PopupMenuChromium.h @@ -0,0 +1,82 @@ +// Copyright (c) 2006-2008 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 PopupMenuChromium_h +#define PopupMenuChromium_h + +#pragma warning(push, 0) +#include "PopupMenuClient.h" + +#include "FramelessScrollView.h" +#include "IntRect.h" +#pragma warning(pop) + + +namespace WebCore { + +class PopupListBox; + +// TODO(darin): Our FramelessScrollView classes need to implement HostWindow! + +// This class holds a PopupListBox (see cpp file). Its sole purpose is to be +// able to draw a border around its child. All its paint/event handling is just +// forwarded to the child listBox (with the appropriate transforms). +// NOTE: this class is exposed so it can be instantiated direcly for the +// autofill popup. We cannot use the Popup class directly in that case as the +// autofill popup should not be focused when shown and we want to forward the +// key events to it (through handleKeyEvent). + +class PopupContainer : public FramelessScrollView, public RefCounted<PopupContainer> { +public: + static PassRefPtr<PopupContainer> create(PopupMenuClient* client, + bool focusOnShow); + + // Whether a key event should be sent to this popup. + virtual bool isInterestedInEventForKey(int key_code); + + // FramelessScrollView + virtual void paint(GraphicsContext* gc, const IntRect& rect); + virtual void hide(); + virtual bool handleMouseDownEvent(const PlatformMouseEvent& event); + virtual bool handleMouseMoveEvent(const PlatformMouseEvent& event); + virtual bool handleMouseReleaseEvent(const PlatformMouseEvent& event); + virtual bool handleWheelEvent(const PlatformWheelEvent& event); + virtual bool handleKeyEvent(const PlatformKeyboardEvent& event); + + // PopupContainer methods + + // Show the popup + void showPopup(FrameView* view); + + // Show the popup in the specified rect for the specified frame. + // Note: this code was somehow arbitrarily factored-out of the Popup class + // so WebViewImpl can create a PopupContainer. + void show(const IntRect& r, FrameView* v, int index); + + // Hide the popup. Do not call this directly: use client->hidePopup(). + void hidePopup(); + + // Compute size of widget and children. + void layout(); + + PopupListBox* listBox() const { return m_listBox.get(); } + +private: + friend class WTF::RefCounted<PopupContainer>; + + PopupContainer(PopupMenuClient* client, bool focusOnShow); + ~PopupContainer(); + + // Paint the border. + void paintBorder(GraphicsContext* gc, const IntRect& rect); + + RefPtr<PopupListBox> m_listBox; + + // Whether the window showing this popup should be focused when shown. + bool m_focusOnShow; +}; + +} + +#endif // PopupMenuChromium_h |