Internet Explorer 完全解析 [3]
由于超出博客单文章最长长度,将在这里续写第二章内容。
2.3 CElement的成员函数(续)
- CElement::SetUniqueNameHelper。对SetIdentifierHelper的封装。 设置unique name。
- CElement::InvalidateCollection。 将CCollectionCache中的某个元素invalidate。
- CElement::removeAttribute。 移除属性。获取对应属性的attr dispid,然后调用removeAttributeDispid来移除。
- CElement::OnTabIndexChange。 TabIndexChange的响应函数。将对应元素置于focus状态。
- CElement::OnEnterExitInvalidateCollections。 当有name或者id的元素进入tree中时,重新invalidate该collection。调用参数例如CMarkup::SCRIPTS_COLLECTION(
<script>
)、CMarkup::FRAMES_COLLECTION(<frame> <iframe>
)等。 - CElement::DoElementNameChangeCollections。 按tagname去将所有base elements都invalidate。重建WINDOW_COLLECTION。
- InlineEvts::Connect。将元素与events给关联起来。
2.4 小结
CElement即元素之源,它很好的囊括了元素的基础操作,例如事件处理,转发、元素遍历、排列树等等,接下来,我们将要看到的是另一个大将:CMarkup。 在第二章中,CMarkup的身影多次出现,事实上,这个类和CElement关联颇多,现在让我们看一下它的真身。
第三章
3.1 CMarkup概述
CMarkup是最基本的Markup存储结构。HTML中的Hyper-Text Markup Language中也有一个Markup,现在我们要介绍的CMarkup所指代的就是这里面的Markup。Markup一词的来源是用蓝色铅笔将作者的草稿里面的东西特别标记出来(Marking up),在这里的意思是将文本与代码区分开来的东西。
例如,HTML中,<p>
可以表示paragraph段落,而<a>
则表示anchor超链接锚。这些“功能性的”Markup语法使他们和一般文本(Plain Text)相区分。这个就是一个Markup。这里的CMarkup可能稍有不同,向下继续阅读便可知道差距在何处。
3.2 CMarkup的部分成员变量
CMarkup也是一个重要的类,那么它的成员变量肯定也是十分重要的,以下将介绍其,以及其相关类成员变量。
1:CMarkupScriptContext
- CStr _cstrNamespace。这个Markup的命名空间,主markup的命名空间是“
window
”,非主markup的命名空间是ms__idX(id1, id2....)。 - CScriptMethodsTable _ScriptMethodsTable。存储在
window
对象中暴露的dispid对应的,由脚本暴露的派遣项的dispid所组成的映射表。 比较绕口,也就是说window对象会暴露出许多dispid,而脚本解释引擎又会暴露许多派遣项的dispid,这两者之间的映射关系会组成一个映射表,它们就存储在这里。 - CStr _cstrUrl。报告这个markup里所用脚本用的。
- ULONG _cInlineNesting。进入或者离开内联脚本时计数用的。
- ULONG _cScriptDownloading。 脚本下载计数。
- DWORD _dwScriptDownloadingCookie、DWORD _dwScriptCookie。 下载cookie和脚本cookie。
- CAryScriptEnqueued _aryScriptEnqueued。 队列等待wscript引擎执行的脚本。
- CScriptDebugDocument * _pScriptDebugDocument。 脚本调试文档相关。
- ULONG _idxDefaultScriptHolder。 存放这个markup中默认脚本解释引擎的脚本宿主的index。
- BOOL _fWaitScript。为TRUE的时候表明解释器正在消息循环里等待脚本执行。
2: CMarkup
CMarkup
继承于CBase
。同样,它也是一个巨大无比的类,它的规模和CElement不相上下。CDoc
的内容将于第四章说明,CTreePos
和CTreeNode
两个类的内容将于第五章说明。CMarkup也是实现了大量接口,这些接口内容将在下一节介绍,现在让我们先看一看它的成员变量。
- _LoadStatus。 加载状态
- _pHtmCtx。 HTML上下文
- _pProgSink。 Program Sink。
- _OmDoc。 doc frags向脚本暴露的om document默认的dispatch。
- _pDoc。 与该markup相关联的CDoc。
- __lMarkupTreeVersion。 markup tree版本。
- __lMarkupContentsVersion。 markup content版本。事实上,CDoc也有这两个成员变量,版本计算方法很简单,每调用一次UpdateMarkupTreeVersion(),这两个版本的值就会加一。关联的pDoc的两个版本号也会对应加上1。
- _pElementRoot。 Root Element。
- _pElementMaster。 主element。
- _TxtArray。 存储信息的数据。
- _pRootParseCtx。 Root的parsing context。
- _lTopElemsVersion。 顶部元素的版本。
- _pSelRenSvcProvider。 选区状态相关,Selection Rendering Service Provider。
- _aryANotification。 通知数据。
- _pmpFirst。 Markup中的指针链的第一个。
- 伸展树(Splay Tree)相关数据
CTreePos _tpRoot。根节点
CTreePos * _ptpFirst。保存的第一个(最左)节点
void * _pvPool。 pool block的列表(可被释放)
CTreeDataPos * _ptdpFree。 free list的第一个
BYTE _abPoolInitial [ sizeof( void * ) + TREEDATA1SIZE * INITIAL_TREEPOS_POOL_SIZE ]。 TreePos对象的初始池。
另外一提,在Internet Explorer中node的维护事实上是使用了伸展树的结构。
伸展树(Splay Tree),也叫分裂树,是一种二叉排序树,它能在O(log n)内完成插入、查找和删除操作。它由Daniel Sleator和Robert Tarjan创造。它的优势在于不需要记录用于平衡树的冗余信息。在伸展树上的一般操作都基于伸展操作。
图:元素3被访问时的伸展操作,每一步都让3向根节点靠的更近 (成员变量还没写完
)
3.3 CMarkup的成员函数
- CMarkup::CMarkup。 构造函数,接受2个参数CDoc *pDoc, CElement * pElementMaster。这两个参数分别指定了Markup所属的Doc和Markup对应的Master Element。构造函数中将设置Tree Version和Contents Version为1。
- CMarkup::~CMarkup。 析构函数。首先,清除所有的旁视指针(lookaside pointers)。lookaside指针组成的链表将是一个双向链表。如果有StyleSheetArray,那么直接调用其CBase的PrivateRelease来释放它的引用,同时删除stylesheet的lookaside。同时释放一个pDoc的引用。
- CMarkup::UpdateMarkupTreeVersion、CMarkup::UpdateMarkupContentsVersion 。 每调用一次,给对应版本号加一。
- CMarkup::ClearLookasidePtrs。 字面意思,清空旁视列表指针。释放CollectionCache、释放父Markup。
- CMarkup::Init。初始化。指定传来的CRootElement*为根元素,同时也是最右最后的元素。并且根据根元素创造一个最初的Markup。
- CMarkup::UnloadContents。 卸载内容,删除选区服务提供者,删除伸展树,删除上下文,反注册脚本上下文、删除Range选区上下文、释放HTML上下文、卸载program sink、删除StyleSheet数组。
- CMarkup::Passivate。 释放内容。 所有能释放的都会被释放,是UnloadContents的封装,同时还会释放Style Sheet Subobj,同时调用父类的Passivate。
- CMarkup::DoEmbedPointers。 将未绑定的node放入伸展树。操作步骤如下:
1、删除第一个元素,具体方法: a)获取第一个元素存一个副本,然后取得它的下一个元素; b)如果下一个元素不为空,那么把它的前一个元素置为空(不要忘了这是一个双向链表);c)将副本的前后都置为空;
操作情况大致如上,操作后backup还存有一个独立节点,就是之前的First,同时节点链表中不再有第一个元素。
2、假设现在这个backup里面放着的是一个Text类型的Markup,由于Markup有顺序,如果有两个以上Markup同时指向一个Text段中间,当第一个Markup被选中后,伸展树操作会导致它出现一次split
,会导致后面的Markup就会指向无效的ich,所以这里还需要判断。
当当前指向的内容超过前一个Text长度范围时,将这个Markup的chRef
一直减去前一个文本引用的cch
,这样,肯定会有一刻满足chRef < cch。同时,只要文本引用还有效,就一直将文本引用向前移动(Ref的NextTreePos),这样可以确定split的右手侧,也会顺便调整这个指针。
3、做完上述操作之后,如果当前前一个元素文本节点,同时ref的chRef小于ref的cch,这时做一次split.
4、以backup为准,调整gravity和cling,新建一个pointerpos
5、当backup的ref的ichref == cch时,将backup插入。这样可以保证它一直在text pos的最末尾。
6、设置embedded为TRUE,设置ichref为0。~
- CMarkup::GetDD。获取这个Markup的默认Dispatch对象。
- CMarkup::EnsureScriptContext。 确保Script Context是有效的。初始化命名空间,
- CMarkup::PrivateQueryInterface。 QueryInterface的实现。
- CMarkup::Load。 多个重载函数都是对CMarkup::Load (HTMLOADINFO * phtmloadinfo)的封装,被封装的Load创建html context,并当Markup是主markup或者html是异步下载时会创建program sink,并将其关联到之前创建的html context上,设置URL,然后下载网页内容。
- CMarkup::StopDownload。 停止Load函数(调用html context的SetLoad(FALSE, NULL, FALSE)),释放html context。
- CMarkup::LoadStatus。 返回LoadStatus。
- CMarkup::OnLoadStatus。LoadStatus变化时的回调函数,根据状态决定是否向CDoc发送消息,或是清理html context等。
- CMarkup::EnsureTitle。 没有title时,创建一个CElement,类型为ETAG_TITLE_ELEMENT,并添加成Head Element。
- CMarkup::AddHeadElement。 获取第一个TITLE,同时给head里的元素加引用。
- CMarkup::SetXML。 将不认识的tag都当作xml tag处理,调用SetGenericParse(_fXML)。
- CMarkup::PasteClipboard。 从剪贴板粘贴东西,获取剪贴板的对象,然后传到Markup对应的CDoc里,调用AllowPaste来传递剪贴板对象。
- CMarkup::PasteUnixQuickTextToRange。 CLightDTEngine的同名函数的封装。
- CMarkup::SetModified。 告诉CDoc数据变化了(OnDataChange)。
- CMarkup::createTextRange。参数为(IHTMLTxtRange * * ppDisp, CElement * pElemContainer),对4个参数的createTextRange的封装,直接调用createTextRange(ppDisp, pElemContainer, NULL, NULL, TRUE)。
CMarkup::createTextRange(IHTMLTxtRange * * ppDisp, CElement * pElemContainer, IMarkupPointer *pLeft, IMarkupPointer *pRight, BOOL fAdjustPointers)
对整个文档做一次自动获取range的操作。具体操作:
根据pElemContainer来建立一个CAutoRange对象,初始化TextRange的Lookaside列表,pLeft和pRight都设置时,对pAutoRange设置左右Markup,否则将pElemContainer作为TextRange设置到pAutoRange里。
将pAutoRange返回到IHTMLTxtRange*中,引用计数加一。 - CMarkup::AcceptingUndo。返回是否应该撤销。
- CMarkup::OwningDoc。 对pDoc的主Markup进行一次QueryInterface,返回ppDoc,即所有者。
- CMarkup::AddSegment/CMarkup::AddElementSegment/CMarkup::MovePointersToSegment/CMarkup::GetElementSegment/CMarkup::MoveSegmentToPointers/CMarkup::SetElementSegment/CMarkup::ClearSegment/CMarkup::ClearSegments/CMarkup::ClearElementSegments/CMarkup::GetSegmentCount/CMarkup::EnsureSelRenSvc/CMarkup::GetSelectionChunksForLayout/CMarkup::GetFlattenedSelection/CMarkup::HideSelection/CMarkup::ShowSelection/CMarkup::InvalidateSelection/CMarkup::IsElementSelected/CMarkup::GetSelectedElement。 选区渲染服务上下文的同名函数的封装。
- CMarkup::GetElementTop。获取元素客户端,如果获取失败,返回Root。
- CMarkup::EnsureTopElems。 cache顶级元素(html、head、title、frameset、body、text slaves,etc)。
- CMarkup::MetaPersistEnabled。 遍历head的子元素,找到所有meta元素,转为CMetaElement之后调用其IsPersistMeta方法,确定针对特定的元素id,这个meta是否是一个persist meta(这是指一个可以启用或者禁用某一组控件,或者对象的标签,参见参考资料
meta
)。 - CMarkup::LocateHeadMeta。 在head里面查找特定的meta。对比head的所有子meta元素,找到一个和给定的meta相同的meta。
- CMarkup::LocateOrCreateHeadMeta。 找到一个指定meta,如果没有,创建这个meta。
- CMarkup::FirstElement。 获得源中树的第一个位置上第一个分支的第一个元素。
- CMarkup::GetContentTreeExtent。 获得树的区间。找到markup中第一个非root元素,到分支上最后一个元素的区间。
- CMarkup::GetProgSinkC、CMarkup::GetProgSink。 返回_pProgSink。
- CMarkup::IsEditable。 存在master element时返回master element的可编辑状态,否则返回文档是否处于设计模式。
- CMarkup::InvokeEx。 InvokeEx的封装。
- CMarkup::GetDispID。 GetDispatchID的封装。
- CMarkup::GetNextDispID。 GetNextDispID的封装。
- CMarkup::GetMemberName。 获取dispatch对应的成员名。
- CMarkup::GetNameSpaceParent。 对CDoc的GetNameSpaceParent的封装。
- CMarkup::get_designMode。 获取设计模式。
- CMarkup::put_designMode。 设置设计模式。
- CMarkup::open。 open的实现。
- CMarkup::write。 write的实现。
- CMarkup::writeln。 writeln的实现。
- CMarkup::close。 close的实现。
- CMarkup::clear。 clear的实现。 在ie下这是一个无操作的函数,在脚本中,调用document.open();document.close()即会清空页面内容。
- CMarkup::get_bgColor/CMarkup::put_bgColor。 bgColor的实现。
- CMarkup::get_fgColor/CMarkup::put_fgColor。 fgColor的实现。
- CMarkup::get_linkColor/CMarkup::put_linkColor、alinkColor、vlinkColor。 对应属性的实现。参考
linkColor/ alinkColor/ vlinkColor
。 - CMarkup::get_parentWindow。 获取父窗口, parentWindow的实现。
- CMarkup::get_activeElement。 活动元素,activeElement的实现。
- CMarkup::get_URL/CMarkup::put_URL。 URL属性操作,
URL
的实现。 - CMarkup::get_location。 location属性的实现。
- CMarkup::get_lastModified。 lastModified属性的实现。
- CMarkup::get_referrer。 referrer属性的实现。
- CMarkup::get_domain/CMarkup::put_domain。 domain属性的实现。
- CMarkup::get_readyState。 readyState属性的实现。
- CMarkup::get_Script、CMarkup::releaseCapture、CMarkup::get_styleSheets、CMarkup::get_selection。
- CMarkup::get_cookie、CMarkup::put_cookie、 CMarkup::get_expando、 CMarkup::put_expando。
- CMarkup::get_charset、CMarkup::put_charset、CMarkup::get_defaultCharset、CMarkup::put_defaultCharset
- CMarkup::get_dir、CMarkup::put_dir、CMarkup::get_mimeType、CMarkup::get_fileSize、CMarkup::get_fileCreatedDate、CMarkup::get_fileModifiedDate、CMarkup::get_fileUpdatedDate、CMarkup::get_security、CMarkup::get_protocol、CMarkup::get_nameProp。这些都是对应属性的实现,上述所有属性实现部分应当都是document的内容,可以说应当放在CDoc中,多个版本的CMarkup中有这些函数,但是并没有实现他们的功能,这一点可以在调试的时候加以区分。
- CMarkup::toString、CMarkup::attachEvent、CMarkup::detachEvent。 对父类(super)的同名函数的封装。
- CMarkup::recalc。 调用pDoc的recalc,重新计算所有Markup。
- CMarkup::createTextNode。 调用pDoc的createTextNode,创建文本节点。
- CMarkup::get_uniqueID。 调用pDoc的get_uniqueID,获取唯一id。
- CMarkup::createElement。 根据指定的元素名来创建一个元素。
- CMarkup::createStyleSheet。 根据指定的参数创建一个LINK或者STYLE。之后修正引用,排除那些不会有style的元素,例如title、meta等。最后,将创建的LINK或者STYLE元素绑入HEADER中。
- CMarkup::elementFromPoint。 获取指定点上的元素。
- CMarkup::execCommand、CMarkup::execCommandShowHelp、CMarkup::queryCommandSupported、CMarkup::queryCommandEnabled、CMarkup::queryCommandState、CMarkup::queryCommandIndeterm、CMarkup::queryCommandText、CMarkup::queryCommandValue。 暂未实现。
- CMarkup::createDocumentFragment。 为pDoc创建一个Markup,并设置为当前Markup的子Markup。
- CMarkup::get_parentDocument、CMarkup::get_enableDownload、CMarkup::put_enableDownload、CMarkup::get_baseUrl、CMarkup::put_baseUrl。 对应属性的get、put函数。
- CMarkup::get_childNodes。 获取Root节点的子节点。
- CMarkup::get_inheritStyleSheets、CMarkup::put_inheritStyleSheets。 inheritStyleSheets的get和put函数。
- CMarkup::getElementsByName。 getElementsByName的实现,调用GetDispByNameOrID返回一个IHTMLElementCollection**。
- CMarkup::getElementsByTagName。 getElementsByTagName的实现,根据指定的TagName返回一个IHTMLElementCollection**。
- CMarkup::getElementById。 getElementById的实现。根据指定的id返回一个IHTMLElement**。
- CMarkup::zoom、CMarkup::get_zoomNumerator、CMarkup::get_zoomDenominator。 缩放相关属性及操作的实现。
- CMarkup::GetLookasidePtr。 从Doc中获取Lookaside指针。
- CMarkup::SetLookasidePtr。 向Doc中设置Lookaside指针。
- CMarkup::DelLookasidePtr。 删除一个Doc中的Lookaside指针。
- CMarkup::ReplacePtr。替换两个CMarkup指针,并且给其中一个增加一个计数,被替换的那个减少一个引用计数。
- CMarkup::SetPtr。 设置一个CMarkup为另一个CMarkup,并给被设置的CMarkup增加一个引用计数。
- CMarkup::StealPtrSet。 调用SetPtr,同时给另一个CMarkuo减少一个引用计数。
- CMarkup::StealPtrReplace。 调用ReplacePtr,被赋值的那个多释放一次。
- CMarkup::ClearPtr。 置指针为空,同时减少其原指针引用对象的一次引用计数。
- CMarkup::ReleasePtr。 释放一次引用。
上面用到了Document Fragment类,这是一个小类,在这里也一并介绍了。 - CDocFrag::PrivateAddRef。 对Markup调用SubAddRef。
- CDocFrag::PrivateRelease。 对Markup调用SubRelease。
- CDocFrag::PrivateQueryInterface。 QueryInterface的实现。
- CDocFrag::get_document。 对Markup QueryInterface,获取IID_IHTMLDocument2对应的ppIHtmlDoc。
- CDocFrag::GetNameSpaceParent。 对Markup QueryInterface,获取其IDispatchEx。
只有这几个函数而已。 - CMarkup::RegisterForDirtyRange、CMarkup::UnRegisterForDirtyRange、CMarkup::GetAndClearDirtyRange、CMarkup::OnDirtyRangeChange、CMarkup::EnsureDirtyRangeContext、。 对已经标记为dirty的range的处理服务,当一个Range内的内容发生变化时,该Range即标记为Dirty。
- CMarkup::EnsureTopElemCache。 有顶级元素cache时,返回它,否则新建一个CMarkupTopElemCache并设置为顶级元素cache。
- CMarkup::EnsureTextFragContext。 同上,CMarkupTextFragContext的处理函数。
- CMarkupTextFragContext::~CMarkupTextFragContext。 CMarkupTextFragContext的析构函数,释放申请的内存资源。
- CMarkupTextFragContext::AddTextFrag。 申请内存,并填入一个TextFrag。
- CMarkupTextFragContext::RemoveTextFrag。 移除TextFrag,并释放所占用的空间。
- CMarkupTextFragContext::FindTextFragAtCp。 根据一个Cp来查找对应的TextFrag。
- CMarkup::GetTextFragCount。 获得TextFrag的大小。
- CMarkup::GetTextFrag。 获得TextFrag。申请内存,放置TextFrag并把它最终放入树的对应位置上。
- CMarkup::RemoveTextFrag。 删除TextFrag。
- CMarkup::InsertTextFrag。 插入一个TextFrag。
- CMarkup::FindTextFragFromMarkupPointer。 从一个MarkupPointer中找到一个TextFrag。
- CMarkup::EnsureStyleSheets。 确保样式表collection的存在,如果不存在就创建一个并关联上。
- CMarkup::ApplyStyleSheets。 应用样式表,Apply的封装。
- CMarkup::OnCssChange。 OnCssChange的响应函数。
- CMarkup::EnsureFormats。 确保树中所有的元素的格式都是正确的,分别调用各个元素的EnsureFormats。
3.4 小结
CMarkup是Internet Explorer中关于document
的实现,我们可以看到CMarkup实现了document的几乎所有的属性、事件响应、样式表的操作,在下一章中,我们将介绍最后一个大类CDoc的具体实现细节。在下一章结束后,我们将同时给出一个Internet Explorer 11中的实例分析。
参考
Splay Tree
伸展树,http://zh.wikipedia.org/wiki/%E4%BC%B8%E5%B1%95%E6%A0%91
chRef
char reference的缩写
Cch
count of char的缩写
split
Split(x,S):以x为界,将伸展树S分离为两棵伸展树S1和S2,其中S1中所有元素都小于x,S2中的所有元素都大于x。首先执行Find(x,S),将元素x调整为伸展树的根节点,则x的左子树就是S1,而右子树为S2。