diff --git a/atom/browser/api/atom_api_web_contents.cc b/atom/browser/api/atom_api_web_contents.cc index d47a4da2fec67..659daae18b91e 100644 --- a/atom/browser/api/atom_api_web_contents.cc +++ b/atom/browser/api/atom_api_web_contents.cc @@ -982,6 +982,23 @@ bool WebContents::OnMessageReceived(const IPC::Message& message) { return handled; } +bool WebContents::OnMessageReceived(const IPC::Message& message, + content::RenderFrameHost* frame_host) { + bool handled = true; + auto relay = NativeWindowRelay::FromWebContents(web_contents()); + if (!relay) + return false; + IPC_BEGIN_MESSAGE_MAP_WITH_PARAM(NativeWindow, message, frame_host) + IPC_MESSAGE_FORWARD(AtomAutofillFrameHostMsg_ShowPopup, + relay->window.get(), NativeWindow::ShowAutofillPopup) + IPC_MESSAGE_FORWARD(AtomAutofillFrameHostMsg_HidePopup, + relay->window.get(), NativeWindow::HideAutofillPopup) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + return handled; +} + // There are three ways of destroying a webContents: // 1. call webContents.destroy(); // 2. garbage collection; @@ -1621,6 +1638,10 @@ bool WebContents::IsOffScreen() const { return type_ == OFF_SCREEN; } +bool WebContents::IsOffScreenOrEmbedderOffscreen() const { + return IsOffScreen() || (embedder_ && embedder_->IsOffScreen()); +} + void WebContents::OnPaint(const gfx::Rect& dirty_rect, const SkBitmap& bitmap) { Emit("paint", dirty_rect, gfx::Image::CreateFrom1xBitmap(bitmap)); } diff --git a/atom/browser/api/atom_api_web_contents.h b/atom/browser/api/atom_api_web_contents.h index 6a04d6f518cfa..3c8124bf49941 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" @@ -180,6 +181,7 @@ class WebContents : public mate::TrackableObject, // Methods for offscreen rendering bool IsOffScreen() const; + bool IsOffScreenOrEmbedderOffscreen() const; void OnPaint(const gfx::Rect& dirty_rect, const SkBitmap& bitmap); void StartPainting(); void StopPainting(); @@ -329,6 +331,8 @@ class WebContents : public mate::TrackableObject, void DidFinishNavigation( content::NavigationHandle* navigation_handle) override; bool OnMessageReceived(const IPC::Message& message) override; + bool OnMessageReceived(const IPC::Message& message, + content::RenderFrameHost* frame_host) override; void WebContentsDestroyed() override; void NavigationEntryCommitted( const content::LoadCommittedDetails& load_details) override; diff --git a/atom/browser/native_window.cc b/atom/browser/native_window.cc index 9e2c11aec4ab7..f06841cb67ad1 100644 --- a/atom/browser/native_window.cc +++ b/atom/browser/native_window.cc @@ -251,7 +251,7 @@ void NativeWindow::SetSizeConstraints( SetContentSizeConstraints(content_constraints); } -extensions::SizeConstraints NativeWindow::GetSizeConstraints() { +extensions::SizeConstraints NativeWindow::GetSizeConstraints() const { extensions::SizeConstraints content_constraints = GetContentSizeConstraints(); extensions::SizeConstraints window_constraints; if (content_constraints.HasMaximumSize()) { @@ -272,7 +272,7 @@ void NativeWindow::SetContentSizeConstraints( size_constraints_ = size_constraints; } -extensions::SizeConstraints NativeWindow::GetContentSizeConstraints() { +extensions::SizeConstraints NativeWindow::GetContentSizeConstraints() const { return size_constraints_; } @@ -282,7 +282,7 @@ void NativeWindow::SetMinimumSize(const gfx::Size& size) { SetSizeConstraints(size_constraints); } -gfx::Size NativeWindow::GetMinimumSize() { +gfx::Size NativeWindow::GetMinimumSize() const { return GetSizeConstraints().GetMinimumSize(); } @@ -292,7 +292,7 @@ void NativeWindow::SetMaximumSize(const gfx::Size& size) { SetSizeConstraints(size_constraints); } -gfx::Size NativeWindow::GetMaximumSize() { +gfx::Size NativeWindow::GetMaximumSize() const { return GetSizeConstraints().GetMaximumSize(); } diff --git a/atom/browser/native_window.h b/atom/browser/native_window.h index b0d25e03f232f..89eacdb05d788 100644 --- a/atom/browser/native_window.h +++ b/atom/browser/native_window.h @@ -18,10 +18,12 @@ #include "base/observer_list.h" #include "base/supports_user_data.h" #include "content/public/browser/readback_types.h" +#include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/browser/web_contents_user_data.h" #include "extensions/browser/app_window/size_constraints.h" #include "native_mate/persistent_dictionary.h" +#include "ui/gfx/geometry/rect_f.h" #include "ui/gfx/image/image.h" #include "ui/gfx/image/image_skia.h" @@ -98,14 +100,14 @@ class NativeWindow : public base::SupportsUserData, virtual gfx::Rect GetContentBounds(); virtual void SetSizeConstraints( const extensions::SizeConstraints& size_constraints); - virtual extensions::SizeConstraints GetSizeConstraints(); + virtual extensions::SizeConstraints GetSizeConstraints() const; virtual void SetContentSizeConstraints( const extensions::SizeConstraints& size_constraints); - virtual extensions::SizeConstraints GetContentSizeConstraints(); + virtual extensions::SizeConstraints GetContentSizeConstraints() const; virtual void SetMinimumSize(const gfx::Size& size); - virtual gfx::Size GetMinimumSize(); + virtual gfx::Size GetMinimumSize() const; virtual void SetMaximumSize(const gfx::Size& size); - virtual gfx::Size GetMaximumSize(); + virtual gfx::Size GetMaximumSize() const; virtual void SetSheetOffset(const double offsetX, const double offsetY); virtual double GetSheetOffsetX(); virtual double GetSheetOffsetY(); @@ -147,9 +149,9 @@ class NativeWindow : public base::SupportsUserData, virtual void SetMenu(AtomMenuModel* menu); virtual void SetParentWindow(NativeWindow* parent); virtual void SetBrowserView(NativeBrowserView* browser_view) = 0; - virtual gfx::NativeView GetNativeView() = 0; - virtual gfx::NativeWindow GetNativeWindow() = 0; - virtual gfx::AcceleratedWidget GetAcceleratedWidget() = 0; + virtual gfx::NativeView GetNativeView() const = 0; + virtual gfx::NativeWindow GetNativeWindow() const = 0; + virtual gfx::AcceleratedWidget GetAcceleratedWidget() const = 0; // Taskbar/Dock APIs. enum ProgressState { @@ -215,6 +217,12 @@ class NativeWindow : public base::SupportsUserData, virtual void HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) {} + virtual void ShowAutofillPopup( + content::RenderFrameHost* frame_host, + const gfx::RectF& bounds, + const std::vector& values, + const std::vector& labels) {} + virtual void HideAutofillPopup(content::RenderFrameHost* frame_host) {} // Public API used by platform-dependent delegates and observers to send UI // related notifications. @@ -281,8 +289,10 @@ class NativeWindow : public base::SupportsUserData, const std::vector& regions); // Converts between content bounds and window bounds. - virtual gfx::Rect ContentBoundsToWindowBounds(const gfx::Rect& bounds) = 0; - virtual gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) = 0; + virtual gfx::Rect ContentBoundsToWindowBounds( + const gfx::Rect& bounds) const = 0; + virtual gfx::Rect WindowBoundsToContentBounds( + const gfx::Rect& bounds) const = 0; // Called when the window needs to update its draggable region. virtual void UpdateDraggableRegions( diff --git a/atom/browser/native_window_mac.h b/atom/browser/native_window_mac.h index 69bf3afef65a4..b5271e4d43648 100644 --- a/atom/browser/native_window_mac.h +++ b/atom/browser/native_window_mac.h @@ -89,9 +89,9 @@ class NativeWindowMac : public NativeWindow, void SetContentProtection(bool enable) override; void SetBrowserView(NativeBrowserView* browser_view) override; void SetParentWindow(NativeWindow* parent) override; - gfx::NativeView GetNativeView() override; - gfx::NativeWindow GetNativeWindow() override; - gfx::AcceleratedWidget GetAcceleratedWidget() override; + gfx::NativeView GetNativeView() const override; + gfx::NativeWindow GetNativeWindow() const override; + gfx::AcceleratedWidget GetAcceleratedWidget() const override; void SetProgressBar(double progress, const ProgressState state) override; void SetOverlayIcon(const gfx::Image& overlay, const std::string& description) override; @@ -140,8 +140,8 @@ class NativeWindowMac : public NativeWindow, private: // NativeWindow: - gfx::Rect ContentBoundsToWindowBounds(const gfx::Rect& bounds); - gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds); + gfx::Rect ContentBoundsToWindowBounds(const gfx::Rect& bounds) const; + gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) const; void UpdateDraggableRegions( const std::vector& regions) override; diff --git a/atom/browser/native_window_mac.mm b/atom/browser/native_window_mac.mm index 755e46c4f3262..e5192c8b6293b 100644 --- a/atom/browser/native_window_mac.mm +++ b/atom/browser/native_window_mac.mm @@ -1314,15 +1314,15 @@ static bool FromV8(v8::Isolate* isolate, v8::Handle val, [parent->GetNativeWindow() addChildWindow:window_ ordered:NSWindowAbove]; } -gfx::NativeView NativeWindowMac::GetNativeView() { +gfx::NativeView NativeWindowMac::GetNativeView() const { return inspectable_web_contents()->GetView()->GetNativeView(); } -gfx::NativeWindow NativeWindowMac::GetNativeWindow() { +gfx::NativeWindow NativeWindowMac::GetNativeWindow() const { return window_; } -gfx::AcceleratedWidget NativeWindowMac::GetAcceleratedWidget() { +gfx::AcceleratedWidget NativeWindowMac::GetAcceleratedWidget() const { return inspectable_web_contents()->GetView()->GetNativeView(); } @@ -1498,7 +1498,7 @@ static bool FromV8(v8::Isolate* isolate, v8::Handle val, } gfx::Rect NativeWindowMac::ContentBoundsToWindowBounds( - const gfx::Rect& bounds) { + const gfx::Rect& bounds) const { if (has_frame()) { gfx::Rect window_bounds( [window_ frameRectForContentRect:bounds.ToCGRect()]); @@ -1511,7 +1511,7 @@ static bool FromV8(v8::Isolate* isolate, v8::Handle val, } gfx::Rect NativeWindowMac::WindowBoundsToContentBounds( - const gfx::Rect& bounds) { + const gfx::Rect& bounds) const { if (has_frame()) { gfx::Rect content_bounds( [window_ contentRectForFrameRect:bounds.ToCGRect()]); diff --git a/atom/browser/native_window_views.cc b/atom/browser/native_window_views.cc index 00f0befd50fa9..242f11212850b 100644 --- a/atom/browser/native_window_views.cc +++ b/atom/browser/native_window_views.cc @@ -7,6 +7,7 @@ #include #include +#include "atom/browser/api/atom_api_web_contents.h" #include "atom/browser/native_browser_view_views.h" #include "atom/browser/ui/views/menu_bar.h" #include "atom/browser/window_list.h" @@ -319,6 +320,8 @@ NativeWindowViews::NativeWindowViews( window_->CenterWindow(size); Layout(); + autofill_popup_.reset(new AutofillPopup(GetNativeView())); + #if defined(OS_WIN) // Save initial window state. if (fullscreen) @@ -924,11 +927,11 @@ void NativeWindowViews::SetParentWindow(NativeWindow* parent) { #endif } -gfx::NativeView NativeWindowViews::GetNativeView() { +gfx::NativeView NativeWindowViews::GetNativeView() const { return window_->GetNativeView(); } -gfx::NativeWindow NativeWindowViews::GetNativeWindow() { +gfx::NativeWindow NativeWindowViews::GetNativeWindow() const { return window_->GetNativeWindow(); } @@ -999,7 +1002,7 @@ bool NativeWindowViews::IsVisibleOnAllWorkspaces() { return false; } -gfx::AcceleratedWidget NativeWindowViews::GetAcceleratedWidget() { +gfx::AcceleratedWidget NativeWindowViews::GetAcceleratedWidget() const { return GetNativeWindow()->GetHost()->GetAcceleratedWidget(); } @@ -1180,7 +1183,7 @@ void NativeWindowViews::OnWidgetMove() { } gfx::Rect NativeWindowViews::ContentBoundsToWindowBounds( - const gfx::Rect& bounds) { + const gfx::Rect& bounds) const { if (!has_frame()) return bounds; @@ -1201,7 +1204,7 @@ gfx::Rect NativeWindowViews::ContentBoundsToWindowBounds( } gfx::Rect NativeWindowViews::WindowBoundsToContentBounds( - const gfx::Rect& bounds) { + const gfx::Rect& bounds) const { if (!has_frame()) return bounds; @@ -1269,6 +1272,26 @@ void NativeWindowViews::HandleKeyboardEvent( } } +void NativeWindowViews::ShowAutofillPopup( + content::RenderFrameHost* frame_host, + const gfx::RectF& bounds, + const std::vector& values, + const std::vector& labels) { + auto wc = atom::api::WebContents::FromWrappedClass( + v8::Isolate::GetCurrent(), web_contents()); + autofill_popup_->CreateView( + frame_host, + wc->IsOffScreenOrEmbedderOffscreen(), + widget(), + bounds); + autofill_popup_->SetItems(values, labels); +} + +void NativeWindowViews::HideAutofillPopup( + content::RenderFrameHost* frame_host) { + autofill_popup_->Hide(); +} + void NativeWindowViews::Layout() { const auto size = GetContentsBounds().size(); const auto menu_bar_bounds = @@ -1306,11 +1329,11 @@ void NativeWindowViews::Layout() { } } -gfx::Size NativeWindowViews::GetMinimumSize() { +gfx::Size NativeWindowViews::GetMinimumSize() const { return NativeWindow::GetMinimumSize(); } -gfx::Size NativeWindowViews::GetMaximumSize() { +gfx::Size NativeWindowViews::GetMaximumSize() const { return NativeWindow::GetMaximumSize(); } diff --git a/atom/browser/native_window_views.h b/atom/browser/native_window_views.h index 15bdbad188081..5d94c2255bbe0 100644 --- a/atom/browser/native_window_views.h +++ b/atom/browser/native_window_views.h @@ -11,6 +11,7 @@ #include #include "atom/browser/ui/accelerator_util.h" +#include "atom/browser/ui/autofill_popup.h" #include "ui/views/widget/widget_delegate.h" #include "ui/views/widget/widget_observer.h" @@ -106,8 +107,8 @@ class NativeWindowViews : public NativeWindow, void SetMenu(AtomMenuModel* menu_model) override; void SetBrowserView(NativeBrowserView* browser_view) override; void SetParentWindow(NativeWindow* parent) override; - gfx::NativeView GetNativeView() override; - gfx::NativeWindow GetNativeWindow() override; + gfx::NativeView GetNativeView() const override; + gfx::NativeWindow GetNativeWindow() const override; void SetOverlayIcon(const gfx::Image& overlay, const std::string& description) override; void SetProgressBar(double progress, const ProgressState state) override; @@ -118,7 +119,7 @@ class NativeWindowViews : public NativeWindow, void SetVisibleOnAllWorkspaces(bool visible) override; bool IsVisibleOnAllWorkspaces() override; - gfx::AcceleratedWidget GetAcceleratedWidget() override; + gfx::AcceleratedWidget GetAcceleratedWidget() const override; #if defined(OS_WIN) void SetIcon(HICON small_icon, HICON app_icon); @@ -171,16 +172,22 @@ class NativeWindowViews : public NativeWindow, #endif // NativeWindow: - gfx::Rect ContentBoundsToWindowBounds(const gfx::Rect& bounds) override; - gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) override; + gfx::Rect ContentBoundsToWindowBounds(const gfx::Rect& bounds) const override; + gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) const override; void HandleKeyboardEvent( content::WebContents*, const content::NativeWebKeyboardEvent& event) override; + void ShowAutofillPopup( + content::RenderFrameHost* frame_host, + const gfx::RectF& bounds, + const std::vector& values, + const std::vector& labels) override; + void HideAutofillPopup(content::RenderFrameHost* frame_host) override; // views::View: void Layout() override; - gfx::Size GetMinimumSize() override; - gfx::Size GetMaximumSize() override; + gfx::Size GetMinimumSize() const override; + gfx::Size GetMaximumSize() const override; bool AcceleratorPressed(const ui::Accelerator& accelerator) override; // Register accelerators supported by the menu model. @@ -194,6 +201,8 @@ class NativeWindowViews : public NativeWindow, NativeBrowserView* browser_view_; + std::unique_ptr autofill_popup_; + std::unique_ptr menu_bar_; bool menu_bar_autohide_; bool menu_bar_visible_; diff --git a/atom/browser/osr/osr_render_widget_host_view.cc b/atom/browser/osr/osr_render_widget_host_view.cc index 605b42ec1d13a..11523a660b8f1 100644 --- a/atom/browser/osr/osr_render_widget_host_view.cc +++ b/atom/browser/osr/osr_render_widget_host_view.cc @@ -4,6 +4,7 @@ #include "atom/browser/osr/osr_render_widget_host_view.h" +#include #include #include "base/callback_helpers.h" @@ -22,9 +23,12 @@ #include "content/public/browser/browser_thread.h" #include "content/public/browser/context_factory.h" #include "media/base/video_frame.h" +#include "third_party/WebKit/public/platform/WebInputEvent.h" #include "ui/compositor/compositor.h" #include "ui/compositor/layer.h" #include "ui/compositor/layer_type.h" +#include "ui/events/base_event_utils.h" +#include "ui/events/event_constants.h" #include "ui/events/latency_info.h" #include "ui/gfx/geometry/dip_util.h" #include "ui/gfx/native_widget_types.h" @@ -37,6 +41,70 @@ namespace { const float kDefaultScaleFactor = 1.0; const int kFrameRetryLimit = 2; +ui::MouseEvent UiMouseEventFromWebMouseEvent(blink::WebMouseEvent event) { + ui::EventType type = ui::EventType::ET_UNKNOWN; + switch (event.type()) { + case blink::WebInputEvent::MouseDown: + type = ui::EventType::ET_MOUSE_PRESSED; + break; + case blink::WebInputEvent::MouseUp: + type = ui::EventType::ET_MOUSE_RELEASED; + break; + case blink::WebInputEvent::MouseMove: + type = ui::EventType::ET_MOUSE_MOVED; + break; + case blink::WebInputEvent::MouseEnter: + type = ui::EventType::ET_MOUSE_ENTERED; + break; + case blink::WebInputEvent::MouseLeave: + type = ui::EventType::ET_MOUSE_EXITED; + break; + case blink::WebInputEvent::MouseWheel: + type = ui::EventType::ET_MOUSEWHEEL; + break; + default: + type = ui::EventType::ET_UNKNOWN; + break; + } + + int button_flags = 0; + switch (event.button) { + case blink::WebMouseEvent::Button::X1: + button_flags |= ui::EventFlags::EF_BACK_MOUSE_BUTTON; + break; + case blink::WebMouseEvent::Button::X2: + button_flags |= ui::EventFlags::EF_FORWARD_MOUSE_BUTTON; + break; + case blink::WebMouseEvent::Button::Left: + button_flags |= ui::EventFlags::EF_LEFT_MOUSE_BUTTON; + break; + case blink::WebMouseEvent::Button::Middle: + button_flags |= ui::EventFlags::EF_MIDDLE_MOUSE_BUTTON; + break; + case blink::WebMouseEvent::Button::Right: + button_flags |= ui::EventFlags::EF_RIGHT_MOUSE_BUTTON; + break; + default: + button_flags = 0; + break; + } + + ui::MouseEvent ui_event(type, + gfx::Point(std::floor(event.x), std::floor(event.y)), + gfx::Point(std::floor(event.x), std::floor(event.y)), + ui::EventTimeForNow(), + button_flags, button_flags); + ui_event.SetClickCount(event.clickCount); + + return ui_event; +} + +ui::MouseWheelEvent UiMouseWheelEventFromWebMouseEvent( + blink::WebMouseWheelEvent event) { + return ui::MouseWheelEvent(UiMouseEventFromWebMouseEvent(event), + std::floor(event.deltaX), std::floor(event.deltaY)); +} + #if !defined(OS_MACOSX) const int kResizeLockTimeoutMs = 67; @@ -619,6 +687,8 @@ void OffScreenRenderWidgetHostView::Destroy() { child_host_view_->CancelWidget(); for (auto guest_host_view : guest_host_views_) guest_host_view->CancelWidget(); + for (auto proxy_view : proxy_views_) + proxy_view->RemoveObserver(); Hide(); } } @@ -835,6 +905,22 @@ void OffScreenRenderWidgetHostView::RemoveGuestHostView( guest_host_views_.erase(guest_host); } +void OffScreenRenderWidgetHostView::AddViewProxy(OffscreenViewProxy* proxy) { + proxy->SetObserver(this); + proxy_views_.insert(proxy); +} + +void OffScreenRenderWidgetHostView::RemoveViewProxy(OffscreenViewProxy* proxy) { + proxy->RemoveObserver(); + proxy_views_.erase(proxy); +} + +void OffScreenRenderWidgetHostView::ProxyViewDestroyed( + OffscreenViewProxy* proxy) { + proxy_views_.erase(proxy); + Invalidate(); +} + void OffScreenRenderWidgetHostView::RegisterGuestViewFrameSwappedCallback( content::RenderWidgetHostViewGuest* guest_host_view) { guest_host_view->RegisterFrameSwappedCallback(base::MakeUnique( @@ -906,12 +992,16 @@ void CopyBitmapTo( char* dest = static_cast(destination.getPixels()); int pixelsize = source.bytesPerPixel(); - if (pos.x() + pos.width() <= destination.width() && - pos.y() + pos.height() <= destination.height()) { - for (int i = 0; i < pos.height(); i++) { + int width = pos.x() + pos.width() <= destination.width() ? pos.width() + : pos.width() - ((pos.x() + pos.width()) - destination.width()); + int height = pos.y() + pos.height() <= destination.height() ? pos.height() + : pos.height() - ((pos.y() + pos.height()) - destination.height()); + + if (width > 0 && height > 0) { + for (int i = 0; i < height; i++) { memcpy(dest + ((pos.y() + i) * destination.width() + pos.x()) * pixelsize, src + (i * source.width()) * pixelsize, - pos.width() * pixelsize); + width * pixelsize); } } @@ -926,19 +1016,41 @@ void OffScreenRenderWidgetHostView::OnPaint( if (parent_callback_) { parent_callback_.Run(damage_rect, bitmap); - } else if (popup_host_view_ && popup_bitmap_.get()) { - gfx::Rect pos = popup_host_view_->popup_position_; + } else { gfx::Rect damage(damage_rect); - damage.Union(pos); - SkBitmap copy = SkBitmapOperations::CreateTiledBitmap(bitmap, - pos.x(), pos.y(), pos.width(), pos.height()); + std::vector damages; + std::vector bitmaps; + std::vector originals; + + if (popup_host_view_ && popup_bitmap_.get()) { + gfx::Rect pos = popup_host_view_->popup_position_; + damage.Union(pos); + damages.push_back(pos); + bitmaps.push_back(popup_bitmap_.get()); + originals.push_back(SkBitmapOperations::CreateTiledBitmap(bitmap, + pos.x(), pos.y(), pos.width(), pos.height())); + } - CopyBitmapTo(bitmap, *popup_bitmap_, pos); + for (auto proxy_view : proxy_views_) { + gfx::Rect pos = proxy_view->GetBounds(); + damage.Union(pos); + damages.push_back(pos); + bitmaps.push_back(proxy_view->GetBitmap()); + originals.push_back(SkBitmapOperations::CreateTiledBitmap(bitmap, + pos.x(), pos.y(), pos.width(), pos.height())); + } + + for (size_t i = 0; i < damages.size(); i++) { + CopyBitmapTo(bitmap, *(bitmaps[i]), damages[i]); + } + + damage.Intersect(GetViewBounds()); callback_.Run(damage, bitmap); - CopyBitmapTo(bitmap, copy, pos); - } else { - callback_.Run(damage_rect, bitmap); + + for (size_t i = 0; i < damages.size(); i++) { + CopyBitmapTo(bitmap, originals[i], damages[i]); + } } ReleaseResize(); @@ -951,6 +1063,11 @@ void OffScreenRenderWidgetHostView::OnPopupPaint( InvalidateBounds(popup_host_view_->popup_position_); } +void OffScreenRenderWidgetHostView::OnProxyViewPaint( + const gfx::Rect& damage_rect) { + InvalidateBounds(damage_rect); +} + void OffScreenRenderWidgetHostView::HoldResize() { if (!hold_resize_) hold_resize_ = true; @@ -992,6 +1109,21 @@ void OffScreenRenderWidgetHostView::ProcessKeyboardEvent( void OffScreenRenderWidgetHostView::ProcessMouseEvent( const blink::WebMouseEvent& event, const ui::LatencyInfo& latency) { + for (auto proxy_view : proxy_views_) { + gfx::Rect bounds = proxy_view->GetBounds(); + if (bounds.Contains(event.x, event.y)) { + blink::WebMouseEvent proxy_event(event); + proxy_event.x -= bounds.x(); + proxy_event.y -= bounds.y(); + proxy_event.windowX = proxy_event.x; + proxy_event.windowY = proxy_event.y; + + ui::MouseEvent ui_event = UiMouseEventFromWebMouseEvent(proxy_event); + proxy_view->OnEvent(&ui_event); + return; + } + } + if (!IsPopupWidget()) { if (popup_host_view_ && popup_host_view_->popup_position_.Contains(event.x, event.y)) { @@ -1005,6 +1137,7 @@ void OffScreenRenderWidgetHostView::ProcessMouseEvent( return; } } + if (!render_widget_host_) return; render_widget_host_->ForwardMouseEvent(event); @@ -1013,6 +1146,21 @@ void OffScreenRenderWidgetHostView::ProcessMouseEvent( void OffScreenRenderWidgetHostView::ProcessMouseWheelEvent( const blink::WebMouseWheelEvent& event, const ui::LatencyInfo& latency) { + for (auto proxy_view : proxy_views_) { + gfx::Rect bounds = proxy_view->GetBounds(); + if (bounds.Contains(event.x, event.y)) { + blink::WebMouseWheelEvent proxy_event(event); + proxy_event.x -= bounds.x(); + proxy_event.y -= bounds.y(); + proxy_event.windowX = proxy_event.x; + proxy_event.windowY = proxy_event.y; + + ui::MouseWheelEvent ui_event = + UiMouseWheelEventFromWebMouseEvent(proxy_event); + proxy_view->OnEvent(&ui_event); + return; + } + } if (!IsPopupWidget()) { if (popup_host_view_) { if (popup_host_view_->popup_position_.Contains(event.x, event.y)) { diff --git a/atom/browser/osr/osr_render_widget_host_view.h b/atom/browser/osr/osr_render_widget_host_view.h index 1f8558db1f9e0..dc204a311ff55 100644 --- a/atom/browser/osr/osr_render_widget_host_view.h +++ b/atom/browser/osr/osr_render_widget_host_view.h @@ -16,6 +16,7 @@ #include "atom/browser/native_window.h" #include "atom/browser/native_window_observer.h" #include "atom/browser/osr/osr_output_device.h" +#include "atom/browser/osr/osr_view_proxy.h" #include "base/process/kill.h" #include "base/threading/thread.h" #include "base/time/time.h" @@ -68,7 +69,8 @@ class OffScreenRenderWidgetHostView #if !defined(OS_MACOSX) public content::DelegatedFrameHostClient, #endif - public NativeWindowObserver { + public NativeWindowObserver, + public OffscreenViewProxyObserver { public: OffScreenRenderWidgetHostView(bool transparent, const OnPaintCallback& callback, @@ -208,6 +210,9 @@ class OffScreenRenderWidgetHostView void CancelWidget(); void AddGuestHostView(OffScreenRenderWidgetHostView* guest_host); void RemoveGuestHostView(OffScreenRenderWidgetHostView* guest_host); + void AddViewProxy(OffscreenViewProxy* proxy); + void RemoveViewProxy(OffscreenViewProxy* proxy); + void ProxyViewDestroyed(OffscreenViewProxy* proxy); void RegisterGuestViewFrameSwappedCallback( content::RenderWidgetHostViewGuest* guest_host_view); @@ -216,6 +221,7 @@ class OffScreenRenderWidgetHostView void OnPaint(const gfx::Rect& damage_rect, const SkBitmap& bitmap); void OnPopupPaint(const gfx::Rect& damage_rect, const SkBitmap& bitmap); + void OnProxyViewPaint(const gfx::Rect& damage_rect); bool IsPopupWidget() const { return popup_type_ != blink::WebPopupTypeNone; @@ -273,6 +279,7 @@ class OffScreenRenderWidgetHostView std::unique_ptr popup_bitmap_; OffScreenRenderWidgetHostView* child_host_view_; std::set guest_host_views_; + std::set proxy_views_; NativeWindow* native_window_; OffScreenOutputDevice* software_output_device_; diff --git a/atom/browser/osr/osr_view_proxy.cc b/atom/browser/osr/osr_view_proxy.cc new file mode 100644 index 0000000000000..91366b9229e6e --- /dev/null +++ b/atom/browser/osr/osr_view_proxy.cc @@ -0,0 +1,58 @@ +// 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/browser/osr/osr_view_proxy.h" + +namespace atom { + +OffscreenViewProxy::OffscreenViewProxy(views::View* view) + : view_(view), observer_(nullptr) { + view_bitmap_.reset(new SkBitmap); +} + +OffscreenViewProxy::~OffscreenViewProxy() { + if (observer_) { + observer_->ProxyViewDestroyed(this); + } +} + +void OffscreenViewProxy::SetObserver(OffscreenViewProxyObserver* observer) { + if (observer_) { + observer_->ProxyViewDestroyed(this); + } + observer_ = observer; +} + +void OffscreenViewProxy::RemoveObserver() { + observer_ = nullptr; +} + +const SkBitmap* OffscreenViewProxy::GetBitmap() const { + return view_bitmap_.get(); +} + +void OffscreenViewProxy::SetBitmap(const SkBitmap& bitmap) { + if (view_bounds_.width() == bitmap.width() && + view_bounds_.height() == bitmap.height() && + observer_) { + view_bitmap_.reset(new SkBitmap(bitmap)); + observer_->OnProxyViewPaint(view_bounds_); + } +} + +const gfx::Rect& OffscreenViewProxy::GetBounds() { + return view_bounds_; +} + +void OffscreenViewProxy::SetBounds(const gfx::Rect& bounds) { + view_bounds_ = bounds; +} + +void OffscreenViewProxy::OnEvent(ui::Event* event) { + if (view_) { + view_->OnEvent(event); + } +} + +} // namespace atom diff --git a/atom/browser/osr/osr_view_proxy.h b/atom/browser/osr/osr_view_proxy.h new file mode 100644 index 0000000000000..f4d6946aba1a2 --- /dev/null +++ b/atom/browser/osr/osr_view_proxy.h @@ -0,0 +1,53 @@ +// 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_BROWSER_OSR_OSR_VIEW_PROXY_H_ +#define ATOM_BROWSER_OSR_OSR_VIEW_PROXY_H_ + +#include + +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/events/event.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/views/view.h" + +namespace atom { + +class OffscreenViewProxy; + +class OffscreenViewProxyObserver { + public: + virtual void OnProxyViewPaint(const gfx::Rect& damage_rect) = 0; + virtual void ProxyViewDestroyed(OffscreenViewProxy* proxy) = 0; +}; + +class OffscreenViewProxy { + public: + explicit OffscreenViewProxy(views::View* view); + ~OffscreenViewProxy(); + + void SetObserver(OffscreenViewProxyObserver* observer); + void RemoveObserver(); + + const SkBitmap* GetBitmap() const; + void SetBitmap(const SkBitmap& bitmap); + + const gfx::Rect& GetBounds(); + void SetBounds(const gfx::Rect& bounds); + + void OnEvent(ui::Event* event); + + void ResetView() { view_ = nullptr; } + private: + views::View* view_; + + gfx::Rect view_bounds_; + std::unique_ptr view_bitmap_; + + OffscreenViewProxyObserver* observer_; +}; + +} // namespace atom + +#endif // ATOM_BROWSER_OSR_OSR_VIEW_PROXY_H_ diff --git a/atom/browser/ui/autofill_popup.cc b/atom/browser/ui/autofill_popup.cc new file mode 100644 index 0000000000000..f58c0cf884a81 --- /dev/null +++ b/atom/browser/ui/autofill_popup.cc @@ -0,0 +1,266 @@ +// 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 +#include +#include + +#include "atom/browser/osr/osr_render_widget_host_view.h" +#include "atom/browser/osr/osr_view_proxy.h" +#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/geometry/point.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/vector2d.h" +#include "ui/gfx/text_utils.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), view_(nullptr) { + bold_font_list_ = + gfx::FontList().DeriveWithWeight(gfx::Font::Weight::BOLD); + smaller_font_list_ = + gfx::FontList().DeriveWithSizeDelta(kSmallerFontSizeDelta); +} + +AutofillPopup::~AutofillPopup() { + Hide(); +} + +void AutofillPopup::CreateView( + content::RenderFrameHost* frame_host, + bool offscreen, + views::Widget* parent_widget, + const gfx::RectF& r) { + frame_host_ = frame_host; + 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()); + popup_bounds_in_view_ = lb; + views::View::ConvertPointToScreen( + parent_widget->GetContentsView(), &menu_position); + popup_bounds_ = gfx::Rect(menu_position, lb.size()); + element_bounds_ = popup_bounds_; + + Hide(); + view_ = new AutofillPopupView(this, parent_widget); + view_->Show(); + + if (offscreen) { + auto* osr_rwhv = static_cast( + frame_host_->GetView()); + view_->view_proxy_.reset(new OffscreenViewProxy(view_)); + osr_rwhv->AddViewProxy(view_->view_proxy_.get()); + } +} + +void AutofillPopup::Hide() { + if (view_) { + view_->Hide(); + view_ = nullptr; + } +} + +void AutofillPopup::SetItems(const std::vector& values, + const std::vector& labels) { + values_ = values; + labels_ = labels; + UpdatePopupBounds(); + if (view_) { + view_->OnSuggestionsChanged(); + } +} + +void AutofillPopup::AcceptSuggestion(int index) { + frame_host_->Send(new AtomAutofillFrameMsg_AcceptSuggestion( + frame_host_->GetRoutingID(), 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); + popup_bounds_in_view_ = gfx::Rect(popup_bounds_in_view_.origin(), + gfx::Size(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 (size_t 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 (view_ && 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 (size_t i = 0; i < values_.size(); ++i) { + current_height += kRowHeight; + + if (y <= current_height) + return i; + } + + return values_.size() - 1; +} + +} // namespace atom diff --git a/atom/browser/ui/autofill_popup.h b/atom/browser/ui/autofill_popup.h new file mode 100644 index 0000000000000..2dc6b68c248aa --- /dev/null +++ b/atom/browser/ui/autofill_popup.h @@ -0,0 +1,82 @@ +// 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 + +#include "atom/browser/ui/views/autofill_popup_view.h" +#include "content/public/browser/render_frame_host.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: + explicit AutofillPopup(gfx::NativeView); + ~AutofillPopup(); + + void CreateView(content::RenderFrameHost* render_frame, + bool offscreen, 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_; + gfx::Rect popup_bounds_in_view_; + + // 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 + content::RenderFrameHost* frame_host_; + + // The popup view. The lifetime is managed by the owning Widget + AutofillPopupView* view_; + + DISALLOW_COPY_AND_ASSIGN(AutofillPopup); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_AUTOFILL_POPUP_H_ 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 0000000000000..e4999b30263f9 --- /dev/null +++ b/atom/browser/ui/views/autofill_popup_view.cc @@ -0,0 +1,471 @@ +// 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 "base/i18n/rtl.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), + view_proxy_(nullptr), + weak_ptr_factory_(this) { + CreateChildViews(); + SetFocusBehavior(FocusBehavior::ALWAYS); + set_drag_controller(this); +} + +AutofillPopupView::~AutofillPopupView() { + if (popup_) { + auto host = popup_->frame_host_->GetRenderViewHost()->GetWidget(); + host->RemoveKeyPressEventCallback(keypress_callback_); + popup_->view_ = nullptr; + popup_ = nullptr; + } + + RemoveObserver(); + + if (view_proxy_.get()) { + view_proxy_->ResetView(); + } + + if (GetWidget()) { + GetWidget()->Close(); + } +} + +void AutofillPopupView::Show() { + if (!popup_) + return; + + 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_->frame_host_->GetRenderViewHost()->GetWidget(); + host->AddKeyPressEventCallback(keypress_callback_); + + NotifyAccessibilityEvent(ui::AX_EVENT_MENU_START, true); +} + +void AutofillPopupView::Hide() { + if (popup_) { + auto host = popup_->frame_host_->GetRenderViewHost()->GetWidget(); + host->RemoveKeyPressEventCallback(keypress_callback_); + popup_ = nullptr; + } + + RemoveObserver(); + NotifyAccessibilityEvent(ui::AX_EVENT_MENU_END, true); + + if (GetWidget()) { + GetWidget()->Close(); + } +} + +void AutofillPopupView::OnSuggestionsChanged() { + if (!popup_) + return; + + CreateChildViews(); + if (popup_->GetLineCount() == 0) { + popup_->Hide(); + return; + } + DoUpdateBoundsAndRedrawPopup(); +} + +void AutofillPopupView::OnSelectedRowChanged( + base::Optional previous_row_selection, + base::Optional current_row_selection) { + SchedulePaint(); + + if (current_row_selection) { + int selected = current_row_selection.value_or(-1); + if (selected == -1 || selected >= child_count()) + return; + child_at(selected)->NotifyAccessibilityEvent(ui::AX_EVENT_SELECTION, true); + } +} + +void AutofillPopupView::DrawAutofillEntry(gfx::Canvas* canvas, + int index, + const gfx::Rect& entry_rect) { + if (!popup_) + return; + + canvas->FillRect( + entry_rect, + GetNativeTheme()->GetSystemColor( + popup_->GetBackgroundColorIDForRow(index))); + + const bool is_rtl = base::i18n::IsRTL(); + 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() { + if (!popup_) + return; + + 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() { + if (!popup_) + return; + + GetWidget()->SetBounds(popup_->popup_bounds_); + SchedulePaint(); +} + +void AutofillPopupView::OnPaint(gfx::Canvas* canvas) { + if (!popup_ || popup_->GetLineCount() != child_count()) + return; + gfx::Canvas* draw_canvas = canvas; + SkBitmap bitmap; + + if (view_proxy_.get()) { + bitmap.allocN32Pixels(popup_->popup_bounds_in_view_.width(), + popup_->popup_bounds_in_view_.height(), + true); + draw_canvas = new gfx::Canvas(new SkCanvas(bitmap), 1.0); + } + + draw_canvas->DrawColor(GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_ResultsTableNormalBackground)); + OnPaintBorder(draw_canvas); + + for (int i = 0; i < popup_->GetLineCount(); ++i) { + gfx::Rect line_rect = popup_->GetRowBounds(i); + + DrawAutofillEntry(draw_canvas, i, line_rect); + } + + if (view_proxy_.get()) { + view_proxy_->SetBounds(popup_->popup_bounds_in_view_); + view_proxy_->SetBitmap(bitmap); + } +} + +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) { + if (accelerator.modifiers() != ui::EF_NONE) + return false; + + if (accelerator.key_code() == ui::VKEY_ESCAPE) { + if (popup_) + popup_->Hide(); + return true; + } + + if (accelerator.key_code() == ui::VKEY_RETURN) + return AcceptSelectedLine(); + + return false; +} + +bool AutofillPopupView::HandleKeyPressEvent( + const content::NativeWebKeyboardEvent& event) { + if (!popup_) + return false; + 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_) + popup_->Hide(); +} + +void AutofillPopupView::OnWidgetBoundsChanged(views::Widget* widget, + const gfx::Rect& new_bounds) { + if (widget != parent_widget_) + return; + if (popup_) + popup_->Hide(); +} + +void AutofillPopupView::AcceptSuggestion(int index) { + if (!popup_) + return; + + popup_->AcceptSuggestion(index); + popup_->Hide(); +} + +bool AutofillPopupView::AcceptSelectedLine() { + if (!selected_line_ || selected_line_.value() >= popup_->GetLineCount()) + return false; + + AcceptSuggestion(selected_line_.value()); + return true; +} + +void AutofillPopupView::AcceptSelection(const gfx::Point& point) { + if (!popup_) + return; + + SetSelectedLine(popup_->LineFromY(point.y())); + AcceptSelectedLine(); +} + +void AutofillPopupView::SetSelectedLine(base::Optional selected_line) { + if (!popup_) + return; + if (selected_line_ == selected_line) + return; + if (selected_line && selected_line.value() >= popup_->GetLineCount()) + return; + + auto previous_selected_line(selected_line_); + selected_line_ = selected_line; + OnSelectedRowChanged(previous_selected_line, selected_line_); +} + +void AutofillPopupView::SetSelection(const gfx::Point& point) { + if (!popup_) + return; + + SetSelectedLine(popup_->LineFromY(point.y())); +} + +void AutofillPopupView::SelectNextLine() { + if (!popup_) + return; + + 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() { + if (!popup_) + return; + + 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 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 0000000000000..75d93184b06f0 --- /dev/null +++ b/atom/browser/ui/views/autofill_popup_view.h @@ -0,0 +1,152 @@ +// 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 "atom/browser/osr/osr_view_proxy.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: + friend class AutofillPopup; + + 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_; + + std::unique_ptr view_proxy_; + + // 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_ diff --git a/atom/browser/ui/views/menu_bar.cc b/atom/browser/ui/views/menu_bar.cc index 5d9b7152788f8..e403b07378098 100644 --- a/atom/browser/ui/views/menu_bar.cc +++ b/atom/browser/ui/views/menu_bar.cc @@ -119,7 +119,7 @@ bool MenuBar::GetMenuButtonFromScreenPoint(const gfx::Point& point, for (int i = 0; i < child_count(); ++i) { views::View* view = child_at(i); - if (view->bounds().Contains(location) && + if (view->GetMirroredBounds().Contains(location) && (menu_model_->GetTypeAt(i) == AtomMenuModel::TYPE_SUBMENU)) { *menu_model = menu_model_->GetSubmenuModelAt(i); *button = static_cast(view); diff --git a/atom/common/api/api_messages.h b/atom/common/api/api_messages.h index e25089b869a9b..cb8f190903331 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,16 @@ IPC_MESSAGE_ROUTED3(AtomViewMsg_Message, IPC_MESSAGE_ROUTED0(AtomViewMsg_Offscreen) +IPC_MESSAGE_ROUTED3(AtomAutofillFrameHostMsg_ShowPopup, + gfx::RectF /* bounds */, + std::vector /* values */, + std::vector /* labels */) + +IPC_MESSAGE_ROUTED0(AtomAutofillFrameHostMsg_HidePopup) + +IPC_MESSAGE_ROUTED1(AtomAutofillFrameMsg_AcceptSuggestion, + base::string16 /* suggestion */) + // 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 0000000000000..83d1e2cf87931 --- /dev/null +++ b/atom/renderer/atom_autofill_agent.cc @@ -0,0 +1,233 @@ +// 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 + +#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), + helper_(new Helper(this)), + focused_node_was_last_clicked_(false), + was_focused_before_now_(false), + weak_ptr_factory_(this) { + render_frame()->GetWebFrame()->setAutofillClient(this); +} + +void AutofillAgent::OnDestruct() { + delete this; +} + +void AutofillAgent::DidChangeScrollOffset() { + HidePopup(); +} + +void AutofillAgent::FocusedNodeChanged(const blink::WebNode&) { + focused_node_was_last_clicked_ = false; + was_focused_before_now_ = false; + 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; + 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); +} + +AutofillAgent::Helper::Helper(AutofillAgent* agent) + : content::RenderViewObserver(agent->render_frame()->GetRenderView()), + agent_(agent) { +} + +void AutofillAgent::Helper::OnMouseDown(const blink::WebNode& node) { + agent_->focused_node_was_last_clicked_ = !node.isNull() && node.focused(); +} + +void AutofillAgent::Helper::FocusChangeComplete() { + agent_->DoFocusChangeComplete(); +} + +bool AutofillAgent::OnMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(AutofillAgent, message) + IPC_MESSAGE_HANDLER(AtomAutofillFrameMsg_AcceptSuggestion, + OnAcceptSuggestion) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + + return handled; +} + +bool AutofillAgent::IsUserGesture() const { + return blink::WebUserGestureIndicator::isProcessingUserGesture(); +} + +void AutofillAgent::HidePopup() { + Send(new AtomAutofillFrameHostMsg_HidePopup(render_frame()->GetRoutingID())); +} + +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 AtomAutofillFrameHostMsg_ShowPopup( + render_frame()->GetRoutingID(), bounds, values, labels)); +} + +void AutofillAgent::OnAcceptSuggestion(base::string16 suggestion) { + auto element = render_frame()->GetWebFrame()->document().focusedElement(); + if (element.isFormControlElement()) { + toWebInputElement(&element)->setSuggestedValue( + blink::WebString::fromUTF16(suggestion)); + } +} + +void AutofillAgent::DoFocusChangeComplete() { + auto element = render_frame()->GetWebFrame()->document().focusedElement(); + if (element.isNull() || !element.isFormControlElement()) + return; + + if (focused_node_was_last_clicked_ && was_focused_before_now_) { + ShowSuggestionsOptions options; + options.autofill_on_empty_values = true; + auto input_element = toWebInputElement(&element); + if (input_element) + ShowSuggestions(*input_element, options); + } + + was_focused_before_now_ = true; + focused_node_was_last_clicked_ = false; +} + +} // namespace atom diff --git a/atom/renderer/atom_autofill_agent.h b/atom/renderer/atom_autofill_agent.h new file mode 100644 index 0000000000000..81d685b05ccb0 --- /dev/null +++ b/atom/renderer/atom_autofill_agent.h @@ -0,0 +1,91 @@ +// 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 + +#include "base/memory/weak_ptr.h" +#include "content/public/renderer/render_frame_observer.h" +#include "content/public/renderer/render_view_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: + explicit AutofillAgent(content::RenderFrame* frame); + + // content::RenderFrameObserver: + void OnDestruct() override; + + void DidChangeScrollOffset() override; + void FocusedNodeChanged(const blink::WebNode&) override; + + private: + class Helper : public content::RenderViewObserver { + public: + explicit Helper(AutofillAgent* agent); + + // content::RenderViewObserver implementation. + void OnDestruct() override {} + void OnMouseDown(const blink::WebNode&) override; + void FocusChangeComplete() override; + + private: + AutofillAgent* agent_; + }; + friend class Helper; + + struct ShowSuggestionsOptions { + ShowSuggestionsOptions(); + bool autofill_on_empty_values; + bool requires_caret_at_end; + }; + + bool OnMessageReceived(const IPC::Message& message) override; + + // 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); + + void DoFocusChangeComplete(); + + std::unique_ptr helper_; + + // True when the last click was on the focused node. + bool focused_node_was_last_clicked_; + + // This is set to false when the focus changes, then set back to true soon + // afterwards. This helps track whether an event happened after a node was + // already focused, or if it caused the focus to change. + bool was_focused_before_now_; + + 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 1219dfb5dcfae..c7818b2815f6c 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 63b46cb40e57c..f3e9eb06b352f 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -244,6 +244,8 @@ 'atom/browser/osr/osr_render_widget_host_view.cc', 'atom/browser/osr/osr_render_widget_host_view.h', 'atom/browser/osr/osr_render_widget_host_view_mac.mm', + 'atom/browser/osr/osr_view_proxy.cc', + 'atom/browser/osr/osr_view_proxy.h', 'atom/browser/net/about_protocol_handler.cc', 'atom/browser/net/about_protocol_handler.h', 'atom/browser/net/asar/asar_protocol_handler.cc', @@ -291,6 +293,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 +322,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 +485,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',