Skip to content

Commit

Permalink
Extract WideToUTF16String/UTF16StringToWide to FML (flutter#39006)
Browse files Browse the repository at this point in the history
* Extract WideToUTF16String/UTF16StringToWide to FML

In third_party/accessibility, for string conversion, we use a mix of:
* FML
* third_party/accessibility base string utility functions
* static functions local to the translation unit itself

This moves all conversions between UTF16 and wide strings to FML. Note
that this implementation is only safe on platforms where:
  * the size of wchar_t and char16_t are the same
  * the encoding of wchar_t and char16_t are both UTF-16
which is the case for Windows, hence why these functions are implemented
in a Windows-specific translation unit (wstring_conversion).

Issue: flutter/flutter#118811

* Migrate UTF16ToWide as well
  • Loading branch information
cbracken committed Jan 19, 2023
1 parent 7d40e77 commit 7bbe79e
Show file tree
Hide file tree
Showing 10 changed files with 96 additions and 64 deletions.
12 changes: 12 additions & 0 deletions fml/platform/win/wstring_conversion.cc
Expand Up @@ -23,4 +23,16 @@ std::wstring Utf8ToWideString(const std::string_view str) {
return converter.from_bytes(str.data());
}

std::u16string WideStringToUtf16(const std::wstring_view str) {
static_assert(sizeof(std::wstring::value_type) ==
sizeof(std::u16string::value_type));
return {begin(str), end(str)};
}

std::wstring Utf16ToWideString(const std::u16string_view str) {
static_assert(sizeof(std::wstring::value_type) ==
sizeof(std::u16string::value_type));
return {begin(str), end(str)};
}

} // namespace fml
6 changes: 6 additions & 0 deletions fml/platform/win/wstring_conversion.h
Expand Up @@ -16,6 +16,12 @@ std::string WideStringToUtf8(const std::wstring_view str);
// string.
std::wstring Utf8ToWideString(const std::string_view str);

// Returns a UTF-16 encoded equivalent of a UTF-16 encoded wide string.
std::u16string WideStringToUtf16(const std::wstring_view str);

// Returns a UTF-16 encoded wide string equivalent of a UTF-16 string.
std::wstring Utf16ToWideString(const std::u16string_view str);

} // namespace fml

#endif // FLUTTER_FML_PLATFORM_WIN_WSTRING_CONVERSION_H_
26 changes: 25 additions & 1 deletion fml/platform/win/wstring_conversion_unittests.cc
Expand Up @@ -9,7 +9,7 @@
namespace fml {
namespace testing {

TEST(StringConversion, Utf16ToWideStringEmpty) {
TEST(StringConversion, Utf8ToWideStringEmpty) {
EXPECT_EQ(Utf8ToWideString(""), L"");
}

Expand All @@ -33,5 +33,29 @@ TEST(StringConversion, WideStringToUtf8Unicode) {
EXPECT_EQ(WideStringToUtf8(L"\x2603"), "\xe2\x98\x83");
}

TEST(StringConversion, WideStringToUtf16Empty) {
EXPECT_EQ(WideStringToUtf16(L""), u"");
}

TEST(StringConversion, WideStringToUtf16Ascii) {
EXPECT_EQ(WideStringToUtf16(L"abc123"), u"abc123");
}

TEST(StringConversion, WideStringToUtf16Unicode) {
EXPECT_EQ(WideStringToUtf16(L"\xe2\x98\x83"), u"\xe2\x98\x83");
}

TEST(StringConversion, Utf16ToWideStringEmpty) {
EXPECT_EQ(Utf16ToWideString(u""), L"");
}

TEST(StringConversion, Utf16ToWideStringAscii) {
EXPECT_EQ(Utf16ToWideString(u"abc123"), L"abc123");
}

TEST(StringConversion, Utf16ToWideStringUtf8Unicode) {
EXPECT_EQ(Utf16ToWideString(u"\xe2\x98\x83"), L"\xe2\x98\x83");
}

} // namespace testing
} // namespace fml
1 change: 1 addition & 0 deletions third_party/accessibility/ax/BUILD.gn
Expand Up @@ -106,6 +106,7 @@ source_set("ax") {
"oleacc.lib",
"uiautomationcore.lib",
]
deps = [ "//flutter/fml:string_conversion" ]
}

