2014年9月

Internet Explorer 完全解析 [4]

第四章 文档的具象实现
前几个类的介绍都不是完整的,以后有需要再补充。 免责声明:本类文章全部是我的个人理解,可能存在理解错误,如有发现,敬请指正。
4.1 介绍
CDoc910类将是我们要详细叙述的最后一个类,它可以说是网页文档的具象实现,同时,它也会贯穿在所有的类中,在分析时经常会看到许多类都关联着CDoc *pDoc这样一个成员函数,让我们看一看CDoc的真身吧。

4.2 CDoc概述
CDoc(formknl)也就是传说中的根对象(root object),对应传统的文档视图结构的文档部分。有些像是MFC中的CDoc,事实上Internet Explorer中也确实还有一个CView类。CDoc作为IHTMLDocument2/3接口的一个具体的实现,它自身封装了许多与Markup有关的内容,例如创建、增删Markup等等。对Markup具体的介绍请见完全解析Internet Explorer [3]、[5]以及“对Markup的介绍[译]”。
可以极为简化地说,一个CDoc管理着一组CMarkup,而CMarkup又与一组CElement有关。这个便是前几个内容之间的内在联系。

4.3 CDoc的成员变量
CDoc的成员变量列举如下,即使在日常调试中没有私有符号,我想,这依然可以作为参考,至少通过成员变量可以了解这个类具体能做什么。

  • CDefaultElement * _pElementDefault。 指定默认元素,如果文档中没有任何可用元素的话,那么就会用到这个默认元素。
  • CMarkup * _pPrimaryMarkup。 主markup,也就是指向当前根元素的markup。
  • CEditRouter _EditRouter。 提供垂直消息路由(也就是元素层级中传递消息)的。
  • CCaret * _pCaret。判断用户是否在编辑文档时用的。
  • CDocInfo _dci。 文档信息,文档内容传输时用。
  • CElement * _pElemEditContext。存储着当前正在编辑的选区,或者就是当前选区。
  • CElement * _pElemUIActive。当前正在显示着的UI。
  • CElement * _pElemCurrent。当前获得焦点的元素,上面说的消息路由也会从这儿开始。
  • CElement * _pElemDefault。 默认元素。
  • long _lSubCurrent。用来细分当前元素功能用。
  • CElement * _pElemNext。下一个会获得焦点的元素。
  • CRect * _pRectFocus。上一个获得焦点并且被渲染的元素的区域。
  • int _cSurface/_c3DSurface。 计数器。
  • OPTIONSETTINGS * _pOptionSettings。指向当前用户可以编辑的属性,例如文字颜色等。
  • CODEPAGESETTINGS * _pCodepageSettings。 指定codepage。
  • long _icfDefault; 。默认charformat index。
  • const CCharFormat * _pcfDefault;。 默认的charformat。
  • unsigned _cInval; 调用CDoc的Invalidate的次数。
  • unsigned _cProcessingTimeout;。 脚本执行时清空分块的超时值。
  • SHORT _iWheelDeltaRemainder;。 缩放值(zDelta)
  • CStr _cstrPasteUrl;。 粘贴时会用到的url。
  • RADIOGRPNAME *_pRadioGrpName;。 Radio Box的名字。
  • LONG _lRecursionLevel;。 最大递归层次。
  • DWORD _dwHistoryIndex;。 下一个可用的历史记录index。
  • CView _view;。 View的支持。
  • long _lLastTextID; 。 TextID。
  • long __lDocTreeVersion;。 当doc关联的markup中任何元素导致树变化时,这个值会加一。仅仅改变树中文本节点的值的时候不会改变这个版本号。
  • long __lDocContentsVersion;。 任何内容的改变都会导致这个版本加一。不管是markup还是文本节点。
  • CSelectionObject * _pCSelectionObject;。 选区对象。
  • CAtomTable _AtomTable;。 存储着元素-名字(name)的映射
  • IDocHostUIHandler * _pHostUIHandler;。 宿主的整合,这个是UIHandler的接口指针。
  • IDocHostUIHandler * _pBackupHostUIHandler;。 上一个(称为主UIHandler)失败时调用这个备选的。
  • IOleCommandTarget * _pHostUICommandHandler;。 UIHandler的Command Target。
  • DWORD _dwFlagsHostInfo;。 宿主的flags
  • DWORD _dwFrameOptions;。 框架选项。
  • CStr _cstrHostCss; 。宿主下发下来的css规则。
  • CStr _cstrHostNS;。 命名空间列表,用分号分割。
  • CElement * _pElementOMCapture;。 用于处理鼠标捕获。
  • PFN_VOID_MOUSECAPTURE _pfnCapture;。 鼠标捕获事件的处理函数。
  • void * _pvCaptureObject;。 触发事件的对象。
  • CElement * _pMenuObject;。 当前调用site(ie的这个术语当成元素来看比较靠谱?)的菜单对象。
  • CTreeNode * _pNodeLastMouseOver;。 上一次触发mouseOver事件的元素。
  • long _lSubDivisionLast; 鼠标移动过的上一个区域。
  • CTreeNode * _pNodeGotButtonDown;。 捕获到鼠标按下的site。
  • HMENU _hMenuCtx;。 菜单上下文。
  • USHORT _usNumVerbs;。 上下文菜单中的verb(活动元素)
  • HWND _hwndCached;。 处理状态时负责承载的窗口。
  • ULONG _cFreeze;。 冻结计数
  • IUnknown * _punkMimeOle;。 维持MimeOle对象的计数用。(保活)
  • IStream * _pStmDirty;。 脏文档数据流,用于刷新。
  • IMoniker * _pmkName; 。 为IPersistMoniker保存的当前使用的Moniker。
  • CDwnPost * _pDwnPost;。 用于获取当前doc的推送数据。
  • CStr _cstrUrl; 。 内部使用,当前doc的base url。
  • CStr _cstrSetDomain;。 URL Host name的子集。
  • SAFETYLEVEL _safetylevel;。 当前页面的安全等级。
  • SSL_SECURITY_STATE _sslSecurity;。 当前页面安全等级,不安全,混合,安全。
  • SSL_PROMPT_STATE _sslPrompt; 。 SSL提示,允许,询问,拒绝。
  • LONG _cInSslPrompt;。 当前的提示字段指示。
  • IHlinkBrowseContext *_phlbc;。 超链接的浏览上下文,用于历史等地方。
  • DWORD _dwLoadf;。 加载标识,离线,安静,等等。
  • IUrlHistoryStg *_pUrlHistoryStg;。 历史存储(history storage)
  • CTaskLookForBookmark *_pTaskLookForBookmark;。 检查书签的task。
  • CMapElement * _pMapHead;。指向树中的map对象。
  • long _readyState;。 readystate的指示。
  • ULONG _ulProgressPos;。 进度位置。
  • ULONG _ulProgressMax;。 最大进度指示。
  • (以下挑重要的来了,这里的东西实在是太多了)
  • CDoc* _pDocParent;。 父文档的指针
  • IHTMLEditor* _pIHTMLEditor;。 选区管理器
  • CStyleSheetArray *_pHostStyleSheets;。 宿主传来的所有的样式表。

