From f360104beeb4fc363653280c9b6560e476572810 Mon Sep 17 00:00:00 2001 From: Heilig Benedek Date: Fri, 19 May 2017 21:35:13 +0200 Subject: [PATCH] add datalist element view --- atom/browser/api/atom_api_web_contents.cc | 35 ++ atom/browser/api/atom_api_web_contents.h | 11 + atom/browser/ui/autofill_popup.cc | 250 +++++++++++ atom/browser/ui/autofill_popup.h | 79 ++++ atom/browser/ui/views/autofill_popup_view.cc | 414 +++++++++++++++++++ atom/browser/ui/views/autofill_popup_view.h | 147 +++++++ atom/common/api/api_messages.h | 15 + atom/renderer/atom_autofill_agent.cc | 205 +++++++++ atom/renderer/atom_autofill_agent.h | 66 +++ atom/renderer/renderer_client_base.cc | 2 + filenames.gypi | 6 + 11 files changed, 1230 insertions(+) create mode 100644 atom/browser/ui/autofill_popup.cc create mode 100644 atom/browser/ui/autofill_popup.h create mode 100644 atom/browser/ui/views/autofill_popup_view.cc create mode 100644 atom/browser/ui/views/autofill_popup_view.h create mode 100644 atom/renderer/atom_autofill_agent.cc create mode 100644 atom/renderer/atom_autofill_agent.h diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index d47a4da2fec6..7e8e3508934d 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -17,6 +17,7 @@ #include "atom/browser/child_web_contents_tracker.h" #include "atom/browser/lib/bluetooth_chooser.h" #include "atom/browser/native_window.h" +#include "atom/browser/native_window_views.h" #include "atom/browser/net/atom_network_delegate.h" #include "atom/browser/osr/osr_output_device.h" #include "atom/browser/osr/osr_render_widget_host_view.h" @@ -83,6 +84,7 @@ #include "third_party/WebKit/public/web/WebFindOptions.h" #include "ui/display/screen.h" #include "ui/events/base_event_utils.h" +#include "ui/gfx/geometry/rect_f.h" #if !defined(OS_MACOSX) #include "ui/aura/window.h" @@ -441,6 +443,8 @@ void WebContents::InitWithSessionAndOptions(v8::Isolate* isolate, registrar_.Add(this, content::NOTIFICATION_NAV_ENTRY_PENDING, content::Source(controller)); + autofill_popup_ = new AutofillPopup(web_contents->GetNativeView()); + Init(isolate); AttachAsUserData(web_contents); } @@ -740,6 +744,17 @@ void WebContents::RenderViewCreated(content::RenderViewHost* render_view_host) { impl->disable_hidden_ = !background_throttling_; } +void WebContents::RenderFrameCreated(content::RenderFrameHost* host) { + Send(new AtomAutofillViewHostMsg_RoutingId( + host->GetRoutingID(), routing_id())); +} + +void WebContents::RenderFrameHostChanged(content::RenderFrameHost* old_host, + content::RenderFrameHost* new_host) { + Send(new AtomAutofillViewHostMsg_RoutingId( + new_host->GetRoutingID(), routing_id())); +} + void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) { Emit("render-view-deleted", render_view_host->GetProcess()->GetID()); } @@ -976,6 +991,8 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) { OnGetZoomLevel) IPC_MESSAGE_HANDLER_CODE(ViewHostMsg_SetCursor, OnCursorChange, handled = false) + IPC_MESSAGE_HANDLER(AtomAutofillViewMsg_ShowPopup, OnShowAutofillPopup) + IPC_MESSAGE_HANDLER(AtomAutofillViewMsg_HidePopup, OnHideAutofillPopup) IPC_MESSAGE_UNHANDLED(handled = false) IPC_END_MESSAGE_MAP() @@ -1608,6 +1625,24 @@ void WebContents::OnCursorChange(const content::WebCursor& cursor) { } } +void WebContents::OnShowAutofillPopup( + int routing_id, + const gfx::RectF& bounds, + const std::vector& values, + const std::vector& labels) { + auto relay = reinterpret_cast( + NativeWindow::FromWebContents(web_contents())); + autofill_popup_->CreateView( + routing_id, + web_contents(), + relay->widget(), + bounds); + autofill_popup_->SetItems(values, labels); +} +void WebContents::OnHideAutofillPopup() { + autofill_popup_->Hide(); +} + void WebContents::SetSize(const SetSizeParams& params) { if (guest_delegate_) guest_delegate_->SetSize(params); diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 6a04d6f518cf..f571d7358b61 100644 --- a/atom/browser/api/atom_api_web_contents.h +++ b/atom/browser/api/atom_api_web_contents.h @@ -12,6 +12,7 @@ #include "atom/browser/api/save_page_handler.h" #include "atom/browser/api/trackable_object.h" #include "atom/browser/common_web_contents_delegate.h" +#include "atom/browser/ui/autofill_popup.h" #include "content/common/cursors/webcursor.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" @@ -307,6 +308,9 @@ class WebContents : public mate::TrackableObject, // content::WebContentsObserver: void BeforeUnloadFired(const base::TimeTicks& proceed_time) override; void RenderViewCreated(content::RenderViewHost*) override; + void RenderFrameCreated(content::RenderFrameHost*) override; + void RenderFrameHostChanged(content::RenderFrameHost*, + content::RenderFrameHost*) override; void RenderViewDeleted(content::RenderViewHost*) override; void RenderProcessGone(base::TerminationStatus status) override; void DocumentLoadedInFrame( @@ -373,6 +377,12 @@ class WebContents : public mate::TrackableObject, // Called when we receive a CursorChange message from chromium. void OnCursorChange(const content::WebCursor& cursor); + + void OnShowAutofillPopup(int routing_id, + const gfx::RectF& bounds, + const std::vector& values, + const std::vector& labels); + void OnHideAutofillPopup(); // Called when received a message from renderer. void OnRendererMessage(const base::string16& channel, @@ -397,6 +407,7 @@ class WebContents : public mate::TrackableObject, std::unique_ptr dialog_manager_; std::unique_ptr guest_delegate_; + AutofillPopup* autofill_popup_; // The host webcontents that may contain this webcontents. WebContents* embedder_; diff --git a/atom/browser/ui/autofill_popup.cc b/atom/browser/ui/autofill_popup.cc new file mode 100644 index 000000000000..e775bf00c52d --- /dev/null +++ b/atom/browser/ui/autofill_popup.cc @@ -0,0 +1,250 @@ +// Copyright (c) 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. + +#include "atom/browser/ui/autofill_popup.h" +#include "atom/common/api/api_messages.h" +#include "ui/display/display.h" +#include "ui/display/screen.h" +#include "ui/gfx/text_utils.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/vector2d.h" + +namespace atom { + +namespace { + +std::pair CalculatePopupXAndWidth( + const display::Display& left_display, + const display::Display& right_display, + int popup_required_width, + const gfx::Rect& element_bounds, + bool is_rtl) { + int leftmost_display_x = left_display.bounds().x(); + int rightmost_display_x = + right_display.GetSizeInPixel().width() + right_display.bounds().x(); + + // Calculate the start coordinates for the popup if it is growing right or + // the end position if it is growing to the left, capped to screen space. + int right_growth_start = std::max( + leftmost_display_x, std::min(rightmost_display_x, element_bounds.x())); + int left_growth_end = + std::max(leftmost_display_x, + std::min(rightmost_display_x, element_bounds.right())); + + int right_available = rightmost_display_x - right_growth_start; + int left_available = left_growth_end - leftmost_display_x; + + int popup_width = + std::min(popup_required_width, std::max(right_available, left_available)); + + std::pair grow_right(right_growth_start, popup_width); + std::pair grow_left(left_growth_end - popup_width, popup_width); + + // Prefer to grow towards the end (right for LTR, left for RTL). But if there + // is not enough space available in the desired direction and more space in + // the other direction, reverse it. + if (is_rtl) { + return left_available >= popup_width || left_available >= right_available + ? grow_left + : grow_right; + } + return right_available >= popup_width || right_available >= left_available + ? grow_right + : grow_left; +} + +std::pair CalculatePopupYAndHeight( + const display::Display& top_display, + const display::Display& bottom_display, + int popup_required_height, + const gfx::Rect& element_bounds) { + int topmost_display_y = top_display.bounds().y(); + int bottommost_display_y = + bottom_display.GetSizeInPixel().height() + bottom_display.bounds().y(); + + // Calculate the start coordinates for the popup if it is growing down or + // the end position if it is growing up, capped to screen space. + int top_growth_end = std::max( + topmost_display_y, std::min(bottommost_display_y, element_bounds.y())); + int bottom_growth_start = + std::max(topmost_display_y, + std::min(bottommost_display_y, element_bounds.bottom())); + + int top_available = bottom_growth_start - topmost_display_y; + int bottom_available = bottommost_display_y - top_growth_end; + + // TODO(csharp): Restrict the popup height to what is available. + if (bottom_available >= popup_required_height || + bottom_available >= top_available) { + // The popup can appear below the field. + return std::make_pair(bottom_growth_start, popup_required_height); + } else { + // The popup must appear above the field. + return std::make_pair(top_growth_end - popup_required_height, + popup_required_height); + } +} + +display::Display GetDisplayNearestPoint( + const gfx::Point& point, + gfx::NativeView container_view) { + return display::Screen::GetScreen()->GetDisplayNearestPoint(point); +} + +} // namespace + +AutofillPopup::AutofillPopup(gfx::NativeView container_view) + : container_view_(container_view) { + bold_font_list_ = + gfx::FontList().DeriveWithWeight(gfx::Font::Weight::BOLD); + smaller_font_list_ = + gfx::FontList().DeriveWithSizeDelta(kSmallerFontSizeDelta); +} + +AutofillPopup::~AutofillPopup() { + Hide(); +} + +void AutofillPopup::CreateView( + int routing_id, + content::WebContents* web_contents, + views::Widget* parent_widget, + const gfx::RectF& r) { + web_contents_ = web_contents; + gfx::Rect lb(std::floor(r.x()), std::floor(r.y() + r.height()), + std::floor(r.width()), std::floor(r.height())); + gfx::Point menu_position(lb.origin()); + views::View::ConvertPointToScreen(parent_widget->GetContentsView(), &menu_position); + popup_bounds_ = gfx::Rect(menu_position, lb.size()); + element_bounds_ = popup_bounds_; + + view_.reset(new AutofillPopupView(this, parent_widget)); + view_->Show(); + + frame_routing_id_ = routing_id; +} + +void AutofillPopup::Hide() { + if (view_.get()) { + view_->Hide(); + view_.reset(); + } +} + +void AutofillPopup::SetItems(const std::vector& values, + const std::vector& labels) { + values_ = values; + labels_ = labels; + UpdatePopupBounds(); + if (view_.get()) { + view_->OnSuggestionsChanged(); + } +} + +void AutofillPopup::AcceptSuggestion(int index) { + web_contents_->Send(new AtomAutofillViewMsg_AcceptSuggestion( + frame_routing_id_, GetValueAt(index))); +} + +void AutofillPopup::UpdatePopupBounds() { + int desired_width = GetDesiredPopupWidth(); + int desired_height = GetDesiredPopupHeight(); + bool is_rtl = false; + + gfx::Point top_left_corner_of_popup = + element_bounds_.origin() + + gfx::Vector2d(element_bounds_.width() - desired_width, -desired_height); + + // This is the bottom right point of the popup if the popup is below the + // element and grows to the right (since the is the lowest and furthest right + // the popup could go). + gfx::Point bottom_right_corner_of_popup = + element_bounds_.origin() + + gfx::Vector2d(desired_width, element_bounds_.height() + desired_height); + + display::Display top_left_display = + GetDisplayNearestPoint(top_left_corner_of_popup, container_view_); + display::Display bottom_right_display = + GetDisplayNearestPoint(bottom_right_corner_of_popup, container_view_); + + std::pair popup_x_and_width = + CalculatePopupXAndWidth(top_left_display, bottom_right_display, + desired_width, element_bounds_, is_rtl); + std::pair popup_y_and_height = CalculatePopupYAndHeight( + top_left_display, bottom_right_display, desired_height, element_bounds_); + + popup_bounds_ = gfx::Rect(popup_x_and_width.first, popup_y_and_height.first, + popup_x_and_width.second, popup_y_and_height.second); +} + +int AutofillPopup::GetDesiredPopupHeight() { + return 2 * kPopupBorderThickness + values_.size() * kRowHeight; +} + +int AutofillPopup::GetDesiredPopupWidth() { + int popup_width = element_bounds_.width(); + + for (int i = 0; i < values_.size(); ++i) { + int row_size = kEndPadding + 2 * kPopupBorderThickness + + gfx::GetStringWidth(GetValueAt(i), GetValueFontListForRow(i)) + + gfx::GetStringWidth(GetLabelAt(i), GetLabelFontListForRow(i)); + if (GetLabelAt(i).length() > 0) + row_size += kNamePadding + kEndPadding; + + popup_width = std::max(popup_width, row_size); + } + + return popup_width; +} + +gfx::Rect AutofillPopup::GetRowBounds(int index) { + int top = kPopupBorderThickness + index * kRowHeight; + + return gfx::Rect(kPopupBorderThickness, top, + popup_bounds_.width() - 2 * kPopupBorderThickness, + kRowHeight); +} + +const gfx::FontList& AutofillPopup::GetValueFontListForRow(int index) const { + return bold_font_list_; +} + +const gfx::FontList& AutofillPopup::GetLabelFontListForRow(int index) const { + return smaller_font_list_; +} + +ui::NativeTheme::ColorId AutofillPopup::GetBackgroundColorIDForRow( + int index) const { + return index == view_->GetSelectedLine() + ? ui::NativeTheme::kColorId_ResultsTableHoveredBackground + : ui::NativeTheme::kColorId_ResultsTableNormalBackground; +} + +int AutofillPopup::GetLineCount() { + return values_.size(); +} + +base::string16 AutofillPopup::GetValueAt(int i) { + return values_.at(i); +} + +base::string16 AutofillPopup::GetLabelAt(int i) { + return labels_.at(i); +} + +int AutofillPopup::LineFromY(int y) const { + int current_height = kPopupBorderThickness; + + for (int i = 0; i < values_.size(); ++i) { + current_height += kRowHeight; + + if (y <= current_height) + return i; + } + + return values_.size() - 1; +} + +} // namespace atom \ No newline at end of file diff --git a/atom/browser/ui/autofill_popup.h b/atom/browser/ui/autofill_popup.h new file mode 100644 index 000000000000..fd3b8e93bdea --- /dev/null +++ b/atom/browser/ui/autofill_popup.h @@ -0,0 +1,79 @@ +// Copyright (c) 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 ATOM_BROWSER_UI_AUTOFILL_POPUP_H_ +#define ATOM_BROWSER_UI_AUTOFILL_POPUP_H_ + +#include "atom/browser/ui/views/autofill_popup_view.h" +#include "content/public/browser/web_contents.h" +#include "ui/gfx/font_list.h" +#include "ui/native_theme/native_theme.h" +#include "ui/views/widget/widget.h" + +namespace atom { + +class AutofillPopupView; + +class AutofillPopup { + public: + AutofillPopup(gfx::NativeView); + ~AutofillPopup(); + + void CreateView(int routing_id, content::WebContents* web_contents, + views::Widget* widget, const gfx::RectF& bounds); + void Hide(); + + void SetItems(const std::vector& values, + const std::vector& labels); + private: + friend class AutofillPopupView; + + void AcceptSuggestion(int index); + + void UpdatePopupBounds(); + int GetDesiredPopupHeight(); + int GetDesiredPopupWidth(); + gfx::Rect GetRowBounds(int i); + const gfx::FontList& GetValueFontListForRow(int index) const; + const gfx::FontList& GetLabelFontListForRow(int index) const; + ui::NativeTheme::ColorId GetBackgroundColorIDForRow(int index) const; + + int GetLineCount(); + base::string16 GetValueAt(int i); + base::string16 GetLabelAt(int i); + int LineFromY(int y) const; + + // The native view that contains this + gfx::NativeView container_view_; + + int selected_index_; + + // Popup location + gfx::Rect popup_bounds_; + + // Bounds of the autofilled element + gfx::Rect element_bounds_; + + // Datalist suggestions + std::vector values_; + std::vector labels_; + + // Font lists for the suggestions + gfx::FontList smaller_font_list_; + gfx::FontList bold_font_list_; + + // For sending the accepted suggestion to the render frame that + // asked to open the popup + int frame_routing_id_; + content::WebContents* web_contents_; + + // The popup view + std::unique_ptr view_; + + DISALLOW_COPY_AND_ASSIGN(AutofillPopup); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_AUTOFILL_POPUP_H_ \ No newline at end of file diff --git a/atom/browser/ui/views/autofill_popup_view.cc b/atom/browser/ui/views/autofill_popup_view.cc new file mode 100644 index 000000000000..87a2d5995bd0 --- /dev/null +++ b/atom/browser/ui/views/autofill_popup_view.cc @@ -0,0 +1,414 @@ +// Copyright (c) 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. + +#include "atom/browser/ui/views/autofill_popup_view.h" +#include "base/bind.h" +#include "content/public/browser/render_view_host.h" +#include "ui/events/keycodes/keyboard_codes.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/geometry/point.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/text_utils.h" +#include "ui/views/border.h" +#include "ui/views/focus/focus_manager.h" +#include "ui/views/widget/widget.h" + +namespace atom { + +AutofillPopupView::AutofillPopupView( + AutofillPopup* popup, + views::Widget* parent_widget) + : popup_(popup), + parent_widget_(parent_widget), + weak_ptr_factory_(this) { + CreateChildViews(); + SetFocusBehavior(FocusBehavior::ALWAYS); + set_drag_controller(this); +} + +AutofillPopupView::~AutofillPopupView() { + if (popup_) { + auto host = popup_->web_contents_->GetRenderViewHost()->GetWidget(); + host->RemoveKeyPressEventCallback(keypress_callback_); + popup_ = nullptr; + } + + RemoveObserver(); + + if (GetWidget()) { + GetWidget()->Close(); + } +} + +void AutofillPopupView::Show() { + const bool initialize_widget = !GetWidget(); + if (initialize_widget) { + parent_widget_->AddObserver(this); + views::FocusManager* focus_manager = parent_widget_->GetFocusManager(); + focus_manager->RegisterAccelerator( + ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE), + ui::AcceleratorManager::kNormalPriority, + this); + focus_manager->RegisterAccelerator( + ui::Accelerator(ui::VKEY_ESCAPE, ui::EF_NONE), + ui::AcceleratorManager::kNormalPriority, + this); + + // The widget is destroyed by the corresponding NativeWidget, so we use + // a weak pointer to hold the reference and don't have to worry about + // deletion. + views::Widget* widget = new views::Widget; + views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); + params.delegate = this; + params.parent = parent_widget_->GetNativeView(); + widget->Init(params); + + // No animation for popup appearance (too distracting). + widget->SetVisibilityAnimationTransition(views::Widget::ANIMATE_HIDE); + + show_time_ = base::Time::Now(); + } + + SetBorder(views::CreateSolidBorder( + kPopupBorderThickness, + GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_UnfocusedBorderColor))); + + DoUpdateBoundsAndRedrawPopup(); + GetWidget()->Show(); + + if (initialize_widget) + views::WidgetFocusManager::GetInstance()->AddFocusChangeListener(this); + + keypress_callback_ = base::Bind(&AutofillPopupView::HandleKeyPressEvent, + base::Unretained(this)); + auto host = popup_->web_contents_->GetRenderViewHost()->GetWidget(); + host->AddKeyPressEventCallback(keypress_callback_); +} + +void AutofillPopupView::Hide() { + auto host = popup_->web_contents_->GetRenderViewHost()->GetWidget(); + host->RemoveKeyPressEventCallback(keypress_callback_); + popup_ = NULL; + + RemoveObserver(); + + if (GetWidget()) { + GetWidget()->Close(); + } else { + delete this; + } +} + +void AutofillPopupView::OnSuggestionsChanged() { + if (popup_->GetLineCount() == 0) { + popup_->Hide(); + return; + } + CreateChildViews(); + DoUpdateBoundsAndRedrawPopup(); +} + +void AutofillPopupView::OnSelectedRowChanged( + base::Optional previous_row_selection, + base::Optional current_row_selection) { + SchedulePaint(); + + if (current_row_selection) { + DCHECK_LT(*current_row_selection, child_count()); + child_at(*current_row_selection) + ->NotifyAccessibilityEvent(ui::AX_EVENT_SELECTION, true); + } +} + +void AutofillPopupView::DrawAutofillEntry(gfx::Canvas* canvas, + int index, + const gfx::Rect& entry_rect) { + canvas->FillRect( + entry_rect, + GetNativeTheme()->GetSystemColor( + popup_->GetBackgroundColorIDForRow(index))); + + const bool is_rtl = false; + const int text_align = + is_rtl ? gfx::Canvas::TEXT_ALIGN_RIGHT : gfx::Canvas::TEXT_ALIGN_LEFT; + gfx::Rect value_rect = entry_rect; + value_rect.Inset(kEndPadding, 0); + + int x_align_left = value_rect.x(); + const int value_width = gfx::GetStringWidth( + popup_->GetValueAt(index), + popup_->GetValueFontListForRow(index)); + int value_x_align_left = x_align_left; + value_x_align_left = + is_rtl ? value_rect.right() - value_width : value_rect.x(); + + canvas->DrawStringRectWithFlags( + popup_->GetValueAt(index), + popup_->GetValueFontListForRow(index), + GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_ResultsTableNormalText), + gfx::Rect(value_x_align_left, value_rect.y(), value_width, + value_rect.height()), + text_align); + + // Draw the label text, if one exists. + if (!popup_->GetLabelAt(index).empty()) { + const int label_width = gfx::GetStringWidth( + popup_->GetLabelAt(index), + popup_->GetLabelFontListForRow(index)); + int label_x_align_left = x_align_left; + label_x_align_left = + is_rtl ? value_rect.x() : value_rect.right() - label_width; + + canvas->DrawStringRectWithFlags( + popup_->GetLabelAt(index), + popup_->GetLabelFontListForRow(index), + GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_ResultsTableNormalDimmedText), + gfx::Rect(label_x_align_left, entry_rect.y(), label_width, + entry_rect.height()), + text_align); + } +} + +void AutofillPopupView::CreateChildViews() { + RemoveAllChildViews(true); + + for (int i = 0; i < popup_->GetLineCount(); ++i) { + auto child_view = new AutofillPopupChildView(popup_->GetValueAt(i)); + child_view->set_drag_controller(this); + AddChildView(child_view); + } +} + +void AutofillPopupView::DoUpdateBoundsAndRedrawPopup() { + GetWidget()->SetBounds(popup_->popup_bounds_); + SchedulePaint(); +} + +void AutofillPopupView::OnPaint(gfx::Canvas* canvas) { + if (!popup_) + return; + + canvas->DrawColor(GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_ResultsTableNormalBackground)); + OnPaintBorder(canvas); + + DCHECK_EQ(popup_->GetLineCount(), child_count()); + for (int i = 0; i < popup_->GetLineCount(); ++i) { + gfx::Rect line_rect = popup_->GetRowBounds(i); + + DrawAutofillEntry(canvas, i, line_rect); + } +} + +void AutofillPopupView::GetAccessibleNodeData(ui::AXNodeData* node_data) { + node_data->role = ui::AX_ROLE_MENU; + node_data->SetName("Autofill Menu"); +} + +void AutofillPopupView::OnMouseCaptureLost() { + ClearSelection(); +} + +bool AutofillPopupView::OnMouseDragged(const ui::MouseEvent& event) { + if (HitTestPoint(event.location())) { + SetSelection(event.location()); + + // We must return true in order to get future OnMouseDragged and + // OnMouseReleased events. + return true; + } + + // If we move off of the popup, we lose the selection. + ClearSelection(); + return false; +} + +void AutofillPopupView::OnMouseExited(const ui::MouseEvent& event) { + // Pressing return causes the cursor to hide, which will generate an + // OnMouseExited event. Pressing return should activate the current selection + // via AcceleratorPressed, so we need to let that run first. + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&AutofillPopupView::ClearSelection, + weak_ptr_factory_.GetWeakPtr())); +} + +void AutofillPopupView::OnMouseMoved(const ui::MouseEvent& event) { + // A synthesized mouse move will be sent when the popup is first shown. + // Don't preview a suggestion if the mouse happens to be hovering there. +#if defined(OS_WIN) + if (base::Time::Now() - show_time_ <= base::TimeDelta::FromMilliseconds(50)) + return; +#else + if (event.flags() & ui::EF_IS_SYNTHESIZED) + return; +#endif + + if (HitTestPoint(event.location())) + SetSelection(event.location()); + else + ClearSelection(); +} + +bool AutofillPopupView::OnMousePressed(const ui::MouseEvent& event) { + return event.GetClickCount() == 1; +} + +void AutofillPopupView::OnMouseReleased(const ui::MouseEvent& event) { + // We only care about the left click. + if (event.IsOnlyLeftMouseButton() && HitTestPoint(event.location())) + AcceptSelection(event.location()); +} + +void AutofillPopupView::OnGestureEvent(ui::GestureEvent* event) { + switch (event->type()) { + case ui::ET_GESTURE_TAP_DOWN: + case ui::ET_GESTURE_SCROLL_BEGIN: + case ui::ET_GESTURE_SCROLL_UPDATE: + if (HitTestPoint(event->location())) + SetSelection(event->location()); + else + ClearSelection(); + break; + case ui::ET_GESTURE_TAP: + case ui::ET_GESTURE_SCROLL_END: + if (HitTestPoint(event->location())) + AcceptSelection(event->location()); + else + ClearSelection(); + break; + case ui::ET_GESTURE_TAP_CANCEL: + case ui::ET_SCROLL_FLING_START: + ClearSelection(); + break; + default: + return; + } + event->SetHandled(); +} + +bool AutofillPopupView::AcceleratorPressed( + const ui::Accelerator& accelerator) { + DCHECK_EQ(accelerator.modifiers(), ui::EF_NONE); + + if (accelerator.key_code() == ui::VKEY_ESCAPE) { + popup_->Hide(); + return true; + } + + if (accelerator.key_code() == ui::VKEY_RETURN) + return AcceptSelectedLine(); + + NOTREACHED(); + return false; +} + +bool AutofillPopupView::HandleKeyPressEvent( + const content::NativeWebKeyboardEvent& event) { + switch (event.windowsKeyCode) { + case ui::VKEY_UP: + SelectPreviousLine(); + return true; + case ui::VKEY_DOWN: + SelectNextLine(); + return true; + case ui::VKEY_PRIOR: // Page up. + SetSelectedLine(0); + return true; + case ui::VKEY_NEXT: // Page down. + SetSelectedLine(popup_->GetLineCount() - 1); + return true; + case ui::VKEY_ESCAPE: + popup_->Hide(); + return true; + case ui::VKEY_TAB: + // A tab press should cause the selected line to be accepted, but still + // return false so the tab key press propagates and changes the cursor + // location. + AcceptSelectedLine(); + return false; + case ui::VKEY_RETURN: + return AcceptSelectedLine(); + default: + return false; + } +} + +void AutofillPopupView::OnNativeFocusChanged(gfx::NativeView focused_now) { + if (GetWidget() && GetWidget()->GetNativeView() != focused_now) + popup_->Hide(); +} + +void AutofillPopupView::OnWidgetBoundsChanged(views::Widget* widget, + const gfx::Rect& new_bounds) { + DCHECK_EQ(widget, parent_widget_); + popup_->Hide(); +} + +void AutofillPopupView::AcceptSuggestion(int index) { + popup_->AcceptSuggestion(index); + popup_->Hide(); +} + +bool AutofillPopupView::AcceptSelectedLine() { + if (!selected_line_) + return false; + + DCHECK_LT(*selected_line_, popup_->GetLineCount()); + + AcceptSuggestion(*selected_line_); + return true; +} + +void AutofillPopupView::AcceptSelection(const gfx::Point& point) { + SetSelectedLine(popup_->LineFromY(point.y())); + AcceptSelectedLine(); +} + +void AutofillPopupView::SetSelectedLine(base::Optional selected_line) { + if (selected_line_ == selected_line) + return; + + if (selected_line) { + DCHECK_LT(*selected_line, popup_->GetLineCount()); + } + + auto previous_selected_line(selected_line_); + selected_line_ = selected_line; + OnSelectedRowChanged(previous_selected_line, selected_line_); +} + +void AutofillPopupView::SetSelection(const gfx::Point& point) { + SetSelectedLine(popup_->LineFromY(point.y())); +} + +void AutofillPopupView::SelectNextLine() { + int new_selected_line = selected_line_ ? *selected_line_ + 1 : 0; + if (new_selected_line >= popup_->GetLineCount()) + new_selected_line = 0; + + SetSelectedLine(new_selected_line); +} + +void AutofillPopupView::SelectPreviousLine() { + int new_selected_line = selected_line_.value_or(0) - 1; + if (new_selected_line < 0) + new_selected_line = popup_->GetLineCount() - 1; + + SetSelectedLine(new_selected_line); +} + +void AutofillPopupView::ClearSelection() { + SetSelectedLine(base::nullopt); +} + +void AutofillPopupView::RemoveObserver() { + parent_widget_->GetFocusManager()->UnregisterAccelerators(this); + parent_widget_->RemoveObserver(this); + views::WidgetFocusManager::GetInstance()->RemoveFocusChangeListener(this); +} + +} // namespace atom \ No newline at end of file diff --git a/atom/browser/ui/views/autofill_popup_view.h b/atom/browser/ui/views/autofill_popup_view.h new file mode 100644 index 000000000000..5b29fe88a4d3 --- /dev/null +++ b/atom/browser/ui/views/autofill_popup_view.h @@ -0,0 +1,147 @@ +// Copyright (c) 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 ATOM_BROWSER_UI_VIEWS_AUTOFILL_POPUP_VIEW_H_ +#define ATOM_BROWSER_UI_VIEWS_AUTOFILL_POPUP_VIEW_H_ + +#include "atom/browser/ui/autofill_popup.h" + +#include "base/optional.h" +#include "content/public/browser/native_web_keyboard_event.h" +#include "content/public/browser/render_widget_host.h" +#include "ui/accessibility/ax_node_data.h" +#include "ui/views/drag_controller.h" +#include "ui/views/focus/widget_focus_manager.h" +#include "ui/views/widget/widget_delegate.h" +#include "ui/views/widget/widget_observer.h" + +namespace atom { + +const int kPopupBorderThickness = 1; +const int kSmallerFontSizeDelta = -1; +const int kEndPadding = 8; +const int kNamePadding = 15; +const int kRowHeight = 24; + +class AutofillPopup; + +// Child view only for triggering accessibility events. Rendering is handled +// by |AutofillPopupViewViews|. +class AutofillPopupChildView : public views::View { + public: + explicit AutofillPopupChildView(const base::string16& suggestion) + : suggestion_(suggestion) { + SetFocusBehavior(FocusBehavior::ALWAYS); + } + + private: + ~AutofillPopupChildView() override {} + + // views::Views implementation + void GetAccessibleNodeData(ui::AXNodeData* node_data) override { + node_data->role = ui::AX_ROLE_MENU_ITEM; + node_data->SetName(suggestion_); + } + + base::string16 suggestion_; + + DISALLOW_COPY_AND_ASSIGN(AutofillPopupChildView); +}; + +class AutofillPopupView : public views::WidgetDelegateView, + public views::WidgetFocusChangeListener, + public views::WidgetObserver, + public views::DragController { + public: + explicit AutofillPopupView(AutofillPopup* popup, + views::Widget* parent_widget); + ~AutofillPopupView() override; + + void Show(); + void Hide(); + + void OnSuggestionsChanged(); + + int GetSelectedLine() { return selected_line_.value_or(-1); } + + void WriteDragDataForView( + views::View*, const gfx::Point&, ui::OSExchangeData*) override {} + int GetDragOperationsForView(views::View*, const gfx::Point&) override { + return ui::DragDropTypes::DRAG_NONE; + } + bool CanStartDragForView( + views::View*, const gfx::Point&, const gfx::Point&) override { + return false; + } + + private: + void OnSelectedRowChanged(base::Optional previous_row_selection, + base::Optional current_row_selection); + + // Draw the given autofill entry in |entry_rect|. + void DrawAutofillEntry(gfx::Canvas* canvas, + int index, + const gfx::Rect& entry_rect); + + // Creates child views based on the suggestions given by |controller_|. These + // child views are used for accessibility events only. We need child views to + // populate the correct |AXNodeData| when user selects a suggestion. + void CreateChildViews(); + + void DoUpdateBoundsAndRedrawPopup(); + + // views::Views implementation. + void OnPaint(gfx::Canvas* canvas) override; + void GetAccessibleNodeData(ui::AXNodeData* node_data) override; + void OnMouseCaptureLost() override; + bool OnMouseDragged(const ui::MouseEvent& event) override; + void OnMouseExited(const ui::MouseEvent& event) override; + void OnMouseMoved(const ui::MouseEvent& event) override; + bool OnMousePressed(const ui::MouseEvent& event) override; + void OnMouseReleased(const ui::MouseEvent& event) override; + void OnGestureEvent(ui::GestureEvent* event) override; + bool AcceleratorPressed(const ui::Accelerator& accelerator) override; + bool HandleKeyPressEvent(const content::NativeWebKeyboardEvent& event); + + // views::WidgetFocusChangeListener implementation. + void OnNativeFocusChanged(gfx::NativeView focused_now) override; + + // views::WidgetObserver implementation. + void OnWidgetBoundsChanged(views::Widget* widget, + const gfx::Rect& new_bounds) override; + + void AcceptSuggestion(int index); + bool AcceptSelectedLine(); + void AcceptSelection(const gfx::Point& point); + void SetSelectedLine(base::Optional selected_line); + void SetSelection(const gfx::Point& point); + void SelectNextLine(); + void SelectPreviousLine(); + void ClearSelection(); + + // Stop observing the widget. + void RemoveObserver(); + + // Controller for this popup. Weak reference. + AutofillPopup* popup_; + + // The widget of the window that triggered this popup. Weak reference. + views::Widget* parent_widget_; + + // The time when the popup was shown. + base::Time show_time_; + + // The index of the currently selected line + base::Optional selected_line_; + + // The registered keypress callback, responsible for switching lines on + // key presses + content::RenderWidgetHost::KeyPressEventCallback keypress_callback_; + + base::WeakPtrFactory weak_ptr_factory_; +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_VIEWS_AUTOFILL_POPUP_VIEW_H_ \ No newline at end of file diff --git a/atom/common/api/api_messages.h b/atom/common/api/api_messages.h index e25089b869a9..accd2eba0f79 100644 --- a/atom/common/api/api_messages.h +++ b/atom/common/api/api_messages.h @@ -9,6 +9,7 @@ #include "base/values.h" #include "content/public/common/common_param_traits.h" #include "ipc/ipc_message_macros.h" +#include "ui/gfx/geometry/rect_f.h" #include "ui/gfx/ipc/gfx_param_traits.h" // The message starter should be declared in ipc/ipc_message_start.h. Since @@ -37,6 +38,20 @@ IPC_MESSAGE_ROUTED3(AtomViewMsg_Message, IPC_MESSAGE_ROUTED0(AtomViewMsg_Offscreen) +IPC_MESSAGE_ROUTED4(AtomAutofillViewMsg_ShowPopup, + int /* routing_id */, + gfx::RectF /* bounds */, + std::vector /* values */, + std::vector /* labels */) + +IPC_MESSAGE_ROUTED0(AtomAutofillViewMsg_HidePopup) + +IPC_MESSAGE_ROUTED1(AtomAutofillViewMsg_AcceptSuggestion, + base::string16 /* suggestion */) + +IPC_MESSAGE_ROUTED1(AtomAutofillViewHostMsg_RoutingId, + int /* id */) + // Sent by the renderer when the draggable regions are updated. IPC_MESSAGE_ROUTED1(AtomViewHostMsg_UpdateDraggableRegions, std::vector /* regions */) diff --git a/atom/renderer/atom_autofill_agent.cc b/atom/renderer/atom_autofill_agent.cc new file mode 100644 index 000000000000..34446a61b144 --- /dev/null +++ b/atom/renderer/atom_autofill_agent.cc @@ -0,0 +1,205 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/renderer/atom_autofill_agent.h" + +#include "atom/common/api/api_messages.h" +#include "content/public/renderer/render_frame.h" +#include "content/public/renderer/render_view.h" +#include "third_party/WebKit/public/platform/WebKeyboardEvent.h" +#include "third_party/WebKit/public/platform/WebString.h" +#include "third_party/WebKit/public/web/WebDocument.h" +#include "third_party/WebKit/public/web/WebLocalFrame.h" +#include "third_party/WebKit/public/web/WebOptionElement.h" +#include "third_party/WebKit/public/web/WebUserGestureIndicator.h" +#include "ui/events/keycodes/keyboard_codes.h" +#include "ui/gfx/geometry/rect_f.h" + +namespace atom { + +namespace { +const size_t kMaxDataLength = 1024; +const size_t kMaxListSize = 512; + +void GetDataListSuggestions(const blink::WebInputElement& element, + std::vector* values, + std::vector* labels) { + for (const auto& option : element.filteredDataListOptions()) { + values->push_back(option.value().utf16()); + if (option.value() != option.label()) + labels->push_back(option.label().utf16()); + else + labels->push_back(base::string16()); + } +} + +void TrimStringVectorForIPC(std::vector* strings) { + // Limit the size of the vector. + if (strings->size() > kMaxListSize) + strings->resize(kMaxListSize); + + // Limit the size of the strings in the vector. + for (size_t i = 0; i < strings->size(); ++i) { + if ((*strings)[i].length() > kMaxDataLength) + (*strings)[i].resize(kMaxDataLength); + } +} +} // namespace + +AutofillAgent::AutofillAgent( + content::RenderFrame* frame) + : content::RenderFrameObserver(frame), + render_frame_(frame), + weak_ptr_factory_(this) { + render_frame_->GetWebFrame()->setAutofillClient(this); +} + +void AutofillAgent::OnDestruct() { + delete this; +} + +void AutofillAgent::DidChangeScrollOffset() { + HidePopup(); +} + +void AutofillAgent::FocusedNodeChanged(const blink::WebNode&) { + HidePopup(); +} + +void AutofillAgent::textFieldDidEndEditing( + const blink::WebInputElement&) { + HidePopup(); +} + +void AutofillAgent::textFieldDidChange( + const blink::WebFormControlElement& element) { + if (!IsUserGesture() && !render_frame()->IsPasting()) + return; + + weak_ptr_factory_.InvalidateWeakPtrs(); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::Bind(&AutofillAgent::textFieldDidChangeImpl, + weak_ptr_factory_.GetWeakPtr(), element)); +} + +void AutofillAgent::textFieldDidChangeImpl( + const blink::WebFormControlElement& element) { + ShowSuggestionsOptions options; + options.requires_caret_at_end = true; + ShowSuggestions(element, options); +} + +void AutofillAgent::textFieldDidReceiveKeyDown( + const blink::WebInputElement& element, + const blink::WebKeyboardEvent& event) { + if (event.windowsKeyCode == ui::VKEY_DOWN || + event.windowsKeyCode == ui::VKEY_UP) { + ShowSuggestionsOptions options; + options.autofill_on_empty_values = true; + options.requires_caret_at_end = true; + ShowSuggestions(element, options); + } +} + +void AutofillAgent::openTextDataListChooser( + const blink::WebInputElement& element) { + ShowSuggestionsOptions options; + options.autofill_on_empty_values = true; + ShowSuggestions(element, options); +} + +void AutofillAgent::dataListOptionsChanged( + const blink::WebInputElement& element) { + if (!element.focused()) + return; + + ShowSuggestionsOptions options; + options.requires_caret_at_end = true; + ShowSuggestions(element, options); +} + +AutofillAgent::ShowSuggestionsOptions::ShowSuggestionsOptions() + : autofill_on_empty_values(false), + requires_caret_at_end(false) { +} + +void AutofillAgent::ShowSuggestions( + const blink::WebFormControlElement& element, + const ShowSuggestionsOptions& options) { + if (!element.isEnabled() || element.isReadOnly()) + return; + if (!element.suggestedValue().isEmpty()) + return; + const blink::WebInputElement* input_element = toWebInputElement(&element); + if (input_element) { + if (!input_element->isTextField()) + return; + } + + blink::WebString value = element.editingValue(); + if (value.length() > kMaxDataLength || + (!options.autofill_on_empty_values && value.isEmpty()) || + (options.requires_caret_at_end && + (element.selectionStart() != element.selectionEnd() || + element.selectionEnd() != static_cast(value.length())))) { + HidePopup(); + return; + } + + std::vector data_list_values; + std::vector data_list_labels; + if (input_element) { + GetDataListSuggestions( + *input_element, &data_list_values, &data_list_labels); + TrimStringVectorForIPC(&data_list_values); + TrimStringVectorForIPC(&data_list_labels); + } + + ShowPopup(element, data_list_values, data_list_labels); +} + +void AutofillAgent::OnAcceptSuggestion(base::string16 suggestion) { + auto element = render_frame_->GetWebFrame()->document().focusedElement(); + if (element.isFormControlElement()) { + toWebInputElement(&element)->setAutofillValue( + blink::WebString::fromUTF16(suggestion)); + } +} + +bool AutofillAgent::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(AutofillAgent, message) + IPC_MESSAGE_HANDLER(AtomAutofillViewHostMsg_RoutingId, + OnWebContentsRoutingIdReceived) + IPC_MESSAGE_HANDLER(AtomAutofillViewMsg_AcceptSuggestion, + OnAcceptSuggestion) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + return handled; +} + +void AutofillAgent::OnWebContentsRoutingIdReceived(int id) { + web_contents_routing_id_ = id; +} + +bool AutofillAgent::IsUserGesture() const { + return blink::WebUserGestureIndicator::isProcessingUserGesture(); +} + +void AutofillAgent::HidePopup() { + Send(new AtomAutofillViewMsg_HidePopup(web_contents_routing_id_)); +} + +void AutofillAgent::ShowPopup( + const blink::WebFormControlElement& element, + const std::vector& values, + const std::vector& labels) { + gfx::RectF bounds = + render_frame_->GetRenderView()->ElementBoundsInWindow(element); + Send(new AtomAutofillViewMsg_ShowPopup( + web_contents_routing_id_, routing_id(), bounds, values, labels)); +} + +} // namespace atom \ No newline at end of file diff --git a/atom/renderer/atom_autofill_agent.h b/atom/renderer/atom_autofill_agent.h new file mode 100644 index 000000000000..122949577f14 --- /dev/null +++ b/atom/renderer/atom_autofill_agent.h @@ -0,0 +1,66 @@ +// Copyright (c) 2017 GitHub, Inc. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_RENDERER_ATOM_AUTOFILL_AGENT_H_ +#define ATOM_RENDERER_ATOM_AUTOFILL_AGENT_H_ + +#include "base/memory/weak_ptr.h" +#include "content/public/renderer/render_frame_observer.h" +#include "third_party/WebKit/public/web/WebAutofillClient.h" +#include "third_party/WebKit/public/web/WebFormControlElement.h" +#include "third_party/WebKit/public/web/WebInputElement.h" +#include "third_party/WebKit/public/web/WebNode.h" + +namespace atom { + +class AutofillAgent : public content::RenderFrameObserver, + public blink::WebAutofillClient { + public: + AutofillAgent(content::RenderFrame* frame); + + // content::RenderFrameObserver: + void OnDestruct() override; + + void DidChangeScrollOffset() override; + void FocusedNodeChanged(const blink::WebNode&) override; + + private: + struct ShowSuggestionsOptions { + ShowSuggestionsOptions(); + bool autofill_on_empty_values; + bool requires_caret_at_end; + }; + + bool OnMessageReceived(const IPC::Message& message) override; + void OnWebContentsRoutingIdReceived(int); + + // blink::WebAutofillClient: + void textFieldDidEndEditing(const blink::WebInputElement&) override; + void textFieldDidChange(const blink::WebFormControlElement&) override; + void textFieldDidChangeImpl(const blink::WebFormControlElement&); + void textFieldDidReceiveKeyDown(const blink::WebInputElement&, + const blink::WebKeyboardEvent&) override; + void openTextDataListChooser(const blink::WebInputElement&) override; + void dataListOptionsChanged(const blink::WebInputElement&) override; + + bool IsUserGesture() const; + void HidePopup(); + void ShowPopup(const blink::WebFormControlElement&, + const std::vector&, + const std::vector&); + void ShowSuggestions(const blink::WebFormControlElement& element, + const ShowSuggestionsOptions& options); + void OnAcceptSuggestion(base::string16 suggestion); + + content::RenderFrame* render_frame_; + int web_contents_routing_id_; + + base::WeakPtrFactory weak_ptr_factory_; + + DISALLOW_COPY_AND_ASSIGN(AutofillAgent); +}; + +} // namespace atom + +#endif // ATOM_RENDERER_ATOM_AUTOFILL_AGENT_H_ diff --git a/atom/renderer/renderer_client_base.cc b/atom/renderer/renderer_client_base.cc index 1219dfb5dcfa..c7818b2815f6 100644 --- a/atom/renderer/renderer_client_base.cc +++ b/atom/renderer/renderer_client_base.cc @@ -11,6 +11,7 @@ #include "atom/common/color_util.h" #include "atom/common/native_mate_converters/value_converter.h" #include "atom/common/options_switches.h" +#include "atom/renderer/atom_autofill_agent.h" #include "atom/renderer/atom_render_frame_observer.h" #include "atom/renderer/content_settings_observer.h" #include "atom/renderer/guest_view_container.h" @@ -120,6 +121,7 @@ void RendererClientBase::RenderThreadStarted() { void RendererClientBase::RenderFrameCreated( content::RenderFrame* render_frame) { new AtomRenderFrameObserver(render_frame, this); + new AutofillAgent(render_frame); new PepperHelper(render_frame); new ContentSettingsObserver(render_frame); new printing::PrintWebViewHelper(render_frame); diff --git a/filenames.gypi b/filenames.gypi index 63b46cb40e57..367c652ee675 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -291,6 +291,8 @@ 'atom/browser/ui/accelerator_util_views.cc', 'atom/browser/ui/atom_menu_model.cc', 'atom/browser/ui/atom_menu_model.h', + 'atom/browser/ui/autofill_popup.cc', + 'atom/browser/ui/autofill_popup.h', 'atom/browser/ui/certificate_trust.h', 'atom/browser/ui/certificate_trust_mac.mm', 'atom/browser/ui/certificate_trust_win.cc', @@ -318,6 +320,8 @@ 'atom/browser/ui/tray_icon_cocoa.mm', 'atom/browser/ui/tray_icon_observer.h', 'atom/browser/ui/tray_icon_win.cc', + 'atom/browser/ui/views/autofill_popup_view.cc', + 'atom/browser/ui/views/autofill_popup_view.h', 'atom/browser/ui/views/frameless_view.cc', 'atom/browser/ui/views/frameless_view.h', 'atom/browser/ui/views/global_menu_bar_x11.cc', @@ -479,6 +483,8 @@ 'atom/renderer/api/atom_api_spell_check_client.h', 'atom/renderer/api/atom_api_web_frame.cc', 'atom/renderer/api/atom_api_web_frame.h', + 'atom/renderer/atom_autofill_agent.cc', + 'atom/renderer/atom_autofill_agent.h', 'atom/renderer/atom_render_frame_observer.cc', 'atom/renderer/atom_render_frame_observer.h', 'atom/renderer/atom_render_view_observer.cc',