Bug 1816346 part 2: If we force an Accessible to be created due to transform/fixed/sticky despite it being presentational, make it a generic Accessible. r=nlapre

Otherwise, semantics are exposed (e.g. for a table), which completely defeats the author's intent that this be treated as presentational.

Differential Revision: https://phabricator.services.mozilla.com/D170165
This commit is contained in:
James Teh 2023-02-22 04:46:50 +00:00
Родитель 75092d8fb0
Коммит f826326f5f
3 изменённых файлов: 60 добавлений и 26 удалений

Просмотреть файл

@ -112,20 +112,6 @@ static bool MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument) {
return true;
}
// If the frame has been transformed, and the content has any children, we
// should create an Accessible so that we can account for the transform when
// calculating the Accessible's bounds using the parent process cache.
// Ditto for content which is position: fixed or sticky.
// However, don't do this for XUL widgets, as this breaks XUL a11y code
// expectations in some cases. XUL widgets are only used in the parent
// process and can't be cached anyway.
if (aContent->HasChildren() && !aContent->IsXULElement() &&
(frame->IsTransformed() || frame->IsStickyPositioned() ||
(frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
nsLayoutUtils::IsReallyFixedPos(frame)))) {
return true;
}
if (aContent->IsElement()) {
uint32_t attrCount = aContent->AsElement()->GetAttrCount();
for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) {
@ -162,6 +148,34 @@ static bool MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument) {
return false;
}
/**
* Return true if the element must be a generic Accessible, even if it has been
* marked presentational with role="presentation", etc. MustBeAccessible causes
* an Accessible to be created as if it weren't marked presentational at all;
* e.g. <table role="presentation" tabindex="0"> will expose roles::TABLE and
* support TableAccessibleBase. In contrast, this function causes a generic
* Accessible to be created; e.g. <table role="presentation" style="position:
* fixed;"> will expose roles::TEXT_CONTAINER and will not support
* TableAccessibleBase. This is necessary in certain cases for the
* RemoteAccessible cache.
*/
static bool MustBeGenericAccessible(nsIContent* aContent,
DocAccessible* aDocument) {
nsIFrame* frame = aContent->GetPrimaryFrame();
MOZ_ASSERT(frame);
// If the frame has been transformed, and the content has any children, we
// should create an Accessible so that we can account for the transform when
// calculating the Accessible's bounds using the parent process cache.
// Ditto for content which is position: fixed or sticky.
// However, don't do this for XUL widgets, as this breaks XUL a11y code
// expectations in some cases. XUL widgets are only used in the parent
// process and can't be cached anyway.
return aContent->HasChildren() && !aContent->IsXULElement() &&
(frame->IsTransformed() || frame->IsStickyPositioned() ||
(frame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
nsLayoutUtils::IsReallyFixedPos(frame)));
}
bool nsAccessibilityService::ShouldCreateImgAccessible(
mozilla::dom::Element* aElement, DocAccessible* aDocument) {
// The element must have a layout frame for us to proceed. If there is no
@ -1110,14 +1124,23 @@ LocalAccessible* nsAccessibilityService::CreateAccessible(
const nsRoleMapEntry* roleMapEntry = aria::GetRoleMap(content->AsElement());
// If the element is focusable or global ARIA attribute is applied to it or
// it is referenced by ARIA relationship then treat role="presentation" on
// the element as the role is not there.
if (roleMapEntry && (roleMapEntry->Is(nsGkAtoms::presentation) ||
roleMapEntry->Is(nsGkAtoms::none))) {
if (!MustBeAccessible(content, document)) return nullptr;
roleMapEntry = nullptr;
if (MustBeAccessible(content, document)) {
// If the element is focusable, a global ARIA attribute is applied to it
// or it is referenced by an ARIA relationship, then treat
// role="presentation" on the element as if the role is not there.
roleMapEntry = nullptr;
} else if (MustBeGenericAccessible(content, document)) {
// Clear roleMapEntry so that we use the generic role specified below.
// Otherwise, we'd expose roles::NOTHING as specified for presentation in
// ARIAMap.
roleMapEntry = nullptr;
newAcc = new EnumRoleHyperTextAccessible<roles::TEXT_CONTAINER>(content,
document);
} else {
return nullptr;
}
}
if (!newAcc && content->IsHTMLElement()) { // HTML accessibles
@ -1304,6 +1327,9 @@ LocalAccessible* nsAccessibilityService::CreateAccessible(
// Interesting generic non-HTML container
newAcc = new AccessibleWrap(content, document);
}
} else if (!newAcc && MustBeGenericAccessible(content, document)) {
newAcc = new EnumRoleHyperTextAccessible<roles::TEXT_CONTAINER>(content,
document);
}
if (newAcc) {

Просмотреть файл

@ -131,7 +131,7 @@ addAccessibleTask(
</div>
`,
async function(browser, docAcc) {
const tree = { SECTION: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }] };
const tree = { TEXT_CONTAINER: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }] };
const divWithTransform = findAccessibleChildByID(docAcc, "container")
.firstChild;
@ -159,7 +159,7 @@ addAccessibleTask(
`,
async function(browser, docAcc) {
let divToTransform = findAccessibleChildByID(docAcc, "div-to-transform");
ok(!divToTransform, "There should not be a section accessible.");
ok(!divToTransform, "There should not be a div accessible.");
// Translate the div.
await invokeContentTask(browser, [], () => {
@ -171,7 +171,7 @@ addAccessibleTask(
// Verify that the SECTION accessible appeared after we gave it a transform.
divToTransform = findAccessibleChildByID(docAcc, "div-to-transform");
const tree = {
SECTION: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }],
TEXT_CONTAINER: [{ PARAGRAPH: [{ TEXT_LEAF: [] }] }],
};
testAccessibleTree(divToTransform, tree);

Просмотреть файл

@ -4,7 +4,11 @@
"use strict";
loadScripts({ name: "layout.js", dir: MOCHITESTS_DIR });
/* import-globals-from ../../mochitest/role.js */
loadScripts(
{ name: "layout.js", dir: MOCHITESTS_DIR },
{ name: "role.js", dir: MOCHITESTS_DIR }
);
requestLongerTimeout(2);
const appUnitsPerDevPixel = 60;
@ -320,13 +324,17 @@ addAccessibleTask(
*/
addAccessibleTask(
`
<div id="fixed" role="presentation" style="position: fixed;">fixed</div>
<table id="fixed" role="presentation" style="position: fixed;">
<tr><th>fixed</th></tr>
</table>
<div id="mutate" role="presentation">mutate</div>
<hr style="height: 200vh;">
<p>bottom</p>
`,
async function(browser, docAcc) {
ok(findAccessibleChildByID(docAcc, "fixed"), "fixed is accessible");
const fixed = findAccessibleChildByID(docAcc, "fixed");
ok(fixed, "fixed is accessible");
isnot(fixed.role, ROLE_TABLE, "fixed doesn't have ROLE_TABLE");
ok(!findAccessibleChildByID(docAcc, "mutate"), "mutate inaccessible");
info("Setting position: fixed on mutate");
let shown = waitForEvent(EVENT_SHOW, "mutate");