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 @@
#include <vector>
#include "base/i18n/string_search.h"
#include "base/win/scoped_safearray.h"
#include "base/win/scoped_variant.h"
#include "base/win/variant_vector.h"
#include "ui/accessibility/ax_action_data.h"
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
......@@ -385,13 +387,14 @@ HRESULT AXPlatformNodeTextRangeProviderWin::FindAttributeRange(
current_platform_node = static_cast<AXPlatformNodeWin*>(
delegate->GetFromNodeID(current_start->GetAnchor()->id()));
base::win::ScopedVariant current_attribute_value;
base::win::VariantVector current_attribute_value;
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;
}
if (VARCMP_EQ == VarCmp(&attribute_val, current_attribute_value.AsInput(),
LOCALE_USER_DEFAULT, 0)) {
if (!current_attribute_value.Compare(attribute_val)) {
// When we encounter an AXRange instance that matches the attribute
// and its value which we are looking for and no previously matched text
// range exists, we expand or initialize the matched range.
......@@ -469,10 +472,11 @@ HRESULT AXPlatformNodeTextRangeProviderWin::GetAttributeValue(
UIA_VALIDATE_TEXTRANGEPROVIDER_CALL_1_OUT(value);
NormalizeTextRange();
base::win::ScopedVariant attribute_value_variant;
base::win::VariantVector attribute_value;
// 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
for (auto it = start_->AsLeafTextPosition();
......@@ -500,25 +504,34 @@ HRESULT AXPlatformNodeTextRangeProviderWin::GetAttributeValue(
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(
attribute_id, current_variant.Receive());
attribute_id, start_offset, end_offset, &current_value);
if (FAILED(hr))
return E_FAIL;
if (attribute_value_variant.type() == VT_EMPTY) {
attribute_value_variant.Reset(current_variant);
if (attribute_value_variant.type() == VT_UNKNOWN) {
*value = attribute_value_variant.Release();
return S_OK;
}
} else if (attribute_value_variant.Compare(current_variant)) {
if (attribute_value.Type() == VT_EMPTY) {
attribute_value = std::move(current_value);
} else if (attribute_value != current_value) {
V_VT(value) = VT_UNKNOWN;
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;
}
......@@ -1290,4 +1303,49 @@ AXPlatformNodeTextRangeProviderWin::GetLowestAccessibleCommonPlatformNode()
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
......@@ -171,6 +171,13 @@ class AX_EXPORT __declspec(uuid("3071e40d-a10d-45ff-a59f-6e8e1138e2c1"))
const AXNodeRange& new_selection);
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_;
AXPositionInstance start_;
AXPositionInstance end_;
......
......@@ -311,6 +311,12 @@ enum {
return E_INVALIDARG; \
*arg = {};
namespace base {
namespace win {
class VariantVector;
} // namespace win
} // namespace base
namespace ui {
class AXPlatformNodeWin;
......@@ -1059,8 +1065,13 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
REFIID riid,
void** object);
// Support method for ITextRangeProvider::GetAttributeValue
HRESULT GetTextAttributeValue(TEXTATTRIBUTEID attribute_id, VARIANT* result);
// Support method for ITextRangeProvider::GetAttributeValue.
// 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.
bool IsPatternProviderSupported(PATTERNID pattern_id);
......@@ -1095,6 +1106,10 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// Returns the parent node that makes this node inaccessible.
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.
static base::Optional<DWORD> MojoEventToMSAAEvent(ax::mojom::Event event);
......@@ -1338,8 +1353,13 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// Getters for UIA GetTextAttributeValue
//
// Lookup the LCID for the language this node is using
HRESULT GetCultureAttributeAsVariant(VARIANT* result) const;
// Computes the AnnotationTypes Attribute for the current node.
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
COLORREF GetIntAttributeAsCOLORREF(ax::mojom::IntAttribute attribute) const;
// Converts the ListStyle to UIA BulletStyle
......@@ -1356,6 +1376,30 @@ class AX_EXPORT __declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2"))
// Convert mojom TextDirection to UIA FlowDirections enumeration
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 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