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; 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()) { if (aContent->IsElement()) {
uint32_t attrCount = aContent->AsElement()->GetAttrCount(); uint32_t attrCount = aContent->AsElement()->GetAttrCount();
for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) { for (uint32_t attrIdx = 0; attrIdx < attrCount; attrIdx++) {
@ -162,6 +148,34 @@ static bool MustBeAccessible(nsIContent* aContent, DocAccessible* aDocument) {
return false; 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( bool nsAccessibilityService::ShouldCreateImgAccessible(
mozilla::dom::Element* aElement, DocAccessible* aDocument) { mozilla::dom::Element* aElement, DocAccessible* aDocument) {
// The element must have a layout frame for us to proceed. If there is no // 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()); 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) || if (roleMapEntry && (roleMapEntry->Is(nsGkAtoms::presentation) ||
roleMapEntry->Is(nsGkAtoms::none))) { roleMapEntry->Is(nsGkAtoms::none))) {
if (!MustBeAccessible(content, document)) return nullptr; if (MustBeAccessible(content, document)) {
// If the element is focusable, a global ARIA attribute is applied to it
roleMapEntry = nullptr; // 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 if (!newAcc && content->IsHTMLElement()) { // HTML accessibles
@ -1304,6 +1327,9 @@ LocalAccessible* nsAccessibilityService::CreateAccessible(
// Interesting generic non-HTML container // Interesting generic non-HTML container
newAcc = new AccessibleWrap(content, document); newAcc = new AccessibleWrap(content, document);
} }
} else if (!newAcc && MustBeGenericAccessible(content, document)) {
newAcc = new EnumRoleHyperTextAccessible<roles::TEXT_CONTAINER>(content,
document);
} }
if (newAcc) { if (newAcc) {

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

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

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

@ -4,7 +4,11 @@
"use strict"; "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); requestLongerTimeout(2);
const appUnitsPerDevPixel = 60; const appUnitsPerDevPixel = 60;
@ -320,13 +324,17 @@ addAccessibleTask(
*/ */
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> <div id="mutate" role="presentation">mutate</div>
<hr style="height: 200vh;"> <hr style="height: 200vh;">
<p>bottom</p> <p>bottom</p>
`, `,
async function(browser, docAcc) { 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"); ok(!findAccessibleChildByID(docAcc, "mutate"), "mutate inaccessible");
info("Setting position: fixed on mutate"); info("Setting position: fixed on mutate");
let shown = waitForEvent(EVENT_SHOW, "mutate"); let shown = waitForEvent(EVENT_SHOW, "mutate");