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