1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "content/browser/renderer_host/legacy_render_widget_host_win.h"
6 #include "content/browser/renderer_host/render_widget_host_view_aura.h"
7 #include "content/public/test/browser_test.h"
8 #include "content/public/test/content_browser_test.h"
9 #include "content/public/test/content_browser_test_utils.h"
10 #include "content/shell/browser/shell.h"
11 #include "ui/accessibility/accessibility_switches.h"
12 #include "ui/accessibility/platform/ax_platform_node_win.h"
13 #include "ui/accessibility/platform/ax_system_caret_win.h"
14 #include "ui/base/win/hwnd_subclass.h"
15
16 namespace content {
17
18 class AccessibilityObjectLifetimeWinBrowserTest
19 : public content::ContentBrowserTest {
20 public:
21 AccessibilityObjectLifetimeWinBrowserTest() = default;
22 ~AccessibilityObjectLifetimeWinBrowserTest() override = default;
23
24 protected:
GetView()25 RenderWidgetHostViewAura* GetView() {
26 return static_cast<RenderWidgetHostViewAura*>(
27 shell()->web_contents()->GetRenderWidgetHostView());
28 }
29
GetLegacyRenderWidgetHostHWND()30 LegacyRenderWidgetHostHWND* GetLegacyRenderWidgetHostHWND() {
31 return GetView()->legacy_render_widget_host_HWND_;
32 }
33
CacheRootNode(bool is_uia_request)34 void CacheRootNode(bool is_uia_request) {
35 GetLegacyRenderWidgetHostHWND()
36 ->GetOrCreateWindowRootAccessible(is_uia_request)
37 ->QueryInterface(IID_PPV_ARGS(&test_node_));
38 }
39
CacheCaretNode()40 void CacheCaretNode() {
41 GetLegacyRenderWidgetHostHWND()
42 ->ax_system_caret_->GetCaret()
43 ->QueryInterface(IID_PPV_ARGS(&test_node_));
44 }
45
GetHwnd()46 HWND GetHwnd() { return GetView()->AccessibilityGetAcceleratedWidget(); }
47
48 Microsoft::WRL::ComPtr<ui::AXPlatformNodeWin> test_node_;
49
50 private:
51 DISALLOW_COPY_AND_ASSIGN(AccessibilityObjectLifetimeWinBrowserTest);
52 };
53
IN_PROC_BROWSER_TEST_F(AccessibilityObjectLifetimeWinBrowserTest,RootDoesNotLeak)54 IN_PROC_BROWSER_TEST_F(AccessibilityObjectLifetimeWinBrowserTest,
55 RootDoesNotLeak) {
56 EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
57
58 // Cache a pointer to the root node we return to Windows.
59 CacheRootNode(false);
60
61 // Repeatedly call the public API to obtain an accessibility object. If our
62 // code is leaking references, this will drive up the reference count.
63 for (int i = 0; i < 10; i++) {
64 Microsoft::WRL::ComPtr<IAccessible> root_accessible;
65 EXPECT_HRESULT_SUCCEEDED(::AccessibleObjectFromWindow(
66 GetHwnd(), OBJID_CLIENT, IID_PPV_ARGS(&root_accessible)));
67 EXPECT_NE(root_accessible.Get(), nullptr);
68 }
69
70 // Close the main window.
71 shell()->Close();
72
73 // At this point our test reference should be the only one remaining.
74 EXPECT_EQ(test_node_->m_dwRef, 1);
75 }
76
IN_PROC_BROWSER_TEST_F(AccessibilityObjectLifetimeWinBrowserTest,CaretDoesNotLeak)77 IN_PROC_BROWSER_TEST_F(AccessibilityObjectLifetimeWinBrowserTest,
78 CaretDoesNotLeak) {
79 EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
80
81 // Cache a pointer to the object we return to Windows.
82 CacheCaretNode();
83
84 // Repeatedly call the public API to obtain an accessibility object. If our
85 // code is leaking references, this will drive up the reference count.
86 for (int i = 0; i < 10; i++) {
87 Microsoft::WRL::ComPtr<IAccessible> caret_accessible;
88 EXPECT_HRESULT_SUCCEEDED(::AccessibleObjectFromWindow(
89 GetHwnd(), OBJID_CARET, IID_PPV_ARGS(&caret_accessible)));
90 EXPECT_NE(caret_accessible.Get(), nullptr);
91 }
92
93 // Close the main window.
94 shell()->Close();
95
96 // At this point our test reference should be the only one remaining.
97 EXPECT_EQ(test_node_->m_dwRef, 1);
98 }
99
100 // Window subclassing message filter for the legacy window to allow us to
101 // examine state after the call to LegacyRenderWidgetHostHWND::Destroy() but
102 // before the window is torn down completely. Operating system hooks can run in
103 // this narrow window; see crbug.com/945584 for one example.
104 class AccessibilityTeardownTestMessageFilter : public ui::HWNDMessageFilter {
105 public:
AccessibilityTeardownTestMessageFilter(LegacyRenderWidgetHostHWND * legacy_render_widget_host_HWND)106 AccessibilityTeardownTestMessageFilter(
107 LegacyRenderWidgetHostHWND* legacy_render_widget_host_HWND)
108 : legacy_render_widget_host_HWND_(legacy_render_widget_host_HWND) {
109 HWND hwnd = legacy_render_widget_host_HWND->hwnd();
110 CHECK(hwnd);
111 ui::HWNDSubclass::AddFilterToTarget(hwnd, this);
112 }
113 ~AccessibilityTeardownTestMessageFilter() override = default;
114
115 // ui::HWNDMessageFilter:
FilterMessage(HWND hwnd,UINT message,WPARAM w_param,LPARAM l_param,LRESULT * l_result)116 bool FilterMessage(HWND hwnd,
117 UINT message,
118 WPARAM w_param,
119 LPARAM l_param,
120 LRESULT* l_result) override {
121 if (message == WM_DESTROY) {
122 // Verify that the legacy window does not crash when asked for an
123 // accessibility object.
124 legacy_render_widget_host_HWND_->GetOrCreateWindowRootAccessible(false);
125
126 // Remove ourselves as a subclass.
127 ui::HWNDSubclass::RemoveFilterFromAllTargets(this);
128 }
129
130 return true;
131 }
132
133 private:
134 LegacyRenderWidgetHostHWND* legacy_render_widget_host_HWND_;
135 };
136
IN_PROC_BROWSER_TEST_F(AccessibilityObjectLifetimeWinBrowserTest,DoNotCrashDuringLegacyWindowDestroy)137 IN_PROC_BROWSER_TEST_F(AccessibilityObjectLifetimeWinBrowserTest,
138 DoNotCrashDuringLegacyWindowDestroy) {
139 EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
140
141 AccessibilityTeardownTestMessageFilter test_message_filter(
142 GetLegacyRenderWidgetHostHWND());
143
144 GetView()->Destroy();
145 }
146
147 class AccessibilityObjectLifetimeUiaWinBrowserTest
148 : public AccessibilityObjectLifetimeWinBrowserTest {
149 public:
150 AccessibilityObjectLifetimeUiaWinBrowserTest() = default;
151 ~AccessibilityObjectLifetimeUiaWinBrowserTest() override = default;
152
SetUpCommandLine(base::CommandLine * command_line)153 void SetUpCommandLine(base::CommandLine* command_line) override {
154 base::CommandLine::ForCurrentProcess()->AppendSwitch(
155 ::switches::kEnableExperimentalUIAutomation);
156 }
157
158 private:
159 DISALLOW_COPY_AND_ASSIGN(AccessibilityObjectLifetimeUiaWinBrowserTest);
160 };
161
IN_PROC_BROWSER_TEST_F(AccessibilityObjectLifetimeUiaWinBrowserTest,RootDoesNotLeak)162 IN_PROC_BROWSER_TEST_F(AccessibilityObjectLifetimeUiaWinBrowserTest,
163 RootDoesNotLeak) {
164 EXPECT_TRUE(NavigateToURL(shell(), GURL(url::kAboutBlankURL)));
165
166 // Cache a pointer to the root node we return to Windows.
167 CacheRootNode(false);
168
169 Microsoft::WRL::ComPtr<IUIAutomation> uia;
170 ASSERT_HRESULT_SUCCEEDED(CoCreateInstance(CLSID_CUIAutomation, nullptr,
171 CLSCTX_INPROC_SERVER,
172 IID_IUIAutomation, &uia));
173
174 // Repeatedly call the public API to obtain an accessibility object. If our
175 // code is leaking references, this will drive up the reference count.
176 for (int i = 0; i < 10; i++) {
177 Microsoft::WRL::ComPtr<IUIAutomationElement> root_element;
178 EXPECT_HRESULT_SUCCEEDED(uia->ElementFromHandle(GetHwnd(), &root_element));
179 EXPECT_NE(root_element.Get(), nullptr);
180
181 // Raise an event on the root node. This will cause UIA to cache a pointer
182 // to it.
183 ::UiaRaiseStructureChangedEvent(
184 test_node_.Get(), StructureChangeType_ChildrenInvalidated, nullptr, 0);
185 }
186
187 // Close the main window.
188 shell()->Close();
189
190 // At this point our test reference should be the only one remaining.
191 EXPECT_EQ(test_node_->m_dwRef, 1);
192 }
193
194 } // namespace content
195