File: ax_platform_node_textrangeprovider_win.h

package info (click to toggle)
chromium 138.0.7204.183-1
  • links: PTS, VCS
  • area: main
  • in suites: trixie
  • size: 6,071,908 kB
  • sloc: cpp: 34,937,088; ansic: 7,176,967; javascript: 4,110,704; python: 1,419,953; asm: 946,768; xml: 739,971; pascal: 187,324; sh: 89,623; perl: 88,663; objc: 79,944; sql: 50,304; cs: 41,786; fortran: 24,137; makefile: 21,806; php: 13,980; tcl: 13,166; yacc: 8,925; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (341 lines) | stat: -rw-r--r-- 16,022 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_TEXTRANGEPROVIDER_WIN_H_
#define UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_TEXTRANGEPROVIDER_WIN_H_

#include <wrl/client.h>

#include <string>
#include <tuple>
#include <vector>

#include "base/component_export.h"
#include "ui/accessibility/ax_node_position.h"
#include "ui/accessibility/ax_position.h"
#include "ui/accessibility/ax_range.h"
#include "ui/accessibility/platform/ax_platform_node_win.h"
#include "ui/accessibility/platform/sequence_affine_com_object_root_win.h"

namespace ui {
class COMPONENT_EXPORT(AX_PLATFORM) __declspec(uuid(
    "3071e40d-a10d-45ff-a59f-6e8e1138e2c1")) AXPlatformNodeTextRangeProviderWin
    : public SequenceAffineComObjectRoot,
      public ITextRangeProvider {
 public:
  BEGIN_COM_MAP(AXPlatformNodeTextRangeProviderWin)
  COM_INTERFACE_ENTRY(ITextRangeProvider)
  COM_INTERFACE_ENTRY(AXPlatformNodeTextRangeProviderWin)
  END_COM_MAP()

  AXPlatformNodeTextRangeProviderWin();
  ~AXPlatformNodeTextRangeProviderWin();

  // Creates an instance of the class.
  static void CreateTextRangeProvider(AXNodePosition::AXPositionInstance start,
                                      AXNodePosition::AXPositionInstance end,
                                      ITextRangeProvider** text_range_provider);

  // Creates an instance of the class for unit tests, where AXPlatformNodes
  // cannot be queried automatically from endpoints.
  static void CreateTextRangeProviderForTesting(
      AXPlatformNodeWin* owner,
      AXNodePosition::AXPositionInstance start,
      AXNodePosition::AXPositionInstance end,
      ITextRangeProvider** text_range_provider_out);

  //
  // ITextRangeProvider methods.
  //

  IFACEMETHODIMP Clone(ITextRangeProvider** clone) override;
  IFACEMETHODIMP Compare(ITextRangeProvider* other, BOOL* result) override;
  IFACEMETHODIMP
  CompareEndpoints(TextPatternRangeEndpoint this_endpoint,
                   ITextRangeProvider* other,
                   TextPatternRangeEndpoint other_endpoint,
                   int* result) override;
  IFACEMETHODIMP ExpandToEnclosingUnit(TextUnit unit) override;
  IFACEMETHODIMP
  FindAttribute(TEXTATTRIBUTEID attribute_id,
                VARIANT attribute_val,
                BOOL is_backward,
                ITextRangeProvider** result) override;
  IFACEMETHODIMP
  FindText(BSTR string,
           BOOL backwards,
           BOOL ignore_case,
           ITextRangeProvider** result) override;
  IFACEMETHODIMP GetAttributeValue(TEXTATTRIBUTEID attribute_id,
                                   VARIANT* value) override;
  IFACEMETHODIMP
  GetBoundingRectangles(SAFEARRAY** screen_physical_pixel_rectangles) override;
  IFACEMETHODIMP
  GetEnclosingElement(IRawElementProviderSimple** element) override;
  IFACEMETHODIMP GetText(int max_count, BSTR* text) override;
  IFACEMETHODIMP Move(TextUnit unit, int count, int* units_moved) override;
  IFACEMETHODIMP
  MoveEndpointByUnit(TextPatternRangeEndpoint endpoint,
                     TextUnit unit,
                     int count,
                     int* units_moved) override;
  IFACEMETHODIMP
  MoveEndpointByRange(TextPatternRangeEndpoint this_endpoint,
                      ITextRangeProvider* other,
                      TextPatternRangeEndpoint other_endpoint) override;
  IFACEMETHODIMP Select() override;
  IFACEMETHODIMP AddToSelection() override;
  IFACEMETHODIMP RemoveFromSelection() override;
  IFACEMETHODIMP ScrollIntoView(BOOL align_to_top) override;
  IFACEMETHODIMP GetChildren(SAFEARRAY** children) override;

  AXPlatformNodeWin* GetOwner() const;
  AXPlatformNodeDelegate* GetDelegate() const;
  void SetOwnerForTesting(AXPlatformNodeWin* owner);

 private:
  using AXPositionInstance = AXNodePosition::AXPositionInstance;
  using AXPositionInstanceType = typename AXPositionInstance::element_type;
  using AXNodeRange = AXRange<AXPositionInstanceType>;

  friend class AXPlatformNodeTextRangeProviderTest;
  friend class AXPlatformNodeTextProviderTest;
  friend class AXRangePhysicalPixelRectDelegate;

  static bool AtStartOfLinePredicate(const AXPositionInstance& position);
  static bool AtEndOfLinePredicate(const AXPositionInstance& position);

  static AXPositionInstance GetNextTextBoundaryPosition(
      const AXPositionInstance& position,
      ax::mojom::TextBoundary boundary_type,
      AXMovementOptions options,
      ax::mojom::MoveDirection boundary_direction);

  // Prefer these *Impl methods when functionality is needed internally. We
  // should avoid calling external APIs internally as it will cause the
  // histograms to become innaccurate.
  HRESULT MoveEndpointByUnitImpl(TextPatternRangeEndpoint endpoint,
                                 TextUnit unit,
                                 int count,
                                 int* units_moved);

  IFACEMETHODIMP ExpandToEnclosingUnitImpl(TextUnit unit);

  std::u16string GetString(
      int max_count,
      std::vector<size_t>* appended_newlines_indices = nullptr);

  static size_t GetAppendedNewLinesCountInRange(
      size_t find_start,
      size_t find_length,
      const std::vector<size_t>& appended_newlines_indices);
  const AXPositionInstance& start() const { return endpoints_.GetStart(); }
  const AXPositionInstance& end() const { return endpoints_.GetEnd(); }
  AXPlatformNodeDelegate* GetDelegate(
      const AXPositionInstanceType* position) const;
  AXPlatformNodeDelegate* GetDelegate(const AXTreeID tree_id,
                                      const AXNodeID node_id) const;

  template <typename AnchorIterator, typename ExpandMatchLambda>
  HRESULT FindAttributeRange(const TEXTATTRIBUTEID text_attribute_id,
                             VARIANT attribute_val,
                             const AnchorIterator first,
                             const AnchorIterator last,
                             ExpandMatchLambda expand_match);

  AXPositionInstance MoveEndpointByCharacter(const AXPositionInstance& endpoint,
                                             const int count,
                                             int* units_moved);
  AXPositionInstance MoveEndpointByWord(const AXPositionInstance& endpoint,
                                        const int count,
                                        int* units_moved);
  AXPositionInstance MoveEndpointByLine(const AXPositionInstance& endpoint,
                                        bool is_start_endpoint,
                                        const int count,
                                        int* units_moved);
  AXPositionInstance MoveEndpointByParagraph(const AXPositionInstance& endpoint,
                                             const bool is_start_endpoint,
                                             const int count,
                                             int* units_moved);
  AXPositionInstance MoveEndpointByPage(const AXPositionInstance& endpoint,
                                        const bool is_start_endpoint,
                                        const int count,
                                        int* units_moved);
  AXPositionInstance MoveEndpointByFormat(const AXPositionInstance& endpoint,
                                          const int count,
                                          int* units_moved);
  AXPositionInstance MoveEndpointByDocument(const AXPositionInstance& endpoint,
                                            const int count,
                                            int* units_moved);

  AXPositionInstance MoveEndpointByUnitHelper(
      const AXPositionInstance& endpoint,
      const ax::mojom::TextBoundary boundary_type,
      const int count,
      int* units_moved);

  // A text range normalization is necessary to prevent a |start_| endpoint to
  // be positioned at the end of an anchor when it can be at the start of the
  // next anchor. After normalization, it is guaranteed that:
  // * both endpoints passed by parameter are always positioned on unignored
  //   anchors;
  // * both endpoints passed by parameter are never between a grapheme cluster;
  // * if the endpoints passed by parameter create a degenerate range, both
  //   endpoints are on the same anchor.
  // Normalization never updates the internal endpoints directly. Instead, it
  // normalizes the endpoints passed by parameter.
  void NormalizeTextRange(AXPositionInstance& start, AXPositionInstance& end);
  static void NormalizeAsUnignoredPosition(AXPositionInstance& position);
  static void NormalizeAsUnignoredTextRange(AXPositionInstance& start,
                                            AXPositionInstance& end);

  AXPlatformNodeDelegate* GetRootDelegate(const AXTreeID tree_id);
  AXNode* GetSelectionCommonAnchor();
  void RemoveFocusFromPreviousSelectionIfNeeded(
      const AXNodeRange& new_selection);
  AXPlatformNodeWin* GetPlatformNodeFromAXNode(const AXNode* node) const;
  AXPlatformNodeWin* GetLowestAccessibleCommonPlatformNode() const;
  bool HasTextRangeOrSelectionInAtomicTextField(
      const AXPositionInstance& start_position,
      const AXPositionInstance& end_position) const;

  void SetStart(AXPositionInstance start);
  void SetEnd(AXPositionInstance end);

  void SnapStartAndEndToMaxTextOffsetIfBeyond();

  static bool TextAttributeIsArrayType(TEXTATTRIBUTEID attribute_id);
  static bool TextAttributeIsUiaReservedValue(
      const base::win::VariantVector& vector);
  static bool ShouldReleaseTextAttributeAsSafearray(
      TEXTATTRIBUTEID attribute_id,
      const base::win::VariantVector& vector);

  Microsoft::WRL::ComPtr<AXPlatformNodeWin> owner_for_test_;

  // The TextRangeEndpoints class has the responsibility of keeping the
  // endpoints of the range valid or nullify them when it can't find a valid
  // alternative.
  //
  // An endpoint can become invalid when
  //   A. the node it's on gets deleted,
  //   B. when an ancestor node gets deleted, deleting the subtree our endpoint
  //      is on, or
  //   C. when a descendant node gets deleted, potentially rendering the
  //      position invalid due to a smaller MaxTextOffset value (for a text
  //      position) or fewer child nodes (for a tree position).
  //
  // In all cases, our approach to resolve the endpoints to valid positions
  // takes two steps:
  //   1. Move the endpoint to an equivalent ancestor position before the node
  //      gets deleted - we can't move the position once the node it's on is
  //      deleted since this position would already be considered invalid.
  //   2. Call AsValidPosition on that new position once the node is deleted -
  //      calling this function before the node gets deleted wouldn't do much
  //      since our position would still be considered valid at this point.
  //
  // Because AsValidPosition can potentially be expensive, we only want to run
  // it when necessary. For this reason, we store the node ID and tree ID that
  // causes the first step to happen and only run the second step in
  // OnNodeDeleted for the corresponding node deletion. When OnNodeDeleted is
  // called, the |start_| and |end_| endpoints have already been moved up to an
  // ancestor that is still part of the tree. This is to ensure that we don't
  // have to read the node/tree structure of the deleted node in that function -
  // which would likely result in a crash.
  //
  // Both scenarios A and B are fixed by this approach (by the implementation of
  // OnSubtreeWillBeDeleted), but we still have work to do to fix scenario C.
  // This case, in theory, would only require the second step to ensure that the
  // position is always valid but computing whether node is part of the subtree
  // of the endpoint we're on would be very expensive. Furthermore, because the
  // endpoints are generally on leaf nodes, the scenario is unlikely - we
  // haven't heard of issues caused by this scenario yet. Eventually, we might
  // be able to scope the fix to specific use cases, like when the range is on
  // UIA embedded object (e.g. button, select, etc.)
  //
  // ***
  //
  // Why we can't use a ScopedObserver here:
  // We tried using a ScopedObserver instead of a simple observer in this case,
  // but there appears to be a problem with the lifetime of the referenced
  // AXTreeManager in the ScopedObserver. The AXTreeManager can get deleted
  // before the TextRangeEndpoints does, so when the destructor of the
  // ScopedObserver calls ScopedObserver::RemoveAll on an already deleted
  // AXTreeManager, it crashes.
  class COMPONENT_EXPORT(AX_PLATFORM) TextRangeEndpoints
      : public AXTreeObserver {
   public:
    TextRangeEndpoints();
    ~TextRangeEndpoints() override;
    const AXPositionInstance& GetStart();
    const AXPositionInstance& GetEnd();
    void SetStart(AXPositionInstance new_start);
    void SetEnd(AXPositionInstance new_end);

    void AddObserver(const AXTreeID tree_id);
    void RemoveObserver(const AXTreeID tree_id);
    void OnStringAttributeChanged(AXTree* tree,
                                  AXNode* node,
                                  ax::mojom::StringAttribute attr,
                                  const std::string& old_value,
                                  const std::string& new_value) override;
    void OnSubtreeWillBeDeleted(AXTree* tree, AXNode* node) override;
    void OnNodeDeleted(AXTree* tree, AXNodeID node_id) override;
    void OnTreeManagerWillBeRemoved(AXTreeID previous_tree_id) override;

    // This function is in charge of modifying the text offset when it changes
    // by deletion of text. The renderer fires an even notifying that the text
    // offset changed via a deletion, we listen to that here and adjust
    // accordingly. This is needed so that the text offset that the renderer has
    // and the text offset that we have here is synched in case a deletion
    // happens. Otherwise, there are scenarios where an AT may perform an
    // operation, such as a selection, on a range that is no longer in sync with
    // what the renderer has which can lead to wrong behavior.
    void OnTextDeletionOrInsertion(const AXNode& node,
                                   const AXNodeData& new_data) override;

   private:
    struct DeletionOfInterest {
      AXTreeID tree_id;
      AXNodeID node_id;
      // Needed to defer validation from OnNodeDeleted to
      // ValidateEndpointsAfterNodeDeletionIfNeeded.
      bool validation_needed;
    };

    void AdjustEndpointForSubtreeDeletion(AXTree* tree,
                                          const AXNode* const node,
                                          bool is_start_endpoint);
    // TODO(accessibility): Re-evaluate if we want to continue deferring
    // validation after the BrowserAccessibilityManager-specific nodes have been
    // moved to a single unified tree. At this point, deferring will no longer
    // be necessary as there would be no danger in accessing the tree during
    // OnNodeDeleted. However, it may still be preferable to defer the
    // validation to keep work out of unserialize.
    void ValidateEndpointsAfterNodeDeletionIfNeeded();

    void AdjustEndpointForTextFieldEdit(
        const AXNode& text_field_node,
        const AXPositionInstance& current_position,
        AXNode* edit_start_anchor,
        AXNode* edit_end_anchor,
        int edit_start,
        int edit_end,
        bool is_start,
        ax::mojom::Command op);

    AXPositionInstance start_;
    AXPositionInstance end_;

    std::optional<DeletionOfInterest> validation_necessary_for_start_;
    std::optional<DeletionOfInterest> validation_necessary_for_end_;
  };
  // This is marked as mutable since endpoints will lazily validate their
  // positions after a deletion of interest was actually deleted.
  mutable TextRangeEndpoints endpoints_;
};

}  // namespace ui

#endif  // UI_ACCESSIBILITY_PLATFORM_AX_PLATFORM_NODE_TEXTRANGEPROVIDER_WIN_H_