#NVDAObjects/MSHTML.py
#A part of NonVisual Desktop Access (NVDA)
#Copyright (C) 2006-2015 NV Access Limited, Aleksey Sadovoy
#This file is covered by the GNU General Public License.
#See the file COPYING for more details.
import time
from comtypes import COMError
import comtypes.client
import comtypes.automation
from comtypes import IServiceProvider
import ctypes
import ctypes.wintypes
import contextlib
import winUser
import oleacc
import UIAHandler
import IAccessibleHandler
import aria
from keyboardHandler import KeyboardInputGesture
import api
import textInfos
from logHandler import log
import controlTypes
from . import IAccessible
from ..behaviors import EditableTextWithoutAutoSelectDetection, Dialog
from .. import InvalidNVDAObject
from ..window import Window
from NVDAObjects.UIA import UIA, UIATextInfo
IID_IHTMLElement=comtypes.GUID('{3050F1FF-98B5-11CF-BB82-00AA00BDCE0B}')
class UIAMSHTMLTextInfo(UIATextInfo):
# #4174: MSHTML's UIAutomation implementation does not handle the insertion point at the end of the control correcly.
# Therefore get around it by detecting when the TextInfo is instanciated on it, and ensure that expand and move do the expected thing.
_atEndOfStory=False
def __init__(self,obj,position,_rangeObj=None):
super(UIAMSHTMLTextInfo,self).__init__(obj,position,_rangeObj)
if position==textInfos.POSITION_CARET:
tempRange=self._rangeObj.clone()
tempRange.ExpandToEnclosingUnit(UIAHandler.TextUnit_Character)
if self._rangeObj.CompareEndpoints(UIAHandler.TextPatternRangeEndpoint_Start,tempRange,UIAHandler.TextPatternRangeEndpoint_Start)>0:
self._atEndOfStory=True
def copy(self):
info=super(UIAMSHTMLTextInfo,self).copy()
info._atEndOfStory=self._atEndOfStory
return info
def expand(self,unit):
if unit in (textInfos.UNIT_CHARACTER,textInfos.UNIT_WORD) and self._atEndOfStory:
return
self._atEndOfStory=False
return super(UIAMSHTMLTextInfo,self).expand(unit)
def move(self,unit,direction,endPoint=None):
if direction==0:
return 0
if self._atEndOfStory and direction<0:
direction+=1
self._atEndOfStory=False
if direction==0:
return -1
return super(UIAMSHTMLTextInfo,self).move(unit,direction,endPoint=endPoint)
class HTMLAttribCache(object):
def __init__(self,HTMLNode):
self.HTMLNode=HTMLNode
self.cache={}
def __getitem__(self,item):
try:
return self.cache[item]
except LookupError:
pass
try:
value=self.HTMLNode.getAttribute(item)
except (COMError,NameError):
value=None
self.cache[item]=value
return value
nodeNamesToNVDARoles={
"FRAME":controlTypes.ROLE_FRAME,
"IFRAME":controlTypes.ROLE_FRAME,
"FRAMESET":controlTypes.ROLE_DOCUMENT,
"BODY":controlTypes.ROLE_DOCUMENT,
"TH":controlTypes.ROLE_TABLECELL,
"IMG":controlTypes.ROLE_GRAPHIC,
"A":controlTypes.ROLE_LINK,
"LABEL":controlTypes.ROLE_LABEL,
"#text":controlTypes.ROLE_STATICTEXT,
"#TEXT":controlTypes.ROLE_STATICTEXT,
"H1":controlTypes.ROLE_HEADING,
"H2":controlTypes.ROLE_HEADING,
"H3":controlTypes.ROLE_HEADING,
"H4":controlTypes.ROLE_HEADING,
"H5":controlTypes.ROLE_HEADING,
"H6":controlTypes.ROLE_HEADING,
"DIV":controlTypes.ROLE_SECTION,
"P":controlTypes.ROLE_PARAGRAPH,
"FORM":controlTypes.ROLE_FORM,
"UL":controlTypes.ROLE_LIST,
"OL":controlTypes.ROLE_LIST,
"DL":controlTypes.ROLE_LIST,
"LI":controlTypes.ROLE_LISTITEM,
"DD":controlTypes.ROLE_LISTITEM,
"DT":controlTypes.ROLE_LISTITEM,
"TR":controlTypes.ROLE_TABLEROW,
"THEAD":controlTypes.ROLE_TABLEHEADER,
"TBODY":controlTypes.ROLE_TABLEBODY,
"HR":controlTypes.ROLE_SEPARATOR,
"OBJECT":controlTypes.ROLE_EMBEDDEDOBJECT,
"APPLET":controlTypes.ROLE_EMBEDDEDOBJECT,
"EMBED":controlTypes.ROLE_EMBEDDEDOBJECT,
"FIELDSET":controlTypes.ROLE_GROUPING,
"OPTION":controlTypes.ROLE_LISTITEM,
"BLOCKQUOTE":controlTypes.ROLE_BLOCKQUOTE,
"MATH":controlTypes.ROLE_MATH,
"NAV":controlTypes.ROLE_SECTION,
"SECTION":controlTypes.ROLE_SECTION,
"ARTICLE":controlTypes.ROLE_DOCUMENT,
}
def getZoomFactorsFromHTMLDocument(HTMLDocument):
try:
scr=HTMLDocument.parentWindow.screen
except (COMError,NameError,AttributeError):
log.debugWarning("no screen object for MSHTML document")
return (1,1)
try:
devX=float(scr.deviceXDPI)
devY=float(scr.deviceYDPI)
logX=float(scr.logicalXDPI)
logY=float(scr.logicalYDPI)
except (COMError,NameError,AttributeError,TypeError):
log.debugWarning("unable to fetch DPI factors")
return (1,1)
return (devX/logX,devY/logY)
def IAccessibleFromHTMLNode(HTMLNode):
try:
s=HTMLNode.QueryInterface(IServiceProvider)
return s.QueryService(oleacc.IAccessible._iid_,oleacc.IAccessible)
except COMError:
raise NotImplementedError
def HTMLNodeFromIAccessible(IAccessibleObject):
try:
s=IAccessibleObject.QueryInterface(IServiceProvider)
i=s.QueryService(IID_IHTMLElement,comtypes.automation.IDispatch)
if not i:
# QueryService should fail if IHTMLElement is not supported, but some applications misbehave and return a null COM pointer.
raise NotImplementedError
return comtypes.client.dynamic.Dispatch(i)
except COMError:
raise NotImplementedError
def locateHTMLElementByID(document,ID):
try:
elements=document.getElementsByName(ID)
if elements is not None:
element=elements.item(0)
else: #probably IE 10 in standards mode (#3151)
try:
element=document.all.item(ID)
except:
element=None
except COMError as e:
log.debugWarning("document.getElementsByName failed with COMError %s"%e)
element=None
if element:
return element
try:
nodeName=document.body.nodeName
except COMError as e:
log.debugWarning("document.body.nodeName failed with COMError %s"%e)
return None
if nodeName:
nodeName=nodeName.upper()
if nodeName=="FRAMESET":
tag="frame"
else:
tag="iframe"
try:
frames=document.getElementsByTagName(tag)
except COMError as e:
log.debugWarning("document.getElementsByTagName failed with COMError %s"%e)
return None
if not frames: #frames can be None in IE 10
return None
for frame in frames:
childElement=getChildHTMLNodeFromFrame(frame)
if not childElement:
continue
childElement=locateHTMLElementByID(childElement.document,ID)
if not childElement: continue
return childElement
def getChildHTMLNodeFromFrame(frame):
try:
pacc=IAccessibleFromHTMLNode(frame)
except NotImplementedError:
# #1569: It's not possible to get an IAccessible from frames marked with an ARIA role of presentation.
# In this case, just skip this frame.
return
res=IAccessibleHandler.accChild(pacc,1)
if not res: return
return HTMLNodeFromIAccessible(res[0])
class MSHTMLTextInfo(textInfos.TextInfo):
def _expandToLine(self,textRange):
#Try to calculate the line range by finding screen coordinates and using moveToPoint
parent=textRange.parentElement()
if not parent.isMultiline: #fastest solution for single line edits ()
textRange.expand("textEdit")
return
parentRect=parent.getBoundingClientRect()
#This can be simplified when comtypes is fixed
lineTop=comtypes.client.dynamic._Dispatch(textRange._comobj).offsetTop
lineLeft=parentRect.left+parent.clientLeft
#editable documents have a different right most boundary to