Commit 6920d0af authored by Nektarios Paisios's avatar Nektarios Paisios Committed by Commit Bot

Add more signals when computing document language

Browsers don't use only the HTML lang attribute to compute document language.
We rely on the layout engine to compute language if a layout object is available, , otherwise we use our own logic.
We also use our own logic if the layout engine cannot figure out the language.
Our own logic first looks at the lang attribute on the current object, or any of its ancestors, followed by the "content-language" meta tag, the accept language header and the browsers default UI language.
The latter two steps provide a mere guess, but are better than simply returning en-US, which is our current implementation for IAccessible2. This certainly is an improvement over the current situation, though not perfect by all means.
R=dmazzoni@chromium.org

Bug: 831186
Tested: Manually using foreign language sites without lang attributes and Chrome's the "--lang" command line flag, automatically using layout tests
Change-Id: Ia80c31cc6dabdcea2aeec177aeda6b29d695019e
Reviewed-on: https://chromium-review.googlesource.com/1011332
Commit-Queue: Nektarios Paisios <nektar@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Reviewed-by: default avatarNektarios Paisios <nektar@chromium.org>
Cr-Commit-Position: refs/heads/master@{#551416}
parent 409283ee
rootWebArea
++paragraph language='en-US'
++++staticText language='en-US' name='How are you?'
++++++inlineTextBox language='en-US' name='How are you?'
rootWebArea language='en-US'
++paragraph language='es-ES'
++++staticText language='es-ES' name='Espanyol'
++++++inlineTextBox language='es-ES' name='Espanyol'
++staticText language='fr-FR' name='Comment allez-vous?'
++++inlineTextBox language='fr-FR' name='Comment allez-vous?'
<-- End-of-file -->
\ No newline at end of file
AXWebArea
++AXGroup AXLanguage='en-US'
++++AXStaticText AXValue='How are you?'
AXWebArea AXLanguage='en-US'
++AXGroup AXLanguage='es-ES'
++++AXStaticText AXValue='Espanyol'
++AXStaticText AXValue='Comment allez-vous?' AXLanguage='fr-FR'
ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE offset:0 language:en-US offset:1 language:fr-FR ia2_hypertext='<obj0>Comment allez-vous?'
++IA2_ROLE_PARAGRAPH offset:0 language:en-US ia2_hypertext='How are you?'
++++ROLE_SYSTEM_STATICTEXT name='How are you?' offset:0 language:en-US ia2_hypertext='How are you?'
ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE offset:0 language:es-ES offset:1 language:fr-FR ia2_hypertext='<obj0>Comment allez-vous?'
++IA2_ROLE_PARAGRAPH offset:0 language:es-ES ia2_hypertext='Espanyol'
++++ROLE_SYSTEM_STATICTEXT name='Espanyol' offset:0 language:es-ES ia2_hypertext='Espanyol'
++ROLE_SYSTEM_STATICTEXT name='Comment allez-vous?' offset:0 language:fr-FR ia2_hypertext='Comment allez-vous?'
......@@ -6,9 +6,9 @@
@MAC-ALLOW:AXLang*
-->
<!DOCTYPE html>
<html>
<html lang="en-US">
<body>
<p lang="en-US">How are you?</p>
<p lang="es-ES">Espanyol</p>
<p role="presentation" lang="fr-FR">Comment allez-vous?</p>
</body>
</html>
<!DOCTYPE html>
<html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<body id="body" role="main">
<p id="paragraph">Some text.
<span id="span" lang="pl">Polish.</span>
</p>
<script>
setup(() => {
window.languages = internals.userPreferredLanguages();
window.axBody = accessibilityController.accessibleElementById('body');
window.axParagraph = accessibilityController.accessibleElementById('paragraph');
window.axSpan = accessibilityController.accessibleElementById('span');
});
async_test((t) => {
assert_equals(window.axBody.language, 'AXLanguage: en-US',
'The browser language has not been changed yet.');
internals.setUserPreferredLanguages([ 'de' ]);
t.step_timeout(() => {
assert_equals(window.axBody.language, 'AXLanguage: de',
'The browser language has changed.');
assert_equals(window.axParagraph.language, 'AXLanguage: de',
'The browser language has changed.');
assert_equals(window.axSpan.language, 'AXLanguage: pl',
'The language of the span is controlled by the lang attribute.');
internals.setUserPreferredLanguages(window.languages);
t.done();
}, 0);
}, 'Tests if the browser language is correctly used to guess the language of the page.');
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<meta http-equiv="content-language" content="en, es">
</head>
<body id="body" role="main">
<p id="paragraph" lang="pl">Polish.
<span id="span">Possibly in another language.</span>
</p>
<script>
setup(() => {
window.axBody = accessibilityController.accessibleElementById('body');
window.axParagraph = accessibilityController.accessibleElementById('paragraph');
window.axSpan = accessibilityController.accessibleElementById('span');
});
test(() => {
assert_equals(window.axBody.language, 'AXLanguage: en',
'The first language in the meta tag should apply.');
assert_equals(window.axParagraph.language, 'AXLanguage: pl',
'The language in the lang attribute should apply.');
assert_equals(window.axSpan.language, 'AXLanguage: pl',
'The language in the lang attribute is inherited.');
}, 'Tests if the content-language meta tag and the lang attribute are picked up by the accessibility component.');
test(() => {
assert_equals(window.axBody.language, 'AXLanguage: en',
'The lang attribute has not been changed yet.');
document.documentElement.lang = 'ru';
assert_equals(window.axBody.language, 'AXLanguage: ru',
'The lang attribute on the html element has changed.');
assert_equals(window.axParagraph.language, 'AXLanguage: pl',
'The lang attribute on the html element should not affect the lang attribute on the paragraph.');
assert_equals(window.axSpan.language, 'AXLanguage: pl',
'The lang attribute on the html element should not affect the lang attribute on the span.');
document.documentElement.lang = '';
document.getElementById('paragraph').lang = 'it';
assert_equals(window.axParagraph.language, 'AXLanguage: it',
'The lang attribute on the paragraph has changed.');
assert_equals(window.axSpan.language, 'AXLanguage: it',
'The lang attribute on the paragraph has changed.');
document.getElementById('paragraph').lang = 'pl';
document.getElementById('span').lang = 'es';
assert_equals(window.axSpan.language, 'AXLanguage: es',
'The lang attribute on the span has changed.');
document.getElementById('span').lang = '';
}, 'Tests if dynamically changing lang attributes is picked up by the accessibility component.');
</script>
</body>
</html>
polish
Check whether languages are reported properly
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
AXLanguage: en
AXLanguage: pl
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html>
<head>
<script src="../resources/js-test.js"></script>
<meta http-equiv="content-language" content="en">
</head>
<body id="body">
<p lang="pl">polish</p>
<p id="description"></p>
<div id="console"></div>
<script>
description("Check whether languages are reported properly");
if (window.accessibilityController) {
var body = document.getElementById("body");
body.focus();
var webPageLang = accessibilityController.focusedElement;
debug(webPageLang.language);
var lang = webPageLang.childAtIndex(0);
debug(lang.language);
}
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<meta http-equiv="content-language" content="en, fr">
</head>
<body>
<canvas id="canvas" width="300" height="300" tabindex="-1">
<p id="paragraph">English.
<span id="span" lang="pl">Polish.</span>
</p>
</canvas>
<script>
setup(() => {
window.axCanvas = accessibilityController.accessibleElementById('canvas');
window.axParagraph = accessibilityController.accessibleElementById('paragraph');
window.axSpan = accessibilityController.accessibleElementById('span');
});
async_test((t) => {
internals.setUserPreferredLanguages([ 'de' ]);
assert_equals(window.axCanvas.language, 'AXLanguage: en',
'The first language in the meta tag should apply.');
assert_equals(window.axParagraph.language, 'AXLanguage: en',
'The first language in the meta tag should apply.');
assert_equals(window.axSpan.language, 'AXLanguage: pl',
'The language in the lang attribute should apply.');
let meta = document.querySelector('meta[http-equiv="content-language"]');
assert_not_equals(meta, undefined);
meta.content = '';
t.step_timeout(() => {
assert_equals(window.axCanvas.language, 'AXLanguage: de',
'The browser language should apply.');
assert_equals(window.axParagraph.language, 'AXLanguage: de',
'The browser language should apply.');
assert_equals(window.axSpan.language, 'AXLanguage: pl',
'The language in the lang attribute should apply.');
t.done();
}, 0);
}, 'Tests if the content-language meta tag and the lang attribute are picked up by the accessibility component in a canvas.');
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<!-- The second language in the meta tag should be ignored. -->
<meta http-equiv="content-language" content="en, es">
</head>
<body id="body" role="main">
<p id="paragraph" lang="pl">Polish.
<span id="span">Possibly in another language.</span>
</p>
<script>
setup(() => {
window.axBody = accessibilityController.accessibleElementById('body');
window.axParagraph = accessibilityController.accessibleElementById('paragraph');
window.axSpan = accessibilityController.accessibleElementById('span');
});
async_test((t) => {
assert_equals(window.axBody.language, 'AXLanguage: en',
'The meta tag has not been changed yet.');
let meta = document.querySelector('meta[http-equiv="content-language"]');
assert_not_equals(meta, undefined);
meta.content = 'fr';
assert_equals(window.axBody.language, 'AXLanguage: fr',
'The contents of the meta tag have changed.');
assert_equals(window.axParagraph.language, 'AXLanguage: pl',
'The lang attribute should take priority over the meta tag.');
assert_equals(window.axSpan.language, 'AXLanguage: pl',
'The lang attribute should take priority over the meta tag.');
meta.remove();
let newMeta = document.createElement('meta');
newMeta.httpEquiv = 'content-language';
newMeta.content = 'ja';
document.head.appendChild(newMeta);
t.step_timeout(() => {
assert_equals(window.axBody.language, 'AXLanguage: ja',
'The whole of the meta tag has been replaced.');
assert_equals(window.axParagraph.language, 'AXLanguage: pl',
'The lang attribute should take priority over the meta tag.');
assert_equals(window.axSpan.language, 'AXLanguage: pl',
'The lang attribute should take priority over the meta tag.');
newMeta.remove();
meta.content = 'en';
document.head.appendChild(meta);
t.done();
}, 0);
}, 'Tests if dynamically changing the content-language meta tag is picked up by the accessibility component.');
</script>
</body>
</html>
......@@ -919,7 +919,7 @@ RGBA32 AXLayoutObject::GetColor() const {
return color.Rgb();
}
String AXLayoutObject::FontFamily() const {
AtomicString AXLayoutObject::FontFamily() const {
if (!GetLayoutObject())
return AXNodeObject::FontFamily();
......@@ -1768,6 +1768,27 @@ Element* AXLayoutObject::AnchorElement() const {
return nullptr;
}
AtomicString AXLayoutObject::Language() const {
// Uses the style engine to figure out the object's language.
// The style engine relies on, for example, the "lang" attribute of the
// current node and its ancestors, and the document's "content-language"
// header. See the Language of a Node Spec at
// https://html.spec.whatwg.org/multipage/dom.html#language
if (!GetLayoutObject())
return AXNodeObject::Language();
const ComputedStyle* style = GetLayoutObject()->Style();
if (!style || !style->Locale())
return AXNodeObject::Language();
Vector<String> languages;
String(style->Locale()).Split(',', languages);
if (languages.IsEmpty())
return AXNodeObject::Language();
return AtomicString(languages[0].StripWhiteSpace());
}
//
// Functions that retrieve the current selection.
//
......
......@@ -96,7 +96,7 @@ class MODULES_EXPORT AXLayoutObject : public AXNodeObject {
const AtomicString& AccessKey() const override;
RGBA32 ComputeBackgroundColor() const final;
RGBA32 GetColor() const final;
String FontFamily() const final;
AtomicString FontFamily() const final;
// Font size is in pixels.
float FontSize() const final;
String ImageDataUrl(const IntSize& max_size) const final;
......@@ -172,6 +172,7 @@ class MODULES_EXPORT AXLayoutObject : public AXNodeObject {
Document* GetDocument() const override;
LocalFrameView* DocumentFrameView() const override;
Element* AnchorElement() const override;
AtomicString Language() const override;
// Notifications that this object may have changed.
void HandleActiveDescendantChanged() override;
......
......@@ -50,10 +50,12 @@
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
#include "third_party/blink/renderer/modules/accessibility/ax_range.h"
#include "third_party/blink/renderer/modules/accessibility/ax_sparse_attribute_setter.h"
#include "third_party/blink/renderer/platform/language.h"
#include "third_party/blink/renderer/platform/scroll/scroll_alignment.h"
#include "third_party/blink/renderer/platform/text/platform_locale.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
......@@ -2042,23 +2044,50 @@ LocalFrameView* AXObject::DocumentFrameView() const {
return object->DocumentFrameView();
}
String AXObject::Language() const {
AtomicString AXObject::Language() const {
// This method is used when the style engine is either not available on this
// object, e.g. for canvas fallback content, or is unable to determine the
// document's language. We use the following signals to detect the element's
// language, in decreasing priority:
// 1. The [language of a node] as defined in HTML, if known.
// 2. The list of languages the browser sends in the [Accept-Language] header.
// 3. The browser's default language.
const AtomicString& lang = GetAttribute(langAttr);
if (!lang.IsEmpty())
return lang;
AXObject* parent = ParentObject();
if (parent)
return parent->Language();
const Document* document = GetDocument();
if (document) {
// Fall back to the first content language specified in the meta tag.
// This is not part of what the HTML5 Standard suggests but it still appears
// to be necessary.
if (document->ContentLanguage()) {
const String content_languages = document->ContentLanguage();
Vector<String> languages;
content_languages.Split(',', languages);
if (!languages.IsEmpty())
return AtomicString(languages[0].StripWhiteSpace());
}
// As a last resort, fall back to the content language specified in the meta
// tag.
if (!parent) {
Document* doc = GetDocument();
if (doc)
return doc->ContentLanguage();
return g_null_atom;
if (document->GetPage()) {
// Use the first accept language preference if present.
const String accept_languages =
document->GetPage()->GetChromeClient().AcceptLanguages();
Vector<String> languages;
accept_languages.Split(',', languages);
if (!languages.IsEmpty())
return AtomicString(languages[0].StripWhiteSpace());
}
}
return parent->Language();
// As a last resort, return the default language of the browser's UI.
AtomicString default_language = DefaultLanguage();
return default_language;
}
bool AXObject::HasAttribute(const QualifiedName& attribute) const {
......
......@@ -487,7 +487,7 @@ class MODULES_EXPORT AXObject : public GarbageCollectedFinalized<AXObject> {
// Used by objects of role ColorWellRole.
virtual RGBA32 ColorValue() const { return Color::kTransparent; }
virtual bool CanvasHasFallbackContent() const { return false; }
virtual String FontFamily() const { return g_null_atom; }
virtual AtomicString FontFamily() const { return g_null_atom; }
// Font size is in pixels.
virtual float FontSize() const { return 0.0f; }
// Value should be 1-based. 0 means not supported.
......@@ -686,7 +686,7 @@ class MODULES_EXPORT AXObject : public GarbageCollectedFinalized<AXObject> {
virtual LocalFrameView* DocumentFrameView() const;
virtual Element* AnchorElement() const { return nullptr; }
virtual Element* ActionElement() const { return nullptr; }
String Language() const;
virtual AtomicString Language() const;
bool HasAttribute(const QualifiedName&) const;
const AtomicString& GetAttribute(const QualifiedName&) 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