diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-08-28 15:28:34 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2018-08-28 13:54:51 +0000 |
commit | 2a19c63448c84c1805fb1a585c3651318bb86ca7 (patch) | |
tree | eb17888e8531aa6ee5e85721bd553b832a7e5156 /chromium/ui/views | |
parent | b014812705fc80bff0a5c120dfcef88f349816dc (diff) | |
download | qtwebengine-chromium-2a19c63448c84c1805fb1a585c3651318bb86ca7.tar.gz |
BASELINE: Update Chromium to 69.0.3497.70
Change-Id: I2b7b56e4e7a8b26656930def0d4575dc32b900a0
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/ui/views')
223 files changed, 5394 insertions, 2450 deletions
diff --git a/chromium/ui/views/BUILD.gn b/chromium/ui/views/BUILD.gn index c9ecb17ed42..961f2b06534 100644 --- a/chromium/ui/views/BUILD.gn +++ b/chromium/ui/views/BUILD.gn @@ -95,6 +95,7 @@ jumbo_component("views") { "cocoa/views_nswindow_delegate.h", "cocoa/views_scrollbar_bridge.h", "cocoa/widget_owner_nswindow_adapter.h", + "cocoa/window_touch_bar_delegate.h", "color_chooser/color_chooser_listener.h", "color_chooser/color_chooser_view.h", "context_menu_controller.h", @@ -257,6 +258,7 @@ jumbo_component("views") { "window/window_button_order_provider.h", "window/window_resources.h", "window/window_shape.h", + "window/window_resize_utils.h", "word_lookup_client.h", ] @@ -436,6 +438,7 @@ jumbo_component("views") { "window/native_frame_view.cc", "window/non_client_view.cc", "window/window_button_order_provider.cc", + "window/window_resize_utils.cc", "window/window_shape.cc", ] @@ -575,6 +578,7 @@ jumbo_component("views") { public += [ "accessibility/ax_aura_obj_cache.h", "accessibility/ax_aura_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", @@ -607,6 +611,7 @@ jumbo_component("views") { sources += [ "accessibility/ax_aura_obj_cache.cc", "accessibility/ax_aura_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", @@ -675,20 +680,21 @@ jumbo_component("views") { ] if (use_atk) { sources += [ - "accessibility/native_view_accessibility_auralinux.cc", - "accessibility/native_view_accessibility_auralinux.h", + "accessibility/view_ax_platform_node_delegate_auralinux.cc", + "accessibility/view_ax_platform_node_delegate_auralinux.h", ] configs += [ "//build/config/linux/atk" ] } } else if (is_win) { + public += [ "widget/desktop_aura/desktop_window_tree_host_win.h" ] sources += [ "widget/desktop_aura/desktop_drag_drop_client_win.cc", "widget/desktop_aura/desktop_drag_drop_client_win.h", "widget/desktop_aura/desktop_screen_win.cc", "widget/desktop_aura/desktop_screen_win.h", "widget/desktop_aura/desktop_window_tree_host_win.cc", - "widget/desktop_aura/desktop_window_tree_host_win.h", ] + deps += [ "//ui/events:dom_keyboard_layout" ] } else if (use_ozone) { public += [ "widget/desktop_aura/desktop_screen_ozone.h" ] sources += [ "widget/desktop_aura/desktop_screen_ozone.cc" ] @@ -726,12 +732,12 @@ jumbo_component("views") { if (has_native_accessibility) { sources += [ - "accessibility/native_view_accessibility_base.cc", - "accessibility/native_view_accessibility_base.h", - "accessibility/native_view_accessibility_mac.h", - "accessibility/native_view_accessibility_mac.mm", - "accessibility/native_view_accessibility_win.cc", - "accessibility/native_view_accessibility_win.h", + "accessibility/view_ax_platform_node_delegate.cc", + "accessibility/view_ax_platform_node_delegate.h", + "accessibility/view_ax_platform_node_delegate_mac.h", + "accessibility/view_ax_platform_node_delegate_mac.mm", + "accessibility/view_ax_platform_node_delegate_win.cc", + "accessibility/view_ax_platform_node_delegate_win.h", ] } @@ -829,7 +835,7 @@ jumbo_source_set("test_support_internal") { "//base/test:test_support", "//gpu/ipc/service", "//ipc:test_support", - "//mojo/edk", + "//mojo/core/embedder", "//skia", "//testing/gtest", "//ui/base", @@ -918,11 +924,10 @@ source_set("views_unittests_sources") { "controls/button/label_button_label.h", ] if (has_native_accessibility) { - public += [ "accessibility/native_view_accessibility_base.h" ] + public += [ "accessibility/view_ax_platform_node_delegate.h" ] } sources = [ - "accessibility/native_view_accessibility_win_unittest.cc", "accessible_pane_view_unittest.cc", "animation/bounds_animator_unittest.cc", "animation/flood_fill_ink_drop_ripple_unittest.cc", @@ -991,7 +996,7 @@ source_set("views_unittests_sources") { "view_tracker_unittest.cc", "view_unittest.cc", "view_unittest_mac.mm", - "widget/native_widget_mac_accessibility_unittest.mm", + "widget/ax_native_widget_mac_unittest.mm", "widget/native_widget_mac_unittest.mm", "widget/native_widget_unittest.cc", "widget/root_view_unittest.cc", @@ -999,6 +1004,8 @@ source_set("views_unittests_sources") { "window/custom_frame_view_unittest.cc", "window/dialog_client_view_unittest.cc", "window/dialog_delegate_unittest.cc", + "window/non_client_view_unittest.cc", + "window/window_resize_utils_unittest.cc", ] configs += [ "//build/config:precompiled_headers" ] @@ -1042,21 +1049,28 @@ source_set("views_unittests_sources") { ] if (is_win) { + public += [ "accessibility/view_ax_platform_node_delegate_win.h" ] + public_deps += [ "//build/win:default_exe_manifest", "//third_party/iaccessible2", "//third_party/wtl", ] + libs = [ "imm32.lib", "oleacc.lib", "comctl32.lib", ] - sources += [ "win/pen_event_processor_unittest.cc" ] + + sources += [ + "accessibility/view_ax_platform_node_delegate_win_unittest.cc", + "win/pen_event_processor_unittest.cc", + ] } if (has_native_accessibility) { - sources += [ "accessibility/native_view_accessibility_unittest.cc" ] + sources += [ "accessibility/view_ax_platform_node_delegate_unittest.cc" ] } if (use_x11) { @@ -1074,6 +1088,7 @@ source_set("views_unittests_sources") { if (use_aura) { sources += [ "accessibility/ax_aura_obj_cache_unittest.cc", + "accessibility/ax_tree_source_views_unittest.cc", "controls/native/native_view_host_aura_unittest.cc", "touchui/touch_selection_menu_runner_views_unittest.cc", "view_unittest_aura.cc", @@ -1144,7 +1159,7 @@ test("views_unittests") { deps = [ ":views_unittests_sources", - "//mojo/edk", + "//mojo/core/embedder", ] } @@ -1162,7 +1177,7 @@ source_set("views_interactive_ui_tests") { ":views", "//base", "//base/test:test_support", - "//mojo/edk", + "//mojo/core/embedder", "//skia", "//testing/gtest", "//ui/base:test_support", @@ -1221,7 +1236,7 @@ test("views_perftests") { ":test_support", "//base/test:test_support", "//cc/base:base", - "//mojo/edk", + "//mojo/core/embedder", "//testing/perf", "//ui/resources:ui_test_pak", ] diff --git a/chromium/ui/views/DEPS b/chromium/ui/views/DEPS index e6f8d868ebf..39d2ec74e37 100644 --- a/chromium/ui/views/DEPS +++ b/chromium/ui/views/DEPS @@ -31,10 +31,10 @@ specific_include_rules = { "+ui/wm/test" ], "run_all_unittests_main\.cc": [ - "+mojo/edk/embedder", + "+mojo/core/embedder", ], "views_perftests\.cc": [ - "+mojo/edk/embedder", + "+mojo/core/embedder", ], "view_unittest\.cc": [ "+cc/playback" diff --git a/chromium/ui/views/accessibility/DEPS b/chromium/ui/views/accessibility/DEPS index 62e6a668d8a..35ddbecff9b 100644 --- a/chromium/ui/views/accessibility/DEPS +++ b/chromium/ui/views/accessibility/DEPS @@ -4,6 +4,6 @@ include_rules = [ specific_include_rules = { "ax_system_caret_win_interactive_uitest\.cc": [ - "+mojo/edk/embedder", + "+mojo/core/embedder", ] } diff --git a/chromium/ui/views/accessibility/ax_aura_obj_cache.cc b/chromium/ui/views/accessibility/ax_aura_obj_cache.cc index 3fc1975935b..519d09c2959 100644 --- a/chromium/ui/views/accessibility/ax_aura_obj_cache.cc +++ b/chromium/ui/views/accessibility/ax_aura_obj_cache.cc @@ -4,7 +4,6 @@ #include "ui/views/accessibility/ax_aura_obj_cache.h" -#include "base/memory/ptr_util.h" #include "base/memory/singleton.h" #include "base/strings/string_util.h" #include "ui/aura/client/aura_constants.h" @@ -16,6 +15,7 @@ #include "ui/views/accessibility/ax_window_obj_wrapper.h" #include "ui/views/view.h" #include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" namespace views { @@ -69,8 +69,9 @@ void AXAuraObjCache::Remove(Widget* widget) { // When an entire widget is deleted, it doesn't always send a notification // on each of its views, so we need to explore them recursively. - if (widget->GetRootView()) - RemoveViewSubtree(widget->GetRootView()); + auto* view = widget->GetRootView(); + if (view) + RemoveViewSubtree(view); } void AXAuraObjCache::Remove(aura::Window* window, aura::Window* parent) { @@ -83,28 +84,14 @@ void AXAuraObjCache::Remove(aura::Window* window, aura::Window* parent) { AXAuraObjWrapper* AXAuraObjCache::Get(int32_t id) { auto it = cache_.find(id); - - if (it == cache_.end()) - return nullptr; - - return it->second.get(); -} - -void AXAuraObjCache::Remove(int32_t id) { - AXAuraObjWrapper* obj = Get(id); - - if (id == -1 || !obj) - return; - - cache_.erase(id); + return it != cache_.end() ? it->second.get() : nullptr; } void AXAuraObjCache::GetTopLevelWindows( std::vector<AXAuraObjWrapper*>* children) { - for (auto it = window_to_id_map_.begin(); it != window_to_id_map_.end(); - ++it) { - if (!it->first->parent()) - children->push_back(GetOrCreate(it->first)); + for (const auto& it : window_to_id_map_) { + if (!it.first->parent()) + children->push_back(GetOrCreate(it.first)); } } @@ -168,7 +155,15 @@ View* AXAuraObjCache::GetFocusedView() { if (focused_window->GetProperty( aura::client::kAccessibilityFocusFallsbackToWidgetKey)) { - // If no view is focused, falls back to root view. + // 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 + // focus. + if (focused_widget->non_client_view() && + focused_widget->non_client_view()->client_view() && + focused_widget->non_client_view()->client_view()->has_children()) { + return focused_widget->non_client_view()->client_view()->child_at(0); + } + return focused_widget->GetRootView(); } @@ -204,11 +199,11 @@ AXAuraObjWrapper* AXAuraObjCache::CreateInternal( if (it != aura_view_to_id_map.end()) return Get(it->second); - AXAuraObjWrapper* wrapper = new AuraViewWrapper(aura_view); + auto wrapper = std::make_unique<AuraViewWrapper>(aura_view); int32_t id = wrapper->GetUniqueId().Get(); aura_view_to_id_map[aura_view] = id; - cache_[id] = base::WrapUnique(wrapper); - return wrapper; + cache_[id] = std::move(wrapper); + return cache_[id].get(); } template <typename AuraView> @@ -219,10 +214,7 @@ int32_t AXAuraObjCache::GetIDInternal( return -1; auto it = aura_view_to_id_map.find(aura_view); - if (it != aura_view_to_id_map.end()) - return it->second; - - return -1; + return it != aura_view_to_id_map.end() ? it->second : -1; } template <typename AuraView> @@ -233,7 +225,7 @@ void AXAuraObjCache::RemoveInternal( if (id == -1) return; aura_view_to_id_map.erase(aura_view); - Remove(id); + cache_.erase(id); } } // namespace views diff --git a/chromium/ui/views/accessibility/ax_aura_obj_cache.h b/chromium/ui/views/accessibility/ax_aura_obj_cache.h index 48be286a40c..f798bdb059e 100644 --- a/chromium/ui/views/accessibility/ax_aura_obj_cache.h +++ b/chromium/ui/views/accessibility/ax_aura_obj_cache.h @@ -38,6 +38,8 @@ class VIEWS_EXPORT AXAuraObjCache : public aura::client::FocusChangeObserver { class Delegate { public: + virtual ~Delegate() {} + virtual void OnChildWindowRemoved(AXAuraObjWrapper* parent) = 0; virtual void OnEvent(AXAuraObjWrapper* aura_obj, ax::mojom::Event event_type) = 0; @@ -67,9 +69,6 @@ class VIEWS_EXPORT AXAuraObjCache : public aura::client::FocusChangeObserver { // Lookup a cached entry based on an id. AXAuraObjWrapper* Get(int32_t id); - // Remove a cached entry based on an id. - void Remove(int32_t id); - // Get all top level windows this cache knows about. void GetTopLevelWindows(std::vector<AXAuraObjWrapper*>* children); diff --git a/chromium/ui/views/accessibility/ax_aura_obj_wrapper.h b/chromium/ui/views/accessibility/ax_aura_obj_wrapper.h index 74ffaacc3b0..fa0f7954116 100644 --- a/chromium/ui/views/accessibility/ax_aura_obj_wrapper.h +++ b/chromium/ui/views/accessibility/ax_aura_obj_wrapper.h @@ -28,6 +28,9 @@ class VIEWS_EXPORT AXAuraObjWrapper { public: virtual ~AXAuraObjWrapper() {} + // See ViewAccessibility for details. + virtual bool IsIgnored() = 0; + // Traversal and serialization. virtual AXAuraObjWrapper* GetParent() = 0; virtual void GetChildren( 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 d3821559c0a..84b893fca7f 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 @@ -10,7 +10,7 @@ #include "base/path_service.h" #include "base/strings/utf_string_conversions.h" #include "base/win/scoped_variant.h" -#include "mojo/edk/embedder/embedder.h" +#include "mojo/core/embedder/embedder.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/window.h" #include "ui/aura/window_tree_host.h" @@ -36,7 +36,7 @@ class AXSystemCaretWinTest : public test::WidgetTest { ~AXSystemCaretWinTest() override {} void SetUp() override { - mojo::edk::Init(); + mojo::core::Init(); gl::GLSurfaceTestSupport::InitializeOneOff(); ui::RegisterPathProvider(); base::FilePath ui_test_pak_path; diff --git a/chromium/ui/views/accessibility/ax_tree_source_views.cc b/chromium/ui/views/accessibility/ax_tree_source_views.cc new file mode 100644 index 00000000000..cffc41f79b3 --- /dev/null +++ b/chromium/ui/views/accessibility/ax_tree_source_views.cc @@ -0,0 +1,126 @@ +// 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_tree_source_views.h" + +#include <vector> + +#include "ui/accessibility/ax_action_data.h" +#include "ui/accessibility/platform/ax_unique_id.h" +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/transform.h" +#include "ui/views/accessibility/ax_aura_obj_cache.h" +#include "ui/views/accessibility/ax_aura_obj_wrapper.h" + +namespace views { + +void AXTreeSourceViews::HandleAccessibleAction(const ui::AXActionData& action) { + int id = action.target_node_id; + + // In Views, we only support setting the selection within a single node, + // not across multiple nodes like on the web. + if (action.action == ax::mojom::Action::kSetSelection) { + if (action.anchor_node_id != action.focus_node_id) { + NOTREACHED(); + return; + } + id = action.anchor_node_id; + } + + AXAuraObjWrapper* obj = AXAuraObjCache::GetInstance()->Get(id); + if (obj) + obj->HandleAccessibleAction(action); +} + +bool AXTreeSourceViews::GetTreeData(ui::AXTreeData* tree_data) const { + tree_data->loaded = true; + tree_data->loading_progress = 1.0; + AXAuraObjWrapper* focus = AXAuraObjCache::GetInstance()->GetFocus(); + if (focus) + tree_data->focus_id = focus->GetUniqueId().Get(); + return true; +} + +AXAuraObjWrapper* AXTreeSourceViews::GetFromId(int32_t id) const { + AXAuraObjWrapper* root = GetRoot(); + // Root might not be in the cache. + if (id == root->GetUniqueId().Get()) + return root; + return AXAuraObjCache::GetInstance()->Get(id); +} + +int32_t AXTreeSourceViews::GetId(AXAuraObjWrapper* node) const { + return node->GetUniqueId().Get(); +} + +void AXTreeSourceViews::GetChildren( + AXAuraObjWrapper* node, + std::vector<AXAuraObjWrapper*>* out_children) const { + node->GetChildren(out_children); +} + +AXAuraObjWrapper* AXTreeSourceViews::GetParent(AXAuraObjWrapper* node) const { + AXAuraObjWrapper* root = GetRoot(); + // The root has no parent by definition. + if (node->GetUniqueId() == root->GetUniqueId()) + return nullptr; + AXAuraObjWrapper* parent = node->GetParent(); + // A top-level widget doesn't have a parent, so return the root. + if (!parent) + return root; + return parent; +} + +bool AXTreeSourceViews::IsValid(AXAuraObjWrapper* node) const { + return node && !node->IsIgnored(); +} + +bool AXTreeSourceViews::IsEqual(AXAuraObjWrapper* node1, + AXAuraObjWrapper* node2) const { + return node1 && node2 && node1->GetUniqueId() == node2->GetUniqueId(); +} + +AXAuraObjWrapper* AXTreeSourceViews::GetNull() const { + return nullptr; +} + +void AXTreeSourceViews::SerializeNode(AXAuraObjWrapper* node, + ui::AXNodeData* out_data) const { + node->Serialize(out_data); + + // Converts the global coordinates reported by each AXAuraObjWrapper + // into parent-relative coordinates to be used in the accessibility + // tree. That way when any Window, Widget, or View moves (and fires + // a location changed event), its descendants all move relative to + // it by default. + AXAuraObjWrapper* parent = node->GetParent(); + if (!parent) + return; + ui::AXNodeData parent_data; + parent->Serialize(&parent_data); + out_data->location.Offset(-parent_data.location.OffsetFromOrigin()); + out_data->offset_container_id = parent->GetUniqueId().Get(); +} + +std::string AXTreeSourceViews::ToString(AXAuraObjWrapper* root, + std::string prefix) { + ui::AXNodeData data; + root->Serialize(&data); + std::string output = prefix + data.ToString() + '\n'; + + std::vector<AXAuraObjWrapper*> children; + root->GetChildren(&children); + + prefix += prefix[0]; + for (AXAuraObjWrapper* child : children) + output += ToString(child, prefix); + + return output; +} + +AXTreeSourceViews::AXTreeSourceViews() = default; + +AXTreeSourceViews::~AXTreeSourceViews() = default; + +} // namespace views diff --git a/chromium/ui/views/accessibility/ax_tree_source_views.h b/chromium/ui/views/accessibility/ax_tree_source_views.h new file mode 100644 index 00000000000..1f50c3adddb --- /dev/null +++ b/chromium/ui/views/accessibility/ax_tree_source_views.h @@ -0,0 +1,61 @@ +// 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_TREE_SOURCE_VIEWS_H_ +#define UI_VIEWS_ACCESSIBILITY_AX_TREE_SOURCE_VIEWS_H_ + +#include "base/macros.h" +#include "ui/accessibility/ax_node_data.h" +#include "ui/accessibility/ax_tree_data.h" +#include "ui/accessibility/ax_tree_source.h" +#include "ui/views/views_export.h" + +namespace ui { +struct AXActionData; +} + +namespace views { + +class AXAuraObjWrapper; + +// This class exposes the views hierarchy as an accessibility tree permitting +// use with other accessibility classes. Subclasses must implement GetRoot(). +// The root can be an existing object in the Widget/View hierarchy or a new node +// (for example to create the "desktop" node for the extension API call +// chrome.automation.getDesktop()). +class VIEWS_EXPORT AXTreeSourceViews + : public ui:: + AXTreeSource<AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData> { + public: + // Invokes an action on an Aura object. + void HandleAccessibleAction(const ui::AXActionData& action); + + // AXTreeSource: + bool GetTreeData(ui::AXTreeData* data) const override; + // GetRoot() must be implemented by subclasses. + AXAuraObjWrapper* GetFromId(int32_t id) const override; + int32_t GetId(AXAuraObjWrapper* node) const override; + void GetChildren(AXAuraObjWrapper* node, + std::vector<AXAuraObjWrapper*>* out_children) const override; + AXAuraObjWrapper* GetParent(AXAuraObjWrapper* node) const override; + bool IsValid(AXAuraObjWrapper* node) const override; + bool IsEqual(AXAuraObjWrapper* node1, AXAuraObjWrapper* node2) const override; + AXAuraObjWrapper* GetNull() const override; + void SerializeNode(AXAuraObjWrapper* node, + ui::AXNodeData* out_data) const override; + + // Useful for debugging. + std::string ToString(views::AXAuraObjWrapper* root, std::string prefix); + + protected: + AXTreeSourceViews(); + ~AXTreeSourceViews() override; + + private: + DISALLOW_COPY_AND_ASSIGN(AXTreeSourceViews); +}; + +} // namespace views + +#endif // UI_VIEWS_ACCESSIBILITY_AX_TREE_SOURCE_VIEWS_H_ diff --git a/chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc b/chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc new file mode 100644 index 00000000000..82f6f53a8e5 --- /dev/null +++ b/chromium/ui/views/accessibility/ax_tree_source_views_unittest.cc @@ -0,0 +1,159 @@ +// 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_tree_source_views.h" + +#include <vector> + +#include "base/macros.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/accessibility/platform/ax_unique_id.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/views/accessibility/ax_aura_obj_cache.h" +#include "ui/views/accessibility/ax_aura_obj_wrapper.h" +#include "ui/views/accessibility/view_accessibility.h" +#include "ui/views/controls/label.h" +#include "ui/views/controls/textfield/textfield.h" +#include "ui/views/test/views_test_base.h" +#include "ui/views/widget/widget.h" + +namespace views { +namespace { + +// TestAXTreeSourceViews provides a root object for testing. +class TestAXTreeSourceViews : public AXTreeSourceViews { + public: + TestAXTreeSourceViews(AXAuraObjWrapper* root) : root_(root) {} + ~TestAXTreeSourceViews() override = default; + + // AXTreeSource: + AXAuraObjWrapper* GetRoot() const override { return root_; } + + private: + AXAuraObjWrapper* root_; + DISALLOW_COPY_AND_ASSIGN(TestAXTreeSourceViews); +}; + +class AXTreeSourceViewsTest : public ViewsTestBase { + public: + AXTreeSourceViewsTest() = default; + ~AXTreeSourceViewsTest() override = default; + + // testing::Test: + void SetUp() override { + ViewsTestBase::SetUp(); + widget_ = std::make_unique<Widget>(); + Widget::InitParams params(Widget::InitParams::TYPE_WINDOW_FRAMELESS); + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.bounds = gfx::Rect(11, 22, 333, 444); + params.context = GetContext(); + widget_->Init(params); + widget_->SetContentsView(new View()); + + label1_ = new Label(base::ASCIIToUTF16("Label 1")); + label1_->SetBounds(1, 1, 111, 111); + widget_->GetContentsView()->AddChildView(label1_); + + label2_ = new Label(base::ASCIIToUTF16("Label 2")); + label2_->SetBounds(2, 2, 222, 222); + widget_->GetContentsView()->AddChildView(label2_); + + textfield_ = new Textfield(); + textfield_->SetBounds(222, 2, 20, 200); + widget_->GetContentsView()->AddChildView(textfield_); + } + + void TearDown() override { + widget_.reset(); + ViewsTestBase::TearDown(); + } + + std::unique_ptr<Widget> widget_; + Label* label1_ = nullptr; // Owned by views hierarchy. + Label* label2_ = nullptr; // Owned by views hierarchy. + Textfield* textfield_ = nullptr; // Owned by views hierarchy. + + private: + DISALLOW_COPY_AND_ASSIGN(AXTreeSourceViewsTest); +}; + +TEST_F(AXTreeSourceViewsTest, Basics) { + AXAuraObjCache* cache = AXAuraObjCache::GetInstance(); + + // Start the tree at the Widget's contents view. + AXAuraObjWrapper* root = cache->GetOrCreate(widget_->GetContentsView()); + TestAXTreeSourceViews tree(root); + EXPECT_EQ(root, tree.GetRoot()); + + // The root has no parent. + EXPECT_FALSE(tree.GetParent(root)); + + // The root has the right children. + std::vector<AXAuraObjWrapper*> children; + tree.GetChildren(root, &children); + ASSERT_EQ(3u, children.size()); + + // The labels are the children. + AXAuraObjWrapper* label1 = children[0]; + AXAuraObjWrapper* label2 = children[1]; + AXAuraObjWrapper* textfield = children[2]; + EXPECT_EQ(label1, cache->GetOrCreate(label1_)); + EXPECT_EQ(label2, cache->GetOrCreate(label2_)); + EXPECT_EQ(textfield, cache->GetOrCreate(textfield_)); + + // The parents is correct. + EXPECT_EQ(root, tree.GetParent(label1)); + EXPECT_EQ(root, tree.GetParent(label2)); + EXPECT_EQ(root, tree.GetParent(textfield)); + + // IDs match the ones in the cache. + EXPECT_EQ(root->GetUniqueId().Get(), tree.GetId(root)); + EXPECT_EQ(label1->GetUniqueId().Get(), tree.GetId(label1)); + EXPECT_EQ(label2->GetUniqueId().Get(), tree.GetId(label2)); + EXPECT_EQ(textfield->GetUniqueId().Get(), tree.GetId(textfield)); + + // Reverse ID lookups work. + EXPECT_EQ(root, tree.GetFromId(root->GetUniqueId().Get())); + EXPECT_EQ(label1, tree.GetFromId(label1->GetUniqueId().Get())); + EXPECT_EQ(label2, tree.GetFromId(label2->GetUniqueId().Get())); + EXPECT_EQ(textfield, tree.GetFromId(textfield->GetUniqueId().Get())); + + // Validity. + EXPECT_TRUE(tree.IsValid(root)); + EXPECT_FALSE(tree.IsValid(nullptr)); + + // Comparisons. + EXPECT_TRUE(tree.IsEqual(label1, label1)); + EXPECT_FALSE(tree.IsEqual(label1, label2)); + EXPECT_FALSE(tree.IsEqual(label1, nullptr)); + EXPECT_FALSE(tree.IsEqual(nullptr, label1)); + + // Null pointers is the null value. + EXPECT_EQ(nullptr, tree.GetNull()); +} + +TEST_F(AXTreeSourceViewsTest, GetTreeDataWithFocus) { + AXAuraObjCache* cache = AXAuraObjCache::GetInstance(); + TestAXTreeSourceViews tree(cache->GetOrCreate(widget_.get())); + textfield_->RequestFocus(); + + ui::AXTreeData tree_data; + tree.GetTreeData(&tree_data); + EXPECT_TRUE(tree_data.loaded); + EXPECT_EQ(cache->GetID(textfield_), tree_data.focus_id); +} + +TEST_F(AXTreeSourceViewsTest, IgnoredView) { + View* ignored_view = new View(); + ignored_view->GetViewAccessibility().set_is_ignored(true); + widget_->GetContentsView()->AddChildView(ignored_view); + + AXAuraObjCache* cache = AXAuraObjCache::GetInstance(); + TestAXTreeSourceViews tree(cache->GetOrCreate(widget_.get())); + EXPECT_FALSE(tree.IsValid(cache->GetOrCreate(ignored_view))); +} + +} // namespace +} // namespace views diff --git a/chromium/ui/views/accessibility/ax_view_obj_wrapper.cc b/chromium/ui/views/accessibility/ax_view_obj_wrapper.cc index 2645da529ef..4d1a9867730 100644 --- a/chromium/ui/views/accessibility/ax_view_obj_wrapper.cc +++ b/chromium/ui/views/accessibility/ax_view_obj_wrapper.cc @@ -4,10 +4,8 @@ #include "ui/views/accessibility/ax_view_obj_wrapper.h" -#include "base/strings/utf_string_conversions.h" #include "ui/accessibility/ax_action_data.h" #include "ui/accessibility/ax_node_data.h" -#include "ui/events/event_utils.h" #include "ui/views/accessibility/ax_aura_obj_cache.h" #include "ui/views/accessibility/view_accessibility.h" #include "ui/views/view.h" @@ -22,6 +20,10 @@ AXViewObjWrapper::AXViewObjWrapper(View* view) : view_(view) { AXViewObjWrapper::~AXViewObjWrapper() {} +bool AXViewObjWrapper::IsIgnored() { + return view_->GetViewAccessibility().is_ignored(); +} + AXAuraObjWrapper* AXViewObjWrapper::GetParent() { AXAuraObjCache* cache = AXAuraObjCache::GetInstance(); if (view_->parent()) @@ -30,7 +32,7 @@ AXAuraObjWrapper* AXViewObjWrapper::GetParent() { if (view_->GetWidget()) return cache->GetOrCreate(view_->GetWidget()); - return NULL; + return nullptr; } void AXViewObjWrapper::GetChildren( diff --git a/chromium/ui/views/accessibility/ax_view_obj_wrapper.h b/chromium/ui/views/accessibility/ax_view_obj_wrapper.h index c14058a497c..eff142fc74b 100644 --- a/chromium/ui/views/accessibility/ax_view_obj_wrapper.h +++ b/chromium/ui/views/accessibility/ax_view_obj_wrapper.h @@ -22,6 +22,7 @@ class AXViewObjWrapper : public AXAuraObjWrapper { View* view() { return view_; } // AXAuraObjWrapper overrides. + bool IsIgnored() override; AXAuraObjWrapper* GetParent() override; void GetChildren(std::vector<AXAuraObjWrapper*>* out_children) override; void Serialize(ui::AXNodeData* out_node_data) override; @@ -29,7 +30,7 @@ class AXViewObjWrapper : public AXAuraObjWrapper { bool HandleAccessibleAction(const ui::AXActionData& action) override; private: - View* view_; + View* const view_; DISALLOW_COPY_AND_ASSIGN(AXViewObjWrapper); }; diff --git a/chromium/ui/views/accessibility/ax_widget_obj_wrapper.cc b/chromium/ui/views/accessibility/ax_widget_obj_wrapper.cc index 43eb649aa24..0192ff9f743 100644 --- a/chromium/ui/views/accessibility/ax_widget_obj_wrapper.cc +++ b/chromium/ui/views/accessibility/ax_widget_obj_wrapper.cc @@ -8,7 +8,6 @@ #include "ui/accessibility/ax_node_data.h" #include "ui/views/accessibility/ax_aura_obj_cache.h" #include "ui/views/accessibility/ax_aura_obj_wrapper.h" -#include "ui/views/accessibility/view_accessibility.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_delegate.h" @@ -27,6 +26,10 @@ AXWidgetObjWrapper::~AXWidgetObjWrapper() { widget_ = NULL; } +bool AXWidgetObjWrapper::IsIgnored() { + return false; +} + AXAuraObjWrapper* AXWidgetObjWrapper::GetParent() { return AXAuraObjCache::GetInstance()->GetOrCreate(widget_->GetNativeView()); } diff --git a/chromium/ui/views/accessibility/ax_widget_obj_wrapper.h b/chromium/ui/views/accessibility/ax_widget_obj_wrapper.h index 6cc11bc27aa..5110cfe5981 100644 --- a/chromium/ui/views/accessibility/ax_widget_obj_wrapper.h +++ b/chromium/ui/views/accessibility/ax_widget_obj_wrapper.h @@ -25,6 +25,7 @@ class AXWidgetObjWrapper : public AXAuraObjWrapper, ~AXWidgetObjWrapper() override; // AXAuraObjWrapper overrides. + bool IsIgnored() override; AXAuraObjWrapper* GetParent() override; void GetChildren(std::vector<AXAuraObjWrapper*>* out_children) override; void Serialize(ui::AXNodeData* out_node_data) override; diff --git a/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc b/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc index 5a5ca2dd36c..e7bbe189e43 100644 --- a/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc +++ b/chromium/ui/views/accessibility/ax_window_obj_wrapper.cc @@ -58,6 +58,10 @@ AXWindowObjWrapper::~AXWindowObjWrapper() { window_ = NULL; } +bool AXWindowObjWrapper::IsIgnored() { + return false; +} + AXAuraObjWrapper* AXWindowObjWrapper::GetParent() { if (!window_->parent()) return NULL; diff --git a/chromium/ui/views/accessibility/ax_window_obj_wrapper.h b/chromium/ui/views/accessibility/ax_window_obj_wrapper.h index 9904dc471d7..44f3f64eb3c 100644 --- a/chromium/ui/views/accessibility/ax_window_obj_wrapper.h +++ b/chromium/ui/views/accessibility/ax_window_obj_wrapper.h @@ -32,6 +32,7 @@ class AXWindowObjWrapper : public AXAuraObjWrapper, void set_is_alert(bool is_alert) { is_alert_ = is_alert; } // AXAuraObjWrapper overrides. + bool IsIgnored() override; AXAuraObjWrapper* GetParent() override; void GetChildren(std::vector<AXAuraObjWrapper*>* out_children) override; void Serialize(ui::AXNodeData* out_node_data) override; diff --git a/chromium/ui/views/accessibility/native_view_accessibility.h b/chromium/ui/views/accessibility/native_view_accessibility.h deleted file mode 100644 index d2ed8c176f9..00000000000 --- a/chromium/ui/views/accessibility/native_view_accessibility.h +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2017 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_NATIVE_VIEW_ACCESSIBILITY_H_ -#define UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_H_ - -#include <memory> - -#include "base/macros.h" -#include "ui/accessibility/ax_enums.mojom.h" -#include "ui/gfx/native_widget_types.h" -#include "ui/views/views_export.h" - -namespace views { - -class View; - -// Abstract base for that allows native platform accessibility toolkits to -// interface with a View. -class VIEWS_EXPORT NativeViewAccessibility { - public: - static std::unique_ptr<NativeViewAccessibility> Create(View* view); - - virtual ~NativeViewAccessibility() {} - - virtual gfx::NativeViewAccessible GetNativeObject() = 0; - virtual void NotifyAccessibilityEvent(ax::mojom::Event event_type) = 0; - - protected: - NativeViewAccessibility() {} - - private: - DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibility); -}; - -} // namespace views - -#endif // UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_H_ diff --git a/chromium/ui/views/accessibility/native_view_accessibility_auralinux.h b/chromium/ui/views/accessibility/native_view_accessibility_auralinux.h deleted file mode 100644 index b42fc06133d..00000000000 --- a/chromium/ui/views/accessibility/native_view_accessibility_auralinux.h +++ /dev/null @@ -1,28 +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_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_AURALINUX_H_ -#define UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_AURALINUX_H_ - -#include "base/macros.h" -#include "ui/views/accessibility/native_view_accessibility_base.h" -#include "ui/views/view.h" - -namespace views { - -class NativeViewAccessibilityAuraLinux : public NativeViewAccessibilityBase { - public: - NativeViewAccessibilityAuraLinux(View* view); - ~NativeViewAccessibilityAuraLinux() override; - - // NativeViewAccessibilityBase: - gfx::NativeViewAccessible GetParent() override; - - private: - DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibilityAuraLinux); -}; - -} // namespace views - -#endif // UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_AURALINUX_H_ diff --git a/chromium/ui/views/accessibility/native_view_accessibility_mac.h b/chromium/ui/views/accessibility/native_view_accessibility_mac.h deleted file mode 100644 index 32b02b40b01..00000000000 --- a/chromium/ui/views/accessibility/native_view_accessibility_mac.h +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright 2017 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_NATIVE_VIEW_ACCESSIBILITY_MAC_H_ -#define UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_MAC_H_ - -#include "base/macros.h" -#include "ui/views/accessibility/native_view_accessibility_base.h" - -namespace views { - -// Mac-specific accessibility class for NativeViewAccessibility. -class NativeViewAccessibilityMac : public NativeViewAccessibilityBase { - public: - explicit NativeViewAccessibilityMac(View* view); - - // NativeViewAccessibilityBase: - gfx::NativeViewAccessible GetParent() override; - - DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibilityMac); -}; - -} // namespace views - -#endif // UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_MAC_H_ diff --git a/chromium/ui/views/accessibility/native_view_accessibility_win.h b/chromium/ui/views/accessibility/native_view_accessibility_win.h deleted file mode 100644 index 8f64f68877a..00000000000 --- a/chromium/ui/views/accessibility/native_view_accessibility_win.h +++ /dev/null @@ -1,30 +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_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_WIN_H_ -#define UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_WIN_H_ - -#include "base/macros.h" -#include "ui/views/accessibility/native_view_accessibility_base.h" -#include "ui/views/view.h" - -namespace views { - -class NativeViewAccessibilityWin : public NativeViewAccessibilityBase { - public: - NativeViewAccessibilityWin(View* view); - ~NativeViewAccessibilityWin() override; - - // NativeViewAccessibilityBase: - gfx::NativeViewAccessible GetParent() override; - gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override; - gfx::Rect GetClippedScreenBoundsRect() const override; - gfx::Rect GetUnclippedScreenBoundsRect() const override; - - DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibilityWin); -}; - -} // namespace views - -#endif // UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_WIN_H_ diff --git a/chromium/ui/views/accessibility/view_accessibility.cc b/chromium/ui/views/accessibility/view_accessibility.cc index 91a0db6df8a..db7109d7113 100644 --- a/chromium/ui/views/accessibility/view_accessibility.cc +++ b/chromium/ui/views/accessibility/view_accessibility.cc @@ -5,6 +5,7 @@ #include "ui/views/accessibility/view_accessibility.h" #include "base/strings/utf_string_conversions.h" +#include "ui/accessibility/platform/ax_platform_node.h" #include "ui/base/ui_features.h" #include "ui/views/view.h" #include "ui/views/widget/widget.h" @@ -42,7 +43,7 @@ std::unique_ptr<ViewAccessibility> ViewAccessibility::Create(View* view) { ViewAccessibility::ViewAccessibility(View* view) : owner_view_(view), is_leaf_(false) {} -ViewAccessibility::~ViewAccessibility() {} +ViewAccessibility::~ViewAccessibility() = default; const ui::AXUniqueId& ViewAccessibility::GetUniqueId() const { return unique_id_; diff --git a/chromium/ui/views/accessibility/view_accessibility.h b/chromium/ui/views/accessibility/view_accessibility.h index 6a366b4cb6f..903c4e5d2c6 100644 --- a/chromium/ui/views/accessibility/view_accessibility.h +++ b/chromium/ui/views/accessibility/view_accessibility.h @@ -8,6 +8,7 @@ #include <memory> #include "base/macros.h" +#include "build/build_config.h" #include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_node_data.h" #include "ui/accessibility/platform/ax_unique_id.h" @@ -20,14 +21,13 @@ class View; // An object that manages the accessibility interface for a View. // -// The default accessibility properties of a View is determined by -// calling View::GetAccessibleNodeData(), which is overridden by many -// View subclasses. ViewAccessibility lets you override these for a -// particular view. +// The default accessibility properties of a View is determined by calling +// |View::GetAccessibleNodeData()|, which is overridden by many |View| +// subclasses. |ViewAccessibility| lets you override these for a particular +// view. // -// On some platforms, subclasses of ViewAccessibility own the -// AXPlatformNode that implements the native accessibility APIs on that -// platform. +// In most cases, subclasses of |ViewAccessibility| own the |AXPlatformNode| +// that implements the native accessibility APIs on a specific platform. class VIEWS_EXPORT ViewAccessibility { public: static std::unique_ptr<ViewAccessibility> Create(View* view); @@ -51,16 +51,19 @@ class VIEWS_EXPORT ViewAccessibility { void OverrideDescription(const std::string& description); void OverrideIsLeaf(); // Force this node to be treated as a leaf node. - virtual void OnAutofillShown(){}; - virtual void OnAutofillHidden(){}; - virtual gfx::NativeViewAccessible GetNativeObject(); virtual void NotifyAccessibilityEvent(ax::mojom::Event event_type) {} +#if defined(OS_MACOSX) + virtual void AnnounceText(base::string16& text) {} +#endif virtual const ui::AXUniqueId& GetUniqueId() const; bool IsLeaf() const; + bool is_ignored() const { return is_ignored_; } + void set_is_ignored(bool ignored) { is_ignored_ = ignored; } + protected: explicit ViewAccessibility(View* view); @@ -78,6 +81,14 @@ class VIEWS_EXPORT ViewAccessibility { bool is_leaf_; + // When true the view is ignored when generating the AX node hierarchy, but + // its children are included. For example, if you created a custom table with + // the digits 1 - 9 arranged in a 3 x 3 grid, marking the table and rows + // "ignored" would mean that the digits 1 - 9 would appear as if they were + // immediate children of the root. Likewise "internal" container views can be + // ignored, like a Widget's RootView, ClientView, etc. + bool is_ignored_ = false; + DISALLOW_COPY_AND_ASSIGN(ViewAccessibility); }; diff --git a/chromium/ui/views/accessibility/native_view_accessibility_base.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc index d22d3778b41..10cc9408045 100644 --- a/chromium/ui/views/accessibility/native_view_accessibility_base.cc +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.cc @@ -2,11 +2,11 @@ // 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_ax_platform_node_delegate.h" + #include <map> #include <memory> -#include "ui/views/accessibility/native_view_accessibility_base.h" - #include "base/lazy_instance.h" #include "base/threading/thread_task_runner_handle.h" #include "ui/accessibility/platform/ax_platform_node.h" @@ -99,9 +99,9 @@ void FlushQueue() { } // namespace // static -int32_t NativeViewAccessibilityBase::fake_focus_view_id_ = 0; +int ViewAXPlatformNodeDelegate::menu_depth_ = 0; -NativeViewAccessibilityBase::NativeViewAccessibilityBase(View* view) +ViewAXPlatformNodeDelegate::ViewAXPlatformNodeDelegate(View* view) : ViewAccessibility(view) { ax_node_ = ui::AXPlatformNode::Create(this); DCHECK(ax_node_); @@ -116,16 +116,19 @@ NativeViewAccessibilityBase::NativeViewAccessibilityBase(View* view) g_unique_id_to_ax_platform_node.Get()[GetUniqueId().Get()] = ax_node_; } -NativeViewAccessibilityBase::~NativeViewAccessibilityBase() { +ViewAXPlatformNodeDelegate::~ViewAXPlatformNodeDelegate() { + if (ui::AXPlatformNode::GetPopupFocusOverride() == GetNativeObject()) + ui::AXPlatformNode::SetPopupFocusOverride(nullptr); + g_unique_id_to_ax_platform_node.Get().erase(GetUniqueId().Get()); ax_node_->Destroy(); } -gfx::NativeViewAccessible NativeViewAccessibilityBase::GetNativeObject() { +gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetNativeObject() { return ax_node_->GetNativeViewAccessible(); } -void NativeViewAccessibilityBase::NotifyAccessibilityEvent( +void ViewAXPlatformNodeDelegate::NotifyAccessibilityEvent( ax::mojom::Event event_type) { if (g_is_queueing_events) { g_event_queue.Get().push_back({event_type, GetUniqueId().Get()}); @@ -134,20 +137,63 @@ void NativeViewAccessibilityBase::NotifyAccessibilityEvent( ax_node_->NotifyAccessibilityEvent(event_type); - // A focus context event is intended to send a focus event and a delay - // before the next focus event. It makes sense to delay the entire next - // synchronous batch of next events so that ordering remains the same. - if (event_type == ax::mojom::Event::kFocusContext) { - // Begin queueing subsequent events and flush queue asynchronously. - g_is_queueing_events = true; - base::OnceCallback<void()> cb = base::BindOnce(&FlushQueue); - base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(cb)); + // Some events have special handling. + switch (event_type) { + case ax::mojom::Event::kMenuStart: + OnMenuStart(); + break; + case ax::mojom::Event::kMenuEnd: + OnMenuEnd(); + break; + case ax::mojom::Event::kSelection: + if (menu_depth_ && GetData().role == ax::mojom::Role::kMenuItem) + OnMenuItemActive(); + break; + case ax::mojom::Event::kFocusContext: { + // A focus context event is intended to send a focus event and a delay + // before the next focus event. It makes sense to delay the entire next + // synchronous batch of next events so that ordering remains the same. + // Begin queueing subsequent events and flush queue asynchronously. + g_is_queueing_events = true; + base::OnceCallback<void()> cb = base::BindOnce(&FlushQueue); + base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(cb)); + break; + } + default: + break; } } +#if defined(OS_MACOSX) +void ViewAXPlatformNodeDelegate::AnnounceText(base::string16& text) { + ax_node_->AnnounceText(text); +} +#endif + +void ViewAXPlatformNodeDelegate::OnMenuItemActive() { + // When a native menu is shown and has an item selected, treat it and the + // currently selected item as focused, even though the actual focus is in the + // browser's currently focused textfield. + ui::AXPlatformNode::SetPopupFocusOverride( + ax_node_->GetNativeViewAccessible()); +} + +void ViewAXPlatformNodeDelegate::OnMenuStart() { + ++menu_depth_; +} + +void ViewAXPlatformNodeDelegate::OnMenuEnd() { + // When a native menu is hidden, restore accessibility focus to the current + // focus in the document. + if (menu_depth_ >= 1) + --menu_depth_; + if (menu_depth_ == 0) + ui::AXPlatformNode::SetPopupFocusOverride(nullptr); +} + // ui::AXPlatformNodeDelegate -const ui::AXNodeData& NativeViewAccessibilityBase::GetData() const { +const ui::AXNodeData& ViewAXPlatformNodeDelegate::GetData() const { // Clear it, then populate it. data_ = ui::AXNodeData(); GetAccessibleNodeData(&data_); @@ -175,12 +221,7 @@ const ui::AXNodeData& NativeViewAccessibilityBase::GetData() const { return data_; } -const ui::AXTreeData& NativeViewAccessibilityBase::GetTreeData() const { - CR_DEFINE_STATIC_LOCAL(ui::AXTreeData, empty_data, ()); - return empty_data; -} - -int NativeViewAccessibilityBase::GetChildCount() { +int ViewAXPlatformNodeDelegate::GetChildCount() { if (IsLeaf()) return 0; int child_count = view()->child_count(); @@ -192,7 +233,7 @@ int NativeViewAccessibilityBase::GetChildCount() { return child_count; } -gfx::NativeViewAccessible NativeViewAccessibilityBase::ChildAtIndex(int index) { +gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::ChildAtIndex(int index) { if (IsLeaf()) return nullptr; @@ -211,13 +252,13 @@ gfx::NativeViewAccessible NativeViewAccessibilityBase::ChildAtIndex(int index) { return nullptr; } -gfx::NativeWindow NativeViewAccessibilityBase::GetTopLevelWidget() { +gfx::NativeWindow ViewAXPlatformNodeDelegate::GetTopLevelWidget() { if (view()->GetWidget()) return view()->GetWidget()->GetTopLevelWidget()->GetNativeWindow(); return nullptr; } -gfx::NativeViewAccessible NativeViewAccessibilityBase::GetParent() { +gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetParent() { if (view()->parent()) return view()->parent()->GetNativeViewAccessible(); @@ -230,17 +271,17 @@ gfx::NativeViewAccessible NativeViewAccessibilityBase::GetParent() { return nullptr; } -gfx::Rect NativeViewAccessibilityBase::GetClippedScreenBoundsRect() const { +gfx::Rect ViewAXPlatformNodeDelegate::GetClippedScreenBoundsRect() const { // We could optionally add clipping here if ever needed. return view()->GetBoundsInScreen(); } -gfx::Rect NativeViewAccessibilityBase::GetUnclippedScreenBoundsRect() const { +gfx::Rect ViewAXPlatformNodeDelegate::GetUnclippedScreenBoundsRect() const { return view()->GetBoundsInScreen(); } -gfx::NativeViewAccessible NativeViewAccessibilityBase::HitTestSync(int x, - int y) { +gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::HitTestSync(int x, + int y) { if (!view() || !view()->GetWidget()) return nullptr; @@ -281,110 +322,42 @@ gfx::NativeViewAccessible NativeViewAccessibilityBase::HitTestSync(int x, return GetNativeObject(); } -void NativeViewAccessibilityBase::OnAutofillShown() { - // When the autofill is shown, treat it and the currently selected item as - // focused, even though the actual focus is in the browser's currently - // focused textfield. - DCHECK(!fake_focus_view_id_) << "Cannot have more that one fake focus."; - fake_focus_view_id_ = GetUniqueId().Get(); - ui::AXPlatformNode::OnAutofillShown(); -} - -void NativeViewAccessibilityBase::OnAutofillHidden() { - DCHECK(fake_focus_view_id_) << "No autofill fake focus set."; - DCHECK_EQ(fake_focus_view_id_, GetUniqueId().Get()) - << "Cannot clear autofill fake focus on an object that did not have it."; - fake_focus_view_id_ = 0; - ui::AXPlatformNode::OnAutofillHidden(); -} +gfx::NativeViewAccessible ViewAXPlatformNodeDelegate::GetFocus() { + gfx::NativeViewAccessible focus_override = + ui::AXPlatformNode::GetPopupFocusOverride(); + if (focus_override) + return focus_override; -gfx::NativeViewAccessible NativeViewAccessibilityBase::GetFocus() { FocusManager* focus_manager = view()->GetFocusManager(); View* focused_view = focus_manager ? focus_manager->GetFocusedView() : nullptr; - if (fake_focus_view_id_) { - ui::AXPlatformNode* ax_node = PlatformNodeFromNodeID(fake_focus_view_id_); - if (ax_node) - return ax_node->GetNativeViewAccessible(); - } + return focused_view ? focused_view->GetNativeViewAccessible() : nullptr; } -ui::AXPlatformNode* NativeViewAccessibilityBase::GetFromNodeID(int32_t id) { +ui::AXPlatformNode* ViewAXPlatformNodeDelegate::GetFromNodeID(int32_t id) { return PlatformNodeFromNodeID(id); } -int NativeViewAccessibilityBase::GetIndexInParent() const { - return -1; -} - -gfx::AcceleratedWidget -NativeViewAccessibilityBase::GetTargetForNativeAccessibilityEvent() { - return gfx::kNullAcceleratedWidget; -} - -int NativeViewAccessibilityBase::GetTableRowCount() const { - return 0; -} - -int NativeViewAccessibilityBase::GetTableColCount() const { - return 0; -} - -std::vector<int32_t> NativeViewAccessibilityBase::GetColHeaderNodeIds( - int32_t col_index) const { - return std::vector<int32_t>(); -} - -std::vector<int32_t> NativeViewAccessibilityBase::GetRowHeaderNodeIds( - int32_t row_index) const { - return std::vector<int32_t>(); -} - -int32_t NativeViewAccessibilityBase::GetCellId(int32_t row_index, - int32_t col_index) const { - return 0; -} - -int32_t NativeViewAccessibilityBase::CellIdToIndex(int32_t cell_id) const { - return -1; -} - -int32_t NativeViewAccessibilityBase::CellIndexToId(int32_t cell_index) const { - return 0; -} - -bool NativeViewAccessibilityBase::AccessibilityPerformAction( +bool ViewAXPlatformNodeDelegate::AccessibilityPerformAction( const ui::AXActionData& data) { return view()->HandleAccessibleAction(data); } -bool NativeViewAccessibilityBase::ShouldIgnoreHoveredStateForTesting() { +bool ViewAXPlatformNodeDelegate::ShouldIgnoreHoveredStateForTesting() { return false; } -bool NativeViewAccessibilityBase::IsOffscreen() const { +bool ViewAXPlatformNodeDelegate::IsOffscreen() const { // TODO: need to implement. return false; } -std::set<int32_t> NativeViewAccessibilityBase::GetReverseRelations( - ax::mojom::IntAttribute attr, - int32_t dst_id) { - return std::set<int32_t>(); -} - -std::set<int32_t> NativeViewAccessibilityBase::GetReverseRelations( - ax::mojom::IntListAttribute attr, - int32_t dst_id) { - return std::set<int32_t>(); -} - -const ui::AXUniqueId& NativeViewAccessibilityBase::GetUniqueId() const { +const ui::AXUniqueId& ViewAXPlatformNodeDelegate::GetUniqueId() const { return ViewAccessibility::GetUniqueId(); } -void NativeViewAccessibilityBase::PopulateChildWidgetVector( +void ViewAXPlatformNodeDelegate::PopulateChildWidgetVector( std::vector<Widget*>* result_child_widgets) { // Only attach child widgets to the root view. Widget* widget = view()->GetWidget(); diff --git a/chromium/ui/views/accessibility/native_view_accessibility_base.h b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h index 11f0d7e778b..f3829054acc 100644 --- a/chromium/ui/views/accessibility/native_view_accessibility_base.h +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate.h @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#ifndef UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_BASE_H_ -#define UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_BASE_H_ +#ifndef UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_H_ +#define UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_H_ #include <memory> @@ -13,7 +13,7 @@ #include "ui/accessibility/ax_node_data.h" #include "ui/accessibility/ax_tree_data.h" #include "ui/accessibility/platform/ax_platform_node.h" -#include "ui/accessibility/platform/ax_platform_node_delegate.h" +#include "ui/accessibility/platform/ax_platform_node_delegate_base.h" #include "ui/accessibility/platform/ax_unique_id.h" #include "ui/gfx/native_widget_types.h" #include "ui/views/accessibility/view_accessibility.h" @@ -26,23 +26,24 @@ class View; class Widget; // Shared base class for platforms that require an implementation of -// NativeViewAccessibility to interface with the native accessibility toolkit. -// This class owns the AXPlatformNode, which implements those native APIs. -class VIEWS_EXPORT NativeViewAccessibilityBase +// |ViewAXPlatformNodeDelegate| to interface with the native accessibility +// toolkit. This class owns the |AXPlatformNode|, which implements those native +// APIs. +class VIEWS_EXPORT ViewAXPlatformNodeDelegate : public ViewAccessibility, - public ui::AXPlatformNodeDelegate { + public ui::AXPlatformNodeDelegateBase { public: - ~NativeViewAccessibilityBase() override; + ~ViewAXPlatformNodeDelegate() override; // ViewAccessibility: gfx::NativeViewAccessible GetNativeObject() override; void NotifyAccessibilityEvent(ax::mojom::Event event_type) override; - void OnAutofillShown() override; - void OnAutofillHidden() override; +#if defined(OS_MACOSX) + void AnnounceText(base::string16& text) override; +#endif // ui::AXPlatformNodeDelegate const ui::AXNodeData& GetData() const override; - const ui::AXTreeData& GetTreeData() const override; int GetChildCount() override; gfx::NativeViewAccessible ChildAtIndex(int index) override; gfx::NativeWindow GetTopLevelWidget() override; @@ -52,45 +53,34 @@ class VIEWS_EXPORT NativeViewAccessibilityBase gfx::NativeViewAccessible HitTestSync(int x, int y) override; gfx::NativeViewAccessible GetFocus() override; ui::AXPlatformNode* GetFromNodeID(int32_t id) override; - int GetIndexInParent() const override; - gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override; - int GetTableRowCount() const override; - int GetTableColCount() const override; - std::vector<int32_t> GetColHeaderNodeIds(int32_t col_index) const override; - std::vector<int32_t> GetRowHeaderNodeIds(int32_t row_index) const override; - int32_t GetCellId(int32_t row_index, int32_t col_index) const override; - int32_t CellIdToIndex(int32_t cell_id) const override; - int32_t CellIndexToId(int32_t cell_index) const override; bool AccessibilityPerformAction(const ui::AXActionData& data) override; bool ShouldIgnoreHoveredStateForTesting() override; bool IsOffscreen() const override; const ui::AXUniqueId& GetUniqueId() const override; // Also in ViewAccessibility - std::set<int32_t> GetReverseRelations(ax::mojom::IntAttribute attr, - int32_t dst_id) override; - std::set<int32_t> GetReverseRelations(ax::mojom::IntListAttribute attr, - int32_t dst_id) override; protected: - explicit NativeViewAccessibilityBase(View* view); + explicit ViewAXPlatformNodeDelegate(View* view); private: void PopulateChildWidgetVector(std::vector<Widget*>* result_child_widgets); + void OnMenuItemActive(); + void OnMenuStart(); + void OnMenuEnd(); + // We own this, but it is reference-counted on some platforms so we can't use // a scoped_ptr. It is dereferenced in the destructor. ui::AXPlatformNode* ax_node_; mutable ui::AXNodeData data_; - // This allows UI popups like autofill to act as if they are focused in the - // exposed platform accessibility API, even though true focus remains in - // underlying content. - static int32_t fake_focus_view_id_; + // Levels of menu are currently open, e.g. 0: none, 1: top, 2: submenu ... + static int32_t menu_depth_; - DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibilityBase); + DISALLOW_COPY_AND_ASSIGN(ViewAXPlatformNodeDelegate); }; } // namespace views -#endif // UI_VIEWS_ACCESSIBILITY_NATIVE_VIEW_ACCESSIBILITY_BASE_H_ +#endif // UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_H_ diff --git a/chromium/ui/views/accessibility/native_view_accessibility_auralinux.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.cc index a098c1b5877..4a0df2df5cc 100644 --- a/chromium/ui/views/accessibility/native_view_accessibility_auralinux.cc +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "ui/views/accessibility/native_view_accessibility_auralinux.h" +#include "ui/views/accessibility/view_ax_platform_node_delegate_auralinux.h" #include <algorithm> #include <memory> @@ -15,8 +15,9 @@ #include "ui/accessibility/ax_enums.mojom.h" #include "ui/accessibility/ax_node_data.h" #include "ui/accessibility/platform/ax_platform_node_auralinux.h" -#include "ui/accessibility/platform/ax_platform_node_delegate.h" +#include "ui/accessibility/platform/ax_platform_node_delegate_base.h" #include "ui/gfx/native_widget_types.h" +#include "ui/views/view.h" #include "ui/views/views_delegate.h" #include "ui/views/widget/widget.h" #include "ui/views/widget/widget_observer.h" @@ -31,9 +32,8 @@ namespace { // object. Every time we create an accessibility object for a View, we add its // top-level widget to a vector so we can return the list of all top-level // windows as children of this application object. -class AuraLinuxApplication - : public ui::AXPlatformNodeDelegate, - public WidgetObserver { +class AuraLinuxApplication : public ui::AXPlatformNodeDelegateBase, + public WidgetObserver { public: // Get the single instance of this class. static AuraLinuxApplication* GetInstance() { @@ -73,20 +73,7 @@ class AuraLinuxApplication const ui::AXNodeData& GetData() const override { return data_; } - const ui::AXTreeData& GetTreeData() const override { - CR_DEFINE_STATIC_LOCAL(ui::AXTreeData, empty_data, ()); - return empty_data; - } - - gfx::NativeWindow GetTopLevelWidget() override { return nullptr; } - - gfx::NativeViewAccessible GetParent() override { - return nullptr; - } - - int GetChildCount() override { - return static_cast<int>(widgets_.size()); - } + int GetChildCount() override { return static_cast<int>(widgets_.size()); } gfx::NativeViewAccessible ChildAtIndex(int index) override { if (index < 0 || index >= GetChildCount()) @@ -97,68 +84,6 @@ class AuraLinuxApplication return widget->GetRootView()->GetNativeViewAccessible(); } - gfx::Rect GetClippedScreenBoundsRect() const override { return gfx::Rect(); } - gfx::Rect GetUnclippedScreenBoundsRect() const override { - return gfx::Rect(); - } - - gfx::NativeViewAccessible HitTestSync(int x, int y) override { - return nullptr; - } - - gfx::NativeViewAccessible GetFocus() override { - return nullptr; - } - - bool IsOffscreen() const override { - // TODO: need to implement. - return false; - } - - int GetIndexInParent() const override { return -1; } - - ui::AXPlatformNode* GetFromNodeID(int32_t id) override { return nullptr; } - - gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override { - return gfx::kNullAcceleratedWidget; - } - - int GetTableRowCount() const override { return 0; } - - int GetTableColCount() const override { return 0; } - - std::vector<int32_t> GetColHeaderNodeIds(int32_t col_index) const override { - return std::vector<int32_t>(); - } - - std::vector<int32_t> GetRowHeaderNodeIds(int32_t row_index) const override { - return std::vector<int32_t>(); - } - - int32_t GetCellId(int32_t row_index, int32_t col_index) const override { - return -1; - } - - int32_t CellIdToIndex(int32_t cell_id) const override { return -1; } - - int32_t CellIndexToId(int32_t cell_index) const override { return -1; } - - bool AccessibilityPerformAction(const ui::AXActionData& data) override { - return false; - } - - bool ShouldIgnoreHoveredStateForTesting() override { return false; } - - std::set<int32_t> GetReverseRelations(ax::mojom::IntAttribute attr, - int32_t dst_id) override { - return std::set<int32_t>(); - } - - std::set<int32_t> GetReverseRelations(ax::mojom::IntListAttribute attr, - int32_t dst_id) override { - return std::set<int32_t>(); - } - private: friend struct base::DefaultSingletonTraits<AuraLinuxApplication>; @@ -194,17 +119,18 @@ class AuraLinuxApplication // static std::unique_ptr<ViewAccessibility> ViewAccessibility::Create(View* view) { AuraLinuxApplication::GetInstance()->RegisterWidget(view->GetWidget()); - return std::make_unique<NativeViewAccessibilityAuraLinux>(view); + return std::make_unique<ViewAXPlatformNodeDelegateAuraLinux>(view); } -NativeViewAccessibilityAuraLinux::NativeViewAccessibilityAuraLinux(View* view) - : NativeViewAccessibilityBase(view) {} +ViewAXPlatformNodeDelegateAuraLinux::ViewAXPlatformNodeDelegateAuraLinux( + View* view) + : ViewAXPlatformNodeDelegate(view) {} -NativeViewAccessibilityAuraLinux::~NativeViewAccessibilityAuraLinux() { -} +ViewAXPlatformNodeDelegateAuraLinux::~ViewAXPlatformNodeDelegateAuraLinux() = + default; -gfx::NativeViewAccessible NativeViewAccessibilityAuraLinux::GetParent() { - gfx::NativeViewAccessible parent = NativeViewAccessibilityBase::GetParent(); +gfx::NativeViewAccessible ViewAXPlatformNodeDelegateAuraLinux::GetParent() { + gfx::NativeViewAccessible parent = ViewAXPlatformNodeDelegate::GetParent(); if (!parent) parent = AuraLinuxApplication::GetInstance()->GetNativeViewAccessible(); return parent; diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.h b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.h new file mode 100644 index 00000000000..d7bac7f22b2 --- /dev/null +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_auralinux.h @@ -0,0 +1,29 @@ +// 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_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_AURALINUX_H_ +#define UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_AURALINUX_H_ + +#include "base/macros.h" +#include "ui/views/accessibility/view_ax_platform_node_delegate.h" + +namespace views { + +class View; + +class ViewAXPlatformNodeDelegateAuraLinux : public ViewAXPlatformNodeDelegate { + public: + explicit ViewAXPlatformNodeDelegateAuraLinux(View* view); + ~ViewAXPlatformNodeDelegateAuraLinux() override; + + // |ViewAXPlatformNodeDelegate| overrides: + gfx::NativeViewAccessible GetParent() override; + + private: + DISALLOW_COPY_AND_ASSIGN(ViewAXPlatformNodeDelegateAuraLinux); +}; + +} // namespace views + +#endif // UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_AURALINUX_H_ diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_mac.h b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_mac.h new file mode 100644 index 00000000000..75f679979b2 --- /dev/null +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_mac.h @@ -0,0 +1,28 @@ +// Copyright 2017 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_AX_PLATFORM_NODE_DELEGATE_MAC_H_ +#define UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_MAC_H_ + +#include "base/macros.h" +#include "ui/views/accessibility/view_ax_platform_node_delegate.h" + +namespace views { + +// Mac-specific accessibility class for |ViewAXPlatformNodeDelegate|. +class ViewAXPlatformNodeDelegateMac : public ViewAXPlatformNodeDelegate { + public: + explicit ViewAXPlatformNodeDelegateMac(View* view); + ~ViewAXPlatformNodeDelegateMac() override; + + // |ViewAXPlatformNodeDelegate| overrides: + gfx::NativeViewAccessible GetParent() override; + + private: + DISALLOW_COPY_AND_ASSIGN(ViewAXPlatformNodeDelegateMac); +}; + +} // namespace views + +#endif // UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_MAC_H_ diff --git a/chromium/ui/views/accessibility/native_view_accessibility_mac.mm b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_mac.mm index be5b1978fde..993119fb012 100644 --- a/chromium/ui/views/accessibility/native_view_accessibility_mac.mm +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_mac.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. -#include "ui/views/accessibility/native_view_accessibility_mac.h" +#include "ui/views/accessibility/view_ax_platform_node_delegate_mac.h" #include <memory> @@ -13,13 +13,15 @@ namespace views { // static std::unique_ptr<ViewAccessibility> ViewAccessibility::Create(View* view) { - return std::make_unique<NativeViewAccessibilityMac>(view); + return std::make_unique<ViewAXPlatformNodeDelegateMac>(view); } -NativeViewAccessibilityMac::NativeViewAccessibilityMac(View* view) - : NativeViewAccessibilityBase(view) {} +ViewAXPlatformNodeDelegateMac::ViewAXPlatformNodeDelegateMac(View* view) + : ViewAXPlatformNodeDelegate(view) {} -gfx::NativeViewAccessible NativeViewAccessibilityMac::GetParent() { +ViewAXPlatformNodeDelegateMac::~ViewAXPlatformNodeDelegateMac() = default; + +gfx::NativeViewAccessible ViewAXPlatformNodeDelegateMac::GetParent() { if (view()->parent()) return view()->parent()->GetNativeViewAccessible(); diff --git a/chromium/ui/views/accessibility/native_view_accessibility_unittest.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc index c4904034e0c..8a702690827 100644 --- a/chromium/ui/views/accessibility/native_view_accessibility_unittest.cc +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc @@ -2,13 +2,14 @@ // 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_ax_platform_node_delegate.h" + #include "base/strings/utf_string_conversions.h" #include "ui/accessibility/ax_node_data.h" #include "ui/gfx/geometry/rect_conversions.h" #include "ui/views/accessibility/ax_aura_obj_cache.h" #include "ui/views/accessibility/ax_aura_obj_wrapper.h" #include "ui/views/accessibility/ax_widget_obj_wrapper.h" -#include "ui/views/accessibility/native_view_accessibility_base.h" #include "ui/views/controls/button/button.h" #include "ui/views/controls/label.h" #include "ui/views/test/views_test_base.h" @@ -17,21 +18,23 @@ namespace views { namespace test { -class NativeViewAccessibilityTest; - namespace { class TestButton : public Button { public: TestButton() : Button(NULL) {} + ~TestButton() override = default; + + private: + DISALLOW_COPY_AND_ASSIGN(TestButton); }; } // namespace -class NativeViewAccessibilityTest : public ViewsTestBase { +class ViewAXPlatformNodeDelegateTest : public ViewsTestBase { public: - NativeViewAccessibilityTest() {} - ~NativeViewAccessibilityTest() override {} + ViewAXPlatformNodeDelegateTest() = default; + ~ViewAXPlatformNodeDelegateTest() override = default; void SetUp() override { ViewsTestBase::SetUp(); @@ -57,33 +60,33 @@ class NativeViewAccessibilityTest : public ViewsTestBase { ViewsTestBase::TearDown(); } - NativeViewAccessibilityBase* button_accessibility() { - return static_cast<NativeViewAccessibilityBase*>( + ViewAXPlatformNodeDelegate* button_accessibility() { + return static_cast<ViewAXPlatformNodeDelegate*>( &button_->GetViewAccessibility()); } - NativeViewAccessibilityBase* label_accessibility() { - return static_cast<NativeViewAccessibilityBase*>( + ViewAXPlatformNodeDelegate* label_accessibility() { + return static_cast<ViewAXPlatformNodeDelegate*>( &label_->GetViewAccessibility()); } - bool SetFocused(NativeViewAccessibilityBase* view_accessibility, - bool focused) { + bool SetFocused(ViewAXPlatformNodeDelegate* ax_delegate, bool focused) { ui::AXActionData data; data.action = focused ? ax::mojom::Action::kFocus : ax::mojom::Action::kBlur; - return view_accessibility->AccessibilityPerformAction(data); + return ax_delegate->AccessibilityPerformAction(data); } protected: - Widget* widget_; - TestButton* button_; - Label* label_; + Widget* widget_ = nullptr; + Button* button_ = nullptr; + Label* label_ = nullptr; - DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibilityTest); + private: + DISALLOW_COPY_AND_ASSIGN(ViewAXPlatformNodeDelegateTest); }; -TEST_F(NativeViewAccessibilityTest, RoleShouldMatch) { +TEST_F(ViewAXPlatformNodeDelegateTest, RoleShouldMatch) { EXPECT_EQ(ax::mojom::Role::kButton, button_accessibility()->GetData().role); // Since the label is a subview of |button_|, and the button is keyboard // focusable, the label is assumed to form part of the button and not have a @@ -97,7 +100,7 @@ TEST_F(NativeViewAccessibilityTest, RoleShouldMatch) { label_accessibility()->GetData().role); } -TEST_F(NativeViewAccessibilityTest, BoundsShouldMatch) { +TEST_F(ViewAXPlatformNodeDelegateTest, BoundsShouldMatch) { gfx::Rect bounds = gfx::ToEnclosingRect(button_accessibility()->GetData().location); gfx::Rect screen_bounds = @@ -107,7 +110,7 @@ TEST_F(NativeViewAccessibilityTest, BoundsShouldMatch) { EXPECT_EQ(screen_bounds, bounds); } -TEST_F(NativeViewAccessibilityTest, LabelIsChildOfButton) { +TEST_F(ViewAXPlatformNodeDelegateTest, LabelIsChildOfButton) { // |button_| is focusable, so |label_| (as its child) should be ignored. EXPECT_EQ(View::FocusBehavior::ACCESSIBLE_ONLY, button_->focus_behavior()); EXPECT_EQ(1, button_accessibility()->GetChildCount()); @@ -126,7 +129,7 @@ TEST_F(NativeViewAccessibilityTest, LabelIsChildOfButton) { } // Verify Views with invisible ancestors have ax::mojom::State::kInvisible. -TEST_F(NativeViewAccessibilityTest, InvisibleViews) { +TEST_F(ViewAXPlatformNodeDelegateTest, InvisibleViews) { EXPECT_TRUE(widget_->IsVisible()); EXPECT_FALSE( button_accessibility()->GetData().HasState(ax::mojom::State::kInvisible)); @@ -139,8 +142,9 @@ TEST_F(NativeViewAccessibilityTest, InvisibleViews) { label_accessibility()->GetData().HasState(ax::mojom::State::kInvisible)); } -TEST_F(NativeViewAccessibilityTest, WritableFocus) { - // Make |button_| focusable, and focus/unfocus it via NativeViewAccessibility. +TEST_F(ViewAXPlatformNodeDelegateTest, WritableFocus) { + // Make |button_| focusable, and focus/unfocus it via + // ViewAXPlatformNodeDelegate. button_->SetFocusBehavior(View::FocusBehavior::ALWAYS); EXPECT_EQ(nullptr, button_->GetFocusManager()->GetFocusedView()); EXPECT_EQ(nullptr, button_accessibility()->GetFocus()); @@ -157,28 +161,19 @@ TEST_F(NativeViewAccessibilityTest, WritableFocus) { EXPECT_FALSE(SetFocused(button_accessibility(), true)); } -// Subclass of NativeViewAccessibility that destroys itself when its -// parent widget is destroyed, for the purposes of making sure this -// doesn't lead to a crash. -class TestNativeViewAccessibility : public NativeViewAccessibilityBase { - public: - explicit TestNativeViewAccessibility(View* view) - : NativeViewAccessibilityBase(view) {} -}; - #if defined(USE_AURA) class DerivedTestView : public View { public: DerivedTestView() : View() {} - ~DerivedTestView() override {} + ~DerivedTestView() override = default; void OnBlur() override { SetVisible(false); } }; class AxTestViewsDelegate : public TestViewsDelegate { public: - AxTestViewsDelegate() {} - ~AxTestViewsDelegate() override {} + AxTestViewsDelegate() = default; + ~AxTestViewsDelegate() override = default; void NotifyAccessibilityEvent(View* view, ax::mojom::Event event_type) override { @@ -192,10 +187,10 @@ class AxTestViewsDelegate : public TestViewsDelegate { DISALLOW_COPY_AND_ASSIGN(AxTestViewsDelegate); }; -class AXViewTest : public ViewsTestBase { +class ViewAccessibilityTest : public ViewsTestBase { public: - AXViewTest() {} - ~AXViewTest() override {} + ViewAccessibilityTest() = default; + ~ViewAccessibilityTest() override = default; void SetUp() override { std::unique_ptr<TestViewsDelegate> views_delegate( new AxTestViewsDelegate()); @@ -206,7 +201,7 @@ class AXViewTest : public ViewsTestBase { // Check if the destruction of the widget ends successfully if |view|'s // visibility changed during destruction. -TEST_F(AXViewTest, LayoutCalledInvalidateRootView) { +TEST_F(ViewAccessibilityTest, LayoutCalledInvalidateRootView) { std::unique_ptr<Widget> widget(new Widget); Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; diff --git a/chromium/ui/views/accessibility/native_view_accessibility_win.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win.cc index 7491a60f6f0..68b4118be9d 100644 --- a/chromium/ui/views/accessibility/native_view_accessibility_win.cc +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "ui/views/accessibility/native_view_accessibility_win.h" +#include "ui/views/accessibility/view_ax_platform_node_delegate_win.h" #include <oleacc.h> @@ -24,6 +24,7 @@ #include "ui/base/win/atl_module.h" #include "ui/display/win/screen_win.h" #include "ui/views/controls/button/button.h" +#include "ui/views/view.h" #include "ui/views/widget/widget.h" #include "ui/views/win/hwnd_util.h" #include "ui/wm/core/window_util.h" @@ -48,15 +49,15 @@ aura::Window* GetWindowParentIncludingTransient(aura::Window* window) { // static std::unique_ptr<ViewAccessibility> ViewAccessibility::Create(View* view) { - return std::make_unique<NativeViewAccessibilityWin>(view); + return std::make_unique<ViewAXPlatformNodeDelegateWin>(view); } -NativeViewAccessibilityWin::NativeViewAccessibilityWin(View* view) - : NativeViewAccessibilityBase(view) {} +ViewAXPlatformNodeDelegateWin::ViewAXPlatformNodeDelegateWin(View* view) + : ViewAXPlatformNodeDelegate(view) {} -NativeViewAccessibilityWin::~NativeViewAccessibilityWin() {} +ViewAXPlatformNodeDelegateWin::~ViewAXPlatformNodeDelegateWin() = default; -gfx::NativeViewAccessible NativeViewAccessibilityWin::GetParent() { +gfx::NativeViewAccessible ViewAXPlatformNodeDelegateWin::GetParent() { // If the View has a parent View, return that View's IAccessible. if (view()->parent()) return view()->parent()->GetNativeViewAccessible(); @@ -96,16 +97,16 @@ gfx::NativeViewAccessible NativeViewAccessibilityWin::GetParent() { } gfx::AcceleratedWidget -NativeViewAccessibilityWin::GetTargetForNativeAccessibilityEvent() { +ViewAXPlatformNodeDelegateWin::GetTargetForNativeAccessibilityEvent() { return HWNDForView(view()); } -gfx::Rect NativeViewAccessibilityWin::GetClippedScreenBoundsRect() const { +gfx::Rect ViewAXPlatformNodeDelegateWin::GetClippedScreenBoundsRect() const { // We could optionally add clipping here if ever needed. return GetUnclippedScreenBoundsRect(); } -gfx::Rect NativeViewAccessibilityWin::GetUnclippedScreenBoundsRect() const { +gfx::Rect ViewAXPlatformNodeDelegateWin::GetUnclippedScreenBoundsRect() const { gfx::Rect bounds = view()->GetBoundsInScreen(); return display::win::ScreenWin::DIPToScreenRect(HWNDForView(view()), bounds); } diff --git a/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win.h b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win.h new file mode 100644 index 00000000000..2715eae70bd --- /dev/null +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win.h @@ -0,0 +1,32 @@ +// 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_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_WIN_H_ +#define UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_WIN_H_ + +#include "base/macros.h" +#include "ui/views/accessibility/view_ax_platform_node_delegate.h" + +namespace views { + +class View; + +class ViewAXPlatformNodeDelegateWin : public ViewAXPlatformNodeDelegate { + public: + explicit ViewAXPlatformNodeDelegateWin(View* view); + ~ViewAXPlatformNodeDelegateWin() override; + + // |ViewAXPlatformNodeDelegate| overrides: + gfx::NativeViewAccessible GetParent() override; + gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent() override; + gfx::Rect GetClippedScreenBoundsRect() const override; + gfx::Rect GetUnclippedScreenBoundsRect() const override; + + private: + DISALLOW_COPY_AND_ASSIGN(ViewAXPlatformNodeDelegateWin); +}; + +} // namespace views + +#endif // UI_VIEWS_ACCESSIBILITY_VIEW_AX_PLATFORM_NODE_DELEGATE_WIN_H_ diff --git a/chromium/ui/views/accessibility/native_view_accessibility_win_unittest.cc b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win_unittest.cc index 0b1c5dc7b55..b279a468af7 100644 --- a/chromium/ui/views/accessibility/native_view_accessibility_win_unittest.cc +++ b/chromium/ui/views/accessibility/view_ax_platform_node_delegate_win_unittest.cc @@ -2,13 +2,14 @@ // 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_ax_platform_node_delegate_win.h" + #include <oleacc.h> #include <wrl/client.h> #include "base/win/scoped_bstr.h" #include "base/win/scoped_variant.h" #include "third_party/iaccessible2/ia2_api_all.h" -#include "ui/views/accessibility/native_view_accessibility_base.h" #include "ui/views/controls/label.h" #include "ui/views/controls/scroll_view.h" #include "ui/views/controls/textfield/textfield.h" @@ -43,25 +44,23 @@ bool IsSameObject(T* left, U* right) { } // namespace -class NativeViewAccessibilityWinTest : public ViewsTestBase { +class ViewAXPlatformNodeDelegateWinTest : public ViewsTestBase { public: - NativeViewAccessibilityWinTest() {} - ~NativeViewAccessibilityWinTest() override {} + ViewAXPlatformNodeDelegateWinTest() = default; + ~ViewAXPlatformNodeDelegateWinTest() override = default; protected: void GetIAccessible2InterfaceForView(View* view, IAccessible2_2** result) { ComPtr<IAccessible> view_accessible(view->GetNativeViewAccessible()); ComPtr<IServiceProvider> service_provider; ASSERT_EQ(S_OK, view_accessible.CopyTo(service_provider.GetAddressOf())); - ASSERT_EQ(S_OK, - service_provider->QueryService(IID_IAccessible2_2, result)); + ASSERT_EQ(S_OK, service_provider->QueryService(IID_IAccessible2_2, result)); } }; -TEST_F(NativeViewAccessibilityWinTest, TextfieldAccessibility) { +TEST_F(ViewAXPlatformNodeDelegateWinTest, TextfieldAccessibility) { Widget widget; - Widget::InitParams init_params = - CreateParams(Widget::InitParams::TYPE_POPUP); + Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP); init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; widget.Init(init_params); @@ -88,13 +87,13 @@ TEST_F(NativeViewAccessibilityWinTest, TextfieldAccessibility) { ScopedBstr name; ScopedVariant childid_self(CHILDID_SELF); - ASSERT_EQ(S_OK, textfield_accessible->get_accName( - childid_self, name.Receive())); + ASSERT_EQ(S_OK, + textfield_accessible->get_accName(childid_self, name.Receive())); ASSERT_STREQ(L"Name", name); ScopedBstr value; - ASSERT_EQ(S_OK, textfield_accessible->get_accValue( - childid_self, value.Receive())); + ASSERT_EQ(S_OK, + textfield_accessible->get_accValue(childid_self, value.Receive())); ASSERT_STREQ(L"Value", value); ScopedBstr new_value(L"New value"); @@ -103,7 +102,7 @@ TEST_F(NativeViewAccessibilityWinTest, TextfieldAccessibility) { ASSERT_STREQ(L"New value", textfield->text().c_str()); } -TEST_F(NativeViewAccessibilityWinTest, TextfieldAssociatedLabel) { +TEST_F(ViewAXPlatformNodeDelegateWinTest, TextfieldAssociatedLabel) { Widget widget; Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP); init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; @@ -153,25 +152,25 @@ TEST_F(NativeViewAccessibilityWinTest, TextfieldAssociatedLabel) { EXPECT_EQ(ROLE_SYSTEM_STATICTEXT, V_I4(role.ptr())); } -// A subclass of NativeViewAccessibilityWinTest that we run twice, +// A subclass of ViewAXPlatformNodeDelegateWinTest that we run twice, // first where we create an transient child widget (child = false), the second // time where we create a child widget (child = true). -class NativeViewAccessibilityWinTestWithBoolChildFlag - : public NativeViewAccessibilityWinTest, +class ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag + : public ViewAXPlatformNodeDelegateWinTest, public testing::WithParamInterface<bool> { public: - NativeViewAccessibilityWinTestWithBoolChildFlag() {} - ~NativeViewAccessibilityWinTestWithBoolChildFlag() override {} + ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag() {} + ~ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag() override {} private: - DISALLOW_COPY_AND_ASSIGN(NativeViewAccessibilityWinTestWithBoolChildFlag); + DISALLOW_COPY_AND_ASSIGN(ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag); }; INSTANTIATE_TEST_CASE_P(, - NativeViewAccessibilityWinTestWithBoolChildFlag, + ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag, testing::Bool()); -TEST_P(NativeViewAccessibilityWinTestWithBoolChildFlag, AuraChildWidgets) { +TEST_P(ViewAXPlatformNodeDelegateWinTestWithBoolChildFlag, AuraChildWidgets) { // Create the parent widget. Widget widget; Widget::InitParams init_params = @@ -251,10 +250,9 @@ TEST_P(NativeViewAccessibilityWinTestWithBoolChildFlag, AuraChildWidgets) { } // Flaky on Windows: https://crbug.com/461837. -TEST_F(NativeViewAccessibilityWinTest, DISABLED_RetrieveAllAlerts) { +TEST_F(ViewAXPlatformNodeDelegateWinTest, DISABLED_RetrieveAllAlerts) { Widget widget; - Widget::InitParams init_params = - CreateParams(Widget::InitParams::TYPE_POPUP); + Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP); init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; widget.Init(init_params); @@ -285,7 +283,7 @@ TEST_F(NativeViewAccessibilityWinTest, DISABLED_RetrieveAllAlerts) { IUnknown** targets; long n_targets; ASSERT_EQ(S_FALSE, root_view_accessible->get_relationTargetsOfType( - alerts_bstr, 0, &targets, &n_targets)); + alerts_bstr, 0, &targets, &n_targets)); ASSERT_EQ(0, n_targets); // Fire alert events on the infobars. @@ -294,7 +292,7 @@ TEST_F(NativeViewAccessibilityWinTest, DISABLED_RetrieveAllAlerts) { // Now calling get_relationTargetsOfType should retrieve the alerts. ASSERT_EQ(S_OK, root_view_accessible->get_relationTargetsOfType( - alerts_bstr, 0, &targets, &n_targets)); + alerts_bstr, 0, &targets, &n_targets)); ASSERT_EQ(2, n_targets); ASSERT_TRUE(IsSameObject(infobar_accessible.Get(), targets[0])); ASSERT_TRUE(IsSameObject(infobar2_accessible.Get(), targets[1])); @@ -302,7 +300,7 @@ TEST_F(NativeViewAccessibilityWinTest, DISABLED_RetrieveAllAlerts) { // If we set max_targets to 1, we should only get the first one. ASSERT_EQ(S_OK, root_view_accessible->get_relationTargetsOfType( - alerts_bstr, 1, &targets, &n_targets)); + alerts_bstr, 1, &targets, &n_targets)); ASSERT_EQ(1, n_targets); ASSERT_TRUE(IsSameObject(infobar_accessible.Get(), targets[0])); CoTaskMemFree(targets); @@ -310,14 +308,14 @@ TEST_F(NativeViewAccessibilityWinTest, DISABLED_RetrieveAllAlerts) { // If we delete the first view, we should only get the second one now. delete infobar; ASSERT_EQ(S_OK, root_view_accessible->get_relationTargetsOfType( - alerts_bstr, 0, &targets, &n_targets)); + alerts_bstr, 0, &targets, &n_targets)); ASSERT_EQ(1, n_targets); ASSERT_TRUE(IsSameObject(infobar2_accessible.Get(), targets[0])); CoTaskMemFree(targets); } // Test trying to retrieve child widgets during window close does not crash. -TEST_F(NativeViewAccessibilityWinTest, GetAllOwnedWidgetsCrash) { +TEST_F(ViewAXPlatformNodeDelegateWinTest, GetAllOwnedWidgetsCrash) { Widget widget; Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_WINDOW); @@ -332,7 +330,7 @@ TEST_F(NativeViewAccessibilityWinTest, GetAllOwnedWidgetsCrash) { EXPECT_EQ(1L, child_count); } -TEST_F(NativeViewAccessibilityWinTest, WindowHasRoleApplication) { +TEST_F(ViewAXPlatformNodeDelegateWinTest, WindowHasRoleApplication) { // We expect that our internal window object does not expose // ROLE_SYSTEM_WINDOW, but ROLE_SYSTEM_PANE instead. Widget widget; @@ -350,7 +348,7 @@ TEST_F(NativeViewAccessibilityWinTest, WindowHasRoleApplication) { EXPECT_EQ(ROLE_SYSTEM_PANE, V_I4(role.ptr())); } -TEST_F(NativeViewAccessibilityWinTest, Overrides) { +TEST_F(ViewAXPlatformNodeDelegateWinTest, Overrides) { // We expect that our internal window object does not expose // ROLE_SYSTEM_WINDOW, but ROLE_SYSTEM_PANE instead. Widget widget; diff --git a/chromium/ui/views/animation/bounds_animator.cc b/chromium/ui/views/animation/bounds_animator.cc index 13180c3f2e2..fc4e04c1854 100644 --- a/chromium/ui/views/animation/bounds_animator.cc +++ b/chromium/ui/views/animation/bounds_animator.cc @@ -11,21 +11,10 @@ #include "ui/views/animation/bounds_animator_observer.h" #include "ui/views/view.h" -// Duration in milliseconds for animations. -static const int kDefaultAnimationDuration = 200; - -using gfx::Animation; -using gfx::AnimationContainer; -using gfx::SlideAnimation; -using gfx::Tween; - namespace views { BoundsAnimator::BoundsAnimator(View* parent) - : parent_(parent), - container_(new AnimationContainer()), - animation_duration_ms_(kDefaultAnimationDuration), - tween_type_(Tween::EASE_OUT) { + : parent_(parent), container_(new gfx::AnimationContainer()) { container_->set_observer(this); } @@ -36,8 +25,8 @@ BoundsAnimator::~BoundsAnimator() { // Delete all the animations, but don't remove any child views. We assume the // view owns us and is going to be deleted anyway. - for (ViewToDataMap::iterator i = data_.begin(); i != data_.end(); ++i) - CleanupData(false, &(i->second), i->first); + for (auto& entry : data_) + CleanupData(false, &entry.second); } void BoundsAnimator::AnimateViewTo(View* view, const gfx::Rect& target) { @@ -47,8 +36,8 @@ void BoundsAnimator::AnimateViewTo(View* view, const gfx::Rect& target) { Data existing_data; if (IsAnimating(view)) { - // Don't immediatly delete the animation, that might trigger a callback from - // the animationcontainer. + // Don't immediately delete the animation, that might trigger a callback + // from the animation container. existing_data = RemoveFromMaps(view); } @@ -66,38 +55,37 @@ void BoundsAnimator::AnimateViewTo(View* view, const gfx::Rect& target) { data.animation->Show(); - CleanupData(true, &existing_data, nullptr); + CleanupData(true, &existing_data); } void BoundsAnimator::SetTargetBounds(View* view, const gfx::Rect& target) { - if (!IsAnimating(view)) { + const auto i = data_.find(view); + if (i == data_.end()) AnimateViewTo(view, target); - return; - } - - data_[view].target_bounds = target; + else + i->second.target_bounds = target; } -gfx::Rect BoundsAnimator::GetTargetBounds(View* view) { - if (!IsAnimating(view)) - return view->bounds(); - return data_[view].target_bounds; +gfx::Rect BoundsAnimator::GetTargetBounds(const View* view) const { + const auto i = data_.find(view); + return (i == data_.end()) ? view->bounds() : i->second.target_bounds; } void BoundsAnimator::SetAnimationForView( View* view, - std::unique_ptr<SlideAnimation> animation) { + std::unique_ptr<gfx::SlideAnimation> animation) { DCHECK(animation); - if (!IsAnimating(view)) + const auto i = data_.find(view); + if (i == data_.end()) return; // We delay deleting the animation until the end so that we don't prematurely // send out notification that we're done. - std::unique_ptr<Animation> old_animation = ResetAnimationForView(view); + std::unique_ptr<gfx::Animation> old_animation = ResetAnimationForView(view); - SlideAnimation* animation_ptr = animation.get(); - data_[view].animation = std::move(animation); + gfx::SlideAnimation* animation_ptr = animation.get(); + i->second.animation = std::move(animation); animation_to_view_[animation_ptr] = view; animation_ptr->set_delegate(this); @@ -105,23 +93,24 @@ void BoundsAnimator::SetAnimationForView( animation_ptr->Show(); } -const SlideAnimation* BoundsAnimator::GetAnimationForView(View* view) { - return !IsAnimating(view) ? nullptr : data_[view].animation.get(); +const gfx::SlideAnimation* BoundsAnimator::GetAnimationForView(View* view) { + const auto i = data_.find(view); + return (i == data_.end()) ? nullptr : i->second.animation.get(); } void BoundsAnimator::SetAnimationDelegate( View* view, std::unique_ptr<AnimationDelegate> delegate) { - DCHECK(IsAnimating(view)); + const auto i = data_.find(view); + DCHECK(i != data_.end()); - data_[view].delegate = std::move(delegate); + i->second.delegate = std::move(delegate); } void BoundsAnimator::StopAnimatingView(View* view) { - if (!IsAnimating(view)) - return; - - data_[view].animation->Stop(); + const auto i = data_.find(view); + if (i != data_.end()) + i->second.animation->Stop(); } bool BoundsAnimator::IsAnimating(View* view) const { @@ -156,8 +145,7 @@ void BoundsAnimator::RemoveObserver(BoundsAnimatorObserver* observer) { } std::unique_ptr<gfx::SlideAnimation> BoundsAnimator::CreateAnimation() { - std::unique_ptr<gfx::SlideAnimation> animation = - std::make_unique<SlideAnimation>(this); + auto animation = std::make_unique<gfx::SlideAnimation>(this); animation->SetContainer(container_.get()); animation->SetSlideDuration(animation_duration_ms_); animation->SetTweenType(tween_type_); @@ -170,16 +158,17 @@ BoundsAnimator::Data& BoundsAnimator::Data::operator=(Data&&) = default; BoundsAnimator::Data::~Data() = default; BoundsAnimator::Data BoundsAnimator::RemoveFromMaps(View* view) { - DCHECK(data_.count(view) > 0); - DCHECK(animation_to_view_.count(data_[view].animation.get()) > 0); + const auto i = data_.find(view); + DCHECK(i != data_.end()); + DCHECK(animation_to_view_.count(i->second.animation.get()) > 0); - Data old_data = std::move(data_[view]); + Data old_data = std::move(i->second); data_.erase(view); animation_to_view_.erase(old_data.animation.get()); return old_data; } -void BoundsAnimator::CleanupData(bool send_cancel, Data* data, View* view) { +void BoundsAnimator::CleanupData(bool send_cancel, Data* data) { if (send_cancel && data->delegate) data->delegate->AnimationCanceled(data->animation.get()); @@ -191,11 +180,14 @@ void BoundsAnimator::CleanupData(bool send_cancel, Data* data, View* view) { } } -std::unique_ptr<Animation> BoundsAnimator::ResetAnimationForView(View* view) { - if (!IsAnimating(view)) +std::unique_ptr<gfx::Animation> BoundsAnimator::ResetAnimationForView( + View* view) { + const auto i = data_.find(view); + if (i == data_.end()) return nullptr; - std::unique_ptr<Animation> old_animation = std::move(data_[view].animation); + std::unique_ptr<gfx::Animation> old_animation = + std::move(i->second.animation); animation_to_view_.erase(old_animation.get()); // Reset the delegate so that we don't attempt any processing when the // animation calls us back. @@ -203,7 +195,7 @@ std::unique_ptr<Animation> BoundsAnimator::ResetAnimationForView(View* view) { return old_animation; } -void BoundsAnimator::AnimationEndedOrCanceled(const Animation* animation, +void BoundsAnimator::AnimationEndedOrCanceled(const gfx::Animation* animation, AnimationEndType type) { DCHECK(animation_to_view_.find(animation) != animation_to_view_.end()); @@ -222,10 +214,10 @@ void BoundsAnimator::AnimationEndedOrCanceled(const Animation* animation, } } - CleanupData(false, &data, view); + CleanupData(false, &data); } -void BoundsAnimator::AnimationProgressed(const Animation* animation) { +void BoundsAnimator::AnimationProgressed(const gfx::Animation* animation) { DCHECK(animation_to_view_.find(animation) != animation_to_view_.end()); View* view = animation_to_view_[animation]; @@ -247,16 +239,16 @@ void BoundsAnimator::AnimationProgressed(const Animation* animation) { data.delegate->AnimationProgressed(animation); } -void BoundsAnimator::AnimationEnded(const Animation* animation) { +void BoundsAnimator::AnimationEnded(const gfx::Animation* animation) { AnimationEndedOrCanceled(animation, ANIMATION_ENDED); } -void BoundsAnimator::AnimationCanceled(const Animation* animation) { +void BoundsAnimator::AnimationCanceled(const gfx::Animation* animation) { AnimationEndedOrCanceled(animation, ANIMATION_CANCELED); } void BoundsAnimator::AnimationContainerProgressed( - AnimationContainer* container) { + gfx::AnimationContainer* container) { if (!repaint_bounds_.IsEmpty()) { // Adjust for rtl. repaint_bounds_.set_x(parent_->GetMirroredXWithWidthInView( @@ -276,7 +268,7 @@ void BoundsAnimator::AnimationContainerProgressed( } } -void BoundsAnimator::AnimationContainerEmpty(AnimationContainer* container) { -} +void BoundsAnimator::AnimationContainerEmpty( + gfx::AnimationContainer* container) {} } // namespace views diff --git a/chromium/ui/views/animation/bounds_animator.h b/chromium/ui/views/animation/bounds_animator.h index 5fdb04dd713..c0a6501fc0d 100644 --- a/chromium/ui/views/animation/bounds_animator.h +++ b/chromium/ui/views/animation/bounds_animator.h @@ -56,7 +56,7 @@ class VIEWS_EXPORT BoundsAnimator : public gfx::AnimationDelegate, // Returns the target bounds for the specified view. If |view| is not // animating its current bounds is returned. - gfx::Rect GetTargetBounds(View* view); + gfx::Rect GetTargetBounds(const View* view) const; // Sets the animation for the specified view. void SetAnimationForView(View* view, @@ -127,7 +127,7 @@ class VIEWS_EXPORT BoundsAnimator : public gfx::AnimationDelegate, ANIMATION_CANCELED }; - typedef std::map<View*, Data> ViewToDataMap; + typedef std::map<const View*, Data> ViewToDataMap; typedef std::map<const gfx::Animation*, View*> AnimationToViewMap; @@ -137,7 +137,7 @@ class VIEWS_EXPORT BoundsAnimator : public gfx::AnimationDelegate, // Does the necessary cleanup for |data|. If |send_cancel| is true and a // delegate has been installed on |data| AnimationCanceled is invoked on it. - void CleanupData(bool send_cancel, Data* data, View* view); + void CleanupData(bool send_cancel, Data* data); // Used when changing the animation for a view. This resets the maps for // the animation used by view and returns the current animation. Ownership @@ -178,9 +178,9 @@ class VIEWS_EXPORT BoundsAnimator : public gfx::AnimationDelegate, // to repaint these bounds. gfx::Rect repaint_bounds_; - int animation_duration_ms_; + int animation_duration_ms_ = 200; - gfx::Tween::Type tween_type_; + gfx::Tween::Type tween_type_ = gfx::Tween::EASE_OUT; DISALLOW_COPY_AND_ASSIGN(BoundsAnimator); }; diff --git a/chromium/ui/views/animation/flood_fill_ink_drop_ripple.cc b/chromium/ui/views/animation/flood_fill_ink_drop_ripple.cc index 220240b02cb..1ce501ed80f 100644 --- a/chromium/ui/views/animation/flood_fill_ink_drop_ripple.cc +++ b/chromium/ui/views/animation/flood_fill_ink_drop_ripple.cc @@ -15,6 +15,7 @@ #include "ui/gfx/geometry/point_conversions.h" #include "ui/gfx/geometry/vector2d_f.h" #include "ui/views/animation/ink_drop_util.h" +#include "ui/views/style/platform_style.h" namespace { @@ -447,8 +448,10 @@ float FloodFillInkDropRipple::MaxDistanceToCorners( // Returns the InkDropState sub animation duration for the given |state|. base::TimeDelta FloodFillInkDropRipple::GetAnimationDuration(int state) { - if (!gfx::Animation::ShouldRenderRichAnimation()) + if (!PlatformStyle::kUseRipples || + !gfx::Animation::ShouldRenderRichAnimation()) { return base::TimeDelta(); + } int state_override = state; // Override the requested state if needed. diff --git a/chromium/ui/views/animation/ink_drop_host_view.cc b/chromium/ui/views/animation/ink_drop_host_view.cc index 1d024628715..f21c750bd1f 100644 --- a/chromium/ui/views/animation/ink_drop_host_view.cc +++ b/chromium/ui/views/animation/ink_drop_host_view.cc @@ -118,8 +118,7 @@ gfx::Size InkDropHostView::CalculateLargeInkDropSize( InkDropHostView::InkDropHostView() : ink_drop_mode_(InkDropMode::OFF), ink_drop_(nullptr), - ink_drop_visible_opacity_( - PlatformStyle::kUseRipples ? kInkDropVisibleOpacity : 0), + ink_drop_visible_opacity_(kInkDropVisibleOpacity), ink_drop_small_corner_radius_(kInkDropSmallCornerRadius), ink_drop_large_corner_radius_(kInkDropLargeCornerRadius), old_paint_to_layer_(false), diff --git a/chromium/ui/views/animation/ink_drop_impl.cc b/chromium/ui/views/animation/ink_drop_impl.cc index 96328205c27..7e00a268fd2 100644 --- a/chromium/ui/views/animation/ink_drop_impl.cc +++ b/chromium/ui/views/animation/ink_drop_impl.cc @@ -286,7 +286,7 @@ class InkDropImpl::HideHighlightOnRippleHiddenState // The timer used to delay the highlight fade in after an ink drop ripple // animation. - std::unique_ptr<base::Timer> highlight_after_ripple_timer_; + std::unique_ptr<base::OneShotTimer> highlight_after_ripple_timer_; DISALLOW_COPY_AND_ASSIGN(HideHighlightOnRippleHiddenState); }; @@ -621,9 +621,7 @@ void InkDropImpl::SetAutoHighlightMode(AutoHighlightMode auto_highlight_mode) { } void InkDropImpl::SetAutoHighlightModeForPlatform() { - SetAutoHighlightMode(PlatformStyle::kUseRipples - ? AutoHighlightMode::HIDE_ON_RIPPLE - : AutoHighlightMode::SHOW_ON_RIPPLE); + SetAutoHighlightMode(AutoHighlightMode::HIDE_ON_RIPPLE); } void InkDropImpl::HostSizeChanged(const gfx::Size& new_size) { diff --git a/chromium/ui/views/animation/square_ink_drop_ripple.cc b/chromium/ui/views/animation/square_ink_drop_ripple.cc index 276b5d5f732..c4a25252b0e 100644 --- a/chromium/ui/views/animation/square_ink_drop_ripple.cc +++ b/chromium/ui/views/animation/square_ink_drop_ripple.cc @@ -17,6 +17,7 @@ #include "ui/gfx/geometry/vector3d_f.h" #include "ui/gfx/transform_util.h" #include "ui/views/animation/ink_drop_painted_layer_delegates.h" +#include "ui/views/style/platform_style.h" #include "ui/views/view.h" namespace views { @@ -129,8 +130,10 @@ int kAnimationDurationInMs[] = { // Returns the InkDropState sub animation duration for the given |state|. base::TimeDelta GetAnimationDuration(InkDropSubAnimations state) { - if (!gfx::Animation::ShouldRenderRichAnimation()) + if (!PlatformStyle::kUseRipples || + !gfx::Animation::ShouldRenderRichAnimation()) { return base::TimeDelta(); + } return base::TimeDelta::FromMilliseconds( (InkDropRipple::UseFastAnimations() diff --git a/chromium/ui/views/bubble/bubble_border.cc b/chromium/ui/views/bubble/bubble_border.cc index 6961a92ef6f..b746bb4c231 100644 --- a/chromium/ui/views/bubble/bubble_border.cc +++ b/chromium/ui/views/bubble/bubble_border.cc @@ -20,6 +20,7 @@ #include "ui/gfx/shadow_value.h" #include "ui/gfx/skia_paint_util.h" #include "ui/resources/grit/ui_resources.h" +#include "ui/views/layout/layout_provider.h" #include "ui/views/painter.h" #include "ui/views/resources/grit/views_resources.h" #include "ui/views/view.h" @@ -423,8 +424,7 @@ const gfx::ShadowValues& BubbleBorder::GetShadowValues( gfx::ShadowValues shadows; if (elevation.has_value()) { DCHECK(elevation.value() >= 0); - shadows = gfx::ShadowValues( - gfx::ShadowValue::MakeMdShadowValues(elevation.value())); + shadows = LayoutProvider::Get()->MakeShadowValues(elevation.value()); } else { constexpr int kSmallShadowVerticalOffset = 2; constexpr int kSmallShadowBlur = 4; diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate.cc b/chromium/ui/views/bubble/bubble_dialog_delegate.cc index eb5a9fd9b42..0bfe54962a2 100644 --- a/chromium/ui/views/bubble/bubble_dialog_delegate.cc +++ b/chromium/ui/views/bubble/bubble_dialog_delegate.cc @@ -136,15 +136,17 @@ NonClientFrameView* BubbleDialogDelegateView::CreateNonClientFrameView( Widget* widget) { BubbleFrameView* frame = new BubbleDialogFrameView(title_margins_); + LayoutProvider* provider = LayoutProvider::Get(); frame->set_footnote_margins( - LayoutProvider::Get()->GetInsetsMetric(INSETS_DIALOG_SUBSECTION)); + provider->GetInsetsMetric(INSETS_DIALOG_SUBSECTION)); frame->SetFootnoteView(CreateFootnoteView()); BubbleBorder::Arrow adjusted_arrow = arrow(); if (base::i18n::IsRTL() && mirror_arrow_in_rtl_) adjusted_arrow = BubbleBorder::horizontal_mirror(adjusted_arrow); - frame->SetBubbleBorder(std::unique_ptr<BubbleBorder>( - new BubbleBorder(adjusted_arrow, shadow(), color()))); + std::unique_ptr<BubbleBorder> border = + std::make_unique<BubbleBorder>(adjusted_arrow, shadow(), color()); + frame->SetBubbleBorder(std::move(border)); return frame; } @@ -323,24 +325,32 @@ void BubbleDialogDelegateView::SetAnchorView(View* anchor_view) { // change as well. if (!anchor_view || anchor_widget() != anchor_view->GetWidget()) { if (anchor_widget()) { + if (GetWidget() && GetWidget()->IsVisible()) + UpdateAnchorWidgetRenderState(false); anchor_widget_->RemoveObserver(this); anchor_widget_ = NULL; } if (anchor_view) { anchor_widget_ = anchor_view->GetWidget(); - if (anchor_widget_) + if (anchor_widget_) { anchor_widget_->AddObserver(this); + UpdateAnchorWidgetRenderState(GetWidget() && GetWidget()->IsVisible()); + } } } anchor_view_tracker_->SetView(anchor_view); - // Do not update anchoring for NULL views; this could indicate that our - // NativeWindow is being destroyed, so it would be dangerous for us to update - // our anchor bounds at that point. (It's safe to skip this, since if we were - // to update the bounds when |anchor_view| is NULL, the bubble won't move.) - if (anchor_view && GetWidget()) + if (anchor_view && GetWidget()) { + // Do not update anchoring for NULL views; this could indicate + // that our NativeWindow is being destroyed, so it would be + // dangerous for us to update our anchor bounds at that + // point. (It's safe to skip this, since if we were to update the + // bounds when |anchor_view| is NULL, the bubble won't move.) OnAnchorBoundsChanged(); + + EnableFocusTraversalFromAnchorView(); + } } void BubbleDialogDelegateView::SetAnchorRect(const gfx::Rect& rect) { @@ -385,10 +395,8 @@ void BubbleDialogDelegateView::UpdateColorsFromTheme( void BubbleDialogDelegateView::HandleVisibilityChanged(Widget* widget, bool visible) { - if (widget == GetWidget() && anchor_widget() && - anchor_widget()->GetTopLevelWidget()) { - anchor_widget()->GetTopLevelWidget()->SetAlwaysRenderAsActive(visible); - } + if (widget == GetWidget()) + UpdateAnchorWidgetRenderState(visible); // Fire ax::mojom::Event::kAlert for bubbles marked as // ax::mojom::Role::kAlertDialog; this instructs accessibility tools to read @@ -408,4 +416,11 @@ void BubbleDialogDelegateView::OnDeactivate() { GetWidget()->Close(); } +void BubbleDialogDelegateView::UpdateAnchorWidgetRenderState(bool visible) { + if (!anchor_widget() || !anchor_widget()->GetTopLevelWidget()) + return; + + anchor_widget()->GetTopLevelWidget()->SetAlwaysRenderAsActive(visible); +} + } // namespace views diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate.h b/chromium/ui/views/bubble/bubble_dialog_delegate.h index c564cb85dcf..216578e0961 100644 --- a/chromium/ui/views/bubble/bubble_dialog_delegate.h +++ b/chromium/ui/views/bubble/bubble_dialog_delegate.h @@ -187,6 +187,9 @@ class VIEWS_EXPORT BubbleDialogDelegateView : public DialogDelegateView, // Called when a deactivation is detected. void OnDeactivate(); + // When a bubble is visible, the anchor widget should always render as active. + void UpdateAnchorWidgetRenderState(bool visible); + // A flag controlling bubble closure on deactivation. bool close_on_deactivate_; diff --git a/chromium/ui/views/bubble/bubble_dialog_delegate_unittest.cc b/chromium/ui/views/bubble/bubble_dialog_delegate_unittest.cc index 129caea4902..6b0e5d89b70 100644 --- a/chromium/ui/views/bubble/bubble_dialog_delegate_unittest.cc +++ b/chromium/ui/views/bubble/bubble_dialog_delegate_unittest.cc @@ -36,6 +36,8 @@ class TestBubbleDialogDelegateView : public BubbleDialogDelegateView { } ~TestBubbleDialogDelegateView() override {} + using BubbleDialogDelegateView::SetAnchorView; + // BubbleDialogDelegateView overrides: View* GetInitiallyFocusedView() override { return view_; } gfx::Size CalculatePreferredSize() const override { @@ -476,4 +478,20 @@ TEST_F(BubbleDialogDelegateTest, StyledLabelTitle) { bubble_widget->GetWindowBoundsInScreen().height()); } +TEST_F(BubbleDialogDelegateTest, VisibleAnchorChanges) { + std::unique_ptr<Widget> anchor_widget(CreateTestWidget()); + TestBubbleDialogDelegateView* bubble_delegate = + new TestBubbleDialogDelegateView(nullptr); + bubble_delegate->set_parent_window(anchor_widget->GetNativeView()); + + Widget* bubble_widget = + BubbleDialogDelegateView::CreateBubble(bubble_delegate); + bubble_widget->Show(); + EXPECT_FALSE(anchor_widget->IsAlwaysRenderAsActive()); + bubble_delegate->SetAnchorView(anchor_widget->GetContentsView()); + EXPECT_TRUE(anchor_widget->IsAlwaysRenderAsActive()); + + bubble_widget->Hide(); +} + } // namespace views diff --git a/chromium/ui/views/bubble/bubble_frame_view.cc b/chromium/ui/views/bubble/bubble_frame_view.cc index 5021792e327..50fce2ddc25 100644 --- a/chromium/ui/views/bubble/bubble_frame_view.cc +++ b/chromium/ui/views/bubble/bubble_frame_view.cc @@ -17,6 +17,7 @@ #include "ui/compositor/paint_recorder.h" #include "ui/display/display.h" #include "ui/display/screen.h" +#include "ui/gfx/color_palette.h" #include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/path.h" #include "ui/gfx/skia_util.h" @@ -41,12 +42,6 @@ namespace views { namespace { -// Background color of the footnote view. -constexpr SkColor kFootnoteBackgroundColor = SkColorSetRGB(250, 250, 250); - -// Color of the top border of the footnote. -constexpr SkColor kFootnoteBorderColor = SkColorSetRGB(235, 235, 235); - // Get the |vertical| or horizontal amount that |available_bounds| overflows // |window_bounds|. int GetOffScreenLength(const gfx::Rect& available_bounds, @@ -273,7 +268,8 @@ void BubbleFrameView::UpdateWindowIcon() { void BubbleFrameView::UpdateWindowTitle() { if (default_title_) { const WidgetDelegate* delegate = GetWidget()->widget_delegate(); - default_title_->SetVisible(delegate->ShouldShowWindowTitle()); + default_title_->SetVisible(delegate->ShouldShowWindowTitle() && + !delegate->GetWindowTitle().empty()); default_title_->SetText(delegate->GetWindowTitle()); } // custom_title_'s updates are handled by its creator. } @@ -413,6 +409,9 @@ void BubbleFrameView::OnNativeThemeChanged(const ui::NativeTheme* theme) { void BubbleFrameView::ViewHierarchyChanged( const ViewHierarchyChangedDetails& details) { + if (details.is_add && details.child == this) + OnThemeChanged(); + if (!details.is_add && details.parent == footnote_container_ && footnote_container_->child_count() == 1 && details.child == footnote_container_->child_at(0)) { @@ -464,9 +463,9 @@ void BubbleFrameView::SetFootnoteView(View* view) { footnote_container_->SetLayoutManager( std::make_unique<BoxLayout>(BoxLayout::kVertical, footnote_margins_, 0)); footnote_container_->SetBackground( - CreateSolidBackground(kFootnoteBackgroundColor)); + CreateSolidBackground(gfx::kGoogleGrey050)); footnote_container_->SetBorder( - CreateSolidSidedBorder(1, 0, 0, 0, kFootnoteBorderColor)); + CreateSolidSidedBorder(1, 0, 0, 0, gfx::kGoogleGrey200)); footnote_container_->AddChildView(view); footnote_container_->SetVisible(view->visible()); AddChildView(footnote_container_); diff --git a/chromium/ui/views/bubble/bubble_frame_view_unittest.cc b/chromium/ui/views/bubble/bubble_frame_view_unittest.cc index 9b16c5a3247..bd891dc540b 100644 --- a/chromium/ui/views/bubble/bubble_frame_view_unittest.cc +++ b/chromium/ui/views/bubble/bubble_frame_view_unittest.cc @@ -789,6 +789,7 @@ TEST_F(BubbleFrameViewTest, LayoutWithIcon) { delegate.SetAnchorView(anchor.widget().GetContentsView()); SkBitmap bitmap; bitmap.allocN32Pixels(20, 80); + bitmap.eraseColor(SK_ColorYELLOW); delegate.set_icon(gfx::ImageSkia::CreateFrom1xBitmap(bitmap)); Widget* widget = BubbleDialogDelegateView::CreateBubble(&delegate); diff --git a/chromium/ui/views/bubble/tooltip_icon.cc b/chromium/ui/views/bubble/tooltip_icon.cc index b25fd527510..2b88baf6279 100644 --- a/chromium/ui/views/bubble/tooltip_icon.cc +++ b/chromium/ui/views/bubble/tooltip_icon.cc @@ -14,8 +14,9 @@ namespace views { -TooltipIcon::TooltipIcon(const base::string16& tooltip) +TooltipIcon::TooltipIcon(const base::string16& tooltip, int tooltip_icon_size) : tooltip_(tooltip), + tooltip_icon_size_(tooltip_icon_size), mouse_inside_(false), bubble_(nullptr), preferred_width_(0), @@ -69,10 +70,10 @@ void TooltipIcon::MouseMovedOutOfHost() { } void TooltipIcon::SetDrawAsHovered(bool hovered) { - SetImage(gfx::CreateVectorIcon(vector_icons::kInfoOutlineIcon, 18, - hovered - ? SkColorSetARGB(0xBD, 0, 0, 0) - : SkColorSetARGB(0xBD, 0x44, 0x44, 0x44))); + SetImage( + gfx::CreateVectorIcon(vector_icons::kInfoOutlineIcon, tooltip_icon_size_, + hovered ? SkColorSetARGB(0xBD, 0, 0, 0) + : SkColorSetARGB(0xBD, 0x44, 0x44, 0x44))); } void TooltipIcon::ShowBubble() { @@ -83,7 +84,7 @@ void TooltipIcon::ShowBubble() { bubble_ = new InfoBubble(this, tooltip_); bubble_->set_preferred_width(preferred_width_); - bubble_->set_arrow(BubbleBorder::TOP_RIGHT); + bubble_->set_arrow(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/tooltip_icon.h b/chromium/ui/views/bubble/tooltip_icon.h index 327bdd505b2..97dbe043b45 100644 --- a/chromium/ui/views/bubble/tooltip_icon.h +++ b/chromium/ui/views/bubble/tooltip_icon.h @@ -11,6 +11,7 @@ #include "base/scoped_observer.h" #include "base/strings/string16.h" #include "base/timer/timer.h" +#include "ui/views/bubble/bubble_border.h" #include "ui/views/controls/image_view.h" #include "ui/views/mouse_watcher.h" #include "ui/views/widget/widget_observer.h" @@ -24,7 +25,8 @@ class VIEWS_EXPORT TooltipIcon : public ImageView, public MouseWatcherListener, public WidgetObserver { public: - explicit TooltipIcon(const base::string16& tooltip); + explicit TooltipIcon(const base::string16& tooltip, + int tooltip_icon_size = 16); ~TooltipIcon() override; // ImageView: @@ -45,6 +47,10 @@ class VIEWS_EXPORT TooltipIcon : public ImageView, preferred_width_ = preferred_width; } + void set_anchor_point_arrow(BubbleBorder::Arrow arrow) { + anchor_point_arrow_ = arrow; + } + private: // Changes the color to reflect the hover node_data. void SetDrawAsHovered(bool hovered); @@ -59,6 +65,14 @@ class VIEWS_EXPORT TooltipIcon : public ImageView, // The text to show in a bubble when hovered. base::string16 tooltip_; + // The size of the tooltip icon, in dip. + // Must be set in the constructor, otherwise the pre-hovered icon will show + // the default size. + int tooltip_icon_size_; + + // The point at which to anchor the tooltip. + BubbleBorder::Arrow anchor_point_arrow_ = BubbleBorder::TOP_RIGHT; + // Whether the mouse is inside this tooltip. bool mouse_inside_; diff --git a/chromium/ui/views/bubble/tray_bubble_view.cc b/chromium/ui/views/bubble/tray_bubble_view.cc index 80ae22b84af..397b49e9b04 100644 --- a/chromium/ui/views/bubble/tray_bubble_view.cc +++ b/chromium/ui/views/bubble/tray_bubble_view.cc @@ -274,7 +274,6 @@ void TrayBubbleView::InitializeAndShowBubble() { void TrayBubbleView::UpdateBubble() { if (GetWidget()) { SizeToContents(); - bubble_content_mask_->layer()->SetBounds(GetBubbleBounds()); GetWidget()->GetRootView()->SchedulePaint(); // When extra keyboard accessibility is enabled, focus the default item if @@ -313,6 +312,10 @@ void TrayBubbleView::ResetDelegate() { delegate_ = nullptr; } +void TrayBubbleView::ChangeAnchorView(views::View* anchor_view) { + BubbleDialogDelegateView::SetAnchorView(anchor_view); +} + int TrayBubbleView::GetDialogButtons() const { return ui::DIALOG_BUTTON_NONE; } @@ -326,7 +329,7 @@ ax::mojom::Role TrayBubbleView::GetAccessibleWindowRole() const { void TrayBubbleView::SizeToContents() { BubbleDialogDelegateView::SizeToContents(); - bubble_content_mask_->layer()->SetBounds(GetBubbleBounds()); + bubble_content_mask_->layer()->SetBounds(layer()->parent()->bounds()); } void TrayBubbleView::OnBeforeBubbleWidgetInit(Widget::InitParams* params, diff --git a/chromium/ui/views/bubble/tray_bubble_view.h b/chromium/ui/views/bubble/tray_bubble_view.h index bd78fbc3509..346641e9ad5 100644 --- a/chromium/ui/views/bubble/tray_bubble_view.h +++ b/chromium/ui/views/bubble/tray_bubble_view.h @@ -130,6 +130,9 @@ class VIEWS_EXPORT TrayBubbleView : public BubbleDialogDelegateView, // 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; } diff --git a/chromium/ui/views/cocoa/bridged_content_view.h b/chromium/ui/views/cocoa/bridged_content_view.h index 5bfbe297e5c..a5a9b690ef2 100644 --- a/chromium/ui/views/cocoa/bridged_content_view.h +++ b/chromium/ui/views/cocoa/bridged_content_view.h @@ -33,6 +33,11 @@ class View; // hierarchy rooted at |hostedView_|. Owned by the focused View. 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_|. + ui::TextInputClient* pendingTextInputClient_; + // A tracking area installed to enable mouseMoved events. ui::ScopedCrTrackingArea cursorTrackingArea_; diff --git a/chromium/ui/views/cocoa/bridged_content_view.mm b/chromium/ui/views/cocoa/bridged_content_view.mm index 17f7339f7d8..445d42c389a 100644 --- a/chromium/ui/views/cocoa/bridged_content_view.mm +++ b/chromium/ui/views/cocoa/bridged_content_view.mm @@ -80,6 +80,13 @@ 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; +} + // 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 @@ -240,6 +247,11 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { 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; @@ -302,14 +314,83 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { 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 { - textInputClient_ = nullptr; + [self setTextInputClient:nullptr]; hostedView_ = 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 the point is classified as HTCAPTION (background, draggable), return nil // so that it can lead to a window drag or double-click in the title bar. - (NSView*)hitTest:(NSPoint)point { @@ -328,16 +409,31 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { NSWindow* target = [self window]; DCHECK(target); + BOOL isScrollEvent = [theEvent type] == NSScrollWheel; + // If it's the view's window, process normally. if ([target isEqual:source]) { - [self mouseEvent:theEvent]; + if (isScrollEvent) + [self scrollWheel:theEvent]; + else + [self mouseEvent:theEvent]; + return; } - ui::MouseEvent event(theEvent); - event.set_location( - MovePointToWindow([theEvent locationInWindow], source, target)); - hostedView_->GetWidget()->OnMouseEvent(&event); + gfx::Point event_location = + MovePointToWindow([theEvent locationInWindow], source, target); + [self updateTooltipIfRequiredAt:event_location]; + + if (isScrollEvent) { + ui::ScrollEvent event(theEvent); + event.set_location(event_location); + hostedView_->GetWidget()->OnScrollEvent(&event); + } else { + ui::MouseEvent event(theEvent); + event.set_location(event_location); + hostedView_->GetWidget()->OnMouseEvent(&event); + } } - (void)updateTooltipIfRequiredAt:(const gfx::Point&)locationInContent { @@ -427,6 +523,14 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { hostedView_->GetWidget()->GetInputMethod()->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]); @@ -441,7 +545,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { text = [text string]; bool isCharacterEvent = keyDownEvent_ && [text length] == 1; - // Pass the character event to the View hierarchy. Cases this handles (non- + // 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 @@ -453,14 +557,26 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { // 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 active composition text. These key + // 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. - - // Also note that a single key down event can cause multiple - // insertText:replacementRange: action messages. Example, on pressing Alt+e, + // 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 @@ -470,7 +586,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { // Currently there seems to be no use case to pass non-character events routed // from insertText: handlers to the View hierarchy. - if (isCharacterEvent && ![self hasMarkedText]) { + if (isFinalInsertForKeyEvent && ![self hasMarkedText]) { ui::KeyEvent charEvent([text characterAtIndex:0], ui::KeyboardCodeFromNSEvent(keyDownEvent_), ui::EF_NONE); @@ -490,18 +606,26 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { // the key modifier since |text| already accounts for the pressed key // modifiers. - // Also, note we don't 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. + // 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::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; } - hasUnhandledKeyDownEvent_ = NO; } } @@ -566,14 +690,9 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { if (!hostedView_) return; + DCHECK([theEvent type] != NSScrollWheel); ui::MouseEvent event(theEvent); - - // ui::EventLocationFromNative() assumes the event hit the contentView. - // Adjust if that's not the case (e.g. for reparented views). - if ([theEvent window] && [[self window] contentView] != self) { - NSPoint p = [self convertPoint:[theEvent locationInWindow] fromView:nil]; - event.set_location(gfx::Point(p.x, NSHeight([self frame]) - p.y)); - } + [self adjustUiEventLocation:&event fromNativeEvent:theEvent]; // Aura updates tooltips with the help of aura::Window::AddPreTargetHandler(). // Mac hooks in here. @@ -680,7 +799,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { - (NSTextInputContext*)inputContext { // If the textInputClient_ does not exist, return nil since this view does not // conform to NSTextInputClient protocol. - if (!textInputClient_) + if (!pendingTextInputClient_) return nil; // If a menu is active, and -[NSView interpretKeyEvents:] asks for the @@ -693,7 +812,7 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { // (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 (textInputClient_->GetTextInputType()) { + switch (pendingTextInputClient_->GetTextInputType()) { case ui::TEXT_INPUT_TYPE_NONE: case ui::TEXT_INPUT_TYPE_PASSWORD: return nil; @@ -744,6 +863,12 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { 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()]; + hostedView_->GetWidget()->OnScrollEvent(&event); } @@ -1253,6 +1378,14 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { // 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 { @@ -1311,13 +1444,9 @@ ui::TextEditCommand GetTextEditCommandForMenuAction(SEL action) { } - (void)insertText:(id)text replacementRange:(NSRange)replacementRange { - if (!hostedView_) + if (!hostedView_ || !textInputClient_) return; - // Verify inputContext is not nil, i.e. |textInputClient_| is valid and no - // menu is active. - DCHECK([self inputContext]); - textInputClient_->DeleteRange(gfx::Range(replacementRange)); [self insertTextInternal:text]; } diff --git a/chromium/ui/views/cocoa/bridged_native_widget.h b/chromium/ui/views/cocoa/bridged_native_widget.h index ec83dfb3089..674a9b78228 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget.h +++ b/chromium/ui/views/cocoa/bridged_native_widget.h @@ -14,6 +14,7 @@ #include "base/macros.h" #include "components/viz/common/surfaces/parent_local_surface_id_allocator.h" #import "ui/accelerated_widget_mac/accelerated_widget_mac.h" +#include "ui/accelerated_widget_mac/ca_transaction_observer.h" #include "ui/accelerated_widget_mac/display_ca_layer_tree.h" #include "ui/base/ime/input_method_delegate.h" #include "ui/compositor/layer_owner.h" @@ -30,6 +31,7 @@ namespace ui { class InputMethod; +class RecyclableCompositorMac; } namespace views { @@ -47,7 +49,8 @@ class View; // 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 ui::LayerDelegate, + : public ui::CATransactionCoordinator::PreCommitObserver, + public ui::LayerDelegate, public ui::LayerOwner, public ui::internal::InputMethodDelegate, public CocoaMouseCaptureDelegate, @@ -208,9 +211,25 @@ class VIEWS_EXPORT BridgedNativeWidget 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_; } - bool animate() const { return animate_; } - void set_animate(bool animate) { animate_ = animate; } + // 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; + + // ui::CATransactionCoordinator::PreCommitObserver implementation + bool ShouldWaitInPreCommit() override; + base::TimeDelta PreCommitTimeout() override; // Overridden from ui::internal::InputMethodDelegate: ui::EventDispatchDetails DispatchKeyEventPostIME(ui::KeyEvent* key) override; @@ -234,8 +253,7 @@ class VIEWS_EXPORT BridgedNativeWidget void InitCompositor(); void DestroyCompositor(); - // Installs the NSView for hosting the composited layer. It is later provided - // to |compositor_widget_| via AcceleratedWidgetGetNSView(). + // Installs the NSView for hosting the composited layer. void AddCompositorSuperview(); // Size the layer to match the client area bounds, taking into account display @@ -271,7 +289,6 @@ class VIEWS_EXPORT BridgedNativeWidget float new_device_scale_factor) override; // Overridden from ui::AcceleratedWidgetMac: - NSView* AcceleratedWidgetGetNSView() const override; void AcceleratedWidgetCALayerParamsUpdated() override; // Overridden from BridgedNativeWidgetOwner: @@ -283,6 +300,11 @@ class VIEWS_EXPORT BridgedNativeWidget // DialogObserver: void OnDialogModelChanged() override; + // Set |layer()| to be visible or not visible based on |window_visible_|. If + // the layer is not visible, then lock the compositor, so we don't draw any + // new frames. + void UpdateLayerVisibility(); + views::NativeWidgetMac* native_widget_mac_; // Weak. Owns this. base::scoped_nsobject<NSWindow> window_; base::scoped_nsobject<ViewsNSWindowDelegate> window_delegate_; @@ -300,16 +322,25 @@ class VIEWS_EXPORT BridgedNativeWidget std::vector<BridgedNativeWidget*> child_windows_; base::scoped_nsobject<NSView> compositor_superview_; - std::unique_ptr<ui::AcceleratedWidgetMac> compositor_widget_; std::unique_ptr<ui::DisplayCALayerTree> display_ca_layer_tree_; - std::unique_ptr<ui::Compositor> compositor_; - viz::ParentLocalSurfaceIdAllocator parent_local_surface_id_allocator_; + std::unique_ptr<ui::RecyclableCompositorMac> compositor_; // 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_; + // Tracks the origin of the window (from the top-left of the screen) when it + // was last reported to toolkit-views. Needed to trigger move notifications + // associated with a window resize since AppKit won't send move notifications + // when the top-left point of the window moves vertically. The origin of the + // window in AppKit coordinates is not actually changing in this case. + gfx::Point last_window_frame_origin_; + + // 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_; @@ -326,18 +357,14 @@ class VIEWS_EXPORT BridgedNativeWidget // currently hidden due to having a hidden parent. bool wants_to_be_visible_; - // Whether to animate the window (when it is appropriate to do so). - bool animate_ = true; + // 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; - // Whether the window's visibility is suppressed currently. For opaque non- - // modal windows, the window's alpha value is set to 0, till the frame from - // the compositor arrives to avoid "blinking". - bool initial_visibility_suppressed_ = false; - AssociatedViews associated_views_; DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidget); diff --git a/chromium/ui/views/cocoa/bridged_native_widget.mm b/chromium/ui/views/cocoa/bridged_native_widget.mm index c37fd628634..69c5f1f44d7 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget.mm +++ b/chromium/ui/views/cocoa/bridged_native_widget.mm @@ -8,12 +8,12 @@ #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/threading/thread_task_runner_handle.h" #include "components/viz/common/features.h" #include "components/viz/common/surfaces/local_surface_id.h" #include "ui/accelerated_widget_mac/window_resize_helper_mac.h" @@ -22,7 +22,8 @@ #include "ui/base/ime/input_method.h" #include "ui/base/ime/input_method_factory.h" #include "ui/base/layout.h" -#include "ui/compositor/compositor_switches.h" +#include "ui/base/ui_base_switches.h" +#include "ui/compositor/recyclable_compositor_mac.h" #include "ui/gfx/geometry/dip_util.h" #import "ui/gfx/mac/coordinate_conversion.h" #import "ui/gfx/mac/nswindow_frame_controls.h" @@ -51,6 +52,9 @@ CGError CGSSetWindowBackgroundBlurRadius(CGSConnection connection, int radius); } +namespace { +constexpr auto kUIPaintTimeout = base::TimeDelta::FromSeconds(5); +} // namespace // 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 @@ -168,18 +172,6 @@ gfx::Size GetClientSizeForWindowSize(NSWindow* window, return gfx::Size([window contentRectForFrameRect:frame_rect].size); } -// Returns a task runner for creating a ui::Compositor. This allows compositor -// tasks to be funneled through ui::WindowResizeHelper's task runner to allow -// resize operations to coordinate with frames provided by the GPU process. -scoped_refptr<base::SingleThreadTaskRunner> GetCompositorTaskRunner() { - // If the WindowResizeHelper's pumpable task runner is set, it means the GPU - // process is directing messages there, and the compositor can synchronize - // with it. Otherwise, just use the UI thread. - scoped_refptr<base::SingleThreadTaskRunner> task_runner = - ui::WindowResizeHelperMac::Get()->task_runner(); - return task_runner ? task_runner : base::ThreadTaskRunnerHandle::Get(); -} - void RankNSViews(views::View* view, const views::BridgedNativeWidget::AssociatedViews& hosts, RankMap* rank) { @@ -251,6 +243,7 @@ BridgedNativeWidget::BridgedNativeWidget(NativeWidgetMac* parent) DCHECK(parent); window_delegate_.reset( [[ViewsNSWindowDelegate alloc] initWithBridgedNativeWidget:this]); + ui::CATransactionCoordinator::Get().AddPreCommitObserver(this); } BridgedNativeWidget::~BridgedNativeWidget() { @@ -259,6 +252,7 @@ BridgedNativeWidget::~BridgedNativeWidget() { // destructor is called. DCHECK(![window_ delegate]); + ui::CATransactionCoordinator::Get().RemovePreCommitObserver(this); RemoveOrDestroyChildren(); DCHECK(child_windows_.empty()); SetFocusManager(nullptr); @@ -318,6 +312,15 @@ void BridgedNativeWidget::Init(base::scoped_nsobject<NSWindow> window, NSWindowCollectionBehaviorTransient]; } + // Include "regular" windows without the standard frame in the window cycle. + // These use NSBorderlessWindowMask so do not get it by default. + if (widget_type_ == Widget::InitParams::TYPE_WINDOW && + params.remove_standard_frame) { + [window_ + setCollectionBehavior:[window_ collectionBehavior] | + NSWindowCollectionBehaviorParticipatesInCycle]; + } + // OSX likes to put shadows on most things. However, frameless windows (with // styleMask = NSBorderlessWindowMask) default to no shadow. So change that. // SHADOW_TYPE_DROP is used for Menus, which get the same shadow style on Mac. @@ -379,13 +382,21 @@ void BridgedNativeWidget::SetFocusManager(FocusManager* focus_manager) { if (focus_manager_ == focus_manager) return; - if (focus_manager_) + if (focus_manager_) { + // Only the destructor can replace the focus manager (and it passes null). + DCHECK(![window_ delegate]); + DCHECK(!focus_manager); + if (View* old_focus = focus_manager_->GetFocusedView()) + OnDidChangeFocus(old_focus, nullptr); focus_manager_->RemoveFocusChangeListener(this); - - if (focus_manager) - focus_manager->AddFocusChangeListener(this); + focus_manager_ = nullptr; + return; + } focus_manager_ = focus_manager; + focus_manager_->AddFocusChangeListener(this); + if (View* new_focus = focus_manager_->GetFocusedView()) + OnDidChangeFocus(nullptr, new_focus); } void BridgedNativeWidget::SetBounds(const gfx::Rect& new_bounds) { @@ -430,7 +441,7 @@ void BridgedNativeWidget::SetRootView(views::View* view) { // If this is ever false, the compositor will need to be properly torn down // and replaced, pointing at the new view. - DCHECK(!view || !compositor_widget_); + DCHECK(!view || !compositor_); drag_drop_client_.reset(); [bridged_view_ clearView]; @@ -447,6 +458,11 @@ void BridgedNativeWidget::SetRootView(views::View* view) { // 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_]; } @@ -481,6 +497,10 @@ void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) { } 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()) @@ -491,23 +511,6 @@ void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) { return; } - // Non-modal windows are not animated. Hence opaque non-modal windows can - // appear with a "flash" if they are made visible before the frame from the - // compositor arrives. To get around this, set the alpha value of the window - // to 0, till we receive the correct frame from the compositor. Also, ignore - // mouse clicks till then. Also check for an active task runner on the - // WindowResizeHelperMac instance to ensure visibility is only suppressed when - // there is an active GPU process. - // TODO(karandeepb): Investigate whether similar technique is needed for other - // dialog types. - if (layer() && [window_ isOpaque] && !window_visible_ && - !native_widget_mac_->GetWidget()->IsModal() && - ui::WindowResizeHelperMac::Get()->task_runner()) { - initial_visibility_suppressed_ = true; - [window_ setAlphaValue:0.0]; - [window_ setIgnoresMouseEvents:YES]; - } - if (new_state == SHOW_AND_ACTIVATE_WINDOW) { [window_ makeKeyAndOrderFront:nil]; [NSApp activateIgnoringOtherApps:YES]; @@ -516,8 +519,22 @@ void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) { // 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_) + 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]; @@ -526,7 +543,7 @@ void BridgedNativeWidget::SetVisibilityState(WindowVisibilityState new_state) { // For non-sheet modal types, use the constrained window animations to make // the window appear. - if (animate_ && native_widget_mac_->GetWidget()->IsModal()) { + if (ShouldRunCustomAnimationFor(Widget::ANIMATE_SHOW)) { show_animation_.reset( [[ModalShowAnimationWithLayer alloc] initWithBridgedNativeWidget:this]); @@ -660,6 +677,9 @@ void BridgedNativeWidget::OnFullscreenTransitionStart( // If going into fullscreen, store an answer for GetRestoredBounds(). if (target_fullscreen_state) bounds_before_fullscreen_ = gfx::ScreenRectFromNSRect([window_ frame]); + + // Notify that fullscreen state changed. + native_widget_mac_->OnWindowFullscreenStateChange(); } void BridgedNativeWidget::OnFullscreenTransitionComplete( @@ -723,6 +743,14 @@ void BridgedNativeWidget::ToggleDesiredFullscreenState(bool async) { } void BridgedNativeWidget::OnSizeChanged() { + const gfx::Rect new_bounds = native_widget_mac_->GetWindowBoundsInScreen(); + if (new_bounds.origin() != last_window_frame_origin_) { + native_widget_mac_->GetWidget()->OnNativeWidgetMove(); + last_window_frame_origin_ = new_bounds.origin(); + } + + // Note we can't use new_bounds.size(), since it includes the titlebar for the + // purposes of detecting a window move. gfx::Size new_size = GetClientAreaSize(); native_widget_mac_->GetWidget()->OnNativeWidgetSizeChanged(new_size); if (layer()) { @@ -733,6 +761,13 @@ void BridgedNativeWidget::OnSizeChanged() { } void BridgedNativeWidget::OnPositionChanged() { + // When a window grows vertically, the AppKit origin changes, but as far as + // tookit-views is concerned, the window hasn't moved. Suppress these. + const gfx::Rect new_bounds = native_widget_mac_->GetWindowBoundsInScreen(); + if (new_bounds.origin() == last_window_frame_origin_) + return; + + last_window_frame_origin_ = new_bounds.origin(); native_widget_mac_->GetWidget()->OnNativeWidgetMove(); } @@ -767,12 +802,11 @@ void BridgedNativeWidget::OnVisibilityChanged() { // TODO(tapted): Investigate whether we want this for Mac. This is what Aura // does, and it is what tests expect. However, because layer drawing is // asynchronous (and things like deminiaturize in AppKit are not), it can - // result in a CALayer appearing on screen before it has been redrawn in the - // GPU process. This is a general problem. In content, a helper class, - // RenderWidgetResizeHelper, blocks the UI thread in -[NSView setFrameSize:] - // and RenderWidgetHostView::Show() until a frame is ready. + // result in the compositor producing a blank frame during the time that the + // layer is not visible. Avoid this by locking the compositor (preventing any + // new frames) in UpdateLayerVisibility whenever the layer is hidden. if (layer()) { - layer()->SetVisible(window_visible_); + UpdateLayerVisibility(); layer()->SchedulePaint(gfx::Rect(GetClientAreaSize())); // For translucent windows which are made visible, recalculate shadow when @@ -875,7 +909,7 @@ void BridgedNativeWidget::CreateLayer(ui::LayerType layer_type, SetLayer(std::make_unique<ui::Layer>(layer_type)); // Note, except for controls, this will set the layer to be hidden, since it // is only called during Init(). - layer()->SetVisible(window_visible_); + UpdateLayerVisibility(); layer()->set_delegate(this); InitCompositor(); @@ -893,12 +927,16 @@ void BridgedNativeWidget::CreateLayer(ui::LayerType layer_type, // native shape is what's most appropriate for displaying sheets on Mac. if (translucent && !native_widget_mac_->IsWindowModalSheet()) { [window_ setOpaque:NO]; - // For Mac OS versions earlier than Yosemite, the Window server isn't able - // to generate a window shadow from the composited CALayer. To get around - // this, let the window background remain opaque and clip the window - // boundary in drawRect method of BridgedContentView. See crbug.com/543671. - if (base::mac::IsAtLeastOS10_10()) - [window_ setBackgroundColor:[NSColor clearColor]]; + [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_); } UpdateLayerProperties(); @@ -970,6 +1008,48 @@ void BridgedNativeWidget::ReparentNativeView(NSView* native_view, } } +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); + + // Custom animations are only used for tab-modals. Note this also checks the + // native animation property. Clearing that will also disable custom + // animations to ensure that the views::Widget API behaves consistently. + return (transitions_to_animate_ & transition) && + native_widget_mac_->GetWidget()->IsModal() && + [window_ animationBehavior] != NSWindowAnimationBehaviorNone && + !base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kDisableModalAnimations); +} + +//////////////////////////////////////////////////////////////////////////////// +// BridgedNativeWidget, ui::CATransactionObserver + +bool BridgedNativeWidget::ShouldWaitInPreCommit() { + if (!window_visible_) + return false; + if (ca_transaction_sync_suppressed_) + return false; + if (!compositor_) + return false; + return !compositor_->widget()->HasFrameOfSize(GetClientAreaSize()); +} + +base::TimeDelta BridgedNativeWidget::PreCommitTimeout() { + return kUIPaintTimeout; +} + //////////////////////////////////////////////////////////////////////////////// // BridgedNativeWidget, internal::InputMethodDelegate: @@ -1011,6 +1091,9 @@ void BridgedNativeWidget::OnDidChangeFocus(View* focused_before, native_widget_mac_->GetWidget()->GetInputMethod(); if (input_method) { ui::TextInputClient* input_client = input_method->GetTextInputClient(); + // 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); [bridged_view_ setTextInputClient:input_client]; } } @@ -1019,7 +1102,6 @@ void BridgedNativeWidget::OnDidChangeFocus(View* focused_before, // BridgedNativeWidget, LayerDelegate: void BridgedNativeWidget::OnPaintLayer(const ui::PaintContext& context) { - DCHECK(window_visible_); native_widget_mac_->GetWidget()->OnNativeWidgetPaint(context); } @@ -1033,28 +1115,21 @@ void BridgedNativeWidget::OnDeviceScaleFactorChanged( //////////////////////////////////////////////////////////////////////////////// // BridgedNativeWidget, AcceleratedWidgetMac: -NSView* BridgedNativeWidget::AcceleratedWidgetGetNSView() const { - return compositor_superview_; -} - void BridgedNativeWidget::AcceleratedWidgetCALayerParamsUpdated() { // Ignore frames arriving "late" for an old size. A frame at the new size // should arrive soon. - if (!compositor_widget_->HasFrameOfSize(GetClientAreaSize())) + if (!compositor_->widget()->HasFrameOfSize(GetClientAreaSize())) return; // Update the DisplayCALayerTree with the most recent CALayerParams, to make // the content display on-screen. const gfx::CALayerParams* ca_layer_params = - compositor_widget_->GetCALayerParams(); + compositor_->widget()->GetCALayerParams(); if (ca_layer_params) display_ca_layer_tree_->UpdateCALayerTree(*ca_layer_params); - if (initial_visibility_suppressed_) { - initial_visibility_suppressed_ = false; - [window_ setAlphaValue:1.0]; - [window_ setIgnoresMouseEvents:NO]; - } + if (ca_transaction_sync_suppressed_) + ca_transaction_sync_suppressed_ = false; if (invalidate_shadow_on_frame_swap_) { invalidate_shadow_on_frame_swap_ = false; @@ -1173,7 +1248,6 @@ gfx::Size BridgedNativeWidget::GetClientAreaSize() const { void BridgedNativeWidget::CreateCompositor() { DCHECK(!compositor_); - DCHECK(!compositor_widget_); DCHECK(ViewsDelegate::GetInstance()); ui::ContextFactory* context_factory = @@ -1184,24 +1258,20 @@ void BridgedNativeWidget::CreateCompositor() { AddCompositorSuperview(); - compositor_widget_.reset(new ui::AcceleratedWidgetMac()); - compositor_.reset(new ui::Compositor( - context_factory_private->AllocateFrameSinkId(), context_factory, - context_factory_private, GetCompositorTaskRunner(), - features::IsSurfaceSynchronizationEnabled(), - ui::IsPixelCanvasRecordingEnabled())); - compositor_->SetAcceleratedWidget(compositor_widget_->accelerated_widget()); - compositor_widget_->SetNSView(this); + compositor_ = ui::RecyclableCompositorMacFactory::Get()->CreateCompositor( + context_factory, context_factory_private); + compositor_->widget()->SetNSView(this); } void BridgedNativeWidget::InitCompositor() { + TRACE_EVENT0("ui", "BridgedNativeWidget::InitCompositor"); DCHECK(layer()); float scale_factor = GetDeviceScaleFactorFromView(compositor_superview_); gfx::Size size_in_dip = GetClientAreaSize(); - compositor_->SetScaleAndSize(scale_factor, - ConvertSizeToPixel(scale_factor, size_in_dip), - parent_local_surface_id_allocator_.GenerateId()); - compositor_->SetRootLayer(layer()); + compositor_->UpdateSurface(ConvertSizeToPixel(scale_factor, size_in_dip), + scale_factor); + compositor_->compositor()->SetRootLayer(layer()); + compositor_->Unsuspend(); } void BridgedNativeWidget::DestroyCompositor() { @@ -1216,13 +1286,12 @@ void BridgedNativeWidget::DestroyCompositor() { } DestroyLayer(); - if (!compositor_widget_) { - DCHECK(!compositor_); + if (!compositor_) return; - } - compositor_widget_->ResetNSView(); - compositor_.reset(); - compositor_widget_.reset(); + compositor_->widget()->ResetNSView(); + compositor_->compositor()->SetRootLayer(nullptr); + ui::RecyclableCompositorMacFactory::Get()->RecycleCompositor( + std::move(compositor_)); } void BridgedNativeWidget::AddCompositorSuperview() { @@ -1261,14 +1330,11 @@ void BridgedNativeWidget::UpdateLayerProperties() { gfx::Size size_in_dip = GetClientAreaSize(); gfx::Size size_in_pixel = ConvertSizeToPixel(scale_factor, size_in_dip); - layer()->SetBounds(gfx::Rect(size_in_dip)); + if (!ca_transaction_sync_suppressed_) + ui::CATransactionCoordinator::Get().Synchronize(); - if (compositor_->size() != size_in_pixel || - compositor_->device_scale_factor() != scale_factor) { - compositor_->SetScaleAndSize( - scale_factor, size_in_pixel, - parent_local_surface_id_allocator_.GenerateId()); - } + layer()->SetBounds(gfx::Rect(size_in_dip)); + compositor_->UpdateSurface(size_in_pixel, scale_factor); // For a translucent window, the shadow calculation needs to be carried out // after the frame from the compositor arrives. @@ -1277,7 +1343,8 @@ void BridgedNativeWidget::UpdateLayerProperties() { } void BridgedNativeWidget::MaybeWaitForFrame(const gfx::Size& size_in_dip) { - if (!layer()->IsDrawn() || compositor_widget_->HasFrameOfSize(size_in_dip)) + return; // TODO(https://crbug.com/682825): Delete this during cleanup. + if (!layer()->IsDrawn() || compositor_->widget()->HasFrameOfSize(size_in_dip)) return; const int kPaintMsgTimeoutMS = 50; @@ -1293,7 +1360,7 @@ void BridgedNativeWidget::MaybeWaitForFrame(const gfx::Size& size_in_dip) { // Since the UI thread is blocked, the size shouldn't change. DCHECK(size_in_dip == GetClientAreaSize()); - if (compositor_widget_->HasFrameOfSize(size_in_dip)) + if (compositor_->widget()->HasFrameOfSize(size_in_dip)) return; // Frame arrived. } } @@ -1303,16 +1370,20 @@ void BridgedNativeWidget::ShowAsModalSheet() { // 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; - layer()->SetVisible(true); + UpdateLayerVisibility(); native_widget_mac_->GetWidget()->OnNativeWidgetVisibilityChanged(true); MaybeWaitForFrame(GetClientAreaSize()); 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_ delegate] + modalDelegate:window_ didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:nullptr]; } @@ -1328,4 +1399,12 @@ NSMutableDictionary* BridgedNativeWidget::GetWindowProperties() const { return properties; } +void BridgedNativeWidget::UpdateLayerVisibility() { + layer()->SetVisible(window_visible_); + if (window_visible_) + compositor_->Unsuspend(); + else + compositor_->Suspend(); +} + } // namespace views diff --git a/chromium/ui/views/cocoa/bridged_native_widget_unittest.mm b/chromium/ui/views/cocoa/bridged_native_widget_unittest.mm index 282a17b231e..f25287d5bb5 100644 --- a/chromium/ui/views/cocoa/bridged_native_widget_unittest.mm +++ b/chromium/ui/views/cocoa/bridged_native_widget_unittest.mm @@ -10,6 +10,7 @@ #import "base/mac/foundation_util.h" #import "base/mac/mac_util.h" +#import "base/mac/scoped_objc_class_swizzler.h" #import "base/mac/sdk_forward_declarations.h" #include "base/macros.h" #include "base/strings/stringprintf.h" @@ -28,6 +29,7 @@ #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" #include "ui/views/test/test_views_delegate.h" #include "ui/views/view.h" @@ -197,8 +199,54 @@ bool IsRTLSelectBuggy(SEL sel) { sel == @selector(moveLeftAndModifySelection:); } +// Used by InterpretKeyEventsDonorForNSView to simulate IME behavior. +using InterpretKeyEventsCallback = base::RepeatingCallback<void(id)>; +InterpretKeyEventsCallback* g_fake_interpret_key_events = nullptr; + +// Used by UpdateWindowsDonorForNSApp to hook -[NSApp updateWindows]. +base::RepeatingClosure* g_update_windows_closure = nullptr; + +// Used to provide a return value for +[NSTextInputContext currentInputContext]. +NSTextInputContext* g_fake_current_input_context = nullptr; + } // namespace +// Class to hook [NSView interpretKeyEvents:] to simulate it interacting with an +// IME window. +@interface InterpretKeyEventsDonorForNSView : NSView +@end + +@implementation InterpretKeyEventsDonorForNSView + +- (void)interpretKeyEvents:(NSArray<NSEvent*>*)eventArray { + ASSERT_TRUE(g_fake_interpret_key_events); + g_fake_interpret_key_events->Run(self); +} + +@end + +@interface UpdateWindowsDonorForNSApp : NSApplication +@end + +@implementation UpdateWindowsDonorForNSApp + +- (void)updateWindows { + ASSERT_TRUE(g_update_windows_closure); + g_update_windows_closure->Run(); +} + +@end +@interface CurrentInputContextDonorForNSTextInputContext : NSTextInputContext +@end + +@implementation CurrentInputContextDonorForNSTextInputContext + ++ (NSTextInputContext*)currentInputContext { + return g_fake_current_input_context; +} + +@end + // Class to override -[NSWindow toggleFullScreen:] to a no-op. This simulates // NSWindow's behavior when attempting to toggle fullscreen state again, when // the last attempt failed but Cocoa has not yet sent @@ -326,22 +374,21 @@ class BridgedNativeWidgetTestBase : public ui::CocoaTest { DISALLOW_COPY_AND_ASSIGN(BridgedNativeWidgetTestBase); }; -class BridgedNativeWidgetTest : public BridgedNativeWidgetTestBase { +class BridgedNativeWidgetTest : public BridgedNativeWidgetTestBase, + public TextfieldController { public: + using HandleKeyEventCallback = + base::RepeatingCallback<bool(Textfield*, const ui::KeyEvent& key_event)>; + BridgedNativeWidgetTest(); ~BridgedNativeWidgetTest() override; // Install a textfield with input type |text_input_type| in the view hierarchy // and make it the text input client. Also initializes |dummy_text_view_|. - void InstallTextField(const base::string16& text, - ui::TextInputType text_input_type); - - // Install a textfield with input type ui::TEXT_INPUT_TYPE_TEXT in the view - // hierarchy and make it the text input client. Also initializes - // |dummy_text_view_|. - void InstallTextField(const base::string16& text); - - void InstallTextField(const std::string& text); + Textfield* InstallTextField( + const base::string16& text, + ui::TextInputType text_input_type = ui::TEXT_INPUT_TYPE_TEXT); + Textfield* InstallTextField(const std::string& text); // Returns the actual current text for |ns_view_|, or the selected substring. NSString* GetActualText(); @@ -372,10 +419,17 @@ class BridgedNativeWidgetTest : public BridgedNativeWidgetTestBase { // Helper method to set the private |keyDownEvent_| field on |ns_view_|. void SetKeyDownEvent(NSEvent* event); + // Sets a callback to run on the next HandleKeyEvent(). + void SetHandleKeyEventCallback(HandleKeyEventCallback callback); + // testing::Test: void SetUp() override; void TearDown() override; + // TextfieldController: + bool HandleKeyEvent(Textfield* sender, + const ui::KeyEvent& key_event) override; + protected: // Test delete to beginning of line or paragraph based on |sel|. |sel| can be // either deleteToBeginningOfLine: or deleteToBeginningOfParagraph:. @@ -402,6 +456,8 @@ class BridgedNativeWidgetTest : public BridgedNativeWidgetTestBase { // An NSTextView which helps set the expectations for our tests. base::scoped_nsobject<NSTextView> dummy_text_view_; + HandleKeyEventCallback handle_key_event_callback_; + base::test::ScopedTaskEnvironment scoped_task_environment_; private: @@ -415,12 +471,13 @@ BridgedNativeWidgetTest::BridgedNativeWidgetTest() BridgedNativeWidgetTest::~BridgedNativeWidgetTest() { } -void BridgedNativeWidgetTest::InstallTextField( +Textfield* BridgedNativeWidgetTest::InstallTextField( const base::string16& text, ui::TextInputType text_input_type) { Textfield* textfield = new Textfield(); textfield->SetText(text); textfield->SetTextInputType(text_input_type); + textfield->set_controller(this); view_->RemoveAllChildViews(true); view_->AddChildView(textfield); textfield->SetBoundsRect(init_params_.bounds); @@ -437,14 +494,11 @@ void BridgedNativeWidgetTest::InstallTextField( dummy_text_view_.reset( [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, 100, 100)]); [dummy_text_view_ setString:SysUTF16ToNSString(text)]; + return textfield; } -void BridgedNativeWidgetTest::InstallTextField(const base::string16& text) { - InstallTextField(text, ui::TEXT_INPUT_TYPE_TEXT); -} - -void BridgedNativeWidgetTest::InstallTextField(const std::string& text) { - InstallTextField(base::ASCIIToUTF16(text), ui::TEXT_INPUT_TYPE_TEXT); +Textfield* BridgedNativeWidgetTest::InstallTextField(const std::string& text) { + return InstallTextField(base::ASCIIToUTF16(text)); } NSString* BridgedNativeWidgetTest::GetActualText() { @@ -502,6 +556,11 @@ void BridgedNativeWidgetTest::SetKeyDownEvent(NSEvent* event) { [ns_view_ setValue:event forKey:@"keyDownEvent_"]; } +void BridgedNativeWidgetTest::SetHandleKeyEventCallback( + HandleKeyEventCallback callback) { + handle_key_event_callback_ = std::move(callback); +} + void BridgedNativeWidgetTest::SetUp() { BridgedNativeWidgetTestBase::SetUp(); @@ -534,6 +593,13 @@ void BridgedNativeWidgetTest::TearDown() { BridgedNativeWidgetTestBase::TearDown(); } +bool BridgedNativeWidgetTest::HandleKeyEvent(Textfield* sender, + const ui::KeyEvent& key_event) { + if (handle_key_event_callback_) + return handle_key_event_callback_.Run(sender, key_event); + return false; +} + void BridgedNativeWidgetTest::TestDeleteBeginning(SEL sel) { InstallTextField("foo bar baz"); EXPECT_EQ_RANGE_3(NSMakeRange(11, 0), GetExpectedSelectionRange(), @@ -1325,6 +1391,187 @@ TEST_F(BridgedNativeWidgetTest, TextInput_FirstRectForCharacterRange) { EXPECT_EQ_RANGE(query_range, actual_range); } +// Test simulated codepaths for IMEs that do not always "mark" text. E.g. +// phonetic languages such as Korean and Vietnamese. +TEST_F(BridgedNativeWidgetTest, TextInput_SimulatePhoneticIme) { + Textfield* textfield = InstallTextField(""); + EXPECT_TRUE([ns_view_ textInputClient]); + + base::mac::ScopedObjCClassSwizzler interpret_key_events_swizzler( + [NSView class], [InterpretKeyEventsDonorForNSView class], + @selector(interpretKeyEvents:)); + + // 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); + 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); + 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. + }); + + // Add a hook for the KeyEvent being received by the TextfieldController. E.g. + // this is where the Omnibox would start to search when Return is pressed. + bool saw_vkey_return = false; + SetHandleKeyEventCallback(base::BindRepeating( + [](bool* saw_return, Textfield* textfield, const ui::KeyEvent& event) { + if (event.key_code() == ui::VKEY_RETURN) { + EXPECT_FALSE(*saw_return); + *saw_return = true; + EXPECT_EQ(base::SysNSStringToUTF16(@"배"), textfield->text()); + } + return false; + }, + &saw_vkey_return)); + + EXPECT_EQ(base::UTF8ToUTF16(""), textfield->text()); + + g_fake_interpret_key_events = &handle_q_in_ime; + [ns_view_ keyDown:q_in_ime]; + EXPECT_EQ(base::SysNSStringToUTF16(@"ㅂ"), textfield->text()); + EXPECT_FALSE(saw_vkey_return); + + g_fake_interpret_key_events = &handle_o_in_ime; + [ns_view_ keyDown:o_in_ime]; + EXPECT_EQ(base::SysNSStringToUTF16(@"배"), textfield->text()); + EXPECT_FALSE(saw_vkey_return); + + // 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]; + EXPECT_EQ(base::SysNSStringToUTF16(@"배"), textfield->text()); + + // VKEY_RETURN should be seen by via the unhandled key event handler (but not + // via -insertText:. + EXPECT_TRUE(saw_vkey_return); + + g_fake_interpret_key_events = nullptr; +} + +// Test a codepath that could hypothetically cause [NSApp updateWindows] to be +// called recursively due to IME dismissal during teardown triggering a focus +// change. Twice. +TEST_F(BridgedNativeWidgetTest, TextInput_RecursiveUpdateWindows) { + Textfield* textfield = InstallTextField(""); + EXPECT_TRUE([ns_view_ textInputClient]); + + base::mac::ScopedObjCClassSwizzler interpret_key_events_swizzler( + [NSView class], [InterpretKeyEventsDonorForNSView class], + @selector(interpretKeyEvents:)); + base::mac::ScopedObjCClassSwizzler update_windows_swizzler( + [NSApplication class], [UpdateWindowsDonorForNSApp class], + @selector(updateWindows)); + base::mac::ScopedObjCClassSwizzler current_input_context_swizzler( + [NSTextInputContext class], + [CurrentInputContextDonorForNSTextInputContext class], + @selector(currentInputContext)); + + int vkey_return_count = 0; + + // Everything happens with this one event. + NSEvent* return_with_fake_ime = cocoa_test_event_utils::SynthesizeKeyEvent( + widget_->GetNativeWindow(), true, ui::VKEY_RETURN, 0); + + InterpretKeyEventsCallback generate_return_and_fake_ime = base::BindRepeating( + [](int* saw_return_count, id view) { + EXPECT_EQ(0, *saw_return_count); + // First generate the return to simulate an input context change. + [view insertText:@"\r" replacementRange:NSMakeRange(NSNotFound, 0)]; + + EXPECT_EQ(1, *saw_return_count); + }, + &vkey_return_count); + + bool saw_update_windows = false; + base::RepeatingClosure update_windows_closure = base::BindRepeating( + [](bool* saw_update_windows, BridgedContentView* view, + Textfield* textfield) { + // Ensure updateWindows is not invoked recursively. + EXPECT_FALSE(*saw_update_windows); + *saw_update_windows = true; + + // Inside updateWindows, assume the IME got dismissed and wants to + // insert its last bit of text for the old input context. + [view insertText:@"배" replacementRange:NSMakeRange(0, 1)]; + + // This is triggered by the setTextInputClient:nullptr in + // SetHandleKeyEventCallback(), so -inputContext should also be nil. + EXPECT_FALSE([view inputContext]); + + // Ensure we can't recursively call updateWindows. A TextInputClient + // reacting to InsertChar could theoretically do this, but toolkit-views + // DCHECKs if there is recursive event dispatch, so call + // setTextInputClient directly. + [view setTextInputClient:textfield]; + + // Finally simulate what -[NSApp updateWindows] should _actually_ do, + // which is to update the input context (from the first responder). + g_fake_current_input_context = [view inputContext]; + + // Now, the |textfield| set above should have been set again. + EXPECT_TRUE(g_fake_current_input_context); + }, + &saw_update_windows, ns_view_, textfield); + + SetHandleKeyEventCallback(base::BindRepeating( + [](int* saw_return_count, BridgedContentView* view, Textfield* textfield, + const ui::KeyEvent& event) { + if (event.key_code() == ui::VKEY_RETURN) { + *saw_return_count += 1; + // Simulate Textfield::OnBlur() by clearing the input method. + // Textfield needs to be in a Widget to do this normally. + [view setTextInputClient:nullptr]; + } + return false; + }, + &vkey_return_count, ns_view_)); + + // Starting text (just insert it). + [ns_view_ insertText:@"ㅂ" replacementRange:NSMakeRange(NSNotFound, 0)]; + + EXPECT_EQ(base::SysNSStringToUTF16(@"ㅂ"), textfield->text()); + + g_fake_interpret_key_events = &generate_return_and_fake_ime; + g_update_windows_closure = &update_windows_closure; + g_fake_current_input_context = [ns_view_ inputContext]; + EXPECT_TRUE(g_fake_current_input_context); + [ns_view_ keyDown:return_with_fake_ime]; + + // We should see one VKEY_RETURNs and one updateWindows. In particular, note + // that there is not a second VKEY_RETURN seen generated by keyDown: thinking + // the event has been unhandled. This is because it was handled when the fake + // IME sent \r. + EXPECT_TRUE(saw_update_windows); + EXPECT_EQ(1, vkey_return_count); + + // The text inserted during updateWindows should have been inserted, even + // though we were trying to change the input context. + EXPECT_EQ(base::SysNSStringToUTF16(@"배"), textfield->text()); + + EXPECT_TRUE(g_fake_current_input_context); + + g_fake_current_input_context = nullptr; + g_fake_interpret_key_events = nullptr; + g_update_windows_closure = nullptr; +} + typedef BridgedNativeWidgetTestBase BridgedNativeWidgetSimulateFullscreenTest; // Simulate the notifications that AppKit would send out if a fullscreen diff --git a/chromium/ui/views/cocoa/cocoa_mouse_capture.mm b/chromium/ui/views/cocoa/cocoa_mouse_capture.mm index ea5bbc72139..b1f3cb216de 100644 --- a/chromium/ui/views/cocoa/cocoa_mouse_capture.mm +++ b/chromium/ui/views/cocoa/cocoa_mouse_capture.mm @@ -71,12 +71,14 @@ NSWindow* CocoaMouseCapture::ActiveEventTap::GetGlobalCaptureWindow() { } 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 | NSMouseEnteredMask | NSMouseExitedMask | - NSScrollWheelMask | NSOtherMouseDownMask | NSOtherMouseUpMask | - NSOtherMouseDraggedMask; + 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_|. diff --git a/chromium/ui/views/cocoa/drag_drop_client_mac.mm b/chromium/ui/views/cocoa/drag_drop_client_mac.mm index ce3f37b36a9..78e7b0b3226 100644 --- a/chromium/ui/views/cocoa/drag_drop_client_mac.mm +++ b/chromium/ui/views/cocoa/drag_drop_client_mac.mm @@ -104,6 +104,12 @@ void DragDropClientMac::StartDragAndDrop( NSImage* image = gfx::NSImageFromImageSkiaWithColorSpace( provider.GetDragImage(), base::mac::GetSRGBColorSpace()); + // TODO(crbug/876201): This shouldn't happen. When a repro for this + // is identified and the bug is fixed, change the early return to + // a DCHECK. + if (!image || NSEqualSizes([image size], NSZeroSize)) + return; + base::scoped_nsobject<NSPasteboardItem> item([[NSPasteboardItem alloc] init]); [item setDataProvider:data_source_.get() forTypes:provider.GetAvailableTypes()]; 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 54989956996..dd5edea74a8 100644 --- a/chromium/ui/views/cocoa/drag_drop_client_mac_unittest.mm +++ b/chromium/ui/views/cocoa/drag_drop_client_mac_unittest.mm @@ -12,6 +12,7 @@ #include "base/strings/utf_string_conversions.h" #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" #include "ui/views/test/widget_test.h" #include "ui/views/view.h" @@ -241,6 +242,8 @@ TEST_F(DragDropClientMacTest, ReleaseCapture) { OSExchangeData data; const base::string16& text = ASCIIToUTF16("text"); data.SetString(text); + data.provider().SetDragImage(gfx::test::CreateImageSkia(100, 100), + gfx::Vector2d()); SetData(data); // There's no way to cleanly stop NSDraggingSession inside unit tests, so just diff --git a/chromium/ui/views/cocoa/native_widget_mac_nswindow.h b/chromium/ui/views/cocoa/native_widget_mac_nswindow.h index adc6b041693..8a0f51b791b 100644 --- a/chromium/ui/views/cocoa/native_widget_mac_nswindow.h +++ b/chromium/ui/views/cocoa/native_widget_mac_nswindow.h @@ -9,6 +9,25 @@ #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. @@ -18,6 +37,16 @@ VIEWS_EXPORT // 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 index 0f44dc785d3..ea244d97722 100644 --- a/chromium/ui/views/cocoa/native_widget_mac_nswindow.mm +++ b/chromium/ui/views/cocoa/native_widget_mac_nswindow.mm @@ -6,15 +6,22 @@ #include "base/mac/foundation_util.h" #import "base/mac/sdk_forward_declarations.h" -#import "ui/views/cocoa/bridged_native_widget.h" #import "ui/base/cocoa/user_interface_item_command_handler.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 () @@ -28,10 +35,53 @@ - (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 @@ -59,6 +109,21 @@ [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 { @@ -82,6 +147,17 @@ // 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; @@ -89,6 +165,13 @@ 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 @@ -172,14 +255,18 @@ [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]); } -- (BOOL)redispatchKeyEvent:(NSEvent*)event { - return [commandDispatcher_ redispatchKeyEvent:event]; +- (CommandDispatcher*)commandDispatcher { + return commandDispatcher_.get(); } - (BOOL)defaultPerformKeyEquivalent:(NSEvent*)event { @@ -210,6 +297,9 @@ // 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 diff --git a/chromium/ui/views/cocoa/views_nswindow_delegate.mm b/chromium/ui/views/cocoa/views_nswindow_delegate.mm index 96acf216de7..9472e5e4043 100644 --- a/chromium/ui/views/cocoa/views_nswindow_delegate.mm +++ b/chromium/ui/views/cocoa/views_nswindow_delegate.mm @@ -4,8 +4,8 @@ #import "ui/views/cocoa/views_nswindow_delegate.h" +#include "base/bind.h" #include "base/logging.h" -#import "base/mac/bind_objc_block.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" @@ -34,7 +34,40 @@ return; cursor_.reset([newCursor retain]); - [parent_->ns_window() resetCursorRects]; + + // 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 { @@ -48,10 +81,6 @@ - (void)sheetDidEnd:(NSWindow*)sheet returnCode:(NSInteger)returnCode contextInfo:(void*)contextInfo { - // |parent_| will be null when triggered from the block in -windowWillClose:. - if (!parent_) - return; - [sheet orderOut:nil]; parent_->OnWindowWillClose(); } @@ -99,11 +128,6 @@ } - (void)windowWillClose:(NSNotification*)notification { - // Retain |self|. |parent_| should be cleared. OnWindowWillClose() may delete - // |parent_|, but it may also dealloc |self| before returning. However, the - // observers it notifies before that need a valid |parent_| on the delegate, - // so it can only be cleared after OnWindowWillClose() returns. - base::scoped_nsobject<NSObject> keepAlive(self, base::scoped_policy::RETAIN); NSWindow* window = parent_->ns_window(); if (NSWindow* sheetParent = [window sheetParent]) { // On no! Something called -[NSWindow close] on a sheet rather than calling @@ -111,20 +135,23 @@ // 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. - // The NSWindowDelegate (i.e. |self|) must also be explicitly retained. Even - // though the call to OnWindowWillClose() below will remove |self| as the - // NSWindow delegate, the call to -[NSApp beginSheet:] also took a weak - // reference to the delegate, which will be destroyed when the sheet's - // BridgedNativeWidget is destroyed. - base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, base::BindBlock(^{ - [sheetParent endSheet:window]; - [[self retain] release]; // Force |self| to be retained for the block. - })); + // 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(); - parent_ = nullptr; + // |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 { diff --git a/chromium/ui/views/cocoa/window_touch_bar_delegate.h b/chromium/ui/views/cocoa/window_touch_bar_delegate.h new file mode 100644 index 00000000000..daef5466ed6 --- /dev/null +++ b/chromium/ui/views/cocoa/window_touch_bar_delegate.h @@ -0,0 +1,21 @@ +// 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/controls/button/button.cc b/chromium/ui/views/controls/button/button.cc index 5fa2f6f1ebb..f73a3102e63 100644 --- a/chromium/ui/views/controls/button/button.cc +++ b/chromium/ui/views/controls/button/button.cc @@ -91,6 +91,7 @@ void Button::SetTooltipText(const base::string16& tooltip_text) { tooltip_text_ = tooltip_text; if (accessible_name_.empty()) accessible_name_ = tooltip_text_; + OnSetTooltipText(tooltip_text); TooltipTextChanged(); } @@ -515,6 +516,8 @@ void Button::OnClickCanceled(const ui::Event& event) { } } +void Button::OnSetTooltipText(const base::string16& tooltip_text) {} + void Button::StateChanged(ButtonState old_state) {} bool Button::IsTriggerableEvent(const ui::Event& event) { diff --git a/chromium/ui/views/controls/button/button.h b/chromium/ui/views/controls/button/button.h index 6f0ce20627b..b5347ff0e00 100644 --- a/chromium/ui/views/controls/button/button.h +++ b/chromium/ui/views/controls/button/button.h @@ -211,6 +211,9 @@ class VIEWS_EXPORT Button : public InkDropHostView, // events. virtual void OnClickCanceled(const ui::Event& event); + // Called when the tooltip is set. + virtual void OnSetTooltipText(const base::string16& tooltip_text); + // Invoked from SetState() when SetState() is passed a value that differs from // the current node_data. Button's implementation of StateChanged() does // nothing; this method is provided for subclasses that wish to do something @@ -247,6 +250,8 @@ class VIEWS_EXPORT Button : public InkDropHostView, return hover_animation_; } + FocusRing* focus_ring() { return focus_ring_.get(); } + // The button's listener. Notified when clicked. ButtonListener* listener_; diff --git a/chromium/ui/views/controls/button/checkbox.cc b/chromium/ui/views/controls/button/checkbox.cc index cfe84643274..5dc51b40272 100644 --- a/chromium/ui/views/controls/button/checkbox.cc +++ b/chromium/ui/views/controls/button/checkbox.cc @@ -33,8 +33,10 @@ namespace views { // static const char Checkbox::kViewClassName[] = "Checkbox"; -Checkbox::Checkbox(const base::string16& label, bool force_md) - : LabelButton(NULL, label), +Checkbox::Checkbox(const base::string16& label, + ButtonListener* listener, + bool force_md) + : LabelButton(listener, label), checked_(false), label_ax_id_(0), use_md_(force_md || @@ -117,7 +119,7 @@ void Checkbox::SetAssociatedLabel(View* labelling_view) { ui::AXNodeData node_data; labelling_view->GetAccessibleNodeData(&node_data); // TODO(aleventhal) automatically handle setting the name from the related - // label in view_accessibility and have it update the name if the text of the + // label in ViewAccessibility and have it update the name if the text of the // associated label changes. SetAccessibleName( node_data.GetString16Attribute(ax::mojom::StringAttribute::kName)); @@ -223,7 +225,7 @@ void Checkbox::Layout() { SkPath Checkbox::GetFocusRingPath() const { SkPath path; - gfx::Rect bounds = image()->bounds(); + gfx::Rect bounds = image()->GetMirroredBounds(); bounds.Inset(1, 1); path.addRect(RectToSkRect(bounds)); return path; diff --git a/chromium/ui/views/controls/button/checkbox.h b/chromium/ui/views/controls/button/checkbox.h index de681d69271..6fb4c93b61a 100644 --- a/chromium/ui/views/controls/button/checkbox.h +++ b/chromium/ui/views/controls/button/checkbox.h @@ -27,13 +27,11 @@ class VIEWS_EXPORT Checkbox : public LabelButton { static const char kViewClassName[]; // |force_md| forces MD even when --secondary-ui-md flag is not set. - explicit Checkbox(const base::string16& label, bool force_md = false); + explicit Checkbox(const base::string16& label, + ButtonListener* listener = nullptr, + bool force_md = false); ~Checkbox() override; - // Sets a listener for this checkbox. Checkboxes aren't required to have them - // since their state can be read independently of them being toggled. - void set_listener(ButtonListener* listener) { listener_ = listener; } - // Sets/Gets whether or not the checkbox is checked. virtual void SetChecked(bool checked); bool checked() const { return checked_; } diff --git a/chromium/ui/views/controls/button/image_button.cc b/chromium/ui/views/controls/button/image_button.cc index 677d4dc3d84..851a2079c80 100644 --- a/chromium/ui/views/controls/button/image_button.cc +++ b/chromium/ui/views/controls/button/image_button.cc @@ -305,4 +305,8 @@ void ToggleImageButton::GetAccessibleNodeData(ui::AXNodeData* node_data) { } } +bool ToggleImageButton::toggled_for_testing() const { + return toggled_; +} + } // namespace views diff --git a/chromium/ui/views/controls/button/image_button.h b/chromium/ui/views/controls/button/image_button.h index bc69e6e350b..6e5b0cb292f 100644 --- a/chromium/ui/views/controls/button/image_button.h +++ b/chromium/ui/views/controls/button/image_button.h @@ -161,6 +161,8 @@ class VIEWS_EXPORT ToggleImageButton : public ImageButton { base::string16* tooltip) const override; void GetAccessibleNodeData(ui::AXNodeData* node_data) override; + bool toggled_for_testing() const; + private: // The parent class's images_ member is used for the current images, // and this array is used to hold the alternative images. diff --git a/chromium/ui/views/controls/button/menu_button_unittest.cc b/chromium/ui/views/controls/button/menu_button_unittest.cc index e12bae6e2d9..969b67c86c5 100644 --- a/chromium/ui/views/controls/button/menu_button_unittest.cc +++ b/chromium/ui/views/controls/button/menu_button_unittest.cc @@ -264,7 +264,7 @@ class TestDragDropClient : public aura::client::DragDropClient, // True while receiving ui::LocatedEvents for drag operations. bool drag_in_progress_; - // Target window where drag operations are occuring. + // Target window where drag operations are occurring. aura::Window* target_; DISALLOW_COPY_AND_ASSIGN(TestDragDropClient); @@ -411,7 +411,7 @@ TEST_F(MenuButtonTest, ButtonStateForMenuButtonsWithPressedLocks) { pressed_lock1.reset(); EXPECT_EQ(Button::STATE_PRESSED, button()->state()); - // Reseting the final lock should return the button's state to normal... + // Resetting the final lock should return the button's state to normal... pressed_lock2.reset(); EXPECT_EQ(Button::STATE_NORMAL, button()->state()); @@ -568,8 +568,8 @@ TEST_F(MenuButtonTest, // Tests that the MenuButton does not become pressed if it can be dragged, and a // DragDropClient is processing the events. TEST_F(MenuButtonTest, DraggableMenuButtonDoesNotActivateOnDrag) { - // TODO: test uses GetContext(), which is not applicable to aura-mus. - // http://crbug.com/663809. + // TODO(https://crbug.com/663809): test uses GetContext(), which is not + // applicable to aura-mus. if (IsMus()) return; TestMenuButtonListener menu_button_listener; diff --git a/chromium/ui/views/controls/button/radio_button.cc b/chromium/ui/views/controls/button/radio_button.cc index efadd78b3e9..b9dbdbe82dc 100644 --- a/chromium/ui/views/controls/button/radio_button.cc +++ b/chromium/ui/views/controls/button/radio_button.cc @@ -22,7 +22,7 @@ const char RadioButton::kViewClassName[] = "RadioButton"; RadioButton::RadioButton(const base::string16& label, int group_id, bool force_md) - : Checkbox(label, force_md) { + : Checkbox(label, nullptr, force_md) { SetGroup(group_id); if (!UseMd()) { @@ -163,7 +163,7 @@ const gfx::VectorIcon& RadioButton::GetVectorIcon() const { SkPath RadioButton::GetFocusRingPath() const { SkPath path; - path.addOval(gfx::RectToSkRect(image()->bounds())); + path.addOval(gfx::RectToSkRect(image()->GetMirroredBounds())); return path; } diff --git a/chromium/ui/views/controls/focus_ring.cc b/chromium/ui/views/controls/focus_ring.cc index c17bc868e2f..148c22ae909 100644 --- a/chromium/ui/views/controls/focus_ring.cc +++ b/chromium/ui/views/controls/focus_ring.cc @@ -34,12 +34,12 @@ std::unique_ptr<FocusRing> FocusRing::Install(View* parent) { // static bool FocusRing::IsPathUseable(const SkPath& path) { - return path.isRect(nullptr) || path.isOval(nullptr) || path.isRRect(nullptr); + return !path.isEmpty() && (path.isRect(nullptr) || path.isOval(nullptr) || + path.isRRect(nullptr)); } void FocusRing::SetPath(const SkPath& path) { - DCHECK(IsPathUseable(path)); - path_ = path; + path_ = IsPathUseable(path) ? path : SkPath(); SchedulePaint(); } diff --git a/chromium/ui/views/controls/focus_ring.h b/chromium/ui/views/controls/focus_ring.h index 1b4d99ca2b1..a608042b24f 100644 --- a/chromium/ui/views/controls/focus_ring.h +++ b/chromium/ui/views/controls/focus_ring.h @@ -49,11 +49,15 @@ class VIEWS_EXPORT FocusRing : public View, public ViewObserver { static std::unique_ptr<FocusRing> Install(View* parent); // Returns whether this class can draw a focus ring from |path|. Not all paths - // are useable since not all paths can be easily outset. + // are useable since not all paths can be easily outset. If a FocusRing is + // configured to use an unuseable path, it will fall back to the default focus + // ring path. static bool IsPathUseable(const SkPath& path); // Sets the path to draw this FocusRing around. This path is in the parent - // view's coordinate system, *not* in the FocusRing's coordinate system. + // 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. 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 5b7f65a5522..b1abacf1e0d 100644 --- a/chromium/ui/views/controls/image_view.cc +++ b/chromium/ui/views/controls/image_view.cc @@ -9,6 +9,7 @@ #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" @@ -43,6 +44,7 @@ void ImageView::SetImage(const gfx::ImageSkia& img) { last_painted_bitmap_pixels_ = nullptr; gfx::Size pref_size(GetPreferredSize()); image_ = img; + scaled_image_ = gfx::ImageSkia(); if (pref_size != GetPreferredSize()) PreferredSizeChanged(); SchedulePaint(); @@ -207,25 +209,50 @@ void ImageView::OnPaintImage(gfx::Canvas* canvas) { last_paint_scale_ = canvas->image_scale(); last_painted_bitmap_pixels_ = nullptr; - if (image_.isNull()) + gfx::ImageSkia image = GetPaintImage(last_paint_scale_); + if (image.isNull()) return; gfx::Rect image_bounds(GetImageBounds()); if (image_bounds.IsEmpty()) return; - if (image_bounds.size() != gfx::Size(image_.width(), image_.height())) { + if (image_bounds.size() != gfx::Size(image.width(), image.height())) { // Resize case cc::PaintFlags flags; flags.setFilterQuality(kLow_SkFilterQuality); - canvas->DrawImageInt(image_, 0, 0, image_.width(), image_.height(), + canvas->DrawImageInt(image, 0, 0, image.width(), image.height(), image_bounds.x(), image_bounds.y(), image_bounds.width(), image_bounds.height(), true, flags); } else { - canvas->DrawImageInt(image_, image_bounds.x(), image_bounds.y()); + canvas->DrawImageInt(image, image_bounds.x(), image_bounds.y()); } - last_painted_bitmap_pixels_ = GetBitmapPixels(image_, last_paint_scale_); + last_painted_bitmap_pixels_ = GetBitmapPixels(image, last_paint_scale_); +} + +gfx::ImageSkia ImageView::GetPaintImage(float scale) { + if (image_.isNull()) + return image_; + + const gfx::ImageSkiaRep& rep = image_.GetRepresentation(scale); + if (rep.scale() == scale) + return image_; + + if (scaled_image_.HasRepresentation(scale)) + return scaled_image_; + + // Only caches one image rep for the current scale. + scaled_image_ = gfx::ImageSkia(); + + 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_BEST, + scaled_size.width(), scaled_size.height()), + scale)); + return scaled_image_; } } // namespace views diff --git a/chromium/ui/views/controls/image_view.h b/chromium/ui/views/controls/image_view.h index c19a3c5ea8e..c3adf654e36 100644 --- a/chromium/ui/views/controls/image_view.h +++ b/chromium/ui/views/controls/image_view.h @@ -87,6 +87,9 @@ class VIEWS_EXPORT ImageView : public View { void OnPaintImage(gfx::Canvas* canvas); + // Gets an ImageSkia to paint that has proper rep for |scale|. + gfx::ImageSkia GetPaintImage(float scale); + // Returns true if |img| is the same as the last image we painted. This is // intended to be a quick check, not exhaustive. In other words it's possible // for this to return false even though the images are in fact equal. @@ -105,6 +108,9 @@ class VIEWS_EXPORT ImageView : public View { // The underlying image. gfx::ImageSkia image_; + // Caches the scaled image reps. + gfx::ImageSkia scaled_image_; + // Horizontal alignment. Alignment horizontal_alignment_; diff --git a/chromium/ui/views/controls/label_unittest.cc b/chromium/ui/views/controls/label_unittest.cc index ec25ede20df..e5eafe1bd53 100644 --- a/chromium/ui/views/controls/label_unittest.cc +++ b/chromium/ui/views/controls/label_unittest.cc @@ -16,6 +16,7 @@ #include "ui/base/clipboard/clipboard.h" #include "ui/base/l10n/l10n_util.h" #include "ui/base/ui_base_features.h" +#include "ui/base/ui_base_switches.h" #include "ui/compositor/canvas_painter.h" #include "ui/events/base_event_utils.h" #include "ui/events/test/event_generator.h" @@ -276,10 +277,14 @@ class MDLabelTest : public LabelTest, // LabelTest: void SetUp() override { - if (GetParam() == SecondaryUiMode::MD) + if (GetParam() == SecondaryUiMode::MD) { scoped_feature_list_.InitAndEnableFeature(features::kSecondaryUiMd); - else + } else { + // Force Refresh UI to be off, since that mode implies MD secondary UI. + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + switches::kTopChromeMD, switches::kTopChromeMDMaterial); scoped_feature_list_.InitAndDisableFeature(features::kSecondaryUiMd); + } LabelTest::SetUp(); } diff --git a/chromium/ui/views/controls/menu/menu_config.cc b/chromium/ui/views/controls/menu/menu_config.cc index c398009a47d..db532134db5 100644 --- a/chromium/ui/views/controls/menu/menu_config.cc +++ b/chromium/ui/views/controls/menu/menu_config.cc @@ -9,11 +9,12 @@ #include "ui/views/controls/menu/menu_image_util.h" #include "ui/views/controls/menu/menu_item_view.h" #include "ui/views/round_rect_painter.h" + namespace views { MenuConfig::MenuConfig() : arrow_color(SK_ColorBLACK), - menu_vertical_border_size(3), + menu_vertical_border_size(4), menu_horizontal_border_size(views::RoundRectPainter::kBorderWidth), submenu_horizontal_inset(3), item_top_margin(4), @@ -23,12 +24,12 @@ MenuConfig::MenuConfig() minimum_text_item_height(0), minimum_container_item_height(0), minimum_menu_width(0), - item_left_margin(10), - touchable_item_left_margin(16), - label_to_arrow_padding(10), + // TODO(ftirelo): Paddings should come from the layout provider, once + // Harmony is the default behavior. + item_horizontal_padding(8), + touchable_item_horizontal_padding(16), + label_to_arrow_padding(8), arrow_to_edge_padding(5), - icon_to_label_padding(10), - touchable_icon_to_label_padding(16), touchable_icon_size(20), touchable_icon_color(SkColorSetRGB(0x5F, 0x63, 0x60)), check_width(kMenuCheckSize), @@ -44,7 +45,6 @@ MenuConfig::MenuConfig() show_mnemonics(false), use_mnemonics(true), scroll_arrow_height(3), - label_to_minor_text_padding(10), item_min_height(0), actionable_submenu_arrow_to_edge_padding(14), actionable_submenu_width(37), diff --git a/chromium/ui/views/controls/menu/menu_config.h b/chromium/ui/views/controls/menu/menu_config.h index c07195b1be6..df02a976c59 100644 --- a/chromium/ui/views/controls/menu/menu_config.h +++ b/chromium/ui/views/controls/menu/menu_config.h @@ -64,11 +64,11 @@ struct VIEWS_EXPORT MenuConfig { int minimum_container_item_height; int minimum_menu_width; - // Margins between the left of the item and the icon. - int item_left_margin; + // Horizontal padding between components in a menu item. + int item_horizontal_padding; - // Margins between the left of the touchable item and the icon. - int touchable_item_left_margin; + // Horizontal padding between components in a touchable menu item. + int touchable_item_horizontal_padding; // Padding between the label and submenu arrow. int label_to_arrow_padding; @@ -76,12 +76,6 @@ struct VIEWS_EXPORT MenuConfig { // Padding between the arrow and the edge. int arrow_to_edge_padding; - // Padding between the icon and label. - int icon_to_label_padding; - - // Padding between the icon and label for touchable menu items. - int touchable_icon_to_label_padding; - // The icon size used for icons in touchable menu items. int touchable_icon_size; @@ -127,10 +121,6 @@ struct VIEWS_EXPORT MenuConfig { // Height of the scroll arrow. int scroll_arrow_height; - // Padding between the label and minor text. Only used if there is an - // accelerator or sublabel. - int label_to_minor_text_padding; - // Minimum height of menu item. int item_min_height; diff --git a/chromium/ui/views/controls/menu/menu_config_chromeos.cc b/chromium/ui/views/controls/menu/menu_config_chromeos.cc index 75250e7c43f..085c1adb937 100644 --- a/chromium/ui/views/controls/menu/menu_config_chromeos.cc +++ b/chromium/ui/views/controls/menu/menu_config_chromeos.cc @@ -17,8 +17,6 @@ void MenuConfig::Init() { separator_spacing_height = 7; separator_lower_height = 8; separator_upper_height = 8; - label_to_arrow_padding = 20; - label_to_minor_text_padding = 20; always_use_icon_to_label_padding = true; align_arrow_and_shortcut = true; offset_context_menus = true; diff --git a/chromium/ui/views/controls/menu/menu_config_mac.mm b/chromium/ui/views/controls/menu/menu_config_mac.mm index 7854af9d373..90939262b2e 100644 --- a/chromium/ui/views/controls/menu/menu_config_mac.mm +++ b/chromium/ui/views/controls/menu/menu_config_mac.mm @@ -14,25 +14,21 @@ namespace { void InitMaterialMenuConfig(views::MenuConfig* config) { // These config parameters are from https://crbug.com/829347 and the spec // images linked from that bug. - config->menu_vertical_border_size = 8; config->menu_horizontal_border_size = 0; config->submenu_horizontal_inset = 0; - config->minimum_text_item_height = 32; - config->minimum_container_item_height = 48; + config->minimum_text_item_height = 28; + config->minimum_container_item_height = 40; config->minimum_menu_width = 320; - config->item_left_margin = 8; config->label_to_arrow_padding = 0; config->arrow_to_edge_padding = 16; - config->icon_to_label_padding = 8; config->check_width = 16; config->check_height = 16; config->arrow_width = 8; - config->separator_height = 17; - config->separator_lower_height = 9; - config->separator_upper_height = 9; - config->separator_spacing_height = 9; + config->separator_height = 9; + config->separator_lower_height = 4; + config->separator_upper_height = 4; + config->separator_spacing_height = 5; config->separator_thickness = 1; - config->label_to_minor_text_padding = 8; config->align_arrow_and_shortcut = true; config->use_outer_border = false; config->icons_in_label = true; diff --git a/chromium/ui/views/controls/menu/menu_controller.cc b/chromium/ui/views/controls/menu/menu_controller.cc index 00590f09d91..beecb7f2717 100644 --- a/chromium/ui/views/controls/menu/menu_controller.cc +++ b/chromium/ui/views/controls/menu/menu_controller.cc @@ -10,6 +10,7 @@ #include "base/i18n/case_conversion.h" #include "base/i18n/rtl.h" #include "base/macros.h" +#include "base/numerics/ranges.h" #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "build/build_config.h" @@ -77,7 +78,7 @@ const int kCloseOnExitTime = 1200; // If a context menu is invoked by touch, we shift the menu by this offset so // that the finger does not obscure the menu. -const int kCenteredContextMenuYOffset = -15; +const int kTouchYPadding = 15; // The spacing offset for the bubble tip. const int kBubbleTipSizeLeftRight = 12; @@ -311,8 +312,8 @@ class MenuController::MenuScrollTask { if (!scrolling_timer_.IsRunning()) { scrolling_timer_.Start(FROM_HERE, - TimeDelta::FromMilliseconds(kScrollTimerMS), - this, &MenuScrollTask::Run); + TimeDelta::FromMilliseconds(kScrollTimerMS), this, + &MenuScrollTask::Run); } } @@ -333,9 +334,10 @@ class MenuController::MenuScrollTask { const int delta_y = static_cast<int>( (base::Time::Now() - start_scroll_time_).InMilliseconds() * pixels_per_second_ / 1000); - vis_rect.set_y(is_scrolling_up_ ? - std::max(0, start_y_ - delta_y) : - std::min(submenu_->height() - vis_rect.height(), start_y_ + delta_y)); + vis_rect.set_y(is_scrolling_up_ + ? std::max(0, start_y_ - delta_y) + : std::min(submenu_->height() - vis_rect.height(), + start_y_ + delta_y)); submenu_->ScrollRectToVisible(vis_rect); } @@ -367,8 +369,7 @@ struct MenuController::SelectByCharDetails { : first_match(-1), has_multiple(false), index_of_item(-1), - next_match(-1) { - } + next_match(-1) {} // Index of the first menu with the specified mnemonic. int first_match; @@ -392,8 +393,7 @@ MenuController::State::State() hot_button(nullptr), submenu_open(false), anchor(MENU_ANCHOR_TOPLEFT), - context_menu(false) { -} + context_menu(false) {} MenuController::State::State(const State& other) = default; @@ -424,16 +424,16 @@ void MenuController::Run(Widget* parent, menu_start_time_ = base::TimeTicks::Now(); menu_start_mouse_press_loc_ = gfx::Point(); + ui::Event* event = nullptr; if (parent) { View* root_view = parent->GetRootView(); if (root_view) { - const ui::Event* event = - static_cast<internal::RootView*>(root_view)->current_event(); + event = static_cast<internal::RootView*>(root_view)->current_event(); if (event && event->type() == ui::ET_MOUSE_PRESSED) { gfx::Point screen_loc( static_cast<const ui::MouseEvent*>(event)->location()); - View::ConvertPointToScreen( - static_cast<View*>(event->target()), &screen_loc); + View::ConvertPointToScreen(static_cast<View*>(event->target()), + &screen_loc); menu_start_mouse_press_loc_ = screen_loc; } } @@ -476,8 +476,10 @@ void MenuController::Run(Widget* parent, // Set the selection, which opens the initial menu. SetSelection(root, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); - if (button) - pressed_lock_ = std::make_unique<MenuButton::PressedLock>(button); + if (button) { + pressed_lock_ = std::make_unique<MenuButton::PressedLock>( + button, false, ui::LocatedEvent::FromIfValid(event)); + } if (for_drop_) { if (!is_nested_drag) { @@ -677,8 +679,7 @@ void MenuController::OnMouseReleased(SubmenuView* source, MenuItemView* menu = part.menu; // |menu| is NULL means this event is from an empty menu or a separator. // If it is from an empty menu, use parent context menu instead of that. - if (menu == NULL && - part.submenu->child_count() == 1 && + if (menu == NULL && part.submenu->child_count() == 1 && part.submenu->child_at(0)->id() == MenuItemView::kEmptyMenuItemViewID) { menu = part.parent; } @@ -748,6 +749,14 @@ void MenuController::OnMouseMoved(SubmenuView* source, return; } + // Ignore mouse move events whose location is the same as where the mouse + // was when a menu was opened. This fixes the issue of opening a menu + // with the keyboard and having the menu item under the current mouse + // position incorrectly selected. + if (menu_open_mouse_loc_ && *menu_open_mouse_loc_ == event.location()) + return; + + menu_open_mouse_loc_.reset(); MenuHostRootView* root_view = GetRootView(source, event.location()); if (root_view) { root_view->ProcessMouseMoved(event); @@ -757,8 +766,7 @@ void MenuController::OnMouseMoved(SubmenuView* source, // mouse and keyboard are used to navigate the menu. ui::MouseEvent event_for_root(event); ConvertLocatedEventForRootView(source, root_view, &event_for_root); - View* view = - root_view->GetEventHandlerForPoint(event_for_root.location()); + View* view = root_view->GetEventHandlerForPoint(event_for_root.location()); Button* button = Button::AsButton(view); if (button && button->IsHotTracked()) SetHotTrackedButton(button); @@ -776,6 +784,10 @@ void MenuController::OnMouseEntered(SubmenuView* source, bool MenuController::OnMouseWheel(SubmenuView* source, const ui::MouseWheelEvent& event) { MenuPart part = GetMenuPart(source, event.location()); + + SetSelection(part.menu ? part.menu : state_.item, + SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); + return part.submenu && part.submenu->OnMouseWheel(event); } @@ -800,8 +812,7 @@ void MenuController::OnGestureEvent(SubmenuView* source, // Reset hot-tracking if a different view is getting a touch event. ui::GestureEvent event_for_root(*event); ConvertLocatedEventForRootView(source, root_view, &event_for_root); - View* view = - root_view->GetEventHandlerForPoint(event_for_root.location()); + View* view = root_view->GetEventHandlerForPoint(event_for_root.location()); Button* button = Button::AsButton(view); if (hot_button_ && hot_button_ != button) SetHotTrackedButton(nullptr); @@ -822,8 +833,7 @@ void MenuController::OnGestureEvent(SubmenuView* source, } else if (event->type() == ui::ET_GESTURE_TAP) { if (!part.is_scroll() && part.menu && !(part.should_submenu_show && part.menu->HasSubmenu())) { - if (part.menu->GetDelegate()->IsTriggerableEvent( - part.menu, *event)) { + if (part.menu->GetDelegate()->IsTriggerableEvent(part.menu, *event)) { item_selected_by_touch_ = true; Accept(part.menu, event->flags()); } @@ -834,14 +844,13 @@ void MenuController::OnGestureEvent(SubmenuView* source, SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); event->StopPropagation(); } - } else if (event->type() == ui::ET_GESTURE_TAP_CANCEL && - part.menu && + } else if (event->type() == ui::ET_GESTURE_TAP_CANCEL && part.menu && part.type == MenuPart::MENU_ITEM) { // Move the selection to the parent menu so that the selection in the // current menu is unset. Make sure the submenu remains open by sending the // appropriate SetSelectionTypes flags. SetSelection(part.menu->GetParentMenuItem(), - SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); + SELECTION_OPEN_SUBMENU | SELECTION_UPDATE_IMMEDIATELY); event->StopPropagation(); } @@ -893,9 +902,9 @@ void MenuController::ViewHierarchyChanged( } bool MenuController::GetDropFormats( - SubmenuView* source, - int* formats, - std::set<ui::Clipboard::FormatType>* format_types) { + SubmenuView* source, + int* formats, + std::set<ui::Clipboard::FormatType>* format_types) { return source->GetMenuItem()->GetDelegate()->GetDropFormats( source->GetMenuItem(), formats, format_types); } @@ -947,8 +956,9 @@ int MenuController::OnDragUpdated(SubmenuView* source, menu_item_loc.y() < (menu_item_height - kDropBetweenPixels))) { drop_position = MenuDelegate::DROP_ON; } else { - drop_position = (menu_item_loc.y() < menu_item_height / 2) ? - MenuDelegate::DROP_BEFORE : MenuDelegate::DROP_AFTER; + drop_position = (menu_item_loc.y() < menu_item_height / 2) + ? MenuDelegate::DROP_BEFORE + : MenuDelegate::DROP_AFTER; } query_menu_item = menu_item; } else { @@ -959,8 +969,8 @@ int MenuController::OnDragUpdated(SubmenuView* source, query_menu_item, event, &drop_position); // If the menu has a submenu, schedule the submenu to open. - SetSelection(menu_item, menu_item->HasSubmenu() ? SELECTION_OPEN_SUBMENU : - SELECTION_DEFAULT); + SetSelection(menu_item, menu_item->HasSubmenu() ? SELECTION_OPEN_SUBMENU + : SELECTION_DEFAULT); if (drop_position == MenuDelegate::DROP_NONE || drop_operation == ui::DragDropTypes::DRAG_NONE) @@ -1014,8 +1024,8 @@ int MenuController::OnPerformDrop(SubmenuView* source, // WARNING: the call to MenuClosed deletes us. - return drop_target->GetDelegate()->OnPerformDrop( - drop_target, drop_position, event); + return drop_target->GetDelegate()->OnPerformDrop(drop_target, drop_position, + event); } void MenuController::OnDragEnteredScrollButton(SubmenuView* source, @@ -1123,8 +1133,8 @@ void MenuController::UpdateSubmenuSelection(SubmenuView* submenu) { gfx::Point point = display::Screen::GetScreen()->GetCursorScreenPoint(); const SubmenuView* root_submenu = submenu->GetMenuItem()->GetRootMenuItem()->GetSubmenu(); - View::ConvertPointFromScreen( - root_submenu->GetWidget()->GetRootView(), &point); + View::ConvertPointFromScreen(root_submenu->GetWidget()->GetRootView(), + &point); HandleMouseLocation(submenu, point); } } @@ -1308,7 +1318,7 @@ void MenuController::StartDrag(SubmenuView* source, base::WeakPtr<MenuController> this_ref = AsWeakPtr(); // TODO(varunjain): Properly determine and send DRAG_EVENT_SOURCE below. item->GetWidget()->RunShellDrag(NULL, data, widget_loc, drag_ops, - ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE); + ui::DragDropTypes::DRAG_EVENT_SOURCE_MOUSE); // MenuController may have been deleted so check before accessing member // variables. if (this_ref) @@ -1528,18 +1538,15 @@ bool MenuController::ShowSiblingMenu(SubmenuView* source, return false; View* source_view = source->GetScrollViewContainer(); - if (mouse_location.x() >= 0 && - mouse_location.x() < source_view->width() && - mouse_location.y() >= 0 && - mouse_location.y() < source_view->height()) { + if (mouse_location.x() >= 0 && mouse_location.x() < source_view->width() && + mouse_location.y() >= 0 && mouse_location.y() < source_view->height()) { // The mouse is over the menu, no need to continue. return false; } // TODO(oshima): Replace with views only API. - if (!owner_ || - !display::Screen::GetScreen()->IsWindowUnderCursor( - owner_->GetNativeWindow())) { + if (!owner_ || !display::Screen::GetScreen()->IsWindowUnderCursor( + owner_->GetNativeWindow())) { return false; } @@ -1550,9 +1557,9 @@ bool MenuController::ShowSiblingMenu(SubmenuView* source, MenuAnchorPosition anchor; bool has_mnemonics; MenuButton* button = NULL; - MenuItemView* alt_menu = source->GetMenuItem()->GetDelegate()-> - GetSiblingMenu(source->GetMenuItem()->GetRootMenuItem(), - screen_point, &anchor, &has_mnemonics, &button); + MenuItemView* alt_menu = source->GetMenuItem()->GetDelegate()->GetSiblingMenu( + source->GetMenuItem()->GetRootMenuItem(), screen_point, &anchor, + &has_mnemonics, &button); if (!alt_menu || (state_.item && state_.item->GetRootMenuItem() == alt_menu)) return false; @@ -1877,9 +1884,11 @@ void MenuController::OpenMenuImpl(MenuItemView* item, bool show) { bool prefer_leading = state_.open_leading.empty() ? true : state_.open_leading.back(); bool resulting_direction; - gfx::Rect bounds = MenuItemView::IsBubble(state_.anchor) ? - CalculateBubbleMenuBounds(item, prefer_leading, &resulting_direction) : - CalculateMenuBounds(item, prefer_leading, &resulting_direction); + gfx::Rect bounds = + MenuItemView::IsBubble(state_.anchor) + ? CalculateBubbleMenuBounds(item, prefer_leading, + &resulting_direction) + : CalculateMenuBounds(item, prefer_leading, &resulting_direction); state_.open_leading.push_back(resulting_direction); bool do_capture = (!did_capture_ && !for_drop_); showing_submenu_ = true; @@ -1887,7 +1896,22 @@ void MenuController::OpenMenuImpl(MenuItemView* item, bool show) { // Menus are the only place using kGroupingPropertyKey, so any value (other // than 0) is fine. const int kGroupingId = 1001; + item->GetSubmenu()->ShowAt(owner_, bounds, do_capture); + // Figure out if the mouse is under the menu; if so, remember the mouse + // location so we can ignore the first mouse move event(s) with that + // location. We do this after ShowAt because ConvertPointFromScreen + // doesn't work correctly if the widget isn't shown. + if (item->GetSubmenu()->GetWidget() != nullptr) { + gfx::Point mouse_pos = + display::Screen::GetScreen()->GetCursorScreenPoint(); + View::ConvertPointFromScreen(item->submenu_->GetWidget()->GetRootView(), + &mouse_pos); + MenuPart part_under_mouse = GetMenuPart(item->submenu_, mouse_pos); + if (part_under_mouse.type != MenuPart::NONE) + menu_open_mouse_loc_ = mouse_pos; + } + item->GetSubmenu()->GetWidget()->SetNativeWindowProperty( TooltipManager::kGroupingPropertyKey, reinterpret_cast<void*>(kGroupingId)); @@ -1963,8 +1987,8 @@ void MenuController::StopShowTimer() { void MenuController::StartCancelAllTimer() { cancel_all_timer_.Start(FROM_HERE, - TimeDelta::FromMilliseconds(kCloseOnExitTime), - this, &MenuController::CancelAll); + TimeDelta::FromMilliseconds(kCloseOnExitTime), this, + &MenuController::CancelAll); } void MenuController::StopCancelAllTimer() { @@ -1979,133 +2003,33 @@ gfx::Rect MenuController::CalculateMenuBounds(MenuItemView* item, SubmenuView* submenu = item->GetSubmenu(); DCHECK(submenu); - gfx::Size pref = submenu->GetScrollViewContainer()->GetPreferredSize(); + gfx::Rect menu_bounds = + gfx::Rect(submenu->GetScrollViewContainer()->GetPreferredSize()); + + const gfx::Rect& monitor_bounds = state_.monitor_bounds; + const gfx::Rect& anchor_bounds = state_.initial_bounds; // For comboboxes, ensure the menu is at least as wide as the anchor. if (is_combobox_) - pref.set_width(std::max(pref.width(), state_.initial_bounds.width())); + menu_bounds.set_width(std::max(menu_bounds.width(), anchor_bounds.width())); - // Don't let the menu go too wide. - pref.set_width( - std::min(pref.width(), item->GetDelegate()->GetMaxWidthForMenu(item))); - if (!state_.monitor_bounds.IsEmpty()) - pref.set_width(std::min(pref.width(), state_.monitor_bounds.width())); + // Don't let the menu go too wide or too tall. + menu_bounds.set_width(std::min( + menu_bounds.width(), item->GetDelegate()->GetMaxWidthForMenu(item))); + if (!monitor_bounds.IsEmpty()) { + menu_bounds.set_width( + std::min(menu_bounds.width(), monitor_bounds.width())); + menu_bounds.set_height( + std::min(menu_bounds.height(), monitor_bounds.height())); + } // Assume we can honor prefer_leading. *is_leading = prefer_leading; - int x, y; - const MenuConfig& menu_config = MenuConfig::instance(); - if (!item->GetParentMenuItem()) { - // First item, position relative to initial location. - x = state_.initial_bounds.x(); - - // Offsets for context menu prevent menu items being selected by - // simply opening the menu (bug 142992). - if (menu_config.offset_context_menus && state_.context_menu) - x += 1; - - y = state_.initial_bounds.bottom(); - if (state_.anchor == MENU_ANCHOR_TOPRIGHT) { - x = x + state_.initial_bounds.width() - pref.width(); - if (menu_config.offset_context_menus && state_.context_menu) - x -= 1; - } else if (state_.anchor == MENU_ANCHOR_BOTTOMCENTER) { - x += (state_.initial_bounds.width() - pref.width()) / 2; - if (pref.height() > - state_.initial_bounds.y() + kCenteredContextMenuYOffset) { - // Place the menu below if it does not fit above. - y = state_.initial_bounds.y() - kCenteredContextMenuYOffset; - } else { - y = std::max(0, state_.initial_bounds.y() - pref.height()) + - kCenteredContextMenuYOffset; - } - } else if (state_.anchor == MENU_ANCHOR_FIXED_BOTTOMCENTER) { - x += (state_.initial_bounds.width() - pref.width()) / 2; - } else if (state_.anchor == MENU_ANCHOR_FIXED_SIDECENTER) { - y += (state_.initial_bounds.height() - pref.height()) / 2; - } - - if (!state_.monitor_bounds.IsEmpty() && - y + pref.height() > state_.monitor_bounds.bottom()) { - // The menu doesn't fit fully below the button on the screen. The menu - // position with respect to the bounds will be preserved if it has - // already been drawn. When the requested positioning is below the bounds - // it will shrink the menu to make it fit below. - // If the requested positioning is best fit, it will first try to fit the - // menu below. If that does not fit it will try to place it above. If - // that will not fit it will place it at the bottom of the work area and - // moving it off the initial_bounds region to avoid overlap. - // In all other requested position styles it will be flipped above and - // the height will be shrunken to the usable height. - if (item->actual_menu_position() == MenuItemView::POSITION_BELOW_BOUNDS) { - pref.set_height(std::min(pref.height(), - state_.monitor_bounds.bottom() - y)); - } else if (item->actual_menu_position() == - MenuItemView::POSITION_BEST_FIT) { - MenuItemView::MenuPosition orientation = - MenuItemView::POSITION_BELOW_BOUNDS; - if (state_.monitor_bounds.height() < pref.height()) { - // Handle very tall menus. - pref.set_height(state_.monitor_bounds.height()); - y = state_.monitor_bounds.y(); - } else if (state_.monitor_bounds.y() + pref.height() < - state_.initial_bounds.y()) { - // Flipping upwards if there is enough space. - y = state_.initial_bounds.y() - pref.height(); - orientation = MenuItemView::POSITION_ABOVE_BOUNDS; - } else { - // It is allowed to move the menu a bit around in order to get the - // best fit and to avoid showing scroll elements. - y = state_.monitor_bounds.bottom() - pref.height(); - } - if (orientation == MenuItemView::POSITION_BELOW_BOUNDS) { - // The menu should never overlap the owning button. So move it. - // We use the anchor view style to determine the preferred position - // relative to the owning button. - if (state_.anchor == MENU_ANCHOR_TOPLEFT) { - // The menu starts with the same x coordinate as the owning button. - if (x + state_.initial_bounds.width() + pref.width() > - state_.monitor_bounds.right()) - x -= pref.width(); // Move the menu to the left of the button. - else - x += state_.initial_bounds.width(); // Move the menu right. - } else { - // The menu should end with the same x coordinate as the owning - // button. - if (state_.monitor_bounds.x() > - state_.initial_bounds.x() - pref.width()) - x = state_.initial_bounds.right(); // Move right of the button. - else - x = state_.initial_bounds.x() - pref.width(); // Move left. - } - } - item->set_actual_menu_position(orientation); - } else { - pref.set_height(std::min(pref.height(), - state_.initial_bounds.y() - state_.monitor_bounds.y())); - y = state_.initial_bounds.y() - pref.height(); - item->set_actual_menu_position(MenuItemView::POSITION_ABOVE_BOUNDS); - } - } else if (item->actual_menu_position() == - MenuItemView::POSITION_ABOVE_BOUNDS) { - pref.set_height(std::min(pref.height(), - state_.initial_bounds.y() - state_.monitor_bounds.y())); - y = state_.initial_bounds.y() - pref.height(); - } else { - item->set_actual_menu_position(MenuItemView::POSITION_BELOW_BOUNDS); - } - if (state_.monitor_bounds.width() != 0 && - menu_config.offset_context_menus && state_.context_menu) { - if (x + pref.width() > state_.monitor_bounds.right()) - x = state_.initial_bounds.x() - pref.width() - 1; - if (x < state_.monitor_bounds.x()) - x = state_.monitor_bounds.x(); - } - } else { - // Not the first menu; position it relative to the bounds of the menu + if (item->GetParentMenuItem()) { + // Not the first menu; position it relative to the bounds of its parent menu // item. gfx::Point item_loc; View::ConvertPointToScreen(item, &item_loc); @@ -2113,48 +2037,117 @@ gfx::Rect MenuController::CalculateMenuBounds(MenuItemView* item, // We must make sure we take into account the UI layout. If the layout is // RTL, then a 'leading' menu is positioned to the left of the parent menu // item and not to the right. - bool layout_is_rtl = base::i18n::IsRTL(); - bool create_on_the_right = (prefer_leading && !layout_is_rtl) || - (!prefer_leading && layout_is_rtl); - int submenu_horizontal_inset = menu_config.submenu_horizontal_inset; + const bool layout_is_rtl = base::i18n::IsRTL(); + const bool create_on_right = prefer_leading != layout_is_rtl; + const int submenu_horizontal_inset = menu_config.submenu_horizontal_inset; - if (create_on_the_right) { - x = item_loc.x() + item->width() - submenu_horizontal_inset; - if (state_.monitor_bounds.width() != 0 && - x + pref.width() > state_.monitor_bounds.right()) { - if (layout_is_rtl) - *is_leading = true; - else - *is_leading = false; - x = item_loc.x() - pref.width() + submenu_horizontal_inset; - } + const int left_of_parent = + item_loc.x() - menu_bounds.width() + submenu_horizontal_inset; + const int right_of_parent = + item_loc.x() + item->width() - submenu_horizontal_inset; + + menu_bounds.set_y(item_loc.y() - menu_config.menu_vertical_border_size); + + // Assume the menu can be placed in the preferred location. + menu_bounds.set_x(create_on_right ? right_of_parent : left_of_parent); + + // Everything after this check requires monitor bounds to be non-empty. + if (monitor_bounds.IsEmpty()) + return menu_bounds; + + // Menu does not actually fit where it was placed, move it to the other side + // and update |is_leading|. + if (menu_bounds.x() < monitor_bounds.x()) { + *is_leading = !layout_is_rtl; + menu_bounds.set_x(right_of_parent); + } else if (menu_bounds.right() > monitor_bounds.right()) { + *is_leading = layout_is_rtl; + menu_bounds.set_x(left_of_parent); + } + } else { + // First item, align top left corner of menu with bottom left corner of + // anchor bounds. + menu_bounds.set_x(anchor_bounds.x()); + menu_bounds.set_y(anchor_bounds.bottom()); + + const int above_anchor = anchor_bounds.y() - menu_bounds.height(); + const int horizontally_centered = + anchor_bounds.x() + (anchor_bounds.width() - menu_bounds.width()) / 2; + const int vertically_centered = + anchor_bounds.y() + (anchor_bounds.height() - menu_bounds.height()) / 2; + + if (state_.anchor == MENU_ANCHOR_TOPRIGHT) { + // Move the menu so that its right edge is aligned with the anchor + // bounds right edge. + menu_bounds.set_x(anchor_bounds.right() - menu_bounds.width()); + } else if (state_.anchor == MENU_ANCHOR_BOTTOMCENTER) { + // Try to fit the menu above the anchor bounds. If it doesn't fit, place + // it below. + menu_bounds.set_x(horizontally_centered); + menu_bounds.set_y(above_anchor - kTouchYPadding); + if (menu_bounds.y() < monitor_bounds.y()) + menu_bounds.set_y(anchor_bounds.y() + kTouchYPadding); + } else if (state_.anchor == MENU_ANCHOR_FIXED_BOTTOMCENTER) { + menu_bounds.set_x(horizontally_centered); + } else if (state_.anchor == MENU_ANCHOR_FIXED_SIDECENTER) { + menu_bounds.set_y(vertically_centered); + } + + if (item->actual_menu_position() == MenuItemView::POSITION_ABOVE_BOUNDS) { + // Menu has already been drawn above, put it above the anchor bounds. + menu_bounds.set_y(above_anchor); + } + + // Everything beyond this point requires monitor bounds to be non-empty. + if (monitor_bounds.IsEmpty()) + return menu_bounds; + + // If the menu position is below or above the anchor bounds, force it to fit + // on the screen. Otherwise, try to fit the menu in the following locations: + // 1.) Below the anchor bounds + // 2.) Above the anchor bounds + // 3.) At the bottom of the monitor and off the side of the anchor bounds + if (item->actual_menu_position() == MenuItemView::POSITION_BELOW_BOUNDS || + item->actual_menu_position() == MenuItemView::POSITION_ABOVE_BOUNDS) { + // Menu has been drawn below/above the anchor bounds, make sure it fits + // on the screen in its current location. + menu_bounds.Intersect(monitor_bounds); + } else if (menu_bounds.bottom() <= monitor_bounds.bottom()) { + // Menu fits below anchor bounds. + item->set_actual_menu_position(MenuItemView::POSITION_BELOW_BOUNDS); + } else if (above_anchor >= monitor_bounds.y()) { + // Menu fits above anchor bounds. + menu_bounds.set_y(above_anchor); + item->set_actual_menu_position(MenuItemView::POSITION_ABOVE_BOUNDS); } else { - x = item_loc.x() - pref.width() + submenu_horizontal_inset; - if (state_.monitor_bounds.width() != 0 && x < state_.monitor_bounds.x()) { - if (layout_is_rtl) - *is_leading = false; - else - *is_leading = true; - x = item_loc.x() + item->width() - submenu_horizontal_inset; + const int left_of_anchor = anchor_bounds.x() - menu_bounds.width(); + const int right_of_anchor = anchor_bounds.right(); + + menu_bounds.set_y(monitor_bounds.bottom() - menu_bounds.height()); + if (state_.anchor == MENU_ANCHOR_TOPLEFT) { + // Prefer menu to right of anchor bounds but move it to left if it + // doesn't fit. + menu_bounds.set_x(right_of_anchor); + if (menu_bounds.right() > monitor_bounds.right()) + menu_bounds.set_x(left_of_anchor); + } else { + // Prefer menu to left of anchor bounds but move it to right if it + // doesn't fit. + menu_bounds.set_x(left_of_anchor); + if (menu_bounds.x() < monitor_bounds.x()) + menu_bounds.set_x(right_of_anchor); } } - y = item_loc.y() - menu_config.menu_vertical_border_size; - if (state_.monitor_bounds.width() != 0) { - pref.set_height(std::min(pref.height(), state_.monitor_bounds.height())); - if (y + pref.height() > state_.monitor_bounds.bottom()) - y = state_.monitor_bounds.bottom() - pref.height(); - if (y < state_.monitor_bounds.y()) - y = state_.monitor_bounds.y(); - } } - if (state_.monitor_bounds.width() != 0) { - if (x + pref.width() > state_.monitor_bounds.right()) - x = state_.monitor_bounds.right() - pref.width(); - if (x < state_.monitor_bounds.x()) - x = state_.monitor_bounds.x(); - } - return gfx::Rect(x, y, pref.width(), pref.height()); + menu_bounds.set_x( + base::ClampToRange(menu_bounds.x(), monitor_bounds.x(), + monitor_bounds.right() - menu_bounds.width())); + menu_bounds.set_y( + base::ClampToRange(menu_bounds.y(), monitor_bounds.y(), + monitor_bounds.bottom() - menu_bounds.height())); + + return menu_bounds; } gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, @@ -2239,7 +2232,7 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, border_and_shadow_insets.right(); } // Align the top of the menu with the bottom of the anchor. - if (y < 0) { + if (y < state_.monitor_bounds.y()) { y = owner_bounds.bottom() - border_and_shadow_insets.top() + menu_config.touchable_anchor_offset; } @@ -2251,7 +2244,7 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, menu_config.touchable_anchor_offset; y = owner_bounds.origin().y() - border_and_shadow_insets.top(); // Align the left of the menu with the right of the anchor. - if (x < 0) { + if (x < state_.monitor_bounds.x()) { x = owner_bounds.right() - border_and_shadow_insets.left() + menu_config.touchable_anchor_offset; } @@ -2260,6 +2253,23 @@ gfx::Rect MenuController::CalculateBubbleMenuBounds(MenuItemView* item, y = owner_bounds.bottom() - pref.height() + border_and_shadow_insets.bottom(); } + } else if (state_.anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_RIGHT) { + // Align the left of the menu with the right of the anchor, and the top of + // the menu with the top of the anchor. + x = owner_bounds.right() - border_and_shadow_insets.left() + + menu_config.touchable_anchor_offset; + y = owner_bounds.origin().y() - border_and_shadow_insets.top(); + if (x + pref.width() > state_.monitor_bounds.width()) { + // Align the right of the menu with the left of the anchor. + x = owner_bounds.origin().x() - pref.width() + + border_and_shadow_insets.right() - + menu_config.touchable_anchor_offset; + } + if (y + pref.height() > state_.monitor_bounds.height()) { + // Align the bottom of the menu with the bottom of the anchor. + y = owner_bounds.bottom() - pref.height() + + border_and_shadow_insets.bottom(); + } } else { if (state_.anchor == MENU_ANCHOR_BUBBLE_RIGHT) x = owner_bounds.right() - kBubbleTipSizeLeftRight; @@ -2346,9 +2356,9 @@ void MenuController::IncrementSelection( SetHotTrackedButton(nullptr); } bool direction_is_down = direction == INCREMENT_SELECTION_DOWN; - View* to_make_hot = button - ? GetNextFocusableView(item, button, direction_is_down) - : GetInitialFocusableView(item, direction_is_down); + View* to_make_hot = + button ? GetNextFocusableView(item, button, direction_is_down) + : GetInitialFocusableView(item, direction_is_down); Button* hot_button = Button::AsButton(to_make_hot); if (hot_button) { SetHotTrackedButton(hot_button); @@ -2363,7 +2373,7 @@ void MenuController::IncrementSelection( for (int i = 0; i < parent_count; ++i) { if (parent->GetSubmenu()->GetMenuItemAt(i) == item) { MenuItemView* to_select = - FindNextSelectableMenuItem(parent, i, direction); + FindNextSelectableMenuItem(parent, i, direction, false); SetInitialHotTrackedView(to_select, direction); break; } @@ -2376,13 +2386,14 @@ MenuItemView* MenuController::FindInitialSelectableMenuItem( MenuItemView* parent, SelectionIncrementDirectionType direction) { return FindNextSelectableMenuItem( - parent, direction == INCREMENT_SELECTION_DOWN ? -1 : 0, direction); + parent, direction == INCREMENT_SELECTION_DOWN ? -1 : 0, direction, true); } MenuItemView* MenuController::FindNextSelectableMenuItem( MenuItemView* parent, int index, - SelectionIncrementDirectionType direction) { + SelectionIncrementDirectionType direction, + bool is_initial) { int parent_count = parent->GetSubmenu()->GetMenuItemCount(); int stop_index = (index + parent_count) % parent_count; bool include_all_items = @@ -2392,7 +2403,7 @@ MenuItemView* MenuController::FindNextSelectableMenuItem( // Loop through the menu items skipping any invisible menus. The loop stops // when we wrap or find a visible and enabled child. do { - if (!MenuConfig::instance().arrow_key_selection_wraps) { + if (!MenuConfig::instance().arrow_key_selection_wraps && !is_initial) { if (index == 0 && direction == INCREMENT_SELECTION_UP) return nullptr; if (index == parent_count - 1 && direction == INCREMENT_SELECTION_DOWN) @@ -2481,8 +2492,7 @@ void MenuController::AcceptOrSelect(MenuItemView* parent, SetSelection(submenu->GetMenuItemAt(details.first_match), SELECTION_DEFAULT); } else { - SetSelection(submenu->GetMenuItemAt(details.next_match), - SELECTION_DEFAULT); + SetSelection(submenu->GetMenuItemAt(details.next_match), SELECTION_DEFAULT); } } @@ -2493,7 +2503,7 @@ void MenuController::SelectByChar(base::char16 character) { if (!character) return; - base::char16 char_array[] = { character, 0 }; + base::char16 char_array[] = {character, 0}; base::char16 key = base::i18n::ToLower(char_array)[0]; MenuItemView* item = pending_state_.item; if (!item->SubmenuIsShowing()) @@ -2574,9 +2584,8 @@ void MenuController::RepostEventAndCancel(SubmenuView* source, Cancel(exit_type); } -void MenuController::SetDropMenuItem( - MenuItemView* new_target, - MenuDelegate::DropPosition new_position) { +void MenuController::SetDropMenuItem(MenuItemView* new_target, + MenuDelegate::DropPosition new_position) { if (new_target == drop_target_ && new_position == drop_position_) return; @@ -2629,8 +2638,7 @@ void MenuController::UpdateActiveMouseView(SubmenuView* event_source, active_mouse_view_tracker_->SetView(active_mouse_view); if (active_mouse_view) { gfx::Point target_point(target_menu_loc); - View::ConvertPointToTarget( - target_menu, active_mouse_view, &target_point); + View::ConvertPointToTarget(target_menu, active_mouse_view, &target_point); ui::MouseEvent mouse_entered_event(ui::ET_MOUSE_ENTERED, target_point, target_point, ui::EventTimeForNow(), 0, 0); diff --git a/chromium/ui/views/controls/menu/menu_controller.h b/chromium/ui/views/controls/menu/menu_controller.h index 67b8849bca5..1fcfaa99d46 100644 --- a/chromium/ui/views/controls/menu/menu_controller.h +++ b/chromium/ui/views/controls/menu/menu_controller.h @@ -10,6 +10,7 @@ #include <list> #include <memory> #include <set> +#include <utility> #include <vector> #include "base/compiler_specific.h" @@ -54,6 +55,7 @@ class MenuRunnerImpl; namespace test { class MenuControllerTest; class MenuControllerTestApi; +class MenuControllerUITest; } // MenuController ------------------------------------------------------------- @@ -118,7 +120,7 @@ class VIEWS_EXPORT MenuController // WARNING: this may be NULL. Widget* owner() { return owner_; } - // Get the anchor position wich is used to show this menu. + // Get the anchor position which is used to show this menu. MenuAnchorPosition GetAnchorPosition() { return state_.anchor; } // Cancels the current Run. See ExitType for a description of what happens @@ -220,6 +222,7 @@ class VIEWS_EXPORT MenuController friend class internal::MenuRunnerImpl; friend class test::MenuControllerTest; friend class test::MenuControllerTestApi; + friend class test::MenuControllerUITest; friend class MenuHostRootView; friend class MenuItemView; friend class SubmenuView; @@ -230,18 +233,18 @@ class VIEWS_EXPORT MenuController // Values supplied to SetSelection. enum SetSelectionTypes { - SELECTION_DEFAULT = 0, + SELECTION_DEFAULT = 0, // If set submenus are opened immediately, otherwise submenus are only - // openned after a timer fires. - SELECTION_UPDATE_IMMEDIATELY = 1 << 0, + // opened after a timer fires. + SELECTION_UPDATE_IMMEDIATELY = 1 << 0, // If set and the menu_item has a submenu, the submenu is shown. - SELECTION_OPEN_SUBMENU = 1 << 1, + SELECTION_OPEN_SUBMENU = 1 << 1, // SetSelection is being invoked as the result exiting or cancelling the // menu. This is used for debugging. - SELECTION_EXIT = 1 << 2, + SELECTION_EXIT = 1 << 2, }; // Direction for IncrementSelection and FindInitialSelectableMenuItem. @@ -323,7 +326,7 @@ class VIEWS_EXPORT MenuController // Sets the selection to |menu_item|. A value of NULL unselects // everything. |types| is a bitmask of |SetSelectionTypes|. // - // Internally this updates pending_state_ immediatley. state_ is only updated + // Internally this updates pending_state_ immediately. state_ is only updated // immediately if SELECTION_UPDATE_IMMEDIATELY is set. If // SELECTION_UPDATE_IMMEDIATELY is not set CommitPendingSelection is invoked // to show/hide submenus and update state_. @@ -498,7 +501,8 @@ class VIEWS_EXPORT MenuController MenuItemView* FindNextSelectableMenuItem( MenuItemView* parent, int index, - SelectionIncrementDirectionType direction); + SelectionIncrementDirectionType direction, + bool is_initial); // If the selected item has a submenu and it isn't currently open, the // the selection is changed such that the menu opens immediately. @@ -559,7 +563,7 @@ class VIEWS_EXPORT MenuController // Sets exit type. Calling this can terminate the active nested message-loop. void SetExitType(ExitType type); - // Performs the teardown of menus. This will notifiy the |delegate_|. If + // Performs the teardown of menus. This will notify the |delegate_|. If // |exit_type_| is EXIT_ALL all nested runs will be exited. void ExitMenu(); @@ -690,6 +694,12 @@ class VIEWS_EXPORT MenuController // screen coordinates). Otherwise this will be (0, 0). gfx::Point menu_start_mouse_press_loc_; + // If the mouse was under the menu when the menu was run, this will have its + // location. Otherwise it will be null. This is used to ignore mouse move + // events triggered by the menu opening, to avoid selecting the menu item + // over the mouse. + base::Optional<gfx::Point> menu_open_mouse_loc_; + // Controls behavior differences between a combobox and other types of menu // (like a context menu). bool is_combobox_ = false; diff --git a/chromium/ui/views/controls/menu/menu_controller_unittest.cc b/chromium/ui/views/controls/menu/menu_controller_unittest.cc index 2d1c138bdc0..c8052ea2734 100644 --- a/chromium/ui/views/controls/menu/menu_controller_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_controller_unittest.cc @@ -12,8 +12,6 @@ #include "base/strings/utf_string_conversions.h" #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" -#include "ui/aura/scoped_window_targeter.h" -#include "ui/aura/window.h" #include "ui/events/event.h" #include "ui/events/event_constants.h" #include "ui/events/event_handler.h" @@ -119,7 +117,7 @@ void TestMenuControllerDelegate::SiblingMenuCreated(MenuItemView* menu) {} class SubmenuViewShown : public SubmenuView { public: - SubmenuViewShown(MenuItemView* parent) : SubmenuView(parent) {} + using SubmenuView::SubmenuView; ~SubmenuViewShown() override {} bool IsShowing() override { return true; } @@ -132,7 +130,7 @@ class TestEventHandler : public ui::EventHandler { TestEventHandler() : outstanding_touches_(0) {} void OnTouchEvent(ui::TouchEvent* event) override { - switch(event->type()) { + switch (event->type()) { case ui::ET_TOUCH_PRESSED: outstanding_touches_++; break; @@ -246,25 +244,50 @@ void DestructingTestViewsDelegate::ReleaseRef() { class TestMenuItemViewShown : public MenuItemView { public: - TestMenuItemViewShown(MenuDelegate* delegate) : MenuItemView(delegate) { + explicit TestMenuItemViewShown(MenuDelegate* delegate) + : MenuItemView(delegate) { submenu_ = new SubmenuViewShown(this); } ~TestMenuItemViewShown() override {} - void SetController(MenuController* controller) { - set_controller(controller); - } + void SetController(MenuController* controller) { set_controller(controller); } void AddEmptyMenusForTest() { AddEmptyMenus(); } + void SetActualMenuPosition(MenuItemView::MenuPosition position) { + set_actual_menu_position(position); + } + private: DISALLOW_COPY_AND_ASSIGN(TestMenuItemViewShown); }; -class MenuControllerTest : public ViewsTestBase { +class TestMenuItemViewNotShown : public MenuItemView { public: - MenuControllerTest() : menu_controller_(nullptr) { + explicit TestMenuItemViewNotShown(MenuDelegate* delegate) + : MenuItemView(delegate) { + submenu_ = new SubmenuView(this); } + ~TestMenuItemViewNotShown() override {} + + void SetController(MenuController* controller) { set_controller(controller); } + + private: + DISALLOW_COPY_AND_ASSIGN(TestMenuItemViewNotShown); +}; + +struct MenuBoundsOptions { + public: + gfx::Rect anchor_bounds = gfx::Rect(500, 500, 10, 10); + gfx::Rect monitor_bounds = gfx::Rect(0, 0, 1000, 1000); + gfx::Size menu_size = gfx::Size(100, 100); + MenuAnchorPosition menu_anchor = MENU_ANCHOR_TOPLEFT; + MenuItemView::MenuPosition menu_position = MenuItemView::POSITION_BEST_FIT; +}; + +class MenuControllerTest : public ViewsTestBase { + public: + MenuControllerTest() : menu_controller_(nullptr) {} ~MenuControllerTest() override {} @@ -286,9 +309,7 @@ class MenuControllerTest : public ViewsTestBase { ViewsTestBase::TearDown(); } - void ReleaseTouchId(int id) { - event_generator_->ReleaseTouchId(id); - } + void ReleaseTouchId(int id) { event_generator_->ReleaseTouchId(id); } void PressKey(ui::KeyboardCode key_code) { event_generator_->PressKey(key_code, 0); @@ -299,6 +320,18 @@ class MenuControllerTest : public ViewsTestBase { menu_controller_->OnWillDispatchKeyEvent(&event); } + gfx::Rect CalculateMenuBounds(const MenuBoundsOptions& options) { + menu_controller_->state_.anchor = options.menu_anchor; + menu_controller_->state_.initial_bounds = options.anchor_bounds; + menu_controller_->state_.monitor_bounds = options.monitor_bounds; + menu_item_->SetActualMenuPosition(options.menu_position); + menu_item_->GetSubmenu()->GetScrollViewContainer()->SetPreferredSize( + options.menu_size); + bool resulting_direction; + return menu_controller_->CalculateMenuBounds(menu_item_.get(), true, + &resulting_direction); + } + #if defined(USE_AURA) // Verifies that a non-nested menu fully closes when receiving an escape key. void TestAsyncEscapeKey() { @@ -371,9 +404,8 @@ class MenuControllerTest : public ViewsTestBase { void ResetSelection() { menu_controller_->SetSelection( - nullptr, - MenuController::SELECTION_EXIT | - MenuController::SELECTION_UPDATE_IMMEDIATELY); + nullptr, MenuController::SELECTION_EXIT | + MenuController::SELECTION_UPDATE_IMMEDIATELY); } void IncrementSelection() { @@ -405,15 +437,14 @@ class MenuControllerTest : public ViewsTestBase { MenuItemView* FindNextSelectableMenuItem(MenuItemView* parent, int index) { - return menu_controller_->FindNextSelectableMenuItem( - parent, index, MenuController::INCREMENT_SELECTION_DOWN); + parent, index, MenuController::INCREMENT_SELECTION_DOWN, false); } MenuItemView* FindPreviousSelectableMenuItem(MenuItemView* parent, int index) { return menu_controller_->FindNextSelectableMenuItem( - parent, index, MenuController::INCREMENT_SELECTION_UP); + parent, index, MenuController::INCREMENT_SELECTION_UP, false); } internal::MenuControllerDelegate* GetCurrentDelegate() { @@ -655,12 +686,8 @@ TEST_F(MenuControllerTest, InitialSelectedItem) { // The last selectable item should be item "Four". MenuItemView* last_selectable = FindInitialSelectableMenuItemUp(menu_item()); - if (SelectionWraps()) { - ASSERT_NE(nullptr, last_selectable); - EXPECT_EQ(4, last_selectable->GetCommand()); - } else { - ASSERT_EQ(nullptr, last_selectable); - } + ASSERT_NE(nullptr, last_selectable); + EXPECT_EQ(4, last_selectable->GetCommand()); // Leave items "One" and "Two" enabled. menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true); @@ -673,12 +700,8 @@ TEST_F(MenuControllerTest, InitialSelectedItem) { EXPECT_EQ(1, first_selectable->GetCommand()); // The last selectable item should be item "Two". last_selectable = FindInitialSelectableMenuItemUp(menu_item()); - if (SelectionWraps()) { - ASSERT_NE(nullptr, last_selectable); - EXPECT_EQ(2, last_selectable->GetCommand()); - } else { - ASSERT_EQ(nullptr, last_selectable); - } + ASSERT_NE(nullptr, last_selectable); + EXPECT_EQ(2, last_selectable->GetCommand()); // Leave only a single item "One" enabled. menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(true); @@ -691,12 +714,8 @@ TEST_F(MenuControllerTest, InitialSelectedItem) { EXPECT_EQ(1, first_selectable->GetCommand()); // The last selectable item should be item "One". last_selectable = FindInitialSelectableMenuItemUp(menu_item()); - if (SelectionWraps()) { - ASSERT_NE(nullptr, last_selectable); - EXPECT_EQ(1, last_selectable->GetCommand()); - } else { - ASSERT_EQ(nullptr, last_selectable); - } + ASSERT_NE(nullptr, last_selectable); + EXPECT_EQ(1, last_selectable->GetCommand()); // Leave only a single item "Three" enabled. menu_item()->GetSubmenu()->GetMenuItemAt(0)->SetEnabled(false); @@ -709,12 +728,8 @@ TEST_F(MenuControllerTest, InitialSelectedItem) { EXPECT_EQ(3, first_selectable->GetCommand()); // The last selectable item should be item "Three". last_selectable = FindInitialSelectableMenuItemUp(menu_item()); - if (SelectionWraps()) { - ASSERT_NE(nullptr, last_selectable); - EXPECT_EQ(3, last_selectable->GetCommand()); - } else { - ASSERT_EQ(nullptr, last_selectable); - } + ASSERT_NE(nullptr, last_selectable); + EXPECT_EQ(3, last_selectable->GetCommand()); // Leave only a single item ("Two") selected. It should be the first and the // last selectable item. @@ -726,12 +741,8 @@ TEST_F(MenuControllerTest, InitialSelectedItem) { ASSERT_NE(nullptr, first_selectable); EXPECT_EQ(2, first_selectable->GetCommand()); last_selectable = FindInitialSelectableMenuItemUp(menu_item()); - if (SelectionWraps()) { - ASSERT_NE(nullptr, last_selectable); - EXPECT_EQ(2, last_selectable->GetCommand()); - } else { - ASSERT_EQ(nullptr, last_selectable); - } + ASSERT_NE(nullptr, last_selectable); + EXPECT_EQ(2, last_selectable->GetCommand()); // There should be no next or previous selectable item since there is only a // single enabled item in the menu. @@ -799,10 +810,7 @@ TEST_F(MenuControllerTest, PreviousSelectedItem) { // Move up and select a previous (in our case the last enabled) item. DecrementSelection(); - if (SelectionWraps()) - EXPECT_EQ(3, pending_state_item()->GetCommand()); - else - EXPECT_EQ(0, pending_state_item()->GetCommand()); + EXPECT_EQ(3, pending_state_item()->GetCommand()); // Clear references in menu controller to the menu item that is going away. ResetSelection(); @@ -1107,7 +1115,7 @@ TEST_F(MenuControllerTest, AsynchronousCancelDuringDrag) { controller_delegate->on_menu_closed_notify_type()); } -// Tests that if a menu is destroyed while drag operations are occuring, that +// Tests that if a menu is destroyed while drag operations are occurring, that // the MenuHost does not crash as the drag completes. TEST_F(MenuControllerTest, AsynchronousDragHostDeleted) { SubmenuView* submenu = menu_item()->GetSubmenu(); @@ -1142,8 +1150,8 @@ TEST_F(MenuControllerTest, HostReceivesInputBeforeDestruction) { root_view->OnMouseMoved(event); } -// Tets that an asynchronous menu nested within an asynchronous menu closes both -// menus, and notifies both delegates. +// Tests that an asynchronous menu nested within an asynchronous menu closes +// both menus, and notifies both delegates. TEST_F(MenuControllerTest, DoubleAsynchronousNested) { MenuController* controller = menu_controller(); TestMenuControllerDelegate* delegate = menu_controller_delegate(); @@ -1190,7 +1198,7 @@ TEST_F(MenuControllerTest, PreserveGestureForOwner) { controller->OnGestureEvent(sub_menu, &event2); EXPECT_EQ(CountOwnerOnGestureEvent(), 2); - // ET_GESTURE_END resets the |send_gesture_events_to_owner_| flag, so futher + // ET_GESTURE_END resets the |send_gesture_events_to_owner_| flag, so further // gesture events should not be sent to the owner. controller->OnGestureEvent(sub_menu, &event2); EXPECT_EQ(CountOwnerOnGestureEvent(), 2); @@ -1361,7 +1369,231 @@ TEST_F(MenuControllerTest, ArrowKeysAtEnds) { EXPECT_EQ(4, pending_state_item()->GetCommand()); } +// Test that the menu is properly placed where it best fits. +TEST_F(MenuControllerTest, CalculateMenuBoundsBestFitTest) { + MenuBoundsOptions options; + gfx::Rect expected; + + // Fits in all locations -> placed below. + options.anchor_bounds = + gfx::Rect(options.menu_size.width(), options.menu_size.height(), 0, 0); + options.monitor_bounds = + gfx::Rect(0, 0, options.anchor_bounds.right() + options.menu_size.width(), + options.anchor_bounds.bottom() + options.menu_size.height()); + expected = + gfx::Rect(options.anchor_bounds.x(), options.anchor_bounds.bottom(), + options.menu_size.width(), options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + // Fits above and to both sides -> placed above. + options.anchor_bounds = + gfx::Rect(options.menu_size.width(), options.menu_size.height(), 0, 0); + options.monitor_bounds = + gfx::Rect(0, 0, options.anchor_bounds.right() + options.menu_size.width(), + options.anchor_bounds.bottom()); + expected = gfx::Rect(options.anchor_bounds.x(), + options.anchor_bounds.y() - options.menu_size.height(), + options.menu_size.width(), options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + // Fits on both sides, prefer right -> placed right. + options.anchor_bounds = gfx::Rect(options.menu_size.width(), + options.menu_size.height() / 2, 0, 0); + options.monitor_bounds = + gfx::Rect(0, 0, options.anchor_bounds.right() + options.menu_size.width(), + options.menu_size.height()); + expected = + gfx::Rect(options.anchor_bounds.right(), + options.monitor_bounds.bottom() - options.menu_size.height(), + options.menu_size.width(), options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + // Fits only on left -> placed left. + options.anchor_bounds = gfx::Rect(options.menu_size.width(), + options.menu_size.height() / 2, 0, 0); + options.monitor_bounds = gfx::Rect(0, 0, options.anchor_bounds.right(), + options.menu_size.height()); + expected = + gfx::Rect(options.anchor_bounds.x() - options.menu_size.width(), + options.monitor_bounds.bottom() - options.menu_size.height(), + options.menu_size.width(), options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + // Fits on both sides, prefer left -> placed left. + options.menu_anchor = MENU_ANCHOR_TOPRIGHT; + options.anchor_bounds = gfx::Rect(options.menu_size.width(), + options.menu_size.height() / 2, 0, 0); + options.monitor_bounds = + gfx::Rect(0, 0, options.anchor_bounds.right() + options.menu_size.width(), + options.menu_size.height()); + expected = + gfx::Rect(options.anchor_bounds.x() - options.menu_size.width(), + options.monitor_bounds.bottom() - options.menu_size.height(), + options.menu_size.width(), options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + // Fits only on right -> placed right. + options.anchor_bounds = gfx::Rect(0, options.menu_size.height() / 2, 0, 0); + options.monitor_bounds = + gfx::Rect(0, 0, options.anchor_bounds.right() + options.menu_size.width(), + options.menu_size.height()); + expected = + gfx::Rect(options.anchor_bounds.right(), + options.monitor_bounds.bottom() - options.menu_size.height(), + options.menu_size.width(), options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); +} + +// Tests that the menu is properly placed according to its anchor. +TEST_F(MenuControllerTest, CalculateMenuBoundsAnchorTest) { + MenuBoundsOptions options; + gfx::Rect expected; + + options.menu_anchor = MENU_ANCHOR_TOPLEFT; + expected = + gfx::Rect(options.anchor_bounds.x(), options.anchor_bounds.bottom(), + options.menu_size.width(), options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + options.menu_anchor = MENU_ANCHOR_TOPRIGHT; + expected = + gfx::Rect(options.anchor_bounds.right() - options.menu_size.width(), + options.anchor_bounds.bottom(), options.menu_size.width(), + options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + // Menu will be placed above or below with an offset. + options.menu_anchor = MENU_ANCHOR_BOTTOMCENTER; + const int kTouchYPadding = 15; + + // Menu fits above -> placed above. + expected = gfx::Rect( + options.anchor_bounds.x() + + (options.anchor_bounds.width() - options.menu_size.width()) / 2, + options.anchor_bounds.y() - options.menu_size.height() - kTouchYPadding, + options.menu_size.width(), options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + // Menu does not fit above -> placed below. + options.anchor_bounds = gfx::Rect(options.menu_size.height() / 2, + options.menu_size.width(), 0, 0); + expected = gfx::Rect( + options.anchor_bounds.x() + + (options.anchor_bounds.width() - options.menu_size.width()) / 2, + options.anchor_bounds.y() + kTouchYPadding, options.menu_size.width(), + options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + // Assumes anchor bounds is at the bottom of screen. + options.menu_anchor = MENU_ANCHOR_FIXED_BOTTOMCENTER; + options.anchor_bounds = + gfx::Rect(options.menu_size.width(), options.menu_size.height(), 0, 0); + options.monitor_bounds = gfx::Rect(0, 0, options.menu_size.width() * 2, + options.menu_size.height()); + expected = gfx::Rect( + options.anchor_bounds.x() + + (options.anchor_bounds.width() - options.menu_size.width()) / 2, + options.anchor_bounds.y() - options.menu_size.height(), + options.menu_size.width(), options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + // Assumes anchor bounds is on left/right edge of screen. + options.menu_anchor = MENU_ANCHOR_FIXED_SIDECENTER; + options.monitor_bounds = gfx::Rect(0, 0, options.menu_size.width(), + options.menu_size.height() * 2); + options.anchor_bounds = + gfx::Rect(options.monitor_bounds.x(), options.menu_size.height(), 0, 0); + expected = gfx::Rect( + options.anchor_bounds.x(), + options.anchor_bounds.y() + + (options.anchor_bounds.height() - options.menu_size.height()) / 2, + options.menu_size.width(), options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + options.anchor_bounds = gfx::Rect(options.monitor_bounds.right(), + options.menu_size.height(), 0, 0); + expected = gfx::Rect( + options.anchor_bounds.right() - options.menu_size.width(), + options.anchor_bounds.y() + + (options.anchor_bounds.height() - options.menu_size.height()) / 2, + options.menu_size.width(), options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); +} + +TEST_F(MenuControllerTest, CalculateMenuBoundsMonitorFitTest) { + MenuBoundsOptions options; + gfx::Rect expected; + options.monitor_bounds = gfx::Rect(0, 0, 100, 100); + options.anchor_bounds = gfx::Rect(); + + options.menu_size = gfx::Size(options.monitor_bounds.width() / 2, + options.monitor_bounds.height() * 2); + expected = + gfx::Rect(options.anchor_bounds.x(), options.anchor_bounds.bottom(), + options.menu_size.width(), options.monitor_bounds.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + options.menu_size = gfx::Size(options.monitor_bounds.width() * 2, + options.monitor_bounds.height() / 2); + expected = + gfx::Rect(options.anchor_bounds.x(), options.anchor_bounds.bottom(), + options.monitor_bounds.width(), options.menu_size.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); + + options.menu_size = gfx::Size(options.monitor_bounds.width() * 2, + options.monitor_bounds.height() * 2); + expected = gfx::Rect( + options.anchor_bounds.x(), options.anchor_bounds.bottom(), + options.monitor_bounds.width(), options.monitor_bounds.height()); + EXPECT_EQ(expected, CalculateMenuBounds(options)); +} + #if defined(USE_AURA) +// This tests that mouse moved events from the initial position of the mouse +// when the menu was shown don't select the menu item at the mouse position. +TEST_F(MenuControllerTest, MouseAtMenuItemOnShow) { + // aura::Window::MoveCursorTo check fails in Mus due to null + // window_manager_client_. + if (IsMus()) + return; + + // Most tests create an already shown menu but this test needs one that's + // not shown, so it can show it. The mouse position is remembered when + // the menu is shown. + std::unique_ptr<TestMenuItemViewNotShown> menu_item( + new TestMenuItemViewNotShown(menu_delegate())); + MenuItemView* first_item = + menu_item->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One")); + menu_item->AppendMenuItemWithLabel(2, base::ASCIIToUTF16("Two")); + menu_item->SetController(menu_controller()); + + // Move the mouse to where the first menu item will be shown, + // and show the menu. + gfx::Size item_size = first_item->CalculatePreferredSize(); + gfx::Point location(item_size.width() / 2, item_size.height() / 2); + owner()->GetNativeWindow()->GetRootWindow()->MoveCursorTo(location); + menu_controller()->Run(owner(), nullptr, menu_item.get(), gfx::Rect(), + MENU_ANCHOR_TOPLEFT, false, false); + + EXPECT_EQ(0, pending_state_item()->GetCommand()); + + // Synthesize an event at the mouse position when the menu was opened. + // It should be ignored, and selected item shouldn't change. + SubmenuView* sub_menu = menu_item->GetSubmenu(); + View::ConvertPointFromScreen(sub_menu->GetScrollViewContainer(), &location); + ui::MouseEvent event(ui::ET_MOUSE_MOVED, location, location, + ui::EventTimeForNow(), 0, 0); + ProcessMouseMoved(sub_menu, event); + EXPECT_EQ(0, pending_state_item()->GetCommand()); + // Synthesize an event at a slightly different mouse position. It + // should cause the item under the cursor to be selected. + location.Offset(0, 1); + ui::MouseEvent second_event(ui::ET_MOUSE_MOVED, location, location, + ui::EventTimeForNow(), 0, 0); + ProcessMouseMoved(sub_menu, second_event); + EXPECT_EQ(1, pending_state_item()->GetCommand()); +} + // Tests that when an asynchronous menu receives a cancel event, that it closes. TEST_F(MenuControllerTest, AsynchronousCancelEvent) { ExitMenuRun(); @@ -1447,7 +1679,7 @@ TEST_F(MenuControllerTest, RepostEventToEmptyMenuItem) { ->SetContentsView(base_submenu->GetScrollViewContainer()); // Build the submenu to have an empty menu item. Additionally hook up - // appropriate Widget and View containersm with counds, so that hit testing + // appropriate Widget and View containers with bounds, so that hit testing // works. std::unique_ptr<TestMenuDelegate> sub_menu_item_delegate = std::make_unique<TestMenuDelegate>(); @@ -1532,7 +1764,7 @@ TEST_F(MenuControllerTest, RepostEventToEmptyMenuItem) { gfx::Rect(150, 50, 100, 100), MENU_ANCHOR_TOPLEFT, true, false); - // The escapce key should only close the nested menu. SelectByChar should not + // The escape key should only close the nested menu. SelectByChar should not // crash. TestAsyncEscapeKey(); EXPECT_EQ(nested_controller_delegate_2->on_menu_closed_called(), 1); diff --git a/chromium/ui/views/controls/menu/menu_item_view.cc b/chromium/ui/views/controls/menu/menu_item_view.cc index 22a219b4660..4ede4cb5fa0 100644 --- a/chromium/ui/views/controls/menu/menu_item_view.cc +++ b/chromium/ui/views/controls/menu/menu_item_view.cc @@ -204,7 +204,8 @@ bool MenuItemView::IsBubble(MenuAnchorPosition anchor) { anchor == MENU_ANCHOR_BUBBLE_ABOVE || anchor == MENU_ANCHOR_BUBBLE_BELOW || anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_ABOVE || - anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_LEFT; + anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_LEFT || + anchor == MENU_ANCHOR_BUBBLE_TOUCHABLE_RIGHT; } // static @@ -331,6 +332,14 @@ void MenuItemView::AppendSeparator() { ui::NORMAL_SEPARATOR); } +void MenuItemView::AddSeparatorAt(int index) { + AddMenuItemAt(index, /*item_id=*/0, /*label=*/base::string16(), + /*sub_label=*/base::string16(), + /*minor_text=*/base::string16(), /*minor_icon=*/nullptr, + /*icon=*/gfx::ImageSkia(), /*type=*/SEPARATOR, + /*separator_style=*/ui::NORMAL_SEPARATOR); +} + MenuItemView* MenuItemView::AppendMenuItemWithIcon(int item_id, const base::string16& label, const gfx::ImageSkia& icon) { @@ -616,12 +625,12 @@ void MenuItemView::Layout() { if (icon_view_) { icon_view_->SizeToPreferredSize(); gfx::Size size = icon_view_->GetPreferredSize(); - int x = config.item_left_margin + left_icon_margin_ + + int x = config.item_horizontal_padding + left_icon_margin_ + (icon_area_width_ - size.width()) / 2; if (config.icons_in_label || type_ == CHECKBOX || type_ == RADIO) x = label_start_; if (GetMenuController() && GetMenuController()->use_touchable_layout()) - x = config.touchable_item_left_margin; + x = config.touchable_item_horizontal_padding; int y = (height() + GetTopMargin() - GetBottomMargin() - size.height()) / 2; @@ -629,9 +638,9 @@ void MenuItemView::Layout() { } if (radio_check_image_view_) { - int x = config.item_left_margin + left_icon_margin_; + int x = config.item_horizontal_padding + left_icon_margin_; if (GetMenuController() && GetMenuController()->use_touchable_layout()) - x = config.touchable_item_left_margin; + x = config.touchable_item_horizontal_padding; int y = (height() + GetTopMargin() - GetBottomMargin() - kMenuCheckSize) / 2; radio_check_image_view_->SetBounds(x, y, kMenuCheckSize, kMenuCheckSize); @@ -723,18 +732,20 @@ void MenuItemView::UpdateMenuPartSizes() { const bool use_touchable_layout = GetMenuController() && GetMenuController()->use_touchable_layout(); - label_start_ = (use_touchable_layout ? config.touchable_item_left_margin - : config.item_left_margin) + - icon_area_width_; + label_start_ = + (use_touchable_layout ? config.touchable_item_horizontal_padding + : config.item_horizontal_padding) + + icon_area_width_; int padding = 0; if (config.always_use_icon_to_label_padding) { - padding = config.icon_to_label_padding; + padding = config.item_horizontal_padding; } else if (!config.icons_in_label) { - padding = (has_icons_ || HasChecksOrRadioButtons()) ? - config.icon_to_label_padding : 0; + padding = (has_icons_ || HasChecksOrRadioButtons()) + ? config.item_horizontal_padding + : 0; } if (use_touchable_layout) - padding = config.touchable_icon_to_label_padding; + padding = config.touchable_item_horizontal_padding; label_start_ += padding; @@ -845,6 +856,9 @@ const gfx::FontList& MenuItemView::GetFontList() const { if (font_list) return *font_list; } + + if (GetMenuController() && GetMenuController()->use_touchable_layout()) + return style::GetFont(style::CONTEXT_TOUCH_MENU, style::STYLE_PRIMARY); return MenuConfig::instance().font_list; } @@ -1006,7 +1020,7 @@ void MenuItemView::PaintMinorIconAndText(gfx::Canvas* canvas, SkColor color) { int image_x = GetMirroredRect(minor_text_bounds).right() - render_text->GetContentWidth() - - (minor_text.empty() ? 0 : config.icon_to_label_padding) - + (minor_text.empty() ? 0 : config.item_horizontal_padding) - image.width(); int minor_text_center_y = minor_text_bounds.y() + minor_text_bounds.height() / 2; @@ -1102,18 +1116,25 @@ MenuItemView::MenuItemDimensions MenuItemView::CalculateDimensions() const { gfx::Size child_size = GetChildPreferredSize(); MenuItemDimensions dimensions; - // Get the container height. dimensions.children_width = child_size.width(); const MenuConfig& menu_config = MenuConfig::instance(); if (GetMenuController() && GetMenuController()->use_touchable_layout()) { - // Touchable layout uses a fixed size, but adjusts the height for icons. dimensions.height = menu_config.touchable_menu_height; + + // For container MenuItemViews, the width components should only include the + // |children_width|. Setting a |standard_width| would result in additional + // width being added to the container because the total width used in layout + // is |children_width| + |standard_width|. + if (IsContainer()) + return dimensions; + + dimensions.standard_width = menu_config.touchable_menu_width; + if (icon_view_) { dimensions.height = icon_view_->height() + 2 * menu_config.vertical_touchable_menu_item_padding; } - dimensions.standard_width = menu_config.touchable_menu_width; return dimensions; } @@ -1195,14 +1216,14 @@ int MenuItemView::GetLabelStartForThisItem() const { // Touchable items with icons do not respect |label_start_|. if (GetMenuController() && GetMenuController()->use_touchable_layout() && icon_view_) { - return config.touchable_item_left_margin + icon_view_->width() + - config.touchable_icon_to_label_padding; + return 2 * config.touchable_item_horizontal_padding + icon_view_->width(); } int label_start = label_start_ + left_icon_margin_ + right_icon_margin_; if ((config.icons_in_label || type_ == CHECKBOX || type_ == RADIO) && - icon_view_) - label_start += icon_view_->size().width() + config.icon_to_label_padding; + icon_view_) { + label_start += icon_view_->size().width() + config.item_horizontal_padding; + } return label_start; } diff --git a/chromium/ui/views/controls/menu/menu_item_view.h b/chromium/ui/views/controls/menu/menu_item_view.h index 6276932ceaf..929f3f87204 100644 --- a/chromium/ui/views/controls/menu/menu_item_view.h +++ b/chromium/ui/views/controls/menu/menu_item_view.h @@ -40,6 +40,7 @@ class MenuRunnerImpl; namespace test { class TestMenuItemViewShown; +class TestMenuItemViewNotShown; } class MenuController; @@ -201,6 +202,9 @@ class VIEWS_EXPORT MenuItemView : public View { // Adds a separator to this menu void AppendSeparator(); + // Adds a separator to this menu at the specified position. + void AddSeparatorAt(int index); + // Appends a menu item with an icon. This is for the menu item which // needs an icon. Calling this function forces the Menu class to draw // the menu, instead of relying on Windows. @@ -377,6 +381,7 @@ class VIEWS_EXPORT MenuItemView : public View { private: friend class internal::MenuRunnerImpl; // For access to ~MenuItemView. friend class test::TestMenuItemViewShown; // for access to |submenu_|; + friend class test::TestMenuItemViewNotShown; // for access to |submenu_|; friend class TestMenuItemView; // For access to AddEmptyMenus(); enum PaintButtonMode { PB_NORMAL, PB_FOR_DRAG }; diff --git a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm index 8ccd3382da9..f0ea47108de 100644 --- a/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm +++ b/chromium/ui/views/controls/menu/menu_runner_impl_cocoa.mm @@ -123,6 +123,10 @@ MenuRunnerImplInterface* MenuRunnerImplInterface::Create( ui::MenuModel* menu_model, int32_t run_types, const base::Closure& on_menu_closed_callback) { + if ((run_types & MenuRunner::CONTEXT_MENU) && + !(run_types & MenuRunner::IS_NESTED)) { + return new MenuRunnerImplCocoa(menu_model, on_menu_closed_callback); + } return new MenuRunnerImplAdapter(menu_model, on_menu_closed_callback); } @@ -214,8 +218,7 @@ base::TimeTicks MenuRunnerImplCocoa::GetClosingEventTime() const { return closing_event_time_; } -MenuRunnerImplCocoa::~MenuRunnerImplCocoa() { -} +MenuRunnerImplCocoa::~MenuRunnerImplCocoa() {} } // namespace internal } // namespace views diff --git a/chromium/ui/views/controls/menu/menu_runner_unittest.cc b/chromium/ui/views/controls/menu/menu_runner_unittest.cc index 4c6c320cd93..704f242106d 100644 --- a/chromium/ui/views/controls/menu/menu_runner_unittest.cc +++ b/chromium/ui/views/controls/menu/menu_runner_unittest.cc @@ -64,8 +64,12 @@ class MenuRunnerTest : public ViewsTestBase { MenuRunnerTest(); ~MenuRunnerTest() override; - // Initializes a MenuRunner with |run_types|. It takes ownership of - // |menu_item_view_|. + // Initializes the delegates and views needed for a menu. It does not create + // the MenuRunner. + void InitMenuViews(); + + // Initializes all delegates and views needed for a menu. A MenuRunner is also + // created with |run_types|, it takes ownership of |menu_item_view_|. void InitMenuRunner(int32_t run_types); MenuItemView* menu_item_view() { return menu_item_view_; } @@ -74,12 +78,11 @@ class MenuRunnerTest : public ViewsTestBase { Widget* owner() { return owner_.get(); } // ViewsTestBase: - void SetUp() override; void TearDown() override; private: - // Owned by MenuRunner. - MenuItemView* menu_item_view_; + // Owned by menu_runner_. + MenuItemView* menu_item_view_ = nullptr; std::unique_ptr<TestMenuDelegate> menu_delegate_; std::unique_ptr<MenuRunner> menu_runner_; @@ -92,12 +95,7 @@ MenuRunnerTest::MenuRunnerTest() {} MenuRunnerTest::~MenuRunnerTest() {} -void MenuRunnerTest::InitMenuRunner(int32_t run_types) { - menu_runner_.reset(new MenuRunner(menu_item_view_, run_types)); -} - -void MenuRunnerTest::SetUp() { - ViewsTestBase::SetUp(); +void MenuRunnerTest::InitMenuViews() { menu_delegate_.reset(new TestMenuDelegate); menu_item_view_ = new MenuItemView(menu_delegate_.get()); menu_item_view_->AppendMenuItemWithLabel(1, base::ASCIIToUTF16("One")); @@ -111,8 +109,14 @@ void MenuRunnerTest::SetUp() { owner_->Show(); } +void MenuRunnerTest::InitMenuRunner(int32_t run_types) { + InitMenuViews(); + menu_runner_.reset(new MenuRunner(menu_item_view_, run_types)); +} + void MenuRunnerTest::TearDown() { - owner_->CloseNow(); + if (owner_) + owner_->CloseNow(); ViewsTestBase::TearDown(); } @@ -367,7 +371,21 @@ TEST_F(MenuRunnerWidgetTest, ClearsMouseHandlerOnRun) { EXPECT_EQ(1, second_event_count_view->GetEventCount(ui::ET_MOUSE_PRESSED)); } -typedef MenuRunnerTest MenuRunnerImplTest; +class MenuRunnerImplTest : public MenuRunnerTest { + public: + MenuRunnerImplTest() {} + ~MenuRunnerImplTest() override {} + + void SetUp() override; + + private: + DISALLOW_COPY_AND_ASSIGN(MenuRunnerImplTest); +}; + +void MenuRunnerImplTest::SetUp() { + MenuRunnerTest::SetUp(); + InitMenuViews(); +} // Tests that when nested menu runners are destroyed out of order, that // MenuController is not accessed after it has been destroyed. This should not @@ -472,6 +490,7 @@ void MenuRunnerDestructionTest::SetUp() { views_delegate_ = views_delegate.get(); set_views_delegate(std::move(views_delegate)); MenuRunnerTest::SetUp(); + InitMenuViews(); } // Tests that when ViewsDelegate is released that a nested Cancel of the diff --git a/chromium/ui/views/controls/menu/menu_scroll_view_container.cc b/chromium/ui/views/controls/menu/menu_scroll_view_container.cc index abcc8e28e7e..16a1bef6715 100644 --- a/chromium/ui/views/controls/menu/menu_scroll_view_container.cc +++ b/chromium/ui/views/controls/menu/menu_scroll_view_container.cc @@ -335,6 +335,7 @@ BubbleBorder::Arrow MenuScrollViewContainer::BubbleBorderTypeFromAnchor( return BubbleBorder::TOP_CENTER; case MENU_ANCHOR_BUBBLE_TOUCHABLE_ABOVE: case MENU_ANCHOR_BUBBLE_TOUCHABLE_LEFT: + case MENU_ANCHOR_BUBBLE_TOUCHABLE_RIGHT: return BubbleBorder::FLOAT; default: return BubbleBorder::NONE; diff --git a/chromium/ui/views/controls/menu/menu_separator.h b/chromium/ui/views/controls/menu/menu_separator.h index e0dbdb9760d..4e91f1707e2 100644 --- a/chromium/ui/views/controls/menu/menu_separator.h +++ b/chromium/ui/views/controls/menu/menu_separator.h @@ -9,10 +9,11 @@ #include "base/macros.h" #include "ui/base/models/menu_separator_types.h" #include "ui/views/view.h" +#include "ui/views/views_export.h" namespace views { -class MenuSeparator : public View { +class VIEWS_EXPORT MenuSeparator : public View { public: explicit MenuSeparator(ui::MenuSeparatorType type) : type_(type) {} diff --git a/chromium/ui/views/controls/menu/menu_types.h b/chromium/ui/views/controls/menu/menu_types.h index 03a0c72a5db..d8419e6ad1e 100644 --- a/chromium/ui/views/controls/menu/menu_types.h +++ b/chromium/ui/views/controls/menu/menu_types.h @@ -22,7 +22,10 @@ enum MenuAnchorPosition { MENU_ANCHOR_BUBBLE_ABOVE, MENU_ANCHOR_BUBBLE_BELOW, MENU_ANCHOR_BUBBLE_TOUCHABLE_ABOVE, - MENU_ANCHOR_BUBBLE_TOUCHABLE_LEFT + MENU_ANCHOR_BUBBLE_TOUCHABLE_LEFT, + MENU_ANCHOR_BUBBLE_TOUCHABLE_RIGHT, + // Keep this the last item. + MENU_ANCHOR_POSITION_LAST }; } // namespace views diff --git a/chromium/ui/views/controls/menu/submenu_view.cc b/chromium/ui/views/controls/menu/submenu_view.cc index b69a318e69f..cdb36b6df6c 100644 --- a/chromium/ui/views/controls/menu/submenu_view.cc +++ b/chromium/ui/views/controls/menu/submenu_view.cc @@ -172,7 +172,7 @@ gfx::Size SubmenuView::CalculatePreferredSize() const { } } if (max_minor_text_width_ > 0) - max_minor_text_width_ += MenuConfig::instance().label_to_minor_text_padding; + max_minor_text_width_ += MenuConfig::instance().item_horizontal_padding; // Finish calculating our optimum width. gfx::Insets insets = GetInsets(); @@ -385,6 +385,7 @@ void SubmenuView::ShowAt(Widget* parent, const gfx::Rect& bounds, bool do_capture) { if (host_) { + host_->SetMenuHostBounds(bounds); host_->ShowMenuHost(do_capture); } else { host_ = new MenuHost(this); diff --git a/chromium/ui/views/controls/native/native_view_host.cc b/chromium/ui/views/controls/native/native_view_host.cc index f15b82824d5..32019ba3fdb 100644 --- a/chromium/ui/views/controls/native/native_view_host.cc +++ b/chromium/ui/views/controls/native/native_view_host.cc @@ -8,6 +8,7 @@ #include "ui/base/cursor/cursor.h" #include "ui/gfx/canvas.h" #include "ui/views/controls/native/native_view_host_wrapper.h" +#include "ui/views/painter.h" #include "ui/views/widget/widget.h" namespace views { @@ -49,7 +50,14 @@ void NativeViewHost::Detach() { } bool NativeViewHost::SetCornerRadius(int corner_radius) { - return native_wrapper_->SetCornerRadius(corner_radius); + return SetCustomMask(views::Painter::CreatePaintedLayer( + views::Painter::CreateSolidRoundRectPainter(SK_ColorBLACK, + corner_radius))); +} + +bool NativeViewHost::SetCustomMask(std::unique_ptr<ui::LayerOwner> mask) { + DCHECK(native_wrapper_); + return native_wrapper_->SetCustomMask(std::move(mask)); } void NativeViewHost::SetNativeViewSize(const gfx::Size& size) { diff --git a/chromium/ui/views/controls/native/native_view_host.h b/chromium/ui/views/controls/native/native_view_host.h index c81cb7443a5..5bd897cf3ed 100644 --- a/chromium/ui/views/controls/native/native_view_host.h +++ b/chromium/ui/views/controls/native/native_view_host.h @@ -49,9 +49,14 @@ class VIEWS_EXPORT NativeViewHost : public View { // Sets the corner radius for clipping gfx::NativeView. Returns true on // success or false if the platform doesn't support the operation. - // NB: This does not interact nicely with fast_resize. + // This method calls SetCustomMask internally. bool SetCornerRadius(int corner_radius); + // Sets the custom layer mask for clipping gfx::NativeView. Returns true on + // success or false if the platform doesn't support the operation. + // NB: This does not interact nicely with fast_resize. + bool SetCustomMask(std::unique_ptr<ui::LayerOwner> mask); + // Sets the size for the NativeView that may or may not match the size of this // View when it is being captured. If the size does not match, scaling will // occur. Pass an empty size to revert to the default behavior, where the 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 097bcd4879b..6afc8fbf9c3 100644 --- a/chromium/ui/views/controls/native/native_view_host_aura.cc +++ b/chromium/ui/views/controls/native/native_view_host_aura.cc @@ -19,6 +19,7 @@ #include "ui/views/view_constants_aura.h" #include "ui/views/view_properties.h" #include "ui/views/widget/widget.h" +#include "ui/wm/core/window_util.h" namespace views { @@ -148,16 +149,16 @@ void NativeViewHostAura::RemovedFromWidget() { } } -bool NativeViewHostAura::SetCornerRadius(int corner_radius) { +bool NativeViewHostAura::SetCustomMask(std::unique_ptr<ui::LayerOwner> mask) { #if defined(OS_WIN) // TODO(crbug/843250): On Aura, layer masks don't play with HiDPI. Fix this // and enable this on Windows. return false; #else - mask_ = views::Painter::CreatePaintedLayer( - views::Painter::CreateSolidRoundRectPainter(SK_ColorBLACK, - corner_radius)); - mask_->layer()->SetFillsBoundsOpaquely(false); + UninstallMask(); + mask_ = std::move(mask); + if (mask_) + mask_->layer()->SetFillsBoundsOpaquely(false); InstallMask(); return true; #endif @@ -237,8 +238,15 @@ void NativeViewHostAura::OnWindowBoundsChanged( const gfx::Rect& old_bounds, const gfx::Rect& new_bounds, ui::PropertyChangeReason reason) { - if (mask_) + if (mask_) { + // Having a mask means this layer has a render surface of its own. This + // means we want this layer snapped as the render surface uses this layer + // (its primary layer) to snap to the physical pixel grid. + // See https://crbug.com/843250 for more details. + wm::SnapWindowToPixelBoundary(window); + mask_->layer()->SetBounds(gfx::Rect(host_->native_view()->bounds().size())); + } } void NativeViewHostAura::OnWindowDestroying(aura::Window* window) { @@ -292,9 +300,24 @@ void NativeViewHostAura::InstallMask() { if (!mask_) return; if (host_->native_view()) { + // Setting a mask triggers this layer to have a render surface of its own. + // This means we cannot skip computing its subpixel offset positioning as + // the render surface uses this layer (its primary layer) to snap to the + // physical pixel grid. + // See https://crbug.com/843250 for more details. + wm::SnapWindowToPixelBoundary(host_->native_view()); + mask_->layer()->SetBounds(gfx::Rect(host_->native_view()->bounds().size())); host_->native_view()->layer()->SetMaskLayer(mask_->layer()); } } +void NativeViewHostAura::UninstallMask() { + if (!host_->native_view() || !mask_) + return; + + host_->native_view()->layer()->SetMaskLayer(nullptr); + mask_.reset(); +} + } // namespace views 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 41a56cc63e4..0ea78965b8f 100644 --- a/chromium/ui/views/controls/native/native_view_host_aura.h +++ b/chromium/ui/views/controls/native/native_view_host_aura.h @@ -30,7 +30,7 @@ class NativeViewHostAura : public NativeViewHostWrapper, void NativeViewDetaching(bool destroyed) override; void AddedToWidget() override; void RemovedFromWidget() override; - bool SetCornerRadius(int corner_radius) override; + bool SetCustomMask(std::unique_ptr<ui::LayerOwner> mask) override; void InstallClip(int x, int y, int w, int h) override; bool HasInstalledClip() override; void UninstallClip() override; @@ -64,6 +64,9 @@ class NativeViewHostAura : public NativeViewHostWrapper, // Sets or updates the mask layer on the native view's layer. void InstallMask(); + // Unsets the mask layer on the native view's layer. + void UninstallMask(); + // Our associated NativeViewHost. NativeViewHost* host_; 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 5db28b49aba..69659c9aafd 100644 --- a/chromium/ui/views/controls/native/native_view_host_mac.h +++ b/chromium/ui/views/controls/native/native_view_host_mac.h @@ -10,6 +10,10 @@ #include "ui/views/controls/native/native_view_host_wrapper.h" #include "ui/views/views_export.h" +namespace ui { +class LayerOwner; +} + namespace views { class NativeViewHost; @@ -25,7 +29,7 @@ class NativeViewHostMac : public NativeViewHostWrapper { void NativeViewDetaching(bool destroyed) override; void AddedToWidget() override; void RemovedFromWidget() override; - bool SetCornerRadius(int corner_radius) override; + bool SetCustomMask(std::unique_ptr<ui::LayerOwner> mask) override; void InstallClip(int x, int y, int w, int h) override; bool HasInstalledClip() override; void UninstallClip() override; 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 0ca2c4a6a41..debbe51f102 100644 --- a/chromium/ui/views/controls/native/native_view_host_mac.mm +++ b/chromium/ui/views/controls/native/native_view_host_mac.mm @@ -7,6 +7,8 @@ #import <Cocoa/Cocoa.h> #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" #include "ui/views/controls/native/native_view_host.h" #include "ui/views/widget/native_widget_mac.h" @@ -36,6 +38,17 @@ void EnsureNativeViewHasNoChildWidgets(NSView* native_view) { } } +AXPlatformNodeCocoa* ClosestPlatformAncestorNode(views::View* view) { + do { + gfx::NativeViewAccessible accessible = view->GetNativeViewAccessible(); + if ([accessible isKindOfClass:[AXPlatformNodeCocoa class]]) { + return NSAccessibilityUnignoredAncestor(accessible); + } + view = view->parent(); + } while (view); + return nil; +} + } // namespace NativeViewHostMac::NativeViewHostMac(NativeViewHost* host) : host_(host) { @@ -56,6 +69,28 @@ void NativeViewHostMac::AttachNativeView() { 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. + id hostable = native_view_; + [hostable setAccessibilityParentElement:ClosestPlatformAncestorNode( + host_->parent())]; + } EnsureNativeViewHasNoChildWidgets(native_view_); BridgedNativeWidget* bridge = NativeWidgetMac::GetBridgeForNativeWindow( @@ -85,6 +120,10 @@ void NativeViewHostMac::NativeViewDetaching(bool destroyed) { 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]; + } EnsureNativeViewHasNoChildWidgets(host_->native_view()); BridgedNativeWidget* bridge = NativeWidgetMac::GetBridgeForNativeWindow( @@ -111,7 +150,7 @@ void NativeViewHostMac::RemovedFromWidget() { NativeViewDetaching(false); } -bool NativeViewHostMac::SetCornerRadius(int corner_radius) { +bool NativeViewHostMac::SetCustomMask(std::unique_ptr<ui::LayerOwner> mask) { NOTIMPLEMENTED(); return false; } @@ -164,7 +203,7 @@ void NativeViewHostMac::SetFocus() { } gfx::NativeViewAccessible NativeViewHostMac::GetNativeViewAccessible() { - return NULL; + return nullptr; } gfx::NativeCursor NativeViewHostMac::GetCursor(int x, int y) { 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 bb2493f5c11..4a4fdcad1b1 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,11 +12,19 @@ #import "base/mac/scoped_nsobject.h" #include "base/macros.h" #import "testing/gtest_mac.h" +#import "ui/base/cocoa/accessibility_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; +@end +@implementation TestAccessibilityHostableView +@synthesize accessibilityParentElement = accessibilityParentElement_; +@end + namespace views { class NativeViewHostMacTest : public test::NativeViewHostTestBase { @@ -100,6 +108,24 @@ TEST_F(NativeViewHostMacTest, Attach) { DestroyHost(); } +// Ensure the native view is integrated into the views accessibility +// hierarchy if the native view conforms to the AccessibilityParent +// protocol. +TEST_F(NativeViewHostMacTest, AccessibilityParent) { + CreateHost(); + host()->Detach(); + + base::scoped_nsobject<TestAccessibilityHostableView> view( + [[TestAccessibilityHostableView alloc] init]); + host()->Attach(view); + EXPECT_NSEQ([view accessibilityParentElement], + toplevel()->GetRootView()->GetNativeViewAccessible()); + + host()->Detach(); + DestroyHost(); + EXPECT_FALSE([view accessibilityParentElement]); +} + // Test that the content windows' bounds are set to the correct values while the // native size is equal or not equal to the View size. TEST_F(NativeViewHostMacTest, ContentViewPositionAndSize) { 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 b8c156f14fc..513c35f1a71 100644 --- a/chromium/ui/views/controls/native/native_view_host_wrapper.h +++ b/chromium/ui/views/controls/native/native_view_host_wrapper.h @@ -8,6 +8,10 @@ #include "ui/gfx/native_widget_types.h" #include "ui/views/views_export.h" +namespace ui { +class LayerOwner; +} + namespace views { class NativeViewHost; @@ -38,9 +42,9 @@ class NativeViewHostWrapper { // rooted at a valid Widget. virtual void RemovedFromWidget() = 0; - // Sets the corner radius for clipping gfx::NativeView. Returns true on + // Sets the custom mask for clipping gfx::NativeView. Returns true on // success or false if the platform doesn't support the operation. - virtual bool SetCornerRadius(int corner_radius) = 0; + virtual bool SetCustomMask(std::unique_ptr<ui::LayerOwner> mask) = 0; // Installs a clip on the gfx::NativeView. These values are in the coordinate // space of the Widget, so if this method is called from ShowWidget diff --git a/chromium/ui/views/controls/prefix_selector.cc b/chromium/ui/views/controls/prefix_selector.cc index 6590a8e0b05..9844d192d78 100644 --- a/chromium/ui/views/controls/prefix_selector.cc +++ b/chromium/ui/views/controls/prefix_selector.cc @@ -145,10 +145,10 @@ bool PrefixSelector::IsTextEditCommandEnabled( void PrefixSelector::SetTextEditCommandForNextKeyEvent( ui::TextEditCommand command) {} -const std::string& PrefixSelector::GetClientSourceInfo() const { - // TODO(yhanada): Implement this method. +ukm::SourceId PrefixSelector::GetClientSourceForMetrics() const { + // TODO(shend): Implement this method. NOTIMPLEMENTED_LOG_ONCE(); - return base::EmptyString(); + return ukm::SourceId(); } bool PrefixSelector::ShouldDoLearning() { diff --git a/chromium/ui/views/controls/prefix_selector.h b/chromium/ui/views/controls/prefix_selector.h index e3654eebbb3..ba2005191b3 100644 --- a/chromium/ui/views/controls/prefix_selector.h +++ b/chromium/ui/views/controls/prefix_selector.h @@ -60,7 +60,7 @@ class VIEWS_EXPORT PrefixSelector : public ui::TextInputClient { bool IsTextEditCommandEnabled(ui::TextEditCommand command) const override; void SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command) override; - const std::string& GetClientSourceInfo() const override; + ukm::SourceId GetClientSourceForMetrics() const override; bool ShouldDoLearning() override; private: diff --git a/chromium/ui/views/controls/scroll_view_unittest.cc b/chromium/ui/views/controls/scroll_view_unittest.cc index d2596d71109..4da3917fefb 100644 --- a/chromium/ui/views/controls/scroll_view_unittest.cc +++ b/chromium/ui/views/controls/scroll_view_unittest.cc @@ -44,7 +44,8 @@ class ScrollViewTestApi { return static_cast<BaseScrollBar*>(scroll_bar); } - const base::Timer& GetScrollBarTimer(ScrollBarOrientation orientation) { + const base::OneShotTimer& GetScrollBarTimer( + ScrollBarOrientation orientation) { return GetBaseScrollBar(orientation)->repeater_.timer_for_testing(); } @@ -58,7 +59,8 @@ class ScrollViewTestApi { gfx::ScrollOffset CurrentOffset() { return scroll_view_->CurrentOffset(); } - base::Timer* GetScrollBarHideTimer(ScrollBarOrientation orientation) { + base::RetainingOneShotTimer* GetScrollBarHideTimer( + ScrollBarOrientation orientation) { return BaseScrollBar::GetHideTimerForTest(GetBaseScrollBar(orientation)); } @@ -987,8 +989,9 @@ TEST_F(WidgetScrollViewTest, ScrollersOnRest) { ScrollViewTestApi test_api(scroll_view); BaseScrollBar* bar[]{test_api.GetBaseScrollBar(HORIZONTAL), test_api.GetBaseScrollBar(VERTICAL)}; - base::Timer* hide_timer[]{test_api.GetScrollBarHideTimer(HORIZONTAL), - test_api.GetScrollBarHideTimer(VERTICAL)}; + base::RetainingOneShotTimer* hide_timer[] = { + test_api.GetScrollBarHideTimer(HORIZONTAL), + test_api.GetScrollBarHideTimer(VERTICAL)}; EXPECT_EQ(0, bar[HORIZONTAL]->layer()->opacity()); EXPECT_EQ(0, bar[VERTICAL]->layer()->opacity()); @@ -1459,7 +1462,7 @@ TEST_F(WidgetScrollViewTest, ScrollTrackScrolling) { ui::MouseEvent press(TestLeftMouseAt(location, ui::ET_MOUSE_PRESSED)); ui::MouseEvent release(TestLeftMouseAt(location, ui::ET_MOUSE_RELEASED)); - const base::Timer& timer = test_api.GetScrollBarTimer(VERTICAL); + const base::OneShotTimer& timer = test_api.GetScrollBarTimer(VERTICAL); EXPECT_FALSE(timer.IsRunning()); EXPECT_EQ(0, scroll_view->GetVisibleRect().y()); diff --git a/chromium/ui/views/controls/scrollbar/base_scroll_bar.cc b/chromium/ui/views/controls/scrollbar/base_scroll_bar.cc index bf652746430..0801c66123a 100644 --- a/chromium/ui/views/controls/scrollbar/base_scroll_bar.cc +++ b/chromium/ui/views/controls/scrollbar/base_scroll_bar.cc @@ -405,7 +405,8 @@ int BaseScrollBar::GetScrollIncrement(bool is_page, bool is_positive) { #if !defined(OS_MACOSX) // static -base::Timer* BaseScrollBar::GetHideTimerForTest(BaseScrollBar* scroll_bar) { +base::RetainingOneShotTimer* BaseScrollBar::GetHideTimerForTest( + BaseScrollBar* scroll_bar) { return nullptr; } #endif diff --git a/chromium/ui/views/controls/scrollbar/base_scroll_bar.h b/chromium/ui/views/controls/scrollbar/base_scroll_bar.h index 07ae5b04111..9b447add2ee 100644 --- a/chromium/ui/views/controls/scrollbar/base_scroll_bar.h +++ b/chromium/ui/views/controls/scrollbar/base_scroll_bar.h @@ -111,7 +111,8 @@ class VIEWS_EXPORT BaseScrollBar : public ScrollBar, FRIEND_TEST_ALL_PREFIXES(ScrollBarViewsTest, ScrollBarFitsToBottom); FRIEND_TEST_ALL_PREFIXES(ScrollBarViewsTest, ThumbFullLengthOfTrack); - static base::Timer* GetHideTimerForTest(BaseScrollBar* scroll_bar); + static base::RetainingOneShotTimer* GetHideTimerForTest( + BaseScrollBar* scroll_bar); int GetThumbSizeForTest(); // Changes to 'pushed' state and starts a timer to scroll repeatedly. diff --git a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h index a78bc8467ad..a0c88726dab 100644 --- a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h +++ b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.h @@ -98,7 +98,7 @@ class VIEWS_EXPORT CocoaScrollBar : public BaseScrollBar, NSScrollerStyle scroller_style_; // Timer that will start the scrollbar's hiding animation when it reaches 0. - base::Timer hide_scrollbar_timer_; + base::RetainingOneShotTimer hide_scrollbar_timer_; // Slide animation that animates the thickness of an overlay scrollbar. // The animation expands the scrollbar as the showing animation and shrinks diff --git a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm index 1f977ae170e..05431a33382 100644 --- a/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm +++ b/chromium/ui/views/controls/scrollbar/cocoa_scroll_bar.mm @@ -178,8 +178,7 @@ CocoaScrollBar::CocoaScrollBar(bool horizontal) hide_scrollbar_timer_( FROM_HERE, base::TimeDelta::FromMilliseconds(kScrollbarHideTimeoutMs), - base::Bind(&CocoaScrollBar::HideScrollbar, base::Unretained(this)), - false), + base::Bind(&CocoaScrollBar::HideScrollbar, base::Unretained(this))), thickness_animation_(this), last_contents_scroll_offset_(0), is_expanded_(false), @@ -542,7 +541,8 @@ CocoaScrollBarThumb* CocoaScrollBar::GetCocoaScrollBarThumb() const { } // static -base::Timer* BaseScrollBar::GetHideTimerForTest(BaseScrollBar* scroll_bar) { +base::RetainingOneShotTimer* BaseScrollBar::GetHideTimerForTest( + BaseScrollBar* scroll_bar) { return &static_cast<CocoaScrollBar*>(scroll_bar)->hide_scrollbar_timer_; } diff --git a/chromium/ui/views/controls/scrollbar/overlay_scroll_bar.cc b/chromium/ui/views/controls/scrollbar/overlay_scroll_bar.cc index e62c0c84a1b..acd37458520 100644 --- a/chromium/ui/views/controls/scrollbar/overlay_scroll_bar.cc +++ b/chromium/ui/views/controls/scrollbar/overlay_scroll_bar.cc @@ -124,7 +124,7 @@ void OverlayScrollBar::Thumb::OnStateChanged() { } OverlayScrollBar::OverlayScrollBar(bool horizontal) - : BaseScrollBar(horizontal), hide_timer_(false, false) { + : BaseScrollBar(horizontal) { auto* thumb = new Thumb(this); SetThumb(thumb); thumb->Init(); diff --git a/chromium/ui/views/controls/scrollbar/overlay_scroll_bar.h b/chromium/ui/views/controls/scrollbar/overlay_scroll_bar.h index b0f2a7c7c9a..ac1ac3849c0 100644 --- a/chromium/ui/views/controls/scrollbar/overlay_scroll_bar.h +++ b/chromium/ui/views/controls/scrollbar/overlay_scroll_bar.h @@ -59,7 +59,7 @@ class VIEWS_EXPORT OverlayScrollBar : public BaseScrollBar { // Starts a countdown that hides this when it fires. void StartHideCountdown(); - base::Timer hide_timer_; + base::OneShotTimer hide_timer_; DISALLOW_COPY_AND_ASSIGN(OverlayScrollBar); }; diff --git a/chromium/ui/views/controls/styled_label_unittest.cc b/chromium/ui/views/controls/styled_label_unittest.cc index 4f18b6880c4..a71c62d657a 100644 --- a/chromium/ui/views/controls/styled_label_unittest.cc +++ b/chromium/ui/views/controls/styled_label_unittest.cc @@ -18,6 +18,7 @@ #include "testing/gtest/include/gtest/gtest.h" #include "third_party/skia/include/core/SkColor.h" #include "ui/base/ui_base_features.h" +#include "ui/base/ui_base_switches.h" #include "ui/gfx/font_list.h" #include "ui/views/border.h" #include "ui/views/controls/link.h" @@ -79,6 +80,12 @@ class MDStyledLabelTest // StyledLabelTest: void SetUp() override { + if (GetParam() == SecondaryUiMode::NON_MD) { + // Force Refresh UI to be off, since that mode implies MD secondary UI. + // Must be done before ViewsTestBase::SetUp(). + base::CommandLine::ForCurrentProcess()->AppendSwitchASCII( + switches::kTopChromeMD, switches::kTopChromeMDMaterial); + } // This works while StyledLabelTest has no SetUp() of its own. Otherwise the // mode should be set after ViewsTestBase::SetUp(), but before the rest of // StyledLabelTest::SetUp(), so that StyledLabelTest::SetUp() obeys the MD diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc index 17f9259f012..4cd621b05c5 100644 --- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc +++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.cc @@ -4,6 +4,7 @@ #include "ui/views/controls/tabbed_pane/tabbed_pane.h" +#include "base/i18n/rtl.h" #include "base/logging.h" #include "base/macros.h" #include "cc/paint/paint_flags.h" @@ -18,6 +19,7 @@ #include "ui/gfx/animation/linear_animation.h" #include "ui/gfx/animation/tween.h" #include "ui/gfx/canvas.h" +#include "ui/gfx/color_palette.h" #include "ui/gfx/font_list.h" #include "ui/gfx/geometry/insets.h" #include "ui/native_theme/native_theme.h" @@ -37,19 +39,19 @@ namespace { // TODO(markusheintz|msw): Use NativeTheme colors. constexpr SkColor kTabTitleColor_InactiveBorder = SkColorSetARGB(0xFF, 0x64, 0x64, 0x64); -constexpr SkColor kTabTitleColor_InactiveHighlight = - SkColorSetARGB(0xFF, 0x80, 0x86, 0x8B); +constexpr SkColor kTabTitleColor_InactiveHighlight = gfx::kGoogleGrey700; constexpr SkColor kTabTitleColor_ActiveBorder = SK_ColorBLACK; -constexpr SkColor kTabTitleColor_ActiveHighlight = - SkColorSetARGB(0xFF, 0x42, 0x85, 0xF4); +constexpr SkColor kTabTitleColor_ActiveHighlight = gfx::kGoogleBlue600; const SkColor kTabTitleColor_Hovered = SK_ColorBLACK; const SkColor kTabBorderColor = SkColorSetRGB(0xC8, 0xC8, 0xC8); const SkScalar kTabBorderThickness = 1.0f; -constexpr SkColor kTabHighlightBackgroundColor = +constexpr SkColor kTabHighlightBackgroundColor_Active = SkColorSetARGB(0xFF, 0xE8, 0xF0, 0xFE); +constexpr SkColor kTabHighlightBackgroundColor_Focused = + SkColorSetARGB(0xFF, 0xD2, 0xE3, 0xFC); constexpr int kTabHighlightBorderRadius = 32; constexpr int kTabHighlightPreferredHeight = 32; -constexpr int kTabHighlightPreferredWidth = 208; +constexpr int kTabHighlightPreferredWidth = 192; const gfx::Font::Weight kHoverWeightBorder = gfx::Font::Weight::NORMAL; const gfx::Font::Weight kHoverWeightHighlight = gfx::Font::Weight::MEDIUM; @@ -134,10 +136,8 @@ Tab::Tab(TabbedPane* tabbed_pane, const base::string16& title, View* contents) const bool is_highlight_style = tabbed_pane_->GetStyle() == TabbedPane::TabStripStyle::kHighlight; - if (is_vertical) { + if (is_vertical) title_->SetHorizontalAlignment(gfx::HorizontalAlignment::ALIGN_LEFT); - title_->SetElideBehavior(gfx::ElideBehavior::NO_ELIDE); - } if (is_highlight_style && is_vertical) { const int kTabVerticalPadding = 8; @@ -273,13 +273,22 @@ void Tab::OnPaint(gfx::Canvas* canvas) { } SkScalar radius = SkIntToScalar(kTabHighlightBorderRadius); - const SkScalar kRadius[8] = {0, 0, radius, radius, radius, radius, 0, 0}; SkPath path; gfx::Rect bounds(size()); - path.addRoundRect(gfx::RectToSkRect(bounds), kRadius); + if (base::i18n::IsRTL()) { + const SkScalar kRadius[8] = {radius, radius, 0, 0, 0, 0, radius, radius}; + path.addRoundRect(gfx::RectToSkRect(bounds), kRadius); + } else { + const SkScalar kRadius[8] = {0, 0, radius, radius, radius, radius, 0, 0}; + path.addRoundRect(gfx::RectToSkRect(bounds), kRadius); + } + cc::PaintFlags fill_flags; fill_flags.setAntiAlias(true); - fill_flags.setColor(kTabHighlightBackgroundColor); + if (HasFocus()) + fill_flags.setColor(kTabHighlightBackgroundColor_Focused); + else + fill_flags.setColor(kTabHighlightBackgroundColor_Active); canvas->DrawPath(path, fill_flags); } @@ -367,16 +376,21 @@ gfx::Size MdTab::CalculatePreferredSize() const { } void MdTab::OnFocus() { - SetBorder(CreateSolidBorder( - GetInsets().top(), - SkColorSetA(GetNativeTheme()->GetSystemColor( - ui::NativeTheme::kColorId_FocusedBorderColor), - 0x66))); + // Do not draw focus ring in kHighlight mode. + if (tabbed_pane()->GetStyle() != TabbedPane::TabStripStyle::kHighlight) { + SetBorder(CreateSolidBorder( + GetInsets().top(), + SkColorSetA(GetNativeTheme()->GetSystemColor( + ui::NativeTheme::kColorId_FocusedBorderColor), + 0x66))); + } SchedulePaint(); } void MdTab::OnBlur() { - SetBorder(CreateEmptyBorder(GetInsets())); + // Do not draw focus ring in kHighlight mode. + if (tabbed_pane()->GetStyle() != TabbedPane::TabStripStyle::kHighlight) + SetBorder(CreateEmptyBorder(GetInsets())); SchedulePaint(); } diff --git a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h index 0d3cec8d493..50a34abf01b 100644 --- a/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h +++ b/chromium/ui/views/controls/tabbed_pane/tabbed_pane.h @@ -153,6 +153,8 @@ class Tab : public View { protected: Label* title() { return title_; } + TabbedPane* tabbed_pane() { return tabbed_pane_; } + // Called whenever |tab_state_| changes. virtual void OnStateChanged(); diff --git a/chromium/ui/views/controls/textfield/textfield.cc b/chromium/ui/views/controls/textfield/textfield.cc index 3c1d189c674..c9a96b43788 100644 --- a/chromium/ui/views/controls/textfield/textfield.cc +++ b/chromium/ui/views/controls/textfield/textfield.cc @@ -325,7 +325,7 @@ void Textfield::SetAssociatedLabel(View* labelling_view) { ui::AXNodeData node_data; labelling_view->GetAccessibleNodeData(&node_data); // TODO(aleventhal) automatically handle setting the name from the related - // label in view_accessibility and have it update the name if the text of the + // label in ViewAccessibility and have it update the name if the text of the // associated label changes. SetAccessibleName( node_data.GetString16Attribute(ax::mojom::StringAttribute::kName)); @@ -531,10 +531,10 @@ void Textfield::SetHorizontalAlignment(gfx::HorizontalAlignment alignment) { GetRenderText()->SetHorizontalAlignment(alignment); } -void Textfield::ShowImeIfNeeded() { +void Textfield::ShowVirtualKeyboardIfEnabled() { // GetInputMethod() may return nullptr in tests. if (enabled() && !read_only() && GetInputMethod()) - GetInputMethod()->ShowImeIfNeeded(); + GetInputMethod()->ShowVirtualKeyboardIfEnabled(); } bool Textfield::IsIMEComposing() const { @@ -666,7 +666,7 @@ bool Textfield::OnMousePressed(const ui::MouseEvent& event) { (event.IsOnlyLeftMouseButton() || event.IsOnlyRightMouseButton())) { if (!had_focus) RequestFocusWithPointer(ui::EventPointerType::POINTER_TYPE_MOUSE); - ShowImeIfNeeded(); + ShowVirtualKeyboardIfEnabled(); } #if defined(OS_LINUX) && !defined(OS_CHROMEOS) @@ -746,7 +746,7 @@ void Textfield::OnGestureEvent(ui::GestureEvent* event) { switch (event->type()) { case ui::ET_GESTURE_TAP_DOWN: RequestFocusWithPointer(event->details().primary_pointer_type()); - ShowImeIfNeeded(); + ShowVirtualKeyboardIfEnabled(); event->SetHandled(); break; case ui::ET_GESTURE_TAP: @@ -1035,11 +1035,11 @@ bool Textfield::HandleAccessibleAction(const ui::AXActionData& action_data) { return View::HandleAccessibleAction(action_data); if (action_data.action == ax::mojom::Action::kSetValue) { - SetText(action_data.value); + SetText(base::UTF8ToUTF16(action_data.value)); ClearSelection(); return true; } else if (action_data.action == ax::mojom::Action::kReplaceSelectedText) { - InsertOrReplaceText(action_data.value); + InsertOrReplaceText(base::UTF8ToUTF16(action_data.value)); ClearSelection(); return true; } @@ -1168,6 +1168,15 @@ void Textfield::OnCompositionTextConfirmedOrCleared() { void Textfield::ShowContextMenuForView(View* source, const gfx::Point& point, ui::MenuSourceType source_type) { +#if defined(OS_MACOSX) + // On Mac, the context menu contains a look up item which displays the + // selected text. As such, the menu needs to be updated if the selection has + // changed. Be careful to reset the MenuRunner first so it doesn't reference + // the old model. + context_menu_runner_.reset(); + context_menu_contents_.reset(); +#endif + UpdateContextMenu(); context_menu_runner_->RunMenuAt(GetWidget(), NULL, gfx::Rect(point, gfx::Size()), @@ -1743,10 +1752,10 @@ void Textfield::SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command) { scheduled_text_edit_command_ = command; } -const std::string& Textfield::GetClientSourceInfo() const { - // TODO(yhanada): Implement this method. +ukm::SourceId Textfield::GetClientSourceForMetrics() const { + // TODO(shend): Implement this method. NOTIMPLEMENTED_LOG_ONCE(); - return base::EmptyString(); + return ukm::SourceId(); } bool Textfield::ShouldDoLearning() { @@ -1984,6 +1993,14 @@ void Textfield::OffsetDoubleClickWord(int offset) { selection_controller_.OffsetDoubleClickWord(offset); } +bool Textfield::IsDropCursorForInsertion() const { + return true; +} + +bool Textfield::ShouldShowPlaceholderText() const { + return text().empty() && !GetPlaceholderText().empty(); +} + //////////////////////////////////////////////////////////////////////////////// // Textfield, private: @@ -2136,7 +2153,7 @@ void Textfield::PaintTextAndCursor(gfx::Canvas* canvas) { // Draw placeholder text if needed. gfx::RenderText* render_text = GetRenderText(); - if (text().empty() && !GetPlaceholderText().empty()) { + if (ShouldShowPlaceholderText()) { // Disable subpixel rendering when the background color is not opaque // because it draws incorrect colors around the glyphs in that case. // See crbug.com/786343 @@ -2155,10 +2172,26 @@ void Textfield::PaintTextAndCursor(gfx::Canvas* canvas) { render_text->display_rect(), placeholder_text_draw_flags); } - render_text->Draw(canvas); + if (drop_cursor_visible_ && !IsDropCursorForInsertion()) { + // Draw as selected to mark the entire text that will be replaced by drop. + // TODO(http://crbug.com/853678): Replace this state push/pop with a clean + // call to a finer grained rendering API when one becomes available. These + // changes are only applied because RenderText is so stateful and subtle + // (e.g. focus is required for the selection to be rendered as selected). + const gfx::SelectionModel sm = render_text->selection_model(); + const bool focused = render_text->focused(); + render_text->SelectAll(true); + render_text->set_focused(true); + render_text->Draw(canvas); + render_text->set_focused(focused); + render_text->SetSelection(sm); + } else { + // Draw text as normal + render_text->Draw(canvas); + } - // Draw the detached drop cursor that marks where the text will be dropped. - if (drop_cursor_visible_) { + if (drop_cursor_visible_ && IsDropCursorForInsertion()) { + // Draw a drop cursor that marks where the text will be dropped/inserted. canvas->FillRect(render_text->GetCursorBounds(drop_cursor_position_, true), GetTextColor()); } @@ -2177,15 +2210,6 @@ void Textfield::OnCaretBoundsChanged() { if (touch_selection_controller_) touch_selection_controller_->SelectionChanged(); -#if defined(OS_MACOSX) - // On Mac, the context menu contains a look up item which displays the - // selected text. As such, the menu needs to be updated if the selection has - // changed. Be careful to reset the MenuRunner first so it doesn't reference - // the old model. - context_menu_runner_.reset(); - context_menu_contents_.reset(); -#endif - // Screen reader users don't expect notifications about unfocused textfields. if (HasFocus()) NotifyAccessibilityEvent(ax::mojom::Event::kTextSelectionChanged, true); diff --git a/chromium/ui/views/controls/textfield/textfield.h b/chromium/ui/views/controls/textfield/textfield.h index 3df902cef6d..314bde9feff 100644 --- a/chromium/ui/views/controls/textfield/textfield.h +++ b/chromium/ui/views/controls/textfield/textfield.h @@ -197,7 +197,7 @@ class VIEWS_EXPORT Textfield : public View, void SetHorizontalAlignment(gfx::HorizontalAlignment alignment); // Displays a virtual keyboard or alternate input view if enabled. - void ShowImeIfNeeded(); + void ShowVirtualKeyboardIfEnabled(); // Returns whether or not an IME is composing text. bool IsIMEComposing() const; @@ -359,7 +359,7 @@ class VIEWS_EXPORT Textfield : public View, void EnsureCaretNotInRect(const gfx::Rect& rect) override; bool IsTextEditCommandEnabled(ui::TextEditCommand command) const override; void SetTextEditCommandForNextKeyEvent(ui::TextEditCommand command) override; - const std::string& GetClientSourceInfo() const override; + ukm::SourceId GetClientSourceForMetrics() const override; bool ShouldDoLearning() override; protected: @@ -383,6 +383,15 @@ class VIEWS_EXPORT Textfield : public View, // This is harmless if there is not a currently double-clicked word. void OffsetDoubleClickWord(int offset); + // Returns true if the drop cursor is for insertion at a target text location, + // the standard behavior/style. Returns false when drop will do something + // else (like replace the text entirely). + virtual bool IsDropCursorForInsertion() const; + + // Returns true if the placeholder text should be shown. Subclasses may + // override this to customize when the placeholder text is shown. + virtual bool ShouldShowPlaceholderText() const; + private: friend class TextfieldTestApi; diff --git a/chromium/ui/views/controls/textfield/textfield_unittest.cc b/chromium/ui/views/controls/textfield/textfield_unittest.cc index 12e9a987863..6ccde824c4d 100644 --- a/chromium/ui/views/controls/textfield/textfield_unittest.cc +++ b/chromium/ui/views/controls/textfield/textfield_unittest.cc @@ -96,7 +96,7 @@ class MockInputMethod : public ui::InputMethodBase { void OnCaretBoundsChanged(const ui::TextInputClient* client) override {} void CancelComposition(const ui::TextInputClient* client) override; bool IsCandidatePopupOpen() const override; - void ShowImeIfNeeded() override {} + void ShowVirtualKeyboardIfEnabled() override {} bool untranslated_ime_message_called() const { return untranslated_ime_message_called_; @@ -698,14 +698,6 @@ class TextfieldTest : public ViewsTestBase, public TextfieldController { textfield_->GetSelectedRange().length() == text.length(); int menu_index = 0; -#if defined(OS_MACOSX) - // On Mac, the Look Up item should appear at the top of the menu if the - // textfield has a selection. - if (textfield_has_selection) { - EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* LOOK UP */)); - EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); - } -#endif if (ui::IsEmojiPanelSupported()) { EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* EMOJI */)); EXPECT_TRUE(menu->IsEnabledAt(menu_index++ /* Separator */)); @@ -3550,6 +3542,11 @@ TEST_F(TextfieldTest, LookUpItemUpdate) { EXPECT_EQ(context_menu->GetLabelAt(0), l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, kTextOne)); +#if !defined(OS_MACOSX) + // Mac context menus don't behave this way: it's not possible to update the + // text while the menu is still "open", but also the selection can't change + // while the menu is open (because the user can't interact with the rest of + // the app). const base::string16 kTextTwo = ASCIIToUTF16("rail"); textfield_->SetText(kTextTwo); textfield_->SelectAll(false); @@ -3559,6 +3556,7 @@ TEST_F(TextfieldTest, LookUpItemUpdate) { EXPECT_GT(context_menu->GetItemCount(), 0); EXPECT_EQ(context_menu->GetLabelAt(0), l10n_util::GetStringFUTF16(IDS_CONTENT_CONTEXT_LOOK_UP, kTextTwo)); +#endif } // Tests to see if the look up item is hidden for password fields. diff --git a/chromium/ui/views/controls/views_text_services_context_menu.cc b/chromium/ui/views/controls/views_text_services_context_menu.cc index 03a3a7b6e61..5e1559e61bc 100644 --- a/chromium/ui/views/controls/views_text_services_context_menu.cc +++ b/chromium/ui/views/controls/views_text_services_context_menu.cc @@ -25,4 +25,4 @@ bool ViewsTextServicesContextMenu::IsTextDirectionCheckedForTesting( return false; } -} // namespace views
\ No newline at end of file +} // namespace views 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 e5fdea933b5..eae69906422 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 @@ -4,6 +4,7 @@ #include "ui/views/controls/views_text_services_context_menu_base.h" +#include "base/metrics/histogram_macros.h" #include "ui/base/emoji/emoji_panel_helper.h" #include "ui/base/models/simple_menu_model.h" #include "ui/resources/grit/ui_resources.h" @@ -12,6 +13,9 @@ namespace views { +const char kViewsTextServicesContextMenuHistogram[] = + "ViewsTextServicesContextMenu.Used"; + ViewsTextServicesContextMenuBase::ViewsTextServicesContextMenuBase( ui::SimpleMenuModel* menu, Textfield* client) @@ -21,7 +25,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, IDS_CONTENT_CONTEXT_EMOJI, + menu->InsertItemWithStringIdAt(0, static_cast<int>(Command::kEmoji), IDS_CONTENT_CONTEXT_EMOJI); } } @@ -29,7 +33,7 @@ ViewsTextServicesContextMenuBase::ViewsTextServicesContextMenuBase( ViewsTextServicesContextMenuBase::~ViewsTextServicesContextMenuBase() {} bool ViewsTextServicesContextMenuBase::SupportsCommand(int command_id) const { - return command_id == IDS_CONTENT_CONTEXT_EMOJI; + return command_id == static_cast<int>(Command::kEmoji); } bool ViewsTextServicesContextMenuBase::IsCommandIdChecked( @@ -39,15 +43,18 @@ bool ViewsTextServicesContextMenuBase::IsCommandIdChecked( bool ViewsTextServicesContextMenuBase::IsCommandIdEnabled( int command_id) const { - if (command_id == IDS_CONTENT_CONTEXT_EMOJI) + if (command_id == static_cast<int>(Command::kEmoji)) return true; return false; } void ViewsTextServicesContextMenuBase::ExecuteCommand(int command_id) { - if (command_id == IDS_CONTENT_CONTEXT_EMOJI) + if (command_id == static_cast<int>(Command::kEmoji)) { ui::ShowEmojiPanel(); + UMA_HISTOGRAM_ENUMERATION(kViewsTextServicesContextMenuHistogram, + Command::kEmoji); + } } -} // namespace views
\ No newline at end of file +} // namespace views 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 7c39439b83a..08e5a7ad0c0 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 @@ -30,6 +30,9 @@ 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 5fb08006306..ece41dd5f53 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 @@ -13,8 +13,8 @@ namespace views { void UnhandledKeyboardEventHandler::HandleNativeKeyboardEvent( gfx::NativeEvent event, FocusManager* focus_manager) { - [base::mac::ObjCCastStrict<NativeWidgetMacNSWindow>([event window]) - redispatchKeyEvent:event]; + [[base::mac::ObjCCastStrict<NativeWidgetMacNSWindow>([event window]) + commandDispatcher] redispatchKeyEvent:event]; } } // 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 323d9f895f8..90a9612cf61 100644 --- a/chromium/ui/views/controls/webview/web_dialog_view.cc +++ b/chromium/ui/views/controls/webview/web_dialog_view.cc @@ -271,10 +271,11 @@ bool WebDialogView::HandleContextMenu( //////////////////////////////////////////////////////////////////////////////// // content::WebContentsDelegate implementation: -void WebDialogView::MoveContents(WebContents* source, const gfx::Rect& pos) { +void WebDialogView::SetContentsBounds(WebContents* source, + const gfx::Rect& bounds) { // The contained web page wishes to resize itself. We let it do this because // if it's a dialog we know about, we trust it not to be mean to the user. - GetWidget()->SetBounds(pos); + GetWidget()->SetBounds(bounds); } // A simplified version of BrowserView::HandleKeyboardEvent(). diff --git a/chromium/ui/views/controls/webview/web_dialog_view.h b/chromium/ui/views/controls/webview/web_dialog_view.h index bfba06181e7..0fd7bc03288 100644 --- a/chromium/ui/views/controls/webview/web_dialog_view.h +++ b/chromium/ui/views/controls/webview/web_dialog_view.h @@ -94,8 +94,8 @@ class WEBVIEW_EXPORT WebDialogView : public views::ClientView, bool HandleContextMenu(const content::ContextMenuParams& params) override; // Overridden from content::WebContentsDelegate: - void MoveContents(content::WebContents* source, - const gfx::Rect& pos) override; + void SetContentsBounds(content::WebContents* source, + const gfx::Rect& bounds) override; void HandleKeyboardEvent( content::WebContents* source, const content::NativeWebKeyboardEvent& event) override; diff --git a/chromium/ui/views/controls/webview/webview.cc b/chromium/ui/views/controls/webview/webview.cc index 161185af3b8..74ddbc9b3a4 100644 --- a/chromium/ui/views/controls/webview/webview.cc +++ b/chromium/ui/views/controls/webview/webview.cc @@ -91,6 +91,8 @@ void WebView::SetWebContents(content::WebContents* replacement) { } AttachWebContents(); NotifyAccessibilityWebContentsChanged(); + + MaybeEnableAutoResize(); } void WebView::SetEmbedFullscreenWidgetMode(bool enable) { @@ -109,6 +111,14 @@ void WebView::SetFastResize(bool fast_resize) { holder_->set_fast_resize(fast_resize); } +void WebView::EnableSizingFromWebContents(const gfx::Size& min_size, + const gfx::Size& max_size) { + DCHECK(!max_size.IsEmpty()); + min_size_ = min_size; + max_size_ = max_size; + MaybeEnableAutoResize(); +} + void WebView::SetResizeBackgroundColor(SkColor resize_background_color) { holder_->set_resize_background_color(resize_background_color); } @@ -271,6 +281,10 @@ bool WebView::EmbedsFullscreenWidget() const { //////////////////////////////////////////////////////////////////////////////// // WebView, content::WebContentsObserver implementation: +void WebView::RenderViewCreated(content::RenderViewHost* render_view_host) { + MaybeEnableAutoResize(); +} + void WebView::RenderViewReady() { UpdateCrashedOverlayView(); NotifyAccessibilityWebContentsChanged(); @@ -283,6 +297,8 @@ void WebView::RenderViewDeleted(content::RenderViewHost* render_view_host) { void WebView::RenderViewHostChanged(content::RenderViewHost* old_host, content::RenderViewHost* new_host) { + MaybeEnableAutoResize(); + if (HasFocus()) OnFocus(); NotifyAccessibilityWebContentsChanged(); @@ -326,6 +342,14 @@ void WebView::RenderProcessGone(base::TerminationStatus status) { NotifyAccessibilityWebContentsChanged(); } +void WebView::ResizeDueToAutoResize(content::WebContents* source, + const gfx::Size& new_size) { + if (source != web_contents()) + return; + + SetPreferredSize(new_size); +} + //////////////////////////////////////////////////////////////////////////////// // WebView, private: @@ -377,14 +401,7 @@ void WebView::ReattachForFullscreenChange(bool enter_fullscreen) { } void WebView::UpdateCrashedOverlayView() { - // TODO(dmazzoni): Fix WebContents::IsCrashed() so we can call that - // instead of checking termination status codes. - if (web_contents() && - web_contents()->GetCrashedStatus() != - base::TERMINATION_STATUS_NORMAL_TERMINATION && - web_contents()->GetCrashedStatus() != - base::TERMINATION_STATUS_STILL_RUNNING && - crashed_overlay_view_) { + if (web_contents() && web_contents()->IsCrashed() && crashed_overlay_view_) { SetFocusBehavior(FocusBehavior::NEVER); crashed_overlay_view_->SetVisible(true); return; @@ -418,4 +435,15 @@ std::unique_ptr<content::WebContents> WebView::CreateWebContents( return contents; } +void WebView::MaybeEnableAutoResize() { + if (max_size_.IsEmpty() || !web_contents() || + !web_contents()->GetRenderWidgetHostView()) { + return; + } + + content::RenderWidgetHostView* render_widget_host_view = + web_contents()->GetRenderWidgetHostView(); + render_widget_host_view->EnableAutoResize(min_size_, max_size_); +} + } // namespace views diff --git a/chromium/ui/views/controls/webview/webview.h b/chromium/ui/views/controls/webview/webview.h index 87d9a1e7259..994520b83b1 100644 --- a/chromium/ui/views/controls/webview/webview.h +++ b/chromium/ui/views/controls/webview/webview.h @@ -76,6 +76,11 @@ class WEBVIEW_EXPORT WebView : public View, // resizing performance during interactive resizes and animations. void SetFastResize(bool fast_resize); + // If enabled, this will make the WebView's preferred size dependent on the + // WebContents' size. + void EnableSizingFromWebContents(const gfx::Size& min_size, + const gfx::Size& max_size); + // Set the background color to use while resizing with a clip. This is white // by default. void SetResizeBackgroundColor(SkColor resize_background_color); @@ -94,6 +99,10 @@ class WEBVIEW_EXPORT WebView : public View, // Overridden from View: const char* GetClassName() const override; + // Overridden from content::WebContentsDelegate: + void ResizeDueToAutoResize(content::WebContents* source, + const gfx::Size& new_size) override; + NativeViewHost* holder() { return holder_; } using WebContentsCreator = base::RepeatingCallback<std::unique_ptr<content::WebContents>( @@ -123,6 +132,9 @@ class WEBVIEW_EXPORT WebView : public View, virtual void OnLetterboxingChanged() {} bool is_letterboxing() const { return is_letterboxing_; } + const gfx::Size& min_size() const { return min_size_; } + const gfx::Size& max_size() const { return max_size_; } + // Overridden from View: void OnBoundsChanged(const gfx::Rect& previous_bounds) override; void ViewHierarchyChanged( @@ -138,6 +150,7 @@ class WEBVIEW_EXPORT WebView : public View, bool EmbedsFullscreenWidget() const override; // Overridden from content::WebContentsObserver: + void RenderViewCreated(content::RenderViewHost* render_view_host) override; void RenderViewReady() override; void RenderViewDeleted(content::RenderViewHost* render_view_host) override; void RenderViewHostChanged(content::RenderViewHost* old_host, @@ -167,6 +180,11 @@ class WEBVIEW_EXPORT WebView : public View, void UpdateCrashedOverlayView(); void NotifyAccessibilityWebContentsChanged(); + // Registers for ResizeDueToAutoResize() notifications from the + // RenderWidgetHostView whenever it is created or changes, if + // EnableSizingFromWebContents() has been called. + void MaybeEnableAutoResize(); + // Create a regular or test web contents (based on whether we're running // in a unit test or not). std::unique_ptr<content::WebContents> CreateWebContents( @@ -188,6 +206,11 @@ class WEBVIEW_EXPORT WebView : public View, bool allow_accelerators_; View* crashed_overlay_view_ = nullptr; + // Minimum and maximum sizes to determine WebView bounds for auto-resizing. + // Empty if auto resize is not enabled. + gfx::Size min_size_; + gfx::Size max_size_; + DISALLOW_COPY_AND_ASSIGN(WebView); }; diff --git a/chromium/ui/views/examples/BUILD.gn b/chromium/ui/views/examples/BUILD.gn index 661329c2482..c757e20b7ff 100644 --- a/chromium/ui/views/examples/BUILD.gn +++ b/chromium/ui/views/examples/BUILD.gn @@ -111,9 +111,9 @@ executable("views_examples_exe") { "//base", "//base:i18n", "//base/test:test_support", - "//build/config:exe_and_shlib_deps", "//build/win:default_exe_manifest", "//components/viz/host", + "//components/viz/service", "//ui/base", "//ui/compositor", "//ui/compositor:test_support", @@ -172,7 +172,6 @@ executable("views_examples_with_content_exe") { ":copy_content_resources", ":views_examples_with_content_lib", "//base", - "//build/config:exe_and_shlib_deps", "//build/win:default_exe_manifest", "//content", "//content:sandbox_helper_win", diff --git a/chromium/ui/views/examples/DEPS b/chromium/ui/views/examples/DEPS index c55d1e8778f..38ff08c5007 100644 --- a/chromium/ui/views/examples/DEPS +++ b/chromium/ui/views/examples/DEPS @@ -1,5 +1,6 @@ include_rules = [ "+components/viz/host", + "+components/viz/service", # In-process viz service. "+content/public", "+content/shell", "+sandbox", diff --git a/chromium/ui/views/examples/box_layout_example.cc b/chromium/ui/views/examples/box_layout_example.cc index 2c4fba15fb2..a17fd295dee 100644 --- a/chromium/ui/views/examples/box_layout_example.cc +++ b/chromium/ui/views/examples/box_layout_example.cc @@ -331,10 +331,10 @@ void BoxLayoutExample::CreateExampleView(View* container) { border_insets_[i] = CreateRawTextfield(horizontal_pos, border_insets_[0]->y(), true); - collapse_margins_ = new Checkbox(base::ASCIIToUTF16("Collapse margins")); + collapse_margins_ = + new Checkbox(base::ASCIIToUTF16("Collapse margins"), this); collapse_margins_->SetPosition(gfx::Point(kPadding, vertical_pos)); collapse_margins_->SizeToPreferredSize(); - collapse_margins_->set_listener(this); control_panel_->AddChildView(collapse_margins_); UpdateLayoutManager(); diff --git a/chromium/ui/views/examples/checkbox_example.cc b/chromium/ui/views/examples/checkbox_example.cc index b28a6b66526..c5a5dc49f94 100644 --- a/chromium/ui/views/examples/checkbox_example.cc +++ b/chromium/ui/views/examples/checkbox_example.cc @@ -20,8 +20,7 @@ CheckboxExample::~CheckboxExample() { } void CheckboxExample::CreateExampleView(View* container) { - button_ = new Checkbox(base::ASCIIToUTF16("Checkbox")); - button_->set_listener(this); + button_ = new Checkbox(base::ASCIIToUTF16("Checkbox"), this); container->SetLayoutManager(std::make_unique<FillLayout>()); container->AddChildView(button_); } diff --git a/chromium/ui/views/examples/dialog_example.cc b/chromium/ui/views/examples/dialog_example.cc index 48426144dfa..e10f2fadd5b 100644 --- a/chromium/ui/views/examples/dialog_example.cc +++ b/chromium/ui/views/examples/dialog_example.cc @@ -205,8 +205,7 @@ void DialogExample::StartTextfieldRow(GridLayout* layout, } void DialogExample::AddCheckbox(GridLayout* layout, Checkbox** member) { - Checkbox* checkbox = new Checkbox(base::string16()); - checkbox->set_listener(this); + Checkbox* checkbox = new Checkbox(base::string16(), this); checkbox->SetChecked(true); layout->AddView(checkbox); *member = checkbox; diff --git a/chromium/ui/views/examples/examples_main.cc b/chromium/ui/views/examples/examples_main.cc index 9ddb537f8c5..4dd22a71197 100644 --- a/chromium/ui/views/examples/examples_main.cc +++ b/chromium/ui/views/examples/examples_main.cc @@ -17,6 +17,8 @@ #include "base/test/test_discardable_memory_allocator.h" #include "build/build_config.h" #include "components/viz/host/host_frame_sink_manager.h" +#include "components/viz/service/display_embedder/server_shared_bitmap_manager.h" +#include "components/viz/service/frame_sinks/frame_sink_manager_impl.h" #include "ui/base/ime/input_method_initializer.h" #include "ui/base/material_design/material_design_controller.h" #include "ui/base/resource/resource_bundle.h" @@ -68,7 +70,8 @@ int main(int argc, char** argv) { // The ContextFactory must exist before any Compositors are created. viz::HostFrameSinkManager host_frame_sink_manager; - viz::FrameSinkManagerImpl frame_sink_manager; + viz::ServerSharedBitmapManager shared_bitmap_manager; + viz::FrameSinkManagerImpl frame_sink_manager(&shared_bitmap_manager); host_frame_sink_manager.SetLocalManager(&frame_sink_manager); frame_sink_manager.SetLocalClient(&host_frame_sink_manager); auto context_factory = std::make_unique<ui::InProcessContextFactory>( diff --git a/chromium/ui/views/examples/label_example.cc b/chromium/ui/views/examples/label_example.cc index 4af92326d72..bdf6644cc79 100644 --- a/chromium/ui/views/examples/label_example.cc +++ b/chromium/ui/views/examples/label_example.cc @@ -200,14 +200,11 @@ void LabelExample::AddCustomLabel(View* container) { column_set->AddColumn(GridLayout::LEADING, GridLayout::LEADING, 0, GridLayout::USE_PREF, 0, 0); layout->StartRow(0, 1); - multiline_ = new Checkbox(base::ASCIIToUTF16("Multiline")); - multiline_->set_listener(this); + multiline_ = new Checkbox(base::ASCIIToUTF16("Multiline"), this); layout->AddView(multiline_); - shadows_ = new Checkbox(base::ASCIIToUTF16("Shadows")); - shadows_->set_listener(this); + shadows_ = new Checkbox(base::ASCIIToUTF16("Shadows"), this); layout->AddView(shadows_); - selectable_ = new Checkbox(base::ASCIIToUTF16("Selectable")); - selectable_->set_listener(this); + selectable_ = new Checkbox(base::ASCIIToUTF16("Selectable"), this); layout->AddView(selectable_); layout->AddPaddingRow(0, 8); diff --git a/chromium/ui/views/examples/multiline_example.cc b/chromium/ui/views/examples/multiline_example.cc index 4e5225bd99a..7ecd6834d67 100644 --- a/chromium/ui/views/examples/multiline_example.cc +++ b/chromium/ui/views/examples/multiline_example.cc @@ -145,14 +145,12 @@ void MultilineExample::CreateExampleView(View* container) { label_->SetMultiLine(true); label_->SetBorder(CreateSolidBorder(2, SK_ColorCYAN)); - label_checkbox_ = new Checkbox(ASCIIToUTF16("views::Label:")); + label_checkbox_ = new Checkbox(ASCIIToUTF16("views::Label:"), this); label_checkbox_->SetChecked(true); - label_checkbox_->set_listener(this); label_checkbox_->set_request_focus_on_press(false); - elision_checkbox_ = new Checkbox(ASCIIToUTF16("elide text?")); + elision_checkbox_ = new Checkbox(ASCIIToUTF16("elide text?"), this); elision_checkbox_->SetChecked(false); - elision_checkbox_->set_listener(this); elision_checkbox_->set_request_focus_on_press(false); textfield_ = new Textfield(); diff --git a/chromium/ui/views/examples/radio_button_example.cc b/chromium/ui/views/examples/radio_button_example.cc index 4f6e05b475d..65826da1509 100644 --- a/chromium/ui/views/examples/radio_button_example.cc +++ b/chromium/ui/views/examples/radio_button_example.cc @@ -17,10 +17,7 @@ namespace views { namespace examples { -RadioButtonExample::RadioButtonExample() - : ExampleBase("Radio Button"), - count_(0) { -} +RadioButtonExample::RadioButtonExample() : ExampleBase("Radio Button") {} RadioButtonExample::~RadioButtonExample() { } @@ -35,7 +32,6 @@ void RadioButtonExample::CreateExampleView(View* container) { base::UTF8ToUTF16(base::StringPrintf( "Radio %d in group %d", static_cast<int>(i) + 1, group)), group); - radio_buttons_[i]->set_listener(this); } GridLayout* layout = container->SetLayoutManager( @@ -63,8 +59,6 @@ void RadioButtonExample::ButtonPressed(Button* sender, const ui::Event& event) { BoolToOnOff(radio_buttons_[0]->checked()), BoolToOnOff(radio_buttons_[1]->checked()), BoolToOnOff(radio_buttons_[2]->checked())); - } else { - PrintStatus("Pressed! count:%d", ++count_); } } diff --git a/chromium/ui/views/examples/radio_button_example.h b/chromium/ui/views/examples/radio_button_example.h index 7dfd81c87ef..0cb2edf0934 100644 --- a/chromium/ui/views/examples/radio_button_example.h +++ b/chromium/ui/views/examples/radio_button_example.h @@ -38,9 +38,6 @@ class VIEWS_EXAMPLES_EXPORT RadioButtonExample : public ExampleBase, LabelButton* select_; LabelButton* status_; - // The number of times the button is pressed. - int count_; - DISALLOW_COPY_AND_ASSIGN(RadioButtonExample); }; diff --git a/chromium/ui/views/examples/table_example.cc b/chromium/ui/views/examples/table_example.cc index 1fd544f7d78..68b17a21d86 100644 --- a/chromium/ui/views/examples/table_example.cc +++ b/chromium/ui/views/examples/table_example.cc @@ -41,22 +41,18 @@ TableExample::~TableExample() { } void TableExample::CreateExampleView(View* container) { - column1_visible_checkbox_ = new Checkbox( - ASCIIToUTF16("Fruit column visible")); + column1_visible_checkbox_ = + new Checkbox(ASCIIToUTF16("Fruit column visible"), this); column1_visible_checkbox_->SetChecked(true); - column1_visible_checkbox_->set_listener(this); - column2_visible_checkbox_ = new Checkbox( - ASCIIToUTF16("Color column visible")); + column2_visible_checkbox_ = + new Checkbox(ASCIIToUTF16("Color column visible"), this); column2_visible_checkbox_->SetChecked(true); - column2_visible_checkbox_->set_listener(this); - column3_visible_checkbox_ = new Checkbox( - ASCIIToUTF16("Origin column visible")); + column3_visible_checkbox_ = + new Checkbox(ASCIIToUTF16("Origin column visible"), this); column3_visible_checkbox_->SetChecked(true); - column3_visible_checkbox_->set_listener(this); - column4_visible_checkbox_ = new Checkbox( - ASCIIToUTF16("Price column visible")); + column4_visible_checkbox_ = + new Checkbox(ASCIIToUTF16("Price column visible"), this); column4_visible_checkbox_->SetChecked(true); - column4_visible_checkbox_->set_listener(this); GridLayout* layout = container->SetLayoutManager( std::make_unique<views::GridLayout>(container)); diff --git a/chromium/ui/views/examples/text_example.cc b/chromium/ui/views/examples/text_example.cc index 17439cc8bc6..11db9591b83 100644 --- a/chromium/ui/views/examples/text_example.cc +++ b/chromium/ui/views/examples/text_example.cc @@ -135,8 +135,7 @@ TextExample::~TextExample() { } Checkbox* TextExample::AddCheckbox(GridLayout* layout, const char* name) { - Checkbox* checkbox = new Checkbox(base::ASCIIToUTF16(name)); - checkbox->set_listener(this); + Checkbox* checkbox = new Checkbox(base::ASCIIToUTF16(name), this); layout->AddView(checkbox); return checkbox; } diff --git a/chromium/ui/views/focus/focus_manager.cc b/chromium/ui/views/focus/focus_manager.cc index 6847bae9d64..faded394493 100644 --- a/chromium/ui/views/focus/focus_manager.cc +++ b/chromium/ui/views/focus/focus_manager.cc @@ -133,6 +133,13 @@ void FocusManager::AdvanceFocus(bool reverse) { DCHECK(v->GetWidget()); v->GetWidget()->GetFocusManager()->SetFocusedViewWithReason( v, kReasonFocusTraversal); + + // When moving focus from a child widget to a top-level widget, + // the top-level widget may report IsActive()==true because it's + // active even though it isn't focused. Explicitly activate the + // widget to ensure that case is handled. + if (v->GetWidget()->GetFocusManager() != this) + v->GetWidget()->Activate(); } } @@ -296,7 +303,8 @@ View* FocusManager::GetNextFocusableView(View* original_starting_view, // Easy, just clear the selection and press tab again. // By calling with nullptr as the starting view, we'll start from either // the starting views widget or |widget_|. - Widget* widget = original_starting_view->GetWidget(); + Widget* widget = starting_view ? starting_view->GetWidget() + : original_starting_view->GetWidget(); if (widget->widget_delegate()->ShouldAdvanceFocusToTopLevelWidget()) widget = widget_; return GetNextFocusableView(nullptr, widget, reverse, true); diff --git a/chromium/ui/views/focus/focus_manager_unittest.cc b/chromium/ui/views/focus/focus_manager_unittest.cc index 3c1278fe2f3..5e7c7a6778e 100644 --- a/chromium/ui/views/focus/focus_manager_unittest.cc +++ b/chromium/ui/views/focus/focus_manager_unittest.cc @@ -14,6 +14,7 @@ #include "base/strings/utf_string_conversions.h" #include "base/test/icu_test_util.h" #include "ui/base/accelerators/accelerator.h" +#include "ui/base/accelerators/test_accelerator_target.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/views/accessible_pane_view.h" #include "ui/views/bubble/bubble_dialog_delegate.h" @@ -21,10 +22,17 @@ #include "ui/views/focus/focus_manager_factory.h" #include "ui/views/focus/widget_focus_manager.h" #include "ui/views/test/focus_manager_test.h" +#include "ui/views/test/native_widget_factory.h" +#include "ui/views/test/test_platform_native_widget.h" #include "ui/views/test/widget_test.h" #include "ui/views/view_properties.h" #include "ui/views/widget/widget.h" +#if defined(USE_AURA) +#include "ui/aura/client/focus_client.h" +#include "ui/views/widget/native_widget_aura.h" +#endif // USE_AURA + namespace views { enum FocusTestEventType { ON_FOCUS = 0, ON_BLUR }; @@ -163,44 +171,13 @@ TEST_F(FocusManagerTest, WidgetFocusChangeListener) { EXPECT_EQ(native_view2, widget_listener.focus_changes()[1]); } -// Counts accelerator calls. -class TestAcceleratorTarget : public ui::AcceleratorTarget { - public: - explicit TestAcceleratorTarget(bool process_accelerator) - : accelerator_count_(0), - process_accelerator_(process_accelerator), - can_handle_accelerators_(true) {} - - bool AcceleratorPressed(const ui::Accelerator& accelerator) override { - ++accelerator_count_; - return process_accelerator_; - } - - bool CanHandleAccelerators() const override { - return can_handle_accelerators_; - } - - int accelerator_count() const { return accelerator_count_; } - - void set_can_handle_accelerators(bool can_handle_accelerators) { - can_handle_accelerators_ = can_handle_accelerators; - } - - private: - int accelerator_count_; // number of times that the accelerator is activated - bool process_accelerator_; // return value of AcceleratorPressed - bool can_handle_accelerators_; // return value of CanHandleAccelerators - - DISALLOW_COPY_AND_ASSIGN(TestAcceleratorTarget); -}; - TEST_F(FocusManagerTest, CallsNormalAcceleratorTarget) { FocusManager* focus_manager = GetFocusManager(); ui::Accelerator return_accelerator(ui::VKEY_RETURN, ui::EF_NONE); ui::Accelerator escape_accelerator(ui::VKEY_ESCAPE, ui::EF_NONE); - TestAcceleratorTarget return_target(true); - TestAcceleratorTarget escape_target(true); + ui::TestAcceleratorTarget return_target(true); + ui::TestAcceleratorTarget escape_target(true); EXPECT_EQ(return_target.accelerator_count(), 0); EXPECT_EQ(escape_target.accelerator_count(), 0); @@ -223,7 +200,7 @@ TEST_F(FocusManagerTest, CallsNormalAcceleratorTarget) { EXPECT_EQ(escape_target.accelerator_count(), 1); // Register another target for the return key. - TestAcceleratorTarget return_target2(true); + ui::TestAcceleratorTarget return_target2(true); EXPECT_EQ(return_target2.accelerator_count(), 0); focus_manager->RegisterAccelerator(return_accelerator, ui::AcceleratorManager::kNormalPriority, @@ -235,7 +212,7 @@ TEST_F(FocusManagerTest, CallsNormalAcceleratorTarget) { EXPECT_EQ(return_target2.accelerator_count(), 1); // Register a target that does not process the accelerator event. - TestAcceleratorTarget return_target3(false); + ui::TestAcceleratorTarget return_target3(false); EXPECT_EQ(return_target3.accelerator_count(), 0); focus_manager->RegisterAccelerator(return_accelerator, ui::AcceleratorManager::kNormalPriority, @@ -275,8 +252,8 @@ TEST_F(FocusManagerTest, HighPriorityHandlers) { FocusManager* focus_manager = GetFocusManager(); ui::Accelerator escape_accelerator(ui::VKEY_ESCAPE, ui::EF_NONE); - TestAcceleratorTarget escape_target_high(true); - TestAcceleratorTarget escape_target_normal(true); + ui::TestAcceleratorTarget escape_target_high(true); + ui::TestAcceleratorTarget escape_target_normal(true); EXPECT_EQ(escape_target_high.accelerator_count(), 0); EXPECT_EQ(escape_target_normal.accelerator_count(), 0); EXPECT_FALSE(focus_manager->HasPriorityHandler(escape_accelerator)); @@ -350,8 +327,8 @@ TEST_F(FocusManagerTest, CallsEnabledAcceleratorTargetsOnly) { FocusManager* focus_manager = GetFocusManager(); ui::Accelerator return_accelerator(ui::VKEY_RETURN, ui::EF_NONE); - TestAcceleratorTarget return_target1(true); - TestAcceleratorTarget return_target2(true); + ui::TestAcceleratorTarget return_target1(true); + ui::TestAcceleratorTarget return_target2(true); focus_manager->RegisterAccelerator(return_accelerator, ui::AcceleratorManager::kNormalPriority, @@ -385,28 +362,21 @@ TEST_F(FocusManagerTest, CallsEnabledAcceleratorTargetsOnly) { } // Unregisters itself when its accelerator is invoked. -class SelfUnregisteringAcceleratorTarget : public ui::AcceleratorTarget { +class SelfUnregisteringAcceleratorTarget : public ui::TestAcceleratorTarget { public: - SelfUnregisteringAcceleratorTarget(ui::Accelerator accelerator, + SelfUnregisteringAcceleratorTarget(const ui::Accelerator& accelerator, FocusManager* focus_manager) - : accelerator_(accelerator), - focus_manager_(focus_manager), - accelerator_count_(0) {} + : accelerator_(accelerator), focus_manager_(focus_manager) {} + // ui::TestAcceleratorTarget: bool AcceleratorPressed(const ui::Accelerator& accelerator) override { - ++accelerator_count_; focus_manager_->UnregisterAccelerator(accelerator, this); - return true; + return ui::TestAcceleratorTarget::AcceleratorPressed(accelerator); } - bool CanHandleAccelerators() const override { return true; } - - int accelerator_count() const { return accelerator_count_; } - private: ui::Accelerator accelerator_; FocusManager* focus_manager_; - int accelerator_count_; DISALLOW_COPY_AND_ASSIGN(SelfUnregisteringAcceleratorTarget); }; @@ -433,7 +403,7 @@ TEST_F(FocusManagerTest, CallsSelfDeletingAcceleratorTarget) { TEST_F(FocusManagerTest, SuspendAccelerators) { const ui::KeyEvent event(ui::ET_KEY_PRESSED, ui::VKEY_RETURN, ui::EF_NONE); ui::Accelerator accelerator(event.key_code(), event.flags()); - TestAcceleratorTarget target(true); + ui::TestAcceleratorTarget target(true); FocusManager* focus_manager = GetFocusManager(); focus_manager->RegisterAccelerator( accelerator, ui::AcceleratorManager::kNormalPriority, &target); @@ -869,10 +839,28 @@ class TestBubbleDialogDelegateView : public BubbleDialogDelegateView { : BubbleDialogDelegateView(anchor, BubbleBorder::NONE) {} ~TestBubbleDialogDelegateView() override {} + // If this is called, the bubble will be forced to use a NativeWidgetAura. + // If not set, it might get a DesktopNativeWidgetAura depending on the + // platform and other factors. + void UseNativeWidgetAura() { use_native_widget_aura_ = true; } + // ui::DialogModel override. int GetDialogButtons() const override { return 0; } + void OnBeforeBubbleWidgetInit(Widget::InitParams* params, + Widget* widget) const override { +#if defined(USE_AURA) + if (use_native_widget_aura_) { + params->native_widget = + new test::TestPlatformNativeWidget<NativeWidgetAura>(widget, false, + nullptr); + } +#endif // USE_AURA + } + private: + bool use_native_widget_aura_ = false; + DISALLOW_COPY_AND_ASSIGN(TestBubbleDialogDelegateView); }; @@ -1047,4 +1035,71 @@ TEST_F(FocusManagerTest, AnchoredDialogOnContainerView) { EXPECT_TRUE(parent3->HasFocus()); } +// Desktop native widget Aura tests are for non Chrome OS platforms. +// This test is specifically for the permutation where the main +// widget is a DesktopNativeWidgetAura and the bubble is a +// NativeWidgetAura. When focus moves back from the bubble to the +// parent widget, ensure that the DNWA's aura window is focused. +#if defined(USE_AURA) && !defined(OS_CHROMEOS) +TEST_F(FocusManagerTest, AnchoredDialogInDesktopNativeWidgetAura) { + Widget widget; + Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.bounds = gfx::Rect(0, 0, 1024, 768); + params.native_widget = + test::CreatePlatformDesktopNativeWidgetImpl(params, &widget, nullptr); + widget.Init(params); + widget.Show(); + widget.Activate(); + + View* parent1 = new View(); + View* parent2 = new View(); + + parent1->SetFocusBehavior(View::FocusBehavior::ALWAYS); + parent2->SetFocusBehavior(View::FocusBehavior::ALWAYS); + + widget.GetRootView()->AddChildView(parent1); + widget.GetRootView()->AddChildView(parent2); + + TestBubbleDialogDelegateView* bubble_delegate = + new TestBubbleDialogDelegateView(parent2); + bubble_delegate->UseNativeWidgetAura(); + test::WidgetTest::WidgetAutoclosePtr bubble_widget( + BubbleDialogDelegateView::CreateBubble(bubble_delegate)); + bubble_delegate->EnableFocusTraversalFromAnchorView(); + View* child = new View(); + child->SetFocusBehavior(View::FocusBehavior::ALWAYS); + bubble_widget->GetRootView()->AddChildView(child); + bubble_delegate->set_close_on_deactivate(false); + bubble_widget->Show(); + + widget.Activate(); + parent1->RequestFocus(); + base::RunLoop().RunUntilIdle(); + + // Initially the outer widget's window is focused. + aura::client::FocusClient* focus_client = + aura::client::GetFocusClient(widget.GetNativeView()); + ASSERT_EQ(widget.GetNativeView(), focus_client->GetFocusedWindow()); + + // Navigate forwards + widget.GetFocusManager()->AdvanceFocus(false); + base::RunLoop().RunUntilIdle(); + EXPECT_TRUE(parent2->HasFocus()); + widget.GetFocusManager()->AdvanceFocus(false); + EXPECT_TRUE(child->HasFocus()); + + // Now the bubble widget's window is focused. + ASSERT_NE(widget.GetNativeView(), focus_client->GetFocusedWindow()); + ASSERT_EQ(bubble_widget->GetNativeView(), focus_client->GetFocusedWindow()); + + // Navigate backwards + bubble_widget->GetFocusManager()->AdvanceFocus(true); + EXPECT_TRUE(parent2->HasFocus()); + + // Finally, the outer widget's window should be focused again. + ASSERT_EQ(widget.GetNativeView(), focus_client->GetFocusedWindow()); +} +#endif // defined(USE_AURA) && !defined(OS_CHROMEOS) + } // namespace views diff --git a/chromium/ui/views/layout/grid_layout.h b/chromium/ui/views/layout/grid_layout.h index 4721adf215f..cd046c70163 100644 --- a/chromium/ui/views/layout/grid_layout.h +++ b/chromium/ui/views/layout/grid_layout.h @@ -24,17 +24,17 @@ // columns->AddColumn(FILL, // Views are horizontally resized to fill column. // FILL, // Views starting in this column are vertically // // resized. -// 1, // This column has a resize weight of 1. +// 1.0, // This column has a resize weight of 1. // USE_PREF, // Use the preferred size of the view. // 0, // Ignored for USE_PREF. // 0); // A minimum width of 0. -// columns->AddPaddingColumn(0, // The padding column is not resizable. -// 10); // And has a width of 10 pixels. -// columns->AddColumn(FILL, FILL, 0, USE_PREF, 0, 0); +// columns->AddPaddingColumn(kFixedSize, // The padding column is not resizable. +// 10); // And has a width of 10 pixels. +// columns->AddColumn(FILL, FILL, kFixedSize, USE_PREF, 0, 0); // Now add the views: // // First start a row. -// layout->StartRow(0, // This row isn't vertically resizable. -// 0); // The column set to use for this row. +// layout->StartRow(kFixedSize, // This row isn't vertically resizable. +// 0); // The column set to use for this row. // layout->AddView(v1); // Notice you need not skip over padding columns, that's done for you. // layout->AddView(v2); @@ -77,6 +77,10 @@ struct ViewState; class VIEWS_EXPORT GridLayout : public LayoutManager { public: + // Use for |resize_percent| or |vertical_resize| when the column or row is not + // resizable. + static constexpr float kFixedSize = 0.f; + // An enumeration of the possible alignments supported by GridLayout. enum Alignment { // Leading equates to left along the horizontal axis, and top along the diff --git a/chromium/ui/views/layout/layout_provider.cc b/chromium/ui/views/layout/layout_provider.cc index 822088d3723..caba8a5228e 100644 --- a/chromium/ui/views/layout/layout_provider.cc +++ b/chromium/ui/views/layout/layout_provider.cc @@ -146,22 +146,28 @@ int LayoutProvider::GetCornerRadiusMetric(EmphasisMetric emphasis_metric, const bool is_touch = ui::MaterialDesignController::IsTouchOptimizedUiEnabled(); switch (emphasis_metric) { + case views::EMPHASIS_NONE: + NOTREACHED(); + return 0; case EMPHASIS_LOW: - return is_touch ? 4 : 2; case EMPHASIS_MEDIUM: - return is_touch ? 8 : 4; + return is_touch ? 4 : 2; case EMPHASIS_HIGH: + return is_touch ? 8 : 4; + case EMPHASIS_MAXIMUM: return is_touch ? std::min(size.width(), size.height()) / 2 : 4; - default: - NOTREACHED(); - return 0; } } int LayoutProvider::GetShadowElevationMetric( EmphasisMetric emphasis_metric) const { - // Just return a value for now. - return 2; + // Return a value similar to the (deprecated) default shadow style for bubbles + // and dialogs. + return 3; +} + +gfx::ShadowValues LayoutProvider::MakeShadowValues(int elevation) const { + return gfx::ShadowValue::MakeMdShadowValues(elevation); } } // namespace views diff --git a/chromium/ui/views/layout/layout_provider.h b/chromium/ui/views/layout/layout_provider.h index c61276edd2b..cf92ab718c9 100644 --- a/chromium/ui/views/layout/layout_provider.h +++ b/chromium/ui/views/layout/layout_provider.h @@ -8,6 +8,7 @@ #include "base/macros.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/size.h" +#include "ui/gfx/shadow_value.h" #include "ui/views/style/typography_provider.h" #include "ui/views/views_export.h" @@ -117,10 +118,12 @@ enum EmphasisMetric { // Use this to indicate low-emphasis interactive elements such as buttons and // text fields. EMPHASIS_LOW, - // Use this for components with medium emphasis, such as tabs or dialogs. + // Use this for components with medium emphasis, such the autofill dropdown. EMPHASIS_MEDIUM, - // High-emphasis components like the omnibox or rich suggestions. + // High-emphasis components, such as tabs or dialogs. EMPHASIS_HIGH, + // Maximum emphasis components like the omnibox or rich suggestions. + EMPHASIS_MAXIMUM, }; class VIEWS_EXPORT LayoutProvider { @@ -170,6 +173,10 @@ class VIEWS_EXPORT LayoutProvider { // Returns the shadow elevation metric for the given emphasis. virtual int GetShadowElevationMetric(EmphasisMetric emphasis_metric) const; + // Creates shadows for the given elevation. Use GetShadowElevationMetric for + // the appropriate elevation. + virtual gfx::ShadowValues MakeShadowValues(int elevation) const; + private: DefaultTypographyProvider typography_provider_; diff --git a/chromium/ui/views/linux_ui/linux_ui.h b/chromium/ui/views/linux_ui/linux_ui.h index 07f30dfe53a..c1f14d5c65a 100644 --- a/chromium/ui/views/linux_ui/linux_ui.h +++ b/chromium/ui/views/linux_ui/linux_ui.h @@ -22,6 +22,8 @@ // The main entrypoint into Linux toolkit specific code. GTK code should only // be executed behind this interface. +class PrefService; + namespace aura { class Window; } @@ -101,7 +103,9 @@ class VIEWS_EXPORT LinuxUI : public ui::LinuxInputMethodContextFactory, virtual void Initialize() = 0; virtual bool GetTint(int id, color_utils::HSL* tint) const = 0; - virtual bool GetColor(int id, SkColor* color) const = 0; + virtual bool GetColor(int id, + SkColor* color, + PrefService* pref_service) const = 0; // Returns the preferences that we pass to WebKit. virtual SkColor GetFocusRingColor() const = 0; @@ -165,9 +169,9 @@ class VIEWS_EXPORT LinuxUI : public ui::LinuxInputMethodContextFactory, NonClientWindowFrameActionSourceType source) = 0; // Notifies the window manager that start up has completed. - // Normally Chromium opens a new window on startup and GTK does this - // automatically. In case Chromium does not open a new window on startup, - // e.g. an existing browser window already exists, this should be called. + // This needs to be called explicitly both on the primary and the "remote" + // instances (e.g. an existing browser window already exists), since we no + // longer use GTK (which did this automatically) for the main windows. virtual void NotifyWindowManagerStartupComplete() = 0; // Updates the device scale factor so that the default font size can be diff --git a/chromium/ui/views/mus/BUILD.gn b/chromium/ui/views/mus/BUILD.gn index ed64ffdeced..0c9092d362b 100644 --- a/chromium/ui/views/mus/BUILD.gn +++ b/chromium/ui/views/mus/BUILD.gn @@ -17,8 +17,10 @@ jumbo_component("mus") { sources = [ "aura_init.cc", "aura_init.h", - "clipboard_mus.cc", - "clipboard_mus.h", + "ax_remote_host.cc", + "ax_remote_host.h", + "ax_tree_source_mus.cc", + "ax_tree_source_mus.h", "desktop_window_tree_host_mus.cc", "desktop_window_tree_host_mus.h", "mus_client.cc", @@ -26,6 +28,8 @@ jumbo_component("mus") { "mus_client_observer.h", "mus_export.h", "mus_property_mirror.h", + "mus_views_delegate.cc", + "mus_views_delegate.h", "pointer_watcher_event_router.cc", "pointer_watcher_event_router.h", "screen_mus.cc", @@ -60,7 +64,10 @@ jumbo_component("mus") { "//services/ui/public/interfaces", "//skia", "//third_party/icu", + "//ui/accessibility", + "//ui/accessibility/mojom", "//ui/aura", + "//ui/base/mojo:lib", "//ui/compositor", "//ui/display", "//ui/events", @@ -78,13 +85,6 @@ jumbo_component("mus") { "//ui/wm", "//ui/wm/public", ] - - if (is_linux && !is_android) { - deps += [ "//components/services/font/public/cpp" ] - data_deps = [ - "//components/services/font:font_service", - ] - } } repack("resources") { @@ -113,6 +113,7 @@ jumbo_static_library("test_support") { sources = [ "../test/native_widget_factory_aura_mus.cc", + "mus_client_test_api.h", "views_mus_test_suite.cc", "views_mus_test_suite.h", ] @@ -121,7 +122,7 @@ jumbo_static_library("test_support") { ":mus", "//base", "//base/test:test_support", - "//mojo/edk", + "//mojo/core/embedder", "//services/catalog:lib", "//services/service_manager/background:lib", "//services/service_manager/public/cpp", @@ -152,6 +153,8 @@ test("views_mus_unittests") { testonly = true sources = [ + "ax_remote_host_unittest.cc", + "ax_tree_source_mus_unittest.cc", "desktop_window_tree_host_mus_unittest.cc", "pointer_watcher_event_router_unittest.cc", "run_all_unittests_mus.cc", @@ -173,6 +176,7 @@ test("views_mus_unittests") { "//testing/gtest", "//third_party/icu", "//ui/accessibility", + "//ui/accessibility/mojom", "//ui/aura", "//ui/aura:test_support", "//ui/base", @@ -199,7 +203,7 @@ test("views_mus_unittests") { data_deps = [ ":views_mus_tests_catalog_copy", "//services/ui/ime/test_ime_driver", - "//services/ui/test_wm", + "//services/ui/test_ws", ] if (is_win) { @@ -244,7 +248,7 @@ test("views_mus_interactive_ui_tests") { ":mus", ":test_support", "//base", - "//mojo/edk", + "//mojo/core/embedder", "//testing/gmock", "//testing/gtest", "//ui/aura", @@ -252,6 +256,7 @@ test("views_mus_interactive_ui_tests") { "//ui/base", "//ui/base:test_support", "//ui/base/ime", + "//ui/base/mojo:lib", "//ui/events:events_base", "//ui/events:test_support", "//ui/gl:test_support", @@ -264,7 +269,7 @@ test("views_mus_interactive_ui_tests") { data_deps = [ ":views_mus_tests_catalog_copy", - "//services/ui/test_wm", + "//services/ui/test_ws", ] if (is_win) { @@ -299,9 +304,10 @@ catalog("views_mus_tests_catalog") { ":interactive_ui_tests_manifest", ] - standalone_services = [ "//services/ui/test_wm:manifest" ] - - catalog_deps = [ "//mash:catalog" ] + standalone_services = [ + "//services/ui/test_ws:manifest", + "//services/ui/ime/test_ime_driver:manifest", + ] } copy("views_mus_tests_catalog_copy") { diff --git a/chromium/ui/views/mus/DEPS b/chromium/ui/views/mus/DEPS index f56df79ddca..8953b55dfbd 100644 --- a/chromium/ui/views/mus/DEPS +++ b/chromium/ui/views/mus/DEPS @@ -1,10 +1,9 @@ include_rules = [ "+cc", - "+components/services/font/public", "+components/gpu", "+mojo/cc", "+mojo/converters", - "+mojo/edk/embedder", + "+mojo/core/embedder", "+mojo/public", "+services/catalog", "+services/service_manager/public", diff --git a/chromium/ui/views/mus/OWNERS b/chromium/ui/views/mus/OWNERS index e2906750656..3a73ee3f4a5 100644 --- a/chromium/ui/views/mus/OWNERS +++ b/chromium/ui/views/mus/OWNERS @@ -1,3 +1,9 @@ +jamescook@chromium.org +msw@chromium.org +sky@chromium.org + +per-file ax_*=file://ui/accessibility/OWNERS + per-file interactive_ui_tests_manifest.json=set noparent per-file interactive_ui_tests_manifest.json=file://ipc/SECURITY_OWNERS diff --git a/chromium/ui/views/mus/aura_init.cc b/chromium/ui/views/mus/aura_init.cc index 96e0ecfc398..56b6c3c0a1f 100644 --- a/chromium/ui/views/mus/aura_init.cc +++ b/chromium/ui/views/mus/aura_init.cc @@ -6,7 +6,6 @@ #include <utility> -#include "base/lazy_instance.h" #include "base/macros.h" #include "base/memory/ptr_util.h" #include "base/path_service.h" @@ -19,135 +18,71 @@ #include "ui/base/material_design/material_design_controller.h" #include "ui/base/resource/resource_bundle.h" #include "ui/base/ui_base_paths.h" -#include "ui/views/layout/layout_provider.h" #include "ui/views/mus/mus_client.h" -#include "ui/views/views_delegate.h" - -#if defined(OS_LINUX) -#include "components/services/font/public/cpp/font_loader.h" -#include "ui/gfx/platform_font_linux.h" -#endif +#include "ui/views/mus/mus_views_delegate.h" namespace views { -namespace { - -class MusViewsDelegate : public ViewsDelegate { - public: - MusViewsDelegate() {} - ~MusViewsDelegate() override {} - - private: -#if defined(OS_WIN) - HICON GetSmallWindowIcon() const override { return nullptr; } -#endif - void OnBeforeWidgetInit( - Widget::InitParams* params, - internal::NativeWidgetDelegate* delegate) override {} - - LayoutProvider layout_provider_; +AuraInit::InitParams::InitParams() : resource_file("views_mus_resources.pak") {} - DISALLOW_COPY_AND_ASSIGN(MusViewsDelegate); -}; - -} // namespace +AuraInit::InitParams::~InitParams() = default; AuraInit::AuraInit() { if (!ViewsDelegate::GetInstance()) views_delegate_ = std::make_unique<MusViewsDelegate>(); } -AuraInit::~AuraInit() { -#if defined(OS_LINUX) - if (font_loader_.get()) { - SkFontConfigInterface::SetGlobal(nullptr); - // FontLoader is ref counted. We need to explicitly shutdown the background - // thread, otherwise the background thread may be shutdown after the app is - // torn down, when we're in a bad state. - font_loader_->Shutdown(); - } -#endif -} +AuraInit::~AuraInit() = default; -std::unique_ptr<AuraInit> AuraInit::Create( - service_manager::Connector* connector, - const service_manager::Identity& identity, - const std::string& resource_file, - const std::string& resource_file_200, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, - Mode mode, - bool register_path_provider) { +// static +std::unique_ptr<AuraInit> AuraInit::Create(const InitParams& params) { + // Using 'new' to access a non-public constructor. go/totw/134 std::unique_ptr<AuraInit> aura_init = base::WrapUnique(new AuraInit()); - if (!aura_init->Init(connector, identity, resource_file, resource_file_200, - io_task_runner, mode, register_path_provider)) { + if (!aura_init->Init(params)) aura_init.reset(); - } return aura_init; } -bool AuraInit::Init(service_manager::Connector* connector, - const service_manager::Identity& identity, - const std::string& resource_file, - const std::string& resource_file_200, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, - Mode mode, - bool register_path_provider) { +bool AuraInit::Init(const InitParams& params) { env_ = aura::Env::CreateInstance(aura::Env::Mode::MUS); - if (mode == Mode::AURA_MUS || mode == Mode::AURA_MUS2) { - MusClient::InitParams params; - params.connector = connector; - params.identity = identity; - params.io_task_runner = io_task_runner; - params.wtc_config = mode == Mode::AURA_MUS2 - ? aura::WindowTreeClient::Config::kMus2 - : aura::WindowTreeClient::Config::kMash; - params.create_wm_state = true; - mus_client_ = std::make_unique<MusClient>(params); + if (params.mode == Mode::AURA_MUS || params.mode == Mode::AURA_MUS2) { + MusClient::InitParams mus_params; + mus_params.connector = params.connector; + mus_params.identity = params.identity; + mus_params.io_task_runner = params.io_task_runner; + mus_params.wtc_config = + params.mode == Mode::AURA_MUS2 + ? aura::WindowTreeClient::Config::kMus2 + : aura::WindowTreeClient::Config::kMashDeprecated; + mus_params.create_wm_state = true; + mus_params.use_accessibility_host = params.use_accessibility_host; + mus_client_ = std::make_unique<MusClient>(mus_params); } // MaterialDesignController may have initialized already (such as happens // in the utility process). if (!ui::MaterialDesignController::is_mode_initialized()) ui::MaterialDesignController::Initialize(); - if (!InitializeResources(connector, resource_file, resource_file_200, - register_path_provider)) { - return false; - } - -// Initialize the skia font code to go ask fontconfig underneath. -#if defined(OS_LINUX) - font_loader_ = sk_make_sp<font_service::FontLoader>(connector); - SkFontConfigInterface::SetGlobal(font_loader_); - - // Initialize static default font, by running this now, before any other apps - // load, we ensure all the state is set up. - bool success = gfx::PlatformFontLinux::InitDefaultFont(); - - // If a remote service manager has shut down, initializing the font will fail. - if (!success) + if (!InitializeResources(params)) return false; -#endif // defined(OS_LINUX) ui::InitializeInputMethodForTesting(); return true; } -bool AuraInit::InitializeResources(service_manager::Connector* connector, - const std::string& resource_file, - const std::string& resource_file_200, - bool register_path_provider) { +bool AuraInit::InitializeResources(const InitParams& params) { // Resources may have already been initialized (e.g. when chrome with mash is // used to launch the current app). if (ui::ResourceBundle::HasSharedInstance()) return true; - std::set<std::string> resource_paths({resource_file}); - if (!resource_file_200.empty()) - resource_paths.insert(resource_file_200); + std::set<std::string> resource_paths({params.resource_file}); + if (!params.resource_file_200.empty()) + resource_paths.insert(params.resource_file_200); catalog::ResourceLoader loader; filesystem::mojom::DirectoryPtr directory; - connector->BindInterface(catalog::mojom::kServiceName, &directory); + params.connector->BindInterface(catalog::mojom::kServiceName, &directory); // TODO(jonross): if this proves useful in resolving the crash of // mash_unittests then switch AuraInit to have an Init method, returning a // bool for success. Then update all callsites to use this to determine the @@ -157,17 +92,17 @@ bool AuraInit::InitializeResources(service_manager::Connector* connector, // Calling services will shutdown ServiceContext as appropriate. if (!loader.OpenFiles(std::move(directory), resource_paths)) return false; - if (register_path_provider) + if (params.register_path_provider) ui::RegisterPathProvider(); - base::File pak_file = loader.TakeFile(resource_file); + base::File pak_file = loader.TakeFile(params.resource_file); base::File pak_file_2 = pak_file.Duplicate(); ui::ResourceBundle::InitSharedInstanceWithPakFileRegion( std::move(pak_file), base::MemoryMappedFile::Region::kWholeFile); ui::ResourceBundle::GetSharedInstance().AddDataPackFromFile( std::move(pak_file_2), ui::SCALE_FACTOR_100P); - if (!resource_file_200.empty()) + if (!params.resource_file_200.empty()) ui::ResourceBundle::GetSharedInstance().AddDataPackFromFile( - loader.TakeFile(resource_file_200), ui::SCALE_FACTOR_200P); + loader.TakeFile(params.resource_file_200), ui::SCALE_FACTOR_200P); return true; } diff --git a/chromium/ui/views/mus/aura_init.h b/chromium/ui/views/mus/aura_init.h index c76c8314a24..c821deae031 100644 --- a/chromium/ui/views/mus/aura_init.h +++ b/chromium/ui/views/mus/aura_init.h @@ -9,8 +9,7 @@ #include <string> #include "base/macros.h" -#include "build/build_config.h" -#include "third_party/skia/include/core/SkRefCnt.h" +#include "services/service_manager/public/cpp/identity.h" #include "ui/aura/env.h" #include "ui/views/mus/mus_export.h" @@ -22,13 +21,8 @@ namespace base { class SingleThreadTaskRunner; } -namespace font_service { -class FontLoader; -} - namespace service_manager { class Connector; -class Identity; } namespace views { @@ -55,20 +49,28 @@ class VIEWS_MUS_EXPORT AuraInit { ~AuraInit(); + struct VIEWS_MUS_EXPORT InitParams { + InitParams(); + ~InitParams(); + service_manager::Connector* connector = nullptr; + service_manager::Identity identity; + // File for strings and 1x icons. Defaults to views_mus_resources.pak. + std::string resource_file; + // File for 2x icons. Can be empty. + std::string resource_file_200; + scoped_refptr<base::SingleThreadTaskRunner> io_task_runner = nullptr; + Mode mode = Mode::AURA_MUS; + bool register_path_provider = true; + // When true the client application will connect to the accessibility host + // in the browser to supply AX node trees and handle AX actions (e.g. to + // support ChromeVox). + bool use_accessibility_host = false; + }; + // Returns an AuraInit if initialization can be completed successfully, // otherwise a nullptr is returned. If initialization fails then Aura is in an // unusable state, and calling services should shutdown. - // |resource_file| is the file to load strings and 1x icons from. - // |resource_file_200| can be an empty string, otherwise it is the file to - // load 2x icons from. - static std::unique_ptr<AuraInit> Create( - service_manager::Connector* connector, - const service_manager::Identity& identity, - const std::string& resource_file, - const std::string& resource_file_200 = std::string(), - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner = nullptr, - Mode mode = Mode::AURA_MUS, - bool register_path_provider = true); + static std::unique_ptr<AuraInit> Create(const InitParams& params); // Only valid if Mode::AURA_MUS was passed to constructor. MusClient* mus_client() { return mus_client_.get(); } @@ -79,22 +81,10 @@ class VIEWS_MUS_EXPORT AuraInit { // Returns true if AuraInit was able to successfully complete initialization. // If this returns false, then Aura is in an unusable state, and calling // services should shutdown. - bool Init(service_manager::Connector* connector, - const service_manager::Identity& identity, - const std::string& resource_file, - const std::string& resource_file_200, - scoped_refptr<base::SingleThreadTaskRunner> io_task_runner, - Mode mode, - bool register_path_provider); - - bool InitializeResources(service_manager::Connector* connector, - const std::string& resource_file, - const std::string& resource_file_200, - bool register_path_provider); - -#if defined(OS_LINUX) - sk_sp<font_service::FontLoader> font_loader_; -#endif + bool Init(const InitParams& params); + + // Returns true on success. + bool InitializeResources(const InitParams& params); std::unique_ptr<aura::Env> env_; std::unique_ptr<MusClient> mus_client_; diff --git a/chromium/ui/views/mus/ax_remote_host.cc b/chromium/ui/views/mus/ax_remote_host.cc new file mode 100644 index 00000000000..bc1d4edb33e --- /dev/null +++ b/chromium/ui/views/mus/ax_remote_host.cc @@ -0,0 +1,196 @@ +// 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/mus/ax_remote_host.h" + +#include <stddef.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/ax_unique_id.h" +#include "ui/aura/window.h" +#include "ui/views/accessibility/ax_aura_obj_wrapper.h" +#include "ui/views/mus/ax_tree_source_mus.h" +#include "ui/views/mus/mus_client.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" + +namespace views { + +// For external linkage. +constexpr int AXRemoteHost::kRemoteAXTreeID; + +AXRemoteHost::AXRemoteHost() { + AXAuraObjCache::GetInstance()->SetDelegate(this); +} + +AXRemoteHost::~AXRemoteHost() { + if (widget_) + StopMonitoringWidget(); + AXAuraObjCache::GetInstance()->SetDelegate(nullptr); +} + +void AXRemoteHost::Init(service_manager::Connector* connector) { + connector->BindInterface(ax::mojom::kAXHostServiceName, &ax_host_ptr_); + BindAndSetRemote(); +} + +void AXRemoteHost::InitForTesting(ax::mojom::AXHostPtr host_ptr) { + ax_host_ptr_ = std::move(host_ptr); + BindAndSetRemote(); +} + +void AXRemoteHost::StartMonitoringWidget(Widget* widget) { + if (!enabled_) + return; + + // Check if we're already tracking a widget. + // TODO(jamescook): Support multiple widgets. + if (widget_) + return; + widget_ = widget; + widget_->AddObserver(this); + + // The cache needs to track the root window to follow focus changes. + AXAuraObjCache* cache = AXAuraObjCache::GetInstance(); + cache->OnRootWindowObjCreated(widget_->GetNativeWindow()); + + // Start the AX tree with the contents view because the window frame is + // handled by the window manager in another process. + View* contents_view = widget_->widget_delegate()->GetContentsView(); + AXAuraObjWrapper* contents_wrapper = cache->GetOrCreate(contents_view); + + tree_source_ = std::make_unique<AXTreeSourceMus>(contents_wrapper); + tree_serializer_ = std::make_unique<AuraAXTreeSerializer>(tree_source_.get()); + + SendEvent(contents_wrapper, ax::mojom::Event::kLoadComplete); +} + +void AXRemoteHost::StopMonitoringWidget() { + DCHECK(widget_); + DCHECK(widget_->HasObserver(this)); + widget_->RemoveObserver(this); + AXAuraObjCache* cache = AXAuraObjCache::GetInstance(); + cache->OnRootWindowObjDestroyed(widget_->GetNativeWindow()); + cache->Remove(widget_->widget_delegate()->GetContentsView()); + widget_ = nullptr; + // Delete source and serializers to save memory. + tree_serializer_.reset(); + tree_source_.reset(); +} + +void AXRemoteHost::HandleEvent(View* view, ax::mojom::Event event_type) { + if (!enabled_) + return; + + AXAuraObjWrapper* aura_obj = + view ? AXAuraObjCache::GetInstance()->GetOrCreate(view) + : tree_source_->GetRoot(); + SendEvent(aura_obj, event_type); +} + +void AXRemoteHost::OnAutomationEnabled(bool enabled) { + if (enabled) + Enable(); + else + Disable(); +} + +void AXRemoteHost::PerformAction(const ui::AXActionData& action) { + // TODO(jamescook): Support ax::mojom::Action::kHitTest. + tree_source_->HandleAccessibleAction(action); +} + +void AXRemoteHost::OnWidgetDestroying(Widget* widget) { + DCHECK_EQ(widget_, widget); + StopMonitoringWidget(); +} + +void AXRemoteHost::OnChildWindowRemoved(AXAuraObjWrapper* parent) { + if (!enabled_) + return; + + if (!parent) + parent = tree_source_->GetRoot(); + + SendEvent(parent, ax::mojom::Event::kChildrenChanged); +} + +void AXRemoteHost::OnEvent(AXAuraObjWrapper* aura_obj, + ax::mojom::Event event_type) { + SendEvent(aura_obj, event_type); +} + +void AXRemoteHost::FlushForTesting() { + ax_host_ptr_.FlushForTesting(); +} + +void AXRemoteHost::BindAndSetRemote() { + ax::mojom::AXRemoteHostPtr remote; + binding_.Bind(mojo::MakeRequest(&remote)); + ax_host_ptr_->SetRemoteHost(std::move(remote)); +} + +void AXRemoteHost::Enable() { + // Extensions can send multiple enable events. + if (enabled_) + return; + enabled_ = true; + + std::set<aura::Window*> roots = + MusClient::Get()->window_tree_client()->GetRoots(); + if (roots.empty()) { + // Client hasn't opened any widgets yet. + return; + } + + // TODO(jamescook): Support multiple roots. + aura::Window* root_window = *roots.begin(); + DCHECK(root_window); + Widget* root_widget = Widget::GetWidgetForNativeWindow(root_window); + DCHECK(root_widget); + StartMonitoringWidget(root_widget); +} + +void AXRemoteHost::Disable() { + if (!enabled_) + return; + enabled_ = false; + StopMonitoringWidget(); +} + +void AXRemoteHost::SendEvent(AXAuraObjWrapper* aura_obj, + ax::mojom::Event event_type) { + if (!enabled_ || !tree_serializer_) + return; + + ui::AXTreeUpdate update; + if (!tree_serializer_->SerializeChanges(aura_obj, &update)) { + LOG(ERROR) << "Unable to serialize accessibility tree."; + return; + } + + std::vector<ui::AXTreeUpdate> updates; + updates.push_back(update); + + // Make sure the focused node is serialized. + AXAuraObjWrapper* focus = AXAuraObjCache::GetInstance()->GetFocus(); + if (focus) { + ui::AXTreeUpdate focused_node_update; + tree_serializer_->SerializeChanges(focus, &focused_node_update); + updates.push_back(focused_node_update); + } + + ui::AXEvent event; + event.id = aura_obj->GetUniqueId().Get(); + event.event_type = event_type; + // Other fields are not used. + + ax_host_ptr_->HandleAccessibilityEvent(kRemoteAXTreeID, updates, event); +} + +} // namespace views diff --git a/chromium/ui/views/mus/ax_remote_host.h b/chromium/ui/views/mus/ax_remote_host.h new file mode 100644 index 00000000000..f213060ab95 --- /dev/null +++ b/chromium/ui/views/mus/ax_remote_host.h @@ -0,0 +1,112 @@ +// 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_MUS_AX_REMOTE_HOST_H_ +#define UI_VIEWS_MUS_AX_REMOTE_HOST_H_ + +#include <memory> +#include <vector> + +#include "base/macros.h" +#include "mojo/public/cpp/bindings/binding.h" +#include "ui/accessibility/ax_tree_serializer.h" +#include "ui/accessibility/mojom/ax_host.mojom.h" +#include "ui/views/accessibility/ax_aura_obj_cache.h" +#include "ui/views/mus/mus_export.h" +#include "ui/views/widget/widget_observer.h" + +namespace service_manager { +class Connector; +} + +namespace ui { +struct AXActionData; +struct AXNodeData; +struct AXTreeData; +} // namespace ui + +namespace views { + +class AXAuraObjWrapper; +class AXTreeSourceMus; +class View; +class Widget; + +// Manages a tree of automation nodes for a mojo app outside the browser process +// (e.g. the keyboard shortcut viewer app). +class VIEWS_MUS_EXPORT AXRemoteHost : public ax::mojom::AXRemoteHost, + public WidgetObserver, + 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; + + // Initializes and adds ourself as a client of the host service. + void Init(service_manager::Connector* connector); + + // Initializes with a fake host. + void InitForTesting(ax::mojom::AXHostPtr host_ptr); + + // Sends the initial AX node tree to the host then starts monitoring for AX + // events and tree changes. + void StartMonitoringWidget(Widget* widget); + void StopMonitoringWidget(); + + // Handles an event fired upon a |view|. + void HandleEvent(View* view, ax::mojom::Event event_type); + + // ax::mojom::AXRemoteHost: + void OnAutomationEnabled(bool enabled) override; + void PerformAction(const ui::AXActionData& action) override; + + // WidgetObserver: + void OnWidgetDestroying(Widget* widget) override; + + // AXAuraObjCache::Delegate: + void OnChildWindowRemoved(AXAuraObjWrapper* parent) override; + void OnEvent(AXAuraObjWrapper* aura_obj, + ax::mojom::Event event_type) override; + + void FlushForTesting(); + + private: + // Registers this object as a remote host for the parent AXHost. + void BindAndSetRemote(); + + void Enable(); + void Disable(); + + // Sends an event to the host. + void SendEvent(AXAuraObjWrapper* aura_obj, ax::mojom::Event event_type); + + // Accessibility host service in the browser. + ax::mojom::AXHostPtr ax_host_ptr_; + + mojo::Binding<ax::mojom::AXRemoteHost> binding_{this}; + + // Whether accessibility automation support is enabled. + bool enabled_ = false; + + // Top-level widget being tracked. + Widget* widget_ = nullptr; + + // Holds the active views-based accessibility tree. A tree consists of all + // views descendant to a Widget's content area. + std::unique_ptr<AXTreeSourceMus> tree_source_; + + // Serializes incremental updates on the currently active |tree_source_|. + using AuraAXTreeSerializer = + ui::AXTreeSerializer<AXAuraObjWrapper*, ui::AXNodeData, ui::AXTreeData>; + std::unique_ptr<AuraAXTreeSerializer> tree_serializer_; + + DISALLOW_COPY_AND_ASSIGN(AXRemoteHost); +}; + +} // namespace views + +#endif // UI_VIEWS_MUS_AX_REMOTE_HOST_H_ diff --git a/chromium/ui/views/mus/ax_remote_host_unittest.cc b/chromium/ui/views/mus/ax_remote_host_unittest.cc new file mode 100644 index 00000000000..cf93bb3d11e --- /dev/null +++ b/chromium/ui/views/mus/ax_remote_host_unittest.cc @@ -0,0 +1,169 @@ +// 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/mus/ax_remote_host.h" + +#include "base/macros.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/accessibility/mojom/ax_host.mojom.h" +#include "ui/views/accessibility/ax_aura_obj_cache.h" +#include "ui/views/mus/mus_client_test_api.h" +#include "ui/views/test/views_test_base.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_delegate.h" + +namespace views { +namespace { + +// Simulates the AXHostService in the browser. +class TestAXHostService : public ax::mojom::AXHost { + public: + explicit TestAXHostService(bool automation_enabled) + : automation_enabled_(automation_enabled) {} + ~TestAXHostService() override = default; + + ax::mojom::AXHostPtr CreateInterfacePtr() { + ax::mojom::AXHostPtr ptr; + binding_.Bind(mojo::MakeRequest(&ptr)); + return ptr; + } + + // ax::mojom::AXHost: + void SetRemoteHost(ax::mojom::AXRemoteHostPtr client) override { + ++add_client_count_; + client->OnAutomationEnabled(automation_enabled_); + client.FlushForTesting(); + } + void HandleAccessibilityEvent(int32_t tree_id, + const std::vector<ui::AXTreeUpdate>& updates, + const ui::AXEvent& event) override { + ++event_count_; + last_tree_id_ = tree_id; + last_event_ = event; + } + + mojo::Binding<ax::mojom::AXHost> binding_{this}; + bool automation_enabled_ = false; + int add_client_count_ = 0; + int event_count_ = 0; + int last_tree_id_ = 0; + ui::AXEvent last_event_; + + private: + DISALLOW_COPY_AND_ASSIGN(TestAXHostService); +}; + +// TestView senses accessibility actions. +class TestView : public View { + public: + TestView() = default; + ~TestView() override = default; + + // View: + bool HandleAccessibleAction(const ui::AXActionData& action) override { + ++action_count_; + last_action_ = action; + return true; + } + + int action_count_ = 0; + ui::AXActionData last_action_; + + private: + DISALLOW_COPY_AND_ASSIGN(TestView); +}; + +AXRemoteHost* CreateRemote(TestAXHostService* service) { + std::unique_ptr<AXRemoteHost> remote = std::make_unique<AXRemoteHost>(); + remote->InitForTesting(service->CreateInterfacePtr()); + remote->FlushForTesting(); + // Install the AXRemoteHost on MusClient so it monitors Widget creation. + MusClientTestApi::SetAXRemoteHost(std::move(remote)); + return MusClient::Get()->ax_remote_host(); +} + +std::unique_ptr<Widget> CreateTestWidget() { + std::unique_ptr<Widget> widget = std::make_unique<Widget>(); + Widget::InitParams params; + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.bounds = gfx::Rect(1, 2, 333, 444); + widget->Init(params); + return widget; +} + +using AXRemoteHostTest = ViewsTestBase; + +TEST_F(AXRemoteHostTest, CreateRemote) { + TestAXHostService service(false /*automation_enabled*/); + CreateRemote(&service); + + // Client registered itself with service. + EXPECT_EQ(1, service.add_client_count_); +} + +TEST_F(AXRemoteHostTest, AutomationEnabled) { + TestAXHostService service(true /*automation_enabled*/); + AXRemoteHost* remote = CreateRemote(&service); + std::unique_ptr<Widget> widget = CreateTestWidget(); + remote->FlushForTesting(); + + // Event was sent with initial hierarchy. + EXPECT_EQ(ax::mojom::Event::kLoadComplete, service.last_event_.event_type); + EXPECT_EQ(AXAuraObjCache::GetInstance()->GetID( + widget->widget_delegate()->GetContentsView()), + service.last_event_.id); +} + +// 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) { + TestAXHostService service(true /*automation_enabled*/); + AXRemoteHost* remote = CreateRemote(&service); + views::View view; + remote->HandleEvent(&view, ax::mojom::Event::kLocationChanged); + // No crash. +} + +TEST_F(AXRemoteHostTest, CreateWidgetThenEnableAutomation) { + TestAXHostService service(false /*automation_enabled*/); + AXRemoteHost* remote = CreateRemote(&service); + std::unique_ptr<Widget> widget = CreateTestWidget(); + remote->FlushForTesting(); + + // No events were sent because automation isn't enabled. + EXPECT_EQ(0, service.event_count_); + + remote->OnAutomationEnabled(true); + remote->FlushForTesting(); + + // Event was sent with initial hierarchy. + EXPECT_EQ(ax::mojom::Event::kLoadComplete, service.last_event_.event_type); + EXPECT_EQ(AXAuraObjCache::GetInstance()->GetID( + widget->widget_delegate()->GetContentsView()), + service.last_event_.id); +} + +TEST_F(AXRemoteHostTest, PerformAction) { + TestAXHostService service(true /*automation_enabled*/); + AXRemoteHost* remote = CreateRemote(&service); + + // Create a view to sense the action. + TestView view; + AXAuraObjCache::GetInstance()->GetOrCreate(&view); + + // Request an action on the view. + ui::AXActionData action; + action.action = ax::mojom::Action::kScrollDown; + action.target_node_id = AXAuraObjCache::GetInstance()->GetID(&view); + remote->PerformAction(action); + + // View received the action. + EXPECT_EQ(1, view.action_count_); + EXPECT_EQ(ax::mojom::Action::kScrollDown, view.last_action_.action); +} + +} // namespace +} // namespace views diff --git a/chromium/ui/views/mus/ax_tree_source_mus.cc b/chromium/ui/views/mus/ax_tree_source_mus.cc new file mode 100644 index 00000000000..6acef96a413 --- /dev/null +++ b/chromium/ui/views/mus/ax_tree_source_mus.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/mus/ax_tree_source_mus.h" + +#include "ui/gfx/geometry/point_f.h" +#include "ui/gfx/transform.h" +#include "ui/views/accessibility/ax_aura_obj_wrapper.h" +#include "ui/views/mus/ax_remote_host.h" + +namespace views { + +AXTreeSourceMus::AXTreeSourceMus(AXAuraObjWrapper* root) : root_(root) { + DCHECK(root_); +} + +AXTreeSourceMus::~AXTreeSourceMus() = default; + +bool AXTreeSourceMus::GetTreeData(ui::AXTreeData* tree_data) const { + tree_data->tree_id = AXRemoteHost::kRemoteAXTreeID; + return AXTreeSourceViews::GetTreeData(tree_data); +} + +AXAuraObjWrapper* AXTreeSourceMus::GetRoot() const { + return root_; +} + +void AXTreeSourceMus::SerializeNode(AXAuraObjWrapper* node, + ui::AXNodeData* out_data) const { + if (IsEqual(node, root_)) { + node->Serialize(out_data); + // Root is a contents view with an offset from the containing Widget. + // However, the contents view in the host (browser) already has an offset + // from its Widget, so the root should start at (0,0). + out_data->location.set_origin(gfx::PointF()); + out_data->transform.reset(); + return; + } + + AXTreeSourceViews::SerializeNode(node, out_data); +} + +} // namespace views diff --git a/chromium/ui/views/mus/ax_tree_source_mus.h b/chromium/ui/views/mus/ax_tree_source_mus.h new file mode 100644 index 00000000000..340f14271d9 --- /dev/null +++ b/chromium/ui/views/mus/ax_tree_source_mus.h @@ -0,0 +1,41 @@ +// 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_MUS_AX_TREE_SOURCE_MUS_H_ +#define UI_VIEWS_MUS_AX_TREE_SOURCE_MUS_H_ + +#include "base/macros.h" +#include "ui/views/accessibility/ax_tree_source_views.h" +#include "ui/views/mus/mus_export.h" + +namespace views { + +class AXAuraObjWrapper; + +// This class exposes the views hierarchy as an accessibility tree permitting +// use with other accessibility classes. Only used for out-of-process views +// apps (e.g. Chrome OS shortcut_viewer app). The browser process uses +// AXTreeSourceAura. +class VIEWS_MUS_EXPORT AXTreeSourceMus : public AXTreeSourceViews { + public: + // |root| must outlive this object. + explicit AXTreeSourceMus(AXAuraObjWrapper* root); + ~AXTreeSourceMus() override; + + // AXTreeSource: + bool GetTreeData(ui::AXTreeData* data) const override; + AXAuraObjWrapper* GetRoot() const override; + void SerializeNode(AXAuraObjWrapper* node, + ui::AXNodeData* out_data) const override; + + private: + // The top-level object to use for the AX tree. + AXAuraObjWrapper* root_; + + DISALLOW_COPY_AND_ASSIGN(AXTreeSourceMus); +}; + +} // namespace views + +#endif // UI_VIEWS_MUS_AX_TREE_SOURCE_MUS_H_ diff --git a/chromium/ui/views/mus/ax_tree_source_mus_unittest.cc b/chromium/ui/views/mus/ax_tree_source_mus_unittest.cc new file mode 100644 index 00000000000..d652b04712e --- /dev/null +++ b/chromium/ui/views/mus/ax_tree_source_mus_unittest.cc @@ -0,0 +1,90 @@ +// 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/mus/ax_tree_source_mus.h" + +#include <vector> + +#include "base/macros.h" +#include "base/strings/utf_string_conversions.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/accessibility/ax_tree_data.h" +#include "ui/accessibility/platform/ax_unique_id.h" +#include "ui/gfx/geometry/rect_f.h" +#include "ui/views/accessibility/ax_aura_obj_cache.h" +#include "ui/views/accessibility/ax_aura_obj_wrapper.h" +#include "ui/views/controls/label.h" +#include "ui/views/mus/ax_remote_host.h" +#include "ui/views/test/views_test_base.h" +#include "ui/views/widget/widget.h" + +namespace views { +namespace { + +class AXTreeSourceMusTest : public ViewsTestBase { + public: + AXTreeSourceMusTest() = default; + ~AXTreeSourceMusTest() override = default; + + // testing::Test: + void SetUp() override { + ViewsTestBase::SetUp(); + widget_ = std::make_unique<Widget>(); + Widget::InitParams params(Widget::InitParams::TYPE_WINDOW_FRAMELESS); + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.bounds = gfx::Rect(11, 22, 333, 444); + params.context = GetContext(); + widget_->Init(params); + widget_->SetContentsView(new View()); + label_ = new Label(base::ASCIIToUTF16("Label")); + label_->SetBounds(1, 1, 111, 111); + widget_->GetContentsView()->AddChildView(label_); + } + + void TearDown() override { + widget_.reset(); + ViewsTestBase::TearDown(); + } + + std::unique_ptr<Widget> widget_; + Label* label_ = nullptr; // Owned by views hierarchy. + + private: + DISALLOW_COPY_AND_ASSIGN(AXTreeSourceMusTest); +}; + +TEST_F(AXTreeSourceMusTest, GetTreeData) { + AXAuraObjWrapper* root = + AXAuraObjCache::GetInstance()->GetOrCreate(widget_->GetContentsView()); + AXTreeSourceMus tree(root); + ui::AXTreeData tree_data; + tree.GetTreeData(&tree_data); + EXPECT_EQ(AXRemoteHost::kRemoteAXTreeID, tree_data.tree_id); +} + +TEST_F(AXTreeSourceMusTest, Serialize) { + AXAuraObjCache* cache = AXAuraObjCache::GetInstance(); + AXAuraObjWrapper* root = cache->GetOrCreate(widget_->GetContentsView()); + + AXTreeSourceMus tree(root); + EXPECT_EQ(root, tree.GetRoot()); + + // Serialize the root. + ui::AXNodeData node_data; + tree.SerializeNode(root, &node_data); + + // Root is at the origin and has no parent container. + EXPECT_EQ(gfx::RectF(0, 0, 333, 444), node_data.location); + EXPECT_EQ(-1, node_data.offset_container_id); + + // Serialize a child. + tree.SerializeNode(cache->GetOrCreate(label_), &node_data); + + // Child has relative position with the root as the container. + EXPECT_EQ(gfx::RectF(1, 1, 111, 111), node_data.location); + EXPECT_EQ(root->GetUniqueId().Get(), node_data.offset_container_id); +} + +} // namespace +} // namespace views diff --git a/chromium/ui/views/mus/clipboard_mus.cc b/chromium/ui/views/mus/clipboard_mus.cc deleted file mode 100644 index 65805757abf..00000000000 --- a/chromium/ui/views/mus/clipboard_mus.cc +++ /dev/null @@ -1,347 +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/mus/clipboard_mus.h" - -#include <string> -#include <utility> -#include <vector> - -#include "base/logging.h" -#include "base/stl_util.h" -#include "base/strings/utf_string_conversions.h" -#include "mojo/public/cpp/bindings/sync_call_restrictions.h" -#include "services/service_manager/public/cpp/connector.h" -#include "services/ui/public/interfaces/constants.mojom.h" -#include "third_party/skia/include/core/SkBitmap.h" -#include "ui/base/clipboard/custom_data_helper.h" -#include "ui/gfx/codec/png_codec.h" - -namespace views { -namespace { - -ui::mojom::Clipboard::Type GetType(ui::ClipboardType type) { - switch (type) { - case ui::CLIPBOARD_TYPE_COPY_PASTE: - return ui::mojom::Clipboard::Type::COPY_PASTE; - case ui::CLIPBOARD_TYPE_SELECTION: - return ui::mojom::Clipboard::Type::SELECTION; - case ui::CLIPBOARD_TYPE_DRAG: - // Only OSX uses a drag clipboard. - break; - } - - NOTREACHED(); - return ui::mojom::Clipboard::Type::COPY_PASTE; -} - -// The source URL of copied HTML. -const char kInternalSourceURL[] = "chromium/internal-url"; - -} // namespace - -ClipboardMus::ClipboardMus() {} - -ClipboardMus::~ClipboardMus() {} - -void ClipboardMus::Init(service_manager::Connector* connector) { - connector->BindInterface(ui::mojom::kServiceName, &clipboard_); -} - -// TODO(erg): This isn't optimal. It would be better to move the entire -// FormatType system to mime types throughout chrome, but that's a very large -// change. -std::string ClipboardMus::GetMimeTypeFor(const FormatType& format) { - if (format.Equals(GetUrlFormatType()) || format.Equals(GetUrlWFormatType())) - return ui::mojom::kMimeTypeURIList; - if (format.Equals(GetMozUrlFormatType())) - return ui::mojom::kMimeTypeMozillaURL; - if (format.Equals(GetPlainTextFormatType()) || - format.Equals(GetPlainTextWFormatType())) { - return ui::mojom::kMimeTypeText; - } - if (format.Equals(GetHtmlFormatType())) - return ui::mojom::kMimeTypeHTML; - if (format.Equals(GetRtfFormatType())) - return ui::mojom::kMimeTypeRTF; - if (format.Equals(GetBitmapFormatType())) - return ui::mojom::kMimeTypePNG; - if (format.Equals(GetWebKitSmartPasteFormatType())) - return kMimeTypeWebkitSmartPaste; - if (format.Equals(GetWebCustomDataFormatType())) - return kMimeTypeWebCustomData; - if (format.Equals(GetPepperCustomDataFormatType())) - return kMimeTypePepperCustomData; - - // TODO(erg): This isn't optimal, but it's the best we can do. On windows, - // this will return strings that aren't MIME types, though they'll be - // unique and should be serializable on the other side of the mojo - // connection. - return format.Serialize(); -} - -bool ClipboardMus::HasMimeType(const std::vector<std::string>& available_types, - const std::string& type) const { - return base::ContainsValue(available_types, type); -} - -void ClipboardMus::OnPreShutdown() {} - -uint64_t ClipboardMus::GetSequenceNumber(ui::ClipboardType type) const { - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; - uint64_t sequence_number = 0; - clipboard_->GetSequenceNumber(GetType(type), &sequence_number); - return sequence_number; -} - -bool ClipboardMus::IsFormatAvailable(const FormatType& format, - ui::ClipboardType type) const { - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; - - uint64_t sequence_number = 0; - std::vector<std::string> available_types; - clipboard_->GetAvailableMimeTypes(GetType(type), &sequence_number, - &available_types); - - std::string format_in_mime = GetMimeTypeFor(format); - return base::ContainsValue(available_types, format_in_mime); -} - -void ClipboardMus::Clear(ui::ClipboardType type) { - // Sends the data to mus server. - uint64_t sequence_number = 0; - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; - clipboard_->WriteClipboardData(GetType(type), base::nullopt, - &sequence_number); -} - -void ClipboardMus::ReadAvailableTypes(ui::ClipboardType type, - std::vector<base::string16>* types, - bool* contains_filenames) const { - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; - - uint64_t sequence_number = 0; - std::vector<std::string> available_types; - clipboard_->GetAvailableMimeTypes(GetType(type), &sequence_number, - &available_types); - - types->clear(); - if (HasMimeType(available_types, ui::mojom::kMimeTypeText)) - types->push_back(base::UTF8ToUTF16(ui::mojom::kMimeTypeText)); - if (HasMimeType(available_types, ui::mojom::kMimeTypeHTML)) - types->push_back(base::UTF8ToUTF16(ui::mojom::kMimeTypeHTML)); - if (HasMimeType(available_types, ui::mojom::kMimeTypeRTF)) - types->push_back(base::UTF8ToUTF16(ui::mojom::kMimeTypeRTF)); - if (HasMimeType(available_types, ui::mojom::kMimeTypePNG)) - types->push_back(base::UTF8ToUTF16(ui::mojom::kMimeTypePNG)); - - if (HasMimeType(available_types, kMimeTypeWebCustomData)) { - base::Optional<std::vector<uint8_t>> custom_data; - uint64_t sequence_number = 0; - if (clipboard_->ReadClipboardData(GetType(type), kMimeTypeWebCustomData, - &sequence_number, &custom_data) && - custom_data.has_value()) { - ui::ReadCustomDataTypes(&custom_data->front(), custom_data->size(), - types); - } - } - - *contains_filenames = false; -} - -void ClipboardMus::ReadText(ui::ClipboardType type, - base::string16* result) const { - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; - base::Optional<std::vector<uint8_t>> text_data; - uint64_t sequence_number = 0; - if (clipboard_->ReadClipboardData(GetType(type), ui::mojom::kMimeTypeText, - &sequence_number, &text_data) && - text_data) { - *result = base::UTF8ToUTF16(base::StringPiece( - reinterpret_cast<char*>(text_data->data()), text_data->size())); - } -} - -void ClipboardMus::ReadAsciiText(ui::ClipboardType type, - std::string* result) const { - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; - base::Optional<std::vector<uint8_t>> text_data; - uint64_t sequence_number = 0; - if (clipboard_->ReadClipboardData(GetType(type), ui::mojom::kMimeTypeText, - &sequence_number, &text_data) && - text_data) { - result->assign(text_data->begin(), text_data->end()); - } -} - -void ClipboardMus::ReadHTML(ui::ClipboardType type, - base::string16* markup, - std::string* src_url, - uint32_t* fragment_start, - uint32_t* fragment_end) const { - markup->clear(); - if (src_url) - src_url->clear(); - *fragment_start = 0; - *fragment_end = 0; - - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; - base::Optional<std::vector<uint8_t>> html_data; - uint64_t sequence_number = 0; - if (clipboard_->ReadClipboardData(GetType(type), ui::mojom::kMimeTypeHTML, - &sequence_number, &html_data) && - html_data) { - *markup = base::UTF8ToUTF16(base::StringPiece( - reinterpret_cast<char*>(html_data->data()), html_data->size())); - *fragment_end = static_cast<uint32_t>(markup->length()); - - // We only bother fetching the source url if we were the ones who wrote - // this html data to the clipboard. - base::Optional<std::vector<uint8_t>> url_data; - if (clipboard_->ReadClipboardData(GetType(type), kInternalSourceURL, - &sequence_number, &url_data) && - url_data) { - src_url->assign(url_data->begin(), url_data->end()); - } - } -} - -void ClipboardMus::ReadRTF(ui::ClipboardType type, std::string* result) const { - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; - base::Optional<std::vector<uint8_t>> rtf_data; - uint64_t sequence_number = 0; - if (clipboard_->ReadClipboardData(GetType(type), ui::mojom::kMimeTypeRTF, - &sequence_number, &rtf_data) && - rtf_data) { - result->assign(rtf_data->begin(), rtf_data->end()); - } -} - -SkBitmap ClipboardMus::ReadImage(ui::ClipboardType type) const { - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; - base::Optional<std::vector<uint8_t>> data; - uint64_t sequence_number = 0; - if (clipboard_->ReadClipboardData(GetType(type), ui::mojom::kMimeTypePNG, - &sequence_number, &data) && - data.has_value()) { - SkBitmap bitmap; - if (gfx::PNGCodec::Decode(&data->front(), data->size(), &bitmap)) - return SkBitmap(bitmap); - } - - return SkBitmap(); -} - -void ClipboardMus::ReadCustomData(ui::ClipboardType clipboard_type, - const base::string16& type, - base::string16* result) const { - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; - base::Optional<std::vector<uint8_t>> custom_data; - uint64_t sequence_number = 0; - if (clipboard_->ReadClipboardData(GetType(clipboard_type), - kMimeTypeWebCustomData, &sequence_number, - &custom_data) && - custom_data.has_value()) { - ui::ReadCustomDataForType(&custom_data->front(), custom_data->size(), type, - result); - } -} - -void ClipboardMus::ReadBookmark(base::string16* title, std::string* url) const { - // TODO(erg): This is NOTIMPLEMENTED() on all linux platforms? - NOTIMPLEMENTED(); -} - -void ClipboardMus::ReadData(const FormatType& format, - std::string* result) const { - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; - base::Optional<std::vector<uint8_t>> data; - uint64_t sequence_number = 0; - if (clipboard_->ReadClipboardData(ui::mojom::Clipboard::Type::COPY_PASTE, - GetMimeTypeFor(format), &sequence_number, - &data) && - data) { - result->assign(data->begin(), data->end()); - } -} - -void ClipboardMus::WriteObjects(ui::ClipboardType type, - const ObjectMap& objects) { - current_clipboard_.emplace(); - for (const auto& p : objects) - DispatchObject(static_cast<ObjectType>(p.first), p.second); - - // Sends the data to mus server. - uint64_t sequence_number = 0; - mojo::SyncCallRestrictions::ScopedAllowSyncCall allow_sync_call; - clipboard_->WriteClipboardData(GetType(type), std::move(current_clipboard_), - &sequence_number); -} - -void ClipboardMus::WriteText(const char* text_data, size_t text_len) { - DCHECK(current_clipboard_); - current_clipboard_.value()[ui::mojom::kMimeTypeText] = - std::vector<uint8_t>(text_data, text_data + text_len); -} - -void ClipboardMus::WriteHTML(const char* markup_data, - size_t markup_len, - const char* url_data, - size_t url_len) { - DCHECK(current_clipboard_); - current_clipboard_.value()[ui::mojom::kMimeTypeHTML] = - std::vector<uint8_t>(markup_data, markup_data + markup_len); - if (url_len > 0) { - current_clipboard_.value()[kInternalSourceURL] = - std::vector<uint8_t>(url_data, url_data + url_len); - } -} - -void ClipboardMus::WriteRTF(const char* rtf_data, size_t data_len) { - DCHECK(current_clipboard_); - current_clipboard_.value()[ui::mojom::kMimeTypeRTF] = - std::vector<uint8_t>(rtf_data, rtf_data + data_len); -} - -void ClipboardMus::WriteBookmark(const char* title_data, - size_t title_len, - const char* url_data, - size_t url_len) { - // Writes a Mozilla url (UTF16: URL, newline, title) - base::string16 bookmark = - base::UTF8ToUTF16(base::StringPiece(url_data, url_len)) + - base::ASCIIToUTF16("\n") + - base::UTF8ToUTF16(base::StringPiece(title_data, title_len)); - - DCHECK(current_clipboard_); - current_clipboard_.value()[ui::mojom::kMimeTypeMozillaURL] = - std::vector<uint8_t>( - reinterpret_cast<const uint8_t*>(bookmark.data()), - reinterpret_cast<const uint8_t*>(bookmark.data() + bookmark.size())); -} - -void ClipboardMus::WriteWebSmartPaste() { - DCHECK(current_clipboard_); - current_clipboard_.value()[kMimeTypeWebkitSmartPaste] = - std::vector<uint8_t>(); -} - -void ClipboardMus::WriteBitmap(const SkBitmap& bitmap) { - DCHECK(current_clipboard_); - // Encode the bitmap as a PNG for transport. - std::vector<unsigned char> output; - if (gfx::PNGCodec::FastEncodeBGRASkBitmap(bitmap, false, &output)) { - current_clipboard_.value()[ui::mojom::kMimeTypePNG] = std::move(output); - } -} - -void ClipboardMus::WriteData(const FormatType& format, - const char* data_data, - size_t data_len) { - DCHECK(current_clipboard_); - current_clipboard_.value()[GetMimeTypeFor(format)] = - std::vector<uint8_t>(data_data, data_data + data_len); -} - -} // namespace views diff --git a/chromium/ui/views/mus/clipboard_mus.h b/chromium/ui/views/mus/clipboard_mus.h deleted file mode 100644 index 54cbe4abea7..00000000000 --- a/chromium/ui/views/mus/clipboard_mus.h +++ /dev/null @@ -1,89 +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_MUS_CLIPBOARD_MUS_H_ -#define UI_VIEWS_MUS_CLIPBOARD_MUS_H_ - -#include "base/containers/flat_map.h" -#include "services/ui/public/interfaces/clipboard.mojom.h" -#include "ui/base/clipboard/clipboard.h" -#include "ui/views/mus/mus_export.h" - -namespace service_manager { -class Connector; -} - -namespace views { - -// An adaptor class which translates the ui::Clipboard interface to the -// clipboard provided by mus. -class VIEWS_MUS_EXPORT ClipboardMus : public ui::Clipboard { - public: - ClipboardMus(); - ~ClipboardMus() override; - - void Init(service_manager::Connector* connector); - - private: - bool HasMimeType(const std::vector<std::string>& available_types, - const std::string& type) const; - - // Clipboard overrides: - void OnPreShutdown() override; - uint64_t GetSequenceNumber(ui::ClipboardType type) const override; - bool IsFormatAvailable(const FormatType& format, - ui::ClipboardType type) const override; - void Clear(ui::ClipboardType type) override; - void ReadAvailableTypes(ui::ClipboardType type, - std::vector<base::string16>* types, - bool* contains_filenames) const override; - void ReadText(ui::ClipboardType type, base::string16* result) const override; - void ReadAsciiText(ui::ClipboardType type, - std::string* result) const override; - void ReadHTML(ui::ClipboardType type, - base::string16* markup, - std::string* src_url, - uint32_t* fragment_start, - uint32_t* fragment_end) const override; - void ReadRTF(ui::ClipboardType type, std::string* result) const override; - SkBitmap ReadImage(ui::ClipboardType type) const override; - void ReadCustomData(ui::ClipboardType clipboard_type, - const base::string16& type, - base::string16* result) const override; - void ReadBookmark(base::string16* title, std::string* url) const override; - void ReadData(const FormatType& format, std::string* result) const override; - void WriteObjects(ui::ClipboardType type, const ObjectMap& objects) override; - void WriteText(const char* text_data, size_t text_len) override; - void WriteHTML(const char* markup_data, - size_t markup_len, - const char* url_data, - size_t url_len) override; - void WriteRTF(const char* rtf_data, size_t data_len) override; - void WriteBookmark(const char* title_data, - size_t title_len, - const char* url_data, - size_t url_len) override; - void WriteWebSmartPaste() override; - void WriteBitmap(const SkBitmap& bitmap) override; - void WriteData(const FormatType& format, - const char* data_data, - size_t data_len) override; - - static std::string GetMimeTypeFor(const FormatType& format); - - ui::mojom::ClipboardPtr clipboard_; - - // Internal buffer used to accumulate data types. The public interface is - // WriteObjects(), which then calls our base class DispatchObject() which - // then calls into each data type specific Write() function. Once we've - // collected all the data types, we then pass this to the mus server. - base::Optional<base::flat_map<std::string, std::vector<uint8_t>>> - current_clipboard_; - - DISALLOW_COPY_AND_ASSIGN(ClipboardMus); -}; - -} // namespace views - -#endif // UI_VIEWS_MUS_CLIPBOARD_MUS_H_ diff --git a/chromium/ui/views/mus/clipboard_unittest.cc b/chromium/ui/views/mus/clipboard_unittest.cc index b04292e94b7..a00f61588e4 100644 --- a/chromium/ui/views/mus/clipboard_unittest.cc +++ b/chromium/ui/views/mus/clipboard_unittest.cc @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "ui/views/mus/clipboard_mus.h" +#include "ui/base/mojo/clipboard_client.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/events/platform/platform_event_source.h" @@ -11,117 +11,11 @@ namespace ui { -// So we can't make ScopedViewsTestHelper a global. We must set up -// ScopedViewsTestHelper on every test (which will create the connection to -// mus). And we can't modify PlatformClipboardTraits to not be a pure static -// struct. So to solve these lifetime issues, create an adapter that owns the -// ScopedViewsTestHelper and then . -class ForwardingTestingClipboard : public ui::Clipboard { - public: - ForwardingTestingClipboard() - : test_helper_(new views::ScopedViewsTestHelper), - clipboard_to_test_(Clipboard::GetForCurrentThread()) { - // If we don't have a window manager connection, we will get the default - // platform clipboard instead. - EXPECT_TRUE(views::MusClient::Exists()); - } - - ~ForwardingTestingClipboard() override { - Clipboard::DestroyClipboardForCurrentThread(); - } +namespace { - void Destroy() { - delete this; - } +std::unique_ptr<views::ScopedViewsTestHelper> g_scoped_views_test_helper; - protected: - // Overridden from ui::Clipboard: - void OnPreShutdown() override {} - - uint64_t GetSequenceNumber(ClipboardType type) const override { - return clipboard_to_test_->GetSequenceNumber(type); - } - bool IsFormatAvailable(const FormatType& format, - ClipboardType type) const override { - return clipboard_to_test_->IsFormatAvailable(format, type); - } - void Clear(ClipboardType type) override { - clipboard_to_test_->Clear(type); - } - void ReadAvailableTypes(ClipboardType type, - std::vector<base::string16>* types, - bool* contains_filenames) const override { - clipboard_to_test_->ReadAvailableTypes(type, types, contains_filenames); - } - void ReadText(ClipboardType type, base::string16* result) const override { - clipboard_to_test_->ReadText(type, result); - } - void ReadAsciiText(ClipboardType type, std::string* result) const override { - clipboard_to_test_->ReadAsciiText(type, result); - } - void ReadHTML(ClipboardType type, base::string16* markup, - std::string* src_url, uint32_t* fragment_start, - uint32_t* fragment_end) const override { - clipboard_to_test_->ReadHTML(type, markup, src_url, - fragment_start, fragment_end); - } - void ReadRTF(ClipboardType type, std::string* result) const override { - clipboard_to_test_->ReadRTF(type, result); - } - SkBitmap ReadImage(ClipboardType type) const override { - return clipboard_to_test_->ReadImage(type); - } - void ReadCustomData(ClipboardType clipboard_type, - const base::string16& type, - base::string16* result) const override { - clipboard_to_test_->ReadCustomData(clipboard_type, type, result); - } - void ReadBookmark(base::string16* title, std::string* url) const override { - clipboard_to_test_->ReadBookmark(title, url); - } - void ReadData(const FormatType& format, std::string* result) const override { - clipboard_to_test_->ReadData(format, result); - } - void WriteObjects(ClipboardType type, const ObjectMap& objects) override { - clipboard_to_test_->WriteObjects(type, objects); - } - void WriteText(const char* text_data, size_t text_len) override { - clipboard_to_test_->WriteText(text_data, text_len); - } - void WriteHTML(const char* markup_data, - size_t markup_len, - const char* url_data, - size_t url_len) override { - clipboard_to_test_->WriteHTML(markup_data, markup_len, url_data, url_len); - } - void WriteRTF(const char* rtf_data, size_t data_len) override { - clipboard_to_test_->WriteRTF(rtf_data, data_len); - } - void WriteBookmark(const char* title_data, - size_t title_len, - const char* url_data, - size_t url_len) override { - clipboard_to_test_->WriteBookmark(title_data, title_len, - url_data, url_len); - } - void WriteWebSmartPaste() override { - clipboard_to_test_->WriteWebSmartPaste(); - } - void WriteBitmap(const SkBitmap& bitmap) override { - clipboard_to_test_->WriteBitmap(bitmap); - } - void WriteData(const FormatType& format, - const char* data_data, - size_t data_len) override { - clipboard_to_test_->WriteData(format, data_data, data_len); - } - - private: - std::unique_ptr<views::ScopedViewsTestHelper> test_helper_; - ui::Clipboard* clipboard_to_test_; - - DISALLOW_COPY_AND_ASSIGN(ForwardingTestingClipboard); -}; +} // namespace struct PlatformClipboardTraits { static std::unique_ptr<PlatformEventSource> GetEventSource() { @@ -129,13 +23,14 @@ struct PlatformClipboardTraits { } static Clipboard* Create() { - return new ForwardingTestingClipboard(); + g_scoped_views_test_helper = + std::make_unique<views::ScopedViewsTestHelper>(); + EXPECT_TRUE(views::MusClient::Exists()); + return Clipboard::GetForCurrentThread(); } - static bool IsMusTest() { return true; } - static void Destroy(Clipboard* clipboard) { - static_cast<ForwardingTestingClipboard*>(clipboard)->Destroy(); + g_scoped_views_test_helper.reset(); } }; 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 0233bc336cd..67be4ae4d87 100644 --- a/chromium/ui/views/mus/desktop_window_tree_host_mus.cc +++ b/chromium/ui/views/mus/desktop_window_tree_host_mus.cc @@ -21,6 +21,7 @@ #include "ui/base/hit_test.h" #include "ui/display/screen.h" #include "ui/gfx/geometry/dip_util.h" +#include "ui/views/accessibility/view_accessibility.h" #include "ui/views/corewm/tooltip_aura.h" #include "ui/views/mus/mus_client.h" #include "ui/views/mus/mus_property_mirror.h" @@ -46,7 +47,11 @@ namespace { class ClientSideNonClientFrameView : public NonClientFrameView { public: explicit ClientSideNonClientFrameView(views::Widget* widget) - : widget_(widget) {} + : widget_(widget) { + // Not part of the accessibility node hierarchy because the window frame is + // provided by the window manager. + GetViewAccessibility().set_is_ignored(true); + } ~ClientSideNonClientFrameView() override {} private: @@ -57,6 +62,11 @@ class ClientSideNonClientFrameView : public NonClientFrameView { return is_maximized ? values.maximized_insets : values.normal_insets; } + // View: + const char* GetClassName() const override { + return "ClientSideNonClientFrameView"; + } + // NonClientFrameView: gfx::Rect GetBoundsForClientView() const override { gfx::Rect result(GetLocalBounds()); @@ -294,15 +304,10 @@ bool DesktopWindowTreeHostMus::ShouldSendClientAreaToServer() const { } void DesktopWindowTreeHostMus::Init(const Widget::InitParams& params) { - // |TYPE_WINDOW| and |TYPE_PANEL| are forced to transparent as otherwise the - // window is opaque and the client decorations drawn by the window manager - // would not be seen. - const bool transparent = - params.opacity == Widget::InitParams::TRANSLUCENT_WINDOW || - params.type == Widget::InitParams::TYPE_WINDOW || - params.type == Widget::InitParams::TYPE_PANEL; - content_window()->SetTransparent(transparent); - window()->SetTransparent(transparent); + const bool translucent = + MusClient::ShouldMakeWidgetWindowsTranslucent(params); + content_window()->SetTransparent(translucent); + window()->SetTransparent(translucent); window()->SetProperty(aura::client::kShowStateKey, params.show_state); @@ -316,9 +321,40 @@ void DesktopWindowTreeHostMus::Init(const Widget::InitParams& params) { NativeWidgetAura::SetShadowElevationFromInitParams(window(), params); - // Transient parents are connected using the Window created by WindowTreeHost, - // which is owned by the window manager. This way the window manager can - // properly identify and honor transients. + // Widget's |InitParams::parent| has different meanings depending on the + // NativeWidgetPrivate implementation that the Widget creates (each Widget + // creates a NativeWidgetPrivate). When DesktopNativeWidgetAura is used as + // the NativeWidgetPrivate implementation, |InitParams::parent| means the + // entirety of the contents of the new Widget should be stacked above the + // entirety of the contents of the Widget for |InitParams::parent|, and + // the new Widget should be deleted when the Widget for + // |InitParams::parent| is deleted. Aura and mus provide support for + // transient windows, which provides both the stacking and ownership needed to + // support |InitParams::parent|. + // + // DesktopNativeWidgetAura internally creates two aura::Windows (one by + // WindowTreeHost, the other in |DesktopNativeWidgetAura::content_window_|). + // To have the entirety of the contents of the Widget appear on top of the + // entirety of the contents of another Widget, the stacking is done on the + // WindowTreeHost's window. For these reasons, the following code uses the + // Window associated with the WindowTreeHost of the |params.parent|. + // + // Views/Aura provide support for child-modal windows. Child-modal windows + // are windows that are modal to their transient parent. Because this code + // implements |InitParams::parent| in terms of transient parents, it means + // it is not possible to support both |InitParams::parent| as well as a + // child-modal window. This is *only* an issue if a Widget that uses a + // DesktopNativeWidgetAura needs to be child-modal to another window. At + // the current time NativeWidgetAura is always used for child-modal windows, + // so this isn't an issue. + // + // If we end up needing to use DesktopNativeWidgetAura for child-modal + // Widgets then we need something different. Possibilities include: + // . Have mus ignore child-modal windows and instead implement child-modal + // entirely in the client (this is what we do on Windows). To get this + // right likely means we need the ability to disable windows (see + // HWNDMessageHandler::InitModalType() for how Windows OS does this). + // . Implement |InitParams::parent| using a different (new) API. if (params.parent && params.parent->GetHost()) { aura::client::GetTransientWindowClient()->AddTransientChild( params.parent->GetHost()->window(), window()); @@ -367,6 +403,16 @@ void DesktopWindowTreeHostMus::OnWidgetInitDone() { MusClient::Get()->OnCaptureClientSet( aura::client::GetCaptureClient(window())); + + // These views are not part of the accessibility node hierarchy because the + // window frame is provided by the window manager. + Widget* widget = native_widget_delegate_->AsWidget(); + if (widget->non_client_view()) + widget->non_client_view()->GetViewAccessibility().set_is_ignored(true); + if (widget->client_view()) + widget->client_view()->GetViewAccessibility().set_is_ignored(true); + + MusClient::Get()->OnWidgetInitDone(widget); } std::unique_ptr<corewm::Tooltip> DesktopWindowTreeHostMus::CreateTooltip() { @@ -388,6 +434,9 @@ void DesktopWindowTreeHostMus::Close() { // (otherwise events may be processed, which is unexpected). Hide(); + // This has to happen *after* Hide() above, otherwise animations won't work. + content_window()->Hide(); + // Close doesn't delete this immediately, as 'this' may still be on the stack // resulting in possible crashes when the stack unwindes. base::ThreadTaskRunnerHandle::Get()->PostTask( @@ -640,8 +689,14 @@ bool DesktopWindowTreeHostMus::IsVisibleOnAllWorkspaces() const { } bool DesktopWindowTreeHostMus::SetWindowTitle(const base::string16& title) { - if (window()->GetTitle() == title) + WidgetDelegate* widget_delegate = + native_widget_delegate_->AsWidget()->widget_delegate(); + const bool show = widget_delegate && widget_delegate->ShouldShowWindowTitle(); + if (window()->GetTitle() == title && + window()->GetProperty(aura::client::kTitleShownKey) == show) { return false; + } + window()->SetProperty(aura::client::kTitleShownKey, show); window()->SetTitle(title); return true; } @@ -727,6 +782,9 @@ void DesktopWindowTreeHostMus::SetWindowIcons(const gfx::ImageSkia& window_icon, } void DesktopWindowTreeHostMus::InitModalType(ui::ModalType modal_type) { + // See comment in Init() related to |InitParams::parent| as to why this DCHECK + // is here. + DCHECK_NE(modal_type, ui::MODAL_TYPE_CHILD); window()->SetProperty(aura::client::kModalKey, modal_type); } 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 def36405a77..92d4e468695 100644 --- a/chromium/ui/views/mus/desktop_window_tree_host_mus.h +++ b/chromium/ui/views/mus/desktop_window_tree_host_mus.h @@ -114,6 +114,7 @@ class VIEWS_MUS_EXPORT DesktopWindowTreeHostMus void SetFullscreen(bool fullscreen) override; bool IsFullscreen() const override; void SetOpacity(float opacity) override; + void SetAspectRatio(const gfx::SizeF& aspect_ratio) override {} void SetWindowIcons(const gfx::ImageSkia& window_icon, const gfx::ImageSkia& app_icon) override; void InitModalType(ui::ModalType modal_type) override; 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 769904d73e1..79aef70d12d 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 @@ -4,9 +4,7 @@ #include "ui/views/mus/desktop_window_tree_host_mus.h" -#include "base/debug/stack_trace.h" -#include "base/run_loop.h" - +#include "base/strings/utf_string_conversions.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/client/cursor_client.h" #include "ui/aura/client/focus_client.h" @@ -22,6 +20,7 @@ #include "ui/aura/window.h" #include "ui/events/base_event_utils.h" #include "ui/events/event.h" +#include "ui/views/accessibility/view_accessibility.h" #include "ui/views/mus/mus_client.h" #include "ui/views/mus/screen_mus.h" #include "ui/views/test/views_test_base.h" @@ -80,9 +79,7 @@ class ExpectsNullCursorClientDuringTearDown : public aura::WindowObserver { window_->AddObserver(this); } - ~ExpectsNullCursorClientDuringTearDown() override { - EXPECT_FALSE(window_); - } + ~ExpectsNullCursorClientDuringTearDown() override { EXPECT_FALSE(window_); } private: // aura::WindowObserver: @@ -160,7 +157,8 @@ TEST_F(DesktopWindowTreeHostMusTest, Capture) { ->capture_window()); } -TEST_F(DesktopWindowTreeHostMusTest, Deactivate) { +// TODO(http://crbug.com/864614): Fails flakily in mus with ws2. +TEST_F(DesktopWindowTreeHostMusTest, DISABLED_Deactivate) { std::unique_ptr<Widget> widget1(CreateWidget()); widget1->Show(); @@ -260,8 +258,7 @@ TEST_F(DesktopWindowTreeHostMusTest, StackAtTop) { std::unique_ptr<Widget> widget2(CreateWidget()); widget2->Show(); - aura::test::ChangeCompletionWaiter waiter( - aura::ChangeType::REORDER, true); + aura::test::ChangeCompletionWaiter waiter(aura::ChangeType::REORDER, true); widget1->StackAtTop(); waiter.Wait(); @@ -277,21 +274,20 @@ TEST_F(DesktopWindowTreeHostMusTest, StackAtTopAlreadyOnTop) { std::unique_ptr<Widget> widget2(CreateWidget()); widget2->Show(); - aura::test::ChangeCompletionWaiter waiter( - aura::ChangeType::REORDER, true); + aura::test::ChangeCompletionWaiter waiter(aura::ChangeType::REORDER, true); widget2->StackAtTop(); waiter.Wait(); } -TEST_F(DesktopWindowTreeHostMusTest, StackAbove) { +// TODO(http://crbug.com/864615): Fails consistently in mus with ws2. +TEST_F(DesktopWindowTreeHostMusTest, DISABLED_StackAbove) { std::unique_ptr<Widget> widget1(CreateWidget(nullptr)); widget1->Show(); std::unique_ptr<Widget> widget2(CreateWidget(nullptr)); widget2->Show(); - aura::test::ChangeCompletionWaiter waiter( - aura::ChangeType::REORDER, true); + aura::test::ChangeCompletionWaiter waiter(aura::ChangeType::REORDER, true); widget1->StackAboveWidget(widget2.get()); waiter.Wait(); } @@ -396,4 +392,132 @@ TEST_F(DesktopWindowTreeHostMusTest, GetWindowBoundsInScreen) { EXPECT_EQ(gfx::Rect(800, 0, 100, 100), widget2.GetWindowBoundsInScreen()); } +// WidgetDelegate implementation that allows setting window-title and whether +// the title should be shown. +class WindowTitleWidgetDelegate : public WidgetDelegateView { + public: + WindowTitleWidgetDelegate() = default; + ~WindowTitleWidgetDelegate() override = default; + + void set_window_title(const base::string16& title) { window_title_ = title; } + void set_should_show_window_title(bool value) { + should_show_window_title_ = value; + } + + // WidgetDelegateView: + base::string16 GetWindowTitle() const override { return window_title_; } + bool ShouldShowWindowTitle() const override { + return should_show_window_title_; + } + + private: + base::string16 window_title_; + bool should_show_window_title_ = true; + + DISALLOW_COPY_AND_ASSIGN(WindowTitleWidgetDelegate); +}; + +TEST_F(DesktopWindowTreeHostMusTest, WindowTitle) { + // Owned by |widget|. + WindowTitleWidgetDelegate* delegate = new WindowTitleWidgetDelegate(); + std::unique_ptr<Widget> widget(CreateWidget(delegate)); + aura::Window* window = widget->GetNativeWindow()->GetRootWindow(); + + // Set the title in the delegate and verify it propagates. + const base::string16 title1 = base::ASCIIToUTF16("X"); + delegate->set_window_title(title1); + widget->UpdateWindowTitle(); + EXPECT_TRUE(window->GetProperty(aura::client::kTitleShownKey)); + EXPECT_EQ(title1, window->GetTitle()); + + // Hiding the title should not change the title. + delegate->set_should_show_window_title(false); + widget->UpdateWindowTitle(); + EXPECT_FALSE(window->GetProperty(aura::client::kTitleShownKey)); + EXPECT_EQ(title1, window->GetTitle()); + + // Show the title again with a different value. + delegate->set_should_show_window_title(true); + const base::string16 title2 = base::ASCIIToUTF16("Z"); + delegate->set_window_title(title2); + widget->UpdateWindowTitle(); + EXPECT_TRUE(window->GetProperty(aura::client::kTitleShownKey)); + EXPECT_EQ(title2, window->GetTitle()); +} + +TEST_F(DesktopWindowTreeHostMusTest, Accessibility) { + std::unique_ptr<Widget> widget = CreateWidget(); + // Widget frame views do not participate in accessibility node hierarchy + // because the frame is provided by the window manager. + views::NonClientView* non_client_view = widget->non_client_view(); + EXPECT_TRUE(non_client_view->GetViewAccessibility().is_ignored()); + EXPECT_TRUE( + non_client_view->frame_view()->GetViewAccessibility().is_ignored()); + EXPECT_TRUE(widget->client_view()->GetViewAccessibility().is_ignored()); +} + +// Used to ensure the visibility of the root window is changed before that of +// the content window. This is necessary else close/hide animations end up +// animating a hidden (black) window. +class WidgetWindowVisibilityObserver : public aura::WindowObserver { + public: + explicit WidgetWindowVisibilityObserver(Widget* widget) + : content_window_(widget->GetNativeWindow()), + root_window_(content_window_->GetRootWindow()) { + EXPECT_NE(content_window_, root_window_); + content_window_->AddObserver(this); + root_window_->AddObserver(this); + EXPECT_TRUE(content_window_->IsVisible()); + EXPECT_TRUE(root_window_->IsVisible()); + } + + ~WidgetWindowVisibilityObserver() override { + content_window_->RemoveObserver(this); + root_window_->RemoveObserver(this); + } + + bool got_content_window_hidden() const { return got_content_window_hidden_; } + + bool got_root_window_hidden() const { return got_root_window_hidden_; } + + private: + // aura::WindowObserver: + void OnWindowVisibilityChanging(aura::Window* window, bool visible) override { + if (visible) + return; + + if (!got_root_window_hidden_) { + EXPECT_EQ(window, root_window_); + got_root_window_hidden_ = true; + } else if (!got_content_window_hidden_) { + EXPECT_EQ(window, content_window_); + got_content_window_hidden_ = true; + } + } + + aura::Window* content_window_; + aura::Window* root_window_; + + // Set to true when |content_window_| is hidden. This is only checked after + // the |root_window_| is hidden. + bool got_content_window_hidden_ = false; + + // Set to true when |root_window_| is hidden. + bool got_root_window_hidden_ = false; + + DISALLOW_COPY_AND_ASSIGN(WidgetWindowVisibilityObserver); +}; + +// See comments above WidgetWindowVisibilityObserver for details on what this +// verifies. +TEST_F(DesktopWindowTreeHostMusTest, + HideChangesRootWindowVisibilityBeforeContentWindowVisibility) { + std::unique_ptr<Widget> widget(CreateWidget()); + widget->Show(); + WidgetWindowVisibilityObserver observer(widget.get()); + widget->Close(); + EXPECT_TRUE(observer.got_content_window_hidden()); + EXPECT_TRUE(observer.got_root_window_hidden()); +} + } // namespace views diff --git a/chromium/ui/views/mus/drag_interactive_uitest.cc b/chromium/ui/views/mus/drag_interactive_uitest.cc index 50bb37c5d49..d222948c84c 100644 --- a/chromium/ui/views/mus/drag_interactive_uitest.cc +++ b/chromium/ui/views/mus/drag_interactive_uitest.cc @@ -145,7 +145,8 @@ void DragTest_Part1(int64_t display_id, base::BindOnce(&DragTest_Part2, display_id, quit_closure)); } -TEST_F(DragTestInteractive, DragTest) { +// TODO(http://crbug.com/864616): Hangs indefinitely in mus with ws2. +TEST_F(DragTestInteractive, DISABLED_DragTest) { Widget* source_widget = CreateTopLevelFramelessPlatformWidget(); View* source_view = new DraggableView; source_widget->SetContentsView(source_view); diff --git a/chromium/ui/views/mus/interactive_ui_tests_manifest.json b/chromium/ui/views/mus/interactive_ui_tests_manifest.json index a961e49f56c..36a5b66daf1 100644 --- a/chromium/ui/views/mus/interactive_ui_tests_manifest.json +++ b/chromium/ui/views/mus/interactive_ui_tests_manifest.json @@ -4,7 +4,8 @@ "interface_provider_specs": { "service_manager:connector": { "requires": { - "*": [ "app", "test" ] + "*": [ "app", "test" ], + "ui": [ "window_manager" ] } } } diff --git a/chromium/ui/views/mus/mus_client.cc b/chromium/ui/views/mus/mus_client.cc index b8d4dc77bfd..09eb2fcd5bc 100644 --- a/chromium/ui/views/mus/mus_client.cc +++ b/chromium/ui/views/mus/mus_client.cc @@ -24,8 +24,9 @@ #include "ui/aura/mus/window_tree_host_mus_init_params.h" #include "ui/aura/window.h" #include "ui/aura/window_tree_host.h" +#include "ui/base/mojo/clipboard_client.h" #include "ui/views/mus/aura_init.h" -#include "ui/views/mus/clipboard_mus.h" +#include "ui/views/mus/ax_remote_host.h" #include "ui/views/mus/desktop_window_tree_host_mus.h" #include "ui/views/mus/mus_property_mirror.h" #include "ui/views/mus/pointer_watcher_event_router.h" @@ -104,10 +105,8 @@ MusClient::MusClient(const InitParams& params) : identity_(params.identity) { wm_state_ = std::make_unique<wm::WMState>(); service_manager::Connector* connector = params.connector; - if (params.bind_test_ws_interfaces) { - connector->BindInterface(ui::mojom::kServiceName, &server_test_ptr_); + if (params.bind_test_ws_interfaces) connector->BindInterface(ui::mojom::kServiceName, &event_injector_); - } if (!params.window_tree_client) { DCHECK(io_task_runner); @@ -131,11 +130,20 @@ MusClient::MusClient(const InitParams& params) : identity_(params.identity) { input_device_client_->Connect(std::move(input_device_server)); screen_ = std::make_unique<ScreenMus>(this); - screen_->Init(connector); - - std::unique_ptr<ClipboardMus> clipboard = std::make_unique<ClipboardMus>(); - clipboard->Init(connector); - ui::Clipboard::SetClipboardForCurrentThread(std::move(clipboard)); + if (params.wtc_config == aura::WindowTreeClient::Config::kMashDeprecated) + screen_->InitDeprecated(connector); + else + window_tree_client_->WaitForDisplays(); + + ui::mojom::ClipboardHostPtr clipboard_host_ptr; + connector->BindInterface(ui::mojom::kServiceName, &clipboard_host_ptr); + ui::Clipboard::SetClipboardForCurrentThread( + std::make_unique<ui::ClipboardClient>(std::move(clipboard_host_ptr))); + + if (params.use_accessibility_host) { + ax_remote_host_ = std::make_unique<AXRemoteHost>(); + ax_remote_host_->Init(connector); + } } ViewsDelegate::GetInstance()->set_native_widget_factory( @@ -173,6 +181,16 @@ bool MusClient::ShouldCreateDesktopNativeWidgetAura( } // static +bool MusClient::ShouldMakeWidgetWindowsTranslucent( + const Widget::InitParams& params) { + // |TYPE_WINDOW| and |TYPE_PANEL| are forced to translucent so that the + // window manager can draw the client decorations. + return params.opacity == Widget::InitParams::TRANSLUCENT_WINDOW || + params.type == Widget::InitParams::TYPE_WINDOW || + params.type == Widget::InitParams::TYPE_PANEL; +} + +// static std::map<std::string, std::vector<uint8_t>> MusClient::ConfigurePropertiesFromParams( const Widget::InitParams& init_params) { @@ -190,8 +208,8 @@ MusClient::ConfigurePropertiesFromParams( mojo::ConvertTo<TransportType>(init_params.CanActivate()); properties[WindowManager::kTranslucent_InitProperty] = - mojo::ConvertTo<TransportType>(init_params.opacity == - Widget::InitParams::TRANSLUCENT_WINDOW); + mojo::ConvertTo<TransportType>( + ShouldMakeWidgetWindowsTranslucent(init_params)); if (!init_params.bounds.IsEmpty()) { properties[WindowManager::kBounds_InitProperty] = @@ -226,6 +244,18 @@ MusClient::ConfigurePropertiesFromParams( init_params.delegate->GetResizeBehavior())); } + if (init_params.delegate->ShouldShowWindowTitle()) { + properties[WindowManager::kWindowTitleShown_Property] = + mojo::ConvertTo<TransportType>(static_cast<PrimitiveType>( + init_params.delegate->ShouldShowWindowTitle())); + } + + if (!init_params.delegate->GetWindowTitle().empty()) { + properties[WindowManager::kWindowTitle_Property] = + mojo::ConvertTo<TransportType>( + init_params.delegate->GetWindowTitle()); + } + // 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(); @@ -233,6 +263,7 @@ MusClient::ConfigurePropertiesFromParams( properties[WindowManager::kAppIcon_Property] = mojo::ConvertTo<TransportType>(app_bitmap); } + // 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(); @@ -265,6 +296,12 @@ NativeWidget* MusClient::CreateNativeWidget( return native_widget; } +void MusClient::OnWidgetInitDone(Widget* widget) { + // Start tracking the widget for accessibility. + if (ax_remote_host_) + ax_remote_host_->StartMonitoringWidget(widget); +} + void MusClient::OnCaptureClientSet( aura::client::CaptureClient* capture_client) { pointer_watcher_event_router_->AttachToCaptureClient(capture_client); @@ -286,6 +323,7 @@ void MusClient::AddObserver(MusClientObserver* observer) { void MusClient::RemoveObserver(MusClientObserver* observer) { observer_list_.RemoveObserver(observer); } + void MusClient::SetMusPropertyMirror( std::unique_ptr<MusPropertyMirror> mirror) { mus_property_mirror_ = std::move(mirror); @@ -299,13 +337,6 @@ void MusClient::CloseAllWidgets() { } } -ui::mojom::WindowServerTest* MusClient::GetTestingInterface() const { - // This will only be set in tests. CHECK to ensure it doesn't get used - // elsewhere. - CHECK(server_test_ptr_); - return server_test_ptr_.get(); -} - ui::mojom::EventInjector* MusClient::GetTestingEventInjector() const { CHECK(event_injector_); return event_injector_.get(); @@ -345,6 +376,14 @@ void MusClient::OnPointerEventObserved(const ui::PointerEvent& event, target); } +void MusClient::OnDisplaysChanged( + std::vector<ui::mojom::WsDisplayPtr> ws_displays, + int64_t primary_display_id, + int64_t internal_display_id) { + screen_->OnDisplaysChanged(std::move(ws_displays), primary_display_id, + internal_display_id); +} + void MusClient::OnWindowManagerFrameValuesChanged() { for (auto& observer : observer_list_) observer.OnWindowManagerFrameValuesChanged(); diff --git a/chromium/ui/views/mus/mus_client.h b/chromium/ui/views/mus/mus_client.h index 60239f46548..cb3d6b6cef5 100644 --- a/chromium/ui/views/mus/mus_client.h +++ b/chromium/ui/views/mus/mus_client.h @@ -14,7 +14,6 @@ #include "base/macros.h" #include "services/service_manager/public/cpp/identity.h" #include "services/ui/public/interfaces/event_injector.mojom.h" -#include "services/ui/public/interfaces/window_server_test.mojom.h" #include "ui/aura/client/capture_client.h" #include "ui/aura/mus/window_tree_client.h" #include "ui/aura/mus/window_tree_client_delegate.h" @@ -48,6 +47,7 @@ class WMState; namespace views { +class AXRemoteHost; class DesktopNativeWidgetAura; class MusClientObserver; class MusPropertyMirror; @@ -76,7 +76,7 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, service_manager::Identity identity; scoped_refptr<base::SingleThreadTaskRunner> io_task_runner = nullptr; aura::WindowTreeClient::Config wtc_config = - aura::WindowTreeClient::Config::kMash; + aura::WindowTreeClient::Config::kMashDeprecated; // Create a wm::WMState. Some processes (e.g. the browser) may already // have one. @@ -89,6 +89,10 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, // If provided, MusClient will not create the WindowTreeClient. Not owned. // Must outlive MusClient. aura::WindowTreeClient* window_tree_client = nullptr; + + // Connect to the accessibility host service in the browser (e.g. to support + // ChromeVox). + bool use_accessibility_host = false; }; // Most clients should use AuraInit, which creates a MusClient. @@ -104,6 +108,10 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, static bool ShouldCreateDesktopNativeWidgetAura( const Widget::InitParams& init_params); + // Returns true if the windows backing the Widget should be made translucent. + static bool ShouldMakeWidgetWindowsTranslucent( + const Widget::InitParams& params); + // Returns the properties to supply to mus when creating a window. static std::map<std::string, std::vector<uint8_t>> ConfigurePropertiesFromParams(const Widget::InitParams& init_params); @@ -114,6 +122,8 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, return pointer_watcher_event_router_.get(); } + AXRemoteHost* ax_remote_host() { return ax_remote_host_.get(); } + // Getter for type safety. Most code can use display::Screen::GetScreen(). ScreenMus* screen() { return screen_.get(); } @@ -122,6 +132,8 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, // NativeWidget has not been explicitly set. NativeWidget* CreateNativeWidget(const Widget::InitParams& init_params, internal::NativeWidgetDelegate* delegate); + void OnWidgetInitDone(Widget* widget); + // Called when the capture client has been set for a window to notify // PointerWatcherEventRouter and CaptureSynchronizer. void OnCaptureClientSet(aura::client::CaptureClient* capture_client); @@ -141,16 +153,13 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, // Close all widgets this client knows. void CloseAllWidgets(); - // Returns an interface to test drawing in mus. Only available when created - // with MusClientTestingState::CREATE_TESTING_STATE. - ui::mojom::WindowServerTest* GetTestingInterface() const; - // Returns an interface to inject events into the Window Service. Only // available when created with MusClientTestingState::CREATE_TESTING_STATE. ui::mojom::EventInjector* GetTestingEventInjector() const; private: friend class AuraInit; + friend class MusClientTestApi; // Creates a DesktopWindowTreeHostMus. This is set as the factory function // ViewsDelegate such that if DesktopNativeWidgetAura is created without a @@ -169,6 +178,9 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, int64_t display_id, aura::Window* target) override; aura::PropertyConverter* GetPropertyConverter() override; + void OnDisplaysChanged(std::vector<ui::mojom::WsDisplayPtr> ws_displays, + int64_t primary_display_id, + int64_t internal_display_id) override; // ScreenMusDelegate: void OnWindowManagerFrameValuesChanged() override; @@ -206,7 +218,11 @@ class VIEWS_MUS_EXPORT MusClient : public aura::WindowTreeClientDelegate, // Gives services transparent remote access the InputDeviceManager. std::unique_ptr<ui::InputDeviceClient> input_device_client_; - ui::mojom::WindowServerTestPtr server_test_ptr_; + // Forwards accessibility events to extensions in the browser. Can be null for + // apps that do not need accessibility support and for the browser itself + // under OopAsh. + std::unique_ptr<AXRemoteHost> ax_remote_host_; + ui::mojom::EventInjectorPtr event_injector_; DISALLOW_COPY_AND_ASSIGN(MusClient); diff --git a/chromium/ui/views/mus/mus_client_test_api.h b/chromium/ui/views/mus/mus_client_test_api.h new file mode 100644 index 00000000000..e3052276ce8 --- /dev/null +++ b/chromium/ui/views/mus/mus_client_test_api.h @@ -0,0 +1,30 @@ +// 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_MUS_MUS_CLIENT_TEST_API_H_ +#define UI_VIEWS_MUS_MUS_CLIENT_TEST_API_H_ + +#include <memory> +#include <utility> + +#include "base/macros.h" +#include "ui/views/mus/mus_client.h" + +namespace views { + +class AXRemoteHost; + +class MusClientTestApi { + public: + static void SetAXRemoteHost(std::unique_ptr<AXRemoteHost> client) { + MusClient::Get()->ax_remote_host_ = std::move(client); + } + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(MusClientTestApi); +}; + +} // namespace views + +#endif // UI_VIEWS_MUS_MUS_CLIENT_TEST_API_H_ diff --git a/chromium/ui/views/mus/mus_views_delegate.cc b/chromium/ui/views/mus/mus_views_delegate.cc new file mode 100644 index 00000000000..927c065ee5f --- /dev/null +++ b/chromium/ui/views/mus/mus_views_delegate.cc @@ -0,0 +1,23 @@ +// 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/mus/mus_views_delegate.h" + +#include "ui/views/mus/ax_remote_host.h" +#include "ui/views/mus/mus_client.h" + +namespace views { + +MusViewsDelegate::MusViewsDelegate() = default; + +MusViewsDelegate::~MusViewsDelegate() = default; + +void MusViewsDelegate::NotifyAccessibilityEvent(View* view, + ax::mojom::Event event_type) { + // Null in AuraInit::Mode::AURA_MUS_WINDOW_MANAGER which is used in mash. + if (MusClient::Get() && MusClient::Get()->ax_remote_host()) + MusClient::Get()->ax_remote_host()->HandleEvent(view, event_type); +} + +} // namespace views diff --git a/chromium/ui/views/mus/mus_views_delegate.h b/chromium/ui/views/mus/mus_views_delegate.h new file mode 100644 index 00000000000..b7d55503ac1 --- /dev/null +++ b/chromium/ui/views/mus/mus_views_delegate.h @@ -0,0 +1,32 @@ +// 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_MUS_MUS_VIEWS_DELEGATE_H_ +#define UI_VIEWS_MUS_MUS_VIEWS_DELEGATE_H_ + +#include "base/macros.h" +#include "ui/views/layout/layout_provider.h" +#include "ui/views/mus/mus_export.h" +#include "ui/views/views_delegate.h" + +namespace views { + +class VIEWS_MUS_EXPORT MusViewsDelegate : public ViewsDelegate { + public: + MusViewsDelegate(); + ~MusViewsDelegate() override; + + // ViewsDelegate: + void NotifyAccessibilityEvent(View* view, + ax::mojom::Event event_type) override; + + private: + LayoutProvider layout_provider_; + + DISALLOW_COPY_AND_ASSIGN(MusViewsDelegate); +}; + +} // namespace views + +#endif // UI_VIEWS_MUS_MUS_VIEWS_DELEGATE_H_ diff --git a/chromium/ui/views/mus/remote_view/BUILD.gn b/chromium/ui/views/mus/remote_view/BUILD.gn index a5232997d72..8b333326484 100644 --- a/chromium/ui/views/mus/remote_view/BUILD.gn +++ b/chromium/ui/views/mus/remote_view/BUILD.gn @@ -8,7 +8,7 @@ source_set("remote_view_host") { "remote_view_host.h", ] - deps = [ + public_deps = [ "//base", "//ui/aura", "//ui/views", diff --git a/chromium/ui/views/mus/screen_mus.cc b/chromium/ui/views/mus/screen_mus.cc index 8c8dd07f7e9..1238f81aefe 100644 --- a/chromium/ui/views/mus/screen_mus.cc +++ b/chromium/ui/views/mus/screen_mus.cc @@ -47,7 +47,7 @@ ScreenMus::~ScreenMus() { display::Screen::SetScreenInstance(nullptr); } -void ScreenMus::Init(service_manager::Connector* connector) { +void ScreenMus::InitDeprecated(service_manager::Connector* connector) { connector->BindInterface(ui::mojom::kServiceName, &screen_provider_); ui::mojom::ScreenProviderObserverPtr observer; @@ -72,28 +72,6 @@ void ScreenMus::Init(service_manager::Connector* connector) { } } -display::Display ScreenMus::GetDisplayNearestWindow( - gfx::NativeWindow window) const { - aura::WindowTreeHostMus* window_tree_host_mus = - aura::WindowTreeHostMus::ForWindow(window); - if (!window_tree_host_mus) - return GetPrimaryDisplay(); - return window_tree_host_mus->GetDisplay(); -} - -gfx::Point ScreenMus::GetCursorScreenPoint() { - return aura::Env::GetInstance()->last_mouse_location(); -} - -bool ScreenMus::IsWindowUnderCursor(gfx::NativeWindow window) { - return window && window->IsVisible() && - window->GetBoundsInScreen().Contains(GetCursorScreenPoint()); -} - -aura::Window* ScreenMus::GetWindowAtScreenPoint(const gfx::Point& point) { - return delegate_->GetWindowAtScreenPoint(point); -} - void ScreenMus::OnDisplaysChanged( std::vector<ui::mojom::WsDisplayPtr> ws_displays, int64_t primary_display_id, @@ -154,4 +132,26 @@ void ScreenMus::OnDisplaysChanged( } } +display::Display ScreenMus::GetDisplayNearestWindow( + gfx::NativeWindow window) const { + aura::WindowTreeHostMus* window_tree_host_mus = + aura::WindowTreeHostMus::ForWindow(window); + if (!window_tree_host_mus) + return GetPrimaryDisplay(); + return window_tree_host_mus->GetDisplay(); +} + +gfx::Point ScreenMus::GetCursorScreenPoint() { + return aura::Env::GetInstance()->last_mouse_location(); +} + +bool ScreenMus::IsWindowUnderCursor(gfx::NativeWindow window) { + return window && window->IsVisible() && + window->GetBoundsInScreen().Contains(GetCursorScreenPoint()); +} + +aura::Window* ScreenMus::GetWindowAtScreenPoint(const gfx::Point& point) { + return delegate_->GetWindowAtScreenPoint(point); +} + } // namespace views diff --git a/chromium/ui/views/mus/screen_mus.h b/chromium/ui/views/mus/screen_mus.h index 42c4d150573..ad94f9f1090 100644 --- a/chromium/ui/views/mus/screen_mus.h +++ b/chromium/ui/views/mus/screen_mus.h @@ -25,7 +25,13 @@ class VIEWS_MUS_EXPORT ScreenMus : public display::ScreenBase, explicit ScreenMus(ScreenMusDelegate* delegate); ~ScreenMus() override; - void Init(service_manager::Connector* connector); + // TODO(sky): not used with ws2. Remove. https://crbug.com/842365. + void InitDeprecated(service_manager::Connector* connector); + + // ui::mojom::ScreenProviderObserver: + void OnDisplaysChanged(std::vector<ui::mojom::WsDisplayPtr> ws_displays, + int64_t primary_display_id, + int64_t internal_display_id) override; private: friend class ScreenMusTestApi; @@ -37,11 +43,6 @@ class VIEWS_MUS_EXPORT ScreenMus : public display::ScreenBase, bool IsWindowUnderCursor(gfx::NativeWindow window) override; aura::Window* GetWindowAtScreenPoint(const gfx::Point& point) override; - // ui::mojom::ScreenProvider: - void OnDisplaysChanged(std::vector<ui::mojom::WsDisplayPtr> ws_displays, - int64_t primary_display_id, - int64_t internal_display_id) override; - ScreenMusDelegate* delegate_; ui::mojom::ScreenProviderPtr screen_provider_; mojo::Binding<ui::mojom::ScreenProviderObserver> diff --git a/chromium/ui/views/mus/views_mus_test_suite.cc b/chromium/ui/views/mus/views_mus_test_suite.cc index 304d7625316..959f5ad7ede 100644 --- a/chromium/ui/views/mus/views_mus_test_suite.cc +++ b/chromium/ui/views/mus/views_mus_test_suite.cc @@ -15,8 +15,8 @@ #include "base/synchronization/waitable_event.h" #include "base/threading/simple_thread.h" #include "base/threading/thread.h" -#include "mojo/edk/embedder/embedder.h" -#include "mojo/edk/embedder/scoped_ipc_support.h" +#include "mojo/core/embedder/embedder.h" +#include "mojo/core/embedder/scoped_ipc_support.h" #include "services/catalog/catalog.h" #include "services/service_manager/background/background_service_manager.h" #include "services/service_manager/public/cpp/connector.h" @@ -72,12 +72,12 @@ class ServiceManagerConnection { ipc_thread_("IPC thread") { catalog::Catalog::LoadDefaultCatalogManifest( base::FilePath(kCatalogFilename)); - mojo::edk::Init(); + mojo::core::Init(); ipc_thread_.StartWithOptions( base::Thread::Options(base::MessageLoop::TYPE_IO, 0)); - ipc_support_ = std::make_unique<mojo::edk::ScopedIPCSupport>( + ipc_support_ = std::make_unique<mojo::core::ScopedIPCSupport>( ipc_thread_.task_runner(), - mojo::edk::ScopedIPCSupport::ShutdownPolicy::CLEAN); + mojo::core::ScopedIPCSupport::ShutdownPolicy::CLEAN); base::WaitableEvent wait(base::WaitableEvent::ResetPolicy::AUTOMATIC, base::WaitableEvent::InitialState::NOT_SIGNALED); @@ -104,6 +104,7 @@ class ServiceManagerConnection { params.connector = GetConnector(); params.identity = service_manager_identity_; params.bind_test_ws_interfaces = true; + params.wtc_config = aura::WindowTreeClient::Config::kMus2; return std::make_unique<MusClient>(params); } @@ -136,11 +137,7 @@ class ServiceManagerConnection { service_manager::Identity( GetTestName(), service_manager::mojom::kRootUserID), std::move(service), nullptr); - - // ui/views/mus requires a WindowManager running, so launch test_wm. - service_manager::Connector* connector = context_->connector(); - connector->StartService("test_wm"); - service_manager_connector_ = connector->Clone(); + service_manager_connector_ = context_->connector()->Clone(); service_manager_identity_ = context_->identity(); wait->Signal(); } @@ -150,19 +147,18 @@ class ServiceManagerConnection { wait->Signal(); } - // Returns the name of the test executable, e.g. - // "views_mus_unittests". + // Returns the name of the test executable, e.g. "views_mus_unittests". std::string GetTestName() { base::FilePath executable = base::CommandLine::ForCurrentProcess() ->GetProgram() .BaseName() .RemoveExtension(); - return std::string("") + executable.MaybeAsASCII(); + return executable.MaybeAsASCII(); } base::Thread thread_; base::Thread ipc_thread_; - std::unique_ptr<mojo::edk::ScopedIPCSupport> ipc_support_; + std::unique_ptr<mojo::core::ScopedIPCSupport> ipc_support_; std::unique_ptr<service_manager::BackgroundServiceManager> background_service_manager_; std::unique_ptr<service_manager::ServiceContext> context_; diff --git a/chromium/ui/views/mus/window_manager_constants_converters.h b/chromium/ui/views/mus/window_manager_constants_converters.h index 25764a8c474..e9493eb6c84 100644 --- a/chromium/ui/views/mus/window_manager_constants_converters.h +++ b/chromium/ui/views/mus/window_manager_constants_converters.h @@ -5,7 +5,7 @@ #ifndef UI_VIEWS_MUS_WINDOW_MANAGER_CONSTANTS_CONVERTERS_H_ #define UI_VIEWS_MUS_WINDOW_MANAGER_CONSTANTS_CONVERTERS_H_ -#include "services/ui/public/interfaces/window_manager_constants.mojom.h" +#include "services/ui/public/interfaces/window_tree_constants.mojom.h" #include "ui/views/mus/mus_export.h" #include "ui/views/widget/widget.h" diff --git a/chromium/ui/views/painter.cc b/chromium/ui/views/painter.cc index d93eacd40f1..24bb9e2a566 100644 --- a/chromium/ui/views/painter.cc +++ b/chromium/ui/views/painter.cc @@ -28,7 +28,10 @@ namespace { // of the background. class SolidRoundRectPainter : public Painter { public: - SolidRoundRectPainter(SkColor bg_color, SkColor stroke_color, float radius); + SolidRoundRectPainter(SkColor bg_color, + SkColor stroke_color, + float radius, + const gfx::Insets& insets); ~SolidRoundRectPainter() override; // Painter: @@ -39,14 +42,19 @@ class SolidRoundRectPainter : public Painter { const SkColor bg_color_; const SkColor stroke_color_; const float radius_; + const gfx::Insets insets_; DISALLOW_COPY_AND_ASSIGN(SolidRoundRectPainter); }; SolidRoundRectPainter::SolidRoundRectPainter(SkColor bg_color, SkColor stroke_color, - float radius) - : bg_color_(bg_color), stroke_color_(stroke_color), radius_(radius) {} + float radius, + const gfx::Insets& insets) + : bg_color_(bg_color), + stroke_color_(stroke_color), + radius_(radius), + insets_(insets) {} SolidRoundRectPainter::~SolidRoundRectPainter() {} @@ -58,7 +66,9 @@ void SolidRoundRectPainter::Paint(gfx::Canvas* canvas, const gfx::Size& size) { gfx::ScopedCanvas scoped_canvas(canvas); const float scale = canvas->UndoDeviceScaleFactor(); - gfx::RectF border_rect_f(gfx::ScaleToEnclosingRect(gfx::Rect(size), scale)); + 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); cc::PaintFlags flags; @@ -253,10 +263,12 @@ void Painter::PaintFocusPainter(View* view, } // static -std::unique_ptr<Painter> Painter::CreateSolidRoundRectPainter(SkColor color, - float radius) { +std::unique_ptr<Painter> Painter::CreateSolidRoundRectPainter( + SkColor color, + float radius, + const gfx::Insets& insets) { return std::make_unique<SolidRoundRectPainter>(color, SK_ColorTRANSPARENT, - radius); + radius, insets); } // static @@ -264,8 +276,8 @@ std::unique_ptr<Painter> Painter::CreateRoundRectWith1PxBorderPainter( SkColor bg_color, SkColor stroke_color, float radius) { - return std::make_unique<SolidRoundRectPainter>(bg_color, stroke_color, - radius); + return std::make_unique<SolidRoundRectPainter>(bg_color, stroke_color, radius, + gfx::Insets()); } // static diff --git a/chromium/ui/views/painter.h b/chromium/ui/views/painter.h index cd9672304d8..cb3f495dc8d 100644 --- a/chromium/ui/views/painter.h +++ b/chromium/ui/views/painter.h @@ -13,12 +13,12 @@ #include "base/macros.h" #include "third_party/skia/include/core/SkColor.h" #include "ui/base/nine_image_painter_factory.h" +#include "ui/gfx/geometry/insets.h" #include "ui/views/views_export.h" namespace gfx { class Canvas; class ImageSkia; -class Insets; class InsetsF; class Rect; class Size; @@ -54,8 +54,10 @@ class VIEWS_EXPORT Painter { // Creates a painter that draws a RoundRect with a solid color and given // corner radius. - static std::unique_ptr<Painter> CreateSolidRoundRectPainter(SkColor color, - float radius); + static std::unique_ptr<Painter> CreateSolidRoundRectPainter( + SkColor color, + float radius, + const gfx::Insets& insets = gfx::Insets()); // 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. diff --git a/chromium/ui/views/run_all_unittests_main.cc b/chromium/ui/views/run_all_unittests_main.cc index 51d21871cbc..5e7810477a4 100644 --- a/chromium/ui/views/run_all_unittests_main.cc +++ b/chromium/ui/views/run_all_unittests_main.cc @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "mojo/edk/embedder/embedder.h" +#include "mojo/core/embedder/embedder.h" #include "ui/views/views_test_suite.h" int main(int argc, char** argv) { - mojo::edk::Init(); + mojo::core::Init(); return views::ViewsTestSuite(argc, argv).RunTests(); } diff --git a/chromium/ui/views/style/platform_style.cc b/chromium/ui/views/style/platform_style.cc index ef68fb58bbb..564d6e6440b 100644 --- a/chromium/ui/views/style/platform_style.cc +++ b/chromium/ui/views/style/platform_style.cc @@ -40,6 +40,11 @@ const bool PlatformStyle::kIsOkButtonLeading = true; const bool PlatformStyle::kIsOkButtonLeading = false; #endif +// Set kFocusHaloInset to negative half of kFocusHaloThickness to draw half of +// the focus ring inside and half outside the parent elmeent +const float PlatformStyle::kFocusHaloThickness = 2.f; +const float PlatformStyle::kFocusHaloInset = -1.f; + #if !defined(OS_MACOSX) const int PlatformStyle::kMinLabelButtonWidth = 70; @@ -57,9 +62,6 @@ const bool PlatformStyle::kUseRipples = true; const bool PlatformStyle::kTextfieldScrollsToStartOnFocusChange = false; const bool PlatformStyle::kTextfieldUsesDragCursorWhenDraggable = true; const bool PlatformStyle::kShouldElideBookmarksInBookmarksBar = false; -const float PlatformStyle::kFocusHaloThickness = 2.f; -const float PlatformStyle::kFocusHaloInset = - -PlatformStyle::kFocusHaloThickness; const bool PlatformStyle::kPreferFocusRings = false; // static diff --git a/chromium/ui/views/style/platform_style_mac.mm b/chromium/ui/views/style/platform_style_mac.mm index 2106dccefa2..945f4b2efcf 100644 --- a/chromium/ui/views/style/platform_style_mac.mm +++ b/chromium/ui/views/style/platform_style_mac.mm @@ -42,8 +42,6 @@ const bool PlatformStyle::kTextfieldUsesDragCursorWhenDraggable = false; const bool PlatformStyle::kTreeViewSelectionPaintsEntireRow = true; const bool PlatformStyle::kShouldElideBookmarksInBookmarksBar = true; const bool PlatformStyle::kUseRipples = false; -const float PlatformStyle::kFocusHaloThickness = 4.f; -const float PlatformStyle::kFocusHaloInset = -2.f; const bool PlatformStyle::kPreferFocusRings = true; const Button::NotifyAction PlatformStyle::kMenuNotifyActivationAction = diff --git a/chromium/ui/views/style/typography_provider.cc b/chromium/ui/views/style/typography_provider.cc index e208380993d..2f0f2718059 100644 --- a/chromium/ui/views/style/typography_provider.cc +++ b/chromium/ui/views/style/typography_provider.cc @@ -115,7 +115,7 @@ void DefaultTypographyProvider::GetDefaultFont(int context, *size_delta = ui::kTitleFontSizeDelta; break; case style::CONTEXT_TOUCH_MENU: - *size_delta = -1; + *size_delta = 2; break; default: *size_delta = ui::kLabelFontSizeDelta; diff --git a/chromium/ui/views/touchui/touch_selection_menu_runner_views.cc b/chromium/ui/views/touchui/touch_selection_menu_runner_views.cc index 3b283a149a9..df2048dfd10 100644 --- a/chromium/ui/views/touchui/touch_selection_menu_runner_views.cc +++ b/chromium/ui/views/touchui/touch_selection_menu_runner_views.cc @@ -131,6 +131,11 @@ TouchSelectionMenuRunnerViews::Menu::Menu(TouchSelectionMenuRunnerViews* owner, bounds.AdjustToFit(work_area); widget->SetBounds(bounds); } + // Using BubbleDialogDelegateView engages its CreateBubbleWidget() which + // invokes widget->StackAbove(context). That causes the bubble to stack + // _immediately_ above |context|; below any already-existing bubbles. That + // doesn't make sense for a menu, so put it back on top. + widget->StackAtTop(); widget->Show(); } diff --git a/chromium/ui/views/view.cc b/chromium/ui/views/view.cc index 655e745ee61..c53f79d424e 100644 --- a/chromium/ui/views/view.cc +++ b/chromium/ui/views/view.cc @@ -325,6 +325,8 @@ void View::SetBoundsRect(const gfx::Rect& bounds) { if (bounds == bounds_) { if (needs_layout_) { needs_layout_ = false; + TRACE_EVENT1("views", "View::Layout(set_bounds)", "class", + GetClassName()); Layout(); } return; @@ -1419,12 +1421,14 @@ bool View::HandleAccessibleAction(const ui::AXActionData& action_data) { break; case ax::mojom::Action::kDoDefault: { const gfx::Point center = GetLocalBounds().CenterPoint(); - OnMousePressed(ui::MouseEvent( - ui::ET_MOUSE_PRESSED, center, center, ui::EventTimeForNow(), - ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); - OnMouseReleased(ui::MouseEvent( - ui::ET_MOUSE_RELEASED, center, center, ui::EventTimeForNow(), - ui::EF_LEFT_MOUSE_BUTTON, ui::EF_LEFT_MOUSE_BUTTON)); + ui::MouseEvent press(ui::ET_MOUSE_PRESSED, center, center, + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON); + OnEvent(&press); + ui::MouseEvent release(ui::ET_MOUSE_RELEASED, center, center, + ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, + ui::EF_LEFT_MOUSE_BUTTON); + OnEvent(&release); return true; } case ax::mojom::Action::kFocus: @@ -2185,6 +2189,8 @@ void View::BoundsChanged(const gfx::Rect& previous_bounds) { if (needs_layout_ || previous_bounds.size() != size()) { needs_layout_ = false; + TRACE_EVENT1("views", "View::Layout(bounds_changed)", "class", + GetClassName()); Layout(); } diff --git a/chromium/ui/views/view_model.cc b/chromium/ui/views/view_model.cc index b0e49c2ece5..baa6cfb2b1b 100644 --- a/chromium/ui/views/view_model.cc +++ b/chromium/ui/views/view_model.cc @@ -67,8 +67,7 @@ int ViewModelBase::GetIndexOfView(const View* view) const { return -1; } -ViewModelBase::ViewModelBase() { -} +ViewModelBase::ViewModelBase() = default; void ViewModelBase::AddUnsafe(View* view, int index) { DCHECK_LE(index, static_cast<int>(entries_.size())); diff --git a/chromium/ui/views/views_delegate.cc b/chromium/ui/views/views_delegate.cc index 7b303dac473..846e579237c 100644 --- a/chromium/ui/views/views_delegate.cc +++ b/chromium/ui/views/views_delegate.cc @@ -80,6 +80,10 @@ HICON ViewsDelegate::GetDefaultWindowIcon() const { return nullptr; } +HICON ViewsDelegate::GetSmallWindowIcon() const { + return nullptr; +} + bool ViewsDelegate::IsWindowInMetro(gfx::NativeWindow window) const { return false; } @@ -100,6 +104,10 @@ void ViewsDelegate::AddRef() { void ViewsDelegate::ReleaseRef() { } +void ViewsDelegate::OnBeforeWidgetInit( + Widget::InitParams* params, + internal::NativeWidgetDelegate* delegate) {} + base::TimeDelta ViewsDelegate::GetTextfieldPasswordRevealDuration() { return base::TimeDelta(); } diff --git a/chromium/ui/views/views_delegate.h b/chromium/ui/views/views_delegate.h index 8f703482084..3f16d177c21 100644 --- a/chromium/ui/views/views_delegate.h +++ b/chromium/ui/views/views_delegate.h @@ -151,7 +151,7 @@ class VIEWS_EXPORT ViewsDelegate { // Retrieves the default window icon to use for windows if none is specified. virtual HICON GetDefaultWindowIcon() const; // Retrieves the small window icon to use for windows if none is specified. - virtual HICON GetSmallWindowIcon() const = 0; + virtual HICON GetSmallWindowIcon() const; // Returns true if the window passed in is in the Windows 8 metro // environment. virtual bool IsWindowInMetro(gfx::NativeWindow window) const; @@ -171,7 +171,7 @@ class VIEWS_EXPORT ViewsDelegate { // Gives the platform a chance to modify the properties of a Widget. virtual void OnBeforeWidgetInit(Widget::InitParams* params, - internal::NativeWidgetDelegate* delegate) = 0; + internal::NativeWidgetDelegate* delegate); // Returns the password reveal duration for Textfield. virtual base::TimeDelta GetTextfieldPasswordRevealDuration(); diff --git a/chromium/ui/views/views_perftests.cc b/chromium/ui/views/views_perftests.cc index 6740c887c41..3e06f188cb0 100644 --- a/chromium/ui/views/views_perftests.cc +++ b/chromium/ui/views/views_perftests.cc @@ -3,12 +3,12 @@ // found in the LICENSE file. #include "base/test/launcher/unit_test_launcher.h" -#include "mojo/edk/embedder/embedder.h" +#include "mojo/core/embedder/embedder.h" #include "ui/views/views_test_suite.h" int main(int argc, char** argv) { views::ViewsTestSuite test_suite(argc, argv); - mojo::edk::Init(); + mojo::core::Init(); return base::LaunchUnitTestsSerially( argc, argv, base::BindOnce(&views::ViewsTestSuite::Run, diff --git a/chromium/ui/views/widget/DEPS b/chromium/ui/views/widget/DEPS index 74d84ca36a1..a244cf72baf 100644 --- a/chromium/ui/views/widget/DEPS +++ b/chromium/ui/views/widget/DEPS @@ -1,5 +1,5 @@ specific_include_rules = { "widget_interactive_uitest\.cc": [ - "+mojo/edk/embedder", + "+mojo/core/embedder", ] } diff --git a/chromium/ui/views/widget/native_widget_mac_accessibility_unittest.mm b/chromium/ui/views/widget/ax_native_widget_mac_unittest.mm index a6baee39626..7e79906a819 100644 --- a/chromium/ui/views/widget/native_widget_mac_accessibility_unittest.mm +++ b/chromium/ui/views/widget/ax_native_widget_mac_unittest.mm @@ -96,9 +96,9 @@ class TestWidgetDelegate : public test::TestDesktopWidgetDelegate { constexpr char TestWidgetDelegate::kAccessibleWindowTitle[]; -class NativeWidgetMacAccessibilityTest : public test::WidgetTest { +class AXNativeWidgetMacTest : public test::WidgetTest { public: - NativeWidgetMacAccessibilityTest() {} + AXNativeWidgetMacTest() {} void SetUp() override { test::WidgetTest::SetUp(); @@ -198,14 +198,14 @@ class NativeWidgetMacAccessibilityTest : public test::WidgetTest { private: TestWidgetDelegate widget_delegate_; - DISALLOW_COPY_AND_ASSIGN(NativeWidgetMacAccessibilityTest); + DISALLOW_COPY_AND_ASSIGN(AXNativeWidgetMacTest); }; } // namespace // Test that all methods in the NSAccessibility informal protocol can be called // on a retained accessibility object after the source view is deleted. -TEST_F(NativeWidgetMacAccessibilityTest, Lifetime) { +TEST_F(AXNativeWidgetMacTest, Lifetime) { Textfield* view = AddChildTextfield(widget()->GetContentsView()->size()); base::scoped_nsobject<NSObject> ax_node(view->GetNativeViewAccessible(), base::scoped_policy::RETAIN); @@ -286,7 +286,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, Lifetime) { } // Check that potentially keyboard-focusable elements are always leaf nodes. -TEST_F(NativeWidgetMacAccessibilityTest, FocusableElementsAreLeafNodes) { +TEST_F(AXNativeWidgetMacTest, FocusableElementsAreLeafNodes) { // LabelButtons will have a label inside the button. The label should be // ignored because the button is potentially keyboard focusable. TestLabelButton* button = new TestLabelButton(); @@ -323,7 +323,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, FocusableElementsAreLeafNodes) { // Test for NSAccessibilityChildrenAttribute, and ensure it excludes ignored // children from the accessibility tree. -TEST_F(NativeWidgetMacAccessibilityTest, ChildrenAttribute) { +TEST_F(AXNativeWidgetMacTest, ChildrenAttribute) { // Check childless views don't have accessibility children. EXPECT_EQ(0u, [AttributeValueAtMidpoint(NSAccessibilityChildrenAttribute) count]); @@ -346,7 +346,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, ChildrenAttribute) { // Test for NSAccessibilityParentAttribute, including for a Widget with no // parent. -TEST_F(NativeWidgetMacAccessibilityTest, ParentAttribute) { +TEST_F(AXNativeWidgetMacTest, ParentAttribute) { Textfield* child = AddChildTextfield(widget()->GetContentsView()->size()); // Views with Widget parents will have a NSWindow parent. @@ -373,7 +373,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, ParentAttribute) { // Test for NSAccessibilityPositionAttribute, including on Widget movement // updates. -TEST_F(NativeWidgetMacAccessibilityTest, PositionAttribute) { +TEST_F(AXNativeWidgetMacTest, PositionAttribute) { NSValue* widget_origin = [NSValue valueWithPoint:gfx::ScreenPointToNSPoint( GetWidgetBounds().bottom_left())]; @@ -390,7 +390,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, PositionAttribute) { } // Test for NSAccessibilityHelpAttribute. -TEST_F(NativeWidgetMacAccessibilityTest, HelpAttribute) { +TEST_F(AXNativeWidgetMacTest, HelpAttribute) { Label* label = new Label(base::SysNSStringToUTF16(kTestStringValue)); label->SetSize(GetWidgetBounds().size()); EXPECT_NSEQ(@"", AttributeValueAtMidpoint(NSAccessibilityHelpAttribute)); @@ -402,7 +402,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, HelpAttribute) { // Test view properties that should report the native NSWindow, and test // specific properties on that NSWindow. -TEST_F(NativeWidgetMacAccessibilityTest, NativeWindowProperties) { +TEST_F(AXNativeWidgetMacTest, NativeWindowProperties) { FlexibleRoleTestView* view = new FlexibleRoleTestView(ax::mojom::Role::kGroup); view->SetSize(GetWidgetBounds().size()); @@ -422,7 +422,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, NativeWindowProperties) { // Tests for accessibility attributes on a views::Textfield. // TODO(patricialor): Test against Cocoa-provided attributes as well to ensure // consistency between Cocoa and toolkit-views. -TEST_F(NativeWidgetMacAccessibilityTest, TextfieldGenericAttributes) { +TEST_F(AXNativeWidgetMacTest, TextfieldGenericAttributes) { Textfield* textfield = AddChildTextfield(GetWidgetBounds().size()); // NSAccessibilityEnabledAttribute. @@ -480,7 +480,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, TextfieldGenericAttributes) { NSAccessibilitySizeAttribute) sizeValue])); } -TEST_F(NativeWidgetMacAccessibilityTest, TextfieldEditableAttributes) { +TEST_F(AXNativeWidgetMacTest, TextfieldEditableAttributes) { Textfield* textfield = AddChildTextfield(GetWidgetBounds().size()); textfield->set_placeholder_text( base::SysNSStringToUTF16(kTestPlaceholderText)); @@ -539,7 +539,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, TextfieldEditableAttributes) { // Test writing accessibility attributes via an accessibility client for normal // Views. -TEST_F(NativeWidgetMacAccessibilityTest, ViewWritableAttributes) { +TEST_F(AXNativeWidgetMacTest, ViewWritableAttributes) { FlexibleRoleTestView* view = new FlexibleRoleTestView(ax::mojom::Role::kGroup); view->SetSize(GetWidgetBounds().size()); @@ -566,7 +566,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, ViewWritableAttributes) { // Test writing accessibility attributes via an accessibility client for // editable controls (in this case, views::Textfields). -TEST_F(NativeWidgetMacAccessibilityTest, TextfieldWritableAttributes) { +TEST_F(AXNativeWidgetMacTest, TextfieldWritableAttributes) { Textfield* textfield = AddChildTextfield(GetWidgetBounds().size()); // Get the Textfield accessibility object. @@ -668,7 +668,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, TextfieldWritableAttributes) { } // Test parameterized text attributes. -TEST_F(NativeWidgetMacAccessibilityTest, TextParameterizedAttributes) { +TEST_F(AXNativeWidgetMacTest, TextParameterizedAttributes) { AddChildTextfield(GetWidgetBounds().size()); id ax_node = A11yElementAtMidpoint(); EXPECT_TRUE(ax_node); @@ -723,7 +723,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, TextParameterizedAttributes) { } // Test performing a 'click' on Views with clickable roles work. -TEST_F(NativeWidgetMacAccessibilityTest, PressAction) { +TEST_F(AXNativeWidgetMacTest, PressAction) { FlexibleRoleTestView* view = new FlexibleRoleTestView(ax::mojom::Role::kButton); widget()->GetContentsView()->AddChildView(view); @@ -740,7 +740,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, PressAction) { // Test text-specific attributes that should not be supported for protected // textfields. -TEST_F(NativeWidgetMacAccessibilityTest, ProtectedTextfields) { +TEST_F(AXNativeWidgetMacTest, ProtectedTextfields) { Textfield* textfield = AddChildTextfield(GetWidgetBounds().size()); textfield->SetTextInputType(ui::TEXT_INPUT_TYPE_PASSWORD); @@ -776,9 +776,8 @@ TEST_F(NativeWidgetMacAccessibilityTest, ProtectedTextfields) { if (base::mac::IsAtLeastOS10_10()) { // Check Cocoa's attribute values for PlaceHolder and Value here separately // - these are using the new NSAccessibility protocol. - EXPECT_TRUE([cocoa_secure_textfield - isAccessibilitySelectorAllowed:@selector( - accessibilityPlaceholderValue)]); + EXPECT_TRUE([cocoa_secure_textfield isAccessibilitySelectorAllowed:@selector + (accessibilityPlaceholderValue)]); EXPECT_TRUE([cocoa_secure_textfield isAccessibilitySelectorAllowed:@selector(accessibilityValue)]); } @@ -795,8 +794,9 @@ TEST_F(NativeWidgetMacAccessibilityTest, ProtectedTextfields) { [ax_node accessibilityIsAttributeSettable:NSAccessibilityValueAttribute]); EXPECT_NSEQ(NSAccessibilityTextFieldRole, AXRoleString()); - NSString* kShownValue = @"•" - @"••••••••••••••••"; + NSString* kShownValue = + @"•" + @"••••••••••••••••"; // Sanity check. EXPECT_EQ(kTestStringLength, static_cast<int>([kShownValue length])); EXPECT_NSEQ(kShownValue, AXValue()); @@ -825,7 +825,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, ProtectedTextfields) { } // Test text-specific attributes of Labels. -TEST_F(NativeWidgetMacAccessibilityTest, Label) { +TEST_F(AXNativeWidgetMacTest, Label) { Label* label = new Label; label->SetText(base::SysNSStringToUTF16(kTestStringValue)); label->SetSize(GetWidgetBounds().size()); @@ -872,7 +872,7 @@ TEST_F(NativeWidgetMacAccessibilityTest, Label) { } // Labels used as title bars should be exposed as normal static text on Mac. -TEST_F(NativeWidgetMacAccessibilityTest, LabelUsedAsTitleBar) { +TEST_F(AXNativeWidgetMacTest, LabelUsedAsTitleBar) { Label* label = new Label(base::SysNSStringToUTF16(kTestStringValue), style::CONTEXT_DIALOG_TITLE, style::STYLE_PRIMARY); label->SetSize(GetWidgetBounds().size()); @@ -902,7 +902,7 @@ class TestComboboxModel : public ui::ComboboxModel { }; // Test a11y attributes of Comboboxes. -TEST_F(NativeWidgetMacAccessibilityTest, Combobox) { +TEST_F(AXNativeWidgetMacTest, Combobox) { Combobox* combobox = new Combobox(std::make_unique<TestComboboxModel>()); combobox->SetSize(GetWidgetBounds().size()); widget()->GetContentsView()->AddChildView(combobox); 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 42c838cb065..b482b7781c4 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 @@ -8,7 +8,7 @@ #include "base/macros.h" #include "base/trace_event/trace_event.h" #include "build/build_config.h" -#include "services/ui/public/interfaces/window_manager_constants.mojom.h" +#include "services/ui/public/interfaces/window_tree_constants.mojom.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/client/cursor_client.h" #include "ui/aura/client/drag_drop_client.h" @@ -737,7 +737,6 @@ void DesktopNativeWidgetAura::Close() { return; content_window_->SuppressPaint(); - content_window_->Hide(); desktop_window_tree_host_->Close(); } @@ -789,8 +788,20 @@ bool DesktopNativeWidgetAura::IsVisible() const { } void DesktopNativeWidgetAura::Activate() { - if (content_window_) + if (content_window_) { + bool was_active = IsActive(); desktop_window_tree_host_->Activate(); + + // If the whole window tree host was already active, + // treat this as a request to focus |content_window_|. + // + // Note: it might make sense to always focus |content_window_|, + // since if client code is calling Widget::Activate() they probably + // want that particular widget to be activated, not just something + // within that widget hierarchy. + if (was_active && focus_client_->GetFocusedWindow() != content_window_) + focus_client_->FocusWindow(content_window_); + } } void DesktopNativeWidgetAura::Deactivate() { @@ -858,6 +869,11 @@ void DesktopNativeWidgetAura::SetOpacity(float opacity) { desktop_window_tree_host_->SetOpacity(opacity); } +void DesktopNativeWidgetAura::SetAspectRatio(const gfx::SizeF& aspect_ratio) { + if (content_window_) + desktop_window_tree_host_->SetAspectRatio(aspect_ratio); +} + void DesktopNativeWidgetAura::FlashFrame(bool flash_frame) { if (content_window_) desktop_window_tree_host_->FlashFrame(flash_frame); diff --git a/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.h b/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.h index be8fd0378c6..40d5b1bfb7e 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.h +++ b/chromium/ui/views/widget/desktop_aura/desktop_native_widget_aura.h @@ -164,6 +164,7 @@ class VIEWS_EXPORT DesktopNativeWidgetAura void SetFullscreen(bool fullscreen) override; bool IsFullscreen() const override; void SetOpacity(float opacity) override; + void SetAspectRatio(const gfx::SizeF& aspect_ratio) override; void FlashFrame(bool flash_frame) override; void RunShellDrag(View* view, const ui::OSExchangeData& data, 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 8fd5d379d34..e90710c2c39 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11.cc @@ -216,8 +216,13 @@ display::Display DesktopScreenX11::GetDisplayNearestWindow( if (host) { DesktopWindowTreeHostX11* rwh = DesktopWindowTreeHostX11::GetHostForXID( host->GetAcceleratedWidget()); - if (rwh) - return GetDisplayMatching(rwh->GetX11RootWindowBounds()); + if (rwh) { + const float scale = 1.0f / GetDeviceScaleFactor(); + const gfx::Rect pixel_rect = rwh->GetX11RootWindowBounds(); + return GetDisplayMatching( + gfx::Rect(gfx::ScaleToFlooredPoint(pixel_rect.origin(), scale), + gfx::ScaleToCeiledSize(pixel_rect.size(), scale))); + } } return GetPrimaryDisplay(); diff --git a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11_unittest.cc b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11_unittest.cc index 25c923b7e75..546411eaf7e 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_screen_x11_unittest.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_screen_x11_unittest.cc @@ -9,7 +9,7 @@ #include <memory> #include "base/macros.h" -#include "services/ui/public/interfaces/window_manager_constants.mojom.h" +#include "services/ui/public/interfaces/window_tree_constants.mojom.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host.h b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host.h index 437a0294a4d..a6e2eca3abd 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host.h +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host.h @@ -141,6 +141,8 @@ class VIEWS_EXPORT DesktopWindowTreeHost { virtual void SetOpacity(float opacity) = 0; + virtual void SetAspectRatio(const gfx::SizeF& aspect_ratio) = 0; + virtual void SetWindowIcons(const gfx::ImageSkia& window_icon, const gfx::ImageSkia& app_icon) = 0; 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 60d2c66cca0..2081478b174 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 @@ -10,13 +10,48 @@ #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_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/widget_aura_utils.h" #include "ui/views/window/native_frame_view.h" #include "ui/wm/core/window_util.h" namespace views { +namespace { + +ui::PlatformWindowInitProperties ConvertWidgetInitParamsToInitProperties( + const Widget::InitParams& params) { + ui::PlatformWindowInitProperties properties; + + switch (params.type) { + case Widget::InitParams::TYPE_WINDOW: + properties.type = ui::PlatformWindowType::kWindow; + break; + + case Widget::InitParams::TYPE_MENU: + properties.type = ui::PlatformWindowType::kMenu; + break; + + case Widget::InitParams::TYPE_TOOLTIP: + properties.type = ui::PlatformWindowType::kTooltip; + break; + + default: + properties.type = ui::PlatformWindowType::kPopup; + break; + } + + properties.bounds = params.bounds; + + if (params.parent && params.parent->GetHost()) + properties.parent_widget = params.parent->GetHost()->GetAcceleratedWidget(); + + return properties; +} + +} // namespace //////////////////////////////////////////////////////////////////////////////// // DesktopWindowTreeHostPlatform: @@ -41,7 +76,10 @@ void DesktopWindowTreeHostPlatform::SetBoundsInDIP( } void DesktopWindowTreeHostPlatform::Init(const Widget::InitParams& params) { - CreateAndSetDefaultPlatformWindow(); + ui::PlatformWindowInitProperties properties = + ConvertWidgetInitParamsToInitProperties(params); + + CreateAndSetPlatformWindow(std::move(properties)); CreateCompositor(viz::FrameSinkId(), params.force_software_compositing); aura::WindowTreeHost::OnAcceleratedWidgetAvailable(); InitHost(); @@ -76,6 +114,8 @@ void DesktopWindowTreeHostPlatform::Close() { if (waiting_for_close_now_) return; + desktop_native_widget_aura_->content_window()->Hide(); + // Hide while waiting for the close. // Please note that it's better to call WindowTreeHost::Hide, which also calls // PlatformWindow::Hide and Compositor::SetVisible(false). 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 551bcb598b8..cac4832e63a 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 @@ -77,6 +77,7 @@ class VIEWS_EXPORT DesktopWindowTreeHostPlatform void SetFullscreen(bool fullscreen) override; bool IsFullscreen() const override; void SetOpacity(float opacity) override; + void SetAspectRatio(const gfx::SizeF& aspect_ratio) override {} void SetWindowIcons(const gfx::ImageSkia& window_icon, const gfx::ImageSkia& app_icon) override; void InitModalType(ui::ModalType modal_type) override; diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc index 288b92c3ead..9c6b588516f 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc @@ -21,6 +21,7 @@ #include "ui/display/win/screen_win.h" #include "ui/events/keyboard_hook.h" #include "ui/events/keycodes/dom/dom_code.h" +#include "ui/events/keycodes/dom/dom_keyboard_layout_map.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/native_widget_types.h" @@ -173,6 +174,8 @@ DesktopWindowTreeHostWin::CreateDragDropClient( } void DesktopWindowTreeHostWin::Close() { + content_window()->Hide(); + // TODO(beng): Move this entire branch to DNWA so it can be shared with X11. if (should_animate_window_close_) { pending_close_ = true; @@ -446,6 +449,12 @@ void DesktopWindowTreeHostWin::SetOpacity(float opacity) { content_window()->layer()->SetOpacity(opacity); } +void DesktopWindowTreeHostWin::SetAspectRatio(const gfx::SizeF& aspect_ratio) { + DCHECK(!aspect_ratio.IsEmpty()); + message_handler_->SetAspectRatio(aspect_ratio.width() / + aspect_ratio.height()); +} + void DesktopWindowTreeHostWin::SetWindowIcons( const gfx::ImageSkia& window_icon, const gfx::ImageSkia& app_icon) { message_handler_->SetWindowIcons(window_icon, app_icon); @@ -587,8 +596,7 @@ bool DesktopWindowTreeHostWin::IsKeyLocked(ui::DomCode dom_code) { base::flat_map<std::string, std::string> DesktopWindowTreeHostWin::GetKeyboardLayoutMap() { - NOTIMPLEMENTED(); - return {}; + return ui::GenerateDomKeyboardLayoutMap(); } void DesktopWindowTreeHostWin::SetCursorNative(gfx::NativeCursor cursor) { diff --git a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h index 327e4fb5df8..022de4037ff 100644 --- a/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h +++ b/chromium/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h @@ -110,6 +110,7 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin void SetFullscreen(bool fullscreen) override; bool IsFullscreen() const override; void SetOpacity(float opacity) override; + void SetAspectRatio(const gfx::SizeF& aspect_ratio) override; void SetWindowIcons(const gfx::ImageSkia& window_icon, const gfx::ImageSkia& app_icon) override; void InitModalType(ui::ModalType modal_type) override; 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 cbfbfcbdcdb..22a9914957a 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 @@ -40,7 +40,6 @@ #include "ui/events/keycodes/dom/dom_code.h" #include "ui/events/null_event_targeter.h" #include "ui/events/platform/platform_event_source.h" -#include "ui/events/platform/x11/x11_event_source.h" #include "ui/gfx/geometry/insets.h" #include "ui/gfx/geometry/size_conversions.h" #include "ui/gfx/image/image_skia.h" @@ -451,6 +450,8 @@ DesktopWindowTreeHostX11::CreateDragDropClient( } void DesktopWindowTreeHostX11::Close() { + content_window()->Hide(); + // TODO(erg): Might need to do additional hiding tasks here. delayed_resize_task_.Cancel(); @@ -799,11 +800,10 @@ bool DesktopWindowTreeHostX11::IsActive() const { } void DesktopWindowTreeHostX11::Maximize() { - if (ui::HasWMSpecProperty(window_properties_, + if (ui::HasWMSpecProperty(window_properties_in_server_, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"))) { // Unfullscreen the window if it is fullscreen. - ui::SetWMSpecState(xwindow_, false, - gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"), x11::None); + SetWMSpecState(false, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"), x11::None); // Resize the window so that it does not have the same size as a monitor. // (Otherwise, some window managers immediately put the window back in @@ -823,9 +823,8 @@ void DesktopWindowTreeHostX11::Maximize() { // heuristics that are in the PropertyNotify and ConfigureNotify handlers. restored_bounds_in_pixels_ = bounds_in_pixels_; - ui::SetWMSpecState(xwindow_, true, - gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"), - gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")); + SetWMSpecState(true, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"), + gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")); if (IsMinimized()) ShowWindowWithState(ui::SHOW_STATE_NORMAL); } @@ -837,22 +836,21 @@ void DesktopWindowTreeHostX11::Minimize() { void DesktopWindowTreeHostX11::Restore() { should_maximize_after_map_ = false; - ui::SetWMSpecState(xwindow_, false, - gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"), - gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")); + SetWMSpecState(false, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT"), + gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ")); if (IsMinimized()) ShowWindowWithState(ui::SHOW_STATE_NORMAL); } bool DesktopWindowTreeHostX11::IsMaximized() const { - return (ui::HasWMSpecProperty(window_properties_, + return (ui::HasWMSpecProperty(window_properties_in_server_, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_VERT")) && - ui::HasWMSpecProperty(window_properties_, + ui::HasWMSpecProperty(window_properties_in_server_, gfx::GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ"))); } bool DesktopWindowTreeHostX11::IsMinimized() const { - return ui::HasWMSpecProperty(window_properties_, + return ui::HasWMSpecProperty(window_properties_in_server_, gfx::GetAtom("_NET_WM_STATE_HIDDEN")); } @@ -862,8 +860,7 @@ bool DesktopWindowTreeHostX11::HasCapture() const { void DesktopWindowTreeHostX11::SetAlwaysOnTop(bool always_on_top) { is_always_on_top_ = always_on_top; - ui::SetWMSpecState(xwindow_, always_on_top, - gfx::GetAtom("_NET_WM_STATE_ABOVE"), x11::None); + SetWMSpecState(always_on_top, gfx::GetAtom("_NET_WM_STATE_ABOVE"), x11::None); } bool DesktopWindowTreeHostX11::IsAlwaysOnTop() const { @@ -878,8 +875,8 @@ void DesktopWindowTreeHostX11::SetVisible(bool visible) { } void DesktopWindowTreeHostX11::SetVisibleOnAllWorkspaces(bool always_visible) { - ui::SetWMSpecState(xwindow_, always_visible, - gfx::GetAtom("_NET_WM_STATE_STICKY"), x11::None); + SetWMSpecState(always_visible, gfx::GetAtom("_NET_WM_STATE_STICKY"), + x11::None); int new_desktop = 0; if (always_visible) { @@ -1018,8 +1015,8 @@ void DesktopWindowTreeHostX11::SetFullscreen(bool fullscreen) { if (unmaximize_and_remaximize) Restore(); - ui::SetWMSpecState(xwindow_, fullscreen, - gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"), x11::None); + SetWMSpecState(fullscreen, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN"), + x11::None); if (unmaximize_and_remaximize) Maximize(); @@ -1039,7 +1036,7 @@ void DesktopWindowTreeHostX11::SetFullscreen(bool fullscreen) { OnHostMovedInPixels(bounds_in_pixels_.origin()); OnHostResizedInPixels(bounds_in_pixels_.size()); - if (ui::HasWMSpecProperty(window_properties_, + if (ui::HasWMSpecProperty(window_properties_in_server_, gfx::GetAtom("_NET_WM_STATE_FULLSCREEN")) == fullscreen) { Relayout(); @@ -1075,6 +1072,18 @@ void DesktopWindowTreeHostX11::SetOpacity(float opacity) { } } +void DesktopWindowTreeHostX11::SetAspectRatio(const gfx::SizeF& aspect_ratio) { + XSizeHints size_hints; + size_hints.flags = 0; + long supplied_return; + + XGetWMNormalHints(xdisplay_, xwindow_, &size_hints, &supplied_return); + size_hints.flags |= PAspect; + size_hints.min_aspect.x = size_hints.max_aspect.x = aspect_ratio.width(); + size_hints.min_aspect.y = size_hints.max_aspect.y = aspect_ratio.height(); + XSetWMNormalHints(xdisplay_, xwindow_, &size_hints); +} + void DesktopWindowTreeHostX11::SetWindowIcons( const gfx::ImageSkia& window_icon, const gfx::ImageSkia& app_icon) { // TODO(erg): The way we handle icons across different versions of chrome @@ -1542,6 +1551,8 @@ void DesktopWindowTreeHostX11::InitX11Window( // SetWMSpecState) has no effect here since the window has not yet been // mapped. So we manually change the state. if (!state_atom_list.empty()) { + DCHECK(window_properties_in_client_.empty()); + window_properties_in_client_ = state_atom_list; ui::SetAtomArrayProperty(xwindow_, "_NET_WM_STATE", "ATOM", @@ -1639,6 +1650,21 @@ gfx::Size DesktopWindowTreeHostX11::AdjustSize( return size_in_pixels; } +void DesktopWindowTreeHostX11::SetWMSpecState(bool enabled, + XAtom state1, + XAtom state2) { + if (IsVisible()) + ui::SetWMSpecState(xwindow_, enabled, state1, state2); + for (XAtom atom : {state1, state2}) { + if (atom != x11::None) { + if (enabled) + window_properties_in_client_.insert(atom); + else + window_properties_in_client_.erase(atom); + } + } +} + void DesktopWindowTreeHostX11::OnWMStateUpdated() { std::vector< ::Atom> atom_list; // Ignore the return value of gfx::GetAtomArrayProperty(). Fluxbox removes the @@ -1648,9 +1674,10 @@ void DesktopWindowTreeHostX11::OnWMStateUpdated() { bool was_minimized = IsMinimized(); bool was_maximized = IsMaximized(); - window_properties_.clear(); + window_properties_in_server_.clear(); std::copy(atom_list.begin(), atom_list.end(), - inserter(window_properties_, window_properties_.begin())); + inserter(window_properties_in_server_, + window_properties_in_server_.begin())); bool is_minimized = IsMinimized(); bool is_maximized = IsMaximized(); @@ -1699,7 +1726,7 @@ void DesktopWindowTreeHostX11::OnWMStateUpdated() { // do preprocessing before the x window's fullscreen state is toggled. is_always_on_top_ = ui::HasWMSpecProperty( - window_properties_, gfx::GetAtom("_NET_WM_STATE_ABOVE")); + window_properties_in_server_, gfx::GetAtom("_NET_WM_STATE_ABOVE")); if (was_maximized != is_maximized) OnMaximizedStateChanged(); @@ -1955,13 +1982,13 @@ void DesktopWindowTreeHostX11::MapWindow(ui::WindowShowState show_state) { 1); } - ui::X11EventSource* event_source = ui::X11EventSource::GetInstance(); - DCHECK(event_source); - UpdateMinAndMaxSize(); XMapWindow(xdisplay_, xwindow_); window_mapped_in_client_ = true; + + for (XAtom atom : window_properties_in_client_) + ui::SetWMSpecState(xwindow_, true, atom, x11::None); } void DesktopWindowTreeHostX11::SetWindowTransparency() { 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 f424728b967..c5e6df7ce81 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 @@ -146,6 +146,7 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 void SetFullscreen(bool fullscreen) override; bool IsFullscreen() const override; void SetOpacity(float opacity) override; + void SetAspectRatio(const gfx::SizeF& aspect_ratio) override; void SetWindowIcons(const gfx::ImageSkia& window_icon, const gfx::ImageSkia& app_icon) override; void InitModalType(ui::ModalType modal_type) override; @@ -191,6 +192,7 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 private: friend class DesktopWindowTreeHostX11HighDPITest; + // Initializes our X11 surface to draw on. This method performs all // initialization related to talking to the X11 server. void InitX11Window(const Widget::InitParams& params); @@ -204,6 +206,11 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 // fullscreen. gfx::Size AdjustSize(const gfx::Size& requested_size); + // If mapped, sends a message to the window manager to enable or disable the + // states |state1| and |state2|. Otherwise, the states will be enabled or + // disabled on the next map. + void SetWMSpecState(bool enabled, XAtom state1, XAtom state2); + // Called when |xwindow_|'s _NET_WM_STATE property is updated. void OnWMStateUpdated(); @@ -336,8 +343,12 @@ class VIEWS_EXPORT DesktopWindowTreeHostX11 // _NET_WM_DESKTOP is unset. base::Optional<int> workspace_; - // The window manager state bits. - base::flat_set<::Atom> window_properties_; + // The window manager state bits as indicated by the server. May be + // out-of-sync. May include bits set by non-Chrome apps. + base::flat_set<::Atom> window_properties_in_server_; + + // The window manager state bits that Chrome has set. + base::flat_set<::Atom> window_properties_in_client_; // Whether |xwindow_| was requested to be fullscreen via SetFullscreen(). bool is_fullscreen_; 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 2d3170ca5cd..e2d9e3e8a6f 100644 --- a/chromium/ui/views/widget/desktop_aura/window_event_filter.cc +++ b/chromium/ui/views/widget/desktop_aura/window_event_filter.cc @@ -4,7 +4,7 @@ #include "ui/views/widget/desktop_aura/window_event_filter.h" -#include "services/ui/public/interfaces/window_manager_constants.mojom.h" +#include "services/ui/public/interfaces/window_tree_constants.mojom.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" #include "ui/aura/window_delegate.h" diff --git a/chromium/ui/views/widget/desktop_aura/x11_window_event_filter.cc b/chromium/ui/views/widget/desktop_aura/x11_window_event_filter.cc index ccde928898a..3d5ecdc2cfe 100644 --- a/chromium/ui/views/widget/desktop_aura/x11_window_event_filter.cc +++ b/chromium/ui/views/widget/desktop_aura/x11_window_event_filter.cc @@ -4,7 +4,7 @@ #include "ui/views/widget/desktop_aura/x11_window_event_filter.h" -#include "services/ui/public/interfaces/window_manager_constants.mojom.h" +#include "services/ui/public/interfaces/window_tree_constants.mojom.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/window.h" #include "ui/aura/window_delegate.h" diff --git a/chromium/ui/views/widget/native_widget_aura.cc b/chromium/ui/views/widget/native_widget_aura.cc index 8c0bbd9db9b..0b3370a18ac 100644 --- a/chromium/ui/views/widget/native_widget_aura.cc +++ b/chromium/ui/views/widget/native_widget_aura.cc @@ -11,7 +11,6 @@ #include "base/threading/thread_task_runner_handle.h" #include "build/build_config.h" #include "services/ui/public/interfaces/window_manager.mojom.h" -#include "services/ui/public/interfaces/window_manager_constants.mojom.h" #include "services/ui/public/interfaces/window_tree_constants.mojom.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/client/capture_client.h" @@ -201,13 +200,15 @@ void NativeWidgetAura::InitNativeWidget(const Widget::InitParams& params) { } // SetAlwaysOnTop before SetParent so that always-on-top container is used. SetAlwaysOnTop(params.keep_on_top); + // Make sure we have a real |window_bounds|. - if (parent && window_bounds == gfx::Rect()) { - // If a parent is specified but no bounds are given, - // use the origin of the parent's display so that the widget - // will be added to the same display as the parent. + aura::Window* parent_or_context = parent ? parent : context; + if (parent_or_context && window_bounds == gfx::Rect()) { + // If a parent or context is specified but no bounds are given, use the + // origin of the display so that the widget will be added to the same + // display as the parent or context. gfx::Rect bounds = display::Screen::GetScreen() - ->GetDisplayNearestWindow(parent) + ->GetDisplayNearestWindow(parent_or_context) .bounds(); window_bounds.set_origin(bounds.origin()); } @@ -671,6 +672,16 @@ void NativeWidgetAura::SetOpacity(float opacity) { window_->layer()->SetOpacity(opacity); } +void NativeWidgetAura::SetAspectRatio(const gfx::SizeF& aspect_ratio) { + DCHECK(!aspect_ratio.IsEmpty()); + if (window_) { + // aura::client::kAspectRatio is owned, which allows for passing in this + // raw pointer. + window_->SetProperty(aura::client::kAspectRatio, + new gfx::SizeF(aspect_ratio)); + } +} + void NativeWidgetAura::FlashFrame(bool flash) { if (window_) window_->SetProperty(aura::client::kDrawAttentionKey, flash); @@ -1065,12 +1076,6 @@ void Widget::CloseAllSecondaryWidgets() { #endif } -bool Widget::ConvertRect(const Widget* source, - const Widget* target, - gfx::Rect* rect) { - return false; -} - const ui::NativeTheme* Widget::GetNativeTheme() const { #if defined(USE_X11) const LinuxUI* linux_ui = LinuxUI::instance(); diff --git a/chromium/ui/views/widget/native_widget_aura.h b/chromium/ui/views/widget/native_widget_aura.h index 74bd738bcf9..b0276c0158b 100644 --- a/chromium/ui/views/widget/native_widget_aura.h +++ b/chromium/ui/views/widget/native_widget_aura.h @@ -123,6 +123,7 @@ class VIEWS_EXPORT NativeWidgetAura : public internal::NativeWidgetPrivate, void SetFullscreen(bool fullscreen) override; bool IsFullscreen() const override; void SetOpacity(float opacity) override; + void SetAspectRatio(const gfx::SizeF& aspect_ratio) override; void FlashFrame(bool flash_frame) override; void RunShellDrag(View* view, const ui::OSExchangeData& data, diff --git a/chromium/ui/views/widget/native_widget_aura_unittest.cc b/chromium/ui/views/widget/native_widget_aura_unittest.cc index e767025b6fc..b1ef3a6805f 100644 --- a/chromium/ui/views/widget/native_widget_aura_unittest.cc +++ b/chromium/ui/views/widget/native_widget_aura_unittest.cc @@ -9,7 +9,7 @@ #include "base/command_line.h" #include "base/macros.h" #include "base/run_loop.h" -#include "services/ui/public/interfaces/window_manager_constants.mojom.h" +#include "services/ui/public/interfaces/window_tree_constants.mojom.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/client/aura_constants.h" #include "ui/aura/env.h" diff --git a/chromium/ui/views/widget/native_widget_mac.h b/chromium/ui/views/widget/native_widget_mac.h index c1d97e3ac54..d515251a082 100644 --- a/chromium/ui/views/widget/native_widget_mac.h +++ b/chromium/ui/views/widget/native_widget_mac.h @@ -51,6 +51,9 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { // from the bottom of the window. virtual int SheetPositionY(); + // Notifies that the widget starts to enter or exit fullscreen mode. + virtual void OnWindowFullscreenStateChange() {} + // internal::NativeWidgetPrivate: void InitNativeWidget(const Widget::InitParams& params) override; void OnWidgetInitDone() override; @@ -113,6 +116,7 @@ class VIEWS_EXPORT NativeWidgetMac : public internal::NativeWidgetPrivate { void SetFullscreen(bool fullscreen) override; bool IsFullscreen() const override; void SetOpacity(float opacity) override; + void SetAspectRatio(const gfx::SizeF& aspect_ratio) override; void FlashFrame(bool flash_frame) override; void RunShellDrag(View* view, const ui::OSExchangeData& data, diff --git a/chromium/ui/views/widget/native_widget_mac.mm b/chromium/ui/views/widget/native_widget_mac.mm index c0e28737b88..3cf9af6b957 100644 --- a/chromium/ui/views/widget/native_widget_mac.mm +++ b/chromium/ui/views/widget/native_widget_mac.mm @@ -8,8 +8,7 @@ #include <utility> -#include "base/command_line.h" -#import "base/mac/bind_objc_block.h" +#include "base/bind.h" #include "base/mac/foundation_util.h" #include "base/mac/scoped_nsobject.h" #include "base/strings/sys_string_conversions.h" @@ -17,7 +16,6 @@ #include "components/crash/core/common/crash_key.h" #import "ui/base/cocoa/constrained_window/constrained_window_animation.h" #import "ui/base/cocoa/window_size_constants.h" -#include "ui/base/ui_base_switches.h" #include "ui/display/display.h" #include "ui/display/screen.h" #include "ui/gfx/font_list.h" @@ -50,11 +48,6 @@ namespace views { namespace { -bool AreModalAnimationsEnabled() { - return !base::CommandLine::ForCurrentProcess()->HasSwitch( - switches::kDisableModalAnimations); -} - 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 @@ -392,13 +385,17 @@ void NativeWidgetMac::Close() { // 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. - [NSApp performSelector:@selector(endSheet:) withObject:window afterDelay:0]; + // 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 (bridge_->animate() && AreModalAnimationsEnabled() && - delegate_->IsModal()) { + if (bridge_->ShouldRunCustomAnimationFor(Widget::ANIMATE_HIDE)) { [ViewsNSWindowCloseAnimator closeWindowWithAnimation:window]; return; } @@ -416,9 +413,10 @@ void NativeWidgetMac::Close() { // 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::BindBlock(^{ - [window close]; - })); + base::ThreadTaskRunnerHandle::Get()->PostTask( + FROM_HERE, base::BindOnce(base::RetainBlock(^{ + [window close]; + }))); } void NativeWidgetMac::CloseNow() { @@ -557,6 +555,11 @@ void NativeWidgetMac::SetOpacity(float opacity) { [GetNativeWindow() setAlphaValue:opacity]; } +void NativeWidgetMac::SetAspectRatio(const gfx::SizeF& aspect_ratio) { + [GetNativeWindow() setContentAspectRatio:NSMakeSize(aspect_ratio.width(), + aspect_ratio.height())]; +} + void NativeWidgetMac::FlashFrame(bool flash_frame) { NOTIMPLEMENTED(); } @@ -623,7 +626,7 @@ void NativeWidgetMac::EndMoveLoop() { void NativeWidgetMac::SetVisibilityChangedAnimationsEnabled(bool value) { if (bridge_) - bridge_->set_animate(value); + bridge_->SetAnimationEnabled(value); } void NativeWidgetMac::SetVisibilityAnimationDuration( @@ -633,7 +636,8 @@ void NativeWidgetMac::SetVisibilityAnimationDuration( void NativeWidgetMac::SetVisibilityAnimationTransition( Widget::VisibilityTransition transition) { - NOTIMPLEMENTED(); + if (bridge_) + bridge_->set_transitions_to_animate(transition); } bool NativeWidgetMac::IsTranslucentWindowOpacitySupported() const { @@ -694,12 +698,6 @@ void Widget::CloseAllSecondaryWidgets() { } } -bool Widget::ConvertRect(const Widget* source, - const Widget* target, - gfx::Rect* rect) { - return false; -} - const ui::NativeTheme* Widget::GetNativeTheme() const { return ui::NativeTheme::GetInstanceForNativeUi(); } diff --git a/chromium/ui/views/widget/native_widget_mac_interactive_uitest.mm b/chromium/ui/views/widget/native_widget_mac_interactive_uitest.mm index faa01ce577b..f6d68885a92 100644 --- a/chromium/ui/views/widget/native_widget_mac_interactive_uitest.mm +++ b/chromium/ui/views/widget/native_widget_mac_interactive_uitest.mm @@ -13,6 +13,7 @@ #import "ui/base/test/windowed_nsnotification_observer.h" #import "ui/events/test/cocoa_test_event_utils.h" #include "ui/views/bubble/bubble_dialog_delegate.h" +#include "ui/views/controls/textfield/textfield.h" #include "ui/views/test/test_widget_observer.h" #include "ui/views/test/views_interactive_ui_test_base.h" #include "ui/views/test/widget_test.h" @@ -282,6 +283,40 @@ TEST_F(NativeWidgetMacInteractiveUITest, BubbleDismiss) { parent_widget->CloseNow(); } +// Ensure BridgedContentView's inputContext can handle its window being torn +// away mid-way through event processing. Toolkit-views guarantees to move focus +// away from any Widget when the window is torn down. This test ensures that +// global references AppKit may have held on to are also updated. +TEST_F(NativeWidgetMacInteractiveUITest, GlobalNSTextInputContextUpdates) { + Widget* widget = CreateNativeDesktopWidget(); + Textfield* textfield = new Textfield; + textfield->SetBounds(0, 0, 100, 100); + widget->GetContentsView()->AddChildView(textfield); + textfield->RequestFocus(); + { + WidgetActivationWaiter wait_for_first_active(widget, true); + widget->Show(); + wait_for_first_active.Wait(); + } + EXPECT_TRUE([widget->GetNativeView() inputContext]); + EXPECT_EQ([widget->GetNativeView() inputContext], + [NSTextInputContext currentInputContext]); + + widget->GetContentsView()->RemoveChildView(textfield); + + // NSTextInputContext usually only updates at the end of an AppKit event loop + // iteration. We just tore out the inputContext, so ensure the raw, weak + // global pointer that AppKit likes to keep around has been updated manually. + EXPECT_EQ(nil, [NSTextInputContext currentInputContext]); + EXPECT_FALSE([widget->GetNativeView() inputContext]); + + // RemoveChildView() doesn't delete the view. + delete textfield; + + widget->Close(); + base::RunLoop().RunUntilIdle(); +} + INSTANTIATE_TEST_CASE_P(NativeWidgetMacInteractiveUITestInstance, NativeWidgetMacInteractiveUITest, ::testing::Bool()); diff --git a/chromium/ui/views/widget/native_widget_mac_unittest.mm b/chromium/ui/views/widget/native_widget_mac_unittest.mm index e8c6ebeeaa7..8fcb213c579 100644 --- a/chromium/ui/views/widget/native_widget_mac_unittest.mm +++ b/chromium/ui/views/widget/native_widget_mac_unittest.mm @@ -23,6 +23,7 @@ #import "ui/base/cocoa/constrained_window/constrained_window_animation.h" #import "ui/base/cocoa/window_size_constants.h" #import "ui/base/test/scoped_fake_full_keyboard_access.h" +#include "ui/compositor/recyclable_compositor_mac.h" #import "ui/events/test/cocoa_test_event_utils.h" #include "ui/events/test/event_generator.h" #import "ui/gfx/mac/coordinate_conversion.h" @@ -59,9 +60,11 @@ @interface NativeWidgetMacTestWindow : NativeWidgetMacNSWindow { @private int invalidateShadowCount_; + int orderWindowCount_; bool* deallocFlag_; } @property(readonly, nonatomic) int invalidateShadowCount; +@property(readonly, nonatomic) int orderWindowCount; @property(assign, nonatomic) bool* deallocFlag; @end @@ -102,7 +105,7 @@ class BridgedNativeWidgetTestApi { const float kScaleFactor = 1.0f; ui::CALayerFrameSink* ca_layer_frame_sink = ui::CALayerFrameSink::FromAcceleratedWidget( - bridge_->compositor_widget_->accelerated_widget()); + bridge_->compositor_->widget()->accelerated_widget()); gfx::CALayerParams ca_layer_params; ca_layer_params.is_empty = false; ca_layer_params.pixel_size = size; @@ -467,6 +470,44 @@ TEST_F(NativeWidgetMacTest, DISABLED_OrderFrontAfterMiniaturize) { widget->Close(); } +// Test that ShowInactive() on already-visible child widgets is ignored, since +// it may cause a space transition. See https://crbug.com/866760. +TEST_F(NativeWidgetMacTest, ShowInactiveOnChildWidget) { + NativeWidgetMacTestWindow* parent_window; + NativeWidgetMacTestWindow* child_window; + + Widget::InitParams init_params = + CreateParams(Widget::InitParams::TYPE_WINDOW); + init_params.bounds = gfx::Rect(100, 100, 200, 200); + Widget* parent = CreateWidgetWithTestWindow(init_params, &parent_window); + + // CreateWidgetWithTestWindow calls Show() + EXPECT_EQ(1, [parent_window orderWindowCount]); + + init_params.parent = parent->GetNativeView(); + Widget* child = CreateWidgetWithTestWindow(init_params, &child_window); + + // The child is ordered twice, once by Show() and again (by AppKit) when it is + // registered as a child window. + EXPECT_EQ(2, [child_window orderWindowCount]); + + // Parent is unchanged. + EXPECT_EQ(1, [parent_window orderWindowCount]); + + // ShowInactive() on a visible regular window may serve to raise its stacking + // order without taking focus, so it should invoke -[NSWindow orderWindow:..]. + parent->ShowInactive(); + EXPECT_EQ(2, [parent_window orderWindowCount]); // Increases. + + // However, ShowInactive() on the child should have no effect. It should + // already be in a correct stacking order and we must avoid a Space switch. + child->ShowInactive(); + EXPECT_EQ(2, [child_window orderWindowCount]); // No change. + EXPECT_EQ(2, [parent_window orderWindowCount]); // Parent also unchanged. + + parent->CloseNow(); +} + // Test minimized states triggered externally, implied visibility and restored // bounds whilst minimized. TEST_F(NativeWidgetMacTest, MiniaturizeExternally) { @@ -861,7 +902,8 @@ TEST_F(NativeWidgetMacTest, NonWidgetParentLastReference) { } // Tests visibility for child of native NSWindow, reshowing after -[NSApp hide]. -TEST_F(NativeWidgetMacTest, VisibleAfterNativeParentShow) { +// Occasionally flaky (maybe due to [NSApp hide]). See https://crbug.com/777247. +TEST_F(NativeWidgetMacTest, DISABLED_VisibleAfterNativeParentShow) { NSWindow* native_parent = MakeNativeParent(); Widget* child = AttachPopupToNativeParent(native_parent); child->Show(); @@ -1216,12 +1258,24 @@ TEST_F(NativeWidgetMacTest, ShowAnimationControl) { retained_animation.reset(); // Disable animations and show again. - modal_dialog_widget->SetVisibilityChangedAnimationsEnabled(false); + modal_dialog_widget->SetVisibilityAnimationTransition(Widget::ANIMATE_NONE); modal_dialog_widget->Show(); EXPECT_FALSE(test_api.show_animation()); // No animation this time. modal_dialog_widget->Hide(); // Test after re-enabling. + modal_dialog_widget->SetVisibilityAnimationTransition(Widget::ANIMATE_BOTH); + modal_dialog_widget->Show(); + EXPECT_TRUE(test_api.show_animation()); + retained_animation.reset(test_api.show_animation(), + base::scoped_policy::RETAIN); + + // Test whether disabling native animations also disables custom modal ones. + modal_dialog_widget->SetVisibilityChangedAnimationsEnabled(false); + modal_dialog_widget->Show(); + EXPECT_FALSE(test_api.show_animation()); // No animation this time. + modal_dialog_widget->Hide(); + // Renable. modal_dialog_widget->SetVisibilityChangedAnimationsEnabled(true); modal_dialog_widget->Show(); EXPECT_TRUE(test_api.show_animation()); @@ -1353,6 +1407,7 @@ TEST_F(NativeWidgetMacTest, WindowModalSheet) { TEST_F(NativeWidgetMacTest, CloseWithWindowModalSheet) { NSWindow* native_parent = MakeNativeParentWithStyle(NSClosableWindowMask | NSTitledWindowMask); + { Widget* sheet_widget = ShowWindowModalWidget(native_parent); EXPECT_TRUE([sheet_widget->GetNativeWindow() isVisible]); @@ -1383,17 +1438,112 @@ TEST_F(NativeWidgetMacTest, CloseWithWindowModalSheet) { base::RunLoop().RunUntilIdle(); } + // Similar, but invoke -[NSWindow close] immediately after an asynchronous + // Close(). This exercises a scenario where two tasks to end the sheet may be + // posted. Experimentally (on 10.13) both tasks run, but the second will never + // attempt to invoke -didEndSheet: on the |modalDelegate| arg of -beginSheet:. + // (If it did, it would be fine.) + { + Widget* sheet_widget = ShowWindowModalWidget(native_parent); + base::scoped_nsobject<NSWindow> sheet_window( + sheet_widget->GetNativeWindow(), base::scoped_policy::RETAIN); + EXPECT_TRUE([sheet_window isVisible]); + + WidgetChangeObserver widget_observer(sheet_widget); + sheet_widget->Close(); // Asynchronous. Can't be called after -close. + EXPECT_FALSE(widget_observer.widget_closed()); + [sheet_window close]; + EXPECT_TRUE(widget_observer.widget_closed()); + base::RunLoop().RunUntilIdle(); + + // Pretend both tasks ran fully. Note that |sheet_window| serves as its own + // |modalDelegate|. + [base::mac::ObjCCastStrict<NativeWidgetMacNSWindow>(sheet_window) + sheetDidEnd:sheet_window + returnCode:NSModalResponseStop + contextInfo:nullptr]; + } + + // Test another hypothetical: What if -sheetDidEnd: was invoked somehow + // without going through [NSApp endSheet:] or -[NSWindow endSheet:]. + { + base::mac::ScopedNSAutoreleasePool pool; + Widget* sheet_widget = ShowWindowModalWidget(native_parent); + NSWindow* sheet_window = sheet_widget->GetNativeWindow(); + EXPECT_TRUE([sheet_window isVisible]); + + WidgetChangeObserver widget_observer(sheet_widget); + sheet_widget->Close(); + + [base::mac::ObjCCastStrict<NativeWidgetMacNSWindow>(sheet_window) + sheetDidEnd:sheet_window + returnCode:NSModalResponseStop + contextInfo:nullptr]; + + EXPECT_TRUE(widget_observer.widget_closed()); + // Here, the ViewsNSWindowDelegate should be dealloc'd. + } + base::RunLoop().RunUntilIdle(); // Run the task posted in Close(). + + // Test -[NSWindow close] on the parent window. { Widget* sheet_widget = ShowWindowModalWidget(native_parent); EXPECT_TRUE([sheet_widget->GetNativeWindow() isVisible]); WidgetChangeObserver widget_observer(sheet_widget); - // Test -[NSWindow close] on the parent window. [native_parent close]; EXPECT_TRUE(widget_observer.widget_closed()); } } +// Exercise a scenario where the task posted in the asynchronous Close() could +// eventually complete on a destroyed NSWindowDelegate. Regression test for +// https://crbug.com/851376. +TEST_F(NativeWidgetMacTest, CloseWindowModalSheetWithoutSheetParent) { + NSWindow* native_parent = + MakeNativeParentWithStyle(NSClosableWindowMask | NSTitledWindowMask); + { + base::mac::ScopedNSAutoreleasePool pool; + Widget* sheet_widget = ShowWindowModalWidget(native_parent); + NSWindow* sheet_window = sheet_widget->GetNativeWindow(); + EXPECT_TRUE([sheet_window isVisible]); + + sheet_widget->Close(); // Asynchronous. Can't be called after -close. + + // Now there's a task to end the sheet in the message queue. But destroying + // the NSWindowDelegate without _also_ posting a task that will _retain_ it + // is hard. It _is_ possible for a -performSelector:afterDelay: already in + // the queue to happen _after_ a PostTask posted now, but it's a very rare + // occurrence. So to simulate it, we pretend the sheet isn't actually a + // sheet by hiding its sheetParent. This avoids a task being posted that + // would retain the delegate, but also puts |native_parent| into a weird + // state. + // + // In fact, the "real" suspected trigger for this bug requires the PostTask + // to still be posted, then run to completion, and to dealloc the delegate + // it retains all before the -performSelector:afterDelay runs. That's the + // theory anyway. + // + // In reality, it didn't seem possible for -sheetDidEnd: to be invoked twice + // (AppKit would suppress it on subsequent calls to -[NSApp endSheet:] or + // -[NSWindow endSheet:]), so if the PostTask were to run to completion, the + // waiting -performSelector would always no- op. So this is actually testing + // a hypothetical where the sheetParent may be somehow nil during teardown + // (perhaps due to the parent window being further along in its teardown). + EXPECT_TRUE([sheet_window sheetParent]); + [sheet_window setValue:nil forKey:@"sheetParent"]; + EXPECT_FALSE([sheet_window sheetParent]); + [sheet_window close]; + + // To repro the crash, we need a dealloc to occur here on |sheet_widget|'s + // NSWindowDelegate. + } + // Now there is still a task to end the sheet in the message queue, which + // should not crash. + base::RunLoop().RunUntilIdle(); + [native_parent close]; +} + // 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) { @@ -2225,6 +2375,7 @@ TEST_F(NativeWidgetMacTest, TouchBar) { @implementation NativeWidgetMacTestWindow @synthesize invalidateShadowCount = invalidateShadowCount_; +@synthesize orderWindowCount = orderWindowCount_; @synthesize deallocFlag = deallocFlag_; - (void)dealloc { @@ -2240,6 +2391,12 @@ TEST_F(NativeWidgetMacTest, TouchBar) { [super invalidateShadow]; } +- (void)orderWindow:(NSWindowOrderingMode)orderingMode + relativeTo:(NSInteger)otherWindowNumber { + ++orderWindowCount_; + [super orderWindow:orderingMode relativeTo:otherWindowNumber]; +} + @end @implementation MockBridgedView diff --git a/chromium/ui/views/widget/native_widget_private.h b/chromium/ui/views/widget/native_widget_private.h index 226dcba1511..53c60d77ace 100644 --- a/chromium/ui/views/widget/native_widget_private.h +++ b/chromium/ui/views/widget/native_widget_private.h @@ -206,6 +206,7 @@ class VIEWS_EXPORT NativeWidgetPrivate : public NativeWidget { virtual void SetFullscreen(bool fullscreen) = 0; virtual bool IsFullscreen() const = 0; virtual void SetOpacity(float opacity) = 0; + virtual void SetAspectRatio(const gfx::SizeF& aspect_ratio) = 0; virtual void FlashFrame(bool flash) = 0; virtual void RunShellDrag(View* view, const ui::OSExchangeData& data, diff --git a/chromium/ui/views/widget/util_mac.h b/chromium/ui/views/widget/util_mac.h new file mode 100644 index 00000000000..604c693214b --- /dev/null +++ b/chromium/ui/views/widget/util_mac.h @@ -0,0 +1,17 @@ +// 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_WIDGET_UTIL_MAC_H_ +#define UI_VIEWS_WIDGET_UTIL_MAC_H_ + +#import <Cocoa/Cocoa.h> + +#include "base/mac/foundation_util.h" + +// Weak lets Chrome launch even if a future macOS doesn't have NSThemeFrame. +WEAK_IMPORT_ATTRIBUTE +@interface NSThemeFrame : NSView +@end + +#endif // UI_VIEWS_WIDGET_UTIL_MAC_H_ diff --git a/chromium/ui/views/widget/widget.cc b/chromium/ui/views/widget/widget.cc index bf4b1d07f7f..e4e7fc6ce96 100644 --- a/chromium/ui/views/widget/widget.cc +++ b/chromium/ui/views/widget/widget.cc @@ -718,6 +718,10 @@ void Widget::SetOpacity(float opacity) { native_widget_->SetOpacity(opacity); } +void Widget::SetAspectRatio(const gfx::SizeF& aspect_ratio) { + native_widget_->SetAspectRatio(aspect_ratio); +} + void Widget::FlashFrame(bool flash) { native_widget_->FlashFrame(flash); } @@ -830,6 +834,7 @@ void Widget::UpdateWindowTitle() { base::i18n::AdjustStringForLocaleDirection(&window_title); if (!native_widget_->SetWindowTitle(window_title)) return; + non_client_view_->UpdateWindowTitle(); // If the non-client view is rendering its own title, it'll need to relayout @@ -995,7 +1000,8 @@ bool Widget::IsTranslucentWindowOpacitySupported() const { void Widget::OnSizeConstraintsChanged() { native_widget_->OnSizeConstraintsChanged(); - non_client_view_->SizeConstraintsChanged(); + if (non_client_view_) + non_client_view_->SizeConstraintsChanged(); } void Widget::OnOwnerClosing() {} diff --git a/chromium/ui/views/widget/widget.h b/chromium/ui/views/widget/widget.h index bc3b94fa4f3..6862cfb45d0 100644 --- a/chromium/ui/views/widget/widget.h +++ b/chromium/ui/views/widget/widget.h @@ -327,14 +327,6 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, // during application shutdown when the last non-secondary widget is closed. static void CloseAllSecondaryWidgets(); - // Converts a rectangle from one Widget's coordinate system to another's. - // Returns false if the conversion couldn't be made, because either these two - // Widgets do not have a common ancestor or they are not on the screen yet. - // The value of |*rect| won't be changed when false is returned. - static bool ConvertRect(const Widget* source, - const Widget* target, - gfx::Rect* rect); - // Retrieves the Widget implementation associated with the given // NativeView or Window, or NULL if the supplied handle has no associated // Widget. @@ -428,6 +420,9 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, // fit the entire size of the RootView. The RootView takes ownership of this // View, unless it is set as not being parent-owned. void SetContentsView(View* view); + + // NOTE: This may not be the same view as WidgetDelegate::GetContentsView(). + // See RootView::GetContentsView(). View* GetContentsView(); // Returns the bounds of the Widget in screen coordinates. @@ -552,6 +547,12 @@ class VIEWS_EXPORT Widget : public internal::NativeWidgetDelegate, // underlying windowing system. void SetOpacity(float opacity); + // Sets the aspect ratio of the widget's content, which will be maintained + // during interactive resizing. This size disregards title bar and borders. + // Once set, some platforms ensure the content will only size to integer + // multiples of |aspect_ratio|. + void SetAspectRatio(const gfx::SizeF& aspect_ratio); + // Flashes the frame of the window to draw attention to it. Currently only // implemented on Windows for non-Aura. void FlashFrame(bool flash); diff --git a/chromium/ui/views/widget/widget_delegate.cc b/chromium/ui/views/widget/widget_delegate.cc index ea1c78ff1d6..f1a5004ec93 100644 --- a/chromium/ui/views/widget/widget_delegate.cc +++ b/chromium/ui/views/widget/widget_delegate.cc @@ -6,7 +6,7 @@ #include "base/logging.h" #include "base/strings/utf_string_conversions.h" -#include "services/ui/public/interfaces/window_manager_constants.mojom.h" +#include "services/ui/public/interfaces/window_tree_constants.mojom.h" #include "ui/gfx/image/image_skia.h" #include "ui/views/view.h" #include "ui/views/views_delegate.h" diff --git a/chromium/ui/views/widget/widget_interactive_uitest.cc b/chromium/ui/views/widget/widget_interactive_uitest.cc index 15a9ad10398..54744d6d8ed 100644 --- a/chromium/ui/views/widget/widget_interactive_uitest.cc +++ b/chromium/ui/views/widget/widget_interactive_uitest.cc @@ -16,7 +16,7 @@ #include "base/threading/thread_task_runner_handle.h" #include "base/win/windows_version.h" #include "build/build_config.h" -#include "mojo/edk/embedder/embedder.h" +#include "mojo/core/embedder/embedder.h" #include "ui/base/ime/input_method.h" #include "ui/base/ime/text_input_client.h" #include "ui/base/resource/resource_bundle.h" @@ -281,7 +281,7 @@ class WidgetTestInteractive : public WidgetTest { // Mojo is initialized here similar to how each browser test case // initializes Mojo when starting. This only works because each // interactive_ui_test runs in a new process. - mojo::edk::Init(); + mojo::core::Init(); gl::GLSurfaceTestSupport::InitializeOneOff(); ui::RegisterPathProvider(); @@ -621,6 +621,10 @@ TEST_F(WidgetTestInteractive, DISABLED_GrabUngrab) { // Tests mouse move outside of the window into the "resize controller" and back // will still generate an OnMouseEntered and OnMouseExited event.. TEST_F(WidgetTestInteractive, CheckResizeControllerEvents) { + // TODO(http://crbug.com/864787): Crashes flakily in mus with ws2. + if (IsMus()) + return; + Widget* toplevel = CreateTopLevelPlatformWidget(); toplevel->SetBounds(gfx::Rect(0, 0, 100, 100)); @@ -1337,6 +1341,10 @@ TEST_F(WidgetTestInteractive, InactiveWidgetDoesNotGrabActivation) { // Test that window state is not changed after getting out of full screen. TEST_F(WidgetTestInteractive, MAYBE_ExitFullscreenRestoreState) { + // TODO(http://crbug.com/864618): Fails flakily in mus with ws2. + if (IsMus()) + return; + Widget* toplevel = CreateTopLevelPlatformWidget(); toplevel->Show(); @@ -1898,7 +1906,13 @@ class WidgetInputMethodInteractiveTest : public WidgetTestInteractive { }; // Test input method focus changes affected by top window activaction. -TEST_F(WidgetInputMethodInteractiveTest, Activation) { +TEST_F(WidgetInputMethodInteractiveTest, +#if defined(OS_MACOSX) + DISABLED_Activation +#else + Activation +#endif + ) { if (IsMus()) return; diff --git a/chromium/ui/views/widget/widget_unittest.cc b/chromium/ui/views/widget/widget_unittest.cc index 8e298d2f647..3c77de47e4e 100644 --- a/chromium/ui/views/widget/widget_unittest.cc +++ b/chromium/ui/views/widget/widget_unittest.cc @@ -741,6 +741,10 @@ class WidgetObserverTest : public WidgetTest, public WidgetObserver { #endif TEST_F(WidgetObserverTest, MAYBE_ActivationChange) { + // TODO(http://crbug.com/864800): Fails flakily in mus with ws2. + if (IsMus()) + return; + WidgetAutoclosePtr toplevel(CreateTopLevelPlatformWidget()); WidgetAutoclosePtr toplevel1(NewWidget()); WidgetAutoclosePtr toplevel2(NewWidget()); @@ -949,6 +953,65 @@ TEST_F(WidgetObserverTest, WidgetBoundsChangedNative) { EXPECT_FALSE(widget_bounds_changed()); } +namespace { + +class MoveTrackingTestDesktopWidgetDelegate : public TestDesktopWidgetDelegate { + public: + int move_count() const { return move_count_; } + + // WidgetDelegate: + void OnWidgetMove() override { ++move_count_; } + + private: + int move_count_ = 0; +}; + +} // namespace + +// An extension to the WidgetBoundsChangedNative test above to ensure move +// notifications propagate to the WidgetDelegate. +TEST_F(WidgetObserverTest, OnWidgetMovedWhenOriginChangesNative) { + MoveTrackingTestDesktopWidgetDelegate delegate; + Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); + delegate.InitWidget(params); + Widget* widget = delegate.GetWidget(); + widget->Show(); + widget->SetBounds(gfx::Rect(100, 100, 300, 200)); + + const int moves_during_init = delegate.move_count(); + +#if defined(OS_WIN) + // Windows reliably notifies twice per origin change. https://crbug.com/864938 + constexpr int kDeltaPerMove = 2; +#else + constexpr int kDeltaPerMove = 1; +#endif + + // Resize without changing origin. No move. + widget->SetBounds(gfx::Rect(100, 100, 310, 210)); + EXPECT_EQ(moves_during_init, delegate.move_count()); + + // Move without changing size. Moves. + widget->SetBounds(gfx::Rect(110, 110, 310, 210)); + EXPECT_EQ(moves_during_init + kDeltaPerMove, delegate.move_count()); + + // Changing both moves. + widget->SetBounds(gfx::Rect(90, 90, 330, 230)); + EXPECT_EQ(moves_during_init + 2 * kDeltaPerMove, delegate.move_count()); + + // Just grow vertically. On Mac, this changes the AppKit origin since it is + // from the bottom left of the screen, but there is no move as far as views is + // concerned. + widget->SetBounds(gfx::Rect(90, 90, 330, 240)); + // No change. + EXPECT_EQ(moves_during_init + 2 * kDeltaPerMove, delegate.move_count()); + + // For a similar reason, move the widget down by the same amount that it grows + // vertically. The AppKit origin does not change, but it is a move. + widget->SetBounds(gfx::Rect(90, 100, 330, 250)); + EXPECT_EQ(moves_during_init + 3 * kDeltaPerMove, delegate.move_count()); +} + // Test correct behavior when widgets close themselves in response to visibility // changes. TEST_F(WidgetObserverTest, ClosingOnHiddenParent) { @@ -2235,6 +2298,14 @@ TEST_F(WidgetTest, NoCrashOnWidgetDelete) { widget->Init(params); } +TEST_F(WidgetTest, NoCrashOnResizeConstraintsWindowTitleOnPopup) { + std::unique_ptr<Widget> widget(new Widget); + Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_POPUP); + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + widget->Init(params); + widget->OnSizeConstraintsChanged(); +} + // Tests that we do not crash when a Widget is destroyed before it finishes // processing of pending input events in the message loop. TEST_F(WidgetTest, NoCrashOnWidgetDeleteWithPendingEvents) { diff --git a/chromium/ui/views/win/DEPS b/chromium/ui/views/win/DEPS index 5386933cab8..572e8b045ab 100644 --- a/chromium/ui/views/win/DEPS +++ b/chromium/ui/views/win/DEPS @@ -21,4 +21,5 @@ include_rules = [ "+ui/views/views_export.h", "+ui/views/widget/widget_hwnd_utils.h", "+ui/views/win", + "+ui/views/window/window_resize_utils.h", ] diff --git a/chromium/ui/views/win/hwnd_message_handler.cc b/chromium/ui/views/win/hwnd_message_handler.cc index cadeb232262..d5f442f71da 100644 --- a/chromium/ui/views/win/hwnd_message_handler.cc +++ b/chromium/ui/views/win/hwnd_message_handler.cc @@ -245,6 +245,31 @@ bool IsHitTestOnResizeHandle(LRESULT hittest) { hittest == HTBOTTOMLEFT || hittest == HTBOTTOMRIGHT; } +// Convert |param| to the HitTest used in WindowResizeUtils. +HitTest GetWindowResizeHitTest(UINT param) { + switch (param) { + case WMSZ_BOTTOM: + return HitTest::kBottom; + case WMSZ_TOP: + return HitTest::kTop; + case WMSZ_LEFT: + return HitTest::kLeft; + case WMSZ_RIGHT: + return HitTest::kRight; + case WMSZ_TOPLEFT: + return HitTest::kTopLeft; + case WMSZ_TOPRIGHT: + return HitTest::kTopRight; + case WMSZ_BOTTOMLEFT: + return HitTest::kBottomLeft; + case WMSZ_BOTTOMRIGHT: + return HitTest::kBottomRight; + default: + NOTREACHED(); + return HitTest::kBottomRight; + } +} + const int kTouchDownContextResetTimeout = 500; // Windows does not flag synthesized mouse messages from touch or pen in all @@ -868,6 +893,23 @@ void HWNDMessageHandler::SetFullscreen(bool fullscreen) { PerformDwmTransition(); } +void HWNDMessageHandler::SetAspectRatio(float aspect_ratio) { + // If the aspect ratio is not in the valid range, do nothing. + DCHECK_GT(aspect_ratio, 0.0f); + + aspect_ratio_ = aspect_ratio; + + // When the aspect ratio is set, size the window to adhere to it. This keeps + // the same origin point as the original window. + RECT window_rect; + if (GetWindowRect(hwnd(), &window_rect)) { + gfx::Rect rect(window_rect); + + SizeRectToAspectRatio(WMSZ_BOTTOMRIGHT, &rect); + SetBoundsInternal(rect, false); + } +} + void HWNDMessageHandler::SizeConstraintsChanged() { LONG style = GetWindowLong(hwnd(), GWL_STYLE); // Ignore if this is not a standard window. @@ -987,7 +1029,7 @@ void HWNDMessageHandler::OnInputMethodDestroyed( DestroyAXSystemCaret(); } -void HWNDMessageHandler::OnShowImeIfNeeded() {} +void HWNDMessageHandler::OnShowVirtualKeyboardIfEnabled() {} LRESULT HWNDMessageHandler::HandleMouseMessage(unsigned int message, WPARAM w_param, @@ -1599,18 +1641,26 @@ LRESULT HWNDMessageHandler::OnDpiChanged(UINT msg, if (LOWORD(w_param) != HIWORD(w_param)) NOTIMPLEMENTED() << "Received non-square scaling factors"; + int dpi; + float scaling_factor; + if (display::Display::HasForceDeviceScaleFactor()) { + scaling_factor = display::Display::GetForcedDeviceScaleFactor(); + dpi = display::win::GetDPIFromScalingFactor(scaling_factor); + } else { + dpi = LOWORD(w_param); + scaling_factor = display::win::GetScalingFactorFromDPI(dpi_); + } + // The first WM_DPICHANGED originates from EnableChildWindowDpiMessage during // initialization. We don't want to propagate this as the client is already // set at the current scale factor and may cause the window to display too // soon. See http://crbug.com/625076. - int dpi = LOWORD(w_param); if (dpi_ == dpi) return 0; dpi_ = dpi; SetBoundsInternal(gfx::Rect(*reinterpret_cast<RECT*>(l_param)), false); - delegate_->HandleWindowScaleFactorChanged( - display::win::GetScalingFactorFromDPI(dpi_)); + delegate_->HandleWindowScaleFactorChanged(scaling_factor); return 0; } @@ -2329,6 +2379,19 @@ void HWNDMessageHandler::OnSize(UINT param, const gfx::Size& size) { ResetWindowRegion(false, true); } +void HWNDMessageHandler::OnSizing(UINT param, RECT* rect) { + // If the aspect ratio was not specified for the window, do nothing. + if (!aspect_ratio_.has_value()) + return; + + gfx::Rect window_rect(*rect); + SizeRectToAspectRatio(param, &window_rect); + + // TODO(apacible): Account for window borders as part of the aspect ratio. + // https://crbug/869487. + *rect = window_rect.ToRECT(); +} + void HWNDMessageHandler::OnSysCommand(UINT notification_code, const gfx::Point& point) { // Windows uses the 4 lower order bits of |notification_code| for type- @@ -2912,17 +2975,21 @@ LRESULT HWNDMessageHandler::HandlePointerEventTypeTouch(UINT message, ui::GetModifiersFromKeyState(), rotation_angle); event.latency()->AddLatencyNumberWithTimestamp( - ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, event_time, 1); + ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, event_time, 1); // There are cases where the code handling the message destroys the // window, so use the weak ptr to check if destruction occurred or not. base::WeakPtr<HWNDMessageHandler> ref(msg_handler_weak_factory_.GetWeakPtr()); delegate_->HandleTouchEvent(&event); - if (event_type == ui::ET_TOUCH_RELEASED) - id_generator_.ReleaseNumber(pointer_id); - if (ref) + if (ref) { + // Release the pointer id only when |HWNDMessageHandler| and |id_generator_| + // are not destroyed. + if (event_type == ui::ET_TOUCH_RELEASED) + id_generator_.ReleaseNumber(pointer_id); + SetMsgHandled(event.handled()); + } return 0; } @@ -3031,10 +3098,7 @@ void HWNDMessageHandler::GenerateTouchEvent(ui::EventType event_type, event.set_flags(ui::GetModifiersFromKeyState()); event.latency()->AddLatencyNumberWithTimestamp( - ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, - 0, - time_stamp, - 1); + ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, time_stamp, 1); touch_events->push_back(event); } @@ -3184,4 +3248,18 @@ void HWNDMessageHandler::DestroyAXSystemCaret() { ax_system_caret_ = nullptr; } +void HWNDMessageHandler::SizeRectToAspectRatio(UINT param, + gfx::Rect* window_rect) { + gfx::Size min_window_size; + gfx::Size max_window_size; + delegate_->GetMinMaxSize(&min_window_size, &max_window_size); + WindowResizeUtils::SizeMinMaxToAspectRatio( + aspect_ratio_.value(), &min_window_size, &max_window_size); + min_window_size = delegate_->DIPToScreenSize(min_window_size); + max_window_size = delegate_->DIPToScreenSize(max_window_size); + WindowResizeUtils::SizeRectToAspectRatio( + GetWindowResizeHitTest(param), aspect_ratio_.value(), min_window_size, + max_window_size, window_rect); +} + } // namespace views diff --git a/chromium/ui/views/win/hwnd_message_handler.h b/chromium/ui/views/win/hwnd_message_handler.h index 5afb32aa043..91afedec8e2 100644 --- a/chromium/ui/views/win/hwnd_message_handler.h +++ b/chromium/ui/views/win/hwnd_message_handler.h @@ -31,6 +31,7 @@ #include "ui/gfx/win/window_impl.h" #include "ui/views/views_export.h" #include "ui/views/win/pen_event_processor.h" +#include "ui/views/window/window_resize_utils.h" namespace gfx { class ImageSkia; @@ -161,6 +162,9 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl, void SetFullscreen(bool fullscreen); + // Updates the aspect ratio of the window. + void SetAspectRatio(float aspect_ratio); + // Updates the window style to reflect whether it can be resized or maximized. void SizeConstraintsChanged(); @@ -188,7 +192,7 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl, void OnCaretBoundsChanged(const ui::TextInputClient* client) override; void OnTextInputStateChanged(const ui::TextInputClient* client) override; void OnInputMethodDestroyed(const ui::InputMethod* input_method) override; - void OnShowImeIfNeeded() override; + void OnShowVirtualKeyboardIfEnabled() override; // Overridden from WindowEventTarget LRESULT HandleMouseMessage(unsigned int message, @@ -408,6 +412,7 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl, CR_MSG_WM_SETTEXT(OnSetText) CR_MSG_WM_SETTINGCHANGE(OnSettingChange) CR_MSG_WM_SIZE(OnSize) + CR_MSG_WM_SIZING(OnSizing) CR_MSG_WM_SYSCOMMAND(OnSysCommand) CR_MSG_WM_THEMECHANGED(OnThemeChanged) CR_MSG_WM_TIMECHANGE(OnTimeChange) @@ -467,6 +472,7 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl, LRESULT OnSetText(const wchar_t* text); void OnSettingChange(UINT flags, const wchar_t* section); void OnSize(UINT param, const gfx::Size& size); + void OnSizing(UINT param, RECT* rect); void OnSysCommand(UINT notification_code, const gfx::Point& point); void OnThemeChanged(); void OnTimeChange(); @@ -560,6 +566,10 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl, // if they request its location. void DestroyAXSystemCaret(); + // Updates |rect| to adhere to the |aspect_ratio| of the window. |param| + // refers to the edge of the window being sized. + void SizeRectToAspectRatio(UINT param, gfx::Rect* rect); + HWNDMessageHandlerDelegate* delegate_; std::unique_ptr<FullscreenHandler> fullscreen_handler_; @@ -586,6 +596,10 @@ class VIEWS_EXPORT HWNDMessageHandler : public gfx::WindowImpl, // The icon created from the bitmap image of the app icon. base::win::ScopedHICON app_icon_; + // The aspect ratio for the window. This is only used for sizing operations + // for the non-client area. + base::Optional<float> aspect_ratio_; + // The current DPI. int dpi_; diff --git a/chromium/ui/views/win/pen_event_processor.cc b/chromium/ui/views/win/pen_event_processor.cc index 95089ddb7fa..b5e70938413 100644 --- a/chromium/ui/views/win/pen_event_processor.cc +++ b/chromium/ui/views/win/pen_event_processor.cc @@ -41,11 +41,17 @@ std::unique_ptr<ui::Event> PenEventProcessor::GenerateEvent( // the WM_POINTER message and then setting up an associated pointer // details in the MouseEvent which contains the pen's information. ui::EventPointerType input_type = ui::EventPointerType::POINTER_TYPE_PEN; - // TODO(lanwei): penFlags of PEN_FLAG_INVERTED may also indicate we are using - // an eraser, but it is under debate. Please see - // https://github.com/w3c/pointerevents/issues/134/. - if (pointer_pen_info.penFlags & PEN_FLAG_ERASER) + // For the pointerup event, the penFlags is not set to PEN_FLAG_ERASER, so we + // have to check if previously the pointer type is an eraser. + if (pointer_pen_info.penFlags & PEN_FLAG_ERASER) { input_type = ui::EventPointerType::POINTER_TYPE_ERASER; + DCHECK(eraser_pointer_id_ == -1 || eraser_pointer_id_ == mapped_pointer_id); + eraser_pointer_id_ = mapped_pointer_id; + } else if (eraser_pointer_id_ == mapped_pointer_id && + message == WM_POINTERUP) { + input_type = ui::EventPointerType::POINTER_TYPE_ERASER; + eraser_pointer_id_ = -1; + } // convert pressure into a float [0, 1]. The range of the pressure is // [0, 1024] as specified on MSDN. @@ -188,7 +194,7 @@ std::unique_ptr<ui::Event> PenEventProcessor::GenerateTouchEvent( flags | ui::GetModifiersFromKeyState(), rotation_angle); event->set_hovering(event_type == ui::ET_TOUCH_RELEASED); event->latency()->AddLatencyNumberWithTimestamp( - ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, 0, event_time, 1); + ui::INPUT_EVENT_LATENCY_ORIGINAL_COMPONENT, event_time, 1); return event; } diff --git a/chromium/ui/views/win/pen_event_processor.h b/chromium/ui/views/win/pen_event_processor.h index 60d55489d5e..87d949c01bf 100644 --- a/chromium/ui/views/win/pen_event_processor.h +++ b/chromium/ui/views/win/pen_event_processor.h @@ -54,6 +54,7 @@ class VIEWS_EXPORT PenEventProcessor { bool send_touch_for_pen_ = false; bool sent_mouse_down_ = false; bool sent_touch_start_ = false; + int eraser_pointer_id_ = -1; DISALLOW_COPY_AND_ASSIGN(PenEventProcessor); }; diff --git a/chromium/ui/views/win/pen_event_processor_unittest.cc b/chromium/ui/views/win/pen_event_processor_unittest.cc index 1fa9319f1c9..d7fbaf2c7fa 100644 --- a/chromium/ui/views/win/pen_event_processor_unittest.cc +++ b/chromium/ui/views/win/pen_event_processor_unittest.cc @@ -227,4 +227,37 @@ TEST(PenProcessorTest, MouseFlagDMEnabled) { event->AsMouseEvent()->changed_button_flags()); } +TEST(PenProcessorTest, PenEraserFlagDMEnabled) { + ui::SequentialIDGenerator id_generator(0); + PenEventProcessor processor(&id_generator, + /*direct_manipulation_enabled*/ true); + + POINTER_PEN_INFO pen_info; + memset(&pen_info, 0, sizeof(POINTER_PEN_INFO)); + gfx::Point point(100, 100); + + pen_info.pointerInfo.pointerFlags = + POINTER_FLAG_INCONTACT | POINTER_FLAG_FIRSTBUTTON; + pen_info.pointerInfo.ButtonChangeType = POINTER_CHANGE_FIRSTBUTTON_DOWN; + pen_info.penFlags = PEN_FLAG_ERASER; + + std::unique_ptr<ui::Event> event = + processor.GenerateEvent(WM_POINTERDOWN, 0, pen_info, point); + ASSERT_TRUE(event); + ASSERT_TRUE(event->IsTouchEvent()); + EXPECT_EQ(ui::ET_TOUCH_PRESSED, event->AsTouchEvent()->type()); + EXPECT_EQ(ui::EventPointerType::POINTER_TYPE_ERASER, + event->AsTouchEvent()->pointer_details().pointer_type); + + pen_info.pointerInfo.pointerFlags = POINTER_FLAG_UP; + pen_info.pointerInfo.ButtonChangeType = POINTER_CHANGE_FIRSTBUTTON_UP; + + event = processor.GenerateEvent(WM_POINTERUP, 0, pen_info, point); + ASSERT_TRUE(event); + ASSERT_TRUE(event->IsTouchEvent()); + EXPECT_EQ(ui::ET_TOUCH_RELEASED, event->AsTouchEvent()->type()); + EXPECT_EQ(ui::EventPointerType::POINTER_TYPE_ERASER, + event->AsTouchEvent()->pointer_details().pointer_type); +} + } // namespace views diff --git a/chromium/ui/views/window/dialog_delegate.cc b/chromium/ui/views/window/dialog_delegate.cc index 739e5e548b2..806e809b824 100644 --- a/chromium/ui/views/window/dialog_delegate.cc +++ b/chromium/ui/views/window/dialog_delegate.cc @@ -11,6 +11,7 @@ #include "build/build_config.h" #include "ui/accessibility/ax_node_data.h" #include "ui/base/l10n/l10n_util.h" +#include "ui/base/material_design/material_design_controller.h" #include "ui/gfx/color_palette.h" #include "ui/strings/grit/ui_strings.h" #include "ui/views/bubble/bubble_border.h" @@ -205,12 +206,12 @@ NonClientFrameView* DialogDelegate::CreateNonClientFrameView(Widget* widget) { // static NonClientFrameView* DialogDelegate::CreateDialogFrameView(Widget* widget) { + LayoutProvider* provider = LayoutProvider::Get(); BubbleFrameView* frame = new BubbleFrameView( - LayoutProvider::Get()->GetInsetsMetric(INSETS_DIALOG_TITLE), - gfx::Insets()); + provider->GetInsetsMetric(INSETS_DIALOG_TITLE), gfx::Insets()); const BubbleBorder::Shadow kShadow = BubbleBorder::DIALOG_SHADOW; - std::unique_ptr<BubbleBorder> border( - new BubbleBorder(BubbleBorder::FLOAT, kShadow, gfx::kPlaceholderColor)); + std::unique_ptr<BubbleBorder> border = std::make_unique<BubbleBorder>( + BubbleBorder::FLOAT, kShadow, gfx::kPlaceholderColor); border->set_use_theme_background_color(true); frame->SetBubbleBorder(std::move(border)); DialogDelegate* delegate = widget->widget_delegate()->AsDialogDelegate(); diff --git a/chromium/ui/views/window/dialog_delegate_unittest.cc b/chromium/ui/views/window/dialog_delegate_unittest.cc index e3eefefbc1b..bb2b8d54a35 100644 --- a/chromium/ui/views/window/dialog_delegate_unittest.cc +++ b/chromium/ui/views/window/dialog_delegate_unittest.cc @@ -40,9 +40,7 @@ class TestDialog : public DialogDelegateView { } // WidgetDelegate overrides: - bool ShouldShowWindowTitle() const override { - return !title_.empty(); - } + bool ShouldShowWindowTitle() const override { return !title_.empty(); } bool ShouldShowCloseButton() const override { return show_close_button_; } // DialogDelegateView overrides: diff --git a/chromium/ui/views/window/frame_background.cc b/chromium/ui/views/window/frame_background.cc index 7bbbb8db902..a84ce63cc04 100644 --- a/chromium/ui/views/window/frame_background.cc +++ b/chromium/ui/views/window/frame_background.cc @@ -4,6 +4,7 @@ #include "ui/views/window/frame_background.h" +#include "build/build_config.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColor.h" #include "ui/base/theme_provider.h" @@ -18,6 +19,7 @@ FrameBackground::FrameBackground() use_custom_frame_(true), is_active_(true), incognito_(false), + theme_image_y_inset_(0), top_area_height_(0), left_edge_(nullptr), top_edge_(nullptr), @@ -54,17 +56,12 @@ void FrameBackground::SetCornerImages(const gfx::ImageSkia* top_left, void FrameBackground::PaintRestored(gfx::Canvas* canvas, const View* view) const { - // Fill with the frame color first so we have a constant background for - // areas not covered by the theme image. - PaintFrameColor(canvas, view); + // Restored window painting is a superset of maximized window painting; let + // the maximized code paint the frame color and images. + PaintMaximized(canvas, view); - // Draw the theme frame and overlay, if available. - if (!theme_image_.isNull()) { - canvas->TileImageInt(theme_image_, 0, 0, view->width(), - theme_image_.height()); - } - if (!theme_overlay_image_.isNull()) - canvas->DrawImageInt(theme_overlay_image_, 0, 0); + // Fill the frame borders with the frame color before drawing the edge images. + FillFrameBorders(canvas, view); // Draw the top corners and edge, scaling the corner images down if they // are too big and relative to the vertical space available. @@ -123,30 +120,37 @@ void FrameBackground::PaintRestored(gfx::Canvas* canvas, void FrameBackground::PaintMaximized(gfx::Canvas* canvas, const View* view) const { - // We will be painting from -|maximized_top_inset_| to - // -|maximized_top_inset_| + |theme_image_|.height(). If this is less than - // |top_area_height_|, we need to paint the frame color to fill in the area - // beneath the image. - int theme_frame_bottom = -maximized_top_inset_ + - (theme_image_.isNull() ? 0 : theme_image_.height()); - if (top_area_height_ > theme_frame_bottom) - PaintFrameTopArea(canvas, view); - - // Draw the theme frame. +// Fill the top with the frame color first so we have a constant background +// for areas not covered by the theme image. +#if defined(OS_LINUX) && !defined(OS_CHROMEOS) + auto* native_theme = view->GetNativeTheme(); + ui::NativeTheme::ExtraParams params; + params.frame_top_area.use_custom_frame = use_custom_frame_; + params.frame_top_area.is_active = is_active_; + params.frame_top_area.incognito = incognito_; + params.frame_top_area.default_background_color = frame_color_; + native_theme->Paint(canvas->sk_canvas(), ui::NativeTheme::kFrameTopArea, + ui::NativeTheme::kNormal, + gfx::Rect(0, 0, view->width(), top_area_height_), params); +#else + canvas->FillRect(gfx::Rect(0, 0, view->width(), top_area_height_), + frame_color_); +#endif + + // Draw the theme frame and overlay, if available. if (!theme_image_.isNull()) { - canvas->TileImageInt(theme_image_, 0, -maximized_top_inset_, view->width(), - theme_image_.height()); + canvas->TileImageInt(theme_image_, 0, theme_image_y_inset_, 0, 0, + view->width(), top_area_height_, 1.0f, + SkShader::kRepeat_TileMode, + SkShader::kMirror_TileMode); } - // Draw the theme frame overlay, if available. if (!theme_overlay_image_.isNull()) canvas->DrawImageInt(theme_overlay_image_, 0, -maximized_top_inset_); } -void FrameBackground::PaintFrameColor(gfx::Canvas* canvas, - const View* view) const { - PaintFrameTopArea(canvas, view); - - // If the window is very short, we're done. +void FrameBackground::FillFrameBorders(gfx::Canvas* canvas, + const View* view) const { + // If the window is very short, we don't need to fill any borders. int remaining_height = view->height() - top_area_height_; if (remaining_height <= 0) return; @@ -171,22 +175,4 @@ void FrameBackground::PaintFrameColor(gfx::Canvas* canvas, frame_color_); } -void FrameBackground::PaintFrameTopArea(gfx::Canvas* canvas, - const View* view) const { -#if defined(OS_LINUX) && !defined(OS_CHROMEOS) - auto* native_theme = view->GetNativeTheme(); - ui::NativeTheme::ExtraParams params; - params.frame_top_area.use_custom_frame = use_custom_frame_; - params.frame_top_area.is_active = is_active_; - params.frame_top_area.incognito = incognito_; - params.frame_top_area.default_background_color = frame_color_; - native_theme->Paint(canvas->sk_canvas(), ui::NativeTheme::kFrameTopArea, - ui::NativeTheme::kNormal, - gfx::Rect(0, 0, view->width(), top_area_height_), params); -#else - canvas->FillRect(gfx::Rect(0, 0, view->width(), top_area_height_), - frame_color_); -#endif -} - } // namespace views diff --git a/chromium/ui/views/window/frame_background.h b/chromium/ui/views/window/frame_background.h index 191e5209d94..c8c28703aae 100644 --- a/chromium/ui/views/window/frame_background.h +++ b/chromium/ui/views/window/frame_background.h @@ -43,6 +43,9 @@ class VIEWS_EXPORT FrameBackground { // Memory is owned by the caller. void set_theme_image(const gfx::ImageSkia& image) { theme_image_ = image; } + // Sets an inset into the theme image to begin painting at. + void set_theme_image_y_inset(int y_inset) { theme_image_y_inset_ = y_inset; } + // Sets an image that overlays the top window image. Usually used to add // edge highlighting to provide the illusion of depth. May be null (empty). // Memory is owned by the caller. @@ -81,17 +84,15 @@ class VIEWS_EXPORT FrameBackground { void PaintMaximized(gfx::Canvas* canvas, const View* view) const; private: - // Fills the frame area with the frame color. - void PaintFrameColor(gfx::Canvas* canvas, const View* view) const; - - // Paints the background of the tab strip. - void PaintFrameTopArea(gfx::Canvas* canvas, const View* view) const; + // Fills the frame side and bottom borders with the frame color. + void FillFrameBorders(gfx::Canvas* canvas, const View* view) const; SkColor frame_color_; bool use_custom_frame_; bool is_active_; bool incognito_; gfx::ImageSkia theme_image_; + int theme_image_y_inset_; gfx::ImageSkia theme_overlay_image_; int top_area_height_; diff --git a/chromium/ui/views/window/non_client_view.cc b/chromium/ui/views/window/non_client_view.cc index 1dbbeb4f839..cad54b2424a 100644 --- a/chromium/ui/views/window/non_client_view.cc +++ b/chromium/ui/views/window/non_client_view.cc @@ -127,16 +127,14 @@ void NonClientView::SizeConstraintsChanged() { void NonClientView::LayoutFrameView() { // First layout the NonClientFrameView, which determines the size of the // ClientView... - frame_view_->SetBounds(0, 0, width(), height()); - - // We need to manually call Layout here because layout for the frame view can - // change independently of the bounds changing - e.g. after the initial - // display of the window the metrics of the native window controls can change, - // which does not change the bounds of the window but requires a re-layout to - // trigger a repaint. We override OnBoundsChanged() for the NonClientFrameView - // to do nothing so that SetBounds above doesn't cause Layout to be called - // twice. - frame_view_->Layout(); + gfx::Rect new_frame_bounds = GetLocalBounds(); + if (frame_view_->bounds() == new_frame_bounds) { + // SetBoundsRect does a |needs_layout_| check if the bounds aren't actually + // changing before triggering a layout. Ensure we do a layout either way. + frame_view_->Layout(); + } else { + frame_view_->SetBoundsRect(new_frame_bounds); + } } void NonClientView::SetAccessibleName(const base::string16& name) { @@ -173,16 +171,16 @@ void NonClientView::Layout() { if (base::i18n::IsRTL() && !mirror_client_in_rtl_) client_bounds.set_x(GetMirroredXForRect(client_bounds)); - client_view_->SetBoundsRect(client_bounds); + if (client_bounds != client_view_->bounds()) { + client_view_->SetBoundsRect(client_bounds); + } else { + client_view_->Layout(); + } gfx::Path client_clip; if (frame_view_->GetClientMask(client_view_->size(), &client_clip)) client_view_->set_clip_path(client_clip); - // We need to manually call Layout on the ClientView as well for the same - // reason as above. - client_view_->Layout(); - if (overlay_view_ && overlay_view_->visible()) overlay_view_->SetBoundsRect(GetLocalBounds()); } @@ -345,9 +343,4 @@ bool NonClientFrameView::DoesIntersectRect(const View* target, return !GetWidget()->client_view()->bounds().Intersects(rect); } -void NonClientFrameView::OnBoundsChanged(const gfx::Rect& previous_bounds) { - // Overridden to do nothing. The NonClientView manually calls Layout on the - // FrameView when it is itself laid out, see comment in NonClientView::Layout. -} - } // namespace views diff --git a/chromium/ui/views/window/non_client_view.h b/chromium/ui/views/window/non_client_view.h index e2970c464b0..27cc19d8bad 100644 --- a/chromium/ui/views/window/non_client_view.h +++ b/chromium/ui/views/window/non_client_view.h @@ -104,9 +104,6 @@ class VIEWS_EXPORT NonClientFrameView : public View, bool DoesIntersectRect(const View* target, const gfx::Rect& rect) const override; - // View: - void OnBoundsChanged(const gfx::Rect& previous_bounds) override; - void set_active_state_override(bool* active_state_override) { active_state_override_ = active_state_override; } diff --git a/chromium/ui/views/window/non_client_view_unittest.cc b/chromium/ui/views/window/non_client_view_unittest.cc new file mode 100644 index 00000000000..7f2d5c1d25f --- /dev/null +++ b/chromium/ui/views/window/non_client_view_unittest.cc @@ -0,0 +1,99 @@ +// 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/window/non_client_view.h" + +#include "ui/views/test/views_test_base.h" +#include "ui/views/widget/widget_delegate.h" +#include "ui/views/window/client_view.h" +#include "ui/views/window/native_frame_view.h" + +namespace views { +namespace test { + +using NonClientViewTest = ViewsTestBase; + +namespace { + +class NonClientFrameTestView : public NativeFrameView { + public: + using NativeFrameView::NativeFrameView; + int layout_count() const { return layout_count_; } + + // NativeFrameView: + void Layout() override { + NativeFrameView::Layout(); + ++layout_count_; + } + + private: + int layout_count_ = 0; +}; + +class ClientTestView : public ClientView { + public: + using ClientView::ClientView; + int layout_count() const { return layout_count_; } + + // ClientView: + void Layout() override { + ClientView::Layout(); + ++layout_count_; + } + + private: + int layout_count_ = 0; +}; + +class TestWidgetDelegate : public WidgetDelegateView { + public: + // WidgetDelegateView: + NonClientFrameView* CreateNonClientFrameView(Widget* widget) override { + return new NonClientFrameTestView(widget); + } + + views::ClientView* CreateClientView(Widget* widget) override { + return new ClientTestView(widget, this); + } +}; + +} // namespace + +// Ensure Layout() is not called excessively on a ClientView when Widget bounds +// are changing. +TEST_F(NonClientViewTest, OnlyLayoutChildViewsOnce) { + Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); + params.delegate = new TestWidgetDelegate; + params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + views::Widget widget; + widget.Init(params); + + NonClientView* non_client_view = widget.non_client_view(); + + auto* frame_view = + static_cast<NonClientFrameTestView*>(non_client_view->frame_view()); + auto* client_view = + static_cast<ClientTestView*>(non_client_view->client_view()); + + int initial_frame_view_layouts = frame_view->layout_count(); + int initial_client_view_layouts = client_view->layout_count(); + + non_client_view->Layout(); + EXPECT_EQ(frame_view->layout_count(), initial_frame_view_layouts + 1); + EXPECT_EQ(client_view->layout_count(), initial_client_view_layouts + 1); + + // One more time to make sure it does the layout + // even though nothing has changed. + non_client_view->Layout(); + EXPECT_EQ(frame_view->layout_count(), initial_frame_view_layouts + 2); + EXPECT_EQ(client_view->layout_count(), initial_client_view_layouts + 2); + + // Ensure changing bounds triggers a (single) layout. + widget.SetBounds(gfx::Rect(0, 0, 161, 100)); + EXPECT_EQ(frame_view->layout_count(), initial_frame_view_layouts + 3); + EXPECT_EQ(client_view->layout_count(), initial_client_view_layouts + 3); +} + +} // namespace test +} // namespace views diff --git a/chromium/ui/views/window/window_resize_utils.cc b/chromium/ui/views/window/window_resize_utils.cc new file mode 100644 index 00000000000..61ddee448d7 --- /dev/null +++ b/chromium/ui/views/window/window_resize_utils.cc @@ -0,0 +1,106 @@ +// 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/window/window_resize_utils.h" + +#include <algorithm> + +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +namespace views { + +// static +void WindowResizeUtils::SizeMinMaxToAspectRatio(float aspect_ratio, + gfx::Size* min_window_size, + gfx::Size* max_window_size) { + DCHECK_GT(aspect_ratio, 0.0f); + + // Calculate the height using the min-width and aspect ratio. + int min_height = min_window_size->width() / aspect_ratio; + if (min_height < min_window_size->height()) { + // The supplied width is too small to honor the min size, so use the height + // to determine the minimum width. + min_window_size->set_width(min_window_size->height() * aspect_ratio); + } else { + min_window_size->set_height(min_height); + } + + // Calculate the height using the max-width and aspect ratio. + int max_height = max_window_size->width() / aspect_ratio; + if (max_height > max_window_size->height()) { + // The supplied width is too large to honor the max size, so use the height + // to determine the maximum width. + max_window_size->set_width(max_window_size->height() * aspect_ratio); + } else { + max_window_size->set_height(max_height); + } + + DCHECK_GE(max_window_size->width(), min_window_size->width()); + DCHECK_GE(max_window_size->height(), min_window_size->height()); +} + +// static +void WindowResizeUtils::SizeRectToAspectRatio(HitTest param, + float aspect_ratio, + const gfx::Size& min_window_size, + const gfx::Size& max_window_size, + gfx::Rect* rect) { + DCHECK_GT(aspect_ratio, 0.0f); + DCHECK_GE(max_window_size.width(), min_window_size.width()); + DCHECK_GE(max_window_size.height(), min_window_size.height()); + + float rect_width = 0.0; + float rect_height = 0.0; + if (param == HitTest::kLeft || param == HitTest::kRight || + param == HitTest::kTopLeft || + param == HitTest::kBottomLeft) { /* horizontal axis to pivot */ + rect_width = std::min(max_window_size.width(), + std::max(rect->width(), min_window_size.width())); + rect_height = rect_width / aspect_ratio; + } else { /* vertical axis to pivot */ + rect_height = std::min(max_window_size.height(), + std::max(rect->height(), min_window_size.height())); + rect_width = rect_height * aspect_ratio; + } + + // |rect| bounds before sizing to aspect ratio. + int left = rect->x(); + int top = rect->y(); + int right = rect->right(); + int bottom = rect->bottom(); + + switch (param) { + case HitTest::kRight: + case HitTest::kBottom: + right = rect_width + left; + bottom = top + rect_height; + break; + case HitTest::kTop: + right = rect_width + left; + top = bottom - rect_height; + break; + case HitTest::kLeft: + case HitTest::kTopLeft: + left = right - rect_width; + top = bottom - rect_height; + break; + case HitTest::kTopRight: + right = left + rect_width; + top = bottom - rect_height; + break; + case HitTest::kBottomLeft: + left = right - rect_width; + bottom = top + rect_height; + break; + case HitTest::kBottomRight: + right = left + rect_width; + bottom = top + rect_height; + break; + } + + rect->SetByBounds(left, top, right, bottom); +} + +} // namespace views
\ No newline at end of file diff --git a/chromium/ui/views/window/window_resize_utils.h b/chromium/ui/views/window/window_resize_utils.h new file mode 100644 index 00000000000..f833e82967c --- /dev/null +++ b/chromium/ui/views/window/window_resize_utils.h @@ -0,0 +1,56 @@ +// 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_WINDOW_WINDOW_RESIZE_UTILS_H_ +#define UI_VIEWS_WINDOW_WINDOW_RESIZE_UTILS_H_ + +#include "base/macros.h" +#include "ui/views/views_export.h" + +namespace gfx { +class Size; +class Rect; +} // namespace gfx + +namespace views { + +enum class HitTest { + kBottom, + kBottomLeft, + kBottomRight, + kLeft, + kRight, + kTop, + kTopLeft, + kTopRight +}; + +class VIEWS_EXPORT WindowResizeUtils { + public: + // Force the min and max window sizes to adhere to the aspect ratio. + // |aspect_ratio| must be valid and is found using width / height. + static void SizeMinMaxToAspectRatio(float aspect_ratio, + gfx::Size* min_window_size, + gfx::Size* max_window_size); + + // Updates |rect| to adhere to the |aspect_ratio| of the window, if it has + // been set. |param| refers to the edge of the window being sized. + // |min_window_size| and |max_window_size| are expected to adhere to the + // given aspect ratio. + // |aspect_ratio| must be valid and is found using width / height. + // TODO(apacible): |max_window_size| is expected to be non-empty. Handle + // unconstrained max sizes and sizing when windows are maximized. + static void SizeRectToAspectRatio(HitTest param, + float aspect_ratio, + const gfx::Size& min_window_size, + const gfx::Size& max_window_size, + gfx::Rect* rect); + + private: + DISALLOW_IMPLICIT_CONSTRUCTORS(WindowResizeUtils); +}; + +} // namespace views + +#endif // UI_VIEWS_WINDOW_WINDOW_RESIZE_UTILS_H_
\ No newline at end of file diff --git a/chromium/ui/views/window/window_resize_utils_unittest.cc b/chromium/ui/views/window/window_resize_utils_unittest.cc new file mode 100644 index 00000000000..9510ac8784a --- /dev/null +++ b/chromium/ui/views/window/window_resize_utils_unittest.cc @@ -0,0 +1,161 @@ +// CopykRight 2018 The Chromium Authors. All kRights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/views/window/window_resize_utils.h" + +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/gfx/geometry/rect.h" +#include "ui/gfx/geometry/size.h" + +namespace views { + +namespace { +// Aspect ratio is defined by width / height. +constexpr float kAspectRatioSquare = 1.0f; +constexpr float kAspectRatioHorizontal = 2.0f; +constexpr float kAspectRatioVertical = 0.5f; + +const gfx::Size kMinSizeSquare = gfx::Size(10, 10); +const gfx::Size kMaxSizeSquare = gfx::Size(50, 50); + +const gfx::Size kMinSizeHorizontal = gfx::Size(20, 10); +const gfx::Size kMaxSizeHorizontal = gfx::Size(50, 25); + +const gfx::Size kMinSizeVertical = gfx::Size(10, 20); +const gfx::Size kMaxSizeVertical = gfx::Size(25, 50); +} // namespace + +// Tests resizing of window with a 1:1 aspect ratio. This test also tests the +// 'pivot points' when resizing, i.e. the opposite side or corner of the +// window. +TEST(WindowResizeUtilsTest, SizeToSquareAspectRatio) { + // Size from the top of the window. + // |window_rect| within the bounds of kMinSizeSquare and kMaxSizeSquare. + gfx::Rect window_rect(100, 100, 15, 15); + WindowResizeUtils::SizeRectToAspectRatio(HitTest::kTop, kAspectRatioSquare, + kMinSizeSquare, kMaxSizeSquare, + &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(100, 100, 15, 15)); + + // Size from the bottom right corner of the window. + // |window_rect| smaller than kMinSizeSquare. + window_rect.SetRect(100, 100, 5, 5); + WindowResizeUtils::SizeRectToAspectRatio(HitTest::kBottomRight, + kAspectRatioSquare, kMinSizeSquare, + kMaxSizeSquare, &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(100, 100, kMinSizeSquare.width(), + kMinSizeSquare.height())); + + // Size from the top of the window. + // |window_rect| larger than kMaxSizeSquare. + window_rect.SetRect(100, 100, 100, 100); + WindowResizeUtils::SizeRectToAspectRatio(HitTest::kTop, kAspectRatioSquare, + kMinSizeSquare, kMaxSizeSquare, + &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(100, 150, kMaxSizeSquare.width(), + kMaxSizeSquare.height())); + + // Size from the bottom of the window. + window_rect.SetRect(100, 100, 100, 100); + WindowResizeUtils::SizeRectToAspectRatio(HitTest::kBottom, kAspectRatioSquare, + kMinSizeSquare, kMaxSizeSquare, + &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(100, 100, kMaxSizeSquare.width(), + kMaxSizeSquare.height())); + + // Size from the left of the window. + window_rect.SetRect(100, 100, 100, 100); + WindowResizeUtils::SizeRectToAspectRatio(HitTest::kLeft, kAspectRatioSquare, + kMinSizeSquare, kMaxSizeSquare, + &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(150, 150, kMaxSizeSquare.width(), + kMaxSizeSquare.height())); + + // Size from the right of the window. + window_rect.SetRect(100, 100, 100, 100); + WindowResizeUtils::SizeRectToAspectRatio(HitTest::kRight, kAspectRatioSquare, + kMinSizeSquare, kMaxSizeSquare, + &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(100, 100, kMaxSizeSquare.width(), + kMaxSizeSquare.height())); + + // Size from the top left corner of the window. + window_rect.SetRect(100, 100, 100, 100); + WindowResizeUtils::SizeRectToAspectRatio(HitTest::kTopLeft, + kAspectRatioSquare, kMinSizeSquare, + kMaxSizeSquare, &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(150, 150, kMaxSizeSquare.width(), + kMaxSizeSquare.height())); + + // Size from the top right corner of the window. + window_rect.SetRect(100, 100, 100, 100); + WindowResizeUtils::SizeRectToAspectRatio(HitTest::kTopRight, + kAspectRatioSquare, kMinSizeSquare, + kMaxSizeSquare, &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(100, 150, kMaxSizeSquare.width(), + kMaxSizeSquare.height())); + + // Size from the bottom left corner of the window. + window_rect.SetRect(100, 100, 100, 100); + WindowResizeUtils::SizeRectToAspectRatio(HitTest::kBottomLeft, + kAspectRatioSquare, kMinSizeSquare, + kMaxSizeSquare, &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(150, 100, kMaxSizeSquare.width(), + kMaxSizeSquare.height())); +} + +// Tests the aspect ratio of the gfx::Rect adheres to the horizontal aspect +// ratio. +TEST(WindowResizeUtilsTest, SizeToHorizontalAspectRatio) { + // |window_rect| within bounds of kMinSizeHorizontal and kMaxSizeHorizontal. + gfx::Rect window_rect(100, 100, 20, 10); + WindowResizeUtils::SizeRectToAspectRatio( + HitTest::kTop, kAspectRatioHorizontal, kMinSizeHorizontal, + kMaxSizeHorizontal, &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(100, 100, 20, 10)); + + // |window_rect| smaller than kMinSizeHorizontal. + window_rect.SetRect(100, 100, 5, 5); + WindowResizeUtils::SizeRectToAspectRatio( + HitTest::kBottomRight, kAspectRatioHorizontal, kMinSizeHorizontal, + kMaxSizeHorizontal, &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(100, 100, kMinSizeHorizontal.width(), + kMinSizeHorizontal.height())); + + // |window_rect| greater than kMaxSizeHorizontal. + window_rect.SetRect(100, 100, 100, 100); + WindowResizeUtils::SizeRectToAspectRatio( + HitTest::kTop, kAspectRatioHorizontal, kMinSizeHorizontal, + kMaxSizeHorizontal, &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(100, 175, kMaxSizeHorizontal.width(), + kMaxSizeHorizontal.height())); +} + +// Tests the aspect ratio of the gfx::Rect adheres to the vertical aspect ratio. +TEST(WindowResizeUtilsTest, SizeToVerticalAspectRatio) { + // |window_rect| within bounds of kMinSizeVertical and kMaxSizeVertical. + gfx::Rect window_rect(100, 100, 10, 20); + WindowResizeUtils::SizeRectToAspectRatio( + HitTest::kBottomRight, kAspectRatioVertical, kMinSizeVertical, + kMaxSizeVertical, &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(100, 100, 10, 20)); + + // |window_rect| smaller than kMinSizeVertical. + window_rect.SetRect(100, 100, 5, 5); + WindowResizeUtils::SizeRectToAspectRatio( + HitTest::kBottomRight, kAspectRatioVertical, kMinSizeVertical, + kMaxSizeVertical, &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(100, 100, kMinSizeVertical.width(), + kMinSizeVertical.height())); + + // |window_rect| greater than kMaxSizeVertical. + window_rect.SetRect(100, 100, 100, 100); + WindowResizeUtils::SizeRectToAspectRatio( + HitTest::kBottomRight, kAspectRatioVertical, kMinSizeVertical, + kMaxSizeVertical, &window_rect); + EXPECT_EQ(window_rect, gfx::Rect(100, 100, kMaxSizeVertical.width(), + kMaxSizeVertical.height())); +} + +} // namespace views
\ No newline at end of file |