4.4 CDoc的成员函数
CDoc提供了一大堆的成员函数,在这里我们将列举出部分重要或者常用的成员函数,以供参考。

  • InitDocClass。 初始化,生成一个CDoc类,由DLL的LibMain调用。
  • CDoc::CDoc。 CDoc的构造函数,这个和CDoc::Init合起来完成了CDoc的初始化,当然,构造函数是第一个被调用的。
  • CDoc::~CDoc。 CDoc类的析构函数。
  • CDoc::CreateRoot。为当前CDoc创建一个主根元素。
  • CDoc::Init。 初始化的第二阶段。
  • CDoc::Passivate。释放对其他元素的引用,藉由可以释放主对象。
  • CDoc::UnloadContents。 释放所有资源。
  • CDoc::RunningToLoaded。 通知主根元素OS_RUNNING事件。
  • CDoc::HitTestPoint。 在指定点击位置上向CView发出请求,找到对应位置的元素。
  • CDoc::Update。 更新对应View的缓存。
  • CDoc::IsUpToDate。 获取Cache状态是否为最新。
  • CDoc::Close。 关闭当前对象。
  • CDoc::ParentFrameSite。 当当前CDoc是在frameset中呈现的话,这个会返回它的parent site,否则返回null。
  • CDoc::ParentIFrameSite。 同上,不过是iframe。
  • CDoc::BroadcastNotify。向整个树中广播通知。
  • CDoc::SetDocParent。 设置parent。

  • 有参考意义的网页 http://msdn.microsoft.com/en-us/library/aa752038%28v=vs.85%29.aspxhttp://msdn.microsoft.com/en-us/library/aa741317%28v=vs.85%29.aspxhttp://msdn.microsoft.com/en-us/library/bb508514%28VS.85%29.aspx

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的内容将于第四章说明,CTreePosCTreeNode两个类的内容将于第五章说明。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创造。它的优势在于不需要记录用于平衡树的冗余信息。在伸展树上的一般操作都基于伸展操作。

1nul.gif
图:元素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)将副本的前后都置为空;
    nulpw3.png
    操作情况大致如上,操作后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。

Internet Explorer 完全解析 [2]

第二章 元素之源
2.1 CElement
介绍了mshtml的一个重量级明星CBase之后,这一步,我们需要看看一看CElement,同样,可以猜测它是很多元素的基类。事实如何?我们深入看一下这个类。

本节中,由于CElement会引入许多之前未介绍的类,因此,在这节中,当引用到一个新类的时候,我都会在之后另开一章介绍,而且会修改本文指向那一个类。

2.2 巨人CElement的结构
CElement是CBase的派生类。不同的元素通过派生和重载此类来区别。CElement作为元素的基类,结构十分庞大,从自身来说,它实现了IDispatchEx、IProvideMultipleClassInfo、IServiceProvider、IRecalcProperty、IMsoCommandTarget等接口。这些我们稍后都会看到。

CElement的成员变量中,__pvChain是其最重要的关联成员,这是一个void*格式的变量,在CElement构造时,这个变量将会被赋值。如果一个element有layout(见参考,简单的说,就是有layout的元素自己会处理自己的大小之类的东西,参见layout _2)的话,__pvChain会指向这个layout,如果它没有layout,而它在一个树中,那么它会指向树中下一个ped,其他情况下,它指向当前document。

