add datalist element view

This commit is contained in:
Heilig Benedek 2017-05-19 21:35:13 +02:00
parent 8404bdd568
commit f360104bee
11 changed files with 1230 additions and 0 deletions

View file

@ -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<content::NavigationController>(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<base::string16>& values,
const std::vector<base::string16>& labels) {
auto relay = reinterpret_cast<NativeWindowViews*>(
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);

View file

@ -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<WebContents>,
// 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(
@ -374,6 +378,12 @@ class WebContents : public mate::TrackableObject<WebContents>,
// 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<base::string16>& values,
const std::vector<base::string16>& labels);
void OnHideAutofillPopup();
// Called when received a message from renderer.
void OnRendererMessage(const base::string16& channel,
const base::ListValue& args);
@ -397,6 +407,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
std::unique_ptr<AtomJavaScriptDialogManager> dialog_manager_;
std::unique_ptr<WebViewGuestDelegate> guest_delegate_;
AutofillPopup* autofill_popup_;
// The host webcontents that may contain this webcontents.
WebContents* embedder_;

View file

@ -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<int, int> 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<int, int> grow_right(right_growth_start, popup_width);
std::pair<int, int> 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<int, int> 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<base::string16>& values,
const std::vector<base::string16>& 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<int, int> popup_x_and_width =
CalculatePopupXAndWidth(top_left_display, bottom_right_display,
desired_width, element_bounds_, is_rtl);
std::pair<int, int> 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

View file

@ -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<base::string16>& values,
const std::vector<base::string16>& 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<base::string16> values_;
std::vector<base::string16> 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<AutofillPopupView> view_;
DISALLOW_COPY_AND_ASSIGN(AutofillPopup);
};
} // namespace atom
#endif // ATOM_BROWSER_UI_AUTOFILL_POPUP_H_

View file

@ -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<int> previous_row_selection,
base::Optional<int> 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<int> 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

View file

@ -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<int> previous_row_selection,
base::Optional<int> 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<int> 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<int> selected_line_;
// The registered keypress callback, responsible for switching lines on
// key presses
content::RenderWidgetHost::KeyPressEventCallback keypress_callback_;
base::WeakPtrFactory<AutofillPopupView> weak_ptr_factory_;
};
} // namespace atom
#endif // ATOM_BROWSER_UI_VIEWS_AUTOFILL_POPUP_VIEW_H_

View file

@ -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<base::string16> /* values */,
std::vector<base::string16> /* 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<atom::DraggableRegion> /* regions */)

View file

@ -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<base::string16>* values,
std::vector<base::string16>* 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<base::string16>* 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<int>(value.length())))) {
HidePopup();
return;
}
std::vector<base::string16> data_list_values;
std::vector<base::string16> 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<base::string16>& values,
const std::vector<base::string16>& labels) {
gfx::RectF bounds =
render_frame_->GetRenderView()->ElementBoundsInWindow(element);
Send(new AtomAutofillViewMsg_ShowPopup(
web_contents_routing_id_, routing_id(), bounds, values, labels));
}
} // namespace atom

View file

@ -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<base::string16>&,
const std::vector<base::string16>&);
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<AutofillAgent> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(AutofillAgent);
};
} // namespace atom
#endif // ATOM_RENDERER_ATOM_AUTOFILL_AGENT_H_

View file

@ -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);

View file

@ -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',