Bug 1950782 part 1: Expose the SELECTABLE_TEXT state on DocAccessibles when appropriate. r=nlapre,devtools-reviewers,nchevobbe

We already had code to expose SELECTABLE_TEXT in HyperTextAccessible, from which DocAccessible inherits.
However, DocAccessible overrides the method and doesn't call the HyperTextAccessible implementation.
We don't want to do that because it does work that isn't necessary for DocAccessible.
In addition, we need to use a different layout frame for the document.
See the code comments for details.
Specific code has been added to DocAccessible to expose this state.

This fixes the UIA Text pattern's SupportedTextSelection property on documents.

Differential Revision: https://phabricator.services.mozilla.com/D239854
This commit is contained in:
James Teh
2025-03-04 12:17:25 +00:00
parent debda864b9
commit 4fd90edd29
8 changed files with 120 additions and 6 deletions

View File

@@ -240,6 +240,18 @@ uint64_t DocAccessible::NativeState() const {
RefPtr<EditorBase> editorBase = GetEditor();
state |= editorBase ? states::EDITABLE : states::READONLY;
// GetFrame() returns the root frame, which is normally what we want. However,
// user-select: none might be set on the body, in which case this won't be
// exposed on the root frame. Therefore, we explicitly use the body frame
// here (if any).
nsIFrame* bodyFrame = mContent ? mContent->GetPrimaryFrame() : nullptr;
if ((state & states::EDITABLE) ||
(bodyFrame && bodyFrame->IsSelectable(nullptr))) {
// If the accessible is editable the layout selectable state only disables
// mouse selection, but keyboard (shift+arrow) selection is still possible.
state |= states::SELECTABLE_TEXT;
}
return state;
}

View File

@@ -863,3 +863,43 @@ addAccessibleTask(
},
{ chrome: true, topLevel: true }
);
/**
* Test the selectable text state.
*/
addAccessibleTask(
`
<p id="selectableP">selectableP</p>
<p id="unselectableP" style="user-select: none;">unselectableP</p>
`,
async function testSelectableText(browser, docAcc) {
testStates(docAcc, 0, EXT_STATE_SELECTABLE_TEXT);
const selectableP = findAccessibleChildByID(docAcc, "selectableP");
testStates(selectableP, 0, EXT_STATE_SELECTABLE_TEXT);
const unselectableP = findAccessibleChildByID(docAcc, "unselectableP");
testStates(unselectableP, 0, 0, 0, EXT_STATE_SELECTABLE_TEXT);
},
{ chrome: true, topLevel: true }
);
/**
* Test the selectable text state on an unselectable body.
*/
addAccessibleTask(
`
<style>
body {
user-select: none;
}
<p id="p">p</p>
`,
async function testSelectableTextUnselectableBody(browser, docAcc) {
testStates(docAcc, 0, 0, 0, EXT_STATE_SELECTABLE_TEXT);
const p = findAccessibleChildByID(docAcc, "p");
testStates(p, 0, 0, 0, EXT_STATE_SELECTABLE_TEXT);
},
{
chrome: true,
topLevel: true,
}
);

View File

@@ -1423,6 +1423,23 @@ body {
}
);
/**
* Test the Text pattern's SupportedTextSelection property on a document with a
* selectable body.
*/
addUiaTask(``, async function testTextSupportedTextSelectionSelectableBody() {
is(
await runPython(`
global doc
doc = getDocUia()
text = getUiaPattern(doc, "Text")
return text.SupportedTextSelection
`),
SupportedTextSelection_Multiple,
"doc SupportedTextSelection correct"
);
});
/**
* Test the Text pattern's GetSelection method with the caret.
*/

View File

@@ -79,7 +79,14 @@ const OOP_FRAME_DOCUMENT_SNAPSHOT = {
"text-align": "start",
"text-indent": "0px",
},
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
states: [
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
],
children: [
{
childCount: 1,
@@ -176,7 +183,14 @@ const EXPECTED_SNAPSHOT = {
"text-align": "start",
"text-indent": "0px",
},
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
states: [
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
],
children: [OOP_FRAME_SNAPSHOT],
};

View File

@@ -52,6 +52,7 @@ const tests = [
"focused",
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
@@ -153,7 +154,14 @@ const tests = [
keyboardShortcut: "",
childCount: 2,
indexInParent: 0,
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
states: [
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
],
},
},
},

View File

@@ -39,6 +39,7 @@ const tests = [
"focused",
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
@@ -54,7 +55,13 @@ const tests = [
),
expected: {
sidebar: {
states: ["unavailable", "readonly", "focusable", "opaque"],
states: [
"unavailable",
"readonly",
"focusable",
"selectable text",
"opaque",
],
},
},
},

View File

@@ -48,6 +48,7 @@ const tests = [
"focused",
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
@@ -146,7 +147,14 @@ const tests = [
keyboardShortcut: "",
childCount: 2,
indexInParent: 0,
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
states: [
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
],
},
},
},

View File

@@ -40,6 +40,7 @@ const tests = [
"focused",
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
@@ -132,7 +133,14 @@ const tests = [
keyboardShortcut: "",
childCount: 2,
indexInParent: 0,
states: ["readonly", "focusable", "opaque", "enabled", "sensitive"],
states: [
"readonly",
"focusable",
"selectable text",
"opaque",
"enabled",
"sensitive",
],
},
},
},