Skip to content

Commit

Permalink
Support for editing of rich text in Google Chrome and Chrome-based br…
Browse files Browse the repository at this point in the history
…owsers such as Google Docs with braille support enabled.

This requires Chrome 47 or later.
As part of this, unify common code for web browsers exposing IAccessible2 into a new ia2Web module and use it for Mozilla and Chromium.
Fixes #2634.
  • Loading branch information
jcsteh committed Nov 2, 2015
1 parent 3f2f7b5 commit a75357d
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 73 deletions.
9 changes: 9 additions & 0 deletions source/NVDAObjects/IAccessible/__init__.py
Expand Up @@ -1427,6 +1427,15 @@ def _get_IA2States(self):
log.debugWarning("could not get IAccessible2 states", exc_info=True)
return IAccessibleHandler.IA2_STATE_DEFUNCT

def __contains__(self, obj):
if not isinstance(obj, IAccessible) or not isinstance(obj.IAccessibleObject, IAccessibleHandler.IAccessible2):
return False
try:
self.IAccessibleObject.accChild(obj.IA2UniqueID)
return True
except COMError:
return False

class ContentGenericClient(IAccessible):

TextInfo=displayModel.DisplayModelTextInfo
Expand Down
22 changes: 6 additions & 16 deletions source/NVDAObjects/IAccessible/chromium.py
Expand Up @@ -8,10 +8,12 @@
"""

from comtypes import COMError
import oleacc
import controlTypes
import IAccessibleHandler
from NVDAObjects.IAccessible import IAccessible
from virtualBuffers.gecko_ia2 import Gecko_ia2 as GeckoVBuf
from NVDAObjects.behaviors import Dialog
from . import ia2Web

class ChromeVBuf(GeckoVBuf):

Expand All @@ -27,8 +29,7 @@ def __contains__(self, obj):
return False
return True

class Document(IAccessible):
value = None
class Document(ia2Web.Document):

def _get_treeInterceptorClass(self):
states = self.states
Expand All @@ -40,16 +41,5 @@ def findExtraOverlayClasses(obj, clsList):
"""Determine the most appropriate class(es) for Chromium objects.
This works similarly to L{NVDAObjects.NVDAObject.findOverlayClasses} except that it never calls any other findOverlayClasses method.
"""
role = obj.role
if role == controlTypes.ROLE_DOCUMENT:
clsList.append(Document)
return

if role == controlTypes.ROLE_DIALOG:
xmlRoles = obj.IA2Attributes.get("xml-roles", "").split(" ")
if "dialog" in xmlRoles:
# #2390: Don't try to calculate text for ARIA dialogs.
try:
clsList.remove(Dialog)
except ValueError:
pass
ia2Web.findExtraOverlayClasses(obj, clsList,
documentClass=Document)
80 changes: 80 additions & 0 deletions source/NVDAObjects/IAccessible/ia2Web.py
@@ -0,0 +1,80 @@
#NVDAObjects/IAccessible/ia2Web.py
#A part of NonVisual Desktop Access (NVDA)
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
#Copyright (C) 2006-2015 NV Access Limited

"""Base classes with common support for browsers exposing IAccessible2.
"""

import oleacc
import IAccessibleHandler
import controlTypes
from NVDAObjects.behaviors import Dialog
from . import IAccessible
from .ia2TextMozilla import MozillaCompoundTextInfo

class Ia2Web(IAccessible):
IAccessibleTableUsesTableCellIndexAttrib=True

def _get_positionInfo(self):
info=super(Ia2Web,self).positionInfo
level=info.get('level',None)
if not level:
level=self.IA2Attributes.get('level',None)
if level:
info['level']=level
return info

class Document(Ia2Web):
value = None

def _get_shouldCreateTreeInterceptor(self):
return controlTypes.STATE_READONLY in self.states

class Application(Document):
shouldCreateTreeInterceptor = False

class BlockQuote(Ia2Web):
role = controlTypes.ROLE_BLOCKQUOTE

class Editor(Ia2Web):
TextInfo = MozillaCompoundTextInfo

def findExtraOverlayClasses(obj, clsList, baseClass=Ia2Web, documentClass=None):
"""Determine the most appropriate class if this is an IA2 web object.
This should be called after finding any classes for the specific web implementation.
@param baseClass: The base class for all web objects.
@param documentClass: The class to use for document roots, including ARIA applications.
"""
if not documentClass:
raise ValueError("documentClass cannot be None")
if not isinstance(obj.IAccessibleObject, IAccessibleHandler.IAccessible2):
return

iaRole = obj.IAccessibleRole
if iaRole == IAccessibleHandler.IA2_ROLE_SECTION and obj.IA2Attributes.get("tag", None) == "blockquote":
clsList.append(BlockQuote)
elif iaRole == oleacc.ROLE_SYSTEM_ALERT:
clsList.append(Dialog)

isApp = iaRole in (oleacc.ROLE_SYSTEM_APPLICATION, oleacc.ROLE_SYSTEM_DIALOG)
if isApp:
clsList.append(Application)
if isApp or iaRole == oleacc.ROLE_SYSTEM_DOCUMENT:
clsList.append(documentClass)

if obj.IA2States & IAccessibleHandler.IA2_STATE_EDITABLE and obj.IAccessibleStates & oleacc.STATE_SYSTEM_FOCUSABLE:
clsList.append(Editor)

if iaRole in (oleacc.ROLE_SYSTEM_DIALOG,oleacc.ROLE_SYSTEM_PROPERTYPAGE):
xmlRoles = obj.IA2Attributes.get("xml-roles", "").split(" ")
if "dialog" in xmlRoles or "tabpanel" in xmlRoles:
# #2390: Don't try to calculate text for ARIA dialogs.
# #4638: Don't try to calculate text for ARIA tab panels.
try:
clsList.remove(Dialog)
except ValueError:
pass

clsList.append(baseClass)
63 changes: 6 additions & 57 deletions source/NVDAObjects/IAccessible/mozilla.py
Expand Up @@ -18,11 +18,9 @@
import textInfos.offsets
from NVDAObjects.behaviors import RowWithFakeNavigation
from . import IA2TextTextInfo
from .ia2TextMozilla import MozillaCompoundTextInfo
from . import ia2Web

class Mozilla(IAccessible):

IAccessibleTableUsesTableCellIndexAttrib=True
class Mozilla(ia2Web.Ia2Web):

def _get_parent(self):
#Special code to support Mozilla node_child_of relation (for comboboxes)
Expand Down Expand Up @@ -58,24 +56,6 @@ def _get_presentationType(self):
presType=self.presType_layout
return presType

def _get_positionInfo(self):
info=super(Mozilla,self).positionInfo
level=info.get('level',None)
if not level:
level=self.IA2Attributes.get('level',None)
if level:
info['level']=level
return info

def __contains__(self, obj):
if not isinstance(obj, Mozilla):
return False
try:
self.IAccessibleObject.accChild(obj.IA2UniqueID)
return True
except COMError:
return False

class Gecko1_9(Mozilla):

def _get_description(self):
Expand Down Expand Up @@ -105,9 +85,7 @@ def __nonzero__(self):
# As far as NVDA is concerned, this is a useless object.
return False

class Document(Mozilla):

value=None
class Document(ia2Web.Document):

def _get_treeInterceptorClass(self):
ver=getGeckoVersion(self)
Expand All @@ -121,9 +99,6 @@ def _get_treeInterceptorClass(self):
return virtualBuffers.gecko_ia2.Gecko_ia2
return super(Document,self).treeInterceptorClass

def _get_shouldCreateTreeInterceptor(self):
return controlTypes.STATE_READONLY in self.states

class EmbeddedObject(Mozilla):

def _get_shouldAllowIAccessibleFocusEvent(self):
Expand Down Expand Up @@ -181,12 +156,6 @@ class TextLeaf(Mozilla):
role = controlTypes.ROLE_STATICTEXT
beTransparentToMouse = True

class Application(Document):
shouldCreateTreeInterceptor = False

class BlockQuote(Mozilla):
role = controlTypes.ROLE_BLOCKQUOTE

class Math(Mozilla):

def _get_mathMl(self):
Expand All @@ -208,9 +177,6 @@ def _get_mathMl(self):
attrs = ""
return "<math%s>%s</math>" % (attrs, node.innerHTML)

class Editor(Mozilla):
TextInfo = MozillaCompoundTextInfo

def findExtraOverlayClasses(obj, clsList):
"""Determine the most appropriate class if this is a Mozilla object.
This works similarly to L{NVDAObjects.NVDAObject.findOverlayClasses} except that it never calls any other findOverlayClasses method.
Expand All @@ -237,16 +203,12 @@ def findExtraOverlayClasses(obj, clsList):
# This excludes a non-focusable @role="textbox".
if not (obj.IA2States & IAccessibleHandler.IA2_STATE_EDITABLE):
cls = TextLeaf
elif iaRole == IAccessibleHandler.IA2_ROLE_SECTION and obj.IA2Attributes.get("tag", None) == "blockquote":
cls = BlockQuote
if not cls:
cls = _IAccessibleRolesToOverlayClasses.get(iaRole)
if cls:
clsList.append(cls)

