MSHTML.py 36.4 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110
#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 (<input type="text">)
			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 <textarea> elements.
		if self.obj.HTMLNode.document.body.isContentEditable:
			lineRight=parentRect.right 
		else:
			lineRight=parentRect.left+parent.clientWidth
		tempRange=textRange.duplicate()
		try:
			tempRange.moveToPoint(lineLeft,lineTop)
			textRange.setEndPoint("startToStart",tempRange)
			tempRange.moveToPoint(lineRight,lineTop)
			textRange.setEndPoint("endToStart",tempRange)
			return
		except COMError:
			pass
		#MoveToPoint fails on Some (possibly floated) textArea elements.
		#Instead use the physical selection, by moving it with key presses, to work out the line.
		#This approach is somewhat slower, and less accurate.
		with self.obj.suspendCaretEvents():
			selObj=parent.document.selection
			oldSelRange=selObj.createRange().duplicate()
			# #1566: Calling textRange.select() sometimes throws focus onto the document,
			# so create a new range from the selection and move the selection using that.
			selObj.createRange().moveToBookmark(textRange.getBookmark())
			KeyboardInputGesture.fromName("home").send()
			api.processPendingEvents(False)
			newSelStartMark=selObj.createRange().getBookmark()
			KeyboardInputGesture.fromName("end").send()
			api.processPendingEvents(False)
			newSelEndMark=selObj.createRange().getBookmark()
			tempRange.moveToBookmark(newSelStartMark)
			textRange.setEndPoint("startToStart",tempRange)
			tempRange.moveToBookmark(newSelEndMark)
			textRange.setEndPoint("endToStart",tempRange)
			oldSelRange.select()

	def __init__(self,obj,position,_rangeObj=None):
		super(MSHTMLTextInfo,self).__init__(obj,position)
		if _rangeObj:
			self._rangeObj=_rangeObj.duplicate()
			return
		try:
			editableBody=self.obj.HTMLNodeName=="BODY" and self.obj.isContentEditable
		except:
			editableBody=False
		if editableBody:
			self._rangeObj=self.obj.HTMLNode.document.selection.createRange()
		else:
			self._rangeObj=self.obj.HTMLNode.createTextRange()
		if position in (textInfos.POSITION_CARET,textInfos.POSITION_SELECTION):
			try:
				activeElement=self.obj.HTMLNode.document.activeElement
			except COMError:
				activeElement=None
			if not activeElement or self.obj.HTMLNode.uniqueNumber!=activeElement.uniqueNumber:
				raise RuntimeError("Only works with currently selected element")
			if not editableBody:
				mark=self.obj.HTMLNode.document.selection.createRange().GetBookmark()
				self._rangeObj.MoveToBookmark(mark)
			if position==textInfos.POSITION_CARET:
				self._rangeObj.collapse()
			return
		if position==textInfos.POSITION_FIRST:
			self._rangeObj.collapse()
		elif position==textInfos.POSITION_LAST:
			self._rangeObj.expand("textedit")
			self.collapse(True)
			self._rangeObj.move("character",-1)
		elif position==textInfos.POSITION_ALL:
			self._rangeObj.expand("textedit")
		elif isinstance(position,textInfos.Bookmark):
			if position.infoClass==self.__class__:
				self._rangeObj.moveToBookmark(position.data)
			else:
				raise TypeError("Bookmark was for %s type, not for %s type"%(position.infoClass.__name__,self.__class__.__name__))
		else:
			raise NotImplementedError("position: %s"%position)

	def expand(self,unit):
		if unit==textInfos.UNIT_PARAGRAPH:
			unit=textInfos.UNIT_LINE
		if unit==textInfos.UNIT_LINE and self.basePosition not in [textInfos.POSITION_SELECTION,textInfos.POSITION_CARET]:
			unit=textInfos.UNIT_SENTENCE
		if unit==textInfos.UNIT_READINGCHUNK:
			unit=textInfos.UNIT_SENTENCE
		if unit==textInfos.UNIT_CHARACTER:
			self._rangeObj.expand("character")
		elif unit==textInfos.UNIT_WORD:
			#Expand to word at the start of a control is broken in MSHTML
			#Unless we expand to character first.
			self._rangeObj.expand("character")
			self._rangeObj.expand("word")
		elif unit==textInfos.UNIT_SENTENCE:
			self._rangeObj.expand("sentence")
		elif unit==textInfos.UNIT_LINE:
			self._expandToLine(self._rangeObj)
		elif unit==textInfos.UNIT_STORY:
			self._rangeObj.expand("textedit")
		else:
			raise NotImplementedError("unit: %s"%unit)

	def _get_isCollapsed(self):
		if self._rangeObj.compareEndPoints("startToEnd",self._rangeObj)==0:
			return True
		else:
			return False

	def collapse(self,end=False):
		self._rangeObj.collapse(not end)

	def copy(self):
		return self.__class__(self.obj,None,_rangeObj=self._rangeObj.duplicate())

	def compareEndPoints(self,other,which):
		return self._rangeObj.compareEndPoints(which,other._rangeObj)

	def setEndPoint(self,other,which):
		self._rangeObj.setEndPoint(which,other._rangeObj)

	def _get_text(self):
		text=self._rangeObj.text
		if not text:
			text=u""
		if controlTypes.STATE_PROTECTED in self.obj.states:
			text=u'*'*len(text)
		return text

	def move(self,unit,direction, endPoint=None):
		if unit in [textInfos.UNIT_READINGCHUNK,textInfos.UNIT_LINE]:
			unit=textInfos.UNIT_SENTENCE
		if unit==textInfos.UNIT_STORY:
			unit="textedit"
		if endPoint=="start":
			moveFunc=self._rangeObj.moveStart
		elif endPoint=="end":
			moveFunc=self._rangeObj.moveEnd
		else:
			moveFunc=self._rangeObj.move
			if direction<0:
				# #1605: If at the end of a line, moving back seems to land on a blank unit,
				# which breaks backspacing.
				# Expanding first seems to fix this.
				self._rangeObj.expand("character")
		res=moveFunc(unit,direction)
		return res

	def updateCaret(self):
		self._rangeObj.select()

	def updateSelection(self):
		self._rangeObj.select()

	def _get_bookmark(self):
		return textInfos.Bookmark(self.__class__,self._rangeObj.getBookmark())

