From 5819e9cd2c40e97f0563ad2febde6084ff22dfaa Mon Sep 17 00:00:00 2001 From: James Teh Date: Wed, 28 Jul 2010 09:21:04 +1000 Subject: [PATCH 1/8] Add an NVDAObjects.IAccessible.mozilla.findExtraOverlayClasses() function, which finds overlay classes specific to Mozilla. Use this in IAccessible.findOverlayClasses() instead of the mappings in _staticMap. This moves the code which determines Mozilla overlay classes into the mozilla module, as well as removing many duplicates in _staticMap due to Mozilla's use of several window classes. It will also be necessary for some upcoming, more complicated overlay class detection. --- source/NVDAObjects/IAccessible/__init__.py | 15 ++----------- source/NVDAObjects/IAccessible/mozilla.py | 25 +++++++++++++++++++++- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/source/NVDAObjects/IAccessible/__init__.py b/source/NVDAObjects/IAccessible/__init__.py index 8e0bc8bb8cb..5e9d952eee6 100644 --- a/source/NVDAObjects/IAccessible/__init__.py +++ b/source/NVDAObjects/IAccessible/__init__.py @@ -377,8 +377,8 @@ def findOverlayClasses(self,clsList): from . import adobeFlash adobeFlash.findExtraOverlayClasses(self, clsList) elif windowClassName.startswith('Mozilla'): - from .mozilla import Mozilla - clsList.append( Mozilla) + from . import mozilla + mozilla.findExtraOverlayClasses(self, clsList) elif windowClassName.startswith('bosa_sdm'): from .msOffice import SDM clsList.append(SDM) @@ -1450,17 +1450,6 @@ def _get_lastChild(self): ("TrayClockWClass",oleacc.ROLE_SYSTEM_CLIENT):"TrayClockWClass", ("TRxRichEdit",oleacc.ROLE_SYSTEM_CLIENT):"delphi.TRxRichEdit", (None,oleacc.ROLE_SYSTEM_OUTLINEITEM):"OutlineItem", - ("MozillaDialogClass",oleacc.ROLE_SYSTEM_ALERT):"Dialog", - ("MozillaContentWindowClass",oleacc.ROLE_SYSTEM_COMBOBOX):"mozilla.ComboBox", - ("MozillaContentWindowClass",oleacc.ROLE_SYSTEM_LIST):"mozilla.List", - ("MozillaWindowClass",oleacc.ROLE_SYSTEM_LISTITEM):"mozilla.ListItem", - ("MozillaContentWindowClass",oleacc.ROLE_SYSTEM_LISTITEM):"mozilla.ListItem", - ("MozillaContentWindowClass",oleacc.ROLE_SYSTEM_DOCUMENT):"mozilla.Document", - ("MozillaWindowClass",oleacc.ROLE_SYSTEM_DOCUMENT):"mozilla.Document", - ("MozillaUIWindowClass",oleacc.ROLE_SYSTEM_TABLE):"mozilla.Table", - ("MozillaUIWindowClass",oleacc.ROLE_SYSTEM_OUTLINE):"mozilla.Tree", - ("MozillaContentWindowClass",IAccessibleHandler.IA2_ROLE_EMBEDDED_OBJECT):"mozilla.EmbeddedObject", - ("MozillaContentWindowClass","embed"):"mozilla.EmbeddedObject", ("ConsoleWindowClass",oleacc.ROLE_SYSTEM_WINDOW):"ConsoleWindowClass", (None,oleacc.ROLE_SYSTEM_LIST):"List", (None,oleacc.ROLE_SYSTEM_COMBOBOX):"ComboBox", diff --git a/source/NVDAObjects/IAccessible/mozilla.py b/source/NVDAObjects/IAccessible/mozilla.py index 7fb5d771071..98578797e84 100755 --- a/source/NVDAObjects/IAccessible/mozilla.py +++ b/source/NVDAObjects/IAccessible/mozilla.py @@ -9,7 +9,7 @@ import winUser import eventHandler import controlTypes -from . import IAccessible +from . import IAccessible, Dialog import textInfos from logHandler import log @@ -105,3 +105,26 @@ def _get_shouldAllowIAccessibleFocusEvent(self): # We don't want to override the focus event fired by the embedded object. return False return super(EmbeddedObject, self).shouldAllowIAccessibleFocusEvent + +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. + """ + iaRole = obj.IAccessibleRole + cls = _IAccessibleRolesToOverlayClasses.get(iaRole) + if cls: + clsList.append(cls) + clsList.append(Mozilla) + +#: Maps IAccessible roles to NVDAObject overlay classes. +_IAccessibleRolesToOverlayClasses = { + oleacc.ROLE_SYSTEM_ALERT: Dialog, + oleacc.ROLE_SYSTEM_COMBOBOX: ComboBox, + oleacc.ROLE_SYSTEM_LIST: List, + oleacc.ROLE_SYSTEM_LISTITEM: ListItem, + oleacc.ROLE_SYSTEM_DOCUMENT: Document, + oleacc.ROLE_SYSTEM_TABLE: Table, + oleacc.ROLE_SYSTEM_OUTLINE: Tree, + IAccessibleHandler.IA2_ROLE_EMBEDDED_OBJECT: EmbeddedObject, + "embed": EmbeddedObject, +} From 25f12ae82118f1119d0941957ec242c31988e081 Mon Sep 17 00:00:00 2001 From: James Teh Date: Wed, 28 Jul 2010 09:34:03 +1000 Subject: [PATCH 2/8] Remove unnecessary import. Change static property into class variable. --- source/NVDAObjects/IAccessible/mozilla.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/source/NVDAObjects/IAccessible/mozilla.py b/source/NVDAObjects/IAccessible/mozilla.py index 98578797e84..065d3722f76 100755 --- a/source/NVDAObjects/IAccessible/mozilla.py +++ b/source/NVDAObjects/IAccessible/mozilla.py @@ -10,7 +10,6 @@ import eventHandler import controlTypes from . import IAccessible, Dialog -import textInfos from logHandler import log class Mozilla(IAccessible): @@ -53,6 +52,7 @@ def _get_states(self): class Document(Mozilla): shouldAllowIAccessibleFocusEvent=True + value=None def _get_treeInterceptorClass(self): states=self.states @@ -61,9 +61,6 @@ def _get_treeInterceptorClass(self): return virtualBuffers.gecko_ia2.Gecko_ia2 return super(Document,self).treeInterceptorClass - def _get_value(self): - return - class ListItem(Mozilla): shouldAllowIAccessibleFocusEvent=True From 6529544e30b07b1df63f63118ca1e2795ece5300 Mon Sep 17 00:00:00 2001 From: James Teh Date: Wed, 28 Jul 2010 09:53:47 +1000 Subject: [PATCH 3/8] mozilla NVDAObjects: Since a broken focused state seems to be rather common in Mozilla, allow roles where this is known to occur to simply be included in a separate set. This eliminates a lot of pointless classes which only set shouldAllowIAccessibleFocusEvent = True. --- source/NVDAObjects/IAccessible/mozilla.py | 36 ++++++++++------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/source/NVDAObjects/IAccessible/mozilla.py b/source/NVDAObjects/IAccessible/mozilla.py index 065d3722f76..0fd51a2c912 100755 --- a/source/NVDAObjects/IAccessible/mozilla.py +++ b/source/NVDAObjects/IAccessible/mozilla.py @@ -49,9 +49,11 @@ def _get_states(self): states.add(controlTypes.STATE_CHECKABLE) return states +class BrokenFocusedState(Mozilla): + shouldAllowIAccessibleFocusEvent=True + class Document(Mozilla): - shouldAllowIAccessibleFocusEvent=True value=None def _get_treeInterceptorClass(self): @@ -63,8 +65,6 @@ def _get_treeInterceptorClass(self): class ListItem(Mozilla): - shouldAllowIAccessibleFocusEvent=True - def _get_name(self): name=super(ListItem,self)._get_name() if self.IAccessibleStates&oleacc.STATE_SYSTEM_READONLY: @@ -79,20 +79,6 @@ def _get_children(self): del children[0] return children -class ComboBox(Mozilla): - - shouldAllowIAccessibleFocusEvent=True - -class List(Mozilla): - - shouldAllowIAccessibleFocusEvent=True - -class Table(Mozilla): - shouldAllowIAccessibleFocusEvent=True - -class Tree(Mozilla): - shouldAllowIAccessibleFocusEvent=True - class EmbeddedObject(Mozilla): def _get_shouldAllowIAccessibleFocusEvent(self): @@ -111,17 +97,25 @@ def findExtraOverlayClasses(obj, clsList): cls = _IAccessibleRolesToOverlayClasses.get(iaRole) if cls: clsList.append(cls) + if iaRole in _IAccessibleRolesWithBrokenFocusedState: + clsList.append(BrokenFocusedState) clsList.append(Mozilla) #: Maps IAccessible roles to NVDAObject overlay classes. _IAccessibleRolesToOverlayClasses = { oleacc.ROLE_SYSTEM_ALERT: Dialog, - oleacc.ROLE_SYSTEM_COMBOBOX: ComboBox, - oleacc.ROLE_SYSTEM_LIST: List, oleacc.ROLE_SYSTEM_LISTITEM: ListItem, oleacc.ROLE_SYSTEM_DOCUMENT: Document, - oleacc.ROLE_SYSTEM_TABLE: Table, - oleacc.ROLE_SYSTEM_OUTLINE: Tree, IAccessibleHandler.IA2_ROLE_EMBEDDED_OBJECT: EmbeddedObject, "embed": EmbeddedObject, } + +#: Roles that mightn't set the focused state when they are focused. +_IAccessibleRolesWithBrokenFocusedState = frozenset(( + oleacc.ROLE_SYSTEM_COMBOBOX, + oleacc.ROLE_SYSTEM_LIST, + oleacc.ROLE_SYSTEM_LISTITEM, + oleacc.ROLE_SYSTEM_DOCUMENT, + oleacc.ROLE_SYSTEM_TABLE, + oleacc.ROLE_SYSTEM_OUTLINE, +)) From cee258765406f20d9a3b305c41fbd42cd309e467 Mon Sep 17 00:00:00 2001 From: James Teh Date: Wed, 28 Jul 2010 10:15:07 +1000 Subject: [PATCH 4/8] mozilla NVDAObjects: * Detect the version of Gecko using IAccessibleApplication. Cache it on the app module to avoid looking this up for every accessible. There should only ever be one version of Gecko in use in a given application, so caching on the app module (per process) is fine. * Add an overlay class for Gecko 1.9. * Gecko >= 2.0 will no longer expose position information (and thus the "Description: " prefix) in accDescription, so move this into the Gecko 1.9 overlay. * Mozilla.event_scrollingStart is only needed for Firefox 3.6, so put this in the Gecko 1.9 overlay as well. --- source/NVDAObjects/IAccessible/mozilla.py | 46 ++++++++++++++++------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/source/NVDAObjects/IAccessible/mozilla.py b/source/NVDAObjects/IAccessible/mozilla.py index 0fd51a2c912..e4ec4c5f3a6 100755 --- a/source/NVDAObjects/IAccessible/mozilla.py +++ b/source/NVDAObjects/IAccessible/mozilla.py @@ -7,6 +7,7 @@ import IAccessibleHandler import oleacc import winUser +from comtypes import IServiceProvider, COMError import eventHandler import controlTypes from . import IAccessible, Dialog @@ -21,13 +22,6 @@ def _get_beTransparentToMouse(self): return True return super(Mozilla,self).beTransparentToMouse - def _get_description(self): - rawDescription=super(Mozilla,self).description - if isinstance(rawDescription,basestring) and rawDescription.startswith('Description: '): - return rawDescription[13:] - else: - return "" - def _get_parent(self): #Special code to support Mozilla node_child_of relation (for comboboxes) res=IAccessibleHandler.accNavigate(self.IAccessibleObject,self.IAccessibleChildID,IAccessibleHandler.NAVRELATION_NODE_CHILD_OF) @@ -37,18 +31,27 @@ def _get_parent(self): return newObj return super(Mozilla,self).parent - def event_scrollingStart(self): - #Firefox 3.6 fires scrollingStart on leaf nodes which is not useful to us. - #Bounce the event up to the node's parent so that any possible virtualBuffers will detect it. - if self.role==controlTypes.ROLE_EDITABLETEXT and controlTypes.STATE_READONLY in self.states: - eventHandler.queueEvent("scrollingStart",self.parent) - def _get_states(self): states = super(Mozilla, self).states if self.IAccessibleStates & oleacc.STATE_SYSTEM_MARQUEED: states.add(controlTypes.STATE_CHECKABLE) return states +class Gecko1_9(Mozilla): + + def _get_description(self): + rawDescription=super(Mozilla,self).description + if isinstance(rawDescription,basestring) and rawDescription.startswith('Description: '): + return rawDescription[13:] + else: + return "" + + def event_scrollingStart(self): + #Firefox 3.6 fires scrollingStart on leaf nodes which is not useful to us. + #Bounce the event up to the node's parent so that any possible virtualBuffers will detect it. + if self.role==controlTypes.ROLE_EDITABLETEXT and controlTypes.STATE_READONLY in self.states: + eventHandler.queueEvent("scrollingStart",self.parent) + class BrokenFocusedState(Mozilla): shouldAllowIAccessibleFocusEvent=True @@ -89,6 +92,19 @@ def _get_shouldAllowIAccessibleFocusEvent(self): return False return super(EmbeddedObject, self).shouldAllowIAccessibleFocusEvent +def _getGeckoVersion(obj): + appMod = obj.appModule + try: + return appMod._geckoVersion + except AttributeError: + pass + try: + ver = obj.IAccessibleObject.QueryInterface(IServiceProvider).QueryService(IAccessibleHandler.IAccessibleApplication._iid_, IAccessibleHandler.IAccessibleApplication).toolkitVersion + except COMError: + return None + appMod._geckoVersion = ver + return ver + 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. @@ -99,6 +115,10 @@ def findExtraOverlayClasses(obj, clsList): clsList.append(cls) if iaRole in _IAccessibleRolesWithBrokenFocusedState: clsList.append(BrokenFocusedState) + + if _getGeckoVersion(obj).startswith("1.9"): + clsList.append(Gecko1_9) + clsList.append(Mozilla) #: Maps IAccessible roles to NVDAObject overlay classes. From 3dfb704d48fd8f8def68bd0f43fd994484e5a938 Mon Sep 17 00:00:00 2001 From: James Teh Date: Wed, 28 Jul 2010 10:30:22 +1000 Subject: [PATCH 5/8] mozilla NVDAObjects: Don't even bother if the object doesn't support IAccessible2. None of our code will work without IAccessible2; i.e. Gecko < 1.9. --- source/NVDAObjects/IAccessible/mozilla.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/source/NVDAObjects/IAccessible/mozilla.py b/source/NVDAObjects/IAccessible/mozilla.py index e4ec4c5f3a6..c902b313a53 100755 --- a/source/NVDAObjects/IAccessible/mozilla.py +++ b/source/NVDAObjects/IAccessible/mozilla.py @@ -61,7 +61,7 @@ class Document(Mozilla): def _get_treeInterceptorClass(self): states=self.states - if isinstance(self.IAccessibleObject,IAccessibleHandler.IAccessible2) and controlTypes.STATE_READONLY in states and controlTypes.STATE_BUSY not in states and self.windowClassName=="MozillaContentWindowClass": + if controlTypes.STATE_READONLY in states and controlTypes.STATE_BUSY not in states and self.windowClassName=="MozillaContentWindowClass": import virtualBuffers.gecko_ia2 return virtualBuffers.gecko_ia2.Gecko_ia2 return super(Document,self).treeInterceptorClass @@ -109,6 +109,10 @@ 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. """ + if not isinstance(obj.IAccessibleObject, IAccessibleHandler.IAccessible2): + # We require IAccessible2; i.e. Gecko >= 1.9. + return + iaRole = obj.IAccessibleRole cls = _IAccessibleRolesToOverlayClasses.get(iaRole) if cls: From 37188bdd58f33d7c18d034909988b6946aa1f7d6 Mon Sep 17 00:00:00 2001 From: James Teh Date: Wed, 28 Jul 2010 12:49:22 +1000 Subject: [PATCH 6/8] Mozilla exposes a root application accessible as the parent of all top level frames. This is non-standard; the top level accessible should be the top level window. NVDA expects the standard behaviour, so make sure this object is ignored. Fixes the issue where new windows (such as dialogs) did not silence speech when they popped up in Firefox 4. --- source/NVDAObjects/IAccessible/mozilla.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/source/NVDAObjects/IAccessible/mozilla.py b/source/NVDAObjects/IAccessible/mozilla.py index c902b313a53..9d5d1813ddd 100755 --- a/source/NVDAObjects/IAccessible/mozilla.py +++ b/source/NVDAObjects/IAccessible/mozilla.py @@ -55,6 +55,17 @@ def event_scrollingStart(self): class BrokenFocusedState(Mozilla): shouldAllowIAccessibleFocusEvent=True +class RootApplication(Mozilla): + """Mozilla exposes a root application accessible as the parent of all top level frames. + See MozillaBug:555861. + This is non-standard; the top level accessible should be the top level window. + NVDA expects the standard behaviour, so we never want to see this object. + """ + + def __nonzero__(self): + # As far as NVDA is concerned, this is a useless object. + return False + class Document(Mozilla): value=None @@ -114,7 +125,15 @@ def findExtraOverlayClasses(obj, clsList): return iaRole = obj.IAccessibleRole - cls = _IAccessibleRolesToOverlayClasses.get(iaRole) + cls = None + if iaRole == oleacc.ROLE_SYSTEM_APPLICATION: + try: + if not obj.IAccessibleObject.windowHandle: + cls = RootApplication + except COMError: + pass + if not cls: + cls = _IAccessibleRolesToOverlayClasses.get(iaRole) if cls: clsList.append(cls) if iaRole in _IAccessibleRolesWithBrokenFocusedState: From 3ad8dbfc938e63597212cd7976268615ec2379fe Mon Sep 17 00:00:00 2001 From: James Teh Date: Wed, 28 Jul 2010 14:59:10 +1000 Subject: [PATCH 7/8] Update copyright header. --- source/NVDAObjects/IAccessible/mozilla.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/NVDAObjects/IAccessible/mozilla.py b/source/NVDAObjects/IAccessible/mozilla.py index 9d5d1813ddd..169e3d196ad 100755 --- a/source/NVDAObjects/IAccessible/mozilla.py +++ b/source/NVDAObjects/IAccessible/mozilla.py @@ -1,8 +1,8 @@ #NVDAObjects/IAccessible/mozilla.py #A part of NonVisual Desktop Access (NVDA) -#Copyright (C) 2006-2007 NVDA Contributors #This file is covered by the GNU General Public License. #See the file COPYING for more details. +#Copyright (C) 2006-2010 Michael Curran , James Teh import IAccessibleHandler import oleacc From ebd3927e040cae65f84b88a7fdfe1163586eab27 Mon Sep 17 00:00:00 2001 From: James Teh Date: Wed, 28 Jul 2010 15:47:45 +1000 Subject: [PATCH 8/8] * mozilla: Add application to the list of roles for which the focused state is broken. * speech: Add application to the list of roles for which the value should not be reported. This makes the Add-ons Manager in Firefox 4 behave a little more nicely when it appears. --- source/NVDAObjects/IAccessible/mozilla.py | 1 + source/speech.py | 1 + 2 files changed, 2 insertions(+) diff --git a/source/NVDAObjects/IAccessible/mozilla.py b/source/NVDAObjects/IAccessible/mozilla.py index 169e3d196ad..283dd744ff1 100755 --- a/source/NVDAObjects/IAccessible/mozilla.py +++ b/source/NVDAObjects/IAccessible/mozilla.py @@ -159,6 +159,7 @@ def findExtraOverlayClasses(obj, clsList): oleacc.ROLE_SYSTEM_LIST, oleacc.ROLE_SYSTEM_LISTITEM, oleacc.ROLE_SYSTEM_DOCUMENT, + oleacc.ROLE_SYSTEM_APPLICATION, oleacc.ROLE_SYSTEM_TABLE, oleacc.ROLE_SYSTEM_OUTLINE, )) diff --git a/source/speech.py b/source/speech.py index d0fdf212b5f..c1f48b762e3 100755 --- a/source/speech.py +++ b/source/speech.py @@ -431,6 +431,7 @@ def speakTypedCharacters(ch): controlTypes.ROLE_RADIOBUTTON, controlTypes.ROLE_LINK, controlTypes.ROLE_MENUITEM, + controlTypes.ROLE_APPLICATION, ]) def processPositiveStates(role, states, reason, positiveStates):