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 in NVDAObjects/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 with return self.IA2Attributes.get("current", False)
    • This will likely handle both FireFox and Chrome, but do test this to make sure.
  • class MSHTML in NVDAObjects/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 with return self.HTMLAttributes["aria-current"]
  • class EdgeNode in NVDAObjects/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.

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 in class NVDAObjectRegion to pass appropriate args to getBrailleTextForProperties

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