class MSHTML(IAccessible):

	def _get__UIAControl(self):
		if UIAHandler.handler and self.role==controlTypes.ROLE_EDITABLETEXT and controlTypes.STATE_FOCUSED in self.states:
			e=UIAHandler.handler.clientObject.getFocusedElementBuildCache(UIAHandler.handler.baseCacheRequest)
			obj=UIA(UIAElement=e)
			if isinstance(obj,EditableTextWithoutAutoSelectDetection):
				obj.parent=self.parent
				obj.TextInfo=UIAMSHTMLTextInfo
				self._UIAControl=obj
				return obj

	def makeTextInfo(self,position):
		if self._UIAControl:
			return self._UIAControl.makeTextInfo(position)
		return super(MSHTML,self).makeTextInfo(position)

	HTMLNodeNameNavSkipList=['#comment','SCRIPT','HEAD','HTML','PARAM','STYLE']
	HTMLNodeNameEmbedList=['OBJECT','EMBED','APPLET','FRAME','IFRAME']

	_ignoreCaretEvents=False #:Set to true when moving the caret to calculate lines, event_caret will be disabled.

	@contextlib.contextmanager
	def suspendCaretEvents(self):
		"""Suspends caret events while you need to move the caret to calculate things."""
		oldVal=self._ignoreCaretEvents
		self._ignoreCaretEvents=True
		yield oldVal
		self._ignoreCaretEvents=oldVal

	def event_caret(self):
		if self._ignoreCaretEvents: return
		if self.TextInfo is not MSHTMLTextInfo and not self._UIAControl:
			return
		try:
			newCaretBookmark=self.makeTextInfo(textInfos.POSITION_CARET).bookmark
		except RuntimeError: #caret events can be fired on the object (focus) when  its not the real MSHTML selection 
			newCaretBookmark=None
		if not newCaretBookmark or newCaretBookmark==getattr(self,'_oldCaretBookmark',None):
			return
		self._oldCaretBookmark=newCaretBookmark
		return super(MSHTML,self).event_caret()

	@classmethod
	def kwargsFromSuper(cls,kwargs,relation=None):
		IAccessibleObject=kwargs['IAccessibleObject']
		#MSHTML should not be used for MSAA child elements.
		#However, objectFromPoint can hit an MSAA child element but we still should try MSHTML's own elementFromPoint even in this case.
		if kwargs.get('IAccessibleChildID') and not isinstance(relation,tuple):
			return False
		HTMLNode=None
		try:
			HTMLNode=HTMLNodeFromIAccessible(IAccessibleObject)
		except NotImplementedError:
			pass
		if not HTMLNode:
			return False

		if relation=="focus":
			# #4045: we must recurse into frames ourselves when fetching the active element of a document. 
			while True:
				try:
					HTMLNode=HTMLNode.document.activeElement
				except:
					log.exception("Error getting activeElement")
					break
				nodeName=HTMLNode.nodeName or ""
				if nodeName.lower() not in ("frame","iframe"):
					# The IAccessibleObject may be incorrect now, so let the constructor recalculate it.
					del kwargs['IAccessibleObject']
					break
				childElement=getChildHTMLNodeFromFrame(HTMLNode)
				if not childElement:
					break
				HTMLNode=childElement
			
		elif isinstance(relation,tuple):
			windowHandle=kwargs.get('windowHandle')
			p=ctypes.wintypes.POINT(x=relation[0],y=relation[1])
			ctypes.windll.user32.ScreenToClient(windowHandle,ctypes.byref(p))
			# #3494: MSHTML's internal coordinates are always at a hardcoded DPI (usually 96) no matter the system DPI or zoom level.
			xFactor,yFactor=getZoomFactorsFromHTMLDocument(HTMLNode.document)
			try:
				HTMLNode=HTMLNode.document.elementFromPoint(p.x/xFactor,p.y/yFactor)
			except:
				HTMLNode=None
			if not HTMLNode:
				log.debugWarning("Error getting HTMLNode with elementFromPoint")
				return False
			del kwargs['IAccessibleObject']
			del kwargs['IAccessibleChildID']
		kwargs['HTMLNode']=HTMLNode
		return True

	def findOverlayClasses(self,clsList):
		if self.TextInfo == MSHTMLTextInfo or self._UIAControl:
			clsList.append(EditableTextWithoutAutoSelectDetection)
		nodeName = self.HTMLNodeName
		if nodeName:
			if nodeName=="SELECT" and self.windowStyle&winUser.WS_POPUP:
				clsList.append(PopupList)
			elif nodeNamesToNVDARoles.get(nodeName) == controlTypes.ROLE_DOCUMENT:
				try:
					isBodyNode=self.HTMLNodeUniqueNumber==self.HTMLNode.document.body.uniqueNumber
				except (COMError,NameError):
					isBodyNode=False
				if isBodyNode:
					clsList.append(Body)
			elif nodeName == "OBJECT":
				clsList.append(Object)
			elif nodeName=="FIELDSET":
				clsList.append(Fieldset)
			elif nodeName=="MATH":
				clsList.append(Math)
		clsList.append(MSHTML)
		if not self.HTMLNodeHasAncestorIAccessible:
			# The IAccessibleObject is for this node (not an ancestor), so IAccessible overlay classes are relevant.
			super(MSHTML,self).findOverlayClasses(clsList)
			if self.IAccessibleRole == oleacc.ROLE_SYSTEM_DIALOG:
				ariaRoles = self.HTMLAttributes["role"].split(" ")
				if "dialog" in ariaRoles:
					# #2390: Don't try to calculate text for ARIA dialogs.
					try:
						clsList.remove(Dialog)
					except ValueError:
						pass

	def _get_treeInterceptorClass(self):
		if self.role in (controlTypes.ROLE_DOCUMENT, controlTypes.ROLE_APPLICATION, controlTypes.ROLE_DIALOG) and not self.isContentEditable:
			import virtualBuffers.MSHTML
			return virtualBuffers.MSHTML.MSHTML
		return super(MSHTML,self).treeInterceptorClass

	def _get_HTMLAttributes(self):
		return HTMLAttribCache(self.HTMLNode)

	def __init__(self,HTMLNode=None,IAccessibleObject=None,IAccessibleChildID=None,**kwargs):
		self.HTMLNodeHasAncestorIAccessible=False
		if not IAccessibleObject:
			# Find an IAccessible for HTMLNode and determine whether it is for an ancestor.
			tempNode=HTMLNode
			while tempNode:
				try:
					IAccessibleObject=IAccessibleFromHTMLNode(tempNode)
				except NotImplementedError:
					IAccessibleObject=None
				if IAccessibleObject:
					IAccessibleChildID=0
					if tempNode is not HTMLNode:
						self.HTMLNodeHasAncestorIAccessible=True
					break
				try:
					tempNode=tempNode.parentNode
				except COMError:
					tempNode=None

		if not IAccessibleObject:
			raise InvalidNVDAObject("Couldn't get IAccessible, probably dead object")

		super(MSHTML,self).__init__(IAccessibleObject=IAccessibleObject,IAccessibleChildID=IAccessibleChildID,**kwargs)
		self.HTMLNode=HTMLNode

		#object and embed nodes give back an incorrect IAccessible via queryService, so we must treet it as an ancestor IAccessible
		if self.HTMLNodeName in ("OBJECT","EMBED"):
			self.HTMLNodeHasAncestorIAccessible=True

	def _get_zoomFactors(self):
		return getZoomFactorsFromHTMLDocument(self.HTMLNode.document)

	def _get_location(self):
		if self.HTMLNodeName and not self.HTMLNodeName.startswith('#'):
			try:
				r=self.HTMLNode.getBoundingClientRect()
			except COMError:
				return None
			# #3494: MSHTML's internal coordinates are always at a hardcoded DPI (usually 96) no matter the system DPI or zoom level.
			xFactor,yFactor=self.zoomFactors
			left=int(r.left*xFactor)
			top=int(r.top*yFactor)
			right=int(r.right*xFactor)
			bottom=int(r.bottom*yFactor)
			width=right-left
			height=bottom-top
			p=ctypes.wintypes.POINT(x=left,y=top)
			ctypes.windll.user32.ClientToScreen(self.windowHandle,ctypes.byref(p))
			return (p.x,p.y,width,height)
		return None

	def _get_TextInfo(self):
		if not hasattr(self,'_HTMLNodeSupportsTextRanges'):
			try:
				self.HTMLNode.createTextRange()
				self._HTMLNodeSupportsTextRanges=True
			except (COMError,NameError):
				self._HTMLNodeSupportsTextRanges=False
		if self._HTMLNodeSupportsTextRanges:
			return MSHTMLTextInfo
		return super(MSHTML,self).TextInfo

	def isDuplicateIAccessibleEvent(self,obj):
		if not super(MSHTML,self).isDuplicateIAccessibleEvent(obj):
			return False
		#MSHTML winEvents can't be trusted for uniqueness, so just do normal object comparison.
		return self==obj


	def _isEqual(self, other):
		if self.HTMLNode and other.HTMLNode:
			try:
				return self.windowHandle == other.windowHandle and self.HTMLNodeUniqueNumber == other.HTMLNodeUniqueNumber
			except (COMError,NameError):
				pass
		return super(MSHTML, self)._isEqual(other)

	def _get_presentationType(self):
		presType=super(MSHTML,self).presentationType
		if presType==self.presType_content and self.HTMLAttributes['role']=="presentation":
			presType=self.presType_layout
		if presType==self.presType_content and self.role in (controlTypes.ROLE_TABLECELL,controlTypes.ROLE_TABLEROW,controlTypes.ROLE_TABLE,controlTypes.ROLE_TABLEBODY):
			ti=self.treeInterceptor
			try:
				if ti and ti.isReady and ti.isNVDAObjectPartOfLayoutTable(self):
					presType=self.presType_layout
			except LookupError:
				pass
		return presType


	def _get_shouldAllowIAccessibleFocusEvent(self):
		ariaRole=self.HTMLAttributes['aria-role']
		if ariaRole=="gridcell":
			return True
		return super(MSHTML,self).shouldAllowIAccessibleFocusEvent

	def _get_name(self):
		ariaLabelledBy=self.HTMLAttributes['aria-labelledBy']
		if ariaLabelledBy:
			try:
				labelNode=self.HTMLNode.document.getElementById(ariaLabelledBy)
			except (COMError,NameError):
				labelNode=None
			if labelNode:
				try:
					return labelNode.innerText
				except (COMError,NameError):
					pass
		ariaLabel=self.HTMLAttributes['aria-label']
		if ariaLabel:
			return ariaLabel
		if self.IAccessibleRole==oleacc.ROLE_SYSTEM_TABLE:
			summary=self.HTMLAttributes['summary']
			if summary:
				return summary
		if (
			self.HTMLNodeHasAncestorIAccessible or
			#IE inappropriately generates the name from descendants on some controls
			self.IAccessibleRole in (oleacc.ROLE_SYSTEM_MENUBAR,oleacc.ROLE_SYSTEM_TOOLBAR,oleacc.ROLE_SYSTEM_LIST,oleacc.ROLE_SYSTEM_TABLE,oleacc.ROLE_SYSTEM_DOCUMENT,oleacc.ROLE_SYSTEM_DIALOG) or
			#Adding an ARIA landmark or unknown role to a DIV or NAV node makes an IAccessible with role_system_grouping and a name calculated from descendants.
			# This name should also be ignored, but check NVDA's role, not accRole as its possible that NVDA chose a better role
			# E.g. row (#2780)
			(self.HTMLNodeName in ("DIV","NAV") and self.role==controlTypes.ROLE_GROUPING)
		):
			title=self.HTMLAttributes['title']
			# #2121: MSHTML sometimes returns a node for the title attribute.
			# This doesn't make any sense, so ignore it.
			if title and isinstance(title,basestring):
				return title
			return ""
		return super(MSHTML,self).name

	def _get_value(self):
		if self.HTMLNodeHasAncestorIAccessible:
			try:
				value=self.HTMLNode.data
			except (COMError,NameError):
				value=""
			return value
		IARole=self.IAccessibleRole
		# value is not useful on certain nodes that just expose a URL, or they  have other ways of getting their content (#4976 - editble combos).
		if IARole in (oleacc.ROLE_SYSTEM_PANE,oleacc.ROLE_SYSTEM_TEXT) or (IARole==oleacc.ROLE_SYSTEM_COMBOBOX and controlTypes.STATE_EDITABLE in self.states):
			return ""
		else:
			return super(MSHTML,self).value

	def _get_description(self):
		ariaDescribedBy=self.HTMLAttributes['aria-describedBy']
		if ariaDescribedBy:
			try:
				descNode=self.HTMLNode.document.getElementById(ariaDescribedBy)
			except (COMError,NameError):
				descNode=None
			if descNode:
				try:
					return descNode.innerText
				except (COMError,NameError):
					pass
		if self.HTMLNodeHasAncestorIAccessible:
			return ""
		return super(MSHTML,self).description

	def _get_basicText(self):
		if self.HTMLNode and not self.HTMLNodeName=="SELECT":
			try:
				return self.HTMLNode.data or ""
			except (COMError, AttributeError, NameError):
				pass
			try:
				return self.HTMLNode.innerText or super(MSHTML,self).basicText
			except (COMError, AttributeError, NameError):
				pass
		return super(MSHTML,self).basicText

	def _get_role(self):
		if self.HTMLNode:
			ariaRole=self.HTMLAttributes['role']
			if ariaRole:
				role=aria.ariaRolesToNVDARoles.get(ariaRole)
				if role:
					return role
			nodeName=self.HTMLNodeName
			if nodeName:
				if nodeName in ("OBJECT","EMBED","APPLET"):
					return controlTypes.ROLE_EMBEDDEDOBJECT
				if self.HTMLNodeHasAncestorIAccessible or nodeName in ("BODY","FRAMESET","FRAME","IFRAME","LABEL","NAV","SECTION","ARTICLE"):
					return nodeNamesToNVDARoles.get(nodeName,controlTypes.ROLE_SECTION)
		if self.IAccessibleChildID>0:
			states=super(MSHTML,self).states
			if controlTypes.STATE_LINKED in states:
				return controlTypes.ROLE_LINK
		role=super(MSHTML,self).role
		#IE uses a MSAA role of ROLE_SYSTEM_TEXT with no readonly state for unsupported or future tags with an explicit ARIA role.
		#If this is the case, force the role to staticText so this is not confused as a real edit field.
		if role==controlTypes.ROLE_EDITABLETEXT and ariaRole and ariaRole!="textbox":
			role=controlTypes.ROLE_STATICTEXT
		return role

	def _get_states(self):
		if not self.HTMLNodeHasAncestorIAccessible:
			states=super(MSHTML,self).states
		else:
			states=set()
		ariaSort=self.HTMLAttributes['aria-sort']
		state=aria.ariaSortValuesToNVDAStates.get(ariaSort)
		if state is not None:
			states.add(state)
		ariaRequired=self.HTMLAttributes['aria-required']
		if ariaRequired=="true":
			states.add(controlTypes.STATE_REQUIRED)
		ariaSelected=self.HTMLAttributes['aria-selected']
		if ariaSelected=="true":
			states.add(controlTypes.STATE_SELECTED)
		elif ariaSelected=="false":
			states.discard(controlTypes.STATE_SELECTED)
		ariaExpanded=self.HTMLAttributes['aria-expanded']
		if ariaExpanded=="true":
			states.add(controlTypes.STATE_EXPANDED)
		elif ariaExpanded=="false":
			states.add(controlTypes.STATE_COLLAPSED)
		ariaInvalid=self.HTMLAttributes['aria-invalid']
		if ariaInvalid=="true":
			states.add(controlTypes.STATE_INVALID_ENTRY)
		ariaGrabbed=self.HTMLAttributes['aria-grabbed']
		if ariaGrabbed=="true":
			states.add(controlTypes.STATE_DRAGGING)
		elif ariaGrabbed=="false":
			states.add(controlTypes.STATE_DRAGGABLE)
		ariaDropeffect=self.HTMLAttributes['aria-dropeffect']
		if ariaDropeffect and ariaDropeffect!="none":
			states.add(controlTypes.STATE_DROPTARGET)
		if self.HTMLAttributes["aria-hidden"]=="true":
			states.add(controlTypes.STATE_INVISIBLE)
		if self.isContentEditable:
			states.add(controlTypes.STATE_EDITABLE)
			states.discard(controlTypes.STATE_READONLY)
		nodeName=self.HTMLNodeName
		if nodeName=="TEXTAREA":
			states.add(controlTypes.STATE_MULTILINE)
		# #4667: Internet Explorer 11 correctly fires focus events for aria-activeDescendant, but fails to set the focused state.
		# Therefore check aria-activeDescendant manually and set these states if this is the active descendant. 
		try:
			activeElement=self.HTMLNode.document.activeElement
		except COMError:
			activeElement=None
		if activeElement:
			activeID=activeElement.getAttribute('aria-activedescendant')
			if activeID and activeID==self.HTMLNode.ID:
				states.add(controlTypes.STATE_FOCUSABLE)
				states.add(controlTypes.STATE_FOCUSED)
		return states

	def _get_isContentEditable(self):
		try:
			return bool(self.HTMLNode.isContentEditable)
		except:
			return False

	def _get_parent(self):
		if self.HTMLNode:
			try:
				parentNode=self.HTMLNode.parentElement
			except (COMError,NameError):
				parentNode=None
			if not parentNode and self.HTMLNodeHasAncestorIAccessible:
				try:
					parentNode=self.HTMLNode.parentNode
				except (COMError,NameError):
					parentNode=None
			if parentNode:
				obj=MSHTML(HTMLNode=parentNode)
				if obj and obj.HTMLNodeName not in self.HTMLNodeNameNavSkipList:
					return obj
		return super(MSHTML,self).parent

	def _get_previous(self):
		if self.HTMLNode:
			try:
				previousNode=self.HTMLNode.previousSibling
			except COMError:
				previousNode=None
			if not previousNode:
				return None
			obj=MSHTML(HTMLNode=previousNode)
			if obj and obj.HTMLNodeName in self.HTMLNodeNameNavSkipList:
				obj=obj.previous
			return obj
		return super(MSHTML,self).previous

	def _get_next(self):
		if self.HTMLNode:
			try:
				nextNode=self.HTMLNode.nextSibling
			except COMError:
				nextNode=None
			if not nextNode:
				return None
			obj=MSHTML(HTMLNode=nextNode)
			if obj and obj.HTMLNodeName in self.HTMLNodeNameNavSkipList:
				obj=obj.next
			return obj
		return super(MSHTML,self).next

	def _get_firstChild(self):
		if self.HTMLNode:
			if self.HTMLNodeName in ("FRAME","IFRAME"):
				return super(MSHTML,self).firstChild
			try:
				childNode=self.HTMLNode.firstChild
			except COMError:
				childNode=None
			if not childNode:
				return None
			obj=MSHTML(HTMLNode=childNode)
			if obj and obj.HTMLNodeName in self.HTMLNodeNameNavSkipList:
				return obj.next
			return obj
		if self.HTMLNodeHasAncestorIAccessible:
			return None
		return super(MSHTML,self).firstChild

	def _get_lastChild(self):
		if self.HTMLNode:
			if self.HTMLNodeName in ("FRAME","IFRAME"):
				return super(MSHTML,self).lastChild
			try:
				childNode=self.HTMLNode.lastChild
			except COMError:
				childNode=None
			if not childNode:
				return None
			obj=MSHTML(HTMLNode=childNode)
			if obj and obj.HTMLNodeName in self.HTMLNodeNameNavSkipList:
				return obj.previous
			return obj
		if self.HTMLNodeHasAncestorIAccessible:
			return None
		return super(MSHTML,self).lastChild

	def _get_columnNumber(self):
		if not self.role==controlTypes.ROLE_TABLECELL or not self.HTMLNode:
			raise NotImplementedError
		try:
			return self.HTMLNode.cellIndex+1
		except:
			raise NotImplementedError

	def _get_rowNumber(self):
		if not self.role==controlTypes.ROLE_TABLECELL or not self.HTMLNode:
			raise NotImplementedError
		HTMLNode=self.HTMLNode
		while HTMLNode:
			try:
				return HTMLNode.rowIndex+1
			except:
				pass
			HTMLNode=HTMLNode.parentNode
		raise NotImplementedError

	def _get_rowCount(self):
		if self.role!=controlTypes.ROLE_TABLE or not self.HTMLNode:
			raise NotImplementedError
		try:
			return len([x for x in self.HTMLNode.rows])
		except:
			raise NotImplementedError

	def scrollIntoView(self):
		if not self.HTMLNode:
			return
		try:
			self.HTMLNode.scrollInToView()
		except (COMError,NameError):
			pass

	def doAction(self, index=None):
		if self.HTMLNode:
			try:
				self.HTMLNode.click()
				return
			except COMError:
				return
			except NameError:
				pass
		super(MSHTML,self).doAction(index=index)

	def _get_isFocusable(self):
		nodeName = self.HTMLNodeName
		attribs = self.HTMLAttributes
		if nodeName in ("BUTTON", "INPUT", "ISINDEX", "SELECT", "TEXTAREA"):
			return not attribs["disabled"]
		if nodeName == "A":
			return bool(attribs["href"])
		if nodeName in ( "BODY", "OBJECT", "APPLET"):
			return True
		if nodeName=="IMG" and not self.HTMLNodeHasAncestorIAccessible and self.IAccessibleRole==oleacc.ROLE_SYSTEM_GRAPHIC and self.IAccessibleStates&oleacc.STATE_SYSTEM_FOCUSABLE:
			return True
		return self.HTMLNode.hasAttribute("tabindex")

	def setFocus(self):
		if self.HTMLNodeHasAncestorIAccessible:
			try:
				self.HTMLNode.focus()
			except (COMError, AttributeError, NameError):
				pass
			return
		super(MSHTML,self).setFocus()

	def _get_table(self):
		if self.role not in (controlTypes.ROLE_TABLECELL,controlTypes.ROLE_TABLEROW) or not self.HTMLNode:
			raise NotImplementedError
		HTMLNode=self.HTMLNode
		while HTMLNode:
			nodeName=HTMLNode.nodeName
			if nodeName:
				nodeName=nodeName.upper()
			if nodeName=="TABLE": return MSHTML(HTMLNode=HTMLNode)
			HTMLNode=HTMLNode.parentNode
		raise NotImplementedError

	def _get_HTMLNodeUniqueNumber(self):
		if not hasattr(self,'_HTMLNodeUniqueNumber'):
			try:
				self._HTMLNodeUniqueNumber=self.HTMLNode.uniqueNumber
			except COMError:
				return None
		return self._HTMLNodeUniqueNumber

	def _get_HTMLNodeName(self):
		if not hasattr(self,'_HTMLNodeName'):
			try:
				self._HTMLNodeName=self.HTMLNode.nodeName
			except (COMError,NameError):
				return ""
			if self._HTMLNodeName:
				self._HTMLNodeName=self._HTMLNodeName.upper()
		return self._HTMLNodeName

	def _get_devInfo(self):
		info = super(MSHTML, self).devInfo
		info.append("MSHTML node has ancestor IAccessible: %r" % self.HTMLNodeHasAncestorIAccessible)
		htmlNode = self.HTMLNode
		try:
			ret = repr(htmlNode.nodeName)
		except Exception as e:
			ret = "exception: %s" % e
		info.append("MSHTML nodeName: %s" % ret)
		return info

	def _get_language(self):
		ti = self.treeInterceptor
		if not ti:
			# This is too slow to calculate without a buffer.
			# This case should be pretty rare anyway.
			return None
		try:
			return ti.getControlFieldForNVDAObject(self)["language"]
		except LookupError:
			return None

