Commit c46f5707 authored by Adam Ettenberger's avatar Adam Ettenberger Committed by Commit Bot

Expose UIA_AnnotationTypesAttributeId for Spelling and Grammar Errors

This CL Exposes UIA_AnnotationTypesAttributeId, specifically for :
  - AnnotationType_GrammarError
  - AnnotationType_SpellingError

This allows ATs such as Narrator to announce when a word is
misspelled or there are grammar errors when the user types or
navigates through editable content.

---

- In ax_platform_node_textrangeprovider_win_unittest.cc I collapsed
  these macros into EXPECT_UIA_SAFEARRAY_EQ
  - EXPECT_UIA_DOUBLE_SAFEARRAY_EQ
  - EXPECT_UIA_VT_UNKNOWN_SAFEARRAY_EQ

- In ax_platform_node_textrangeprovider_win_unittest.cc I added a
  helper method, CreateTextRangeProviderWin, which handles creating
  the start and end AXNodePositions.

- Internally passing a VariantVector* rather than a VARIANT* for
  getting attribute values for ITextRangeProvider::FindAttribute and
  ITextRangeProvider::GetAttributeValue.
  - This affects a few methods in ax_platform_node_win and
    ax_platform_node_textrangeprovider_win.cc

- Added test cases to
  AXPlatformNodeTextRangeProviderTest.TestITextRangeProviderGetAttributeValue

Bug: 928948

