Flutter Engine
The Flutter Engine
ax_platform_node_textrangeprovider_win.h
Go to the documentation of this file.
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#ifndef UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_TEXTRANGEPROVIDER_WIN_H_
6#define UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_TEXTRANGEPROVIDER_WIN_H_
7
8#include <atlbase.h>
9#include <atlcom.h>
10
11#include <UIAutomationCore.h>
12
13#include "ax/ax_node_position.h"
14#include "ax/ax_tree_observer.h"
17
18namespace ui {
19
20class AX_EXPORT __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1"))
21 AXPlatformNodeTextRangeProviderWin
22 : public CComObjectRootEx<CComMultiThreadModel>,
23 public ITextRangeProvider {
24 public:
25 BEGIN_COM_MAP(AXPlatformNodeTextRangeProviderWin)
26 COM_INTERFACE_ENTRY(ITextRangeProvider)
27 COM_INTERFACE_ENTRY(AXPlatformNodeTextRangeProviderWin)
28 END_COM_MAP()
29
30 AXPlatformNodeTextRangeProviderWin();
31 ~AXPlatformNodeTextRangeProviderWin();
32
33 static ITextRangeProvider* CreateTextRangeProvider(
34 AXNodePosition::AXPositionInstance start,
35 AXNodePosition::AXPositionInstance end);
36
37 // Creates an instance of the class for unit tests, where AXPlatformNodes
38 // cannot be queried automatically from endpoints.
39 static ITextRangeProvider* CreateTextRangeProviderForTesting(
40 AXPlatformNodeWin* owner,
41 AXNodePosition::AXPositionInstance start,
42 AXNodePosition::AXPositionInstance end);
43
44 //
45 // ITextRangeProvider methods.
46 //
47
48 IFACEMETHODIMP Clone(ITextRangeProvider** clone) override;
49 IFACEMETHODIMP Compare(ITextRangeProvider* other, BOOL* result) override;
50 IFACEMETHODIMP
51 CompareEndpoints(TextPatternRangeEndpoint this_endpoint,
52 ITextRangeProvider* other,
53 TextPatternRangeEndpoint other_endpoint,
54 int* result) override;
55 IFACEMETHODIMP ExpandToEnclosingUnit(TextUnit unit) override;
56 IFACEMETHODIMP
57 FindAttribute(TEXTATTRIBUTEID attribute_id,
58 VARIANT attribute_val,
59 BOOL is_backward,
60 ITextRangeProvider** result) override;
61 IFACEMETHODIMP
62 FindText(BSTR string,
63 BOOL backwards,
64 BOOL ignore_case,
65 ITextRangeProvider** result) override;
66 IFACEMETHODIMP GetAttributeValue(TEXTATTRIBUTEID attribute_id,
67 VARIANT* value) override;
68 IFACEMETHODIMP
69 GetBoundingRectangles(SAFEARRAY** screen_physical_pixel_rectangles) override;
70 IFACEMETHODIMP
71 GetEnclosingElement(IRawElementProviderSimple** element) override;
72 IFACEMETHODIMP GetText(int max_count, BSTR* text) override;
73 IFACEMETHODIMP Move(TextUnit unit, int count, int* units_moved) override;
74 IFACEMETHODIMP
75 MoveEndpointByUnit(TextPatternRangeEndpoint endpoint,
76 TextUnit unit,
77 int count,
78 int* units_moved) override;
79 IFACEMETHODIMP
80 MoveEndpointByRange(TextPatternRangeEndpoint this_endpoint,
81 ITextRangeProvider* other,
82 TextPatternRangeEndpoint other_endpoint) override;
83 IFACEMETHODIMP Select() override;
84 IFACEMETHODIMP AddToSelection() override;
85 IFACEMETHODIMP RemoveFromSelection() override;
86 IFACEMETHODIMP ScrollIntoView(BOOL align_to_top) override;
87 IFACEMETHODIMP GetChildren(SAFEARRAY** children) override;
88
89 AXPlatformNodeWin* GetOwner() const;
90 void SetOwnerForTesting(AXPlatformNodeWin* owner);
91
92 private:
93 using AXPositionInstance = AXNodePosition::AXPositionInstance;
94 using AXPositionInstanceType = typename AXPositionInstance::element_type;
95 using AXNodeRange = AXRange<AXPositionInstanceType>;
96
97 friend class AXPlatformNodeTextRangeProviderTest;
98 friend class AXPlatformNodeTextProviderTest;
99 friend class AXRangePhysicalPixelRectDelegate;
100
101 static bool AtStartOfLinePredicate(const AXPositionInstance& position);
102 static bool AtEndOfLinePredicate(const AXPositionInstance& position);
103
104 static AXPositionInstance GetNextTextBoundaryPosition(
105 const AXPositionInstance& position,
106 ax::mojom::TextBoundary boundary_type,
108 ax::mojom::MoveDirection boundary_direction);
109
110 // Prefer these *Impl methods when functionality is needed internally. We
111 // should avoid calling external APIs internally as it will cause the
112 // histograms to become innaccurate.
113 HRESULT MoveEndpointByUnitImpl(TextPatternRangeEndpoint endpoint,
114 TextUnit unit,
115 int count,
116 int* units_moved);
117
118 IFACEMETHODIMP ExpandToEnclosingUnitImpl(TextUnit unit);
119
120 std::u16string GetString(int max_count,
121 size_t* appended_newlines_count = nullptr);
122 const AXPositionInstance& start() const { return endpoints_.GetStart(); }
123 const AXPositionInstance& end() const { return endpoints_.GetEnd(); }
124 AXPlatformNodeDelegate* GetDelegate(
125 const AXPositionInstanceType* position) const;
126 AXPlatformNodeDelegate* GetDelegate(const AXTreeID tree_id,
127 const AXNode::AXID node_id) const;
128
129 template <typename AnchorIterator, typename ExpandMatchLambda>
130 HRESULT FindAttributeRange(const TEXTATTRIBUTEID text_attribute_id,
131 VARIANT attribute_val,
132 const AnchorIterator first,
133 const AnchorIterator last,
134 ExpandMatchLambda expand_match);
135
136 AXPositionInstance MoveEndpointByCharacter(const AXPositionInstance& endpoint,
137 const int count,
138 int* units_moved);
139 AXPositionInstance MoveEndpointByWord(const AXPositionInstance& endpoint,
140 const int count,
141 int* units_moved);
142 AXPositionInstance MoveEndpointByLine(const AXPositionInstance& endpoint,
143 bool is_start_endpoint,
144 const int count,
145 int* units_moved);
146 AXPositionInstance MoveEndpointByParagraph(const AXPositionInstance& endpoint,
147 const bool is_start_endpoint,
148 const int count,
149 int* units_moved);
150 AXPositionInstance MoveEndpointByPage(const AXPositionInstance& endpoint,
151 const bool is_start_endpoint,
152 const int count,
153 int* units_moved);
154 AXPositionInstance MoveEndpointByDocument(const AXPositionInstance& endpoint,
155 const int count,
156 int* units_moved);
157
158 AXPositionInstance MoveEndpointByUnitHelper(
159 const AXPositionInstance& endpoint,
160 const ax::mojom::TextBoundary boundary_type,
161 const int count,
162 int* units_moved);
163
164 // A text range normalization is necessary to prevent a |start_| endpoint to
165 // be positioned at the end of an anchor when it can be at the start of the
166 // next anchor. After normalization, it is guaranteed that:
167 // * both endpoints passed by parameter are always positioned on unignored
168 // anchors;
169 // * both endpoints passed by parameter are never between a grapheme cluster;
170 // * if the endpoints passed by parameter create a degenerate range, both
171 // endpoints are on the same anchor.
172 // Normalization never updates the internal endpoints directly. Instead, it
173 // normalizes the endpoints passed by parameter.
174 void NormalizeTextRange(AXPositionInstance& start, AXPositionInstance& end);
175 static void NormalizeAsUnignoredPosition(AXPositionInstance& position);
176 static void NormalizeAsUnignoredTextRange(AXPositionInstance& start,
177 AXPositionInstance& end);
178
179 AXPlatformNodeDelegate* GetRootDelegate(const ui::AXTreeID tree_id);
180 AXNode* GetSelectionCommonAnchor();
181 void RemoveFocusFromPreviousSelectionIfNeeded(
182 const AXNodeRange& new_selection);
183 AXPlatformNodeWin* GetPlatformNodeFromAXNode(const AXNode* node) const;
184 AXPlatformNodeWin* GetLowestAccessibleCommonPlatformNode() const;
185 bool HasTextRangeOrSelectionInAtomicTextField(
186 const AXPositionInstance& start_position,
187 const AXPositionInstance& end_position) const;
188
189 void SetStart(AXPositionInstance start);
190 void SetEnd(AXPositionInstance end);
191
192 static bool TextAttributeIsArrayType(TEXTATTRIBUTEID attribute_id);
193 static bool TextAttributeIsUiaReservedValue(
194 const base::win::VariantVector& vector);
195 static bool ShouldReleaseTextAttributeAsSafearray(
196 TEXTATTRIBUTEID attribute_id,
197 const base::win::VariantVector& vector);
198
199 Microsoft::WRL::ComPtr<AXPlatformNodeWin> owner_for_test_;
200
201 // The TextRangeEndpoints class has the responsibility of keeping the
202 // endpoints of the range valid or nullify them when it can't find a valid
203 // alternative.
204 //
205 // An endpoint can become invalid when
206 // A. the node it's on gets deleted,
207 // B. when an ancestor node gets deleted, deleting the subtree our endpoint
208 // is on, or
209 // C. when a descendant node gets deleted, potentially rendering the
210 // position invalid due to a smaller MaxTextOffset value (for a text
211 // position) or fewer child nodes (for a tree position).
212 //
213 // In all cases, our approach to resolve the endpoints to valid positions
214 // takes two steps:
215 // 1. Move the endpoint to an equivalent ancestor position before the node
216 // gets deleted - we can't move the position once the node it's on is
217 // deleted since this position would already be considered invalid.
218 // 2. Call AsValidPosition on that new position once the node is deleted -
219 // calling this function before the node gets deleted wouldn't do much
220 // since our position would still be considered valid at this point.
221 //
222 // Because AsValidPosition can potentially be expensive, we only want to run
223 // it when necessary. For this reason, we store the node ID and tree ID that
224 // causes the first step to happen and only run the second step in
225 // OnNodeDeleted for the corresponding node deletion. When OnNodeDeleted is
226 // called, the |start_| and |end_| endpoints have already been moved up to an
227 // ancestor that is still part of the tree. This is to ensure that we don't
228 // have to read the node/tree structure of the deleted node in that function -
229 // which would likely result in a crash.
230 //
231 // Both scenarios A and B are fixed by this approach (by the implementation of
232 // OnSubtreeWillBeDeleted), but we still have work to do to fix scenario C.
233 // This case, in theory, would only require the second step to ensure that the
234 // position is always valid but computing whether node is part of the subtree
235 // of the endpoint we're on would be very expensive. Furthermore, because the
236 // endpoints are generally on leaf nodes, the scenario is unlikely - we
237 // haven't heard of issues caused by this scenario yet. Eventually, we might
238 // be able to scope the fix to specific use cases, like when the range is on
239 // UIA embedded object (e.g. button, select, etc.)
240 //
241 // ***
242 //
243 // Why we can't use a ScopedObserver here:
244 // We tried using a ScopedObserver instead of a simple observer in this case,
245 // but there appears to be a problem with the lifetime of the referenced
246 // AXTreeManager in the ScopedObserver. The AXTreeManager can get deleted
247 // before the TextRangeEndpoints does, so when the destructor of the
248 // ScopedObserver calls ScopedObserver::RemoveAll on an already deleted
249 // AXTreeManager, it crashes.
250 class TextRangeEndpoints : public AXTreeObserver {
251 public:
252 TextRangeEndpoints();
253 ~TextRangeEndpoints() override;
254 const AXPositionInstance& GetStart() const { return start_; }
255 const AXPositionInstance& GetEnd() const { return end_; }
256 void SetStart(AXPositionInstance new_start);
257 void SetEnd(AXPositionInstance new_end);
258
259 void AddObserver(const AXTreeID tree_id);
260 void RemoveObserver(const AXTreeID tree_id);
261 void OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) override;
262 void OnNodeDeleted(AXTree* tree, AXNode::AXID node_id) override;
263
264 private:
265 struct DeletionOfInterest {
266 AXTreeID tree_id;
267 AXNode::AXID node_id;
268 };
269
270 void AdjustEndpointForSubtreeDeletion(AXTree* tree,
271 const AXNode* const node,
272 bool is_start_endpoint);
273
274 AXPositionInstance start_;
275 AXPositionInstance end_;
276
277 std::optional<DeletionOfInterest> validation_necessary_for_start_;
278 std::optional<DeletionOfInterest> validation_necessary_for_end_;
279 };
280 TextRangeEndpoints endpoints_;
281};
282
283// Optionally case-insensitive or reverse string search.
284//
285// Exposed as non-static for use in testing.
286bool StringSearch(std::u16string_view search_string,
287 std::u16string_view find_in,
288 size_t* find_start,
289 size_t* find_length,
290 bool ignore_case,
291 bool backwards);
292
293} // namespace ui
294
295#endif // UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_TEXTRANGEPROVIDER_WIN_H_
const char * options
int count
Definition: FontMgrTest.cpp:50
#define AX_EXPORT
Definition: ax_export.h:29
int32_t AXID
Definition: ax_node.h:36
glong glong end
uint8_t value
GAsyncResult * result
std::u16string text
Definition: ref_ptr.h:256
AXBoundaryBehavior
Definition: ax_position.h:43
bool StringSearch(std::u16string_view search_string, std::u16string_view find_in, size_t *find_start, size_t *find_length, bool ignore_case, bool backwards)
int BOOL
Definition: windows_types.h:37