Commit fd13d5f2 authored by Aaron Leventhal's avatar Aaron Leventhal Committed by Chromium LUCI CQ

Create AXObject for head/style/script subtrees if and only if visible

Do not create AXObjects for head, style and script subtrees unless
known to be visible (and we don't know that if it's display locked, so
in that case we assume those subtrees shouldn't be created).

Bug: None
Change-Id: I86649a346d0ffaa1ca402c7076e7ccfe197600c9
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2633925
Commit-Queue: Aaron Leventhal <aleventhal@chromium.org>
Reviewed-by: default avatarAlice Boxhall <aboxhall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#845247}
parent 7849177e
......@@ -306,6 +306,17 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityCSSFontSize) {
RunCSSTest(FILE_PATH_LITERAL("font-size.html"));
}
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
AccessibilityCSSHeadStyleScriptDisplayBlock) {
RunCSSTest(FILE_PATH_LITERAL("head-style-script-display-block.html"));
}
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
AccessibilityCSSHeadStyleScriptContentVisibilityHidden) {
RunCSSTest(
FILE_PATH_LITERAL("head-style-script-content-visibility-hidden.html"));
}
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityCSSDisplayNone) {
RunCSSTest(FILE_PATH_LITERAL("display-none.html"));
}
......
......@@ -7,7 +7,5 @@ rootWebArea
++++++++++genericContainer invisible
++++++++++++staticText invisible name='target'
++++++++++staticText invisible name='xxx<newline>'
++++++++++genericContainer invisible
++++++++++++staticText invisible name='<newline>function runTest() {<newline> window.location.href += "#target";<newline>}<newline>window.onload = () => requestAnimationFrame(() => requestAnimationFrame(runTest));<newline>'
++++++++++staticText invisible name='<newline>'
++++++++staticText invisible name='<newline>'
rootWebArea htmlTag='#document' name='Done'
++genericContainer ignored htmlTag='html'
++++genericContainer ignored htmlTag='body'
<!--
@BLINK-ALLOW:htmlTag*
@WAIT-FOR:Done
-->
<!-- Do not create accessible objects for head, style, script subtrees
when hidden via CSS content-visibility -->
<html>
<head>
<title>Loading...</title>
<style>
head,style,script { display:block; content-visibility:hidden; }
</style>
<style>
p { outline: 2px solid green; }
</style>
<script>
document.addEventListener('DOMContentLoaded', () => {
setTimeout(() => {
// Change inner text of head, style, and script to see if it causes
// an attempt to create an accessible object inside a subtree of head,
// style, or script, which is not allowed. A DCHECK will be triggered
// if an accessible object is created within any of these elements.
// Leaves the first two style and script elements alone as they are
// used for the test implementation.
document.querySelectorAll('style')[1].innerText = 'p { outline: 3px dotted blue; }';
document.querySelectorAll('style')[2].innerText = 'p { outline: 3px dotted blue; }';
document.querySelectorAll('script')[1].innerText = 'var y = 5;';
document.querySelectorAll('script')[2].innerText = 'var y = 5;';
document.head.appendChild(document.createElement('meta'));
document.head.appendChild(document.createElement('p'));
document.title = 'Done';
}, 50);
});
</script>
<script>
var x = 3;
</script>
</head>
</html>
<body>
<style>
p { outline: 2px solid green; }
</style>
<script>
var x = 3;
</script>
</body>
rootWebArea htmlTag='#document'
++genericContainer ignored htmlTag='html'
++++genericContainer ignored htmlTag='head'
++++++genericContainer htmlTag='style'
++++++++staticText name='head,style,script { display:block; }'
++++++++++inlineTextBox name='head,style,script { display:block; }'
++++++genericContainer htmlTag='script'
++++++++staticText name='var x = 3;'
++++++++++inlineTextBox name='var x = 3;'
++++genericContainer ignored htmlTag='body'
++++++genericContainer htmlTag='style'
++++++++staticText name='p { outline: 2px solid green; }'
++++++++++inlineTextBox name='p { outline: 2px solid green; }'
++++++genericContainer htmlTag='script'
++++++++staticText name='var x = 3;'
++++++++++inlineTextBox name='var x = 3;'
<!--
@BLINK-ALLOW:htmlTag*
-->
<!-- Create accessible objects inside of head, style, if styled to be visible -->
<html>
<head>
<style>
head,style,script { display:block; }
</style>
<script>
var x = 3;
</script>
</head>
</html>
<body>
<style>
p { outline: 2px solid green; }
</style>
<script>
var x = 3;
</script>
</body>
......@@ -48,6 +48,10 @@
#include "third_party/blink/renderer/core/html/forms/html_select_element.h"
#include "third_party/blink/renderer/core/html/html_dialog_element.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/html/html_head_element.h"
#include "third_party/blink/renderer/core/html/html_script_element.h"
#include "third_party/blink/renderer/core/html/html_style_element.h"
#include "third_party/blink/renderer/core/html/html_title_element.h"
#include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
#include "third_party/blink/renderer/core/input/context_menu_allowed_scope.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
......@@ -2550,8 +2554,16 @@ bool AXObject::ComputeIsHiddenViaStyle() const {
return false;
// Display-locked nodes are always hidden.
if (DisplayLockUtilities::NearestLockedExclusiveAncestor(*node))
if (DisplayLockUtilities::NearestLockedExclusiveAncestor(*node)) {
// Ensure contents of head, style and script are never exposed.
// Note: an AXObject is created for <title> to gather the document's name.
DCHECK(!Traversal<HTMLHeadElement>::FirstAncestorOrSelf(*node) ||
IsA<HTMLTitleElement>(node))
<< node;
DCHECK(!Traversal<HTMLStyleElement>::FirstAncestorOrSelf(*node)) << node;
DCHECK(!Traversal<HTMLScriptElement>::FirstAncestorOrSelf(*node)) << node;
return true;
}
// Style elements in SVG are not display: none, unlike HTML style elements,
// but they are still hidden and thus treated as hidden from style.
......
......@@ -57,7 +57,10 @@
#include "third_party/blink/renderer/core/html/forms/listed_element.h"
#include "third_party/blink/renderer/core/html/html_area_element.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/html/html_head_element.h"
#include "third_party/blink/renderer/core/html/html_image_element.h"
#include "third_party/blink/renderer/core/html/html_script_element.h"
#include "third_party/blink/renderer/core/html/html_style_element.h"
#include "third_party/blink/renderer/core/html/html_table_cell_element.h"
#include "third_party/blink/renderer/core/html/html_table_element.h"
#include "third_party/blink/renderer/core/html/html_table_row_element.h"
......@@ -142,16 +145,49 @@ bool IsLayoutObjectRelevantForAccessibility(const Node* node) {
return !ShouldCreateAXMenuListOptionFor(node) && !IsA<HTMLAreaElement>(node);
}
bool IsNodeRelevantForAccessibility(const Node* node) {
bool IsNodeRelevantForAccessibility(const Node* node, bool parent_ax_known) {
if (!node || !node->isConnected())
return false;
if (!node->IsElementNode() && !node->IsTextNode() && !node->IsDocumentNode())
return false; // Only documents, elements and text nodes get ax objects.
// When there is a layout object, the element is known to be visible, so
// consider it relevant and return early. Checking the layout object is only
// useful when display locking (content-visibility) is not used.
if (node->GetLayoutObject() &&
!DisplayLockUtilities::NearestLockedInclusiveAncestor(*node)) {
return true;
}
// The node is either hidden or display locked:
// Do not consider <head>/<style>/<script> relevant in these cases.
if (IsA<HTMLHeadElement>(node))
return false;
if (IsA<HTMLStyleElement>(node))
return false;
if (IsA<HTMLScriptElement>(node))
return false;
// Not a <head>/<style>/<script>:
// Use a slower check to see if this node is anywhere inside of a <head>,
// <style> or <script>.
// This check is not necessary if the parent_ax is already known, which means
// we are attempting to add this object from something already relevant in the
// AX tree, and therefore can't be inside a <head>, <style> or <script>.
if (parent_ax_known)
return true; // No need to check inside if the parent exists.
// Objects inside <head> are irrelevant, except <title> (collects title text).
if (Traversal<HTMLHeadElement>::FirstAncestor(*node))
return IsA<HTMLTitleElement>(node);
// Objects inside a <style> are irrelevant.
if (Traversal<HTMLStyleElement>::FirstAncestor(*node))
return false;
// Objects inside a <script> are irrelevant.
if (Traversal<HTMLScriptElement>::FirstAncestor(*node))
return false;
// All other objects are relevant, even if hidden.
return true;
}
......@@ -499,7 +535,7 @@ AXObject* AXObjectCacheImpl::GetOrCreate(const Node* node,
AXObject* AXObjectCacheImpl::GetOrCreate(Node* node,
AXObject* parent_if_known) {
if (!IsNodeRelevantForAccessibility(node))
if (!node)
return nullptr;
if (AXObject* obj = Get(node))
......@@ -511,8 +547,11 @@ AXObject* AXObjectCacheImpl::GetOrCreate(Node* node,
AXObject* AXObjectCacheImpl::CreateAndInit(Node* node,
AXObject* parent_if_known,
AXID use_axid) {
#if DCHECK_IS_ON()
DCHECK(node);
if (!IsNodeRelevantForAccessibility(node, parent_if_known))
return nullptr;
#if DCHECK_IS_ON()
DCHECK(node->isConnected());
DCHECK(node->IsElementNode() || node->IsTextNode() || node->IsDocumentNode());
Document* document = &node->GetDocument();
......@@ -534,10 +573,6 @@ AXObject* AXObjectCacheImpl::CreateAndInit(Node* node,
// for example, an <img> has a user agent shadow root containing a <span> for
// the alt text. Do not create an accessible for that as it would be unable
// to have a parent that has it as a child.
// TODO(aleventhal) There may be similar cases for content inside a <head>,
// <style> or <script> -- for style/script the inner text might change.
// In those cases the nodes are most likely display:none, but if they are
// display locked we may not know that they are display:none.
if (node->IsInShadowTree()) {
AXObject* shadow_host = Get(node->OwnerShadowHost());
if (shadow_host && !shadow_host->CanHaveChildren())
......@@ -590,6 +625,9 @@ AXObject* AXObjectCacheImpl::CreateAndInit(LayoutObject* layout_object,
DCHECK(!node || IsLayoutObjectRelevantForAccessibility(node))
<< "Shouldn't get here if the layout object is not relevant for a11y";
if (node && !IsNodeRelevantForAccessibility(node, parent_if_known))
return nullptr;
// Return null if inside a shadow tree of something that can't have children,
// for example, an <img> has a user agent shadow root containing a <span> for
// the alt text. Do not create an accessible for that as it would be unable
......
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