__pNodeFirstBranch指向第一个分支的node。还有两个成员变量DWORD _fMark1、DWORD _fMark2,它们用位来标记作用,分别如下:
_fMark1:
0~7 element tag; 8~14 如果旁视列表有指针的话为TRUE; 15 元素有name或者id属性; 16~31 元素锁定标记
_fMark2:
0 元素有一个Markup指针时为TRUE
1 元素有一个layout指针时为TRUE
2 有挂起状态的filter任务时为TRUE
3 有挂起状态的recalc任务时为TRUE
4 元素是一个site或者从未有过layout时为TRUE
5 元素控制着所有其下的函数运转时为TRUE
6 元素用来继承site和一些特殊格式时为TRUE
7 元素占1行,即使没有任何文字
8-11 未使用
12 元素没有边框
13 元素与tabindex关联(_aryTabIndexInfo)
14 元素有至少一个图像上下文
15 图片变化时需要强制重置大小
16 元素有关闭tag (例如<p></p>
17 用户创建时为FALSE,代码生成时TRUE
18 未使用
19 元素的行为像按钮
20 为init中的TestClassFlag而设置
21 元素默认就有layout
22 默认的确认按钮
23 默认的取消按钮
24 元素提交的事件被挂起
25 其他元素或者对象需要一个连接点或者设置一个事件
26 _pAA中加入了FilterCollectionPtr
27 元素有一个挂起的Exittree消息
28 GetFirstCommonAncestor会用到这个位,标记是否为第一个祖先
29-30 随机位
31 元素有style expression

为了做到普适性,这个CElement不得不称作一个巨人,下面我们介绍它的成员函数。

2.3 CElement的成员函数

  • CElement::CElement。构造函数,将当前CElement所属pDoc赋给__pvChain,调用pDoc->SubAddRef(),增加一个Reference。并保存传入的ELEMENT_TAG类型数据到自身的_etag中,标记当前CElement的真正Tag。
  • CElement::~CElement。 析构函数,Passivate()中做了清理,这儿几乎没动作。
  • CElement::CLock::CLock。 设置某个CElement元素的ELEMENTLOCK_FLAG。
  • CElement::CLock::~CLock。 将设置的Lock取消。 CElement::Passivate。
    清理函数,当调用这个函数时,CElement
      (1)不会在一棵树中(如果在树中就不应该被清理);
      (2)延迟队列中不能有当前CElement,否则会重复释放。
      当满足上述2个要求时,程序检查是否有pending task,如果有的话,则从pDoc中获取View,然后调用删除任务的函数,将pending task清除。
      如果当前CElement有任何sub Markup,那么将它们删除,然后清除master Makrup,再Release释放计数。如果当前CElement是一个peer holder(节点持有者),那么在当前CElement上也只应该有1个peer,否则不应该调用这个清理函数,有的话,随后将这1个peer的Reference清除。
      接着,清除peer Manager(如果有),data bindings(如果有),element capture,methods,images,layouts(如果有),accessible objs(如果有),pending filter tasks(如果有),recalculate task(如果有),filter collections pointer(如果有,AA的),cached style pointer(如果有,调用super::Passivate来清除关联的属性数组),all lookasides(也即上述这些)。
      最终,减少CElement的计数和obj count。
  • CElement::PrivateQueryInterface。 IUnknown的QueryInterface实现。
  • CElement::PrivateAddRef。 IUnknown的AddRef实现。
  • CElement::PrivateRelease。IUnknown的Release实现。
  • CElement::PrivateEnterTree。 进入树处理时调整引用计数。会把在Markup中的当前CElement父类引用加一,MarkupPtr的引用也随之加一。
  • CElement::PrivateExitTree。 退出树处理时调整引用计数。与上述操作相反,引用数大于1时,调用父类的Relase将引用计数减一,同时将传入的Markup引用减一(如果引用大于1时)。
  • CElement::contains。 IHTMLElement的函数。传入一个IHTMLDocument*类型的参数,如果CElement里包含这个IHTMLDocument*类型的对象,将返回一个VARIANT_BOOL。
    判断方式:
    (1)获取一个CTreeNode*(从传入的PIHTMLDocument中QueryInterface:CLSID_CTreeNode),如果失败,则获取第一个分支CElement * (QueryInterface:CLSID_CElement),调用获取到的CElement*的GetFirstBranch()函数。
    (2)从(1)步获得的这个Node开始向上遍历,直至遍历到根Node,或者当前Node为止;
    (3)如果(2)步的结果是遍历到了根Node,那么返回VB_FALSE,如果是遍历到了当前Node,返回VB_TRUE,这个逻辑很好理解:
    f1.gif
  • CElement::ClearRunCaches。获得当前CElement所在的Markup,调用Markup的ClearRunCaches函数。
  • CElement::IsFormatCacheValid。遍历当前所在树中第一个branch开始的所有branch(First->>Next->>Next...),进行有效性检查。
  • CElement::EnsureFormatCacheChange。当当前函数不在一个树中或者没有设置需要清理cache时,返回S_OK;如果设置了标记位,那么调用ClearRunCaches。
  • CElement::OnPropertyChange。 onpropertychange事件。notenote: onproprty事件是IE专属
    (1)当前CElement的onpropertychange事件刚刚被设置时应该执行事件;
    例如:
    <div id="txt"> www.nul.pw</div> <script> function d(){alert(5);} document.getElementById('txt').onpropertychange=d(); </script>
    将会触发alert(5)。 (IE11可能不一定会正确触发,请在IE10-,或者兼容性视图中测试)
    (2)当前CElement不在任何tree中是不可以触发的,只有当前CElement在放置到tree中之后才能触发事件;
    <div id="txt"> www.nul.pw</div> <script> function d(){alert(5);} p = document.createElement("div"); p.onpropertychange=d(); p.innerText="123"; document.getElementById('txt').appendChild(p); </script>
    例如上述代码不能触发onpropertychange事件。
    <div id="txt"> www.nul.pw</div> <script> function d(){alert(5);} p = document.createElement("div"); p.onpropertychange=d(); document.getElementById('txt').appendChild(p); p.innerText="123"; </script>
    则可以触发事件。
     
      以及一些额外的操作,例如cache调整,根据传入的dispid来设置对应应该触发事件的对象,或者调整branch。因为onpropertychange是在一个元素的任意属性改变时触发的,所以这儿的判断流程十分长,例如background-image(背景图片)、list-style-image(列表图片)、z-index(顺序)、left,right,bottom,top(位置)、disabled(有效性)、visiablilty(可见性)、marginleft,right,bottom,top(边缘对齐)、clipleft,right,bottom,top(剪裁绝对元素)等等等等,几乎是所有样式操作导致属性变化都会有对应的方式来处理,这一部分可以参考w3school的对应部分。例如;clip:rectlist-style-image等。
     
      当propertychange发生时,如果当前CElement有layout的话,将获取离得最近的layout的视图,然后向它发送一个property changed 的notify。
     
      如果pDoc中的current元素指向自身,但是获取不到layout,可标记里还是提示有layout;或者获取到了layout,但是标记里面提示没有layout,那么将最近的一个layout的视图的owner变成当前元素的子元素。这个逻辑是:标记有layout但是获取不到layout的时候,表明当前元素有子元素,但是获取不到layout,证明子元素没有设置owner标记; 获取到了layout,证明子元素可达,而且有layout标记,但是标记里没有layout,这时应当也设置一次owner标记,让其也标记上layout。
     
      这步操作之后,会设置上RemeasureInParent标记,告知Parent应该重新测量自己的子元素属性。同时去除SIZECHANGED属性。
     
      RemeasureInParent之后应该设置DisplayChange通知,将标记元素祖先为脏的数据,因此只有ZParent全部计算完成之后,ZParentChange才会被调用,之前都一直会在队列中排着。
    (注:DisplayChange请参见CNotification节中的内容,将在后续介绍。 )
     
      除非是在一个non-site的元素上进行的z-index改变,否则z-index的改变将CSite的z-order处理函数FixZOrder来处理。
     
      当一个property改变而且会引起继承的值都会出现改变时(通过dwFlags确认),会调用Invalidate()通知parent,然后发送一个notification以通知后代。
     
      当触发onpropertychange之后,之前获取到的layout就不再可靠了,因为脚本可能会改变页面内容,所以此时,还应当测试性的调用一次onchage事件(当然,不一定触发,这里面还是会检查是否应当触发事件的),以及通知当前document数据变化了,最后会做一些其他小的触发行为。根据ie版本不同而不同,ie11以及兼容性视图行为差异很大。
  • CElement::OpenView。打开与document关联的view(这个view是与最近的一个layout相关联的)。
  • CElement::*Element。 发送消息用的辅助函数。InvalidateElement、MinMaxElement、ResizeElement(没有layout、当前标记为clean、它的父容器没有设置过它的大小时,这个消息才会送达)、RemeasureElement、RemeasureInParentContext、RepositionElement、ZChangeElement。
  • CElement::SendNotification。重载函数,传入一个CNotification*,向关联的element发送一个notification,做法是:先获取Markup,然后调用Markup的Notification函数发送。
  • CElement::SendNotification。重载函数,带NTYPE和NFLAGS的,当场组合一个CNotification,然后发送。
  • CElement::DirtyLayout。 标记当前元素关联的layout为脏。
  • CElement::HitTestPoint。 和常见的WM_NCHITTEST的目的差不多,它的作用是确定某个点是否在某个CElement上。判断方式:获取最近的layout,获取其View(CView),然后转而调用View的HitTest函数。
  • CElement::GetRange。 返回这个CElement的文字范围,包括结束节点。计算方式:获取起始节点和最终节点的范围,相减后加一。
  • CElement::GetFocusShape。返回当一个控件被选中时应该被重绘的区域范围,区域是一个从CShape派生来的对象。默认的TextRange是用这个的(GetClientRect),其他的例如多选框之类的,都是从这儿重载的,否则无法提供正确的区域范围。
  • CElement::GetImageUrlCookie。返回向指定Doc的图像url缓存里面添加一个url的结果,称为新cookie。
  • CElement::AddImgCtx。向AA中添加在ImgCtxInfo中一个指定的信息。如果AA中现在已经有值了,先会把它释放掉。具体步骤:先找到AAIndex,这时候会得到一个cookie,根据它来检索,如果有ImgCtx(image context),释放旧的,然后添加新的cookie。
  • CElement::ReleaseImageCtxts。 释放所有与此CElement关联的图像上下文,例如背景图像。
  • CElement::DeleteImageCtx。同上,只不过删掉的是所有dispid指定的与CElement关联的图像上下文。
  • CElement::GetSourceIndex。 获得当前元素的SourceIndex。由于ROOT node的存在,获取的SourceIndex会减一修正。
  • CElement::CompareZOrder。 与另一个CElement比较ZOrder。返回值:>0,这个元素z-index比较大;<0,这个元素的z-index比较小;0,元素的z-index相等。
  • CElement::GetTreeExtent。遍历上下文chain,然后获取第一个和最后一个node的位置。
  • CElement::GetLastBranch。 获得最后一个branch。从first branch一直获取nextbranch即可。
  • CElement::ReplacePtr。 替换两个Ptr,同时修正引用。
  • CElement::ReplacePtrSub。 类似,只不过修正的是subref。
  • CElement::SetPtr。 类似,只不过修正一个元素的ref。
  • CElement::StealPtrSet。 SetPtr的封装,修正引用。
  • CElement::StealPtrReplace。 ReplacePtr的封装,修正引用。
  • CElement::ClearPtr。 置NULL,Release ref。
  • CElement::ReleasePtr。 Relase ref。
  • CElement::IsBlockElement。 返回这个CElement是否是Block Element
  • CElement::IsOwnLineElement。 返回这个CElement是否是内联元素。
  • CElement::IsBlockTag。 返回是否是Block Tag,例如css里面设置的display:block的元素。
  • CElement::BreaksLine。 返回这个节点是否起新行,例如hr,div。
  • CElement::HasFlag。 返回这个节点是否有指定的flag。
  • CElement::SetTagNameAndScope。 设置tag,然后scope一个元素的属性。
  • CElement::TagName。 返回tag名称。
  • CElement::Namespace。 返回GetAAscopeName。
  • CElement::NamespaceHtml。 返回namespace,如果没有namespace,返回“HTML”。
  • SameScope。 重载函数,参数为CTreeNode,CElement 或者 CElement, CTreeNode 或者两个CTreeNode ; 检查CTreeNode所包含的元素是否就是后面的CElement。
  • CElement::NameOrIDOfParentFor。如果有的话,返回一个对象的父form的id或者name,具体方式是获取第一个branch,然后从branch到root搜索任意一个tag为ETAG_FORM的对象,这个即可算作父form。
  • CElement::SaveAttribute。将一个属性存入流(CStreamWriteBuff类)中。也就是加入一个属性="值"的对,同时请注意到IE中的一个特性是属性中的部分中的回车都不会忽略
  • CElement::SaveUnknown。把所有的expando(AAType为CAttrValue::AA_Expando)的值输出到流中。
  • CElement::SaveUnknown。重载函数,输出到IPropertyBag中。没有值的属性不会被保存。例如<script defer>的defer,但是建议写成defer="defer"
  • StoreLineAndOffsetInfo。将对象的AA属性的行号等存储起来。
  • CElement::GetLineAndOffsetInfo。 从某个AA中返回某个属性的行号和偏移值。
  • CElement::ConnectEventHandler。从AA中获取指定的dispid对应的字符串,然后从这个字符串里构建一段代码,然后把对应的dispid和这段代码放置到AA中。当倒数第二个参数为TRUE时,表明这是普通的事件,例如onclick,AA中的AA_Attribute以dispid来区分;为FALSE时,代表dispid指代的是AA的AA_Expando节。
  • CElement::InitAttrBag。从CHtmTag里面取值放入bag中。bag就是AttrBag,存储属性-值对的一个“包”。
  • CElement::MergeAttrBag。将CHtmTag中任何不在attrbag的属性放到bag中。
  • CElement::Init2。 元素级别的初始化。
  • CElement::Save。将元素保存到流中。
  • CElement::SaveAttributes。 将属性保存到流/IPropertyBag中。
  • CElement::HandleMessage。 处理特定元素的消息。将对应消息转交给CLayOut去处理。
  • CElement::DisallowSelection。 当选区必须不能被选中时返回TRUE。对话框中,只有能编辑的部分可以被选中。
  • CElement::CloseErrorInfo。 调用Doc的CloseErrorInfo,以便返回它的clsid而不是控件的clsid。
  • CElement::SetCursorStyle。响应类似这样的onmouseover="this.style.cursor='pointer';this.style.cursor='hand'" 的"element.style.cursor"属性,在发生WM_CURSOR消息时处理消息。
  • IsIDMSuperscript/IsIDMSubscript/IsIDMBold/IsIDMCharAttr。 判断tag是否是上标(sup)/下标(sub)/加粗(b/strong)/文字(这样的话当用户选择移除文字格式时,可以判断这个元素是否可以删除)
  • IsIDMItalic/IsIDMUnderlined。判断tag是否为斜体(i)/下划线(u)
  • CElement::WriteTag。向流中写入一个完整的tag,也就是包括<xxx>www.nul.pw</xxx>的tag对。
  • CElement::scrollIntoView。 对ScrollIntoView的封装。
  • CElement::DeferScrollIntoView。 两个重载,延迟ScrollIntoView,其实是把消息post进队列了,也是ScrollIntoView的封装。
  • CElement::GetAtomTable。获取AtomTable,如果一个CElement没有atom table,那么可以证明它没有和CDoc关联。
  • CElement::GetPlainTextInScope。 返回这个CElement的所有文字部分,像是innerText的东西,这个函数的参数传入NULL的话返回的是文字长度。
  • CElement::MatchAccessKey。 接受键盘消息并判断是否为快捷键。
  • CElement::ShowMessage。 CElement::ShowMessageV的封装。
  • CElement::ShowMessageV。 CDoc的ShowMessageV的封装。
  • CElement::ShowLastErrorInfo。 CDoc的ShowLastErrorInfo的封装。
  • CElement::ShowHelp。 CDoc的ShowHelp的封装。
  • CElement::GetOmWindow。 返回CDoc的OmWindow成员。
  • CElement::UndoManager。CDoc的UndoManager的封装,获取撤销管理器的接口(IOleUndoManager)。
  • CElement::QueryCreateUndo。 CDoc的QueryCreateUndo的封装。判断是否应该建立一个撤销对象,会影响到CDoc的内容。
  • CElement::ShowTooltip。 显示站点的tooltip提示,如果有标题的话,那标题就是tooltip,可以把鼠标移动到tab上面看一看,一会儿出来的黄色方框就是tooltip。
  • CElementFactory::PrivateQueryInterface。 QueryInterface的实现。
  • CElementFactory::InvokeEx。 InvokeEx的实现。
  • CElement::MergeAttributes。 将指定元素的属性复制到另一个元素里面,只有相同元素名的元素才可以做此操作,这个操作发生在编辑元素时将一个元素以另一个替代时。merge的方式是将一个inline元素的aa给merge到另一个里面,对CAttrArray的Merge的封装。
  • CElement::GetNameSpaceParent。获取命名空间层次上的parent。通常发生在FORM里面,具体做法:从node的parent向上遍历所有branch到第一个form。当当前元素不在任何markup(即标识)中或者在主markup中,将返回omdoc作为命名空间,即返回Markup的IDispatchEx的query结果。非主markup时,返回Markup的DefaultDispatch的IDispatchEx。
  • CElement::CanShow。元素此时是否可以展示。加载完成时可以展示。
  • CElement::OnCssChangeStable。 当前元素在markup中时,向markup发送OnCssChange事件。
  • CElement::Invalidate。 Invalidate当前元素的区域。
  • CElement::GetElementRc。 返回元素位置。
  • CElement::ComputeHorzBorderAndPadding。 计算水平边框宽度和padding,位置计算辅助函数。
  • CElement::Clone。 字面意思,复制一个一模一样的元素。
  • CElement::StealAttributes。 从另一个元素里面“偷”来属性。计算完所有属性,创建完属性包之后如果需要替换元素的话,就会调用这个函数。
  • CElement::AddExtension。创建一个css扩展对象(css filter),alpha/blur/dropshadow这些滤镜,ie中它们是和一个clsid对应的,使用时都要CoCreateInstance创建对应实例才可以,其中,被实例化的对象必须标记为safe for initsafe for script,IPersistPropertyBag2或者IPersistPropertyBag中可以查询到SAFETY_INIT、SAFETY_SCRIPT的结果。
  • CElement::get_filters。返回filter collection。
  • CElement::toString。 BSTR转为CString。
  • CElement::setCapture。 设置鼠标捕获。
  • CElement::releaseCapture。 释放鼠标捕获。
  • CElement::WantTextChangeNotifications。返回该element是否应该有文本变换时的通知。
  • CElement::Notify。 通知处理函数。
  • CElement::EnterTree。 进入树,连接上事件处理器并保存。
  • CElement::ExitTree。 退出树,并发送notify,以及做维护工作。
  • CElement::IsEqualObject。 IObjectIdentity的实现,判断对象是否相等。
  • CElement::QueryService。 IServiceProvider的实现。
  • CElement::ContextThunk_Invoke。 IDispatch对应的Invoke。
  • CElement::ContextThunk_InvokeEx。 读取context(放到eax),然后传给ContextInvokeEx。
  • CElement::GetDispID。 获得DispID。
  • CElement::GetExpandoDispID。 获取Expando的Dispid。
  • CElement::GetNextDispID。 获取下一个DispID。
  • CElement::GetMemberName。 从dispid获取成员名。
  • CElement::GetMultiTypeInfoCount。 IProvideMultipleClassInfo的实现。
  • CElement::CreateTearOffThunk。 对::CreateTearOffThunk的封装。
  • CElement::get_dir/CElement::put_dir。 获取/设置dir属性。
  • CElement::GetLookasidePtr/CElement::SetLookasidePtr/CElement::DelLookasidePtr。当页面有LookasidePtr时,获取/设置/删除Lookasideptr。
  • CElement::put_onresize/CElement::set_onresize。 设置/获取onresize事件属性。
  • CElement::Fire_onfocus。 触发onfocus事件,FireEvent的封装,事件名是“focus”。
  • CElement::Fire_onblur。 触发onblur事件。
  • CElement::CreateAccObj。 根据不同的CElement tag(例如body、frameset、a,etc)创建CAccElement可访问对象。
  • CElement::GetParentAncestorSafe。 获取某个etag的祖先,有有效性判断。
  • CElement::GetIDHelper。 CElement的collection辅助函数。获取dispid。
  • CElement::SetIdentifierHelper。 辅助函数。 非script的标签,将所有关联的脚本全部unhook,设置name或者id,如果没有的话,从aa中获取指定的字符,命名设置标识符,然后hook上脚本。
    其中,第二个参数为DISPID_CElement_id、第三个参数为STDPROPID_XOBJ_NAME时设置ID,第2个参数为STDPROPID_XOBJ_NAME时设置NAME。第四个参数为STDPROPID_XOBJ_NAME时设置unique name(一个name只允许出现1次)。
  • CElement::SetIDHelper。 对SetIdentifierHelper的封装。设置ID。
  • CElement::GetnameHelper。 获取name。
  • CElement::SetnameHelper。 对SetIdentifierHelper的封装。设置name。

参考
layout http://msdn.microsoft.com/en-us/library/ie/ms530764(v=vs.85).aspx
layout #2 http://www.sitepoint.com/web-foundations/internet-explorer-haslayout-property/
ped 结构中的一个个体
node 节点
site 显示出来的一个实节点
style expression style中包含有expression表达式
clip:rect http://www.w3school.com.cn/cssref/pr_pos_clip.asp
list-style-image http://www.w3school.com.cn/cssref/pr_list-style-image.asp
ZParent, 按z-index排列的parent
non-site 是个元素,但是并没有展现出来(根本没有加入到文档里,而不是不可见之类的操作)
Block Element 块元素,块级元素生成一个元素框,(默认地)它会填充其父级元素的内容,旁边不能有其他元素。换句话说,他在元素框之前和之后生成了“分隔”符。我们最熟悉的HTML元素是p和div。
safe for initsafe for script http://msdn.microsoft.com/en-us/library/aa751977%28VS.85%29.aspx

Internet Explorer 完全解析 [1]

Internet Explorer 是一个广泛使用的浏览器,囿于微软的闭源策略,Internet Explorer的许多特性无法被人深入理解,现在,我要大概使用40章的篇幅来完全解析一下Internet Explorer的方方面面,最终我会给出修改好的pdf,40章我是随读随写的,所以不免会有问题,请以最终的pdf为准。

历史等介绍前略

文章来源 www.nul.pw

第1章 intro
1.1 Internet Explorer的结构
关于这部分,网上有一个很经典的图,大致描述起来就是 宿主-shdocvw.dll-mshtml.dll-html文档 的四层结构
mshtml是一个活动文档解释器,或者更通俗的说,mshtml负责解析html。既然是浏览器,无论是平时的分析还是漏洞测试,页面解析这块必然是重头戏,因此mshtml.dll是我这次首先开始解析的部分。

1.2 初识CBase
mshtml.dll中,可以作为“基类”存在的类为CBase,CBase实现了IDispatchEx、IProvideMultipleClassInfo、ISupportErrorInfo、IOleCommandTarget、ISpecifyPropertyPages这些基本的自动化接口,还有IObjectIdentity接口,用于判断两个对象是否相同。CBaseCF类实现了类厂(Class Factory)功能。

CDispParams类用来协助CBase的实现,它用来对DISPPARAMS进行二次封装(因此无疑它继承了DISPPARAMS),方便Invoke和InvokeEx这两个函数的调用(它们有一个参数是DISPPARAMS类型)。
CDispParams的成员变量都继承自父类DISPPARAMS,为rgvarg、rgdispidNamedArgs、cArgs、cNamedArgs。
关于DISPPARAMS的定义可以参见微软MSDN的介绍*1。其结构如下:

typedef struct FARSTRUCT tagDISPPARAMS {
  VARIANTARG FAR*  rgvarg; //参数数组
  DISPID FAR* rgdispidNamedArgs; //已命名参数的DISPID(Dispatch identifiers)
  unsigned int cArgs; //参数个数 
  unsigned int cNamedArgs; //已命名的参数的个数
} DISPPARAMS;

其成员函数及作用列举如下:
【1】HRESULT CDispParams::Create(DISPPARAMS*)
这个函数即会从DISPPARMS*中获取对应参数信息,参数数组的初始化值是VT_NULL,命名参数数组的初始化值是DISPID_UNKNOWN。

【2】HRESULT CDispParams::MoveArgsToDispParams (DISPPARAMS *pOutDispParams, UINT cNumArgs, BOOL fFromEnd)
这个函数的作用是把参数数组的内容给移动到pOutDispParams中。其实它虽然调用了memcpy,但是事实上只是把参数列表(CDispParams的成员变量:rgvarg[])的指针给复制到了pOutDispParams->rgvarg里,所以现在pOutDispParams和CDispParams事实上拥有了同样的对象(当然,释放的话只能释放一次)。最后一个参数指定了复制时是从最后(index: cArgs - cNumArgs)开始复制还是从最开始(index: 0)开始复制。

1.3 CBase的_pAA,大家的_pAA

在介绍CBase之前,也许非常有必要去解释一下常见的_pAA参数,这个参数实际上是“样式属性数组的指针”的缩写(pointer of attribute array),它是CAttrArray类的一个实例,之所以将它提前介绍,是因为_pAA就是元素的样式表,因此它的身影几乎贯穿在所有的类中间。CAttrArray继承于CAttrValue
CAttrValueflags数据结构如下:

  31              24  23              16  15             8  7           0
  -----------------------------------------------------------------------
  |                 |                   |                 |             |
  |     VT_TYPE     |              AAExtraBits            |    AATYPE   |
  |                 |                   |                 |             |
  -----------------------------------------------------------------------

struct AttrFlags {
    BYTE    _aaType;
    WORD    _aaExtraBits;
    BYTE    _aaVTType;
};

_aaType指定了这个AA是什么类型的,_aaExtraBits指定了这个AA的具体类型,例如important类型(“important”)、隐含式(“background: none”)之类的,最后一个_aaVTType指定VT_TYPE。这个类具体来说就是样式表的解析器。

CAttrArray继承自CAttrValue(CDataAry)(see formsary),CBase是它的友类。这个类综合了多个AttrValue,然后提供了一组函数便于查找、增删、对比和修改AttrValue的函数。

1.4 CBase的成员函数

  • CBase::CBase。构造类中初始化了所有变量。
  • CBase::CBaseCheckThread。
    如果CBase被线程A创建,那么只有A能访问这个创建的CBase对象。这个函数的用途是检查引用CBase对象的线程是否是创建这个被引用的CBase对象的线程。
  • CBase::Init。检查是否正确初始化。
  • CBase::Passivate。通过释放对其他对象的引用、清理资源使用来释放main对象。主对象引用数为0的时候会调用这个函数。主对象和所有子对象的引用为0时会调用析构函数。在析构时,Passivate还会删除_pAA对象。
  • CBase::IsEqualObject。实现了IObjectIdentity,判断一个对象是否和自身相同,具体方式:自己和待比较对象分别获取IID_IUnknown接口,比较最终结果是否相同。
  • CBase::PrivateQueryInterface。IPrivateUnknown中的实现,和每个IIDTable中的值相比较,实现查询指定栈接口功能。如果传递的是非栈接口,那么代表着很可能所有派生类都需要在各自的PrivateQueryInterface中处理这些消息。
  • CBase::PrivateAddRef/CBase::PrivateRelease。IPrivateUnknown中的实现,AddRef/ReleaseRef用。
  • CBase::SubAddRef/CBase::SubRelease。SubBase的Add/Release Ref用。
  • CBase::CLock::CLock/CBase::CLock::~CLock。增加/释放CBase对象中资源锁用的函数。
  • CBase::QueryService。空函数,需要由派生类重载。目的是为了查询Service,传入的参数分别是:service的GUID、service interface的IID、service的pointer of pointer。
  • CBase::GetPages。ISpecifyPropertyPages中的实现,用来指定这个对象可以接受的属性页,传来的参数指向一个UUID(CLSID)的数组。CLSID分为两个部分,每组都以NULL结尾,第一组用作浏览模式(run-mode),第二组用于编辑模式(design mode)。
  • CBase::HasPages。返回这个对象是否支持任何属性页,返回值是bool类型。其实就是检查property pages中是否有元素。
  • CBase:GetClassInfo。IProvideMultipleClassInfo的实现,返回控件的coclass 信息。
  • CBase:GetGUID。IProvideMultipleClassInfo的实现,接受两个参数,第一个是guid的类型,第二个是接受guid的缓冲区的指针,根据第一个guid类型返回对应的guid。
  • CBase::GetMultiTypeInfoCount。IProvideMultipleClassInfo的实现,返回这个对象上type info的数量。
  • CBase:GetAggMultiTypeInfoCount。IProvideMultipleClassInfo的聚合体(无构造函数、无非公开的成员函数、无基类、无虚函数的数组、类或者结构体*2)的帮助函数。函数接受聚合体的IUnkown接口,通过读取其IID_IProvideMultipleClassInfo接口获得一个IProvideMultipleClassInfo实例,然后调用其GetMultiTypeInfoCount获取其type info 的数量。
  • CBase:GetInfoOfIndex。IProvideMultipleClassInfo的实现,从index的type info中获取信息。
  • CBase::GetAggInfoOfIndex。在一个聚合体中,根据index获取一些type info。
  • CBase:InterfaceSupportsErrorInfo。ISupportErrorInfo的实现,当传入的接口(传入的是IID)支持错误信息的时候返回true。
  • CBase::GetClassID。 IPersist的实现。
  • CBase::InvokeDispatchWithThis。 它的作用是,接受IDispatch*的变量,通过把这个Dispatch的IUnknown加到参数列表里面来引用(invokes)它。它的动作为:
    1. 获取传入的pDisp的IDispatchEx(/IDispatch)接口;
    2. 将其信息保存到DISPPARAMS的rgvarg中去;
    3. 调用其InvokeEx(/Invoke)函数 (DISPID_VALUE);*3
    4. 如果需要传递这个参数,而且参数数量>1时,用原始参数替代现在的参数。
      其实,主要的就是在第三步 。
  • CBase::InvokeDispatchExtraParam。依然是invoke pDispatch,多了一个额外参数。
  • CBase::Invoke。 IDispatch中的标准实现。
  • CBase::GetIDsOfNames。 IDispatch中的标准实现。
  • CBase::InvokeAA。 辅助函数。
    类型为PROPERTY_GET、实际参数为0个的时候,从dispidMembers中找到对应的aaType,返回这个VARIANT。
    类型为PROPERTY_PUT、实际参数为1个的时候,dispMembers中找到对应的aaType,把已知(未知)的aaType替换掉(加到)dispMembers的ArgList里面的对应位置,通知dispidMember有OnPropertyChange。
    flags为GET/METHOD但是执行失败时,试着invoke一次。具体做法:dispMembers找到对应的aaType(返回AAINDEX),如果是已知的AAINDEX,获取该位置上对应的dispatch对象,然后调用InvokeDispatchWithThis来invoke。
  • CBase::ContextInvokeEx。真的InvokeEx实现,用传入的参数来调用函数。~
  • CBase::GetDispID。IDispatchEx中的实现。
  • CBase::DeleteMemberByName/CBase::DeleteMemberByDispID。 IDispatchEx中的实现。
  • CBase::GetMemberProperties/CBase::GetMemberName。IDispatchEx中的实现。后者返回ExpandoName或者ExposedName或者Name。
  • CBase::GetNextDispID。IDispatchEx中的实现。从ITypeInfo中遍历所有项目的值,然后枚举所有expando的属性。这个函数会枚举所有普通的property和所有expando。
  • CBase::GetNameSpaceParent。IDispatchEx的实现。
  • CBase::GetInternalDispID。 GetIDsOfName的封装。
  • CBase::GetExpandoDispID。 获取Expando的DispID。
  • CBase::GetInternalNextDispID。 获取NextDispID。
  • CBase::NextTypeInfoProperty。 获取下一个TypeInfo Property(通过INVOKE_PROPERTYGET/PUT来区分)。
  • CBase::AddExpando。 辅助函数, 将一个expando property添加到attr array中。
  • CBase::SetExpando。 辅助函数,顾名思义,Set Expando的property。
  • CBase::GetNextDispIDExpando。 获取dispid的下一个expando。~
  • CBase::GetExpandoName。 获取expando的名字。
  • CBase::NextProperty。 辅助函数,遍历用。遍历范围和GetNextDispID一样。
  • CBase::FindNextAttach。 遍历找到下一个attach event的attr array。
  • CBase::GetTheDocument。 从IID_IHTMLDocument2获取接口,然后从接口中获取IID_IDispatchEx,再获取"document"的dispID,然后Invoke,并从中返回一个IHTMLDocument2*,写入*ppDoc(传入时类型是IHTMLDocument2** ppDoc)。
  • CBase::FindEventName。 从event table中找到对应的Event。
  • CBase::FireEvent。 从主dispatch中触发一个与之关联的事件。仔细看一下这个函数。
    1.首先触发任何会导致这个event的property (除了OnError);
    2.触发任何附加的事件;
    3.触发任何通过普通连接点监听着的property,它们通过一个内部的dispid存在AA里;这一步里,遍历所有AA,然后存储起来,最后通过ITridentEventSink的FireEvent调用它们;
  • CBase::FireAttachEvents。触发所有通过attachEvent(脚本)附加的事件。
  • CBase::FireAttachEventV。FireAttachEvents的封装。根据arrayType将va_list转为DISPPARAMS。
  • CBase::FireEventV。跟上面的类似,FireEvent的封装。
  • CBase::FirePropertyNotify。 触发Property OnChanged或者OnRequestEdit事件。
  • CBase::FireCancelableEvent。试图触发事件,然后返回脚本的返回值。封装的是FireEventV。
  • CBase::GetEnabled/CBase::GetValid。 辅助函数,多数函数都会重载它。
  • CBase::FindAAType。 AttrArray的辅助函数。_pAA中查找AAIndex并返回查找到的序数。
  • CBase::DidFindAAIndexAndDelete。 查找到AA之后把它删除,对FindAAType的封装。
  • CBase::FindNextAAIndex。 通过给定的dispid和AAType查找AAIndex。
  • CBase::GetStringAt。 获取指定AAINDEX上的字符串。
  • CBase::GetIntoBSTRAt/CBase::GetIntoStringAt。 获取指定AAINDEX上的字符串到BSTR/LPCTSTR变量中。
  • CBase::GetPointerAt/CBase::GetVariantAt。获取指定AAINDEX上的指针/VARIANT。
  • CBase::FetchObject。 找到AttrValue对象后,如果类型是VT_UNKNOWN/VT_DISPATCH给其IUnknown/IDispatch接口增加1个计数。
  • CBase::GetObjectAt。 找到对象后调用FetchObject。
  • CBase::GetCookieAt/CBase::SetCookieAt。 找到对象后获取/设置其Cookie,Cookie是一个DWORD类型的变量。
  • CBase::GetAAtypeAt/CBase::GetVariantTypeAt。 找到对象后获取其AAType/VariantType。
  • CBase::Add*****。 各种添加函数,封装了Set函数。
  • CBase::AddUnknownObjectMultiple/CBase::AddDispatchObjectMultiple/CBase::AddDispatchObject。 把一个对象加入attr array,表明这个对象在指定的DISPID下允许有多个入口。(VT_UNKNOWN/VT_DISPATCH的区别)
  • CBase::StoreEventsToHook。 对CAttrValue的StoreEventsToHook的封装。
    1.先调用CAttrArray::EnsureHeader,这个函数的作用是将this转为CAttrValue*类型,然后获取自己的GetAVType (AttrValue Type),如果确认类型是CAttrValue::VT_AAHEADER,则返回转换后的CAttrValue*,否则,调用SetHeader()设置一个头,然后返回转换后的CAttrValue*,失败的话返回NULL。
    2.调用CAttrValue*的SetEventsToHook函数,传入这个函数的参数,类型为InlineEvts*。CAttrValue::SetEventsToHook其实就做了一件事:GetAAHeader()->_pEventsToHook = pEventsToHook;。
  • CBase::GetEventsToHook。与Set一样,最后获取到_pEventsToHook的值,返回。
  • CBase::ChangeSimpleAt。 修改指定AAIndex位置的VARIANT为一个VT_I4类型的值,值为long类型的dwSimple(由参数传入)。
  • CBase::ChangeStringAt/CBase::ChangeUnknownObjectAt/CBase::ChangeDispatchObjectAt/CBase::ChangeVariantAt/CBase::ChangeAATypeAt。 同上,换成了VT_LPWSTR/VT_UNKNOWN/VT_DISPATCH/some variant/some aatype。
  • CBase::FindVTableEntryForName。 从vtable中找到对应名字的项目并返回。返回VTABLEDESC*类型,这是一个结构体。
  • CBase::getAttribute。获取指定的DISPID,然后FindPropDescFromDispID从中找到Property返回。 ~
  • CBase::removeAttributeDispid/CBase::setAttribute。 从指定的DISPID删除/设置属性。
  • CBase::IsExpandoDISPID。 判断某个DISPID是否是一个Expando DISPID。
  • CBase::toString。将DISPID_VALUE获取到的内容转为 BSTR。
  • CBase::FindPropDescFromDispID。通过DISPID找到PROPERTYDESC。 先获取vtable array(struct VTABLEDESC*),然后从property desc中找到对应的dispid。
  • CBase::DefaultMembers。 调用Default将所有prop desc list的所有类成员设置为成员。
  • CBase::GetEnumDescFromDispID。IPerPropertyBrowsing的辅助函数,从给定的dispid(property)中获取enumdesc。
  • CBase::GetDisplayString。 IPerPropertyBrowsing::GetDisplayString,获取指定的dispid(property)对应的字符串值。
  • CBase::MapPropertyToPage。IPerPropertyBrowsing::MapPropertyToPage的实现。
  • CBase::GetPredefinedStrings。 IPerPropertyBrowsing::GetPredefinedStrings的实现。返回一个带字符数量的string array(也即LPOLESTRS指针),里面是支持的所有可用的dispid(property)。
  • CBase::GetPredefinedValue。 IPerPropertyBrowsing::GetPredefinedValue的实现。从某个dispid返回关联到某个预定义字符串的variant。
  • CBase::attachEvent。把一个AA_AttachEvent添加到attrarray里面,以支持多播onXXXX事件。~
  • CBase::detachEvent。遍历attrarray中的AA_AttachEvent,移除第一个发现的COM标识和传入的pDisp一样的项目。
  • STDAPI MatchExactGetIDsOfNames。遍历ITypeInfo里面的所有property,导出函数,shdocvw.dll会使用。
  • LONG GetArgsActual。 辅助函数,从dispparms的cArgs中获取准确的参数数量;如果有DISPID_THIS的话,数量减一。

1.5 总结:CBase
CBase类是mshtml.dll中提供的一个基础类,提供了许多功能函数的支持,同时也支持重要的事件操作(例如支持javascript中的addEventListener) ,之后还会看到很多与之有关联的类,本节到此结束。

引用
1 http://msdn.microsoft.com/en-us/library/aa912051.aspx
2 http://msdn.microsoft.com/en-us/library/sdwe79a4.aspx
3 http://msdn.microsoft.com/en-us/library/asd22sd4(v=vs.94).aspx
引申
expando http://msdn.microsoft.com/en-us/library/ie/ms533747(v=vs.85).aspx
attachEvent http://msdn.microsoft.com/en-us/library/ie/ms536343(v=vs.85).aspx
标记
~ wait for further analysis
property 可以简单的理解为dispid