Skip to content

Commit 0c8809c

Browse files
authored
Merge a8b165f into a2a6e23
2 parents a2a6e23 + a8b165f commit 0c8809c

11 files changed

Lines changed: 152 additions & 12 deletions

File tree

nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.cpp

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,10 @@ bool hasXmlRoleAttribContainingValue(const map<wstring,wstring>& attribsMap, con
4747
return attribsMapIt != attribsMap.end() && attribsMapIt->second.find(roleName) != wstring::npos;
4848
}
4949

50-
CComPtr<IAccessible2> GeckoVBufBackend_t::getLabelElement(IAccessible2_2* element) {
50+
CComPtr<IAccessible2> GeckoVBufBackend_t::getRelationElement(
51+
LPCOLESTR ia2TargetRelation,
52+
IAccessible2_2* element
53+
) {
5154
IUnknown** ppUnk=nullptr;
5255
long nTargets=0;
5356
// We only need to request one relation target
@@ -60,7 +63,12 @@ CComPtr<IAccessible2> GeckoVBufBackend_t::getLabelElement(IAccessible2_2* elemen
6063
numRelations=0;
6164
}
6265
// the relation type string *must* be passed correctly as a BSTR otherwise we can see crashes in 32 bit Firefox.
63-
HRESULT res=element->get_relationTargetsOfType(CComBSTR(IA2_RELATION_LABELLED_BY),numRelations,&ppUnk,&nTargets);
66+
HRESULT res = element->get_relationTargetsOfType(
67+
CComBSTR(ia2TargetRelation),
68+
numRelations,
69+
&ppUnk,
70+
&nTargets
71+
);
6472
if(res!=S_OK) return nullptr;
6573
// Grab all the returned IUnknowns and store them as smart pointers within a smart pointer array
6674
// so that any further returns will correctly release all the objects.
@@ -71,7 +79,10 @@ CComPtr<IAccessible2> GeckoVBufBackend_t::getLabelElement(IAccessible2_2* elemen
7179
// we can now free the memory that Gecko allocated to give us the IUnknowns
7280
CoTaskMemFree(ppUnk);
7381
if(nTargets==0) {
74-
LOG_DEBUG(L"relationTargetsOfType for IA2_RELATION_LABELLED_BY found no targets");
82+
std::wstring debugMsg = L"relationTargetsOfType for ";
83+
debugMsg.append((const wchar_t*) CComBSTR(ia2TargetRelation));
84+
debugMsg.append(L" found no targets");
85+
LOG_DEBUG(debugMsg);
7586
return nullptr;
7687
}
7788
return CComQIPtr<IAccessible2>(ppUnk_smart[0]);
@@ -267,7 +278,7 @@ using OptionalLabelInfo = optional< LabelInfo >;
267278
OptionalLabelInfo GeckoVBufBackend_t::getLabelInfo(IAccessible2* pacc2) {
268279
CComQIPtr<IAccessible2_2> pacc2_2=pacc2;
269280
if (!pacc2_2) return OptionalLabelInfo();
270-
auto targetAcc=getLabelElement(pacc2_2);
281+
auto targetAcc = getRelationElement(IA2_RELATION_LABELLED_BY, pacc2_2);
271282
if(!targetAcc) return OptionalLabelInfo();
272283
CComVariant child;
273284
child.vt = VT_I4;
@@ -279,6 +290,15 @@ OptionalLabelInfo GeckoVBufBackend_t::getLabelInfo(IAccessible2* pacc2) {
279290
return LabelInfo { isVisible, ID } ;
280291
}
281292

293+
std::optional<int> GeckoVBufBackend_t::getRelationId(LPCOLESTR ia2TargetRelation, IAccessible2* pacc2) {
294+
CComQIPtr<IAccessible2_2> pacc2_2 = pacc2;
295+
if (pacc2_2 == nullptr) return std::optional<int>();
296+
auto targetAcc = getRelationElement(ia2TargetRelation, pacc2_2);
297+
if (targetAcc == nullptr) return std::optional<int>();
298+
auto ID = getIAccessible2UniqueID(targetAcc);
299+
return ID;
300+
}
301+
282302
long getChildCount(const bool isAriaHidden, IAccessible2 * const pacc){
283303
long rawChildCount = 0;
284304
if(!isAriaHidden){
@@ -1124,6 +1144,29 @@ VBufStorage_fieldNode_t* GeckoVBufBackend_t::fillVBuf(
11241144
}
11251145
}
11261146

1147+
1148+
/* Set the details summary by checking for both IA2_RELATION_DETAILS and IA2_RELATION_DETAILS_FOR as one
1149+
of the nodes in the relationship will not be in the buffer yet */
1150+
std::optional<int> detailsId = getRelationId(IA2_RELATION_DETAILS, pacc);
1151+
if (detailsId) {
1152+
auto detailsControlFieldNode = buffer->getControlFieldNodeWithIdentifier(docHandle, detailsId.value());
1153+
if (detailsControlFieldNode) {
1154+
std::wstring detailsSummary = L"";
1155+
detailsControlFieldNode->getTextInRange(0, detailsControlFieldNode->getLength(), detailsSummary, false);
1156+
parentNode->addAttribute(L"detailsSummary", detailsSummary);
1157+
}
1158+
}
1159+
1160+
std::optional<int> detailsForId = getRelationId(IA2_RELATION_DETAILS_FOR, pacc);
1161+
if (detailsForId) {
1162+
auto detailsControlFieldNode = buffer->getControlFieldNodeWithIdentifier(docHandle, detailsForId.value());
1163+
if (detailsControlFieldNode) {
1164+
std::wstring detailsSummary = L"";
1165+
parentNode->getTextInRange(0, parentNode->getLength(), detailsSummary, false);
1166+
detailsControlFieldNode->addAttribute(L"detailsSummary", detailsSummary);
1167+
}
1168+
}
1169+
11271170
// Clean up.
11281171
if(name)
11291172
SysFreeString(name);

nvdaHelper/vbufBackends/gecko_ia2/gecko_ia2.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ class GeckoVBufBackend_t: public VBufBackend_t {
4040

4141
std::wstring toolkitName;
4242

43+
std::optional<int> getRelationId(LPCOLESTR ia2TargetRelation, IAccessible2* pacc2);
4344
std::optional< LabelInfo > getLabelInfo(IAccessible2* pacc2);
44-
CComPtr<IAccessible2> getLabelElement(IAccessible2_2* element);
45+
CComPtr<IAccessible2> getRelationElement(LPCOLESTR ia2TargetRelation, IAccessible2_2* element);
4546
CComPtr<IAccessible2> getSelectedItem(IAccessible2* container,
4647
const std::map<std::wstring, std::wstring>& attribs);
4748

source/braille.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,8 @@
221221
controlTypes.STATE_SORTED_ASCENDING: _("sorted asc"),
222222
# Translators: Displayed in braille when an object is sorted descending.
223223
controlTypes.STATE_SORTED_DESCENDING: _("sorted desc"),
224+
# Translators: Displayed in braille when an object has additional details (such as a comment section).
225+
controlTypes.STATE_HAS_ARIA_DETAILS: _("details"),
224226
# Translators: Displayed in braille when an object (usually a graphic) has a long description.
225227
controlTypes.STATE_HASLONGDESC: _("ldesc"),
226228
# Translators: Displayed in braille when there is a formula on a spreadsheet cell.

source/browseMode.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,6 +1399,26 @@ def script_activateLongDesc(self,gesture):
13991399
# Translators: the description for the activateLongDescription script on browseMode documents.
14001400
script_activateLongDesc.__doc__=_("Shows the long description at this position if one is found.")
14011401

1402+
@script(
1403+
description=_(
1404+
# Translators: the description for the activateAriaDetailsSummary script on browseMode documents.
1405+
"Shows a summary of the details at this position if found."
1406+
)
1407+
)
1408+
def script_activateAriaDetailsSummary(self, gesture):
1409+
info = self.makeTextInfo(textInfos.POSITION_CARET)
1410+
info.expand("character")
1411+
for field in reversed(info.getTextWithFields()):
1412+
if isinstance(field, textInfos.FieldCommand) and field.command == "controlStart":
1413+
states = field.field.get('states')
1414+
if states and controlTypes.STATE_HAS_ARIA_DETAILS in states:
1415+
ui.message(field.field['detailsSummary'])
1416+
return
1417+
1418+
# Translators: the message presented when the activateAriaDetailsSummary script cannot locate a
1419+
# set of details to read.
1420+
ui.message(_("No additional details"))
1421+
14021422
def event_caretMovementFailed(self, obj, nextHandler, gesture=None):
14031423
if not self.passThrough or not gesture or not config.conf["virtualBuffers"]["autoPassThroughOnCaretMove"]:
14041424
return nextHandler()

source/controlTypes.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@
203203
STATE_CROPPED=0x8000000000
204204
STATE_OVERFLOWING=0x10000000000
205205
STATE_UNLOCKED=0x20000000000
206+
STATE_HAS_ARIA_DETAILS = 0x40000000000
206207

207208
roleLabels: Dict[int, str] = {
208209
# Translators: The word for an unknown control type.
@@ -573,6 +574,8 @@
573574
STATE_SORTED_DESCENDING:_("sorted descending"),
574575
# Translators: a state that denotes that an object (usually a graphic) has a long description.
575576
STATE_HASLONGDESC:_("has long description"),
577+
# Translators: a state that denotes that an object has additional details (such as a comment section).
578+
STATE_HAS_ARIA_DETAILS: _("has details"),
576579
# Translators: a state that denotes that an object is pinned in its current location
577580
STATE_PINNED:_("pinned"),
578581
# Translators: a state that denotes the existance of a formula on a spreadsheet cell
@@ -757,6 +760,8 @@ def processPositiveStates(role, states, reason: OutputReason, positiveStates=Non
757760
positiveStates.discard(STATE_EXPANDED)
758761
if STATE_FOCUSABLE not in states:
759762
positiveStates.discard(STATE_EDITABLE)
763+
# reading aria-details is an experimental feature still and should not be reported. (#12364)
764+
positiveStates.discard(STATE_HAS_ARIA_DETAILS)
760765
return positiveStates
761766

762767

source/virtualBuffers/gecko_ia2.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,8 @@ def _normalizeControlField(self,attrs):
7575
# This is a text leaf.
7676
# See NVDAObjects.Iaccessible.mozilla.findOverlayClasses for an explanation of these checks.
7777
role = controlTypes.ROLE_STATICTEXT
78+
if attrs.get("detailsSummary") is not None:
79+
states.add(controlTypes.STATE_HAS_ARIA_DETAILS)
7880
if attrs.get("IAccessibleAction_showlongdesc") is not None:
7981
states.add(controlTypes.STATE_HASLONGDESC)
8082
if "IAccessibleAction_click" in attrs:

tests/system/libraries/NvdaLib.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,12 @@ def _createTestIdFileName(name):
114114
return outputFileName
115115

116116
@staticmethod
117-
def setup_nvda_profile(configFileName):
117+
def setup_nvda_profile(configFileName, gesturesFileName: Optional[str] = None):
118118
configManager.setupProfile(
119119
_locations.repoRoot,
120120
configFileName,
121-
_locations.stagingDir
121+
_locations.stagingDir,
122+
gesturesFileName,
122123
)
123124

124125
@staticmethod
@@ -247,9 +248,9 @@ def start_NVDAInstaller(self, settingsFileName):
247248
self.nvdaSpy.wait_for_NVDA_startup_to_complete()
248249
return nvdaProcessHandle
249250

250-
def start_NVDA(self, settingsFileName):
251+
def start_NVDA(self, settingsFileName: str, gesturesFileName: Optional[str] = None):
251252
builtIn.log(f"Starting NVDA with config: {settingsFileName}")
252-
self.setup_nvda_profile(settingsFileName)
253+
self.setup_nvda_profile(settingsFileName, gesturesFileName)
253254
nvdaProcessHandle = self._startNVDAProcess()
254255
process.process_should_be_running(nvdaProcessHandle)
255256
self._connectToRemoteServer()

tests/system/libraries/SystemTestSpy/configManager.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from os.path import join as _pJoin
1212
from .getLib import _getLib
1313
import sys
14-
14+
from typing import Optional
1515

1616
# Imported for type information
1717
from robot.libraries.BuiltIn import BuiltIn
@@ -91,13 +91,24 @@ def _copyPythonLibs(pythonImports, libsDest):
9191
opSys.copy_file(libSource, libsDest)
9292

9393

94-
def setupProfile(repoRoot: str, settingsFileName: str, stagingDir: str):
94+
def setupProfile(
95+
repoRoot: str,
96+
settingsFileName: str,
97+
stagingDir: str,
98+
gesturesFileName: Optional[str] = None,
99+
):
95100
builtIn.log("Copying files into NVDA profile", level='DEBUG')
96101
opSys.copy_file(
97102
# Despite duplication, specify full paths for clarity.
98103
_pJoin(repoRoot, "tests", "system", "nvdaSettingsFiles", settingsFileName),
99104
_pJoin(stagingDir, "nvdaProfile", "nvda.ini")
100105
)
106+
if gesturesFileName is not None:
107+
opSys.copy_file(
108+
# Despite duplication, specify full paths for clarity.
109+
_pJoin(repoRoot, "tests", "system", "nvdaSettingsFiles", gesturesFileName),
110+
_pJoin(stagingDir, "nvdaProfile", "gestures.ini")
111+
)
101112
# create a package to use as the globalPlugin
102113
_installSystemTestSpyToScratchPad(
103114
repoRoot,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[browseMode.BrowseModeDocumentTreeInterceptor]
2+
activateAriaDetailsSummary = kb:nvda+\

tests/system/robot/chromeTests.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,55 @@ def checkbox_labelled_by_inner_element():
4848
)
4949

5050

51+
def test_aria_details():
52+
_chrome.prepareChrome(
53+
"""
54+
<div>
55+
<p>The word <mark aria-details="cat-details">cat</mark> has a comment tied to it.</p>
56+
<div id="cat-details" role="comment">
57+
Cats go woof BTW<br>&mdash;Jonathon Commentor
58+
<div role="comment">
59+
No they don't<br>&mdash;Zara
60+
</div>
61+
<div role="form">
62+
<textarea cols="80" placeholder="Add reply..."></textarea>
63+
<input type="submit">
64+
</div>
65+
</div>
66+
</div>
67+
"""
68+
)
69+
actualSpeech = _chrome.getSpeechAfterKey('downArrow')
70+
_asserts.strings_match(
71+
actualSpeech,
72+
"The word marked content cat out of marked content has a comment tied to it."
73+
)
74+
# this word has no details attached
75+
actualSpeech = _chrome.getSpeechAfterKey("control+rightArrow")
76+
_asserts.strings_match(
77+
actualSpeech,
78+
"word"
79+
)
80+
# check that there is no summary reported
81+
actualSpeech = _chrome.getSpeechAfterKey("NVDA+\\")
82+
_asserts.strings_match(
83+
actualSpeech,
84+
"No additional details"
85+
)
86+
# this word has details attached to it
87+
actualSpeech = _chrome.getSpeechAfterKey("control+rightArrow")
88+
_asserts.strings_match(
89+
actualSpeech,
90+
"marked content has details cat out of marked content"
91+
)
92+
# read the details summary
93+
actualSpeech = _chrome.getSpeechAfterKey("NVDA+\\")
94+
_asserts.strings_match(
95+
actualSpeech,
96+
"Cats go woof BTW Jonathon Commentor No they don't Zara Submit"
97+
)
98+
99+
51100
def announce_list_item_when_moving_by_word_or_character():
52101
_chrome.prepareChrome(
53102
r"""

0 commit comments

Comments
 (0)