#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