public_deps = [
Expand Down
57 changes: 27 additions & 30 deletions third_party/accessibility/ax/ax_node_position_unittest.cc
Expand Up @@ -19,6 +19,7 @@
#include "ax/ax_tree_id.h"
#include "ax/ax_tree_update.h"
#include "ax/test_ax_tree_manager.h"
#include "flutter/fml/platform/win/wstring_conversion.h"
#include "gtest/gtest.h"

namespace ui {
Expand All @@ -28,10 +29,6 @@ using TestPositionRange = AXRange<AXPosition<AXNodePosition, AXNode>>;

namespace {

std::u16string WideToUTF16(const std::wstring wide) {
return std::u16string(wide.begin(), wide.end());
}

constexpr AXNode::AXID ROOT_ID = 1;
constexpr AXNode::AXID BUTTON_ID = 2;
constexpr AXNode::AXID CHECK_BOX_ID = 3;
Expand All @@ -43,20 +40,20 @@ constexpr AXNode::AXID STATIC_TEXT2_ID = 8;
constexpr AXNode::AXID INLINE_BOX2_ID = 9;

// A group of basic and extended characters.
constexpr const wchar_t* kGraphemeClusters[] = {
constexpr const char16_t* kGraphemeClusters[] = {
// The English word "hey" consisting of four ASCII characters.
L"h",
L"e",
L"y",
u"h",
u"e",
u"y",
// A Hindi word (which means "Hindi") consisting of two Devanagari
// grapheme clusters.
L"\x0939\x093F",
L"\x0928\x094D\x0926\x0940",
u"\x0939\x093F",
u"\x0928\x094D\x0926\x0940",
// A Thai word (which means "feel") consisting of three Thai grapheme
// clusters.
L"\x0E23\x0E39\x0E49",
L"\x0E2A\x0E36",
L"\x0E01",
u"\x0E23\x0E39\x0E49",
u"\x0E2A\x0E36",
u"\x0E01",
};

class AXPositionTest : public testing::Test, public TestAXTreeManager {
Expand Down Expand Up @@ -419,7 +416,7 @@ std::unique_ptr<AXTree> AXPositionTest::CreateMultilingualDocument(

std::u16string english_text;
for (int i = 0; i < 3; ++i) {
std::u16string grapheme = WideToUTF16(kGraphemeClusters[i]);
std::u16string grapheme = kGraphemeClusters[i];
EXPECT_EQ(1u, grapheme.length())
<< "All English characters should be one UTF16 code unit in length.";
text_offsets->push_back(text_offsets->back() +
Expand All @@ -429,7 +426,7 @@ std::unique_ptr<AXTree> AXPositionTest::CreateMultilingualDocument(

std::u16string hindi_text;
for (int i = 3; i < 5; ++i) {
std::u16string grapheme = WideToUTF16(kGraphemeClusters[i]);
std::u16string grapheme = kGraphemeClusters[i];
EXPECT_LE(2u, grapheme.length()) << "All Hindi characters should be two "
"or more UTF16 code units in length.";
text_offsets->push_back(text_offsets->back() +
Expand All @@ -439,7 +436,7 @@ std::unique_ptr<AXTree> AXPositionTest::CreateMultilingualDocument(

std::u16string thai_text;
for (int i = 5; i < 8; ++i) {
std::u16string grapheme = WideToUTF16(kGraphemeClusters[i]);
std::u16string grapheme = kGraphemeClusters[i];
EXPECT_LT(0u, grapheme.length())
<< "One of the Thai characters should be one UTF16 code unit, "
"whilst others should be two or more.";
Expand Down Expand Up @@ -618,7 +615,7 @@ TEST_F(AXPositionTest, ToString) {
AXNodeData static_text_data_2;
static_text_data_2.id = 3;
static_text_data_2.role = ax::mojom::Role::kStaticText;
static_text_data_2.SetName(WideToUTF16(L"\xfffc"));
static_text_data_2.SetName(u"\xfffc");

AXNodeData static_text_data_3;
static_text_data_3.id = 4;
Expand Down Expand Up @@ -876,7 +873,7 @@ TEST_F(AXPositionTest, GetTextFromNullPosition) {
TestPositionType text_position = AXNodePosition::CreateNullPosition();
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsNullPosition());
ASSERT_EQ(WideToUTF16(L""), text_position->GetText());
ASSERT_EQ(u"", text_position->GetText());
}

TEST_F(AXPositionTest, GetTextFromRoot) {
Expand All @@ -885,7 +882,7 @@ TEST_F(AXPositionTest, GetTextFromRoot) {
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(WideToUTF16(L"Line 1\nLine 2"), text_position->GetText());
ASSERT_EQ(u"Line 1\nLine 2", text_position->GetText());
}

TEST_F(AXPositionTest, GetTextFromButton) {
Expand All @@ -894,7 +891,7 @@ TEST_F(AXPositionTest, GetTextFromButton) {
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(WideToUTF16(L""), text_position->GetText());
ASSERT_EQ(u"", text_position->GetText());
}

TEST_F(AXPositionTest, GetTextFromCheckbox) {
Expand All @@ -903,7 +900,7 @@ TEST_F(AXPositionTest, GetTextFromCheckbox) {
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(WideToUTF16(L""), text_position->GetText());
ASSERT_EQ(u"", text_position->GetText());
}

TEST_F(AXPositionTest, GetTextFromTextField) {
Expand All @@ -912,7 +909,7 @@ TEST_F(AXPositionTest, GetTextFromTextField) {
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(WideToUTF16(L"Line 1\nLine 2"), text_position->GetText());
ASSERT_EQ(u"Line 1\nLine 2", text_position->GetText());
}

TEST_F(AXPositionTest, GetTextFromStaticText) {
Expand All @@ -921,7 +918,7 @@ TEST_F(AXPositionTest, GetTextFromStaticText) {
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(WideToUTF16(L"Line 1"), text_position->GetText());
ASSERT_EQ(u"Line 1", text_position->GetText());
}

TEST_F(AXPositionTest, GetTextFromInlineTextBox) {
Expand All @@ -930,7 +927,7 @@ TEST_F(AXPositionTest, GetTextFromInlineTextBox) {
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(WideToUTF16(L"Line 1"), text_position->GetText());
ASSERT_EQ(u"Line 1", text_position->GetText());
}

TEST_F(AXPositionTest, GetTextFromLineBreak) {
Expand All @@ -939,7 +936,7 @@ TEST_F(AXPositionTest, GetTextFromLineBreak) {
ax::mojom::TextAffinity::kUpstream);
ASSERT_NE(nullptr, text_position);
ASSERT_TRUE(text_position->IsTextPosition());
ASSERT_EQ(WideToUTF16(L"\n"), text_position->GetText());
ASSERT_EQ(u"\n", text_position->GetText());
}

TEST_F(AXPositionTest, GetMaxTextOffsetFromNullPosition) {
Expand Down Expand Up @@ -1106,7 +1103,7 @@ TEST_F(AXPositionTest, GetMaxTextOffsetAndGetTextWithGeneratedContent) {
ASSERT_NE(nullptr, text_position);
EXPECT_TRUE(text_position->IsTextPosition());
EXPECT_EQ(38, text_position->MaxTextOffset());
EXPECT_EQ(WideToUTF16(L"Placeholder from generated content3.14"),
EXPECT_EQ(u"Placeholder from generated content3.14",
text_position->GetText());
}

Expand Down Expand Up @@ -7712,10 +7709,10 @@ TEST_F(AXPositionTest, EmptyObjectReplacedByCharacterTextNavigation) {
GetTreeID(), root_1.id, 0 /* text_offset */,
ax::mojom::TextAffinity::kDownstream);

expected_text = WideToUTF16(L"Hello ") + AXNodePosition::kEmbeddedCharacter +
WideToUTF16(L" world3.14") +
AXNodePosition::kEmbeddedCharacter + WideToUTF16(L"hey") +
AXNodePosition::kEmbeddedCharacter;
expected_text =
std::u16string(u"Hello ") + AXNodePosition::kEmbeddedCharacter +
std::u16string(u" world3.14") + AXNodePosition::kEmbeddedCharacter +
std::u16string(u"hey") + AXNodePosition::kEmbeddedCharacter;
ASSERT_EQ(expected_text, position->GetText());

// MaxTextOffset() with an embedded object replacement character.
Expand Down
Expand Up @@ -349,7 +349,7 @@ TEST_F(AXPlatformNodeTextProviderTest,
base::win::ScopedBstr text_content;
EXPECT_HRESULT_SUCCEEDED(
text_range_provider->GetText(-1, text_content.Receive()));
EXPECT_EQ(base::WideToUTF16(text_content.Get()),
EXPECT_EQ(fml::WideStringToUtf16(text_content.Get()),
u"Dialog label.Dialog description." + kEmbeddedCharacterAsString +
u"ok.Some more detail " + u"about dialog.");

Expand Down
Expand Up @@ -13,6 +13,7 @@
#include "ax/platform/ax_platform_node_win.h"
#include "ax/platform/ax_platform_tree_manager.h"
#include "base/win/variant_vector.h"
#include "flutter/fml/platform/win/wstring_conversion.h"

#define UIA_VALIDATE_TEXTRANGEPROVIDER_CALL() \
if (!GetOwner() || !GetOwner()->GetDelegate() || !start() || \
Expand Down Expand Up @@ -483,7 +484,7 @@ HRESULT AXPlatformNodeTextRangeProviderWin::FindText(
ScopedAXEmbeddedObjectBehaviorSetter ax_embedded_object_behavior(
AXEmbeddedObjectBehavior::kSuppressCharacter);

std::u16string search_string = base::WideToUTF16(string);
std::u16string search_string = fml::WideStringToUtf16(string);
if (search_string.length() <= 0)
return E_INVALIDARG;

Expand Down Expand Up @@ -703,7 +704,7 @@ HRESULT AXPlatformNodeTextRangeProviderWin::GetText(int max_count, BSTR* text) {
if (max_count < -1)
return E_INVALIDARG;

std::wstring full_text = base::UTF16ToWide(GetString(max_count));
std::wstring full_text = fml::Utf16ToWideString(GetString(max_count));
if (!full_text.empty()) {
size_t length = full_text.length();

Expand Down

0 comments on commit 7bbe79e

Please sign in to comment.