if cls is not TextLeaf and obj.IA2States & IAccessibleHandler.IA2_STATE_EDITABLE and obj.IAccessibleStates & oleacc.STATE_SYSTEM_FOCUSABLE:
clsList.append(Editor)
elif iaRole == oleacc.ROLE_SYSTEM_ROW:
if iaRole == oleacc.ROLE_SYSTEM_ROW:
clsList.append(RowWithFakeNavigation)
elif iaRole == oleacc.ROLE_SYSTEM_LISTITEM and hasattr(obj.parent, "IAccessibleTableObject"):
clsList.append(RowWithFakeNavigation)
Expand All @@ -268,27 +230,14 @@ def findExtraOverlayClasses(obj, clsList):
if ver and ver.full.startswith("1.9"):
clsList.append(Gecko1_9)

if iaRole in (oleacc.ROLE_SYSTEM_DIALOG,oleacc.ROLE_SYSTEM_PROPERTYPAGE):
xmlRoles = obj.IA2Attributes.get("xml-roles", "").split(" ")
if "dialog" in xmlRoles or "tabpanel" in xmlRoles:
# #2390: Don't try to calculate text for ARIA dialogs.
# #4638: Don't try to calculate text for ARIA tab panels.
try:
clsList.remove(Dialog)
except ValueError:
pass

