ax_language_detection_unittest.cc 54 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760
  1. // Copyright 2018 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. #include "ui/accessibility/ax_language_detection.h"
  5. #include <stddef.h>
  6. #include <stdint.h>
  7. #include <memory>
  8. #include "base/command_line.h"
  9. #include "base/containers/flat_set.h"
  10. #include "base/test/metrics/histogram_tester.h"
  11. #include "base/test/scoped_feature_list.h"
  12. #include "testing/gtest/include/gtest/gtest.h"
  13. #include "ui/accessibility/accessibility_features.h"
  14. #include "ui/accessibility/accessibility_switches.h"
  15. #include "ui/accessibility/ax_enums.mojom.h"
  16. #include "ui/accessibility/ax_node.h"
  17. #include "ui/accessibility/ax_tree.h"
  18. namespace ui {
  19. const std::string kTextEnglish =
  20. "This is text created using Google Translate, it is unlikely to be "
  21. "idiomatic in the given target language. This text is only used to "
  22. "test language detection";
  23. const std::string kTextFrench =
  24. "Ce texte a été créé avec Google Translate, il est peu probable qu'il "
  25. "soit idiomatique dans la langue cible indiquée Ce texte est "
  26. "uniquement utilisé pour tester la détection de la langue.";
  27. const std::string kTextGerman =
  28. "Dies ist ein mit Google Translate erstellter Text. Es ist "
  29. "unwahrscheinlich, dass er in der angegebenen Zielsprache idiomatisch "
  30. "ist. Dieser Text wird nur zum Testen der Spracherkennung verwendet.";
  31. const std::string kTextSpanish =
  32. "Este es un texto creado usando Google Translate, es poco probable que sea "
  33. "idiomático en el idioma de destino dado. Este texto solo se usa para "
  34. "probar la detección de idioma.";
  35. // This test fixture is a friend of classes in ax_language_detection.h in order
  36. // to enable testing of internals.
  37. //
  38. // When used with TEST_F, the test body is a subclass of this fixture, so we
  39. // need to re-expose any members through this fixture in order for them to
  40. // be accessible from within the test body.
  41. class AXLanguageDetectionTestFixture : public testing::Test {
  42. public:
  43. AXLanguageDetectionTestFixture() = default;
  44. ~AXLanguageDetectionTestFixture() override = default;
  45. AXLanguageDetectionTestFixture(const AXLanguageDetectionTestFixture&) =
  46. delete;
  47. AXLanguageDetectionTestFixture& operator=(
  48. const AXLanguageDetectionTestFixture&) = delete;
  49. protected:
  50. bool IsStaticLanguageDetectionEnabled() {
  51. return AXLanguageDetectionManager::IsStaticLanguageDetectionEnabled();
  52. }
  53. bool IsDynamicLanguageDetectionEnabled() {
  54. return AXLanguageDetectionManager::IsDynamicLanguageDetectionEnabled();
  55. }
  56. AXLanguageDetectionObserver* getObserver(AXTree& tree) {
  57. return tree.language_detection_manager->language_detection_observer_.get();
  58. }
  59. int get_score(AXTree& tree, const std::string& lang) {
  60. return tree.language_detection_manager->lang_info_stats_.GetScore(lang);
  61. }
  62. // Accessors for testing metric data.
  63. void disable_metric_clearing(AXTree& tree) {
  64. tree.language_detection_manager->lang_info_stats_.disable_metric_clearing_ =
  65. true;
  66. }
  67. int count_detection_attempted(AXTree& tree) const {
  68. return tree.language_detection_manager->lang_info_stats_
  69. .count_detection_attempted_;
  70. }
  71. int count_detection_results(AXTree& tree) const {
  72. return tree.language_detection_manager->lang_info_stats_
  73. .count_detection_results_;
  74. }
  75. int count_labelled(AXTree& tree) const {
  76. return tree.language_detection_manager->lang_info_stats_.count_labelled_;
  77. }
  78. int count_labelled_with_top_result(AXTree& tree) const {
  79. return tree.language_detection_manager->lang_info_stats_
  80. .count_labelled_with_top_result_;
  81. }
  82. int count_overridden(AXTree& tree) const {
  83. return tree.language_detection_manager->lang_info_stats_.count_overridden_;
  84. }
  85. const base::flat_set<std::string>& unique_top_lang_detected(
  86. AXTree& tree) const {
  87. return tree.language_detection_manager->lang_info_stats_
  88. .unique_top_lang_detected_;
  89. }
  90. };
  91. class AXLanguageDetectionTestStaticContent
  92. : public AXLanguageDetectionTestFixture {
  93. public:
  94. AXLanguageDetectionTestStaticContent() = default;
  95. ~AXLanguageDetectionTestStaticContent() override = default;
  96. AXLanguageDetectionTestStaticContent(
  97. const AXLanguageDetectionTestStaticContent&) = delete;
  98. AXLanguageDetectionTestStaticContent& operator=(
  99. const AXLanguageDetectionTestStaticContent&) = delete;
  100. void SetUp() override {
  101. AXLanguageDetectionTestFixture::SetUp();
  102. base::CommandLine::ForCurrentProcess()->AppendSwitch(
  103. ::switches::kEnableExperimentalAccessibilityLanguageDetection);
  104. }
  105. };
  106. class AXLanguageDetectionTestDynamicContent
  107. : public AXLanguageDetectionTestStaticContent {
  108. public:
  109. AXLanguageDetectionTestDynamicContent() = default;
  110. ~AXLanguageDetectionTestDynamicContent() override = default;
  111. AXLanguageDetectionTestDynamicContent(
  112. const AXLanguageDetectionTestDynamicContent&) = delete;
  113. AXLanguageDetectionTestDynamicContent& operator=(
  114. const AXLanguageDetectionTestDynamicContent&) = delete;
  115. void SetUp() override {
  116. AXLanguageDetectionTestStaticContent::SetUp();
  117. base::CommandLine::ForCurrentProcess()->AppendSwitch(
  118. ::switches::kEnableExperimentalAccessibilityLanguageDetectionDynamic);
  119. }
  120. };
  121. TEST_F(AXLanguageDetectionTestFixture, StaticContentFeatureFlag) {
  122. // TODO(crbug/889370): Remove this test once this feature is stable
  123. EXPECT_FALSE(
  124. ::switches::IsExperimentalAccessibilityLanguageDetectionEnabled());
  125. EXPECT_FALSE(IsStaticLanguageDetectionEnabled());
  126. base::CommandLine::ForCurrentProcess()->AppendSwitch(
  127. ::switches::kEnableExperimentalAccessibilityLanguageDetection);
  128. EXPECT_TRUE(
  129. ::switches::IsExperimentalAccessibilityLanguageDetectionEnabled());
  130. EXPECT_TRUE(IsStaticLanguageDetectionEnabled());
  131. }
  132. TEST_F(AXLanguageDetectionTestFixture, DynamicContentFeatureFlag) {
  133. // TODO(crbug/889370): Remove this test once this feature is stable
  134. EXPECT_FALSE(
  135. ::switches::IsExperimentalAccessibilityLanguageDetectionDynamicEnabled());
  136. EXPECT_FALSE(IsDynamicLanguageDetectionEnabled());
  137. base::CommandLine::ForCurrentProcess()->AppendSwitch(
  138. ::switches::kEnableExperimentalAccessibilityLanguageDetectionDynamic);
  139. EXPECT_TRUE(
  140. ::switches::IsExperimentalAccessibilityLanguageDetectionDynamicEnabled());
  141. EXPECT_TRUE(IsDynamicLanguageDetectionEnabled());
  142. }
  143. TEST_F(AXLanguageDetectionTestFixture, FeatureFlag) {
  144. // TODO(crbug/889370): Remove this test once this feature is stable
  145. EXPECT_FALSE(IsStaticLanguageDetectionEnabled());
  146. EXPECT_FALSE(IsDynamicLanguageDetectionEnabled());
  147. base::test::ScopedFeatureList scoped_feature_list;
  148. scoped_feature_list.InitWithFeatures(
  149. {features::kEnableAccessibilityLanguageDetection}, {});
  150. EXPECT_TRUE(IsStaticLanguageDetectionEnabled());
  151. EXPECT_TRUE(IsDynamicLanguageDetectionEnabled());
  152. }
  153. TEST(AXLanguageDetectionTest, LangAttrInheritanceFeatureFlagOff) {
  154. // Test lang attribute inheritance when feature flag is off.
  155. //
  156. // Lang attribute inheritance is handled by GetLanguage.
  157. //
  158. // Tree:
  159. // 1
  160. // 2 3
  161. // 4
  162. // 5
  163. //
  164. // 1 - English lang attribute
  165. // 2 - French lang attribute
  166. //
  167. // Expected:
  168. // 3 - inherit English from 1
  169. // 4 - inherit French from 2
  170. // 5 - inherit Frnech from 4/2
  171. AXTreeUpdate initial_state;
  172. initial_state.root_id = 1;
  173. initial_state.nodes.resize(5);
  174. {
  175. AXNodeData& node1 = initial_state.nodes[0];
  176. node1.id = 1;
  177. node1.role = ax::mojom::Role::kGenericContainer;
  178. node1.child_ids.resize(2);
  179. node1.child_ids[0] = 2;
  180. node1.child_ids[1] = 3;
  181. node1.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "en");
  182. }
  183. {
  184. AXNodeData& node2 = initial_state.nodes[1];
  185. node2.id = 2;
  186. node2.role = ax::mojom::Role::kGenericContainer;
  187. node2.child_ids.resize(1);
  188. node2.child_ids[0] = 4;
  189. node2.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "fr");
  190. }
  191. {
  192. AXNodeData& node3 = initial_state.nodes[2];
  193. node3.id = 3;
  194. node3.role = ax::mojom::Role::kStaticText;
  195. }
  196. {
  197. AXNodeData& node4 = initial_state.nodes[3];
  198. node4.id = 4;
  199. node4.role = ax::mojom::Role::kStaticText;
  200. node4.child_ids.resize(1);
  201. node4.child_ids[0] = 5;
  202. }
  203. {
  204. AXNodeData& node5 = initial_state.nodes[4];
  205. node5.id = 5;
  206. node5.role = ax::mojom::Role::kInlineTextBox;
  207. }
  208. AXTree tree(initial_state);
  209. ASSERT_NE(tree.language_detection_manager, nullptr);
  210. tree.language_detection_manager->DetectLanguages();
  211. tree.language_detection_manager->LabelLanguages();
  212. {
  213. AXNode* node1 = tree.GetFromId(1);
  214. EXPECT_EQ(node1->GetLanguageInfo(), nullptr);
  215. EXPECT_EQ(node1->GetLanguage(), "en");
  216. }
  217. {
  218. AXNode* node2 = tree.GetFromId(2);
  219. EXPECT_EQ(node2->GetLanguageInfo(), nullptr);
  220. EXPECT_EQ(node2->GetLanguage(), "fr");
  221. }
  222. {
  223. AXNode* node3 = tree.GetFromId(3);
  224. EXPECT_EQ(node3->GetLanguageInfo(), nullptr);
  225. EXPECT_EQ(node3->GetLanguage(), "en");
  226. }
  227. {
  228. AXNode* node4 = tree.GetFromId(4);
  229. EXPECT_EQ(node4->GetLanguageInfo(), nullptr);
  230. EXPECT_EQ(node4->GetLanguage(), "fr");
  231. }
  232. {
  233. AXNode* node5 = tree.GetFromId(5);
  234. EXPECT_EQ(node5->GetLanguageInfo(), nullptr);
  235. EXPECT_EQ(node5->GetLanguage(), "fr");
  236. }
  237. }
  238. TEST(AXLanguageDetectionTest, LangAttrInheritanceFeatureFlagOn) {
  239. base::CommandLine::ForCurrentProcess()->AppendSwitch(
  240. ::switches::kEnableExperimentalAccessibilityLanguageDetection);
  241. // Test lang attribute inheritance in the absence of any detected language.
  242. //
  243. // Lang attribute inheritance is handled by the Label step.
  244. //
  245. // Tree:
  246. // 1
  247. // 2 3
  248. // 4
  249. // 5
  250. //
  251. // 1 - English lang attribute
  252. // 2 - French lang attribute
  253. //
  254. // Expected:
  255. // 3 - inherit English from 1
  256. // 4 - inherit French from 2
  257. // 5 - inherit Frnech from 4/2
  258. AXTreeUpdate initial_state;
  259. initial_state.root_id = 1;
  260. initial_state.nodes.resize(5);
  261. {
  262. AXNodeData& node1 = initial_state.nodes[0];
  263. node1.id = 1;
  264. node1.role = ax::mojom::Role::kGenericContainer;
  265. node1.child_ids.resize(2);
  266. node1.child_ids[0] = 2;
  267. node1.child_ids[1] = 3;
  268. node1.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "en");
  269. }
  270. {
  271. AXNodeData& node2 = initial_state.nodes[1];
  272. node2.id = 2;
  273. node2.role = ax::mojom::Role::kGenericContainer;
  274. node2.child_ids.resize(1);
  275. node2.child_ids[0] = 4;
  276. node2.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "fr");
  277. }
  278. {
  279. AXNodeData& node3 = initial_state.nodes[2];
  280. node3.id = 3;
  281. node3.role = ax::mojom::Role::kStaticText;
  282. }
  283. {
  284. AXNodeData& node4 = initial_state.nodes[3];
  285. node4.id = 4;
  286. node4.role = ax::mojom::Role::kStaticText;
  287. node4.child_ids.resize(1);
  288. node4.child_ids[0] = 5;
  289. }
  290. {
  291. AXNodeData& node5 = initial_state.nodes[4];
  292. node5.id = 5;
  293. node5.role = ax::mojom::Role::kInlineTextBox;
  294. }
  295. AXTree tree(initial_state);
  296. ASSERT_NE(tree.language_detection_manager, nullptr);
  297. tree.language_detection_manager->DetectLanguages();
  298. tree.language_detection_manager->LabelLanguages();
  299. {
  300. AXNode* node1 = tree.GetFromId(1);
  301. // No detection for non text nodes.
  302. EXPECT_EQ(node1->GetLanguageInfo(), nullptr);
  303. EXPECT_EQ(node1->GetLanguage(), "en");
  304. }
  305. {
  306. AXNode* node2 = tree.GetFromId(2);
  307. EXPECT_EQ(node2->GetLanguageInfo(), nullptr);
  308. EXPECT_EQ(node2->GetLanguage(), "fr");
  309. }
  310. {
  311. AXNode* node3 = tree.GetFromId(3);
  312. // Inherited languages are not stored in lang info.
  313. EXPECT_EQ(node3->GetLanguageInfo(), nullptr);
  314. EXPECT_EQ(node3->GetLanguage(), "en");
  315. }
  316. {
  317. AXNode* node4 = tree.GetFromId(4);
  318. // Inherited languages are not stored in lang info.
  319. EXPECT_EQ(node4->GetLanguageInfo(), nullptr);
  320. EXPECT_EQ(node4->GetLanguage(), "fr");
  321. }
  322. {
  323. AXNode* node5 = tree.GetFromId(5);
  324. // Inherited languages are not stored in lang info.
  325. EXPECT_EQ(node5->GetLanguageInfo(), nullptr);
  326. EXPECT_EQ(node5->GetLanguage(), "fr");
  327. }
  328. }
  329. // Tests that AXNode::GetLanguage() terminates when there is no lang attribute.
  330. TEST_F(AXLanguageDetectionTestStaticContent, GetLanguageBoringTree) {
  331. // This test checks the behaviour of Detect, Label, and GetLanguage on a
  332. // 'boring' tree.
  333. //
  334. // The tree built here contains no lang attributes, nor does it contain any
  335. // text to perform detection on.
  336. //
  337. // Tree:
  338. // 1
  339. // 2 3
  340. // 4
  341. AXTreeUpdate initial_state;
  342. initial_state.root_id = 1;
  343. initial_state.nodes.resize(4);
  344. initial_state.nodes[0].id = 1;
  345. initial_state.nodes[0].child_ids.resize(2);
  346. initial_state.nodes[0].child_ids[0] = 2;
  347. initial_state.nodes[0].child_ids[1] = 3;
  348. initial_state.nodes[1].id = 2;
  349. initial_state.nodes[1].child_ids.resize(1);
  350. initial_state.nodes[1].child_ids[0] = 4;
  351. initial_state.nodes[2].id = 3;
  352. initial_state.nodes[3].id = 4;
  353. AXTree tree(initial_state);
  354. ASSERT_NE(tree.language_detection_manager, nullptr);
  355. tree.language_detection_manager->DetectLanguages();
  356. tree.language_detection_manager->LabelLanguages();
  357. // Check that tree parenting conforms to expected shape.
  358. AXNode* node1 = tree.GetFromId(1);
  359. EXPECT_EQ(node1->parent(), nullptr);
  360. AXNode* node2 = tree.GetFromId(2);
  361. ASSERT_EQ(node2->parent(), node1);
  362. EXPECT_EQ(node2->parent()->parent(), nullptr);
  363. AXNode* node3 = tree.GetFromId(3);
  364. ASSERT_EQ(node3->parent(), node1);
  365. EXPECT_EQ(node3->parent()->parent(), nullptr);
  366. AXNode* node4 = tree.GetFromId(4);
  367. ASSERT_EQ(node4->parent(), node2);
  368. ASSERT_EQ(node4->parent()->parent(), node1);
  369. EXPECT_EQ(node4->parent()->parent()->parent(), nullptr);
  370. EXPECT_EQ(node1->GetLanguage(), "");
  371. EXPECT_EQ(node2->GetLanguage(), "");
  372. EXPECT_EQ(node3->GetLanguage(), "");
  373. EXPECT_EQ(node4->GetLanguage(), "");
  374. }
  375. TEST_F(AXLanguageDetectionTestStaticContent, Basic) {
  376. // Tree:
  377. // 1
  378. // 2 3
  379. // 4
  380. // 5
  381. //
  382. // 1 - German lang attribute, no text
  383. // 2 - French lang attribute, no text
  384. // 3 - no attribute, French text
  385. // 4 - no attribute, English text
  386. // 5 - no attribute, no text
  387. //
  388. // Expected:
  389. // 3 - French detected
  390. // 4 - English detected
  391. // 5 - inherit English from 4
  392. AXTreeUpdate initial_state;
  393. initial_state.root_id = 1;
  394. initial_state.nodes.resize(5);
  395. {
  396. AXNodeData& node1 = initial_state.nodes[0];
  397. node1.id = 1;
  398. node1.role = ax::mojom::Role::kGenericContainer;
  399. node1.child_ids.resize(2);
  400. node1.child_ids[0] = 2;
  401. node1.child_ids[1] = 3;
  402. node1.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "de");
  403. }
  404. {
  405. AXNodeData& node2 = initial_state.nodes[1];
  406. node2.id = 2;
  407. node2.role = ax::mojom::Role::kGenericContainer;
  408. node2.child_ids.resize(1);
  409. node2.child_ids[0] = 4;
  410. node2.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "fr");
  411. }
  412. {
  413. AXNodeData& node3 = initial_state.nodes[2];
  414. node3.id = 3;
  415. node3.role = ax::mojom::Role::kStaticText;
  416. node3.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextFrench);
  417. }
  418. {
  419. AXNodeData& node4 = initial_state.nodes[3];
  420. node4.id = 4;
  421. node4.child_ids.resize(1);
  422. node4.child_ids[0] = 5;
  423. node4.role = ax::mojom::Role::kStaticText;
  424. node4.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextEnglish);
  425. }
  426. {
  427. AXNodeData& node5 = initial_state.nodes[4];
  428. node5.id = 5;
  429. node5.role = ax::mojom::Role::kInlineTextBox;
  430. node5.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextEnglish);
  431. }
  432. AXTree tree(initial_state);
  433. ASSERT_NE(tree.language_detection_manager, nullptr);
  434. tree.language_detection_manager->DetectLanguages();
  435. tree.language_detection_manager->LabelLanguages();
  436. {
  437. AXNode* node1 = tree.GetFromId(1);
  438. // node1 is not a text node, so no lang info should be attached.
  439. EXPECT_EQ(node1->GetLanguageInfo(), nullptr);
  440. EXPECT_EQ(node1->GetLanguage(), "de");
  441. }
  442. {
  443. AXNode* node2 = tree.GetFromId(2);
  444. // node2 is not a text node, so no lang info should be attached.
  445. EXPECT_EQ(node2->GetLanguageInfo(), nullptr);
  446. EXPECT_EQ(node2->GetLanguage(), "fr");
  447. }
  448. {
  449. AXNode* node3 = tree.GetFromId(3);
  450. EXPECT_TRUE(node3->IsText());
  451. EXPECT_NE(node3->GetLanguageInfo(), nullptr);
  452. EXPECT_EQ(node3->GetLanguage(), "fr");
  453. }
  454. {
  455. AXNode* node4 = tree.GetFromId(4);
  456. EXPECT_TRUE(node4->IsText());
  457. EXPECT_NE(node4->GetLanguageInfo(), nullptr);
  458. EXPECT_EQ(node4->GetLanguage(), "en");
  459. }
  460. {
  461. AXNode* node5 = tree.GetFromId(5);
  462. EXPECT_TRUE(node5->IsText());
  463. // Inherited languages are not stored in lang info.
  464. EXPECT_EQ(node5->GetLanguageInfo(), nullptr);
  465. EXPECT_EQ(node5->GetLanguage(), "en");
  466. }
  467. }
  468. TEST_F(AXLanguageDetectionTestStaticContent, MetricCollection) {
  469. // Tree:
  470. // 1
  471. // 2 3 4 5 6
  472. //
  473. // 1 - German lang attribute, no text
  474. // 2 - no attribute, German text
  475. // 3 - no attribute, French text
  476. // 4 - no attribute, English text
  477. // 5 - no attribute, Spanish text
  478. // 6 - no attribute, text too short to get detection results.
  479. //
  480. // Expected:
  481. // 2 - German detected
  482. // 3 - French detected
  483. // 4 - English detected
  484. // 5 - Spanish detected
  485. // 6 - too short for results
  486. //
  487. // only 3 of these languages can be labelled due to heuristics.
  488. AXTreeUpdate initial_state;
  489. initial_state.root_id = 1;
  490. initial_state.nodes.resize(6);
  491. {
  492. AXNodeData& node1 = initial_state.nodes[0];
  493. node1.id = 1;
  494. node1.role = ax::mojom::Role::kGenericContainer;
  495. node1.child_ids.resize(5);
  496. node1.child_ids[0] = 2;
  497. node1.child_ids[1] = 3;
  498. node1.child_ids[2] = 4;
  499. node1.child_ids[3] = 5;
  500. node1.child_ids[4] = 6;
  501. node1.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "de");
  502. }
  503. {
  504. AXNodeData& node2 = initial_state.nodes[1];
  505. node2.id = 2;
  506. node2.role = ax::mojom::Role::kStaticText;
  507. node2.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextGerman);
  508. }
  509. {
  510. AXNodeData& node3 = initial_state.nodes[2];
  511. node3.id = 3;
  512. node3.role = ax::mojom::Role::kStaticText;
  513. node3.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextFrench);
  514. }
  515. {
  516. AXNodeData& node4 = initial_state.nodes[3];
  517. node4.id = 4;
  518. node4.role = ax::mojom::Role::kStaticText;
  519. node4.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextEnglish);
  520. }
  521. {
  522. AXNodeData& node5 = initial_state.nodes[4];
  523. node5.id = 5;
  524. node5.role = ax::mojom::Role::kStaticText;
  525. node5.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextSpanish);
  526. }
  527. {
  528. AXNodeData& node6 = initial_state.nodes[5];
  529. node6.id = 6;
  530. node6.role = ax::mojom::Role::kStaticText;
  531. node6.AddStringAttribute(ax::mojom::StringAttribute::kName,
  532. "too short for detection.");
  533. }
  534. AXTree tree(initial_state);
  535. ASSERT_NE(tree.language_detection_manager, nullptr);
  536. // Specifically disable clearing of metrics.
  537. disable_metric_clearing(tree);
  538. // Our histogram for testing.
  539. base::HistogramTester histograms;
  540. tree.language_detection_manager->DetectLanguages();
  541. tree.language_detection_manager->LabelLanguages();
  542. // All 4 of our languages should have been detected for one node each, scoring
  543. // a maximum 3 points.
  544. EXPECT_EQ(3, get_score(tree, "de"));
  545. EXPECT_EQ(3, get_score(tree, "en"));
  546. EXPECT_EQ(3, get_score(tree, "fr"));
  547. EXPECT_EQ(3, get_score(tree, "es"));
  548. // 5 nodes (2, 3, 4, 5, 6) should have had detection attempted.
  549. EXPECT_EQ(5, count_detection_attempted(tree));
  550. histograms.ExpectUniqueSample(
  551. "Accessibility.LanguageDetection.CountDetectionAttempted", 5, 1);
  552. // 4 nodes (2, 3, 4, 5) should have had detection results.
  553. EXPECT_EQ(4, count_detection_results(tree));
  554. // 5 nodes attempted, 4 got results = 4*100/5 = 80%
  555. histograms.ExpectUniqueSample(
  556. "Accessibility.LanguageDetection.PercentageLanguageDetected", 80, 1);
  557. // 3 nodes (any of 2, 3, 4, 5) should have been labelled.
  558. EXPECT_EQ(3, count_labelled(tree));
  559. histograms.ExpectUniqueSample("Accessibility.LanguageDetection.CountLabelled",
  560. 3, 1);
  561. // 3 nodes (any of 2, 3, 4, 5) should have been given top label.
  562. EXPECT_EQ(3, count_labelled_with_top_result(tree));
  563. // 3 nodes labelled, all of them given top result = 100%.
  564. histograms.ExpectUniqueSample(
  565. "Accessibility.LanguageDetection.PercentageLabelledWithTop", 100, 1);
  566. // 3 nodes (3, 4, 5) should have been labelled to disagree with node1 author
  567. // provided language.
  568. EXPECT_EQ(3, count_overridden(tree));
  569. // 3 nodes labelled, all 3 disagree with node1 = 100%.
  570. histograms.ExpectUniqueSample(
  571. "Accessibility.LanguageDetection.PercentageOverridden", 100, 1);
  572. // There should be 4 unique languages (de, en, fr, es).
  573. {
  574. const auto& top_lang = unique_top_lang_detected(tree);
  575. const base::flat_set<std::string> expected_top_lang = {"de", "en", "es",
  576. "fr"};
  577. EXPECT_EQ(top_lang, expected_top_lang);
  578. }
  579. histograms.ExpectUniqueSample("Accessibility.LanguageDetection.LangsPerPage",
  580. 4, 1);
  581. }
  582. TEST_F(AXLanguageDetectionTestStaticContent, DetectOnly) {
  583. // This tests a Detect step without any matching Label step.
  584. //
  585. // Tree:
  586. // 1
  587. // 2 3
  588. // 4
  589. // 5
  590. //
  591. // 1 - German lang attribute, no text
  592. // 2 - French lang attribute, no text
  593. // 3 - no attribute, French text
  594. // 4 - no attribute, English text
  595. // 5 - no attribute, no text
  596. //
  597. // Expected:
  598. // 3 - French detected, never labelled, so still inherits German from 1
  599. // 4 - English detected, never labelled, so still inherits French from 2
  600. // 5 - English inherited from 4, still inherits French from 4
  601. AXTreeUpdate initial_state;
  602. initial_state.root_id = 1;
  603. initial_state.nodes.resize(5);
  604. {
  605. AXNodeData& node1 = initial_state.nodes[0];
  606. node1.id = 1;
  607. node1.role = ax::mojom::Role::kGenericContainer;
  608. node1.child_ids.resize(2);
  609. node1.child_ids[0] = 2;
  610. node1.child_ids[1] = 3;
  611. node1.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "de");
  612. }
  613. {
  614. AXNodeData& node2 = initial_state.nodes[1];
  615. node2.id = 2;
  616. node2.role = ax::mojom::Role::kGenericContainer;
  617. node2.child_ids.resize(1);
  618. node2.child_ids[0] = 4;
  619. node2.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "fr");
  620. }
  621. {
  622. AXNodeData& node3 = initial_state.nodes[2];
  623. node3.id = 3;
  624. node3.role = ax::mojom::Role::kStaticText;
  625. node3.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextFrench);
  626. }
  627. {
  628. AXNodeData& node4 = initial_state.nodes[3];
  629. node4.id = 4;
  630. node4.child_ids.resize(1);
  631. node4.child_ids[0] = 5;
  632. node4.role = ax::mojom::Role::kStaticText;
  633. node4.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextEnglish);
  634. }
  635. {
  636. AXNodeData& node5 = initial_state.nodes[4];
  637. node5.id = 5;
  638. node5.role = ax::mojom::Role::kInlineTextBox;
  639. node5.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextEnglish);
  640. }
  641. AXTree tree(initial_state);
  642. ASSERT_NE(tree.language_detection_manager, nullptr);
  643. tree.language_detection_manager->DetectLanguages();
  644. // Purposefully not calling Label so we can test Detect in isolation.
  645. {
  646. AXNode* node1 = tree.GetFromId(1);
  647. // node1 is not a text node, so no lang info should be attached.
  648. EXPECT_EQ(node1->GetLanguageInfo(), nullptr);
  649. EXPECT_EQ(node1->GetLanguage(), "de");
  650. }
  651. {
  652. AXNode* node2 = tree.GetFromId(2);
  653. // node2 is not a text node, so no lang info should be attached.
  654. EXPECT_EQ(node2->GetLanguageInfo(), nullptr);
  655. EXPECT_EQ(node2->GetLanguage(), "fr");
  656. }
  657. {
  658. AXNode* node3 = tree.GetFromId(3);
  659. EXPECT_TRUE(node3->IsText());
  660. ASSERT_NE(node3->GetLanguageInfo(), nullptr);
  661. ASSERT_GT(node3->GetLanguageInfo()->detected_languages.size(), (unsigned)0);
  662. ASSERT_EQ(node3->GetLanguageInfo()->detected_languages[0], "fr");
  663. EXPECT_TRUE(node3->GetLanguageInfo()->language.empty());
  664. EXPECT_EQ(node3->GetLanguage(), "de");
  665. }
  666. {
  667. AXNode* node4 = tree.GetFromId(4);
  668. EXPECT_TRUE(node4->IsText());
  669. ASSERT_NE(node4->GetLanguageInfo(), nullptr);
  670. ASSERT_GT(node4->GetLanguageInfo()->detected_languages.size(), (unsigned)0);
  671. ASSERT_EQ(node4->GetLanguageInfo()->detected_languages[0], "en");
  672. EXPECT_TRUE(node4->GetLanguageInfo()->language.empty());
  673. EXPECT_EQ(node4->GetLanguage(), "fr");
  674. }
  675. {
  676. AXNode* node5 = tree.GetFromId(5);
  677. EXPECT_TRUE(node5->IsText());
  678. // Inherited languages are not stored in lang info.
  679. ASSERT_EQ(node5->GetLanguageInfo(), nullptr);
  680. EXPECT_EQ(node5->GetLanguage(), "fr");
  681. }
  682. }
  683. TEST_F(AXLanguageDetectionTestStaticContent, kLanguageUntouched) {
  684. // This test is to ensure that the kLanguage string attribute is not updated
  685. // during language detection and labelling, even when it disagrees with the
  686. // detected language.
  687. // Built tree:
  688. // 1
  689. // 2 3
  690. //
  691. // 1 - German lang attribute, no text
  692. // 2 - English lang attribute, French text
  693. // 3 - French lang attribute, English text
  694. AXTreeUpdate initial_state;
  695. initial_state.root_id = 1;
  696. initial_state.nodes.resize(3);
  697. {
  698. AXNodeData& node1 = initial_state.nodes[0];
  699. node1.id = 1;
  700. node1.role = ax::mojom::Role::kGenericContainer;
  701. node1.child_ids.resize(2);
  702. node1.child_ids[0] = 2;
  703. node1.child_ids[1] = 3;
  704. node1.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "de");
  705. }
  706. {
  707. AXNodeData& node2 = initial_state.nodes[1];
  708. node2.id = 2;
  709. node2.role = ax::mojom::Role::kStaticText;
  710. node2.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "en");
  711. node2.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextFrench);
  712. }
  713. {
  714. AXNodeData& node3 = initial_state.nodes[2];
  715. node3.id = 3;
  716. node3.role = ax::mojom::Role::kStaticText;
  717. node3.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "fr");
  718. node3.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextEnglish);
  719. }
  720. AXTree tree(initial_state);
  721. ASSERT_NE(tree.language_detection_manager, nullptr);
  722. tree.language_detection_manager->DetectLanguages();
  723. tree.language_detection_manager->LabelLanguages();
  724. {
  725. AXNode* node1 = tree.GetFromId(1);
  726. ASSERT_EQ(node1->GetLanguageInfo(), nullptr);
  727. EXPECT_EQ(node1->GetLanguage(), "de");
  728. }
  729. {
  730. // French should be detected, original English attr should be untouched.
  731. AXNode* node2 = tree.GetFromId(2);
  732. ASSERT_NE(node2->GetLanguageInfo(), nullptr);
  733. EXPECT_EQ(node2->GetLanguageInfo()->language, "fr");
  734. EXPECT_EQ(node2->GetStringAttribute(ax::mojom::StringAttribute::kLanguage),
  735. "en");
  736. EXPECT_EQ(node2->GetLanguage(), "fr");
  737. }
  738. {
  739. // English should be detected, original French attr should be untouched.
  740. AXNode* node3 = tree.GetFromId(3);
  741. ASSERT_NE(node3->GetLanguageInfo(), nullptr);
  742. EXPECT_EQ(node3->GetLanguageInfo()->language, "en");
  743. EXPECT_EQ(node3->GetStringAttribute(ax::mojom::StringAttribute::kLanguage),
  744. "fr");
  745. EXPECT_EQ(node3->GetLanguage(), "en");
  746. }
  747. }
  748. // Test RegisterLanguageDetectionObserver correctly respects the command line
  749. // flags.
  750. TEST_F(AXLanguageDetectionTestFixture, ObserverRegistrationObeysFlag) {
  751. // Enable only the flag controlling static language detection.
  752. base::CommandLine::ForCurrentProcess()->AppendSwitch(
  753. ::switches::kEnableExperimentalAccessibilityLanguageDetection);
  754. // Construct empty tree and check initialisation.
  755. AXTree tree;
  756. ASSERT_NE(tree.language_detection_manager, nullptr);
  757. ASSERT_EQ(getObserver(tree), nullptr);
  758. // Try registration without enabling Dynamic feature flag, should be a no-op.
  759. tree.language_detection_manager->RegisterLanguageDetectionObserver();
  760. ASSERT_EQ(getObserver(tree), nullptr);
  761. // Now enable the dynamic feature flag.
  762. base::CommandLine::ForCurrentProcess()->AppendSwitch(
  763. ::switches::kEnableExperimentalAccessibilityLanguageDetectionDynamic);
  764. // Try registration again, this should construct and register observer as flag
  765. // is now enabled.
  766. tree.language_detection_manager->RegisterLanguageDetectionObserver();
  767. // Check our observer was constructed.
  768. ASSERT_NE(getObserver(tree), nullptr);
  769. // Check our observer was registered in our tree.
  770. ASSERT_TRUE(tree.HasObserver(getObserver(tree)));
  771. }
  772. // Test RegisterLanguageDetectionObserver correctly respects the feature flag.
  773. TEST_F(AXLanguageDetectionTestFixture, ObserverRegistrationObeysFeatureFlag) {
  774. // Construct empty tree and check initialisation.
  775. AXTree tree;
  776. ASSERT_NE(tree.language_detection_manager, nullptr);
  777. ASSERT_EQ(getObserver(tree), nullptr);
  778. // Try registration without enabling Dynamic feature flag, should be a no-op.
  779. tree.language_detection_manager->RegisterLanguageDetectionObserver();
  780. ASSERT_EQ(getObserver(tree), nullptr);
  781. // Enable general feature flag which gates both Static and Dynamic features.
  782. base::test::ScopedFeatureList scoped_feature_list;
  783. scoped_feature_list.InitWithFeatures(
  784. {features::kEnableAccessibilityLanguageDetection}, {});
  785. // Try registration again, this should now construct and register an observer.
  786. tree.language_detection_manager->RegisterLanguageDetectionObserver();
  787. // Check our observer was constructed.
  788. ASSERT_NE(getObserver(tree), nullptr);
  789. // Check our observer was registered in our tree.
  790. ASSERT_TRUE(tree.HasObserver(getObserver(tree)));
  791. }
  792. TEST_F(AXLanguageDetectionTestDynamicContent, Basic) {
  793. // Tree:
  794. // 1
  795. // 2
  796. //
  797. // 1 - kStaticText - English text.
  798. // 2 - kInlineTextBox - English text.
  799. AXTreeUpdate initial_state;
  800. initial_state.root_id = 1;
  801. initial_state.nodes.resize(2);
  802. // TODO(chrishall): Create more realistic kStaticText with multiple
  803. // kInlineTextBox(es) children. Look at the real-world behaviour of
  804. // kStaticText, kInlineText and kLineBreak around empty divs and empty lines
  805. // within paragraphs of text.
  806. {
  807. AXNodeData& node1 = initial_state.nodes[0];
  808. node1.id = 1;
  809. node1.role = ax::mojom::Role::kStaticText;
  810. node1.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextEnglish);
  811. node1.child_ids.resize(1);
  812. node1.child_ids[0] = 2;
  813. }
  814. {
  815. AXNodeData& node2 = initial_state.nodes[1];
  816. node2.id = 2;
  817. node2.role = ax::mojom::Role::kInlineTextBox;
  818. node2.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextEnglish);
  819. }
  820. AXTree tree(initial_state);
  821. ASSERT_NE(tree.language_detection_manager, nullptr);
  822. // Manually run initial language detection and labelling.
  823. tree.language_detection_manager->DetectLanguages();
  824. tree.language_detection_manager->LabelLanguages();
  825. // Quickly verify "before" state
  826. {
  827. AXNode* node1 = tree.GetFromId(1);
  828. ASSERT_NE(node1, nullptr);
  829. ASSERT_NE(node1->GetLanguageInfo(), nullptr);
  830. ASSERT_EQ(node1->GetLanguage(), "en");
  831. AXNode* node2 = tree.GetFromId(2);
  832. ASSERT_NE(node2, nullptr);
  833. // Inherited language not stored in lang info.
  834. ASSERT_EQ(node2->GetLanguageInfo(), nullptr);
  835. // Should still inherit language from parent.
  836. ASSERT_EQ(node2->GetLanguage(), "en");
  837. }
  838. // Manually register observer.
  839. AXLanguageDetectionObserver observer(&tree);
  840. // Observer constructor is responsible for attaching itself to tree.
  841. ASSERT_TRUE(tree.HasObserver(&observer));
  842. // Dynamic update
  843. //
  844. // New tree:
  845. // 1
  846. // 2
  847. //
  848. // 1 - Text changed to German.
  849. // 2 - Text changed to German.
  850. AXTreeUpdate update_state;
  851. update_state.root_id = 1;
  852. update_state.nodes.resize(2);
  853. // Change text to German.
  854. {
  855. AXNodeData& node1 = update_state.nodes[0];
  856. node1.id = 1;
  857. node1.role = ax::mojom::Role::kStaticText;
  858. node1.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextGerman);
  859. node1.child_ids.resize(1);
  860. node1.child_ids[0] = 2;
  861. }
  862. {
  863. AXNodeData& node2 = update_state.nodes[1];
  864. node2.id = 2;
  865. node2.role = ax::mojom::Role::kInlineTextBox;
  866. node2.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextGerman);
  867. }
  868. // Perform update.
  869. ASSERT_TRUE(tree.Unserialize(update_state));
  870. // Check language detection was re-run on new content.
  871. {
  872. AXNode* node1 = tree.GetFromId(1);
  873. ASSERT_NE(node1, nullptr);
  874. ASSERT_NE(node1->GetLanguageInfo(), nullptr);
  875. ASSERT_EQ(node1->GetLanguage(), "de");
  876. }
  877. {
  878. AXNode* node2 = tree.GetFromId(2);
  879. ASSERT_NE(node2, nullptr);
  880. // Inherited language not stored in lang info.
  881. ASSERT_EQ(node2->GetLanguageInfo(), nullptr);
  882. // Should inherit new language from parent.
  883. ASSERT_EQ(node2->GetLanguage(), "de");
  884. }
  885. }
  886. TEST_F(AXLanguageDetectionTestDynamicContent, MetricCollection) {
  887. // Tree:
  888. // 1
  889. // 2 3
  890. //
  891. // 1 - kGenericContainer, French lang attribute.
  892. // 2 - kStaticText - English text.
  893. // 3 - kSTaticText - German text.
  894. AXTreeUpdate initial_state;
  895. initial_state.root_id = 1;
  896. initial_state.nodes.resize(3);
  897. // TODO(chrishall): Create more realistic kStaticText with multiple
  898. // kInlineTextBox(es) children. Look at the real-world behaviour of
  899. // kStaticText, kInlineText and kLineBreak around empty divs and empty lines
  900. // within paragraphs of text.
  901. {
  902. AXNodeData& node1 = initial_state.nodes[0];
  903. node1.id = 1;
  904. node1.role = ax::mojom::Role::kGenericContainer;
  905. node1.child_ids.resize(2);
  906. node1.child_ids[0] = 2;
  907. node1.child_ids[1] = 3;
  908. node1.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "fr");
  909. }
  910. {
  911. AXNodeData& node2 = initial_state.nodes[1];
  912. node2.id = 2;
  913. node2.role = ax::mojom::Role::kStaticText;
  914. node2.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextEnglish);
  915. }
  916. {
  917. AXNodeData& node3 = initial_state.nodes[2];
  918. node3.id = 3;
  919. node3.role = ax::mojom::Role::kStaticText;
  920. node3.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextGerman);
  921. }
  922. AXTree tree(initial_state);
  923. ASSERT_NE(tree.language_detection_manager, nullptr);
  924. // Manually run initial language detection and labelling.
  925. tree.language_detection_manager->DetectLanguages();
  926. tree.language_detection_manager->LabelLanguages();
  927. // Quickly verify "before" metrics were cleared.
  928. EXPECT_EQ(0, count_detection_attempted(tree));
  929. // Specifically disable clearing of metrics for dynamic only.
  930. disable_metric_clearing(tree);
  931. // Our histogram for testing.
  932. base::HistogramTester histograms;
  933. // Manually register observer.
  934. AXLanguageDetectionObserver observer(&tree);
  935. // Observer constructor is responsible for attaching itself to tree.
  936. ASSERT_TRUE(tree.HasObserver(&observer));
  937. // Dynamic update
  938. //
  939. // New tree:
  940. // 1
  941. // 2 3 4
  942. //
  943. // 1 - no change.
  944. // 2 - Text changed to French.
  945. // 3 - no change.
  946. // 4 - new kStaticText node, Spanish text.
  947. AXTreeUpdate update_state;
  948. update_state.root_id = 1;
  949. update_state.nodes.resize(3);
  950. // Change text to German.
  951. {
  952. AXNodeData& node1 = update_state.nodes[0];
  953. node1.id = 1;
  954. node1.role = ax::mojom::Role::kGenericContainer;
  955. node1.child_ids.resize(3);
  956. node1.child_ids[0] = 2;
  957. node1.child_ids[1] = 3;
  958. node1.child_ids[2] = 4;
  959. node1.AddStringAttribute(ax::mojom::StringAttribute::kLanguage, "fr");
  960. }
  961. {
  962. AXNodeData& node2 = update_state.nodes[1];
  963. node2.id = 2;
  964. node2.role = ax::mojom::Role::kStaticText;
  965. node2.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextFrench);
  966. }
  967. {
  968. AXNodeData& node4 = update_state.nodes[2];
  969. node4.id = 4;
  970. node4.role = ax::mojom::Role::kStaticText;
  971. node4.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextSpanish);
  972. }
  973. // Perform update.
  974. ASSERT_TRUE(tree.Unserialize(update_state));
  975. // Check "after" metrics.
  976. // note that the metrics were cleared after static work had finished, so these
  977. // metrics only reflect the dynamic work.
  978. // All 4 of our languages should have been detected for one node each, scoring
  979. // a maximum 3 points.
  980. EXPECT_EQ(3, get_score(tree, "de"));
  981. EXPECT_EQ(3, get_score(tree, "en"));
  982. EXPECT_EQ(3, get_score(tree, "fr"));
  983. EXPECT_EQ(3, get_score(tree, "es"));
  984. // 2 nodes (2, 4) should have had detection attempted.
  985. EXPECT_EQ(2, count_detection_attempted(tree));
  986. histograms.ExpectUniqueSample(
  987. "Accessibility.LanguageDetection.CountDetectionAttempted", 2, 1);
  988. // 2 nodes (2, 4) should have had detection results
  989. EXPECT_EQ(2, count_detection_results(tree));
  990. histograms.ExpectUniqueSample(
  991. "Accessibility.LanguageDetection.PercentageLanguageDetected", 100, 1);
  992. // 2 nodes (2, 4) should have been labelled
  993. EXPECT_EQ(2, count_labelled(tree));
  994. histograms.ExpectUniqueSample("Accessibility.LanguageDetection.CountLabelled",
  995. 2, 1);
  996. // 2 nodes (2, 4) should have been given top label
  997. EXPECT_EQ(2, count_labelled_with_top_result(tree));
  998. histograms.ExpectUniqueSample(
  999. "Accessibility.LanguageDetection.PercentageLabelledWithTop", 100, 1);
  1000. // 1 nodes (4) should have been labelled to disagree with node1 author
  1001. // provided language.
  1002. EXPECT_EQ(1, count_overridden(tree));
  1003. // 2 nodes were labelled, 1 disagreed with node1 = 50%.
  1004. histograms.ExpectUniqueSample(
  1005. "Accessibility.LanguageDetection.PercentageOverridden", 50, 1);
  1006. // There should be 2 unique languages (fr, es).
  1007. {
  1008. auto top_lang = unique_top_lang_detected(tree);
  1009. const base::flat_set<std::string> expected_top_lang = {"es", "fr"};
  1010. EXPECT_EQ(top_lang, expected_top_lang);
  1011. }
  1012. // There should be a single (unique, 1) value for '2' unique languages.
  1013. histograms.ExpectUniqueSample("Accessibility.LanguageDetection.LangsPerPage",
  1014. 2, 1);
  1015. }
  1016. TEST_F(AXLanguageDetectionTestDynamicContent, MultipleUpdates) {
  1017. // This test runs language detection a total of 3 times, once for the initial
  1018. // 'static' content, and then twice for 'dynamic' updates.
  1019. // Tree:
  1020. // 1
  1021. // 2
  1022. //
  1023. // 1 - GenericContainer
  1024. // 2 - English text
  1025. AXTreeUpdate initial_state;
  1026. initial_state.root_id = 1;
  1027. initial_state.nodes.resize(2);
  1028. {
  1029. AXNodeData& node1 = initial_state.nodes[0];
  1030. node1.id = 1;
  1031. node1.role = ax::mojom::Role::kGenericContainer;
  1032. node1.child_ids.resize(1);
  1033. node1.child_ids[0] = 2;
  1034. }
  1035. {
  1036. AXNodeData& node2 = initial_state.nodes[1];
  1037. node2.id = 2;
  1038. node2.role = ax::mojom::Role::kStaticText;
  1039. node2.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextEnglish);
  1040. }
  1041. AXTree tree(initial_state);
  1042. ASSERT_NE(tree.language_detection_manager, nullptr);
  1043. // Run initial language detection and labelling.
  1044. tree.language_detection_manager->DetectLanguages();
  1045. tree.language_detection_manager->LabelLanguages();
  1046. // Quickly verify "before" state
  1047. {
  1048. AXNode* node1 = tree.GetFromId(1);
  1049. ASSERT_NE(node1, nullptr);
  1050. ASSERT_EQ(node1->GetLanguage(), "");
  1051. AXNode* node2 = tree.GetFromId(2);
  1052. ASSERT_NE(node2, nullptr);
  1053. ASSERT_EQ(node2->GetLanguage(), "en");
  1054. }
  1055. // Register dynamic content observer.
  1056. tree.language_detection_manager->RegisterLanguageDetectionObserver();
  1057. ASSERT_NE(getObserver(tree), nullptr);
  1058. ASSERT_TRUE(tree.HasObserver(getObserver(tree)));
  1059. // First update
  1060. {
  1061. // Dynamic update
  1062. //
  1063. // New tree layout will be:
  1064. // 1
  1065. // 2 3
  1066. //
  1067. // 1 - GenericContainer, unchanged
  1068. // 2 - changed to German text
  1069. // 3 - new child, French text
  1070. AXTreeUpdate update_state;
  1071. update_state.root_id = 1;
  1072. update_state.nodes.resize(3);
  1073. // Update node1 to include new child node3.
  1074. {
  1075. AXNodeData& node1 = update_state.nodes[0];
  1076. node1.id = 1;
  1077. node1.role = ax::mojom::Role::kGenericContainer;
  1078. node1.child_ids.resize(2);
  1079. node1.child_ids[0] = 2;
  1080. node1.child_ids[1] = 3;
  1081. }
  1082. // Change node2 text to German
  1083. {
  1084. AXNodeData& node2 = update_state.nodes[1];
  1085. node2.id = 2;
  1086. node2.role = ax::mojom::Role::kStaticText;
  1087. node2.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextGerman);
  1088. }
  1089. // Add new node3 with French text.
  1090. {
  1091. AXNodeData& node3 = update_state.nodes[2];
  1092. node3.id = 3;
  1093. node3.role = ax::mojom::Role::kStaticText;
  1094. node3.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextFrench);
  1095. }
  1096. // Perform update.
  1097. ASSERT_TRUE(tree.Unserialize(update_state));
  1098. {
  1099. AXNode* node1 = tree.GetFromId(1);
  1100. ASSERT_NE(node1, nullptr);
  1101. ASSERT_EQ(node1->GetLanguage(), "");
  1102. }
  1103. {
  1104. // Detection should have been re-run on node2, detecting German.
  1105. AXNode* node2 = tree.GetFromId(2);
  1106. ASSERT_NE(node2, nullptr);
  1107. ASSERT_EQ(node2->GetLanguage(), "de");
  1108. }
  1109. {
  1110. // New node3 should have detected French.
  1111. AXNode* node3 = tree.GetFromId(3);
  1112. ASSERT_NE(node3, nullptr);
  1113. ASSERT_EQ(node3->GetLanguage(), "fr");
  1114. }
  1115. }
  1116. // Second update
  1117. {
  1118. // Dynamic update
  1119. //
  1120. // New tree layout will be:
  1121. // 1
  1122. // 2 x
  1123. //
  1124. // 1 - GenericContainer, unchanged
  1125. // 2 - changed to French text
  1126. // 3 - deleted
  1127. AXTreeUpdate update_state;
  1128. update_state.root_id = 1;
  1129. update_state.nodes.resize(2);
  1130. // Update node1 to delete child node3.
  1131. {
  1132. AXNodeData& node1 = update_state.nodes[0];
  1133. node1.id = 1;
  1134. node1.role = ax::mojom::Role::kGenericContainer;
  1135. node1.child_ids.resize(1);
  1136. node1.child_ids[0] = 2;
  1137. }
  1138. // Change node2 text to French
  1139. {
  1140. AXNodeData& node2 = update_state.nodes[1];
  1141. node2.id = 2;
  1142. node2.role = ax::mojom::Role::kStaticText;
  1143. node2.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextFrench);
  1144. }
  1145. // Perform update.
  1146. ASSERT_TRUE(tree.Unserialize(update_state));
  1147. {
  1148. AXNode* node1 = tree.GetFromId(1);
  1149. ASSERT_NE(node1, nullptr);
  1150. ASSERT_EQ(node1->GetLanguage(), "");
  1151. }
  1152. {
  1153. // Detection should have been re-run on node2, detecting French.
  1154. AXNode* node2 = tree.GetFromId(2);
  1155. ASSERT_NE(node2, nullptr);
  1156. ASSERT_EQ(node2->GetLanguage(), "fr");
  1157. }
  1158. {
  1159. // Node3 should be no more.
  1160. AXNode* node3 = tree.GetFromId(3);
  1161. ASSERT_EQ(node3, nullptr);
  1162. }
  1163. }
  1164. // Third update.
  1165. {
  1166. // Dynamic update
  1167. //
  1168. // New tree layout will be:
  1169. // 1
  1170. // 2 3
  1171. //
  1172. // 1 - GenericContainer, unchanged
  1173. // 2 - French text, unchanged
  1174. // 3 - new node, German text
  1175. AXTreeUpdate update_state;
  1176. update_state.root_id = 1;
  1177. update_state.nodes.resize(2);
  1178. // Update node1 to include new child node3.
  1179. {
  1180. AXNodeData& node1 = update_state.nodes[0];
  1181. node1.id = 1;
  1182. node1.role = ax::mojom::Role::kGenericContainer;
  1183. node1.child_ids.resize(2);
  1184. node1.child_ids[0] = 2;
  1185. node1.child_ids[1] = 3;
  1186. }
  1187. // Add new node3 with German text.
  1188. {
  1189. AXNodeData& node3 = update_state.nodes[1];
  1190. node3.id = 3;
  1191. node3.role = ax::mojom::Role::kStaticText;
  1192. node3.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextGerman);
  1193. }
  1194. // Perform update.
  1195. ASSERT_TRUE(tree.Unserialize(update_state));
  1196. {
  1197. AXNode* node1 = tree.GetFromId(1);
  1198. ASSERT_NE(node1, nullptr);
  1199. ASSERT_EQ(node1->GetLanguage(), "");
  1200. }
  1201. {
  1202. AXNode* node2 = tree.GetFromId(2);
  1203. ASSERT_NE(node2, nullptr);
  1204. ASSERT_EQ(node2->GetLanguage(), "fr");
  1205. }
  1206. {
  1207. AXNode* node3 = tree.GetFromId(3);
  1208. ASSERT_NE(node3, nullptr);
  1209. ASSERT_EQ(node3->GetLanguage(), "de");
  1210. }
  1211. }
  1212. }
  1213. TEST_F(AXLanguageDetectionTestDynamicContent, NewRoot) {
  1214. // Artificial test change which changes the root node.
  1215. // Tree:
  1216. // 1
  1217. //
  1218. // 1 - GenericContainer
  1219. AXTreeUpdate initial_state;
  1220. initial_state.root_id = 1;
  1221. initial_state.nodes.resize(1);
  1222. {
  1223. AXNodeData& node1 = initial_state.nodes[0];
  1224. node1.id = 1;
  1225. node1.role = ax::mojom::Role::kGenericContainer;
  1226. }
  1227. AXTree tree(initial_state);
  1228. ASSERT_NE(tree.language_detection_manager, nullptr);
  1229. // Run initial language detection and labelling.
  1230. tree.language_detection_manager->DetectLanguages();
  1231. tree.language_detection_manager->LabelLanguages();
  1232. // Register dynamic content observer.
  1233. tree.language_detection_manager->RegisterLanguageDetectionObserver();
  1234. ASSERT_NE(getObserver(tree), nullptr);
  1235. ASSERT_TRUE(tree.HasObserver(getObserver(tree)));
  1236. // New Tree:
  1237. // 2
  1238. // 2 - new root, German text
  1239. AXTreeUpdate update_state;
  1240. update_state.root_id = 2;
  1241. update_state.nodes.resize(1);
  1242. {
  1243. AXNodeData& node2 = update_state.nodes[0];
  1244. node2.id = 2;
  1245. node2.role = ax::mojom::Role::kStaticText;
  1246. node2.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextGerman);
  1247. }
  1248. // Perform update.
  1249. ASSERT_TRUE(tree.Unserialize(update_state));
  1250. {
  1251. AXNode* node2 = tree.GetFromId(2);
  1252. ASSERT_NE(node2, nullptr);
  1253. ASSERT_EQ(node2->GetLanguage(), "de");
  1254. }
  1255. }
  1256. TEST_F(AXLanguageDetectionTestDynamicContent, ChainOfNewNodes) {
  1257. // Artificial test which adds two new nodes in a 'chain', simultaneously
  1258. // adding a child of the root and a grandchild of the root.
  1259. // Tree:
  1260. // 1
  1261. //
  1262. // 1 - GenericContainer
  1263. AXTreeUpdate initial_state;
  1264. initial_state.root_id = 1;
  1265. initial_state.nodes.resize(1);
  1266. {
  1267. AXNodeData& node1 = initial_state.nodes[0];
  1268. node1.id = 1;
  1269. node1.role = ax::mojom::Role::kGenericContainer;
  1270. }
  1271. AXTree tree(initial_state);
  1272. ASSERT_NE(tree.language_detection_manager, nullptr);
  1273. // Run initial language detection and labelling.
  1274. tree.language_detection_manager->DetectLanguages();
  1275. tree.language_detection_manager->LabelLanguages();
  1276. // Register dynamic content observer.
  1277. tree.language_detection_manager->RegisterLanguageDetectionObserver();
  1278. ASSERT_NE(getObserver(tree), nullptr);
  1279. ASSERT_TRUE(tree.HasObserver(getObserver(tree)));
  1280. // New Tree:
  1281. // 1
  1282. // 2
  1283. // 3
  1284. // 1 - generic container
  1285. // 2 - generic container
  1286. // 3 - German text
  1287. AXTreeUpdate update_state;
  1288. update_state.root_id = 1;
  1289. update_state.nodes.resize(3);
  1290. {
  1291. AXNodeData& node1 = update_state.nodes[0];
  1292. node1.id = 1;
  1293. node1.role = ax::mojom::Role::kGenericContainer;
  1294. node1.child_ids.resize(1);
  1295. node1.child_ids[0] = 2;
  1296. }
  1297. {
  1298. AXNodeData& node2 = update_state.nodes[1];
  1299. node2.id = 2;
  1300. node2.role = ax::mojom::Role::kGenericContainer;
  1301. node2.child_ids.resize(1);
  1302. node2.child_ids[0] = 3;
  1303. }
  1304. {
  1305. AXNodeData& node3 = update_state.nodes[2];
  1306. node3.id = 3;
  1307. node3.role = ax::mojom::Role::kStaticText;
  1308. node3.AddStringAttribute(ax::mojom::StringAttribute::kName, kTextGerman);
  1309. }
  1310. // Perform update.
  1311. ASSERT_TRUE(tree.Unserialize(update_state));
  1312. {
  1313. AXNode* node3 = tree.GetFromId(3);
  1314. ASSERT_NE(node3, nullptr);
  1315. ASSERT_EQ(node3->GetLanguage(), "de");
  1316. }
  1317. }
  1318. TEST(AXLanguageDetectionTest, AXLanguageInfoStatsBasic) {
  1319. AXLanguageInfoStats stats;
  1320. {
  1321. std::vector<std::string> detected_languages;
  1322. detected_languages.push_back("en");
  1323. detected_languages.push_back("fr");
  1324. detected_languages.push_back("ja");
  1325. stats.Add(detected_languages);
  1326. }
  1327. ASSERT_EQ(stats.GetScore("en"), 3);
  1328. ASSERT_EQ(stats.GetScore("fr"), 2);
  1329. ASSERT_EQ(stats.GetScore("ja"), 1);
  1330. EXPECT_TRUE(stats.CheckLanguageWithinTop("en"));
  1331. EXPECT_TRUE(stats.CheckLanguageWithinTop("fr"));
  1332. EXPECT_TRUE(stats.CheckLanguageWithinTop("ja"));
  1333. {
  1334. std::vector<std::string> detected_languages;
  1335. detected_languages.push_back("en");
  1336. detected_languages.push_back("de");
  1337. detected_languages.push_back("fr");
  1338. stats.Add(detected_languages);
  1339. }
  1340. ASSERT_EQ(stats.GetScore("en"), 6);
  1341. ASSERT_EQ(stats.GetScore("fr"), 3);
  1342. ASSERT_EQ(stats.GetScore("de"), 2);
  1343. ASSERT_EQ(stats.GetScore("ja"), 1);
  1344. EXPECT_TRUE(stats.CheckLanguageWithinTop("en"));
  1345. EXPECT_TRUE(stats.CheckLanguageWithinTop("fr"));
  1346. EXPECT_TRUE(stats.CheckLanguageWithinTop("de"));
  1347. EXPECT_FALSE(stats.CheckLanguageWithinTop("ja"));
  1348. {
  1349. std::vector<std::string> detected_languages;
  1350. detected_languages.push_back("fr");
  1351. stats.Add(detected_languages);
  1352. }
  1353. ASSERT_EQ(stats.GetScore("en"), 6);
  1354. ASSERT_EQ(stats.GetScore("fr"), 6);
  1355. ASSERT_EQ(stats.GetScore("de"), 2);
  1356. ASSERT_EQ(stats.GetScore("ja"), 1);
  1357. EXPECT_TRUE(stats.CheckLanguageWithinTop("en"));
  1358. EXPECT_TRUE(stats.CheckLanguageWithinTop("fr"));
  1359. EXPECT_TRUE(stats.CheckLanguageWithinTop("de"));
  1360. EXPECT_FALSE(stats.CheckLanguageWithinTop("ja"));
  1361. {
  1362. std::vector<std::string> detected_languages;
  1363. detected_languages.push_back("ja");
  1364. detected_languages.push_back("qq");
  1365. detected_languages.push_back("zz");
  1366. stats.Add(detected_languages);
  1367. }
  1368. ASSERT_EQ(stats.GetScore("en"), 6);
  1369. ASSERT_EQ(stats.GetScore("fr"), 6);
  1370. ASSERT_EQ(stats.GetScore("ja"), 4);
  1371. ASSERT_EQ(stats.GetScore("de"), 2);
  1372. ASSERT_EQ(stats.GetScore("qq"), 2);
  1373. ASSERT_EQ(stats.GetScore("zz"), 1);
  1374. EXPECT_TRUE(stats.CheckLanguageWithinTop("en"));
  1375. EXPECT_TRUE(stats.CheckLanguageWithinTop("fr"));
  1376. EXPECT_TRUE(stats.CheckLanguageWithinTop("ja"));
  1377. EXPECT_FALSE(stats.CheckLanguageWithinTop("de"));
  1378. EXPECT_FALSE(stats.CheckLanguageWithinTop("qq"));
  1379. EXPECT_FALSE(stats.CheckLanguageWithinTop("zz"));
  1380. }
  1381. TEST(AXLanguageDetectionTest, ShortLanguageDetectorLabeledTest) {
  1382. base::CommandLine::ForCurrentProcess()->AppendSwitch(
  1383. ::switches::kEnableExperimentalAccessibilityLanguageDetection);
  1384. AXTreeUpdate initial_state;
  1385. initial_state.root_id = 1;
  1386. initial_state.nodes.resize(2);
  1387. initial_state.nodes[0].id = 1;
  1388. initial_state.nodes[0].child_ids = {2};
  1389. initial_state.nodes[1].id = 2;
  1390. initial_state.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kName,
  1391. "Hello");
  1392. initial_state.nodes[1].AddStringAttribute(
  1393. ax::mojom::StringAttribute::kLanguage, "en");
  1394. AXTree tree(initial_state);
  1395. AXNode* item = tree.GetFromId(2);
  1396. std::vector<AXLanguageSpan> annotation;
  1397. ASSERT_NE(tree.language_detection_manager, nullptr);
  1398. // Empty output.
  1399. annotation =
  1400. tree.language_detection_manager->GetLanguageAnnotationForStringAttribute(
  1401. *item, ax::mojom::StringAttribute::kInnerHtml);
  1402. ASSERT_EQ(0, (int)annotation.size());
  1403. // Returns single AXLanguageSpan.
  1404. annotation =
  1405. tree.language_detection_manager->GetLanguageAnnotationForStringAttribute(
  1406. *item, ax::mojom::StringAttribute::kName);
  1407. ASSERT_EQ(1, (int)annotation.size());
  1408. AXLanguageSpan* lang_span = &annotation[0];
  1409. ASSERT_EQ("en", lang_span->language);
  1410. std::string name =
  1411. item->GetStringAttribute(ax::mojom::StringAttribute::kName);
  1412. ASSERT_EQ("Hello",
  1413. name.substr(lang_span->start_index,
  1414. lang_span->end_index - lang_span->start_index));
  1415. }
  1416. TEST(AXLanguageDetectionTest, ShortLanguageDetectorCharacterTest) {
  1417. base::CommandLine::ForCurrentProcess()->AppendSwitch(
  1418. ::switches::kEnableExperimentalAccessibilityLanguageDetection);
  1419. AXTreeUpdate initial_state;
  1420. initial_state.root_id = 1;
  1421. initial_state.nodes.resize(2);
  1422. initial_state.nodes[0].id = 1;
  1423. initial_state.nodes[0].child_ids = {2};
  1424. initial_state.nodes[1].id = 2;
  1425. initial_state.nodes[1].AddStringAttribute(ax::mojom::StringAttribute::kName,
  1426. "δ");
  1427. AXTree tree(initial_state);
  1428. AXNode* item = tree.GetFromId(2);
  1429. std::vector<AXLanguageSpan> annotation;
  1430. ASSERT_NE(tree.language_detection_manager, nullptr);
  1431. // Returns single LanguageSpan.
  1432. annotation =
  1433. tree.language_detection_manager->GetLanguageAnnotationForStringAttribute(
  1434. *item, ax::mojom::StringAttribute::kName);
  1435. ASSERT_EQ(1, (int)annotation.size());
  1436. AXLanguageSpan* lang_span = &annotation[0];
  1437. ASSERT_EQ("el", lang_span->language);
  1438. std::string name =
  1439. item->GetStringAttribute(ax::mojom::StringAttribute::kName);
  1440. ASSERT_EQ("δ", name.substr(lang_span->start_index,
  1441. lang_span->end_index - lang_span->start_index));
  1442. }
  1443. TEST(AXLanguageDetectionTest, ShortLanguageDetectorMultipleLanguagesTest) {
  1444. base::CommandLine::ForCurrentProcess()->AppendSwitch(
  1445. ::switches::kEnableExperimentalAccessibilityLanguageDetection);
  1446. AXTreeUpdate initial_state;
  1447. initial_state.root_id = 1;
  1448. initial_state.nodes.resize(2);
  1449. initial_state.nodes[0].id = 1;
  1450. initial_state.nodes[0].child_ids = {2};
  1451. initial_state.nodes[1].id = 2;
  1452. initial_state.nodes[1].AddStringAttribute(
  1453. ax::mojom::StringAttribute::kName,
  1454. "This text should be read in English. 차에 한하여 중임할 수. Followed "
  1455. "by English.");
  1456. AXTree tree(initial_state);
  1457. AXNode* item = tree.GetFromId(2);
  1458. ASSERT_NE(tree.language_detection_manager, nullptr);
  1459. std::vector<AXLanguageSpan> annotation =
  1460. tree.language_detection_manager->GetLanguageAnnotationForStringAttribute(
  1461. *item, ax::mojom::StringAttribute::kName);
  1462. ASSERT_EQ(3, (int)annotation.size());
  1463. std::string name =
  1464. item->GetStringAttribute(ax::mojom::StringAttribute::kName);
  1465. AXLanguageSpan* lang_span = &annotation[0];
  1466. ASSERT_EQ("This text should be read in English. ",
  1467. name.substr(lang_span->start_index,
  1468. lang_span->end_index - lang_span->start_index));
  1469. lang_span = &annotation[1];
  1470. ASSERT_EQ("차에 한하여 중임할 수. ",
  1471. name.substr(lang_span->start_index,
  1472. lang_span->end_index - lang_span->start_index));
  1473. lang_span = &annotation[2];
  1474. ASSERT_EQ("Followed by English.",
  1475. name.substr(lang_span->start_index,
  1476. lang_span->end_index - lang_span->start_index));
  1477. }
  1478. // Assert that GetLanguageAnnotationForStringAttribute works for attributes
  1479. // other than kName.
  1480. TEST(AXLanguageDetectionTest, DetectLanguagesForRoleTest) {
  1481. base::CommandLine::ForCurrentProcess()->AppendSwitch(
  1482. ::switches::kEnableExperimentalAccessibilityLanguageDetection);
  1483. AXTreeUpdate initial_state;
  1484. initial_state.root_id = 1;
  1485. initial_state.nodes.resize(1);
  1486. initial_state.nodes[0].id = 1;
  1487. initial_state.nodes[0].AddStringAttribute(ax::mojom::StringAttribute::kValue,
  1488. "どうぞよろしくお願いします.");
  1489. AXTree tree(initial_state);
  1490. AXNode* item = tree.GetFromId(1);
  1491. ASSERT_NE(tree.language_detection_manager, nullptr);
  1492. std::vector<AXLanguageSpan> annotation =
  1493. tree.language_detection_manager->GetLanguageAnnotationForStringAttribute(
  1494. *item, ax::mojom::StringAttribute::kValue);
  1495. ASSERT_EQ(1, (int)annotation.size());
  1496. std::string value =
  1497. item->GetStringAttribute(ax::mojom::StringAttribute::kValue);
  1498. AXLanguageSpan* lang_span = &annotation[0];
  1499. ASSERT_EQ("どうぞよろしくお願いします.",
  1500. value.substr(lang_span->start_index,
  1501. lang_span->end_index - lang_span->start_index));
  1502. }
  1503. } // namespace ui