class V6ComboBox(IAccessible):
	"""The object which receives value change events for combo boxes in MSHTML/IE 6.
	"""

	def event_valueChange(self):
		focus = api.getFocusObject()
		if controlTypes.STATE_FOCUSED not in self.states or focus.role != controlTypes.ROLE_COMBOBOX:
			# This combo box is not focused.
			return super(V6ComboBox, self).event_valueChange()
		# This combo box is focused. However, the value change is not fired on the real focus object.
		# Therefore, redirect this event to the real focus object.
		focus.event_valueChange()

class Fieldset(MSHTML):

	def _get_name(self):
		try:
			child=self.HTMLNode.children[0]
		except (COMError,NameError):
			child=None
		if not child:
			return super(Fieldset,self).name
		try:
			nodeName=child.nodeName
		except (COMError,NameError):
			return super(Fieldset,self).name
		if nodeName:
			nodeName=nodeName.upper()
		if nodeName!="LEGEND":
			return super(Fieldset,self).name
		try:
			text=child.innerText
		except (COMError,NameError):
			return super(Fieldset,self).name
		return text

class Body(MSHTML):

	def _get_parent(self):
		# The parent of the body accessible may be an irrelevant client object (description: MSAAHTML Registered Handler).
		# This object isn't returned when requesting OBJID_CLIENT, nor is it returned as a child of its parent.
		# Therefore, eliminate it from the ancestry completely.
		# However it is possible that this body is a child document of a parent frame. In this case don't skip it.
		parent = super(Body, self).parent
		if parent and not isinstance(parent,MSHTML):
			return parent.parent
		else:
			return parent

	def _get_shouldAllowIAccessibleFocusEvent(self):
		# We must override this because we override parent to skip the MSAAHTML Registered Handler client,
		# which might have the focused state.
		if controlTypes.STATE_FOCUSED in self.states:
			return True
		parent = super(Body, self).parent
		if not parent:
			return False
		return parent.shouldAllowIAccessibleFocusEvent

