Bug 1951720: Don't exclude 0 width whitespace text nodes from the accessibility tree if they're at the end of a wrapped line. r=morgan
In bug 1951067, I pruned all 0 width whitespace text nodes from the accessibility tree. However, it turns out that whitespace text nodes at the end of wrapped lines can also be 0 width. These have semantic importance, since otherwise, words in separate inline nodes can be merged together without a space. To fix this, explicitly check that the node is before a hard line break, not a soft (wrapped) line break. Differential Revision: https://phabricator.services.mozilla.com/D242290
This commit is contained in:
@@ -823,11 +823,10 @@ void NotificationController::WillRefresh(mozilla::TimeStamp aTime) {
|
|||||||
if (textAcc) {
|
if (textAcc) {
|
||||||
// Remove the TextLeafAccessible if:
|
// Remove the TextLeafAccessible if:
|
||||||
// 1. The rendered text is empty; or
|
// 1. The rendered text is empty; or
|
||||||
// 2. The text is just a space, but its layout frame has a width of 0,
|
// 2. The text is invisible, semantically irrelevant whitespace before a
|
||||||
// so it isn't visible. This can happen if there is whitespace before an
|
// hard line break.
|
||||||
// invisible element at the end of a block.
|
|
||||||
if (text.mString.IsEmpty() ||
|
if (text.mString.IsEmpty() ||
|
||||||
(text.mString.EqualsLiteral(" ") && textFrame->GetRect().IsEmpty())) {
|
nsCoreUtils::IsTrimmedWhitespaceBeforeHardLineBreak(textFrame)) {
|
||||||
#ifdef A11Y_LOG
|
#ifdef A11Y_LOG
|
||||||
if (logging::IsEnabled(logging::eTree | logging::eText)) {
|
if (logging::IsEnabled(logging::eTree | logging::eText)) {
|
||||||
logging::MsgBegin("TREE", "text node lost its content; doc: %p",
|
logging::MsgBegin("TREE", "text node lost its content; doc: %p",
|
||||||
|
|||||||
@@ -1306,7 +1306,7 @@ LocalAccessible* nsAccessibilityService::CreateAccessible(
|
|||||||
// Ignore not rendered text nodes and whitespace text nodes between table
|
// Ignore not rendered text nodes and whitespace text nodes between table
|
||||||
// cells.
|
// cells.
|
||||||
if (text.mString.IsEmpty() ||
|
if (text.mString.IsEmpty() ||
|
||||||
(text.mString.EqualsLiteral(" ") && frame->GetRect().IsEmpty()) ||
|
nsCoreUtils::IsTrimmedWhitespaceBeforeHardLineBreak(frame) ||
|
||||||
(aContext->IsTableRow() &&
|
(aContext->IsTableRow() &&
|
||||||
nsCoreUtils::IsWhitespaceString(text.mString))) {
|
nsCoreUtils::IsWhitespaceString(text.mString))) {
|
||||||
if (aIsSubtreeHidden) *aIsSubtreeHidden = true;
|
if (aIsSubtreeHidden) *aIsSubtreeHidden = true;
|
||||||
|
|||||||
@@ -661,3 +661,19 @@ Element* nsCoreUtils::GetAriaActiveDescendantElement(Element* aElement) {
|
|||||||
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool nsCoreUtils::IsTrimmedWhitespaceBeforeHardLineBreak(nsIFrame* aFrame) {
|
||||||
|
if (!aFrame->GetRect().IsEmpty() ||
|
||||||
|
!aFrame->HasAnyStateBits(TEXT_END_OF_LINE)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Normally, accessibility calls nsIFrame::GetRenderedText with
|
||||||
|
// TrailingWhitespace::NoTrim. Using TrailingWhitespace::Trim instead trims 0
|
||||||
|
// width whitespace before a hard line break, resulting in an empty string if
|
||||||
|
// that is all the frame contains. Note that TrailingWhitespace::Trim does
|
||||||
|
// *not* trim whitespace before a soft line break (wrapped line).
|
||||||
|
nsIFrame::RenderedText text = aFrame->GetRenderedText(
|
||||||
|
0, UINT32_MAX, nsIFrame::TextOffsetType::OffsetsInContentText,
|
||||||
|
nsIFrame::TrailingWhitespace::Trim);
|
||||||
|
return text.mString.IsEmpty();
|
||||||
|
}
|
||||||
|
|||||||
@@ -336,6 +336,17 @@ class nsCoreUtils {
|
|||||||
nsINode* aStartAncestor);
|
nsINode* aStartAncestor);
|
||||||
|
|
||||||
static Element* GetAriaActiveDescendantElement(Element* aElement);
|
static Element* GetAriaActiveDescendantElement(Element* aElement);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if the given text frame is 0 width whitespace before a hard
|
||||||
|
* line break. This is not visible and is semantically irrelevant. This can
|
||||||
|
* happen if there is whitespace before an invisible element at the end of a
|
||||||
|
* block. For example:
|
||||||
|
* <div><span>a</span> <span hidden>b</span></div>
|
||||||
|
* This results in a text node for "a" and a text node for " ". This function
|
||||||
|
* will return true for the latter node.
|
||||||
|
*/
|
||||||
|
static bool IsTrimmedWhitespaceBeforeHardLineBreak(nsIFrame* aFrame);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -69,24 +69,29 @@ addAccessibleTask(
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test whitespace before a hidden element at the end of a block.
|
* Test whitespace before hard and soft line breaks.
|
||||||
*/
|
*/
|
||||||
addAccessibleTask(
|
addAccessibleTask(
|
||||||
`<div id="container"><span>a</span> <span id="b" hidden>b</span></div>`,
|
`
|
||||||
async function testBeforeHiddenElementAtEnd(browser, docAcc) {
|
<div id="hardContainer"><span>a</span> <span id="b" hidden>b</span></div>
|
||||||
const container = findAccessibleChildByID(docAcc, "container");
|
<div id="softContainer" style="width: 1ch; font-family: monospace;">
|
||||||
testAccessibleTree(container, {
|
<span>c</span> <span>d</span>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
async function testBeforeLineBreaks(browser, docAcc) {
|
||||||
|
const hardContainer = findAccessibleChildByID(docAcc, "hardContainer");
|
||||||
|
testAccessibleTree(hardContainer, {
|
||||||
role: ROLE_SECTION,
|
role: ROLE_SECTION,
|
||||||
children: [{ role: ROLE_TEXT_LEAF, name: "a" }],
|
children: [{ role: ROLE_TEXT_LEAF, name: "a" }],
|
||||||
});
|
});
|
||||||
|
|
||||||
info("Showing b");
|
info("Showing b");
|
||||||
let reordered = waitForEvent(EVENT_REORDER, container);
|
let reordered = waitForEvent(EVENT_REORDER, hardContainer);
|
||||||
await invokeContentTask(browser, [], () => {
|
await invokeContentTask(browser, [], () => {
|
||||||
content.document.getElementById("b").hidden = false;
|
content.document.getElementById("b").hidden = false;
|
||||||
});
|
});
|
||||||
await reordered;
|
await reordered;
|
||||||
testAccessibleTree(container, {
|
testAccessibleTree(hardContainer, {
|
||||||
role: ROLE_SECTION,
|
role: ROLE_SECTION,
|
||||||
children: [
|
children: [
|
||||||
{ role: ROLE_TEXT_LEAF, name: "a" },
|
{ role: ROLE_TEXT_LEAF, name: "a" },
|
||||||
@@ -96,15 +101,25 @@ addAccessibleTask(
|
|||||||
});
|
});
|
||||||
|
|
||||||
info("Hiding b");
|
info("Hiding b");
|
||||||
reordered = waitForEvent(EVENT_REORDER, container);
|
reordered = waitForEvent(EVENT_REORDER, hardContainer);
|
||||||
await invokeContentTask(browser, [], () => {
|
await invokeContentTask(browser, [], () => {
|
||||||
content.document.getElementById("b").hidden = true;
|
content.document.getElementById("b").hidden = true;
|
||||||
});
|
});
|
||||||
await reordered;
|
await reordered;
|
||||||
testAccessibleTree(container, {
|
testAccessibleTree(hardContainer, {
|
||||||
role: ROLE_SECTION,
|
role: ROLE_SECTION,
|
||||||
children: [{ role: ROLE_TEXT_LEAF, name: "a" }],
|
children: [{ role: ROLE_TEXT_LEAF, name: "a" }],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const softContainer = findAccessibleChildByID(docAcc, "softContainer");
|
||||||
|
testAccessibleTree(softContainer, {
|
||||||
|
role: ROLE_SECTION,
|
||||||
|
children: [
|
||||||
|
{ role: ROLE_TEXT_LEAF, name: "c" },
|
||||||
|
{ role: ROLE_TEXT_LEAF, name: " " },
|
||||||
|
{ role: ROLE_TEXT_LEAF, name: "d" },
|
||||||
|
],
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{ chrome: true, topLevel: true }
|
{ chrome: true, topLevel: true }
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user