Notes. Adding support for aria attributes to browsers - nvaccess/nvda GitHub Wiki
This page will outline some notes about adding support for a new aria attribute.
Focus mode
We'll start with focus mode, since it is a little simpler.
Overview
In focus mode, we populate an NVDAObject
with information about the element that currently has focus, then in speech.py
we need to know how to speak this element.
Extending the NVDAObject interface
In order to do this, a new property should be added to the NVDAObject
base class in NVDAObjects/__init__.py
. We will need to provide a solution to this across all browsers, one way to do this might look like the following example which will add a message to log (and give an error sound) with the name of the concrete class that is falling back to the base implementation, and a stack trace of how we got to that point.
def _get_isCurrent(self):
log.debugWarning("impl required for: %s" % repr(self), stack_info=True)
Note: Once we are done investigating / testing here we need to replace this with return False
or return None
or potentially raise NotImplementedError
. In this case we do not want to raise an exception because it's valid that this code is hit by implementations that don't support getting the isCurrent
value, and we do not want them to cause an error sound / log information.
Another option would be to use NVDA+F1
or the python console to investigate the attributes on an object while browsing.
The classes that will likely need an implementation of this are:
class Ia2Web
inNVDAObjects/IAccessible/ia2Web.py
- Here you can log out
self.IA2Attributes
to ensure that your attribute is available. Then once you are sure it exists (and know how it is named), get and return it withreturn self.IA2Attributes.get("current", False)
- This will likely handle both FireFox and Chrome, but do test this to make sure.
- Here you can log out
class MSHTML
inNVDAObjects/IAccessible/MSHTML.py
- Similarly to Chrome and Firefox, first try logging out the value of
self.HTMLAttributes
, then once you are sure of the name try returning the value of your new property withreturn self.HTMLAttributes["aria-current"]
- Similarly to Chrome and Firefox, first try logging out the value of
class EdgeNode
inNVDAObjects/UIA/edge.py
- In the case of Edge you will need to first try logging out
self.UIAElement.currentAriaProperties
then construct a Regular Expression to the get the value of the property.
- In the case of Edge you will need to first try logging out
Knowing how to speak it
In speech.py
we extract the property values (in speakObjectProperties
) that we are interested in (often based on current speech settings) and map these property values to speech (in getSpeechTextForProperties
).
Browse mode
Browse mode works a little differently from focus mode:
- Text infos instead of NVDAObjects
- Some text infos are implemented with virtual buffers.
- We need to be able do quickNav, so we must be able to quickly jump to an element (eg previous heading, next link, etc). In this way, browse mode needs to be aware of much more than focus mode which is only concerned with one object.
- Its worth noting that while virtual buffers are used for Chrome, Firefox, and IE, virtual buffers are not used as a backend to browse mode for Edge, Kindle and Word.
Getting the value out of the virtual buffer
In order to report text from Browse mode, we need to ensure it is exposed from and extract the new attribute from the virtual buffer which is used for Chrome, Firefox, and IE.
Chrome and Firefox
The new attribute should be exposed in the attrs
argument passed to def _normalizeControlField
in class Gecko_ia2_TextInfo
(file virtualBuffers/gecko_ia2.py
). Its likely that you will need to log out the contents of this argument to get the name, then map it to your desired property. Consider:
ariaCurrent = attrs.get("IAccessible2::attribute_current")
if ariaCurrent != None:
attrs['current']= ariaCurrent
IE
For IE, we need to expose the new attribute from the virtual buffer.
In nvdaHelper/vbufBackends/mshtml/mshtml.cpp
see the function void getAttributesFromHTMLDOMNode
and add a line like macro_addHTMLAttributeToMap(L"aria-current",false,pHTMLAttributeCollection2,attribsMap,tempVar,tempAttribNode);
. We then need to expose, and map this to the property we use to define the speech. In this case we wish to map 'aria-current' to 'current'. To do this, look in def _normalizeControlField
of class MSHTMLTextInfo
(file virtualBuffers/MSHTML.py
) we do something like:
ariaCurrent = attrs.get('HTMLAttrib::aria-current', None)
if ariaCurrent is not None:
attrs['current']=ariaCurrent
Edge
A little different from the others since we do not use virtual buffers for Edge. We can actually just extract the value from the NVDAObject
we set up when handling focus mode. In def _getControlFieldForObject
of NVDAObjects/UIA/edge.py
try something like field['current']=obj.isCurrent
Speaking the value
We then need to "allow" the property in the def getControlFieldSpeech
function of speech.py
. This will require adding something like valueForCurrent = attrs.get('current', None)
, then getting the speech text for this value speechForCurrent = getSpeechTextForProperties(reason=reason,current=valueForCurrent)
. The tricky bit here is knowing the circumstances to add this text to the returned text.
Braille
Don't forget to implement and test Braille output.
See (all in braille.py
):
def getBrailleTextForProperties
def getControlFieldBraille
def update
inclass NVDAObjectRegion
to pass appropriate args togetBrailleTextForProperties
Testing Braille
If you do not have a braille display, checkout the wiki page: Testing-braille-output-without-a-braille-display
Full example
For a full example see PR #6860