class Object(MSHTML):

	def _get_firstChild(self):
		# We want firstChild to return the accessible for the embedded object.
		from objidl import IOleWindow
		# Try to get the window for the embedded object.
		try:
			window = self.HTMLNode.object.QueryInterface(IOleWindow).GetWindow()
		except COMError:
			window = None
		if not window or window == self.windowHandle:
			return super(Object, self).firstChild
		return Window(windowHandle=window)

class PluginWindow(IAccessible):
	"""A window for a plugin.
	"""

	# MSHTML fires focus on this window after the plugin may already have fired a focus event.
	# We don't want this to override the focus event fired by the plugin.
	shouldAllowIAccessibleFocusEvent = False

class PopupList(MSHTML):
	"""
	Temporary popup lists created when expanding a combo box have a correct accParent which points back to the combobox, so use that. The parentElement  points to a temporary document fragment which is not useful.
	"""

	def _get_parent(self):
		return super(MSHTML,self).parent

class RootClient(IAccessible):
	"""The top level client of an MSHTML control.
	"""

	# Get rid of the URL.
	name = None
	# Get rid of "MSAAHTML Registered Handler".
	description = None

class MSAATextLeaf(IAccessible):
	role=controlTypes.ROLE_STATICTEXT

class Math(MSHTML):
	role = controlTypes.ROLE_MATH

	def _get_mathMl(self):
		import mathPres
		mathMl = mathPres.stripExtraneousXml(self.HTMLNode.outerHTML)
		if not mathPres.getLanguageFromMath(mathMl) and self.language:
			mathMl = mathPres.insertLanguageIntoMath(mathMl, self.language)
		return mathMl

def findExtraIAccessibleOverlayClasses(obj, clsList):
	"""Determine the most appropriate class for MSHTML objects.
	This works similarly to L{NVDAObjects.NVDAObject.findOverlayClasses} except that it never calls any other findOverlayClasses method.
	"""
	windowClass = obj.windowClassName
	iaRole = obj.IAccessibleRole
	if windowClass == "Internet Explorer_TridentCmboBx" and iaRole == oleacc.ROLE_SYSTEM_COMBOBOX:
		clsList.append(V6ComboBox)
		return

	if windowClass != "Internet Explorer_Server":
		return

	if obj.IAccessibleChildID>0 and iaRole==oleacc.ROLE_SYSTEM_TEXT:
		clsList.append(MSAATextLeaf)
		return

	if iaRole == oleacc.ROLE_SYSTEM_WINDOW and obj.event_objectID > 0:
		clsList.append(PluginWindow)
	elif iaRole == oleacc.ROLE_SYSTEM_CLIENT and obj.event_objectID == winUser.OBJID_CLIENT:
		clsList.append(RootClient)