clsList.append(Mozilla)
ia2Web.findExtraOverlayClasses(obj, clsList,
baseClass=Mozilla, documentClass=Document)

#: Maps IAccessible roles to NVDAObject overlay classes.
_IAccessibleRolesToOverlayClasses = {
oleacc.ROLE_SYSTEM_ALERT: Dialog,
oleacc.ROLE_SYSTEM_DOCUMENT: Document,
IAccessibleHandler.IA2_ROLE_EMBEDDED_OBJECT: EmbeddedObject,
"embed": EmbeddedObject,
"object": EmbeddedObject,
oleacc.ROLE_SYSTEM_APPLICATION: Application,
oleacc.ROLE_SYSTEM_DIALOG: Application,
oleacc.ROLE_SYSTEM_EQUATION: Math,
}

Expand Down
2 changes: 2 additions & 0 deletions user_docs/en/changes.t2t
Expand Up @@ -16,6 +16,8 @@
- In Microsoft Excel, NVDA now reports any input messages set by the sheet author on cells. (#5051)
- Support for the Baum Pronto! V4 and VarioUltra braille displays when connected via Bluetooth. (#3717)
- Support for editing of rich text in Mozilla applications such as Google Docs with braille support enabled in Mozilla Firefox and HTML composition in Mozilla Thunderbird. (#1668)
- Support for editing of rich text in Google Chrome and Chrome-based browsers such as Google Docs with braille support enabled. (#2634)
- This requires Chrome version 47 or later.
- In browse mode in Microsoft Excel, you can navigate to locked cells in protected sheets. (#4952)


Expand Down

0 comments on commit a75357d

Please sign in to comment.