AX-Relnotes: Fixed an issue where Narrator was not announcing misspelled words.
Change-Id: I0b5b4342f3bd78a1e84a8ba9d820458936822bd4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2246918
Commit-Queue: Adam Ettenberger <Adam.Ettenberger@microsoft.com>
Reviewed-by: default avatarNektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarKevin Babbitt <kbabbitt@microsoft.com>
Reviewed-by: default avatarKurt Catti-Schmidt <kschmi@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#787820}
parent feae0a58
...@@ -8,7 +8,9 @@ ...@@ -8,7 +8,9 @@
#include <vector> #include <vector>
#include "base/i18n/string_search.h" #include "base/i18n/string_search.h"
#include "base/win/scoped_safearray.h"
#include "base/win/scoped_variant.h" #include "base/win/scoped_variant.h"
#include "base/win/variant_vector.h"
#include "ui/accessibility/ax_action_data.h" #include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h" #include "ui/accessibility/platform/ax_platform_node_delegate.h"
...@@ -385,13 +387,14 @@ HRESULT AXPlatformNodeTextRangeProviderWin::FindAttributeRange( ...@@ -385,13 +387,14 @@ HRESULT AXPlatformNodeTextRangeProviderWin::FindAttributeRange(
current_platform_node = static_cast<AXPlatformNodeWin*>( current_platform_node = static_cast<AXPlatformNodeWin*>(
delegate->GetFromNodeID(current_start->GetAnchor()->id())); delegate->GetFromNodeID(current_start->GetAnchor()->id()));
base::win::ScopedVariant current_attribute_value; base::win::VariantVector current_attribute_value;
if (FAILED(current_platform_node->GetTextAttributeValue( if (FAILED(current_platform_node->GetTextAttributeValue(
text_attribute_id, current_attribute_value.Receive()))) text_attribute_id, current_start->text_offset(),
current_end->text_offset(), &current_attribute_value))) {
return E_FAIL; return E_FAIL;
}
if (VARCMP_EQ == VarCmp(&attribute_val, current_attribute_value.AsInput(), if (!current_attribute_value.Compare(attribute_val)) {
LOCALE_USER_DEFAULT, 0)) {
// When we encounter an AXRange instance that matches the attribute // When we encounter an AXRange instance that matches the attribute
// and its value which we are looking for and no previously matched text // and its value which we are looking for and no previously matched text
// range exists, we expand or initialize the matched range. // range exists, we expand or initialize the matched range.
...@@ -469,10 +472,11 @@ HRESULT AXPlatformNodeTextRangeProviderWin::GetAttributeValue( ...@@ -469,10 +472,11 @@ HRESULT AXPlatformNodeTextRangeProviderWin::GetAttributeValue(
UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(value); UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(value);
NormalizeTextRange(); NormalizeTextRange();
base::win::ScopedVariant attribute_value_variant; base::win::VariantVector attribute_value;
// The range is inclusive, so advance our endpoint to the next position // The range is inclusive, so advance our endpoint to the next position
auto end = end_->AsLeafTextPosition()->CreateNextAnchorPosition(); const auto end_leaf_text_position = end_->AsLeafTextPosition();
auto end = end_leaf_text_position->CreateNextAnchorPosition();
// Iterate over anchor positions // Iterate over anchor positions
for (auto it = start_->AsLeafTextPosition(); for (auto it = start_->AsLeafTextPosition();
...@@ -500,25 +504,34 @@ HRESULT AXPlatformNodeTextRangeProviderWin::GetAttributeValue( ...@@ -500,25 +504,34 @@ HRESULT AXPlatformNodeTextRangeProviderWin::GetAttributeValue(
DCHECK(platform_node); DCHECK(platform_node);
} }
base::win::ScopedVariant current_variant; base::win::VariantVector current_value;
const bool at_end_leaf_text_anchor =
it->anchor_id() == end_leaf_text_position->anchor_id() &&
it->tree_id() == end_leaf_text_position->tree_id();
const base::Optional<int> start_offset =
it->IsTextPosition() ? base::make_optional(it->text_offset())
: base::nullopt;
const base::Optional<int> end_offset =
at_end_leaf_text_anchor
? base::make_optional(end_leaf_text_position->text_offset())
: base::nullopt;
HRESULT hr = platform_node->GetTextAttributeValue( HRESULT hr = platform_node->GetTextAttributeValue(
attribute_id, current_variant.Receive()); attribute_id, start_offset, end_offset, &current_value);
if (FAILED(hr)) if (FAILED(hr))
return E_FAIL; return E_FAIL;
if (attribute_value_variant.type() == VT_EMPTY) { if (attribute_value.Type() == VT_EMPTY) {
attribute_value_variant.Reset(current_variant); attribute_value = std::move(current_value);
if (attribute_value_variant.type() == VT_UNKNOWN) { } else if (attribute_value != current_value) {
*value = attribute_value_variant.Release();
return S_OK;
}
} else if (attribute_value_variant.Compare(current_variant)) {
V_VT(value) = VT_UNKNOWN; V_VT(value) = VT_UNKNOWN;
return ::UiaGetReservedMixedAttributeValue(&V_UNKNOWN(value)); return ::UiaGetReservedMixedAttributeValue(&V_UNKNOWN(value));
} }
} }
*value = attribute_value_variant.Release(); if (ShouldReleaseTextAttributeAsSafearray(attribute_id, attribute_value))
*value = attribute_value.ReleaseAsSafearrayVariant();
else
*value = attribute_value.ReleaseAsScalarVariant();
return S_OK; return S_OK;
} }
...@@ -1290,4 +1303,49 @@ AXPlatformNodeTextRangeProviderWin::GetLowestAccessibleCommonPlatformNode() ...@@ -1290,4 +1303,49 @@ AXPlatformNodeTextRangeProviderWin::GetLowestAccessibleCommonPlatformNode()
return platform_node->GetLowestAccessibleElement(); return platform_node->GetLowestAccessibleElement();
} }
// static
bool AXPlatformNodeTextRangeProviderWin::TextAttributeIsArrayType(
TEXTATTRIBUTEID attribute_id) {
// https://docs.microsoft.com/en-us/windows/win32/winauto/uiauto-textattribute-ids
return attribute_id == UIA_AnnotationTypesAttributeId ||
attribute_id == UIA_TabsAttributeId;
}
// static
bool AXPlatformNodeTextRangeProviderWin::TextAttributeIsUiaReservedValue(
const base::win::VariantVector& vector) {
// Reserved values are always IUnknown.
if (vector.Type() != VT_UNKNOWN)
return false;
base::win::ScopedVariant mixed_attribute_value_variant;
{
Microsoft::WRL::ComPtr<IUnknown> mixed_attribute_value;
HRESULT hr = ::UiaGetReservedMixedAttributeValue(&mixed_attribute_value);
DCHECK(SUCCEEDED(hr));
mixed_attribute_value_variant.Set(mixed_attribute_value.Get());
}
base::win::ScopedVariant not_supported_value_variant;
{
Microsoft::WRL::ComPtr<IUnknown> not_supported_value;
HRESULT hr = ::UiaGetReservedNotSupportedValue(&not_supported_value);
DCHECK(SUCCEEDED(hr));
not_supported_value_variant.Set(not_supported_value.Get());
}
return !vector.Compare(mixed_attribute_value_variant) ||
!vector.Compare(not_supported_value_variant);
}
// static
bool AXPlatformNodeTextRangeProviderWin::ShouldReleaseTextAttributeAsSafearray(
TEXTATTRIBUTEID attribute_id,
const base::win::VariantVector& attribute_value) {
// |vector| may be pre-populated with a UIA reserved value. In such a case, we
// must release as a scalar variant.
return TextAttributeIsArrayType(attribute_id) &&
!TextAttributeIsUiaReservedValue(attribute_value);
}
} // namespace ui } // namespace ui
...@@ -171,6 +171,13 @@ class AX_EXPORT __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1")) ...@@ -171,6 +171,13 @@ class AX_EXPORT __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1"))
const AXNodeRange& new_selection); const AXNodeRange& new_selection);
AXPlatformNodeWin* GetLowestAccessibleCommonPlatformNode() const; AXPlatformNodeWin* GetLowestAccessibleCommonPlatformNode() const;
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_; Microsoft::WRL::ComPtr<AXPlatformNodeWin> owner_;
AXPositionInstance start_; AXPositionInstance start_;
AXPositionInstance end_; AXPositionInstance end_;
......
...@@ -311,6 +311,12 @@ enum { ...@@ -311,6 +311,12 @@ enum {
return E_INVALIDARG; \ return E_INVALIDARG; \
*arg = {}; *arg = {};
namespace base {
namespace win {
class VariantVector;
} // namespace win
} // namespace base
namespace ui { namespace ui {
class AXPlatformNodeWin; class AXPlatformNodeWin;
...@@ -1059,8 +1065,13 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2")) ...@@ -1059,8 +1065,13 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
REFIID riid, REFIID riid,
void** object); void** object);
// Support method for ITextRangeProvider::GetAttributeValue // Support method for ITextRangeProvider::GetAttributeValue.
HRESULT GetTextAttributeValue(TEXTATTRIBUTEID attribute_id, VARIANT* result); // If either |start_offset| or |end_offset| are not provided then the
// endpoint is treated as the start or end of the node respectively.
HRESULT GetTextAttributeValue(TEXTATTRIBUTEID attribute_id,
const base::Optional<int>& start_offset,
const base::Optional<int>& end_offset,
base::win::VariantVector* result);
// IRawElementProviderSimple support method. // IRawElementProviderSimple support method.
bool IsPatternProviderSupported(PATTERNID pattern_id); bool IsPatternProviderSupported(PATTERNID pattern_id);
...@@ -1095,6 +1106,10 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2")) ...@@ -1095,6 +1106,10 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// Returns the parent node that makes this node inaccessible. // Returns the parent node that makes this node inaccessible.
AXPlatformNodeWin* GetLowestAccessibleElement(); AXPlatformNodeWin* GetLowestAccessibleElement();
// Returns the first |IsTextOnlyObject| descendant using
// depth-first pre-order traversal.
AXPlatformNodeWin* GetFirstTextOnlyDescendant();
// Convert a mojo event to an MSAA event. Exposed for testing. // Convert a mojo event to an MSAA event. Exposed for testing.
static base::Optional<DWORD> MojoEventToMSAAEvent(ax::mojom::Event event); static base::Optional<DWORD> MojoEventToMSAAEvent(ax::mojom::Event event);
...@@ -1338,8 +1353,13 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2")) ...@@ -1338,8 +1353,13 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// Getters for UIA GetTextAttributeValue // Getters for UIA GetTextAttributeValue
// //
// Lookup the LCID for the language this node is using // Computes the AnnotationTypes Attribute for the current node.
HRESULT GetCultureAttributeAsVariant(VARIANT* result) const; HRESULT GetAnnotationTypesAttribute(const base::Optional<int>& start_offset,
const base::Optional<int>& end_offset,
base::win::VariantVector* result);
// Lookup the LCID for the language this node is using.
// Returns base::nullopt if there was an error.
base::Optional<LCID> GetCultureAttributeAsLCID() const;
// Converts an int attribute to a COLORREF // Converts an int attribute to a COLORREF
COLORREF GetIntAttributeAsCOLORREF(ax::mojom::IntAttribute attribute) const; COLORREF GetIntAttributeAsCOLORREF(ax::mojom::IntAttribute attribute) const;
// Converts the ListStyle to UIA BulletStyle // Converts the ListStyle to UIA BulletStyle
...@@ -1356,6 +1376,30 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2")) ...@@ -1356,6 +1376,30 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// Convert mojom TextDirection to UIA FlowDirections enumeration // Convert mojom TextDirection to UIA FlowDirections enumeration
static FlowDirections TextDirectionToFlowDirections(ax::mojom::TextDirection); static FlowDirections TextDirectionToFlowDirections(ax::mojom::TextDirection);
// Helper method for |GetMarkerTypeFromRange| which aggregates all
// of the ranges for |marker_type| attached to |node|.
static void AggregateRangesForMarkerType(
AXPlatformNodeBase* node,
ax::mojom::MarkerType marker_type,
int offset_ranges_amount,
std::vector<std::pair<int, int>>* ranges);
enum class MarkerTypeRangeResult {
// The MarkerType does not overlap the range.
kNone,
// The MarkerType overlaps the entire range.
kMatch,
// The MarkerType partially overlaps the range.
kMixed,
};
// Determine if a text range overlaps a |marker_type|, and whether
// the overlap is a partial or or complete match.
MarkerTypeRangeResult GetMarkerTypeFromRange(
const base::Optional<int>& start_offset,
const base::Optional<int>& end_offset,
ax::mojom::MarkerType marker_type);
bool IsAncestorComboBox(); bool IsAncestorComboBox();
bool IsPlaceholderText() const; bool IsPlaceholderText() const;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment