diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-12-10 16:19:40 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-12-10 16:01:50 +0000 |
commit | 51f6c2793adab2d864b3d2b360000ef8db1d3e92 (patch) | |
tree | 835b3b4446b012c75e80177cef9fbe6972cc7dbe /chromium/ui/views | |
parent | 6036726eb981b6c4b42047513b9d3f4ac865daac (diff) | |
download | qtwebengine-chromium-51f6c2793adab2d864b3d2b360000ef8db1d3e92.tar.gz |
BASELINE: Update Chromium to 71.0.3578.93
Change-Id: I6a32086c33670e1b033f8b10e6bf1fd4da1d105d
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Diffstat (limited to 'chromium/ui/views')
205 files changed, 4410 insertions, 7241 deletions
diff --git a/chromium/ui/views/BUILD.gn b/chromium/ui/views/BUILD.gn index 3265121c103..b7fa6d3073c 100644 --- a/chromium/ui/views/BUILD.gn +++ b/chromium/ui/views/BUILD.gn @@ -54,7 +54,13 @@ jumbo_component("views") { all_dependent_configs = [ ":flags" ] public = [ + # TODO(ccameron): Move these sources to the views_bridge_mac component + "../views_bridge_mac/bridge_factory_impl.h", + "../views_bridge_mac/bridged_native_widget_impl.h", + "../views_bridge_mac/native_widget_mac_nswindow.h", + "../views_bridge_mac/window_touch_bar_delegate.h", "accessibility/view_accessibility.h", + "accessibility/view_accessibility_utils.h", "accessible_pane_view.h", "animation/bounds_animator.h", "animation/bounds_animator_observer.h", @@ -84,13 +90,12 @@ jumbo_component("views") { "bubble/info_bubble.h", "bubble/tooltip_icon.h", "button_drag_utils.h", - "cocoa/bridged_native_widget.h", - "cocoa/native_widget_mac_nswindow.h", - "cocoa/window_touch_bar_delegate.h", + "cocoa/bridge_factory_host.h", "color_chooser/color_chooser_listener.h", "color_chooser/color_chooser_view.h", "context_menu_controller.h", "controls/animated_icon_view.h", + "controls/animated_image_view.h", "controls/button/blue_button.h", "controls/button/button.h", "controls/button/checkbox.h", @@ -108,6 +113,7 @@ jumbo_component("views") { "controls/focus_ring.h", "controls/focusable_border.h", "controls/image_view.h", + "controls/image_view_base.h", "controls/label.h", "controls/link.h", "controls/link_listener.h", @@ -176,6 +182,7 @@ jumbo_component("views") { "drag_utils.h", "event_monitor.h", "event_monitor_mac.h", + "event_utils.h", "focus/external_focus_tracker.h", "focus/focus_manager.h", "focus/focus_manager_delegate.h", @@ -256,7 +263,11 @@ jumbo_component("views") { ] sources = [ + # TODO(ccameron): Move these sources to the views_bridge_mac component + "../views_bridge_mac/bridged_native_widget_impl.mm", + "../views_bridge_mac/native_widget_mac_nswindow.mm", "accessibility/view_accessibility.cc", + "accessibility/view_accessibility_utils.cc", "accessible_pane_view.cc", "animation/bounds_animator.cc", "animation/flood_fill_ink_drop_ripple.cc", @@ -281,10 +292,9 @@ jumbo_component("views") { "bubble/info_bubble.cc", "bubble/tooltip_icon.cc", "button_drag_utils.cc", - "cocoa/bridged_native_widget.mm", - "cocoa/native_widget_mac_nswindow.mm", "color_chooser/color_chooser_view.cc", "controls/animated_icon_view.cc", + "controls/animated_image_view.cc", "controls/button/blue_button.cc", "controls/button/button.cc", "controls/button/checkbox.cc", @@ -300,6 +310,7 @@ jumbo_component("views") { "controls/focus_ring.cc", "controls/focusable_border.cc", "controls/image_view.cc", + "controls/image_view_base.cc", "controls/label.cc", "controls/link.cc", "controls/menu/display_change_listener_mac.cc", @@ -362,6 +373,7 @@ jumbo_component("views") { "drag_utils.cc", "drag_utils_mac.mm", "event_monitor_mac.mm", + "event_utils.cc", "focus/external_focus_tracker.cc", "focus/focus_manager.cc", "focus/focus_manager_factory.cc", @@ -434,28 +446,24 @@ jumbo_component("views") { # Internal sources. TODO(https://crbug.com/871123): Move more headers from # public into this list, along with the implementation file. sources += [ - "cocoa/bridged_content_view.h", - "cocoa/bridged_content_view.mm", - "cocoa/bridged_content_view_touch_bar.mm", - "cocoa/bridged_native_widget_host.h", + # TODO(ccameron): Move these sources to the views_bridge_mac component + "../views_bridge_mac/bridge_factory_impl.mm", + "../views_bridge_mac/bridged_content_view.h", + "../views_bridge_mac/bridged_content_view.mm", + "../views_bridge_mac/bridged_content_view_touch_bar.mm", + "../views_bridge_mac/cocoa_window_move_loop.h", + "../views_bridge_mac/cocoa_window_move_loop.mm", + "../views_bridge_mac/views_nswindow_delegate.h", + "../views_bridge_mac/views_nswindow_delegate.mm", + "../views_bridge_mac/views_scrollbar_bridge.h", + "../views_bridge_mac/views_scrollbar_bridge.mm", + "cocoa/bridge_factory_host.cc", "cocoa/bridged_native_widget_host_impl.h", "cocoa/bridged_native_widget_host_impl.mm", - "cocoa/bridged_native_widget_owner.h", - "cocoa/cocoa_mouse_capture.h", - "cocoa/cocoa_mouse_capture.mm", - "cocoa/cocoa_mouse_capture_delegate.h", - "cocoa/cocoa_window_move_loop.h", - "cocoa/cocoa_window_move_loop.mm", "cocoa/drag_drop_client_mac.h", "cocoa/drag_drop_client_mac.mm", "cocoa/tooltip_manager_mac.h", "cocoa/tooltip_manager_mac.mm", - "cocoa/views_nswindow_delegate.h", - "cocoa/views_nswindow_delegate.mm", - "cocoa/views_scrollbar_bridge.h", - "cocoa/views_scrollbar_bridge.mm", - "cocoa/widget_owner_nswindow_adapter.h", - "cocoa/widget_owner_nswindow_adapter.mm", "controls/button/label_button_label.cc", "controls/button/label_button_label.h", "controls/menu/menu_pre_target_handler.h", @@ -477,6 +485,7 @@ jumbo_component("views") { "//base:i18n", "//base/third_party/dynamic_annotations", "//cc/paint", + "//mojo/public/cpp/bindings", "//services/ws/public/mojom", "//skia", "//third_party/icu", @@ -506,7 +515,6 @@ jumbo_component("views") { "//ui/gfx/animation", "//ui/gfx/geometry", "//ui/views/resources", - "//ui/views_bridge_mac:mojo", ] if (use_x11) { @@ -588,11 +596,11 @@ jumbo_component("views") { public += [ "accessibility/ax_aura_obj_cache.h", "accessibility/ax_aura_obj_wrapper.h", + "accessibility/ax_root_obj_wrapper.h", "accessibility/ax_tree_source_views.h", "accessibility/ax_view_obj_wrapper.h", "accessibility/ax_widget_obj_wrapper.h", "accessibility/ax_window_obj_wrapper.h", - "bubble/tray_bubble_view.h", "controls/native/native_view_host_aura.h", "corewm/cursor_height_provider_win.h", "corewm/tooltip.h", @@ -620,11 +628,11 @@ jumbo_component("views") { sources += [ "accessibility/ax_aura_obj_cache.cc", "accessibility/ax_aura_obj_wrapper.cc", + "accessibility/ax_root_obj_wrapper.cc", "accessibility/ax_tree_source_views.cc", "accessibility/ax_view_obj_wrapper.cc", "accessibility/ax_widget_obj_wrapper.cc", "accessibility/ax_window_obj_wrapper.cc", - "bubble/tray_bubble_view.cc", "controls/menu/display_change_listener_aura.cc", "controls/menu/menu_pre_target_handler_aura.cc", "controls/menu/menu_pre_target_handler_aura.h", @@ -658,6 +666,7 @@ jumbo_component("views") { "//services/ws/public/mojom", "//ui/aura", "//ui/platform_window", + "//ui/platform_window/platform_window_handler", "//ui/touch_selection", "//ui/wm", "//ui/wm/public", @@ -734,7 +743,9 @@ jumbo_component("views") { "//components/crash/core/common", "//ui/accelerated_widget_mac", "//ui/events:dom_keycode_converter", + "//ui/views_bridge_mac", ] + public_deps += [ "//ui/views_bridge_mac:mojo" ] libs = [ "AppKit.framework", "CoreGraphics.framework", @@ -1111,7 +1122,10 @@ source_set("views_unittests_sources") { if (is_mac) { # views_unittests not yet compiling on Mac. http://crbug.com/378134 sources -= [ "controls/native/native_view_host_unittest.cc" ] - public_deps += [ "//ui/accelerated_widget_mac" ] + public_deps += [ + "//ui/accelerated_widget_mac", + "//ui/views_bridge_mac:views_bridge_mac", + ] } } @@ -1211,12 +1225,23 @@ source_set("views_interactive_ui_tests") { "corewm/desktop_capture_controller_unittest.cc", "widget/native_widget_aura_interactive_uitest.cc", ] + deps += [ "//ui/aura", "//ui/aura:test_support", "//ui/wm", "//ui/wm/public", ] + + if (!is_chromeos && ((is_linux && !use_x11) || is_fuchsia)) { + sources += [ "widget/desktop_aura/desktop_window_tree_host_platform_interactive_uitest.cc" ] + } + + deps += [ + "//ui/events/platform", + "//ui/platform_window", + "//ui/platform_window/platform_window_handler", + ] } if (use_x11) { diff --git a/chromium/ui/views/DEPS b/chromium/ui/views/DEPS index f591ea6f71c..a2d0ab1f98c 100644 --- a/chromium/ui/views/DEPS +++ b/chromium/ui/views/DEPS @@ -2,6 +2,7 @@ include_rules = [ "+cc/paint", "+components/crash/core/common/crash_key.h", "+components/vector_icons", + "+mojo/public/cpp/bindings", "+services/ws/public/mojom", "+skia/ext", "+third_party/iaccessible2", @@ -20,6 +21,7 @@ include_rules = [ "+ui/resources/grit/ui_resources.h", "+ui/strings/grit/ui_strings.h", "+ui/touch_selection", + "+ui/views_bridge_mac", "+ui/wm/core", "+ui/wm/public", diff --git a/chromium/ui/views/OWNERS b/chromium/ui/views/OWNERS index 4aca57c69ca..57c9ed66f82 100644 --- a/chromium/ui/views/OWNERS +++ b/chromium/ui/views/OWNERS @@ -10,4 +10,7 @@ per-file *_mac.*=ellyjones@chromium.org per-file *_cocoa.*=ellyjones@chromium.org per-file *.mm=ellyjones@chromium.org +# If you're doing structural changes get a review from one of the OWNERS. +per-file BUILD.gn=* + # COMPONENT: Internals>Views diff --git a/chromium/ui/views/accessibility/ax_aura_obj_cache.cc b/chromium/ui/views/accessibility/ax_aura_obj_cache.cc index 519d09c2959..5811bf26790 100644 --- a/chromium/ui/views/accessibility/ax_aura_obj_cache.cc +++ b/chromium/ui/views/accessibility/ax_aura_obj_cache.cc @@ -31,6 +31,9 @@ AXAuraObjCache* AXAuraObjCache::GetInstance() { } AXAuraObjWrapper* AXAuraObjCache::GetOrCreate(View* view) { + // Avoid problems with transient focus events. https://crbug.com/729449 + if (!view->GetWidget()) + return nullptr; return CreateInternal<AXViewObjWrapper>(view, view_to_id_map_); } @@ -122,24 +125,28 @@ AXAuraObjCache::~AXAuraObjCache() { } View* AXAuraObjCache::GetFocusedView() { - if (root_windows_.empty()) - return nullptr; - aura::client::FocusClient* focus_client = - GetFocusClient(*root_windows_.begin()); - if (!focus_client) - return nullptr; - - aura::Window* focused_window = focus_client->GetFocusedWindow(); - if (!focused_window) - return nullptr; - - Widget* focused_widget = Widget::GetWidgetForNativeView(focused_window); - while (!focused_widget) { - focused_window = focused_window->parent(); + Widget* focused_widget = focused_widget_for_testing_; + aura::Window* focused_window = nullptr; + if (!focused_widget) { + if (root_windows_.empty()) + return nullptr; + aura::client::FocusClient* focus_client = + GetFocusClient(*root_windows_.begin()); + if (!focus_client) + return nullptr; + + focused_window = focus_client->GetFocusedWindow(); if (!focused_window) - break; + return nullptr; focused_widget = Widget::GetWidgetForNativeView(focused_window); + while (!focused_widget) { + focused_window = focused_window->parent(); + if (!focused_window) + break; + + focused_widget = Widget::GetWidgetForNativeView(focused_window); + } } if (!focused_widget) @@ -153,7 +160,8 @@ View* AXAuraObjCache::GetFocusedView() { if (focused_view) return focused_view; - if (focused_window->GetProperty( + if (focused_window && + focused_window->GetProperty( aura::client::kAccessibilityFocusFallsbackToWidgetKey)) { // If focused widget has non client view, falls back to first child view of // its client view. We don't expect that non client view gets keyboard diff --git a/chromium/ui/views/accessibility/ax_aura_obj_cache.h b/chromium/ui/views/accessibility/ax_aura_obj_cache.h index f798bdb059e..3a4a448e0ed 100644 --- a/chromium/ui/views/accessibility/ax_aura_obj_cache.h +++ b/chromium/ui/views/accessibility/ax_aura_obj_cache.h @@ -45,8 +45,11 @@ class VIEWS_EXPORT AXAuraObjCache : public aura::client::FocusChangeObserver { ax::mojom::Event event_type) = 0; }; - // Get or create an entry in the cache based on an Aura view. + // Get or create an entry in the cache. May return null if the View is not + // associated with a Widget. AXAuraObjWrapper* GetOrCreate(View* view); + + // Get or create an entry in the cache. AXAuraObjWrapper* GetOrCreate(Widget* widget); AXAuraObjWrapper* GetOrCreate(aura::Window* window); @@ -92,6 +95,13 @@ class VIEWS_EXPORT AXAuraObjCache : public aura::client::FocusChangeObserver { void SetDelegate(Delegate* delegate) { delegate_ = delegate; } + // Changes the behavior of GetFocusedView() so that it only considers + // views within the given Widget, this enables making tests + // involving focus reliable. + void set_focused_widget_for_testing(views::Widget* widget) { + focused_widget_for_testing_ = widget; + } + private: friend struct base::DefaultSingletonTraits<AXAuraObjCache>; @@ -131,6 +141,8 @@ class VIEWS_EXPORT AXAuraObjCache : public aura::client::FocusChangeObserver { std::set<aura::Window*> root_windows_; + views::Widget* focused_widget_for_testing_ = nullptr; + DISALLOW_COPY_AND_ASSIGN(AXAuraObjCache); }; diff --git a/chromium/ui/views/accessibility/ax_root_obj_wrapper.cc b/chromium/ui/views/accessibility/ax_root_obj_wrapper.cc new file mode 100644 index 00000000000..8f16302c7cb --- /dev/null +++ b/chromium/ui/views/accessibility/ax_root_obj_wrapper.cc @@ -0,0 +1,115 @@ +// Copyright 2018 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 "ui/views/accessibility/ax_root_obj_wrapper.h" + +#include <utility> + +#include "base/stl_util.h" +#include "base/strings/utf_string_conversions.h" +#include "ui/accessibility/ax_enums.mojom.h" +#include "ui/accessibility/ax_node_data.h" +#include "ui/aura/env.h" +#include "ui/aura/window.h" +#include "ui/display/display.h" +#include "ui/display/screen.h" +#include "ui/views/accessibility/ax_aura_obj_cache.h" +#include "ui/views/accessibility/ax_window_obj_wrapper.h" + +AXRootObjWrapper::AXRootObjWrapper(views::AXAuraObjCache::Delegate* delegate) + : alert_window_(std::make_unique<aura::Window>(nullptr)), + delegate_(delegate) { + alert_window_->Init(ui::LAYER_NOT_DRAWN); +#if !defined(IS_CHROMECAST) + aura::Env::GetInstance()->AddObserver(this); + + if (display::Screen::GetScreen()) + display::Screen::GetScreen()->AddObserver(this); +#endif +} + +AXRootObjWrapper::~AXRootObjWrapper() { +#if !defined(IS_CHROMECAST) + if (display::Screen::GetScreen()) + display::Screen::GetScreen()->RemoveObserver(this); + + // If alert_window_ is nullptr already, that means OnWillDestroyEnv + // was already called, so we shouldn't call RemoveObserver(this) again. + if (!alert_window_) + return; + + aura::Env::GetInstance()->RemoveObserver(this); +#endif + alert_window_.reset(); +} + +views::AXAuraObjWrapper* AXRootObjWrapper::GetAlertForText( + const std::string& text) { + alert_window_->SetTitle(base::UTF8ToUTF16((text))); + views::AXWindowObjWrapper* window_obj = + static_cast<views::AXWindowObjWrapper*>( + views::AXAuraObjCache::GetInstance()->GetOrCreate( + alert_window_.get())); + window_obj->set_is_alert(true); + return window_obj; +} + +bool AXRootObjWrapper::HasChild(views::AXAuraObjWrapper* child) { + std::vector<views::AXAuraObjWrapper*> children; + GetChildren(&children); + return base::ContainsValue(children, child); +} + +bool AXRootObjWrapper::IsIgnored() { + return false; +} + +views::AXAuraObjWrapper* AXRootObjWrapper::GetParent() { + return NULL; +} + +void AXRootObjWrapper::GetChildren( + std::vector<views::AXAuraObjWrapper*>* out_children) { + views::AXAuraObjCache::GetInstance()->GetTopLevelWindows(out_children); + out_children->push_back( + views::AXAuraObjCache::GetInstance()->GetOrCreate(alert_window_.get())); +} + +void AXRootObjWrapper::Serialize(ui::AXNodeData* out_node_data) { + out_node_data->id = unique_id_.Get(); + out_node_data->role = ax::mojom::Role::kDesktop; + +#if !defined(IS_CHROMECAST) + display::Screen* screen = display::Screen::GetScreen(); + if (!screen) + return; + + const display::Display& display = screen->GetPrimaryDisplay(); + + // Utilize the display bounds to figure out if this screen is in landscape or + // portrait. We use this rather than |rotation| because some devices default + // to landscape, some in portrait. Encode landscape as horizontal state, + // portrait as vertical state. + if (display.bounds().width() > display.bounds().height()) + out_node_data->AddState(ax::mojom::State::kHorizontal); + else + out_node_data->AddState(ax::mojom::State::kVertical); +#endif +} + +const ui::AXUniqueId& AXRootObjWrapper::GetUniqueId() const { + return unique_id_; +} + +void AXRootObjWrapper::OnDisplayMetricsChanged(const display::Display& display, + uint32_t changed_metrics) { + delegate_->OnEvent(this, ax::mojom::Event::kLocationChanged); +} + +void AXRootObjWrapper::OnWindowInitialized(aura::Window* window) {} + +void AXRootObjWrapper::OnWillDestroyEnv() { + alert_window_.reset(); + aura::Env::GetInstance()->RemoveObserver(this); +} diff --git a/chromium/ui/views/accessibility/ax_root_obj_wrapper.h b/chromium/ui/views/accessibility/ax_root_obj_wrapper.h new file mode 100644 index 00000000000..f90f9853dab --- /dev/null +++ b/chromium/ui/views/accessibility/ax_root_obj_wrapper.h @@ -0,0 +1,60 @@ +// Copyright 2018 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 UI_VIEWS_ACCESSIBILITY_AX_ROOT_OBJ_WRAPPER_H_ +#define UI_VIEWS_ACCESSIBILITY_AX_ROOT_OBJ_WRAPPER_H_ + +#include <stdint.h> + +#include <memory> +#include <string> +#include <vector> + +#include "base/macros.h" +#include "ui/accessibility/platform/ax_unique_id.h" +#include "ui/aura/env_observer.h" +#include "ui/display/display_observer.h" +#include "ui/views/accessibility/ax_aura_obj_cache.h" +#include "ui/views/accessibility/ax_aura_obj_wrapper.h" + +class VIEWS_EXPORT AXRootObjWrapper : public views::AXAuraObjWrapper, + display::DisplayObserver, + aura::EnvObserver { + public: + explicit AXRootObjWrapper(views::AXAuraObjCache::Delegate* delegate); + ~AXRootObjWrapper() override; + + // Returns an AXAuraObjWrapper for an alert window with title set to |text|. + views::AXAuraObjWrapper* GetAlertForText(const std::string& text); + + // Convenience method to check for existence of a child. + bool HasChild(views::AXAuraObjWrapper* child); + + // views::AXAuraObjWrapper overrides. + bool IsIgnored() override; + views::AXAuraObjWrapper* GetParent() override; + void GetChildren( + std::vector<views::AXAuraObjWrapper*>* out_children) override; + void Serialize(ui::AXNodeData* out_node_data) override; + const ui::AXUniqueId& GetUniqueId() const override; + + private: + // display::DisplayObserver: + void OnDisplayMetricsChanged(const display::Display& display, + uint32_t changed_metrics) override; + + // aura::EnvObserver: + void OnWindowInitialized(aura::Window* window) override; + void OnWillDestroyEnv() override; + + ui::AXUniqueId unique_id_; + + std::unique_ptr<aura::Window> alert_window_; + + views::AXAuraObjCache::Delegate* delegate_; + + DISALLOW_COPY_AND_ASSIGN(AXRootObjWrapper); +}; + +#endif // UI_VIEWS_ACCESSIBILITY_AX_ROOT_OBJ_WRAPPER_H_ diff --git a/chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc b/chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc index 84b893fca7f..aa9e497e6b1 100644 --- a/chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc +++ b/chromium/ui/views/accessibility/ax_system_caret_win_interactive_uitest.cc @@ -21,6 +21,7 @@ #include "ui/gfx/geometry/rect.h" #include "ui/gfx/native_widget_types.h" #include "ui/gl/test/gl_surface_test_support.h" +#include "ui/views/controls/button/label_button.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/controls/textfield/textfield_test_api.h" #include "ui/views/test/widget_test.h" @@ -74,6 +75,132 @@ class AXSystemCaretWinTest : public test::WidgetTest { DISALLOW_COPY_AND_ASSIGN(AXSystemCaretWinTest); }; +class WinAccessibilityCaretEventMonitor { + public: + WinAccessibilityCaretEventMonitor(UINT event_min, UINT event_max); + ~WinAccessibilityCaretEventMonitor(); + + // Blocks until the next event is received. When it's received, it + // queries accessibility information about the object that fired the + // event and populates the event, hwnd, role, state, and name in the + // passed arguments. + void WaitForNextEvent(DWORD* out_event, UINT* out_role, UINT* out_state); + + private: + void OnWinEventHook(HWINEVENTHOOK handle, + DWORD event, + HWND hwnd, + LONG obj_id, + LONG child_id, + DWORD event_thread, + DWORD event_time); + + static void CALLBACK WinEventHookThunk(HWINEVENTHOOK handle, + DWORD event, + HWND hwnd, + LONG obj_id, + LONG child_id, + DWORD event_thread, + DWORD event_time); + + struct EventInfo { + DWORD event; + HWND hwnd; + LONG obj_id; + LONG child_id; + }; + + base::circular_deque<EventInfo> event_queue_; + base::RunLoop loop_runner_; + HWINEVENTHOOK win_event_hook_handle_; + static WinAccessibilityCaretEventMonitor* instance_; + + DISALLOW_COPY_AND_ASSIGN(WinAccessibilityCaretEventMonitor); +}; + +// static +WinAccessibilityCaretEventMonitor* + WinAccessibilityCaretEventMonitor::instance_ = NULL; + +WinAccessibilityCaretEventMonitor::WinAccessibilityCaretEventMonitor( + UINT event_min, + UINT event_max) { + CHECK(!instance_) << "There can be only one instance of" + << " WinAccessibilityCaretEventMonitor at a time."; + instance_ = this; + win_event_hook_handle_ = + SetWinEventHook(event_min, event_max, NULL, + &WinAccessibilityCaretEventMonitor::WinEventHookThunk, + GetCurrentProcessId(), + 0, // Hook all threads + WINEVENT_OUTOFCONTEXT); +} + +WinAccessibilityCaretEventMonitor::~WinAccessibilityCaretEventMonitor() { + UnhookWinEvent(win_event_hook_handle_); + instance_ = NULL; +} + +void WinAccessibilityCaretEventMonitor::WaitForNextEvent(DWORD* out_event, + UINT* out_role, + UINT* out_state) { + if (event_queue_.empty()) + loop_runner_.Run(); + + EventInfo event_info = event_queue_.front(); + event_queue_.pop_front(); + + *out_event = event_info.event; + + Microsoft::WRL::ComPtr<IAccessible> acc_obj; + base::win::ScopedVariant child_variant; + CHECK(S_OK == AccessibleObjectFromEvent( + event_info.hwnd, event_info.obj_id, event_info.child_id, + acc_obj.GetAddressOf(), child_variant.Receive())); + + base::win::ScopedVariant role_variant; + if (S_OK == acc_obj->get_accRole(child_variant, role_variant.Receive())) + *out_role = V_I4(role_variant.ptr()); + else + *out_role = 0; + + base::win::ScopedVariant state_variant; + if (S_OK == acc_obj->get_accState(child_variant, state_variant.Receive())) + *out_state = V_I4(state_variant.ptr()); + else + *out_state = 0; +} + +void WinAccessibilityCaretEventMonitor::OnWinEventHook(HWINEVENTHOOK handle, + DWORD event, + HWND hwnd, + LONG obj_id, + LONG child_id, + DWORD event_thread, + DWORD event_time) { + EventInfo event_info; + event_info.event = event; + event_info.hwnd = hwnd; + event_info.obj_id = obj_id; + event_info.child_id = child_id; + event_queue_.push_back(event_info); + loop_runner_.Quit(); +} + +// static +void CALLBACK +WinAccessibilityCaretEventMonitor::WinEventHookThunk(HWINEVENTHOOK handle, + DWORD event, + HWND hwnd, + LONG obj_id, + LONG child_id, + DWORD event_thread, + DWORD event_time) { + if (instance_ && obj_id == OBJID_CARET) { + instance_->OnWinEventHook(handle, event, hwnd, obj_id, child_id, + event_thread, event_time); + } +} } // namespace TEST_F(AXSystemCaretWinTest, DISABLED_TestOnCaretBoundsChangeInTextField) { @@ -183,4 +310,81 @@ TEST_F(AXSystemCaretWinTest, DISABLED_TestMovingWindow) { EXPECT_EQ(height, height3); } +TEST_F(AXSystemCaretWinTest, DISABLED_TestCaretMSAAEvents) { + TextfieldTestApi textfield_test_api(textfield_); + Microsoft::WRL::ComPtr<IAccessible> caret_accessible; + gfx::NativeWindow native_window = widget_->GetNativeWindow(); + ASSERT_NE(nullptr, native_window); + HWND hwnd = native_window->GetHost()->GetAcceleratedWidget(); + EXPECT_HRESULT_SUCCEEDED(AccessibleObjectFromWindow( + hwnd, static_cast<DWORD>(OBJID_CARET), IID_IAccessible, + reinterpret_cast<void**>(caret_accessible.GetAddressOf()))); + + DWORD event; + UINT role; + UINT state; + + { + // Set caret to start of textfield. + WinAccessibilityCaretEventMonitor monitor(EVENT_OBJECT_SHOW, + EVENT_OBJECT_LOCATIONCHANGE); + textfield_test_api.ExecuteTextEditCommand( + ui::TextEditCommand::MOVE_TO_BEGINNING_OF_DOCUMENT); + monitor.WaitForNextEvent(&event, &role, &state); + ASSERT_EQ(event, static_cast<DWORD>(EVENT_OBJECT_LOCATIONCHANGE)) + << "Event should be EVENT_OBJECT_LOCATIONCHANGE"; + ASSERT_EQ(role, static_cast<UINT>(ROLE_SYSTEM_CARET)) + << "Role should be ROLE_SYSTEM_CARET"; + ASSERT_EQ(state, static_cast<UINT>(0)) << "State should be 0"; + } + + { + // Set caret to end of textfield. + WinAccessibilityCaretEventMonitor monitor(EVENT_OBJECT_SHOW, + EVENT_OBJECT_LOCATIONCHANGE); + textfield_test_api.ExecuteTextEditCommand( + ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT); + monitor.WaitForNextEvent(&event, &role, &state); + ASSERT_EQ(event, static_cast<DWORD>(EVENT_OBJECT_LOCATIONCHANGE)) + << "Event should be EVENT_OBJECT_LOCATIONCHANGE"; + ASSERT_EQ(role, static_cast<UINT>(ROLE_SYSTEM_CARET)) + << "Role should be ROLE_SYSTEM_CARET"; + ASSERT_EQ(state, static_cast<UINT>(0)) << "State should be 0"; + } + + { + // Move focus to a button. + LabelButton button(nullptr, base::string16()); + button.SetBounds(500, 0, 200, 20); + widget_->GetRootView()->AddChildView(&button); + test::WidgetActivationWaiter waiter(widget_, true); + WinAccessibilityCaretEventMonitor monitor(EVENT_OBJECT_SHOW, + EVENT_OBJECT_LOCATIONCHANGE); + widget_->Show(); + waiter.Wait(); + button.SetFocusBehavior(View::FocusBehavior::ALWAYS); + button.RequestFocus(); + monitor.WaitForNextEvent(&event, &role, &state); + ASSERT_EQ(event, static_cast<DWORD>(EVENT_OBJECT_HIDE)) + << "Event should be EVENT_OBJECT_HIDE"; + ASSERT_EQ(role, static_cast<UINT>(ROLE_SYSTEM_CARET)) + << "Role should be ROLE_SYSTEM_CARET"; + ASSERT_EQ(state, static_cast<UINT>(STATE_SYSTEM_INVISIBLE)) + << "State should be STATE_SYSTEM_INVISIBLE"; + } + + { + // Move focus back to the text field. + WinAccessibilityCaretEventMonitor monitor(EVENT_OBJECT_SHOW, + EVENT_OBJECT_LOCATIONCHANGE); + textfield_->RequestFocus(); + monitor.WaitForNextEvent(&event, &role, &state); + ASSERT_EQ(event, static_cast<DWORD>(EVENT_OBJECT_SHOW)) + << "Event should be EVENT_OBJECT_SHOW"; + ASSERT_EQ(role, static_cast<UINT>(ROLE_SYSTEM_CARET)) + << "Role should be ROLE_SYSTEM_CARET"; + ASSERT_EQ(state, static_cast<UINT>(0)) << "State should be 0"; + } +} + } // namespace views diff --git a/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc b/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc index b8ea4c3c754..cd77dabfd1b 100644 --- a/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc +++ b/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc @@ -9,6 +9,7 @@ #include "base/strings/utf_string_conversions.h" #include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_node_data.h" +#include "ui/accessibility/ax_tree_id.h" #include "ui/accessibility/platform/aura_window_properties.h" #include "ui/accessibility/platform/ax_unique_id.h" #include "ui/aura/client/focus_client.h" @@ -96,9 +97,9 @@ void AXWindowObjWrapper::Serialize(ui::AXNodeData* out_node_data) { if (!window_->IsVisible()) out_node_data->AddState(ax::mojom::State::kInvisible); out_node_data->location = gfx::RectF(window_->GetBoundsInScreen()); - ui::AXTreeIDRegistry::AXTreeID child_ax_tree_id = - window_->GetProperty(ui::kChildAXTreeID); - if (child_ax_tree_id != ui::AXTreeIDRegistry::kNoAXTreeID) { + std::string* child_ax_tree_id_ptr = window_->GetProperty(ui::kChildAXTreeID); + if (child_ax_tree_id_ptr && ui::AXTreeID::FromString(*child_ax_tree_id_ptr) != + ui::AXTreeIDUnknown()) { // Most often, child AX trees are parented to Views. We need to handle // the case where they're not here, but we don't want the same AX tree // to be a child of two different parents. @@ -110,8 +111,8 @@ void AXWindowObjWrapper::Serialize(ui::AXNodeData* out_node_data) { return; } - out_node_data->AddIntAttribute(ax::mojom::IntAttribute::kChildTreeId, - child_ax_tree_id); + out_node_data->AddStringAttribute(ax::mojom::StringAttribute::kChildTreeId, + *child_ax_tree_id_ptr); } } diff --git a/chromium/ui/views/accessibility/view_accessibility_utils.cc b/chromium/ui/views/accessibility/view_accessibility_utils.cc new file mode 100644 index 00000000000..9663921aec7 --- /dev/null +++ b/chromium/ui/views/accessibility/view_accessibility_utils.cc @@ -0,0 +1,44 @@ +// Copyright 2018 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 "ui/views/accessibility/view_accessibility_utils.h" + +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" + +namespace views { + +// static +Widget* ViewAccessibilityUtils::GetFocusedChildWidgetForAccessibility( + const View* view) { + const FocusManager* focus_manager = view->GetFocusManager(); + if (!focus_manager) + return nullptr; + const View* focused_view = view->GetFocusManager()->GetFocusedView(); + if (!focused_view) + return nullptr; + + std::set<Widget*> child_widgets; + Widget::GetAllOwnedWidgets(view->GetWidget()->GetNativeView(), + &child_widgets); + for (auto iter = child_widgets.begin(); iter != child_widgets.end(); ++iter) { + Widget* child_widget = *iter; + DCHECK_NE(view->GetWidget(), child_widget); + + if (IsFocusedChildWidget(child_widget, focused_view)) + return child_widget; + } + + return nullptr; +} + +// static +bool ViewAccessibilityUtils::IsFocusedChildWidget(Widget* widget, + const View* focused_view) { + return widget->IsVisible() && + widget->GetContentsView()->Contains(focused_view); +}; + +} // namespace views diff --git a/chromium/ui/views/accessibility/view_accessibility_utils.h b/chromium/ui/views/accessibility/view_accessibility_utils.h new file mode 100644 index 00000000000..24c1a324b14 --- /dev/null +++ b/chromium/ui/views/accessibility/view_accessibility_utils.h @@ -0,0 +1,29 @@ +// Copyright 2018 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 UI_VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_UTILS_H_ +#define UI_VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_UTILS_H_ + +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" + +namespace views { + +class VIEWS_EXPORT ViewAccessibilityUtils { + public: + // Returns a focused child widget if the view has a child that should be + // treated as a special case. For example, if a tab modal dialog is visible + // and focused, this will return the dialog when called on the BrowserView. + // This helper function is used to treat such widgets as separate windows for + // accessibility. Returns nullptr if no such widget is present. + static Widget* GetFocusedChildWidgetForAccessibility(const View* view); + + // Used by GetFocusedChildWidgetForAccessibility to determine if a Widget + // should be handled separately. + static bool IsFocusedChildWidget(Widget* widget, const View* focused_view); +}; + +} // namespace views + +#endif // UI_VIEWS_ACCESSIBILITY_VIEW_ACCESSIBILITY_UTILS_H_ diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc index 10cc9408045..adc4575fd6e 100644 --- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc @@ -9,12 +9,15 @@ #include "base/lazy_instance.h" #include "base/threading/thread_task_runner_handle.h" +#include "ui/accessibility/ax_role_properties.h" #include "ui/accessibility/platform/ax_platform_node.h" #include "ui/events/event_utils.h" #include "ui/gfx/native_widget_types.h" +#include "ui/views/accessibility/view_accessibility_utils.h" #include "ui/views/controls/native/native_view_host.h" #include "ui/views/view.h" #include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" namespace views { @@ -146,7 +149,7 @@ void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent( OnMenuEnd(); break; case ax::mojom::Event::kSelection: - if (menu_depth_ && GetData().role == ax::mojom::Role::kMenuItem) + if (menu_depth_ && ui::IsMenuItem(GetData().role)) OnMenuItemActive(); break; case ax::mojom::Event::kFocusContext: { @@ -227,7 +230,12 @@ int ViewAXPlatformNodeDelegate::GetChildCount() { int child_count = view()->child_count(); std::vector<Widget*> child_widgets; - PopulateChildWidgetVector(&child_widgets); + bool is_tab_modal_showing; + PopulateChildWidgetVector(&child_widgets, &is_tab_modal_showing); + if (is_tab_modal_showing) { + DCHECK_EQ(child_widgets.size(), 1ULL); + return 1; + } child_count += child_widgets.size(); return child_count; @@ -239,7 +247,16 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::ChildAtIndex(int index) { // If this is a root view, our widget might have child widgets. Include std::vector<Widget*> child_widgets; - PopulateChildWidgetVector(&child_widgets); + bool is_tab_modal_showing; + PopulateChildWidgetVector(&child_widgets, &is_tab_modal_showing); + + // If a visible tab modal dialog is present, ignore |index| and return the + // dialog. + if (is_tab_modal_showing) { + DCHECK_EQ(child_widgets.size(), 1ULL); + return child_widgets[0]->GetRootView()->GetNativeViewAccessible(); + } + int child_widget_count = static_cast<int>(child_widgets.size()); if (index < view()->child_count()) { @@ -290,7 +307,8 @@ gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::HitTestSync(int x, // Search child widgets first, since they're on top in the z-order. std::vector<Widget*> child_widgets; - PopulateChildWidgetVector(&child_widgets); + bool is_tab_modal_showing; + PopulateChildWidgetVector(&child_widgets, &is_tab_modal_showing); for (Widget* child_widget : child_widgets) { View* child_root_view = child_widget->GetRootView(); gfx::Point point(x, y); @@ -358,13 +376,20 @@ const ui::AXUniqueId& ViewAXPlatformNodeDelegate::GetUniqueId() const { } void ViewAXPlatformNodeDelegate::PopulateChildWidgetVector( - std::vector<Widget*>* result_child_widgets) { + std::vector<Widget*>* result_child_widgets, + bool* is_tab_modal_showing) { // Only attach child widgets to the root view. Widget* widget = view()->GetWidget(); // Note that during window close, a Widget may exist in a state where it has // no NativeView, but hasn't yet torn down its view hierarchy. - if (!widget || !widget->GetNativeView() || widget->GetRootView() != view()) + if (!widget || !widget->GetNativeView() || widget->GetRootView() != view()) { + *is_tab_modal_showing = false; return; + } + + const views::FocusManager* focus_manager = view()->GetFocusManager(); + const views::View* focused_view = + focus_manager ? focus_manager->GetFocusedView() : nullptr; std::set<Widget*> child_widgets; Widget::GetAllOwnedWidgets(widget->GetNativeView(), &child_widgets); @@ -378,8 +403,19 @@ void ViewAXPlatformNodeDelegate::PopulateChildWidgetVector( if (widget->GetNativeWindowProperty(kWidgetNativeViewHostKey)) continue; + // Focused child widgets should take the place of the web page they cover in + // the accessibility tree. + if (ViewAccessibilityUtils::IsFocusedChildWidget(child_widget, + focused_view)) { + result_child_widgets->clear(); + result_child_widgets->push_back(child_widget); + *is_tab_modal_showing = true; + return; + } + result_child_widgets->push_back(child_widget); } + *is_tab_modal_showing = false; } } // namespace views diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h index f3829054acc..8dea736b61f 100644 --- a/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h @@ -63,7 +63,11 @@ class VIEWS_EXPORT ViewAXPlatformNodeDelegate explicit ViewAXPlatformNodeDelegate(View* view); private: - void PopulateChildWidgetVector(std::vector<Widget*>* result_child_widgets); + // |is_tab_modal_showing| is set to true if, instead of populating + // |result_child_widgets| normally, a single child widget was returned (e.g. a + // dialog that should be read instead of the rest of the page contents). + void PopulateChildWidgetVector(std::vector<Widget*>* result_child_widgets, + bool* is_tab_modal_showing); void OnMenuItemActive(); void OnMenuStart(); diff --git a/chromium/ui/views/accessible_pane_view.cc b/chromium/ui/views/accessible_pane_view.cc index cfd86c97ac1..a0b033b19fd 100644 --- a/chromium/ui/views/accessible_pane_view.cc +++ b/chromium/ui/views/accessible_pane_view.cc @@ -174,6 +174,9 @@ bool AccessiblePaneView::AcceleratorPressed( case ui::VKEY_ESCAPE: { RemovePaneFocus(); View* last_focused_view = last_focused_view_tracker_->view(); + // Ignore |last_focused_view| if it's no longer in the same widget. + if (last_focused_view && GetWidget() != last_focused_view->GetWidget()) + last_focused_view = nullptr; if (last_focused_view) { focus_manager_->SetFocusedViewWithReason( last_focused_view, FocusManager::kReasonFocusRestore); diff --git a/chromium/ui/views/accessible_pane_view_unittest.cc b/chromium/ui/views/accessible_pane_view_unittest.cc index 7aef74a3bbe..f8e88ebfb46 100644 --- a/chromium/ui/views/accessible_pane_view_unittest.cc +++ b/chromium/ui/views/accessible_pane_view_unittest.cc @@ -206,7 +206,7 @@ TEST_F(AccessiblePaneViewTest, PaneFocusTraversal) { EXPECT_TRUE(original_test_view->SetPaneFocus( original_test_view->third_child_button())); - // Test travesal in second view. + // Test traversal in second view. // Set pane focus on second child. EXPECT_TRUE(test_view->SetPaneFocus(test_view->second_child_button())); // home @@ -234,4 +234,35 @@ TEST_F(AccessiblePaneViewTest, PaneFocusTraversal) { widget->CloseNow(); widget.reset(); } + +TEST_F(AccessiblePaneViewTest, DoesntCrashOnEscapeWithRemovedView) { + TestBarView* test_view1 = new TestBarView(); + TestBarView* test_view2 = new TestBarView(); + Widget widget; + Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.bounds = gfx::Rect(50, 50, 650, 650); + widget.Init(params); + View* root = widget.GetRootView(); + root->AddChildView(test_view1); + root->AddChildView(test_view2); + widget.Show(); + widget.Activate(); + + View* v1 = test_view1->child_button(); + View* v2 = test_view2->child_button(); + // Do the following: + // 1. focus |v1|. + // 2. focus |v2|. This makes |test_view2| remember |v1| as having focus. + // 3. Removes |v1| from it's parent. + // 4. Presses escape on |test_view2|. Escape attempts to revert focus to |v1| + // (because of step 2). Because |v1| is not in a widget this should not + // attempt to focus anything. + EXPECT_TRUE(test_view1->SetPaneFocus(v1)); + EXPECT_TRUE(test_view2->SetPaneFocus(v2)); + v1->parent()->RemoveChildView(v1); + // This shouldn't hit a CHECK in the FocusManager. + EXPECT_TRUE(test_view2->AcceleratorPressed(test_view2->escape_key())); +} + } // namespace views diff --git a/chromium/ui/views/animation/ink_drop_host_view.cc b/chromium/ui/views/animation/ink_drop_host_view.cc index f21c750bd1f..b98691b786e 100644 --- a/chromium/ui/views/animation/ink_drop_host_view.cc +++ b/chromium/ui/views/animation/ink_drop_host_view.cc @@ -16,6 +16,7 @@ #include "ui/views/animation/ink_drop_stub.h" #include "ui/views/animation/square_ink_drop_ripple.h" #include "ui/views/style/platform_style.h" +#include "ui/views/view_properties.h" namespace views { namespace { @@ -170,6 +171,9 @@ std::unique_ptr<InkDropHighlight> InkDropHostView::CreateInkDropHighlight() } std::unique_ptr<views::InkDropMask> InkDropHostView::CreateInkDropMask() const { + if (gfx::Path* highlight_path = GetProperty(kHighlightPathKey)) + return std::make_unique<views::PathInkDropMask>(size(), *highlight_path); + return nullptr; } diff --git a/chromium/ui/views/animation/ink_drop_mask.cc b/chromium/ui/views/animation/ink_drop_mask.cc index a2d2adb366e..63c93c61b4a 100644 --- a/chromium/ui/views/animation/ink_drop_mask.cc +++ b/chromium/ui/views/animation/ink_drop_mask.cc @@ -75,4 +75,20 @@ void CircleInkDropMask::OnPaintLayer(const ui::PaintContext& context) { recorder.canvas()->DrawCircle(mask_center_, mask_radius_, flags); } +// PathInkDropMask + +PathInkDropMask::PathInkDropMask(const gfx::Size& layer_size, + const gfx::Path& path) + : InkDropMask(layer_size), path_(path) {} + +void PathInkDropMask::OnPaintLayer(const ui::PaintContext& context) { + cc::PaintFlags flags; + flags.setAlpha(255); + flags.setStyle(cc::PaintFlags::kFill_Style); + flags.setAntiAlias(true); + + ui::PaintRecorder recorder(context, layer()->size()); + recorder.canvas()->DrawPath(path_, flags); +} + } // namespace views diff --git a/chromium/ui/views/animation/ink_drop_mask.h b/chromium/ui/views/animation/ink_drop_mask.h index debfb1604e2..1a3fa87e066 100644 --- a/chromium/ui/views/animation/ink_drop_mask.h +++ b/chromium/ui/views/animation/ink_drop_mask.h @@ -11,6 +11,7 @@ #include "ui/gfx/geometry/insets_f.h" #include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" +#include "ui/gfx/path.h" #include "ui/views/views_export.h" namespace views { @@ -33,7 +34,7 @@ class VIEWS_EXPORT InkDropMask : public ui::LayerDelegate { explicit InkDropMask(const gfx::Size& layer_size); private: - // Overriden from ui::LayerDelegate: + // ui::LayerDelegate: void OnDeviceScaleFactorChanged(float old_device_scale_factor, float new_device_scale_factor) override; @@ -50,7 +51,7 @@ class VIEWS_EXPORT RoundRectInkDropMask : public InkDropMask { float corner_radius); private: - // Overriden from InkDropMask: + // InkDropMask: void OnPaintLayer(const ui::PaintContext& context) override; gfx::InsetsF mask_insets_; @@ -67,7 +68,7 @@ class VIEWS_EXPORT CircleInkDropMask : public InkDropMask { int mask_radius); private: - // Overriden from InkDropMask: + // InkDropMask: void OnPaintLayer(const ui::PaintContext& context) override; gfx::Point mask_center_; @@ -76,6 +77,20 @@ class VIEWS_EXPORT CircleInkDropMask : public InkDropMask { DISALLOW_COPY_AND_ASSIGN(CircleInkDropMask); }; +// An ink-drop mask that paints a specified path. +class VIEWS_EXPORT PathInkDropMask : public InkDropMask { + public: + PathInkDropMask(const gfx::Size& layer_size, const gfx::Path& path); + + private: + // InkDropMask: + void OnPaintLayer(const ui::PaintContext& context) override; + + gfx::Path path_; + + DISALLOW_COPY_AND_ASSIGN(PathInkDropMask); +}; + } // namespace views #endif // UI_VIEWS_ANIMATION_INK_DROP_MASK_H_ diff --git a/chromium/ui/views/bubble/OWNERS b/chromium/ui/views/bubble/OWNERS index f04407c0829..d17d1b2257c 100644 --- a/chromium/ui/views/bubble/OWNERS +++ b/chromium/ui/views/bubble/OWNERS @@ -1,4 +1,3 @@ msw@chromium.org -per-file tray_bubble_view.*=stevenjb@chromium.org # COMPONENT: UI>Browser>Bubbles diff --git a/chromium/ui/views/bubble/bubble_border.cc b/chromium/ui/views/bubble/bubble_border.cc index 9fc042cd02f..88e4653b928 100644 --- a/chromium/ui/views/bubble/bubble_border.cc +++ b/chromium/ui/views/bubble/bubble_border.cc @@ -14,6 +14,7 @@ #include "third_party/skia/include/core/SkPath.h" #include "ui/base/material_design/material_design_controller.h" #include "ui/base/resource/resource_bundle.h" +#include "ui/gfx/color_palette.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/path.h" #include "ui/gfx/scoped_canvas.h" @@ -127,7 +128,8 @@ gfx::Rect BubbleBorder::GetBounds(const gfx::Rect& anchor_rect, } // With NO_ASSETS, there should be further insets, but the same logic is // used to position the bubble origin according to |anchor_rect|. - DCHECK(shadow_ != NO_ASSETS || shadow_insets.IsEmpty()); + DCHECK((shadow_ != NO_ASSETS && shadow_ != NO_SHADOW) || + shadow_insets.IsEmpty()); contents_bounds.Inset(-shadow_insets); // |arrow_offset_| is used to adjust bubbles that would normally be // partially offscreen. @@ -177,6 +179,9 @@ void BubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) { if (shadow_ == NO_ASSETS) return PaintNoAssets(view, canvas); + if (shadow_ == NO_SHADOW) + return PaintNoShadow(view, canvas); + gfx::ScopedCanvas scoped(canvas); SkRRect r_rect = GetClientRect(view); @@ -188,9 +193,11 @@ void BubbleBorder::Paint(const views::View& view, gfx::Canvas* canvas) { } gfx::Insets BubbleBorder::GetInsets() const { - return (shadow_ == NO_ASSETS) - ? gfx::Insets() - : GetBorderAndShadowInsets(md_shadow_elevation_); + if (shadow_ == NO_ASSETS) + return gfx::Insets(); + if (shadow_ == NO_SHADOW) + return gfx::Insets(kBorderThicknessDip); + return GetBorderAndShadowInsets(md_shadow_elevation_); } gfx::Size BubbleBorder::GetMinimumSize() const { @@ -247,8 +254,8 @@ const cc::PaintFlags& BubbleBorder::GetBorderAndShadowFlags( return flag_map->find(key)->second; cc::PaintFlags flags; - constexpr SkColor kBorderColor = SkColorSetA(SK_ColorBLACK, 0x26); - flags.setColor(kBorderColor); + constexpr SkColor kBlurredBorderColor = SkColorSetA(SK_ColorBLACK, 0x26); + flags.setColor(kBlurredBorderColor); flags.setAntiAlias(true); flags.setLooper( gfx::CreateShadowDrawLooper(GetShadowValues(elevation, color))); @@ -280,6 +287,18 @@ void BubbleBorder::PaintNoAssets(const View& view, gfx::Canvas* canvas) { canvas->sk_canvas()->drawColor(SK_ColorTRANSPARENT, SkBlendMode::kSrc); } +void BubbleBorder::PaintNoShadow(const View& view, gfx::Canvas* canvas) { + gfx::RectF bounds(view.GetLocalBounds()); + bounds.Inset(gfx::InsetsF(kBorderThicknessDip / 2.0f)); + cc::PaintFlags flags; + flags.setAntiAlias(true); + flags.setStyle(cc::PaintFlags::kStroke_Style); + flags.setStrokeWidth(kBorderThicknessDip); + constexpr SkColor kBorderColor = gfx::kGoogleGrey600; + flags.setColor(kBorderColor); + canvas->DrawRoundRect(bounds, GetBorderCornerRadius(), flags); +} + void BubbleBackground::Paint(gfx::Canvas* canvas, views::View* view) const { if (border_->shadow() == BubbleBorder::NO_SHADOW_OPAQUE_BORDER) canvas->DrawColor(border_->background_color()); diff --git a/chromium/ui/views/bubble/bubble_border.h b/chromium/ui/views/bubble/bubble_border.h index d4a6e2a28a7..79c182cffd1 100644 --- a/chromium/ui/views/bubble/bubble_border.h +++ b/chromium/ui/views/bubble/bubble_border.h @@ -229,6 +229,9 @@ class VIEWS_EXPORT BubbleBorder : public Border { // to make the window shape based on insets and GetBorderCornerRadius(). void PaintNoAssets(const View& view, gfx::Canvas* canvas); + // Paint for the NO_SHADOW shadow type. This paints a simple line border. + void PaintNoShadow(const View& view, gfx::Canvas* canvas); + Arrow arrow_; int arrow_offset_; // Corner radius for the bubble border. If supplied the border will use diff --git a/chromium/ui/views/bubble/bubble_border_unittest.cc b/chromium/ui/views/bubble/bubble_border_unittest.cc index e1420089ab5..54476123fc1 100644 --- a/chromium/ui/views/bubble/bubble_border_unittest.cc +++ b/chromium/ui/views/bubble/bubble_border_unittest.cc @@ -314,6 +314,9 @@ TEST_F(BubbleBorderTest, GetBoundsOriginTest) { const int kStrokeWidth = shadow == BubbleBorder::NO_ASSETS ? 0 : BubbleBorder::kStroke; + const int kBorderedContentHeight = + kContentSize.height() + (2 * kStrokeWidth); + const int kTopHorizArrowY = kAnchor.bottom() + kStrokeWidth - kInsets.top(); const int kBottomHorizArrowY = kAnchor.y() - kTotalSize.height(); const int kLeftVertArrowX = kAnchor.x() + kAnchor.width(); @@ -338,9 +341,9 @@ TEST_F(BubbleBorderTest, GetBoundsOriginTest) { // Vertical arrow tests. {BubbleBorder::LEFT_TOP, kLeftVertArrowX, kAnchor.y() + kStrokeWidth}, {BubbleBorder::LEFT_CENTER, - kLeftVertArrowX - (kInsets.right() - kStrokeWidth), - kAnchor.CenterPoint().y() - (kTotalSize.height() / 2) + - (2 * kStrokeWidth)}, + kLeftVertArrowX - (kInsets.left() - kStrokeWidth), + kAnchor.CenterPoint().y() - (kBorderedContentHeight / 2) - + (kInsets.top() - kStrokeWidth)}, {BubbleBorder::RIGHT_BOTTOM, kRightVertArrowX, kAnchor.y() + kAnchor.height() - kTotalSize.height() - kStrokeWidth}, diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate_view.cc b/chromium/ui/views/bubble/bubble_dialog_delegate_view.cc index 84d9ade9513..434ae1ea7db 100644 --- a/chromium/ui/views/bubble/bubble_dialog_delegate_view.cc +++ b/chromium/ui/views/bubble/bubble_dialog_delegate_view.cc @@ -17,7 +17,6 @@ #include "ui/views/layout/layout_manager.h" #include "ui/views/layout/layout_provider.h" #include "ui/views/view_properties.h" -#include "ui/views/view_tracker.h" #include "ui/views/views_delegate.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_observer.h" @@ -51,17 +50,30 @@ class BubbleDialogFrameView : public BubbleFrameView { DISALLOW_COPY_AND_ASSIGN(BubbleDialogFrameView); }; +bool CustomShadowsSupported() { +#if defined(OS_WIN) + return ui::win::IsAeroGlassEnabled(); +#else + return true; +#endif +} + // Create a widget to host the bubble. Widget* CreateBubbleWidget(BubbleDialogDelegateView* bubble) { Widget* bubble_widget = new Widget(); Widget::InitParams bubble_params(Widget::InitParams::TYPE_BUBBLE); bubble_params.delegate = bubble; - bubble_params.opacity = Widget::InitParams::TRANSLUCENT_WINDOW; + bubble_params.opacity = CustomShadowsSupported() + ? Widget::InitParams::TRANSLUCENT_WINDOW + : Widget::InitParams::OPAQUE_WINDOW; bubble_params.accept_events = bubble->accept_events(); // Use a window default shadow if the bubble doesn't provides its own. - bubble_params.shadow_type = bubble->shadow() == BubbleBorder::NO_ASSETS - ? Widget::InitParams::SHADOW_TYPE_DEFAULT - : Widget::InitParams::SHADOW_TYPE_NONE; + if (bubble->GetShadow() == BubbleBorder::NO_ASSETS) + bubble_params.shadow_type = Widget::InitParams::SHADOW_TYPE_DEFAULT; + else if (CustomShadowsSupported()) + bubble_params.shadow_type = Widget::InitParams::SHADOW_TYPE_NONE; + else + bubble_params.shadow_type = Widget::InitParams::SHADOW_TYPE_DROP; if (bubble->parent_window()) bubble_params.parent = bubble->parent_window(); else if (bubble->anchor_widget()) @@ -102,12 +114,7 @@ Widget* BubbleDialogDelegateView::CreateBubble( bubble_delegate->SetAnchorView(bubble_delegate->GetAnchorView()); Widget* bubble_widget = CreateBubbleWidget(bubble_delegate); -#if defined(OS_WIN) - // If glass is enabled, the bubble is allowed to extend outside the bounds of - // the parent frame and let DWM handle compositing. If not, then we don't - // want to allow the bubble to extend the frame because it will be clipped. - bubble_delegate->set_adjust_if_offscreen(ui::win::IsAeroGlassEnabled()); -#elif (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_MACOSX) +#if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_MACOSX) // Linux clips bubble windows that extend outside their parent window bounds. // Mac never adjusts. bubble_delegate->set_adjust_if_offscreen(false); @@ -145,7 +152,11 @@ NonClientFrameView* BubbleDialogDelegateView::CreateNonClientFrameView( if (base::i18n::IsRTL() && mirror_arrow_in_rtl_) adjusted_arrow = BubbleBorder::horizontal_mirror(adjusted_arrow); std::unique_ptr<BubbleBorder> border = - std::make_unique<BubbleBorder>(adjusted_arrow, shadow(), color()); + std::make_unique<BubbleBorder>(adjusted_arrow, GetShadow(), color()); + // If custom shadows aren't supported we fall back to an OS provided square + // shadow. + if (!CustomShadowsSupported()) + border->SetCornerRadius(0); frame->SetBubbleBorder(std::move(border)); return frame; } @@ -198,16 +209,34 @@ void BubbleDialogDelegateView::OnWidgetBoundsChanged( SizeToContents(); } +BubbleBorder::Shadow BubbleDialogDelegateView::GetShadow() const { + if (CustomShadowsSupported() || shadow_ == BubbleBorder::NO_ASSETS) + return shadow_; + return BubbleBorder::NO_SHADOW; +} + View* BubbleDialogDelegateView::GetAnchorView() const { return anchor_view_tracker_->view(); } -void BubbleDialogDelegateView::set_arrow(BubbleBorder::Arrow arrow) { +void BubbleDialogDelegateView::SetHighlightedButton( + Button* highlighted_button) { + bool visible = GetWidget() && GetWidget()->IsVisible(); + // If the Widget is visible, ensure the old highlight (if any) is removed + // when the highlighted view changes. + if (visible) + UpdateHighlightedButton(false); + highlighted_button_tracker_.SetView(highlighted_button); + if (visible) + UpdateHighlightedButton(true); +} + +void BubbleDialogDelegateView::SetArrow(BubbleBorder::Arrow arrow) { if (arrow_ == arrow) return; arrow_ = arrow; - // If set_arrow() is called before CreateWidget(), there's no need to update + // If SetArrow() is called before CreateWidget(), there's no need to update // the BubbleFrameView. if (GetBubbleFrameView()) { GetBubbleFrameView()->bubble_border()->set_arrow(arrow); @@ -321,8 +350,10 @@ void BubbleDialogDelegateView::SetAnchorView(View* anchor_view) { // change as well. if (!anchor_view || anchor_widget() != anchor_view->GetWidget()) { if (anchor_widget()) { - if (GetWidget() && GetWidget()->IsVisible()) + if (GetWidget() && GetWidget()->IsVisible()) { UpdateAnchorWidgetRenderState(false); + UpdateHighlightedButton(false); + } anchor_widget_->RemoveObserver(this); anchor_widget_ = NULL; } @@ -330,7 +361,9 @@ void BubbleDialogDelegateView::SetAnchorView(View* anchor_view) { anchor_widget_ = anchor_view->GetWidget(); if (anchor_widget_) { anchor_widget_->AddObserver(this); - UpdateAnchorWidgetRenderState(GetWidget() && GetWidget()->IsVisible()); + const bool visible = GetWidget() && GetWidget()->IsVisible(); + UpdateAnchorWidgetRenderState(visible); + UpdateHighlightedButton(visible); } } } @@ -391,8 +424,10 @@ void BubbleDialogDelegateView::UpdateColorsFromTheme( void BubbleDialogDelegateView::HandleVisibilityChanged(Widget* widget, bool visible) { - if (widget == GetWidget()) + if (widget == GetWidget()) { UpdateAnchorWidgetRenderState(visible); + UpdateHighlightedButton(visible); + } // Fire ax::mojom::Event::kAlert for bubbles marked as // ax::mojom::Role::kAlertDialog; this instructs accessibility tools to read @@ -419,4 +454,11 @@ void BubbleDialogDelegateView::UpdateAnchorWidgetRenderState(bool visible) { anchor_widget()->GetTopLevelWidget()->SetAlwaysRenderAsActive(visible); } +void BubbleDialogDelegateView::UpdateHighlightedButton(bool highlighted) { + Button* button = Button::AsButton(highlighted_button_tracker_.view()); + button = button ? button : Button::AsButton(anchor_view_tracker_->view()); + if (button) + button->SetHighlighted(highlighted); +} + } // namespace views diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate_view.h b/chromium/ui/views/bubble/bubble_dialog_delegate_view.h index 51a95a0ab17..8d6561420c6 100644 --- a/chromium/ui/views/bubble/bubble_dialog_delegate_view.h +++ b/chromium/ui/views/bubble/bubble_dialog_delegate_view.h @@ -12,6 +12,7 @@ #include "build/build_config.h" #include "ui/accessibility/ax_enums.mojom.h" #include "ui/views/bubble/bubble_border.h" +#include "ui/views/view_tracker.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_observer.h" #include "ui/views/window/dialog_delegate.h" @@ -27,7 +28,7 @@ class Rect; namespace views { class BubbleFrameView; -class ViewTracker; +class Button; // BubbleDialogDelegateView is a special DialogDelegateView for bubbles. class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView, @@ -68,15 +69,17 @@ class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView, View* GetAnchorView() const; Widget* anchor_widget() const { return anchor_widget_; } + void SetHighlightedButton(Button* highlighted_button); + // The anchor rect is used in the absence of an assigned anchor view. const gfx::Rect& anchor_rect() const { return anchor_rect_; } BubbleBorder::Arrow arrow() const { return arrow_; } - void set_arrow(BubbleBorder::Arrow arrow); + void SetArrow(BubbleBorder::Arrow arrow); void set_mirror_arrow_in_rtl(bool mirror) { mirror_arrow_in_rtl_ = mirror; } - BubbleBorder::Shadow shadow() const { return shadow_; } + BubbleBorder::Shadow GetShadow() const; void set_shadow(BubbleBorder::Shadow shadow) { shadow_ = shadow; } SkColor color() const { return color_; } @@ -182,6 +185,11 @@ class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView, // When a bubble is visible, the anchor widget should always render as active. void UpdateAnchorWidgetRenderState(bool visible); + // Update the button highlight, which may be the anchor view or an explicit + // view set in |highlighted_button_tracker_|. This can be overridden to + // provide different highlight effects. + virtual void UpdateHighlightedButton(bool highlighted); + // A flag controlling bubble closure on deactivation. bool close_on_deactivate_; @@ -191,6 +199,11 @@ class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView, std::unique_ptr<ViewTracker> anchor_view_tracker_; Widget* anchor_widget_; + // If provided, this button should be highlighted while the bubble is visible. + // If not provided, the anchor_view will attempt to be highlighted. A + // ViewTracker is used because the view can be deleted. + ViewTracker highlighted_button_tracker_; + // The anchor rect used in the absence of an anchor view. mutable gfx::Rect anchor_rect_; diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc b/chromium/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc index 5de626a2bd4..cf205c20d3f 100644 --- a/chromium/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc +++ b/chromium/ui/views/bubble/bubble_dialog_delegate_view_unittest.cc @@ -11,7 +11,10 @@ #include "base/strings/utf_string_conversions.h" #include "ui/base/hit_test.h" #include "ui/events/event_utils.h" +#include "ui/views/animation/test/ink_drop_host_view_test_api.h" +#include "ui/views/animation/test/test_ink_drop.h" #include "ui/views/bubble/bubble_frame_view.h" +#include "ui/views/controls/button/button.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/controls/styled_label.h" #include "ui/views/test/test_views.h" @@ -22,6 +25,8 @@ namespace views { +using test::TestInkDrop; + namespace { constexpr int kContentHeight = 200; @@ -362,6 +367,7 @@ TEST_F(BubbleDialogDelegateViewTest, CloseMethods) { BubbleDialogDelegateView::CreateBubble(bubble_delegate); bubble_widget->Show(); BubbleFrameView* frame_view = bubble_delegate->GetBubbleFrameView(); + frame_view->ResetViewShownTimeStampForTesting(); Button* close_button = frame_view->close_; ASSERT_TRUE(close_button); frame_view->ButtonPressed( @@ -477,6 +483,62 @@ TEST_F(BubbleDialogDelegateViewTest, StyledLabelTitle) { bubble_widget->GetWindowBoundsInScreen().height()); } +// Ensure associated buttons are highlighted or unhighlighted when the bubble +// widget is shown or hidden respectively. +TEST_F(BubbleDialogDelegateViewTest, AttachedWidgetShowsInkDropWhenVisible) { + std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); + LabelButton* button = new LabelButton(nullptr, base::string16()); + anchor_widget->GetContentsView()->AddChildView(button); + TestInkDrop* ink_drop = new TestInkDrop(); + test::InkDropHostViewTestApi(button).SetInkDrop(base::WrapUnique(ink_drop)); + TestBubbleDialogDelegateView* bubble_delegate = + new TestBubbleDialogDelegateView(nullptr); + bubble_delegate->set_parent_window(anchor_widget->GetNativeView()); + + Widget* bubble_widget = + BubbleDialogDelegateView::CreateBubble(bubble_delegate); + bubble_delegate->SetHighlightedButton(button); + bubble_widget->Show(); + // Explicitly calling OnWidgetVisibilityChanging to test functionality for + // OS_WIN. Outside of the test environment this happens automatically by way + // of HWNDMessageHandler. + bubble_delegate->OnWidgetVisibilityChanging(bubble_widget, true); + EXPECT_EQ(InkDropState::ACTIVATED, ink_drop->GetTargetInkDropState()); + + bubble_widget->Close(); + bubble_delegate->OnWidgetVisibilityChanging(bubble_widget, false); + EXPECT_EQ(InkDropState::DEACTIVATED, ink_drop->GetTargetInkDropState()); +} + +// Ensure associated buttons are highlighted or unhighlighted when the bubble +// widget is shown or hidden respectively when highlighted button is set after +// widget is shown. +TEST_F(BubbleDialogDelegateViewTest, VisibleWidgetShowsInkDropOnAttaching) { + std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); + LabelButton* button = new LabelButton(nullptr, base::string16()); + anchor_widget->GetContentsView()->AddChildView(button); + TestInkDrop* ink_drop = new TestInkDrop(); + test::InkDropHostViewTestApi(button).SetInkDrop(base::WrapUnique(ink_drop)); + TestBubbleDialogDelegateView* bubble_delegate = + new TestBubbleDialogDelegateView(nullptr); + bubble_delegate->set_parent_window(anchor_widget->GetNativeView()); + + Widget* bubble_widget = + BubbleDialogDelegateView::CreateBubble(bubble_delegate); + bubble_widget->Show(); + // Explicitly calling OnWidgetVisibilityChanging to test functionality for + // OS_WIN. Outside of the test environment this happens automatically by way + // of HWNDMessageHandler. + bubble_delegate->OnWidgetVisibilityChanging(bubble_widget, true); + EXPECT_EQ(InkDropState::HIDDEN, ink_drop->GetTargetInkDropState()); + bubble_delegate->SetHighlightedButton(button); + EXPECT_EQ(InkDropState::ACTIVATED, ink_drop->GetTargetInkDropState()); + + bubble_widget->Close(); + bubble_delegate->OnWidgetVisibilityChanging(bubble_widget, false); + EXPECT_EQ(InkDropState::DEACTIVATED, ink_drop->GetTargetInkDropState()); +} + TEST_F(BubbleDialogDelegateViewTest, VisibleAnchorChanges) { std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); TestBubbleDialogDelegateView* bubble_delegate = diff --git a/chromium/ui/views/bubble/bubble_frame_view.cc b/chromium/ui/views/bubble/bubble_frame_view.cc index f43e505afb4..02aa4a05ff8 100644 --- a/chromium/ui/views/bubble/bubble_frame_view.cc +++ b/chromium/ui/views/bubble/bubble_frame_view.cc @@ -28,10 +28,12 @@ #include "ui/views/controls/button/image_button.h" #include "ui/views/controls/button/image_button_factory.h" #include "ui/views/controls/image_view.h" +#include "ui/views/event_utils.h" #include "ui/views/layout/box_layout.h" #include "ui/views/layout/layout_provider.h" #include "ui/views/paint_info.h" #include "ui/views/resources/grit/views_resources.h" +#include "ui/views/view_properties.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" #include "ui/views/window/client_view.h" @@ -131,6 +133,11 @@ Button* BubbleFrameView::CreateCloseButton(ButtonListener* listener) { close_button->SetTooltipText(l10n_util::GetStringUTF16(IDS_APP_CLOSE)); close_button->SizeToPreferredSize(); + // Let the close button use a circular inkdrop shape. + auto highlight_path = std::make_unique<gfx::Path>(); + highlight_path->addOval(gfx::RectToSkRect(gfx::Rect(close_button->size()))); + close_button->SetProperty(kHighlightPathKey, highlight_path.release()); + // Remove the close button from tab traversal on all platforms. Note this does // not affect screen readers' ability to focus the close button. Keyboard // access to the close button when not using a screen reader is done via the @@ -407,6 +414,13 @@ void BubbleFrameView::ViewHierarchyChanged( } } +void BubbleFrameView::VisibilityChanged(View* starting_from, bool is_visible) { + NonClientFrameView::VisibilityChanged(starting_from, is_visible); + + if (is_visible) + view_shown_time_stamp_ = base::TimeTicks::Now(); +} + void BubbleFrameView::OnPaint(gfx::Canvas* canvas) { OnPaintBackground(canvas); // Border comes after children. @@ -424,6 +438,9 @@ void BubbleFrameView::PaintChildren(const PaintInfo& paint_info) { } void BubbleFrameView::ButtonPressed(Button* sender, const ui::Event& event) { + if (IsPossiblyUnintendedInteraction(view_shown_time_stamp_, event)) + return; + if (sender == close_) { close_button_clicked_ = true; GetWidget()->Close(); @@ -477,6 +494,10 @@ gfx::Rect BubbleFrameView::GetUpdatedWindowBounds(const gfx::Rect& anchor_rect, return bubble_border_->GetBounds(anchor_rect, size); } +void BubbleFrameView::ResetViewShownTimeStampForTesting() { + view_shown_time_stamp_ = base::TimeTicks(); +} + gfx::Rect BubbleFrameView::GetAvailableScreenBounds( const gfx::Rect& rect) const { // The bubble attempts to fit within the current screen bounds. diff --git a/chromium/ui/views/bubble/bubble_frame_view.h b/chromium/ui/views/bubble/bubble_frame_view.h index eef3fda7a3e..bfb407a231f 100644 --- a/chromium/ui/views/bubble/bubble_frame_view.h +++ b/chromium/ui/views/bubble/bubble_frame_view.h @@ -8,6 +8,7 @@ #include "base/compiler_specific.h" #include "base/gtest_prod_util.h" #include "base/macros.h" +#include "base/time/time.h" #include "ui/gfx/font_list.h" #include "ui/gfx/geometry/insets.h" #include "ui/views/controls/button/button.h" @@ -65,6 +66,7 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView, void OnNativeThemeChanged(const ui::NativeTheme* theme) override; void ViewHierarchyChanged( const ViewHierarchyChangedDetails& details) override; + void VisibilityChanged(View* starting_from, bool is_visible) override; // ButtonListener: void ButtonPressed(Button* sender, const ui::Event& event) override; @@ -99,6 +101,11 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView, Button* GetCloseButtonForTest() { return close_; } + // Resets the time when view has been shown. Tests may need to call this + // method if they use events that could be otherwise treated as unintended. + // See IsPossiblyUnintendedInteraction(). + void ResetViewShownTimeStampForTesting(); + protected: // Returns the available screen bounds if the frame were to show in |rect|. virtual gfx::Rect GetAvailableScreenBounds(const gfx::Rect& rect) const; @@ -115,6 +122,7 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView, FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewTest, GetBoundsForClientView); FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewTest, RemoveFootnoteView); FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewTest, LayoutWithIcon); + FRIEND_TEST_ALL_PREFIXES(BubbleFrameViewTest, IgnorePossiblyUnintendedClicks); FRIEND_TEST_ALL_PREFIXES(BubbleDelegateTest, CloseReasons); FRIEND_TEST_ALL_PREFIXES(BubbleDialogDelegateViewTest, CloseMethods); @@ -180,6 +188,9 @@ class VIEWS_EXPORT BubbleFrameView : public NonClientFrameView, // Whether the close button was clicked. bool close_button_clicked_; + // Time when view has been shown. + base::TimeTicks view_shown_time_stamp_; + DISALLOW_COPY_AND_ASSIGN(BubbleFrameView); }; diff --git a/chromium/ui/views/bubble/bubble_frame_view_unittest.cc b/chromium/ui/views/bubble/bubble_frame_view_unittest.cc index 0a060d2fbf5..38930c39de0 100644 --- a/chromium/ui/views/bubble/bubble_frame_view_unittest.cc +++ b/chromium/ui/views/bubble/bubble_frame_view_unittest.cc @@ -8,14 +8,19 @@ #include "base/macros.h" #include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" #include "build/build_config.h" +#include "ui/events/base_event_utils.h" +#include "ui/events/event.h" #include "ui/gfx/geometry/insets.h" +#include "ui/gfx/geometry/point.h" #include "ui/gfx/geometry/rect.h" #include "ui/gfx/geometry/size.h" #include "ui/gfx/text_utils.h" #include "ui/views/bubble/bubble_border.h" #include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/controls/button/label_button.h" +#include "ui/views/metrics.h" #include "ui/views/test/test_layout_provider.h" #include "ui/views/test/test_views.h" #include "ui/views/test/views_test_base.h" @@ -84,7 +89,7 @@ class TestBubbleFrameView : public BubbleFrameView { : BubbleFrameView(gfx::Insets(), gfx::Insets(kMargin)), available_bounds_(gfx::Rect(0, 0, 1000, 1000)) { SetBubbleBorder(std::make_unique<BubbleBorder>( - kArrow, BubbleBorder::NO_SHADOW, kColor)); + kArrow, BubbleBorder::BIG_SHADOW, kColor)); widget_ = std::make_unique<Widget>(); widget_delegate_ = std::make_unique<TestBubbleFrameViewWidgetDelegate>(widget_.get()); @@ -751,4 +756,28 @@ TEST_F(BubbleFrameViewTest, NoElideTitle) { EXPECT_EQ(title, title_label->GetDisplayTextForTesting()); } +// Ensures that clicks are ignored for short time after view has been shown. +TEST_F(BubbleFrameViewTest, IgnorePossiblyUnintendedClicks) { + TestBubbleDialogDelegateView delegate; + TestAnchor anchor(CreateParams(Widget::InitParams::TYPE_WINDOW)); + delegate.SetAnchorView(anchor.widget().GetContentsView()); + Widget* bubble = BubbleDialogDelegateView::CreateBubble(&delegate); + bubble->Show(); + + BubbleFrameView* frame = delegate.GetBubbleFrameView(); + frame->ButtonPressed( + frame->close_, + ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), + ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE)); + EXPECT_FALSE(bubble->IsClosed()); + + frame->ButtonPressed( + frame->close_, + ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), + ui::EventTimeForNow() + base::TimeDelta::FromMilliseconds( + GetDoubleClickInterval()), + ui::EF_NONE, ui::EF_NONE)); + EXPECT_TRUE(bubble->IsClosed()); +} + } // namespace views diff --git a/chromium/ui/views/bubble/info_bubble.cc b/chromium/ui/views/bubble/info_bubble.cc index 65fc0411ece..442287d5d22 100644 --- a/chromium/ui/views/bubble/info_bubble.cc +++ b/chromium/ui/views/bubble/info_bubble.cc @@ -79,7 +79,7 @@ NonClientFrameView* InfoBubble::CreateNonClientFrameView(Widget* widget) { frame_ = new InfoBubbleFrame(margins()); frame_->set_available_bounds(anchor_widget()->GetWindowBoundsInScreen()); frame_->SetBubbleBorder(std::unique_ptr<BubbleBorder>( - new BubbleBorder(arrow(), shadow(), color()))); + new BubbleBorder(arrow(), GetShadow(), color()))); return frame_; } diff --git a/chromium/ui/views/bubble/tooltip_icon.cc b/chromium/ui/views/bubble/tooltip_icon.cc index 8e1969cd449..09be527d930 100644 --- a/chromium/ui/views/bubble/tooltip_icon.cc +++ b/chromium/ui/views/bubble/tooltip_icon.cc @@ -84,7 +84,7 @@ void TooltipIcon::ShowBubble() { bubble_ = new InfoBubble(this, tooltip_); bubble_->set_preferred_width(preferred_width_); - bubble_->set_arrow(anchor_point_arrow_); + bubble_->SetArrow(anchor_point_arrow_); // When shown due to a gesture event, close on deactivate (i.e. don't use // "focusless"). bubble_->set_can_activate(!mouse_inside_); diff --git a/chromium/ui/views/bubble/tray_bubble_view.cc b/chromium/ui/views/bubble/tray_bubble_view.cc deleted file mode 100644 index 9e2994cfefd..00000000000 --- a/chromium/ui/views/bubble/tray_bubble_view.cc +++ /dev/null @@ -1,496 +0,0 @@ -// 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 "ui/views/bubble/tray_bubble_view.h" - -#include <algorithm> - -#include "base/macros.h" -#include "cc/paint/paint_flags.h" -#include "third_party/skia/include/core/SkCanvas.h" -#include "third_party/skia/include/core/SkColor.h" -#include "third_party/skia/include/core/SkPath.h" -#include "third_party/skia/include/effects/SkBlurImageFilter.h" -#include "ui/accessibility/ax_node_data.h" -#include "ui/aura/env.h" -#include "ui/aura/window.h" -#include "ui/compositor/layer.h" -#include "ui/compositor/layer_owner.h" -#include "ui/events/event.h" -#include "ui/gfx/canvas.h" -#include "ui/gfx/color_palette.h" -#include "ui/gfx/geometry/insets.h" -#include "ui/gfx/geometry/rect.h" -#include "ui/gfx/path.h" -#include "ui/gfx/skia_util.h" -#include "ui/views/bubble/bubble_frame_view.h" -#include "ui/views/layout/box_layout.h" -#include "ui/views/painter.h" -#include "ui/views/views_delegate.h" -#include "ui/views/widget/widget.h" -#include "ui/wm/core/shadow_types.h" -#include "ui/wm/core/window_util.h" - -namespace views { - -namespace { - -// The sampling time for mouse position changes in ms - which is roughly a frame -// time. -const int kFrameTimeInMS = 30; - -BubbleBorder::Arrow GetArrowAlignment( - TrayBubbleView::AnchorAlignment alignment) { - if (alignment == TrayBubbleView::ANCHOR_ALIGNMENT_BOTTOM) { - return base::i18n::IsRTL() ? BubbleBorder::BOTTOM_LEFT - : BubbleBorder::BOTTOM_RIGHT; - } - if (alignment == TrayBubbleView::ANCHOR_ALIGNMENT_LEFT) - return BubbleBorder::LEFT_BOTTOM; - return BubbleBorder::RIGHT_BOTTOM; -} - -// Only one TrayBubbleView is visible at a time, but there are cases where the -// lifetimes of two different bubbles can overlap briefly. -int g_current_tray_bubble_showing_count_ = 0; - -} // namespace - -namespace internal { - -// Detects any mouse movement. This is needed to detect mouse movements by the -// user over the bubble if the bubble got created underneath the cursor. -class MouseMoveDetectorHost : public MouseWatcherHost { - public: - MouseMoveDetectorHost(); - ~MouseMoveDetectorHost() override; - - bool Contains(const gfx::Point& screen_point, MouseEventType type) override; - - private: - DISALLOW_COPY_AND_ASSIGN(MouseMoveDetectorHost); -}; - -MouseMoveDetectorHost::MouseMoveDetectorHost() { -} - -MouseMoveDetectorHost::~MouseMoveDetectorHost() { -} - -bool MouseMoveDetectorHost::Contains(const gfx::Point& screen_point, - MouseEventType type) { - return false; -} - -// Custom layout for the bubble-view. Does the default box-layout if there is -// enough height. Otherwise, makes sure the bottom rows are visible. -class BottomAlignedBoxLayout : public BoxLayout { - public: - explicit BottomAlignedBoxLayout(TrayBubbleView* bubble_view) - : BoxLayout(BoxLayout::kVertical), bubble_view_(bubble_view) {} - - ~BottomAlignedBoxLayout() override {} - - private: - void Layout(View* host) override { - if (host->height() >= host->GetPreferredSize().height() || - !bubble_view_->is_gesture_dragging()) { - BoxLayout::Layout(host); - return; - } - - int consumed_height = 0; - for (int i = host->child_count() - 1; - i >= 0 && consumed_height < host->height(); --i) { - View* child = host->child_at(i); - if (!child->visible()) - continue; - gfx::Size size = child->GetPreferredSize(); - child->SetBounds(0, host->height() - consumed_height - size.height(), - host->width(), size.height()); - consumed_height += size.height(); - } - } - - TrayBubbleView* bubble_view_; - - DISALLOW_COPY_AND_ASSIGN(BottomAlignedBoxLayout); -}; - -} // namespace internal - -using internal::BottomAlignedBoxLayout; - -TrayBubbleView::Delegate::~Delegate() {} - -void TrayBubbleView::Delegate::BubbleViewDestroyed() {} - -void TrayBubbleView::Delegate::OnMouseEnteredView() {} - -void TrayBubbleView::Delegate::OnMouseExitedView() {} - -base::string16 TrayBubbleView::Delegate::GetAccessibleNameForBubble() { - return base::string16(); -} - -bool TrayBubbleView::Delegate::ShouldEnableExtraKeyboardAccessibility() { - return false; -} - -void TrayBubbleView::Delegate::HideBubble(const TrayBubbleView* bubble_view) {} - -void TrayBubbleView::Delegate::ProcessGestureEventForBubble( - ui::GestureEvent* event) {} - -TrayBubbleView::InitParams::InitParams() = default; - -TrayBubbleView::InitParams::InitParams(const InitParams& other) = default; - -TrayBubbleView::RerouteEventHandler::RerouteEventHandler( - TrayBubbleView* tray_bubble_view) - : tray_bubble_view_(tray_bubble_view) { - aura::Env::GetInstance()->AddPreTargetHandler( - this, ui::EventTarget::Priority::kSystem); -} - -TrayBubbleView::RerouteEventHandler::~RerouteEventHandler() { - aura::Env::GetInstance()->RemovePreTargetHandler(this); -} - -void TrayBubbleView::RerouteEventHandler::OnKeyEvent(ui::KeyEvent* event) { - // Do not handle a key event if it is targeted to the tray or its descendants, - // or if the target has the tray as a transient ancestor. RerouteEventHandler - // is for rerouting events which are not targetted to the tray. Those events - // should be handled by the target. - aura::Window* target = static_cast<aura::Window*>(event->target()); - aura::Window* tray_window = tray_bubble_view_->GetWidget()->GetNativeView(); - if (target && (tray_window->Contains(target) || - wm::HasTransientAncestor(target, tray_window))) { - return; - } - - // Only passes Tab, Shift+Tab, Esc to the widget as it can consume more key - // events. e.g. Alt+Tab can be consumed as focus traversal by FocusManager. - ui::KeyboardCode key_code = event->key_code(); - int flags = event->flags(); - if ((key_code == ui::VKEY_TAB && flags == ui::EF_NONE) || - (key_code == ui::VKEY_TAB && flags == ui::EF_SHIFT_DOWN) || - (key_code == ui::VKEY_ESCAPE && flags == ui::EF_NONE)) { - // Make TrayBubbleView activatable as the following Widget::OnKeyEvent might - // try to activate it. - tray_bubble_view_->set_can_activate(true); - - tray_bubble_view_->GetWidget()->OnKeyEvent(event); - - if (event->handled()) - return; - } - - // Always consumes key event not to pass it to other widgets. Calling - // StopPropagation here to make this consistent with - // MenuController::OnWillDispatchKeyEvent. - event->StopPropagation(); - - // To provide consistent behavior with a menu, process accelerator as a menu - // is open if the event is not handled by the widget. - ui::Accelerator accelerator(*event); - ViewsDelegate::ProcessMenuAcceleratorResult result = - ViewsDelegate::GetInstance()->ProcessAcceleratorWhileMenuShowing( - accelerator); - if (result == ViewsDelegate::ProcessMenuAcceleratorResult::CLOSE_MENU) - tray_bubble_view_->CloseBubbleView(); -} - -TrayBubbleView::TrayBubbleView(const InitParams& init_params) - : BubbleDialogDelegateView(init_params.anchor_view, - GetArrowAlignment(init_params.anchor_alignment)), - params_(init_params), - layout_(nullptr), - delegate_(init_params.delegate), - preferred_width_(init_params.min_width), - bubble_border_(new BubbleBorder( - arrow(), - init_params.has_shadow ? BubbleBorder::NO_ASSETS - : BubbleBorder::NO_SHADOW, - init_params.bg_color.value_or(gfx::kPlaceholderColor))), - owned_bubble_border_(bubble_border_), - is_gesture_dragging_(false), - mouse_actively_entered_(false) { - DCHECK(delegate_); - DCHECK(params_.parent_window); - DCHECK(anchor_widget()); // Computed by BubbleDialogDelegateView(). - bubble_border_->set_use_theme_background_color(!init_params.bg_color); - if (init_params.corner_radius) - bubble_border_->SetCornerRadius(init_params.corner_radius.value()); - set_parent_window(params_.parent_window); - set_can_activate(false); - set_notify_enter_exit_on_child(true); - set_close_on_deactivate(init_params.close_on_deactivate); - set_margins(gfx::Insets()); - SetPaintToLayer(); - - bubble_content_mask_ = views::Painter::CreatePaintedLayer( - views::Painter::CreateSolidRoundRectPainter( - SK_ColorBLACK, bubble_border_->GetBorderCornerRadius())); - - auto layout = std::make_unique<BottomAlignedBoxLayout>(this); - layout->SetDefaultFlex(1); - layout_ = SetLayoutManager(std::move(layout)); -} - -TrayBubbleView::~TrayBubbleView() { - mouse_watcher_.reset(); - - if (delegate_) { - // Inform host items (models) that their views are being destroyed. - delegate_->BubbleViewDestroyed(); - } -} - -// static -bool TrayBubbleView::IsATrayBubbleOpen() { - return g_current_tray_bubble_showing_count_ > 0; -} - -void TrayBubbleView::InitializeAndShowBubble() { - layer()->parent()->SetMaskLayer(bubble_content_mask_->layer()); - - GetWidget()->Show(); - UpdateBubble(); - - ++g_current_tray_bubble_showing_count_; - - // If TrayBubbleView cannot be activated and is shown by clicking on the - // corresponding tray view, register pre target event handler to reroute key - // events to the widget for activating the view or closing it. - if (!CanActivate() && params_.show_by_click) { - reroute_event_handler_ = std::make_unique<RerouteEventHandler>(this); - } -} - -void TrayBubbleView::UpdateBubble() { - if (GetWidget()) { - SizeToContents(); - GetWidget()->GetRootView()->SchedulePaint(); - - // When extra keyboard accessibility is enabled, focus the default item if - // no item is focused. - if (delegate_ && delegate_->ShouldEnableExtraKeyboardAccessibility()) - FocusDefaultIfNeeded(); - } -} - -void TrayBubbleView::SetMaxHeight(int height) { - params_.max_height = height; - if (GetWidget()) - SizeToContents(); -} - -void TrayBubbleView::SetBottomPadding(int padding) { - layout_->set_inside_border_insets(gfx::Insets(0, 0, padding, 0)); -} - -void TrayBubbleView::SetWidth(int width) { - width = std::max(std::min(width, params_.max_width), params_.min_width); - if (preferred_width_ == width) - return; - preferred_width_ = width; - if (GetWidget()) - SizeToContents(); -} - -gfx::Insets TrayBubbleView::GetBorderInsets() const { - return bubble_border_->GetInsets(); -} - -void TrayBubbleView::ResetDelegate() { - reroute_event_handler_.reset(); - - delegate_ = nullptr; -} - -void TrayBubbleView::ChangeAnchorView(views::View* anchor_view) { - BubbleDialogDelegateView::SetAnchorView(anchor_view); -} - -int TrayBubbleView::GetDialogButtons() const { - return ui::DIALOG_BUTTON_NONE; -} - -ax::mojom::Role TrayBubbleView::GetAccessibleWindowRole() const { - // We override the role because the base class sets it to alert dialog. - // This would make screen readers announce the whole of the system tray - // which is undesirable. - return ax::mojom::Role::kDialog; -} - -void TrayBubbleView::SizeToContents() { - BubbleDialogDelegateView::SizeToContents(); - bubble_content_mask_->layer()->SetBounds(layer()->parent()->bounds()); -} - -void TrayBubbleView::OnBeforeBubbleWidgetInit(Widget::InitParams* params, - Widget* bubble_widget) const { - if (bubble_border_->shadow() == BubbleBorder::NO_ASSETS) { - // Apply a WM-provided shadow (see ui/wm/core/). - params->shadow_type = Widget::InitParams::SHADOW_TYPE_DROP; - params->shadow_elevation = wm::kShadowElevationActiveWindow; - } -} - -void TrayBubbleView::OnWidgetClosing(Widget* widget) { - // We no longer need to watch key events for activation if the widget is - // closing. - reroute_event_handler_.reset(); - - BubbleDialogDelegateView::OnWidgetClosing(widget); - --g_current_tray_bubble_showing_count_; - DCHECK_GE(g_current_tray_bubble_showing_count_, 0) - << "Closing " << widget->GetName(); -} - -void TrayBubbleView::OnWidgetActivationChanged(Widget* widget, bool active) { - // We no longer need to watch key events for activation if the widget is - // activated. - reroute_event_handler_.reset(); - - BubbleDialogDelegateView::OnWidgetActivationChanged(widget, active); -} - -NonClientFrameView* TrayBubbleView::CreateNonClientFrameView(Widget* widget) { - BubbleFrameView* frame = static_cast<BubbleFrameView*>( - BubbleDialogDelegateView::CreateNonClientFrameView(widget)); - frame->SetBubbleBorder(std::move(owned_bubble_border_)); - return frame; -} - -bool TrayBubbleView::WidgetHasHitTestMask() const { - return true; -} - -void TrayBubbleView::GetWidgetHitTestMask(gfx::Path* mask) const { - DCHECK(mask); - mask->addRect(gfx::RectToSkRect(GetBubbleFrameView()->GetContentsBounds())); -} - -base::string16 TrayBubbleView::GetAccessibleWindowTitle() const { - if (delegate_) - return delegate_->GetAccessibleNameForBubble(); - else - return base::string16(); -} - -gfx::Size TrayBubbleView::CalculatePreferredSize() const { - DCHECK_LE(preferred_width_, params_.max_width); - return gfx::Size(preferred_width_, GetHeightForWidth(preferred_width_)); -} - -int TrayBubbleView::GetHeightForWidth(int width) const { - int height = GetInsets().height(); - width = std::max(width - GetInsets().width(), 0); - for (int i = 0; i < child_count(); ++i) { - const View* child = child_at(i); - if (child->visible()) - height += child->GetHeightForWidth(width); - } - - return (params_.max_height != 0) ? - std::min(height, params_.max_height) : height; -} - -void TrayBubbleView::OnMouseEntered(const ui::MouseEvent& event) { - mouse_watcher_.reset(); - if (delegate_ && !(event.flags() & ui::EF_IS_SYNTHESIZED)) { - // Coming here the user was actively moving the mouse over the bubble and - // we inform the delegate that we entered. This will prevent the bubble - // to auto close. - delegate_->OnMouseEnteredView(); - mouse_actively_entered_ = true; - } else { - // Coming here the bubble got shown and the mouse was 'accidentally' over it - // which is not a reason to prevent the bubble to auto close. As such we - // do not call the delegate, but wait for the first mouse move within the - // bubble. The used MouseWatcher will notify use of a movement and call - // |MouseMovedOutOfHost|. - mouse_watcher_ = std::make_unique<MouseWatcher>( - std::make_unique<views::internal::MouseMoveDetectorHost>(), this); - // Set the mouse sampling frequency to roughly a frame time so that the user - // cannot see a lag. - mouse_watcher_->set_notify_on_exit_time( - base::TimeDelta::FromMilliseconds(kFrameTimeInMS)); - mouse_watcher_->Start(GetWidget()->GetNativeWindow()); - } -} - -void TrayBubbleView::OnMouseExited(const ui::MouseEvent& event) { - // If there was a mouse watcher waiting for mouse movements we disable it - // immediately since we now leave the bubble. - mouse_watcher_.reset(); - // Do not notify the delegate of an exit if we never told it that we entered. - if (delegate_ && mouse_actively_entered_) - delegate_->OnMouseExitedView(); -} - -void TrayBubbleView::GetAccessibleNodeData(ui::AXNodeData* node_data) { - if (delegate_ && CanActivate()) { - node_data->role = ax::mojom::Role::kWindow; - node_data->SetName(delegate_->GetAccessibleNameForBubble()); - } -} - -void TrayBubbleView::OnGestureEvent(ui::GestureEvent* event) { - if (delegate_) - delegate_->ProcessGestureEventForBubble(event); - - if (!event->handled()) - BubbleDialogDelegateView::OnGestureEvent(event); -} - -void TrayBubbleView::MouseMovedOutOfHost() { - // The mouse was accidentally over the bubble when it opened and the AutoClose - // logic was not activated. Now that the user did move the mouse we tell the - // delegate to disable AutoClose. - if (delegate_) - delegate_->OnMouseEnteredView(); - mouse_actively_entered_ = true; - mouse_watcher_->Stop(); -} - -void TrayBubbleView::ChildPreferredSizeChanged(View* child) { - SizeToContents(); -} - -void TrayBubbleView::ViewHierarchyChanged( - const ViewHierarchyChangedDetails& details) { - if (details.is_add && details.child == this) { - details.parent->SetPaintToLayer(); - details.parent->layer()->SetMasksToBounds(true); - } -} - -void TrayBubbleView::CloseBubbleView() { - if (!delegate_) - return; - - delegate_->HideBubble(this); -} - -void TrayBubbleView::FocusDefaultIfNeeded() { - views::FocusManager* manager = GetFocusManager(); - if (!manager || manager->GetFocusedView()) - return; - - views::View* view = - manager->GetNextFocusableView(nullptr, nullptr, false, false); - if (!view) - return; - - // No need to explicitly activate the widget. View::RequestFocus will activate - // it if necessary. - set_can_activate(true); - - view->RequestFocus(); -} - -} // namespace views diff --git a/chromium/ui/views/bubble/tray_bubble_view.h b/chromium/ui/views/bubble/tray_bubble_view.h deleted file mode 100644 index 5ef449781bc..00000000000 --- a/chromium/ui/views/bubble/tray_bubble_view.h +++ /dev/null @@ -1,230 +0,0 @@ -// 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 UI_VIEWS_BUBBLE_TRAY_BUBBLE_VIEW_H_ -#define UI_VIEWS_BUBBLE_TRAY_BUBBLE_VIEW_H_ - -#include <memory> - -#include "base/macros.h" -#include "base/optional.h" -#include "ui/accessibility/ax_enums.mojom.h" -#include "ui/events/event.h" -#include "ui/gfx/native_widget_types.h" -#include "ui/views/bubble/bubble_dialog_delegate_view.h" -#include "ui/views/mouse_watcher.h" -#include "ui/views/views_export.h" - -namespace ui { -class LayerOwner; -} - -namespace views { -class BoxLayout; -class View; -class Widget; -} - -namespace views { - -// Specialized bubble view for bubbles associated with a tray icon (e.g. the -// Ash status area). Mostly this handles custom anchor location and arrow and -// border rendering. This also has its own delegate for handling mouse events -// and other implementation specific details. -class VIEWS_EXPORT TrayBubbleView : public BubbleDialogDelegateView, - public MouseWatcherListener { - public: - // AnchorAlignment determines to which side of the anchor the bubble will - // align itself. - enum AnchorAlignment { - ANCHOR_ALIGNMENT_BOTTOM, - ANCHOR_ALIGNMENT_LEFT, - ANCHOR_ALIGNMENT_RIGHT, - }; - - class VIEWS_EXPORT Delegate { - public: - typedef TrayBubbleView::AnchorAlignment AnchorAlignment; - - Delegate() {} - virtual ~Delegate(); - - // Called when the view is destroyed. Any pointers to the view should be - // cleared when this gets called. - virtual void BubbleViewDestroyed(); - - // Called when the mouse enters/exits the view. - // Note: This event will only be called if the mouse gets actively moved by - // the user to enter the view. - virtual void OnMouseEnteredView(); - virtual void OnMouseExitedView(); - - // Called from GetAccessibleNodeData(); should return the appropriate - // accessible name for the bubble. - virtual base::string16 GetAccessibleNameForBubble(); - - // Should return true if extra keyboard accessibility is enabled. - // TrayBubbleView will put focus on the default item if extra keyboard - // accessibility is enabled. - virtual bool ShouldEnableExtraKeyboardAccessibility(); - - // Called when a bubble wants to hide/destroy itself (e.g. last visible - // child view was closed). - virtual void HideBubble(const TrayBubbleView* bubble_view); - - // Called to process the gesture events that happened on the TrayBubbleView. - // Swiping down on the opened TrayBubbleView to close the bubble. - virtual void ProcessGestureEventForBubble(ui::GestureEvent* event); - - private: - DISALLOW_COPY_AND_ASSIGN(Delegate); - }; - - struct VIEWS_EXPORT InitParams { - InitParams(); - InitParams(const InitParams& other); - Delegate* delegate = nullptr; - gfx::NativeWindow parent_window = nullptr; - View* anchor_view = nullptr; - AnchorAlignment anchor_alignment = ANCHOR_ALIGNMENT_BOTTOM; - int min_width = 0; - int max_width = 0; - int max_height = 0; - bool close_on_deactivate = true; - // Indicates whether tray bubble view is shown by click on the tray view. - bool show_by_click = false; - // If not provided, the bg color will be derived from the NativeTheme. - base::Optional<SkColor> bg_color; - base::Optional<int> corner_radius; - bool has_shadow = true; - }; - - explicit TrayBubbleView(const InitParams& init_params); - ~TrayBubbleView() override; - - // Returns whether a tray bubble is active. - static bool IsATrayBubbleOpen(); - - // Sets up animations, and show the bubble. Must occur after CreateBubble() - // is called. - void InitializeAndShowBubble(); - - // Called whenever the bubble size or location may have changed. - void UpdateBubble(); - - // Sets the maximum bubble height and resizes the bubble. - void SetMaxHeight(int height); - - // Sets the bottom padding that child views will be laid out within. - void SetBottomPadding(int padding); - - // Sets the bubble width. - void SetWidth(int width); - - // Returns the border insets. Called by TrayEventFilter. - gfx::Insets GetBorderInsets() const; - - // Called when the delegate is destroyed. This must be called before the - // delegate is actually destroyed. TrayBubbleView will do clean up in - // ResetDelegate. - void ResetDelegate(); - - // Anchors the bubble to |anchor_view|. - void ChangeAnchorView(views::View* anchor_view); - - Delegate* delegate() { return delegate_; } - - void set_gesture_dragging(bool dragging) { is_gesture_dragging_ = dragging; } - bool is_gesture_dragging() const { return is_gesture_dragging_; } - - // Overridden from views::WidgetDelegate. - views::NonClientFrameView* CreateNonClientFrameView( - views::Widget* widget) override; - bool WidgetHasHitTestMask() const override; - void GetWidgetHitTestMask(gfx::Path* mask) const override; - base::string16 GetAccessibleWindowTitle() const override; - - // Overridden from views::BubbleDialogDelegateView. - void OnBeforeBubbleWidgetInit(Widget::InitParams* params, - Widget* bubble_widget) const override; - void OnWidgetClosing(Widget* widget) override; - void OnWidgetActivationChanged(Widget* widget, bool active) override; - - // Overridden from views::View. - gfx::Size CalculatePreferredSize() const override; - int GetHeightForWidth(int width) const override; - void OnMouseEntered(const ui::MouseEvent& event) override; - void OnMouseExited(const ui::MouseEvent& event) override; - void GetAccessibleNodeData(ui::AXNodeData* node_data) override; - void OnGestureEvent(ui::GestureEvent* event) override; - - // Overridden from MouseWatcherListener - void MouseMovedOutOfHost() override; - - protected: - // Overridden from views::BubbleDialogDelegateView. - int GetDialogButtons() const override; - ax::mojom::Role GetAccessibleWindowRole() const override; - void SizeToContents() override; - - // Overridden from views::View. - void ChildPreferredSizeChanged(View* child) override; - void ViewHierarchyChanged( - const ViewHierarchyChangedDetails& details) override; - - private: - // This reroutes receiving key events to the TrayBubbleView passed in the - // constructor. TrayBubbleView is not activated by default. But we want to - // activate it if user tries to interact it with keyboard. To capture those - // key events in early stage, RerouteEventHandler installs this handler to - // aura::Env. RerouteEventHandler also sends key events to ViewsDelegate to - // process accelerator as menu is currently open. - class RerouteEventHandler : public ui::EventHandler { - public: - explicit RerouteEventHandler(TrayBubbleView* tray_bubble_view); - ~RerouteEventHandler() override; - - // Overridden from ui::EventHandler - void OnKeyEvent(ui::KeyEvent* event) override; - - private: - // TrayBubbleView to which key events are going to be rerouted. Not owned. - TrayBubbleView* tray_bubble_view_; - - DISALLOW_COPY_AND_ASSIGN(RerouteEventHandler); - }; - - void CloseBubbleView(); - - // Focus the default item if no item is focused. - void FocusDefaultIfNeeded(); - - InitParams params_; - BoxLayout* layout_; - Delegate* delegate_; - int preferred_width_; - // |bubble_border_| and |owned_bubble_border_| point to the same thing, but - // the latter ensures we don't leak it before passing off ownership. - BubbleBorder* bubble_border_; - std::unique_ptr<views::BubbleBorder> owned_bubble_border_; - std::unique_ptr<ui::LayerOwner> bubble_content_mask_; - bool is_gesture_dragging_; - - // True once the mouse cursor was actively moved by the user over the bubble. - // Only then the OnMouseExitedView() event will get passed on to listeners. - bool mouse_actively_entered_; - - // Used to find any mouse movements. - std::unique_ptr<MouseWatcher> mouse_watcher_; - - // Used to activate tray bubble view if user tries to interact the tray with - // keyboard. - std::unique_ptr<EventHandler> reroute_event_handler_; - - DISALLOW_COPY_AND_ASSIGN(TrayBubbleView); -}; - -} // namespace views - -#endif // UI_VIEWS_BUBBLE_TRAY_BUBBLE_VIEW_H_ diff --git a/chromium/ui/views/cocoa/DEPS b/chromium/ui/views/cocoa/DEPS index fcaeee7d596..440d45f5357 100644 --- a/chromium/ui/views/cocoa/DEPS +++ b/chromium/ui/views/cocoa/DEPS @@ -1,5 +1,4 @@ include_rules = [ - "+components/viz/common", "+ui/accelerated_widget_mac", "+ui/views_bridge_mac", ] diff --git a/chromium/ui/views/cocoa/bridge_factory_host.cc b/chromium/ui/views/cocoa/bridge_factory_host.cc new file mode 100644 index 00000000000..4350aae5c1a --- /dev/null +++ b/chromium/ui/views/cocoa/bridge_factory_host.cc @@ -0,0 +1,35 @@ +// Copyright 2018 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 "ui/views/cocoa/bridge_factory_host.h" + +#include "mojo/public/cpp/bindings/interface_request.h" + +namespace views { + +BridgeFactoryHost::BridgeFactoryHost( + uint64_t host_id, + views_bridge_mac::mojom::BridgeFactoryAssociatedRequest* request) + : host_id_(host_id) { + *request = mojo::MakeRequest(&bridge_factory_ptr_); +} + +BridgeFactoryHost::~BridgeFactoryHost() { + for (Observer& obs : observers_) + obs.OnBridgeFactoryHostDestroying(this); +} + +views_bridge_mac::mojom::BridgeFactory* BridgeFactoryHost::GetFactory() { + return bridge_factory_ptr_.get(); +} + +void BridgeFactoryHost::AddObserver(Observer* observer) { + observers_.AddObserver(observer); +} + +void BridgeFactoryHost::RemoveObserver(const Observer* observer) { + observers_.RemoveObserver(observer); +} + +} // namespace views diff --git a/chromium/ui/views/cocoa/bridge_factory_host.h b/chromium/ui/views/cocoa/bridge_factory_host.h new file mode 100644 index 00000000000..14a197dce5e --- /dev/null +++ b/chromium/ui/views/cocoa/bridge_factory_host.h @@ -0,0 +1,47 @@ +// Copyright 2018 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 UI_VIEWS_COCOA_BRIDGE_FACTORY_HOST_H_ +#define UI_VIEWS_COCOA_BRIDGE_FACTORY_HOST_H_ + +#include "base/observer_list.h" +#include "base/observer_list_types.h" +#include "ui/views/views_export.h" +#include "ui/views_bridge_mac/mojo/bridge_factory.mojom.h" + +namespace views { + +class VIEWS_EXPORT BridgeFactoryHost { + public: + class Observer : public base::CheckedObserver { + public: + virtual void OnBridgeFactoryHostDestroying(BridgeFactoryHost* host) = 0; + + protected: + ~Observer() override {} + }; + + BridgeFactoryHost( + uint64_t host_id, + views_bridge_mac::mojom::BridgeFactoryAssociatedRequest* request); + ~BridgeFactoryHost(); + + // Return an id for the host process. This can be used to look up other + // factories to create NSViews (e.g in content). + uint64_t GetHostId() const { return host_id_; } + + views_bridge_mac::mojom::BridgeFactory* GetFactory(); + + void AddObserver(Observer* observer); + void RemoveObserver(const Observer* observer); + + private: + const uint64_t host_id_; + views_bridge_mac::mojom::BridgeFactoryAssociatedPtr bridge_factory_ptr_; + base::ObserverList<Observer> observers_; +}; + +} // namespace views + +#endif // UI_VIEWS_COCOA_BRIDGE_FACTORY_HOST_H_ diff --git a/chromium/ui/views/cocoa/bridged_content_view.h b/chromium/ui/views/cocoa/bridged_content_view.h deleted file mode 100644 index a6daf562123..00000000000 --- a/chromium/ui/views/cocoa/bridged_content_view.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2014 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 UI_VIEWS_COCOA_BRIDGED_CONTENT_VIEW_H_ -#define UI_VIEWS_COCOA_BRIDGED_CONTENT_VIEW_H_ - -#import <Cocoa/Cocoa.h> - -#include "base/strings/string16.h" -#import "ui/base/cocoa/tool_tip_base_view.h" -#import "ui/base/cocoa/tracking_area.h" -#include "ui/views/views_export.h" - -namespace ui { -class TextInputClient; -} - -namespace views { -class BridgedNativeWidget; -} - -// The NSView that sits as the root contentView of the NSWindow, whilst it has -// a views::RootView present. Bridges requests from Cocoa to the hosted -// views::View. -VIEWS_EXPORT -@interface BridgedContentView : ToolTipBaseView<NSTextInputClient, - NSUserInterfaceValidations, - NSDraggingSource> { - @private - // Weak, reset by clearView. - views::BridgedNativeWidget* bridge_; - - // Weak. If non-null the TextInputClient of the currently focused View in the - // hierarchy rooted at |hostedView_|. Owned by the focused View. - // TODO(ccameron): Remove this member. - ui::TextInputClient* textInputClient_; - - // The TextInputClient about to be set. Requests for a new -inputContext will - // use this, but while the input is changing, |self| still needs to service - // IME requests using the old |textInputClient_|. - // TODO(ccameron): Remove this member. - ui::TextInputClient* pendingTextInputClient_; - - // A tracking area installed to enable mouseMoved events. - ui::ScopedCrTrackingArea cursorTrackingArea_; - - // The keyDown event currently being handled, nil otherwise. - NSEvent* keyDownEvent_; - - // Whether there's an active key down event which is not handled yet. - BOOL hasUnhandledKeyDownEvent_; - - // The last tooltip text, used to limit updates. - base::string16 lastTooltipText_; -} - -@property(readonly, nonatomic) views::BridgedNativeWidget* bridge; -@property(assign, nonatomic) ui::TextInputClient* textInputClient; -@property(assign, nonatomic) BOOL drawMenuBackgroundForBlur; - -// Initialize the NSView -> views::View bridge. |viewToHost| must be non-NULL. -- (id)initWithBridge:(views::BridgedNativeWidget*)bridge bounds:(gfx::Rect)rect; - -// Clear the hosted view. For example, if it is about to be destroyed. -- (void)clearView; - -// Process a mouse event captured while the widget had global mouse capture. -- (void)processCapturedMouseEvent:(NSEvent*)theEvent; - -// Mac's version of views::corewm::TooltipController::UpdateIfRequired(). -// Updates the tooltip on the ToolTipBaseView if the text needs to change. -// |locationInContent| is the position from the top left of the window's -// contentRect (also this NSView's frame), as given by a ui::LocatedEvent. -- (void)updateTooltipIfRequiredAt:(const gfx::Point&)locationInContent; - -// Notifies the associated FocusManager whether full keyboard access is enabled -// or not. -- (void)updateFullKeyboardAccess; - -@end - -#endif // UI_VIEWS_COCOA_BRIDGED_CONTENT_VIEW_H_ diff --git a/chromium/ui/views/cocoa/bridged_content_view.mm b/chromium/ui/views/cocoa/bridged_content_view.mm deleted file mode 100644 index 9ffa0c183b3..00000000000 --- a/chromium/ui/views/cocoa/bridged_content_view.mm +++ /dev/null @@ -1,1577 +0,0 @@ -// Copyright 2014 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. - -#import "ui/views/cocoa/bridged_content_view.h" - -#include "base/logging.h" -#import "base/mac/mac_util.h" -#import "base/mac/scoped_nsobject.h" -#import "base/mac/sdk_forward_declarations.h" -#include "base/strings/sys_string_conversions.h" -#include "skia/ext/skia_utils_mac.h" -#import "ui/base/cocoa/appkit_utils.h" -#include "ui/base/cocoa/cocoa_base_utils.h" -#include "ui/base/dragdrop/drag_drop_types.h" -#include "ui/base/dragdrop/os_exchange_data_provider_mac.h" -#include "ui/base/ime/input_method.h" -#include "ui/base/ime/text_edit_commands.h" -#include "ui/base/ime/text_input_client.h" -#include "ui/compositor/canvas_painter.h" -#import "ui/events/cocoa/cocoa_event_utils.h" -#include "ui/events/event_utils.h" -#include "ui/events/keycodes/dom/dom_code.h" -#import "ui/events/keycodes/keyboard_code_conversion_mac.h" -#include "ui/gfx/canvas_paint_mac.h" -#include "ui/gfx/decorated_text.h" -#import "ui/gfx/decorated_text_mac.h" -#include "ui/gfx/geometry/rect.h" -#import "ui/gfx/mac/coordinate_conversion.h" -#include "ui/gfx/path.h" -#import "ui/gfx/path_mac.h" -#include "ui/gfx/scoped_ns_graphics_context_save_gstate_mac.h" -#import "ui/views/cocoa/bridged_native_widget.h" -#import "ui/views/cocoa/bridged_native_widget_host.h" -#import "ui/views/cocoa/drag_drop_client_mac.h" -#include "ui/views/controls/label.h" -#include "ui/views/view.h" -#include "ui/views/widget/native_widget_mac.h" -#include "ui/views/widget/widget.h" - -namespace { - -NSString* const kFullKeyboardAccessChangedNotification = - @"com.apple.KeyboardUIModeDidChange"; - -// Convert a |point| in |source_window|'s AppKit coordinate system (origin at -// the bottom left of the window) to |target_window|'s content rect, with the -// origin at the top left of the content area. -// If |source_window| is nil, |point| will be treated as screen coordinates. -gfx::Point MovePointToWindow(const NSPoint& point, - NSWindow* source_window, - NSWindow* target_window) { - NSPoint point_in_screen = source_window - ? ui::ConvertPointFromWindowToScreen(source_window, point) - : point; - - NSPoint point_in_window = - ui::ConvertPointFromScreenToWindow(target_window, point_in_screen); - NSRect content_rect = - [target_window contentRectForFrameRect:[target_window frame]]; - return gfx::Point(point_in_window.x, - NSHeight(content_rect) - point_in_window.y); -} - -// Returns true if |client| has RTL text. -bool IsTextRTL(const ui::TextInputClient* client) { - return client && client->GetTextDirection() == base::i18n::RIGHT_TO_LEFT; -} - -// Returns true if |event| may have triggered dismissal of an IME and would -// otherwise be ignored by a ui::TextInputClient when inserted. -bool IsImeTriggerEvent(NSEvent* event) { - ui::KeyboardCode key = ui::KeyboardCodeFromNSEvent(event); - return key == ui::VKEY_RETURN || key == ui::VKEY_TAB || - key == ui::VKEY_ESCAPE; -} - -// Returns the boundary rectangle for composition characters in the -// |requested_range|. Sets |actual_range| corresponding to the returned -// rectangle. For cases, where there is no composition text or the -// |requested_range| lies outside the composition range, a zero width rectangle -// corresponding to the caret bounds is returned. Logic used is similar to -// RenderWidgetHostViewMac::GetCachedFirstRectForCharacterRange(...). -gfx::Rect GetFirstRectForRangeHelper(const ui::TextInputClient* client, - const gfx::Range& requested_range, - gfx::Range* actual_range) { - // NSRange doesn't support reversed ranges. - DCHECK(!requested_range.is_reversed()); - DCHECK(actual_range); - - // Set up default return values, to be returned in case of unusual cases. - gfx::Rect default_rect; - *actual_range = gfx::Range::InvalidRange(); - if (!client) - return default_rect; - - default_rect = client->GetCaretBounds(); - default_rect.set_width(0); - - // If possible, modify actual_range to correspond to caret position. - gfx::Range selection_range; - if (client->GetSelectionRange(&selection_range)) { - // Caret bounds correspond to end index of selection_range. - *actual_range = gfx::Range(selection_range.end()); - } - - gfx::Range composition_range; - if (!client->HasCompositionText() || - !client->GetCompositionTextRange(&composition_range) || - !composition_range.Contains(requested_range)) - return default_rect; - - DCHECK(!composition_range.is_reversed()); - - const size_t from = requested_range.start() - composition_range.start(); - const size_t to = requested_range.end() - composition_range.start(); - - // Pick the first character's bounds as the initial rectangle, then grow it to - // the full |requested_range| if possible. - const bool request_is_composition_end = from == composition_range.length(); - const size_t first_index = request_is_composition_end ? from - 1 : from; - gfx::Rect union_rect; - if (!client->GetCompositionCharacterBounds(first_index, &union_rect)) - return default_rect; - - // If requested_range is empty, return a zero width rectangle corresponding to - // it. - if (from == to) { - if (request_is_composition_end && !IsTextRTL(client)) { - // In case of an empty requested range at end of composition, return the - // rectangle to the right of the last compositioned character. - union_rect.set_origin(union_rect.top_right()); - } - union_rect.set_width(0); - *actual_range = requested_range; - return union_rect; - } - - // Toolkit-views textfields are always single-line, so no need to check for - // line breaks. - for (size_t i = from + 1; i < to; i++) { - gfx::Rect current_rect; - if (client->GetCompositionCharacterBounds(i, ¤t_rect)) { - union_rect.Union(current_rect); - } else { - *actual_range = - gfx::Range(requested_range.start(), i + composition_range.start()); - return union_rect; - } - } - *actual_range = requested_range; - return union_rect; -} - -// Returns the string corresponding to |requested_range| for the given |client|. -// If a gfx::Range::InvalidRange() is passed, the full string stored by |client| -// is returned. Sets |actual_range| corresponding to the returned string. -base::string16 AttributedSubstringForRangeHelper( - const ui::TextInputClient* client, - const gfx::Range& requested_range, - gfx::Range* actual_range) { - // NSRange doesn't support reversed ranges. - DCHECK(!requested_range.is_reversed()); - DCHECK(actual_range); - - base::string16 substring; - gfx::Range text_range; - *actual_range = gfx::Range::InvalidRange(); - if (!client || !client->GetTextRange(&text_range)) - return substring; - - // gfx::Range::Intersect() behaves a bit weirdly. If B is an empty range - // contained inside a non-empty range A, B intersection A returns - // gfx::Range::InvalidRange(), instead of returning B. - *actual_range = text_range.Contains(requested_range) - ? requested_range - : text_range.Intersect(requested_range); - - // This is a special case for which the complete string should should be - // returned. NSTextView also follows this, though the same is not mentioned in - // NSTextInputClient documentation. - if (!requested_range.IsValid()) - *actual_range = text_range; - - client->GetTextFromRange(*actual_range, &substring); - return substring; -} - -ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { - if (action == @selector(undo:)) - return ui::TextEditCommand::UNDO; - if (action == @selector(redo:)) - return ui::TextEditCommand::REDO; - if (action == @selector(cut:)) - return ui::TextEditCommand::CUT; - if (action == @selector(copy:)) - return ui::TextEditCommand::COPY; - if (action == @selector(paste:)) - return ui::TextEditCommand::PASTE; - if (action == @selector(selectAll:)) - return ui::TextEditCommand::SELECT_ALL; - return ui::TextEditCommand::INVALID_COMMAND; -} - -} // namespace - -@interface BridgedContentView () - -// Dispatch |event| to |bridge_|'s host. -- (void)dispatchKeyEvent:(ui::KeyEvent*)event; - -// Returns true if active menu controller corresponds to this widget. Note that -// this will synchronously call into the browser process. -- (BOOL)hasActiveMenuController; - -// Dispatch |event| to |menu_controller| and return true if |event| is -// swallowed. -- (BOOL)dispatchKeyEventToMenuController:(ui::KeyEvent*)event; - -// Passes |event| to the InputMethod for dispatch. -- (void)handleKeyEvent:(ui::KeyEvent*)event; - -// Allows accelerators to be handled at different points in AppKit key event -// dispatch. Checks for an unhandled event passed in to -keyDown: and passes it -// to the Widget for processing. Returns YES if the Widget handles it. -- (BOOL)handleUnhandledKeyDownAsKeyEvent; - -// Handles an NSResponder Action Message by mapping it to a corresponding text -// editing command from ui_strings.grd and, when not being sent to a -// TextInputClient, the keyCode that toolkit-views expects internally. -// For example, moveToLeftEndOfLine: would pass ui::VKEY_HOME in non-RTL locales -// even though the Home key on Mac defaults to moveToBeginningOfDocument:. -// This approach also allows action messages a user -// may have remapped in ~/Library/KeyBindings/DefaultKeyBinding.dict to be -// catered for. -// Note: default key bindings in Mac can be read from StandardKeyBinding.dict -// which lives in /System/Library/Frameworks/AppKit.framework/Resources. Do -// `plutil -convert xml1 -o StandardKeyBinding.xml StandardKeyBinding.dict` to -// get something readable. -- (void)handleAction:(ui::TextEditCommand)command - keyCode:(ui::KeyboardCode)keyCode - domCode:(ui::DomCode)domCode - eventFlags:(int)eventFlags; - -// ui::EventLocationFromNative() assumes the event hit the contentView. -// Adjust |event| if that's not the case (e.g. for reparented views). -- (void)adjustUiEventLocation:(ui::LocatedEvent*)event - fromNativeEvent:(NSEvent*)nativeEvent; - -// Notification handler invoked when the Full Keyboard Access mode is changed. -- (void)onFullKeyboardAccessModeChanged:(NSNotification*)notification; - -// Helper method which forwards |text| to the active menu or |textInputClient_|. -- (void)insertTextInternal:(id)text; - -// Returns the native Widget's drag drop client. Possibly null. -- (views::DragDropClientMac*)dragDropClient; - -// Menu action handlers. -- (void)undo:(id)sender; -- (void)redo:(id)sender; -- (void)cut:(id)sender; -- (void)copy:(id)sender; -- (void)paste:(id)sender; -- (void)selectAll:(id)sender; - -@end - -@implementation BridgedContentView - -@synthesize bridge = bridge_; -@synthesize textInputClient = textInputClient_; -@synthesize drawMenuBackgroundForBlur = drawMenuBackgroundForBlur_; - -- (id)initWithBridge:(views::BridgedNativeWidget*)bridge - bounds:(gfx::Rect)bounds { - // To keep things simple, assume the origin is (0, 0) until there exists a use - // case for something other than that. - DCHECK(bounds.origin().IsOrigin()); - NSRect initialFrame = NSMakeRect(0, 0, bounds.width(), bounds.height()); - if ((self = [super initWithFrame:initialFrame])) { - bridge_ = bridge; - - // Apple's documentation says that NSTrackingActiveAlways is incompatible - // with NSTrackingCursorUpdate, so use NSTrackingActiveInActiveApp. - cursorTrackingArea_.reset([[CrTrackingArea alloc] - initWithRect:NSZeroRect - options:NSTrackingMouseMoved | NSTrackingCursorUpdate | - NSTrackingActiveInActiveApp | NSTrackingInVisibleRect | - NSTrackingMouseEnteredAndExited - owner:self - userInfo:nil]); - [self addTrackingArea:cursorTrackingArea_.get()]; - - // Get notified whenever Full Keyboard Access mode is changed. - [[NSDistributedNotificationCenter defaultCenter] - addObserver:self - selector:@selector(onFullKeyboardAccessModeChanged:) - name:kFullKeyboardAccessChangedNotification - object:nil]; - - // Initialize the focus manager with the correct keyboard accessibility - // setting. - [self updateFullKeyboardAccess]; - [self registerForDraggedTypes:ui::OSExchangeDataProviderMac:: - SupportedPasteboardTypes()]; - } - return self; -} - -- (void)dealloc { - // By the time |self| is dealloc'd, it should never be in an NSWindow, and it - // should never be the current input context. - DCHECK_EQ(nil, [self window]); - // Sanity check: NSView always provides an -inputContext. - DCHECK_NE(nil, [super inputContext]); - DCHECK_NE([NSTextInputContext currentInputContext], [super inputContext]); - [super dealloc]; -} - -- (void)clearView { - [self setTextInputClient:nullptr]; - bridge_ = nullptr; - [[NSDistributedNotificationCenter defaultCenter] removeObserver:self]; - [cursorTrackingArea_.get() clearOwner]; - [self removeTrackingArea:cursorTrackingArea_.get()]; -} - -- (void)setTextInputClient:(ui::TextInputClient*)newTextInputClient { - if (pendingTextInputClient_ == newTextInputClient) - return; - - // This method may cause the IME window to dismiss, which may cause it to - // insert text (e.g. to replace marked text with "real" text). That should - // happen in the old -inputContext (which AppKit stores a reference to). - // Unfortunately, the only way to invalidate the the old -inputContext is to - // invoke -[NSApp updateWindows], which also wants a reference to the _new_ - // -inputContext. So put the new inputContext in |pendingTextInputClient_| and - // only use it for -inputContext. - ui::TextInputClient* oldInputClient = textInputClient_; - - // Since dismissing an IME may insert text, a misbehaving IME or a - // ui::TextInputClient that acts on InsertChar() to change focus a second time - // may invoke -setTextInputClient: recursively; with [NSApp updateWindows] - // still on the stack. Calling [NSApp updateWindows] recursively may upset - // an IME. Since the rest of this method is only to decide whether to call - // updateWindows, and we're already calling it, just bail out. - if (textInputClient_ != pendingTextInputClient_) { - pendingTextInputClient_ = newTextInputClient; - return; - } - - // Start by assuming no need to invoke -updateWindows. - textInputClient_ = newTextInputClient; - pendingTextInputClient_ = newTextInputClient; - - // If |self| was being used for the input context, and would now report a - // different input context, manually invoke [NSApp updateWindows]. This is - // necessary because AppKit holds on to a raw pointer to a NSTextInputContext - // (which may have been the one returned by [self inputContext]) that is only - // updated by -updateWindows. And although AppKit invokes that on each - // iteration through most runloop modes, it does not call it when running - // NSEventTrackingRunLoopMode, and not _within_ a run loop iteration, where - // the inputContext may change before further event processing. - NSTextInputContext* current = [NSTextInputContext currentInputContext]; - if (!current) - return; - - NSTextInputContext* newContext = [self inputContext]; - // If the newContext is non-nil, then it can only be [super inputContext]. So - // the input context is either not changing, or it was not from |self|. In - // both cases, there's no need to call -updateWindows. - if (newContext) { - DCHECK_EQ(newContext, [super inputContext]); - return; - } - - if (current == [super inputContext]) { - DCHECK_NE(oldInputClient, textInputClient_); - textInputClient_ = oldInputClient; - [NSApp updateWindows]; - // Note: |pendingTextInputClient_| (and therefore +[NSTextInputContext - // currentInputContext] may have changed if called recursively. - textInputClient_ = pendingTextInputClient_; - } -} - -// If |point| is classified as a draggable background (HTCAPTION), return nil so -// that it can lead to a window drag or double-click in the title bar. Dragging -// could be optimized by telling the window server which regions should be -// instantly draggable without asking (tracked at https://crbug.com/830962). -- (NSView*)hitTest:(NSPoint)point { - gfx::Point flippedPoint(point.x, NSHeight(self.superview.bounds) - point.y); - bool isDraggableBackground = false; - bridge_->host()->GetIsDraggableBackgroundAt(flippedPoint, - &isDraggableBackground); - if (isDraggableBackground) - return nil; - return [super hitTest:point]; -} - -- (void)processCapturedMouseEvent:(NSEvent*)theEvent { - if (!bridge_) - return; - - NSWindow* source = [theEvent window]; - NSWindow* target = [self window]; - DCHECK(target); - - BOOL isScrollEvent = [theEvent type] == NSScrollWheel; - - // If it's the view's window, process normally. - if ([target isEqual:source]) { - if (isScrollEvent) - [self scrollWheel:theEvent]; - else - [self mouseEvent:theEvent]; - - return; - } - - gfx::Point event_location = - MovePointToWindow([theEvent locationInWindow], source, target); - [self updateTooltipIfRequiredAt:event_location]; - - if (isScrollEvent) { - ui::ScrollEvent event(theEvent); - event.set_location(event_location); - bridge_->host()->OnScrollEvent(event); - } else { - ui::MouseEvent event(theEvent); - event.set_location(event_location); - bridge_->host()->OnMouseEvent(event); - } -} - -- (void)updateTooltipIfRequiredAt:(const gfx::Point&)locationInContent { - DCHECK(bridge_); - base::string16 newTooltipText; - - bridge_->host()->GetTooltipTextAt(locationInContent, &newTooltipText); - if (newTooltipText != lastTooltipText_) { - std::swap(newTooltipText, lastTooltipText_); - [self setToolTipAtMousePoint:base::SysUTF16ToNSString(lastTooltipText_)]; - } -} - -- (void)updateFullKeyboardAccess { - if (!bridge_) - return; - bridge_->host()->SetKeyboardAccessible([NSApp isFullKeyboardAccessEnabled]); -} - -// BridgedContentView private implementation. - -- (void)dispatchKeyEvent:(ui::KeyEvent*)event { - bool eventHandled = false; - if (bridge_) - bridge_->host()->DispatchKeyEvent(*event, &eventHandled); - if (eventHandled) - event->SetHandled(); -} - -- (BOOL)hasActiveMenuController { - bool hasMenuController = false; - if (bridge_) - bridge_->host()->GetHasMenuController(&hasMenuController); - return hasMenuController; -} - -- (BOOL)dispatchKeyEventToMenuController:(ui::KeyEvent*)event { - bool eventSwallowed = false; - bool eventHandled = false; - if (bridge_) { - bridge_->host()->DispatchKeyEventToMenuController(*event, &eventSwallowed, - &eventHandled); - } - if (eventHandled) - event->SetHandled(); - return eventSwallowed; -} - -- (void)handleKeyEvent:(ui::KeyEvent*)event { - DCHECK(event); - if ([self dispatchKeyEventToMenuController:event]) - return; - - [self dispatchKeyEvent:event]; -} - -- (BOOL)handleUnhandledKeyDownAsKeyEvent { - if (!hasUnhandledKeyDownEvent_) - return NO; - - ui::KeyEvent event(keyDownEvent_); - [self handleKeyEvent:&event]; - hasUnhandledKeyDownEvent_ = NO; - return event.handled(); -} - -- (void)handleAction:(ui::TextEditCommand)command - keyCode:(ui::KeyboardCode)keyCode - domCode:(ui::DomCode)domCode - eventFlags:(int)eventFlags { - if (!bridge_) - return; - - // Always propagate the shift modifier if present. Shift doesn't always alter - // the command selector, but should always be passed along. Control and Alt - // have different meanings on Mac, so they do not propagate automatically. - if ([keyDownEvent_ modifierFlags] & NSShiftKeyMask) - eventFlags |= ui::EF_SHIFT_DOWN; - - // Generate a synthetic event with the keycode toolkit-views expects. - ui::KeyEvent event(ui::ET_KEY_PRESSED, keyCode, domCode, eventFlags); - - if ([self dispatchKeyEventToMenuController:&event]) - return; - - // If there's an active TextInputClient, schedule the editing command to be - // performed. - if (textInputClient_ && textInputClient_->IsTextEditCommandEnabled(command)) - textInputClient_->SetTextEditCommandForNextKeyEvent(command); - - [self dispatchKeyEvent:&event]; -} - -- (void)adjustUiEventLocation:(ui::LocatedEvent*)event - fromNativeEvent:(NSEvent*)nativeEvent { - if ([nativeEvent window] && [[self window] contentView] != self) { - NSPoint p = [self convertPoint:[nativeEvent locationInWindow] fromView:nil]; - event->set_location(gfx::Point(p.x, NSHeight([self frame]) - p.y)); - } -} - -- (void)onFullKeyboardAccessModeChanged:(NSNotification*)notification { - DCHECK([[notification name] - isEqualToString:kFullKeyboardAccessChangedNotification]); - [self updateFullKeyboardAccess]; -} - -- (void)insertTextInternal:(id)text { - if (!bridge_) - return; - - if ([text isKindOfClass:[NSAttributedString class]]) - text = [text string]; - - bool isCharacterEvent = keyDownEvent_ && [text length] == 1; - // Pass "character" events to the View hierarchy. Cases this handles (non- - // exhaustive)- - // - Space key press on controls. Unlike Tab and newline which have - // corresponding action messages, an insertText: message is generated for - // the Space key (insertText:replacementRange: when there's an active - // input context). - // - Menu mnemonic selection. - // Note we create a custom character ui::KeyEvent (and not use the - // ui::KeyEvent(NSEvent*) constructor) since we can't just rely on the event - // key code to get the actual characters from the ui::KeyEvent. This for - // example is necessary for menu mnemonic selection of non-latin text. - - // Don't generate a key event when there is marked composition text. These key - // down events should be consumed by the IME and not reach the Views layer. - // For example, on pressing Return to commit composition text, if we passed a - // synthetic key event to the View hierarchy, it will have the effect of - // performing the default action on the current dialog. We do not want this - // when there is marked text (Return should only confirm the IME). - - // However, IME for phonetic languages such as Korean do not always _mark_ - // text when a composition is active. For these, correct behaviour is to - // handle the final -keyDown: that caused the composition to be committed, but - // only _after_ the sequence of insertText: messages coming from IME have been - // sent to the TextInputClient. Detect this by comparing to -[NSEvent - // characters]. Note we do not use -charactersIgnoringModifiers: so that, - // e.g., ß (Alt+s) will match mnemonics with ß rather than s. - bool isFinalInsertForKeyEvent = - isCharacterEvent && [text isEqualToString:[keyDownEvent_ characters]]; - - // Also note that a single, non-IME key down event can also cause multiple - // insertText:replacementRange: action messages being generated from within - // -keyDown:'s call to -interpretKeyEvents:. One example, on pressing Alt+e, - // the accent (´) character is composed via setMarkedText:. Now on pressing - // the character 'r', two insertText:replacementRange: action messages are - // generated with the text value of accent (´) and 'r' respectively. The key - // down event will have characters field of length 2. The first of these - // insertText messages won't generate a KeyEvent since there'll be active - // marked text. However, a KeyEvent will be generated corresponding to 'r'. - - // Currently there seems to be no use case to pass non-character events routed - // from insertText: handlers to the View hierarchy. - if (isFinalInsertForKeyEvent && ![self hasMarkedText]) { - ui::KeyEvent charEvent([text characterAtIndex:0], - ui::KeyboardCodeFromNSEvent(keyDownEvent_), - ui::DomCodeFromNSEvent(keyDownEvent_), ui::EF_NONE); - [self handleKeyEvent:&charEvent]; - hasUnhandledKeyDownEvent_ = NO; - if (charEvent.handled()) - return; - } - - // Forward the |text| to |textInputClient_| if no menu is active. - if (textInputClient_ && ![self hasActiveMenuController]) { - // If a single character is inserted by keyDown's call to - // interpretKeyEvents: then use InsertChar() to allow editing events to be - // merged. We use ui::VKEY_UNKNOWN as the key code since it's not feasible - // to determine the correct key code for each unicode character. Also a - // correct keycode is not needed in the current context. Send ui::EF_NONE as - // the key modifier since |text| already accounts for the pressed key - // modifiers. - - // Also, note we don't check isFinalInsertForKeyEvent, nor use - // |keyDownEvent_| to generate the synthetic ui::KeyEvent since: For - // composed text, [keyDownEvent_ characters] might not be the same as - // |text|. This is because |keyDownEvent_| will correspond to the event that - // caused the composition text to be confirmed, say, Return key press. - if (isCharacterEvent) { - textInputClient_->InsertChar( - ui::KeyEvent([text characterAtIndex:0], ui::VKEY_UNKNOWN, - ui::DomCode::NONE, ui::EF_NONE)); - // Leave character events that may have triggered IME confirmation for - // inline IME (e.g. Korean) as "unhandled". There will be no more - // -insertText: messages, but we are unable to handle these via - // -handleKeyEvent: earlier in this method since toolkit-views client code - // assumes it can ignore characters associated with, e.g., VKEY_TAB. - DCHECK(keyDownEvent_); // Otherwise it is not a character event. - if ([self hasMarkedText] || !IsImeTriggerEvent(keyDownEvent_)) - hasUnhandledKeyDownEvent_ = NO; - } else { - textInputClient_->InsertText(base::SysNSStringToUTF16(text)); - hasUnhandledKeyDownEvent_ = NO; - } - } -} - -- (views::DragDropClientMac*)dragDropClient { - return bridge_ ? bridge_->drag_drop_client() : nullptr; -} - -- (void)undo:(id)sender { - [self handleAction:ui::TextEditCommand::UNDO - keyCode:ui::VKEY_Z - domCode:ui::DomCode::US_Z - eventFlags:ui::EF_CONTROL_DOWN]; -} - -- (void)redo:(id)sender { - [self handleAction:ui::TextEditCommand::REDO - keyCode:ui::VKEY_Z - domCode:ui::DomCode::US_Z - eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; -} - -- (void)cut:(id)sender { - [self handleAction:ui::TextEditCommand::CUT - keyCode:ui::VKEY_X - domCode:ui::DomCode::US_X - eventFlags:ui::EF_CONTROL_DOWN]; -} - -- (void)copy:(id)sender { - [self handleAction:ui::TextEditCommand::COPY - keyCode:ui::VKEY_C - domCode:ui::DomCode::US_C - eventFlags:ui::EF_CONTROL_DOWN]; -} - -- (void)paste:(id)sender { - [self handleAction:ui::TextEditCommand::PASTE - keyCode:ui::VKEY_V - domCode:ui::DomCode::US_V - eventFlags:ui::EF_CONTROL_DOWN]; -} - -- (void)selectAll:(id)sender { - [self handleAction:ui::TextEditCommand::SELECT_ALL - keyCode:ui::VKEY_A - domCode:ui::DomCode::US_A - eventFlags:ui::EF_CONTROL_DOWN]; -} - -// BaseView implementation. - -// Don't use tracking areas from BaseView. BridgedContentView's tracks -// NSTrackingCursorUpdate and Apple's documentation suggests it's incompatible. -- (void)enableTracking { -} - -// Translates the location of |theEvent| to toolkit-views coordinates and passes -// the event to NativeWidgetMac for handling. -- (void)mouseEvent:(NSEvent*)theEvent { - if (!bridge_) - return; - - DCHECK([theEvent type] != NSScrollWheel); - ui::MouseEvent event(theEvent); - [self adjustUiEventLocation:&event fromNativeEvent:theEvent]; - - // Aura updates tooltips with the help of aura::Window::AddPreTargetHandler(). - // Mac hooks in here. - [self updateTooltipIfRequiredAt:event.location()]; - bridge_->host()->OnMouseEvent(event); -} - -- (void)forceTouchEvent:(NSEvent*)theEvent { - if (ui::ForceClickInvokesQuickLook()) - [self quickLookWithEvent:theEvent]; -} - -// NSView implementation. - -// This view must consistently return YES or else dragging a tab may drag the -// entire window. See r549802 for details. -- (BOOL)acceptsFirstResponder { - return YES; -} - -- (BOOL)becomeFirstResponder { - if ([[self window] firstResponder] != self) - return NO; - BOOL result = [super becomeFirstResponder]; - if (result && bridge_) - bridge_->host()->SetIsFirstResponder(true); - return result; -} - -- (BOOL)resignFirstResponder { - BOOL result = [super resignFirstResponder]; - if (result && bridge_) - bridge_->host()->SetIsFirstResponder(false); - return result; -} - -- (void)viewDidMoveToWindow { - // When this view is added to a window, AppKit calls setFrameSize before it is - // added to the window, so the behavior in setFrameSize is not triggered. - NSWindow* window = [self window]; - if (window) - [self setFrameSize:NSZeroSize]; -} - -- (void)setFrameSize:(NSSize)newSize { - // The size passed in here does not always use - // -[NSWindow contentRectForFrameRect]. The following ensures that the - // contentView for a frameless window can extend over the titlebar of the new - // window containing it, since AppKit requires a titlebar to give frameless - // windows correct shadows and rounded corners. - NSWindow* window = [self window]; - if (window && [window contentView] == self) { - newSize = [window contentRectForFrameRect:[window frame]].size; - // Ensure that the window geometry be updated on the host side before the - // view size is updated. - // TODO(ccameron): Consider updating the view size and window size and - // position together in UpdateWindowGeometry. - // https://crbug.com/875776, https://crbug.com/875731 - if (bridge_) - bridge_->UpdateWindowGeometry(); - } - - [super setFrameSize:newSize]; - - if (bridge_) - bridge_->host()->SetViewSize(gfx::Size(newSize.width, newSize.height)); -} - -- (BOOL)isOpaque { - return bridge_ ? !bridge_->is_translucent_window() : NO; -} - -// To maximize consistency with the Cocoa browser (mac_views_browser=0), accept -// mouse clicks immediately so that clicking on Chrome from an inactive window -// will allow the event to be processed, rather than merely activate the window. -- (BOOL)acceptsFirstMouse:(NSEvent*)theEvent { - return YES; -} - -// NSDraggingDestination protocol overrides. - -- (NSDragOperation)draggingEntered:(id<NSDraggingInfo>)sender { - return [self draggingUpdated:sender]; -} - -- (NSDragOperation)draggingUpdated:(id<NSDraggingInfo>)sender { - views::DragDropClientMac* client = [self dragDropClient]; - return client ? client->DragUpdate(sender) : ui::DragDropTypes::DRAG_NONE; -} - -- (void)draggingExited:(id<NSDraggingInfo>)sender { - views::DragDropClientMac* client = [self dragDropClient]; - if (client) - client->DragExit(); -} - -- (BOOL)performDragOperation:(id<NSDraggingInfo>)sender { - views::DragDropClientMac* client = [self dragDropClient]; - return client && client->Drop(sender) != NSDragOperationNone; -} - -- (NSTextInputContext*)inputContext { - // If the textInputClient_ does not exist, return nil since this view does not - // conform to NSTextInputClient protocol. - if (!pendingTextInputClient_) - return nil; - - // If a menu is active, and -[NSView interpretKeyEvents:] asks for the - // input context, return nil. This ensures the action message is sent to - // the view, rather than any NSTextInputClient a subview has installed. - if ([self hasActiveMenuController]) - return nil; - - // When not in an editable mode, or while entering passwords - // (http://crbug.com/23219), we don't want to show IME candidate windows. - // Returning nil prevents this view from getting messages defined as part of - // the NSTextInputClient protocol. - switch (pendingTextInputClient_->GetTextInputType()) { - case ui::TEXT_INPUT_TYPE_NONE: - case ui::TEXT_INPUT_TYPE_PASSWORD: - return nil; - default: - return [super inputContext]; - } -} - -// NSResponder implementation. - -- (BOOL)_wantsKeyDownForEvent:(NSEvent*)event { - // This is a SPI that AppKit apparently calls after |performKeyEquivalent:| - // returned NO. If this function returns |YES|, Cocoa sends the event to - // |keyDown:| instead of doing other things with it. Ctrl-tab will be sent - // to us instead of doing key view loop control, ctrl-left/right get handled - // correctly, etc. - // (However, there are still some keys that Cocoa swallows, e.g. the key - // equivalent that Cocoa uses for toggling the input language. In this case, - // that's actually a good thing, though -- see http://crbug.com/26115 .) - return YES; -} - -- (void)keyDown:(NSEvent*)theEvent { - BOOL hadMarkedTextAtKeyDown = [self hasMarkedText]; - - // Convert the event into an action message, according to OSX key mappings. - keyDownEvent_ = theEvent; - hasUnhandledKeyDownEvent_ = YES; - [self interpretKeyEvents:@[ theEvent ]]; - - // When there is marked text, -[NSView interpretKeyEvents:] may handle the - // event by dismissing the IME window in a way that neither unmarks text, nor - // updates any composition. That is, no signal is given either to the - // NSTextInputClient or the NSTextInputContext that the IME changed state. - // However, we must ensure this key down is not processed as an accelerator. - // TODO(tapted): Investigate removing the IsImeTriggerEvent() check - it's - // probably not required, but helps tests that expect some events to always - // get processed (i.e. TextfieldTest.TextInputClientTest). - if (hadMarkedTextAtKeyDown && IsImeTriggerEvent(theEvent)) - hasUnhandledKeyDownEvent_ = NO; - - // If |keyDownEvent_| wasn't cleared during -interpretKeyEvents:, it wasn't - // handled. Give Widget accelerators a chance to handle it. - [self handleUnhandledKeyDownAsKeyEvent]; - DCHECK(!hasUnhandledKeyDownEvent_); - keyDownEvent_ = nil; -} - -- (void)keyUp:(NSEvent*)theEvent { - ui::KeyEvent event(theEvent); - [self handleKeyEvent:&event]; -} - -- (void)flagsChanged:(NSEvent*)theEvent { - ui::KeyEvent event(theEvent); - [self handleKeyEvent:&event]; -} - -- (void)scrollWheel:(NSEvent*)theEvent { - if (!bridge_) - return; - - ui::ScrollEvent event(theEvent); - [self adjustUiEventLocation:&event fromNativeEvent:theEvent]; - - // Aura updates tooltips with the help of aura::Window::AddPreTargetHandler(). - // Mac hooks in here. - [self updateTooltipIfRequiredAt:event.location()]; - bridge_->host()->OnScrollEvent(event); -} - -// Called when we get a three-finger swipe, and they're enabled in System -// Preferences. -- (void)swipeWithEvent:(NSEvent*)event { - if (!bridge_) - return; - - // themblsha: In my testing all three-finger swipes send only a single event - // with a value of +/-1 on either axis. Diagonal swipes are not handled by - // AppKit. - - // We need to invert deltas in order to match GestureEventDetails's - // directions. - ui::GestureEventDetails swipeDetails(ui::ET_GESTURE_SWIPE, -[event deltaX], - -[event deltaY]); - swipeDetails.set_device_type(ui::GestureDeviceType::DEVICE_TOUCHPAD); - swipeDetails.set_touch_points(3); - - gfx::PointF location = ui::EventLocationFromNative(event); - // Note this uses the default unique_touch_event_id of 0 (Swipe events do not - // support -[NSEvent eventNumber]). This doesn't seem like a real omission - // because the three-finger swipes are instant and can't be tracked anyway. - ui::GestureEvent gestureEvent(location.x(), location.y(), - ui::EventFlagsFromNative(event), - ui::EventTimeFromNative(event), swipeDetails); - bridge_->host()->OnGestureEvent(gestureEvent); -} - -- (void)quickLookWithEvent:(NSEvent*)theEvent { - if (!bridge_) - return; - - const gfx::Point locationInContent = - gfx::ToFlooredPoint(ui::EventLocationFromNative(theEvent)); - - bool foundWord = false; - gfx::DecoratedText decoratedWord; - gfx::Point baselinePoint; - bridge_->host()->GetWordAt(locationInContent, &foundWord, &decoratedWord, - &baselinePoint); - if (!foundWord) - return; - - NSPoint baselinePointAppKit = NSMakePoint( - baselinePoint.x(), NSHeight([self frame]) - baselinePoint.y()); - [self showDefinitionForAttributedString: - gfx::GetAttributedStringFromDecoratedText(decoratedWord) - atPoint:baselinePointAppKit]; -} - -//////////////////////////////////////////////////////////////////////////////// -// NSResponder Action Messages. Keep sorted according NSResponder.h (from the -// 10.9 SDK). The list should eventually be complete. Anything not defined will -// beep when interpretKeyEvents: would otherwise call it. -// TODO(tapted): Make this list complete, except for insert* methods which are -// dispatched as regular key events in doCommandBySelector:. - -// views::Textfields are single-line only, map Paragraph and Document commands -// to Line. Also, Up/Down commands correspond to beginning/end of line. - -// The insertText action message forwards to the TextInputClient unless a menu -// is active. Note that NSResponder's interpretKeyEvents: implementation doesn't -// direct insertText: through doCommandBySelector:, so this is still needed to -// handle the case when inputContext: is nil. When inputContext: returns non-nil -// text goes directly to insertText:replacementRange:. -- (void)insertText:(id)text { - DCHECK_EQ(nil, [self inputContext]); - [self insertTextInternal:text]; -} - -// Selection movement and scrolling. - -- (void)moveForward:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_FORWARD - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)moveRight:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_RIGHT - keyCode:ui::VKEY_RIGHT - domCode:ui::DomCode::ARROW_RIGHT - eventFlags:0]; -} - -- (void)moveBackward:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_BACKWARD - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)moveLeft:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_LEFT - keyCode:ui::VKEY_LEFT - domCode:ui::DomCode::ARROW_LEFT - eventFlags:0]; -} - -- (void)moveUp:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_UP - keyCode:ui::VKEY_UP - domCode:ui::DomCode::ARROW_UP - eventFlags:0]; -} - -- (void)moveDown:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_DOWN - keyCode:ui::VKEY_DOWN - domCode:ui::DomCode::ARROW_DOWN - eventFlags:0]; -} - -- (void)moveWordForward:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_WORD_FORWARD - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)moveWordBackward:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_WORD_BACKWARD - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)moveToBeginningOfLine:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_TO_BEGINNING_OF_LINE - keyCode:ui::VKEY_HOME - domCode:ui::DomCode::HOME - eventFlags:0]; -} - -- (void)moveToEndOfLine:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_TO_END_OF_LINE - keyCode:ui::VKEY_END - domCode:ui::DomCode::END - eventFlags:0]; -} - -- (void)moveToBeginningOfParagraph:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_TO_BEGINNING_OF_PARAGRAPH - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)moveToEndOfParagraph:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_TO_END_OF_PARAGRAPH - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)moveToEndOfDocument:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_TO_END_OF_DOCUMENT - keyCode:ui::VKEY_END - domCode:ui::DomCode::END - eventFlags:ui::EF_CONTROL_DOWN]; -} - -- (void)moveToBeginningOfDocument:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_TO_BEGINNING_OF_DOCUMENT - keyCode:ui::VKEY_HOME - domCode:ui::DomCode::HOME - eventFlags:ui::EF_CONTROL_DOWN]; -} - -- (void)pageDown:(id)sender { - // The pageDown: action message is bound to the key combination - // [Option+PageDown]. - [self handleAction:ui::TextEditCommand::MOVE_PAGE_DOWN - keyCode:ui::VKEY_NEXT - domCode:ui::DomCode::PAGE_DOWN - eventFlags:ui::EF_ALT_DOWN]; -} - -- (void)pageUp:(id)sender { - // The pageUp: action message is bound to the key combination [Option+PageUp]. - [self handleAction:ui::TextEditCommand::MOVE_PAGE_UP - keyCode:ui::VKEY_PRIOR - domCode:ui::DomCode::PAGE_UP - eventFlags:ui::EF_ALT_DOWN]; -} - -- (void)moveBackwardAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_BACKWARD_AND_MODIFY_SELECTION - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)moveForwardAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_FORWARD_AND_MODIFY_SELECTION - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)moveWordForwardAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_WORD_FORWARD_AND_MODIFY_SELECTION - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)moveWordBackwardAndModifySelection:(id)sender { - [self - handleAction:ui::TextEditCommand::MOVE_WORD_BACKWARD_AND_MODIFY_SELECTION - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)moveUpAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_UP_AND_MODIFY_SELECTION - keyCode:ui::VKEY_UP - domCode:ui::DomCode::ARROW_UP - eventFlags:ui::EF_SHIFT_DOWN]; -} - -- (void)moveDownAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_DOWN_AND_MODIFY_SELECTION - keyCode:ui::VKEY_DOWN - domCode:ui::DomCode::ARROW_DOWN - eventFlags:ui::EF_SHIFT_DOWN]; -} - -- (void)moveToBeginningOfLineAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand:: - MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION - keyCode:ui::VKEY_HOME - domCode:ui::DomCode::HOME - eventFlags:ui::EF_SHIFT_DOWN]; -} - -- (void)moveToEndOfLineAndModifySelection:(id)sender { - [self - handleAction:ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION - keyCode:ui::VKEY_END - domCode:ui::DomCode::END - eventFlags:ui::EF_SHIFT_DOWN]; -} - -- (void)moveToBeginningOfParagraphAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand:: - MOVE_TO_BEGINNING_OF_PARAGRAPH_AND_MODIFY_SELECTION - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)moveToEndOfParagraphAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand:: - MOVE_TO_END_OF_PARAGRAPH_AND_MODIFY_SELECTION - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)moveToEndOfDocumentAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand:: - MOVE_TO_END_OF_DOCUMENT_AND_MODIFY_SELECTION - keyCode:ui::VKEY_END - domCode:ui::DomCode::END - eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; -} - -- (void)moveToBeginningOfDocumentAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand:: - MOVE_TO_BEGINNING_OF_DOCUMENT_AND_MODIFY_SELECTION - keyCode:ui::VKEY_HOME - domCode:ui::DomCode::HOME - eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; -} - -- (void)pageDownAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_PAGE_DOWN_AND_MODIFY_SELECTION - keyCode:ui::VKEY_NEXT - domCode:ui::DomCode::PAGE_DOWN - eventFlags:ui::EF_SHIFT_DOWN]; -} - -- (void)pageUpAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_PAGE_UP_AND_MODIFY_SELECTION - keyCode:ui::VKEY_PRIOR - domCode:ui::DomCode::PAGE_UP - eventFlags:ui::EF_SHIFT_DOWN]; -} - -- (void)moveParagraphForwardAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand:: - MOVE_PARAGRAPH_FORWARD_AND_MODIFY_SELECTION - keyCode:ui::VKEY_DOWN - domCode:ui::DomCode::ARROW_DOWN - eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; -} - -- (void)moveParagraphBackwardAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand:: - MOVE_PARAGRAPH_BACKWARD_AND_MODIFY_SELECTION - keyCode:ui::VKEY_UP - domCode:ui::DomCode::ARROW_UP - eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; -} - -- (void)moveWordRight:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_WORD_RIGHT - keyCode:ui::VKEY_RIGHT - domCode:ui::DomCode::ARROW_RIGHT - eventFlags:ui::EF_CONTROL_DOWN]; -} - -- (void)moveWordLeft:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_WORD_LEFT - keyCode:ui::VKEY_LEFT - domCode:ui::DomCode::ARROW_LEFT - eventFlags:ui::EF_CONTROL_DOWN]; -} - -- (void)moveRightAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_RIGHT_AND_MODIFY_SELECTION - keyCode:ui::VKEY_RIGHT - domCode:ui::DomCode::ARROW_RIGHT - eventFlags:ui::EF_SHIFT_DOWN]; -} - -- (void)moveLeftAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_LEFT_AND_MODIFY_SELECTION - keyCode:ui::VKEY_LEFT - domCode:ui::DomCode::ARROW_LEFT - eventFlags:ui::EF_SHIFT_DOWN]; -} - -- (void)moveWordRightAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_WORD_RIGHT_AND_MODIFY_SELECTION - keyCode:ui::VKEY_RIGHT - domCode:ui::DomCode::ARROW_RIGHT - eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; -} - -- (void)moveWordLeftAndModifySelection:(id)sender { - [self handleAction:ui::TextEditCommand::MOVE_WORD_LEFT_AND_MODIFY_SELECTION - keyCode:ui::VKEY_LEFT - domCode:ui::DomCode::ARROW_LEFT - eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; -} - -- (void)moveToLeftEndOfLine:(id)sender { - IsTextRTL(textInputClient_) ? [self moveToEndOfLine:sender] - : [self moveToBeginningOfLine:sender]; -} - -- (void)moveToRightEndOfLine:(id)sender { - IsTextRTL(textInputClient_) ? [self moveToBeginningOfLine:sender] - : [self moveToEndOfLine:sender]; -} - -- (void)moveToLeftEndOfLineAndModifySelection:(id)sender { - IsTextRTL(textInputClient_) - ? [self moveToEndOfLineAndModifySelection:sender] - : [self moveToBeginningOfLineAndModifySelection:sender]; -} - -- (void)moveToRightEndOfLineAndModifySelection:(id)sender { - IsTextRTL(textInputClient_) - ? [self moveToBeginningOfLineAndModifySelection:sender] - : [self moveToEndOfLineAndModifySelection:sender]; -} - -// Graphical Element transposition - -- (void)transpose:(id)sender { - [self handleAction:ui::TextEditCommand::TRANSPOSE - keyCode:ui::VKEY_T - domCode:ui::DomCode::US_T - eventFlags:ui::EF_CONTROL_DOWN]; -} - -// Deletions. - -- (void)deleteForward:(id)sender { - [self handleAction:ui::TextEditCommand::DELETE_FORWARD - keyCode:ui::VKEY_DELETE - domCode:ui::DomCode::DEL - eventFlags:0]; -} - -- (void)deleteBackward:(id)sender { - [self handleAction:ui::TextEditCommand::DELETE_BACKWARD - keyCode:ui::VKEY_BACK - domCode:ui::DomCode::BACKSPACE - eventFlags:0]; -} - -- (void)deleteWordForward:(id)sender { - [self handleAction:ui::TextEditCommand::DELETE_WORD_FORWARD - keyCode:ui::VKEY_DELETE - domCode:ui::DomCode::DEL - eventFlags:ui::EF_CONTROL_DOWN]; -} - -- (void)deleteWordBackward:(id)sender { - [self handleAction:ui::TextEditCommand::DELETE_WORD_BACKWARD - keyCode:ui::VKEY_BACK - domCode:ui::DomCode::BACKSPACE - eventFlags:ui::EF_CONTROL_DOWN]; -} - -- (void)deleteToBeginningOfLine:(id)sender { - [self handleAction:ui::TextEditCommand::DELETE_TO_BEGINNING_OF_LINE - keyCode:ui::VKEY_BACK - domCode:ui::DomCode::BACKSPACE - eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; -} - -- (void)deleteToEndOfLine:(id)sender { - [self handleAction:ui::TextEditCommand::DELETE_TO_END_OF_LINE - keyCode:ui::VKEY_DELETE - domCode:ui::DomCode::DEL - eventFlags:ui::EF_CONTROL_DOWN | ui::EF_SHIFT_DOWN]; -} - -- (void)deleteToBeginningOfParagraph:(id)sender { - [self handleAction:ui::TextEditCommand::DELETE_TO_BEGINNING_OF_PARAGRAPH - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)deleteToEndOfParagraph:(id)sender { - [self handleAction:ui::TextEditCommand::DELETE_TO_END_OF_PARAGRAPH - keyCode:ui::VKEY_UNKNOWN - domCode:ui::DomCode::NONE - eventFlags:0]; -} - -- (void)yank:(id)sender { - [self handleAction:ui::TextEditCommand::YANK - keyCode:ui::VKEY_Y - domCode:ui::DomCode::US_Y - eventFlags:ui::EF_CONTROL_DOWN]; -} - -// Cancellation. - -- (void)cancelOperation:(id)sender { - [self handleAction:ui::TextEditCommand::INVALID_COMMAND - keyCode:ui::VKEY_ESCAPE - domCode:ui::DomCode::ESCAPE - eventFlags:0]; -} - -// Support for Services in context menus. -// Currently we only support reading and writing plain strings. -- (id)validRequestorForSendType:(NSString*)sendType - returnType:(NSString*)returnType { - BOOL canWrite = [sendType isEqualToString:NSStringPboardType] && - [self selectedRange].length > 0; - BOOL canRead = [returnType isEqualToString:NSStringPboardType]; - // Valid if (sendType, returnType) is either (string, nil), (nil, string), - // or (string, string). - BOOL valid = textInputClient_ && ((canWrite && (canRead || !returnType)) || - (canRead && (canWrite || !sendType))); - return valid ? self : [super validRequestorForSendType:sendType - returnType:returnType]; -} - -// NSServicesRequests informal protocol. - -- (BOOL)writeSelectionToPasteboard:(NSPasteboard*)pboard types:(NSArray*)types { - DCHECK([types containsObject:NSStringPboardType]); - if (!textInputClient_) - return NO; - - gfx::Range selectionRange; - if (!textInputClient_->GetSelectionRange(&selectionRange)) - return NO; - - base::string16 text; - textInputClient_->GetTextFromRange(selectionRange, &text); - return [pboard writeObjects:@[ base::SysUTF16ToNSString(text) ]]; -} - -- (BOOL)readSelectionFromPasteboard:(NSPasteboard*)pboard { - NSArray* objects = - [pboard readObjectsForClasses:@[ [NSString class] ] options:0]; - DCHECK([objects count] == 1); - [self insertText:[objects lastObject]]; - return YES; -} - -// NSTextInputClient protocol implementation. - -// IMPORTANT: Always null-check |textInputClient_|. It can change (or be -// cleared) in -setTextInputClient:, which requires informing AppKit that the -// -inputContext has changed and to update its raw pointer. However, the AppKit -// method which does that may also spin a nested run loop communicating with an -// IME window and cause it to *use* the exact same NSTextInputClient (i.e., -// |self|) that we're trying to invalidate in -setTextInputClient:. -// See https://crbug.com/817097#c12 for further details on this atrocity. - -- (NSAttributedString*) - attributedSubstringForProposedRange:(NSRange)range - actualRange:(NSRangePointer)actualRange { - // On TouchBar Macs, the IME subsystem sometimes sends an invalid range with a - // non-zero length. This will cause a DCHECK in gfx::Range, so repair it here. - // See https://crbug.com/888782. - if (range.location == NSNotFound) - range.length = 0; - - gfx::Range actual_range; - base::string16 substring = AttributedSubstringForRangeHelper( - textInputClient_, gfx::Range(range), &actual_range); - if (actualRange) { - // To maintain consistency with NSTextView, return range {0,0} for an out of - // bounds requested range. - *actualRange = - actual_range.IsValid() ? actual_range.ToNSRange() : NSMakeRange(0, 0); - } - return substring.empty() - ? nil - : [[[NSAttributedString alloc] - initWithString:base::SysUTF16ToNSString(substring)] - autorelease]; -} - -- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint { - NOTIMPLEMENTED(); - return 0; -} - -- (void)doCommandBySelector:(SEL)selector { - // Like the renderer, handle insert action messages as a regular key dispatch. - // This ensures, e.g., insertTab correctly changes focus between fields. - if (keyDownEvent_ && [NSStringFromSelector(selector) hasPrefix:@"insert"]) - return; // Handle in -keyDown:. - - if ([self respondsToSelector:selector]) { - [self performSelector:selector withObject:nil]; - hasUnhandledKeyDownEvent_ = NO; - return; - } - - // For events that AppKit sends via doCommandBySelector:, first attempt to - // handle as a Widget accelerator. Forward along the responder chain only if - // the Widget doesn't handle it. - if (![self handleUnhandledKeyDownAsKeyEvent]) - [[self nextResponder] doCommandBySelector:selector]; -} - -- (NSRect)firstRectForCharacterRange:(NSRange)range - actualRange:(NSRangePointer)actualNSRange { - gfx::Range actualRange; - gfx::Rect rect = GetFirstRectForRangeHelper(textInputClient_, - gfx::Range(range), &actualRange); - if (actualNSRange) - *actualNSRange = actualRange.ToNSRange(); - return gfx::ScreenRectToNSRect(rect); -} - -- (BOOL)hasMarkedText { - return textInputClient_ && textInputClient_->HasCompositionText(); -} - -- (void)insertText:(id)text replacementRange:(NSRange)replacementRange { - if (!bridge_ || !textInputClient_) - return; - - textInputClient_->DeleteRange(gfx::Range(replacementRange)); - [self insertTextInternal:text]; -} - -- (NSRange)markedRange { - if (!textInputClient_) - return NSMakeRange(NSNotFound, 0); - - gfx::Range range; - textInputClient_->GetCompositionTextRange(&range); - return range.ToNSRange(); -} - -- (NSRange)selectedRange { - if (!textInputClient_) - return NSMakeRange(NSNotFound, 0); - - gfx::Range range; - textInputClient_->GetSelectionRange(&range); - return range.ToNSRange(); -} - -- (void)setMarkedText:(id)text - selectedRange:(NSRange)selectedRange - replacementRange:(NSRange)replacementRange { - if (!textInputClient_) - return; - - if ([text isKindOfClass:[NSAttributedString class]]) - text = [text string]; - - textInputClient_->DeleteRange(gfx::Range(replacementRange)); - ui::CompositionText composition; - composition.text = base::SysNSStringToUTF16(text); - composition.selection = gfx::Range(selectedRange); - - // Add an underline with text color and a transparent background to the - // composition text. TODO(karandeepb): On Cocoa textfields, the target clause - // of the composition has a thick underlines. The composition text also has - // discontinous underlines for different clauses. This is also supported in - // the Chrome renderer. Add code to extract underlines from |text| once our - // render text implementation supports thick underlines and discontinous - // underlines for consecutive characters. See http://crbug.com/612675. - composition.ime_text_spans.push_back( - ui::ImeTextSpan(ui::ImeTextSpan::Type::kComposition, 0, [text length], - ui::ImeTextSpan::Thickness::kThin, SK_ColorTRANSPARENT)); - textInputClient_->SetCompositionText(composition); - hasUnhandledKeyDownEvent_ = NO; -} - -- (void)unmarkText { - if (!textInputClient_) - return; - - textInputClient_->ConfirmCompositionText(); - hasUnhandledKeyDownEvent_ = NO; -} - -- (NSArray*)validAttributesForMarkedText { - return @[]; -} - -// NSUserInterfaceValidations protocol implementation. - -- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { - ui::TextEditCommand command = GetTextEditCommandForMenuAction([item action]); - - if (command == ui::TextEditCommand::INVALID_COMMAND) - return NO; - - if (textInputClient_) - return textInputClient_->IsTextEditCommandEnabled(command); - - // views::Label does not implement the TextInputClient interface but still - // needs to intercept the Copy and Select All menu actions. - if (command != ui::TextEditCommand::COPY && - command != ui::TextEditCommand::SELECT_ALL) - return NO; - - bool is_textual = false; - bridge_->host()->GetIsFocusedViewTextual(&is_textual); - return is_textual; -} - -// NSDraggingSource protocol implementation. - -- (NSDragOperation)draggingSession:(NSDraggingSession*)session - sourceOperationMaskForDraggingContext:(NSDraggingContext)context { - return NSDragOperationEvery; -} - -- (void)draggingSession:(NSDraggingSession*)session - endedAtPoint:(NSPoint)screenPoint - operation:(NSDragOperation)operation { - views::DragDropClientMac* client = [self dragDropClient]; - if (client) - client->EndDrag(); -} - -// NSAccessibility informal protocol implementation. - -- (id)accessibilityAttributeValue:(NSString*)attribute { - if ([attribute isEqualToString:NSAccessibilityChildrenAttribute]) { - return @[ bridge_->host()->GetNativeViewAccessible() ]; - } - - return [super accessibilityAttributeValue:attribute]; -} - -- (id)accessibilityHitTest:(NSPoint)point { - return - [bridge_->host()->GetNativeViewAccessible() accessibilityHitTest:point]; -} - -- (id)accessibilityFocusedUIElement { - if (!bridge_) - return nil; - return [bridge_->host()->GetNativeViewAccessible() - accessibilityFocusedUIElement]; -} - -@end diff --git a/chromium/ui/views/cocoa/bridged_content_view_touch_bar.mm b/chromium/ui/views/cocoa/bridged_content_view_touch_bar.mm deleted file mode 100644 index 6f1d18d11c4..00000000000 --- a/chromium/ui/views/cocoa/bridged_content_view_touch_bar.mm +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2016 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 "base/mac/availability.h" -#import "base/mac/scoped_nsobject.h" -#import "base/mac/sdk_forward_declarations.h" -#include "base/strings/sys_string_conversions.h" -#import "ui/base/cocoa/touch_bar_forward_declarations.h" -#import "ui/views/cocoa/bridged_content_view.h" -#import "ui/views/cocoa/bridged_native_widget.h" -#import "ui/views/cocoa/bridged_native_widget_host.h" - -namespace { - -NSString* const kTouchBarDialogButtonsGroupId = - @"com.google.chrome-DIALOG-BUTTONS-GROUP"; -NSString* const kTouchBarOKId = @"com.google.chrome-OK"; -NSString* const kTouchBarCancelId = @"com.google.chrome-CANCEL"; - -} // namespace - -@interface BridgedContentView (TouchBarAdditions)<NSTouchBarDelegate> -- (void)touchBarButtonAction:(id)sender; -@end - -@implementation BridgedContentView (TouchBarAdditions) - -- (void)touchBarButtonAction:(id)sender { - ui::DialogButton type = static_cast<ui::DialogButton>([sender tag]); - if (bridge_) - bridge_->host()->DoDialogButtonAction(type); -} - -// NSTouchBarDelegate protocol implementation. - -- (NSTouchBarItem*)touchBar:(NSTouchBar*)touchBar - makeItemForIdentifier:(NSTouchBarItemIdentifier)identifier - API_AVAILABLE(macos(10.12.2)) { - if (!bridge_) - return nil; - - if ([identifier isEqualToString:kTouchBarDialogButtonsGroupId]) { - NSMutableArray* items = [NSMutableArray arrayWithCapacity:2]; - for (NSTouchBarItemIdentifier i in @[ kTouchBarCancelId, kTouchBarOKId ]) { - NSTouchBarItem* item = [self touchBar:touchBar makeItemForIdentifier:i]; - if (item) - [items addObject:item]; - } - if ([items count] == 0) - return nil; - return [NSClassFromString(@"NSGroupTouchBarItem") - groupItemWithIdentifier:identifier - items:items]; - } - - ui::DialogButton type = ui::DIALOG_BUTTON_NONE; - if ([identifier isEqualToString:kTouchBarOKId]) - type = ui::DIALOG_BUTTON_OK; - else if ([identifier isEqualToString:kTouchBarCancelId]) - type = ui::DIALOG_BUTTON_CANCEL; - else - return nil; - - bool buttonExists = false; - base::string16 buttonLabel; - bool isButtonEnabled = false; - bool isButtonDefault = false; - bridge_->host()->GetDialogButtonInfo(type, &buttonExists, &buttonLabel, - &isButtonEnabled, &isButtonDefault); - if (!buttonExists) - return nil; - - base::scoped_nsobject<NSCustomTouchBarItem> item([[NSClassFromString( - @"NSCustomTouchBarItem") alloc] initWithIdentifier:identifier]); - NSButton* button = - [NSButton buttonWithTitle:base::SysUTF16ToNSString(buttonLabel) - target:self - action:@selector(touchBarButtonAction:)]; - if (isButtonDefault) { - // NSAlert uses a private NSButton subclass (_NSTouchBarGroupButton) with - // more bells and whistles. It doesn't use -setBezelColor: directly, but - // this gives an appearance matching the default _NSTouchBarGroupButton. - [button setBezelColor:[NSColor colorWithSRGBRed:0.168 - green:0.51 - blue:0.843 - alpha:1.0]]; - } - [button setEnabled:isButtonEnabled]; - [button setTag:type]; - [item setView:button]; - return item.autorelease(); -} - -// NSTouchBarProvider protocol implementation (via NSResponder category). - -- (NSTouchBar*)makeTouchBar { - if (!bridge_) - return nil; - - bool buttonsExist = false; - bridge_->host()->GetDoDialogButtonsExist(&buttonsExist); - if (!buttonsExist) - return nil; - - base::scoped_nsobject<NSTouchBar> bar( - [[NSClassFromString(@"NSTouchBar") alloc] init]); - [bar setDelegate:self]; - - // Use a group rather than individual items so they can be centered together. - [bar setDefaultItemIdentifiers:@[ kTouchBarDialogButtonsGroupId ]]; - - // Setting the group as principal will center it in the TouchBar. - [bar setPrincipalItemIdentifier:kTouchBarDialogButtonsGroupId]; - return bar.autorelease(); -} - -@end diff --git a/chromium/ui/views/cocoa/bridged_native_widget.h b/chromium/ui/views/cocoa/bridged_native_widget.h deleted file mode 100644 index 6d2d12e6455..00000000000 --- a/chromium/ui/views/cocoa/bridged_native_widget.h +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright 2014 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 UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_H_ -#define UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_H_ - -#import <Cocoa/Cocoa.h> - -#include <memory> -#include <vector> - -#import "base/mac/scoped_nsobject.h" -#include "base/macros.h" -#include "components/viz/common/surfaces/parent_local_surface_id_allocator.h" -#include "ui/accelerated_widget_mac/ca_transaction_observer.h" -#include "ui/accelerated_widget_mac/display_ca_layer_tree.h" -#include "ui/base/ime/text_input_client.h" -#include "ui/display/display_observer.h" -#import "ui/views/cocoa/bridged_native_widget_owner.h" -#import "ui/views/cocoa/cocoa_mouse_capture_delegate.h" -#import "ui/views/focus/focus_manager.h" -#include "ui/views/views_export.h" -#include "ui/views/widget/widget.h" -#include "ui/views_bridge_mac/mojo/bridged_native_widget.mojom.h" - -@class BridgedContentView; -@class ModalShowAnimationWithLayer; -@class NativeWidgetMacNSWindow; -@class ViewsNSWindowDelegate; - -namespace views { -namespace test { -class BridgedNativeWidgetTestApi; -} - -class BridgedNativeWidgetHost; -class CocoaMouseCapture; -class CocoaWindowMoveLoop; -class DragDropClientMac; -class NativeWidgetMac; -class View; - -// A bridge to an NSWindow managed by an instance of NativeWidgetMac or -// DesktopNativeWidgetMac. Serves as a helper class to bridge requests from the -// NativeWidgetMac to the Cocoa window. Behaves a bit like an aura::Window. -class VIEWS_EXPORT BridgedNativeWidget - : public views_bridge_mac::mojom::BridgedNativeWidget, - public display::DisplayObserver, - public ui::CATransactionCoordinator::PreCommitObserver, - public CocoaMouseCaptureDelegate, - public BridgedNativeWidgetOwner { - public: - // Contains NativeViewHost->gfx::NativeView associations. - using AssociatedViews = std::map<const views::View*, NSView*>; - - // Return the size that |window| will take for the given client area |size|, - // based on its current style mask. - static gfx::Size GetWindowSizeForClientSize(NSWindow* window, - const gfx::Size& size); - - // Creates one side of the bridge. |host| and |parent| must not be NULL. - BridgedNativeWidget(BridgedNativeWidgetHost* host, NativeWidgetMac* parent); - ~BridgedNativeWidget() override; - - // Initialize the NSWindow by taking ownership of the specified object. - // TODO(ccameron): When a BridgedNativeWidget is allocated across a process - // boundary, it will not be possible to explicitly set an NSWindow in this - // way. - void SetWindow(base::scoped_nsobject<NativeWidgetMacNSWindow> window); - - // Create the drag drop client for this widget. - // TODO(ccameron): This function takes a views::View (and is not rolled into - // CreateContentView) because drag-drop has not been routed through |host_| - // yet. - void CreateDragDropClient(views::View* view); - - // Set the parent NSView for the widget. - // TODO(ccameron): Like SetWindow, this will need to pass a handle instead of - // an NSView across processes. - void SetParent(NSView* parent); - - // Changes the bounds of the window and the hosted layer if present. The - // origin is a location in screen coordinates except for "child" windows, - // which are positioned relative to their parent(). SetBounds() considers a - // "child" window to be one initialized with InitParams specifying all of: - // a |parent| NSWindow, the |child| attribute, and a |type| that - // views::GetAuraWindowTypeForWidgetType does not consider a "popup" type. - void SetBounds(const gfx::Rect& new_bounds); - - // Start moving the window, pinned to the mouse cursor, and monitor events. - // Return MOVE_LOOP_SUCCESSFUL on mouse up or MOVE_LOOP_CANCELED on premature - // termination via EndMoveLoop() or when window is destroyed during the drag. - Widget::MoveLoopResult RunMoveLoop(const gfx::Vector2d& drag_offset); - void EndMoveLoop(); - - // See views::Widget. - void SetNativeWindowProperty(const char* key, void* value); - void* GetNativeWindowProperty(const char* key) const; - - // Sets the cursor associated with the NSWindow. Retains |cursor|. - void SetCursor(NSCursor* cursor); - - // Called internally by the NSWindowDelegate when the window is closing. - void OnWindowWillClose(); - - // Called by the NSWindowDelegate when a fullscreen operation begins. If - // |target_fullscreen_state| is true, the target state is fullscreen. - // Otherwise, a transition has begun to come out of fullscreen. - void OnFullscreenTransitionStart(bool target_fullscreen_state); - - // Called when a fullscreen transition completes. If target_fullscreen_state() - // does not match |actual_fullscreen_state|, a new transition will begin. - void OnFullscreenTransitionComplete(bool actual_fullscreen_state); - - // Transition the window into or out of fullscreen. This will immediately - // invert the value of target_fullscreen_state(). - void ToggleDesiredFullscreenState(bool async = false); - - // Called by the NSWindowDelegate when the size of the window changes. - void OnSizeChanged(); - - // Called once by the NSWindowDelegate when the position of the window has - // changed. - void OnPositionChanged(); - - // Called by the NSWindowDelegate when the visibility of the window may have - // changed. For example, due to a (de)miniaturize operation, or the window - // being reordered in (or out of) the screen list. - void OnVisibilityChanged(); - - // Called by the NSWindowDelegate when the system control tint changes. - void OnSystemControlTintChanged(); - - // Called by the NSWindowDelegate on a scale factor or color space change. - void OnBackingPropertiesChanged(); - - // Called by the NSWindowDelegate when the window becomes or resigns key. - void OnWindowKeyStatusChangedTo(bool is_key); - - // Called by the window show animation when it completes and wants to destroy - // itself. - void OnShowAnimationComplete(); - - // Updates |associated_views_| on NativeViewHost::Attach()/Detach(). - void SetAssociationForView(const views::View* view, NSView* native_view); - void ClearAssociationForView(const views::View* view); - // Sorts child NSViews according to NativeViewHosts order in views hierarchy. - void ReorderChildViews(); - - NativeWidgetMac* native_widget_mac() { return native_widget_mac_; } - BridgedContentView* ns_view() { return bridged_view_; } - BridgedNativeWidgetHost* host() { return host_; } - NSWindow* ns_window(); - - TooltipManager* tooltip_manager() { return tooltip_manager_.get(); } - - DragDropClientMac* drag_drop_client() { return drag_drop_client_.get(); } - bool is_translucent_window() const { return is_translucent_window_; } - - // The parent widget specified in Widget::InitParams::parent. If non-null, the - // parent will close children before the parent closes, and children will be - // raised above their parent when window z-order changes. - BridgedNativeWidgetOwner* parent() { return parent_; } - const std::vector<BridgedNativeWidget*>& child_windows() { - return child_windows_; - } - - // Re-parent a |native_view| in this Widget to be a child of |new_parent|. - // |native_view| must either be |ns_view()| or a descendant of |ns_view()|. - // |native_view| is added as a subview of |new_parent| unless it is the - // contentView of a top-level Widget. If |native_view| is |ns_view()|, |this| - // also becomes a child window of |new_parent|'s NSWindow. - void ReparentNativeView(NSView* native_view, NSView* new_parent); - - bool target_fullscreen_state() const { return target_fullscreen_state_; } - bool window_visible() const { return window_visible_; } - bool wants_to_be_visible() const { return wants_to_be_visible_; } - bool in_fullscreen_transition() const { return in_fullscreen_transition_; } - - // Enables or disables all window animations. - void SetAnimationEnabled(bool animate); - - // Sets which transitions will animate. Currently this only affects non-native - // animations. TODO(tapted): Use scoping to disable native animations at - // appropriate times as well. - void set_transitions_to_animate(int transitions) { - transitions_to_animate_ = transitions; - } - - // Whether to run a custom animation for the provided |transition|. - bool ShouldRunCustomAnimationFor( - Widget::VisibilityTransition transition) const; - - // display::DisplayObserver: - void OnDisplayMetricsChanged(const display::Display& display, - uint32_t metrics) override; - - // ui::CATransactionCoordinator::PreCommitObserver: - bool ShouldWaitInPreCommit() override; - base::TimeDelta PreCommitTimeout() override; - - // views_bridge_mac::mojom::BridgedNativeWidget: - void InitWindow(views_bridge_mac::mojom::BridgedNativeWidgetInitParamsPtr - params) override; - void InitCompositorView() override; - void CreateContentView(const gfx::Rect& bounds) override; - void DestroyContentView() override; - void CloseWindow() override; - void CloseWindowNow() override; - void SetInitialBounds(const gfx::Rect& new_bounds, - const gfx::Size& minimum_content_size, - const gfx::Vector2d& parent_offset) override; - void SetBounds(const gfx::Rect& new_bounds, - const gfx::Size& minimum_content_size) override; - void SetVisibilityState( - views_bridge_mac::mojom::WindowVisibilityState new_state) override; - void SetVisibleOnAllSpaces(bool always_visible) override; - void SetFullscreen(bool fullscreen) override; - void SetMiniaturized(bool miniaturized) override; - void SetSizeConstraints(const gfx::Size& min_size, - const gfx::Size& max_size, - bool is_resizable, - bool is_maximizable) override; - void SetOpacity(float opacity) override; - void SetContentAspectRatio(const gfx::SizeF& aspect_ratio) override; - void SetCALayerParams(const gfx::CALayerParams& ca_layer_params) override; - void SetWindowTitle(const base::string16& title) override; - void MakeFirstResponder() override; - void ClearTouchBar() override; - void AcquireCapture() override; - void ReleaseCapture() override; - - // TODO(ccameron): This method exists temporarily as we move all direct access - // of TextInputClient out of BridgedContentView. - void SetTextInputClient(ui::TextInputClient* text_input_client); - - // Compute the window and content size, and forward them to |host_|. This will - // update widget and compositor size. - void UpdateWindowGeometry(); - - private: - friend class test::BridgedNativeWidgetTestApi; - - // Closes all child windows. BridgedNativeWidget children will be destroyed. - void RemoveOrDestroyChildren(); - - // Notify descendants of a visibility change. - void NotifyVisibilityChangeDown(); - - // Installs the NSView for hosting the composited layer. - void AddCompositorSuperview(); - - // Query the display properties of the monitor that |window_| is on, and - // forward them to |host_|. - void UpdateWindowDisplay(); - - // Return true if the delegate's modal type is window-modal. These display as - // a native window "sheet", and have a different lifetime to regular windows. - bool IsWindowModalSheet() const; - - // Show the window using -[NSApp beginSheet:..], modal for the parent window. - void ShowAsModalSheet(); - - // Returns true if capture exists and is currently active. - bool HasCapture(); - - // CocoaMouseCaptureDelegate: - void PostCapturedEvent(NSEvent* event) override; - void OnMouseCaptureLost() override; - NSWindow* GetWindow() const override; - - // Returns a properties dictionary associated with the NSWindow. - // Creates and attaches a new instance if not found. - NSMutableDictionary* GetWindowProperties() const; - - // BridgedNativeWidgetOwner: - NSWindow* GetNSWindow() override; - gfx::Vector2d GetChildWindowOffset() const override; - bool IsVisibleParent() const override; - void RemoveChildWindow(BridgedNativeWidget* child) override; - - BridgedNativeWidgetHost* const host_; // Weak. Owns this. - NativeWidgetMac* const native_widget_mac_; // Weak. Owns |host_|. - base::scoped_nsobject<NativeWidgetMacNSWindow> window_; - base::scoped_nsobject<ViewsNSWindowDelegate> window_delegate_; - base::scoped_nsobject<BridgedContentView> bridged_view_; - base::scoped_nsobject<ModalShowAnimationWithLayer> show_animation_; - std::unique_ptr<CocoaMouseCapture> mouse_capture_; - std::unique_ptr<CocoaWindowMoveLoop> window_move_loop_; - std::unique_ptr<TooltipManager> tooltip_manager_; - std::unique_ptr<DragDropClientMac> drag_drop_client_; - ui::ModalType modal_type_ = ui::MODAL_TYPE_NONE; - bool is_translucent_window_ = false; - - BridgedNativeWidgetOwner* parent_ = nullptr; // Weak. If non-null, owns this. - std::vector<BridgedNativeWidget*> child_windows_; - - // The size of the content area of the window most recently sent to |host_| - // (and its compositor). - gfx::Size content_dip_size_; - - // The size of the frame most recently *received from* the compositor. Note - // that during resize (and showing new windows), this will lag behind - // |content_dip_size_|, which is the frame size most recently *sent to* the - // compositor. - gfx::Size compositor_frame_dip_size_; - base::scoped_nsobject<NSView> compositor_superview_; - std::unique_ptr<ui::DisplayCALayerTree> display_ca_layer_tree_; - - // Tracks the bounds when the window last started entering fullscreen. Used to - // provide an answer for GetRestoredBounds(), but not ever sent to Cocoa (it - // has its own copy, but doesn't provide access to it). - gfx::Rect bounds_before_fullscreen_; - - // The transition types to animate when not relying on native NSWindow - // animation behaviors. Bitmask of Widget::VisibilityTransition. - int transitions_to_animate_ = Widget::ANIMATE_BOTH; - - // Whether this window wants to be fullscreen. If a fullscreen animation is in - // progress then it might not be actually fullscreen. - bool target_fullscreen_state_ = false; - - // Whether this window is in a fullscreen transition, and the fullscreen state - // can not currently be changed. - bool in_fullscreen_transition_ = false; - - // Stores the value last read from -[NSWindow isVisible], to detect visibility - // changes. - bool window_visible_ = false; - - // If true, the window is either visible, or wants to be visible but is - // currently hidden due to having a hidden parent. - bool wants_to_be_visible_ = false; - - // If true, then ignore interactions with CATransactionCoordinator until the - // first frame arrives. - bool ca_transaction_sync_suppressed_ = false; - - // If true, the window has been made visible or changed shape and the window - // shadow needs to be invalidated when a frame is received for the new shape. - bool invalidate_shadow_on_frame_swap_ = false; - - AssociatedViews associated_views_; - - DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidget); -}; - -} // namespace views - -#endif // UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_H_ diff --git a/chromium/ui/views/cocoa/bridged_native_widget.mm b/chromium/ui/views/cocoa/bridged_native_widget.mm deleted file mode 100644 index abdbcc45fab..00000000000 --- a/chromium/ui/views/cocoa/bridged_native_widget.mm +++ /dev/null @@ -1,1283 +0,0 @@ -// Copyright 2014 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. - -#import "ui/views/cocoa/bridged_native_widget.h" - -#import <objc/runtime.h> -#include <stddef.h> -#include <stdint.h> - -#include "base/command_line.h" -#include "base/logging.h" -#import "base/mac/foundation_util.h" -#include "base/mac/mac_util.h" -#import "base/mac/sdk_forward_declarations.h" -#include "base/single_thread_task_runner.h" -#include "base/strings/sys_string_conversions.h" -#import "ui/base/cocoa/constrained_window/constrained_window_animation.h" -#include "ui/base/hit_test.h" -#include "ui/base/layout.h" -#include "ui/base/ui_base_switches.h" -#include "ui/display/screen.h" -#include "ui/gfx/geometry/dip_util.h" -#import "ui/gfx/mac/coordinate_conversion.h" -#import "ui/gfx/mac/nswindow_frame_controls.h" -#import "ui/native_theme/native_theme_mac.h" -#import "ui/views/cocoa/bridged_content_view.h" -#import "ui/views/cocoa/bridged_native_widget_host.h" -#import "ui/views/cocoa/cocoa_mouse_capture.h" -#import "ui/views/cocoa/cocoa_window_move_loop.h" -#import "ui/views/cocoa/drag_drop_client_mac.h" -#import "ui/views/cocoa/native_widget_mac_nswindow.h" -#include "ui/views/cocoa/tooltip_manager_mac.h" -#import "ui/views/cocoa/views_nswindow_delegate.h" -#import "ui/views/cocoa/widget_owner_nswindow_adapter.h" -#include "ui/views/widget/native_widget_mac.h" -#include "ui/views/widget/widget.h" - -using views_bridge_mac::mojom::WindowVisibilityState; - -namespace { -constexpr auto kUIPaintTimeout = base::TimeDelta::FromSeconds(5); -} // namespace - -// Self-owning animation delegate that starts a hide animation, then calls -// -[NSWindow close] when the animation ends, releasing itself. -@interface ViewsNSWindowCloseAnimator : NSObject<NSAnimationDelegate> { - @private - base::scoped_nsobject<NSWindow> window_; - base::scoped_nsobject<NSAnimation> animation_; -} -+ (void)closeWindowWithAnimation:(NSWindow*)window; -@end - -@implementation ViewsNSWindowCloseAnimator - -- (id)initWithWindow:(NSWindow*)window { - if ((self = [super init])) { - window_.reset([window retain]); - animation_.reset( - [[ConstrainedWindowAnimationHide alloc] initWithWindow:window]); - [animation_ setDelegate:self]; - [animation_ setAnimationBlockingMode:NSAnimationNonblocking]; - [animation_ startAnimation]; - } - return self; -} - -+ (void)closeWindowWithAnimation:(NSWindow*)window { - [[ViewsNSWindowCloseAnimator alloc] initWithWindow:window]; -} - -- (void)animationDidEnd:(NSAnimation*)animation { - [window_ close]; - [animation_ setDelegate:nil]; - [self release]; -} -@end - -// The NSView that hosts the composited CALayer drawing the UI. It fills the -// window but is not hittable so that accessibility hit tests always go to the -// BridgedContentView. -@interface ViewsCompositorSuperview : NSView -@end - -@implementation ViewsCompositorSuperview -- (NSView*)hitTest:(NSPoint)aPoint { - return nil; -} -@end - -// This class overrides NSAnimation methods to invalidate the shadow for each -// frame. It is required because the show animation uses CGSSetWindowWarp() -// which is touchy about the consistency of the points it is given. The show -// animation includes a translate, which fails to apply properly to the window -// shadow, when that shadow is derived from a layer-hosting view. So invalidate -// it. This invalidation is only needed to cater for the translate. It is not -// required if CGSSetWindowWarp() is used in a way that keeps the center point -// of the window stationary (e.g. a scale). It's also not required for the hide -// animation: in that case, the shadow is never invalidated so retains the -// shadow calculated before a translate is applied. -@interface ModalShowAnimationWithLayer - : ConstrainedWindowAnimationShow<NSAnimationDelegate> -@end - -@implementation ModalShowAnimationWithLayer { - // This is the "real" delegate, but this class acts as the NSAnimationDelegate - // to avoid a separate object. - views::BridgedNativeWidget* bridgedNativeWidget_; -} -- (instancetype)initWithBridgedNativeWidget: - (views::BridgedNativeWidget*)widget { - if ((self = [super initWithWindow:widget->ns_window()])) { - bridgedNativeWidget_ = widget; - [self setDelegate:self]; - } - return self; -} -- (void)dealloc { - DCHECK(!bridgedNativeWidget_); - [super dealloc]; -} -- (void)animationDidEnd:(NSAnimation*)animation { - DCHECK(bridgedNativeWidget_); - bridgedNativeWidget_->OnShowAnimationComplete(); - bridgedNativeWidget_ = nullptr; - [self setDelegate:nil]; -} -- (void)stopAnimation { - [super stopAnimation]; - [window_ invalidateShadow]; -} -- (void)setCurrentProgress:(NSAnimationProgress)progress { - [super setCurrentProgress:progress]; - [window_ invalidateShadow]; -} -@end - -namespace { - -using RankMap = std::map<NSView*, int>; - -// SDK 10.11 contains incompatible changes of sortSubviewsUsingFunction. -// It takes (__kindof NSView*) as comparator argument. -// https://llvm.org/bugs/show_bug.cgi?id=25149 -#if !defined(MAC_OS_X_VERSION_10_11) || \ - MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_11 -using NSViewComparatorValue = id; -#else -using NSViewComparatorValue = __kindof NSView*; -#endif - -int kWindowPropertiesKey; - -// Returns true if the content_view is reparented. -bool PositionWindowInNativeViewParent(NSView* content_view) { - return [[content_view window] contentView] != content_view; -} - -// Return the offset of the parent native view from the window. -gfx::Vector2d GetNativeViewParentOffset(NSView* content_view) { - NSWindow* window = [content_view window]; - NSView* parent_view = [content_view superview]; - NSPoint p = NSMakePoint(0, NSHeight([parent_view frame])); - p = [parent_view convertPoint:p toView:nil]; - return gfx::Vector2d(p.x, NSHeight([window frame]) - p.y); -} - -// Return the content size for a minimum or maximum widget size. -gfx::Size GetClientSizeForWindowSize(NSWindow* window, - const gfx::Size& window_size) { - NSRect frame_rect = - NSMakeRect(0, 0, window_size.width(), window_size.height()); - // Note gfx::Size will prevent dimensions going negative. They are allowed to - // be zero at this point, because Widget::GetMinimumSize() may later increase - // the size. - return gfx::Size([window contentRectForFrameRect:frame_rect].size); -} - -void RankNSViews(views::View* view, - const views::BridgedNativeWidget::AssociatedViews& hosts, - RankMap* rank) { - auto it = hosts.find(view); - if (it != hosts.end()) - rank->emplace(it->second, rank->size()); - for (int i = 0; i < view->child_count(); ++i) - RankNSViews(view->child_at(i), hosts, rank); -} - -NSComparisonResult SubviewSorter(NSViewComparatorValue lhs, - NSViewComparatorValue rhs, - void* rank_as_void) { - DCHECK_NE(lhs, rhs); - - const RankMap* rank = static_cast<const RankMap*>(rank_as_void); - auto left_rank = rank->find(lhs); - auto right_rank = rank->find(rhs); - bool left_found = left_rank != rank->end(); - bool right_found = right_rank != rank->end(); - - // Sort unassociated views above associated views. - if (left_found != right_found) - return left_found ? NSOrderedAscending : NSOrderedDescending; - - if (left_found) { - return left_rank->second < right_rank->second ? NSOrderedAscending - : NSOrderedDescending; - } - - // If both are unassociated, consider that order is not important - return NSOrderedSame; -} - -// Counts windows managed by a BridgedNativeWidget instance in the -// |child_windows| array ignoring the windows added by AppKit. -NSUInteger CountBridgedWindows(NSArray* child_windows) { - NSUInteger count = 0; - for (NSWindow* child in child_windows) - if ([[child delegate] isKindOfClass:[ViewsNSWindowDelegate class]]) - ++count; - - return count; -} - -} // namespace - -namespace views { - -// static -gfx::Size BridgedNativeWidget::GetWindowSizeForClientSize( - NSWindow* window, - const gfx::Size& content_size) { - NSRect content_rect = - NSMakeRect(0, 0, content_size.width(), content_size.height()); - NSRect frame_rect = [window frameRectForContentRect:content_rect]; - return gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect)); -} - -BridgedNativeWidget::BridgedNativeWidget(BridgedNativeWidgetHost* host, - NativeWidgetMac* parent) - : host_(host), native_widget_mac_(parent) { - DCHECK(parent); - window_delegate_.reset( - [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); - ui::CATransactionCoordinator::Get().AddPreCommitObserver(this); -} - -BridgedNativeWidget::~BridgedNativeWidget() { - // The delegate should be cleared already. Note this enforces the precondition - // that -[NSWindow close] is invoked on the hosted window before the - // destructor is called. - DCHECK(![window_ delegate]); - - ui::CATransactionCoordinator::Get().RemovePreCommitObserver(this); - RemoveOrDestroyChildren(); - DCHECK(child_windows_.empty()); - DestroyContentView(); -} - -void BridgedNativeWidget::SetWindow( - base::scoped_nsobject<NativeWidgetMacNSWindow> window) { - DCHECK(!window_); - window_ = std::move(window); - [window_ setReleasedWhenClosed:NO]; // Owned by scoped_nsobject. - [window_ setDelegate:window_delegate_]; -} - -void BridgedNativeWidget::SetParent(NSView* new_parent) { - BridgedNativeWidget* bridged_native_widget_parent = - NativeWidgetMac::GetBridgeForNativeWindow([new_parent window]); - // Remove from the old parent. - if (parent_) { - parent_->RemoveChildWindow(this); - parent_ = nullptr; - } - - if (!new_parent) - return; - - // Disallow creating child windows of views not currently in an NSWindow. - CHECK([new_parent window]); - - // If the parent is another BridgedNativeWidget, just add to the collection - // of child windows it owns and manages. Otherwise, create an adapter to - // anchor the child widget and observe when the parent NSWindow is closed. - if (bridged_native_widget_parent) { - parent_ = bridged_native_widget_parent; - bridged_native_widget_parent->child_windows_.push_back(this); - } else { - parent_ = new WidgetOwnerNSWindowAdapter(this, new_parent); - } - - // Widget::ShowInactive() could result in a Space switch when the widget has a - // parent, and we're calling -orderWindow:relativeTo:. Use Transient - // collection behaviour to prevent that. - // https://crbug.com/697829 - [window_ setCollectionBehavior:[window_ collectionBehavior] | - NSWindowCollectionBehaviorTransient]; -} - -void BridgedNativeWidget::InitWindow( - views_bridge_mac::mojom::BridgedNativeWidgetInitParamsPtr params) { - modal_type_ = params->modal_type; - is_translucent_window_ = params->is_translucent; - - // Register for application hide notifications so that visibility can be - // properly tracked. This is not done in the delegate so that the lifetime is - // tied to the C++ object, rather than the delegate (which may be reference - // counted). This is required since the application hides do not send an - // orderOut: to individual windows. Unhide, however, does send an order - // message. - [[NSNotificationCenter defaultCenter] - addObserver:window_delegate_ - selector:@selector(onWindowOrderChanged:) - name:NSApplicationDidHideNotification - object:nil]; - - [[NSNotificationCenter defaultCenter] - addObserver:window_delegate_ - selector:@selector(onSystemControlTintChanged:) - name:NSControlTintDidChangeNotification - object:nil]; - - // Validate the window's initial state, otherwise the bridge's initial - // tracking state will be incorrect. - DCHECK(![window_ isVisible]); - DCHECK_EQ(0u, [window_ styleMask] & NSFullScreenWindowMask); - - // Include "regular" windows without the standard frame in the window cycle. - // These use NSBorderlessWindowMask so do not get it by default. - if (params->force_into_collection_cycle) { - [window_ - setCollectionBehavior:[window_ collectionBehavior] | - NSWindowCollectionBehaviorParticipatesInCycle]; - } - - [window_ setHasShadow:params->has_window_server_shadow]; - tooltip_manager_.reset(new TooltipManagerMac(this)); -} - -void BridgedNativeWidget::SetInitialBounds( - const gfx::Rect& new_bounds, - const gfx::Size& minimum_content_size, - const gfx::Vector2d& bounds_offset_for_parent) { - gfx::Rect adjusted_bounds = new_bounds; - if (new_bounds.IsEmpty()) { - // If a position is set, but no size, complain. Otherwise, a 1x1 window - // would appear there, which might be unexpected. - DCHECK(new_bounds.origin().IsOrigin()) - << "Zero-sized windows not supported on Mac."; - - // Otherwise, bounds is all zeroes. Cocoa will currently have the window at - // the bottom left of the screen. To support a client calling SetSize() only - // (and for consistency across platforms) put it at the top-left instead. - // Read back the current frame: it will be a 1x1 context rect but the frame - // size also depends on the window style. - NSRect frame_rect = [window_ frame]; - adjusted_bounds = gfx::Rect( - gfx::Point(), gfx::Size(NSWidth(frame_rect), NSHeight(frame_rect))); - } - adjusted_bounds.Offset(bounds_offset_for_parent); - SetBounds(adjusted_bounds, minimum_content_size); -} - -void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds, - const gfx::Size& minimum_content_size) { - // -[NSWindow contentMinSize] is only checked by Cocoa for user-initiated - // resizes. This is not what toolkit-views expects, so clamp. Note there is - // no check for maximum size (consistent with aura::Window::SetBounds()). - gfx::Size clamped_content_size = - GetClientSizeForWindowSize(window_, new_bounds.size()); - clamped_content_size.SetToMax(minimum_content_size); - - // A contentRect with zero width or height is a banned practice in ChromeMac, - // due to unpredictable OSX treatment. - DCHECK(!clamped_content_size.IsEmpty()) - << "Zero-sized windows not supported on Mac"; - - if (!window_visible_ && IsWindowModalSheet()) { - // Window-Modal dialogs (i.e. sheets) are positioned by Cocoa when shown for - // the first time. They also have no frame, so just update the content size. - [window_ setContentSize:NSMakeSize(clamped_content_size.width(), - clamped_content_size.height())]; - return; - } - gfx::Rect actual_new_bounds( - new_bounds.origin(), - GetWindowSizeForClientSize(window_, clamped_content_size)); - - if (PositionWindowInNativeViewParent(bridged_view_)) - actual_new_bounds.Offset(GetNativeViewParentOffset(bridged_view_)); - - [window_ setFrame:gfx::ScreenRectToNSRect(actual_new_bounds) - display:YES - animate:NO]; -} - -void BridgedNativeWidget::DestroyContentView() { - if (!bridged_view_) - return; - drag_drop_client_.reset(); - [bridged_view_ clearView]; - bridged_view_.reset(); - [window_ setContentView:nil]; -} - -void BridgedNativeWidget::CreateContentView(const gfx::Rect& bounds) { - DCHECK(!bridged_view_); - // The compositor needs to point at the new content view created here. - DCHECK(!compositor_superview_); - - bridged_view_.reset( - [[BridgedContentView alloc] initWithBridge:this bounds:bounds]); - - // Objective C initializers can return nil. However, if |view| is non-NULL - // this should be treated as an error and caught early. - CHECK(bridged_view_); - - // Layer backing the content view improves resize performance, reduces memory - // use (no backing store), and clips sublayers to rounded window corners. - [bridged_view_ setWantsLayer:YES]; - - [window_ setContentView:bridged_view_]; -} - -void BridgedNativeWidget::CreateDragDropClient(views::View* view) { - drag_drop_client_.reset(new DragDropClientMac(this, view)); -} - -void BridgedNativeWidget::CloseWindow() { - // Keep |window| on the stack so that the ObjectiveC block below can capture - // it and properly increment the reference count bound to the posted task. - NSWindow* window = ns_window(); - - if (IsWindowModalSheet()) { - // Sheets can't be closed normally. This starts the sheet closing. Once the - // sheet has finished animating, it will call sheetDidEnd: on the parent - // window's delegate. Note it still needs to be asynchronous, since code - // calling Widget::Close() doesn't expect things to be deleted upon return. - // Ensure |window| is retained by a block. Note in some cases during - // teardown, [window sheetParent] may be nil. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(base::RetainBlock(^{ - [NSApp endSheet:window]; - }))); - return; - } - - // For other modal types, animate the close. - if (ShouldRunCustomAnimationFor(Widget::ANIMATE_HIDE)) { - [ViewsNSWindowCloseAnimator closeWindowWithAnimation:window]; - return; - } - - // Destroy the content view so that it won't call back into |host_| while - // being torn down. - DestroyContentView(); - - // Widget::Close() ensures [Non]ClientView::CanClose() returns true, so there - // is no need to call the NSWindow or its delegate's -windowShouldClose: - // implementation in the manner of -[NSWindow performClose:]. But, - // like -performClose:, first remove the window from AppKit's display - // list to avoid crashes like http://crbug.com/156101. - [window orderOut:nil]; - - // Many tests assume that base::RunLoop().RunUntilIdle() is always sufficient - // to execute a close. However, in rare cases, -performSelector:..afterDelay:0 - // does not do this. So post a regular task. - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(base::RetainBlock(^{ - [window close]; - }))); -} - -void BridgedNativeWidget::CloseWindowNow() { - // NSWindows must be retained until -[NSWindow close] returns. - auto window_retain = window_; - - // If there's a bridge at this point, it means there must be a window as well. - DCHECK(window_); - [window_ close]; - // Note: |this| will be deleted here. -} - -void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) { - // Ensure that: - // - A window with an invisible parent is not made visible. - // - A parent changing visibility updates child window visibility. - // * But only when changed via this function - ignore changes via the - // NSWindow API, or changes propagating out from here. - wants_to_be_visible_ = new_state != WindowVisibilityState::kHideWindow; - - [show_animation_ stopAnimation]; - DCHECK(!show_animation_); - - if (new_state == WindowVisibilityState::kHideWindow) { - // Calling -orderOut: on a window with an attached sheet encounters broken - // AppKit behavior. The sheet effectively becomes "lost". - // See http://crbug.com/667602. Alternatives: call -setAlphaValue:0 and - // -setIgnoresMouseEvents:YES on the NSWindow, or dismiss the sheet before - // hiding. - // - // TODO(ellyjones): Sort this entire situation out. This DCHECK doesn't - // trigger in shipped builds, but it does trigger when the browser exits - // "abnormally" (not via one of the UI paths to exiting), such as in browser - // tests, so this breaks a slew of browser tests in MacViews mode. See also - // https://crbug.com/834926. - // DCHECK(![window_ attachedSheet]); - - [window_ orderOut:nil]; - DCHECK(!window_visible_); - return; - } - - DCHECK(wants_to_be_visible_); - - if (!ca_transaction_sync_suppressed_) - ui::CATransactionCoordinator::Get().Synchronize(); - - // If the parent (or an ancestor) is hidden, return and wait for it to become - // visible. - if (parent() && !parent()->IsVisibleParent()) - return; - - if (IsWindowModalSheet()) { - ShowAsModalSheet(); - return; - } - - if (new_state == WindowVisibilityState::kShowAndActivateWindow) { - [window_ makeKeyAndOrderFront:nil]; - [NSApp activateIgnoringOtherApps:YES]; - } else { - // ui::SHOW_STATE_INACTIVE is typically used to avoid stealing focus from a - // parent window. So, if there's a parent, order above that. Otherwise, this - // will order above all windows at the same level. - NSInteger parent_window_number = 0; - if (parent_) { - // When there's a parent, check if the window is already visible. If - // ShowInactive() is called on an already-visible window, there should be - // no effect: the macOS childWindow mechanism should have already raised - // the window to the right stacking order. More importantly, invoking - // -[NSWindow orderWindow:] could cause a Space switch, which defeats the - // point of ShowInactive(), so avoid it. See https://crbug.com/866760. - - // Sanity check: if the window is visible, the prior Show should have - // hooked it up as a native child window already. - DCHECK_EQ(window_visible_, !![window_ parentWindow]); - if (window_visible_) - return; // Avoid a Spaces transition. - - parent_window_number = [parent_->GetNSWindow() windowNumber]; - } - - [window_ orderWindow:NSWindowAbove - relativeTo:parent_window_number]; - } - DCHECK(window_visible_); - - // For non-sheet modal types, use the constrained window animations to make - // the window appear. - if (ShouldRunCustomAnimationFor(Widget::ANIMATE_SHOW)) { - show_animation_.reset( - [[ModalShowAnimationWithLayer alloc] initWithBridgedNativeWidget:this]); - - // The default mode is blocking, which would block the UI thread for the - // duration of the animation, but would keep it smooth. The window also - // hasn't yet received a frame from the compositor at this stage, so it is - // fully transparent until the GPU sends a frame swap IPC. For the blocking - // option, the animation needs to wait until - // AcceleratedWidgetCALayerParamsUpdated has been called at least once, - // otherwise it will animate nothing. - [show_animation_ setAnimationBlockingMode:NSAnimationNonblocking]; - [show_animation_ startAnimation]; - } -} - -void BridgedNativeWidget::AcquireCapture() { - if (HasCapture()) - return; - if (!window_visible_) - return; // Capture on hidden windows is disallowed. - - mouse_capture_.reset(new CocoaMouseCapture(this)); - host_->OnMouseCaptureActiveChanged(true); - - // Initiating global event capture with addGlobalMonitorForEventsMatchingMask: - // will reset the mouse cursor to an arrow. Asking the window for an update - // here will restore what we want. However, it can sometimes cause the cursor - // to flicker, once, on the initial mouseDown. - // TODO(tapted): Make this unnecessary by only asking for global mouse capture - // for the cases that need it (e.g. menus, but not drag and drop). - [window_ cursorUpdate:[NSApp currentEvent]]; -} - -void BridgedNativeWidget::ReleaseCapture() { - mouse_capture_.reset(); -} - -bool BridgedNativeWidget::HasCapture() { - return mouse_capture_ && mouse_capture_->IsActive(); -} - -Widget::MoveLoopResult BridgedNativeWidget::RunMoveLoop( - const gfx::Vector2d& drag_offset) { - DCHECK(!HasCapture()); - DCHECK(!window_move_loop_); - - // RunMoveLoop caller is responsible for updating the window to be under the - // mouse, but it does this using possibly outdated coordinate from the mouse - // event, and mouse is very likely moved beyound that point. - - // Compensate for mouse drift by shifting the initial mouse position we pass - // to CocoaWindowMoveLoop, so as it handles incoming move events the window's - // top left corner will be |drag_offset| from the current mouse position. - - const gfx::Rect frame = gfx::ScreenRectFromNSRect([window_ frame]); - const gfx::Point mouse_in_screen(frame.x() + drag_offset.x(), - frame.y() + drag_offset.y()); - window_move_loop_.reset(new CocoaWindowMoveLoop( - this, gfx::ScreenPointToNSPoint(mouse_in_screen))); - - return window_move_loop_->Run(); - - // |this| may be destroyed during the RunLoop, causing it to exit early. - // Even if that doesn't happen, CocoaWindowMoveLoop will clean itself up by - // calling EndMoveLoop(). So window_move_loop_ will always be null before the - // function returns. But don't DCHECK since |this| might not be valid. -} - -void BridgedNativeWidget::EndMoveLoop() { - DCHECK(window_move_loop_); - window_move_loop_->End(); - window_move_loop_.reset(); -} - -void BridgedNativeWidget::SetNativeWindowProperty(const char* name, - void* value) { - NSString* key = [NSString stringWithUTF8String:name]; - if (value) { - [GetWindowProperties() setObject:[NSValue valueWithPointer:value] - forKey:key]; - } else { - [GetWindowProperties() removeObjectForKey:key]; - } -} - -void* BridgedNativeWidget::GetNativeWindowProperty(const char* name) const { - NSString* key = [NSString stringWithUTF8String:name]; - return [[GetWindowProperties() objectForKey:key] pointerValue]; -} - -void BridgedNativeWidget::SetCursor(NSCursor* cursor) { - [window_delegate_ setCursor:cursor]; -} - -void BridgedNativeWidget::OnWindowWillClose() { - host_->OnWindowWillClose(); - - // Ensure BridgedNativeWidget does not have capture, otherwise - // OnMouseCaptureLost() may reference a deleted |host_| when called via - // ~CocoaMouseCapture() upon the destruction of |mouse_capture_|. See - // https://crbug.com/622201. Also we do this before setting the delegate to - // nil, because this may lead to callbacks to bridge which rely on a valid - // delegate. - ReleaseCapture(); - - if (parent_) { - parent_->RemoveChildWindow(this); - parent_ = nullptr; - } - [[NSNotificationCenter defaultCenter] removeObserver:window_delegate_]; - - [show_animation_ stopAnimation]; // If set, calls OnShowAnimationComplete(). - DCHECK(!show_animation_); - - [window_ setDelegate:nil]; - host_->OnWindowHasClosed(); - // Note: |this| and its host will be deleted here. -} - -void BridgedNativeWidget::OnFullscreenTransitionStart( - bool target_fullscreen_state) { - // Note: This can fail for fullscreen changes started externally, but a user - // shouldn't be able to do that if the window is invisible to begin with. - DCHECK(window_visible_); - - DCHECK_NE(target_fullscreen_state, target_fullscreen_state_); - target_fullscreen_state_ = target_fullscreen_state; - in_fullscreen_transition_ = true; - - host_->OnWindowFullscreenTransitionStart(target_fullscreen_state); -} - -void BridgedNativeWidget::OnFullscreenTransitionComplete( - bool actual_fullscreen_state) { - in_fullscreen_transition_ = false; - - if (target_fullscreen_state_ == actual_fullscreen_state) { - host_->OnWindowFullscreenTransitionComplete(actual_fullscreen_state); - return; - } - - // The transition completed, but into the wrong state. This can happen when - // there are calls to change the fullscreen state whilst mid-transition. - // First update to reflect reality so that OnTargetFullscreenStateChanged() - // expects the change. - target_fullscreen_state_ = actual_fullscreen_state; - ToggleDesiredFullscreenState(true /* async */); -} - -void BridgedNativeWidget::ToggleDesiredFullscreenState(bool async) { - // If there is currently an animation into or out of fullscreen, then AppKit - // emits the string "not in fullscreen state" to stdio and does nothing. For - // this case, schedule a transition back into the desired state when the - // animation completes. - if (in_fullscreen_transition_) { - target_fullscreen_state_ = !target_fullscreen_state_; - return; - } - - // Going fullscreen implicitly makes the window visible. AppKit does this. - // That is, -[NSWindow isVisible] is always true after a call to -[NSWindow - // toggleFullScreen:]. Unfortunately, this change happens after AppKit calls - // -[NSWindowDelegate windowWillEnterFullScreen:], and AppKit doesn't send an - // orderWindow message. So intercepting the implicit change is hard. - // Luckily, to trigger externally, the window typically needs to be visible in - // the first place. So we can just ensure the window is visible here instead - // of relying on AppKit to do it, and not worry that OnVisibilityChanged() - // won't be called for externally triggered fullscreen requests. - if (!window_visible_) - SetVisibilityState(WindowVisibilityState::kShowInactive); - - // Enable fullscreen collection behavior because: - // 1: -[NSWindow toggleFullscreen:] would otherwise be ignored, - // 2: the fullscreen button must be enabled so the user can leave fullscreen. - // This will be reset when a transition out of fullscreen completes. - gfx::SetNSWindowCanFullscreen(window_, true); - - // Until 10.13, AppKit would obey a call to -toggleFullScreen: made inside - // OnFullscreenTransitionComplete(). Starting in 10.13, it behaves as though - // the transition is still in progress and just emits "not in a fullscreen - // state" when trying to exit fullscreen in the same runloop that entered it. - // To handle this case, invoke -toggleFullScreen: asynchronously. - if (async) { - [window_ performSelector:@selector(toggleFullScreen:) - withObject:nil - afterDelay:0]; - } else { - // Suppress synchronous CA transactions during AppKit fullscreen transition - // since there is no need for updates during such transition. - // Re-layout and re-paint will be done after the transtion. See - // https://crbug.com/875707 for potiential problems if we don't suppress. - // |ca_transaction_sync_suppressed_| will be reset to false when the next - // frame comes in. - ca_transaction_sync_suppressed_ = true; - [window_ toggleFullScreen:nil]; - } -} - -void BridgedNativeWidget::OnSizeChanged() { - UpdateWindowGeometry(); -} - -void BridgedNativeWidget::OnPositionChanged() { - UpdateWindowGeometry(); -} - -void BridgedNativeWidget::OnVisibilityChanged() { - const bool window_visible = [window_ isVisible]; - if (window_visible_ == window_visible) - return; - - window_visible_ = window_visible; - - // If arriving via SetVisible(), |wants_to_be_visible_| should already be set. - // If made visible externally (e.g. Cmd+H), just roll with it. Don't try (yet) - // to distinguish being *hidden* externally from being hidden by a parent - // window - we might not need that. - if (window_visible_) { - wants_to_be_visible_ = true; - - // Sheets don't need a parentWindow set, and setting one causes graphical - // glitches (http://crbug.com/605098). - if (parent_ && ![window_ isSheet]) - [parent_->GetNSWindow() addChildWindow:window_ ordered:NSWindowAbove]; - } else { - ReleaseCapture(); // Capture on hidden windows is not permitted. - - // When becoming invisible, remove the entry in any parent's childWindow - // list. Cocoa's childWindow management breaks down when child windows are - // hidden. - if (parent_) - [parent_->GetNSWindow() removeChildWindow:window_]; - } - - // Showing a translucent window after hiding it should trigger shadow - // invalidation. - if (window_visible && ![window_ isOpaque]) - invalidate_shadow_on_frame_swap_ = true; - - NotifyVisibilityChangeDown(); - host_->OnVisibilityChanged(window_visible_); - - // Toolkit-views suppresses redraws while not visible. To prevent Cocoa asking - // for an "empty" draw, disable auto-display while hidden. For example, this - // prevents Cocoa drawing just *after* a minimize, resulting in a blank window - // represented in the deminiaturize animation. - [window_ setAutodisplay:window_visible_]; -} - -void BridgedNativeWidget::OnSystemControlTintChanged() { - ui::NativeTheme::GetInstanceForNativeUi()->NotifyObservers(); -} - -void BridgedNativeWidget::OnBackingPropertiesChanged() { - UpdateWindowDisplay(); -} - -void BridgedNativeWidget::OnWindowKeyStatusChangedTo(bool is_key) { - host_->OnWindowKeyStatusChanged( - is_key, [window_ contentView] == [window_ firstResponder], - [NSApp isFullKeyboardAccessEnabled]); -} - -void BridgedNativeWidget::SetSizeConstraints(const gfx::Size& min_size, - const gfx::Size& max_size, - bool is_resizable, - bool is_maximizable) { - // Don't modify the size constraints or fullscreen collection behavior while - // in fullscreen or during a transition. OnFullscreenTransitionComplete will - // reset these after leaving fullscreen. - if (target_fullscreen_state_ || in_fullscreen_transition_) - return; - - bool shows_resize_controls = - is_resizable && (min_size.IsEmpty() || min_size != max_size); - bool shows_fullscreen_controls = is_resizable && is_maximizable; - - gfx::ApplyNSWindowSizeConstraints(window_, min_size, max_size, - shows_resize_controls, - shows_fullscreen_controls); -} - -void BridgedNativeWidget::OnShowAnimationComplete() { - show_animation_.reset(); -} - -void BridgedNativeWidget::InitCompositorView() { - AddCompositorSuperview(); - - // Use the regular window background for window modal sheets. The layer will - // still paint over most of it, but the native -[NSApp beginSheet:] animation - // blocks the UI thread, so there's no way to invalidate the shadow to match - // the composited layer. This assumes the native window shape is a good match - // for the composited NonClientFrameView, which should be the case since the - // native shape is what's most appropriate for displaying sheets on Mac. - if (is_translucent_window_ && !IsWindowModalSheet()) { - [window_ setOpaque:NO]; - [window_ setBackgroundColor:[NSColor clearColor]]; - - // Don't block waiting for the initial frame of completely transparent - // windows. This allows us to avoid blocking on the UI thread e.g, while - // typing in the omnibox. Note window modal sheets _must_ wait: there is no - // way for a frame to arrive during AppKit's sheet animation. - // https://crbug.com/712268 - ca_transaction_sync_suppressed_ = true; - } else { - DCHECK(!ca_transaction_sync_suppressed_); - } - - // Send the initial window geometry and screen properties. Any future changes - // will be forwarded. - UpdateWindowDisplay(); - UpdateWindowGeometry(); -} - -void BridgedNativeWidget::SetAssociationForView(const views::View* view, - NSView* native_view) { - DCHECK_EQ(0u, associated_views_.count(view)); - associated_views_[view] = native_view; - native_widget_mac_->GetWidget()->ReorderNativeViews(); -} - -void BridgedNativeWidget::ClearAssociationForView(const views::View* view) { - auto it = associated_views_.find(view); - DCHECK(it != associated_views_.end()); - associated_views_.erase(it); -} - -void BridgedNativeWidget::ReorderChildViews() { - // Ignore layer manipulation during a Close(). This can be reached during the - // orderOut: in Close(), which notifies visibility changes to Views. - if (!bridged_view_) - return; - - RankMap rank; - Widget* widget = native_widget_mac_->GetWidget(); - RankNSViews(widget->GetRootView(), associated_views_, &rank); - // Unassociated NSViews should be ordered above associated ones. The exception - // is the UI compositor's superview, which should always be on the very - // bottom, so give it an explicit negative rank. - if (compositor_superview_) - rank[compositor_superview_] = -1; - [bridged_view_ sortSubviewsUsingFunction:&SubviewSorter context:&rank]; -} - -void BridgedNativeWidget::ReparentNativeView(NSView* native_view, - NSView* new_parent) { - DCHECK([new_parent window]); - DCHECK([native_view isDescendantOf:bridged_view_]); - DCHECK(window_ && ![window_ isSheet]); - - BridgedNativeWidget* parent_bridge = - NativeWidgetMac::GetBridgeForNativeWindow([new_parent window]); - if (native_view == bridged_view_.get() && parent_bridge != parent_) { - SetParent(new_parent); - - // TODO(ccameron): This is likely not correct, as the window for |this| - // should only be added as a child window if it is visible. - if (!window_visible_) - NOTIMPLEMENTED(); - [[new_parent window] addChildWindow:window_ ordered:NSWindowAbove]; - } - - if (!native_widget_mac_->GetWidget()->is_top_level() || - native_view != bridged_view_.get()) { - // Make native_view be a child of new_parent by adding it as a subview. - // The window_ must remain visible because it controls the bounds and - // visibility of the ui::Layer. So just hide it by setting alpha value to - // zero. - [new_parent addSubview:native_view]; - if (native_view == bridged_view_.get()) { - [window_ setAlphaValue:0]; - [window_ setIgnoresMouseEvents:YES]; - } - } -} - -void BridgedNativeWidget::SetAnimationEnabled(bool animate) { - [window_ - setAnimationBehavior:(animate ? NSWindowAnimationBehaviorDocumentWindow - : NSWindowAnimationBehaviorNone)]; -} - -bool BridgedNativeWidget::ShouldRunCustomAnimationFor( - Widget::VisibilityTransition transition) const { - // The logic around this needs to change if new transition types are set. - // E.g. it would be nice to distinguish "hide" from "close". Mac currently - // treats "hide" only as "close". Hide (e.g. Cmd+h) should not animate on Mac. - constexpr int kSupported = - Widget::ANIMATE_SHOW | Widget::ANIMATE_HIDE | Widget::ANIMATE_NONE; - DCHECK_EQ(0, transitions_to_animate_ & ~kSupported); - if (!(transitions_to_animate_ & transition)) - return false; - - // Custom animations are only used for tab-modals. - bool widget_is_modal = false; - host_->GetWidgetIsModal(&widget_is_modal); - if (!widget_is_modal) - return false; - - // Note this also checks the native animation property. Clearing that will - // also disable custom animations to ensure that the views::Widget API - // behaves consistently. - if ([window_ animationBehavior] == NSWindowAnimationBehaviorNone) - return false; - - if (base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kDisableModalAnimations)) { - return false; - } - - return true; -} - -NSWindow* BridgedNativeWidget::ns_window() { - return window_.get(); -} - -//////////////////////////////////////////////////////////////////////////////// -// BridgedNativeWidget, ui::CATransactionObserver - -void BridgedNativeWidget::OnDisplayMetricsChanged( - const display::Display& display, - uint32_t metrics) { - UpdateWindowDisplay(); -} - -//////////////////////////////////////////////////////////////////////////////// -// BridgedNativeWidget, ui::CATransactionObserver - -bool BridgedNativeWidget::ShouldWaitInPreCommit() { - if (!window_visible_) - return false; - if (ca_transaction_sync_suppressed_) - return false; - if (!compositor_superview_) - return false; - return content_dip_size_ != compositor_frame_dip_size_; -} - -base::TimeDelta BridgedNativeWidget::PreCommitTimeout() { - return kUIPaintTimeout; -} - -//////////////////////////////////////////////////////////////////////////////// -// BridgedNativeWidget, CocoaMouseCaptureDelegate: - -void BridgedNativeWidget::PostCapturedEvent(NSEvent* event) { - [bridged_view_ processCapturedMouseEvent:event]; -} - -void BridgedNativeWidget::OnMouseCaptureLost() { - host_->OnMouseCaptureActiveChanged(false); -} - -NSWindow* BridgedNativeWidget::GetWindow() const { - return window_; -} - -//////////////////////////////////////////////////////////////////////////////// -// TODO(ccameron): Update class names to: -// BridgedNativeWidgetImpl, BridgedNativeWidget: - -void BridgedNativeWidget::SetVisibleOnAllSpaces(bool always_visible) { - gfx::SetNSWindowVisibleOnAllWorkspaces(window_, always_visible); -} - -void BridgedNativeWidget::SetFullscreen(bool fullscreen) { - if (fullscreen == target_fullscreen_state_) - return; - ToggleDesiredFullscreenState(); -} - -void BridgedNativeWidget::SetMiniaturized(bool miniaturized) { - if (miniaturized) { - // Calling performMiniaturize: will momentarily highlight the button, but - // AppKit will reject it if there is no miniaturize button. - if ([window_ styleMask] & NSMiniaturizableWindowMask) - [window_ performMiniaturize:nil]; - else - [window_ miniaturize:nil]; - } else { - [window_ deminiaturize:nil]; - } -} - -void BridgedNativeWidget::SetOpacity(float opacity) { - [window_ setAlphaValue:opacity]; -} - -void BridgedNativeWidget::SetContentAspectRatio( - const gfx::SizeF& aspect_ratio) { - [window_ setContentAspectRatio:NSMakeSize(aspect_ratio.width(), - aspect_ratio.height())]; -} - -void BridgedNativeWidget::SetCALayerParams( - const gfx::CALayerParams& ca_layer_params) { - // Ignore frames arriving "late" for an old size. A frame at the new size - // should arrive soon. - gfx::Size frame_dip_size = gfx::ConvertSizeToDIP(ca_layer_params.scale_factor, - ca_layer_params.pixel_size); - if (content_dip_size_ != frame_dip_size) - return; - compositor_frame_dip_size_ = frame_dip_size; - - // Update the DisplayCALayerTree with the most recent CALayerParams, to make - // the content display on-screen. - display_ca_layer_tree_->UpdateCALayerTree(ca_layer_params); - - if (ca_transaction_sync_suppressed_) - ca_transaction_sync_suppressed_ = false; - - if (invalidate_shadow_on_frame_swap_) { - invalidate_shadow_on_frame_swap_ = false; - [window_ invalidateShadow]; - } -} - -void BridgedNativeWidget::MakeFirstResponder() { - [window_ makeFirstResponder:bridged_view_]; -} - -void BridgedNativeWidget::SetWindowTitle(const base::string16& title) { - NSString* new_title = base::SysUTF16ToNSString(title); - [window_ setTitle:new_title]; -} - -void BridgedNativeWidget::ClearTouchBar() { - if (@available(macOS 10.12.2, *)) { - if ([bridged_view_ respondsToSelector:@selector(setTouchBar:)]) - [bridged_view_ setTouchBar:nil]; - } -} - -void BridgedNativeWidget::SetTextInputClient( - ui::TextInputClient* text_input_client) { - [bridged_view_ setTextInputClient:text_input_client]; -} - -//////////////////////////////////////////////////////////////////////////////// -// BridgedNativeWidget, BridgedNativeWidgetOwner: - -NSWindow* BridgedNativeWidget::GetNSWindow() { - return window_; -} - -gfx::Vector2d BridgedNativeWidget::GetChildWindowOffset() const { - return gfx::ScreenRectFromNSRect([window_ frame]).OffsetFromOrigin(); -} - -bool BridgedNativeWidget::IsVisibleParent() const { - return parent_ ? window_visible_ && parent_->IsVisibleParent() - : window_visible_; -} - -void BridgedNativeWidget::RemoveChildWindow(BridgedNativeWidget* child) { - auto location = std::find( - child_windows_.begin(), child_windows_.end(), child); - DCHECK(location != child_windows_.end()); - child_windows_.erase(location); - - // Note the child is sometimes removed already by AppKit. This depends on OS - // version, and possibly some unpredictable reference counting. Removing it - // here should be safe regardless. - [window_ removeChildWindow:child->window_]; -} - -//////////////////////////////////////////////////////////////////////////////// -// BridgedNativeWidget, private: - -void BridgedNativeWidget::RemoveOrDestroyChildren() { - // TODO(tapted): Implement unowned child windows if required. - while (!child_windows_.empty()) { - // The NSWindow can only be destroyed after -[NSWindow close] is complete. - // Retain the window, otherwise the reference count can reach zero when the - // child calls back into RemoveChildWindow() via its OnWindowWillClose(). - base::scoped_nsobject<NSWindow> child( - [child_windows_.back()->ns_window() retain]); - [child close]; - } -} - -void BridgedNativeWidget::NotifyVisibilityChangeDown() { - // Child windows sometimes like to close themselves in response to visibility - // changes. That's supported, but only with the asynchronous Widget::Close(). - // Perform a heuristic to detect child removal that would break these loops. - const size_t child_count = child_windows_.size(); - if (!window_visible_) { - for (BridgedNativeWidget* child : child_windows_) { - if (child->window_visible_) - [child->ns_window() orderOut:nil]; - - DCHECK(!child->window_visible_); - CHECK_EQ(child_count, child_windows_.size()); - } - // The orderOut calls above should result in a call to OnVisibilityChanged() - // in each child. There, children will remove themselves from the NSWindow - // childWindow list as well as propagate NotifyVisibilityChangeDown() calls - // to any children of their own. However this is only true for windows - // managed by the BridgedNativeWidget i.e. windows which have - // ViewsNSWindowDelegate as the delegate. - DCHECK_EQ(0u, CountBridgedWindows([window_ childWindows])); - return; - } - - NSUInteger visible_bridged_children = 0; // For a DCHECK below. - NSInteger parent_window_number = [window_ windowNumber]; - for (BridgedNativeWidget* child: child_windows_) { - // Note: order the child windows on top, regardless of whether or not they - // are currently visible. They probably aren't, since the parent was hidden - // prior to this, but they could have been made visible in other ways. - if (child->wants_to_be_visible_) { - ++visible_bridged_children; - // Here -[NSWindow orderWindow:relativeTo:] is used to put the window on - // screen. However, that by itself is insufficient to guarantee a correct - // z-order relationship. If this function is being called from a z-order - // change in the parent, orderWindow turns out to be unreliable (i.e. the - // ordering doesn't always take effect). What this actually relies on is - // the resulting call to OnVisibilityChanged() in the child, which will - // then insert itself into -[NSWindow childWindows] to let Cocoa do its - // internal layering magic. - [child->ns_window() orderWindow:NSWindowAbove - relativeTo:parent_window_number]; - DCHECK(child->window_visible_); - } - CHECK_EQ(child_count, child_windows_.size()); - } - DCHECK_EQ(visible_bridged_children, - CountBridgedWindows([window_ childWindows])); -} - -void BridgedNativeWidget::AddCompositorSuperview() { - DCHECK(!compositor_superview_); - compositor_superview_.reset( - [[ViewsCompositorSuperview alloc] initWithFrame:[bridged_view_ bounds]]); - - // Size and resize automatically with |bridged_view_|. - [compositor_superview_ - setAutoresizingMask:NSViewWidthSizable | NSViewHeightSizable]; - - // Enable HiDPI backing when supported (only on 10.7+). - if ([compositor_superview_ respondsToSelector: - @selector(setWantsBestResolutionOpenGLSurface:)]) { - [compositor_superview_ setWantsBestResolutionOpenGLSurface:YES]; - } - - // Set the layer first to create a layer-hosting view (not layer-backed), and - // set the compositor output to go to that layer. - base::scoped_nsobject<CALayer> background_layer([[CALayer alloc] init]); - display_ca_layer_tree_ = - std::make_unique<ui::DisplayCALayerTree>(background_layer.get()); - [compositor_superview_ setLayer:background_layer]; - [compositor_superview_ setWantsLayer:YES]; - - // The UI compositor should always be the first subview, to ensure webviews - // are drawn on top of it. - DCHECK_EQ(0u, [[bridged_view_ subviews] count]); - [bridged_view_ addSubview:compositor_superview_]; -} - -void BridgedNativeWidget::UpdateWindowGeometry() { - gfx::Rect window_in_screen = gfx::ScreenRectFromNSRect([window_ frame]); - gfx::Rect content_in_screen = gfx::ScreenRectFromNSRect( - [window_ contentRectForFrameRect:[window_ frame]]); - bool content_resized = content_dip_size_ != content_in_screen.size(); - content_dip_size_ = content_in_screen.size(); - - host_->OnWindowGeometryChanged(window_in_screen, content_in_screen); - - if (content_resized && !ca_transaction_sync_suppressed_) - ui::CATransactionCoordinator::Get().Synchronize(); - - // For a translucent window, the shadow calculation needs to be carried out - // after the frame from the compositor arrives. - if (content_resized && ![window_ isOpaque]) - invalidate_shadow_on_frame_swap_ = true; -} - -void BridgedNativeWidget::UpdateWindowDisplay() { - host_->OnWindowDisplayChanged( - display::Screen::GetScreen()->GetDisplayNearestWindow(window_)); -} - -bool BridgedNativeWidget::IsWindowModalSheet() const { - return parent_ && modal_type_ == ui::MODAL_TYPE_WINDOW; -} - -void BridgedNativeWidget::ShowAsModalSheet() { - // -[NSApp beginSheet:] will block the UI thread while the animation runs. - // So that it doesn't animate a fully transparent window, first wait for a - // frame. The first step is to pretend that the window is already visible. - window_visible_ = true; - host_->OnVisibilityChanged(window_visible_); - - NSWindow* parent_window = parent_->GetNSWindow(); - DCHECK(parent_window); - - // -beginSheet: does not retain |modalDelegate| (and we would not want it to). - // Since |this| may destroy [window_ delegate], use |window_| itself as the - // delegate, which will forward to ViewsNSWindowDelegate if |this| is still - // alive (i.e. it has not set the window delegate to nil). - [NSApp beginSheet:window_ - modalForWindow:parent_window - modalDelegate:window_ - didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) - contextInfo:nullptr]; -} - -NSMutableDictionary* BridgedNativeWidget::GetWindowProperties() const { - NSMutableDictionary* properties = objc_getAssociatedObject( - window_, &kWindowPropertiesKey); - if (!properties) { - properties = [NSMutableDictionary dictionary]; - objc_setAssociatedObject(window_, &kWindowPropertiesKey, - properties, OBJC_ASSOCIATION_RETAIN); - } - return properties; -} - -} // namespace views diff --git a/chromium/ui/views/cocoa/bridged_native_widget_host.h b/chromium/ui/views/cocoa/bridged_native_widget_host.h deleted file mode 100644 index 9fa2c68091c..00000000000 --- a/chromium/ui/views/cocoa/bridged_native_widget_host.h +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2018 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 UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_HOST_H_ -#define UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_HOST_H_ - -#include "ui/base/ui_base_types.h" -#include "ui/events/event_utils.h" -#include "ui/gfx/decorated_text.h" -#include "ui/gfx/geometry/point.h" -#include "ui/gfx/geometry/size.h" -#include "ui/views/views_export.h" - -@class NSView; - -namespace views { - -// The interface through which the app shim (BridgedNativeWidgetImpl) -// communicates with the browser process (BridgedNativeWidgetHostImpl). -class VIEWS_EXPORT BridgedNativeWidgetHost { - public: - virtual ~BridgedNativeWidgetHost() = default; - - // Retrieve the NSView for accessibility for this widget. - // TODO(ccameron): This interface cannot be implemented over IPC. A scheme - // for implementing accessibility across processes needs to be designed and - // implemented. - virtual NSView* GetNativeViewAccessible() = 0; - - // Update the views::Widget, ui::Compositor and ui::Layer's visibility. - virtual void OnVisibilityChanged(bool visible) = 0; - - // Resize the underlying views::View to |new_size|. Note that this will not - // necessarily match the content bounds from OnWindowGeometryChanged. - virtual void SetViewSize(const gfx::Size& new_size) = 0; - - // Indicate if full keyboard accessibility is needed and updates focus if - // needed. - virtual void SetKeyboardAccessible(bool enabled) = 0; - - // Indicate if the NSView is the first responder. - virtual void SetIsFirstResponder(bool is_first_responder) = 0; - - // Indicate if mouse capture is active. - virtual void OnMouseCaptureActiveChanged(bool capture_is_active) = 0; - - // Handle events. Note that whether or not the event is actually handled is - // not returned. - virtual void OnScrollEvent(const ui::ScrollEvent& const_event) = 0; - virtual void OnMouseEvent(const ui::MouseEvent& const_event) = 0; - virtual void OnGestureEvent(const ui::GestureEvent& const_event) = 0; - - // Synchronously dispatch a key event and return in |event_handled| whether - // or not the event was handled. - virtual void DispatchKeyEvent(const ui::KeyEvent& const_event, - bool* event_handled) = 0; - - // Synchronously dispatch a key event to the current menu controller (if any) - // exists. Return in |event_swallowed| whether or not the event was swallowed - // (that is, if the menu's dispatch returned POST_DISPATCH_NONE). Return in - // in |event_handled| whether or not the event was handled (that is, if the - // event in the caller's frame should be marked as handled). - virtual void DispatchKeyEventToMenuController(const ui::KeyEvent& const_event, - bool* event_swallowed, - bool* event_handled) = 0; - - // Synchronously return in |has_menu_controller| whether or not a menu - // controller exists for this widget. - virtual void GetHasMenuController(bool* has_menu_controller) = 0; - - // Synchronously query if |location_in_content| is a draggable background. - virtual void GetIsDraggableBackgroundAt(const gfx::Point& location_in_content, - bool* is_draggable_background) = 0; - - // Synchronously query the tooltip text for |location_in_content|. - virtual void GetTooltipTextAt(const gfx::Point& location_in_content, - base::string16* new_tooltip_text) = 0; - - // Synchronously query the quicklook text at |location_in_content|. Return in - // |found_word| whether or not a word was found. - virtual void GetWordAt(const gfx::Point& location_in_content, - bool* found_word, - gfx::DecoratedText* decorated_word, - gfx::Point* baseline_point) = 0; - - // Synchronously query the value of IsModal for this widget and store it in - // |*widget_is_modal|. - virtual void GetWidgetIsModal(bool* widget_is_modal) = 0; - - // Synchronously return in |is_textual| whether or not the focused view - // contains text that can be selected and copied. - virtual void GetIsFocusedViewTextual(bool* is_textual) = 0; - - // Called whenever the NSWindow's size or position changes. - virtual void OnWindowGeometryChanged( - const gfx::Rect& window_bounds_in_screen_dips, - const gfx::Rect& content_bounds_in_screen_dips) = 0; - - // Called when the window begins transitioning to or from being fullscreen. - virtual void OnWindowFullscreenTransitionStart( - bool target_fullscreen_state) = 0; - - // Called when the window has completed its transition to or from being - // fullscreen. Note that if there are multiple consecutive transitions - // (because a new transition was initiated before the previous one completed) - // then this will only be called when all transitions have competed. - virtual void OnWindowFullscreenTransitionComplete(bool is_fullscreen) = 0; - - // Called when the window is miniaturized or deminiaturized. - virtual void OnWindowMiniaturizedChanged(bool miniaturized) = 0; - - // Called when the current display or the properties of the current display - // change. - virtual void OnWindowDisplayChanged(const display::Display& display) = 0; - - // Called before the NSWindow is closed and destroyed. - virtual void OnWindowWillClose() = 0; - - // Called after the NSWindow has been closed and destroyed. - virtual void OnWindowHasClosed() = 0; - - // Called when the NSWindow becomes key or resigns from being key. Additional - // state required for the transition include whether or not the content NSView - // is the first responder for the NSWindow in |is_content_first_responder| and - // whether or not the NSApp's full keyboard access is enabled in - // |full_keyboard_access_enabled|. - virtual void OnWindowKeyStatusChanged(bool is_key, - bool is_content_first_responder, - bool full_keyboard_access_enabled) = 0; - - // Accept or cancel the current dialog window (depending on the value of - // |button|), if a current dialog exists. - virtual void DoDialogButtonAction(ui::DialogButton button) = 0; - - // Synchronously determine if the specified button exists in the current - // dialog (if any), along with its label, whether or not it is enabled, and - // whether or not it is the default button.. - virtual void GetDialogButtonInfo(ui::DialogButton button, - bool* button_exists, - base::string16* title, - bool* is_button_enabled, - bool* is_button_default) = 0; - - // Synchronously return in |buttons_exist| whether or not any buttons exist - // for the current dialog. - virtual void GetDoDialogButtonsExist(bool* buttons_exist) = 0; -}; - -} // namespace views - -#endif // UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_HOST_H_ diff --git a/chromium/ui/views/cocoa/bridged_native_widget_host_impl.h b/chromium/ui/views/cocoa/bridged_native_widget_host_impl.h index 561f3553ca6..5451da8d177 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget_host_impl.h +++ b/chromium/ui/views/cocoa/bridged_native_widget_host_impl.h @@ -6,23 +6,27 @@ #define UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_HOST_IMPL_H_ #include <memory> +#include <vector> +#include "base/mac/scoped_nsobject.h" #include "base/macros.h" +#include "mojo/public/cpp/bindings/associated_binding.h" #include "ui/accelerated_widget_mac/accelerated_widget_mac.h" #include "ui/accelerated_widget_mac/display_link_mac.h" #include "ui/base/ime/input_method_delegate.h" #include "ui/compositor/layer_owner.h" -#include "ui/views/cocoa/bridged_native_widget_host.h" +#include "ui/views/cocoa/bridge_factory_host.h" +#include "ui/views/cocoa/drag_drop_client_mac.h" #include "ui/views/focus/focus_manager.h" #include "ui/views/views_export.h" #include "ui/views/widget/widget.h" #include "ui/views/window/dialog_observer.h" +#include "ui/views_bridge_mac/bridged_native_widget_host_helper.h" +#include "ui/views_bridge_mac/mojo/bridged_native_widget.mojom.h" +#include "ui/views_bridge_mac/mojo/bridged_native_widget_host.mojom.h" -namespace views_bridge_mac { -namespace mojom { -class BridgedNativeWidget; -} // namespace mojom -} // namespace views_bridge_mac +@class NativeWidgetMacNSWindow; +@class NSView; namespace ui { class RecyclableCompositorMac; @@ -30,14 +34,16 @@ class RecyclableCompositorMac; namespace views { -class BridgedNativeWidget; +class BridgedNativeWidgetImpl; class NativeWidgetMac; // The portion of NativeWidgetMac that lives in the browser process. This -// communicates to the BridgedNativeWidget, which interacts with the Cocoa +// communicates to the BridgedNativeWidgetImpl, which interacts with the Cocoa // APIs, and which may live in an app shim process. class VIEWS_EXPORT BridgedNativeWidgetHostImpl - : public BridgedNativeWidgetHost, + : public views_bridge_mac::BridgedNativeWidgetHostHelper, + public BridgeFactoryHost::Observer, + public views_bridge_mac::mojom::BridgedNativeWidgetHost, public DialogObserver, public FocusChangeListener, public ui::internal::InputMethodDelegate, @@ -45,15 +51,64 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl public ui::LayerOwner, public ui::AcceleratedWidgetMacNSView { public: - // Creates one side of the bridge. |parent| must not be NULL. - explicit BridgedNativeWidgetHostImpl(NativeWidgetMac* parent); + // Retrieves the bridge host associated with the given NSWindow. Returns null + // if the supplied handle has no associated Widget. + static BridgedNativeWidgetHostImpl* GetFromNativeWindow( + gfx::NativeWindow window); + + // Unique integer id handles are used to bridge between the + // BridgedNativeWidgetHostImpl in one process and the BridgedNativeWidgetHost + // potentially in another. + static BridgedNativeWidgetHostImpl* GetFromId( + uint64_t bridged_native_widget_id); + uint64_t bridged_native_widget_id() const { return widget_id_; } + + // Creates one side of the bridge. |owner| must not be NULL. + explicit BridgedNativeWidgetHostImpl(NativeWidgetMac* owner); ~BridgedNativeWidgetHostImpl() override; - // Provide direct access to the BridgedNativeWidget that this is hosting. + // The NativeWidgetMac that owns |this|. + views::NativeWidgetMac* native_widget_mac() const { + return native_widget_mac_; + } + BridgedNativeWidgetHostImpl* parent() const { return parent_; } + std::vector<BridgedNativeWidgetHostImpl*> children() const { + return children_; + } + + // The bridge factory that was used to create the true NSWindow for this + // widget. This is nullptr for in-process windows. + BridgeFactoryHost* bridge_factory_host() const { + return bridge_factory_host_; + } + + // A NSWindow that is guaranteed to exist in this process. If the bridge + // object for this host is in this process, then this points to the bridge's + // NSWindow. Otherwise, it mirrors the id and bounds of the child window. + NativeWidgetMacNSWindow* GetLocalNSWindow() const; + + // The mojo interface through which to communicate with the underlying + // NSWindow and NSView. + views_bridge_mac::mojom::BridgedNativeWidget* bridge() const; + + // Direct access to the BridgedNativeWidgetImpl that this is hosting. // TODO(ccameron): Remove all accesses to this member, and replace them // with methods that may be sent across processes. - BridgedNativeWidget* bridge_impl() const { return bridge_impl_.get(); } - views_bridge_mac::mojom::BridgedNativeWidget* bridge() const; + BridgedNativeWidgetImpl* bridge_impl() const { return bridge_impl_.get(); } + + TooltipManager* tooltip_manager() { return tooltip_manager_.get(); } + + DragDropClientMac* drag_drop_client() const { + return drag_drop_client_.get(); + } + + // Create and set the bridge object to be in this process. + void CreateLocalBridge(base::scoped_nsobject<NativeWidgetMacNSWindow> window); + + // Create and set the bridge object to be potentially in another process. + void CreateRemoteBridge( + BridgeFactoryHost* bridge_factory_host, + views_bridge_mac::mojom::CreateWindowParamsPtr window_create_params); void InitWindow(const Widget::InitParams& params); @@ -75,6 +130,9 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl // Set the root view (set during initialization and un-set during teardown). void SetRootView(views::View* root_view); + // Return the id through which the NSView for |root_view_| may be looked up. + uint64_t GetRootViewNSViewId() const { return root_view_id_; } + // Initialize the ui::Compositor and ui::Layer. void CreateCompositor(const Widget::InitParams& params); @@ -93,14 +151,12 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl // Geometry of the window, in DIPs. const gfx::Rect& GetWindowBoundsInScreen() const { - DCHECK(has_received_window_geometry_); return window_bounds_in_screen_; } // Geometry of the content area of the window, in DIPs. Note that this is not // necessarily the same as the views::View's size. const gfx::Rect& GetContentBoundsInScreen() const { - DCHECK(has_received_window_geometry_); return content_bounds_in_screen_; } @@ -111,42 +167,70 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl // fullscreen or transitioning between fullscreen states. gfx::Rect GetRestoredBounds() const; + // Set |parent_| and update the old and new parents' |children_|. It is valid + // to set |new_parent| to nullptr. Propagate this to the BridgedNativeWidget. + void SetParent(BridgedNativeWidgetHostImpl* new_parent); + + // Properties set and queried by views. Not actually native. + void SetNativeWindowProperty(const char* name, void* value); + void* GetNativeWindowProperty(const char* name) const; + + // Updates |associated_views_| on NativeViewHost::Attach()/Detach(). + void SetAssociationForView(const views::View* view, NSView* native_view); + void ClearAssociationForView(const views::View* view); + + // Sorts child NSViews according to NativeViewHosts order in views hierarchy. + void ReorderChildViews(); + bool IsVisible() const { return is_visible_; } bool IsMiniaturized() const { return is_miniaturized_; } bool IsWindowKey() const { return is_window_key_; } bool IsMouseCaptureActive() const { return is_mouse_capture_active_; } + // Used by NativeWidgetPrivate::GetGlobalCapture. + static NSView* GetGlobalCaptureView(); + private: - gfx::Vector2d GetBoundsOffsetForParent() const; void UpdateCompositorProperties(); void DestroyCompositor(); + void RankNSViewsRecursive(View* view, std::map<NSView*, int>* rank) const; - // views::BridgedNativeWidgetHost: + // BridgedNativeWidgetHostHelper: NSView* GetNativeViewAccessible() override; + void DispatchKeyEvent(ui::KeyEvent* event) override; + bool DispatchKeyEventToMenuController(ui::KeyEvent* event) override; + void GetWordAt(const gfx::Point& location_in_content, + bool* found_word, + gfx::DecoratedText* decorated_word, + gfx::Point* baseline_point) override; + double SheetPositionY() override; + views_bridge_mac::DragDropClient* GetDragDropClient() override; + + // BridgeFactoryHost::Observer: + void OnBridgeFactoryHostDestroying(BridgeFactoryHost* host) override; + + // views_bridge_mac::mojom::BridgedNativeWidgetHost: void OnVisibilityChanged(bool visible) override; + void OnWindowNativeThemeChanged() override; void SetViewSize(const gfx::Size& new_size) override; void SetKeyboardAccessible(bool enabled) override; void SetIsFirstResponder(bool is_first_responder) override; void OnMouseCaptureActiveChanged(bool capture_is_active) override; - void OnScrollEvent(const ui::ScrollEvent& const_event) override; - void OnMouseEvent(const ui::MouseEvent& const_event) override; - void OnGestureEvent(const ui::GestureEvent& const_event) override; - void DispatchKeyEvent(const ui::KeyEvent& const_event, - bool* event_handled) override; - void DispatchKeyEventToMenuController(const ui::KeyEvent& const_event, - bool* event_swallowed, - bool* event_handled) override; - void GetHasMenuController(bool* has_menu_controller) override; - void GetIsDraggableBackgroundAt(const gfx::Point& location_in_content, + void OnScrollEvent(std::unique_ptr<ui::Event> event) override; + void OnMouseEvent(std::unique_ptr<ui::Event> event) override; + void OnGestureEvent(std::unique_ptr<ui::Event> event) override; + bool DispatchKeyEventRemote(std::unique_ptr<ui::Event> event, + bool* event_handled) override; + bool DispatchKeyEventToMenuControllerRemote(std::unique_ptr<ui::Event> event, + bool* event_swallowed, + bool* event_handled) override; + bool GetHasMenuController(bool* has_menu_controller) override; + bool GetIsDraggableBackgroundAt(const gfx::Point& location_in_content, bool* is_draggable_background) override; - void GetTooltipTextAt(const gfx::Point& location_in_content, + bool GetTooltipTextAt(const gfx::Point& location_in_content, base::string16* new_tooltip_text) override; - void GetWordAt(const gfx::Point& location_in_content, - bool* found_word, - gfx::DecoratedText* decorated_word, - gfx::Point* baseline_point) override; - void GetWidgetIsModal(bool* widget_is_modal) override; - void GetIsFocusedViewTextual(bool* is_textual) override; + bool GetWidgetIsModal(bool* widget_is_modal) override; + bool GetIsFocusedViewTextual(bool* is_textual) override; void OnWindowGeometryChanged( const gfx::Rect& window_bounds_in_screen_dips, const gfx::Rect& content_bounds_in_screen_dips) override; @@ -161,12 +245,42 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl bool is_content_first_responder, bool full_keyboard_access_enabled) override; void DoDialogButtonAction(ui::DialogButton button) override; - void GetDialogButtonInfo(ui::DialogButton type, + bool GetDialogButtonInfo(ui::DialogButton type, bool* button_exists, base::string16* button_label, bool* is_button_enabled, bool* is_button_default) override; - void GetDoDialogButtonsExist(bool* buttons_exist) override; + bool GetDoDialogButtonsExist(bool* buttons_exist) override; + bool GetShouldShowWindowTitle(bool* should_show_window_title) override; + bool GetCanWindowBecomeKey(bool* can_window_become_key) override; + bool GetAlwaysRenderWindowAsKey(bool* always_render_as_key) override; + bool GetCanWindowClose(bool* can_window_close) override; + + // views_bridge_mac::mojom::BridgedNativeWidgetHost, synchronous callbacks: + void DispatchKeyEventRemote(std::unique_ptr<ui::Event> event, + DispatchKeyEventRemoteCallback callback) override; + void DispatchKeyEventToMenuControllerRemote( + std::unique_ptr<ui::Event> event, + DispatchKeyEventToMenuControllerRemoteCallback callback) override; + void GetHasMenuController(GetHasMenuControllerCallback callback) override; + void GetIsDraggableBackgroundAt( + const gfx::Point& location_in_content, + GetIsDraggableBackgroundAtCallback callback) override; + void GetTooltipTextAt(const gfx::Point& location_in_content, + GetTooltipTextAtCallback callback) override; + void GetWidgetIsModal(GetWidgetIsModalCallback callback) override; + void GetIsFocusedViewTextual( + GetIsFocusedViewTextualCallback callback) override; + void GetDialogButtonInfo(ui::DialogButton button, + GetDialogButtonInfoCallback callback) override; + void GetDoDialogButtonsExist( + GetDoDialogButtonsExistCallback callback) override; + void GetShouldShowWindowTitle( + GetShouldShowWindowTitleCallback callback) override; + void GetCanWindowBecomeKey(GetCanWindowBecomeKeyCallback callback) override; + void GetAlwaysRenderWindowAsKey( + GetAlwaysRenderWindowAsKeyCallback callback) override; + void GetCanWindowClose(GetCanWindowCloseCallback callback) override; // DialogObserver: void OnDialogModelChanged() override; @@ -176,7 +290,9 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl void OnDidChangeFocus(View* focused_before, View* focused_now) override; // ui::internal::InputMethodDelegate: - ui::EventDispatchDetails DispatchKeyEventPostIME(ui::KeyEvent* key) override; + ui::EventDispatchDetails DispatchKeyEventPostIME( + ui::KeyEvent* key, + base::OnceCallback<void(bool)> ack_callback) override; // ui::LayerDelegate: void OnPaintLayer(const ui::PaintContext& context) override; @@ -186,17 +302,38 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl // ui::AcceleratedWidgetMacNSView: void AcceleratedWidgetCALayerParamsUpdated() override; + // The id that this bridge may be looked up from. + const uint64_t widget_id_; views::NativeWidgetMac* const native_widget_mac_; // Weak. Owns |this_|. + // Parent and child widgets. + BridgedNativeWidgetHostImpl* parent_ = nullptr; + std::vector<BridgedNativeWidgetHostImpl*> children_; + + // The factory that was used to create |bridge_ptr_|. This must be the same + // as |parent_->bridge_factory_host_|. + BridgeFactoryHost* bridge_factory_host_ = nullptr; + Widget::InitParams::Type widget_type_ = Widget::InitParams::TYPE_WINDOW; + // The id that may be used to look up the NSView for |root_view_|. + const uint64_t root_view_id_; views::View* root_view_ = nullptr; // Weak. Owned by |native_widget_mac_|. + std::unique_ptr<DragDropClientMac> drag_drop_client_; - // TODO(ccameron): Rather than instantiate a BridgedNativeWidget here, - // we will instantiate a mojo BridgedNativeWidget interface to a Cocoa + // The mojo pointer to a BridgedNativeWidget, which may exist in another + // process. + views_bridge_mac::mojom::BridgedNativeWidgetAssociatedPtr bridge_ptr_; + + // TODO(ccameron): Rather than instantiate a BridgedNativeWidgetImpl here, + // we will instantiate a mojo BridgedNativeWidgetImpl interface to a Cocoa // instance that may be in another process. - std::unique_ptr<BridgedNativeWidget> bridge_impl_; + std::unique_ptr<BridgedNativeWidgetImpl> bridge_impl_; + + // Window that is guaranteed to exist in this process (see GetLocalNSWindow). + base::scoped_nsobject<NativeWidgetMacNSWindow> local_window_; + std::unique_ptr<TooltipManager> tooltip_manager_; std::unique_ptr<ui::InputMethod> input_method_; FocusManager* focus_manager_ = nullptr; // Weak. Owned by our Widget. @@ -209,7 +346,6 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl scoped_refptr<ui::DisplayLinkMac> display_link_; // The geometry of the window and its contents view, in screen coordinates. - bool has_received_window_geometry_ = false; gfx::Rect window_bounds_in_screen_; gfx::Rect content_bounds_in_screen_; bool is_visible_ = false; @@ -222,6 +358,14 @@ class VIEWS_EXPORT BridgedNativeWidgetHostImpl std::unique_ptr<ui::RecyclableCompositorMac> compositor_; + // Properties used by Set/GetNativeWindowProperty. + std::map<std::string, void*> native_window_properties_; + + // Contains NativeViewHost->gfx::NativeView associations. + std::map<const views::View*, NSView*> associated_views_; + + mojo::AssociatedBinding<views_bridge_mac::mojom::BridgedNativeWidgetHost> + host_mojo_binding_; DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetHostImpl); }; diff --git a/chromium/ui/views/cocoa/bridged_native_widget_host_impl.mm b/chromium/ui/views/cocoa/bridged_native_widget_host_impl.mm index 53e6a582b6c..63f9be6db98 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget_host_impl.mm +++ b/chromium/ui/views/cocoa/bridged_native_widget_host_impl.mm @@ -4,6 +4,8 @@ #include "ui/views/cocoa/bridged_native_widget_host_impl.h" +#include "base/mac/foundation_util.h" +#include "ui/accelerated_widget_mac/window_resize_helper_mac.h" #include "ui/base/hit_test.h" #include "ui/base/ime/input_method.h" #include "ui/base/ime/input_method_factory.h" @@ -11,7 +13,9 @@ #include "ui/compositor/recyclable_compositor_mac.h" #include "ui/display/screen.h" #include "ui/gfx/geometry/dip_util.h" -#include "ui/views/cocoa/bridged_native_widget.h" +#include "ui/gfx/mac/coordinate_conversion.h" +#include "ui/native_theme/native_theme_mac.h" +#include "ui/views/cocoa/tooltip_manager_mac.h" #include "ui/views/controls/menu/menu_config.h" #include "ui/views/controls/menu/menu_controller.h" #include "ui/views/views_delegate.h" @@ -21,6 +25,9 @@ #include "ui/views/window/dialog_client_view.h" #include "ui/views/window/dialog_delegate.h" #include "ui/views/word_lookup_client.h" +#include "ui/views_bridge_mac/bridged_native_widget_impl.h" +#include "ui/views_bridge_mac/cocoa_mouse_capture.h" +#include "ui/views_bridge_mac/native_widget_mac_nswindow.h" using views_bridge_mac::mojom::BridgedNativeWidgetInitParams; using views_bridge_mac::mojom::WindowVisibilityState; @@ -40,14 +47,61 @@ bool PositionWindowInScreenCoordinates(Widget* widget, return widget && widget->is_top_level(); } +std::map<uint64_t, BridgedNativeWidgetHostImpl*>& GetIdToWidgetHostImplMap() { + static base::NoDestructor<std::map<uint64_t, BridgedNativeWidgetHostImpl*>> + id_map; + return *id_map; +} + +uint64_t g_last_bridged_native_widget_id = 0; + } // namespace -BridgedNativeWidgetHostImpl::BridgedNativeWidgetHostImpl( - NativeWidgetMac* parent) - : native_widget_mac_(parent), - bridge_impl_(new BridgedNativeWidget(this, parent)) {} +// static +BridgedNativeWidgetHostImpl* BridgedNativeWidgetHostImpl::GetFromNativeWindow( + gfx::NativeWindow window) { + if (NativeWidgetMacNSWindow* widget_window = + base::mac::ObjCCast<NativeWidgetMacNSWindow>(window)) { + return GetFromId([widget_window bridgedNativeWidgetId]); + } + return nullptr; // Not created by NativeWidgetMac. +} + +// static +BridgedNativeWidgetHostImpl* BridgedNativeWidgetHostImpl::GetFromId( + uint64_t bridged_native_widget_id) { + auto found = GetIdToWidgetHostImplMap().find(bridged_native_widget_id); + if (found == GetIdToWidgetHostImplMap().end()) + return nullptr; + return found->second; +} + +BridgedNativeWidgetHostImpl::BridgedNativeWidgetHostImpl(NativeWidgetMac* owner) + : widget_id_(++g_last_bridged_native_widget_id), + native_widget_mac_(owner), + root_view_id_(ui::NSViewIds::GetNewId()), + host_mojo_binding_(this) { + DCHECK(GetIdToWidgetHostImplMap().find(widget_id_) == + GetIdToWidgetHostImplMap().end()); + GetIdToWidgetHostImplMap().insert(std::make_pair(widget_id_, this)); + DCHECK(owner); +} BridgedNativeWidgetHostImpl::~BridgedNativeWidgetHostImpl() { + DCHECK(children_.empty()); + if (bridge_factory_host_) { + bridge_ptr_.reset(); + host_mojo_binding_.Unbind(); + bridge_factory_host_->RemoveObserver(this); + bridge_factory_host_ = nullptr; + } + + // Ensure that |this| cannot be reached by its id while it is being destroyed. + auto found = GetIdToWidgetHostImplMap().find(widget_id_); + DCHECK(found != GetIdToWidgetHostImplMap().end()); + DCHECK_EQ(found->second, this); + GetIdToWidgetHostImplMap().erase(found); + // Destroy the bridge first to prevent any calls back into this during // destruction. // TODO(ccameron): When all communication from |bridge_| to this goes through @@ -57,26 +111,67 @@ BridgedNativeWidgetHostImpl::~BridgedNativeWidgetHostImpl() { DestroyCompositor(); } +NativeWidgetMacNSWindow* BridgedNativeWidgetHostImpl::GetLocalNSWindow() const { + return local_window_.get(); +} + views_bridge_mac::mojom::BridgedNativeWidget* BridgedNativeWidgetHostImpl::bridge() const { - return bridge_impl_.get(); + if (bridge_ptr_) + return bridge_ptr_.get(); + if (bridge_impl_) + return bridge_impl_.get(); + return nullptr; +} + +void BridgedNativeWidgetHostImpl::CreateLocalBridge( + base::scoped_nsobject<NativeWidgetMacNSWindow> window) { + local_window_ = window; + bridge_impl_ = + std::make_unique<BridgedNativeWidgetImpl>(widget_id_, this, this); + bridge_impl_->SetWindow(window); +} + +void BridgedNativeWidgetHostImpl::CreateRemoteBridge( + BridgeFactoryHost* bridge_factory_host, + views_bridge_mac::mojom::CreateWindowParamsPtr window_create_params) { + bridge_factory_host_ = bridge_factory_host; + bridge_factory_host_->AddObserver(this); + + // Create the local window with the same parameters as will be used in the + // other process. + local_window_ = + BridgedNativeWidgetImpl::CreateNSWindow(window_create_params.get()); + [local_window_ setBridgedNativeWidgetId:widget_id_]; + + // Initialize |bridge_ptr_| to point to a bridge created by |factory|. + views_bridge_mac::mojom::BridgedNativeWidgetHostAssociatedPtr host_ptr; + host_mojo_binding_.Bind(mojo::MakeRequest(&host_ptr), + ui::WindowResizeHelperMac::Get()->task_runner()); + bridge_factory_host_->GetFactory()->CreateBridgedNativeWidget( + widget_id_, mojo::MakeRequest(&bridge_ptr_), host_ptr.PassInterface()); + + // Create the window in its process, and attach it to its parent window. + bridge()->CreateWindow(std::move(window_create_params)); } void BridgedNativeWidgetHostImpl::InitWindow(const Widget::InitParams& params) { + Widget* widget = native_widget_mac_->GetWidget(); // Tooltip Widgets shouldn't have their own tooltip manager, but tooltips are // native on Mac, so nothing should ever want one in Widget form. DCHECK_NE(params.type, Widget::InitParams::TYPE_TOOLTIP); widget_type_ = params.type; - - bridge_impl_->SetParent(params.parent); + tooltip_manager_.reset(new TooltipManagerMac(bridge())); // Initialize the window. { auto bridge_params = BridgedNativeWidgetInitParams::New(); - bridge_params->modal_type = - native_widget_mac_->GetWidget()->widget_delegate()->GetModalType(); + bridge_params->modal_type = widget->widget_delegate()->GetModalType(); bridge_params->is_translucent = params.opacity == Widget::InitParams::TRANSLUCENT_WINDOW; + bridge_params->widget_is_top_level = widget->is_top_level(); + bridge_params->position_window_in_screen_coords = + PositionWindowInScreenCoordinates(widget, widget_type_); // OSX likes to put shadows on most things. However, frameless windows (with // styleMask = NSBorderlessWindowMask) default to no shadow. So change that. @@ -111,9 +206,11 @@ void BridgedNativeWidgetHostImpl::InitWindow(const Widget::InitParams& params) { // set at all, the creator of the Widget is expected to call SetBounds() // before calling Widget::Show() to avoid a kWindowSizeDeterminedLater-sized // (i.e. 1x1) window appearing. - bridge()->SetInitialBounds(params.bounds, - native_widget_mac_->GetWidget()->GetMinimumSize(), - GetBoundsOffsetForParent()); + bridge()->SetInitialBounds(params.bounds, widget->GetMinimumSize()); + + // TODO(ccameron): Correctly set these based |local_window_|. + window_bounds_in_screen_ = params.bounds; + content_bounds_in_screen_ = params.bounds; // Widgets for UI controls (usually layered above web contents) start visible. if (widget_type_ == Widget::InitParams::TYPE_CONTROL) @@ -121,21 +218,10 @@ void BridgedNativeWidgetHostImpl::InitWindow(const Widget::InitParams& params) { } void BridgedNativeWidgetHostImpl::SetBounds(const gfx::Rect& bounds) { - gfx::Rect adjusted_bounds = bounds; - adjusted_bounds.Offset(GetBoundsOffsetForParent()); - bridge()->SetBounds(adjusted_bounds, + bridge()->SetBounds(bounds, native_widget_mac_->GetWidget()->GetMinimumSize()); } -gfx::Vector2d BridgedNativeWidgetHostImpl::GetBoundsOffsetForParent() const { - gfx::Vector2d offset; - Widget* widget = native_widget_mac_->GetWidget(); - BridgedNativeWidgetOwner* parent = bridge_impl_->parent(); - if (parent && !PositionWindowInScreenCoordinates(widget, widget_type_)) - offset = parent->GetChildWindowOffset(); - return offset; -} - void BridgedNativeWidgetHostImpl::SetFullscreen(bool fullscreen) { // Note that when the NSWindow begins a fullscreen transition, the value of // |target_fullscreen_state_| updates via OnWindowFullscreenTransitionStart. @@ -148,6 +234,15 @@ void BridgedNativeWidgetHostImpl::SetFullscreen(bool fullscreen) { void BridgedNativeWidgetHostImpl::SetRootView(views::View* root_view) { root_view_ = root_view; + if (root_view_) { + // TODO(ccameron): Drag-drop functionality does not yet run over mojo. + if (bridge_impl_) { + drag_drop_client_.reset( + new DragDropClientMac(bridge_impl_.get(), root_view_)); + } + } else { + drag_drop_client_.reset(); + } } void BridgedNativeWidgetHostImpl::CreateCompositor( @@ -202,7 +297,7 @@ void BridgedNativeWidgetHostImpl::DestroyCompositor() { if (layer()) { // LayerOwner supports a change in ownership, e.g., to animate a closing // window, but that won't work as expected for the root layer in - // BridgedNativeWidget. + // BridgedNativeWidgetImpl. DCHECK_EQ(this, layer()->owner()); layer()->CompleteAllAnimations(); layer()->SuppressPaint(); @@ -267,13 +362,129 @@ gfx::Rect BridgedNativeWidgetHostImpl::GetRestoredBounds() const { return window_bounds_in_screen_; } +void BridgedNativeWidgetHostImpl::SetNativeWindowProperty(const char* name, + void* value) { + if (value) + native_window_properties_[name] = value; + else + native_window_properties_.erase(name); +} + +void* BridgedNativeWidgetHostImpl::GetNativeWindowProperty( + const char* name) const { + auto found = native_window_properties_.find(name); + if (found == native_window_properties_.end()) + return nullptr; + return found->second; +} + +void BridgedNativeWidgetHostImpl::SetParent( + BridgedNativeWidgetHostImpl* new_parent) { + if (new_parent == parent_) + return; + + if (parent_) { + auto found = + std::find(parent_->children_.begin(), parent_->children_.end(), this); + DCHECK(found != parent_->children_.end()); + parent_->children_.erase(found); + } + + parent_ = new_parent; + if (parent_) { + // We can only re-parent to another Widget if that Widget is hosted in the + // same process that we were already hosted by. + CHECK_EQ(bridge_factory_host_, parent_->bridge_factory_host()); + parent_->children_.push_back(this); + bridge()->SetParent(parent_->bridged_native_widget_id()); + } else { + bridge()->SetParent(0); + } +} + +void BridgedNativeWidgetHostImpl::SetAssociationForView(const View* view, + NSView* native_view) { + DCHECK_EQ(0u, associated_views_.count(view)); + associated_views_[view] = native_view; + native_widget_mac_->GetWidget()->ReorderNativeViews(); +} + +void BridgedNativeWidgetHostImpl::ClearAssociationForView(const View* view) { + auto it = associated_views_.find(view); + DCHECK(it != associated_views_.end()); + associated_views_.erase(it); +} + +void BridgedNativeWidgetHostImpl::ReorderChildViews() { + std::map<NSView*, int> rank; + Widget* widget = native_widget_mac_->GetWidget(); + RankNSViewsRecursive(widget->GetRootView(), &rank); + if (bridge_impl_) + bridge_impl_->SortSubviews(std::move(rank)); +} + +void BridgedNativeWidgetHostImpl::RankNSViewsRecursive( + View* view, + std::map<NSView*, int>* rank) const { + auto it = associated_views_.find(view); + if (it != associated_views_.end()) + rank->emplace(it->second, rank->size()); + for (int i = 0; i < view->child_count(); ++i) + RankNSViewsRecursive(view->child_at(i), rank); +} + +// static +NSView* BridgedNativeWidgetHostImpl::GetGlobalCaptureView() { + // TODO(ccameron): This will not work across process boundaries. + return [CocoaMouseCapture::GetGlobalCaptureWindow() contentView]; +} + //////////////////////////////////////////////////////////////////////////////// -// BridgedNativeWidgetHostImpl, views::BridgedNativeWidgetHost: +// BridgedNativeWidgetHostImpl, views_bridge_mac::BridgedNativeWidgetHostHelper: NSView* BridgedNativeWidgetHostImpl::GetNativeViewAccessible() { return root_view_ ? root_view_->GetNativeViewAccessible() : nil; } +void BridgedNativeWidgetHostImpl::DispatchKeyEvent(ui::KeyEvent* event) { + ignore_result( + root_view_->GetWidget()->GetInputMethod()->DispatchKeyEvent(event)); +} + +bool BridgedNativeWidgetHostImpl::DispatchKeyEventToMenuController( + ui::KeyEvent* event) { + MenuController* menu_controller = MenuController::GetActiveInstance(); + if (menu_controller && root_view_ && + menu_controller->owner() == root_view_->GetWidget()) { + return menu_controller->OnWillDispatchKeyEvent(event) == + ui::POST_DISPATCH_NONE; + } + return false; +} + +double BridgedNativeWidgetHostImpl::SheetPositionY() { + return native_widget_mac_->SheetPositionY(); +} + +views_bridge_mac::DragDropClient* +BridgedNativeWidgetHostImpl::GetDragDropClient() { + return drag_drop_client_.get(); +} + +//////////////////////////////////////////////////////////////////////////////// +// BridgedNativeWidgetHostImpl, BridgeFactoryHost::Observer: +void BridgedNativeWidgetHostImpl::OnBridgeFactoryHostDestroying( + BridgeFactoryHost* host) { + DCHECK_EQ(host, bridge_factory_host_); + bridge_factory_host_->RemoveObserver(this); + bridge_factory_host_ = nullptr; + // TODO(ccameron): This should be treated as the window closing. +} + +//////////////////////////////////////////////////////////////////////////////// +// BridgedNativeWidgetHostImpl, +// views_bridge_mac::mojom::BridgedNativeWidgetHost: + void BridgedNativeWidgetHostImpl::OnVisibilityChanged(bool window_visible) { is_visible_ = window_visible; if (compositor_) { @@ -289,54 +500,48 @@ void BridgedNativeWidgetHostImpl::OnVisibilityChanged(bool window_visible) { window_visible); } +void BridgedNativeWidgetHostImpl::OnWindowNativeThemeChanged() { + ui::NativeTheme::GetInstanceForNativeUi()->NotifyObservers(); +} + void BridgedNativeWidgetHostImpl::OnScrollEvent( - const ui::ScrollEvent& const_event) { - ui::ScrollEvent event = const_event; - root_view_->GetWidget()->OnScrollEvent(&event); + std::unique_ptr<ui::Event> event) { + root_view_->GetWidget()->OnScrollEvent(event->AsScrollEvent()); } void BridgedNativeWidgetHostImpl::OnMouseEvent( - const ui::MouseEvent& const_event) { - ui::MouseEvent event = const_event; - root_view_->GetWidget()->OnMouseEvent(&event); + std::unique_ptr<ui::Event> event) { + root_view_->GetWidget()->OnMouseEvent(event->AsMouseEvent()); } void BridgedNativeWidgetHostImpl::OnGestureEvent( - const ui::GestureEvent& const_event) { - ui::GestureEvent event = const_event; - root_view_->GetWidget()->OnGestureEvent(&event); + std::unique_ptr<ui::Event> event) { + root_view_->GetWidget()->OnGestureEvent(event->AsGestureEvent()); } -void BridgedNativeWidgetHostImpl::DispatchKeyEvent( - const ui::KeyEvent& const_event, +bool BridgedNativeWidgetHostImpl::DispatchKeyEventRemote( + std::unique_ptr<ui::Event> event, bool* event_handled) { - ui::KeyEvent event = const_event; - ignore_result( - root_view_->GetWidget()->GetInputMethod()->DispatchKeyEvent(&event)); - *event_handled = event.handled(); + DispatchKeyEvent(event->AsKeyEvent()); + *event_handled = event->handled(); + return true; } -void BridgedNativeWidgetHostImpl::DispatchKeyEventToMenuController( - const ui::KeyEvent& const_event, +bool BridgedNativeWidgetHostImpl::DispatchKeyEventToMenuControllerRemote( + std::unique_ptr<ui::Event> event, bool* event_swallowed, bool* event_handled) { - ui::KeyEvent event = const_event; - MenuController* menu_controller = MenuController::GetActiveInstance(); - if (menu_controller && root_view_ && - menu_controller->owner() == root_view_->GetWidget()) { - *event_swallowed = menu_controller->OnWillDispatchKeyEvent(&event) == - ui::POST_DISPATCH_NONE; - } else { - *event_swallowed = false; - } - *event_handled = event.handled(); + *event_swallowed = DispatchKeyEventToMenuController(event->AsKeyEvent()); + *event_handled = event->handled(); + return true; } -void BridgedNativeWidgetHostImpl::GetHasMenuController( +bool BridgedNativeWidgetHostImpl::GetHasMenuController( bool* has_menu_controller) { MenuController* menu_controller = MenuController::GetActiveInstance(); *has_menu_controller = menu_controller && root_view_ && menu_controller->owner() == root_view_->GetWidget(); + return true; } void BridgedNativeWidgetHostImpl::SetViewSize(const gfx::Size& new_size) { @@ -364,15 +569,16 @@ void BridgedNativeWidgetHostImpl::OnMouseCaptureActiveChanged(bool is_active) { native_widget_mac_->GetWidget()->OnMouseCaptureLost(); } -void BridgedNativeWidgetHostImpl::GetIsDraggableBackgroundAt( +bool BridgedNativeWidgetHostImpl::GetIsDraggableBackgroundAt( const gfx::Point& location_in_content, bool* is_draggable_background) { int component = root_view_->GetWidget()->GetNonClientComponent(location_in_content); *is_draggable_background = component == HTCAPTION; + return true; } -void BridgedNativeWidgetHostImpl::GetTooltipTextAt( +bool BridgedNativeWidgetHostImpl::GetTooltipTextAt( const gfx::Point& location_in_content, base::string16* new_tooltip_text) { views::View* view = @@ -384,6 +590,7 @@ void BridgedNativeWidgetHostImpl::GetTooltipTextAt( if (!view->GetTooltipText(view_point, new_tooltip_text)) DCHECK(new_tooltip_text->empty()); } + return true; } void BridgedNativeWidgetHostImpl::GetWordAt( @@ -414,22 +621,32 @@ void BridgedNativeWidgetHostImpl::GetWordAt( *found_word = true; } -void BridgedNativeWidgetHostImpl::GetWidgetIsModal(bool* widget_is_modal) { +bool BridgedNativeWidgetHostImpl::GetWidgetIsModal(bool* widget_is_modal) { *widget_is_modal = native_widget_mac_->GetWidget()->IsModal(); + return true; } -void BridgedNativeWidgetHostImpl::GetIsFocusedViewTextual(bool* is_textual) { +bool BridgedNativeWidgetHostImpl::GetIsFocusedViewTextual(bool* is_textual) { views::FocusManager* focus_manager = root_view_ ? root_view_->GetWidget()->GetFocusManager() : nullptr; *is_textual = focus_manager && focus_manager->GetFocusedView() && focus_manager->GetFocusedView()->GetClassName() == views::Label::kViewClassName; + return true; } void BridgedNativeWidgetHostImpl::OnWindowGeometryChanged( const gfx::Rect& new_window_bounds_in_screen, const gfx::Rect& new_content_bounds_in_screen) { - has_received_window_geometry_ = true; + // If we are accessing the BridgedNativeWidget through mojo, then + // |local_window_| is not the true window that was just resized. Update + // the frame of |local_window_| to keep it in sync for any native calls + // that may use it (e.g, for context menu positioning). + if (bridge_ptr_) { + [local_window_ setFrame:gfx::ScreenRectToNSRect(new_window_bounds_in_screen) + display:NO + animate:NO]; + } bool window_has_moved = new_window_bounds_in_screen.origin() != window_bounds_in_screen_.origin(); @@ -478,6 +695,8 @@ void BridgedNativeWidgetHostImpl::OnWindowFullscreenTransitionComplete( void BridgedNativeWidgetHostImpl::OnWindowMiniaturizedChanged( bool miniaturized) { is_miniaturized_ = miniaturized; + if (native_widget_mac_) + native_widget_mac_->GetWidget()->OnNativeWidgetWindowShowStateChanged(); } void BridgedNativeWidgetHostImpl::OnWindowDisplayChanged( @@ -486,7 +705,7 @@ void BridgedNativeWidgetHostImpl::OnWindowDisplayChanged( display_.device_scale_factor() != new_display.device_scale_factor(); bool display_id_changed = display_.id() != new_display.id(); display_ = new_display; - if (scale_factor_changed && compositor_ && has_received_window_geometry_) { + if (scale_factor_changed && compositor_) { compositor_->UpdateSurface( ConvertSizeToPixel(display_.device_scale_factor(), content_bounds_in_screen_.size()), @@ -507,9 +726,14 @@ void BridgedNativeWidgetHostImpl::OnWindowWillClose() { if (DialogDelegate* dialog = widget->widget_delegate()->AsDialogDelegate()) dialog->RemoveObserver(this); native_widget_mac_->WindowDestroying(); + // Remove |this| from the parent's list of children. + SetParent(nullptr); } void BridgedNativeWidgetHostImpl::OnWindowHasClosed() { + // OnWindowHasClosed will be called only after all child windows have had + // OnWindowWillClose called on them. + DCHECK(children_.empty()); native_widget_mac_->WindowDestroyed(); } @@ -551,7 +775,7 @@ void BridgedNativeWidgetHostImpl::DoDialogButtonAction( } } -void BridgedNativeWidgetHostImpl::GetDialogButtonInfo( +bool BridgedNativeWidgetHostImpl::GetDialogButtonInfo( ui::DialogButton button, bool* button_exists, base::string16* button_label, @@ -561,17 +785,154 @@ void BridgedNativeWidgetHostImpl::GetDialogButtonInfo( ui::DialogModel* model = root_view_->GetWidget()->widget_delegate()->AsDialogDelegate(); if (!model || !(model->GetDialogButtons() & button)) - return; + return true; *button_exists = true; *button_label = model->GetDialogButtonLabel(button); *is_button_enabled = model->IsDialogButtonEnabled(button); *is_button_default = button == model->GetDefaultDialogButton(); + return true; } -void BridgedNativeWidgetHostImpl::GetDoDialogButtonsExist(bool* buttons_exist) { +bool BridgedNativeWidgetHostImpl::GetDoDialogButtonsExist(bool* buttons_exist) { ui::DialogModel* model = root_view_->GetWidget()->widget_delegate()->AsDialogDelegate(); *buttons_exist = model && model->GetDialogButtons(); + return true; +} + +bool BridgedNativeWidgetHostImpl::GetShouldShowWindowTitle( + bool* should_show_window_title) { + *should_show_window_title = + root_view_ + ? root_view_->GetWidget()->widget_delegate()->ShouldShowWindowTitle() + : true; + return true; +} + +bool BridgedNativeWidgetHostImpl::GetCanWindowBecomeKey( + bool* can_window_become_key) { + *can_window_become_key = + root_view_ ? root_view_->GetWidget()->CanActivate() : false; + return true; +} + +bool BridgedNativeWidgetHostImpl::GetAlwaysRenderWindowAsKey( + bool* always_render_as_key) { + *always_render_as_key = + root_view_ ? root_view_->GetWidget()->IsAlwaysRenderAsActive() : false; + return true; +} + +bool BridgedNativeWidgetHostImpl::GetCanWindowClose(bool* can_window_close) { + *can_window_close = true; + views::NonClientView* non_client_view = + root_view_ ? root_view_->GetWidget()->non_client_view() : nullptr; + if (non_client_view) + *can_window_close = non_client_view->CanClose(); + return true; +} + +//////////////////////////////////////////////////////////////////////////////// +// BridgedNativeWidgetHostImpl, +// views_bridge_mac::mojom::BridgedNativeWidgetHost synchronous callbacks: + +void BridgedNativeWidgetHostImpl::DispatchKeyEventRemote( + std::unique_ptr<ui::Event> event, + DispatchKeyEventRemoteCallback callback) { + bool event_handled = false; + DispatchKeyEventRemote(std::move(event), &event_handled); + std::move(callback).Run(event_handled); +} + +void BridgedNativeWidgetHostImpl::DispatchKeyEventToMenuControllerRemote( + std::unique_ptr<ui::Event> event, + DispatchKeyEventToMenuControllerRemoteCallback callback) { + ui::KeyEvent* key_event = event->AsKeyEvent(); + bool event_swallowed = DispatchKeyEventToMenuController(key_event); + std::move(callback).Run(event_swallowed, key_event->handled()); +} + +void BridgedNativeWidgetHostImpl::GetHasMenuController( + GetHasMenuControllerCallback callback) { + bool has_menu_controller = false; + GetHasMenuController(&has_menu_controller); + std::move(callback).Run(has_menu_controller); +} + +void BridgedNativeWidgetHostImpl::GetIsDraggableBackgroundAt( + const gfx::Point& location_in_content, + GetIsDraggableBackgroundAtCallback callback) { + bool is_draggable_background = false; + GetIsDraggableBackgroundAt(location_in_content, &is_draggable_background); + std::move(callback).Run(is_draggable_background); +} + +void BridgedNativeWidgetHostImpl::GetTooltipTextAt( + const gfx::Point& location_in_content, + GetTooltipTextAtCallback callback) { + base::string16 new_tooltip_text; + GetTooltipTextAt(location_in_content, &new_tooltip_text); + std::move(callback).Run(new_tooltip_text); +} + +void BridgedNativeWidgetHostImpl::GetIsFocusedViewTextual( + GetIsFocusedViewTextualCallback callback) { + bool is_textual = false; + GetIsFocusedViewTextual(&is_textual); + std::move(callback).Run(is_textual); +} + +void BridgedNativeWidgetHostImpl::GetWidgetIsModal( + GetWidgetIsModalCallback callback) { + bool widget_is_modal = false; + GetWidgetIsModal(&widget_is_modal); + std::move(callback).Run(widget_is_modal); +} + +void BridgedNativeWidgetHostImpl::GetDialogButtonInfo( + ui::DialogButton button, + GetDialogButtonInfoCallback callback) { + bool exists = false; + base::string16 label; + bool is_enabled = false; + bool is_default = false; + GetDialogButtonInfo(button, &exists, &label, &is_enabled, &is_default); + std::move(callback).Run(exists, label, is_enabled, is_default); +} + +void BridgedNativeWidgetHostImpl::GetDoDialogButtonsExist( + GetDoDialogButtonsExistCallback callback) { + bool buttons_exist = false; + GetDoDialogButtonsExist(&buttons_exist); + std::move(callback).Run(buttons_exist); +} + +void BridgedNativeWidgetHostImpl::GetShouldShowWindowTitle( + GetShouldShowWindowTitleCallback callback) { + bool should_show_window_title = false; + GetShouldShowWindowTitle(&should_show_window_title); + std::move(callback).Run(should_show_window_title); +} + +void BridgedNativeWidgetHostImpl::GetCanWindowBecomeKey( + GetCanWindowBecomeKeyCallback callback) { + bool can_window_become_key = false; + GetCanWindowBecomeKey(&can_window_become_key); + std::move(callback).Run(can_window_become_key); +} + +void BridgedNativeWidgetHostImpl::GetAlwaysRenderWindowAsKey( + GetAlwaysRenderWindowAsKeyCallback callback) { + bool always_render_as_key = false; + GetAlwaysRenderWindowAsKey(&always_render_as_key); + std::move(callback).Run(always_render_as_key); +} + +void BridgedNativeWidgetHostImpl::GetCanWindowClose( + GetCanWindowCloseCallback callback) { + bool can_window_close = false; + GetCanWindowClose(&can_window_close); + std::move(callback).Run(can_window_close); } //////////////////////////////////////////////////////////////////////////////// @@ -598,20 +959,25 @@ void BridgedNativeWidgetHostImpl::OnDidChangeFocus(View* focused_before, // Sanity check: When focus moves away from the widget (i.e. |focused_now| // is nil), then the textInputClient will be cleared. DCHECK(!!focused_now || !input_client); - bridge_impl_->SetTextInputClient(input_client); + // TODO(ccameron): TextInputClient is not handled across process borders + // yet. + if (bridge_impl_) + bridge_impl_->SetTextInputClient(input_client); } } //////////////////////////////////////////////////////////////////////////////// -// BridgedNativeWidget, internal::InputMethodDelegate: +// BridgedNativeWidgetImpl, internal::InputMethodDelegate: ui::EventDispatchDetails BridgedNativeWidgetHostImpl::DispatchKeyEventPostIME( - ui::KeyEvent* key) { + ui::KeyEvent* key, + base::OnceCallback<void(bool)> ack_callback) { DCHECK(focus_manager_); if (!focus_manager_->OnKeyEvent(*key)) key->StopPropagation(); else native_widget_mac_->GetWidget()->OnKeyEvent(key); + CallDispatchKeyEventPostIMEAck(key, std::move(ack_callback)); return ui::EventDispatchDetails(); } diff --git a/chromium/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm b/chromium/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm index 53c32814537..38f71672f2e 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm +++ b/chromium/ui/views/cocoa/bridged_native_widget_interactive_uitest.mm @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "ui/views/cocoa/bridged_native_widget.h" +#import "ui/views_bridge_mac/bridged_native_widget_impl.h" #import <Cocoa/Cocoa.h> @@ -251,7 +251,7 @@ void WaitForEvent(NSUInteger mask) { } // namespace // This is used to inject test versions of NativeFrameView and -// BridgedNativeWidget. +// BridgedNativeWidgetImpl. class HitTestNativeWidgetMac : public NativeWidgetMac { public: HitTestNativeWidgetMac(internal::NativeWidgetDelegate* delegate, diff --git a/chromium/ui/views/cocoa/bridged_native_widget_owner.h b/chromium/ui/views/cocoa/bridged_native_widget_owner.h deleted file mode 100644 index 9869ed5a938..00000000000 --- a/chromium/ui/views/cocoa/bridged_native_widget_owner.h +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2015 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 UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_OWNER_H_ -#define UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_OWNER_H_ - -namespace gfx { -class Vector2d; -} - -@class NSWindow; - -namespace views { - -class BridgedNativeWidget; - -// An abstract interface wrapping an NSWindow that ties the lifetime of one or -// more child BridgedNativeWidgets to the lifetime of that NSWindow. This is not -// simply an NSWindow, because the child window API provided by NSWindow -// requires child windows to always be visible. -class BridgedNativeWidgetOwner { - public: - // The NSWindow parent. - virtual NSWindow* GetNSWindow() = 0; - - // The offset in screen pixels for positioning child windows owned by |this|. - virtual gfx::Vector2d GetChildWindowOffset() const = 0; - - // Return false if |this| is hidden, or has a hidden ancestor. - virtual bool IsVisibleParent() const = 0; - - // Removes a child window. Note |this| may be deleted after calling, so the - // caller should immediately null out the pointer used to make the call. - virtual void RemoveChildWindow(BridgedNativeWidget* child) = 0; - - protected: - // Instances of this class may be self-deleting. - virtual ~BridgedNativeWidgetOwner() {} -}; - -} // namespace views - -#endif // UI_VIEWS_COCOA_BRIDGED_NATIVE_WIDGET_OWNER_H_ diff --git a/chromium/ui/views/cocoa/bridged_native_widget_unittest.mm b/chromium/ui/views/cocoa/bridged_native_widget_unittest.mm index 8f3abc65650..3a2248849fd 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget_unittest.mm +++ b/chromium/ui/views/cocoa/bridged_native_widget_unittest.mm @@ -2,9 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "ui/views/cocoa/bridged_native_widget.h" +#import "ui/views_bridge_mac/bridged_native_widget_impl.h" #import <Cocoa/Cocoa.h> +#include <objc/runtime.h> #include <memory> @@ -25,10 +26,7 @@ #include "ui/base/test/material_design_controller_test_api.h" #include "ui/events/test/cocoa_test_event_utils.h" #import "ui/gfx/mac/coordinate_conversion.h" -#import "ui/views/cocoa/bridged_content_view.h" #import "ui/views/cocoa/bridged_native_widget_host_impl.h" -#import "ui/views/cocoa/native_widget_mac_nswindow.h" -#import "ui/views/cocoa/views_nswindow_delegate.h" #include "ui/views/controls/textfield/textfield.h" #include "ui/views/controls/textfield/textfield_controller.h" #include "ui/views/controls/textfield/textfield_model.h" @@ -38,6 +36,9 @@ #include "ui/views/widget/root_view.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_observer.h" +#import "ui/views_bridge_mac/bridged_content_view.h" +#import "ui/views_bridge_mac/native_widget_mac_nswindow.h" +#import "ui/views_bridge_mac/views_nswindow_delegate.h" using base::ASCIIToUTF16; using base::SysNSStringToUTF8; @@ -121,8 +122,8 @@ NSArray* const kDeleteActions = @[ @"deleteToBeginningOfParagraph:", @"deleteToEndOfParagraph:" ]; -NSArray* const kMiscActions = - @[ @"insertText:", @"cancelOperation:", @"transpose:", @"yank:" ]; +// This omits @"insertText:":. See BridgedNativeWidgetTest.NilTextInputClient. +NSArray* const kMiscActions = @[ @"cancelOperation:", @"transpose:", @"yank:" ]; // Empty range shortcut for readibility. NSRange EmptyRange() { @@ -293,12 +294,12 @@ NSTextInputContext* g_fake_current_input_context = nullptr; namespace views { namespace test { -// Provides the |parent| argument to construct a BridgedNativeWidget. +// Provides the |parent| argument to construct a BridgedNativeWidgetImpl. class MockNativeWidgetMac : public NativeWidgetMac { public: explicit MockNativeWidgetMac(internal::NativeWidgetDelegate* delegate) : NativeWidgetMac(delegate) {} - using NativeWidgetMac::bridge; + using NativeWidgetMac::bridge_impl; using NativeWidgetMac::bridge_host_for_testing; // internal::NativeWidgetPrivate: @@ -311,7 +312,12 @@ class MockNativeWidgetMac : public NativeWidgetMac { styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO]); - bridge()->SetWindow(window); + bridge_host_for_testing()->CreateLocalBridge(window); + if (BridgedNativeWidgetHostImpl* parent = + BridgedNativeWidgetHostImpl::GetFromNativeWindow( + [params.parent window])) { + bridge_host_for_testing()->SetParent(parent); + } bridge_host_for_testing()->InitWindow(params); // Usually the bridge gets initialized here. It is skipped to run extra @@ -330,7 +336,7 @@ class MockNativeWidgetMac : public NativeWidgetMac { DISALLOW_COPY_AND_ASSIGN(MockNativeWidgetMac); }; -// Helper test base to construct a BridgedNativeWidget with a valid parent. +// Helper test base to construct a BridgedNativeWidgetImpl with a valid parent. class BridgedNativeWidgetTestBase : public ui::CocoaTest { public: struct SkipInitialization {}; @@ -342,11 +348,27 @@ class BridgedNativeWidgetTestBase : public ui::CocoaTest { explicit BridgedNativeWidgetTestBase(SkipInitialization tag) : native_widget_mac_(nullptr) {} - BridgedNativeWidget* bridge() { return native_widget_mac_->bridge(); } + BridgedNativeWidgetImpl* bridge() { + return native_widget_mac_->bridge_impl(); + } BridgedNativeWidgetHostImpl* bridge_host() { return native_widget_mac_->bridge_host_for_testing(); } + // Generate an autoreleased KeyDown NSEvent* in |widget_| for pressing the + // corresponding |key_code|. + NSEvent* VkeyKeyDown(ui::KeyboardCode key_code) { + return cocoa_test_event_utils::SynthesizeKeyEvent( + widget_->GetNativeWindow(), true /* keyDown */, key_code, 0); + } + + // Generate an autoreleased KeyDown NSEvent* using the given keycode, and + // representing the first unicode character of |chars|. + NSEvent* UnicodeKeyDown(int key_code, NSString* chars) { + return cocoa_test_event_utils::KeyEventWithKeyCode( + key_code, [chars characterAtIndex:0], NSKeyDown, 0); + } + // Overridden from testing::Test: void SetUp() override { ui::CocoaTest::SetUp(); @@ -385,7 +407,9 @@ class BridgedNativeWidgetTestBase : public ui::CocoaTest { } NSWindow* bridge_window() const { - return native_widget_mac_->bridge()->ns_window(); + if (native_widget_mac_->bridge_impl()) + return native_widget_mac_->bridge_impl()->ns_window(); + return nil; } protected: @@ -614,7 +638,8 @@ void BridgedNativeWidgetTest::SetUp() { // The delegate should exist before setting the root view. EXPECT_TRUE([window delegate]); bridge_host()->SetRootView(view_.get()); - bridge()->CreateContentView(view_->bounds()); + bridge()->CreateContentView(bridge_host()->GetRootViewNSViewId(), + view_->bounds()); ns_view_ = bridge()->ns_view(); // Pretend it has been shown via NativeWidgetMac::Show(). @@ -853,7 +878,7 @@ class BridgedNativeWidgetInitTest : public BridgedNativeWidgetTestBase { DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetInitTest); }; -// Test that BridgedNativeWidget remains sane if Init() is never called. +// Test that BridgedNativeWidgetImpl remains sane if Init() is never called. TEST_F(BridgedNativeWidgetInitTest, InitNotCalled) { // Don't use a Widget* as the delegate. ~Widget() checks for Widget:: // |native_widget_destroyed_| being set to true. That can only happen with a @@ -862,8 +887,8 @@ TEST_F(BridgedNativeWidgetInitTest, InitNotCalled) { std::unique_ptr<MockNativeWidgetMac> native_widget( new MockNativeWidgetMac(nullptr)); native_widget_mac_ = native_widget.get(); - EXPECT_FALSE(bridge()->ns_view()); - EXPECT_FALSE(bridge()->ns_window()); + EXPECT_FALSE(bridge()); + EXPECT_FALSE(bridge_host()->GetLocalNSWindow()); } // Tests the shadow type given in InitParams. @@ -1323,10 +1348,16 @@ TEST_F(BridgedNativeWidgetTest, NilTextInputClient) { [selectors addObjectsFromArray:kMoveActions]; [selectors addObjectsFromArray:kSelectActions]; [selectors addObjectsFromArray:kDeleteActions]; + + // -insertText: is omitted from this list to avoid a DCHECK in + // doCommandBySelector:. AppKit never passes -insertText: to + // doCommandBySelector: (it calls -insertText: directly instead). [selectors addObjectsFromArray:kMiscActions]; for (NSString* selector in selectors) [ns_view_ doCommandBySelector:NSSelectorFromString(selector)]; + + [ns_view_ insertText:@""]; } // Test transpose command against expectations set by |dummy_text_view_|. @@ -1434,27 +1465,22 @@ TEST_F(BridgedNativeWidgetTest, TextInput_SimulatePhoneticIme) { // Sequence of calls (and corresponding keyDown events) obtained via tracing // with 2-Set Korean IME and pressing q, o, then Enter on the keyboard. - NSEvent* q_in_ime = cocoa_test_event_utils::KeyEventWithKeyCode( - 12, [@"ㅂ" characterAtIndex:0], NSKeyDown, 0); + NSEvent* q_in_ime = UnicodeKeyDown(12, @"ㅂ"); InterpretKeyEventsCallback handle_q_in_ime = base::BindRepeating([](id view) { [view insertText:@"ㅂ" replacementRange:NSMakeRange(NSNotFound, 0)]; }); - NSEvent* o_in_ime = cocoa_test_event_utils::KeyEventWithKeyCode( - 31, [@"ㅐ" characterAtIndex:0], NSKeyDown, 0); + NSEvent* o_in_ime = UnicodeKeyDown(31, @"ㅐ"); InterpretKeyEventsCallback handle_o_in_ime = base::BindRepeating([](id view) { [view insertText:@"배" replacementRange:NSMakeRange(0, 1)]; }); - NSEvent* return_in_ime = cocoa_test_event_utils::SynthesizeKeyEvent( - widget_->GetNativeWindow(), true, ui::VKEY_RETURN, 0); InterpretKeyEventsCallback handle_return_in_ime = base::BindRepeating([](id view) { // When confirming the composition, AppKit repeats itself. [view insertText:@"배" replacementRange:NSMakeRange(0, 1)]; [view insertText:@"배" replacementRange:NSMakeRange(0, 1)]; - // Note: there is no insertText:@"\r", which we would normally see when - // not in an IME context for VKEY_RETURN. + [view doCommandBySelector:@selector(insertNewLine:)]; }); // Add a hook for the KeyEvent being received by the TextfieldController. E.g. @@ -1486,7 +1512,7 @@ TEST_F(BridgedNativeWidgetTest, TextInput_SimulatePhoneticIme) { // Note the "Enter" should not replace the replacement range, even though a // replacement range was set. g_fake_interpret_key_events = &handle_return_in_ime; - [ns_view_ keyDown:return_in_ime]; + [ns_view_ keyDown:VkeyKeyDown(ui::VKEY_RETURN)]; EXPECT_EQ(base::SysNSStringToUTF16(@"배"), textfield->text()); // VKEY_RETURN should be seen by via the unhandled key event handler (but not @@ -1496,6 +1522,76 @@ TEST_F(BridgedNativeWidgetTest, TextInput_SimulatePhoneticIme) { g_fake_interpret_key_events = nullptr; } +// Test simulated codepaths for typing 'm', 'o', 'o', Enter in the Telex IME. +// This IME does not mark text, but, unlike 2-set Korean, it re-inserts the +// entire word on each keypress, even though only the last character in the word +// can be modified. This prevents the keypress being treated as a "character" +// event (which is unavoidably unfortunate for the Undo buffer), but also led to +// a codepath that suppressed a VKEY_RETURN when it should not, since there is +// no candidate IME window to dismiss for this IME. +TEST_F(BridgedNativeWidgetTest, TextInput_SimulateTelexMoo) { + Textfield* textfield = InstallTextField(""); + EXPECT_TRUE([ns_view_ textInputClient]); + + EnterAcceleratorView* enter_view = new EnterAcceleratorView(); + textfield->parent()->AddChildView(enter_view); + + // Sequence of calls (and corresponding keyDown events) obtained via tracing + // with Telex IME and pressing 'm', 'o', 'o', then Enter on the keyboard. + // Note that without the leading 'm', only one character changes, which could + // allow the keypress to be treated as a character event, which would not + // produce the bug. + NSEvent* m_in_ime = UnicodeKeyDown(46, @"m"); + InterpretKeyEventsCallback handle_m_in_ime = base::BindRepeating([](id view) { + [view insertText:@"m" replacementRange:NSMakeRange(NSNotFound, 0)]; + }); + + // Note that (unlike Korean IME), Telex generates a latin "o" for both events: + // it doesn't associate a unicode character on the second NSEvent. + NSEvent* o_in_ime = UnicodeKeyDown(31, @"o"); + InterpretKeyEventsCallback handle_first_o_in_ime = + base::BindRepeating([](id view) { + // Note the whole word is replaced, not just the last character. + [view insertText:@"mo" replacementRange:NSMakeRange(0, 1)]; + }); + InterpretKeyEventsCallback handle_second_o_in_ime = + base::BindRepeating([](id view) { + [view insertText:@"mô" replacementRange:NSMakeRange(0, 2)]; + }); + + InterpretKeyEventsCallback handle_return_in_ime = + base::BindRepeating([](id view) { + // Note the previous -insertText: repeats, even though it is unchanged. + // But the IME also follows with an -insertNewLine:. + [view insertText:@"mô" replacementRange:NSMakeRange(0, 2)]; + [view doCommandBySelector:@selector(insertNewLine:)]; + }); + + EXPECT_EQ(base::UTF8ToUTF16(""), textfield->text()); + EXPECT_EQ(0, enter_view->count()); + + object_setClass(ns_view_, [InterpretKeyEventMockedBridgedContentView class]); + g_fake_interpret_key_events = &handle_m_in_ime; + [ns_view_ keyDown:m_in_ime]; + EXPECT_EQ(base::SysNSStringToUTF16(@"m"), textfield->text()); + EXPECT_EQ(0, enter_view->count()); + + g_fake_interpret_key_events = &handle_first_o_in_ime; + [ns_view_ keyDown:o_in_ime]; + EXPECT_EQ(base::SysNSStringToUTF16(@"mo"), textfield->text()); + EXPECT_EQ(0, enter_view->count()); + + g_fake_interpret_key_events = &handle_second_o_in_ime; + [ns_view_ keyDown:o_in_ime]; + EXPECT_EQ(base::SysNSStringToUTF16(@"mô"), textfield->text()); + EXPECT_EQ(0, enter_view->count()); + + g_fake_interpret_key_events = &handle_return_in_ime; + [ns_view_ keyDown:VkeyKeyDown(ui::VKEY_RETURN)]; + EXPECT_EQ(base::SysNSStringToUTF16(@"mô"), textfield->text()); // No change. + EXPECT_EQ(1, enter_view->count()); // Now we see the accelerator. +} + // Simulate 'a', Enter in Hiragana. This should just insert "あ", suppressing // accelerators. TEST_F(BridgedNativeWidgetTest, TextInput_NoAcceleratorEnterComposition) { @@ -1507,8 +1603,8 @@ TEST_F(BridgedNativeWidgetTest, TextInput_NoAcceleratorEnterComposition) { // Sequence of calls (and corresponding keyDown events) obtained via tracing // with Hiragana IME and pressing 'a', then Enter on the keyboard. - NSEvent* a_in_ime = cocoa_test_event_utils::KeyEventWithKeyCode( - 0, [@"a" characterAtIndex:0], NSKeyDown, 0); + // Note 0 is the actual keyCode for 'a', not a placeholder. + NSEvent* a_in_ime = UnicodeKeyDown(0, @"a"); InterpretKeyEventsCallback handle_a_in_ime = base::BindRepeating([](id view) { // TODO(crbug/612675): |text| should be an NSAttributedString. [view setMarkedText:@"あ" @@ -1516,12 +1612,13 @@ TEST_F(BridgedNativeWidgetTest, TextInput_NoAcceleratorEnterComposition) { replacementRange:NSMakeRange(NSNotFound, 0)]; }); - NSEvent* return_event = cocoa_test_event_utils::SynthesizeKeyEvent( - widget_->GetNativeWindow(), true, ui::VKEY_RETURN, 0); - InterpretKeyEventsCallback handle_return_in_ime = + InterpretKeyEventsCallback handle_first_return_in_ime = base::BindRepeating([](id view) { [view insertText:@"あ" replacementRange:NSMakeRange(NSNotFound, 0)]; + // Note there is no call to -insertNewLine: here. }); + InterpretKeyEventsCallback handle_second_return_in_ime = base::BindRepeating( + [](id view) { [view doCommandBySelector:@selector(insertNewLine:)]; }); EXPECT_EQ(base::UTF8ToUTF16(""), textfield->text()); EXPECT_EQ(0, enter_view->count()); @@ -1532,16 +1629,14 @@ TEST_F(BridgedNativeWidgetTest, TextInput_NoAcceleratorEnterComposition) { EXPECT_EQ(base::SysNSStringToUTF16(@"あ"), textfield->text()); EXPECT_EQ(0, enter_view->count()); - g_fake_interpret_key_events = &handle_return_in_ime; - [ns_view_ keyDown:return_event]; + g_fake_interpret_key_events = &handle_first_return_in_ime; + [ns_view_ keyDown:VkeyKeyDown(ui::VKEY_RETURN)]; EXPECT_EQ(base::SysNSStringToUTF16(@"あ"), textfield->text()); EXPECT_EQ(0, enter_view->count()); // Not seen as an accelerator. - // IME Window is dismissed here and there is no marked text, so remove the - // swizzler. - object_setClass(ns_view_, [BridgedContentView class]); - - [ns_view_ keyDown:return_event]; // Sanity check: send Enter again. + g_fake_interpret_key_events = &handle_second_return_in_ime; + [ns_view_ + keyDown:VkeyKeyDown(ui::VKEY_RETURN)]; // Sanity check: send Enter again. EXPECT_EQ(base::SysNSStringToUTF16(@"あ"), textfield->text()); // No change. EXPECT_EQ(1, enter_view->count()); // Now we see the accelerator. } @@ -1557,8 +1652,7 @@ TEST_F(BridgedNativeWidgetTest, TextInput_NoAcceleratorTabEnterComposition) { // Sequence of calls (and corresponding keyDown events) obtained via tracing // with Hiragana IME and pressing 'a', Tab, then Enter on the keyboard. - NSEvent* a_in_ime = cocoa_test_event_utils::KeyEventWithKeyCode( - 0, [@"a" characterAtIndex:0], NSKeyDown, 0); + NSEvent* a_in_ime = UnicodeKeyDown(0, @"a"); InterpretKeyEventsCallback handle_a_in_ime = base::BindRepeating([](id view) { // TODO(crbug/612675): |text| should have an underline. [view setMarkedText:@"あ" @@ -1566,8 +1660,6 @@ TEST_F(BridgedNativeWidgetTest, TextInput_NoAcceleratorTabEnterComposition) { replacementRange:NSMakeRange(NSNotFound, 0)]; }); - NSEvent* tab_in_ime = cocoa_test_event_utils::SynthesizeKeyEvent( - widget_->GetNativeWindow(), true, ui::VKEY_TAB, 0); InterpretKeyEventsCallback handle_tab_in_ime = base::BindRepeating([](id view) { // TODO(crbug/612675): |text| should be an NSAttributedString (now with @@ -1575,10 +1667,9 @@ TEST_F(BridgedNativeWidgetTest, TextInput_NoAcceleratorTabEnterComposition) { [view setMarkedText:@"a" selectedRange:NSMakeRange(0, 1) replacementRange:NSMakeRange(NSNotFound, 0)]; + // Note there is no -insertTab: generated. }); - NSEvent* return_event = cocoa_test_event_utils::SynthesizeKeyEvent( - widget_->GetNativeWindow(), true, ui::VKEY_RETURN, 0); InterpretKeyEventsCallback handle_first_return_in_ime = base::BindRepeating([](id view) { // Do *nothing*. Enter does not confirm nor change the composition, it @@ -1589,6 +1680,11 @@ TEST_F(BridgedNativeWidgetTest, TextInput_NoAcceleratorTabEnterComposition) { // The second return will confirm the composition. [view insertText:@"a" replacementRange:NSMakeRange(NSNotFound, 0)]; }); + InterpretKeyEventsCallback handle_third_return_in_ime = + base::BindRepeating([](id view) { + // Only the third return will generate -insertNewLine:. + [view doCommandBySelector:@selector(insertNewLine:)]; + }); EXPECT_EQ(base::UTF8ToUTF16(""), textfield->text()); EXPECT_EQ(0, enter_view->count()); @@ -1600,28 +1696,26 @@ TEST_F(BridgedNativeWidgetTest, TextInput_NoAcceleratorTabEnterComposition) { EXPECT_EQ(0, enter_view->count()); g_fake_interpret_key_events = &handle_tab_in_ime; - [ns_view_ keyDown:tab_in_ime]; + [ns_view_ keyDown:VkeyKeyDown(ui::VKEY_TAB)]; // Tab will switch to a Romanji (Latin) character. EXPECT_EQ(base::SysNSStringToUTF16(@"a"), textfield->text()); EXPECT_EQ(0, enter_view->count()); g_fake_interpret_key_events = &handle_first_return_in_ime; - [ns_view_ keyDown:return_event]; + [ns_view_ keyDown:VkeyKeyDown(ui::VKEY_RETURN)]; // Enter just dismisses the IME window. The composition is still active. EXPECT_EQ(base::SysNSStringToUTF16(@"a"), textfield->text()); EXPECT_EQ(0, enter_view->count()); // Not seen as an accelerator. g_fake_interpret_key_events = &handle_second_return_in_ime; - [ns_view_ keyDown:return_event]; + [ns_view_ keyDown:VkeyKeyDown(ui::VKEY_RETURN)]; // Enter now confirms the composition (unmarks text). Note there is still no // IME window visible but, since there is marked text, IME is still active. EXPECT_EQ(base::SysNSStringToUTF16(@"a"), textfield->text()); EXPECT_EQ(0, enter_view->count()); // Not seen as an accelerator. - // No marked text, no IME window. We could remove the swizzler here, but - // that is equivalent to the "do nothing" case, so set than handler again. - g_fake_interpret_key_events = &handle_first_return_in_ime; - [ns_view_ keyDown:return_event]; // Send Enter a _third_ time. + g_fake_interpret_key_events = &handle_third_return_in_ime; + [ns_view_ keyDown:VkeyKeyDown(ui::VKEY_RETURN)]; // Send Enter a third time. EXPECT_EQ(base::SysNSStringToUTF16(@"a"), textfield->text()); // No change. EXPECT_EQ(1, enter_view->count()); // Now we see the accelerator. } @@ -1754,8 +1848,8 @@ TEST_F(BridgedNativeWidgetSimulateFullscreenTest, FailToEnterAndExit) { object:window]; // On a failure, Cocoa starts by sending an unexpected *exit* fullscreen, and - // BridgedNativeWidget will think it's just a delayed transition and try to go - // back into fullscreen but get ignored by Cocoa. + // BridgedNativeWidgetImpl will think it's just a delayed transition and try + // to go back into fullscreen but get ignored by Cocoa. EXPECT_EQ(0, [window ignoredToggleFullScreenCount]); EXPECT_TRUE(bridge()->target_fullscreen_state()); [center postNotificationName:NSWindowDidExitFullScreenNotification diff --git a/chromium/ui/views/cocoa/cocoa_mouse_capture.h b/chromium/ui/views/cocoa/cocoa_mouse_capture.h deleted file mode 100644 index 9b170e4a7e9..00000000000 --- a/chromium/ui/views/cocoa/cocoa_mouse_capture.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2014 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 UI_VIEWS_COCOA_COCOA_MOUSE_CAPTURE_H_ -#define UI_VIEWS_COCOA_COCOA_MOUSE_CAPTURE_H_ - -#include <memory> - -#include "base/macros.h" -#include "ui/views/views_export.h" - -@class NSWindow; - -namespace views { - -class CocoaMouseCaptureDelegate; - -// Basic mouse capture to simulate ::SetCapture() from Windows. This is used to -// support menu widgets (e.g. on Combo boxes). Clicking anywhere other than the -// menu should dismiss the menu and "swallow" the mouse event. All events are -// forwarded, but only events to the same application are "swallowed", which is -// consistent with how native NSMenus behave. -class VIEWS_EXPORT CocoaMouseCapture { - public: - explicit CocoaMouseCapture(CocoaMouseCaptureDelegate* delegate); - ~CocoaMouseCapture(); - - // Returns the NSWindow with capture or nil if no window has capture - // currently. - static NSWindow* GetGlobalCaptureWindow(); - - // True if the event tap is active (i.e. not stolen by a later instance). - bool IsActive() const { return !!active_handle_; } - - private: - class ActiveEventTap; - - // Deactivates the event tap if still active. - void OnOtherClientGotCapture(); - - CocoaMouseCaptureDelegate* delegate_; // Weak. Owns this. - - // The active event tap for this capture. Owned by this, but can be cleared - // out early if another instance of CocoaMouseCapture is created. - std::unique_ptr<ActiveEventTap> active_handle_; - - DISALLOW_COPY_AND_ASSIGN(CocoaMouseCapture); -}; - -} // namespace views - -#endif // UI_VIEWS_COCOA_COCOA_MOUSE_CAPTURE_H_ diff --git a/chromium/ui/views/cocoa/cocoa_mouse_capture.mm b/chromium/ui/views/cocoa/cocoa_mouse_capture.mm deleted file mode 100644 index b1f3cb216de..00000000000 --- a/chromium/ui/views/cocoa/cocoa_mouse_capture.mm +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2014 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. - -#import "ui/views/cocoa/cocoa_mouse_capture.h" - -#import <Cocoa/Cocoa.h> - -#include "base/logging.h" -#include "base/macros.h" -#include "ui/base/cocoa/weak_ptr_nsobject.h" -#import "ui/views/cocoa/cocoa_mouse_capture_delegate.h" - -namespace views { - -// The ActiveEventTap is a RAII handle on the resources being used to capture -// events. There is either 0 or 1 active instance of this class. If a second -// instance is created, it will destroy the other instance before returning from -// its constructor. -class CocoaMouseCapture::ActiveEventTap { - public: - explicit ActiveEventTap(CocoaMouseCapture* owner); - ~ActiveEventTap(); - - // Returns the NSWindow with capture or nil if no window has capture - // currently. - static NSWindow* GetGlobalCaptureWindow(); - - void Init(); - - private: - // Returns the associated NSWindow with capture. - NSWindow* GetCaptureWindow() const; - - // The currently active event tap, or null if there is none. - static ActiveEventTap* g_active_event_tap; - - CocoaMouseCapture* owner_; // Weak. Owns this. - id local_monitor_; - id global_monitor_; - ui::WeakPtrNSObjectFactory<CocoaMouseCapture> factory_; - - DISALLOW_COPY_AND_ASSIGN(ActiveEventTap); -}; - -CocoaMouseCapture::ActiveEventTap* - CocoaMouseCapture::ActiveEventTap::g_active_event_tap = nullptr; - -CocoaMouseCapture::ActiveEventTap::ActiveEventTap(CocoaMouseCapture* owner) - : owner_(owner), - local_monitor_(nil), - global_monitor_(nil), - factory_(owner) { - if (g_active_event_tap) - g_active_event_tap->owner_->OnOtherClientGotCapture(); - DCHECK(!g_active_event_tap); - g_active_event_tap = this; -} - -CocoaMouseCapture::ActiveEventTap::~ActiveEventTap() { - DCHECK_EQ(g_active_event_tap, this); - [NSEvent removeMonitor:global_monitor_]; - [NSEvent removeMonitor:local_monitor_]; - g_active_event_tap = nullptr; - owner_->delegate_->OnMouseCaptureLost(); -} - -// static -NSWindow* CocoaMouseCapture::ActiveEventTap::GetGlobalCaptureWindow() { - return g_active_event_tap ? g_active_event_tap->GetCaptureWindow() : nil; -} - -void CocoaMouseCapture::ActiveEventTap::Init() { - // Consume most things, but not NSMouseEntered/Exited: The Widget doing - // capture will still see its own Entered/Exit events, but not those for other - // NSViews, since consuming those would break their tracking area logic. - NSEventMask event_mask = - NSLeftMouseDownMask | NSLeftMouseUpMask | NSRightMouseDownMask | - NSRightMouseUpMask | NSMouseMovedMask | NSLeftMouseDraggedMask | - NSRightMouseDraggedMask | NSScrollWheelMask | NSOtherMouseDownMask | - NSOtherMouseUpMask | NSOtherMouseDraggedMask; - - // Capture a WeakPtr via NSObject. This allows the block to detect another - // event monitor for the same event deleting |owner_|. - WeakPtrNSObject* handle = factory_.handle(); - - auto local_block = ^NSEvent*(NSEvent* event) { - CocoaMouseCapture* owner = - ui::WeakPtrNSObjectFactory<CocoaMouseCapture>::Get(handle); - if (owner) - owner->delegate_->PostCapturedEvent(event); - return nil; // Swallow all local events. - }; - auto global_block = ^void(NSEvent* event) { - CocoaMouseCapture* owner = - ui::WeakPtrNSObjectFactory<CocoaMouseCapture>::Get(handle); - if (owner) - owner->delegate_->PostCapturedEvent(event); - }; - local_monitor_ = [NSEvent addLocalMonitorForEventsMatchingMask:event_mask - handler:local_block]; - global_monitor_ = - [NSEvent addGlobalMonitorForEventsMatchingMask:event_mask - handler:global_block]; -} - -NSWindow* CocoaMouseCapture::ActiveEventTap::GetCaptureWindow() const { - return owner_->delegate_->GetWindow(); -} - -CocoaMouseCapture::CocoaMouseCapture(CocoaMouseCaptureDelegate* delegate) - : delegate_(delegate), active_handle_(new ActiveEventTap(this)) { - active_handle_->Init(); -} - -CocoaMouseCapture::~CocoaMouseCapture() {} - -// static -NSWindow* CocoaMouseCapture::GetGlobalCaptureWindow() { - return ActiveEventTap::GetGlobalCaptureWindow(); -} - -void CocoaMouseCapture::OnOtherClientGotCapture() { - DCHECK(active_handle_); - active_handle_.reset(); -} - -} // namespace views diff --git a/chromium/ui/views/cocoa/cocoa_mouse_capture_delegate.h b/chromium/ui/views/cocoa/cocoa_mouse_capture_delegate.h deleted file mode 100644 index 9e6d2a0e4ea..00000000000 --- a/chromium/ui/views/cocoa/cocoa_mouse_capture_delegate.h +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright 2014 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 UI_VIEWS_COCOA_COCOA_MOUSE_CAPTURE_DELEGATE_H_ -#define UI_VIEWS_COCOA_COCOA_MOUSE_CAPTURE_DELEGATE_H_ - -@class NSEvent; -@class NSWindow; - -namespace views { - -// Delegate for receiving captured events from a CocoaMouseCapture. -class CocoaMouseCaptureDelegate { - public: - // Called when an event has been captured. This may be an event local to the - // application, or a global event (sent to another application). If it is a - // local event, regular event handling will be suppressed. - virtual void PostCapturedEvent(NSEvent* event) = 0; - - // Called once. When another window acquires capture, or when the - // CocoaMouseCapture is destroyed. - virtual void OnMouseCaptureLost() = 0; - - // Returns the associated NSWindow. - virtual NSWindow* GetWindow() const = 0; -}; - -} // namespace views - -#endif // UI_VIEWS_COCOA_COCOA_MOUSE_CAPTURE_DELEGATE_H_ diff --git a/chromium/ui/views/cocoa/cocoa_mouse_capture_unittest.mm b/chromium/ui/views/cocoa/cocoa_mouse_capture_unittest.mm index 47235a7fcff..295e0617cd8 100644 --- a/chromium/ui/views/cocoa/cocoa_mouse_capture_unittest.mm +++ b/chromium/ui/views/cocoa/cocoa_mouse_capture_unittest.mm @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#import "ui/views/cocoa/cocoa_mouse_capture.h" +#import "ui/views_bridge_mac/cocoa_mouse_capture.h" #import <Cocoa/Cocoa.h> @@ -10,7 +10,7 @@ #include "base/macros.h" #import "ui/base/test/cocoa_helper.h" #import "ui/events/test/cocoa_test_event_utils.h" -#import "ui/views/cocoa/cocoa_mouse_capture_delegate.h" +#import "ui/views_bridge_mac/cocoa_mouse_capture_delegate.h" // Simple test view that counts calls to -[NSView mouseDown:]. @interface CocoaMouseCaptureTestView : NSView { @@ -30,7 +30,7 @@ @end -namespace views { +namespace views_bridge_mac { namespace { // Simple capture delegate that just counts events forwarded. @@ -128,4 +128,4 @@ TEST_F(CocoaMouseCaptureTest, CaptureEvents) { EXPECT_EQ(2, [view mouseDownCount]); } -} // namespace views +} // namespace views_bridge_mac diff --git a/chromium/ui/views/cocoa/cocoa_window_move_loop.h b/chromium/ui/views/cocoa/cocoa_window_move_loop.h deleted file mode 100644 index e688884facb..00000000000 --- a/chromium/ui/views/cocoa/cocoa_window_move_loop.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2016 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 UI_VIEWS_COCOA_COCOA_WINDOW_MOVE_LOOP_H_ -#define UI_VIEWS_COCOA_COCOA_WINDOW_MOVE_LOOP_H_ - -#import <Cocoa/Cocoa.h> - -#include "base/callback.h" -#include "base/memory/weak_ptr.h" -#include "ui/views/widget/widget.h" - -namespace views { -class BridgedNativeWidget; - -// Used by views::BridgedNativeWidget when dragging detached tabs. -class CocoaWindowMoveLoop { - public: - CocoaWindowMoveLoop(BridgedNativeWidget* owner, - const NSPoint& initial_mouse_in_screen); - ~CocoaWindowMoveLoop(); - - // Initiates the drag until a mouse up event is observed, or End() is called. - Widget::MoveLoopResult Run(); - void End(); - - private: - enum LoopExitReason { - ENDED_EXTERNALLY, - MOUSE_UP, - WINDOW_DESTROYED, - }; - - BridgedNativeWidget* owner_; // Weak. Owns this. - - // Initial mouse location at the time before the CocoaWindowMoveLoop is - // created. - NSPoint initial_mouse_in_screen_; - - // Pointer to a stack variable holding the exit reason. - LoopExitReason* exit_reason_ref_ = nullptr; - base::Closure quit_closure_; - - // WeakPtrFactory for event monitor safety. - base::WeakPtrFactory<CocoaWindowMoveLoop> weak_factory_; - - DISALLOW_COPY_AND_ASSIGN(CocoaWindowMoveLoop); -}; - -} // namespace views - -#endif // UI_VIEWS_COCOA_COCOA_WINDOW_MOVE_LOOP_H_ diff --git a/chromium/ui/views/cocoa/cocoa_window_move_loop.mm b/chromium/ui/views/cocoa/cocoa_window_move_loop.mm deleted file mode 100644 index a306f9cfb5c..00000000000 --- a/chromium/ui/views/cocoa/cocoa_window_move_loop.mm +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2016 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 "ui/views/cocoa/cocoa_window_move_loop.h" - -#include "base/run_loop.h" -#include "ui/display/screen.h" -#import "ui/gfx/mac/coordinate_conversion.h" -#import "ui/views/cocoa/bridged_native_widget.h" - -// When event monitors process the events the full list of monitors is cached, -// and if we unregister the event monitor that's at the end of the list while -// processing the first monitor's handler -- the callback for the unregistered -// monitor will still be called even though it's unregistered. This will result -// in dereferencing an invalid pointer. -// -// WeakCocoaWindowMoveLoop is retained by the event monitor and stores weak -// pointer for the CocoaWindowMoveLoop, so there will be no invalid memory -// access. -@interface WeakCocoaWindowMoveLoop : NSObject { - @private - base::WeakPtr<views::CocoaWindowMoveLoop> weak_; -} -@end - -@implementation WeakCocoaWindowMoveLoop -- (id)initWithWeakPtr:(const base::WeakPtr<views::CocoaWindowMoveLoop>&)weak { - if ((self = [super init])) { - weak_ = weak; - } - return self; -} - -- (base::WeakPtr<views::CocoaWindowMoveLoop>&)weak { - return weak_; -} -@end - -namespace views { - -CocoaWindowMoveLoop::CocoaWindowMoveLoop( - BridgedNativeWidget* owner, - const NSPoint& initial_mouse_in_screen) - : owner_(owner), - initial_mouse_in_screen_(initial_mouse_in_screen), - weak_factory_(this) { -} - -CocoaWindowMoveLoop::~CocoaWindowMoveLoop() { - // Handle the pathological case, where |this| is destroyed while running. - if (exit_reason_ref_) { - *exit_reason_ref_ = WINDOW_DESTROYED; - quit_closure_.Run(); - } - - owner_ = nullptr; -} - -Widget::MoveLoopResult CocoaWindowMoveLoop::Run() { - LoopExitReason exit_reason = ENDED_EXTERNALLY; - exit_reason_ref_ = &exit_reason; - NSWindow* window = owner_->ns_window(); - const NSRect initial_frame = [window frame]; - - base::RunLoop run_loop; - quit_closure_ = run_loop.QuitClosure(); - - // Will be retained by the monitor handler block. - WeakCocoaWindowMoveLoop* weak_cocoa_window_move_loop = - [[[WeakCocoaWindowMoveLoop alloc] - initWithWeakPtr:weak_factory_.GetWeakPtr()] autorelease]; - - // Esc keypress is handled by EscapeTracker, which is installed by - // TabDragController. - NSEventMask mask = NSLeftMouseUpMask | NSLeftMouseDraggedMask; - auto handler = ^NSEvent*(NSEvent* event) { - CocoaWindowMoveLoop* strong = [weak_cocoa_window_move_loop weak].get(); - if (!strong || !strong->exit_reason_ref_) { - // By this point CocoaWindowMoveLoop was deleted while processing this - // same event, and this event monitor was not unregistered in time. See - // the WeakCocoaWindowMoveLoop comment above. - // Continue processing the event. - return event; - } - - if ([event type] == NSLeftMouseDragged) { - const NSPoint mouse_in_screen = [NSEvent mouseLocation]; - - const NSRect ns_frame = NSOffsetRect( - initial_frame, mouse_in_screen.x - initial_mouse_in_screen_.x, - mouse_in_screen.y - initial_mouse_in_screen_.y); - [window setFrame:ns_frame display:NO animate:NO]; - - return event; - } - - DCHECK_EQ([event type], NSLeftMouseUp); - *strong->exit_reason_ref_ = MOUSE_UP; - strong->quit_closure_.Run(); - return event; // Process the MouseUp. - }; - id monitor = - [NSEvent addLocalMonitorForEventsMatchingMask:mask handler:handler]; - - run_loop.Run(); - [NSEvent removeMonitor:monitor]; - - if (exit_reason != WINDOW_DESTROYED && exit_reason != ENDED_EXTERNALLY) { - exit_reason_ref_ = nullptr; // Ensure End() doesn't replace the reason. - owner_->EndMoveLoop(); // Deletes |this|. - } - - return exit_reason != MOUSE_UP ? Widget::MOVE_LOOP_CANCELED - : Widget::MOVE_LOOP_SUCCESSFUL; -} - -void CocoaWindowMoveLoop::End() { - if (exit_reason_ref_) { - DCHECK_EQ(*exit_reason_ref_, ENDED_EXTERNALLY); - // Ensure the destructor doesn't replace the reason. - exit_reason_ref_ = nullptr; - quit_closure_.Run(); - } -} - -} // namespace views diff --git a/chromium/ui/views/cocoa/drag_drop_client_mac.h b/chromium/ui/views/cocoa/drag_drop_client_mac.h index 2e1129957b4..e93d8a09441 100644 --- a/chromium/ui/views/cocoa/drag_drop_client_mac.h +++ b/chromium/ui/views/cocoa/drag_drop_client_mac.h @@ -14,6 +14,7 @@ #include "ui/base/dragdrop/os_exchange_data.h" #include "ui/views/views_export.h" #include "ui/views/widget/drop_helper.h" +#include "ui/views_bridge_mac/drag_drop_client.h" // This class acts as a bridge between NSPasteboardItem and OSExchangeData by // implementing NSPasteboardItemDataProvider and writing data from @@ -32,16 +33,16 @@ namespace test { class DragDropClientMacTest; } -class BridgedNativeWidget; +class BridgedNativeWidgetImpl; class View; // Implements drag and drop on MacViews. This class acts as a bridge between // the Views and native system's drag and drop. This class mimics // DesktopDragDropClientAuraX11. -class VIEWS_EXPORT DragDropClientMac { +class VIEWS_EXPORT DragDropClientMac : public views_bridge_mac::DragDropClient { public: - explicit DragDropClientMac(BridgedNativeWidget* bridge, View* root_view); - ~DragDropClientMac(); + DragDropClientMac(BridgedNativeWidgetImpl* bridge, View* root_view); + ~DragDropClientMac() override; // Initiates a drag and drop session. Returns the drag operation that was // applied at the end of the drag drop session. @@ -50,20 +51,14 @@ class VIEWS_EXPORT DragDropClientMac { int operation, ui::DragDropTypes::DragEventSource source); - // Called when mouse is dragged during a drag and drop. - NSDragOperation DragUpdate(id<NSDraggingInfo>); - - // Called when mouse is released during a drag and drop. - NSDragOperation Drop(id<NSDraggingInfo> sender); - - // Called when the drag and drop session has ended. - void EndDrag(); - - // Called when mouse leaves the drop area. - void DragExit(); - DropHelper* drop_helper() { return &drop_helper_; } + // views_bridge_mac::DragDropClient: + NSDragOperation DragUpdate(id<NSDraggingInfo>) override; + NSDragOperation Drop(id<NSDraggingInfo> sender) override; + void EndDrag() override; + void DragExit() override; + private: friend class test::DragDropClientMacTest; @@ -80,7 +75,7 @@ class VIEWS_EXPORT DragDropClientMac { int operation_; // The bridge between the content view and the drag drop client. - BridgedNativeWidget* bridge_; // Weak. Owns |this|. + BridgedNativeWidgetImpl* bridge_; // Weak. Owns |this|. // The closure for the drag and drop's run loop. base::Closure quit_closure_; diff --git a/chromium/ui/views/cocoa/drag_drop_client_mac.mm b/chromium/ui/views/cocoa/drag_drop_client_mac.mm index 78e7b0b3226..2df33355f9e 100644 --- a/chromium/ui/views/cocoa/drag_drop_client_mac.mm +++ b/chromium/ui/views/cocoa/drag_drop_client_mac.mm @@ -10,9 +10,9 @@ #import "ui/base/dragdrop/os_exchange_data_provider_mac.h" #include "ui/gfx/image/image_skia_util_mac.h" #include "ui/views/drag_utils.h" -#import "ui/views/cocoa/bridged_content_view.h" -#import "ui/views/cocoa/bridged_native_widget.h" #include "ui/views/widget/native_widget_mac.h" +#import "ui/views_bridge_mac/bridged_content_view.h" +#import "ui/views_bridge_mac/bridged_native_widget_impl.h" @interface CocoaDragDropDataProvider () - (id)initWithData:(const ui::OSExchangeData&)data; @@ -57,7 +57,7 @@ namespace views { -DragDropClientMac::DragDropClientMac(BridgedNativeWidget* bridge, +DragDropClientMac::DragDropClientMac(BridgedNativeWidgetImpl* bridge, View* root_view) : drop_helper_(root_view), operation_(0), diff --git a/chromium/ui/views/cocoa/drag_drop_client_mac_unittest.mm b/chromium/ui/views/cocoa/drag_drop_client_mac_unittest.mm index 9f3559de2ef..4202d53a348 100644 --- a/chromium/ui/views/cocoa/drag_drop_client_mac_unittest.mm +++ b/chromium/ui/views/cocoa/drag_drop_client_mac_unittest.mm @@ -13,12 +13,12 @@ #include "base/threading/thread_task_runner_handle.h" #import "ui/base/clipboard/clipboard_util_mac.h" #include "ui/gfx/image/image_unittest_util.h" -#import "ui/views/cocoa/bridged_native_widget.h" #import "ui/views/cocoa/bridged_native_widget_host_impl.h" #include "ui/views/test/widget_test.h" #include "ui/views/view.h" #include "ui/views/widget/native_widget_mac.h" #include "ui/views/widget/widget.h" +#import "ui/views_bridge_mac/bridged_native_widget_impl.h" using base::ASCIIToUTF16; @@ -156,7 +156,9 @@ class DragDropClientMacTest : public WidgetTest { public: DragDropClientMacTest() : widget_(new Widget) {} - DragDropClientMac* drag_drop_client() { return bridge_->drag_drop_client(); } + DragDropClientMac* drag_drop_client() { + return bridge_host_->drag_drop_client(); + } NSDragOperation DragUpdate(NSPasteboard* pasteboard) { DragDropClientMac* client = drag_drop_client(); @@ -186,9 +188,9 @@ class DragDropClientMacTest : public WidgetTest { gfx::Rect bounds(0, 0, 100, 100); widget_->SetBounds(bounds); - bridge_ = - NativeWidgetMac::GetBridgeForNativeWindow(widget_->GetNativeWindow()); - bridge_host_ = NativeWidgetMac::GetBridgeHostImplForNativeWindow( + bridge_ = BridgedNativeWidgetImpl::GetFromNativeWindow( + widget_->GetNativeWindow()); + bridge_host_ = BridgedNativeWidgetHostImpl::GetFromNativeWindow( widget_->GetNativeWindow()); widget_->Show(); @@ -207,7 +209,7 @@ class DragDropClientMacTest : public WidgetTest { protected: Widget* widget_ = nullptr; - BridgedNativeWidget* bridge_ = nullptr; + BridgedNativeWidgetImpl* bridge_ = nullptr; BridgedNativeWidgetHostImpl* bridge_host_ = nullptr; DragDropView* target_ = nullptr; base::scoped_nsobject<MockDraggingInfo> dragging_info_; diff --git a/chromium/ui/views/cocoa/native_widget_mac_nswindow.h b/chromium/ui/views/cocoa/native_widget_mac_nswindow.h deleted file mode 100644 index 8a0f51b791b..00000000000 --- a/chromium/ui/views/cocoa/native_widget_mac_nswindow.h +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright 2014 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 UI_VIEWS_COCOA_NATIVE_WIDGET_MAC_NSWINDOW_H_ -#define UI_VIEWS_COCOA_NATIVE_WIDGET_MAC_NSWINDOW_H_ - -#import <Cocoa/Cocoa.h> - -#import "ui/base/cocoa/command_dispatcher.h" -#include "ui/views/views_export.h" -#include "ui/views/widget/util_mac.h" - -@protocol WindowTouchBarDelegate; - -// Weak lets Chrome launch even if a future macOS doesn't have the below classes - -WEAK_IMPORT_ATTRIBUTE -@interface NSNextStepFrame : NSView -@end - -@class NSThemeFrame; - -VIEWS_EXPORT -@interface NativeWidgetMacNSWindowBorderlessFrame : NSNextStepFrame -@end - -VIEWS_EXPORT -@interface NativeWidgetMacNSWindowTitledFrame : NSThemeFrame -@end - -// The NSWindow used by BridgedNativeWidget. Provides hooks into AppKit that -// can only be accomplished by overriding methods. -VIEWS_EXPORT -@interface NativeWidgetMacNSWindow : NSWindow<CommandDispatchingWindow> - -// Set a CommandDispatcherDelegate, i.e. to implement key event handling. -- (void)setCommandDispatcherDelegate:(id<CommandDispatcherDelegate>)delegate; - -// Selector passed to [NSApp beginSheet:]. Forwards to [self delegate], if set. -- (void)sheetDidEnd:(NSWindow*)sheet - returnCode:(NSInteger)returnCode - contextInfo:(void*)contextInfo; - -// Set a WindowTouchBarDelegate to allow creation of a custom TouchBar when -// AppKit follows the responder chain and reaches the NSWindow when trying to -// create one. -- (void)setWindowTouchBarDelegate:(id<WindowTouchBarDelegate>)delegate; - -@end - -#endif // UI_VIEWS_COCOA_NATIVE_WIDGET_MAC_NSWINDOW_H_ diff --git a/chromium/ui/views/cocoa/native_widget_mac_nswindow.mm b/chromium/ui/views/cocoa/native_widget_mac_nswindow.mm deleted file mode 100644 index 3e5775bc184..00000000000 --- a/chromium/ui/views/cocoa/native_widget_mac_nswindow.mm +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright 2014 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. - -#import "ui/views/cocoa/native_widget_mac_nswindow.h" - -#include "base/mac/foundation_util.h" -#import "base/mac/sdk_forward_declarations.h" -#import "ui/base/cocoa/user_interface_item_command_handler.h" -#import "ui/base/cocoa/window_size_constants.h" -#import "ui/views/cocoa/bridged_native_widget.h" -#import "ui/views/cocoa/views_nswindow_delegate.h" -#import "ui/views/cocoa/window_touch_bar_delegate.h" -#include "ui/views/controls/menu/menu_controller.h" -#include "ui/views/widget/native_widget_mac.h" -#include "ui/views/widget/widget_delegate.h" - -@interface NSWindow (Private) -+ (Class)frameViewClassForStyleMask:(NSWindowStyleMask)windowStyle; -- (BOOL)hasKeyAppearance; -- (long long)_resizeDirectionForMouseLocation:(CGPoint)location; - -// Available in later point releases of 10.10. On 10.11+, use the public -// -performWindowDragWithEvent: instead. -- (void)beginWindowDragWithEvent:(NSEvent*)event; -@end - -@interface NativeWidgetMacNSWindow () -- (ViewsNSWindowDelegate*)viewsNSWindowDelegate; -- (views::Widget*)viewsWidget; -- (BOOL)hasViewsMenuActive; -- (id)rootAccessibilityObject; - -// Private API on NSWindow, determines whether the title is drawn on the title -// bar. The title is still visible in menus, Expose, etc. -- (BOOL)_isTitleHidden; -@end - -// Use this category to implement mouseDown: on multiple frame view classes -// with different superclasses. -@interface NSView (CRFrameViewAdditions) -- (void)cr_mouseDownOnFrameView:(NSEvent*)event; -@end - -@implementation NSView (CRFrameViewAdditions) -// If a mouseDown: falls through to the frame view, turn it into a window drag. -- (void)cr_mouseDownOnFrameView:(NSEvent*)event { - if ([self.window _resizeDirectionForMouseLocation:event.locationInWindow] != - -1) - return; - if (@available(macOS 10.11, *)) - [self.window performWindowDragWithEvent:event]; - else if ([self.window - respondsToSelector:@selector(beginWindowDragWithEvent:)]) - [self.window beginWindowDragWithEvent:event]; - else - NOTREACHED(); -} -@end - -@implementation NativeWidgetMacNSWindowTitledFrame -- (void)mouseDown:(NSEvent*)event { - [self cr_mouseDownOnFrameView:event]; - [super mouseDown:event]; -} -- (BOOL)usesCustomDrawing { - return NO; -} -@end - -@implementation NativeWidgetMacNSWindowBorderlessFrame -- (void)mouseDown:(NSEvent*)event { - [self cr_mouseDownOnFrameView:event]; - [super mouseDown:event]; -} -- (BOOL)usesCustomDrawing { - return NO; -} -@end - -@implementation NativeWidgetMacNSWindow { - @private - base::scoped_nsobject<CommandDispatcher> commandDispatcher_; - base::scoped_nsprotocol<id<UserInterfaceItemCommandHandler>> commandHandler_; - id<WindowTouchBarDelegate> touchBarDelegate_; // Weak. -} - -- (instancetype)initWithContentRect:(NSRect)contentRect - styleMask:(NSUInteger)windowStyle - backing:(NSBackingStoreType)bufferingType - defer:(BOOL)deferCreation { - DCHECK(NSEqualRects(contentRect, ui::kWindowSizeDeterminedLater)); - if ((self = [super initWithContentRect:ui::kWindowSizeDeterminedLater - styleMask:windowStyle - backing:bufferingType - defer:deferCreation])) { - commandDispatcher_.reset([[CommandDispatcher alloc] initWithOwner:self]); - } - return self; -} - -// This override doesn't do anything, but keeping it helps diagnose lifetime -// issues in crash stacktraces by inserting a symbol on NativeWidgetMacNSWindow. -- (void)dealloc { - [super dealloc]; -} - -// Public methods. - -- (void)setCommandDispatcherDelegate:(id<CommandDispatcherDelegate>)delegate { - [commandDispatcher_ setDelegate:delegate]; -} - -- (void)sheetDidEnd:(NSWindow*)sheet - returnCode:(NSInteger)returnCode - contextInfo:(void*)contextInfo { - // Note BridgedNativeWidget may have cleared [self delegate], in which case - // this will no-op. This indirection is necessary to handle AppKit invoking - // this selector via a posted task. See https://crbug.com/851376. - [[self viewsNSWindowDelegate] sheetDidEnd:sheet - returnCode:returnCode - contextInfo:contextInfo]; -} - -- (void)setWindowTouchBarDelegate:(id<WindowTouchBarDelegate>)delegate { - touchBarDelegate_ = delegate; -} - -// Private methods. - -- (ViewsNSWindowDelegate*)viewsNSWindowDelegate { - return base::mac::ObjCCastStrict<ViewsNSWindowDelegate>([self delegate]); -} - -- (views::Widget*)viewsWidget { - return [[self viewsNSWindowDelegate] nativeWidgetMac]->GetWidget(); -} - -- (BOOL)hasViewsMenuActive { - views::MenuController* menuController = - views::MenuController::GetActiveInstance(); - return menuController && menuController->owner() == [self viewsWidget]; -} - -- (id)rootAccessibilityObject { - views::Widget* widget = [self viewsWidget]; - return widget ? widget->GetRootView()->GetNativeViewAccessible() : nil; -} - -// NSWindow overrides. - -+ (Class)frameViewClassForStyleMask:(NSWindowStyleMask)windowStyle { - if (windowStyle & NSWindowStyleMaskTitled) { - if (Class customFrame = [NativeWidgetMacNSWindowTitledFrame class]) - return customFrame; - } else if (Class customFrame = - [NativeWidgetMacNSWindowBorderlessFrame class]) { - return customFrame; - } - return [super frameViewClassForStyleMask:windowStyle]; -} - -- (BOOL)_isTitleHidden { - if (![self delegate]) - return NO; - - return ![self viewsWidget]->widget_delegate()->ShouldShowWindowTitle(); -} - -// The base implementation returns YES if the window's frame view is a custom -// class, which causes undesirable changes in behavior. AppKit NSWindow -// subclasses are known to override it and return NO. -- (BOOL)_usesCustomDrawing { - return NO; -} - -// Ignore [super canBecome{Key,Main}Window]. The default is NO for windows with -// NSBorderlessWindowMask, which is not the desired behavior. -// Note these can be called via -[NSWindow close] while the widget is being torn -// down, so check for a delegate. -- (BOOL)canBecomeKeyWindow { - return [self delegate] && [self viewsWidget]->CanActivate(); -} - -- (BOOL)canBecomeMainWindow { - if (![self delegate]) - return NO; - - // Dialogs and bubbles shouldn't take large shadows away from their parent. - views::Widget* widget = [self viewsWidget]; - return widget->CanActivate() && - !views::NativeWidgetMac::GetBridgeForNativeWindow(self)->parent(); -} - -// Lets the traffic light buttons on the parent window keep their active state. -- (BOOL)hasKeyAppearance { - if ([self delegate] && [self viewsWidget]->IsAlwaysRenderAsActive()) - return YES; - return [super hasKeyAppearance]; -} - -// Override sendEvent to intercept window drag events and allow key events to be -// forwarded to a toolkit-views menu while it is active, and while still -// allowing any native subview to retain firstResponder status. -- (void)sendEvent:(NSEvent*)event { - // Let CommandDispatcher check if this is a redispatched event. - if ([commandDispatcher_ preSendEvent:event]) - return; - - NSEventType type = [event type]; - if ((type != NSKeyDown && type != NSKeyUp) || ![self hasViewsMenuActive]) { - [super sendEvent:event]; - return; - } - - // Send to the menu, after converting the event into an action message using - // the content view. - if (type == NSKeyDown) - [[self contentView] keyDown:event]; - else - [[self contentView] keyUp:event]; -} - -// Override window order functions to intercept other visibility changes. This -// is needed in addition to the -[NSWindow display] override because Cocoa -// hardly ever calls display, and reports -[NSWindow isVisible] incorrectly -// when ordering in a window for the first time. -- (void)orderWindow:(NSWindowOrderingMode)orderingMode - relativeTo:(NSInteger)otherWindowNumber { - [super orderWindow:orderingMode relativeTo:otherWindowNumber]; - [[self viewsNSWindowDelegate] onWindowOrderChanged:nil]; -} - -// NSResponder implementation. - -- (BOOL)performKeyEquivalent:(NSEvent*)event { - return [commandDispatcher_ performKeyEquivalent:event]; -} - -- (void)cursorUpdate:(NSEvent*)theEvent { - // The cursor provided by the delegate should only be applied within the - // content area. This is because we rely on the contentView to track the - // mouse cursor and forward cursorUpdate: messages up the responder chain. - // The cursorUpdate: isn't handled in BridgedContentView because views-style - // SetCapture() conflicts with the way tracking events are processed for - // the view during a drag. Since the NSWindow is still in the responder chain - // overriding cursorUpdate: here handles both cases. - if (!NSPointInRect([theEvent locationInWindow], [[self contentView] frame])) { - [super cursorUpdate:theEvent]; - return; - } - - NSCursor* cursor = [[self viewsNSWindowDelegate] cursor]; - if (cursor) - [cursor set]; - else - [super cursorUpdate:theEvent]; -} - -- (NSTouchBar*)makeTouchBar API_AVAILABLE(macos(10.12.2)) { - return touchBarDelegate_ ? [touchBarDelegate_ makeTouchBar] : nil; -} - -// CommandDispatchingWindow implementation. - -- (void)setCommandHandler:(id<UserInterfaceItemCommandHandler>)commandHandler { - commandHandler_.reset([commandHandler retain]); -} - -- (CommandDispatcher*)commandDispatcher { - return commandDispatcher_.get(); -} - -- (BOOL)defaultPerformKeyEquivalent:(NSEvent*)event { - return [super performKeyEquivalent:event]; -} - -- (BOOL)defaultValidateUserInterfaceItem: - (id<NSValidatedUserInterfaceItem>)item { - return [super validateUserInterfaceItem:item]; -} - -- (void)commandDispatch:(id)sender { - [commandDispatcher_ dispatch:sender forHandler:commandHandler_]; -} - -- (void)commandDispatchUsingKeyModifiers:(id)sender { - [commandDispatcher_ dispatchUsingKeyModifiers:sender - forHandler:commandHandler_]; -} - -// NSWindow overrides (NSUserInterfaceItemValidations implementation) - -- (BOOL)validateUserInterfaceItem:(id<NSValidatedUserInterfaceItem>)item { - return [commandDispatcher_ validateUserInterfaceItem:item - forHandler:commandHandler_]; -} - -// NSWindow overrides (NSAccessibility informal protocol implementation). - -- (id)accessibilityFocusedUIElement { - if (![self delegate]) - return [super accessibilityFocusedUIElement]; - - // The SDK documents this as "The deepest descendant of the accessibility - // hierarchy that has the focus" and says "if a child element does not have - // the focus, either return self or, if available, invoke the superclass's - // implementation." - // The behavior of NSWindow is usually to return null, except when the window - // is first shown, when it returns self. But in the second case, we can - // provide richer a11y information by reporting the views::RootView instead. - // Additionally, if we don't do this, VoiceOver reads out the partial a11y - // properties on the NSWindow and repeats them when focusing an item in the - // RootView's a11y group. See http://crbug.com/748221. - views::Widget* widget = [self viewsWidget]; - id superFocus = [super accessibilityFocusedUIElement]; - if (!widget || superFocus != self) - return superFocus; - - return widget->GetRootView()->GetNativeViewAccessible(); -} - -- (id)accessibilityAttributeValue:(NSString*)attribute { - // Check when NSWindow is asked for its title to provide the title given by - // the views::RootView (and WidgetDelegate::GetAccessibleWindowTitle()). For - // all other attributes, use what NSWindow provides by default since diverging - // from NSWindow's behavior can easily break VoiceOver integration. - if (![attribute isEqualToString:NSAccessibilityTitleAttribute]) - return [super accessibilityAttributeValue:attribute]; - - id viewsValue = - [[self rootAccessibilityObject] accessibilityAttributeValue:attribute]; - return viewsValue ? viewsValue - : [super accessibilityAttributeValue:attribute]; -} - -@end diff --git a/chromium/ui/views/cocoa/tooltip_manager_mac.h b/chromium/ui/views/cocoa/tooltip_manager_mac.h index 208fb4e948a..7fa612ca843 100644 --- a/chromium/ui/views/cocoa/tooltip_manager_mac.h +++ b/chromium/ui/views/cocoa/tooltip_manager_mac.h @@ -8,13 +8,19 @@ #include "base/macros.h" #include "ui/views/widget/tooltip_manager.h" -namespace views { +namespace views_bridge_mac { +namespace mojom { class BridgedNativeWidget; +} // namespace mojom +} // namespace views_bridge_mac + +namespace views { -// Manages native Cocoa tooltips for the given BridgedNativeWidget. +// Manages native Cocoa tooltips for the given BridgedNativeWidgetHostImpl. class TooltipManagerMac : public TooltipManager { public: - explicit TooltipManagerMac(BridgedNativeWidget* widget); + explicit TooltipManagerMac( + views_bridge_mac::mojom::BridgedNativeWidget* bridge); ~TooltipManagerMac() override; // TooltipManager: @@ -24,7 +30,8 @@ class TooltipManagerMac : public TooltipManager { void TooltipTextChanged(View* view) override; private: - BridgedNativeWidget* widget_; // Weak. Owns this. + views_bridge_mac::mojom::BridgedNativeWidget* + bridge_; // Weak. Owned by the owner of this. DISALLOW_COPY_AND_ASSIGN(TooltipManagerMac); }; diff --git a/chromium/ui/views/cocoa/tooltip_manager_mac.mm b/chromium/ui/views/cocoa/tooltip_manager_mac.mm index 99a1fb5d906..cbf12b14fda 100644 --- a/chromium/ui/views/cocoa/tooltip_manager_mac.mm +++ b/chromium/ui/views/cocoa/tooltip_manager_mac.mm @@ -4,11 +4,12 @@ #include "ui/views/cocoa/tooltip_manager_mac.h" +#include "base/no_destructor.h" #include "ui/base/cocoa/cocoa_base_utils.h" #include "ui/gfx/font_list.h" #import "ui/gfx/mac/coordinate_conversion.h" -#import "ui/views/cocoa/bridged_content_view.h" -#import "ui/views/cocoa/bridged_native_widget.h" +#import "ui/views_bridge_mac/bridged_content_view.h" +#import "ui/views_bridge_mac/bridged_native_widget_impl.h" namespace { @@ -19,9 +20,9 @@ const int kTooltipMaxWidthPixels = 250; namespace views { -TooltipManagerMac::TooltipManagerMac(BridgedNativeWidget* widget) - : widget_(widget) { -} +TooltipManagerMac::TooltipManagerMac( + views_bridge_mac::mojom::BridgedNativeWidget* bridge) + : bridge_(bridge) {} TooltipManagerMac::~TooltipManagerMac() { } @@ -31,20 +32,13 @@ int TooltipManagerMac::GetMaxWidth(const gfx::Point& location) const { } const gfx::FontList& TooltipManagerMac::GetFontList() const { - CR_DEFINE_STATIC_LOCAL(gfx::FontList, font_list, - (gfx::Font([NSFont toolTipsFontOfSize:0]))); - return font_list; + static base::NoDestructor<gfx::FontList> font_list( + []() { return gfx::Font([NSFont toolTipsFontOfSize:0]); }()); + return *font_list; } void TooltipManagerMac::UpdateTooltip() { - NSWindow* window = widget_->ns_window(); - BridgedContentView* view = widget_->ns_view(); - - NSPoint nspoint = - ui::ConvertPointFromScreenToWindow(window, [NSEvent mouseLocation]); - // Note: flip in the view's frame, which matches the window's contentRect. - gfx::Point point(nspoint.x, NSHeight([view frame]) - nspoint.y); - [view updateTooltipIfRequiredAt:point]; + bridge_->UpdateTooltip(); } void TooltipManagerMac::TooltipTextChanged(View* view) { diff --git a/chromium/ui/views/cocoa/views_nswindow_delegate.h b/chromium/ui/views/cocoa/views_nswindow_delegate.h deleted file mode 100644 index 00c2e13bbab..00000000000 --- a/chromium/ui/views/cocoa/views_nswindow_delegate.h +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright 2014 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 UI_VIEWS_COCOA_VIEWS_NSWINDOW_DELEGATE_H_ -#define UI_VIEWS_COCOA_VIEWS_NSWINDOW_DELEGATE_H_ - -#import <Cocoa/Cocoa.h> - -#import "base/mac/scoped_nsobject.h" -#include "ui/views/views_export.h" - -namespace views { -class NativeWidgetMac; -class BridgedNativeWidget; -} - -// The delegate set on the NSWindow when a views::BridgedNativeWidget is -// initialized. -VIEWS_EXPORT -@interface ViewsNSWindowDelegate : NSObject<NSWindowDelegate> { - @private - views::BridgedNativeWidget* parent_; // Weak. Owns this. - base::scoped_nsobject<NSCursor> cursor_; -} - -// The NativeWidgetMac that created the window this is attached to. Returns -// NULL if not created by NativeWidgetMac. -@property(nonatomic, readonly) views::NativeWidgetMac* nativeWidgetMac; - -// If set, the cursor set in -[NSResponder updateCursor:] when the window is -// reached along the responder chain. -@property(retain, nonatomic) NSCursor* cursor; - -// Initialize with the given |parent|. -- (id)initWithBridgedNativeWidget:(views::BridgedNativeWidget*)parent; - -// Notify that the window has been reordered in (or removed from) the window -// server's screen list. This is a substitute for -[NSWindowDelegate -// windowDidExpose:], which is only sent for nonretained windows (those without -// a backing store). |notification| is optional and can be set when redirecting -// a notification such as NSApplicationDidHideNotification. -- (void)onWindowOrderChanged:(NSNotification*)notification; - -// Notify that the system control tint changed. -- (void)onSystemControlTintChanged:(NSNotification*)notification; - -// Called on the delegate of a modal sheet when its modal session ends. -- (void)sheetDidEnd:(NSWindow*)sheet - returnCode:(NSInteger)returnCode - contextInfo:(void*)contextInfo; - -@end - -#endif // UI_VIEWS_COCOA_VIEWS_NSWINDOW_DELEGATE_H_ diff --git a/chromium/ui/views/cocoa/views_nswindow_delegate.mm b/chromium/ui/views/cocoa/views_nswindow_delegate.mm deleted file mode 100644 index bce74956126..00000000000 --- a/chromium/ui/views/cocoa/views_nswindow_delegate.mm +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2014 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. - -#import "ui/views/cocoa/views_nswindow_delegate.h" - -#include "base/bind.h" -#include "base/logging.h" -#include "base/threading/thread_task_runner_handle.h" -#import "ui/views/cocoa/bridged_content_view.h" -#import "ui/views/cocoa/bridged_native_widget.h" -#include "ui/views/cocoa/bridged_native_widget_host.h" -#include "ui/views/widget/native_widget_mac.h" - -@implementation ViewsNSWindowDelegate - -- (id)initWithBridgedNativeWidget:(views::BridgedNativeWidget*)parent { - DCHECK(parent); - if ((self = [super init])) { - parent_ = parent; - } - return self; -} - -- (views::NativeWidgetMac*)nativeWidgetMac { - return parent_->native_widget_mac(); -} - -- (NSCursor*)cursor { - return cursor_.get(); -} - -- (void)setCursor:(NSCursor*)newCursor { - if (cursor_.get() == newCursor) - return; - - cursor_.reset([newCursor retain]); - - // The window has a tracking rect that was installed in -[BridgedContentView - // initWithView:] that uses the NSTrackingCursorUpdate option. In the case - // where the window is the key window, that tracking rect will cause - // -cursorUpdate: to be sent up the responder chain, which will cause the - // cursor to be set when the message gets to the NativeWidgetMacNSWindow. - NSWindow* window = parent_->ns_window(); - [window resetCursorRects]; - - // However, if this window isn't the key window, that tracking area will have - // no effect. This is good if this window is just some top-level window that - // isn't key, but isn't so good if this window isn't key but is a child window - // of a window that is key. To handle that case, the case where the - // -cursorUpdate: message will never be sent, just set the cursor here. - // - // Only do this for non-key windows so that there will be no flickering - // between cursors set here and set elsewhere. - // - // (This is a known issue; see https://stackoverflow.com/questions/45712066/.) - if (![window isKeyWindow]) { - NSWindow* currentWindow = window; - // Walk up the window chain. If there is a key window in the window parent - // chain, then work around the issue and set the cursor. - while (true) { - NSWindow* parentWindow = [currentWindow parentWindow]; - if (!parentWindow) - break; - currentWindow = parentWindow; - if ([currentWindow isKeyWindow]) { - [(newCursor ? newCursor : [NSCursor arrowCursor]) set]; - break; - } - } - } -} - -- (void)onWindowOrderChanged:(NSNotification*)notification { - parent_->OnVisibilityChanged(); -} - -- (void)onSystemControlTintChanged:(NSNotification*)notification { - parent_->OnSystemControlTintChanged(); -} - -- (void)sheetDidEnd:(NSWindow*)sheet - returnCode:(NSInteger)returnCode - contextInfo:(void*)contextInfo { - [sheet orderOut:nil]; - parent_->OnWindowWillClose(); -} - -// NSWindowDelegate implementation. - -- (void)windowDidFailToEnterFullScreen:(NSWindow*)window { - // Cocoa should already have sent an (unexpected) windowDidExitFullScreen: - // notification, and the attempt to get back into fullscreen should fail. - // Nothing to do except verify |parent_| is no longer trying to fullscreen. - DCHECK(!parent_->target_fullscreen_state()); -} - -- (void)windowDidFailToExitFullScreen:(NSWindow*)window { - // Unlike entering fullscreen, windowDidFailToExitFullScreen: is sent *before* - // windowDidExitFullScreen:. Also, failing to exit fullscreen just dumps the - // window out of fullscreen without an animation; still sending the expected, - // windowDidExitFullScreen: notification. So, again, nothing to do here. - DCHECK(!parent_->target_fullscreen_state()); -} - -- (void)windowDidResize:(NSNotification*)notification { - parent_->OnSizeChanged(); -} - -- (void)windowDidMove:(NSNotification*)notification { - // Note: windowDidMove: is sent only once at the end of a window drag. There - // is also windowWillMove: sent at the start, also once. When the window is - // being moved by the WindowServer live updates are not provided. - parent_->OnPositionChanged(); -} - -- (void)windowDidBecomeKey:(NSNotification*)notification { - parent_->OnWindowKeyStatusChangedTo(true); -} - -- (void)windowDidResignKey:(NSNotification*)notification { - parent_->OnWindowKeyStatusChangedTo(false); -} - -- (BOOL)windowShouldClose:(id)sender { - views::NonClientView* nonClientView = - [self nativeWidgetMac]->GetWidget()->non_client_view(); - return !nonClientView || nonClientView->CanClose(); -} - -- (void)windowWillClose:(NSNotification*)notification { - NSWindow* window = parent_->ns_window(); - if (NSWindow* sheetParent = [window sheetParent]) { - // On no! Something called -[NSWindow close] on a sheet rather than calling - // -[NSWindow endSheet:] on its parent. If the modal session is not ended - // then the parent will never be able to show another sheet. But calling - // -endSheet: here will block the thread with an animation, so post a task. - // Use a block: The argument to -endSheet: must be retained, since it's the - // window that is closing and -performSelector: won't retain the argument - // (putting |window| on the stack above causes this block to retain it). - base::ThreadTaskRunnerHandle::Get()->PostTask( - FROM_HERE, base::BindOnce(base::RetainBlock(^{ - [sheetParent endSheet:window]; - }))); - } - DCHECK([window isEqual:[notification object]]); - parent_->OnWindowWillClose(); - // |self| may be deleted here (it's NSObject, so who really knows). - // |parent_| _will_ be deleted for sure. - - // Note OnWindowWillClose() will clear the NSWindow delegate. That is, |self|. - // That guarantees that the task possibly-posted above will never call into - // our -sheetDidEnd:. (The task's purpose is just to unblock the modal session - // on the parent window.) - DCHECK(![window delegate]); -} - -- (void)windowDidMiniaturize:(NSNotification*)notification { - parent_->host()->OnWindowMiniaturizedChanged(true); - parent_->OnVisibilityChanged(); -} - -- (void)windowDidDeminiaturize:(NSNotification*)notification { - parent_->host()->OnWindowMiniaturizedChanged(false); - parent_->OnVisibilityChanged(); -} - -- (void)windowDidChangeBackingProperties:(NSNotification*)notification { - parent_->OnBackingPropertiesChanged(); -} - -- (void)windowWillEnterFullScreen:(NSNotification*)notification { - parent_->OnFullscreenTransitionStart(true); -} - -- (void)windowDidEnterFullScreen:(NSNotification*)notification { - parent_->OnFullscreenTransitionComplete(true); -} - -- (void)windowWillExitFullScreen:(NSNotification*)notification { - parent_->OnFullscreenTransitionStart(false); -} - -- (void)windowDidExitFullScreen:(NSNotification*)notification { - parent_->OnFullscreenTransitionComplete(false); -} - -// Allow non-resizable windows (without NSResizableWindowMask) to fill the -// screen in fullscreen mode. This only happens when -// -[NSWindow toggleFullscreen:] is called since non-resizable windows have no -// fullscreen button. Without this they would only enter fullscreen at their -// current size. -- (NSSize)window:(NSWindow*)window - willUseFullScreenContentSize:(NSSize)proposedSize { - return proposedSize; -} - -// Override to correctly position modal dialogs. -- (NSRect)window:(NSWindow*)window - willPositionSheet:(NSWindow*)sheet - usingRect:(NSRect)defaultSheetLocation { - // As per NSWindowDelegate documentation, the origin indicates the top left - // point of the host frame in window coordinates. The width changes the - // animation from vertical to trapezoid if it is smaller than the width of the - // dialog. The height is ignored but should be set to zero. - return NSMakeRect(0, [self nativeWidgetMac]->SheetPositionY(), - NSWidth(defaultSheetLocation), 0); -} - -@end diff --git a/chromium/ui/views/cocoa/views_scrollbar_bridge.h b/chromium/ui/views/cocoa/views_scrollbar_bridge.h deleted file mode 100644 index eb070a161c5..00000000000 --- a/chromium/ui/views/cocoa/views_scrollbar_bridge.h +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright 2016 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 UI_VIEWS_COCOA_VIEWS_SCROLLBAR_BRIDGE_DELEGATE_H_ -#define UI_VIEWS_COCOA_VIEWS_SCROLLBAR_BRIDGE_DELEGATE_H_ - -#import <Cocoa/Cocoa.h> - -#import "base/mac/scoped_nsobject.h" -#include "ui/views/views_export.h" - -// The delegate set to ViewsScrollbarBridge. -class ViewsScrollbarBridgeDelegate { - public: - // Invoked by ViewsScrollbarBridge when the system informs the process that - // the preferred scroller style has changed - virtual void OnScrollerStyleChanged() = 0; -}; - -// A bridge to NSScroller managed by NativeCocoaScrollbar. Serves as a helper -// class to bridge NSScroller notifications and functions to CocoaScrollbar. -@interface ViewsScrollbarBridge : NSObject { - @private - ViewsScrollbarBridgeDelegate* delegate_; // Weak. Owns this. -} - -// Initializes with the given delegate and registers for notifications on -// scroller style changes. -- (id)initWithDelegate:(ViewsScrollbarBridgeDelegate*)delegate; - -// Sets |delegate_| to nullptr. --(void)clearDelegate; - -// Returns the style of scrollers that OSX is using. -+ (NSScrollerStyle)getPreferredScrollerStyle; - -@end - -#endif
\ No newline at end of file diff --git a/chromium/ui/views/cocoa/views_scrollbar_bridge.mm b/chromium/ui/views/cocoa/views_scrollbar_bridge.mm deleted file mode 100644 index d80dece0e7b..00000000000 --- a/chromium/ui/views/cocoa/views_scrollbar_bridge.mm +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2016 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. - -#import "ui/views/cocoa/views_scrollbar_bridge.h" - -#import "base/mac/sdk_forward_declarations.h" - -@interface ViewsScrollbarBridge () - -// Called when we receive a NSPreferredScrollerStyleDidChangeNotification. -- (void)onScrollerStyleChanged:(NSNotification*)notification; - -@end - -@implementation ViewsScrollbarBridge - -- (id)initWithDelegate:(ViewsScrollbarBridgeDelegate*)delegate { - if ((self = [super init])) { - delegate_ = delegate; - [[NSNotificationCenter defaultCenter] - addObserver:self - selector:@selector(onScrollerStyleChanged:) - name:NSPreferredScrollerStyleDidChangeNotification - object:nil]; - } - return self; -} - -- (void)dealloc { - DCHECK(!delegate_); - [super dealloc]; -} - -- (void)clearDelegate { - delegate_ = nullptr; - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - -- (void)onScrollerStyleChanged:(NSNotification*)notification { - if (delegate_) - delegate_->OnScrollerStyleChanged(); -} - -+ (NSScrollerStyle)getPreferredScrollerStyle { - return [NSScroller preferredScrollerStyle]; -} - -@end diff --git a/chromium/ui/views/cocoa/widget_owner_nswindow_adapter.h b/chromium/ui/views/cocoa/widget_owner_nswindow_adapter.h deleted file mode 100644 index 8240bead793..00000000000 --- a/chromium/ui/views/cocoa/widget_owner_nswindow_adapter.h +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2015 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 UI_VIEWS_COCOA_WIDGET_OWNER_NSWINDOW_ADAPTER_H_ -#define UI_VIEWS_COCOA_WIDGET_OWNER_NSWINDOW_ADAPTER_H_ - -#import "base/mac/scoped_nsobject.h" -#include "base/macros.h" -#import "ui/views/cocoa/bridged_native_widget_owner.h" - -@class NSView; -@class NSWindow; -@class WidgetOwnerNSWindowAdapterBridge; - -namespace views { - -// An adapter that allows a views::Widget to be owned by an NSWindow that is not -// backed by another BridgedNativeWidget. -class WidgetOwnerNSWindowAdapter : public BridgedNativeWidgetOwner { - public: - // Create an adapter that will own |child|, tying its lifetime with the - // NSWindow containing |anchor_view|. The object is self-deleting, via a call - // to RemoveChildWindow() made in child->OnWindowWillClose(). - WidgetOwnerNSWindowAdapter(BridgedNativeWidget* child, NSView* anchor_view); - - // Called when the owning window is closing. - void OnWindowWillClose(); - - // Called when the owning window is hidden or shown. - void OnWindowDidChangeOcclusionState(); - - // Overridden from BridgedNativeWidgetOwner: - NSWindow* GetNSWindow() override; - gfx::Vector2d GetChildWindowOffset() const override; - bool IsVisibleParent() const override; - void RemoveChildWindow(BridgedNativeWidget* child) override; - - private: - // Self-deleting. - ~WidgetOwnerNSWindowAdapter() override; - - BridgedNativeWidget* child_; // Weak. Owned by its NativeWidgetMac. - base::scoped_nsobject<NSView> anchor_view_; - base::scoped_nsobject<NSWindow> anchor_window_; - base::scoped_nsobject<WidgetOwnerNSWindowAdapterBridge> observer_bridge_; - - DISALLOW_COPY_AND_ASSIGN(WidgetOwnerNSWindowAdapter); -}; - -} // namespace views - -#endif // UI_VIEWS_COCOA_WIDGET_OWNER_NSWINDOW_ADAPTER_H_ diff --git a/chromium/ui/views/cocoa/widget_owner_nswindow_adapter.mm b/chromium/ui/views/cocoa/widget_owner_nswindow_adapter.mm deleted file mode 100644 index 3585b78344e..00000000000 --- a/chromium/ui/views/cocoa/widget_owner_nswindow_adapter.mm +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright 2015 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. - -#import "ui/views/cocoa/widget_owner_nswindow_adapter.h" - -#import <Cocoa/Cocoa.h> - -#include "base/logging.h" -#include "base/mac/sdk_forward_declarations.h" -#include "ui/gfx/geometry/rect.h" -#include "ui/gfx/geometry/vector2d.h" -#import "ui/gfx/mac/coordinate_conversion.h" -#import "ui/views/cocoa/bridged_native_widget.h" - -// Bridges an AppKit observer to observe when the (non-views) NSWindow owning a -// views::Widget will close or change occlusion state. -@interface WidgetOwnerNSWindowAdapterBridge : NSObject { - @private - views::WidgetOwnerNSWindowAdapter* adapter_; // Weak. Owns us. -} -- (instancetype)initWithAdapter:(views::WidgetOwnerNSWindowAdapter*)adapter; -- (void)windowWillClose:(NSNotification*)notification; -- (void)windowDidChangeOcclusionState:(NSNotification*)notification; -@end - -@implementation WidgetOwnerNSWindowAdapterBridge - -- (instancetype)initWithAdapter:(views::WidgetOwnerNSWindowAdapter*)adapter { - if ((self = [super init])) - adapter_ = adapter; - return self; -} - -- (void)windowWillClose:(NSNotification*)notification { - adapter_->OnWindowWillClose(); -} - -- (void)windowDidChangeOcclusionState:(NSNotification*)notification { - adapter_->OnWindowDidChangeOcclusionState(); -} - -@end - -namespace views { - -WidgetOwnerNSWindowAdapter::WidgetOwnerNSWindowAdapter( - BridgedNativeWidget* child, - NSView* anchor_view) - : child_(child), - anchor_view_([anchor_view retain]), - observer_bridge_( - [[WidgetOwnerNSWindowAdapterBridge alloc] initWithAdapter:this]) { - - // Although the |anchor_view| must be in an NSWindow when the child dialog is - // created, it's permitted for the |anchor_view| to be removed from its view - // hierarchy before the child dialog window is fully removed from screen. When - // this happens, [anchor_view_ window] will become nil, so retain both. - anchor_window_.reset([[anchor_view_ window] retain]); - DCHECK(anchor_window_); - - [[NSNotificationCenter defaultCenter] - addObserver:observer_bridge_ - selector:@selector(windowWillClose:) - name:NSWindowWillCloseNotification - object:anchor_window_]; - - // BridgedNativeWidget removes NSWindow parent/child relationships for hidden - // windows. Observe when the parent's visibility changes so they can be - // reconnected. - [[NSNotificationCenter defaultCenter] - addObserver:observer_bridge_ - selector:@selector(windowDidChangeOcclusionState:) - name:NSWindowDidChangeOcclusionStateNotification - object:anchor_window_]; - - // On a deminiaturize, the occlusion state change may occur before -[NSWindow - // isVisible] returns YES. So observe NSWindowDidDeminiaturizeNotification. - // This allows the adapter to check again at the end of the deminiaturize. - [[NSNotificationCenter defaultCenter] - addObserver:observer_bridge_ - selector:@selector(windowDidChangeOcclusionState:) - name:NSWindowDidDeminiaturizeNotification - object:anchor_window_]; -} - -void WidgetOwnerNSWindowAdapter::OnWindowWillClose() { - // Retain the child window before closing it. If the last reference to the - // NSWindow goes away inside -[NSWindow close], then bad stuff can happen. - // See e.g. http://crbug.com/616701. - base::scoped_nsobject<NSWindow> child_window(child_->ns_window(), - base::scoped_policy::RETAIN); - - // AppKit child window relationships break when the windows are not visible, - // so if the child is not visible, it won't currently be a child. - if (![child_window isVisible]) - DCHECK(![child_window parentWindow] && ![child_window sheetParent]); - DCHECK([child_window delegate]); - - [child_window close]; - // Note: |this| will be deleted here. - - DCHECK(![child_window parentWindow]); - DCHECK(![child_window delegate]); -} - -void WidgetOwnerNSWindowAdapter::OnWindowDidChangeOcclusionState() { - // The adapter only needs to handle a parent "show", since the only way it - // should be hidden is via -[NSApp hide], and all BridgedNativeWidgets - // subscribe to NSApplicationDidHideNotification already. - if (![anchor_window_ isVisible]) - return; - - if (child_->window_visible()) { - // A sheet should never have a parentWindow, otherwise dismissing the sheet - // causes graphical glitches (http://crbug.com/605098). Non-sheets should - // always have a parentWindow. - DCHECK([child_->ns_window() isSheet] != - !![child_->ns_window() parentWindow]); - DCHECK(child_->wants_to_be_visible()); - return; - } - - // The parent relationship should have been removed when the child was hidden. - DCHECK(![child_->ns_window() parentWindow]); - if (!child_->wants_to_be_visible()) - return; - - [child_->ns_window() orderWindow:NSWindowAbove - relativeTo:[anchor_window_ windowNumber]]; - - // Ordering the window should add back the relationship (unless it's a sheet). - DCHECK([child_->ns_window() isSheet] != !![child_->ns_window() parentWindow]); - DCHECK(child_->window_visible()); -} - -NSWindow* WidgetOwnerNSWindowAdapter::GetNSWindow() { - return anchor_window_; -} - -gfx::Vector2d WidgetOwnerNSWindowAdapter::GetChildWindowOffset() const { - NSRect rect_in_window = - [anchor_view_ convertRect:[anchor_view_ bounds] toView:nil]; - NSRect rect_in_screen = [anchor_window_ convertRectToScreen:rect_in_window]; - // Ensure we anchor off the top-left of |anchor_view_| (rect_in_screen.origin - // is the bottom-left of the view). - NSPoint anchor_in_screen = - NSMakePoint(NSMinX(rect_in_screen), NSMaxY(rect_in_screen)); - return gfx::ScreenPointFromNSPoint(anchor_in_screen).OffsetFromOrigin(); -} - -bool WidgetOwnerNSWindowAdapter::IsVisibleParent() const { - return [anchor_window_ isVisible]; -} - -void WidgetOwnerNSWindowAdapter::RemoveChildWindow(BridgedNativeWidget* child) { - DCHECK_EQ(child, child_); - [GetNSWindow() removeChildWindow:child->ns_window()]; - delete this; -} - -WidgetOwnerNSWindowAdapter::~WidgetOwnerNSWindowAdapter() { - [[NSNotificationCenter defaultCenter] removeObserver:observer_bridge_]; -} - -} // namespace views diff --git a/chromium/ui/views/cocoa/window_touch_bar_delegate.h b/chromium/ui/views/cocoa/window_touch_bar_delegate.h deleted file mode 100644 index daef5466ed6..00000000000 --- a/chromium/ui/views/cocoa/window_touch_bar_delegate.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2018 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 UI_VIEWS_COCOA_WINDOW_TOUCH_BAR_DELEGATE_H_ -#define UI_VIEWS_COCOA_WINDOW_TOUCH_BAR_DELEGATE_H_ - -#import <Cocoa/Cocoa.h> - -#include "base/mac/availability.h" - -// Bridge delegate class for NativeWidgetMacNSWindow and -// BrowserWindowTouchBarMac. -@protocol WindowTouchBarDelegate<NSObject> - -// Creates and returns a touch bar for the browser window. -- (NSTouchBar*)makeTouchBar API_AVAILABLE(macos(10.12.2)); - -@end - -#endif // UI_VIEWS_COCOA_WINDOW_TOUCH_BAR_DELEGATE_H_
\ No newline at end of file diff --git a/chromium/ui/views/color_chooser/color_chooser_view.cc b/chromium/ui/views/color_chooser/color_chooser_view.cc index d844b6a58f2..19eab2ec3d5 100644 --- a/chromium/ui/views/color_chooser/color_chooser_view.cc +++ b/chromium/ui/views/color_chooser/color_chooser_view.cc @@ -15,10 +15,12 @@ #include "cc/paint/paint_shader.h" #include "third_party/skia/include/core/SkPath.h" #include "third_party/skia/include/effects/SkGradientShader.h" +#include "ui/base/l10n/l10n_util.h" #include "ui/events/event.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/gfx/canvas.h" #include "ui/gfx/geometry/insets.h" +#include "ui/strings/grit/ui_strings.h" #include "ui/views/background.h" #include "ui/views/border.h" #include "ui/views/color_chooser/color_chooser_listener.h" @@ -389,6 +391,8 @@ ColorChooserView::ColorChooserView(ColorChooserListener* listener, textfield_ = new Textfield(); textfield_->set_controller(this); textfield_->SetDefaultWidthInChars(kTextfieldLengthInChars); + textfield_->SetAccessibleName( + l10n_util::GetStringUTF16(IDS_APP_ACCNAME_COLOR_CHOOSER_HEX_INPUT)); layout->AddView(textfield_); selected_color_patch_ = new SelectedColorPatchView(); layout->AddView(selected_color_patch_); diff --git a/chromium/ui/views/controls/animated_image_view.cc b/chromium/ui/views/controls/animated_image_view.cc new file mode 100644 index 00000000000..2d56a88b440 --- /dev/null +++ b/chromium/ui/views/controls/animated_image_view.cc @@ -0,0 +1,139 @@ +// Copyright 2018 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 "ui/views/controls/animated_image_view.h" + +#include <utility> + +#include "base/logging.h" +#include "ui/gfx/canvas.h" +#include "ui/gfx/skottie_wrapper.h" +#include "ui/views/widget/widget.h" + +namespace views { +namespace { + +bool AreAnimatedImagesEqual(const gfx::SkiaVectorAnimation& animation_1, + const gfx::SkiaVectorAnimation& animation_2) { + // In rare cases this may return false, even if the animated images are backed + // by the same resource file. + return animation_1.skottie() == animation_2.skottie(); +} + +} // namespace + +AnimatedImageView::AnimatedImageView() = default; + +AnimatedImageView::~AnimatedImageView() = default; + +void AnimatedImageView::SetAnimatedImage( + std::unique_ptr<gfx::SkiaVectorAnimation> animated_image) { + if (animated_image_ && + AreAnimatedImagesEqual(*animated_image, *animated_image_)) { + Stop(); + return; + } + + gfx::Size preferred_size(GetPreferredSize()); + animated_image_ = std::move(animated_image); + + // Stop the animation to reset it. + Stop(); + + if (preferred_size != GetPreferredSize()) + PreferredSizeChanged(); + SchedulePaint(); +} + +void AnimatedImageView::Play() { + DCHECK(animated_image_); + DCHECK_EQ(state_, State::kStopped); + + state_ = State::kPlaying; + + // We cannot play the animation unless we have a valid compositor. + if (!compositor_) + return; + + // Ensure the class is added as an observer to receive clock ticks. + if (!compositor_->HasAnimationObserver(this)) + compositor_->AddAnimationObserver(this); + + animated_image_->Start(); +} + +void AnimatedImageView::Stop() { + DCHECK(animated_image_); + if (compositor_) + compositor_->RemoveAnimationObserver(this); + + animated_image_->Stop(); + state_ = State::kStopped; +} + +gfx::Size AnimatedImageView::GetImageSize() const { + return image_size_.value_or( + animated_image_ ? animated_image_->GetOriginalSize() : gfx::Size()); +} + +void AnimatedImageView::OnPaint(gfx::Canvas* canvas) { + View::OnPaint(canvas); + if (!animated_image_) + return; + canvas->Save(); + canvas->Translate(GetImageBounds().origin().OffsetFromOrigin()); + + // OnPaint may be called before clock tick was received; in that case just + // paint the first frame. + if (!previous_timestamp_.is_null() && state_ != State::kStopped) + animated_image_->Paint(canvas, previous_timestamp_, GetImageSize()); + else + animated_image_->PaintFrame(canvas, 0, GetImageSize()); + + canvas->Restore(); +} + +const char* AnimatedImageView::GetClassName() const { + return "AnimatedImageView"; +} + +void AnimatedImageView::NativeViewHierarchyChanged() { + // When switching a window from one display to another, the compositor + // associated with the widget changes. + AddedToWidget(); +} + +void AnimatedImageView::AddedToWidget() { + ui::Compositor* compositor = GetWidget()->GetCompositor(); + DCHECK(compositor); + if (compositor_ != compositor) { + if (compositor_ && compositor_->HasAnimationObserver(this)) + compositor_->RemoveAnimationObserver(this); + compositor_ = compositor; + } +} + +void AnimatedImageView::RemovedFromWidget() { + if (compositor_) { + Stop(); + if (compositor_->HasAnimationObserver(this)) + compositor_->RemoveAnimationObserver(this); + compositor_ = nullptr; + } +} + +void AnimatedImageView::OnAnimationStep(base::TimeTicks timestamp) { + previous_timestamp_ = timestamp; + SchedulePaint(); +} + +void AnimatedImageView::OnCompositingShuttingDown(ui::Compositor* compositor) { + if (compositor_ == compositor) { + Stop(); + compositor_->RemoveAnimationObserver(this); + compositor_ = nullptr; + } +} + +} // namespace views diff --git a/chromium/ui/views/controls/animated_image_view.h b/chromium/ui/views/controls/animated_image_view.h new file mode 100644 index 00000000000..c62fe5c3723 --- /dev/null +++ b/chromium/ui/views/controls/animated_image_view.h @@ -0,0 +1,91 @@ +// Copyright 2018 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 UI_VIEWS_CONTROLS_ANIMATED_IMAGE_VIEW_H_ +#define UI_VIEWS_CONTROLS_ANIMATED_IMAGE_VIEW_H_ + +#include <memory> + +#include "base/macros.h" +#include "ui/gfx/skia_vector_animation.h" +#include "ui/views/controls/image_view_base.h" + +namespace gfx { +class SkiaVectorAnimation; +class Canvas; +} // namespace gfx + +namespace ui { +class Compositor; +} + +namespace views { + +///////////////////////////////////////////////////////////////////////////// +// +// AnimatedImageView class. +// +// An AnimatedImageView can display a skia vector animation. The animation paint +// size can be set via SetImageSize. The animation is stopped by default. +// Use this over AnimatedIconView if you want to play a skottie animation file. +// +///////////////////////////////////////////////////////////////////////////// +class VIEWS_EXPORT AnimatedImageView : public ImageViewBase, + public ui::CompositorAnimationObserver { + public: + enum class State { + kPlaying, // The animation is currently playing. + kStopped // The animation is stopped and paint will raster the first + // frame. + }; + + AnimatedImageView(); + ~AnimatedImageView() override; + + // Set the animated image that should be displayed. Setting an animated image + // will result in stopping the current animation. + void SetAnimatedImage( + std::unique_ptr<gfx::SkiaVectorAnimation> animated_image); + + // Plays the animation in loop. + void Play(); + + // Stops any animation and resets it to the start frame. + void Stop(); + + private: + friend class AnimatedImageViewTest; + + // Overridden from View: + void OnPaint(gfx::Canvas* canvas) override; + const char* GetClassName() const override; + void NativeViewHierarchyChanged() override; + void AddedToWidget() override; + void RemovedFromWidget() override; + + // Overridden from ui::CompositorAnimationObserver: + void OnAnimationStep(base::TimeTicks timestamp) override; + void OnCompositingShuttingDown(ui::Compositor* compositor) override; + + // Overridden from ImageViewBase: + gfx::Size GetImageSize() const override; + + // The current state of the animation. + State state_ = State::kStopped; + + // The compositor associated with the widget of this view. + ui::Compositor* compositor_ = nullptr; + + // The most recent timestamp at which a paint was scheduled for this view. + base::TimeTicks previous_timestamp_; + + // The underlying skia vector animation. + std::unique_ptr<gfx::SkiaVectorAnimation> animated_image_; + + DISALLOW_COPY_AND_ASSIGN(AnimatedImageView); +}; + +} // namespace views + +#endif // UI_VIEWS_CONTROLS_ANIMATED_IMAGE_VIEW_H_ diff --git a/chromium/ui/views/controls/button/button.cc b/chromium/ui/views/controls/button/button.cc index 9656e5dc4a3..ebf086404e3 100644 --- a/chromium/ui/views/controls/button/button.cc +++ b/chromium/ui/views/controls/button/button.cc @@ -170,6 +170,12 @@ void Button::SetFocusPainter(std::unique_ptr<Painter> focus_painter) { focus_painter_ = std::move(focus_painter); } +void Button::SetHighlighted(bool bubble_visible) { + AnimateInkDrop(bubble_visible ? views::InkDropState::ACTIVATED + : views::InkDropState::DEACTIVATED, + nullptr); +} + //////////////////////////////////////////////////////////////////////////////// // Button, View overrides: diff --git a/chromium/ui/views/controls/button/button.h b/chromium/ui/views/controls/button/button.h index b5347ff0e00..050512ffa34 100644 --- a/chromium/ui/views/controls/button/button.h +++ b/chromium/ui/views/controls/button/button.h @@ -151,6 +151,9 @@ class VIEWS_EXPORT Button : public InkDropHostView, void SetFocusPainter(std::unique_ptr<Painter> focus_painter); + // Highlights the ink drop for the button. + void SetHighlighted(bool bubble_visible); + // Overridden from View: void OnEnabledChanged() override; const char* GetClassName() const override; diff --git a/chromium/ui/views/controls/button/checkbox.cc b/chromium/ui/views/controls/button/checkbox.cc index 91e398ebfe3..1a84d97ab26 100644 --- a/chromium/ui/views/controls/button/checkbox.cc +++ b/chromium/ui/views/controls/button/checkbox.cc @@ -55,7 +55,10 @@ Checkbox::~Checkbox() { } void Checkbox::SetChecked(bool checked) { - checked_ = checked; + if (checked_ != checked) { + checked_ = checked; + NotifyAccessibilityEvent(ax::mojom::Event::kCheckedStateChanged, true); + } UpdateImage(); } diff --git a/chromium/ui/views/controls/button/image_button_factory.cc b/chromium/ui/views/controls/button/image_button_factory.cc index 649002d85ad..baf33d4ef8d 100644 --- a/chromium/ui/views/controls/button/image_button_factory.cc +++ b/chromium/ui/views/controls/button/image_button_factory.cc @@ -31,7 +31,8 @@ void SetImageFromVectorIcon(ImageButton* button, SkColor related_text_color) { const SkColor icon_color = color_utils::DeriveDefaultIconColor(related_text_color); - const SkColor disabled_color = SkColorSetA(icon_color, 0xff / 2); + const SkColor disabled_color = + SkColorSetA(icon_color, gfx::kDisabledControlAlpha); button->SetImage(Button::STATE_NORMAL, gfx::CreateVectorIcon(icon, icon_color)); button->SetImage(Button::STATE_DISABLED, diff --git a/chromium/ui/views/controls/button/label_button_unittest.cc b/chromium/ui/views/controls/button/label_button_unittest.cc index 4a1ac7683d6..966ab6fec75 100644 --- a/chromium/ui/views/controls/button/label_button_unittest.cc +++ b/chromium/ui/views/controls/button/label_button_unittest.cc @@ -543,8 +543,6 @@ class InkDropLabelButtonTest : public ViewsTestBase { // ViewsTestBase: void SetUp() override { - base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( - switches::kTopChromeMD, switches::kTopChromeMDMaterial); ViewsTestBase::SetUp(); // Create a widget so that the Button can query the hover state @@ -568,7 +566,6 @@ class InkDropLabelButtonTest : public ViewsTestBase { void TearDown() override { widget_.reset(); ViewsTestBase::TearDown(); - ui::test::MaterialDesignControllerTestAPI::Uninitialize(); } protected: diff --git a/chromium/ui/views/controls/button/md_text_button.cc b/chromium/ui/views/controls/button/md_text_button.cc index 4e6011dc24e..0d9f69cab39 100644 --- a/chromium/ui/views/controls/button/md_text_button.cc +++ b/chromium/ui/views/controls/button/md_text_button.cc @@ -7,7 +7,6 @@ #include "base/i18n/case_conversion.h" #include "base/memory/ptr_util.h" #include "build/build_config.h" -#include "ui/base/material_design/material_design_controller.h" #include "ui/gfx/canvas.h" #include "ui/gfx/color_palette.h" #include "ui/gfx/color_utils.h" @@ -197,12 +196,9 @@ void MdTextButton::UpdatePadding() { style::GetFont(style::CONTEXT_BUTTON_MD, style::STYLE_PRIMARY) .GetFontSize(); // TODO(tapted): This should get |target_height| using LayoutProvider:: - // GetControlHeightForFont(). It can't because that only returns a correct - // result with --secondary-ui-md, and MdTextButtons appear in top chrome - // without that. - const int base_height = - ui::MaterialDesignController::IsNewerMaterialUi() ? 32 : 28; - int target_height = std::max(base_height + size_delta * 2, + // GetControlHeightForFont(). + constexpr int kBaseHeight = 32; + int target_height = std::max(kBaseHeight + size_delta * 2, label()->font_list().GetFontSize() * 2); int label_height = label()->GetPreferredSize().height(); diff --git a/chromium/ui/views/controls/button/radio_button.cc b/chromium/ui/views/controls/button/radio_button.cc index 9adf4949221..6113742ab7f 100644 --- a/chromium/ui/views/controls/button/radio_button.cc +++ b/chromium/ui/views/controls/button/radio_button.cc @@ -99,7 +99,7 @@ void RadioButton::SetChecked(bool checked) { if (container) { Views other; container->GetViewsInGroup(GetGroup(), &other); - for (Views::iterator i(other.begin()); i != other.end(); ++i) { + for (auto i(other.begin()); i != other.end(); ++i) { if (*i != this) { if (strcmp((*i)->GetClassName(), kViewClassName)) { NOTREACHED() << "radio-button-nt has same group as other non " diff --git a/chromium/ui/views/controls/focus_ring.cc b/chromium/ui/views/controls/focus_ring.cc index 148c22ae909..bc9597596ce 100644 --- a/chromium/ui/views/controls/focus_ring.cc +++ b/chromium/ui/views/controls/focus_ring.cc @@ -7,6 +7,7 @@ #include "ui/gfx/canvas.h" #include "ui/views/controls/focusable_border.h" #include "ui/views/style/platform_style.h" +#include "ui/views/view_properties.h" namespace { @@ -79,6 +80,11 @@ void FocusRing::OnPaint(gfx::Canvas* canvas) { paint.setStrokeWidth(PlatformStyle::kFocusHaloThickness); SkPath path = path_; + if (path.isEmpty()) { + gfx::Path* highlight_path = parent()->GetProperty(kHighlightPathKey); + if (highlight_path) + path = *highlight_path; + } if (path.isEmpty()) path.addRect(RectToSkRect(parent()->GetLocalBounds())); diff --git a/chromium/ui/views/controls/focus_ring.h b/chromium/ui/views/controls/focus_ring.h index a608042b24f..1627eda9347 100644 --- a/chromium/ui/views/controls/focus_ring.h +++ b/chromium/ui/views/controls/focus_ring.h @@ -58,6 +58,9 @@ class VIEWS_EXPORT FocusRing : public View, public ViewObserver { // view's coordinate system, *not* in the FocusRing's coordinate system. Note // that this path will not be mirrored in RTL, so your View's computation of // it should take RTL into account. + // Note: This method should only be used if the focus ring needs to differ + // from the highlight shape used for inkdrops. Otherwise set kHighlightPathKey + // on the parent and FocusRing will use it as well. void SetPath(const SkPath& path); // Sets whether the FocusRing should show an invalid state for the View it diff --git a/chromium/ui/views/controls/image_view.cc b/chromium/ui/views/controls/image_view.cc index b1abacf1e0d..2e9c4062526 100644 --- a/chromium/ui/views/controls/image_view.cc +++ b/chromium/ui/views/controls/image_view.cc @@ -7,12 +7,9 @@ #include <utility> #include "base/logging.h" -#include "base/strings/utf_string_conversions.h" #include "cc/paint/paint_flags.h" #include "skia/ext/image_operations.h" -#include "ui/accessibility/ax_node_data.h" #include "ui/gfx/canvas.h" -#include "ui/gfx/geometry/insets.h" namespace views { @@ -21,7 +18,7 @@ namespace { // Returns the pixels for the bitmap in |image| at scale |image_scale|. void* GetBitmapPixels(const gfx::ImageSkia& img, float image_scale) { DCHECK_NE(0.0f, image_scale); - return img.GetRepresentation(image_scale).sk_bitmap().getPixels(); + return img.GetRepresentation(image_scale).GetBitmap().getPixels(); } } // namespace @@ -29,13 +26,9 @@ void* GetBitmapPixels(const gfx::ImageSkia& img, float image_scale) { // static const char ImageView::kViewClassName[] = "ImageView"; -ImageView::ImageView() - : horizontal_alignment_(CENTER), - vertical_alignment_(CENTER), - last_paint_scale_(0.f), - last_painted_bitmap_pixels_(nullptr) {} +ImageView::ImageView() = default; -ImageView::~ImageView() {} +ImageView::~ImageView() = default; void ImageView::SetImage(const gfx::ImageSkia& img) { if (IsImageEqual(img)) @@ -63,20 +56,6 @@ const gfx::ImageSkia& ImageView::GetImage() const { return image_; } -void ImageView::SetImageSize(const gfx::Size& image_size) { - image_size_ = image_size; - PreferredSizeChanged(); -} - -gfx::Rect ImageView::GetImageBounds() const { - gfx::Size image_size = GetImageSize(); - return gfx::Rect(ComputeImageOrigin(image_size), image_size); -} - -void ImageView::ResetImageSize() { - image_size_.reset(); -} - bool ImageView::IsImageEqual(const gfx::ImageSkia& img) const { // Even though we copy ImageSkia in SetImage() the backing store // (ImageSkiaStorage) is not copied and may have changed since the last call @@ -92,119 +71,15 @@ gfx::Size ImageView::GetImageSize() const { return image_size_.value_or(image_.size()); } -gfx::Point ImageView::ComputeImageOrigin(const gfx::Size& image_size) const { - gfx::Insets insets = GetInsets(); - - int x = 0; - // In order to properly handle alignment of images in RTL locales, we need - // to flip the meaning of trailing and leading. For example, if the - // horizontal alignment is set to trailing, then we'll use left alignment for - // the image instead of right alignment if the UI layout is RTL. - Alignment actual_horizontal_alignment = horizontal_alignment_; - if (base::i18n::IsRTL() && (horizontal_alignment_ != CENTER)) { - actual_horizontal_alignment = - (horizontal_alignment_ == LEADING) ? TRAILING : LEADING; - } - switch (actual_horizontal_alignment) { - case LEADING: - x = insets.left(); - break; - case TRAILING: - x = width() - insets.right() - image_size.width(); - break; - case CENTER: - x = (width() - insets.width() - image_size.width()) / 2 + insets.left(); - break; - } - - int y = 0; - switch (vertical_alignment_) { - case LEADING: - y = insets.top(); - break; - case TRAILING: - y = height() - insets.bottom() - image_size.height(); - break; - case CENTER: - y = (height() - insets.height() - image_size.height()) / 2 + insets.top(); - break; - } - - return gfx::Point(x, y); -} - void ImageView::OnPaint(gfx::Canvas* canvas) { View::OnPaint(canvas); OnPaintImage(canvas); } -void ImageView::GetAccessibleNodeData(ui::AXNodeData* node_data) { - node_data->role = ax::mojom::Role::kImage; - node_data->SetName(tooltip_text_); -} - const char* ImageView::GetClassName() const { return kViewClassName; } -void ImageView::SetHorizontalAlignment(Alignment alignment) { - if (alignment != horizontal_alignment_) { - horizontal_alignment_ = alignment; - SchedulePaint(); - } -} - -ImageView::Alignment ImageView::GetHorizontalAlignment() const { - return horizontal_alignment_; -} - -void ImageView::SetVerticalAlignment(Alignment alignment) { - if (alignment != vertical_alignment_) { - vertical_alignment_ = alignment; - SchedulePaint(); - } -} - -ImageView::Alignment ImageView::GetVerticalAlignment() const { - return vertical_alignment_; -} - -void ImageView::SetTooltipText(const base::string16& tooltip) { - tooltip_text_ = tooltip; -} - -base::string16 ImageView::GetTooltipText() const { - return tooltip_text_; -} - -bool ImageView::GetTooltipText(const gfx::Point& p, - base::string16* tooltip) const { - if (tooltip_text_.empty()) - return false; - - *tooltip = GetTooltipText(); - return true; -} - -gfx::Size ImageView::CalculatePreferredSize() const { - gfx::Size size = GetImageSize(); - size.Enlarge(GetInsets().width(), GetInsets().height()); - return size; -} - -views::PaintInfo::ScaleType ImageView::GetPaintScaleType() const { - // ImageView contains an image which is rastered at the device scale factor. - // By default, the paint commands are recorded at a scale factor slightly - // different from the device scale factor. Re-rastering the image at this - // paint recording scale will result in a distorted image. Paint recording - // scale might also not be uniform along the x & y axis, thus resulting in - // further distortion in the aspect ratio of the final image. - // |kUniformScaling| ensures that the paint recording scale is uniform along - // the x & y axis and keeps the scale equal to the device scale factor. - // See http://crbug.com/754010 for more details. - return views::PaintInfo::ScaleType::kUniformScaling; -} - void ImageView::OnPaintImage(gfx::Canvas* canvas) { last_paint_scale_ = canvas->image_scale(); last_painted_bitmap_pixels_ = nullptr; @@ -248,7 +123,7 @@ gfx::ImageSkia ImageView::GetPaintImage(float scale) { gfx::Size scaled_size = gfx::ScaleToCeiledSize(rep.pixel_size(), scale / rep.scale()); scaled_image_.AddRepresentation(gfx::ImageSkiaRep( - skia::ImageOperations::Resize(rep.sk_bitmap(), + skia::ImageOperations::Resize(rep.GetBitmap(), skia::ImageOperations::RESIZE_BEST, scaled_size.width(), scaled_size.height()), scale)); diff --git a/chromium/ui/views/controls/image_view.h b/chromium/ui/views/controls/image_view.h index c3adf654e36..e80a97a34a1 100644 --- a/chromium/ui/views/controls/image_view.h +++ b/chromium/ui/views/controls/image_view.h @@ -6,9 +6,8 @@ #define UI_VIEWS_CONTROLS_IMAGE_VIEW_H_ #include "base/macros.h" -#include "base/optional.h" #include "ui/gfx/image/image_skia.h" -#include "ui/views/view.h" +#include "ui/views/controls/image_view_base.h" namespace gfx { class Canvas; @@ -26,17 +25,11 @@ namespace views { // provided image size. // ///////////////////////////////////////////////////////////////////////////// -class VIEWS_EXPORT ImageView : public View { +class VIEWS_EXPORT ImageView : public ImageViewBase { public: // Internal class name. static const char kViewClassName[]; - enum Alignment { - LEADING = 0, - CENTER, - TRAILING - }; - ImageView(); ~ImageView() override; @@ -52,35 +45,13 @@ class VIEWS_EXPORT ImageView : public View { // The returned image is still owned by the ImageView. const gfx::ImageSkia& GetImage() const; - // Set the desired image size for the receiving ImageView. - void SetImageSize(const gfx::Size& image_size); - - // Returns the actual bounds of the visible image inside the view. - gfx::Rect GetImageBounds() const; - - // Reset the image size to the current image dimensions. - void ResetImageSize(); - - // Set / Get the horizontal alignment. - void SetHorizontalAlignment(Alignment ha); - Alignment GetHorizontalAlignment() const; - - // Set / Get the vertical alignment. - void SetVerticalAlignment(Alignment va); - Alignment GetVerticalAlignment() const; - - // Set / Get the tooltip text. - void SetTooltipText(const base::string16& tooltip); - base::string16 GetTooltipText() const; - - // Overriden from View: + // Overridden from View: void OnPaint(gfx::Canvas* canvas) override; - void GetAccessibleNodeData(ui::AXNodeData* node_data) override; const char* GetClassName() const override; - bool GetTooltipText(const gfx::Point& p, - base::string16* tooltip) const override; - gfx::Size CalculatePreferredSize() const override; - views::PaintInfo::ScaleType GetPaintScaleType() const override; + + protected: + // Overridden from ImageViewBase: + gfx::Size GetImageSize() const override; private: friend class ImageViewTest; @@ -95,37 +66,18 @@ class VIEWS_EXPORT ImageView : public View { // for this to return false even though the images are in fact equal. bool IsImageEqual(const gfx::ImageSkia& img) const; - // Returns the size the image will be painted. - gfx::Size GetImageSize() const; - - // Compute the image origin given the desired size and the receiver alignment - // properties. - gfx::Point ComputeImageOrigin(const gfx::Size& image_size) const; - - // The actual image size. - base::Optional<gfx::Size> image_size_; - // The underlying image. gfx::ImageSkia image_; // Caches the scaled image reps. gfx::ImageSkia scaled_image_; - // Horizontal alignment. - Alignment horizontal_alignment_; - - // Vertical alignment. - Alignment vertical_alignment_; - - // The current tooltip text. - base::string16 tooltip_text_; - // Scale last painted at. - float last_paint_scale_; + float last_paint_scale_ = 0.f; // Address of bytes we last painted. This is used only for comparison, so its // safe to cache. - void* last_painted_bitmap_pixels_; + void* last_painted_bitmap_pixels_ = nullptr; DISALLOW_COPY_AND_ASSIGN(ImageView); }; diff --git a/chromium/ui/views/controls/image_view_base.cc b/chromium/ui/views/controls/image_view_base.cc new file mode 100644 index 00000000000..d898798239f --- /dev/null +++ b/chromium/ui/views/controls/image_view_base.cc @@ -0,0 +1,160 @@ +// Copyright 2018 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 "ui/views/controls/image_view_base.h" + +#include <utility> + +#include "base/logging.h" +#include "ui/accessibility/ax_node_data.h" +#include "ui/gfx/geometry/insets.h" + +namespace views { + +ImageViewBase::ImageViewBase() = default; + +ImageViewBase::~ImageViewBase() = default; + +void ImageViewBase::SetImageSize(const gfx::Size& image_size) { + image_size_ = image_size; + PreferredSizeChanged(); +} + +gfx::Rect ImageViewBase::GetImageBounds() const { + return gfx::Rect(image_origin_, GetImageSize()); +} + +void ImageViewBase::ResetImageSize() { + image_size_.reset(); + PreferredSizeChanged(); +} + +void ImageViewBase::GetAccessibleNodeData(ui::AXNodeData* node_data) { + node_data->role = ax::mojom::Role::kImage; + node_data->SetName(accessible_name_); +} + +void ImageViewBase::SetHorizontalAlignment(Alignment alignment) { + if (alignment != horizontal_alignment_) { + horizontal_alignment_ = alignment; + UpdateImageOrigin(); + SchedulePaint(); + } +} + +ImageViewBase::Alignment ImageViewBase::GetHorizontalAlignment() const { + return horizontal_alignment_; +} + +void ImageViewBase::SetVerticalAlignment(Alignment alignment) { + if (alignment != vertical_alignment_) { + vertical_alignment_ = alignment; + UpdateImageOrigin(); + SchedulePaint(); + } +} + +ImageViewBase::Alignment ImageViewBase::GetVerticalAlignment() const { + return vertical_alignment_; +} + +void ImageViewBase::SetAccessibleName(const base::string16& accessible_name) { + accessible_name_ = accessible_name; +} + +base::string16 ImageViewBase::GetAccessibleName() const { + return accessible_name_; +} + +// TODO(crbug.com/890465): Update the duplicate code here and in views::Button. +void ImageViewBase::SetTooltipText(const base::string16& tooltip) { + tooltip_text_ = tooltip; + if (accessible_name_.empty()) + accessible_name_ = tooltip_text_; +} + +base::string16 ImageViewBase::GetTooltipText() const { + return tooltip_text_; +} + +bool ImageViewBase::GetTooltipText(const gfx::Point& p, + base::string16* tooltip) const { + if (tooltip_text_.empty()) + return false; + + *tooltip = GetTooltipText(); + return true; +} + +gfx::Size ImageViewBase::CalculatePreferredSize() const { + gfx::Size size = GetImageSize(); + size.Enlarge(GetInsets().width(), GetInsets().height()); + return size; +} + +views::PaintInfo::ScaleType ImageViewBase::GetPaintScaleType() const { + // ImageViewBase contains an image which is rastered at the device scale + // factor. By default, the paint commands are recorded at a scale factor + // slightly different from the device scale factor. Re-rastering the image at + // this paint recording scale will result in a distorted image. Paint + // recording scale might also not be uniform along the x & y axis, thus + // resulting in further distortion in the aspect ratio of the final image. + // |kUniformScaling| ensures that the paint recording scale is uniform along + // the x & y axis and keeps the scale equal to the device scale factor. + // See http://crbug.com/754010 for more details. + return views::PaintInfo::ScaleType::kUniformScaling; +} + +void ImageViewBase::OnBoundsChanged(const gfx::Rect& previous_bounds) { + UpdateImageOrigin(); +} + +void ImageViewBase::UpdateImageOrigin() { + gfx::Size image_size = GetImageSize(); + gfx::Insets insets = GetInsets(); + + int x = 0; + // In order to properly handle alignment of images in RTL locales, we need + // to flip the meaning of trailing and leading. For example, if the + // horizontal alignment is set to trailing, then we'll use left alignment for + // the image instead of right alignment if the UI layout is RTL. + Alignment actual_horizontal_alignment = horizontal_alignment_; + if (base::i18n::IsRTL() && (horizontal_alignment_ != CENTER)) { + actual_horizontal_alignment = + (horizontal_alignment_ == LEADING) ? TRAILING : LEADING; + } + switch (actual_horizontal_alignment) { + case LEADING: + x = insets.left(); + break; + case TRAILING: + x = width() - insets.right() - image_size.width(); + break; + case CENTER: + x = (width() - insets.width() - image_size.width()) / 2 + insets.left(); + break; + } + + int y = 0; + switch (vertical_alignment_) { + case LEADING: + y = insets.top(); + break; + case TRAILING: + y = height() - insets.bottom() - image_size.height(); + break; + case CENTER: + y = (height() - insets.height() - image_size.height()) / 2 + insets.top(); + break; + } + + image_origin_ = gfx::Point(x, y); +} + +void ImageViewBase::PreferredSizeChanged() { + View::PreferredSizeChanged(); + UpdateImageOrigin(); +} + +} // namespace views diff --git a/chromium/ui/views/controls/image_view_base.h b/chromium/ui/views/controls/image_view_base.h new file mode 100644 index 00000000000..4764f2f4c47 --- /dev/null +++ b/chromium/ui/views/controls/image_view_base.h @@ -0,0 +1,94 @@ +// Copyright 2018 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 UI_VIEWS_CONTROLS_IMAGE_VIEW_BASE_H_ +#define UI_VIEWS_CONTROLS_IMAGE_VIEW_BASE_H_ + +#include "base/macros.h" +#include "base/optional.h" +#include "ui/views/view.h" + +namespace gfx { +class Canvas; +} + +namespace views { + +class VIEWS_EXPORT ImageViewBase : public View { + public: + enum Alignment { LEADING, CENTER, TRAILING }; + + ImageViewBase(); + ~ImageViewBase() override; + + // Set the desired image size for the receiving ImageView. + void SetImageSize(const gfx::Size& image_size); + + // Returns the actual bounds of the visible image inside the view. + gfx::Rect GetImageBounds() const; + + // Reset the image size to the current image dimensions. + void ResetImageSize(); + + // Set / Get the horizontal alignment. + void SetHorizontalAlignment(Alignment ha); + Alignment GetHorizontalAlignment() const; + + // Set / Get the vertical alignment. + void SetVerticalAlignment(Alignment va); + Alignment GetVerticalAlignment() const; + + // Set / Get the tooltip text. + void SetTooltipText(const base::string16& tooltip); + base::string16 GetTooltipText() const; + + // Set / Get the accessible name text. + void SetAccessibleName(const base::string16& name); + base::string16 GetAccessibleName() const; + + // Overridden from View: + void OnPaint(gfx::Canvas* canvas) override = 0; + void GetAccessibleNodeData(ui::AXNodeData* node_data) override; + const char* GetClassName() const override = 0; + bool GetTooltipText(const gfx::Point& p, + base::string16* tooltip) const override; + gfx::Size CalculatePreferredSize() const override; + views::PaintInfo::ScaleType GetPaintScaleType() const override; + void OnBoundsChanged(const gfx::Rect& previous_bounds) override; + void PreferredSizeChanged() override; + + protected: + // Returns the size the image will be painted. + virtual gfx::Size GetImageSize() const = 0; + + // The requested image size. + base::Optional<gfx::Size> image_size_; + + private: + friend class ImageViewTest; + + // Recomputes and updates the |image_origin_|. + void UpdateImageOrigin(); + + // The origin of the image. + gfx::Point image_origin_; + + // Horizontal alignment. + Alignment horizontal_alignment_ = Alignment::CENTER; + + // Vertical alignment. + Alignment vertical_alignment_ = Alignment::CENTER; + + // The current tooltip text. + base::string16 tooltip_text_; + + // The current accessible name text. + base::string16 accessible_name_; + + DISALLOW_COPY_AND_ASSIGN(ImageViewBase); +}; + +} // namespace views + +#endif // UI_VIEWS_CONTROLS_IMAGE_VIEW_BASE_H_ diff --git a/chromium/ui/views/controls/image_view_unittest.cc b/chromium/ui/views/controls/image_view_unittest.cc index 28cbb21460a..5e4d4a5576a 100644 --- a/chromium/ui/views/controls/image_view_unittest.cc +++ b/chromium/ui/views/controls/image_view_unittest.cc @@ -69,8 +69,8 @@ class ImageViewTest : public ViewsTestBase, } int CurrentImageOriginForParam() { - gfx::Point origin = - image_view()->ComputeImageOrigin(image_view()->GetImageSize()); + image_view()->UpdateImageOrigin(); + gfx::Point origin = image_view()->GetImageBounds().origin(); return GetParam() == Axis::kHorizontal ? origin.x() : origin.y(); } @@ -126,6 +126,21 @@ TEST_P(ImageViewTest, CenterAlignment) { EXPECT_EQ(kLeadingInset, CurrentImageOriginForParam()); } +TEST_P(ImageViewTest, ImageOriginForCustomViewBounds) { + gfx::Rect image_view_bounds(10, 10, 80, 80); + image_view()->SetHorizontalAlignment(ImageView::CENTER); + image_view()->SetBoundsRect(image_view_bounds); + + SkBitmap bitmap; + constexpr int kImageSkiaSize = 20; + bitmap.allocN32Pixels(kImageSkiaSize, kImageSkiaSize); + gfx::ImageSkia image_skia = gfx::ImageSkia::CreateFrom1xBitmap(bitmap); + image_view()->SetImage(image_skia); + + EXPECT_EQ(gfx::Point(30, 30), image_view()->GetImageBounds().origin()); + EXPECT_EQ(image_view_bounds, image_view()->bounds()); +} + INSTANTIATE_TEST_CASE_P(, ImageViewTest, ::testing::Values(Axis::kHorizontal, Axis::kVertical)); diff --git a/chromium/ui/views/controls/label_unittest.cc b/chromium/ui/views/controls/label_unittest.cc index d87acf7c42d..1ee2b444c42 100644 --- a/chromium/ui/views/controls/label_unittest.cc +++ b/chromium/ui/views/controls/label_unittest.cc @@ -20,7 +20,6 @@ #include "ui/events/test/event_generator.h" #include "ui/gfx/canvas.h" #include "ui/gfx/render_text.h" -#include "ui/gfx/switches.h" #include "ui/gfx/text_elider.h" #include "ui/strings/grit/ui_strings.h" #include "ui/views/border.h" @@ -163,14 +162,6 @@ class LabelSelectionTest : public LabelTest { // LabelTest overrides: void SetUp() override { -#if defined(OS_MACOSX) - // On Mac, by default RenderTextMac is used for labels which does not - // support text selection. Instead use RenderTextHarfBuzz for selection - // related tests. TODO(crbug.com/661394): Remove this once Mac also uses - // RenderTextHarfBuzz for Labels. - base::CommandLine::ForCurrentProcess()->AppendSwitch( - switches::kEnableHarfBuzzRenderText); -#endif LabelTest::SetUp(); event_generator_ = std::make_unique<ui::test::EventGenerator>(widget()->GetNativeWindow()); diff --git a/chromium/ui/views/controls/menu/menu_closure_animation_mac.h b/chromium/ui/views/controls/menu/menu_closure_animation_mac.h index 5c3b6d69aff..e966edd9888 100644 --- a/chromium/ui/views/controls/menu/menu_closure_animation_mac.h +++ b/chromium/ui/views/controls/menu/menu_closure_animation_mac.h @@ -14,6 +14,7 @@ namespace views { class MenuItemView; +class SubmenuView; // This class implements the Mac menu closure animation: // 1) For 100ms, the selected item is drawn as unselected @@ -24,11 +25,19 @@ class MenuItemView; // will stop the timer or animation (if they are running), so the callback will // *not* be run - which is good, since the MenuController that would have // received it is being deleted. +// +// This class also supports animating a menu away without animating the +// selection effect, which is achieved by passing nullptr for the item to +// animate. In this case, the animation skips straight to step 3 above. class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate { public: - // After this closure animation is done, |callback| is run to finally accept - // |item|. - MenuClosureAnimationMac(MenuItemView* item, base::OnceClosure callback); + // After this closure animation is done, |callback| is run to finish + // dismissing the menu. If |item| is given, this will animate the item being + // accepted before animating the menu closing; if |item| is nullptr, only the + // menu closure will be animated. + MenuClosureAnimationMac(MenuItemView* item, + SubmenuView* menu, + base::OnceClosure callback); ~MenuClosureAnimationMac() override; // Start the animation. @@ -37,6 +46,9 @@ class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate { // Returns the MenuItemView this animation targets. MenuItemView* item() { return item_; } + // Returns the SubmenuView this animation targets. + SubmenuView* menu() { return menu_; } + // Causes animations to take no time for testing purposes. Note that this // still causes the completion callback to be run asynchronously, so test // situations have the same control flow as non-test situations. @@ -51,7 +63,7 @@ class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate { kFinish, }; - static constexpr AnimationStep NextStepFor(AnimationStep step); + AnimationStep NextStepFor(AnimationStep step) const; void AdvanceAnimation(); @@ -64,6 +76,7 @@ class VIEWS_EXPORT MenuClosureAnimationMac : public gfx::AnimationDelegate { base::OneShotTimer timer_; std::unique_ptr<gfx::Animation> fade_animation_; MenuItemView* item_; + SubmenuView* menu_; AnimationStep step_; DISALLOW_COPY_AND_ASSIGN(MenuClosureAnimationMac); diff --git a/chromium/ui/views/controls/menu/menu_closure_animation_mac.mm b/chromium/ui/views/controls/menu/menu_closure_animation_mac.mm index 2bec6d31087..32127db42d9 100644 --- a/chromium/ui/views/controls/menu/menu_closure_animation_mac.mm +++ b/chromium/ui/views/controls/menu/menu_closure_animation_mac.mm @@ -10,6 +10,7 @@ #include "base/threading/thread_task_runner_handle.h" #include "ui/gfx/animation/linear_animation.h" #include "ui/views/controls/menu/menu_item_view.h" +#include "ui/views/controls/menu/submenu_view.h" #include "ui/views/widget/widget.h" namespace { @@ -19,9 +20,11 @@ static bool g_disable_animations_for_testing = false; namespace views { MenuClosureAnimationMac::MenuClosureAnimationMac(MenuItemView* item, + SubmenuView* menu, base::OnceClosure callback) : callback_(std::move(callback)), item_(item), + menu_(menu), step_(AnimationStep::kStart) {} MenuClosureAnimationMac::~MenuClosureAnimationMac() {} @@ -42,12 +45,11 @@ void MenuClosureAnimationMac::Start() { } // static -constexpr MenuClosureAnimationMac::AnimationStep -MenuClosureAnimationMac::NextStepFor( - MenuClosureAnimationMac::AnimationStep step) { +MenuClosureAnimationMac::AnimationStep MenuClosureAnimationMac::NextStepFor( + MenuClosureAnimationMac::AnimationStep step) const { switch (step) { case AnimationStep::kStart: - return AnimationStep::kUnselected; + return item_ ? AnimationStep::kUnselected : AnimationStep::kFading; case AnimationStep::kUnselected: return AnimationStep::kSelected; case AnimationStep::kSelected: @@ -84,7 +86,7 @@ void MenuClosureAnimationMac::DisableAnimationsForTesting() { void MenuClosureAnimationMac::AnimationProgressed( const gfx::Animation* animation) { - NSWindow* window = item_->GetWidget()->GetNativeWindow(); + NSWindow* window = menu_->GetWidget()->GetNativeWindow(); [window setAlphaValue:animation->CurrentValueBetween(1.0, 0.0)]; } diff --git a/chromium/ui/views/controls/menu/menu_config.cc b/chromium/ui/views/controls/menu/menu_config.cc index 7ea5c0104bc..3dc6eef0d4f 100644 --- a/chromium/ui/views/controls/menu/menu_config.cc +++ b/chromium/ui/views/controls/menu/menu_config.cc @@ -5,6 +5,7 @@ #include "ui/views/controls/menu/menu_config.h" #include "base/macros.h" +#include "base/no_destructor.h" #include "ui/views/controls/menu/menu_controller.h" #include "ui/views/controls/menu/menu_image_util.h" #include "ui/views/controls/menu/menu_item_view.h" @@ -100,8 +101,8 @@ bool MenuConfig::ShouldShowAcceleratorText(const MenuItemView* item, // static const MenuConfig& MenuConfig::instance() { - CR_DEFINE_STATIC_LOCAL(MenuConfig, instance, ()); - return instance; + static base::NoDestructor<MenuConfig> instance; + return *instance; } } // namespace views diff --git a/chromium/ui/views/controls/menu/menu_config_win.cc b/chromium/ui/views/controls/menu/menu_config_win.cc index 6048ee062fd..72665510ca0 100644 --- a/chromium/ui/views/controls/menu/menu_config_win.cc +++ b/chromium/ui/views/controls/menu/menu_config_win.cc @@ -10,9 +10,8 @@ #include "base/logging.h" #include "base/win/scoped_gdi_object.h" -#include "base/win/win_client_metrics.h" -#include "ui/base/l10n/l10n_util_win.h" #include "ui/gfx/color_utils.h" +#include "ui/gfx/platform_font_win.h" #include "ui/native_theme/native_theme_win.h" using ui::NativeTheme; @@ -21,15 +20,9 @@ namespace views { void MenuConfig::Init() { arrow_color = color_utils::GetSysSkColor(COLOR_MENUTEXT); + font_list = gfx::FontList(gfx::PlatformFontWin::GetSystemFont( + gfx::PlatformFontWin::SystemFont::kMenu)); - NONCLIENTMETRICS_XP metrics; - base::win::GetNonClientMetrics(&metrics); - l10n_util::AdjustUIFont(&(metrics.lfMenuFont)); - { - base::win::ScopedHFONT new_font(CreateFontIndirect(&metrics.lfMenuFont)); - DLOG_ASSERT(new_font.is_valid()); - font_list = gfx::FontList(gfx::Font(new_font.get())); - } NativeTheme::ExtraParams extra; gfx::Size arrow_size = NativeTheme::GetInstanceForNativeUi()->GetPartSize( NativeTheme::kMenuPopupArrow, NativeTheme::kNormal, extra); diff --git a/chromium/ui/views/controls/menu/menu_controller.cc b/chromium/ui/views/controls/menu/menu_controller.cc index d6806061652..9e56ca54813 100644 --- a/chromium/ui/views/controls/menu/menu_controller.cc +++ b/chromium/ui/views/controls/menu/menu_controller.cc @@ -499,6 +499,10 @@ void MenuController::Run(Widget* parent, } void MenuController::Cancel(ExitType type) { +#if defined(OS_MACOSX) + menu_closure_animation_.reset(); +#endif + // If the menu has already been destroyed, no further cancellation is // needed. We especially don't want to set the |exit_type_| to a lesser // value. @@ -1529,7 +1533,7 @@ void MenuController::UpdateInitialLocation(const gfx::Rect& bounds, void MenuController::Accept(MenuItemView* item, int event_flags) { #if defined(OS_MACOSX) menu_closure_animation_ = std::make_unique<MenuClosureAnimationMac>( - item, + item, item->GetParentMenuItem()->GetSubmenu(), base::BindOnce(&MenuController::ReallyAccept, base::Unretained(this), base::Unretained(item), event_flags)); menu_closure_animation_->Start(); @@ -1845,8 +1849,7 @@ void MenuController::CommitPendingSelection() { // Open all the submenus preceeding the last menu item (last menu item is // handled next). if (new_path.size() > 1) { - for (std::vector<MenuItemView*>::iterator i = new_path.begin(); - i != new_path.end() - 1; ++i) { + for (auto i = new_path.begin(); i != new_path.end() - 1; ++i) { OpenMenu(*i); } } @@ -2070,10 +2073,9 @@ gfx::Rect MenuController::CalculateMenuBounds(MenuItemView* item, const int right_of_parent = item_loc.x() + item->width() - submenu_horizontal_inset; - int border_size = menu_config.CornerRadiusForMenu(this); - if (!border_size) - border_size = menu_config.menu_vertical_border_size; - menu_bounds.set_y(item_loc.y() - border_size); + MenuScrollViewContainer* container = + item->GetParentMenuItem()->GetSubmenu()->GetScrollViewContainer(); + menu_bounds.set_y(item_loc.y() - container->border()->GetInsets().top()); // Assume the menu can be placed in the preferred location. menu_bounds.set_x(create_on_right ? right_of_parent : left_of_parent); @@ -2609,7 +2611,18 @@ void MenuController::RepostEventAndCancel(SubmenuView* source, if (last_part.type != MenuPart::NONE) exit_type = EXIT_OUTERMOST; } +#if defined(OS_MACOSX) + SubmenuView* target = exit_type == EXIT_ALL + ? source + : state_.item->GetRootMenuItem()->GetSubmenu(); + menu_closure_animation_ = std::make_unique<MenuClosureAnimationMac>( + nullptr, target, + base::BindOnce(&MenuController::Cancel, base::Unretained(this), + exit_type)); + menu_closure_animation_->Start(); +#else Cancel(exit_type); +#endif } void MenuController::SetDropMenuItem(MenuItemView* new_target, diff --git a/chromium/ui/views/controls/menu/menu_controller_unittest.cc b/chromium/ui/views/controls/menu/menu_controller_unittest.cc index 57c22228ae9..ad8f6732a80 100644 --- a/chromium/ui/views/controls/menu/menu_controller_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_controller_unittest.cc @@ -1232,6 +1232,7 @@ TEST_F(MenuControllerTest, PreserveGestureForOwner) { // Tests that touch outside menu does not closes the menu when forwarding // gesture events to owner. TEST_F(MenuControllerTest, NoTouchCloseWhenSendingGesturesToOwner) { + views::test::DisableMenuClosureAnimations(); MenuController* controller = menu_controller(); // Owner wants the gesture events. @@ -1259,6 +1260,7 @@ TEST_F(MenuControllerTest, NoTouchCloseWhenSendingGesturesToOwner) { // Touch outside again and menu should be closed. controller->OnTouchEvent(sub_menu, &touch_event); + views::test::WaitForMenuClosureAnimation(); EXPECT_FALSE(IsShowing()); EXPECT_EQ(MenuController::EXIT_ALL, controller->exit_type()); } @@ -1267,6 +1269,7 @@ TEST_F(MenuControllerTest, NoTouchCloseWhenSendingGesturesToOwner) { // occur outside of the bounds of the menu. Instead a proper shutdown should // occur. TEST_F(MenuControllerTest, AsynchronousRepostEvent) { + views::test::DisableMenuClosureAnimations(); MenuController* controller = menu_controller(); TestMenuControllerDelegate* delegate = menu_controller_delegate(); std::unique_ptr<TestMenuControllerDelegate> nested_delegate( @@ -1291,6 +1294,7 @@ TEST_F(MenuControllerTest, AsynchronousRepostEvent) { // When attempting to select outside of all menus this should lead to a // shutdown. This should not crash while attempting to repost the event. SetSelectionOnPointerDown(sub_menu, &event); + views::test::WaitForMenuClosureAnimation(); EXPECT_EQ(delegate, GetCurrentDelegate()); EXPECT_EQ(1, delegate->on_menu_closed_called()); @@ -1305,6 +1309,7 @@ TEST_F(MenuControllerTest, AsynchronousRepostEvent) { // Tests that an asynchronous menu reposts touch events that occur outside of // the bounds of the menu, and that the menu closes. TEST_F(MenuControllerTest, AsynchronousTouchEventRepostEvent) { + views::test::DisableMenuClosureAnimations(); MenuController* controller = menu_controller(); TestMenuControllerDelegate* delegate = menu_controller_delegate(); @@ -1319,6 +1324,7 @@ TEST_F(MenuControllerTest, AsynchronousTouchEventRepostEvent) { ui::ET_TOUCH_PRESSED, location, ui::EventTimeForNow(), ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, 0)); controller->OnTouchEvent(sub_menu, &event); + views::test::WaitForMenuClosureAnimation(); EXPECT_FALSE(IsShowing()); EXPECT_EQ(1, delegate->on_menu_closed_called()); @@ -1332,6 +1338,7 @@ TEST_F(MenuControllerTest, AsynchronousTouchEventRepostEvent) { // Tests that having the MenuController deleted during RepostEvent does not // cause a crash. ASAN bots should not detect use-after-free in MenuController. TEST_F(MenuControllerTest, AsynchronousRepostEventDeletesController) { + views::test::DisableMenuClosureAnimations(); MenuController* controller = menu_controller(); std::unique_ptr<TestMenuControllerDelegate> nested_delegate( new TestMenuControllerDelegate()); @@ -1358,6 +1365,7 @@ TEST_F(MenuControllerTest, AsynchronousRepostEventDeletesController) { // When attempting to select outside of all menus this should lead to a // shutdown. This should not crash while attempting to repost the event. SetSelectionOnPointerDown(sub_menu, &event); + views::test::WaitForMenuClosureAnimation(); // Close to remove observers before test TearDown sub_menu->Close(); diff --git a/chromium/ui/views/controls/menu/menu_host.cc b/chromium/ui/views/controls/menu/menu_host.cc index 104ae4c6da5..780a0f9c27b 100644 --- a/chromium/ui/views/controls/menu/menu_host.cc +++ b/chromium/ui/views/controls/menu/menu_host.cc @@ -86,7 +86,7 @@ void TransferGesture(Widget* source, Widget* target) { #else // !defined(OS_MACOSX) source->GetGestureRecognizer()->TransferEventsTo( source->GetNativeView(), target->GetNativeView(), - ui::GestureRecognizer::ShouldCancelTouches::DontCancel); + ui::TransferTouchesBehavior::kDontCancel); #endif // defined(OS_MACOSX) } diff --git a/chromium/ui/views/controls/menu/menu_item_view.cc b/chromium/ui/views/controls/menu/menu_item_view.cc index 6c007a1030c..7b17d2662ec 100644 --- a/chromium/ui/views/controls/menu/menu_item_view.cc +++ b/chromium/ui/views/controls/menu/menu_item_view.cc @@ -155,7 +155,18 @@ bool MenuItemView::GetTooltipText(const gfx::Point& p, } void MenuItemView::GetAccessibleNodeData(ui::AXNodeData* node_data) { - node_data->role = ax::mojom::Role::kMenuItem; + // Set the role based on the type of menu item. + switch (GetType()) { + case CHECKBOX: + node_data->role = ax::mojom::Role::kMenuItemCheckBox; + break; + case RADIO: + node_data->role = ax::mojom::Role::kMenuItemRadio; + break; + default: + node_data->role = ax::mojom::Role::kMenuItem; + break; + } base::string16 item_text; if (IsContainer()) { @@ -867,7 +878,7 @@ void MenuItemView::GetLabelStyle(MenuDelegate::LabelStyle* style) const { void MenuItemView::AddEmptyMenus() { DCHECK(HasSubmenu()); - if (!submenu_->HasVisibleChildren()) { + if (!submenu_->HasVisibleChildren() && !submenu_->HasEmptyMenuItemView()) { submenu_->AddChildViewAt(new EmptyMenuMenuItem(this), 0); } else { for (int i = 0, item_count = submenu_->GetMenuItemCount(); i < item_count; diff --git a/chromium/ui/views/controls/menu/menu_item_view_unittest.cc b/chromium/ui/views/controls/menu/menu_item_view_unittest.cc index 07273f5de1a..e4f42f008b4 100644 --- a/chromium/ui/views/controls/menu/menu_item_view_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_item_view_unittest.cc @@ -136,13 +136,18 @@ TEST(MenuItemViewUnitTest, TestEmptySubmenuWhenAllChildItemsAreHidden) { EXPECT_EQ(2, submenu->child_count()); // Adds any empty menu items to the menu, if needed. + EXPECT_FALSE(submenu->HasEmptyMenuItemView()); root_menu.AddEmptyMenus(); - + EXPECT_TRUE(submenu->HasEmptyMenuItemView()); // Because all of the submenu's children are hidden, an empty menu item should // have been added. ASSERT_EQ(3, submenu->child_count()); MenuItemView* empty_item = static_cast<MenuItemView*>(submenu->child_at(0)); ASSERT_TRUE(empty_item); + // Not allowed to add an duplicated empty menu item + // if it already has an empty menu item. + root_menu.AddEmptyMenus(); + ASSERT_EQ(3, submenu->child_count()); ASSERT_EQ(MenuItemView::kEmptyMenuItemViewID, empty_item->id()); EXPECT_EQ(l10n_util::GetStringUTF16(IDS_APP_MENU_EMPTY_SUBMENU), empty_item->title()); diff --git a/chromium/ui/views/controls/menu/menu_pre_target_handler_aura.cc b/chromium/ui/views/controls/menu/menu_pre_target_handler_aura.cc index 45b071661bb..b38b0d49d53 100644 --- a/chromium/ui/views/controls/menu/menu_pre_target_handler_aura.cc +++ b/chromium/ui/views/controls/menu/menu_pre_target_handler_aura.cc @@ -6,6 +6,7 @@ #include "ui/aura/env.h" #include "ui/aura/window.h" +#include "ui/base/ui_base_features.h" #include "ui/views/controls/menu/menu_controller.h" #include "ui/views/widget/widget.h" #include "ui/wm/public/activation_client.h" @@ -23,16 +24,27 @@ aura::Window* GetOwnerRootWindow(views::Widget* owner) { MenuPreTargetHandlerAura::MenuPreTargetHandlerAura(MenuController* controller, Widget* owner) : controller_(controller), root_(GetOwnerRootWindow(owner)) { - aura::Env::GetInstance()->AddPreTargetHandler( - this, ui::EventTarget::Priority::kSystem); if (root_) { + root_->env()->AddPreTargetHandler(this, ui::EventTarget::Priority::kSystem); wm::GetActivationClient(root_)->AddObserver(this); root_->AddObserver(this); + } else { + // TODO(mukai): check if this code path can run in ChromeOS and find the + // solution for SingleProcessMash. + if (features::IsUsingWindowService()) { + LOG(WARNING) << "MenuPreTargetHandlerAura is created without owner " + << "widget. This may not work well in SingleProcessMash."; + } + aura::Env::GetInstance()->AddPreTargetHandler( + this, ui::EventTarget::Priority::kSystem); } } MenuPreTargetHandlerAura::~MenuPreTargetHandlerAura() { - aura::Env::GetInstance()->RemovePreTargetHandler(this); + if (root_) + root_->env()->RemovePreTargetHandler(this); + else + aura::Env::GetInstance()->RemovePreTargetHandler(this); Cleanup(); } diff --git a/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm b/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm index 2cc6a8c1a7c..7acfc99f907 100644 --- a/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm +++ b/chromium/ui/views/controls/menu/menu_runner_cocoa_unittest.mm @@ -106,6 +106,8 @@ class MenuRunnerCocoaTest : public ViewsTestBase, gfx::Rect(kWindowOffset, kWindowOffset, kWindowWidth, kWindowHeight)); parent_->Show(); + native_view_subview_count_ = [[parent_->GetNativeView() subviews] count]; + base::Closure on_close = base::Bind(&MenuRunnerCocoaTest::MenuCloseCallback, base::Unretained(this)); if (GetParam() == MenuType::NATIVE) @@ -116,6 +118,9 @@ class MenuRunnerCocoaTest : public ViewsTestBase, } void TearDown() override { + EXPECT_EQ(native_view_subview_count_, + [[parent_->GetNativeView() subviews] count]); + if (runner_) { runner_->Release(); runner_ = NULL; @@ -151,10 +156,6 @@ class MenuRunnerCocoaTest : public ViewsTestBase, void RunMenuAt(const gfx::Rect& anchor) { last_anchor_frame_ = NSZeroRect; - // Should be one child (the compositor layer) before showing, and it should - // go up by one (the anchor view) while the menu is shown. - EXPECT_EQ(1u, [[parent_->GetNativeView() subviews] count]); - base::OnceClosure callback = base::BindOnce(&MenuRunnerCocoaTest::ComboboxRunMenuAtCallback, base::Unretained(this)); @@ -168,9 +169,6 @@ class MenuRunnerCocoaTest : public ViewsTestBase, runner_->RunMenuAt(parent_, nullptr, anchor, MENU_ANCHOR_TOPLEFT, MenuRunner::COMBOBOX); MaybeRunAsync(); - - // Ensure the anchor view is removed. - EXPECT_EQ(1u, [[parent_->GetNativeView() subviews] count]); } void MenuCancelCallback() { @@ -244,6 +242,7 @@ class MenuRunnerCocoaTest : public ViewsTestBase, internal::MenuRunnerImplInterface* runner_ = nullptr; views::Widget* parent_ = nullptr; NSRect last_anchor_frame_ = NSZeroRect; + NSUInteger native_view_subview_count_ = 0; int menu_close_count_ = 0; private: @@ -256,10 +255,11 @@ class MenuRunnerCocoaTest : public ViewsTestBase, NSArray* subviews = [parent_->GetNativeView() subviews]; // An anchor view should only be added for Native menus. if (GetParam() == MenuType::NATIVE) { - ASSERT_EQ(2u, [subviews count]); - last_anchor_frame_ = [[subviews objectAtIndex:1] frame]; + ASSERT_EQ(native_view_subview_count_ + 1, [subviews count]); + last_anchor_frame_ = + [[subviews objectAtIndex:native_view_subview_count_] frame]; } else { - EXPECT_EQ(1u, [subviews count]); + EXPECT_EQ(native_view_subview_count_, [subviews count]); } runner_->Cancel(); } diff --git a/chromium/ui/views/controls/menu/menu_runner_impl.cc b/chromium/ui/views/controls/menu/menu_runner_impl.cc index 1c7b5c4c72a..3f1fb8af38b 100644 --- a/chromium/ui/views/controls/menu/menu_runner_impl.cc +++ b/chromium/ui/views/controls/menu/menu_runner_impl.cc @@ -192,8 +192,7 @@ void MenuRunnerImpl::SiblingMenuCreated(MenuItemView* menu) { MenuRunnerImpl::~MenuRunnerImpl() { delete menu_; - for (std::set<MenuItemView*>::iterator i = sibling_menus_.begin(); - i != sibling_menus_.end(); ++i) + for (auto i = sibling_menus_.begin(); i != sibling_menus_.end(); ++i) delete *i; } diff --git a/chromium/ui/views/controls/menu/submenu_view.cc b/chromium/ui/views/controls/menu/submenu_view.cc index c9ad4e4f5b9..36865a59316 100644 --- a/chromium/ui/views/controls/menu/submenu_view.cc +++ b/chromium/ui/views/controls/menu/submenu_view.cc @@ -61,6 +61,14 @@ SubmenuView::~SubmenuView() { delete scroll_view_container_; } +bool SubmenuView::HasEmptyMenuItemView() { + for (int i = 0; i < child_count(); i++) { + if (child_at(i)->id() == MenuItemView::kEmptyMenuItemViewID) + return true; + } + return false; +} + bool SubmenuView::HasVisibleChildren() { for (int i = 0, item_count = GetMenuItemCount(); i < item_count; i++) { if (GetMenuItemAt(i)->visible()) diff --git a/chromium/ui/views/controls/menu/submenu_view.h b/chromium/ui/views/controls/menu/submenu_view.h index 9ba69331264..4374790035e 100644 --- a/chromium/ui/views/controls/menu/submenu_view.h +++ b/chromium/ui/views/controls/menu/submenu_view.h @@ -51,6 +51,9 @@ class VIEWS_EXPORT SubmenuView : public View, explicit SubmenuView(MenuItemView* parent); ~SubmenuView() override; + // Returns true if the submenu has at least one empty menu item. + bool HasEmptyMenuItemView(); + // Returns true if the submenu has at least one visible child item. bool HasVisibleChildren(); diff --git a/chromium/ui/views/controls/native/native_view_host.cc b/chromium/ui/views/controls/native/native_view_host.cc index 32019ba3fdb..5240d2bf72d 100644 --- a/chromium/ui/views/controls/native/native_view_host.cc +++ b/chromium/ui/views/controls/native/native_view_host.cc @@ -67,6 +67,10 @@ void NativeViewHost::SetNativeViewSize(const gfx::Size& size) { InvalidateLayout(); } +gfx::NativeView NativeViewHost::GetNativeViewContainer() const { + return native_view_ ? native_wrapper_->GetNativeViewContainer() : nullptr; +} + void NativeViewHost::NativeViewDestroyed() { // Detach so we can clear our state and notify the native_wrapper_ to release // ref on the native view. @@ -225,7 +229,7 @@ void NativeViewHost::ClearFocus() { Widget::Widgets widgets; Widget::GetAllChildWidgets(native_view(), &widgets); - for (Widget::Widgets::iterator i = widgets.begin(); i != widgets.end(); ++i) { + for (auto i = widgets.begin(); i != widgets.end(); ++i) { focus_manager->ViewRemoved((*i)->GetRootView()); if (!focus_manager->GetFocusedView()) return; diff --git a/chromium/ui/views/controls/native/native_view_host.h b/chromium/ui/views/controls/native/native_view_host.h index 5bd897cf3ed..ec0b8e9f18a 100644 --- a/chromium/ui/views/controls/native/native_view_host.h +++ b/chromium/ui/views/controls/native/native_view_host.h @@ -63,6 +63,10 @@ class VIEWS_EXPORT NativeViewHost : public View { // NatieView's size always equals this View's size. void SetNativeViewSize(const gfx::Size& size); + // Returns the container that contains this host's native view. Returns null + // if there's no attached native view or it has no container. + gfx::NativeView GetNativeViewContainer() const; + // Fast resizing will move the native view and clip its visible region, this // will result in white areas and will not resize the content (so scrollbars // will be all wrong and content will flow offscreen). Only use this diff --git a/chromium/ui/views/controls/native/native_view_host_aura.cc b/chromium/ui/views/controls/native/native_view_host_aura.cc index 4bd67c7a87d..3be7f536eb1 100644 --- a/chromium/ui/views/controls/native/native_view_host_aura.cc +++ b/chromium/ui/views/controls/native/native_view_host_aura.cc @@ -5,6 +5,7 @@ #include "ui/views/controls/native/native_view_host_aura.h" #include "base/logging.h" +#include "base/optional.h" #include "build/build_config.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/client/focus_client.h" @@ -98,10 +99,12 @@ void NativeViewHostAura::AttachNativeView() { } void NativeViewHostAura::NativeViewDetaching(bool destroyed) { - // This method causes a succession of window tree changes. - // ScopedPauseOcclusionTracking ensures that occlusion is recomputed at the - // end of the method instead of after each change. - aura::WindowOcclusionTracker::ScopedPauseOcclusionTracking pause_occlusion; + // This method causes a succession of window tree changes. ScopedPause ensures + // that occlusion is recomputed at the end of the method instead of after each + // change. + base::Optional<aura::WindowOcclusionTracker::ScopedPause> pause_occlusion; + if (clipping_window_) + pause_occlusion.emplace(clipping_window_->env()); clipping_window_delegate_->set_native_view(NULL); RemoveClippingWindow(); @@ -212,6 +215,10 @@ void NativeViewHostAura::SetFocus() { client->FocusWindow(window); } +gfx::NativeView NativeViewHostAura::GetNativeViewContainer() const { + return clipping_window_.get(); +} + gfx::NativeViewAccessible NativeViewHostAura::GetNativeViewAccessible() { return NULL; } diff --git a/chromium/ui/views/controls/native/native_view_host_aura.h b/chromium/ui/views/controls/native/native_view_host_aura.h index a6072997977..0adf8fb3aec 100644 --- a/chromium/ui/views/controls/native/native_view_host_aura.h +++ b/chromium/ui/views/controls/native/native_view_host_aura.h @@ -41,6 +41,7 @@ class NativeViewHostAura : public NativeViewHostWrapper, override; void HideWidget() override; void SetFocus() override; + gfx::NativeView GetNativeViewContainer() const override; gfx::NativeViewAccessible GetNativeViewAccessible() override; gfx::NativeCursor GetCursor(int x, int y) override; diff --git a/chromium/ui/views/controls/native/native_view_host_mac.h b/chromium/ui/views/controls/native/native_view_host_mac.h index 69659c9aafd..f46e575e442 100644 --- a/chromium/ui/views/controls/native/native_view_host_mac.h +++ b/chromium/ui/views/controls/native/native_view_host_mac.h @@ -7,24 +7,35 @@ #include "base/mac/scoped_nsobject.h" #include "base/macros.h" +#include "ui/base/cocoa/views_hostable.h" #include "ui/views/controls/native/native_view_host_wrapper.h" #include "ui/views/views_export.h" namespace ui { class LayerOwner; -} +class ViewsHostableView; +} // namespace ui namespace views { +class BridgedNativeWidgetHostImpl; class NativeViewHost; // Mac implementation of NativeViewHostWrapper. -class NativeViewHostMac : public NativeViewHostWrapper { +class NativeViewHostMac : public NativeViewHostWrapper, + public ui::ViewsHostableView::Host { public: explicit NativeViewHostMac(NativeViewHost* host); ~NativeViewHostMac() override; - // Overridden from NativeViewHostWrapper: + // ViewsHostableView::Host: + ui::Layer* GetUiLayer() const override; + uint64_t GetViewsFactoryHostId() const override; + uint64_t GetNSViewId() const override; + id GetAccessibilityElement() const override; + void OnHostableViewDestroying() override; + + // NativeViewHostWrapper: void AttachNativeView() override; void NativeViewDetaching(bool destroyed) override; void AddedToWidget() override; @@ -37,16 +48,25 @@ class NativeViewHostMac : public NativeViewHostWrapper { override; void HideWidget() override; void SetFocus() override; + gfx::NativeView GetNativeViewContainer() const override; gfx::NativeViewAccessible GetNativeViewAccessible() override; gfx::NativeCursor GetCursor(int x, int y) override; private: + // Return the BridgedNativeWidgetHostImpl for this hosted view. + BridgedNativeWidgetHostImpl* GetBridgedNativeWidgetHost() const; + // Our associated NativeViewHost. Owns this. NativeViewHost* host_; // Retain the native view as it may be destroyed at an unpredictable time. base::scoped_nsobject<NSView> native_view_; + // If |native_view| supports the ViewsHostable protocol, then this is the + // the corresponding ViewsHostableView interface (which is implemeted only + // by WebContents and tests). + ui::ViewsHostableView* native_view_hostable_ = nullptr; + DISALLOW_COPY_AND_ASSIGN(NativeViewHostMac); }; diff --git a/chromium/ui/views/controls/native/native_view_host_mac.mm b/chromium/ui/views/controls/native/native_view_host_mac.mm index debbe51f102..d98e6e1456c 100644 --- a/chromium/ui/views/controls/native/native_view_host_mac.mm +++ b/chromium/ui/views/controls/native/native_view_host_mac.mm @@ -8,19 +8,11 @@ #include "base/mac/foundation_util.h" #import "ui/accessibility/platform/ax_platform_node_mac.h" -#import "ui/base/cocoa/accessibility_hostable.h" -#import "ui/views/cocoa/bridged_native_widget.h" +#import "ui/views/cocoa/bridged_native_widget_host_impl.h" #include "ui/views/controls/native/native_view_host.h" #include "ui/views/widget/native_widget_mac.h" #include "ui/views/widget/widget.h" -// NSViews that can be drawn as a ui::Layer directly will implement this -// interface. Calling cr_setParentLayer will embed the ui::Layer of the NSView -// under |parentUiLayer|. -@interface NSView (UICompositor) -- (void)cr_setParentUiLayer:(ui::Layer*)parentUiLayer; -@end - namespace views { namespace { @@ -59,6 +51,60 @@ NativeViewHostMac::NativeViewHostMac(NativeViewHost* host) : host_(host) { NativeViewHostMac::~NativeViewHostMac() { } +BridgedNativeWidgetHostImpl* NativeViewHostMac::GetBridgedNativeWidgetHost() + const { + return BridgedNativeWidgetHostImpl::GetFromNativeWindow( + host_->GetWidget()->GetNativeWindow()); +} + +//////////////////////////////////////////////////////////////////////////////// +// NativeViewHostMac, ViewsHostableView::Host implementation: + +ui::Layer* NativeViewHostMac::GetUiLayer() const { + return host_->layer(); +} + +uint64_t NativeViewHostMac::GetViewsFactoryHostId() const { + auto* bridge_host = GetBridgedNativeWidgetHost(); + if (bridge_host && bridge_host->bridge_factory_host()) + return bridge_host->bridge_factory_host()->GetHostId(); + return 0; +} + +uint64_t NativeViewHostMac::GetNSViewId() const { + auto* bridge_host = GetBridgedNativeWidgetHost(); + if (bridge_host) + return bridge_host->GetRootViewNSViewId(); + return 0; +} + +id NativeViewHostMac::GetAccessibilityElement() const { + // Find the closest ancestor view that participates in the views toolkit + // accessibility hierarchy and set its element as the native view's parent. + // This is necessary because a closer ancestor might already be attaching + // to the NSView/content hierarchy. + // For example, web content is currently embedded into the views hierarchy + // roughly like this: + // BrowserView (views) + // |_ WebView (views) + // |_ NativeViewHost (views) + // |_ WebContentView (Cocoa, is |native_view_| in this scenario, + // | accessibility ignored). + // |_ RenderWidgetHostView (Cocoa) + // WebView specifies either the RenderWidgetHostView or the native view as + // its accessibility element. That means that if we were to set it as + // |native_view_|'s parent, the RenderWidgetHostView would be its own + // accessibility parent! Instead, we want to find the browser view and + // attach to its node. + return ClosestPlatformAncestorNode(host_->parent()); +} + +void NativeViewHostMac::OnHostableViewDestroying() { + DCHECK(native_view_hostable_); + host_->NativeViewDestroyed(); + DCHECK(!native_view_hostable_); +} + //////////////////////////////////////////////////////////////////////////////// // NativeViewHostMac, NativeViewHostWrapper implementation: @@ -66,46 +112,25 @@ void NativeViewHostMac::AttachNativeView() { DCHECK(host_->native_view()); DCHECK(!native_view_); native_view_.reset([host_->native_view() retain]); + EnsureNativeViewHasNoChildWidgets(native_view_); - if ([native_view_ respondsToSelector:@selector(cr_setParentUiLayer:)]) - [native_view_ cr_setParentUiLayer:host_->layer()]; - if ([native_view_ conformsToProtocol:@protocol(AccessibilityHostable)]) { - // Find the closest ancestor view that participates in the views toolkit - // accessibility hierarchy and set its element as the native view's parent. - // This is necessary because a closer ancestor might already be attaching - // to the NSView/content hierarchy. - // For example, web content is currently embedded into the views hierarchy - // roughly like this: - // BrowserView (views) - // |_ WebView (views) - // |_ NativeViewHost (views) - // |_ WebContentView (Cocoa, is |native_view_| in this scenario, - // | accessibility ignored). - // |_ RenderWidgetHostView (Cocoa) - // WebView specifies either the RenderWidgetHostView or the native view as - // its accessibility element. That means that if we were to set it as - // |native_view_|'s parent, the RenderWidgetHostView would be its own - // accessibility parent! Instead, we want to find the browser view and - // attach to its node. + auto* bridge_host = GetBridgedNativeWidgetHost(); + DCHECK(bridge_host); + [bridge_host->native_widget_mac()->GetNativeView() addSubview:native_view_]; + bridge_host->SetAssociationForView(host_, native_view_); + + if ([native_view_ conformsToProtocol:@protocol(ViewsHostable)]) { id hostable = native_view_; - [hostable setAccessibilityParentElement:ClosestPlatformAncestorNode( - host_->parent())]; + native_view_hostable_ = [hostable viewsHostableView]; + if (native_view_hostable_) + native_view_hostable_->OnViewsHostableAttached(this); } - - EnsureNativeViewHasNoChildWidgets(native_view_); - BridgedNativeWidget* bridge = NativeWidgetMac::GetBridgeForNativeWindow( - host_->GetWidget()->GetNativeWindow()); - DCHECK(bridge); - [bridge->ns_view() addSubview:native_view_]; - bridge->SetAssociationForView(host_, native_view_); } void NativeViewHostMac::NativeViewDetaching(bool destroyed) { - // |destroyed| is only true if this class calls host_->NativeViewDestroyed(). - // Aura does this after observing an aura OnWindowDestroying, but NSViews - // are reference counted so there isn't a reliable signal. Instead, a - // reference is retained until the NativeViewHost is detached. - DCHECK(!destroyed); + // |destroyed| is only true if this class calls host_->NativeViewDestroyed(), + // which is called if a hosted WebContentsView about to be destroyed (note + // that its corresponding NSView may still exist). // |native_view_| can be nil here if RemovedFromWidget() is called before // NativeViewHost::Detach(). @@ -118,19 +143,16 @@ void NativeViewHostMac::NativeViewDetaching(bool destroyed) { [host_->native_view() setHidden:YES]; [host_->native_view() removeFromSuperview]; - if ([native_view_ respondsToSelector:@selector(cr_setParentUiLayer:)]) - [native_view_ cr_setParentUiLayer:nullptr]; - if ([native_view_ conformsToProtocol:@protocol(AccessibilityHostable)]) { - id hostable = native_view_; - [hostable setAccessibilityParentElement:nil]; + if (native_view_hostable_) { + native_view_hostable_->OnViewsHostableDetached(); + native_view_hostable_ = nullptr; } EnsureNativeViewHasNoChildWidgets(host_->native_view()); - BridgedNativeWidget* bridge = NativeWidgetMac::GetBridgeForNativeWindow( - host_->GetWidget()->GetNativeWindow()); - // BridgedNativeWidget can be null when Widget is closing. - if (bridge) - bridge->ClearAssociationForView(host_); + auto* bridge_host = GetBridgedNativeWidgetHost(); + // BridgedNativeWidgetImpl can be null when Widget is closing. + if (bridge_host) + bridge_host->ClearAssociationForView(host_); native_view_.reset(); } @@ -191,15 +213,29 @@ void NativeViewHostMac::ShowWidget(int x, [[host_->native_view() superview] convertRect:window_rect fromView:nil]; [host_->native_view() setFrame:container_rect]; [host_->native_view() setHidden:NO]; + + if (native_view_hostable_) + native_view_hostable_->OnViewsHostableShow(gfx::Rect(x, y, w, h)); } void NativeViewHostMac::HideWidget() { [host_->native_view() setHidden:YES]; + + if (native_view_hostable_) + native_view_hostable_->OnViewsHostableHide(); } void NativeViewHostMac::SetFocus() { if ([host_->native_view() acceptsFirstResponder]) [[host_->native_view() window] makeFirstResponder:host_->native_view()]; + + if (native_view_hostable_) + native_view_hostable_->OnViewsHostableMakeFirstResponder(); +} + +gfx::NativeView NativeViewHostMac::GetNativeViewContainer() const { + NOTIMPLEMENTED(); + return nullptr; } gfx::NativeViewAccessible NativeViewHostMac::GetNativeViewAccessible() { diff --git a/chromium/ui/views/controls/native/native_view_host_mac_unittest.mm b/chromium/ui/views/controls/native/native_view_host_mac_unittest.mm index 4a4fdcad1b1..85373e5b977 100644 --- a/chromium/ui/views/controls/native/native_view_host_mac_unittest.mm +++ b/chromium/ui/views/controls/native/native_view_host_mac_unittest.mm @@ -12,17 +12,38 @@ #import "base/mac/scoped_nsobject.h" #include "base/macros.h" #import "testing/gtest_mac.h" -#import "ui/base/cocoa/accessibility_hostable.h" +#import "ui/base/cocoa/views_hostable.h" #include "ui/views/controls/native/native_view_host.h" #include "ui/views/controls/native/native_view_host_test_base.h" #include "ui/views/view.h" #include "ui/views/widget/widget.h" -@interface TestAccessibilityHostableView : NSView<AccessibilityHostable> -@property(nonatomic, assign) id accessibilityParentElement; +class TestViewsHostable : public ui::ViewsHostableView { + public: + id parent_accessibility_element() const { + return parent_accessibility_element_; + } + + private: + // ui::ViewsHostableView: + void OnViewsHostableAttached(ui::ViewsHostableView::Host* host) override { + parent_accessibility_element_ = host->GetAccessibilityElement(); + } + void OnViewsHostableDetached() override { + parent_accessibility_element_ = nil; + } + void OnViewsHostableShow(const gfx::Rect& bounds_in_window) override {} + void OnViewsHostableHide() override {} + void OnViewsHostableMakeFirstResponder() override {} + + id parent_accessibility_element_ = nil; +}; + +@interface TestViewsHostableView : NSView<ViewsHostable> +@property(nonatomic, assign) ui::ViewsHostableView* viewsHostableView; @end -@implementation TestAccessibilityHostableView -@synthesize accessibilityParentElement = accessibilityParentElement_; +@implementation TestViewsHostableView +@synthesize viewsHostableView = viewsHostableView_; @end namespace views { @@ -115,15 +136,18 @@ TEST_F(NativeViewHostMacTest, AccessibilityParent) { CreateHost(); host()->Detach(); - base::scoped_nsobject<TestAccessibilityHostableView> view( - [[TestAccessibilityHostableView alloc] init]); + base::scoped_nsobject<TestViewsHostableView> view( + [[TestViewsHostableView alloc] init]); + TestViewsHostable views_hostable; + [view setViewsHostableView:&views_hostable]; + host()->Attach(view); - EXPECT_NSEQ([view accessibilityParentElement], + EXPECT_NSEQ(views_hostable.parent_accessibility_element(), toplevel()->GetRootView()->GetNativeViewAccessible()); host()->Detach(); DestroyHost(); - EXPECT_FALSE([view accessibilityParentElement]); + EXPECT_FALSE(views_hostable.parent_accessibility_element()); } // Test that the content windows' bounds are set to the correct values while the diff --git a/chromium/ui/views/controls/native/native_view_host_wrapper.h b/chromium/ui/views/controls/native/native_view_host_wrapper.h index 513c35f1a71..07cf9f1fea0 100644 --- a/chromium/ui/views/controls/native/native_view_host_wrapper.h +++ b/chromium/ui/views/controls/native/native_view_host_wrapper.h @@ -78,6 +78,10 @@ class NativeViewHostWrapper { // Sets focus to the gfx::NativeView. virtual void SetFocus() = 0; + // Returns the container that contains the NativeViewHost's native view if + // any. + virtual gfx::NativeView GetNativeViewContainer() const = 0; + // Return the native view accessible corresponding to the wrapped native // view. virtual gfx::NativeViewAccessible GetNativeViewAccessible() = 0; diff --git a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h index a0c88726dab..c69087a9382 100644 --- a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h +++ b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h @@ -5,13 +5,13 @@ #ifndef UI_VIEWS_CONTROLS_SCROLLBAR_COCOA_SCROLL_BAR_H_ #define UI_VIEWS_CONTROLS_SCROLLBAR_COCOA_SCROLL_BAR_H_ -#include "base/macros.h" #import "base/mac/scoped_nsobject.h" +#include "base/macros.h" #include "ui/compositor/layer_animation_observer.h" #include "ui/gfx/animation/slide_animation.h" -#import "ui/views/cocoa/views_scrollbar_bridge.h" #include "ui/views/controls/scrollbar/base_scroll_bar.h" #include "ui/views/views_export.h" +#import "ui/views_bridge_mac/views_scrollbar_bridge.h" namespace views { diff --git a/chromium/ui/views/controls/styled_label.cc b/chromium/ui/views/controls/styled_label.cc index 2c12e6e77ef..9e4477f7636 100644 --- a/chromium/ui/views/controls/styled_label.cc +++ b/chromium/ui/views/controls/styled_label.cc @@ -28,13 +28,6 @@ namespace views { namespace { -gfx::Insets FocusBorderInsets(const Label& label) { - // StyledLabel never adds a border, so the only Insets added are for the - // possible focus ring. - DCHECK(label.View::GetInsets().IsEmpty()); - return label.GetInsets(); -} - std::unique_ptr<Label> CreateLabelRange( const base::string16& text, int text_context, @@ -216,24 +209,6 @@ const char* StyledLabel::GetClassName() const { return kViewClassName; } -gfx::Insets StyledLabel::GetInsets() const { - gfx::Insets insets = View::GetInsets(); - if (Link::GetDefaultFocusStyle() != Link::FocusStyle::RING) - return insets; - - // We need a focus border iff we contain a link that will have a focus border. - // That in turn will be true only if the link is non-empty. - for (StyleRanges::const_iterator i(style_ranges_.begin()); - i != style_ranges_.end(); ++i) { - if (i->style_info.IsLink() && !i->range.is_empty()) { - insets += gfx::Insets(Link::kFocusBorderPadding); - break; - } - } - - return insets; -} - void StyledLabel::GetAccessibleNodeData(ui::AXNodeData* node_data) { if (text_context_ == style::CONTEXT_DIALOG_TITLE) node_data->role = ax::mojom::Role::kTitleBar; @@ -488,26 +463,18 @@ gfx::Size StyledLabel::CalculateAndDoLayout(int width, bool dry_run) { gfx::Size view_size = child_view->GetPreferredSize(); // |offset.y()| already contains |insets.top()|. gfx::Point view_origin(insets.left() + offset.x(), offset.y()); - gfx::Insets focus_border_insets; - if (Link::GetDefaultFocusStyle() == Link::FocusStyle::RING && label) { - // Calculate the size of the optional focus border, and overlap by that - // amount. Otherwise, "<a>link</a>," will render as "link ,". - focus_border_insets = FocusBorderInsets(*label); - } - view_origin.Offset(-focus_border_insets.left(), -focus_border_insets.top()); // The custom view could be wider than the available width; clamp as needed. - if (custom_view) { - view_size.set_width(std::min( - view_size.width(), width - offset.x() + focus_border_insets.width())); - } + if (custom_view) + view_size.set_width(std::min(view_size.width(), width - offset.x())); + child_view->SetBoundsRect(gfx::Rect(view_origin, view_size)); - offset.set_x(offset.x() + view_size.width() - focus_border_insets.width()); + offset.set_x(offset.x() + view_size.width()); total_height = - std::max(total_height, child_view->bounds().bottom() + insets.bottom() - - focus_border_insets.bottom()); + std::max(total_height, std::max(child_view->bounds().bottom(), + offset.y() + default_line_height) + + insets.bottom()); used_width = std::max(used_width, offset.x()); - max_line_height = std::max( - max_line_height, view_size.height() - focus_border_insets.height()); + max_line_height = std::max(max_line_height, view_size.height()); if (!dry_run) { views_in_a_line.push_back(child_view); diff --git a/chromium/ui/views/controls/styled_label.h b/chromium/ui/views/controls/styled_label.h index 43544ef691b..110007b1838 100644 --- a/chromium/ui/views/controls/styled_label.h +++ b/chromium/ui/views/controls/styled_label.h @@ -140,7 +140,6 @@ class VIEWS_EXPORT StyledLabel : public View, public LinkListener { // View: const char* GetClassName() const override; - gfx::Insets GetInsets() const override; void GetAccessibleNodeData(ui::AXNodeData* node_data) override; gfx::Size CalculatePreferredSize() const override; int GetHeightForWidth(int w) const override; diff --git a/chromium/ui/views/controls/styled_label_unittest.cc b/chromium/ui/views/controls/styled_label_unittest.cc index e79fadddb32..1d218215e66 100644 --- a/chromium/ui/views/controls/styled_label_unittest.cc +++ b/chromium/ui/views/controls/styled_label_unittest.cc @@ -513,13 +513,32 @@ TEST_F(StyledLabelTest, SetTextContextAndDefaultStyle) { } TEST_F(StyledLabelTest, LineHeight) { - const std::string text("one"); + const std::string text("one\ntwo\nthree"); + InitStyledLabel(text); + styled()->SetLineHeight(18); + EXPECT_EQ(18 * 3, styled()->GetHeightForWidth(100)); +} + +TEST_F(StyledLabelTest, LineHeightWithBorder) { + const std::string text("one\ntwo\nthree"); + InitStyledLabel(text); + styled()->SetLineHeight(18); + styled()->SetBorder(views::CreateSolidBorder(1, SK_ColorGRAY)); + EXPECT_EQ(18 * 3 + 2, styled()->GetHeightForWidth(100)); +} + +TEST_F(StyledLabelTest, LineHeightWithLink) { + const std::string text("one\ntwo\nthree"); InitStyledLabel(text); - int default_height = styled()->GetHeightForWidth(100); - const std::string newline_text("one\ntwo\nthree"); - InitStyledLabel(newline_text); styled()->SetLineHeight(18); - EXPECT_EQ(18 * 2 + default_height, styled()->GetHeightForWidth(100)); + + styled()->AddStyleRange(gfx::Range(0, 3), + StyledLabel::RangeStyleInfo::CreateForLink()); + styled()->AddStyleRange(gfx::Range(4, 7), + StyledLabel::RangeStyleInfo::CreateForLink()); + styled()->AddStyleRange(gfx::Range(8, 13), + StyledLabel::RangeStyleInfo::CreateForLink()); + EXPECT_EQ(18 * 3, styled()->GetHeightForWidth(100)); } TEST_F(StyledLabelTest, HandleEmptyLayout) { diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc index 04e572c25d0..96309bc1709 100644 --- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc +++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc @@ -407,7 +407,7 @@ TabStrip::TabStrip(TabbedPane::Orientation orientation, layout->set_cross_axis_alignment(BoxLayout::CROSS_AXIS_ALIGNMENT_END); } else { const int kTabStripEdgePadding = 8; - const int kTabSpacing = 16; + const int kTabSpacing = 8; layout = std::make_unique<BoxLayout>( BoxLayout::kVertical, gfx::Insets(kTabStripEdgePadding, 0, 0, 0), kTabSpacing); diff --git a/chromium/ui/views/controls/textfield/textfield.cc b/chromium/ui/views/controls/textfield/textfield.cc index 752c642e2da..2ff60335add 100644 --- a/chromium/ui/views/controls/textfield/textfield.cc +++ b/chromium/ui/views/controls/textfield/textfield.cc @@ -19,6 +19,7 @@ #include "ui/base/cursor/cursor.h" #include "ui/base/default_style.h" #include "ui/base/dragdrop/drag_drop_types.h" +#include "ui/base/ime/constants.h" #include "ui/base/ime/input_method.h" #include "ui/base/ime/text_edit_commands.h" #include "ui/base/resource/resource_bundle.h" @@ -73,6 +74,7 @@ #endif #if defined(OS_MACOSX) +#include "ui/base/cocoa/defaults_utils.h" #include "ui/base/cocoa/secure_password_input.h" #endif @@ -149,6 +151,14 @@ ui::TextEditCommand GetCommandForKeyEvent(const ui::KeyEvent& event) { return shift ? ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION : ui::TextEditCommand::MOVE_TO_END_OF_LINE; + case ui::VKEY_UP: + return shift ? ui::TextEditCommand:: + MOVE_TO_BEGINNING_OF_LINE_AND_MODIFY_SELECTION + : ui::TextEditCommand::INVALID_COMMAND; + case ui::VKEY_DOWN: + return shift + ? ui::TextEditCommand::MOVE_TO_END_OF_LINE_AND_MODIFY_SELECTION + : ui::TextEditCommand::INVALID_COMMAND; case ui::VKEY_BACK: if (!control) return ui::TextEditCommand::DELETE_BACKWARD; @@ -203,11 +213,14 @@ ui::TextEditCommand GetTextEditCommandFromMenuCommand(int command_id, return ui::TextEditCommand::INVALID_COMMAND; } -base::TimeDelta GetPasswordRevealDuration() { - return ViewsDelegate::GetInstance() - ? ViewsDelegate::GetInstance() - ->GetTextfieldPasswordRevealDuration() - : base::TimeDelta(); +base::TimeDelta GetPasswordRevealDuration(const ui::KeyEvent& event) { + // The key event may carries the property that indicates it was from the + // virtual keyboard. + // In that case, reveals the password characters for 1 second. + auto* properties = event.properties(); + bool from_vk = + properties && properties->find(ui::kPropertyFromVK) != properties->end(); + return from_vk ? base::TimeDelta::FromSeconds(1) : base::TimeDelta(); } bool IsControlKeyModifier(int flags) { @@ -229,8 +242,6 @@ const char Textfield::kViewClassName[] = "Textfield"; // static base::TimeDelta Textfield::GetCaretBlinkInterval() { - static constexpr base::TimeDelta default_value = - base::TimeDelta::FromMilliseconds(500); #if defined(OS_WIN) static const size_t system_value = ::GetCaretBlinkTime(); if (system_value != 0) { @@ -238,8 +249,12 @@ base::TimeDelta Textfield::GetCaretBlinkInterval() { ? base::TimeDelta() : base::TimeDelta::FromMilliseconds(system_value); } +#elif defined(OS_MACOSX) + base::TimeDelta system_value; + if (ui::TextInsertionCaretBlinkPeriod(&system_value)) + return system_value; #endif - return default_value; + return base::TimeDelta::FromMilliseconds(500); } // static @@ -1398,17 +1413,9 @@ bool Textfield::GetAcceleratorForCommandId(int command_id, *accelerator = ui::Accelerator(ui::VKEY_A, ui::EF_PLATFORM_ACCELERATOR); return true; - case IDS_CONTENT_CONTEXT_EMOJI: -#if defined(OS_MACOSX) - *accelerator = ui::Accelerator(ui::VKEY_SPACE, - ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN); - return true; -#else - return false; -#endif - default: - return false; + return text_services_context_menu_->GetAcceleratorForCommandId( + command_id, accelerator); } } @@ -1495,11 +1502,14 @@ void Textfield::InsertChar(const ui::KeyEvent& event) { DoInsertChar(ch); - if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD && - !GetPasswordRevealDuration().is_zero()) { - const size_t change_offset = model_->GetCursorPosition(); - DCHECK_GT(change_offset, 0u); - RevealPasswordChar(change_offset - 1); + if (text_input_type_ == ui::TEXT_INPUT_TYPE_PASSWORD) { + password_char_reveal_index_ = -1; + base::TimeDelta duration = GetPasswordRevealDuration(event); + if (!duration.is_zero()) { + const size_t change_offset = model_->GetCursorPosition(); + DCHECK_GT(change_offset, 0u); + RevealPasswordChar(change_offset - 1, duration); + } } } @@ -2287,15 +2297,16 @@ bool Textfield::ImeEditingAllowed() const { return (t != ui::TEXT_INPUT_TYPE_NONE && t != ui::TEXT_INPUT_TYPE_PASSWORD); } -void Textfield::RevealPasswordChar(int index) { +void Textfield::RevealPasswordChar(int index, base::TimeDelta duration) { GetRenderText()->SetObscuredRevealIndex(index); SchedulePaint(); + password_char_reveal_index_ = index; if (index != -1) { password_reveal_timer_.Start( - FROM_HERE, GetPasswordRevealDuration(), + FROM_HERE, duration, base::Bind(&Textfield::RevealPasswordChar, - weak_ptr_factory_.GetWeakPtr(), -1)); + weak_ptr_factory_.GetWeakPtr(), -1, duration)); } } diff --git a/chromium/ui/views/controls/textfield/textfield.h b/chromium/ui/views/controls/textfield/textfield.h index 314bde9feff..4d20d983432 100644 --- a/chromium/ui/views/controls/textfield/textfield.h +++ b/chromium/ui/views/controls/textfield/textfield.h @@ -244,6 +244,8 @@ class VIEWS_EXPORT Textfield : public View, // Set extra spacing placed between glyphs; used for obscured text styling. void SetGlyphSpacing(int spacing); + int GetPasswordCharRevealIndex() const { return password_char_reveal_index_; } + // View overrides: int GetBaseline() const override; gfx::Size CalculatePreferredSize() const override; @@ -470,7 +472,8 @@ class VIEWS_EXPORT Textfield : public View, // Reveals the password character at |index| for a set duration. // If |index| is -1, the existing revealed character will be reset. - void RevealPasswordChar(int index); + // |duration| is the time to remain the password char to be visible. + void RevealPasswordChar(int index, base::TimeDelta duration); void CreateTouchSelectionControllerAndNotifyIt(); @@ -628,6 +631,9 @@ class VIEWS_EXPORT Textfield : public View, // The focus ring for this TextField. std::unique_ptr<FocusRing> focus_ring_; + // The password char reveal index, for testing only. + int password_char_reveal_index_ = -1; + // Used to bind callback functions to this object. base::WeakPtrFactory<Textfield> weak_ptr_factory_; diff --git a/chromium/ui/views/controls/textfield/textfield_model.cc b/chromium/ui/views/controls/textfield/textfield_model.cc index 4d77de34ffc..1bfea07108c 100644 --- a/chromium/ui/views/controls/textfield/textfield_model.cc +++ b/chromium/ui/views/controls/textfield/textfield_model.cc @@ -9,6 +9,7 @@ #include "base/logging.h" #include "base/macros.h" #include "base/message_loop/message_loop.h" +#include "base/no_destructor.h" #include "base/strings/string_util.h" #include "base/strings/utf_string_conversions.h" #include "ui/base/clipboard/clipboard.h" @@ -268,9 +269,9 @@ gfx::Range GetFirstEmphasizedRange(const ui::CompositionText& composition) { // NSTextKillRingSize, a text system default. However to keep things simple, // the default kill ring size of 1 (i.e. a single buffer) is assumed. base::string16* GetKillBuffer() { - CR_DEFINE_STATIC_LOCAL(base::string16, kill_buffer, ()); + static base::NoDestructor<base::string16> kill_buffer; DCHECK(base::MessageLoopForUI::IsCurrent()); - return &kill_buffer; + return kill_buffer.get(); } // Helper method to set the kill buffer. @@ -486,7 +487,7 @@ bool TextfieldModel::CanRedo() { if (edit_history_.empty()) return false; // There is no redo iff the current edit is the last element in the history. - EditHistory::iterator iter = current_edit_; + auto iter = current_edit_; return iter == edit_history_.end() || // at the top. ++iter != edit_history_.end(); } @@ -760,7 +761,7 @@ void TextfieldModel::ClearRedoHistory() { ClearEditHistory(); return; } - EditHistory::iterator delete_start = current_edit_; + auto delete_start = current_edit_; ++delete_start; edit_history_.erase(delete_start, edit_history_.end()); } diff --git a/chromium/ui/views/controls/textfield/textfield_unittest.cc b/chromium/ui/views/controls/textfield/textfield_unittest.cc index f8a8292c501..be57d658a24 100644 --- a/chromium/ui/views/controls/textfield/textfield_unittest.cc +++ b/chromium/ui/views/controls/textfield/textfield_unittest.cc @@ -11,6 +11,7 @@ #include <string> #include <vector> +#include "base/bind_helpers.h" #include "base/command_line.h" #include "base/format_macros.h" #include "base/i18n/rtl.h" @@ -27,6 +28,7 @@ #include "ui/base/clipboard/scoped_clipboard_writer.h" #include "ui/base/dragdrop/drag_drop_types.h" #include "ui/base/emoji/emoji_panel_helper.h" +#include "ui/base/ime/constants.h" #include "ui/base/ime/input_method_base.h" #include "ui/base/ime/input_method_delegate.h" #include "ui/base/ime/input_method_factory.h" @@ -157,7 +159,7 @@ ui::EventDispatchDetails MockInputMethod::DispatchKeyEvent(ui::KeyEvent* key) { // which trigger the appropriate NSResponder action messages for composition. #if defined(OS_MACOSX) if (key->is_char()) - return DispatchKeyEventPostIME(key); + return DispatchKeyEventPostIME(key, base::NullCallback()); #endif // Checks whether the key event is from EventGenerator on Windows which will @@ -177,9 +179,9 @@ ui::EventDispatchDetails MockInputMethod::DispatchKeyEvent(ui::KeyEvent* key) { ui::KeyEvent mock_key(ui::ET_KEY_PRESSED, ui::VKEY_PROCESSKEY, key->flags()); - dispatch_details = DispatchKeyEventPostIME(&mock_key); + dispatch_details = DispatchKeyEventPostIME(&mock_key, base::NullCallback()); } else { - dispatch_details = DispatchKeyEventPostIME(key); + dispatch_details = DispatchKeyEventPostIME(key, base::NullCallback()); } if (key->handled() || dispatch_details.dispatcher_destroyed) @@ -553,9 +555,13 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController { SendKeyEvent(key_code, false, false); } - void SendKeyEvent(base::char16 ch) { SendKeyEvent(ch, ui::EF_NONE); } + void SendKeyEvent(base::char16 ch) { SendKeyEvent(ch, ui::EF_NONE, false); } void SendKeyEvent(base::char16 ch, int flags) { + SendKeyEvent(ch, flags, false); + } + + void SendKeyEvent(base::char16 ch, int flags, bool from_vk) { if (ch < 0x80) { ui::KeyboardCode code = ch == ' ' ? ui::VKEY_SPACE : @@ -567,6 +573,11 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController { // Mac, key events don't pass through InputMethod. Hence they are // dispatched regularly. ui::KeyEvent event(ch, ui::VKEY_UNKNOWN, ui::DomCode::NONE, flags); + if (from_vk) { + ui::Event::Properties properties; + properties[ui::kPropertyFromVK] = std::vector<uint8_t>(); + event.SetProperties(properties); + } #if defined(OS_MACOSX) event_generator_->Dispatch(&event); #else @@ -1056,27 +1067,18 @@ TEST_F(TextfieldTest, MoveUpDownAndModifySelection) { textfield_->SetSelectionRange(gfx::Range(6)); - // Shift+[Up/Down] on Mac should execute the command - // MOVE_[UP/DOWN]_AND_MODIFY_SELECTION. On other platforms, textfield won't - // handle these events. + // Shift+[Up/Down] should select the text to the beginning and end of the + // line, respectively. SendKeyEvent(ui::VKEY_UP, true /* shift */, false /* command */); EXPECT_TRUE(textfield_->key_received()); -#if defined(OS_MACOSX) EXPECT_TRUE(textfield_->key_handled()); EXPECT_EQ(gfx::Range(6, 0), textfield_->GetSelectedRange()); -#else - EXPECT_FALSE(textfield_->key_handled()); -#endif textfield_->clear(); SendKeyEvent(ui::VKEY_DOWN, true /* shift */, false /* command */); EXPECT_TRUE(textfield_->key_received()); -#if defined(OS_MACOSX) EXPECT_TRUE(textfield_->key_handled()); EXPECT_EQ(gfx::Range(6, 11), textfield_->GetSelectedRange()); -#else - EXPECT_FALSE(textfield_->key_handled()); -#endif textfield_->clear(); } @@ -1292,8 +1294,15 @@ TEST_F(TextfieldTest, TextInputType_InsertionTest) { EXPECT_EQ(ui::TEXT_INPUT_TYPE_PASSWORD, textfield_->GetTextInputType()); SendKeyEvent(ui::VKEY_A); - SendKeyEvent(kHebrewLetterSamekh); + EXPECT_EQ(-1, textfield_->GetPasswordCharRevealIndex()); + SendKeyEvent(kHebrewLetterSamekh, ui::EF_NONE, true /* from_vk */); +#if !defined(OS_MACOSX) + // Don't verifies the password character reveal on MacOS, because on MacOS, + // the text insertion is not done through TextInputClient::InsertChar(). + EXPECT_EQ(1, textfield_->GetPasswordCharRevealIndex()); +#endif SendKeyEvent(ui::VKEY_B); + EXPECT_EQ(-1, textfield_->GetPasswordCharRevealIndex()); EXPECT_EQ(WideToUTF16(L"a\x05E1" L"b"), diff --git a/chromium/ui/views/controls/views_text_services_context_menu.h b/chromium/ui/views/controls/views_text_services_context_menu.h index bf411354261..726f6e37146 100644 --- a/chromium/ui/views/controls/views_text_services_context_menu.h +++ b/chromium/ui/views/controls/views_text_services_context_menu.h @@ -8,6 +8,7 @@ #include <memory> #include "base/i18n/rtl.h" +#include "ui/base/accelerators/accelerator.h" #include "ui/views/views_export.h" namespace ui { @@ -20,10 +21,8 @@ class Textfield; // This class is used to add and handle text service items in the text context // menu. -class ViewsTextServicesContextMenu { +class ViewsTextServicesContextMenu : public ui::AcceleratorProvider { public: - virtual ~ViewsTextServicesContextMenu() {} - // Creates a platform-specific ViewsTextServicesContextMenu object. static std::unique_ptr<ViewsTextServicesContextMenu> Create( ui::SimpleMenuModel* menu, diff --git a/chromium/ui/views/controls/views_text_services_context_menu_base.cc b/chromium/ui/views/controls/views_text_services_context_menu_base.cc index eae69906422..89858fbb099 100644 --- a/chromium/ui/views/controls/views_text_services_context_menu_base.cc +++ b/chromium/ui/views/controls/views_text_services_context_menu_base.cc @@ -5,17 +5,28 @@ #include "ui/views/controls/views_text_services_context_menu_base.h" #include "base/metrics/histogram_macros.h" +#include "build/build_config.h" +#include "ui/base/accelerators/accelerator.h" #include "ui/base/emoji/emoji_panel_helper.h" #include "ui/base/models/simple_menu_model.h" +#include "ui/events/event.h" +#include "ui/events/event_constants.h" #include "ui/resources/grit/ui_resources.h" #include "ui/strings/grit/ui_strings.h" #include "ui/views/controls/textfield/textfield.h" namespace views { +namespace { + const char kViewsTextServicesContextMenuHistogram[] = "ViewsTextServicesContextMenu.Used"; +// Do not change the values in this enum as they are used by UMA. +enum class Command { kEmoji = 0, kMaxValue = kEmoji }; + +} // namespace + ViewsTextServicesContextMenuBase::ViewsTextServicesContextMenuBase( ui::SimpleMenuModel* menu, Textfield* client) @@ -25,7 +36,7 @@ ViewsTextServicesContextMenuBase::ViewsTextServicesContextMenuBase( // Not inserted on read-only fields or if the OS/version doesn't support it. if (!client_->read_only() && ui::IsEmojiPanelSupported()) { menu->InsertSeparatorAt(0, ui::NORMAL_SEPARATOR); - menu->InsertItemWithStringIdAt(0, static_cast<int>(Command::kEmoji), + menu->InsertItemWithStringIdAt(0, IDS_CONTENT_CONTEXT_EMOJI, IDS_CONTENT_CONTEXT_EMOJI); } } @@ -33,7 +44,27 @@ ViewsTextServicesContextMenuBase::ViewsTextServicesContextMenuBase( ViewsTextServicesContextMenuBase::~ViewsTextServicesContextMenuBase() {} bool ViewsTextServicesContextMenuBase::SupportsCommand(int command_id) const { - return command_id == static_cast<int>(Command::kEmoji); + return command_id == IDS_CONTENT_CONTEXT_EMOJI; +} + +bool ViewsTextServicesContextMenuBase::GetAcceleratorForCommandId( + int command_id, + ui::Accelerator* accelerator) const { + if (command_id == IDS_CONTENT_CONTEXT_EMOJI) { +#if defined(OS_WIN) + *accelerator = ui::Accelerator(ui::VKEY_OEM_PERIOD, ui::EF_COMMAND_DOWN); + return true; +#elif defined(OS_MACOSX) + *accelerator = ui::Accelerator(ui::VKEY_SPACE, + ui::EF_COMMAND_DOWN | ui::EF_CONTROL_DOWN); + return true; +#else + // TODO(crbug.com/887660): Add accelerator key for Chrome OS. + return false; +#endif + } + + return false; } bool ViewsTextServicesContextMenuBase::IsCommandIdChecked( @@ -43,14 +74,14 @@ bool ViewsTextServicesContextMenuBase::IsCommandIdChecked( bool ViewsTextServicesContextMenuBase::IsCommandIdEnabled( int command_id) const { - if (command_id == static_cast<int>(Command::kEmoji)) + if (command_id == IDS_CONTENT_CONTEXT_EMOJI) return true; return false; } void ViewsTextServicesContextMenuBase::ExecuteCommand(int command_id) { - if (command_id == static_cast<int>(Command::kEmoji)) { + if (command_id == IDS_CONTENT_CONTEXT_EMOJI) { ui::ShowEmojiPanel(); UMA_HISTOGRAM_ENUMERATION(kViewsTextServicesContextMenuHistogram, Command::kEmoji); diff --git a/chromium/ui/views/controls/views_text_services_context_menu_base.h b/chromium/ui/views/controls/views_text_services_context_menu_base.h index 08e5a7ad0c0..bbc3e19d206 100644 --- a/chromium/ui/views/controls/views_text_services_context_menu_base.h +++ b/chromium/ui/views/controls/views_text_services_context_menu_base.h @@ -21,6 +21,10 @@ class ViewsTextServicesContextMenuBase : public ViewsTextServicesContextMenu { // Returns true if the given |command_id| is handled by the menu. bool SupportsCommand(int command_id) const override; + // ui::AcceleratorProvider: + bool GetAcceleratorForCommandId(int command_id, + ui::Accelerator* accelerator) const override; + // Methods associated with SimpleMenuModel::Delegate. bool IsCommandIdChecked(int command_id) const override; bool IsCommandIdEnabled(int command_id) const override; @@ -30,9 +34,6 @@ class ViewsTextServicesContextMenuBase : public ViewsTextServicesContextMenu { Textfield* client() const { return client_; } private: - // Do not change the values in this enum as they are used by UMA. - enum class Command { kEmoji = 0, kMaxValue = kEmoji }; - // The view associated with the menu. Weak. Owns |this|. Textfield* client_ = nullptr; diff --git a/chromium/ui/views/controls/webview/unhandled_keyboard_event_handler_mac.mm b/chromium/ui/views/controls/webview/unhandled_keyboard_event_handler_mac.mm index ece41dd5f53..e87f913ff61 100644 --- a/chromium/ui/views/controls/webview/unhandled_keyboard_event_handler_mac.mm +++ b/chromium/ui/views/controls/webview/unhandled_keyboard_event_handler_mac.mm @@ -5,7 +5,7 @@ #include "ui/views/controls/webview/unhandled_keyboard_event_handler.h" #import "base/mac/foundation_util.h" -#import "ui/views/cocoa/native_widget_mac_nswindow.h" +#import "ui/views_bridge_mac/native_widget_mac_nswindow.h" namespace views { diff --git a/chromium/ui/views/controls/webview/web_dialog_view.cc b/chromium/ui/views/controls/webview/web_dialog_view.cc index 90a9612cf61..56fc98b2840 100644 --- a/chromium/ui/views/controls/webview/web_dialog_view.cc +++ b/chromium/ui/views/controls/webview/web_dialog_view.cc @@ -116,7 +116,7 @@ bool WebDialogView::CanClose() { if (!is_attempting_close_dialog_) { // Fire beforeunload event when user attempts to close the dialog. is_attempting_close_dialog_ = true; - web_view_->web_contents()->DispatchBeforeUnload(); + web_view_->web_contents()->DispatchBeforeUnload(false /* auto_cancel */); } return false; } @@ -140,6 +140,12 @@ base::string16 WebDialogView::GetWindowTitle() const { return base::string16(); } +base::string16 WebDialogView::GetAccessibleWindowTitle() const { + if (delegate_) + return delegate_->GetAccessibleDialogTitle(); + return GetWindowTitle(); +} + std::string WebDialogView::GetWindowName() const { if (delegate_) return delegate_->GetDialogName(); diff --git a/chromium/ui/views/controls/webview/web_dialog_view.h b/chromium/ui/views/controls/webview/web_dialog_view.h index 0fd7bc03288..29d18cb2e1f 100644 --- a/chromium/ui/views/controls/webview/web_dialog_view.h +++ b/chromium/ui/views/controls/webview/web_dialog_view.h @@ -66,6 +66,7 @@ class WEBVIEW_EXPORT WebDialogView : public views::ClientView, bool CanResize() const override; ui::ModalType GetModalType() const override; base::string16 GetWindowTitle() const override; + base::string16 GetAccessibleWindowTitle() const override; std::string GetWindowName() const override; void WindowClosing() override; views::View* GetContentsView() override; diff --git a/chromium/ui/views/controls/webview/webview.cc b/chromium/ui/views/controls/webview/webview.cc index 74ddbc9b3a4..2a4cc7e8938 100644 --- a/chromium/ui/views/controls/webview/webview.cc +++ b/chromium/ui/views/controls/webview/webview.cc @@ -149,18 +149,6 @@ const char* WebView::GetClassName() const { return kViewClassName; } -std::unique_ptr<content::WebContents> WebView::SwapWebContents( - std::unique_ptr<content::WebContents> new_web_contents) { - if (wc_owner_) - wc_owner_->SetDelegate(NULL); - std::unique_ptr<content::WebContents> old_web_contents(std::move(wc_owner_)); - wc_owner_ = std::move(new_web_contents); - if (wc_owner_) - wc_owner_->SetDelegate(this); - SetWebContents(wc_owner_.get()); - return old_web_contents; -} - void WebView::OnBoundsChanged(const gfx::Rect& previous_bounds) { if (crashed_overlay_view_) crashed_overlay_view_->SetBoundsRect(gfx::Rect(size())); @@ -258,6 +246,10 @@ void WebView::GetAccessibleNodeData(ui::AXNodeData* node_data) { // provided via other means. Providing it here would be redundant. // Mark the name as explicitly empty so that accessibility_checks pass. node_data->SetNameExplicitlyEmpty(); + if (child_ax_tree_id_ != ui::AXTreeIDUnknown()) { + node_data->AddStringAttribute(ax::mojom::StringAttribute::kChildTreeId, + child_ax_tree_id_); + } } gfx::NativeViewAccessible WebView::GetNativeViewAccessible() { @@ -415,8 +407,13 @@ void WebView::UpdateCrashedOverlayView() { } void WebView::NotifyAccessibilityWebContentsChanged() { - if (web_contents()) - NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged, false); + content::RenderFrameHost* rfh = + web_contents() ? web_contents()->GetMainFrame() : nullptr; + if (rfh) + child_ax_tree_id_ = rfh->GetAXTreeID(); + else + child_ax_tree_id_ = ui::AXTreeIDUnknown(); + NotifyAccessibilityEvent(ax::mojom::Event::kChildrenChanged, false); } std::unique_ptr<content::WebContents> WebView::CreateWebContents( diff --git a/chromium/ui/views/controls/webview/webview.h b/chromium/ui/views/controls/webview/webview.h index 994520b83b1..2a4b4937c16 100644 --- a/chromium/ui/views/controls/webview/webview.h +++ b/chromium/ui/views/controls/webview/webview.h @@ -120,11 +120,6 @@ class WEBVIEW_EXPORT WebView : public View, }; protected: - // Swaps the owned WebContents |wc_owner_| with |new_web_contents|. Returns - // the previously owned WebContents. - std::unique_ptr<content::WebContents> SwapWebContents( - std::unique_ptr<content::WebContents> new_web_contents); - // Called when the web contents is successfully attached. virtual void OnWebContentsAttached() {} // Called when letterboxing (scaling the native view to preserve aspect @@ -211,6 +206,10 @@ class WEBVIEW_EXPORT WebView : public View, gfx::Size min_size_; gfx::Size max_size_; + // Tracks the child accessibility tree id which is associated with the + // WebContents's main RenderFrameHost. + ui::AXTreeID child_ax_tree_id_; + DISALLOW_COPY_AND_ASSIGN(WebView); }; diff --git a/chromium/ui/views/corewm/tooltip_controller.cc b/chromium/ui/views/corewm/tooltip_controller.cc index 02cd71ac0ad..77d84caccc6 100644 --- a/chromium/ui/views/corewm/tooltip_controller.cc +++ b/chromium/ui/views/corewm/tooltip_controller.cc @@ -95,16 +95,14 @@ aura::Window* GetTooltipTarget(const ui::MouseEvent& event, // If |target| has capture all events go to it, even if the mouse is // really over another window. Find the real window the mouse is over. - gfx::Point screen_loc(event.location()); - aura::client::GetScreenPositionClient(event_target->GetRootWindow())-> - ConvertPointToScreen(event_target, &screen_loc); + const gfx::Point screen_loc = event.target()->GetScreenLocation(event); display::Screen* screen = display::Screen::GetScreen(); aura::Window* target = screen->GetWindowAtScreenPoint(screen_loc); if (!target) return NULL; gfx::Point target_loc(screen_loc); - aura::client::GetScreenPositionClient(target->GetRootWindow())-> - ConvertPointFromScreen(target, &target_loc); + aura::client::GetScreenPositionClient(target->GetRootWindow()) + ->ConvertPointFromScreen(target, &target_loc); aura::Window* screen_target = target->GetEventHandlerForPoint(target_loc); if (!IsValidTarget(event_target, screen_target)) return NULL; diff --git a/chromium/ui/views/corewm/tooltip_controller_unittest.cc b/chromium/ui/views/corewm/tooltip_controller_unittest.cc index 1212a6f38b1..83519930cee 100644 --- a/chromium/ui/views/corewm/tooltip_controller_unittest.cc +++ b/chromium/ui/views/corewm/tooltip_controller_unittest.cc @@ -463,10 +463,8 @@ namespace { // Returns the index of |window| in its parent's children. int IndexInParent(const aura::Window* window) { - aura::Window::Windows::const_iterator i = - std::find(window->parent()->children().begin(), - window->parent()->children().end(), - window); + auto i = std::find(window->parent()->children().begin(), + window->parent()->children().end(), window); return i == window->parent()->children().end() ? -1 : static_cast<int>(i - window->parent()->children().begin()); } diff --git a/chromium/ui/views/corewm/tooltip_win.cc b/chromium/ui/views/corewm/tooltip_win.cc index 462365750fb..153d4c47484 100644 --- a/chromium/ui/views/corewm/tooltip_win.cc +++ b/chromium/ui/views/corewm/tooltip_win.cc @@ -4,8 +4,6 @@ #include "ui/views/corewm/tooltip_win.h" -#include <winuser.h> - #include "base/debug/stack_trace.h" #include "base/i18n/rtl.h" #include "base/logging.h" @@ -14,11 +12,28 @@ #include "ui/display/screen.h" #include "ui/display/win/screen_win.h" #include "ui/gfx/geometry/rect.h" +#include "ui/gfx/platform_font_win.h" #include "ui/views/corewm/cursor_height_provider_win.h" namespace views { namespace corewm { +namespace { + +// Substitute GetWindowFont() from windowsx.h. +// Do not include windowsx.h as its macros break the views jumbo build. +HFONT GetWindowFont(HWND hwnd) { + return reinterpret_cast<HFONT>(::SendMessage(hwnd, WM_GETFONT, 0, 0)); +} + +// Substitute SetWindowFont() from windowsx.h. +// Do not include windowsx.h as its macros break the views jumbo build. +void SetWindowFont(HWND hwnd, HFONT hfont, BOOL fRedraw) { + ::SendMessage(hwnd, WM_SETFONT, reinterpret_cast<WPARAM>(hfont), fRedraw); +} + +} // namespace + TooltipWin::TooltipWin(HWND parent) : parent_hwnd_(parent), tooltip_hwnd_(NULL), @@ -70,7 +85,7 @@ bool TooltipWin::EnsureTooltipWindow() { return false; } - l10n_util::AdjustUIFontForWindow(tooltip_hwnd_); + MaybeOverrideFont(); SendMessage(tooltip_hwnd_, TTM_ADDTOOL, 0, reinterpret_cast<LPARAM>(&toolinfo_)); @@ -96,6 +111,33 @@ void TooltipWin::PositionTooltip() { display.work_area())); SetWindowPos(tooltip_hwnd_, NULL, tooltip_bounds.x(), tooltip_bounds.y(), 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE); + + MaybeOverrideFont(); +} + +void TooltipWin::MaybeOverrideFont() { + gfx::PlatformFontWin::FontAdjustment font_adjustment; + const HFONT old_font = GetWindowFont(tooltip_hwnd_); + + // Determine if we need to override the font. + if ((!override_font_ || override_font_->GetNativeFont() != old_font) && + l10n_util::NeedOverrideDefaultUIFont( + &font_adjustment.font_family_override, &font_adjustment.font_scale)) { + // Determine if we need to regenerate the font. + // There are a number of situations under which Windows can replace the + // font in a tooltip, but we don't actually need to regenerate our override + // font unless the underlying text/DPI scale of the window has changed. + const float current_scale = + display::win::ScreenWin::GetScaleFactorForHWND(tooltip_hwnd_); + if (!override_font_ || current_scale != override_scale_) { + override_font_ = + gfx::PlatformFontWin::AdjustExistingFont(old_font, font_adjustment); + override_scale_ = current_scale; + } + + // Override the font in the tooltip. + SetWindowFont(tooltip_hwnd_, override_font_->GetNativeFont(), FALSE); + } } int TooltipWin::GetMaxWidth(const gfx::Point& location) const { @@ -116,12 +158,6 @@ void TooltipWin::SetText(aura::Window* window, // See comment in header for details on why |location_| is needed. location_ = location; - // Without this we get a flicker of the tooltip appearing at 0x0. Not sure - // why. - SetWindowPos(tooltip_hwnd_, NULL, 0, 0, 0, 0, - SWP_HIDEWINDOW | SWP_NOACTIVATE | SWP_NOMOVE | - SWP_NOREPOSITION | SWP_NOSIZE | SWP_NOZORDER); - base::string16 adjusted_text(tooltip_text); base::i18n::AdjustStringForLocaleDirection(&adjusted_text); toolinfo_.lpszText = const_cast<WCHAR*>(adjusted_text.c_str()); @@ -138,8 +174,6 @@ void TooltipWin::Show() { SendMessage(tooltip_hwnd_, TTM_TRACKACTIVATE, TRUE, reinterpret_cast<LPARAM>(&toolinfo_)); - SetWindowPos(tooltip_hwnd_, HWND_TOPMOST, 0, 0, 0, 0, - SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_NOSIZE); } void TooltipWin::Hide() { diff --git a/chromium/ui/views/corewm/tooltip_win.h b/chromium/ui/views/corewm/tooltip_win.h index eb9e5209de3..e656c87d3ce 100644 --- a/chromium/ui/views/corewm/tooltip_win.h +++ b/chromium/ui/views/corewm/tooltip_win.h @@ -5,15 +5,17 @@ #ifndef UI_VIEWS_COREWM_TOOLTIP_WIN_H_ #define UI_VIEWS_COREWM_TOOLTIP_WIN_H_ +#include <windows.h> +#include <commctrl.h> + #include "base/compiler_specific.h" #include "base/macros.h" +#include "base/optional.h" #include "base/strings/string16.h" +#include "ui/gfx/font.h" #include "ui/gfx/geometry/point.h" #include "ui/views/corewm/tooltip.h" -#include <windows.h> -#include <commctrl.h> - namespace views { namespace corewm { @@ -36,6 +38,9 @@ class VIEWS_EXPORT TooltipWin : public Tooltip { // Sets the position of the tooltip. void PositionTooltip(); + // Might override the font size for localization (e.g. Hindi). + void MaybeOverrideFont(); + // Tooltip: int GetMaxWidth(const gfx::Point& location) const override; void SetText(aura::Window* window, @@ -45,6 +50,11 @@ class VIEWS_EXPORT TooltipWin : public Tooltip { void Hide() override; bool IsVisible() override; + // Font we're currently overriding our UI font with. + // (Lets us keep a handle around so we don't leak.) + // Should outlast |tooltip_hwnd_|. + base::Optional<gfx::Font> override_font_; + // The window |tooltip_hwnd_| is parented to. HWND parent_hwnd_; @@ -62,6 +72,10 @@ class VIEWS_EXPORT TooltipWin : public Tooltip { // cache it. gfx::Point location_; + // What the scale was the last time we overrode the font, to see if we can + // re-use our previous override. + float override_scale_ = 0.0f; + DISALLOW_COPY_AND_ASSIGN(TooltipWin); }; diff --git a/chromium/ui/views/event_utils.cc b/chromium/ui/views/event_utils.cc new file mode 100644 index 00000000000..ecec6e0220c --- /dev/null +++ b/chromium/ui/views/event_utils.cc @@ -0,0 +1,22 @@ +// Copyright (c) 2018 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 "ui/views/event_utils.h" + +#include "base/time/time.h" +#include "ui/events/event.h" +#include "ui/views/metrics.h" + +namespace views { + +bool IsPossiblyUnintendedInteraction(const base::TimeTicks& initial_timestamp, + const ui::Event& event) { + return (event.IsMouseEvent() || event.IsPointerEvent() || + event.IsTouchEvent()) && + event.time_stamp() < + initial_timestamp + + base::TimeDelta::FromMilliseconds(GetDoubleClickInterval()); +} + +} // namespace views diff --git a/chromium/ui/views/event_utils.h b/chromium/ui/views/event_utils.h new file mode 100644 index 00000000000..cfc553ff659 --- /dev/null +++ b/chromium/ui/views/event_utils.h @@ -0,0 +1,28 @@ +// Copyright (c) 2018 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 UI_VIEWS_EVENT_UTILS_H_ +#define UI_VIEWS_EVENT_UTILS_H_ + +#include "ui/views/views_export.h" + +namespace base { +class TimeTicks; +} + +namespace ui { +class Event; +} + +namespace views { + +// Returns true if the event is a mouse, touch, or pointer event that took place +// within the double-click time interval after the |initial_timestamp|. +VIEWS_EXPORT bool IsPossiblyUnintendedInteraction( + const base::TimeTicks& initial_timestamp, + const ui::Event& event); + +} // namespace views + +#endif // UI_VIEWS_EVENT_UTILS_H_ diff --git a/chromium/ui/views/examples/BUILD.gn b/chromium/ui/views/examples/BUILD.gn index c757e20b7ff..0069d8c9c8d 100644 --- a/chromium/ui/views/examples/BUILD.gn +++ b/chromium/ui/views/examples/BUILD.gn @@ -9,6 +9,8 @@ jumbo_component("views_examples_lib") { testonly = true sources = [ + "animated_image_view_example.cc", + "animated_image_view_example.h", "box_layout_example.cc", "box_layout_example.h", "bubble_example.cc", diff --git a/chromium/ui/views/examples/animated_image_view_example.cc b/chromium/ui/views/examples/animated_image_view_example.cc new file mode 100644 index 00000000000..4e52a95aeab --- /dev/null +++ b/chromium/ui/views/examples/animated_image_view_example.cc @@ -0,0 +1,143 @@ +// Copyright 2018 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 "ui/views/examples/animated_image_view_example.h" + +#include <memory> + +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/threading/thread_restrictions.h" +#include "build/build_config.h" +#include "ui/gfx/geometry/insets.h" +#include "ui/gfx/skottie_wrapper.h" +#include "ui/views/border.h" +#include "ui/views/controls/animated_image_view.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/controls/button/md_text_button.h" +#include "ui/views/controls/textfield/textfield.h" +#include "ui/views/controls/textfield/textfield_controller.h" +#include "ui/views/layout/box_layout.h" +#include "ui/views/layout/fill_layout.h" +#include "ui/views/view.h" + +namespace views { +namespace examples { + +namespace { + +// This class can load a skottie(and lottie) animation file from disk and play +// it in a view as AnimatedImageView. +// See https://skia.org/user/modules/skottie for more info on skottie. +class AnimationGallery : public View, + public TextfieldController, + public ButtonListener { + public: + AnimationGallery() + : animated_image_view_(new AnimatedImageView()), + image_view_container_(new views::View()), + size_input_(new Textfield()), + file_chooser_(new Textfield()), + file_go_button_( + MdTextButton::Create(this, base::ASCIIToUTF16("Render"))) { + AddChildView(size_input_); + + image_view_container_->AddChildView(animated_image_view_); + image_view_container_->SetLayoutManager(std::make_unique<FillLayout>()); + animated_image_view_->SetBorder( + CreateSolidSidedBorder(1, 1, 1, 1, SK_ColorBLACK)); + AddChildView(image_view_container_); + + BoxLayout* box = SetLayoutManager( + std::make_unique<BoxLayout>(BoxLayout::kVertical, gfx::Insets(10), 10)); + box->SetFlexForView(image_view_container_, 1); + + file_chooser_->set_placeholder_text( + base::ASCIIToUTF16("Enter path to lottie JSON file")); + View* file_container = new View(); + BoxLayout* file_box = + file_container->SetLayoutManager(std::make_unique<BoxLayout>( + BoxLayout::kHorizontal, gfx::Insets(10), 10)); + file_container->AddChildView(file_chooser_); + file_container->AddChildView(file_go_button_); + file_box->SetFlexForView(file_chooser_, 1); + AddChildView(file_container); + + size_input_->set_placeholder_text( + base::ASCIIToUTF16("Size in dip (Empty for default)")); + size_input_->set_controller(this); + } + + ~AnimationGallery() override = default; + + // TextfieldController: + void ContentsChanged(Textfield* sender, + const base::string16& new_contents) override { + if (sender == size_input_) { + if (!base::StringToInt(new_contents, &size_) && (size_ > 0)) { + size_ = 0; + size_input_->SetText(base::string16()); + } + Update(); + } + } + + // ButtonListener: + void ButtonPressed(Button* sender, const ui::Event& event) override { + DCHECK_EQ(file_go_button_, sender); + std::string json; + base::ScopedAllowBlockingForTesting allow_blocking; +#if defined(OS_POSIX) + base::FilePath path(base::UTF16ToUTF8(file_chooser_->text())); +#else + base::FilePath path(file_chooser_->text()); +#endif // defined(OS_POSIX) + base::ReadFileToString(path, &json); + + auto skottie = base::MakeRefCounted<gfx::SkottieWrapper>( + base::RefCountedString::TakeString(&json)); + animated_image_view_->SetAnimatedImage( + std::make_unique<gfx::SkiaVectorAnimation>(skottie)); + animated_image_view_->Play(); + Update(); + } + + private: + void Update() { + if (size_ > 24) + animated_image_view_->SetImageSize(gfx::Size(size_, size_)); + else + animated_image_view_->ResetImageSize(); + Layout(); + } + + AnimatedImageView* animated_image_view_; + View* image_view_container_; + Textfield* size_input_; + Textfield* file_chooser_; + Button* file_go_button_; + + int size_ = 0; + + DISALLOW_COPY_AND_ASSIGN(AnimationGallery); +}; + +} // namespace + +AnimatedImageViewExample::AnimatedImageViewExample() + : ExampleBase("Animated Image View") {} + +AnimatedImageViewExample::~AnimatedImageViewExample() {} + +void AnimatedImageViewExample::CreateExampleView(View* container) { + container->SetLayoutManager(std::make_unique<FillLayout>()); + container->AddChildView(new AnimationGallery()); +} + +} // namespace examples +} // namespace views diff --git a/chromium/ui/views/examples/animated_image_view_example.h b/chromium/ui/views/examples/animated_image_view_example.h new file mode 100644 index 00000000000..8bf30b56a87 --- /dev/null +++ b/chromium/ui/views/examples/animated_image_view_example.h @@ -0,0 +1,29 @@ +// Copyright 2018 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 UI_VIEWS_EXAMPLES_ANIMATED_IMAGE_VIEW_EXAMPLE_H_ +#define UI_VIEWS_EXAMPLES_ANIMATED_IMAGE_VIEW_EXAMPLE_H_ + +#include "base/macros.h" +#include "ui/views/examples/example_base.h" + +namespace views { +namespace examples { + +class VIEWS_EXAMPLES_EXPORT AnimatedImageViewExample : public ExampleBase { + public: + AnimatedImageViewExample(); + ~AnimatedImageViewExample() override; + + // ExampleBase: + void CreateExampleView(View* container) override; + + private: + DISALLOW_COPY_AND_ASSIGN(AnimatedImageViewExample); +}; + +} // namespace examples +} // namespace views + +#endif // UI_VIEWS_EXAMPLES_ANIMATED_IMAGE_VIEW_EXAMPLE_H_ diff --git a/chromium/ui/views/examples/examples_main.cc b/chromium/ui/views/examples/examples_main.cc index 4dd22a71197..f2f3a2a205b 100644 --- a/chromium/ui/views/examples/examples_main.cc +++ b/chromium/ui/views/examples/examples_main.cc @@ -118,9 +118,10 @@ int main(int argc, char** argv) { display::Screen::SetScreenInstance(desktop_screen.get()); #endif - views::examples::ShowExamplesWindow(views::examples::QUIT_ON_CLOSE); + base::RunLoop run_loop; + views::examples::ShowExamplesWindow(run_loop.QuitClosure()); - base::RunLoop().Run(); + run_loop.Run(); ui::ResourceBundle::CleanupSharedInstance(); } diff --git a/chromium/ui/views/examples/examples_window.cc b/chromium/ui/views/examples/examples_window.cc index b903e478740..683d5d59711 100644 --- a/chromium/ui/views/examples/examples_window.cc +++ b/chromium/ui/views/examples/examples_window.cc @@ -18,6 +18,7 @@ #include "ui/views/background.h" #include "ui/views/controls/combobox/combobox.h" #include "ui/views/controls/label.h" +#include "ui/views/examples/animated_image_view_example.h" #include "ui/views/examples/box_layout_example.h" #include "ui/views/examples/bubble_example.h" #include "ui/views/examples/button_example.h" @@ -58,6 +59,7 @@ namespace { // Creates the default set of examples. ExampleVector CreateExamples() { ExampleVector examples; + examples.push_back(std::make_unique<AnimatedImageViewExample>()); examples.push_back(std::make_unique<BoxLayoutExample>()); examples.push_back(std::make_unique<BubbleExample>()); examples.push_back(std::make_unique<ButtonExample>()); @@ -131,11 +133,11 @@ class ComboboxModelExampleList : public ui::ComboboxModel { class ExamplesWindowContents : public WidgetDelegateView, public ComboboxListener { public: - ExamplesWindowContents(Operation operation, ExampleVector examples) + ExamplesWindowContents(base::OnceClosure on_close, ExampleVector examples) : combobox_(new Combobox(&combobox_model_)), example_shown_(new View), status_label_(new Label), - operation_(operation) { + on_close_(std::move(on_close)) { instance_ = this; combobox_->set_listener(this); combobox_model_.SetExamples(std::move(examples)); @@ -188,8 +190,8 @@ class ExamplesWindowContents : public WidgetDelegateView, } void WindowClosing() override { instance_ = NULL; - if (operation_ == QUIT_ON_CLOSE) - base::RunLoop::QuitCurrentWhenIdleDeprecated(); + if (on_close_) + std::move(on_close_).Run(); } gfx::Size CalculatePreferredSize() const override { return gfx::Size(800, 300); @@ -212,7 +214,7 @@ class ExamplesWindowContents : public WidgetDelegateView, Combobox* combobox_; View* example_shown_; Label* status_label_; - const Operation operation_; + base::OnceClosure on_close_; DISALLOW_COPY_AND_ASSIGN(ExamplesWindowContents); }; @@ -220,7 +222,7 @@ class ExamplesWindowContents : public WidgetDelegateView, // static ExamplesWindowContents* ExamplesWindowContents::instance_ = NULL; -void ShowExamplesWindow(Operation operation, +void ShowExamplesWindow(base::OnceClosure on_close, gfx::NativeWindow window_context, ExampleVector extra_examples) { if (ExamplesWindowContents::instance()) { @@ -230,7 +232,7 @@ void ShowExamplesWindow(Operation operation, Widget* widget = new Widget; Widget::InitParams params; params.delegate = - new ExamplesWindowContents(operation, std::move(examples)); + new ExamplesWindowContents(std::move(on_close), std::move(examples)); params.context = window_context; widget->Init(params); widget->Show(); diff --git a/chromium/ui/views/examples/examples_window.h b/chromium/ui/views/examples/examples_window.h index 804252fcc9b..d543c01c3f6 100644 --- a/chromium/ui/views/examples/examples_window.h +++ b/chromium/ui/views/examples/examples_window.h @@ -15,16 +15,11 @@ namespace views { namespace examples { -enum Operation { - DO_NOTHING_ON_CLOSE = 0, - QUIT_ON_CLOSE, -}; - // Shows a window with the views examples in it. |extra_examples| contains any // additional examples to add. |window_context| is used to determine where the // window should be created (see |Widget::InitParams::context| for details). VIEWS_EXAMPLES_EXPORT void ShowExamplesWindow( - Operation operation, + base::OnceClosure on_close, gfx::NativeWindow window_context = nullptr, std::vector<std::unique_ptr<ExampleBase>> extra_examples = std::vector<std::unique_ptr<ExampleBase>>()); diff --git a/chromium/ui/views/examples/examples_window_with_content.cc b/chromium/ui/views/examples/examples_window_with_content.cc index 1133e88fa9a..c9391fa8bd8 100644 --- a/chromium/ui/views/examples/examples_window_with_content.cc +++ b/chromium/ui/views/examples/examples_window_with_content.cc @@ -14,12 +14,13 @@ namespace views { namespace examples { -void ShowExamplesWindowWithContent(Operation operation, +void ShowExamplesWindowWithContent(base::OnceClosure on_close, content::BrowserContext* browser_context, gfx::NativeWindow window_context) { std::vector<std::unique_ptr<ExampleBase>> extra_examples; extra_examples.push_back(std::make_unique<WebViewExample>(browser_context)); - ShowExamplesWindow(operation, window_context, std::move(extra_examples)); + ShowExamplesWindow(std::move(on_close), window_context, + std::move(extra_examples)); } } // namespace examples diff --git a/chromium/ui/views/examples/examples_window_with_content.h b/chromium/ui/views/examples/examples_window_with_content.h index 945201bdf7c..dbcb1fbd806 100644 --- a/chromium/ui/views/examples/examples_window_with_content.h +++ b/chromium/ui/views/examples/examples_window_with_content.h @@ -18,7 +18,7 @@ namespace examples { // Shows a window with the views examples in it. VIEWS_EXAMPLES_WITH_CONTENT_EXPORT void ShowExamplesWindowWithContent( - Operation operation, + base::OnceClosure on_close, content::BrowserContext* browser_context, gfx::NativeWindow window_context); diff --git a/chromium/ui/views/examples/examples_with_content_main_exe.cc b/chromium/ui/views/examples/examples_with_content_main_exe.cc index ffbad7ff3e7..3506d740035 100644 --- a/chromium/ui/views/examples/examples_with_content_main_exe.cc +++ b/chromium/ui/views/examples/examples_with_content_main_exe.cc @@ -15,11 +15,12 @@ namespace { -void ShowContentExampleWindow(content::BrowserContext* browser_context, +void ShowContentExampleWindow(ui::ViewsContentClient* views_content_client, + content::BrowserContext* browser_context, gfx::NativeWindow window_context) { - views::examples::ShowExamplesWindowWithContent(views::examples::QUIT_ON_CLOSE, - browser_context, - window_context); + views::examples::ShowExamplesWindowWithContent( + std::move(views_content_client->quit_closure()), browser_context, + window_context); // These lines serve no purpose other than to introduce an explicit content // dependency. If the main executable doesn't have this dependency, the linker @@ -45,6 +46,7 @@ int main(int argc, const char** argv) { ui::ViewsContentClient views_content_client(argc, argv); #endif - views_content_client.set_task(base::Bind(&ShowContentExampleWindow)); + views_content_client.set_task(base::Bind( + &ShowContentExampleWindow, base::Unretained(&views_content_client))); return views_content_client.RunMain(); } diff --git a/chromium/ui/views/examples/menu_example.cc b/chromium/ui/views/examples/menu_example.cc index 2e9081c9893..3c2713a432c 100644 --- a/chromium/ui/views/examples/menu_example.cc +++ b/chromium/ui/views/examples/menu_example.cc @@ -155,7 +155,7 @@ void ExampleMenuModel::ExecuteCommand(int command_id, int event_flags) { checked_fruit = "Kiwi"; // Update the check status. - std::set<int>::iterator iter = checked_fruits_.find(command_id); + auto iter = checked_fruits_.find(command_id); if (iter == checked_fruits_.end()) { DVLOG(1) << "Checked " << checked_fruit; checked_fruits_.insert(command_id); diff --git a/chromium/ui/views/focus/focus_manager_unittest.cc b/chromium/ui/views/focus/focus_manager_unittest.cc index ed29e91704b..10da1cd7fb3 100644 --- a/chromium/ui/views/focus/focus_manager_unittest.cc +++ b/chromium/ui/views/focus/focus_manager_unittest.cc @@ -130,12 +130,6 @@ TEST_F(FocusManagerTest, FocusChangeListener) { } TEST_F(FocusManagerTest, WidgetFocusChangeListener) { - // TODO: this test ends up calling focus on the aura::Window associated with - // the Widget and expecting that to change activation. This should work for - // aura-mus-client as well. http://crbug.com/664261. - if (IsMus()) - return; - // First, ensure the simulator is aware of the Widget created in SetUp() being // currently active. test::WidgetTest::SimulateNativeActivate(GetWidget()); diff --git a/chromium/ui/views/focus/widget_focus_manager.cc b/chromium/ui/views/focus/widget_focus_manager.cc index 099b286e94c..79517fbd4bd 100644 --- a/chromium/ui/views/focus/widget_focus_manager.cc +++ b/chromium/ui/views/focus/widget_focus_manager.cc @@ -4,17 +4,63 @@ #include "ui/views/focus/widget_focus_manager.h" -#include "base/memory/singleton.h" +#include "base/supports_user_data.h" + +#if defined(USE_AURA) +#include "ui/aura/env.h" +#include "ui/aura/window.h" +#endif namespace views { +#if defined(USE_AURA) +namespace { + +const char kWidgetFocusManagerKey[] = "WidgetFocusManager"; + +} // namespace + +class WidgetFocusManager::Owner : public base::SupportsUserData::Data { + public: + explicit Owner(std::unique_ptr<WidgetFocusManager> focus_manager) + : focus_manager_(std::move(focus_manager)) {} + ~Owner() override = default; + + WidgetFocusManager* focus_manager() { return focus_manager_.get(); } + + private: + std::unique_ptr<WidgetFocusManager> focus_manager_; + + DISALLOW_COPY_AND_ASSIGN(Owner); +}; + +#endif + // WidgetFocusManager ---------------------------------------------------------- // static -WidgetFocusManager* WidgetFocusManager::GetInstance() { - return base::Singleton<WidgetFocusManager>::get(); +WidgetFocusManager* WidgetFocusManager::GetInstance(gfx::NativeWindow context) { +#if defined(USE_AURA) + // With aura there may be multiple Envs, in such a situation the + // WidgetFocusManager needs to be per Env. + aura::Env* env = context ? context->env() : aura::Env::GetInstance(); + DCHECK(env); + Owner* owner = static_cast<Owner*>(env->GetUserData(kWidgetFocusManagerKey)); + if (!owner) { + std::unique_ptr<Owner> owner_ptr = + std::make_unique<Owner>(base::WrapUnique(new WidgetFocusManager())); + owner = owner_ptr.get(); + env->SetUserData(kWidgetFocusManagerKey, std::move(owner_ptr)); + } + return owner->focus_manager(); +#else + static base::NoDestructor<WidgetFocusManager> instance; + return instance.get(); +#endif } +WidgetFocusManager::~WidgetFocusManager() = default; + void WidgetFocusManager::AddFocusChangeListener( WidgetFocusChangeListener* listener) { focus_change_listeners_.AddObserver(listener); @@ -34,8 +80,6 @@ void WidgetFocusManager::OnNativeFocusChanged(gfx::NativeView focused_now) { WidgetFocusManager::WidgetFocusManager() : enabled_(true) {} -WidgetFocusManager::~WidgetFocusManager() {} - // AutoNativeNotificationDisabler ---------------------------------------------- AutoNativeNotificationDisabler::AutoNativeNotificationDisabler() { diff --git a/chromium/ui/views/focus/widget_focus_manager.h b/chromium/ui/views/focus/widget_focus_manager.h index 068a4825b46..eb29250c7e6 100644 --- a/chromium/ui/views/focus/widget_focus_manager.h +++ b/chromium/ui/views/focus/widget_focus_manager.h @@ -6,14 +6,11 @@ #define UI_VIEWS_FOCUS_WIDGET_FOCUS_MANAGER_H_ #include "base/macros.h" +#include "base/no_destructor.h" #include "base/observer_list.h" #include "ui/gfx/native_widget_types.h" #include "ui/views/views_export.h" -namespace base { -template <typename T> struct DefaultSingletonTraits; -} - namespace views { // This interface should be implemented by classes that want to be notified when @@ -32,7 +29,9 @@ class WidgetFocusChangeListener { class VIEWS_EXPORT WidgetFocusManager { public: // Returns the singleton instance. - static WidgetFocusManager* GetInstance(); + static WidgetFocusManager* GetInstance(gfx::NativeWindow context = nullptr); + + ~WidgetFocusManager(); // Adds/removes a WidgetFocusChangeListener |listener| to the set of // active listeners. @@ -50,10 +49,10 @@ class VIEWS_EXPORT WidgetFocusManager { void DisableNotifications() { enabled_ = false; } private: - friend struct base::DefaultSingletonTraits<WidgetFocusManager>; + class Owner; + friend class base::NoDestructor<WidgetFocusManager>; WidgetFocusManager(); - ~WidgetFocusManager(); base::ObserverList<WidgetFocusChangeListener>::Unchecked focus_change_listeners_; diff --git a/chromium/ui/views/layout/box_layout.cc b/chromium/ui/views/layout/box_layout.cc index f9c1b6dc485..cdc9750f08b 100644 --- a/chromium/ui/views/layout/box_layout.cc +++ b/chromium/ui/views/layout/box_layout.cc @@ -363,7 +363,7 @@ void BoxLayout::ViewRemoved(View* host, View* view) { } int BoxLayout::GetFlexForView(const View* view) const { - FlexMap::const_iterator it = flex_map_.find(view); + auto it = flex_map_.find(view); if (it == flex_map_.end()) return default_flex_; @@ -371,7 +371,7 @@ int BoxLayout::GetFlexForView(const View* view) const { } int BoxLayout::GetMinimumSizeForView(const View* view) const { - FlexMap::const_iterator it = flex_map_.find(view); + auto it = flex_map_.find(view); if (it == flex_map_.end() || !it->second.use_min_size) return 0; diff --git a/chromium/ui/views/linux_ui/linux_ui.h b/chromium/ui/views/linux_ui/linux_ui.h index 86a3c0a78cb..759d4ab03f0 100644 --- a/chromium/ui/views/linux_ui/linux_ui.h +++ b/chromium/ui/views/linux_ui/linux_ui.h @@ -101,12 +101,10 @@ class VIEWS_EXPORT LinuxUI : public ui::LinuxInputMethodContextFactory, virtual bool GetColor(int id, SkColor* color, PrefService* pref_service) const = 0; + virtual bool GetDisplayProperty(int id, int* result) const = 0; // Returns the preferences that we pass to WebKit. virtual SkColor GetFocusRingColor() const = 0; - virtual SkColor GetThumbActiveColor() const = 0; - virtual SkColor GetThumbInactiveColor() const = 0; - virtual SkColor GetTrackColor() const = 0; virtual SkColor GetActiveSelectionBgColor() const = 0; virtual SkColor GetActiveSelectionFgColor() const = 0; virtual SkColor GetInactiveSelectionBgColor() const = 0; diff --git a/chromium/ui/views/mus/ax_remote_host.cc b/chromium/ui/views/mus/ax_remote_host.cc index 0affab635ac..01511da2296 100644 --- a/chromium/ui/views/mus/ax_remote_host.cc +++ b/chromium/ui/views/mus/ax_remote_host.cc @@ -6,10 +6,12 @@ #include <stddef.h> +#include "base/no_destructor.h" #include "services/service_manager/public/cpp/connector.h" #include "ui/accessibility/ax_action_data.h" #include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_event.h" +#include "ui/accessibility/platform/aura_window_properties.h" #include "ui/accessibility/platform/ax_unique_id.h" #include "ui/aura/mus/window_tree_client.h" #include "ui/aura/window.h" @@ -27,9 +29,6 @@ using display::Screen; namespace views { -// For external linkage. -constexpr int AXRemoteHost::kRemoteAXTreeID; - AXRemoteHost::AXRemoteHost() { AXAuraObjCache::GetInstance()->SetDelegate(this); } @@ -42,12 +41,12 @@ AXRemoteHost::~AXRemoteHost() { void AXRemoteHost::Init(service_manager::Connector* connector) { connector->BindInterface(ax::mojom::kAXHostServiceName, &ax_host_ptr_); - BindAndSetRemote(); + BindAndRegisterRemote(); } void AXRemoteHost::InitForTesting(ax::mojom::AXHostPtr host_ptr) { ax_host_ptr_ = std::move(host_ptr); - BindAndSetRemote(); + BindAndRegisterRemote(); } void AXRemoteHost::StartMonitoringWidget(Widget* widget) { @@ -62,6 +61,10 @@ void AXRemoteHost::StartMonitoringWidget(Widget* widget) { widget_ = widget; widget_->AddObserver(this); + DCHECK_NE(tree_id_, ui::AXTreeIDUnknown()); + widget_->GetNativeWindow()->SetProperty(ui::kChildAXTreeID, + new std::string(tree_id_.ToString())); + // The cache needs to track the root window to follow focus changes. AXAuraObjCache* cache = AXAuraObjCache::GetInstance(); cache->OnRootWindowObjCreated(widget_->GetNativeWindow()); @@ -71,7 +74,7 @@ void AXRemoteHost::StartMonitoringWidget(Widget* widget) { View* contents_view = widget_->widget_delegate()->GetContentsView(); AXAuraObjWrapper* contents_wrapper = cache->GetOrCreate(contents_view); - tree_source_ = std::make_unique<AXTreeSourceMus>(contents_wrapper); + tree_source_ = std::make_unique<AXTreeSourceMus>(contents_wrapper, tree_id_); tree_serializer_ = std::make_unique<AuraAXTreeSerializer>(tree_source_.get()); // Inform the serializer of the display device scale factor. @@ -99,9 +102,15 @@ void AXRemoteHost::HandleEvent(View* view, ax::mojom::Event event_type) { if (!enabled_) return; - AXAuraObjWrapper* aura_obj = - view ? AXAuraObjCache::GetInstance()->GetOrCreate(view) - : tree_source_->GetRoot(); + if (!view) { + SendEvent(tree_source_->GetRoot(), event_type); + return; + } + + // Can return null for views without a widget. + AXAuraObjWrapper* aura_obj = AXAuraObjCache::GetInstance()->GetOrCreate(view); + if (!aura_obj) + return; SendEvent(aura_obj, event_type); } @@ -168,15 +177,26 @@ void AXRemoteHost::FlushForTesting() { ax_host_ptr_.FlushForTesting(); } -void AXRemoteHost::BindAndSetRemote() { +void AXRemoteHost::BindAndRegisterRemote() { ax::mojom::AXRemoteHostPtr remote; binding_.Bind(mojo::MakeRequest(&remote)); - ax_host_ptr_->SetRemoteHost(std::move(remote)); + ax_host_ptr_->RegisterRemoteHost( + std::move(remote), + base::BindOnce(&AXRemoteHost::RegisterRemoteHostCallback, + base::Unretained(this))); +} + +void AXRemoteHost::RegisterRemoteHostCallback(const ui::AXTreeID& tree_id, + bool enabled) { + tree_id_ = tree_id; + + // Set the initial enabled state and send the AX tree if necessary. + OnAutomationEnabled(enabled); } void AXRemoteHost::Enable() { // Don't early-exit if already enabled. AXRemoteHost can start up in the - // "enabled" state even if ChromeVox is on at the moment the app launches. + // "enabled" state even if ChromeVox is off at the moment the app launches. // Turning on ChromeVox later will generate another OnAutomationEnabled() // call and we need to serialize the node tree again. This is similar to // AutomationManagerAura's behavior. https://crbug.com/876407 @@ -234,7 +254,7 @@ void AXRemoteHost::SendEvent(AXAuraObjWrapper* aura_obj, event.event_type = event_type; // Other fields are not used. - ax_host_ptr_->HandleAccessibilityEvent(kRemoteAXTreeID, updates, event); + ax_host_ptr_->HandleAccessibilityEvent(tree_id_, updates, event); } void AXRemoteHost::PerformHitTest(const ui::AXActionData& action) { diff --git a/chromium/ui/views/mus/ax_remote_host.h b/chromium/ui/views/mus/ax_remote_host.h index 7e506159487..bb432ba5073 100644 --- a/chromium/ui/views/mus/ax_remote_host.h +++ b/chromium/ui/views/mus/ax_remote_host.h @@ -10,6 +10,7 @@ #include "base/macros.h" #include "mojo/public/cpp/bindings/binding.h" +#include "ui/accessibility/ax_tree_id.h" #include "ui/accessibility/ax_tree_serializer.h" #include "ui/accessibility/mojom/ax_host.mojom.h" #include "ui/display/display_observer.h" @@ -41,10 +42,6 @@ class VIEWS_MUS_EXPORT AXRemoteHost : public ax::mojom::AXRemoteHost, public display::DisplayObserver, public AXAuraObjCache::Delegate { public: - // Well-known tree ID for the remote client. - // TODO(jamescook): Support different IDs for different clients. - static constexpr int kRemoteAXTreeID = -2; - AXRemoteHost(); ~AXRemoteHost() override; @@ -84,7 +81,10 @@ class VIEWS_MUS_EXPORT AXRemoteHost : public ax::mojom::AXRemoteHost, private: // Registers this object as a remote host for the parent AXHost. - void BindAndSetRemote(); + void BindAndRegisterRemote(); + + // Callback for initial state from AXHost. + void RegisterRemoteHostCallback(const ui::AXTreeID& tree_id, bool enabled); void Enable(); void Disable(); @@ -102,6 +102,9 @@ class VIEWS_MUS_EXPORT AXRemoteHost : public ax::mojom::AXRemoteHost, mojo::Binding<ax::mojom::AXRemoteHost> binding_{this}; + // ID to use for the AX tree. + ui::AXTreeID tree_id_; + // Whether accessibility automation support is enabled. bool enabled_ = false; diff --git a/chromium/ui/views/mus/ax_remote_host_unittest.cc b/chromium/ui/views/mus/ax_remote_host_unittest.cc index fd187350140..d6620cf9b2a 100644 --- a/chromium/ui/views/mus/ax_remote_host_unittest.cc +++ b/chromium/ui/views/mus/ax_remote_host_unittest.cc @@ -5,9 +5,11 @@ #include "ui/views/mus/ax_remote_host.h" #include "base/macros.h" +#include "base/no_destructor.h" #include "base/run_loop.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/accessibility/mojom/ax_host.mojom.h" +#include "ui/accessibility/platform/aura_window_properties.h" #include "ui/display/display.h" #include "ui/gfx/geometry/vector2d_f.h" #include "ui/gfx/transform.h" @@ -21,6 +23,13 @@ namespace views { namespace { +// Returns a well-known tree ID for the test widget. +const ui::AXTreeID& TestAXTreeID() { + static const base::NoDestructor<ui::AXTreeID> test_ax_tree_id( + ui::AXTreeID::FromString("123")); + return *test_ax_tree_id; +} + // Simulates the AXHostService in the browser. class TestAXHostService : public ax::mojom::AXHost { public: @@ -35,33 +44,34 @@ class TestAXHostService : public ax::mojom::AXHost { } void ResetCounts() { - add_client_count_ = 0; + remote_host_count_ = 0; event_count_ = 0; - last_tree_id_ = 0; + last_tree_id_ = ui::AXTreeIDUnknown(); last_updates_.clear(); last_event_ = ui::AXEvent(); } // ax::mojom::AXHost: - void SetRemoteHost(ax::mojom::AXRemoteHostPtr client) override { - ++add_client_count_; - client->OnAutomationEnabled(automation_enabled_); + void RegisterRemoteHost(ax::mojom::AXRemoteHostPtr client, + RegisterRemoteHostCallback cb) override { + ++remote_host_count_; + std::move(cb).Run(TestAXTreeID(), automation_enabled_); client.FlushForTesting(); } - void HandleAccessibilityEvent(int32_t tree_id, + void HandleAccessibilityEvent(const ui::AXTreeID& tree_id, const std::vector<ui::AXTreeUpdate>& updates, const ui::AXEvent& event) override { ++event_count_; - last_tree_id_ = tree_id; + last_tree_id_ = ui::AXTreeID::FromString(tree_id); last_updates_ = updates; last_event_ = event; } mojo::Binding<ax::mojom::AXHost> binding_{this}; bool automation_enabled_ = false; - int add_client_count_ = 0; + int remote_host_count_ = 0; int event_count_ = 0; - int last_tree_id_ = 0; + ui::AXTreeID last_tree_id_ = ui::AXTreeIDUnknown(); std::vector<ui::AXTreeUpdate> last_updates_; ui::AXEvent last_event_; @@ -127,7 +137,7 @@ TEST_F(AXRemoteHostTest, CreateRemote) { CreateRemote(&service); // Client registered itself with service. - EXPECT_EQ(1, service.add_client_count_); + EXPECT_EQ(1, service.remote_host_count_); } TEST_F(AXRemoteHostTest, AutomationEnabled) { @@ -136,7 +146,15 @@ TEST_F(AXRemoteHostTest, AutomationEnabled) { std::unique_ptr<Widget> widget = CreateTestWidget(); remote->FlushForTesting(); + // Tree ID is assigned. + std::string* tree_id_ptr = + widget->GetNativeWindow()->GetProperty(ui::kChildAXTreeID); + ASSERT_TRUE(tree_id_ptr); + ui::AXTreeID tree_id = ui::AXTreeID::FromString(*tree_id_ptr); + EXPECT_EQ(TestAXTreeID(), tree_id); + // Event was sent with initial hierarchy. + EXPECT_EQ(TestAXTreeID(), service.last_tree_id_); EXPECT_EQ(ax::mojom::Event::kLoadComplete, service.last_event_.event_type); EXPECT_EQ(AXAuraObjCache::GetInstance()->GetID( widget->widget_delegate()->GetContentsView()), @@ -165,13 +183,15 @@ TEST_F(AXRemoteHostTest, AutomationEnabledTwice) { EXPECT_EQ(ax::mojom::Event::kLoadComplete, service.last_event_.event_type); } -// Views can trigger accessibility events during Widget construction before the -// AXRemoteHost starts monitoring the widget. This happens with the material -// design focus ring on text fields. Verify we don't crash in this case. -// https://crbug.com/862759 -TEST_F(AXRemoteHostTest, SendEventBeforeWidgetCreated) { +// Verifies that a remote app doesn't crash if a View triggers an accessibility +// event before it is attached to a Widget. https://crbug.com/889121 +TEST_F(AXRemoteHostTest, SendEventOnViewWithNoWidget) { TestAXHostService service(true /*automation_enabled*/); AXRemoteHost* remote = CreateRemote(&service); + std::unique_ptr<Widget> widget = CreateTestWidget(); + remote->FlushForTesting(); + + // Create a view that is not yet associated with the widget. views::View view; remote->HandleEvent(&view, ax::mojom::Event::kLocationChanged); // No crash. @@ -230,6 +250,9 @@ TEST_F(AXRemoteHostTest, PerformAction) { // Create a view to sense the action. TestView view; + view.SetBounds(0, 0, 100, 100); + std::unique_ptr<Widget> widget = CreateTestWidget(); + widget->GetRootView()->AddChildView(&view); AXAuraObjCache::GetInstance()->GetOrCreate(&view); // Request an action on the view. diff --git a/chromium/ui/views/mus/ax_tree_source_mus.cc b/chromium/ui/views/mus/ax_tree_source_mus.cc index 7f767c443f6..6199fb3cabf 100644 --- a/chromium/ui/views/mus/ax_tree_source_mus.cc +++ b/chromium/ui/views/mus/ax_tree_source_mus.cc @@ -11,14 +11,17 @@ namespace views { -AXTreeSourceMus::AXTreeSourceMus(AXAuraObjWrapper* root) : root_(root) { +AXTreeSourceMus::AXTreeSourceMus(AXAuraObjWrapper* root, + const ui::AXTreeID& tree_id) + : root_(root), tree_id_(tree_id) { DCHECK(root_); + DCHECK_NE(tree_id_, ui::AXTreeIDUnknown()); } AXTreeSourceMus::~AXTreeSourceMus() = default; bool AXTreeSourceMus::GetTreeData(ui::AXTreeData* tree_data) const { - tree_data->tree_id = AXRemoteHost::kRemoteAXTreeID; + tree_data->tree_id = tree_id_; return AXTreeSourceViews::GetTreeData(tree_data); } diff --git a/chromium/ui/views/mus/ax_tree_source_mus.h b/chromium/ui/views/mus/ax_tree_source_mus.h index 6e3681e0773..6c9170747ed 100644 --- a/chromium/ui/views/mus/ax_tree_source_mus.h +++ b/chromium/ui/views/mus/ax_tree_source_mus.h @@ -6,6 +6,7 @@ #define UI_VIEWS_MUS_AX_TREE_SOURCE_MUS_H_ #include "base/macros.h" +#include "ui/accessibility/ax_tree_id.h" #include "ui/views/accessibility/ax_tree_source_views.h" #include "ui/views/mus/mus_export.h" @@ -20,7 +21,7 @@ class AXAuraObjWrapper; class VIEWS_MUS_EXPORT AXTreeSourceMus : public AXTreeSourceViews { public: // |root| must outlive this object. - explicit AXTreeSourceMus(AXAuraObjWrapper* root); + AXTreeSourceMus(AXAuraObjWrapper* root, const ui::AXTreeID& tree_id); ~AXTreeSourceMus() override; void set_device_scale_factor(float scale) { device_scale_factor_ = scale; } @@ -35,6 +36,9 @@ class VIEWS_MUS_EXPORT AXTreeSourceMus : public AXTreeSourceViews { // The top-level object to use for the AX tree. AXAuraObjWrapper* root_; + // ID to use for the AX tree. + const ui::AXTreeID tree_id_; + // The display device scale factor to use while serializing this update. float device_scale_factor_ = 1.f; diff --git a/chromium/ui/views/mus/ax_tree_source_mus_unittest.cc b/chromium/ui/views/mus/ax_tree_source_mus_unittest.cc index 2f7bc9a6299..b4fbdd54ac4 100644 --- a/chromium/ui/views/mus/ax_tree_source_mus_unittest.cc +++ b/chromium/ui/views/mus/ax_tree_source_mus_unittest.cc @@ -51,6 +51,7 @@ class AXTreeSourceMusTest : public ViewsTestBase { std::unique_ptr<Widget> widget_; Label* label_ = nullptr; // Owned by views hierarchy. + const ui::AXTreeID ax_tree_id_ = ui::AXTreeID::FromString("123"); private: DISALLOW_COPY_AND_ASSIGN(AXTreeSourceMusTest); @@ -59,17 +60,17 @@ class AXTreeSourceMusTest : public ViewsTestBase { TEST_F(AXTreeSourceMusTest, GetTreeData) { AXAuraObjWrapper* root = AXAuraObjCache::GetInstance()->GetOrCreate(widget_->GetContentsView()); - AXTreeSourceMus tree(root); + AXTreeSourceMus tree(root, ax_tree_id_); ui::AXTreeData tree_data; tree.GetTreeData(&tree_data); - EXPECT_EQ(AXRemoteHost::kRemoteAXTreeID, tree_data.tree_id); + EXPECT_EQ(ax_tree_id_, tree_data.tree_id); } TEST_F(AXTreeSourceMusTest, Serialize) { AXAuraObjCache* cache = AXAuraObjCache::GetInstance(); AXAuraObjWrapper* root = cache->GetOrCreate(widget_->GetContentsView()); - AXTreeSourceMus tree(root); + AXTreeSourceMus tree(root, ax_tree_id_); EXPECT_EQ(root, tree.GetRoot()); // Serialize the root. @@ -93,7 +94,7 @@ TEST_F(AXTreeSourceMusTest, ScaleFactor) { AXAuraObjWrapper* root = cache->GetOrCreate(widget_->GetContentsView()); // Simulate serializing a widget on a high-dpi display. - AXTreeSourceMus tree(root); + AXTreeSourceMus tree(root, ax_tree_id_); tree.set_device_scale_factor(2.f); // Serialize the root. diff --git a/chromium/ui/views/mus/desktop_window_tree_host_mus.cc b/chromium/ui/views/mus/desktop_window_tree_host_mus.cc index 93d30122d2a..61fd282fbb4 100644 --- a/chromium/ui/views/mus/desktop_window_tree_host_mus.cc +++ b/chromium/ui/views/mus/desktop_window_tree_host_mus.cc @@ -43,7 +43,7 @@ namespace views { namespace { // As the window manager renderers the non-client decorations this class does -// very little but honor the client area insets from the window manager. +// very little but honor kTopViewInset. class ClientSideNonClientFrameView : public NonClientFrameView, public aura::WindowObserver { public: @@ -53,16 +53,22 @@ class ClientSideNonClientFrameView : public NonClientFrameView, // provided by the window manager. GetViewAccessibility().set_is_ignored(true); - observed_.Add(widget_->GetNativeWindow()->GetRootWindow()); + // Initialize kTopViewInset to a default value. Further updates will come + // from Ash. This is necessary so that during app window creation, + // GetWindowBoundsForClientBounds() can calculate correctly. + const auto& values = views::WindowManagerFrameValues::instance(); + widget->GetNativeWindow()->SetProperty(aura::client::kTopViewInset, + widget->IsMaximized() + ? values.maximized_insets.top() + : values.normal_insets.top()); + observed_.Add(window()); } ~ClientSideNonClientFrameView() override {} private: - // Returns the default values of client area insets from the window manager. - static gfx::Insets GetDefaultWindowManagerInsets(bool is_maximized) { - const WindowManagerFrameValues& values = - WindowManagerFrameValues::instance(); - return is_maximized ? values.maximized_insets : values.normal_insets; + gfx::Insets GetClientInsets() const { + const int top_inset = window()->GetProperty(aura::client::kTopViewInset); + return gfx::Insets(top_inset, 0, 0, 0); } // View: @@ -75,7 +81,7 @@ class ClientSideNonClientFrameView : public NonClientFrameView, gfx::Rect result(GetLocalBounds()); if (widget_->IsFullscreen()) return result; - result.Inset(GetDefaultWindowManagerInsets(widget_->IsMaximized())); + result.Inset(GetClientInsets()); return result; } gfx::Rect GetWindowBoundsForClientBounds( @@ -83,12 +89,9 @@ class ClientSideNonClientFrameView : public NonClientFrameView, if (widget_->IsFullscreen()) return client_bounds; - const gfx::Insets insets( - GetDefaultWindowManagerInsets(widget_->IsMaximized())); - return gfx::Rect(client_bounds.x() - insets.left(), - client_bounds.y() - insets.top(), - client_bounds.width() + insets.width(), - client_bounds.height() + insets.height()); + gfx::Rect outset_bounds = client_bounds; + outset_bounds.Inset(-GetClientInsets()); + return outset_bounds; } int NonClientHitTest(const gfx::Point& point) override { return HTNOWHERE; } void GetWindowMask(const gfx::Size& size, gfx::Path* window_mask) override { @@ -136,20 +139,16 @@ class ClientSideNonClientFrameView : public NonClientFrameView, void OnWindowPropertyChanged(aura::Window* window, const void* key, intptr_t old) override { - // Do a re-layout on state changes which affect GetBoundsForClientView(). - // The associated bounds change would also cause a re-layout, but there may - // not be a bounds change or it may come from the server before the state is - // updated. - if (key == aura::client::kShowStateKey) { - if (GetBoundsForClientView() != widget_->client_view()->bounds() && - window->GetProperty(aura::client::kShowStateKey) != - ui::SHOW_STATE_MINIMIZED) { - InvalidateLayout(); - widget_->GetRootView()->Layout(); - } + if (key == aura::client::kTopViewInset) { + InvalidateLayout(); + widget_->GetRootView()->Layout(); } } + aura::Window* window() const { + return widget_->GetNativeWindow()->GetRootWindow(); + } + views::Widget* widget_; ScopedObserver<aura::Window, aura::WindowObserver> observed_{this}; @@ -286,20 +285,6 @@ void DesktopWindowTreeHostMus::SendClientAreaToServer() { std::vector<gfx::Rect>()); } -void DesktopWindowTreeHostMus::SendHitTestMaskToServer() { - if (!native_widget_delegate_->HasHitTestMask()) { - aura::WindowPortMus::Get(window())->SetHitTestMask(base::nullopt); - return; - } - - gfx::Path mask_path; - native_widget_delegate_->GetHitTestMask(&mask_path); - // TODO(jamescook): Use the full path for the mask. - gfx::Rect mask_rect = - gfx::ToEnclosingRect(gfx::SkRectToRectF(mask_path.getBounds())); - aura::WindowPortMus::Get(window())->SetHitTestMask(mask_rect); -} - bool DesktopWindowTreeHostMus::IsFocusClientInstalledOnFocusSynchronizer() const { return MusClient::Get() @@ -340,8 +325,11 @@ void DesktopWindowTreeHostMus::Init(const Widget::InitParams& params) { window()->SetProperty(aura::client::kShowStateKey, params.show_state); - if (!params.bounds.IsEmpty()) + if (!params.bounds.IsEmpty()) { + // Init the scale now (before InitHost below), it is used by SetBoundsInDIP. + IntializeDeviceScaleFactor(GetDisplay().device_scale_factor()); SetBoundsInDIP(params.bounds); + } cursor_manager_ = std::make_unique<wm::CursorManager>( std::make_unique<NativeCursorManagerMus>(window())); @@ -428,7 +416,6 @@ void DesktopWindowTreeHostMus::OnWidgetInitDone() { // the NonClientView was created, which means we may not have sent the // client-area and hit-test-mask. SendClientAreaToServer(); - SendHitTestMaskToServer(); MusClient::Get()->OnCaptureClientSet( aura::client::GetCaptureClient(window())); @@ -871,7 +858,6 @@ void DesktopWindowTreeHostMus::OnWindowManagerFrameValuesChanged() { } SendClientAreaToServer(); - SendHitTestMaskToServer(); } void DesktopWindowTreeHostMus::OnActiveFocusClientChanged( @@ -946,7 +932,6 @@ void DesktopWindowTreeHostMus::OnViewBoundsChanged(views::View* observed_view) { native_widget_delegate_->AsWidget()->non_client_view()->frame_view()); SendClientAreaToServer(); - SendHitTestMaskToServer(); } void DesktopWindowTreeHostMus::OnViewIsDeleting(View* observed_view) { diff --git a/chromium/ui/views/mus/desktop_window_tree_host_mus.h b/chromium/ui/views/mus/desktop_window_tree_host_mus.h index a93e0c297ed..17c0655e987 100644 --- a/chromium/ui/views/mus/desktop_window_tree_host_mus.h +++ b/chromium/ui/views/mus/desktop_window_tree_host_mus.h @@ -49,7 +49,6 @@ class VIEWS_MUS_EXPORT DesktopWindowTreeHostMus private: void SendClientAreaToServer(); - void SendHitTestMaskToServer(); // Returns true if the FocusClient associated with our window is installed on // the FocusSynchronizer. diff --git a/chromium/ui/views/mus/desktop_window_tree_host_mus_unittest.cc b/chromium/ui/views/mus/desktop_window_tree_host_mus_unittest.cc index 85bfbad2488..159770b5a1c 100644 --- a/chromium/ui/views/mus/desktop_window_tree_host_mus_unittest.cc +++ b/chromium/ui/views/mus/desktop_window_tree_host_mus_unittest.cc @@ -24,7 +24,6 @@ #include "ui/views/mus/mus_client.h" #include "ui/views/mus/mus_client_test_api.h" #include "ui/views/mus/screen_mus.h" -#include "ui/views/mus/window_manager_frame_values.h" #include "ui/views/test/views_test_base.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" @@ -240,42 +239,24 @@ TEST_F(DesktopWindowTreeHostMusTest, ActivateBeforeShow) { ->active_focus_client()); } -// Tests that changes to a widget's show state will cause the client area to be -// updated. -TEST_F(DesktopWindowTreeHostMusTest, ServerShowStateChangeUpdatesClientArea) { - WindowManagerFrameValues test_frame_values; - test_frame_values.normal_insets = {3, 0, 0, 0}; - test_frame_values.maximized_insets = {7, 0, 0, 0}; - WindowManagerFrameValues::SetInstance(test_frame_values); - +// Tests that changes to kTopViewInset will cause the client area to be updated. +TEST_F(DesktopWindowTreeHostMusTest, ServerTopInsetChangeUpdatesClientArea) { std::unique_ptr<Widget> widget(CreateWidget()); widget->Show(); - // Simulate state changes from the server. - auto set_widget_state = - [&widget](ui::WindowShowState state) { - widget->GetNativeWindow()->GetRootWindow()->SetProperty( - aura::client::kShowStateKey, state); - }; - - // A restored window respects normal_insets (the client area is inset from the - // root view). - gfx::Rect expected_restored_bounds = widget->GetRootView()->bounds(); - expected_restored_bounds.Inset(test_frame_values.normal_insets); - EXPECT_EQ(expected_restored_bounds, widget->client_view()->bounds()); - - // A fullscreen window has no insets. - EXPECT_FALSE(widget->IsFullscreen()); - set_widget_state(ui::SHOW_STATE_FULLSCREEN); - EXPECT_TRUE(widget->IsFullscreen()); + auto set_top_inset = [&widget](int value) { + widget->GetNativeWindow()->GetRootWindow()->SetProperty( + aura::client::kTopViewInset, value); + }; + EXPECT_EQ(widget->GetRootView()->bounds(), widget->client_view()->bounds()); - // A maximized window respects maximized_insets. - gfx::Rect expected_maximized_bounds = widget->GetRootView()->bounds(); - expected_maximized_bounds.Inset(test_frame_values.maximized_insets); - set_widget_state(ui::SHOW_STATE_MAXIMIZED); - EXPECT_FALSE(widget->IsFullscreen()); - EXPECT_EQ(expected_maximized_bounds, widget->client_view()->bounds()); + set_top_inset(3); + gfx::Rect root_bounds = widget->GetRootView()->bounds(); + root_bounds.Inset(gfx::Insets(3, 0, 0, 0)); + + set_top_inset(0); + EXPECT_EQ(widget->GetRootView()->bounds(), widget->client_view()->bounds()); } TEST_F(DesktopWindowTreeHostMusTest, CursorClientDuringTearDown) { diff --git a/chromium/ui/views/mus/drag_interactive_uitest.cc b/chromium/ui/views/mus/drag_interactive_uitest.cc index 999ec9e269a..92dc07d7a7d 100644 --- a/chromium/ui/views/mus/drag_interactive_uitest.cc +++ b/chromium/ui/views/mus/drag_interactive_uitest.cc @@ -86,24 +86,24 @@ class TargetView : public views::View { DISALLOW_COPY_AND_ASSIGN(TargetView); }; -std::unique_ptr<ui::PointerEvent> CreateMouseMoveEvent(int x, int y) { - return std::make_unique<ui::PointerEvent>(ui::MouseEvent( +std::unique_ptr<ui::MouseEvent> CreateMouseMoveEvent(int x, int y) { + return std::make_unique<ui::MouseEvent>( ui::ET_MOUSE_MOVED, gfx::Point(x, y), gfx::Point(x, y), - ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_NONE)); + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, ui::EF_NONE); } -std::unique_ptr<ui::PointerEvent> CreateMouseDownEvent(int x, int y) { - return std::make_unique<ui::PointerEvent>( - ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(x, y), gfx::Point(x, y), - ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, - ui::EF_LEFT_MOUSE_BUTTON)); +std::unique_ptr<ui::MouseEvent> CreateMouseDownEvent(int x, int y) { + return std::make_unique<ui::MouseEvent>( + ui::ET_MOUSE_PRESSED, gfx::Point(x, y), gfx::Point(x, y), + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON); } -std::unique_ptr<ui::PointerEvent> CreateMouseUpEvent(int x, int y) { - return std::make_unique<ui::PointerEvent>( - ui::MouseEvent(ui::ET_MOUSE_RELEASED, gfx::Point(x, y), gfx::Point(x, y), - ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, - ui::EF_LEFT_MOUSE_BUTTON)); +std::unique_ptr<ui::MouseEvent> CreateMouseUpEvent(int x, int y) { + return std::make_unique<ui::MouseEvent>( + ui::ET_MOUSE_RELEASED, gfx::Point(x, y), gfx::Point(x, y), + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON); } } // namespace diff --git a/chromium/ui/views/mus/mus_client.cc b/chromium/ui/views/mus/mus_client.cc index 4678479162a..4bed24086c3 100644 --- a/chromium/ui/views/mus/mus_client.cc +++ b/chromium/ui/views/mus/mus_client.cc @@ -270,7 +270,7 @@ MusClient::ConfigurePropertiesFromParams( // TODO(crbug.com/667566): Support additional scales or gfx::Image[Skia]. gfx::ImageSkia app_icon = init_params.delegate->GetWindowAppIcon(); - SkBitmap app_bitmap = app_icon.GetRepresentation(1.f).sk_bitmap(); + SkBitmap app_bitmap = app_icon.GetRepresentation(1.f).GetBitmap(); if (!app_bitmap.isNull()) { properties[WindowManager::kAppIcon_Property] = mojo::ConvertTo<TransportType>(app_bitmap); @@ -278,7 +278,7 @@ MusClient::ConfigurePropertiesFromParams( // TODO(crbug.com/667566): Support additional scales or gfx::Image[Skia]. gfx::ImageSkia window_icon = init_params.delegate->GetWindowIcon(); - SkBitmap window_bitmap = window_icon.GetRepresentation(1.f).sk_bitmap(); + SkBitmap window_bitmap = window_icon.GetRepresentation(1.f).GetBitmap(); if (!window_bitmap.isNull()) { properties[WindowManager::kWindowIcon_Property] = mojo::ConvertTo<TransportType>(window_bitmap); diff --git a/chromium/ui/views/mus/mus_views_delegate.cc b/chromium/ui/views/mus/mus_views_delegate.cc index e383ac00d69..6456b0d31d7 100644 --- a/chromium/ui/views/mus/mus_views_delegate.cc +++ b/chromium/ui/views/mus/mus_views_delegate.cc @@ -6,6 +6,7 @@ #include "ui/views/mus/ax_remote_host.h" #include "ui/views/mus/mus_client.h" +#include "ui/views/mus/pointer_watcher_event_router.h" namespace views { @@ -19,4 +20,19 @@ void MusViewsDelegate::NotifyAccessibilityEvent(View* view, MusClient::Get()->ax_remote_host()->HandleEvent(view, event_type); } +void MusViewsDelegate::AddPointerWatcher(PointerWatcher* pointer_watcher, + bool wants_moves) { + MusClient::Get()->pointer_watcher_event_router()->AddPointerWatcher( + pointer_watcher, wants_moves); +} + +void MusViewsDelegate::RemovePointerWatcher(PointerWatcher* pointer_watcher) { + MusClient::Get()->pointer_watcher_event_router()->RemovePointerWatcher( + pointer_watcher); +} + +bool MusViewsDelegate::IsPointerWatcherSupported() const { + return true; +} + } // namespace views diff --git a/chromium/ui/views/mus/mus_views_delegate.h b/chromium/ui/views/mus/mus_views_delegate.h index b7d55503ac1..6346df2979b 100644 --- a/chromium/ui/views/mus/mus_views_delegate.h +++ b/chromium/ui/views/mus/mus_views_delegate.h @@ -20,6 +20,10 @@ class VIEWS_MUS_EXPORT MusViewsDelegate : public ViewsDelegate { // ViewsDelegate: void NotifyAccessibilityEvent(View* view, ax::mojom::Event event_type) override; + void AddPointerWatcher(PointerWatcher* pointer_watcher, + bool wants_moves) override; + void RemovePointerWatcher(PointerWatcher* pointer_watcher) override; + bool IsPointerWatcherSupported() const override; private: LayoutProvider layout_provider_; diff --git a/chromium/ui/views/mus/remote_view/remote_view_host.cc b/chromium/ui/views/mus/remote_view/remote_view_host.cc index a203d2b14fd..a0fc60681e3 100644 --- a/chromium/ui/views/mus/remote_view/remote_view_host.cc +++ b/chromium/ui/views/mus/remote_view/remote_view_host.cc @@ -15,7 +15,14 @@ namespace views { -RemoteViewHost::RemoteViewHost() = default; +RemoteViewHost::RemoteViewHost() + : embedding_root_(std::make_unique<aura::Window>(nullptr)) { + embedding_root_->set_owned_by_parent(false); + embedding_root_->SetName("RemoteViewHostWindow"); + embedding_root_->SetType(aura::client::WINDOW_TYPE_CONTROL); + embedding_root_->Init(ui::LAYER_NOT_DRAWN); +} + RemoteViewHost::~RemoteViewHost() = default; void RemoteViewHost::EmbedUsingToken(const base::UnguessableToken& embed_token, @@ -29,29 +36,11 @@ void RemoteViewHost::EmbedUsingToken(const base::UnguessableToken& embed_token, embed_callback_ = std::move(callback); if (GetWidget()) - CreateEmbeddingRoot(); + EmbedImpl(); } -void RemoteViewHost::CreateEmbeddingRoot() { - // Should not be attached to anything. - DCHECK(!native_view()); - - // There is a pending embed request. - DCHECK(!embed_token_.is_empty()); - - embedding_root_ = std::make_unique<aura::Window>(nullptr); - embedding_root_->set_owned_by_parent(false); - - embedding_root_->SetName("RemoteViewHostWindow"); - embedding_root_->SetProperty(aura::client::kEmbedType, - aura::client::WindowEmbedType::EMBED_IN_OWNER); - embedding_root_->SetType(aura::client::WINDOW_TYPE_CONTROL); - embedding_root_->Init(ui::LAYER_NOT_DRAWN); - - // Must happen before EmbedUsingToken call for window server to figure out - // the relevant display. - Attach(embedding_root_.get()); - +void RemoteViewHost::EmbedImpl() { + DCHECK(IsEmbedPending()); aura::WindowPortMus::Get(embedding_root_.get()) ->EmbedUsingToken(embed_token_, embed_flags_, base::BindOnce(&RemoteViewHost::OnEmbedResult, @@ -60,17 +49,17 @@ void RemoteViewHost::CreateEmbeddingRoot() { void RemoteViewHost::OnEmbedResult(bool success) { LOG_IF(ERROR, !success) << "Failed to embed, token=" << embed_token_; - - if (!success && embedding_root_) - embedding_root_.reset(); - + embed_token_ = {}; if (embed_callback_) std::move(embed_callback_).Run(success); } void RemoteViewHost::AddedToWidget() { - if (!native_view() && !embed_token_.is_empty()) - CreateEmbeddingRoot(); + if (native_view()) + return; + Attach(embedding_root_.get()); + if (IsEmbedPending()) + EmbedImpl(); } } // namespace views diff --git a/chromium/ui/views/mus/remote_view/remote_view_host.h b/chromium/ui/views/mus/remote_view/remote_view_host.h index 44b1552f325..2a3e4530124 100644 --- a/chromium/ui/views/mus/remote_view/remote_view_host.h +++ b/chromium/ui/views/mus/remote_view/remote_view_host.h @@ -36,8 +36,10 @@ class RemoteViewHost : public views::NativeViewHost { EmbedCallback callback); private: + bool IsEmbedPending() const { return !embed_token_.is_empty(); } + // Creates the embedding aura::Window and attach to it. - void CreateEmbeddingRoot(); + void EmbedImpl(); // Invoked after the embed operation. void OnEmbedResult(bool success); @@ -49,7 +51,7 @@ class RemoteViewHost : public views::NativeViewHost { int embed_flags_ = 0; EmbedCallback embed_callback_; - std::unique_ptr<aura::Window> embedding_root_; + const std::unique_ptr<aura::Window> embedding_root_; base::WeakPtrFactory<RemoteViewHost> weak_ptr_factory_{this}; DISALLOW_COPY_AND_ASSIGN(RemoteViewHost); diff --git a/chromium/ui/views/mus/remote_view/remote_view_host_unittest.cc b/chromium/ui/views/mus/remote_view/remote_view_host_unittest.cc index 611c00151cd..30db5c39f9a 100644 --- a/chromium/ui/views/mus/remote_view/remote_view_host_unittest.cc +++ b/chromium/ui/views/mus/remote_view/remote_view_host_unittest.cc @@ -80,21 +80,20 @@ class RemoteViewHostTest : public aura::test::AuraTestBase { DISALLOW_COPY_AND_ASSIGN(RemoteViewHostTest); }; -// Tests that the embed operation fails with an unknown token and RemoteViewHost -// will not be attached. +// Tests that the embed operation fails with an unknown token. TEST_F(RemoteViewHostTest, BadEmbed) { const base::UnguessableToken unknown_token = base::UnguessableToken::Create(); // Ownership will be passed to |widget| later. RemoteViewHost* host = new RemoteViewHost(); std::unique_ptr<views::Widget> widget = CreateTestWidget(host); - EXPECT_FALSE(host->native_view()); + EXPECT_TRUE(host->native_view()); // Embed fails with unknown token. EXPECT_FALSE(Embed(host, unknown_token)); - // |host| is not attached after adding to a widget. - EXPECT_FALSE(host->native_view()); + // |host| is still attached despite the Embed failure. + EXPECT_TRUE(host->native_view()); } // Tests when RemoveViewHost is added to a widget before embedding. @@ -105,15 +104,15 @@ TEST_F(RemoteViewHostTest, AddToWidgetBeforeEmbed) { // Ownership will be passed to |widget| later. RemoteViewHost* host = new RemoteViewHost(); - // |host| is not attached because embed operation is not performed. + // |host| is not attached until the widget is created. EXPECT_FALSE(host->native_view()); std::unique_ptr<views::Widget> widget = CreateTestWidget(host); - EXPECT_FALSE(host->native_view()); + EXPECT_TRUE(host->native_view()); // Embed succeeds. EXPECT_TRUE(Embed(host, token)); - // |host| is now attached to the embedding window. + // |host| is still attached to the embedding window. EXPECT_TRUE(host->native_view()); } diff --git a/chromium/ui/views/mus/views_mus_test_suite.cc b/chromium/ui/views/mus/views_mus_test_suite.cc index 40dafe4c9db..1beddbf9de8 100644 --- a/chromium/ui/views/mus/views_mus_test_suite.cc +++ b/chromium/ui/views/mus/views_mus_test_suite.cc @@ -14,7 +14,6 @@ #include "base/run_loop.h" #include "base/synchronization/waitable_event.h" #include "base/threading/simple_thread.h" -#include "base/threading/thread.h" #include "mojo/core/embedder/embedder.h" #include "mojo/core/embedder/scoped_ipc_support.h" #include "services/catalog/catalog.h" @@ -68,24 +67,18 @@ class DefaultService : public service_manager::Service { class ServiceManagerConnection { public: ServiceManagerConnection() - : thread_("Persistent service_manager connections"), - ipc_thread_("IPC thread") { + : thread_("Persistent service_manager connections") { catalog::Catalog::LoadDefaultCatalogManifest( base::FilePath(kCatalogFilename)); - mojo::core::Init(); - ipc_thread_.StartWithOptions( - base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); - ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>( - ipc_thread_.task_runner(), - mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN); - base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::AUTOMATIC, base::WaitableEvent::InitialState::NOT_SIGNALED); base::Thread::Options options; thread_.StartWithOptions(options); thread_.task_runner()->PostTask( - FROM_HERE, base::BindOnce(&ServiceManagerConnection::SetUpConnections, - base::Unretained(this), &wait)); + FROM_HERE, + base::BindOnce( + &ServiceManagerConnection::SetUpConnectionsOnBackgroundThread, + base::Unretained(this), &wait)); wait.Wait(); } @@ -94,8 +87,9 @@ class ServiceManagerConnection { base::WaitableEvent::InitialState::NOT_SIGNALED); thread_.task_runner()->PostTask( FROM_HERE, - base::BindOnce(&ServiceManagerConnection::TearDownConnections, - base::Unretained(this), &wait)); + base::BindOnce( + &ServiceManagerConnection::TearDownConnectionsOnBackgroundThread, + base::Unretained(this), &wait)); wait.Wait(); } @@ -124,7 +118,7 @@ class ServiceManagerConnection { wait->Signal(); } - void SetUpConnections(base::WaitableEvent* wait) { + void SetUpConnectionsOnBackgroundThread(base::WaitableEvent* wait) { background_service_manager_ = std::make_unique<service_manager::BackgroundServiceManager>(nullptr, nullptr); @@ -140,8 +134,9 @@ class ServiceManagerConnection { wait->Signal(); } - void TearDownConnections(base::WaitableEvent* wait) { + void TearDownConnectionsOnBackgroundThread(base::WaitableEvent* wait) { context_.reset(); + background_service_manager_.reset(); wait->Signal(); } @@ -155,8 +150,6 @@ class ServiceManagerConnection { } base::Thread thread_; - base::Thread ipc_thread_; - std::unique_ptr<mojo::core::ScopedIPCSupport> ipc_support_; std::unique_ptr<service_manager::BackgroundServiceManager> background_service_manager_; std::unique_ptr<service_manager::ServiceContext> context_; @@ -226,7 +219,7 @@ std::unique_ptr<PlatformTestHelper> CreatePlatformTestHelper() { } // namespace ViewsMusTestSuite::ViewsMusTestSuite(int argc, char** argv) - : ViewsTestSuite(argc, argv) {} + : ViewsTestSuite(argc, argv), ipc_thread_("IPC thread") {} ViewsMusTestSuite::~ViewsMusTestSuite() {} @@ -248,6 +241,13 @@ void ViewsMusTestSuite::Initialize() { switches::kEnableFeatures, features::kMash.name); PlatformTestHelper::set_factory(base::Bind(&CreatePlatformTestHelper)); + + mojo::core::Init(); + ipc_thread_.StartWithOptions( + base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); + ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>( + ipc_thread_.task_runner(), + mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN); } void ViewsMusTestSuite::InitializeEnv() { diff --git a/chromium/ui/views/mus/views_mus_test_suite.h b/chromium/ui/views/mus/views_mus_test_suite.h index 4dc9d09dc07..8490ab91912 100644 --- a/chromium/ui/views/mus/views_mus_test_suite.h +++ b/chromium/ui/views/mus/views_mus_test_suite.h @@ -9,8 +9,15 @@ #include "base/macros.h" #include "base/test/scoped_feature_list.h" +#include "base/threading/thread.h" #include "ui/views/views_test_suite.h" +namespace mojo { +namespace core { +class ScopedIPCSupport; +} +} // namespace mojo + namespace views { class ViewsMusTestSuite : public ViewsTestSuite { @@ -24,6 +31,9 @@ class ViewsMusTestSuite : public ViewsTestSuite { void InitializeEnv() override; void DestroyEnv() override; + base::Thread ipc_thread_; + std::unique_ptr<mojo::core::ScopedIPCSupport> ipc_support_; + base::test::ScopedFeatureList feature_list_; std::unique_ptr<aura::Env> env_; diff --git a/chromium/ui/views/painter.cc b/chromium/ui/views/painter.cc index 24bb9e2a566..da297c1a02b 100644 --- a/chromium/ui/views/painter.cc +++ b/chromium/ui/views/painter.cc @@ -31,7 +31,9 @@ class SolidRoundRectPainter : public Painter { SolidRoundRectPainter(SkColor bg_color, SkColor stroke_color, float radius, - const gfx::Insets& insets); + const gfx::Insets& insets, + SkBlendMode blend_mode, + bool antialias); ~SolidRoundRectPainter() override; // Painter: @@ -43,6 +45,8 @@ class SolidRoundRectPainter : public Painter { const SkColor stroke_color_; const float radius_; const gfx::Insets insets_; + const SkBlendMode blend_mode_; + const bool antialias_; DISALLOW_COPY_AND_ASSIGN(SolidRoundRectPainter); }; @@ -50,11 +54,15 @@ class SolidRoundRectPainter : public Painter { SolidRoundRectPainter::SolidRoundRectPainter(SkColor bg_color, SkColor stroke_color, float radius, - const gfx::Insets& insets) + const gfx::Insets& insets, + SkBlendMode blend_mode, + bool antialias) : bg_color_(bg_color), stroke_color_(stroke_color), radius_(radius), - insets_(insets) {} + insets_(insets), + blend_mode_(blend_mode), + antialias_(antialias) {} SolidRoundRectPainter::~SolidRoundRectPainter() {} @@ -68,20 +76,27 @@ void SolidRoundRectPainter::Paint(gfx::Canvas* canvas, const gfx::Size& size) { gfx::Rect inset_rect(size); inset_rect.Inset(insets_); - gfx::RectF border_rect_f(gfx::ScaleToEnclosingRect(inset_rect, scale)); - const SkScalar scaled_corner_radius = SkFloatToScalar(radius_ * scale); + gfx::RectF fill_rect(gfx::ScaleToEnclosingRect(inset_rect, scale)); + gfx::RectF stroke_rect = fill_rect; + float scaled_radius = radius_ * scale; cc::PaintFlags flags; - flags.setAntiAlias(true); + flags.setBlendMode(blend_mode_); + if (antialias_) + flags.setAntiAlias(true); flags.setStyle(cc::PaintFlags::kFill_Style); flags.setColor(bg_color_); - canvas->DrawRoundRect(border_rect_f, scaled_corner_radius, flags); - - border_rect_f.Inset(gfx::InsetsF(0.5f)); - flags.setStyle(cc::PaintFlags::kStroke_Style); - flags.setStrokeWidth(1); - flags.setColor(stroke_color_); - canvas->DrawRoundRect(border_rect_f, scaled_corner_radius, flags); + canvas->DrawRoundRect(fill_rect, scaled_radius, flags); + + if (stroke_color_ != SK_ColorTRANSPARENT) { + constexpr float kStrokeWidth = 1.0f; + stroke_rect.Inset(gfx::InsetsF(kStrokeWidth / 2)); + scaled_radius -= kStrokeWidth / 2; + flags.setStyle(cc::PaintFlags::kStroke_Style); + flags.setStrokeWidth(kStrokeWidth); + flags.setColor(stroke_color_); + canvas->DrawRoundRect(stroke_rect, scaled_radius, flags); + } } // DashedFocusPainter ---------------------------------------------------------- @@ -266,18 +281,22 @@ void Painter::PaintFocusPainter(View* view, std::unique_ptr<Painter> Painter::CreateSolidRoundRectPainter( SkColor color, float radius, - const gfx::Insets& insets) { - return std::make_unique<SolidRoundRectPainter>(color, SK_ColorTRANSPARENT, - radius, insets); + const gfx::Insets& insets, + SkBlendMode blend_mode, + bool antialias) { + return std::make_unique<SolidRoundRectPainter>( + color, SK_ColorTRANSPARENT, radius, insets, blend_mode, antialias); } // static std::unique_ptr<Painter> Painter::CreateRoundRectWith1PxBorderPainter( SkColor bg_color, SkColor stroke_color, - float radius) { - return std::make_unique<SolidRoundRectPainter>(bg_color, stroke_color, radius, - gfx::Insets()); + float radius, + SkBlendMode blend_mode, + bool antialias) { + return std::make_unique<SolidRoundRectPainter>( + bg_color, stroke_color, radius, gfx::Insets(), blend_mode, antialias); } // static diff --git a/chromium/ui/views/painter.h b/chromium/ui/views/painter.h index cb3f495dc8d..984348ee13a 100644 --- a/chromium/ui/views/painter.h +++ b/chromium/ui/views/painter.h @@ -11,6 +11,7 @@ #include "base/compiler_specific.h" #include "base/macros.h" +#include "third_party/skia/include/core/SkBlendMode.h" #include "third_party/skia/include/core/SkColor.h" #include "ui/base/nine_image_painter_factory.h" #include "ui/gfx/geometry/insets.h" @@ -57,14 +58,18 @@ class VIEWS_EXPORT Painter { static std::unique_ptr<Painter> CreateSolidRoundRectPainter( SkColor color, float radius, - const gfx::Insets& insets = gfx::Insets()); + const gfx::Insets& insets = gfx::Insets(), + SkBlendMode blend_mode = SkBlendMode::kSrcOver, + bool antialias = true); // Creates a painter that draws a RoundRect with a solid color and a given // corner radius, and also adds a 1px border (inset) in the given color. static std::unique_ptr<Painter> CreateRoundRectWith1PxBorderPainter( SkColor bg_color, SkColor stroke_color, - float radius); + float radius, + SkBlendMode blend_mode = SkBlendMode::kSrcOver, + bool antialias = true); // Creates a painter that divides |image| into nine regions. The four corners // are rendered at the size specified in insets (eg. the upper-left corner is diff --git a/chromium/ui/views/touchui/touch_selection_controller_impl.cc b/chromium/ui/views/touchui/touch_selection_controller_impl.cc index ae97f1994e4..5264dfe5712 100644 --- a/chromium/ui/views/touchui/touch_selection_controller_impl.cc +++ b/chromium/ui/views/touchui/touch_selection_controller_impl.cc @@ -1,10 +1,9 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Copyright 2018 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 "ui/views/touchui/touch_selection_controller_impl.h" -#include "base/macros.h" #include "base/metrics/histogram_macros.h" #include "base/time/time.h" #include "ui/aura/client/cursor_client.h" @@ -19,6 +18,7 @@ #include "ui/gfx/image/image.h" #include "ui/gfx/path.h" #include "ui/resources/grit/ui_resources.h" +#include "ui/views/views_delegate.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" #include "ui/wm/core/coordinate_conversion.h" @@ -74,14 +74,14 @@ const int kSelectionHandleBarBottomAllowance = 3; // Creates a widget to host SelectionHandleView. views::Widget* CreateTouchSelectionPopupWidget( - gfx::NativeView context, + gfx::NativeView parent, views::WidgetDelegate* widget_delegate) { views::Widget* widget = new views::Widget; views::Widget::InitParams params(views::Widget::InitParams::TYPE_POPUP); params.opacity = views::Widget::InitParams::TRANSLUCENT_WINDOW; params.shadow_type = views::Widget::InitParams::SHADOW_TYPE_NONE; params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; - params.parent = context; + params.parent = parent; params.delegate = widget_delegate; widget->Init(params); return widget; @@ -202,34 +202,6 @@ gfx::Rect BoundToRect(const gfx::SelectionBound& bound) { bound.edge_bottom_rounded()); } -// A WindowTargeter that insets the top of the touch handle's hit-test region. -// This ensures that the client receives touch events above the painted image. -// The widget extends its height to handle touch events below the painted image. -class TouchHandleWindowTargeter : public aura::WindowTargeter { - public: - explicit TouchHandleWindowTargeter(aura::Window* window) : window_(window) {} - ~TouchHandleWindowTargeter() override = default; - - void SetTopInset(int inset) { SetInsets(gfx::Insets(inset, 0, 0, 0)); } - - // aura::WindowTargeter: - void OnSetInsets(const gfx::Insets& last_mouse_extend, - const gfx::Insets& last_touch_extend) override { - // Send the targeter insets to the window service if this is a mus client. - // This helps the window service send events directly to the text window. - // OnSetInsets is generally only called when the insets actually change. - if (window_->env()->mode() == aura::Env::Mode::MUS) { - gfx::Rect mask(window_->bounds().size()); - mask.Inset(touch_extend()); - aura::WindowPortMus::Get(window_->GetRootWindow())->SetHitTestMask(mask); - } - } - - private: - aura::Window* window_; - DISALLOW_COPY_AND_ASSIGN(TouchHandleWindowTargeter); -}; - } // namespace namespace views { @@ -238,20 +210,24 @@ using EditingHandleView = TouchSelectionControllerImpl::EditingHandleView; // A View that displays the text selection handle. class TouchSelectionControllerImpl::EditingHandleView - : public views::WidgetDelegateView { + : public WidgetDelegateView { public: EditingHandleView(TouchSelectionControllerImpl* controller, - gfx::NativeView context, + gfx::NativeView parent, bool is_cursor_handle) : controller_(controller), image_(GetCenterHandleImage()), is_cursor_handle_(is_cursor_handle), draw_invisible_(false), weak_ptr_factory_(this) { - widget_.reset(CreateTouchSelectionPopupWidget(context, this)); + widget_.reset(CreateTouchSelectionPopupWidget(parent, this)); + targeter_ = new aura::WindowTargeter(); aura::Window* window = widget_->GetNativeWindow(); - targeter_ = new TouchHandleWindowTargeter(window); + // For Mus clients, adjust targeting of the handle's client root window, + // constructed by the window server for the handle's "content" window. + if (window->env()->mode() == aura::Env::Mode::MUS) + window = window->GetRootWindow(); window->SetEventTargeter(std::unique_ptr<aura::WindowTargeter>(targeter_)); // We are owned by the TouchSelectionControllerImpl. @@ -264,12 +240,12 @@ class TouchSelectionControllerImpl::EditingHandleView return selection_bound_.type(); } - // Overridden from views::WidgetDelegateView: + // WidgetDelegateView: void DeleteDelegate() override { // We are owned and deleted by TouchSelectionControllerImpl. } - // Overridden from views::View: + // View: void OnPaint(gfx::Canvas* canvas) override { if (draw_invisible_) return; @@ -379,8 +355,10 @@ class TouchSelectionControllerImpl::EditingHandleView selection_bound_.SetEdge(gfx::PointF(edge_top), gfx::PointF(edge_bottom)); } - targeter_->SetTopInset(selection_bound_.GetHeight() + - kSelectionHandleVerticalVisualOffset); + const gfx::Insets insets( + selection_bound_.GetHeight() + kSelectionHandleVerticalVisualOffset, 0, + 0, 0); + targeter_->SetInsets(insets, insets); } void SetDrawInvisible(bool draw_invisible) { @@ -394,11 +372,11 @@ class TouchSelectionControllerImpl::EditingHandleView std::unique_ptr<Widget> widget_; TouchSelectionControllerImpl* controller_; - // A custom targeter that shifts the hit-test target below the apparent bounds + // A WindowTargeter that shifts the hit-test target below the apparent bounds // to make dragging easier. The |widget_|'s NativeWindow takes ownership over // the |targeter_| but since the |widget_|'s lifetime is known to this class, // it can safely access the |targeter_|. - TouchHandleWindowTargeter* targeter_; + aura::WindowTargeter* targeter_; // In local coordinates gfx::SelectionBound selection_bound_; @@ -427,23 +405,20 @@ class TouchSelectionControllerImpl::EditingHandleView TouchSelectionControllerImpl::TouchSelectionControllerImpl( ui::TouchEditable* client_view) : client_view_(client_view), - client_widget_(nullptr), - selection_handle_1_(new EditingHandleView(this, - client_view->GetNativeView(), - false)), - selection_handle_2_(new EditingHandleView(this, - client_view->GetNativeView(), - false)), - cursor_handle_(new EditingHandleView(this, - client_view->GetNativeView(), - true)), - command_executed_(false), - dragging_handle_(nullptr) { + selection_handle_1_( + new EditingHandleView(this, client_view->GetNativeView(), false)), + selection_handle_2_( + new EditingHandleView(this, client_view->GetNativeView(), false)), + cursor_handle_( + new EditingHandleView(this, client_view->GetNativeView(), true)) { selection_start_time_ = base::TimeTicks::Now(); aura::Window* client_window = client_view_->GetNativeView(); client_widget_ = Widget::GetTopLevelWidgetForNativeView(client_window); + // Observe client widget moves and resizes to update the selection handles. if (client_widget_) client_widget_->AddObserver(this); + if (ViewsDelegate::GetInstance()->IsPointerWatcherSupported()) + ViewsDelegate::GetInstance()->AddPointerWatcher(this, true); aura::Env::GetInstance()->AddPreTargetHandler(this); } @@ -452,6 +427,8 @@ TouchSelectionControllerImpl::~TouchSelectionControllerImpl() { command_executed_); HideQuickMenu(); aura::Env::GetInstance()->RemovePreTargetHandler(this); + if (ViewsDelegate::GetInstance()->IsPointerWatcherSupported()) + ViewsDelegate::GetInstance()->RemovePointerWatcher(this); if (client_widget_) client_widget_->RemoveObserver(this); } @@ -649,6 +626,18 @@ void TouchSelectionControllerImpl::OnWidgetBoundsChanged( SelectionChanged(); } +void TouchSelectionControllerImpl::OnPointerEventObserved( + const ui::PointerEvent& event, + const gfx::Point& location_in_screen, + gfx::NativeView target) { + // Disregard CursorClient::IsMouseEventsEnabled, it is disabled for touch + // events in this client, but not re-enabled for mouse events sent elsewhere. + if (event.pointer_details().pointer_type == + ui::EventPointerType::POINTER_TYPE_MOUSE) { + client_view_->DestroyTouchSelection(); + } +} + void TouchSelectionControllerImpl::OnKeyEvent(ui::KeyEvent* event) { client_view_->DestroyTouchSelection(); } @@ -773,11 +762,11 @@ gfx::Rect TouchSelectionControllerImpl::GetExpectedHandleBounds( return GetSelectionWidgetBounds(bound); } -views::WidgetDelegateView* TouchSelectionControllerImpl::GetHandle1View() { +WidgetDelegateView* TouchSelectionControllerImpl::GetHandle1View() { return selection_handle_1_.get(); } -views::WidgetDelegateView* TouchSelectionControllerImpl::GetHandle2View() { +WidgetDelegateView* TouchSelectionControllerImpl::GetHandle2View() { return selection_handle_2_.get(); } diff --git a/chromium/ui/views/touchui/touch_selection_controller_impl.h b/chromium/ui/views/touchui/touch_selection_controller_impl.h index cf222421548..7e231e126e1 100644 --- a/chromium/ui/views/touchui/touch_selection_controller_impl.h +++ b/chromium/ui/views/touchui/touch_selection_controller_impl.h @@ -1,9 +1,9 @@ -// Copyright (c) 2013 The Chromium Authors. All rights reserved. +// Copyright 2018 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 UI_UI_VIEWS_TOUCHUI_TOUCH_SELECTION_CONTROLLER_IMPL_H_ -#define UI_UI_VIEWS_TOUCHUI_TOUCH_SELECTION_CONTROLLER_IMPL_H_ +#ifndef UI_VIEWS_TOUCHUI_TOUCH_SELECTION_CONTROLLER_IMPL_H_ +#define UI_VIEWS_TOUCHUI_TOUCH_SELECTION_CONTROLLER_IMPL_H_ #include "base/macros.h" #include "base/timer/timer.h" @@ -11,6 +11,7 @@ #include "ui/gfx/geometry/point.h" #include "ui/gfx/selection_bound.h" #include "ui/touch_selection/touch_selection_menu_runner.h" +#include "ui/views/pointer_watcher.h" #include "ui/views/view.h" #include "ui/views/views_export.h" #include "ui/views/widget/widget_observer.h" @@ -29,17 +30,16 @@ class VIEWS_EXPORT TouchSelectionControllerImpl : public ui::TouchEditingControllerDeprecated, public ui::TouchSelectionMenuClient, public WidgetObserver, + public PointerWatcher, public ui::EventHandler { public: class EditingHandleView; - // Use TextSelectionController::create(). - explicit TouchSelectionControllerImpl( - ui::TouchEditable* client_view); - + // Use ui::TouchEditingControllerFactory::Create() instead. + explicit TouchSelectionControllerImpl(ui::TouchEditable* client_view); ~TouchSelectionControllerImpl() override; - // TextSelectionController. + // ui::TouchEditingControllerDeprecated: void SelectionChanged() override; bool IsHandleDragInProgress() override; void HideHandles(bool quick) override; @@ -70,19 +70,22 @@ class VIEWS_EXPORT TouchSelectionControllerImpl // |bound| should be the clipped version of the selection bound. bool ShouldShowHandleFor(const gfx::SelectionBound& bound) const; - // Overridden from ui::TouchSelectionMenuClient. + // ui::TouchSelectionMenuClient: bool IsCommandIdEnabled(int command_id) const override; void ExecuteCommand(int command_id, int event_flags) override; void RunContextMenu() override; - // Overridden from WidgetObserver. We will observe the widget backing the - // |client_view_| so that when its moved/resized, we can update the selection - // handles appropriately. + // WidgetObserver: void OnWidgetClosing(Widget* widget) override; void OnWidgetBoundsChanged(Widget* widget, const gfx::Rect& new_bounds) override; - // Overriden from ui::EventHandler. + // PointerWatcher: + void OnPointerEventObserved(const ui::PointerEvent& event, + const gfx::Point& location_in_screen, + gfx::NativeView target) override; + + // ui::EventHandler: void OnKeyEvent(ui::KeyEvent* event) override; void OnMouseEvent(ui::MouseEvent* event) override; void OnScrollEvent(ui::ScrollEvent* event) override; @@ -112,15 +115,15 @@ class VIEWS_EXPORT TouchSelectionControllerImpl bool IsSelectionHandle2Visible(); bool IsCursorHandleVisible(); gfx::Rect GetExpectedHandleBounds(const gfx::SelectionBound& bound); - views::WidgetDelegateView* GetHandle1View(); - views::WidgetDelegateView* GetHandle2View(); + WidgetDelegateView* GetHandle1View(); + WidgetDelegateView* GetHandle2View(); ui::TouchEditable* client_view_; - Widget* client_widget_; + Widget* client_widget_ = nullptr; std::unique_ptr<EditingHandleView> selection_handle_1_; std::unique_ptr<EditingHandleView> selection_handle_2_; std::unique_ptr<EditingHandleView> cursor_handle_; - bool command_executed_; + bool command_executed_ = false; base::TimeTicks selection_start_time_; // Timer to trigger quick menu (Quick menu is not shown if the selection @@ -129,7 +132,7 @@ class VIEWS_EXPORT TouchSelectionControllerImpl base::OneShotTimer quick_menu_timer_; // Pointer to the SelectionHandleView being dragged during a drag session. - EditingHandleView* dragging_handle_; + EditingHandleView* dragging_handle_ = nullptr; // In cursor mode, the two selection bounds are the same and correspond to // |cursor_handle_|; otherwise, they correspond to |selection_handle_1_| and @@ -148,4 +151,4 @@ class VIEWS_EXPORT TouchSelectionControllerImpl } // namespace views -#endif // UI_UI_VIEWS_TOUCHUI_TOUCH_SELECTION_CONTROLLER_IMPL_H_ +#endif // UI_VIEWS_TOUCHUI_TOUCH_SELECTION_CONTROLLER_IMPL_H_ diff --git a/chromium/ui/views/view.cc b/chromium/ui/views/view.cc index d238fad21fd..d0aba92898f 100644 --- a/chromium/ui/views/view.cc +++ b/chromium/ui/views/view.cc @@ -311,7 +311,7 @@ bool View::Contains(const View* view) const { } int View::GetIndexOf(const View* view) const { - Views::const_iterator i(std::find(children_.begin(), children_.end(), view)); + auto i(std::find(children_.begin(), children_.end(), view)); return i != children_.end() ? static_cast<int>(i - children_.begin()) : -1; } @@ -1191,6 +1191,13 @@ void View::ConvertEventToTarget(ui::EventTarget* target, event->ConvertLocationToTarget(this, static_cast<View*>(target)); } +gfx::PointF View::GetScreenLocationF(const ui::LocatedEvent& event) const { + DCHECK_EQ(this, event.target()); + gfx::Point screen_location(event.location()); + ConvertPointToScreen(this, &screen_location); + return gfx::PointF(screen_location); +} + // Accelerators ---------------------------------------------------------------- void View::AddAccelerator(const ui::Accelerator& accelerator) { @@ -1209,8 +1216,7 @@ void View::RemoveAccelerator(const ui::Accelerator& accelerator) { return; } - std::vector<ui::Accelerator>::iterator i( - std::find(accelerators_->begin(), accelerators_->end(), accelerator)); + auto i(std::find(accelerators_->begin(), accelerators_->end(), accelerator)); if (i == accelerators_->end()) { NOTREACHED() << "Removing non-existing accelerator"; return; @@ -2204,7 +2210,7 @@ void View::BoundsChanged(const gfx::Rect& previous_bounds) { // Notify interested Views that visible bounds within the root view may have // changed. if (descendants_to_notify_.get()) { - for (Views::iterator i(descendants_to_notify_->begin()); + for (auto i(descendants_to_notify_->begin()); i != descendants_to_notify_->end(); ++i) { (*i)->OnVisibleBoundsChanged(); } @@ -2254,8 +2260,8 @@ void View::AddDescendantToNotify(View* view) { void View::RemoveDescendantToNotify(View* view) { DCHECK(view && descendants_to_notify_.get()); - Views::iterator i(std::find( - descendants_to_notify_->begin(), descendants_to_notify_->end(), view)); + auto i(std::find(descendants_to_notify_->begin(), + descendants_to_notify_->end(), view)); DCHECK(i != descendants_to_notify_->end()); descendants_to_notify_->erase(i); if (descendants_to_notify_->empty()) diff --git a/chromium/ui/views/view.h b/chromium/ui/views/view.h index 4248609dd3c..161e015c044 100644 --- a/chromium/ui/views/view.h +++ b/chromium/ui/views/view.h @@ -888,6 +888,7 @@ class VIEWS_EXPORT View : public ui::LayerDelegate, ui::EventTargeter* GetEventTargeter() override; void ConvertEventToTarget(ui::EventTarget* target, ui::LocatedEvent* event) override; + gfx::PointF GetScreenLocationF(const ui::LocatedEvent& event) const override; // Overridden from ui::EventHandler: void OnKeyEvent(ui::KeyEvent* event) override; diff --git a/chromium/ui/views/view_properties.cc b/chromium/ui/views/view_properties.cc index e55acc0ca94..2f4f54e35ff 100644 --- a/chromium/ui/views/view_properties.cc +++ b/chromium/ui/views/view_properties.cc @@ -20,6 +20,8 @@ DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, gfx::Insets*); DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, views::BubbleDialogDelegateView*); +DEFINE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, gfx::Path*); + namespace views { DEFINE_UI_CLASS_PROPERTY_KEY(int, kHitTestComponentKey, HTNOWHERE); @@ -27,5 +29,6 @@ DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::Insets, kMarginsKey, nullptr); DEFINE_UI_CLASS_PROPERTY_KEY(views::BubbleDialogDelegateView*, kAnchoredDialogKey, nullptr); +DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(gfx::Path, kHighlightPathKey, nullptr); } // namespace views diff --git a/chromium/ui/views/view_properties.h b/chromium/ui/views/view_properties.h index 2896ee94512..5f63c1b902d 100644 --- a/chromium/ui/views/view_properties.h +++ b/chromium/ui/views/view_properties.h @@ -10,6 +10,7 @@ namespace gfx { class Insets; +class Path; } // namespace gfx namespace views { @@ -30,6 +31,12 @@ VIEWS_EXPORT extern const ui::ClassProperty<gfx::Insets*>* const kMarginsKey; VIEWS_EXPORT extern const ui::ClassProperty<BubbleDialogDelegateView*>* const kAnchoredDialogKey; +// A property to store a highlight path related to the view. This is nominally +// used by the default inkdrop and focus ring that are both used to highlight +// the view in different ways. +VIEWS_EXPORT extern const ui::ClassProperty<gfx::Path*>* const + kHighlightPathKey; + } // namespace views // Declaring the template specialization here to make sure that the @@ -40,5 +47,5 @@ VIEWS_EXPORT extern const ui::ClassProperty<BubbleDialogDelegateView*>* const DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, gfx::Insets*); DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, views::BubbleDialogDelegateView*); - +DECLARE_EXPORTED_UI_CLASS_PROPERTY_TYPE(VIEWS_EXPORT, gfx::Path*); #endif // UI_VIEWS_VIEW_PROPERTIES_H_ diff --git a/chromium/ui/views/views_delegate.cc b/chromium/ui/views/views_delegate.cc index 846e579237c..d580fde1f8b 100644 --- a/chromium/ui/views/views_delegate.cc +++ b/chromium/ui/views/views_delegate.cc @@ -48,8 +48,7 @@ ViewsDelegate* ViewsDelegate::GetInstance() { void ViewsDelegate::SaveWindowPlacement(const Widget* widget, const std::string& window_name, const gfx::Rect& bounds, - ui::WindowShowState show_state) { -} + ui::WindowShowState show_state) {} bool ViewsDelegate::GetSavedWindowPlacement( const Widget* widget, @@ -66,8 +65,7 @@ void ViewsDelegate::NotifyMenuItemFocused(const base::string16& menu_name, const base::string16& menu_item_name, int item_index, int item_count, - bool has_submenu) { -} + bool has_submenu) {} ViewsDelegate::ProcessMenuAcceleratorResult ViewsDelegate::ProcessAcceleratorWhileMenuShowing( @@ -98,20 +96,14 @@ NonClientFrameView* ViewsDelegate::CreateDefaultNonClientFrameView( return nullptr; } -void ViewsDelegate::AddRef() { -} +void ViewsDelegate::AddRef() {} -void ViewsDelegate::ReleaseRef() { -} +void ViewsDelegate::ReleaseRef() {} void ViewsDelegate::OnBeforeWidgetInit( Widget::InitParams* params, internal::NativeWidgetDelegate* delegate) {} -base::TimeDelta ViewsDelegate::GetTextfieldPasswordRevealDuration() { - return base::TimeDelta(); -} - bool ViewsDelegate::WindowManagerProvidesTitleBar(bool maximized) { return false; } @@ -140,4 +132,16 @@ bool ViewsDelegate::ShouldMirrorArrowsInRTL() const { return true; } +void ViewsDelegate::AddPointerWatcher(PointerWatcher*, bool) { + NOTREACHED(); +} + +void ViewsDelegate::RemovePointerWatcher(PointerWatcher*) { + NOTREACHED(); +} + +bool ViewsDelegate::IsPointerWatcherSupported() const { + return false; +} + } // namespace views diff --git a/chromium/ui/views/views_delegate.h b/chromium/ui/views/views_delegate.h index 3f16d177c21..b5237c3f20f 100644 --- a/chromium/ui/views/views_delegate.h +++ b/chromium/ui/views/views_delegate.h @@ -23,10 +23,6 @@ #include "ui/views/views_export.h" #include "ui/views/widget/widget.h" -namespace base { -class TimeDelta; -} - namespace gfx { class ImageSkia; class Rect; @@ -34,13 +30,14 @@ class Rect; namespace ui { class ContextFactory; +class TouchEditingControllerFactory; } namespace views { class NativeWidget; class NonClientFrameView; -class ViewsTouchEditingControllerFactory; +class PointerWatcher; class View; class Widget; @@ -173,9 +170,6 @@ class VIEWS_EXPORT ViewsDelegate { virtual void OnBeforeWidgetInit(Widget::InitParams* params, internal::NativeWidgetDelegate* delegate); - // Returns the password reveal duration for Textfield. - virtual base::TimeDelta GetTextfieldPasswordRevealDuration(); - // Returns true if the operating system's window manager will always provide a // title bar with caption buttons (ignoring the setting to // |remove_standard_frame| in InitParams). If |maximized|, this applies to @@ -206,11 +200,18 @@ class VIEWS_EXPORT ViewsDelegate { // opens in the opposite direction. virtual bool ShouldMirrorArrowsInRTL() const; + // Allows lower-level views components to use Mus-only PointerWatcher wiring. + // TODO(crbug.com/887725): Support PointerWatcher without mus, refactor. + virtual void AddPointerWatcher(PointerWatcher* pointer_watcher, + bool wants_moves); + virtual void RemovePointerWatcher(PointerWatcher* pointer_watcher); + virtual bool IsPointerWatcherSupported() const; + protected: ViewsDelegate(); private: - std::unique_ptr<ViewsTouchEditingControllerFactory> + std::unique_ptr<ui::TouchEditingControllerFactory> editing_controller_factory_; #if defined(USE_AURA) diff --git a/chromium/ui/views/widget/desktop_aura/desktop_capture_client.cc b/chromium/ui/views/widget/desktop_aura/desktop_capture_client.cc index 86578b995a2..38447dc9ae0 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_capture_client.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_capture_client.cc @@ -79,8 +79,7 @@ void DesktopCaptureClient::SetCapture(aura::Window* new_capture_window) { // Notify the other roots that we got capture. This is important so that // they reset state. CaptureClients capture_clients(*capture_clients_); - for (CaptureClients::iterator i = capture_clients.begin(); - i != capture_clients.end(); ++i) { + for (auto i = capture_clients.begin(); i != capture_clients.end(); ++i) { if (*i != this) { aura::client::CaptureDelegate* delegate = (*i)->root_->GetHost()->dispatcher(); diff --git a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc index 00073d3d3bf..f0da4f88142 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11.cc @@ -450,8 +450,7 @@ void DesktopDragDropClientAuraX11::X11DragContext::ReadActions() { int DesktopDragDropClientAuraX11::X11DragContext::GetDragOperation() const { int drag_operation = ui::DragDropTypes::DRAG_NONE; - for (std::vector<::Atom>::const_iterator it = actions_.begin(); - it != actions_.end(); ++it) { + for (auto it = actions_.begin(); it != actions_.end(); ++it) { MaskOperation(*it, &drag_operation); } @@ -711,7 +710,7 @@ void DesktopDragDropClientAuraX11::OnXdndDrop( } if (!IsDragDropInProgress()) { - UMA_HISTOGRAM_COUNTS("Event.DragDrop.ExternalOriginDrop", 1); + UMA_HISTOGRAM_COUNTS_1M("Event.DragDrop.ExternalOriginDrop", 1); } drag_operation = delegate->OnPerformDrop(event); diff --git a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11_unittest.cc b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11_unittest.cc index a6fe148e3d2..f60cc49e7ab 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11_unittest.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_drag_drop_client_aurax11_unittest.cc @@ -354,8 +354,7 @@ void TestDragDropClient::SetTopmostXWindowAndMoveMouse(::Window xid) { } void TestDragDropClient::SendXClientEvent(::Window xid, XEvent* event) { - std::map< ::Window, ClientMessageEventCollector*>::iterator it = - collectors_.find(xid); + auto it = collectors_.find(xid); if (it != collectors_.end()) it->second->RecordEvent(event->xclient); } diff --git a/chromium/ui/views/widget/desktop_aura/desktop_drop_target_win.cc b/chromium/ui/views/widget/desktop_aura/desktop_drop_target_win.cc index 99c525f3219..094a0b00bce 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_drop_target_win.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_drop_target_win.cc @@ -99,7 +99,7 @@ DWORD DesktopDropTargetWin::OnDrop(IDataObject* data_object, DragDropClient* client = aura::client::GetDragDropClient(root_window_); if (client && !client->IsDragDropInProgress() && drag_operation != ui::DragDropTypes::DRAG_NONE) { - UMA_HISTOGRAM_COUNTS("Event.DragDrop.ExternalOriginDrop", 1); + UMA_HISTOGRAM_COUNTS_1M("Event.DragDrop.ExternalOriginDrop", 1); } } if (target_window_) { diff --git a/chromium/ui/views/widget/desktop_aura/desktop_native_cursor_manager.cc b/chromium/ui/views/widget/desktop_aura/desktop_native_cursor_manager.cc index 5d4ae7a9805..d0731338684 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_native_cursor_manager.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_native_cursor_manager.cc @@ -51,7 +51,7 @@ void DesktopNativeCursorManager::SetCursor( delegate->CommitCursor(new_cursor); if (delegate->IsCursorVisible()) { - for (Hosts::const_iterator i = hosts_.begin(); i != hosts_.end(); ++i) + for (auto i = hosts_.begin(); i != hosts_.end(); ++i) (*i)->SetCursor(new_cursor); } } @@ -66,11 +66,11 @@ void DesktopNativeCursorManager::SetVisibility( } else { gfx::NativeCursor invisible_cursor(ui::CursorType::kNone); cursor_loader_->SetPlatformCursor(&invisible_cursor); - for (Hosts::const_iterator i = hosts_.begin(); i != hosts_.end(); ++i) + for (auto i = hosts_.begin(); i != hosts_.end(); ++i) (*i)->SetCursor(invisible_cursor); } - for (Hosts::const_iterator i = hosts_.begin(); i != hosts_.end(); ++i) + for (auto i = hosts_.begin(); i != hosts_.end(); ++i) (*i)->OnCursorVisibilityChanged(visible); } @@ -90,7 +90,7 @@ void DesktopNativeCursorManager::SetMouseEventsEnabled( SetVisibility(delegate->IsCursorVisible(), delegate); - for (Hosts::const_iterator i = hosts_.begin(); i != hosts_.end(); ++i) + for (auto i = hosts_.begin(); i != hosts_.end(); ++i) (*i)->dispatcher()->OnMouseEventsEnableStateChanged(enabled); } diff --git a/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc b/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc index 01a429a67e3..264858b3a46 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.cc @@ -595,11 +595,15 @@ const ui::Layer* DesktopNativeWidgetAura::GetLayer() const { } void DesktopNativeWidgetAura::ReorderNativeViews() { + if (!content_window_) + return; + // Reordering native views causes multiple changes to the window tree. - // Instantiate a ScopedPauseOcclusionTracking to recompute occlusion once at - // the end of this scope rather than after each individual change. + // Instantiate a ScopedPause to recompute occlusion once at the end of this + // scope rather than after each individual change. // https://crbug.com/829918 - aura::WindowOcclusionTracker::ScopedPauseOcclusionTracking pause_occlusion; + aura::WindowOcclusionTracker::ScopedPause pause_occlusion( + content_window_->env()); window_reorderer_->ReorderChildWindows(); } diff --git a/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc b/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc index 9d4d564ee7a..2514071e8bd 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura_unittest.cc @@ -17,7 +17,6 @@ #include "ui/aura/test/test_window_delegate.h" #include "ui/aura/test/window_occlusion_tracker_test_api.h" #include "ui/aura/window.h" -#include "ui/aura/window_occlusion_tracker.h" #include "ui/aura/window_tree_host.h" #include "ui/display/screen.h" #include "ui/events/event_processor.h" @@ -312,7 +311,7 @@ TEST_F(DesktopNativeWidgetAuraTest, ReorderDoesntRecomputeOcclusion) { parent.Show(); aura::Window* parent_window = parent.GetNativeWindow(); - aura::WindowOcclusionTracker::Track(parent_window); + parent_window->TrackOcclusionState(); View* contents_view = parent.GetContentsView(); @@ -335,7 +334,8 @@ TEST_F(DesktopNativeWidgetAuraTest, ReorderDoesntRecomputeOcclusion) { contents_view->AddChildView(host_view3); // Reorder child views. Expect occlusion to only be recomputed once. - aura::test::WindowOcclusionTrackerTestApi window_occlusion_tracker_test_api; + aura::test::WindowOcclusionTrackerTestApi window_occlusion_tracker_test_api( + parent_window->env()); const int num_times_occlusion_recomputed = window_occlusion_tracker_test_api.GetNumTimesOcclusionRecomputed(); contents_view->ReorderChildView(host_view3, 0); diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.cc b/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.cc index a5e871e8dae..6dd57cd7454 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_ozone.cc @@ -4,6 +4,7 @@ #include "ui/views/widget/desktop_aura/desktop_screen_ozone.h" +#include "ui/aura/screen_ozone.h" #include "ui/display/display.h" #include "ui/display/types/display_constants.h" #include "ui/display/types/display_snapshot.h" @@ -55,7 +56,16 @@ void DesktopScreenOzone::OnDisplaySnapshotsInvalidated() {} ////////////////////////////////////////////////////////////////////////////// display::Screen* CreateDesktopScreen() { - return new DesktopScreenOzone; + auto platform_screen = ui::OzonePlatform::GetInstance()->CreateScreen(); + if (!platform_screen) { + // TODO: At the moment, only the Ozone/Headless uses this patch. Fix it: + // https://crbug.com/891613 + LOG(ERROR) << "PlatformScreen is not implemented for this ozone platform. " + "Falling back to old DesktopScreenOzone implementation. See " + "https://crbug.com/872339 for details"; + return new DesktopScreenOzone; + } + return new aura::ScreenOzone(std::move(platform_screen)); } } // namespace views diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc index e90710c2c39..0951d2fb5b9 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc @@ -232,8 +232,7 @@ display::Display DesktopScreenX11::GetDisplayNearestPoint( const gfx::Point& point) const { if (displays_.size() <= 1) return GetPrimaryDisplay(); - for (std::vector<display::Display>::const_iterator it = displays_.begin(); - it != displays_.end(); ++it) { + for (auto it = displays_.begin(); it != displays_.end(); ++it) { if (it->bounds().Contains(point)) return *it; } @@ -244,8 +243,7 @@ display::Display DesktopScreenX11::GetDisplayMatching( const gfx::Rect& match_rect) const { int max_area = 0; const display::Display* matching = NULL; - for (std::vector<display::Display>::const_iterator it = displays_.begin(); - it != displays_.end(); ++it) { + for (auto it = displays_.begin(); it != displays_.end(); ++it) { gfx::Rect intersect = gfx::IntersectRects(it->bounds(), match_rect); int area = intersect.width() * intersect.height(); if (area > max_area) { @@ -450,7 +448,7 @@ std::vector<display::Display> DesktopScreenX11::BuildDisplaysFromXRandRInfo() { if (monitor_iter != output_to_monitor.end() && monitor_iter->second == 0) monitor_order_primary_display_index = displays.size(); - if (!display::Display::HasForceColorProfile()) { + if (!display::Display::HasForceDisplayColorProfile()) { gfx::ICCProfile icc_profile = GetICCProfileForMonitor( monitor_iter == output_to_monitor.end() ? 0 : monitor_iter->second); icc_profile.HistogramDisplay(display.id()); diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc index dcf1d7d40c2..fbfa8df1f7d 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc @@ -6,13 +6,16 @@ #include "ui/aura/client/drag_drop_client.h" #include "ui/aura/client/transient_window_client.h" +#include "ui/base/hit_test.h" #include "ui/display/display.h" #include "ui/display/screen.h" #include "ui/gfx/geometry/dip_util.h" #include "ui/platform_window/platform_window.h" +#include "ui/platform_window/platform_window_handler/wm_move_resize_handler.h" #include "ui/platform_window/platform_window_init_properties.h" #include "ui/views/corewm/tooltip_aura.h" #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" +#include "ui/views/widget/desktop_aura/window_event_filter.h" #include "ui/views/widget/widget_aura_utils.h" #include "ui/views/window/native_frame_view.h" #include "ui/wm/core/window_util.h" @@ -91,6 +94,19 @@ void DesktopWindowTreeHostPlatform::Init(const Widget::InitParams& params) { void DesktopWindowTreeHostPlatform::OnNativeWidgetCreated( const Widget::InitParams& params) { native_widget_delegate_->OnNativeWidgetCreated(true); + + // Setup a non_client_window_event_filter, which handles resize/move, double + // click and other events. + DCHECK(!non_client_window_event_filter_); + std::unique_ptr<WindowEventFilter> window_event_filter = + std::make_unique<WindowEventFilter>(this); + auto* wm_move_resize_handler = GetWmMoveResizeHandler(*platform_window()); + if (wm_move_resize_handler) + window_event_filter->SetWmMoveResizeHandler( + GetWmMoveResizeHandler(*(platform_window()))); + + non_client_window_event_filter_ = std::move(window_event_filter); + window()->AddPreTargetHandler(non_client_window_event_filter_.get()); } void DesktopWindowTreeHostPlatform::OnWidgetInitDone() {} @@ -135,6 +151,8 @@ void DesktopWindowTreeHostPlatform::CloseNow() { if (!weak_ref || got_on_closed_) return; + RemoveNonClientEventFilter(); + native_widget_delegate_->OnNativeWidgetDestroying(); got_on_closed_ = true; @@ -449,7 +467,34 @@ bool DesktopWindowTreeHostPlatform::ShouldCreateVisibilityController() const { return true; } +void DesktopWindowTreeHostPlatform::DispatchEvent(ui::Event* event) { +#if defined(USE_OZONE) + // Make sure the |event| is marked as a non-client if it's a non-client + // mouse down event. This is needed to make sure the WindowEventDispatcher + // does not set a |mouse_pressed_handler_| for such events, because they are + // not always followed with non-client mouse up events in case of + // Ozone/Wayland or Ozone/X11. + // + // Also see the comment in WindowEventDispatcher::PreDispatchMouseEvent.. + aura::Window* content_window = desktop_native_widget_aura_->content_window(); + if (content_window && content_window->delegate()) { + if (event->IsMouseEvent()) { + ui::MouseEvent* mouse_event = event->AsMouseEvent(); + int flags = mouse_event->flags(); + int hit_test_code = content_window->delegate()->GetNonClientComponent( + mouse_event->location()); + if (hit_test_code != HTCLIENT && hit_test_code != HTNOWHERE) + flags |= ui::EF_IS_NON_CLIENT; + mouse_event->set_flags(flags); + } + } +#endif + + WindowTreeHostPlatform::DispatchEvent(event); +} + void DesktopWindowTreeHostPlatform::OnClosed() { + RemoveNonClientEventFilter(); got_on_closed_ = true; desktop_native_widget_aura_->OnHostClosed(); } @@ -492,6 +537,14 @@ void DesktopWindowTreeHostPlatform::Relayout() { widget->GetRootView()->Layout(); } +void DesktopWindowTreeHostPlatform::RemoveNonClientEventFilter() { + if (!non_client_window_event_filter_) + return; + + window()->RemovePreTargetHandler(non_client_window_event_filter_.get()); + non_client_window_event_filter_.reset(); +} + Widget* DesktopWindowTreeHostPlatform::GetWidget() { return native_widget_delegate_->AsWidget(); } diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h index b6219dd00a7..e0b98abdaf0 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h @@ -12,6 +12,8 @@ namespace views { +class WindowEventFilter; + class VIEWS_EXPORT DesktopWindowTreeHostPlatform : public aura::WindowTreeHostPlatform, public DesktopWindowTreeHost { @@ -90,14 +92,19 @@ class VIEWS_EXPORT DesktopWindowTreeHostPlatform bool ShouldCreateVisibilityController() const override; // WindowTreeHostPlatform: + void DispatchEvent(ui::Event* event) override; void OnClosed() override; void OnWindowStateChanged(ui::PlatformWindowState new_state) override; void OnCloseRequest() override; void OnActivationChanged(bool active) override; private: + FRIEND_TEST_ALL_PREFIXES(DesktopWindowTreeHostPlatformTest, HitTest); + void Relayout(); + void RemoveNonClientEventFilter(); + Widget* GetWidget(); gfx::Rect ToDIPRect(const gfx::Rect& rect_in_pixels) const; @@ -113,6 +120,9 @@ class VIEWS_EXPORT DesktopWindowTreeHostPlatform bool is_active_ = false; + // A handler for events intended for non client area. + std::unique_ptr<WindowEventFilter> non_client_window_event_filter_; + base::WeakPtrFactory<DesktopWindowTreeHostPlatform> weak_factory_{this}; DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostPlatform); diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_interactive_uitest.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_interactive_uitest.cc new file mode 100644 index 00000000000..f959917fcfe --- /dev/null +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_interactive_uitest.cc @@ -0,0 +1,272 @@ +// Copyright 2018 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 "ui/views/widget/desktop_aura/desktop_window_tree_host_platform.h" + +#include "ui/aura/window_tree_host_platform.h" +#include "ui/base/hit_test.h" +#include "ui/platform_window/platform_window.h" +#include "ui/platform_window/platform_window_handler/wm_move_resize_handler.h" +#include "ui/views/test/views_interactive_ui_test_base.h" +#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" +#include "ui/views/widget/desktop_aura/window_event_filter.h" +#include "ui/views/widget/widget_delegate.h" +#include "ui/views/window/native_frame_view.h" + +namespace views { + +namespace { + +bool IsNonClientComponent(int hittest) { + switch (hittest) { + case HTBOTTOM: + case HTBOTTOMLEFT: + case HTBOTTOMRIGHT: + case HTCAPTION: + case HTLEFT: + case HTRIGHT: + case HTTOP: + case HTTOPLEFT: + case HTTOPRIGHT: + return true; + default: + return false; + } + return true; +} + +// A fake handler, which just stores the hittest and pointer location values. +class FakeWmMoveResizeHandler : public ui::WmMoveResizeHandler { + public: + using SetBoundsCallback = base::RepeatingCallback<void(gfx::Rect)>; + explicit FakeWmMoveResizeHandler(ui::PlatformWindow* window) + : platform_window_(window), hittest_(-1) {} + ~FakeWmMoveResizeHandler() override = default; + + void Reset() { + hittest_ = -1; + pointer_location_ = gfx::Point(); + } + + int hittest() const { return hittest_; } + gfx::Point pointer_location() const { return pointer_location_; } + + void set_bounds(const gfx::Rect& bounds) { bounds_ = bounds; } + + // ui::WmMoveResizeHandler + void DispatchHostWindowDragMovement( + int hittest, + const gfx::Point& pointer_location) override { + hittest_ = hittest; + pointer_location_ = pointer_location; + + platform_window_->SetBounds(bounds_); + } + + private: + ui::PlatformWindow* platform_window_; + gfx::Rect bounds_; + + int hittest_ = -1; + gfx::Point pointer_location_; + + DISALLOW_COPY_AND_ASSIGN(FakeWmMoveResizeHandler); +}; + +void SetExpectationBasedOnHittestValue(int hittest, + const FakeWmMoveResizeHandler& handler, + const gfx::Point& pointer_location) { + if (IsNonClientComponent(hittest)) { + // Ensure both the pointer location and the hit test value are passed to the + // fake move/resize handler. + EXPECT_EQ(handler.pointer_location().ToString(), + pointer_location.ToString()); + EXPECT_EQ(handler.hittest(), hittest); + return; + } + + // Ensure the handler does not receive the hittest value or the pointer + // location. + EXPECT_TRUE(handler.pointer_location().IsOrigin()); + EXPECT_NE(handler.hittest(), hittest); +} + +// This is used to return a customized result to NonClientHitTest. +class HitTestNonClientFrameView : public NativeFrameView { + public: + explicit HitTestNonClientFrameView(Widget* widget) + : NativeFrameView(widget), hit_test_result_(HTNOWHERE) {} + ~HitTestNonClientFrameView() override {} + + void set_hit_test_result(int component) { hit_test_result_ = component; } + + // NonClientFrameView overrides: + int NonClientHitTest(const gfx::Point& point) override { + return hit_test_result_; + } + + private: + int hit_test_result_; + + DISALLOW_COPY_AND_ASSIGN(HitTestNonClientFrameView); +}; + +// This is used to return HitTestNonClientFrameView on create call. +class HitTestWidgetDelegate : public views::WidgetDelegate { + public: + HitTestWidgetDelegate(views::Widget* widget, + HitTestNonClientFrameView* frame_view) + : widget_(widget), frame_view_(frame_view) {} + ~HitTestWidgetDelegate() override {} + + void set_can_resize(bool can_resize) { + can_resize_ = can_resize; + widget_->OnSizeConstraintsChanged(); + } + + // views::WidgetDelegate: + bool CanResize() const override { return can_resize_; } + views::Widget* GetWidget() override { return widget_; } + views::Widget* GetWidget() const override { return widget_; } + views::NonClientFrameView* CreateNonClientFrameView(Widget* widget) override { + return frame_view_; + } + + private: + views::Widget* widget_; + HitTestNonClientFrameView* frame_view_; + bool can_resize_ = false; + + DISALLOW_COPY_AND_ASSIGN(HitTestWidgetDelegate); +}; + +} // namespace + +class DesktopWindowTreeHostPlatformTest : public ViewsInteractiveUITestBase { + public: + DesktopWindowTreeHostPlatformTest() = default; + ~DesktopWindowTreeHostPlatformTest() override = default; + + protected: + Widget* BuildTopLevelDesktopWidget(const gfx::Rect& bounds) { + Widget* toplevel = new Widget; + frame_view_ = new HitTestNonClientFrameView(toplevel); + delegate_ = new HitTestWidgetDelegate(toplevel, frame_view_); + Widget::InitParams toplevel_params = + CreateParams(Widget::InitParams::TYPE_WINDOW); + toplevel_params.native_widget = + new views::DesktopNativeWidgetAura(toplevel); + toplevel_params.delegate = delegate_; + toplevel_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + toplevel_params.bounds = bounds; + toplevel_params.remove_standard_frame = true; + toplevel->Init(toplevel_params); + return toplevel; + } + + std::unique_ptr<ui::MouseEvent> CreateMouseEvent( + const gfx::Point& pointer_location, + ui::EventType event_type, + int flags) { + std::unique_ptr<ui::MouseEvent> mouse_event = + std::make_unique<ui::MouseEvent>(event_type, pointer_location, + pointer_location, + base::TimeTicks::Now(), flags, flags); + return mouse_event; + } + + HitTestNonClientFrameView* frame_view_ = nullptr; + HitTestWidgetDelegate* delegate_ = nullptr; + + private: + DISALLOW_COPY_AND_ASSIGN(DesktopWindowTreeHostPlatformTest); +}; + +TEST_F(DesktopWindowTreeHostPlatformTest, HitTest) { + gfx::Rect bounds(0, 0, 100, 100); + std::unique_ptr<Widget> widget(BuildTopLevelDesktopWidget(bounds)); + widget->Show(); + + aura::Window* window = widget->GetNativeWindow(); + DesktopWindowTreeHostPlatform* host = + static_cast<DesktopWindowTreeHostPlatform*>(window->GetHost()); + + // Install a fake move/resize handler to intercept the move/resize call. + WindowEventFilter* non_client_filter = + host->non_client_window_event_filter_.get(); + std::unique_ptr<FakeWmMoveResizeHandler> handler = + std::make_unique<FakeWmMoveResizeHandler>(host->platform_window()); + non_client_filter->SetWmMoveResizeHandler(handler.get()); + + delegate_->set_can_resize(true); + + // It is not important to use pointer locations corresponding to the hittests + // values used in the browser itself, because we fake the hit test results, + // which non client frame view sends back. Thus, just make sure the content + // window is able to receive these events. + gfx::Point pointer_location(10, 10); + + constexpr int hittest_values[] = { + HTBOTTOM, HTBOTTOMLEFT, HTBOTTOMRIGHT, HTCAPTION, HTLEFT, + HTRIGHT, HTTOP, HTTOPLEFT, HTTOPRIGHT, HTNOWHERE, + HTBORDER, HTCLIENT, HTCLOSE, HTERROR, HTGROWBOX, + HTHELP, HTHSCROLL, HTMENU, HTMAXBUTTON, HTMINBUTTON, + HTREDUCE, HTSIZE, HTSYSMENU, HTTRANSPARENT, HTVSCROLL, + HTZOOM, + }; + + for (int hittest : hittest_values) { + handler->Reset(); + + // Set the desired hit test result value, which will be returned, when + // WindowEventFilter starts to perform hit testing. + frame_view_->set_hit_test_result(hittest); + + gfx::Rect bounds = window->GetBoundsInScreen(); + + // The wm move/resize handler receives pointer location in the global screen + // coordinate, whereas event dispatcher receives event locations on a local + // system coordinate. Thus, add an offset of a new possible origin value of + // a window to the expected pointer location. + gfx::Point expected_pointer_location(pointer_location); + expected_pointer_location.Offset(bounds.x(), bounds.y()); + + if (hittest == HTCAPTION) { + // Move the window on HTCAPTION hit test value. + bounds = + gfx::Rect(gfx::Point(bounds.x() + 2, bounds.y() + 4), bounds.size()); + handler->set_bounds(bounds); + } else if (IsNonClientComponent(hittest)) { + // Resize the window on other than HTCAPTION non client hit test values. + bounds = gfx::Rect( + gfx::Point(bounds.origin()), + gfx::Size(bounds.size().width() + 5, bounds.size().height() + 10)); + handler->set_bounds(bounds); + } + + // Send mouse down event and make sure the WindowEventFilter calls the + // move/resize handler to start interactive move/resize with the |hittest| + // value we specified. + auto mouse_down_event = CreateMouseEvent( + pointer_location, ui::ET_MOUSE_PRESSED, ui::EF_LEFT_MOUSE_BUTTON); + host->DispatchEvent(mouse_down_event.get()); + + // The test expectation is based on the hit test component. If it is a + // non-client component, which results in a call to move/resize, the handler + // must receive the hittest value and the pointer location in global screen + // coordinate system. In other cases, it must not. + SetExpectationBasedOnHittestValue(hittest, *handler.get(), + expected_pointer_location); + // Make sure the bounds of the content window are correct. + EXPECT_EQ(window->GetBoundsInScreen().ToString(), bounds.ToString()); + + // Dispatch mouse up event to release mouse pressed handler and be able to + // consume future events. + auto mouse_up_event = CreateMouseEvent( + pointer_location, ui::ET_MOUSE_RELEASED, ui::EF_LEFT_MOUSE_BUTTON); + host->DispatchEvent(mouse_up_event.get()); + } +} + +} // namespace views diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc index 4e04197810d..0ab1728d69b 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.cc @@ -476,8 +476,7 @@ void DesktopWindowTreeHostX11::CloseNow() { // If we have children, close them. Use a copy for iteration because they'll // remove themselves. std::set<DesktopWindowTreeHostX11*> window_children_copy = window_children_; - for (std::set<DesktopWindowTreeHostX11*>::iterator it = - window_children_copy.begin(); it != window_children_copy.end(); + for (auto it = window_children_copy.begin(); it != window_children_copy.end(); ++it) { (*it)->CloseNow(); } @@ -1006,7 +1005,6 @@ void DesktopWindowTreeHostX11::SetFullscreen(bool fullscreen) { if (is_fullscreen_ == fullscreen) return; is_fullscreen_ = fullscreen; - OnFullscreenStateChanged(); if (is_fullscreen_) delayed_resize_task_.Cancel(); @@ -1372,10 +1370,6 @@ void DesktopWindowTreeHostX11::OnDisplayMetricsChanged( } } -void DesktopWindowTreeHostX11::OnMaximizedStateChanged() {} - -void DesktopWindowTreeHostX11::OnFullscreenStateChanged() {} - //////////////////////////////////////////////////////////////////////////////// // DesktopWindowTreeHostX11, private: @@ -1677,12 +1671,10 @@ void DesktopWindowTreeHostX11::OnWMStateUpdated() { void DesktopWindowTreeHostX11::UpdateWindowProperties( const base::flat_set<XAtom>& new_window_properties) { bool was_minimized = IsMinimized(); - bool was_maximized = IsMaximized(); window_properties_ = new_window_properties; bool is_minimized = IsMinimized(); - bool is_maximized = IsMaximized(); // Propagate the window minimization information to the content window, so // the render side can update its visibility properly. OnWMStateUpdated() is @@ -1729,9 +1721,6 @@ void DesktopWindowTreeHostX11::UpdateWindowProperties( is_always_on_top_ = ui::HasWMSpecProperty( window_properties_, gfx::GetAtom("_NET_WM_STATE_ABOVE")); - if (was_maximized != is_maximized) - OnMaximizedStateChanged(); - // Now that we have different window properties, we may need to relayout the // window. (The windows code doesn't need this because their window change is // synchronous.) @@ -1935,7 +1924,7 @@ void DesktopWindowTreeHostX11::SerializeImageRepresentation( int height = rep.GetHeight(); data->push_back(height); - const SkBitmap& bitmap = rep.sk_bitmap(); + const SkBitmap& bitmap = rep.GetBitmap(); for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) @@ -2367,7 +2356,7 @@ gfx::Rect DesktopWindowTreeHostX11::ToPixelRect( return gfx::ToEnclosingRect(rect_in_pixels); } -std::unique_ptr<base::OnceClosure> +std::unique_ptr<base::Closure> DesktopWindowTreeHostX11::DisableEventListening() { // Allows to open multiple file-pickers. See https://crbug.com/678982 modal_dialog_counter_++; @@ -2378,9 +2367,9 @@ DesktopWindowTreeHostX11::DisableEventListening() { window(), std::make_unique<aura::NullWindowTargeter>()); } - return std::make_unique<base::OnceClosure>( - base::BindOnce(&DesktopWindowTreeHostX11::EnableEventListening, - weak_factory_.GetWeakPtr())); + return std::make_unique<base::Closure>( + base::Bind(&DesktopWindowTreeHostX11::EnableEventListening, + weak_factory_.GetWeakPtr())); } void DesktopWindowTreeHostX11::EnableEventListening() { diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h index cc9d5fb4823..2600a0621f3 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11.h @@ -86,7 +86,7 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 static void CleanUpWindowList(void (*func)(aura::Window* window)); // Disables event listening to make |dialog| modal. - std::unique_ptr<base::OnceClosure> DisableEventListening(); + std::unique_ptr<base::Closure> DisableEventListening(); // Returns a map of KeyboardEvent code to KeyboardEvent key values. base::flat_map<std::string, std::string> GetKeyboardLayoutMap() override; @@ -184,12 +184,6 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 void OnDisplayMetricsChanged(const display::Display& display, uint32_t changed_metrics) override; - // Called after the window is maximized or restored. - virtual void OnMaximizedStateChanged(); - - // Called after the window is fullscreened or unfullscreened. - virtual void OnFullscreenStateChanged(); - private: friend class DesktopWindowTreeHostX11HighDPITest; diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc index 73507805813..2f3f85b16b1 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_x11_unittest.cc @@ -9,6 +9,7 @@ #include "base/command_line.h" #include "base/macros.h" #include "base/run_loop.h" +#include "base/stl_util.h" #include "ui/aura/window.h" #include "ui/aura/window_tree_host.h" #include "ui/base/hit_test.h" @@ -50,11 +51,8 @@ class WMStateWaiter : public X11PropertyChangeWaiter { // X11PropertyChangeWaiter: bool ShouldKeepOnWaiting(const ui::PlatformEvent& event) override { std::vector<Atom> hints; - if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &hints)) { - auto it = std::find(hints.cbegin(), hints.cend(), gfx::GetAtom(hint_)); - bool hint_set = (it != hints.cend()); - return hint_set != wait_till_set_; - } + if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &hints)) + return base::ContainsValue(hints, gfx::GetAtom(hint_)) != wait_till_set_; return true; } diff --git a/chromium/ui/views/widget/desktop_aura/window_event_filter.cc b/chromium/ui/views/widget/desktop_aura/window_event_filter.cc index 530b079289b..3b01fb995b4 100644 --- a/chromium/ui/views/widget/desktop_aura/window_event_filter.cc +++ b/chromium/ui/views/widget/desktop_aura/window_event_filter.cc @@ -6,6 +6,7 @@ #include "services/ws/public/mojom/window_tree_constants.mojom.h" #include "ui/aura/client/aura_constants.h" +#include "ui/aura/env.h" #include "ui/aura/window.h" #include "ui/aura/window_delegate.h" #include "ui/aura/window_tree_host.h" @@ -14,6 +15,7 @@ #include "ui/display/screen.h" #include "ui/events/event.h" #include "ui/events/event_utils.h" +#include "ui/platform_window/platform_window_handler/wm_move_resize_handler.h" #include "ui/views/linux_ui/linux_ui.h" #include "ui/views/widget/desktop_aura/desktop_window_tree_host.h" #include "ui/views/widget/native_widget_aura.h" @@ -21,6 +23,28 @@ namespace views { +namespace { + +bool CanPerformDragOrResize(int hittest) { + switch (hittest) { + case HTBOTTOM: + case HTBOTTOMLEFT: + case HTBOTTOMRIGHT: + case HTCAPTION: + case HTLEFT: + case HTRIGHT: + case HTTOP: + case HTTOPLEFT: + case HTTOPRIGHT: + return true; + default: + return false; + } + return true; +} + +} // namespace + WindowEventFilter::WindowEventFilter(DesktopWindowTreeHost* window_tree_host) : window_tree_host_(window_tree_host), click_component_(HTNOWHERE) {} @@ -53,6 +77,12 @@ void WindowEventFilter::OnMouseEvent(ui::MouseEvent* event) { } } +void WindowEventFilter::SetWmMoveResizeHandler( + ui::WmMoveResizeHandler* handler) { + DCHECK(!handler_); + handler_ = handler; +} + void WindowEventFilter::OnClickedCaption(ui::MouseEvent* event, int previous_click_component) { aura::Window* target = static_cast<aura::Window*>(event->target()); @@ -149,6 +179,18 @@ void WindowEventFilter::LowerWindow() {} void WindowEventFilter::MaybeDispatchHostWindowDragMovement( int hittest, - ui::MouseEvent* event) {} + ui::MouseEvent* event) { + if (handler_ && event->IsLeftMouseButton() && + CanPerformDragOrResize(hittest)) { + // Some platforms (eg X11) may require last pointer location not in the + // local surface coordinates, but rather in the screen coordinates for + // interactive move/resize. + const gfx::Point last_pointer_location = + aura::Env::GetInstance()->last_mouse_location(); + handler_->DispatchHostWindowDragMovement(hittest, last_pointer_location); + event->StopPropagation(); + return; + } +} } // namespace views diff --git a/chromium/ui/views/widget/desktop_aura/window_event_filter.h b/chromium/ui/views/widget/desktop_aura/window_event_filter.h index a9bc0387c25..d3d7214f021 100644 --- a/chromium/ui/views/widget/desktop_aura/window_event_filter.h +++ b/chromium/ui/views/widget/desktop_aura/window_event_filter.h @@ -10,6 +10,10 @@ #include "ui/events/event_handler.h" #include "ui/views/views_export.h" +namespace ui { +class WmMoveResizeHandler; +} + namespace views { class DesktopWindowTreeHost; @@ -24,6 +28,11 @@ class VIEWS_EXPORT WindowEventFilter : public ui::EventHandler { // Overridden from ui::EventHandler: void OnMouseEvent(ui::MouseEvent* event) override; + // Sets a move resize handler. Currently initialized only by ozone platforms. + // See WaylandWindow::WaylandWindow in the wayland_window.cc file for an + // example. + void SetWmMoveResizeHandler(ui::WmMoveResizeHandler* handler); + private: // Called when the user clicked the caption area. void OnClickedCaption(ui::MouseEvent* event, int previous_click_component); @@ -52,6 +61,11 @@ class VIEWS_EXPORT WindowEventFilter : public ui::EventHandler { // components. int click_component_; + // A handler, which is used for interactive move/resize events if set and + // unless MaybeDispatchHostWindowDragMovement is overridden by a derived + // class. + ui::WmMoveResizeHandler* handler_ = nullptr; + DISALLOW_COPY_AND_ASSIGN(WindowEventFilter); }; diff --git a/chromium/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc b/chromium/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc index e0a11a00d46..a688da8c12b 100644 --- a/chromium/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc +++ b/chromium/ui/views/widget/desktop_aura/x11_topmost_window_finder_interactive_uitest.cc @@ -11,6 +11,7 @@ #include <vector> #include "base/macros.h" +#include "base/stl_util.h" #include "third_party/skia/include/core/SkRect.h" #include "ui/aura/window.h" #include "ui/aura/window_tree_host.h" @@ -42,9 +43,8 @@ class MinimizeWaiter : public X11PropertyChangeWaiter { bool ShouldKeepOnWaiting(const ui::PlatformEvent& event) override { std::vector<Atom> wm_states; if (ui::GetAtomArrayProperty(xwindow(), "_NET_WM_STATE", &wm_states)) { - auto it = std::find(wm_states.cbegin(), wm_states.cend(), - gfx::GetAtom("_NET_WM_STATE_HIDDEN")); - return it == wm_states.cend(); + return !base::ContainsValue(wm_states, + gfx::GetAtom("_NET_WM_STATE_HIDDEN")); } return true; } @@ -80,8 +80,7 @@ class StackingClientListWaiter : public X11PropertyChangeWaiter { std::vector<XID> stack; ui::GetXWindowStack(ui::GetX11RootWindow(), &stack); for (size_t i = 0; i < expected_windows_.size(); ++i) { - auto it = std::find(stack.cbegin(), stack.cend(), expected_windows_[i]); - if (it == stack.cend()) + if (!base::ContainsValue(stack, expected_windows_[i])) return true; } return false; diff --git a/chromium/ui/views/widget/native_widget_aura.cc b/chromium/ui/views/widget/native_widget_aura.cc index e6661017075..28c63b02b81 100644 --- a/chromium/ui/views/widget/native_widget_aura.cc +++ b/chromium/ui/views/widget/native_widget_aura.cc @@ -4,6 +4,9 @@ #include "ui/views/widget/native_widget_aura.h" +#include <memory> +#include <utility> + #include "base/bind.h" #include "base/location.h" #include "base/single_thread_task_runner.h" @@ -57,8 +60,7 @@ #if defined(OS_WIN) #include "base/win/scoped_gdi_object.h" -#include "base/win/win_client_metrics.h" -#include "ui/base/l10n/l10n_util_win.h" +#include "ui/gfx/platform_font_win.h" #include "ui/views/widget/desktop_aura/desktop_window_tree_host_win.h" #endif @@ -1155,8 +1157,7 @@ void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view, } const aura::Window::Windows& child_windows = native_view->children(); - for (aura::Window::Windows::const_iterator i = child_windows.begin(); - i != child_windows.end(); ++i) { + for (auto i = child_windows.begin(); i != child_windows.end(); ++i) { GetAllChildWidgets((*i), children); } } @@ -1192,8 +1193,7 @@ void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view, // First notify all the widgets that they are being disassociated // from their previous parent. - for (Widget::Widgets::iterator it = widgets.begin(); - it != widgets.end(); ++it) { + for (auto it = widgets.begin(); it != widgets.end(); ++it) { (*it)->NotifyNativeViewHierarchyWillChange(); } @@ -1217,8 +1217,7 @@ void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view, } // And now, notify them that they have a brand new parent. - for (Widget::Widgets::iterator it = widgets.begin(); - it != widgets.end(); ++it) { + for (auto it = widgets.begin(); it != widgets.end(); ++it) { (*it)->NotifyNativeViewHierarchyChanged(); } } @@ -1226,11 +1225,8 @@ void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view, // static gfx::FontList NativeWidgetPrivate::GetWindowTitleFontList() { #if defined(OS_WIN) - NONCLIENTMETRICS_XP ncm; - base::win::GetNonClientMetrics(&ncm); - l10n_util::AdjustUIFont(&(ncm.lfCaptionFont)); - base::win::ScopedHFONT caption_font(CreateFontIndirect(&(ncm.lfCaptionFont))); - return gfx::FontList(gfx::Font(caption_font.get())); + return gfx::FontList(gfx::PlatformFontWin::GetSystemFont( + gfx::PlatformFontWin::SystemFont::kCaption)); #else return gfx::FontList(); #endif diff --git a/chromium/ui/views/widget/native_widget_mac.h b/chromium/ui/views/widget/native_widget_mac.h index 5bb34bd75c3..e8e387ccd3c 100644 --- a/chromium/ui/views/widget/native_widget_mac.h +++ b/chromium/ui/views/widget/native_widget_mac.h @@ -15,6 +15,12 @@ class NativeWidgetMacNSWindow; #endif +namespace views_bridge_mac { +namespace mojom { +class BridgedNativeWidget; +} // namespace mojom +} // namespace views_bridge_mac + namespace views { namespace test { class HitTestNativeWidgetMac; @@ -22,7 +28,8 @@ class MockNativeWidgetMac; class WidgetTest; } -class BridgedNativeWidget; +class BridgeFactoryHost; +class BridgedNativeWidgetImpl; class BridgedNativeWidgetHostImpl; class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { @@ -30,15 +37,8 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { explicit NativeWidgetMac(internal::NativeWidgetDelegate* delegate); ~NativeWidgetMac() override; - // Retrieves the bridge associated with the given NSWindow. Returns null if - // the supplied handle has no associated Widget. - static BridgedNativeWidgetHostImpl* GetBridgeHostImplForNativeWindow( - gfx::NativeWindow window); - static BridgedNativeWidget* GetBridgeForNativeWindow( - gfx::NativeWindow window); - // Informs |delegate_| that the native widget is about to be destroyed. - // BridgedNativeWidget::OnWindowWillClose() invokes this early when the + // BridgedNativeWidgetImpl::OnWindowWillClose() invokes this early when the // NSWindowDelegate informs the bridge that the window is being closed (later, // invoking OnWindowDestroyed()). void WindowDestroying(); @@ -144,7 +144,7 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { std::string GetName() const override; protected: - // Creates the NSWindow that will be passed to the BridgedNativeWidget. + // Creates the NSWindow that will be passed to the BridgedNativeWidgetImpl. // Called by InitNativeWidget. The return value will be autoreleased. // Note that some tests (in particular, views_unittests that interact // with ScopedFakeNSWindowFullscreen, on 10.10) assume that these windows @@ -153,11 +153,17 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { virtual NativeWidgetMacNSWindow* CreateNSWindow( const Widget::InitParams& params); + // Return the BridgeFactoryHost that is to be used for creating this window + // and all of its child windows. This will return nullptr if the native + // windows are to be created in the current process. + virtual BridgeFactoryHost* GetBridgeFactoryHost(); + // Optional hook for subclasses invoked by WindowDestroying(). virtual void OnWindowDestroying(NSWindow* window) {} internal::NativeWidgetDelegate* delegate() { return delegate_; } - BridgedNativeWidget* bridge() const; + views_bridge_mac::mojom::BridgedNativeWidget* bridge() const; + BridgedNativeWidgetImpl* bridge_impl() const; BridgedNativeWidgetHostImpl* bridge_host_for_testing() const { return bridge_host_.get(); } diff --git a/chromium/ui/views/widget/native_widget_mac.mm b/chromium/ui/views/widget/native_widget_mac.mm index decbcdf69ac..4f75293c284 100644 --- a/chromium/ui/views/widget/native_widget_mac.mm +++ b/chromium/ui/views/widget/native_widget_mac.mm @@ -10,7 +10,6 @@ #include "base/bind.h" #include "base/lazy_instance.h" -#include "base/mac/foundation_util.h" #include "base/mac/scoped_nsobject.h" #include "base/strings/sys_string_conversions.h" #include "base/threading/thread_task_runner_handle.h" @@ -25,16 +24,15 @@ #import "ui/gfx/mac/nswindow_frame_controls.h" #include "ui/native_theme/native_theme.h" #include "ui/native_theme/native_theme_mac.h" -#import "ui/views/cocoa/bridged_content_view.h" -#import "ui/views/cocoa/bridged_native_widget.h" #import "ui/views/cocoa/bridged_native_widget_host_impl.h" -#include "ui/views/cocoa/cocoa_mouse_capture.h" #import "ui/views/cocoa/drag_drop_client_mac.h" -#import "ui/views/cocoa/native_widget_mac_nswindow.h" -#import "ui/views/cocoa/views_nswindow_delegate.h" #include "ui/views/widget/drop_helper.h" #include "ui/views/widget/widget_delegate.h" #include "ui/views/window/native_frame_view.h" +#import "ui/views_bridge_mac/bridged_content_view.h" +#import "ui/views_bridge_mac/bridged_native_widget_impl.h" +#import "ui/views_bridge_mac/native_widget_mac_nswindow.h" +#import "ui/views_bridge_mac/views_nswindow_delegate.h" using views_bridge_mac::mojom::WindowVisibilityState; @@ -44,17 +42,6 @@ namespace { base::LazyInstance<ui::GestureRecognizerImplMac>::Leaky g_gesture_recognizer_instance = LAZY_INSTANCE_INITIALIZER; -NativeWidgetMac* GetNativeWidgetMacForNativeWindow( - gfx::NativeWindow native_window) { - id<NSWindowDelegate> window_delegate = [native_window delegate]; - if ([window_delegate respondsToSelector:@selector(nativeWidgetMac)]) { - ViewsNSWindowDelegate* delegate = - base::mac::ObjCCastStrict<ViewsNSWindowDelegate>(window_delegate); - return [delegate nativeWidgetMac]; - } - return nullptr; // Not created by NativeWidgetMac. -} - NSInteger StyleMaskForParams(const Widget::InitParams& params) { // If the Widget is modal, it will be displayed as a sheet. This works best if // it has NSTitledWindowMask. For example, with NSBorderlessWindowMask, the @@ -92,22 +79,6 @@ NativeWidgetMac::~NativeWidgetMac() { CloseNow(); } -// static -BridgedNativeWidget* NativeWidgetMac::GetBridgeForNativeWindow( - gfx::NativeWindow window) { - if (NativeWidgetMac* widget = GetNativeWidgetMacForNativeWindow(window)) - return widget->bridge(); - return nullptr; // Not created by NativeWidgetMac. -} - -// static -BridgedNativeWidgetHostImpl* NativeWidgetMac::GetBridgeHostImplForNativeWindow( - gfx::NativeWindow window) { - if (NativeWidgetMac* widget = GetNativeWidgetMacForNativeWindow(window)) - return widget->bridge_host_.get(); - return nullptr; // Not created by NativeWidgetMac. -} - void NativeWidgetMac::WindowDestroying() { OnWindowDestroying(GetNativeWindow()); delegate_->OnNativeWidgetDestroying(); @@ -137,9 +108,29 @@ int NativeWidgetMac::SheetPositionY() { void NativeWidgetMac::InitNativeWidget(const Widget::InitParams& params) { ownership_ = params.ownership; name_ = params.name; - base::scoped_nsobject<NativeWidgetMacNSWindow> window( - [CreateNSWindow(params) retain]); - bridge()->SetWindow(window); + BridgedNativeWidgetHostImpl* parent_host = + BridgedNativeWidgetHostImpl::GetFromNativeWindow([params.parent window]); + + // Determine the factory through which to create the bridge + BridgeFactoryHost* bridge_factory_host = + parent_host ? parent_host->bridge_factory_host() : GetBridgeFactoryHost(); + if (bridge_factory_host) { + // Compute the parameters to describe the NSWindow. + // TODO(ccameron): This is not yet adequate to capture all NSWindow + // sub-classes that may be used. Make the parameter structure more + // expressive. + auto create_window_params = + views_bridge_mac::mojom::CreateWindowParams::New(); + create_window_params->style_mask = StyleMaskForParams(params); + + bridge_host_->CreateRemoteBridge(bridge_factory_host, + std::move(create_window_params)); + } else { + base::scoped_nsobject<NativeWidgetMacNSWindow> window( + [CreateNSWindow(params) retain]); + bridge_host_->CreateLocalBridge(std::move(window)); + } + bridge_host_->SetParent(parent_host); bridge_host_->InitWindow(params); // Only set always-on-top here if it is true since setting it may affect how @@ -151,8 +142,8 @@ void NativeWidgetMac::InitNativeWidget(const Widget::InitParams& params) { DCHECK(GetWidget()->GetRootView()); bridge_host_->SetRootView(GetWidget()->GetRootView()); - bridge()->CreateContentView(GetWidget()->GetRootView()->bounds()); - bridge()->CreateDragDropClient(GetWidget()->GetRootView()); + bridge()->CreateContentView(bridge_host_->GetRootViewNSViewId(), + GetWidget()->GetRootView()->bounds()); if (auto* focus_manager = GetWidget()->GetFocusManager()) { bridge()->MakeFirstResponder(); bridge_host_->SetFocusManager(focus_manager); @@ -201,7 +192,7 @@ gfx::NativeView NativeWidgetMac::GetNativeView() const { } gfx::NativeWindow NativeWidgetMac::GetNativeWindow() const { - return bridge() ? bridge()->ns_window() : nil; + return bridge_host_ ? bridge_host_->GetLocalNSWindow() : nil; } Widget* NativeWidgetMac::GetTopLevelWidget() { @@ -220,31 +211,32 @@ const ui::Layer* NativeWidgetMac::GetLayer() const { } void NativeWidgetMac::ReorderNativeViews() { - if (bridge()) - bridge()->ReorderChildViews(); + if (bridge_host_) + bridge_host_->ReorderChildViews(); } void NativeWidgetMac::ViewRemoved(View* view) { - DragDropClientMac* client = bridge() ? bridge()->drag_drop_client() : nullptr; + DragDropClientMac* client = + bridge_host_ ? bridge_host_->drag_drop_client() : nullptr; if (client) client->drop_helper()->ResetTargetViewIfEquals(view); } void NativeWidgetMac::SetNativeWindowProperty(const char* name, void* value) { - if (bridge()) - bridge()->SetNativeWindowProperty(name, value); + if (bridge_host_) + bridge_host_->SetNativeWindowProperty(name, value); } void* NativeWidgetMac::GetNativeWindowProperty(const char* name) const { - if (bridge()) - return bridge()->GetNativeWindowProperty(name); + if (bridge_host_) + return bridge_host_->GetNativeWindowProperty(name); return nullptr; } TooltipManager* NativeWidgetMac::GetTooltipManager() const { - if (bridge()) - return bridge()->tooltip_manager(); + if (bridge_host_) + return bridge_host_->tooltip_manager(); return nullptr; } @@ -268,12 +260,7 @@ ui::InputMethod* NativeWidgetMac::GetInputMethod() { } void NativeWidgetMac::CenterWindow(const gfx::Size& size) { - SetSize( - BridgedNativeWidget::GetWindowSizeForClientSize(GetNativeWindow(), size)); - // Note that this is not the precise center of screen, but it is the standard - // location for windows like dialogs to appear on screen for Mac. - // TODO(tapted): If there is a parent window, center in that instead. - [GetNativeWindow() center]; + bridge()->SetSizeAndCenter(size, GetWidget()->GetMinimumSize()); } void NativeWidgetMac::GetWindowPlacement( @@ -311,7 +298,7 @@ void NativeWidgetMac::InitModalType(ui::ModalType modal_type) { // A peculiarity of the constrained window framework is that it permits a // dialog of MODAL_TYPE_WINDOW to have a null parent window; falling back to // a non-modal window in this case. - DCHECK(bridge()->parent() || modal_type == ui::MODAL_TYPE_WINDOW); + DCHECK(bridge_host_->parent() || modal_type == ui::MODAL_TYPE_WINDOW); // Everything happens upon show. } @@ -338,19 +325,14 @@ void NativeWidgetMac::SetBounds(const gfx::Rect& bounds) { } void NativeWidgetMac::SetBoundsConstrained(const gfx::Rect& bounds) { - if (!bridge()) + if (!bridge_host_) return; - gfx::Rect new_bounds(bounds); - NativeWidgetPrivate* ancestor = - bridge() && bridge()->parent() - ? GetNativeWidgetForNativeWindow(bridge()->parent()->GetNSWindow()) - : nullptr; - if (!ancestor) { - new_bounds = ConstrainBoundsToDisplayWorkArea(new_bounds); - } else { + if (bridge_host_->parent()) { new_bounds.AdjustToFit( - gfx::Rect(ancestor->GetWindowBoundsInScreen().size())); + gfx::Rect(bridge_host_->parent()->GetWindowBoundsInScreen().size())); + } else { + new_bounds = ConstrainBoundsToDisplayWorkArea(new_bounds); } SetBounds(new_bounds); } @@ -395,8 +377,8 @@ void NativeWidgetMac::Show(ui::WindowShowState show_state, case ui::SHOW_STATE_DEFAULT: case ui::SHOW_STATE_NORMAL: case ui::SHOW_STATE_INACTIVE: - break; case ui::SHOW_STATE_MINIMIZED: + break; case ui::SHOW_STATE_MAXIMIZED: case ui::SHOW_STATE_FULLSCREEN: NOTIMPLEMENTED(); @@ -405,10 +387,12 @@ void NativeWidgetMac::Show(ui::WindowShowState show_state, NOTREACHED(); break; } - bridge()->SetVisibilityState( - show_state == ui::SHOW_STATE_INACTIVE - ? WindowVisibilityState::kShowInactive - : WindowVisibilityState::kShowAndActivateWindow); + auto window_state = WindowVisibilityState::kShowAndActivateWindow; + if (show_state == ui::SHOW_STATE_INACTIVE) + window_state = WindowVisibilityState::kShowInactive; + else if (show_state == ui::SHOW_STATE_MINIMIZED) + window_state = WindowVisibilityState::kHideWindow; + bridge()->SetVisibilityState(window_state); // Ignore the SetInitialFocus() result. BridgedContentView should get // firstResponder status regardless. @@ -517,7 +501,8 @@ void NativeWidgetMac::RunShellDrag(View* view, const gfx::Point& location, int operation, ui::DragDropTypes::DragEventSource source) { - bridge()->drag_drop_client()->StartDragAndDrop(view, data, operation, source); + bridge_host_->drag_drop_client()->StartDragAndDrop(view, data, operation, + source); } void NativeWidgetMac::SchedulePaintInRect(const gfx::Rect& rect) { @@ -535,8 +520,8 @@ void NativeWidgetMac::SchedulePaintInRect(const gfx::Rect& rect) { } void NativeWidgetMac::SetCursor(gfx::NativeCursor cursor) { - if (bridge()) - bridge()->SetCursor(cursor); + if (bridge_impl()) + bridge_impl()->SetCursor(cursor); } bool NativeWidgetMac::IsMouseEventsEnabled() const { @@ -568,20 +553,21 @@ Widget::MoveLoopResult NativeWidgetMac::RunMoveLoop( const gfx::Vector2d& drag_offset, Widget::MoveLoopSource source, Widget::MoveLoopEscapeBehavior escape_behavior) { - if (!bridge()) + if (!bridge_impl()) return Widget::MOVE_LOOP_CANCELED; - return bridge()->RunMoveLoop(drag_offset); + return bridge_impl()->RunMoveLoop(drag_offset) ? Widget::MOVE_LOOP_SUCCESSFUL + : Widget::MOVE_LOOP_CANCELED; } void NativeWidgetMac::EndMoveLoop() { - if (bridge()) - bridge()->EndMoveLoop(); + if (bridge_impl()) + bridge_impl()->EndMoveLoop(); } void NativeWidgetMac::SetVisibilityChangedAnimationsEnabled(bool value) { - if (bridge()) - bridge()->SetAnimationEnabled(value); + if (bridge_impl()) + bridge_impl()->SetAnimationEnabled(value); } void NativeWidgetMac::SetVisibilityAnimationDuration( @@ -590,9 +576,25 @@ void NativeWidgetMac::SetVisibilityAnimationDuration( } void NativeWidgetMac::SetVisibilityAnimationTransition( - Widget::VisibilityTransition transition) { + Widget::VisibilityTransition widget_transitions) { + views_bridge_mac::mojom::VisibilityTransition transitions = + views_bridge_mac::mojom::VisibilityTransition::kNone; + switch (widget_transitions) { + case Widget::ANIMATE_NONE: + transitions = views_bridge_mac::mojom::VisibilityTransition::kNone; + break; + case Widget::ANIMATE_SHOW: + transitions = views_bridge_mac::mojom::VisibilityTransition::kShow; + break; + case Widget::ANIMATE_HIDE: + transitions = views_bridge_mac::mojom::VisibilityTransition::kHide; + break; + case Widget::ANIMATE_BOTH: + transitions = views_bridge_mac::mojom::VisibilityTransition::kBoth; + break; + } if (bridge()) - bridge()->set_transitions_to_animate(transition); + bridge()->SetTransitionsToAnimate(transitions); } bool NativeWidgetMac::IsTranslucentWindowOpacitySupported() const { @@ -631,7 +633,15 @@ NativeWidgetMacNSWindow* NativeWidgetMac::CreateNSWindow( defer:NO] autorelease]; } -BridgedNativeWidget* NativeWidgetMac::bridge() const { +BridgeFactoryHost* NativeWidgetMac::GetBridgeFactoryHost() { + return nullptr; +} + +views_bridge_mac::mojom::BridgedNativeWidget* NativeWidgetMac::bridge() const { + return bridge_host_ ? bridge_host_->bridge() : nullptr; +} + +BridgedNativeWidgetImpl* NativeWidgetMac::bridge_impl() const { return bridge_host_ ? bridge_host_->bridge_impl() : nullptr; } @@ -690,32 +700,31 @@ NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeView( // static NativeWidgetPrivate* NativeWidgetPrivate::GetNativeWidgetForNativeWindow( gfx::NativeWindow window) { - if (NativeWidgetMac* widget = GetNativeWidgetMacForNativeWindow(window)) - return widget; + if (BridgedNativeWidgetHostImpl* bridge_host_impl = + BridgedNativeWidgetHostImpl::GetFromNativeWindow(window)) { + return bridge_host_impl->native_widget_mac(); + } return nullptr; // Not created by NativeWidgetMac. } // static NativeWidgetPrivate* NativeWidgetPrivate::GetTopLevelNativeWidget( gfx::NativeView native_view) { - BridgedNativeWidget* bridge = - NativeWidgetMac::GetBridgeForNativeWindow([native_view window]); - if (!bridge) + BridgedNativeWidgetHostImpl* bridge_host = + BridgedNativeWidgetHostImpl::GetFromNativeWindow([native_view window]); + if (!bridge_host) return nullptr; - - NativeWidgetPrivate* ancestor = - bridge->parent() ? GetTopLevelNativeWidget( - [bridge->parent()->GetNSWindow() contentView]) - : nullptr; - return ancestor ? ancestor : bridge->native_widget_mac(); + while (bridge_host->parent()) + bridge_host = bridge_host->parent(); + return bridge_host->native_widget_mac(); } // static void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view, Widget::Widgets* children) { - BridgedNativeWidget* bridge = - NativeWidgetMac::GetBridgeForNativeWindow([native_view window]); - if (!bridge) { + BridgedNativeWidgetHostImpl* bridge_host = + BridgedNativeWidgetHostImpl::GetFromNativeWindow([native_view window]); + if (!bridge_host) { // The NSWindow is not itself a views::Widget, but it may have children that // are. Support returning Widgets that are parented to the NSWindow, except: // - Ignore requests for children of an NSView that is not a contentView. @@ -738,67 +747,101 @@ void NativeWidgetPrivate::GetAllChildWidgets(gfx::NativeView native_view, // If |native_view| is a subview of the contentView, it will share an // NSWindow, but will itself be a native child of the Widget. That is, adding - // bridge->..->GetWidget() to |children| would be adding the _parent_ of + // bridge_host->..->GetWidget() to |children| would be adding the _parent_ of // |native_view|, not the Widget for |native_view|. |native_view| doesn't have // a corresponding Widget of its own in this case (and so can't have Widget // children of its own on Mac). - if (bridge->ns_view() != native_view) + if (bridge_host->native_widget_mac()->GetNativeView() != native_view) return; // Code expects widget for |native_view| to be added to |children|. - if (bridge->native_widget_mac()->GetWidget()) - children->insert(bridge->native_widget_mac()->GetWidget()); + if (bridge_host->native_widget_mac()->GetWidget()) + children->insert(bridge_host->native_widget_mac()->GetWidget()); - // When the NSWindow *is* a Widget, only consider child_windows(). I.e. do not - // look through -[NSWindow childWindows] as done for the (!bridge) case above. - // -childWindows does not support hidden windows, and anything in there which - // is not in child_windows() would have been added by AppKit. - for (BridgedNativeWidget* child : bridge->child_windows()) - GetAllChildWidgets(child->ns_view(), children); + // When the NSWindow *is* a Widget, only consider children(). I.e. do not + // look through -[NSWindow childWindows] as done for the (!bridge_host) case + // above. -childWindows does not support hidden windows, and anything in there + // which is not in children() would have been added by AppKit. + for (BridgedNativeWidgetHostImpl* child : bridge_host->children()) + GetAllChildWidgets(child->native_widget_mac()->GetNativeView(), children); } // static void NativeWidgetPrivate::GetAllOwnedWidgets(gfx::NativeView native_view, Widget::Widgets* owned) { - BridgedNativeWidget* bridge = - NativeWidgetMac::GetBridgeForNativeWindow([native_view window]); - if (!bridge) { + BridgedNativeWidgetHostImpl* bridge_host = + BridgedNativeWidgetHostImpl::GetFromNativeWindow([native_view window]); + if (!bridge_host) { GetAllChildWidgets(native_view, owned); return; } - if (bridge->ns_view() != native_view) + if (bridge_host->native_widget_mac()->GetNativeView() != native_view) return; - for (BridgedNativeWidget* child : bridge->child_windows()) - GetAllChildWidgets(child->ns_view(), owned); + for (BridgedNativeWidgetHostImpl* child : bridge_host->children()) + GetAllChildWidgets(child->native_widget_mac()->GetNativeView(), owned); } // static void NativeWidgetPrivate::ReparentNativeView(gfx::NativeView native_view, gfx::NativeView new_parent) { DCHECK_NE(native_view, new_parent); + DCHECK([new_parent window]); if (!new_parent || [native_view superview] == new_parent) { NOTREACHED(); return; } - BridgedNativeWidget* bridge = - NativeWidgetMac::GetBridgeForNativeWindow([native_view window]); - BridgedNativeWidget* parent_bridge = - NativeWidgetMac::GetBridgeForNativeWindow([new_parent window]); - DCHECK(bridge); - if (Widget::GetWidgetForNativeView(native_view)->is_top_level() && - bridge->parent() == parent_bridge) + BridgedNativeWidgetHostImpl* bridge_host = + BridgedNativeWidgetHostImpl::GetFromNativeWindow([native_view window]); + DCHECK(bridge_host); + NSView* bridge_view = bridge_host->native_widget_mac()->GetNativeView(); + NSWindow* bridge_window = bridge_host->native_widget_mac()->GetNativeWindow(); + bool bridge_is_top_level = + bridge_host->native_widget_mac()->GetWidget()->is_top_level(); + DCHECK([native_view isDescendantOf:bridge_view]); + DCHECK(bridge_window && ![bridge_window isSheet]); + + BridgedNativeWidgetHostImpl* parent_bridge_host = + BridgedNativeWidgetHostImpl::GetFromNativeWindow([new_parent window]); + + // Early out for no-op changes. + if (native_view == bridge_view && bridge_is_top_level && + bridge_host->parent() == parent_bridge_host) { return; + } + // First notify all the widgets that they are being disassociated from their + // previous parent. Widget::Widgets widgets; GetAllChildWidgets(native_view, &widgets); - - // First notify all the widgets that they are being disassociated - // from their previous parent. for (auto* child : widgets) child->NotifyNativeViewHierarchyWillChange(); - bridge->ReparentNativeView(native_view, new_parent); + // Update |brige_host|'s parent only if + // BridgedNativeWidgetImpl::ReparentNativeView will. + if (native_view == bridge_view) { + bridge_host->SetParent(parent_bridge_host); + if (!bridge_is_top_level) { + // Make |bridge_host|'s NSView be a child of |new_parent| by adding it as + // a subview. Note that this will have the effect of removing + // |bridge_host|'s NSView from its NSWindow. The |NSWindow| must remain + // visible because it controls the bounds and visibility of the ui::Layer, + // so just hide it by setting alpha value to zero. + // TODO(ccameron): This path likely violates assumptions. Verify that this + // path is unused and remove it. + LOG(ERROR) << "Reparenting a non-top-level BridgedNativeWidget. This is " + "likely unsupported."; + [new_parent addSubview:native_view]; + [bridge_window setAlphaValue:0]; + [bridge_window setIgnoresMouseEvents:YES]; + } + } else { + // TODO(ccameron): This path likely violates assumptions. Verify that this + // path is unused and remove it. + LOG(ERROR) << "Reparenting with a non-root BridgedNativeWidget NSView. " + "This is likely unsupported."; + [new_parent addSubview:native_view]; + } // And now, notify them that they have a brand new parent. for (auto* child : widgets) @@ -814,7 +857,7 @@ gfx::FontList NativeWidgetPrivate::GetWindowTitleFontList() { // static gfx::NativeView NativeWidgetPrivate::GetGlobalCapture( gfx::NativeView native_view) { - return [CocoaMouseCapture::GetGlobalCaptureWindow() contentView]; + return BridgedNativeWidgetHostImpl::GetGlobalCaptureView(); } } // namespace internal diff --git a/chromium/ui/views/widget/native_widget_mac_unittest.mm b/chromium/ui/views/widget/native_widget_mac_unittest.mm index ebb9ea6cd6d..4efaef021c4 100644 --- a/chromium/ui/views/widget/native_widget_mac_unittest.mm +++ b/chromium/ui/views/widget/native_widget_mac_unittest.mm @@ -28,9 +28,6 @@ #include "ui/events/test/event_generator.h" #import "ui/gfx/mac/coordinate_conversion.h" #include "ui/views/bubble/bubble_dialog_delegate_view.h" -#import "ui/views/cocoa/bridged_content_view.h" -#import "ui/views/cocoa/bridged_native_widget.h" -#import "ui/views/cocoa/native_widget_mac_nswindow.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/controls/label.h" #include "ui/views/controls/native/native_view_host.h" @@ -42,6 +39,9 @@ #include "ui/views/widget/native_widget_private.h" #include "ui/views/window/dialog_client_view.h" #include "ui/views/window/dialog_delegate.h" +#import "ui/views_bridge_mac/bridged_content_view.h" +#import "ui/views_bridge_mac/bridged_native_widget_impl.h" +#import "ui/views_bridge_mac/native_widget_mac_nswindow.h" // Donates an implementation of -[NSAnimation stopAnimation] which calls the // original implementation, then quits a nested run loop. @@ -86,18 +86,14 @@ @interface FocusableTestNSView : NSView @end -@interface TestNativeParentWindow : NSWindow -@property(assign, nonatomic) bool* deallocFlag; -@end - namespace views { namespace test { -// BridgedNativeWidget friend to access private members. +// BridgedNativeWidgetImpl friend to access private members. class BridgedNativeWidgetTestApi { public: explicit BridgedNativeWidgetTestApi(NSWindow* window) { - bridge_ = NativeWidgetMac::GetBridgeForNativeWindow(window); + bridge_ = BridgedNativeWidgetImpl::GetFromNativeWindow(window); } // Simulate a frame swap from the compositor. @@ -116,7 +112,7 @@ class BridgedNativeWidgetTestApi { } private: - BridgedNativeWidget* bridge_; + BridgedNativeWidgetImpl* bridge_; DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetTestApi); }; @@ -148,30 +144,33 @@ class TestWindowNativeWidgetMac : public NativeWidgetMac { DISALLOW_COPY_AND_ASSIGN(TestWindowNativeWidgetMac); }; -// Tests for parts of NativeWidgetMac not covered by BridgedNativeWidget, which -// need access to Cocoa APIs. +// Tests for parts of NativeWidgetMac not covered by BridgedNativeWidgetImpl, +// which need access to Cocoa APIs. class NativeWidgetMacTest : public WidgetTest { public: NativeWidgetMacTest() {} - // The content size of NSWindows made by MakeNativeParent(). - NSRect ParentRect() const { return NSMakeRect(100, 100, 300, 200); } - - // Make a native NSWindow with the given |style_mask| to use as a parent. - TestNativeParentWindow* MakeNativeParentWithStyle(int style_mask) { - native_parent_.reset([[TestNativeParentWindow alloc] - initWithContentRect:ParentRect() - styleMask:style_mask - backing:NSBackingStoreBuffered - defer:NO]); - [native_parent_ setReleasedWhenClosed:NO]; // Owned by scoped_nsobject. - [native_parent_ makeKeyAndOrderFront:nil]; - return native_parent_; + // Make an NSWindow with a close button and a title bar to use as a parent. + // This NSWindow is backed by a widget that is not exposed to the caller. + // To destroy the Widget, the native NSWindow must be closed. + NativeWidgetMacTestWindow* MakeClosableTitledNativeParent() { + NativeWidgetMacTestWindow* native_parent = nil; + Widget::InitParams parent_init_params = + CreateParams(Widget::InitParams::TYPE_WINDOW); + parent_init_params.bounds = gfx::Rect(100, 100, 200, 200); + CreateWidgetWithTestWindow(parent_init_params, &native_parent); + return native_parent; } - // Make a borderless, native NSWindow to use as a parent. - TestNativeParentWindow* MakeNativeParent() { - return MakeNativeParentWithStyle(NSBorderlessWindowMask); + // Same as the above, but creates a borderless NSWindow. + NativeWidgetMacTestWindow* MakeBorderlessNativeParent() { + NativeWidgetMacTestWindow* native_parent = nil; + Widget::InitParams parent_init_params = + CreateParams(Widget::InitParams::TYPE_WINDOW); + parent_init_params.remove_standard_frame = true; + parent_init_params.bounds = gfx::Rect(100, 100, 200, 200); + CreateWidgetWithTestWindow(parent_init_params, &native_parent); + return native_parent; } // Create a Widget backed by the NativeWidgetMacTestWindow NSWindow subclass. @@ -187,9 +186,6 @@ class NativeWidgetMacTest : public WidgetTest { return widget; } - protected: - base::scoped_nsobject<TestNativeParentWindow> native_parent_; - private: DISALLOW_COPY_AND_ASSIGN(NativeWidgetMacTest); }; @@ -740,26 +736,27 @@ Widget* AttachPopupToNativeParent(NSWindow* native_parent) { // Tests creating a views::Widget parented off a native NSWindow. TEST_F(NativeWidgetMacTest, NonWidgetParent) { - NSWindow* native_parent = MakeNativeParent(); + NSWindow* native_parent = MakeBorderlessNativeParent(); Widget::Widgets children; Widget::GetAllChildWidgets([native_parent contentView], &children); - EXPECT_TRUE(children.empty()); + EXPECT_EQ(1u, children.size()); Widget* child = AttachPopupToNativeParent(native_parent); TestWidgetObserver child_observer(child); - // GetTopLevelNativeWidget() only goes as far as there exists a Widget (i.e. - // must stop at |child|. + // GetTopLevelNativeWidget() will go up through |native_parent|'s Widget. internal::NativeWidgetPrivate* top_level_widget = internal::NativeWidgetPrivate::GetTopLevelNativeWidget( child->GetNativeView()); - EXPECT_EQ(child, top_level_widget->GetWidget()); + EXPECT_EQ(Widget::GetWidgetForNativeWindow(native_parent), + top_level_widget->GetWidget()); + EXPECT_NE(child, top_level_widget->GetWidget()); // To verify the parent, we need to use NativeWidgetMac APIs. - BridgedNativeWidget* bridged_native_widget = - NativeWidgetMac::GetBridgeForNativeWindow(child->GetNativeWindow()); - EXPECT_EQ(native_parent, bridged_native_widget->parent()->GetNSWindow()); + BridgedNativeWidgetImpl* bridged_native_widget = + BridgedNativeWidgetImpl::GetFromNativeWindow(child->GetNativeWindow()); + EXPECT_EQ(native_parent, bridged_native_widget->parent()->ns_window()); const gfx::Rect child_bounds(50, 50, 200, 100); child->SetBounds(child_bounds); @@ -774,8 +771,8 @@ TEST_F(NativeWidgetMacTest, NonWidgetParent) { EXPECT_EQ(native_parent, [child->GetNativeWindow() parentWindow]); Widget::GetAllChildWidgets([native_parent contentView], &children); - ASSERT_EQ(1u, children.size()); - EXPECT_EQ(child, *children.begin()); + ASSERT_EQ(2u, children.size()); + EXPECT_EQ(1u, children.count(child)); // Only non-toplevel Widgets are positioned relative to the parent, so the // bounds set above should be in screen coordinates. @@ -786,7 +783,7 @@ TEST_F(NativeWidgetMacTest, NonWidgetParent) { NSView* anchor_view = [[native_parent contentView] subviews][0]; EXPECT_TRUE(anchor_view); [anchor_view removeFromSuperview]; - EXPECT_EQ(native_parent, bridged_native_widget->parent()->GetNSWindow()); + EXPECT_EQ(native_parent, bridged_native_widget->parent()->ns_window()); // Closing the parent should close and destroy the child. EXPECT_FALSE(child_observer.widget_closed()); @@ -794,6 +791,7 @@ TEST_F(NativeWidgetMacTest, NonWidgetParent) { EXPECT_TRUE(child_observer.widget_closed()); EXPECT_EQ(0u, [[native_parent childWindows] count]); + [native_parent close]; } // Tests that CloseAllSecondaryWidgets behaves in various configurations. @@ -871,15 +869,16 @@ TEST_F(NativeWidgetMacTest, CloseAllSecondaryWidgetsValidState) { TEST_F(NativeWidgetMacTest, NonWidgetParentLastReference) { bool child_dealloced = false; bool native_parent_dealloced = false; + NativeWidgetMacTestWindow* native_parent = nil; { base::mac::ScopedNSAutoreleasePool pool; - TestNativeParentWindow* native_parent = MakeNativeParent(); + native_parent = MakeBorderlessNativeParent(); [native_parent setDeallocFlag:&native_parent_dealloced]; NativeWidgetMacTestWindow* window; Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP); - init_params.parent = [native_parent_ contentView]; + init_params.parent = [native_parent contentView]; init_params.bounds = gfx::Rect(0, 0, 100, 200); CreateWidgetWithTestWindow(init_params, &window); [window setDeallocFlag:&child_dealloced]; @@ -891,7 +890,7 @@ TEST_F(NativeWidgetMacTest, NonWidgetParentLastReference) { // to the child window is released inside WidgetOwnerNSWindowAdapter:: // OnWindowWillClose(). base::mac::ScopedNSAutoreleasePool pool; - [native_parent_.autorelease() close]; + [native_parent close]; EXPECT_TRUE(child_dealloced); } EXPECT_TRUE(native_parent_dealloced); @@ -900,7 +899,7 @@ TEST_F(NativeWidgetMacTest, NonWidgetParentLastReference) { // Tests visibility for child of native NSWindow, reshowing after -[NSApp hide]. // Occasionally flaky (maybe due to [NSApp hide]). See https://crbug.com/777247. TEST_F(NativeWidgetMacTest, DISABLED_VisibleAfterNativeParentShow) { - NSWindow* native_parent = MakeNativeParent(); + NSWindow* native_parent = MakeBorderlessNativeParent(); Widget* child = AttachPopupToNativeParent(native_parent); child->Show(); EXPECT_TRUE(child->IsVisible()); @@ -925,7 +924,7 @@ TEST_F(NativeWidgetMacTest, VisibleAfterNativeParentDeminiaturize) { if (base::mac::IsOS10_10()) return; - NSWindow* native_parent = MakeNativeParent(); + NSWindow* native_parent = MakeBorderlessNativeParent(); [native_parent makeKeyAndOrderFront:nil]; [native_parent miniaturize:nil]; Widget* child = AttachPopupToNativeParent(native_parent); @@ -1173,9 +1172,9 @@ Widget* ShowWindowModalWidget(NSWindow* native_parent) { } // namespace // Tests object lifetime for the show/hide animations used for child-modal -// windows. Parents the dialog off a native parent window (not a views::Widget). +// windows. TEST_F(NativeWidgetMacTest, NativeWindowChildModalShowHide) { - NSWindow* native_parent = MakeNativeParent(); + NSWindow* native_parent = MakeBorderlessNativeParent(); { Widget* modal_dialog_widget = ShowChildModalWidgetAndWait(native_parent); TestWidgetObserver widget_observer(modal_dialog_widget); @@ -1228,7 +1227,7 @@ TEST_F(NativeWidgetMacTest, NativeWindowChildModalShowHide) { // Tests that calls to Hide() a Widget cancel any in-progress show animation, // and that clients can control the triggering of the animation. TEST_F(NativeWidgetMacTest, ShowAnimationControl) { - NSWindow* native_parent = MakeNativeParent(); + NSWindow* native_parent = MakeBorderlessNativeParent(); Widget* modal_dialog_widget = views::DialogDelegate::CreateDialogWidget( new ModalDialogDelegate(ui::MODAL_TYPE_CHILD), nullptr, [native_parent contentView]); @@ -1247,7 +1246,7 @@ TEST_F(NativeWidgetMacTest, ShowAnimationControl) { EXPECT_TRUE([retained_animation isAnimating]); // Hide without waiting for the animation to complete. Animation should cancel - // and clear references from BridgedNativeWidget. + // and clear references from BridgedNativeWidgetImpl. modal_dialog_widget->Hide(); EXPECT_FALSE([retained_animation isAnimating]); EXPECT_FALSE(test_api.show_animation()); @@ -1286,8 +1285,7 @@ TEST_F(NativeWidgetMacTest, ShowAnimationControl) { // Tests behavior of window-modal dialogs, displayed as sheets. TEST_F(NativeWidgetMacTest, WindowModalSheet) { - NSWindow* native_parent = - MakeNativeParentWithStyle(NSClosableWindowMask | NSTitledWindowMask); + NSWindow* native_parent = MakeClosableTitledNativeParent(); Widget* sheet_widget = views::DialogDelegate::CreateDialogWidget( new ModalDialogDelegate(ui::MODAL_TYPE_WINDOW), nullptr, @@ -1328,7 +1326,7 @@ TEST_F(NativeWidgetMacTest, WindowModalSheet) { Widget::Widgets children; Widget::GetAllChildWidgets([native_parent contentView], &children); - EXPECT_TRUE(children.empty()); + ASSERT_EQ(2u, children.size()); sheet_widget->Show(); // Should run the above block, then animate the sheet. EXPECT_TRUE(did_observe); @@ -1336,8 +1334,8 @@ TEST_F(NativeWidgetMacTest, WindowModalSheet) { // Ensure sheets are included as a child. Widget::GetAllChildWidgets([native_parent contentView], &children); - ASSERT_EQ(1u, children.size()); - EXPECT_EQ(sheet_widget, *children.begin()); + ASSERT_EQ(2u, children.size()); + EXPECT_TRUE(children.count(sheet_widget)); // Modal, so the close button in the parent window should get disabled. EXPECT_FALSE([parent_close_button isEnabled]); @@ -1348,9 +1346,9 @@ TEST_F(NativeWidgetMacTest, WindowModalSheet) { // TODO(tapted): Ideally [native_parent orderOut:nil] would also work here. // But it does not. AppKit's childWindow management breaks down after an - // -orderOut: (see BridgedNativeWidget::OnVisibilityChanged()). For regular - // child windows, BridgedNativeWidget fixes the behavior with its own - // management. However, it can't do that for sheets without encountering + // -orderOut: (see BridgedNativeWidgetImpl::OnVisibilityChanged()). For + // regular child windows, BridgedNativeWidgetImpl fixes the behavior with its + // own management. However, it can't do that for sheets without encountering // http://crbug.com/605098 and http://crbug.com/667602. -[NSApp hide:] makes // the NSWindow hidden in a different way, which does not break like // -orderOut: does. Which is good, because a user can always do -[NSApp @@ -1368,41 +1366,24 @@ TEST_F(NativeWidgetMacTest, WindowModalSheet) { sheet_widget->Close(); EXPECT_TRUE(sheet_widget->IsVisible()); - did_observe = false; - - // Experimentally (on 10.10), this notification is posted from within the - // -[NSWindow orderOut:] call that is triggered from -[ViewsNSWindowDelegate - // sheetDidEnd:]. |sheet_widget| will be destroyed next, so it's still safe to - // use in the block. However, since the orderOut just happened, it's not very - // interesting. - observer = [[NSNotificationCenter defaultCenter] - addObserverForName:NSWindowDidEndSheetNotification - object:native_parent - queue:nil - usingBlock:^(NSNotification* note) { - EXPECT_TRUE([sheet_window delegate]); - *did_observe_ptr = true; - }]; - // Pump in order to trigger -[NSWindow endSheet:..], which will block while // the animation runs, then delete |sheet_widget|. EXPECT_TRUE([sheet_window delegate]); base::RunLoop().RunUntilIdle(); EXPECT_FALSE([sheet_window delegate]); - - EXPECT_TRUE(did_observe); // Also ensures the Close() actually uses sheets. [[NSNotificationCenter defaultCenter] removeObserver:observer]; EXPECT_TRUE(widget_observer.widget_closed()); EXPECT_TRUE([parent_close_button isEnabled]); + + [native_parent close]; } // Tests behavior when closing a window that is a sheet, or that hosts a sheet, // and reshowing a sheet on a window after the sheet was closed with -[NSWindow // close]. TEST_F(NativeWidgetMacTest, CloseWithWindowModalSheet) { - NSWindow* native_parent = - MakeNativeParentWithStyle(NSClosableWindowMask | NSTitledWindowMask); + NSWindow* native_parent = MakeClosableTitledNativeParent(); { Widget* sheet_widget = ShowWindowModalWidget(native_parent); @@ -1496,8 +1477,7 @@ TEST_F(NativeWidgetMacTest, CloseWithWindowModalSheet) { // eventually complete on a destroyed NSWindowDelegate. Regression test for // https://crbug.com/851376. TEST_F(NativeWidgetMacTest, CloseWindowModalSheetWithoutSheetParent) { - NSWindow* native_parent = - MakeNativeParentWithStyle(NSClosableWindowMask | NSTitledWindowMask); + NSWindow* native_parent = MakeClosableTitledNativeParent(); { base::mac::ScopedNSAutoreleasePool pool; Widget* sheet_widget = ShowWindowModalWidget(native_parent); @@ -1541,17 +1521,16 @@ TEST_F(NativeWidgetMacTest, CloseWindowModalSheetWithoutSheetParent) { } // Test calls to Widget::ReparentNativeView() that result in a no-op on Mac. -// Tests with both native and non-native parents. TEST_F(NativeWidgetMacTest, NoopReparentNativeView) { - NSWindow* parent = MakeNativeParent(); + NSWindow* parent = MakeBorderlessNativeParent(); Widget* dialog = views::DialogDelegate::CreateDialogWidget( new DialogDelegateView, nullptr, [parent contentView]); - BridgedNativeWidget* bridge = - NativeWidgetMac::GetBridgeForNativeWindow(dialog->GetNativeWindow()); + BridgedNativeWidgetImpl* bridge = + BridgedNativeWidgetImpl::GetFromNativeWindow(dialog->GetNativeWindow()); - EXPECT_EQ(bridge->parent()->GetNSWindow(), parent); + EXPECT_EQ(bridge->parent()->ns_window(), parent); Widget::ReparentNativeView(dialog->GetNativeView(), [parent contentView]); - EXPECT_EQ(bridge->parent()->GetNSWindow(), parent); + EXPECT_EQ(bridge->parent()->ns_window(), parent); [parent close]; @@ -1559,11 +1538,12 @@ TEST_F(NativeWidgetMacTest, NoopReparentNativeView) { parent = parent_widget->GetNativeWindow(); dialog = views::DialogDelegate::CreateDialogWidget( new DialogDelegateView, nullptr, [parent contentView]); - bridge = NativeWidgetMac::GetBridgeForNativeWindow(dialog->GetNativeWindow()); + bridge = + BridgedNativeWidgetImpl::GetFromNativeWindow(dialog->GetNativeWindow()); - EXPECT_EQ(bridge->parent()->GetNSWindow(), parent); + EXPECT_EQ(bridge->parent()->ns_window(), parent); Widget::ReparentNativeView(dialog->GetNativeView(), [parent contentView]); - EXPECT_EQ(bridge->parent()->GetNSWindow(), parent); + EXPECT_EQ(bridge->parent()->ns_window(), parent); parent_widget->CloseNow(); } @@ -1778,7 +1758,7 @@ TEST_F(NativeWidgetMacTest, DISABLED_DoesHideTitle) { // The default window with a title should look different from the // window with an empty title. - EXPECT_FALSE([empty_title_data isEqualToData:this_title_data]); + EXPECT_NSNE(empty_title_data, this_title_data); delegate.set_should_show_title(false); delegate.set_title(base::ASCIIToUTF16("This is another title")); @@ -1788,7 +1768,7 @@ TEST_F(NativeWidgetMacTest, DISABLED_DoesHideTitle) { // With our magic setting, the window with a title should look the // same as the window with an empty title. EXPECT_TRUE([ns_window _isTitleHidden]); - EXPECT_TRUE([empty_title_data isEqualToData:hidden_title_data]); + EXPECT_NSEQ(empty_title_data, hidden_title_data); widget->CloseNow(); } @@ -2068,13 +2048,16 @@ TEST_F(NativeWidgetMacTest, ReparentNativeViewTypes) { CreateParams(Widget::InitParams::TYPE_POPUP); toplevel_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; toplevel1->Init(toplevel_params); + toplevel1->Show(); std::unique_ptr<Widget> toplevel2(new Widget); toplevel2->Init(toplevel_params); + toplevel2->Show(); Widget* child = new Widget; Widget::InitParams child_params(Widget::InitParams::TYPE_CONTROL); child->Init(child_params); + child->Show(); Widget::ReparentNativeView(child->GetNativeView(), toplevel1->GetNativeView()); @@ -2106,8 +2089,8 @@ class NativeWidgetMacFullKeyboardAccessTest : public NativeWidgetMacTest { NativeWidgetMacTest::SetUp(); widget_ = CreateTopLevelPlatformWidget(); - bridge_ = - NativeWidgetMac::GetBridgeForNativeWindow(widget_->GetNativeWindow()); + bridge_ = BridgedNativeWidgetImpl::GetFromNativeWindow( + widget_->GetNativeWindow()); fake_full_keyboard_access_ = ui::test::ScopedFakeFullKeyboardAccess::GetInstance(); DCHECK(fake_full_keyboard_access_); @@ -2120,7 +2103,7 @@ class NativeWidgetMacFullKeyboardAccessTest : public NativeWidgetMacTest { } Widget* widget_ = nullptr; - BridgedNativeWidget* bridge_ = nullptr; + BridgedNativeWidgetImpl* bridge_ = nullptr; ui::test::ScopedFakeFullKeyboardAccess* fake_full_keyboard_access_ = nullptr; }; @@ -2179,8 +2162,7 @@ class NativeWidgetMacViewsOrderTest : public WidgetTest { widget_ = CreateTopLevelPlatformWidget(); - ASSERT_EQ(1u, [[widget_->GetNativeView() subviews] count]); - compositor_view_ = [[widget_->GetNativeView() subviews] firstObject]; + starting_subviews_.reset([[widget_->GetNativeView() subviews] copy]); native_host_parent_ = new View(); widget_->GetContentsView()->AddChildView(native_host_parent_); @@ -2193,9 +2175,10 @@ class NativeWidgetMacViewsOrderTest : public WidgetTest { hosts_.push_back(std::move(holder)); } EXPECT_EQ(kNativeViewCount, native_host_parent_->child_count()); - EXPECT_TRUE(([[widget_->GetNativeView() subviews] isEqualToArray:@[ - compositor_view_, hosts_[0]->view(), hosts_[1]->view(), hosts_[2]->view() - ]])); + EXPECT_NSEQ([widget_->GetNativeView() subviews], + ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[ + hosts_[0]->view(), hosts_[1]->view(), hosts_[2]->view() + ]])); } void TearDown() override { @@ -2205,10 +2188,12 @@ class NativeWidgetMacViewsOrderTest : public WidgetTest { NSView* GetContentNativeView() { return widget_->GetNativeView(); } + NSArray<NSView*>* GetStartingSubviews() { return starting_subviews_; } + Widget* widget_ = nullptr; View* native_host_parent_ = nullptr; - NSView* compositor_view_ = nil; std::vector<std::unique_ptr<NativeHostHolder>> hosts_; + base::scoped_nsobject<NSArray<NSView*>> starting_subviews_; private: DISALLOW_COPY_AND_ASSIGN(NativeWidgetMacViewsOrderTest); @@ -2218,61 +2203,65 @@ class NativeWidgetMacViewsOrderTest : public WidgetTest { // z-order. TEST_F(NativeWidgetMacViewsOrderTest, NativeViewAttached) { hosts_[1]->Detach(); - EXPECT_TRUE(([[GetContentNativeView() subviews] isEqualToArray:@[ - compositor_view_, hosts_[0]->view(), hosts_[2]->view() - ]])); + EXPECT_NSEQ([GetContentNativeView() subviews], + ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[ + hosts_[0]->view(), hosts_[2]->view() + ]])); hosts_[1]->AttachNativeView(); - EXPECT_TRUE(([[GetContentNativeView() subviews] isEqualToArray:@[ - compositor_view_, hosts_[0]->view(), hosts_[1]->view(), - hosts_[2]->view() - ]])); + EXPECT_NSEQ([GetContentNativeView() subviews], + ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[ + hosts_[0]->view(), hosts_[1]->view(), hosts_[2]->view() + ]])); } // Tests that NativeViews order changes according to views::View hierarchy. TEST_F(NativeWidgetMacViewsOrderTest, ReorderViews) { native_host_parent_->ReorderChildView(hosts_[2]->host(), 1); - EXPECT_TRUE(([[GetContentNativeView() subviews] isEqualToArray:@[ - compositor_view_, hosts_[0]->view(), hosts_[2]->view(), - hosts_[1]->view() - ]])); + EXPECT_NSEQ([GetContentNativeView() subviews], + ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[ + hosts_[0]->view(), hosts_[2]->view(), hosts_[1]->view() + ]])); native_host_parent_->RemoveChildView(hosts_[2]->host()); - EXPECT_TRUE(([[GetContentNativeView() subviews] isEqualToArray:@[ - compositor_view_, hosts_[0]->view(), hosts_[1]->view() - ]])); + EXPECT_NSEQ([GetContentNativeView() subviews], + ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[ + hosts_[0]->view(), hosts_[1]->view() + ]])); View* new_parent = new View(); native_host_parent_->RemoveChildView(hosts_[1]->host()); native_host_parent_->AddChildView(new_parent); new_parent->AddChildView(hosts_[1]->host()); new_parent->AddChildView(hosts_[2]->host()); - EXPECT_TRUE(([[GetContentNativeView() subviews] isEqualToArray:@[ - compositor_view_, hosts_[0]->view(), hosts_[1]->view(), - hosts_[2]->view() - ]])); + EXPECT_NSEQ([GetContentNativeView() subviews], + ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[ + hosts_[0]->view(), hosts_[1]->view(), hosts_[2]->view() + ]])); native_host_parent_->ReorderChildView(new_parent, 0); - EXPECT_TRUE(([[GetContentNativeView() subviews] isEqualToArray:@[ - compositor_view_, hosts_[1]->view(), hosts_[2]->view(), - hosts_[0]->view() - ]])); + EXPECT_NSEQ([GetContentNativeView() subviews], + ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[ + hosts_[1]->view(), hosts_[2]->view(), hosts_[0]->view() + ]])); } // Test that unassociated native views stay on top after reordering. TEST_F(NativeWidgetMacViewsOrderTest, UnassociatedViewsIsAbove) { base::scoped_nsobject<NSView> child_view([[NSView alloc] init]); [GetContentNativeView() addSubview:child_view]; - EXPECT_TRUE(([[GetContentNativeView() subviews] isEqualToArray:@[ - compositor_view_, hosts_[0]->view(), hosts_[1]->view(), - hosts_[2]->view(), child_view - ]])); + EXPECT_NSEQ( + [GetContentNativeView() subviews], + ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[ + hosts_[0]->view(), hosts_[1]->view(), hosts_[2]->view(), child_view + ]])); native_host_parent_->ReorderChildView(hosts_[2]->host(), 1); - EXPECT_TRUE(([[GetContentNativeView() subviews] isEqualToArray:@[ - compositor_view_, hosts_[0]->view(), hosts_[2]->view(), - hosts_[1]->view(), child_view - ]])); + EXPECT_NSEQ( + [GetContentNativeView() subviews], + ([GetStartingSubviews() arrayByAddingObjectsFromArray:@[ + hosts_[0]->view(), hosts_[2]->view(), hosts_[1]->view(), child_view + ]])); } // Test -[NSWindowDelegate windowShouldClose:]. @@ -2421,18 +2410,3 @@ TEST_F(NativeWidgetMacTest, TouchBar) { } @end -@implementation TestNativeParentWindow { - bool* deallocFlag_; -} - -@synthesize deallocFlag = deallocFlag_; - -- (void)dealloc { - if (deallocFlag_) { - DCHECK(!*deallocFlag_); - *deallocFlag_ = true; - } - [super dealloc]; -} - -@end diff --git a/chromium/ui/views/widget/widget.cc b/chromium/ui/views/widget/widget.cc index 5ad48b616fa..0353201e793 100644 --- a/chromium/ui/views/widget/widget.cc +++ b/chromium/ui/views/widget/widget.cc @@ -1058,11 +1058,13 @@ bool Widget::OnNativeWidgetActivationChanged(bool active) { } void Widget::OnNativeFocus() { - WidgetFocusManager::GetInstance()->OnNativeFocusChanged(GetNativeView()); + WidgetFocusManager::GetInstance(GetNativeWindow()) + ->OnNativeFocusChanged(GetNativeView()); } void Widget::OnNativeBlur() { - WidgetFocusManager::GetInstance()->OnNativeFocusChanged(nullptr); + WidgetFocusManager::GetInstance(GetNativeWindow()) + ->OnNativeFocusChanged(nullptr); } void Widget::OnNativeWidgetVisibilityChanging(bool visible) { diff --git a/chromium/ui/views/widget/widget_unittest.cc b/chromium/ui/views/widget/widget_unittest.cc index 24c23336a45..352e4fbfe64 100644 --- a/chromium/ui/views/widget/widget_unittest.cc +++ b/chromium/ui/views/widget/widget_unittest.cc @@ -2034,11 +2034,6 @@ TEST_F(WidgetWindowTitleTest, SetWindowTitleChanged_DesktopNativeWidget) { #endif // !OS_CHROMEOS TEST_F(WidgetTest, WidgetDeleted_InOnMousePressed) { - // TODO: test uses GetContext(), which is not applicable to aura-mus. - // http://crbug.com/663809. - if (IsMus()) - return; - Widget* widget = new Widget; Widget::InitParams params = CreateParams(views::Widget::InitParams::TYPE_POPUP); @@ -2049,10 +2044,14 @@ TEST_F(WidgetTest, WidgetDeleted_InOnMousePressed) { widget->SetSize(gfx::Size(100, 100)); widget->Show(); - ui::test::EventGenerator generator(GetContext(), widget->GetNativeWindow()); + ui::test::EventGenerator generator( + IsMus() ? widget->GetNativeWindow() : GetContext(), + widget->GetNativeWindow()); WidgetDeletionObserver deletion_observer(widget); - generator.ClickLeftButton(); + generator.PressLeftButton(); + if (deletion_observer.IsWidgetAlive()) + generator.ReleaseLeftButton(); EXPECT_FALSE(deletion_observer.IsWidgetAlive()); // Yay we did not crash! @@ -4080,7 +4079,7 @@ class CompositingWidgetTest : public views::test::WidgetTest { const Widget::InitParams::WindowOpacity opacity) { for (const auto& widget_type : widget_types_) { #if defined(OS_MACOSX) - // Tooltips are native on Mac. See BridgedNativeWidget::Init. + // Tooltips are native on Mac. See BridgedNativeWidgetImpl::Init. if (widget_type == Widget::InitParams::TYPE_TOOLTIP) continue; #elif defined(OS_WIN) diff --git a/chromium/ui/views/widget/widget_utils_mac.mm b/chromium/ui/views/widget/widget_utils_mac.mm index 58d0a437fb0..990e17faf2f 100644 --- a/chromium/ui/views/widget/widget_utils_mac.mm +++ b/chromium/ui/views/widget/widget_utils_mac.mm @@ -4,13 +4,13 @@ #include "ui/views/widget/widget_utils_mac.h" -#import "ui/views/cocoa/bridged_native_widget.h" +#import "ui/views_bridge_mac/bridged_native_widget_impl.h" namespace views { gfx::Size GetWindowSizeForClientSize(Widget* widget, const gfx::Size& size) { DCHECK(widget); - return BridgedNativeWidget::GetWindowSizeForClientSize( + return BridgedNativeWidgetImpl::GetWindowSizeForClientSize( widget->GetNativeWindow(), size); } diff --git a/chromium/ui/views/widget/window_reorderer.cc b/chromium/ui/views/widget/window_reorderer.cc index 2f83b18fbd9..2f459a861d6 100644 --- a/chromium/ui/views/widget/window_reorderer.cc +++ b/chromium/ui/views/widget/window_reorderer.cc @@ -166,14 +166,13 @@ void WindowReorderer::ReorderChildWindows() { // |view_with_layer_order| backwards and stack windows at the bottom so that // windows not associated to a view are stacked above windows with an // associated view. - for (std::vector<View*>::reverse_iterator it = view_with_layer_order.rbegin(); + for (auto it = view_with_layer_order.rbegin(); it != view_with_layer_order.rend(); ++it) { View* view = *it; ui::Layer* layer = view->layer(); aura::Window* window = NULL; - std::map<View*, aura::Window*>::iterator hosted_window_it = - hosted_windows.find(view); + auto hosted_window_it = hosted_windows.find(view); if (hosted_window_it != hosted_windows.end()) { window = hosted_window_it->second; layer = window->layer(); diff --git a/chromium/ui/views/widget/window_reorderer_unittest.cc b/chromium/ui/views/widget/window_reorderer_unittest.cc index 579e622ee73..faecf6bf9ab 100644 --- a/chromium/ui/views/widget/window_reorderer_unittest.cc +++ b/chromium/ui/views/widget/window_reorderer_unittest.cc @@ -39,9 +39,8 @@ void SetWindowAndLayerName(aura::Window* window, const std::string& name) { // first) of |parent|. The format of the string is "name1 name2 name3 ...". std::string ChildWindowNamesAsString(const aura::Window& parent) { std::string names; - typedef std::vector<aura::Window*> Windows; - for (Windows::const_iterator it = parent.children().begin(); - it != parent.children().end(); ++it) { + for (auto it = parent.children().begin(); it != parent.children().end(); + ++it) { if (!names.empty()) names += " "; names += (*it)->GetName(); diff --git a/chromium/ui/views/win/hwnd_message_handler.cc b/chromium/ui/views/win/hwnd_message_handler.cc index 54eb0fcd0bf..9afbcf6ca30 100644 --- a/chromium/ui/views/win/hwnd_message_handler.cc +++ b/chromium/ui/views/win/hwnd_message_handler.cc @@ -1000,12 +1000,14 @@ void HWNDMessageHandler::OnBlur() {} void HWNDMessageHandler::OnCaretBoundsChanged( const ui::TextInputClient* client) { - if (!client || client->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) - return; - if (!ax_system_caret_) ax_system_caret_ = std::make_unique<ui::AXSystemCaretWin>(hwnd()); + if (!client || client->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) { + ax_system_caret_->Hide(); + return; + } + const gfx::Rect dip_caret_bounds(client->GetCaretBounds()); gfx::Rect caret_bounds = display::win::ScreenWin::DIPToScreenRect(hwnd(), dip_caret_bounds); @@ -1015,7 +1017,10 @@ void HWNDMessageHandler::OnCaretBoundsChanged( } void HWNDMessageHandler::OnTextInputStateChanged( - const ui::TextInputClient* client) {} + const ui::TextInputClient* client) { + if (!client || client->GetTextInputType() == ui::TEXT_INPUT_TYPE_NONE) + OnCaretBoundsChanged(client); +} void HWNDMessageHandler::OnInputMethodDestroyed( const ui::InputMethod* input_method) { @@ -1586,8 +1591,7 @@ LRESULT HWNDMessageHandler::OnCreate(CREATESTRUCT* create_struct) { base::Bind(&HWNDMessageHandler::OnSessionChange, base::Unretained(this)))); - float scale_factor = display::win::ScreenWin::GetScaleFactorForHWND(hwnd()); - dpi_ = display::win::GetDPIFromScalingFactor(scale_factor); + dpi_ = display::win::ScreenWin::GetDPIForHWND(hwnd()); // TODO(beng): move more of NWW::OnCreate here. return 0; @@ -1642,7 +1646,7 @@ LRESULT HWNDMessageHandler::OnDpiChanged(UINT msg, dpi = display::win::GetDPIFromScalingFactor(scaling_factor); } else { dpi = LOWORD(w_param); - scaling_factor = display::win::GetScalingFactorFromDPI(dpi); + scaling_factor = display::win::ScreenWin::GetScaleFactorForDPI(dpi); } // The first WM_DPICHANGED originates from EnableChildWindowDpiMessage during @@ -2967,8 +2971,8 @@ LRESULT HWNDMessageHandler::HandlePointerEventTypeTouch(UINT message, event_type, touch_point, event_time, ui::PointerDetails(ui::EventPointerType::POINTER_TYPE_TOUCH, mapped_pointer_id, radius_x, radius_y, pressure, - pointer_touch_info.orientation, 0.0f, 0.0f, 0.0f), - ui::GetModifiersFromKeyState(), rotation_angle); + rotation_angle), + ui::GetModifiersFromKeyState()); event.latency()->AddLatencyNumberWithTimestamp( ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, event_time, 1); diff --git a/chromium/ui/views/win/pen_event_processor.cc b/chromium/ui/views/win/pen_event_processor.cc index b820c51be5e..e1429935534 100644 --- a/chromium/ui/views/win/pen_event_processor.cc +++ b/chromium/ui/views/win/pen_event_processor.cc @@ -56,12 +56,14 @@ std::unique_ptr<ui::Event> PenEventProcessor::GenerateEvent( // convert pressure into a float [0, 1]. The range of the pressure is // [0, 1024] as specified on MSDN. float pressure = static_cast<float>(pointer_pen_info.pressure) / 1024; - float rotation = pointer_pen_info.rotation; + int rotation_angle = static_cast<int>(pointer_pen_info.rotation) % 180; + if (rotation_angle < 0) + rotation_angle += 180; int tilt_x = pointer_pen_info.tiltX; int tilt_y = pointer_pen_info.tiltY; ui::PointerDetails pointer_details( input_type, mapped_pointer_id, /* radius_x */ 0.0f, /* radius_y */ 0.0f, - pressure, rotation, tilt_x, tilt_y, /* tangential_pressure */ 0.0f); + pressure, rotation_angle, tilt_x, tilt_y, /* tangential_pressure */ 0.0f); // If the flag is disabled, we send mouse events for all pen inputs. if (!direct_manipulation_enabled_) { @@ -186,12 +188,9 @@ std::unique_ptr<ui::Event> PenEventProcessor::GenerateTouchEvent( const base::TimeTicks event_time = ui::EventTimeForNow(); - int rotation_angle = static_cast<int>(pointer_details.twist) % 180; - if (rotation_angle < 0) - rotation_angle += 180; std::unique_ptr<ui::TouchEvent> event = std::make_unique<ui::TouchEvent>( event_type, point, event_time, pointer_details, - flags | ui::GetModifiersFromKeyState(), rotation_angle); + flags | ui::GetModifiersFromKeyState()); event->set_hovering(event_type == ui::ET_TOUCH_RELEASED); event->latency()->AddLatencyNumberWithTimestamp( ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, event_time, 1); diff --git a/chromium/ui/views/window/custom_frame_view.cc b/chromium/ui/views/window/custom_frame_view.cc index 0ae8ebd9ad3..01a5c58eb39 100644 --- a/chromium/ui/views/window/custom_frame_view.cc +++ b/chromium/ui/views/window/custom_frame_view.cc @@ -522,8 +522,7 @@ void CustomFrameView::LayoutWindowControls() { button_order->trailing_buttons(); ImageButton* button = NULL; - for (std::vector<views::FrameButton>::const_iterator it = - leading_buttons.begin(); it != leading_buttons.end(); ++it) { + for (auto it = leading_buttons.begin(); it != leading_buttons.end(); ++it) { button = GetImageButton(*it); if (!button) continue; @@ -538,8 +537,8 @@ void CustomFrameView::LayoutWindowControls() { // Trailing buttions are laid out in a RTL fashion next_button_x = width() - FrameBorderThickness(); - for (std::vector<views::FrameButton>::const_reverse_iterator it = - trailing_buttons.rbegin(); it != trailing_buttons.rend(); ++it) { + for (auto it = trailing_buttons.rbegin(); it != trailing_buttons.rend(); + ++it) { button = GetImageButton(*it); if (!button) continue; diff --git a/chromium/ui/views/window/dialog_client_view.cc b/chromium/ui/views/window/dialog_client_view.cc index 40cc9f35cfa..7c5c56f4c27 100644 --- a/chromium/ui/views/window/dialog_client_view.cc +++ b/chromium/ui/views/window/dialog_client_view.cc @@ -17,6 +17,7 @@ #include "ui/views/controls/button/image_button.h" #include "ui/views/controls/button/label_button.h" #include "ui/views/controls/button/md_text_button.h" +#include "ui/views/event_utils.h" #include "ui/views/layout/grid_layout.h" #include "ui/views/layout/layout_provider.h" #include "ui/views/style/platform_style.h" @@ -164,6 +165,13 @@ gfx::Size DialogClientView::GetMaximumSize() const { return max_size; } +void DialogClientView::VisibilityChanged(View* starting_from, bool is_visible) { + ClientView::VisibilityChanged(starting_from, is_visible); + + if (is_visible) + view_shown_time_stamp_ = base::TimeTicks::Now(); +} + void DialogClientView::Layout() { button_row_container_->SetSize( gfx::Size(width(), button_row_container_->GetHeightForWidth(width()))); @@ -234,6 +242,9 @@ void DialogClientView::ButtonPressed(Button* sender, const ui::Event& event) { if (!GetDialogDelegate()) return; + if (IsPossiblyUnintendedInteraction(view_shown_time_stamp_, event)) + return; + if (sender == ok_button_) AcceptWindow(); else if (sender == cancel_button_) @@ -242,6 +253,10 @@ void DialogClientView::ButtonPressed(Button* sender, const ui::Event& event) { NOTREACHED(); } +void DialogClientView::ResetViewShownTimeStampForTesting() { + view_shown_time_stamp_ = base::TimeTicks(); +} + //////////////////////////////////////////////////////////////////////////////// // DialogClientView, private: diff --git a/chromium/ui/views/window/dialog_client_view.h b/chromium/ui/views/window/dialog_client_view.h index 108d9776d0b..3981e4f8b6c 100644 --- a/chromium/ui/views/window/dialog_client_view.h +++ b/chromium/ui/views/window/dialog_client_view.h @@ -7,6 +7,7 @@ #include "base/gtest_prod_util.h" #include "base/macros.h" +#include "base/time/time.h" #include "ui/base/ui_base_types.h" #include "ui/views/controls/button/button.h" #include "ui/views/window/client_view.h" @@ -54,6 +55,7 @@ class VIEWS_EXPORT DialogClientView : public ClientView, gfx::Size CalculatePreferredSize() const override; gfx::Size GetMinimumSize() const override; gfx::Size GetMaximumSize() const override; + void VisibilityChanged(View* starting_from, bool is_visible) override; void Layout() override; bool AcceleratorPressed(const ui::Accelerator& accelerator) override; @@ -66,6 +68,11 @@ class VIEWS_EXPORT DialogClientView : public ClientView, void set_minimum_size(const gfx::Size& size) { minimum_size_ = size; } + // Resets the time when view has been shown. Tests may need to call this + // method if they use events that could be otherwise treated as unintended. + // See IsPossiblyUnintendedInteraction(). + void ResetViewShownTimeStampForTesting(); + private: enum { // The number of buttons that DialogClientView can support. @@ -134,6 +141,9 @@ class VIEWS_EXPORT DialogClientView : public ClientView, // SetupLayout(). Everything will be manually updated afterwards. bool adding_or_removing_views_ = false; + // Time when view has been shown. + base::TimeTicks view_shown_time_stamp_; + DISALLOW_COPY_AND_ASSIGN(DialogClientView); }; diff --git a/chromium/ui/views/window/dialog_client_view_unittest.cc b/chromium/ui/views/window/dialog_client_view_unittest.cc index d3f0d56cf04..4c4e67e7dbc 100644 --- a/chromium/ui/views/window/dialog_client_view_unittest.cc +++ b/chromium/ui/views/window/dialog_client_view_unittest.cc @@ -8,11 +8,16 @@ #include "base/macros.h" #include "base/strings/utf_string_conversions.h" +#include "base/time/time.h" #include "build/build_config.h" #include "ui/base/ui_base_types.h" +#include "ui/events/base_event_utils.h" +#include "ui/events/event.h" +#include "ui/gfx/geometry/point.h" #include "ui/views/controls/button/checkbox.h" #include "ui/views/controls/button/image_button.h" #include "ui/views/controls/button/label_button.h" +#include "ui/views/metrics.h" #include "ui/views/style/platform_style.h" #include "ui/views/test/test_layout_provider.h" #include "ui/views/test/test_views.h" @@ -505,4 +510,24 @@ TEST_F(DialogClientViewTest, FocusChangingButtons) { EXPECT_EQ(nullptr, focus_manager->GetFocusedView()); } +// Ensures that clicks are ignored for short time after view has been shown. +TEST_F(DialogClientViewTest, IgnorePossiblyUnintendedClicks) { + widget()->Show(); + SetDialogButtons(ui::DIALOG_BUTTON_CANCEL | ui::DIALOG_BUTTON_OK); + + ui::MouseEvent mouse_event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), + ui::EventTimeForNow(), ui::EF_NONE, ui::EF_NONE); + client_view()->ButtonPressed(client_view()->ok_button(), mouse_event); + client_view()->ButtonPressed(client_view()->cancel_button(), mouse_event); + EXPECT_FALSE(widget()->IsClosed()); + + client_view()->ButtonPressed( + client_view()->cancel_button(), + ui::MouseEvent(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(), + ui::EventTimeForNow() + base::TimeDelta::FromMilliseconds( + GetDoubleClickInterval()), + ui::EF_NONE, ui::EF_NONE)); + EXPECT_TRUE(widget()->IsClosed()); +} + } // namespace views |