Mozilla FireFox Gecko内核源代码解析
(5.CNavDTD)
中科院计算技术研究所网络数据科学与工程研究中心
信息抽取小组
耿耘
上一章中我们介绍了nsHTMLTokens,再加上之前介绍的nsHTMLTokenizer,我们了解到火狐的HTML解析器首先HTML源代码进行分词并整理成一个个Token,这些Token是我们后面建立DOM树(火狐中叫做ContentModel)的源数据。
在对nsHTMLTokens的分析中我们得知,火狐会对字符级的错误等进行修正,并且对各个类型的HTML TAG标签进行基本的文法检查,比如注释类型即可以由<!--开始,也可以由<!-开始等等,而Entity型既可以由&加字母组成,也可以由&加上十六进制或者十进制的数字所组成。除此之外,Tokenizer还会对整体的队列进行一次检查,确保每一个开始型TAG都有与其对应的结束型TAG相对应。比如<table><tr><td></td></tr>会自动被补完成为<table><tr><td></td></tr></table>。这些操作都在词法解析时所完成。
然而词法方面的校验只是确保了HTML解析中第一步分词的正确。下一步则是更重要的语法校验。比如我们都知道<tr>只能在<table>中进行使用,然而HTML是一种松散语法的语言,其并没有严格的语法限制,取而代之的是语法纠错,因此即使在<table>之外使用了<tr>也无所谓,页面会自动对其进行修正,然后修正的方式则由浏览器自己决定。
语法校验的规则根据不同的浏览器厂商,不同的DTD声明而不同。由于DTD的多种多样,面对几十甚至上百种的DTD,浏览器不可能一一对其进行判断,而只能将这些DTD进行分类,并对其进行处理。Firefox的语法解析功能代码的第一步就是在CNavDTD.h和CNavDTD.cpp这两个文件中所实现(当然它们要去调用其他文件中的一些方法)。我们通过学习和分析这些文件,便可了解这一著名的浏览器是如何对HTML语法进行识别和处理的。
正是对不同DTD的不同处理,导致了浏览器之间的兼容性问题,Firefox中只拥有3种解析模式,quirk,strict以及view source。而IE由于不公开源代码,很难判定其究竟拥有几种解析模式,但是从其版本和官方公布的数据来看,自其IE5以来,除了IE5采用的是quirk模式,其他的IE 6,7,8,9都是采用的strict模式,而IE 10也即将公布。其解析模式非常不同,如IE 7和IE 9的解析模式就存在很大区别,另外对于html5的支持上也不同。
这一部分代码的逻辑较难理解,主要是对一些语法的二义性的处理上较难把握,代码作者也表示很多地方处理的不一定完善。并且全文充斥着对各种被发现的bug的处理和修复,可见HTML的语法解析是一件不容易的事情。
我们首先来看头文件CNavDTD.h,其开头有一大段模块介绍的语句,其对本模块的内容和作用等做了介绍。
模块简介:
NavDTD是一个对nsIDTD接口的实现。尤其是这个类捕获了最初Navigator解析器产品的行为特点。
这个DTD,像NGLayout中的其他类一样,提供了一些基本的服务:
一.DTD和Parser进行协作,将HTML的纯文本转换成一系列HTML Tokens。
二.DTD描述了所有已知元素的包含规则。
三.DTD控制着解析系统和Content Sink之间的交互。(content sink是一个为Content Model进行代理服务的接口)
四.DTD通过维护一个内部的sytle栈来处理residual style标签。
RESIDUAL-STYLE的处理方式:
在HTML文档中有几种表达style的模式:
1) 显式的style标签(如<B>,<I>等等)
2) 隐式的style标签(如<Hn>等)
3) 基于CSS的style
Residual sytle来自于那些没有被关闭的显式style标签。考虑这个例子:<p>text<b>bold</p>当<p>标签关闭时,<b>标签并没有自动地被关闭。没有被关闭的标签便会由我们称之为遗漏标签处理的机制来处理。
对于遗漏标签的处理有两方面。首先是建立并维护一个residual style的栈,其次是自动将遗漏的style标签放到在HTML文档中之后出现的叶节点上。这一步对于能否正确地将预期的style传播给文档后面的部分而言十分重要。
建立和维护residual style栈是在解析步骤中的建立模型过程中的一步内联(被反复使用的)操作。在模型建立过程中始终维护着一个Content栈,并且这个栈会跟踪被打开的容器间的继承关系。如果一个(或许多)style标签在普通容器关闭时没能正确关闭,这个style标签就会被放置到栈中。若这个style标签在后续的文本中被关闭了(在大多数上下文中都是这样的),则会将其从栈顶弹出,并无需继续关注它。
“发射”residual style只会在style栈不为空的时候发生,并且遇到了叶节点类型的内容。在我们之前的例子中,<b>标签从<p>标签的容器中“漏了出来”。在下一个叶子节点出现之时(在当前或者其他容器中),在栈中的style标签会成功地被发射给它。这些相同的residual style在叶节点的容器关闭时,或者又打开了一个子容器时自动关闭。
上面这段注释可能比较难以理解,尤其是RS(Residual Style)的处理,后面我们通过分析代码慢慢进行说明。其实大部分的包含规则已经放置在了nsElementTable中,并且一些相应的支持方法放在了nsDTDUtil中,我们后面会对这两个文件进行相应的解析工作,如果想要最系统和最好地了解语法解析的流程,最好是将这三个文件一同进行解析,但限于文档限制,我们会依次对这些进行解析。
首先我们来看CNavDTD.h的头文件:
#ifndefNS_NAVHTMLDTD__ #defineNS_NAVHTMLDTD__ #include "nsIDTD.h" #include "nsISupports.h" #include "nsIParser.h" #include "nsHTMLTags.h" #include "nsDeque.h" #include "nsParserCIID.h" #include "nsTime.h" #include "nsDTDUtils.h" #include "nsParser.h" #include "nsCycleCollectionParticipant.h" classnsIHTMLContentSink; classnsIParserNode; classnsDTDContext; classnsEntryStack; classnsITokenizer; classnsCParserNode; classnsTokenAllocator; /*************************************************************** Now the main event: CNavDTD. This not so simple class performs all theduties of token construction and model building. It works inconjunction with an nsParser. ***************************************************************/ //没有任何一个简单的类可以完成所有token建立和model建立的工作。它始终都需要和nsParser进行配合来完成这一任务。 #ifdef_MSC_VER #pragma warning( disable :4275 ) #endif classCNavDTD : public nsIDTD { #ifdef_MSC_VER #pragma warning( default :4275 ) #endif public: /** * Common constructor for navdtd. You probably want to call * NS_NewNavHTMLDTD(). */ //首先是navdtd的通用构造函数。你可能希望用NS_NewNavHTMLDTD()的方式进行调用。 CNavDTD(); virtual ~CNavDTD(); /** * This method is offered publically forthe sole use from * nsParser::ParseFragment. In general, youshould prefer to use methods * that are directly on nsIDTD, since thosewill be guaranteed to do the * right thing. * * @param aNode The parser node thatcontains the token information for * this tag. * @param aTag The actual tag that is beingopened (should correspond to * aNode. * @param aStyleStack The style stack thataNode might be a member of * (usually null). */ //这个方法主要是提供来被nsParser::ParseFragment所调用。通常来讲,你可能更喜欢使用直接作用于nsIDTD的方法,因为那些方法保证了肯定会进行正确的操作。 //aNode 包含了这个tag所有token信息的解析节点 //aTag 被真正打开了的tag(应当和aNode相关联) //aStyleStack 一个style栈,aNode可能是它的成员之一(通常此栈为空) nsresult OpenContainer(constnsCParserNode *aNode, eHTMLTags aTag, nsEntryStack*aStyleStack = nsnull); //一些nsCOM构件的预定义方法,可查看nsISupportImpl.h NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_NSIDTD NS_DECL_CYCLE_COLLECTION_CLASS(CNavDTD) private: /** * This method is called to determine whether or not a tag * of one type can contain a tag of another type. * * @param aParent Tag of parentcontainer * @param aChild Tag of childcontainer * @return PR_TRUE if parent cancontain child */ //这个方法被调用来确认一种类型的tag能否包含另一种类型的tag。 PRBool CanPropagate(eHTMLTags aParent, eHTMLTags aChild, PRInt32aParentContains); /** * This method gets called to determine whether a given * child tag can be omitted by the given parent. * * @param aParent Parent tag beingasked about omitting given child * @param aChild Child tag beingtested for omittability by parent * @param aParentContains Can be0,1,-1 (false,true, unknown) * XXX should bePRInt32, not PRBool * @return PR_TRUE if given tag canbe omitted */ //这个方法用来确定一个给定的子节点tag能否被给定的父节点所”忽略”(即虽然父节点不应包含它,但还是可以强行进行包含) PRBool CanOmit(eHTMLTags aParent, eHTMLTags aChild, PRInt32&aParentContains); /** * Looking at aParent, try to see if we canpropagate from aChild to * aParent. If aParent is a TR tag, thensee if we can start at TD instead * of at aChild. * * @param aParent Tag type of parent * @param aChild Tag type of child * @return PR_TRUE if closure was achieved -- otherwise false */ //以aParent的视角,看看我们能否从aChild节点延展到aParent。如果aParent是一个TR标签,那么看看我们能能否从TD而不是aChild开始。 PRBool ForwardPropagate(nsString& aSequence, eHTMLTags aParent, eHTMLTags aChild); /** * Given aParent that does not containaChild, starting with aChild's * first root tag, try to find aParent. Ifwe can reach aParent simply by * going up each first root tag, thenreturn true. Otherwise, we could not * propagate from aChild up to aParent, so returnfalse. * * @param aParent Tag type of parent * @param aChild Tag type of child * @return PR_TRUE if closure was achieved -- other false */ //给出一个不包含aChild节点的aParent,从aChild的第一个根节点开始,尝试找到aParent。如果我能够每次只通过第一个根节点而找到aParent,那么则返回true。否则的话,则说明我们无法从aChild递推到aParent,因此返回false。 PRBool BackwardPropagate(nsString& aSequence, eHTMLTags aParent, eHTMLTags aChild) const; /** * Attempt forward and/or backwardpropagation for the given child within * the current context vector stack. Andactually open the required tags. * * @param aParent The tag we're trying to open this element inside of. * @param aChild Type of child to be propagated. */ //尝试正向并/或反向递推一个给定的位于当前的上下文向量栈的child。并且真正创建并打开所需的tags。 voidCreateContextStackFor(eHTMLTags aParent, eHTMLTags aChild); /** * Ask if a given container is openanywhere on its stack * * @param id of container you want to test for * @return TRUE if the given container type is open -- otherwise FALSE */ //询问一个给定的容器是否在它的栈上处于打开状态 PRBool HasOpenContainer(eHTMLTags aContainer) const; /** * This method allows the caller todetermine if a any member * in a set of tags is currently open. * * @param aTagSet A set of tags you care about. * @return PR_TRUE if any of the members of aTagSet are currently open. */ //这个方法允许调用者判断在给定的一系列tag中是否有任何一个成员处于打开状态。 PRBool HasOpenContainer(const eHTMLTags aTagSet[], PRInt32 aCount)const; /** * Accessor that retrieves the tag type ofthe topmost item on the DTD's * tag stack. * * @return The tag type (may be unknown) */ //一个可以用来取得DTD的tag栈中最顶端元素的tag 类型的方法。 eHTMLTags GetTopNode() const; /** * Finds the topmost occurrence of giventag within context vector stack. * * @param tag to be found * @return index of topmost tag occurrence -- may be -1 (kNotFound). */ //在当前的上下文向量栈中找到第一个符合指定的tags中任何一个元素的位置 PRInt32 LastOf(eHTMLTags aTagSet[], PRInt32aCount) const; //这个是非常重要的,统一处理Token的入口性质的方法 nsresult HandleToken(CToken* aToken); /** * This method gets called when a start token has been * encountered in the parse process. If the current container * can contain this tag, then add it. Otherwise, you have * two choices: 1) create an implicit container for this tag * to be stored in * 2) close the top container, andadd this to * whatever container ends up on top. * * @param aToken -- next (start)token to be handled * @return Whether or not we shouldblock the parser. */ //这个方法每当在解析过程中遇到了一个起始型tag时就会被调用。如果当前的容器可以包含这个tag,那么就加入它。否则的话你有两个选择:1.为这个tag创建一个单独的容器来包含它。2.关闭最顶端的容器,并且将这个节点加入到之后位于顶端的任意容器中。 nsresult HandleStartToken(CToken* aToken); /** * This method gets called when a start token has been * encountered in the parse process. If the current container * can contain this tag, then add it. Otherwise, you have * two choices: 1) create an implicit container for this tag * to be stored in * 2) close the top container, andadd this to * whatever container ends up ontop. * * @param aToken Next (start) tokento be handled. * @param aChildTag The tagcorresponding to aToken. * @param aNode CParserNode representingthis start token * @return A potential request toblock the parser. */ //同上,但参数有所不同,其实这个是被前一个方法所调用的,算是对大多数节点的一种处理方法,下面还跟了许多对特殊类型的节点的处理方法 nsresult HandleDefaultStartToken(CToken* aToken, eHTMLTags aChildTag, nsCParserNode *aNode); //接下来是一系列处理不同类型Token的方法:结束型标签,实体型标签,注释型标签,属性型标签,指示型标签,文档类型声明类标签。 nsresult HandleEndToken(CToken*aToken); nsresult HandleEntityToken(CToken* aToken); nsresult HandleCommentToken(CToken* aToken); nsresult HandleAttributeToken(CToken* aToken); nsresult HandleProcessingInstructionToken(CToken* aToken); nsresult HandleDocTypeDeclToken(CToken* aToken); //处理那些被“暂时忽略”的标签,这个方法我没找到在哪里进行了调用,该方法对某个特殊tag进行压入tokenizer,之后单独地直接进行buildModel nsresult BuildNeglectedTarget(eHTMLTags aTarget, eHTMLTokenTypes aType); //两个类似于初始化的方法,主要是在开始处理HTML和BODY标签时被调用,打开相应的容器 nsresult OpenHTML(const nsCParserNode*aNode); nsresultOpenBody(const nsCParserNode *aNode); /** * The special purpose methodsautomatically close * one or more open containers. * @return error code - 0 if all went well. */ //下面这些方法主要是用来关闭一个或多个容器 nsresult CloseContainer(const eHTMLTagsaTag, PRBool aMalformed); nsresult CloseContainersTo(eHTMLTags aTag, PRBool aClosedByStartTag); nsresult CloseContainersTo(PRInt32 anIndex, eHTMLTags aTag, PRBool aClosedByStartTag); //关闭某节点的RS栈 nsresult CloseResidualStyleTags(consteHTMLTags aTag, PRBoolaClosedByStartTag); /** * Causes leaf to be added to sink atcurrent vector pos. * @param aNode is leaf node to be added. * @return error code - 0 if all went well. */ //让叶节点在当前的位置上被添加到sink中 nsresultAddLeaf(const nsIParserNode *aNode); //在Head中添加叶节点的方法 nsresultAddHeadContent(nsIParserNode *aNode); /** * This set of methods is used to createand manage the set of * transient styles that occur as a resultof poorly formed HTML * or bugs in the original navigator. * * @param aTag -- represents the transient style tag to be handled. * @return error code -- usually 0 */ //这一系列方法是用来处理那些由于HTML格式不对或者原始navigator浏览器中存在的bug而出现的临时style的。 //这些方法比较复杂和难以理解,因为它们主要是处理各种错误格式的HTML的,因为错误的类型多种多样,因此处理方法也十分复杂 nsresult OpenTransientStyles(eHTMLTags aChildTag, PRBoolaCloseInvalid = PR_TRUE); void PopStyle(eHTMLTags aTag); nsresult PushIntoMisplacedStack(CToken* aToken) { NS_ENSURE_ARG_POINTER(aToken); //确保指针有效 //设置aToken的行数为0,注意到我们已经对这些token的新行数进行了计算 aToken->SetNewlineCount(0); // Note: Wehave already counted the newlines for these tokens //将aToken推入非法内容的栈中 mMisplacedContent.Push(aToken); return NS_OK; } protected: //收集这些节点的属性,然后将这些属性添加入节点 nsresult CollectAttributes(nsIParserNode* aNode,eHTMLTags aTag,PRInt32 aCount); /** * This gets called before we've handled agiven start tag. * It's a generic hook to let us do preprocessing. * * @param aToken contains the tag in question * @param aTag is the tag itself. * @param aNode is the node (tag) with associated attributes. * @return NS_OK if we should continue, a failure code otherwise. */ //这个方法在即将处理起始型节点的时候被调用。主要用来做预处理。 nsresult WillHandleStartTag(CToken*aToken,eHTMLTags aChildTag,nsIParserNode& aNode); //这个方法是在处理起始型标签结束后被调用 nsresult DidHandleStartTag(nsIParserNode&aNode,eHTMLTags aChildTag); /** * This method gets called when a start token has been encountered that * the parent wants to omit. It stashes it in the current element if that * element accepts such misplaced tokens. * * @param aToken Next (start) tokento be handled * @param aChildTag id of the childin question * @param aParent id of the parentin question * @param aNode CParserNoderepresenting this start token */ //这个方法当一个遇到一个起始型标签,并且父节点希望忽略其时被调用。如果当前的节点元素可以接受这样的非法标签,则会将它保存到当前的节点中去。 void HandleOmittedTag(CToken* aToken,eHTMLTags aChildTag, eHTMLTagsaParent, nsIParserNode *aNode); //这个方法用来在table标签结束时,对一些和table相关的内容错误的标签进行处理 nsresult HandleSavedTokens(PRInt32 anIndex); //处理keyGen标签(笔者注:在这里处理HTML5的标签么?) nsresult HandleKeyGen(nsIParserNode *aNode); //处理诸如noscript,noframe等转换标签 PRBool IsAlternateTag(eHTMLTags aTag); //判断给定的tagID是否是block级元素 PRBool IsBlockElement(PRInt32 aTagID,PRInt32 aParentID) const; //判断给定的tagID是否是inline级元素 PRBool IsInlineElement(PRInt32 aTagID,PRInt32 aParentID)const; //用来存放格式错误的内容的队列 nsDeque mMisplacedContent; //用来建立content model的接口 nsCOMPtr<nsIHTMLContentSink>mSink; //用来分配token的分配器 nsTokenAllocator* mTokenAllocator; //位于body元素中时的上下文 nsDTDContext* mBodyContext; //临时变量,用来记录上下文 nsDTDContext* mTempContext; //记录当前代码解析的总行数 PRBool mCountLines; //分词器,弱关联 nsITokenizer* mTokenizer; //weak //当前文件名 nsString mFilename; //临时字符串变量,多种用途(主要用在进行延展的时候记录延展路径所用) nsString mScratch; //used for variouspurposes; non-persistent //记录MimeType nsCString mMimeType; //节点分配器 nsNodeAllocator mNodeAllocator; //当前的DTD模式 nsDTDMode mDTDMode; //当前的DocType eParserDocType mDocType; //当前的解析器命令,如view content/viewsource/viewerror等 eParserCommands mParserCommand; //tells us toviewcontent/viewsource/viewerrors... PRInt32 mLineNumber; //记录当前正在解析的行数 PRInt32 mOpenMapCount; PRInt32 mHeadContainerPosition; //记录head标签的容器所在的下标 PRUint16 mFlags; //标示位,用来记录多种属性 }; #endif //以上就是CNavDTD.h的所有内容,下面我们来看其内部的实现代码CNavDTD.cpp。 //首先是一大堆的include #include "nsDebug.h" #include "nsIAtom.h" #include "CNavDTD.h" #include "nsHTMLTokens.h" #include "nsCRT.h" #include "nsParser.h" #include "nsIHTMLContentSink.h" #include "nsScanner.h" #include "prenv.h" #include "prtypes.h" #include "prio.h" #include "plstr.h" #include "nsDTDUtils.h" #include "nsHTMLTokenizer.h" #include "nsTime.h" #include "nsParserNode.h" #include "nsHTMLEntities.h" #include "nsLinebreakConverter.h" #include "nsIFormProcessor.h" #include "nsTArray.h" #include "nsReadableUtils.h" #include "nsUnicharUtils.h" #include "prmem.h" #include "nsIServiceManager.h" #ifdefNS_DEBUG #include"nsLoggingSink.h" #endif /* * Ignore kFontStyle and kPhrase tags when thestack is deep, bug 58917. */ #defineFONTSTYLE_IGNORE_DEPTH (MAX_REFLOW_DEPTH * 80 / 100) #definePHRASE_IGNORE_DEPTH (MAX_REFLOW_DEPTH *90 / 100) staticNS_DEFINE_CID(kFormProcessorCID, NS_FORMPROCESSOR_CID); #ifdefDEBUG static const char kNullToken[] = "Error: Null tokengiven"; static const char kInvalidTagStackPos[] = "Error:invalid tag stack position"; #endif //请注意这个问题,这是定了了所有htmlelement属性的文件,我们之后所要分析的就是这个文件 #include "nsElementTable.h" #defineSTART_TIMER() #defineSTOP_TIMER() // Some flags for use by theDTD. //一些用于DTD的标示位 #defineNS_DTD_FLAG_NONE 0x00000000 #defineNS_DTD_FLAG_HAS_OPEN_HEAD 0x00000001 #defineNS_DTD_FLAG_HAS_OPEN_BODY 0x00000002 #defineNS_DTD_FLAG_HAS_OPEN_FORM 0x00000004 #defineNS_DTD_FLAG_HAS_EXPLICIT_HEAD 0x00000008 #defineNS_DTD_FLAG_HAD_BODY 0x00000010 #defineNS_DTD_FLAG_HAD_FRAMESET 0x00000020 #defineNS_DTD_FLAG_ENABLE_RESIDUAL_STYLE 0x00000040 #defineNS_DTD_FLAG_ALTERNATE_CONTENT 0x00000080// NOFRAMES, NOSCRIPT #defineNS_DTD_FLAG_MISPLACED_CONTENT 0x00000100 #defineNS_DTD_FLAG_IN_MISPLACED_CONTENT 0x00000200 #defineNS_DTD_FLAG_STOP_PARSING 0x00000400 #defineNS_DTD_FLAG_HAS_MAIN_CONTAINER (NS_DTD_FLAG_HAD_BODY | \ NS_DTD_FLAG_HAD_FRAMESET) //一些预定义好的nsCOM组件接口定义 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CNavDTD) NS_INTERFACE_MAP_ENTRY(nsIDTD) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDTD) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(CNavDTD) NS_IMPL_CYCLE_COLLECTING_RELEASE(CNavDTD) NS_IMPL_CYCLE_COLLECTION_1(CNavDTD,mSink) //初始化构造函数,注意初始化的内容,DTDMode被设置为了quirk模式,而ParserCommand设置为了eViewNormal CNavDTD::CNavDTD() : mMisplacedContent(0), mTokenAllocator(0), mBodyContext(new nsDTDContext()), mTempContext(0), mCountLines(PR_TRUE), mTokenizer(0), mDTDMode(eDTDMode_quirks), mDocType(eHTML_Quirks), mParserCommand(eViewNormal), mLineNumber(1), mOpenMapCount(0), mHeadContainerPosition(-1), mFlags(NS_DTD_FLAG_NONE) { } //这一部分是用来调试用的,并且是用来对Log sink输出的,不需进行太多解释。 #ifdefNS_DEBUG static nsLoggingSink* GetLoggingSink() { // This returns a content sink that is usefulfor following what calls the DTD // makes to the content sink. static PRBool checkForPath = PR_TRUE; static nsLoggingSink *theSink = nsnull; static const char* gLogPath = nsnull; if (checkForPath) { // Only check once per run. gLogPath =PR_GetEnv("PARSE_LOGFILE"); checkForPath = PR_FALSE; } if (gLogPath && !theSink) { static nsLoggingSink gLoggingSink; PRIntn theFlags = PR_CREATE_FILE | PR_RDWR; // Open the record file. PRFileDesc *theLogFile = PR_Open(gLogPath,theFlags, 0); gLoggingSink.SetOutputStream(theLogFile,PR_TRUE); theSink = &gLoggingSink; } return theSink; } #endif //析构函数 CNavDTD::~CNavDTD() { delete mBodyContext; delete mTempContext; #ifdefNS_DEBUG if (mSink) { nsLoggingSink *theLogSink =GetLoggingSink(); if (mSink == theLogSink) { theLogSink->ReleaseProxySink(); } } #endif } //这个方法是在进行Model建立之前所进行调用的 NS_IMETHODIMP CNavDTD::WillBuildModel(const CParserContext& aParserContext, nsITokenizer*aTokenizer, nsIContentSink* aSink) { nsresult result = NS_OK; mFilename = aParserContext.mScanner->GetFilename(); //设置通过mScanner中获取并设置文件名 mFlags = NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE; //设置当前是否启用了residual style mLineNumber = 1; //设置当前的行号为1 mDTDMode = aParserContext.mDTDMode; //通过ParserContext设置当前的DTD模式 mParserCommand = aParserContext.mParserCommand; //通过ParserContext设置当前的解析命令 mMimeType = aParserContext.mMimeType; //通过ParserContext设置当前的MimeType mDocType = aParserContext.mDocType; //通过ParserContext设置当前的DocType mTokenizer = aTokenizer; //获取参数传递过来的Tokenizer mBodyContext->SetNodeAllocator(&mNodeAllocator); //为DTD设置Node分配器 if(!aParserContext.mPrevContext && aSink) { //如果之前的Context为空,并且还传递过来了一个aSink作为参数 if (!mSink) { //如果当前的mSink为空 mSink = do_QueryInterface(aSink, &result); //通过接口获取aSink并设置为mSink if (NS_FAILED(result)) { //如果获取失败 mFlags |= NS_DTD_FLAG_STOP_PARSING; //设置mFlag为停止解析 return result; //返回这个失败的结果 } } // Let's see if the environment is set up forus to write output to // a logging sink. If so, then we'll createone, and make it the // proxy for the real sink we're given from the parser. //下面是进行调试时所运行的代码,这里我们不需要太关注,看一下即可 #ifdefNS_DEBUG nsLoggingSink *theLogSink =GetLoggingSink(); if (theLogSink) { theLogSink->SetProxySink(mSink); mSink = theLogSink; } #endif mFlags |= nsHTMLTokenizer::GetFlags(aSink); //继承aSink的Flag } return result; //返回结果 } //下面是真正建立Model的方法 NS_IMETHODIMP CNavDTD::BuildModel(nsITokenizer*aTokenizer, PRBool aCanInterrupt, PRBool aCountLines, constnsCString*) { NS_PRECONDITION(mBodyContext != nsnull, //判断mBody上下文不能为空 "Createa context before calling build model"); nsresult result = NS_OK; if (!aTokenizer) { //如果tokenizer为空 return NS_OK; //返回成功结果 } nsITokenizer* const oldTokenizer =mTokenizer; //记录上一次的分词器 mCountLines = aCountLines; //设置CountLines mTokenizer = aTokenizer; //设置当前的分词器 mTokenAllocator = mTokenizer->GetTokenAllocator(); //获取Token分配器 if (!mSink) { //如果mSink为空 return (mFlags & NS_DTD_FLAG_STOP_PARSING) ? NS_ERROR_HTMLPARSER_STOPPARSING : result; //返回结果 } if (mBodyContext->GetCount() == 0) { //如果当前的mBodyContext为空 CToken* tempToken; if (ePlainText == mDocType) { //如果当前的DocType为ePlainText //创建一个新的起始型标签 tempToken = mTokenAllocator->CreateTokenOfType(eToken_start, eHTMLTag_pre); if (tempToken) { mTokenizer->PushTokenFront(tempToken); //并且将放置到队列首位 } } // Always open a body if frames are disabled. //只要frames被禁用了,则必须打开一个body容器 if (!(mFlags &NS_IPARSER_FLAG_FRAMES_ENABLED)) { //创建body标签 tempToken = mTokenAllocator->CreateTokenOfType(eToken_start, eHTMLTag_body, NS_LITERAL_STRING("body")); if (tempToken) { mTokenizer->PushTokenFront(tempToken); //将其放置到队首位置 } } // If the content model is empty, then begin by opening<html>. //如果content model是空的,则打开一个<html>标签 CStartToken* theToken = (CStartToken*)mTokenizer->GetTokenAt(0); if (theToken) { //若获取成功,说明当前tokens不为空 //获取该tag的TypeID eHTMLTags theTag = (eHTMLTags)theToken->GetTypeID(); //获取该tag的tokenType eHTMLTokenTypes theType = eHTMLTokenTypes(theToken->GetTokenType()); //如果该tag类型不为html并且或tag不为起始型类型 if (theTag != eHTMLTag_html || theType!= eToken_start) { //创建一个html标签 tempToken = mTokenAllocator->CreateTokenOfType(eToken_start, eHTMLTag_html, NS_LITERAL_STRING("html")); if (tempToken) { mTokenizer->PushTokenFront(tempToken); //并且将其放置到队首 } } } else { //说明content model是空的,打开一个html标签即可 tempToken = mTokenAllocator->CreateTokenOfType(eToken_start, eHTMLTag_html, NS_LITERAL_STRING("html")); if (tempToken) { mTokenizer->PushTokenFront(tempToken); } } } //下面是真正依次对token进行处理的循环 while (NS_SUCCEEDED(result)) { //循环直到result为假 if (!(mFlags &NS_DTD_FLAG_STOP_PARSING)) { //判断是否要终止parsing CToken* theToken = mTokenizer->PopToken(); //从Tokenizer获取一个Token if (!theToken) { //如果获取失败 break; //则退出循环 } result = HandleToken(theToken); //处理这个token } else { result = NS_ERROR_HTMLPARSER_STOPPARSING; //此时解析器被终止,则退出解析 break; } //下面调用mSink的Did方法结束Token处理 if (NS_ERROR_HTMLPARSER_INTERRUPTED ==mSink->DidProcessAToken()) { // The content sink has requested that DTDinterrupt processing tokens // So we need to make sure the parser is in astate where it can be // interrupted (e.g., not in adocument.write). // We also need to make sure that aninterruption does not override // a request to block the parser. //此时content sink要求DTD终止对tokens的处理。因此我们需要确保parser此时处于一个可以被中断的状态(也就是说,不在一个document.write过程中)。 //我们还需要确保这个中断不会阻止一个请求阻断parser的请求 if (aCanInterrupt &&NS_SUCCEEDED(result)) { result = NS_ERROR_HTMLPARSER_INTERRUPTED; break; } } } mTokenizer = oldTokenizer; //恢复mTokenizer为原始的Tokenizer return result; //返回处理的结果 } //创建一个“被忽略了”的标签(其实就是创建一个标签并且放在队列头部) nsresult CNavDTD::BuildNeglectedTarget(eHTMLTagsaTarget, eHTMLTokenTypesaType) { //确保分词器不为空 NS_ASSERTION(mTokenizer, "tokenizer is null! unable to build target."); //确保词分配器不为空 NS_ASSERTION(mTokenAllocator, "unableto create tokens without an allocator."); //如果都为空 if (!mTokenizer || !mTokenAllocator) { return NS_OK; //直接返回 } //创建一个aType类型的Token CToken* target = mTokenAllocator->CreateTokenOfType(aType, aTarget); //如果创建失败则可能是没有内存了 NS_ENSURE_TRUE(target, NS_ERROR_OUT_OF_MEMORY); //将这个token防止到分词器的队列首 mTokenizer->PushTokenFront(target); // Always safe to disallow interruptions, so it doesn'tmatter that we've // forgotten the aCanInterrupt parameter toBuildModel. Also, BuildModel // doesn't seem to care about the charset, and at thispoint we have no idea // what the charset was, so 0 can and must suffice. If either of these // values mattered, we'd want to store them as data membersin BuildModel. //禁用中断的方式永远是安全的,因此我们是否传递aCanInterrupt参数给BuildModel并没有关系。同时,BuildModel也不会关心字符编码的问题,并且在此时我们还不知道编码是什么,因此设置0就可以了。如果一旦这些值中的任何一个有关系,我们想将他们作为数据成员存放在BuildModel。 return BuildModel(mTokenizer, PR_FALSE, mCountLines,0); } //Build model结束之后所调用的方法 NS_IMETHODIMP CNavDTD::DidBuildModel(nsresultanErrorCode) { nsresult result = NS_OK; if (mSink) { //如果mSink不为空 if (NS_OK == anErrorCode) { //如果anErrorCode为NS_OK if (!(mFlags &NS_DTD_FLAG_HAS_MAIN_CONTAINER)) { // This document is not a frameset document,however, it did not contain // a body tag either. So, make one!. Note:Body tag is optional per spec.. // Also note: We ignore the return value ofBuildNeglectedTarget, we // can't reasonably respond to errors (orrequests to block) at this // point in the parsing process. //这个文档不是一个frameset文档,但是它同时也不含一个body标签。因此我们这里创建一个即可!注意:Body标签是可选的。同时还要注意:我们忽视了BuildNeglectedTarget的返回值,我们此时无法承担对error(或者阻塞要求)的回应。 //创建一个body标签 BuildNeglectedTarget(eHTMLTag_body, eToken_start); } if (mFlags &NS_DTD_FLAG_MISPLACED_CONTENT) { // Looks like the misplaced contents are notprocessed yet. // Here is our last chance to handle themisplaced content. //看起来这些格式错误的内容还没有被处理,这里是我们处理这些内容的最后机会 // Keep track of the top index. //记录一下top index PRInt32 topIndex = mBodyContext->mContextTopIndex; // Loop until we've really consumed all of ourmisplaced content. //循环直到我们真正地处理了我们所有格式错误的内容。 do { mFlags &= ~NS_DTD_FLAG_MISPLACED_CONTENT; // mContextTopIndex refers to the misplacedcontent's legal parent index. //处理那些被保存起来的标签 result = HandleSavedTokens(mBodyContext->mContextTopIndex); //如果失败了 if (NS_FAILED(result)) { NS_ERROR("Bugin the DTD"); //DTD中出了bug break; } // If we start handling misplaced contentwhile handling misplaced // content, mContextTopIndex gets modified.However, this new index // necessarily points to the middle of aclosed tag (since we close //new tags after handling the misplaced content). So we restore the // insertion point after every iteration. //如果我们在处理错误内容之时又开始处理错误内容,mContextTopIndex的值就会被改变。然而我们确实需要这个新的index指向一个被关闭的标签的中央(因为我们在处理完错误内容之后会关闭新的标签)。因此我们在每次遍历后都恢复介入点。 mBodyContext->mContextTopIndex = topIndex; //恢复topIndex } while (mFlags &NS_DTD_FLAG_MISPLACED_CONTENT); mBodyContext->mContextTopIndex = -1; //处理完毕,设置其为-1 } // Now let's disable style handling to savetime when closing remaining // stack members. //下面当我们在关闭剩余栈成员的时候取消style处理以节省时间。 mFlags &= ~NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE; //关闭style处理 while (mBodyContext->GetCount() >0) { //当队列不为空时 result = CloseContainersTo(mBodyContext->Last(), PR_FALSE); //关闭队列中的最后一个容器 NS_ENSURE_SUCCESS(result, result); //确保关闭成功 } }else { // If you're here, then an error occured, butwe still have nodes on the stack. // At a minimum, we should grab the nodes andrecycle them. // Just to becorrect, we'll also recycle the nodes. //如果我们到了这里,说明我们遇到了错误,此时我们的栈中仍然拥有节点 //我们最起码应当取下这些节点并且回收它们 //为了保证正确,我们应当同样回收这些节点本身。 while (mBodyContext->GetCount() >0) { //当栈不为空 nsEntryStack* theChildStyles = 0; nsCParserNode* theNode = mBodyContext->Pop(theChildStyles); //出栈 IF_DELETE(theChildStyles, &mNodeAllocator); //回收该Entry IF_FREE(theNode, &mNodeAllocator); //回收该Entry所对应的Node } } // Now make sure the misplaced content list isempty, // by forcefully recycling any tokens we might find there. //现在确保这些错误的内容列表是空的, //通过强制回收我们可能在这里找到的节点 CToken* theToken = 0; while ((theToken =(CToken*)mMisplacedContent.Pop())) { //不断回收mMisplacedContent中的内容 IF_FREE(theToken, mTokenAllocator); } } return result; } //解析终止 NS_IMETHODIMP_(void) CNavDTD::Terminate() { mFlags |= NS_DTD_FLAG_STOP_PARSING; //设置停止解析位 } //获取mDTDMode NS_IMETHODIMP_(nsDTDMode) CNavDTD::GetMode() const { return mDTDMode; } /** * Text and some tags require a body whenthey're added, this function returns * true for those tags. * * @param aToken The current token that we careabout. * @param aTokenizer A tokenizer that we canget the tags attributes off of. * @return PR_TRUE if aToken does indeed forcethe body to open. */ //一些诸如文本节点之类的节点在添加的时候需要一个body,这个方法针对这些类型的节点会返回true。 //该方法就是判断给定的tag是否需要body。 staticPRBool DoesRequireBody(CToken* aToken,nsITokenizer* aTokenizer) { PRBool result = PR_FALSE; if (aToken) { //确保给定的token不为空 eHTMLTags theTag = (eHTMLTags)aToken->GetTypeID(); //获取该Token的类型ID if(gHTMLElements[theTag].HasSpecialProperty(kRequiresBody)) { //根据gHTMLElements中定义的tag属性,判断其是否拥有kRequiresBody这个属性。值得注意的是,这个gHTMLElements是很重要的一个数组,它定义了所有已知的HTML Tags的属性,如是否需要标签,其能够出现在什么样的节点下,其能够包含什么样的节点等等。 if (theTag == eHTMLTag_input) { //IE 和Nav4x 会为type = text的input打开一个body节点 //但是我们不希望为一个没有type属性的<input>打开body节点 //那样做会很诡异 PRInt32 ac = aToken->GetAttributeCount(); //获取属性数量 for(PRInt32 i = 0; i < ac; ++i) { CAttributeToken* attr = static_cast<CAttributeToken*> (aTokenizer->GetTokenAt(i)); //获取当前属性 constnsSubstring& name = attr->GetKey(); //获取属性名 const nsAString& value =attr->GetValue(); //获取属性值 // XXXbz note that this stupid case-sensitivecomparison is // actually depended on by sites... //注意这个大小写匹配的比较和不同的站点有关 if ((name.EqualsLiteral("type") || name.EqualsLiteral("TYPE")) && !(value.EqualsLiteral("hidden") || value.EqualsLiteral("HIDDEN"))) { result = PR_TRUE; //如果有type= hidden,设置结果为true break; //继而跳出循环,不必解析后面的属性了 } } } else { result = PR_TRUE; } } } return result; } staticPRBool ValueIsHidden(constnsAString& aValue) { // Having to deal with whitespace here sucks, but we haveto match // what the content sink does. //需要这里对空格进行处理并不太好,但是我们必须和content sink达到一致 nsAutoString str(aValue); //转换为nsAuto字符串 str.Trim("\n\r\t\b"); //除掉前后的换行,缩进等空白符号 return str.LowerCaseEqualsLiteral("hidden"); //判断对其进行小写处理后是否和hidden一致 } // Check whether aTokencorresponds to a <input type="hidden"> tag. The token // must be a start tag tokenfor an <input>. This must becalled at a point // when all the attributesfor the input are still in the tokenizer. //检查一个token是否是一个类似<input type=”hidden”>的标签。该token必须是一个起始型的<input>标签。这必须在 staticPRBool IsHiddenInput(CToken* aToken,nsITokenizer* aTokenizer) { //确保是起始型标签 NS_PRECONDITION(eHTMLTokenTypes(aToken->GetTokenType()) ==eToken_start, "Mustbe start token"); //确保是input类型的标签 NS_PRECONDITION(eHTMLTags(aToken->GetTypeID()) == eHTMLTag_input, "Mustbe <input> tag"); //获取属性数量 PRInt32 ac = aToken->GetAttributeCount(); NS_ASSERTION(ac <= aTokenizer->GetCount(), //确保属性值正确,必须小于或等于剩下的token数 "Notenough tokens in the tokenizer"); // But we don't really trust ourselves to get that right //还是不能确定上面那步判断能正确,因此这里还是需要设置一下ac为剩余token和ac的最小值 ac = PR_MIN(ac, aTokenizer->GetCount()); for (PRInt32 i = 0; i < ac; ++i) { //循环遍历剩余的token NS_ASSERTION(eHTMLTokenTypes(aTokenizer->GetTokenAt(i)->GetTokenType())== eToken_attribute, "Unexpected token type"); //确保剩余的都是属性标签 // Again, we're not sure we actually manage toguarantee that //这里我们还是不能确信一定能正确获取属性标签 if (eHTMLTokenTypes(aTokenizer->GetTokenAt(i)->GetTokenType())!= eToken_attribute) { //如果不是属性标签,则直接退出循环 break; } CAttributeToken* attrToken = static_cast<CAttributeToken*>(aTokenizer->GetTokenAt(i)); //转换该标签为属性Token类型 if (!attrToken->GetKey().LowerCaseEqualsLiteral("type")) { continue; //如果其属性类型不为type则继续寻找下一个标签 } returnValueIsHidden(attrToken->GetValue()); //返回该属性值是否为hidden } return PR_FALSE; //到了这里说明没有该type属性,默认是不hidden的,因此返回false } /** * Returns whether or not there is a tag oftype aType open on aContext. */ //返回当前的上下文Context中是否有一个类型为aType的打开节点 staticPRBool HasOpenTagOfType(PRInt32 aType, const nsDTDContext& aContext) { PRInt32 count = aContext.GetCount(); //获取当前上下文的token栈中的元素数量 while (--count >= 0) { //循环遍历并做判断 if(gHTMLElements[aContext.TagAt(count)].IsMemberOf(aType)) { return PR_TRUE; //找到了就返回true } } return PR_FALSE; //否则返回false } //下面这个方法就是具体的对每个token循环进行处理的主函数,它采用和tokenizer类似的方法,对不同类型的token调用不同的子处理方法。 nsresult CNavDTD::HandleToken(CToken* aToken) { if (!aToken) { //如果传来的token为空,则直接返回NS_OK return NS_OK; } nsresult result = NS_OK; CHTMLToken* theToken = static_cast<CHTMLToken*>(aToken); //转换该token为HTML类型 eHTMLTokenTypes theType =eHTMLTokenTypes(theToken->GetTokenType()); //获取该Token的类型 eHTMLTags theTag = (eHTMLTags)theToken->GetTypeID(); //获取该Token的ID aToken->SetLineNumber(mLineNumber); //在该token中记录当前的行数 if (mCountLines) { mLineNumber += aToken->GetNewlineCount(); //如果当前有新行,则将行数加上atoken的行数 } if (mFlags & NS_DTD_FLAG_MISPLACED_CONTENT) { //检测当前标签是否是放错位置的标签 // Included TD & TH to fix Bug# 20797 static eHTMLTags gLegalElements[] = { //设置一个类型数组 eHTMLTag_table, eHTMLTag_thead, eHTMLTag_tbody, eHTMLTag_tr, eHTMLTag_td, eHTMLTag_th, eHTMLTag_tfoot }; // Don't even try processing misplaced tokensif we're already // handling misplaced content. See bug 269095 if (mFlags &NS_DTD_FLAG_IN_MISPLACED_CONTENT) { //如果当前我们已经在处理格式错误的内容,则不要去尝试对新的错误内容进行处理 PushIntoMisplacedStack(theToken); //直接将其压入mMisplacedContent栈 return NS_OK; } eHTMLTagstheParentTag = mBodyContext->Last(); //获取当前上下文Context中的栈顶端的Token //下面这个判断比较复杂,我们对其详细说明一下,首先看一下它得注释: if (FindTagInSet(theTag, gLegalElements, NS_ARRAY_LENGTH(gLegalElements)) || (gHTMLElements[theParentTag].CanContain(theTag, mDTDMode) && // Here's a problem. If theTag is legal in here, we don't move it // out. So if we're moving stuff out of here, the parent of theTag // gets closed at this point. But some things are legal //_everywhere_ and hence would effectively close out misplaced // content in tables. This is undesirable, so treat them as // illegal here so they'll be shipped out withtheir parents and // siblings. See bug 40855 for an explanation (that bug was for // comments, but the same issues arise withwhitespace, newlines, // noscript, etc). Script is special, though. Shipping it out // breaksdocument.write stuff. See bug 243064. //这里有个问题,如果theTag在这里是正确的,我们不会将它移出去。因此如果我们从这里移出任何的东西,theTag的父节点在此时会被关闭。但是有些东西在任何位置都是合法的,因此会将tables表中的错误内容全部关闭。这是不对的,因此这里我们将其认为是不合法的,这样在此处它会和它的兄弟和父节点一起被移出去。请查看bug 40855以获得详细内容(该bug是为注释而存在的,但是在遇到空格,新行,以及noscript时都会发生同样的问题)。但脚本比较特殊,将它移出去的话会导致document.write的东西。请查看bug 243064。 //其实这里主要是为了对Text等节点进行特殊处理而编写的,这类节点都通过预先设置了kLegalOpen这个属性来进行了标识,这个判断主要判断此时的Tag是否合法,如果合法则进行对之前一些非法节点的处理,如果不合法,则将其压栈入MisplacedStack //这里判断的条件主要有:(该Tag是否位于gLegalElements中)或((当前的父节点可以包含当前节点)且((当前的节点不是特殊型节点)或(当前节点是Script节点)))或((当前节点是<Input>节点)且(当前节点是起始型节点)且(当前节点的父节点在gLegalElements中)) (!gHTMLElements[theTag].HasSpecialProperty(kLegalOpen) || theTag == eHTMLTag_script)) || (theTag == eHTMLTag_input && theType == eToken_start && FindTagInSet(theParentTag, gLegalElements, NS_ARRAY_LENGTH(gLegalElements))&& IsHiddenInput(theToken, mTokenizer))) // Reset the state since all the misplacedtokens are about to get // handled. mFlags &=~NS_DTD_FLAG_MISPLACED_CONTENT; //因为所有的错误内容都即将被处理,因此将state重设 result = HandleSavedTokens(mBodyContext->mContextTopIndex); NS_ENSURE_SUCCESS(result, result); mBodyContext->mContextTopIndex = -1; } else { PushIntoMisplacedStack(theToken); return result; } } /* * This section of code is used to"move" misplaced content from one location * in our document model to another.(Consider what would happen if we found a * <P> tag in the head.) To movecontent, we throw it onto the * misplacedcontent deque until we can dealwith it. */ //这一部分的代码是用来将错误的内容从我们当前的文档模型中的一个位置“移动到”另一个位置。(考虑下:如果我们在<head>中找到了一个<p>节点该怎么办)为了移动内容,我们将其暂时放置到一个错误内容的双端队列中(misplacedcontent deque),直到我们能够处理它为止。 switch(theTag) { //根据theTag的类型进行选择 case eHTMLTag_html: case eHTMLTag_noframes: case eHTMLTag_script: case eHTMLTag_doctypeDecl: case eHTMLTag_instruction: break; //对以上几种类型不处理 default: //如果以上几种类型都不是 if(!gHTMLElements[eHTMLTag_html].SectionContains(theTag, PR_FALSE)) { if (!(mFlags &(NS_DTD_FLAG_HAS_MAIN_CONTAINER | NS_DTD_FLAG_ALTERNATE_CONTENT))) { // For bug examples from this code, see bugs:18928, 20989. // At this point we know the body/framesetaren't open. // If the child belongs in the head, thenhandle it (which may open // the head); otherwise, push it onto themisplaced stack. // 想要知道这段代码的Bug实例,请查看 18928,20989两个Bug。 //此时我们知道 body/frameset没有被打开 //如果该子节点可以属于head中,那么就对它进行处理(这可能会打开head容器):其他情况下则将它放置到错误内容栈中 //下面这个标示位用来标明该节点是否“只能够”出现在head中 PRBool isExclusive = PR_FALSE; //判断该节点是否能够放置到head中,注意该方法会对isExclusive的赋值进行更改 PRBool theChildBelongsInHead = gHTMLElements[eHTMLTag_head].IsChildOfHead(theTag, isExclusive); //如果该节点可以放置到Head中,且并不是“只能够”放置到Head中,且没有kPreferHead属性(该属性表示如果没有Body被打开的情况下,该节点是否优先被放置到head中) if (theChildBelongsInHead && !isExclusive && !gHTMLElements[theTag].HasSpecialProperty(kPreferHead)) { if(mMisplacedContent.GetSize() == 0 && (!gHTMLElements[theTag].HasSpecialProperty(kPreferBody) || (mFlags & NS_DTD_FLAG_HAS_EXPLICIT_HEAD))){ //这个节点即可以出现在Body中也可以出现在HEAD中。因为目前看来还没有一个BODY容器被打开,将这个token放置到head中。 //值得注意的是,此时的错误内容栈为空 break; //直接退出switch代码段即可 } //Otherwise, we have received some indication that the body is //"open", so push this token onto the misplaced content stack. //其他情况下,我们碰到的现象表明了body容器已经被打开了,因此将这个token直接放置到错误内容栈中。 theChildBelongsInHead = PR_FALSE; } //如果theChildBelongsInHead在上面被置成了false,我们准备将其放置到错误内容栈中 if (!theChildBelongsInHead) { //获取mBodyContext中最后面的一个tag,即当前的容器tag eHTMLTags top =mBodyContext->Last(); //确保该节点不是用户自定义的标签,这类标签应当位于head中 NS_ASSERTION(top !=eHTMLTag_userdefined, "Userdefined tags should act as leaves in thehead"); //判断当前顶端节点不为html,head,且该top节点可以包含当前节点theTag if(top != eHTMLTag_html && top != eHTMLTag_head && gHTMLElements[top].CanContain(theTag, mDTDMode)) { // Sometags (such as <object> and <script>) are opened in the //head and allow other non-head content to be children. //Note: Userdefined tags in the head act like leaves. //一些tags(比如<object>和<script>)在head中被打开了,并且允许其他非head的内容作为他们的子节点。请注意:用户自定义的节点在head中只能作为叶节点而存在 break; //直接退出switch } // Ifyou're here then we found a child of the body that was out of //place. We're going to move it to thebody by storing it //temporarily on the misplaced stack. However, in quirks mode, a // few tagsrequest, ambiguosly, for a BODY. - Bugs 18928, 24204.- //如果代码到达了这里,那么我们找到了一个body下的子节点出现在了错误的地方。我们准备将其移动到Body中的其他位置,我们先暂时将其放置到错误内容栈中。然而,在quirks模式中,一些tags会匿名地申请一个BODY ---请参考BUG 18928,24204 PushIntoMisplacedStack(aToken); //对该theTag的类型进行判断,是否是几种特殊的CDATA类型TAG if(IsAlternateTag(theTag)) { //These tags' contents are consumed as CDATA. If we simply //pushed them on the misplaced content stack, the CDATA //contents would force us to open a body, which could be //wrong. So we collect the whole tag as misplaced in one //gulp. Note that the tokenizer guarantees that there will // bean end tag. //这些tag的内容被当作CDATA而处理。如果我们只简单地将它们放到错误内容栈中,CDATA内容可能会导致我们强制打开一个BODY容器,这样会导致出错。因此我们将整个tag都作为一整块错误内容来处理。请注意,分词器Tokenizer保证了我们此时一定能找到一个对应的结束型tag。 CToken *current = aToken; //获取当前的Token //循环向后查找,直到找到一个theTag所对应的结束型标签为止,并且把这之间所经过的所有内容都放到错误内容栈中 while(current->GetTokenType() != eToken_end || current->GetTypeID() !=theTag) { current = static_cast<CToken*>(mTokenizer->PopToken()); NS_ASSERTION(current, "The tokenizer is not creating good " "alternatetags"); PushIntoMisplacedStack(current); } // XXXAdd code to also collect incorrect attributes on the // endtag. //作者这里期待未来进行一些改进,添加一些方法来处理结束型标签中的错误属性token } //如果aToken属于需要一个Body的tag类型(如text等) if(DoesRequireBody(aToken, mTokenizer)) { //创建一个Body Token CToken* theBodyToken = mTokenAllocator->CreateTokenOfType(eToken_start, eHTMLTag_body, NS_LITERAL_STRING("body")); //并重新调用本方法对其进行处理 result =HandleToken(theBodyToken); } return result; //返回结果 } } } } //如果说以上的代码都是对Token进行分类,并且进行一些通用的处理,那么下面就是开始针对每种不同类型的token调用不同的方法进行分类详细的处理了 if (theToken) { switch (theType) { //根据token的类型进行判断 case eToken_text: case eToken_start: case eToken_whitespace: case eToken_newline: result = HandleStartToken(theToken); //处理起始型节点,注意到空格和换行型token同样也在这里进行处理 break; case eToken_end: result = HandleEndToken(theToken); //处理结束型节点 break; case eToken_cdatasection: case eToken_comment: case eToken_markupDecl: result = HandleCommentToken(theToken); //如果是CDATA,注释,以及markup Declaration类型Token则都当做注释型节点进行处理。 break; case eToken_entity: result = HandleEntityToken(theToken); //处理实体类型的节点 break; case eToken_attribute: result = HandleAttributeToken(theToken); //处理属性型节点 break; case eToken_instruction: result = HandleProcessingInstructionToken(theToken); //处理指示型标签 break; caseeToken_doctypeDecl: result = HandleDocTypeDeclToken(theToken); //处理文档声明类标签 break; default: break; } IF_FREE(theToken, mTokenAllocator); //回收该theToken指针 if (result ==NS_ERROR_HTMLPARSER_STOPPARSING) { mFlags |=NS_DTD_FLAG_STOP_PARSING; } else if(NS_FAILED(result) && result != NS_ERROR_HTMLPARSER_BLOCK) { result = NS_OK; } } return result; //返回处理结果 } //下面我们按照代码的顺序,来查看对start类型的tag处理结束后进行收尾的DidHandleStartTag方法 nsresult CNavDTD::DidHandleStartTag(nsIParserNode&aNode, eHTMLTags aChildTag) { nsresult result = NS_OK; switch (aChildTag) { //这是对html5新增元素<pre>的处理,<listing>目前已经被<pre>所代替,但此处同样对其进行了相同处理 case eHTMLTag_pre: case eHTMLTag_listing: { // Skip the 1st newline inside PRE and LISTINGunless this is a // plain text doc (for which we pushed a PREin CNavDTD::BuildModel). // XXX This code is incorrect in the face ofmisplaced <pre> and // <listing> tags (as direct children of<table>). //跳过PRE和LISTING中的第一个新行,除非这是一个纯文本的文档(这样我们会直接将<PRE>推到CNavDTD::BuildModel方法里去) //作者指出,这段代码在处理位置错误的<pre>和<listing>标签时会出错(比如如果它们作为<table>的直接子节点出现) //查看下一个token CToken* theNextToken = mTokenizer->PeekToken(); //如果当前解析模式不是ePlainText且下一个Token存在 if(ePlainText != mDocType && theNextToken) { //获取下一个token的类型 eHTMLTokenTypes theType =eHTMLTokenTypes(theNextToken->GetTokenType()); //如果下一个token为newline类型 if (eToken_newline == theType) { if(mCountLines) { //将行数加上 mLineNumber +=theNextToken->GetNewlineCount(); } theNextToken =mTokenizer->PopToken(); //将其该newline出栈 //直接将其释放掉 IF_FREE(theNextToken,mTokenAllocator); // fix for Bug 29379 } } } break; default: break; } return result; } //获取当前BodyContext中,自顶向下查找,找到存在于aTagSet内的节点的第一个节点,并返回该节点在BodyContext中的序号 PRInt32 CNavDTD::LastOf(eHTMLTags aTagSet[],PRInt32 aCount) const { for (PRInt32 theIndex = mBodyContext->GetCount() -1; theIndex >= 0; --theIndex) { if(FindTagInSet((*mBodyContext)[theIndex], aTagSet, aCount)) { return theIndex; } } return kNotFound; } //下面这个函数,是从子节点aChildTag的角度出发,用来判断aChildTag本身能否包含于当前的上下文中 staticPRBool CanBeContained(eHTMLTags aChildTag,nsDTDContext& aContext) { /* # Interestingtest cases: Result: * 1. <UL><LI>..<B>..<LI> inner <LI> closes outer<LI> * 2. <CENTER><DL><DT><A><CENTER> allow nested <CENTER> * 3. <TABLE><TR><TD><TABLE>... allow nested <TABLE> * 4. <FRAMESET> ... <FRAMESET> */ //作者通过注释,给出了一些有趣的测试例子: // 例子 结果 //1. <UL><LI>..<B>..<LI> 内部的<LI>关闭了外部的<LI> //2. <CENTER><DL><DT><A><CENTER> 允许嵌入<CENTER> //3. <TABLE><TR><TD><TABLE>… 允许嵌入<TABLE> //4. <FRAMESET>…<FRAMESET> PRBool result = PR_TRUE; PRInt32 theCount = aContext.GetCount(); //获取当前Context的tag数量 if (0 < theCount) { //如果tags数量大于0 //获取当前的根节点tag const TagList* theRootTags =gHTMLElements[aChildTag].GetRootTags(); //获取该节点类型合法的根节点(具体请见nsElementTable.h) const TagList* theSpecialParents = gHTMLElements[aChildTag].GetSpecialParents(); //获取该节点类型的特殊父节点,这种父节点必须是该节点所必须有的直接父节点(比如<area>的特殊父节点是<map>,<caption>的特殊父节点是<table>) if (theRootTags) { //如果该类型节点的rootTags不为空 PRInt32 theRootIndex = LastOf(aContext, *theRootTags); //获取RootTags中任意元素在当前上下文栈中第一次出现的位置 PRInt32 theSPIndex = theSpecialParents //同样,获取特殊父节点第一次出现的位置 ? LastOf(aContext,*theSpecialParents) : kNotFound; PRInt32 theChildIndex = nsHTMLElement::GetIndexOfChildOrSynonym(aContext, aChildTag); //获取该节点类型或该节点的同义类型(比如<h1><h2><h3>就属于同义类型,它们中的任一类型的End类型节点都可以关闭另一类型的Start类型节点)在当前上下文栈中最近一次出现的位置, PRInt32 theTargetIndex = (theRootIndex > theSPIndex) //获取theRootIndex和theSPIndex中最近一次出现的那个(位置标号最大) ? theRootIndex : theSPIndex; //下面这个判断就是语法合法性的判断了,目前targetIndex中存放的就是距离该节点最近的“合法父节点”所处的位置,如果该位置正好位于当前栈顶,那么自然是合法的,返回True,而还有一种情况下也是合法的,即该“合法父节点”位置恰好等于theChildIndex的位置,且该节点类型属于“可以包含自己”(kSelf相应的标示位不为0)的类型,此时说明该节点的直接父节点的tag类型和该节点相同,具体这类标签包含什么,我们会在后面的nsElementTable.cpp文件中详加解析 if (theTargetIndex == theCount-1 || (theTargetIndex == theChildIndex && gHTMLElements[aChildTag].CanContainSelf())) { result = PR_TRUE; } else { //其他情况下则说明没有找到合适的parents result = PR_FALSE; //此时需要进行一些复杂情况的判断 static eHTMLTags gTableElements[] = {eHTMLTag_td, eHTMLTag_th }; //取当前栈顶元素的位置 PRInt32 theIndex = theCount - 1; //下面这个循环是循环遍历,从当前栈顶到上一个theTag或theTag的同义tag的位置,如果没有找到这个位置的话,此时theChildIndex中应当是-1,即下面这个循环会遍历整个栈,并依次判断这之间的tag是否属于块级元素,或td、th等,如果有,那么我们就认为该子元素此时可以被包含。当然同时还需要对这些元素是否属于“必须拥有结束节点”的类型这一条件进行判断,对此我的理解是:因为有些节点如<li>等节点是可以有也可以没有结束节点的,如果是这种节点,那么它可能已经被关闭了,因此我们不能判定当前元素是否能够被包含 while (theChildIndex < theIndex) { eHTMLTags theParentTag = aContext.TagAt(theIndex--); if(gHTMLElements[theParentTag].IsMemberOf(kBlockEntity) || gHTMLElements[theParentTag].IsMemberOf(kHeading) || gHTMLElements[theParentTag].IsMemberOf(kPreformatted)|| gHTMLElements[theParentTag].IsMemberOf(kFormControl) || //fixes bug 44479 gHTMLElements[theParentTag].IsMemberOf(kList)) { if(!HasOptionalEndTag(theParentTag)) { result = PR_TRUE; break; } } else if(FindTagInSet(theParentTag, gTableElements, NS_ARRAY_LENGTH(gTableElements))) { // Addedthis to catch a case we missed; bug 57173. result = PR_TRUE; break; } } } } } return result; } enumeProcessRule { eNormalOp, eLetInlineContainBlock }; //定义两种解析模式的组合,一种是普通的,另一种是允许inline元素包含Block的模式(通常模式下不允许) //下面这个方法是对大多数起始型节点进行处理的默认方法,它实际上是被HandleStartToken所调用的,其作用就是分别判断父节点和子节点的“意见”,并且只有当两个节点都认可的时候才能够添加,否则就需要在当前栈中寻找一个合适的父节点来放置该子节点,并且,如果该情况是可以通过对上下文进行“修补”而得到的,则对相应的上下文进行“修补”(添加必要节点),之后就将该子节点添加到当前的model中去,我们来看一看它是如何对大多数类型的节点进行处理的 nsresult CNavDTD::HandleDefaultStartToken(CToken*aToken, eHTMLTags aChildTag, nsCParserNode*aNode) { NS_PRECONDITION(nsnull != aToken, kNullToken); //确保aToken不为空 nsresult result = NS_OK; //设置result为OK PRBool theChildIsContainer =nsHTMLElement::IsContainer(aChildTag); //首先判断aChildTag是否是容器类型 // Client of parser is spefically trying to parse afragment that // may lack required context. Suspend containment rules if so. //正在调用parser的模块定义了此时正在解析一个html片段,此时可能缺少所需的上下文。因此这时需要禁用相应的包含规则 if (mParserCommand != eViewFragment) { //首先判断此时的解析模式不是eViewFragment PRBool theChildAgrees = PR_TRUE; //默认设置子节点的包含倾向为“可以包含” PRInt32 theIndex = mBodyContext->GetCount(); //获取当前栈顶位置 PRBool theParentContains = PR_FALSE; //默认设置父节点的包含倾向为“不可以包含” //一个循环,从栈顶位置依次开始向下遍历各个元素 do { eHTMLTags theParentTag = mBodyContext->TagAt(--theIndex); //获取当前位置的元素 if (theParentTag ==eHTMLTag_userdefined) { //如果这个元素类型是用户自定义的 continue; //继续下一个循环 } // Figure out whether this is a hidden inputinside a // table/tbody/thead/tfoot/tr //首先判断一下这个元素是不是一个藏在table/tbody/thead/tfoot/tr中的有着hidden属性的input元素 static eHTMLTags sTableElements[] = { eHTMLTag_table, eHTMLTag_thead, eHTMLTag_tbody, eHTMLTag_tr, eHTMLTag_tfoot }; PRBool isHiddenInputInsideTableElement = PR_FALSE; //判断标示变量,默认false //如果解析元素是input,并且theParentTag元素是table/tbody/thead/tfoot/tr中的任意一个 if (aChildTag == eHTMLTag_input&& FindTagInSet(theParentTag, sTableElements, NS_ARRAY_LENGTH(sTableElements))) { //判断其是否拥有type属性,并且该属性的值是否是hidden PRInt32 attrCount = aNode->GetAttributeCount(); for (PRInt32 attrIndex = 0; attrIndex< attrCount; ++attrIndex) { const nsAString& key =aNode->GetKeyAt(attrIndex); if (key.LowerCaseEqualsLiteral("type")) { isHiddenInputInsideTableElement = //将判断结果存放在判断标示变量中 ValueIsHidden(aNode->GetValueAt(attrIndex)); break; } } } // Precompute containment, and pass it toCanOmit()... //预先检查一下包含规则,并且将结果传递给CanOmit()方法…’ //首先设置theParentContains位,如果是隐藏在tables众元素下的hidden input,或者theParentTag此时可以包含aChildTag(由CanContain方法进行判断,该方法属于包含规则的一部分) //值得注意的是,这里的CanContain只是判断“当前的theParentTag下是否允许存放aChildTag类型的节点”,这只是最终判断通过的必要条件 theParentContains = isHiddenInputInsideTableElement || CanContain(theParentTag, aChildTag); //下面我们处理一些omit节点,该情况下主要是某些子节点理论上来说不应存放在某个父节点下,但是此处我们可以忽略其影响,并暂时将其存放在父节点下,我们需要先判断出并处理这类节点 if (!isHiddenInputInsideTableElement&& CanOmit(theParentTag, aChildTag, theParentContains)) { HandleOmittedTag(aToken, aChildTag, theParentTag, aNode); return NS_OK; //直接返回成功结果 } //设置解析规则,默认为普通模式 eProcessRule theRule = eNormalOp; if (!theParentContains && //如果此时父节点不允许包含该子节点 (IsBlockElement(aChildTag, theParentTag) && //且aChildTag是Block级元素(笔者:这里的theParentTag没用) IsInlineElement(theParentTag,theParentTag))) { //且theParentTag为inline级元素 // Don't test for table to fix 57554. //这里修正了bug 57554,不要对table元素进行测试 if (eHTMLTag_li != aChildTag) { //如果aChildTag不是<li>元素的情况下 nsCParserNode* theParentNode = mBodyContext->PeekNode(); if (theParentNode &&theParentNode->mToken->IsWellFormed()) { //确保此时的ParentNode没有词法错误(比如找到了开始节点,但是没有找到结束节点) theRule = eLetInlineContainBlock; //设置规则为“允许inline元素包含block元素” } } } switch (theRule) { //对规则进行判断 case eNormalOp: //如果是普通模式 theChildAgrees = PR_TRUE; //标示为,代表子节点是否认为可以存放在此处,默认为允许 if (theParentContains) { //如果此时父节点同意存放该子节点 eHTMLTags theAncestor =gHTMLElements[aChildTag].mRequiredAncestor; //获取aChildTag所规定的RequiredAncestor,这代表了aChildTag认为自己所必须的直接父节点,如<area>的RequiredAncestor为<map> if(eHTMLTag_unknown != theAncestor) { //如果aChildTag确实有必须的父节点 theChildAgrees =HasOpenContainer(theAncestor); //判断此时是否有一个该类型的父节点处于打开状态 } //如果子节点允许,并且该子节点是一个容器类标签 if(theChildAgrees && theChildIsContainer) { if(theParentTag != aChildTag) { //如果该子节点不为theParentTag //Double check the power structure //Note: The bit is currently set on tags such as <A> and <LI>. //再度察看一下结构 //注意:当前tags的相应比特位已经被设置上了,比如<A>和<LI> //如果当前aChildTag的标示位被设置好了 if(gHTMLElements[aChildTag].ShouldVerifyHierarchy()) { //获取当前上下文中aChildTag同类节点的位置 PRInt32 theChildIndex = nsHTMLElement::GetIndexOfChildOrSynonym(*mBodyContext, aChildTag); //如果找到了 if(kNotFound < theChildIndex && theChildIndex < theIndex) { //下面我们要对一个复杂情况进行处理,我们首先来看一下代码作者的注释: /* * 1 Here's a tricky case from bug 22596: <h5><li><h5> * How do we know that the 2nd <h5>should close the <LI> * rather than nest inside the <LI>?(Afterall, the <h5> * is a legal child of the <LI>). * * The way you know is that there is no rootbetween the * two, so the <h5> binds more tightlyto the 1st <h5> * than to the <LI>. * * 2 Also, bug 6148 shows this case:<SPAN><DIV><SPAN> * From this case we learned not to executethis logic if * the parent is a block. * * 3 Fix for 26583: * <A href=foo.html><B>foo<Ahref-bar.html>bar</A></B></A> * In the above example clicking on"foo" or "bar" should * link to foo.html or bar.html respectively.That is, * the inner <A> should be informedabout the presence of * an open <A> above <B>..so thatthe inner <A> can close * out the outer <A>. The following codedoes it for us. * * 4 Fix for 27865 [ similer to 22596 ]. Ex: * <DL><DD><LI>one<DD><LI>two */ //1.这里我们首先要处理一个bug 22596中提到的一个比较麻烦的问题:<h5><li><h5>,此时我们怎么才能知道第二个<h5>标签应当关闭<li>标签而不是作为<li>标签的子节点呢?(毕竟<h5>可以算作是<li>节点的合法子节点)。判断的方法是,因为这两者之间没有任何的根节点(root node),因此第二个<h5>与另一个<h5>的关联要比它和<li>的关联更加紧密 //2.另外,bug 6148向我们展示了这样一种情景:<SPAN><DIV><SPAN>,从这个实例中我们学到如果父节点是一个block级元素的话,则不要用这种逻辑来进行处理 //3.对bug 26853的修正: //<Ahref=foo.html><B>foo<Ahref-bar.html>bar</A></B></A> //在上面这个例子里,如果点击了foo或者bar,应当各自跳转到foo.html或bar.html。也就是说,内部的<A>应当被告知其外部在<B>之上还有一个<A>标签被打开。因此内部的<A>可以关闭外部的<A>。下面这段代码包含了这一功能。 //4.对bug 27865的修正 [类似于22596]例子: //<DL><DD><LI>one<DD><LI>two theChildAgrees= CanBeContained(aChildTag, *mBodyContext); } } } } } if (!(theParentContains &&theChildAgrees)) { //如果此时父节点或子节点“不同意”当前的包含操作 if(!CanPropagate(theParentTag, aChildTag, theParentContains)) { //尝试看看能否在theParentTag和aChildTag之间进行“延伸”(propagate)(这个propagate方法我们之后会解析,其实际上就是用来弥补一些缺失标签的) if(theChildIsContainer || !theParentContains) { //如果子节点或父节点是容器类型节点 //如果子节点判断失败,且判断一下当前theIndex的节点到栈顶之间这段Tags不能被自动关闭 if(!theChildAgrees && !gHTMLElements[aChildTag].CanAutoCloseTag(*mBodyContext, theIndex, aChildTag)){ //Closing the tags above might cause non-compatible results. //Ex.<TABLE><TR><TD><TBODY>Text</TD></TR></TABLE>. //In the example above <TBODY> is badly misplaced, but //we should not attempt to close the tags above it, //The safest thing to do is to discard this tag. //XXX We get the above example wrong anyway because of //CanBeContained. //这时关闭这些节点可能会导致一些不兼容的问题。比如:<table><tr><td><tbody>Text</td></tr></table>。在这个例子中<tbody>以上的内容格式都出现了错误,但是我们此时不能够尝试去关闭它们,此时最安全的事就是抛弃这个tag。 //代码作者注:这个例子本来就错了,因为CanBeContained的判断就会判断其有错 } elseif (mBodyContext->mContextTopIndex > 0&& theIndex <=mBodyContext->mContextTopIndex) { //Looks like the parent tag does not want to contain the //current tag ( aChildTag ). However, wehave to force the //containment, when handling misplaced content, to avoid data //loss. Ref. bug 138577. //代码运行到这里,看起来父节点并不想饱含当前节点(子节点)。然而,我们必须强制其饱含,这样做是为了在处理出错的内容的时候避免数据损失。详情在bug 138577中。 theParentContains = PR_TRUE; //强制设置父节点包含标示为true } else{ //此时说明可以关闭theIndex到当前栈顶的内容,我们将这些节点进行关闭 CloseContainersTo(theIndex,aChildTag, PR_TRUE); } } else{ break; } } else{ //此时说明我们可以从theParentTag“延伸”到aChildTag,因此也就说明此时我们可以为其补全上下文 CreateContextStackFor(theParentTag, aChildTag); //根据Propagate的结果(mScratch)来补全上下文 theIndex =mBodyContext->GetCount(); //重写设置theIndex为栈顶位置 } } break; case eLetInlineContainBlock: //如果此是是inline可以饱含block模式 // Break out of this loop and open the block. //直接跳出循环并且打开一个block theParentContains = theChildAgrees = PR_TRUE; break; default: NS_NOTREACHED("Invalid ruledetected"); //不应出现这种情况 } } while (!(theParentContains &&theChildAgrees)); //循环遍历条件,可知我们必需要找到一个合适的theParent节点,使得子节点和父节点都同意的情况下才能跳出这个循环 } //如果子节点是一个容器类型 if (theChildIsContainer) { result = OpenContainer(aNode, aChildTag); //为该节点打开相应容器 } else { result = AddLeaf(aNode); //如果不是容器类型,则直接将其作为叶节点添加进去即可 } return result; //返回结果 } //下面这个方法是经典的Will-Do-Did三部曲之一,即在处理起始型节点之前调用的准备方法,它会做一些初始化工作 nsresult CNavDTD::WillHandleStartTag(CToken*aToken, eHTMLTags aTag, nsIParserNode&aNode) { nsresult result = NS_OK; PRInt32 stackDepth = mBodyContext->GetCount(); //首先获取当前上下文栈的深度 if (stackDepth >= FONTSTYLE_IGNORE_DEPTH&& gHTMLElements[aTag].IsMemberOf(kFontStyle)) { // Prevent bug 58917 by tossing the new kFontStyle start tag //为了防止bug 58917的发生,丢掉新的kFontStyle起始型标签 return kHierarchyTooDeep; } //笔者:这两个判断为什么不写到一起呢? if (stackDepth >= PHRASE_IGNORE_DEPTH && gHTMLElements[aTag].IsMemberOf(kPhrase)) { // Prevent bug 58917 by tossing the new kPhrase start tag //为了防止bug 58917的发生,丢掉kPhrase的起始型标签 return kHierarchyTooDeep; } /* * Now a little code to deal with bug #49687(crash when layout stack gets * too deep) I've also opened this up to anycontainer (not just inlines): * re bug 55095 Improved to handle bug 55980(infinite loop caused when * DEPTH is exceeded and </P> isencountered by itself (<P>) is continuously * produced. */ //下面是一点处理bug #49687的代码 (在渲染栈过深的时候崩溃的问题)另外我还将这开启至任何的Container(并不光是inlines):源自bug 55095 我还做了一些改进来处理bug 55980(当栈深度到达极限并且出现的</P>被<P>卡住并无限循环的现象),还在持续更新中 if(stackDepth > MAX_REFLOW_DEPTH) { //如果当前栈的深度大于最大reflow深度 if (nsHTMLElement::IsContainer(aTag)&& !gHTMLElements[aTag].HasSpecialProperty(kHandleStrayTag)){ // Ref. bug 98261,49678,55095,55980 // Instead of throwing away the current tagclose it's parent // such that the stack level does not gobeyond the max_reflow_depth. //This would allow leaf tags, that follow the current tag, to find // the correct node. //源自bug 98261,49678,55095,55980 //取缔了丢弃当前tag并关闭它的父节点以使栈深度不超过max_reflow_depth的方法。这样可以允许跟在当前节点之后的叶子型tag找到正确的节点。 while (stackDepth != MAX_REFLOW_DEPTH&& NS_SUCCEEDED(result)) { //循环关闭栈顶的容器,直到栈的大小回归到阈值以下 result = CloseContainersTo(mBodyContext->Last(), PR_FALSE); --stackDepth; } } } STOP_TIMER() MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:CNavDTD::WillHandleStartTag(), this=%p\n",this)); if (aTag <= NS_HTML_TAG_MAX) { result = mSink->NotifyTagObservers(&aNode); } MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::WillHandleStartTag(), this=%p\n",this)); START_TIMER() return result; } //将错误的属性压入栈中 static void PushMisplacedAttributes(nsIParserNode&aNode, nsDeque& aDeque) { nsCParserNode& theAttrNode = static_cast<nsCParserNode&>(aNode); //做一个类型转换 for (PRInt32 count = aNode.GetAttributeCount(); count> 0; --count) { //依次将该节点所有的属性 CToken* theAttrToken =theAttrNode.PopAttributeTokenFront(); //属性出栈 if (theAttrToken) { theAttrToken->SetNewlineCount(0); //设置一下行数 aDeque.Push(theAttrToken); //讲该attribute Token放置到参数传进的aDeque队列中 } } } //下面这个方法,就是对Omiited的Tag进行处理的方法了,我们前面在HandleDefaultStartToken的时候调用过 void CNavDTD::HandleOmittedTag(CToken*aToken, eHTMLTags aChildTag, eHTMLTags aParent,nsIParserNode* aNode) { NS_PRECONDITION(mBodyContext != nsnull, "needa context to work with"); // The trick here is to see if the parent can contain thechild, but prefers // not to. Only if the parent CANNOT contain the childshould we look to see // if it's potentially a child of another section. If itis, the cache it for // later. //这里的问题是要观察一下父节点是否能够包含该子节点,但是更倾向于不包含该子节点。只有在父节点“不能够”包含子节点的时候我们才应该尝试去查看该子节点是否有可能属于其他为孩子。如果是的话,则将它暂时保存起来准备之后进行处理 PRInt32 theTagCount = mBodyContext->GetCount(); //获取当前的上下文栈中的tag数量 PRBool pushToken = PR_FALSE; //设置一个标示位,用来标示是否需要暂时保存该token //如果父节点类型拥有标示位“kBadContentWatch”(说明其格式内容有错误),且此时的子节点aChildTag不为空字符型节点 if(gHTMLElements[aParent].HasSpecialProperty(kBadContentWatch) && !nsHTMLElement::IsWhitespaceTag(aChildTag)) { eHTMLTagstheTag = eHTMLTag_unknown; // Don't bother saving misplaced stuff in thehead. This can happen in // cases like |<head><noscript><table>foo|.See bug 401169. //不要费心处理在head中的错误内容了。这种错误的情形是:<head><noscript><table>foo。请查看bug 401169 //如果此时是位于head节点中 if (mFlags &NS_DTD_FLAG_HAS_OPEN_HEAD) { NS_ASSERTION(!(mFlags & NS_DTD_FLAG_HAS_MAIN_CONTAINER), "Badstate"); return; //直接返回 } // Determine the insertion point //确定插入点 while (theTagCount > 0) { //自栈顶元素开始,依次向下循环遍历栈内节点 theTag = mBodyContext->TagAt(--theTagCount); if (!gHTMLElements[theTag].HasSpecialProperty(kBadContentWatch)){ //如果当前节点拥有kBadContentWatch位,说明该节点的格式有错误 // This is our insertion point. mBodyContext->mContextTopIndex = theTagCount; //将当前栈的位置调整为出错的该处位置 break; //跳出循环 } } if (mBodyContext->mContextTopIndex> -1) { //如果此时(调整后的)栈顶位置不为-1,即确实有错误格式内容的节点存在 pushToken = PR_TRUE; //设置标示位 // Remember that we've stashed some misplacedcontent. //需要记住我们在栈中存储了一些错误格式的内容 mFlags |= NS_DTD_FLAG_MISPLACED_CONTENT; } } //如果此时aParent和aChildTag不相等(相等肯定有问题) //此处的一个判断,实际上是对前面的一个补充,因为此时的pushToken肯定为false,说明之前的if语句不成立,但此处判断其是否拥有kSaveMisplaced位,如果有则也需要将其进行存储 if (aChildTag != aParent && gHTMLElements[aParent].HasSpecialProperty(kSaveMisplaced)) { NS_ASSERTION(!pushToken, "A strangeelement has both kBadContentWatch " "and kSaveMisplaced"); pushToken = PR_TRUE; //设置标示位 } if (pushToken) { //如果设置了存储token的标示位 // Hold on to this token for later use. RefBug. 53695 //锁定这个token(不要回收)以备将来使用。源自Bug. 53695 IF_HOLD(aToken); PushIntoMisplacedStack(aToken); //将其放置到错误内容栈上 // If the token is attributed then save thoseattributes too. //如果该token还有属性节点,同样将这些属性节点都放入错误内容栈中 PushMisplacedAttributes(*aNode, mMisplacedContent); } } /** * Thismethod gets called when a kegen token is found. * * @update harishd 05/02/00 * @param aNode -- CParserNoderepresenting keygen * @return NS_OK if all went well;ERROR if error occured */ //这个方法是用来专门处理<keygen>节点的,该节点是HTML 5中新增的节点,因此在代码上没有统一处理,而是专门地写了一个方法来进行处理。需要注意的是keygen是配合form提交进行使用的,因此在处理上也需要nsIFormProcessor的配合才行 nsresult CNavDTD::HandleKeyGen(nsIParserNode*aNode) { nsresult result = NS_OK; nsCOMPtr<nsIFormProcessor> theFormProcessor = //调用组件获取方法获取当前的Form处理模块 do_GetService(kFormProcessorCID,&result); if (NS_FAILED(result)) { //如果获取失败 return result; //返回失败结果 } PRInt32 theAttrCount =aNode->GetAttributeCount(); //获取该节点的属性数 nsTArray<nsString> theContent; //设置一个字符串数组 nsAutoString theAttribute; //设置一个字符串 nsAutoString theFormType; //设置一个字符串 CToken* theToken = nsnull; theFormType.AssignLiteral("select"); //在theFormType字符串后追加select字符 //下面调用的一个方法,对theContent和theAttribute都进行了填充,如专门填充了公钥的内容等 result = theFormProcessor->ProvideContent(theFormType, theContent, theAttribute); if (NS_FAILED(result)) { return result; } PRInt32 theIndex = nsnull; //设置theIndex为空 // Populate the tokenizer with the fabricated elements inthe reverse // order such that <SELECT> is on the top fo thetokenizer followed by // <OPTION>s and </SELECT>. //以逆序的方法向分词器中插入,如最后<SELECT>应当在栈顶,后面跟着<OPTION>和</SELECT> //首先插入</SELECT> theToken = mTokenAllocator->CreateTokenOfType(eToken_end, eHTMLTag_select); NS_ENSURE_TRUE(theToken, NS_ERROR_OUT_OF_MEMORY); mTokenizer->PushTokenFront(theToken); //将该token插入到栈顶 //下面为theContent中的每个元素创建<option>节点并将内容最为text类型节点插入 for (theIndex = theContent.Length()-1; theIndex >-1; --theIndex) { //注意此处还是逆序插入 theToken = mTokenAllocator->CreateTokenOfType(eToken_text, //创建文本节点 eHTMLTag_text, theContent[theIndex]); NS_ENSURE_TRUE(theToken, NS_ERROR_OUT_OF_MEMORY); mTokenizer->PushTokenFront(theToken); //插入该文本节点 theToken = mTokenAllocator->CreateTokenOfType(eToken_start, eHTMLTag_option); NS_ENSURE_TRUE(theToken, NS_ERROR_OUT_OF_MEMORY); mTokenizer->PushTokenFront(theToken); //插入<OPTION>节点 } // The attribute ( provided by the form processor ) shouldbe a part // of the SELECT. Placing the attribute token on the tokenizer to get // picked up by the SELECT. //属性值(从form processor所提供来的)应当是SELECT的一部分。将该属性token放置到分词器tokenizer上以使其被SELECT所获取 //创建一个属性类型的token theToken = mTokenAllocator->CreateTokenOfType(eToken_attribute, eHTMLTag_unknown, theAttribute); NS_ENSURE_TRUE(theToken, NS_ERROR_OUT_OF_MEMORY); ((CAttributeToken*)theToken)->SetKey(NS_LITERAL_STRING("_moz-type")); //设置该属性token的key为”_moz-type” mTokenizer->PushTokenFront(theToken); //将该token放置在栈的最前端 // Pop out NAME and CHALLENGE attributes ( from the keygenNODE ) and // place it in the tokenizer such that the attribtues getsucked into // SELECT node. //将NAME和CHALLENGE属性们自栈中弹出(从keygen 节点中),并将它放置在分词器tokenizer中,这样属性就会被自动嵌入到SELECT节点中了 for (theIndex = theAttrCount; theIndex > 0;--theIndex) { mTokenizer->PushTokenFront(((nsCParserNode*)aNode)->PopAttributeToken()); //aNode的所有属性抽出,并依次压入栈中 } //创建一个起始型的<select>节点 theToken = mTokenAllocator->CreateTokenOfType(eToken_start, eHTMLTag_select); NS_ENSURE_TRUE(theToken, NS_ERROR_OUT_OF_MEMORY); // Increment the count because of the additional attributefrom the form processor. // 将count数加一,因为从formprocessor中多出的一个属性 theToken->SetAttributeCount(theAttrCount + 1); mTokenizer->PushTokenFront(theToken); return result; } //判断该Tag是否为Alternate型tag(实际上就是替换型的tag,如noscript替换script) //并且同时还会对当前是否启用了相应的解析模式进行判断,比如只有在启用了SCRIPT时,遇到NOSCRIPT才会返回true PRBool CNavDTD::IsAlternateTag(eHTMLTags aTag) { switch (aTag) { case eHTMLTag_noembed: return PR_TRUE; case eHTMLTag_noscript: return (mFlags &NS_IPARSER_FLAG_SCRIPT_ENABLED) != 0; case eHTMLTag_iframe: case eHTMLTag_noframes: return (mFlags &NS_IPARSER_FLAG_FRAMES_ENABLED) != 0; default: return PR_FALSE; } } //下面是处理起始型Token的标准方法,该方法相当于一个入口,它会去调用前面的HandleDefaultStartToken等方法 nsresult CNavDTD::HandleStartToken(CToken*aToken) { NS_PRECONDITION(nsnull != aToken, kNullToken); //首先为该Token创建一个ParserNode nsCParserNode* theNode = mNodeAllocator.CreateNode(aToken,mTokenAllocator); NS_ENSURE_TRUE(theNode, NS_ERROR_OUT_OF_MEMORY); //确保创建成功 eHTMLTags theChildTag =(eHTMLTags)aToken->GetTypeID(); //获取该Token的Tag类型 PRInt16 attrCount = aToken->GetAttributeCount(); //获取该Token的属性个数 eHTMLTags theParent = mBodyContext->Last(); //获取当前上下文栈中的栈顶元素 nsresult result = NS_OK; if (attrCount > 0) { //如果属性数大于0 result = CollectAttributes(theNode, theChildTag, attrCount); //这一步做的工作是根据当前start中的attrCount即属性数量,从栈中弹出相同数量的属性 tokens(如果弹出的不是属性token则会将其放回栈中),并且将这些attr tokens放入该start node的mAttribute中 } if (NS_OK == result) { //如果结果成功 result = WillHandleStartTag(aToken, theChildTag, *theNode); //调用will方法进行初始化 if (NS_OK == result) { //如果初始化结果成功 //设置两个标示位 PRBool isTokenHandled = PR_FALSE; PRBool theHeadIsParent = PR_FALSE; //下面首先对即将要处理的tag进行判断,判断其是否是会新打开一片区域的tag类型(比如head,html,body,frameset等) if(nsHTMLElement::IsSectionTag(theChildTag)) { switch (theChildTag) { //根据tag类型进行判断 case eHTMLTag_html: //如果是html if(mBodyContext->GetCount() > 0) { //如果当前上下文中的节点个数不为0 result = OpenContainer(theNode,theChildTag); //用该节点打开一个容器 isTokenHandled = PR_TRUE; //设置标示位,代表该token已经被处理 } break; case eHTMLTag_body: //如果是body if(mFlags & NS_DTD_FLAG_HAS_OPEN_BODY) { //如果当前已经有body被打开 result = OpenContainer(theNode,theChildTag); //用该节点打开一个容器 isTokenHandled=PR_TRUE; //token已被处理 } break; case eHTMLTag_head: //如果是head mFlags |=NS_DTD_FLAG_HAS_EXPLICIT_HEAD; //设置一下标示位,说明现在有head节点被显式地打开了 if(mFlags & NS_DTD_FLAG_HAS_MAIN_CONTAINER) { //如果此时有BODY或者FRAMESET HandleOmittedTag(aToken,theChildTag, theParent, theNode); //则将其作为omit节点进行处理 isTokenHandled = PR_TRUE; //token已被处理 } break; default: break; } } PRBool isExclusive = PR_FALSE; //此时设置isExclusive为false theHeadIsParent = nsHTMLElement::IsChildOfHead(theChildTag,isExclusive); //判断该child是否能够是直属于head下的直接子节点 switch (theChildTag) { //根据theChildTag的类型进行选择 case eHTMLTag_area: //如果是area节点 if (!mOpenMapCount) { //如果此时没有Map节点被打开 isTokenHandled = PR_TRUE; //直接认为其已经被处理了 } STOP_TIMER(); MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:CNavDTD::HandleStartToken(), this=%p\n",this)); if (mOpenMapCount > 0 &&mSink) { //如果此时有map节点被打开,并且mSink不为空 result = mSink->AddLeaf(*theNode); //添加该<area>节点为叶节点 isTokenHandled = PR_TRUE; //token处理完毕 } MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::HandleStartToken(), this=%p\n",this)); START_TIMER(); break; caseeHTMLTag_image: //如果是image型节点,则重新设置其类型为eHMTLTag_img(算是一个特殊处理) aToken->SetTypeID(theChildTag = eHTMLTag_img); break; case eHTMLTag_keygen: //如果是<keygen>节点,也将其作为一种特殊节点,用HandleKeyGen进行处理,并设置标示位 result = HandleKeyGen(theNode); isTokenHandled = PR_TRUE; break; case eHTMLTag_script: // Script isn't really exclusively in thehead. However, we treat it // as such to make sure that we don't pullscripts outside the head // into thebody. // XXX Where does the script go in a framesetdocument? //脚本并不是只能位于head中的元素。但是,我们仍然这样去处理它,以便我们不会从head中将script节点取出来并放置到body里。 //代码作者注:如果是frameset文档中的script该去哪里呢? isExclusive = !(mFlags & NS_DTD_FLAG_HAD_BODY); //设置isExclusive位 break; default:; } if (!isTokenHandled) { //如果此时token还没有被处理(或者说该标示位没有被设置) PRBool prefersBody = gHTMLElements[theChildTag].HasSpecialProperty(kPreferBody); //我们判断一下该节点是否拥有kPreferBody标示位,该位标示该类型的节点更希望位于Body中 // If this tag prefers to be in the head (whenneither the head nor the // body have been explicitly opened) thencheck that we haven't seen the // body (true until the <body> tag hasreally been seen). Otherwise, // check if the head has been explicitlyopened. See bug 307122. //如果这个tag更希望位于head中(当无论是head还是body都没有被显示地打开的情况下),那么查看一下我们是否还没有看到body(这个标示位是true,直到我们看到<body>节点为止)。其他情况下,检查一下当前是否有head节点被显示地打开了。请查看bug 307122 //重新设置一下theHeadIsParent theHeadIsParent = theHeadIsParent&& (isExclusive || (prefersBody ? (mFlags &NS_DTD_FLAG_HAS_EXPLICIT_HEAD) && (mFlags &NS_DTD_FLAG_HAS_OPEN_HEAD) : !(mFlags &NS_DTD_FLAG_HAS_MAIN_CONTAINER))); if (theHeadIsParent) { // These tokens prefer to be in the head. //此时说明该token更喜欢在head中 result = AddHeadContent(theNode); //直接将其加入到head中 } else { //否则就调用之前的默认方法HandleDefaultStartToken对其进行处理 result = HandleDefaultStartToken(aToken, theChildTag, theNode); } } // Now do any post processing necessary on thetag... // 现在对该tag进行一些必要的善后处理 if (NS_OK == result) { DidHandleStartTag(*theNode, theChildTag); } } } //如果结果为栈的长度过深了 if (kHierarchyTooDeep == result) { // Reset this error to ok; all that happenshere is that given inline tag // gets dropped because the stack is too deep.Don't terminate parsing. //重新设置这个错误结果为ok;这里的全部处理实际上就是因为栈过深了,我们抛弃了当前的inline类型tag。不要终止解析进程。 result = NS_OK; } IF_FREE(theNode, &mNodeAllocator); //释放theNode return result; //返回结果 } /** * Callthis to see if you have a closeable peer on the stack that * isABOVE one of its root tags. * * @update gess 4/11/99 * @param aRootTagList -- list ofroot tags for aTag * @param aTag -- tag to test forcontainership * @return PR_TRUE if given tag cancontain other tags */ //调用这个方法来判断在当前的栈中,该节点的根节点集合“之上”是否有一个可以被关闭的对应节点 staticPRBool HasCloseablePeerAboveRoot(const TagList& aRootTagList, nsDTDContext&aContext, eHTMLTags aTag,PRBool anEndTag) { //从当前上下文栈中找到第一个属于aRootTagList中的元素 PRInt32 theRootIndex =LastOf(aContext, aRootTagList); //根据传递的参数anEndTag来判断是关闭结束型节点还是起始型节点,并获取相应的该节点能够进行自动关闭的节点类型 const TagList* theCloseTags = anEndTag ?gHTMLElements[aTag].GetAutoCloseEndTags() :gHTMLElements[aTag].GetAutoCloseStartTags(); PRInt32 theChildIndex = -1; //如果theCloseTags不为空 if (theCloseTags) { theChildIndex=LastOf(aContext, *theCloseTags); //从当前上下文中找到theCloseTags中最近一次出现的标签 } else if(anEndTag || !gHTMLElements[aTag].CanContainSelf()) { //否则说明theCloseTags为空,我们判断一下,如果当前是结束型节点,并且aTag属于不能够包含自己的类型 theChildIndex = aContext.LastOf(aTag); //我们获取最近一次aTag出现的位置,因此此时aTag不能包含自己,比如<li>Text<li>Text2,那么第二个<li>就可以关闭第一个<li> } // I changed this to theRootIndex<=theChildIndex so tohandle this case: // <SELECT><OPTGROUP>...</OPTGROUP> //代码作者将这里改成了theRootIndex<=theChildIndex,这样可以处理下面这种情况: // <SELECT><OPTGROUP>…</OPTGROUP> return theRootIndex<=theChildIndex; //确保theRootIndex在theChildIndex之上,即在根节点“之上”,比如前面举的那个例子,此时rootIndex和childIndex都指向了第一个<OPTGROUP> } /** * Thismethod is called to determine whether or not an END tag * canbe autoclosed. This means that based on the current * context, the stack should be closed to the nearest matching * tag. * * @param aTag -- tag enum of childto be tested * @return PR_TRUE if autoclosureshould occur */ //这个方法被调用以确定一个结束型节点能够被自动关闭。这意味着基于当前的上下文,该栈应当被关闭至最近一个匹配的节点。 staticeHTMLTags FindAutoCloseTargetForEndTag(eHTMLTagsaCurrentTag, nsDTDContext& aContext, nsDTDMode aMode) { //获取当前的栈大小 int theTopIndex = aContext.GetCount(); //获取栈顶元素 eHTMLTags thePrevTag = aContext.Last(); //如果当前的节点是一个容器类型,说明当前节点应当被打开了 if (nsHTMLElement::IsContainer(aCurrentTag)) { PRInt32 theChildIndex = nsHTMLElement::GetIndexOfChildOrSynonym(aContext, aCurrentTag); //获取当前节点类型或其同义节点的上一次出现的位置 if (kNotFound < theChildIndex) { //如果找到了 if (thePrevTag ==aContext[theChildIndex]) { //如果该节点就是当前的栈顶节点 return aContext[theChildIndex]; //直接返回该节点 } if(nsHTMLElement::IsBlockCloser(aCurrentTag)) { //运行到这里说明该元素不是栈顶元素,首先我们判断一下该节点是否是可以关闭Block级元素的节点,如果是则继续下面的代码 /* * Here's the algorithm: * Our here is sitting at aChildIndex.There are other tags above it * on the stack. We have to try toclose them out, but we may encounter * one that can block us. The way totell is by comparing each tag on * the stack against our closeTag androotTag list. * * For each tag above our hero on thestack, ask 3 questions: * 1. Is it in the closeTag list? Ifso, the we can skip over it * 2. Is it in the rootTag list? If so,then we're gated by it * 3. Otherwise its non-specified andwe simply presume we can close it. */ //下面的算法简介: //到这里,我们正处于aChildIndex位置之上。栈中在它之上还有一些其它的节点。我们需要尝试关闭它们,但是在这途中我们可能会遇到一个阻止我们的节点。为了确保正确,我们需要将栈中的每个节点和我们closeTag和rootTag列表中的节点进行比较。 //对于我们栈中位于这个特殊节点之上的所有节点,问三个问题: //1. 它是不是在closeTag列表中?如果是,则我们跳过它不处理 //2. 它是不是在rootTag列表中?如果是,那么我们就被挡住了 //3. 其他情况下,它是一个没有特殊意义的节点,我们就简单地认为它可以被关闭。 const TagList* theCloseTags = //获取能够被这个结束型节点自动关闭的节点集合 gHTMLElements[aCurrentTag].GetAutoCloseEndTags(); const TagList* theRootTags = gHTMLElements[aCurrentTag].GetEndRootTags(); if (theCloseTags) { //如果theCloseTags不为空 // At a mininimum, this code is needed for H1..H6 //最起码,这段代码对于H1…H6还是必要的 while (theChildIndex < --theTopIndex){ //对栈顶到theChildIndex的元素进行遍历 eHTMLTags theNextTag =aContext[theTopIndex]; if(!FindTagInSet(theNextTag, theCloseTags->mTags, theCloseTags->mCount) && FindTagInSet(theNextTag,theRootTags->mTags, theRootTags->mCount)) { // Weencountered a tag in root list so fail (we're gated). //我们遇到了一个位于root list中的节点,因此我们失败了(我们被挡住了) returneHTMLTag_unknown; } //Otherwise, presume it's something we can simply ignore and //continue searching. //其他情况下,认为它是一些我们可以忽略的东西,并继续搜索 } eHTMLTags theTarget = aContext.TagAt(theChildIndex); //获取位于theChildIndex的节点 if (aCurrentTag != theTarget) { //如果当前节点不为theTarget aCurrentTag = theTarget; //将两者统一 } // If you make it here, we're ungated andfound a target! //如果运行到了这里,我们没有遭到阻塞,并找到了一个目标 return aCurrentTag; } } } else { // Ok, a much more sensible approach fornon-block closers; use the tag // group to determine closure: For example:%phrasal closes %phrasal, // %fontstyle and %special //好了,既然不是block级节点,那就方便多了:我们直接使用该tag集合来确定闭包:比如:%phrasal 可以关闭 %phrasal,%fontstyle 和 %special returngHTMLElements[aCurrentTag].GetCloseTargetForEndTag(aContext, theChildIndex, aMode); } } } return eHTMLTag_unknown; //不该运行到这里 } //下面这个方法是用来不断移出当前栈中的空白(White Space)型字符,直到遇到不为空白字符位置,要记得统计这些词条的行数 static void StripWSFollowingTag(eHTMLTags aChildTag,nsITokenizer* aTokenizer, nsTokenAllocator*aTokenAllocator, PRInt32* aNewlineCount) { if (!aTokenizer || !aTokenAllocator) { //如果当前没有分词器或词条分配器 return; //直接返回 } CToken* theToken = aTokenizer->PeekToken(); //察看分词器中的下一个token PRInt32 newlineCount = 0; //设置新行数为0 while (theToken) { //当theToken不为空时循环 eHTMLTokenTypes theType = eHTMLTokenTypes(theToken->GetTokenType()); //获取token类型 switch(theType) { //根据其类型进行判断 case eToken_newline: case eToken_whitespace: //如果是空白字符型 theToken = aTokenizer->PopToken(); //将其拿出分词器 newlineCount += theToken->GetNewlineCount(); //并且加上该token的行数 IF_FREE(theToken, aTokenAllocator); //释放该token theToken = aTokenizer->PeekToken(); //并继续察看下一个token break; default: //否则说明不是空白字符,中断循环 theToken = nsnull; break; } } if (aNewlineCount) { *aNewlineCount += newlineCount; //统计并加上新行数 } } /** * Thismethod gets called when an end token has been * encountered in the parse process. If the end tag matches * thestart tag on the stack, then simply close it. Otherwise, * wehave a erroneous state condition. This can be because we * havea close tag with no prior open tag (user error) or because * wescrewed something up in the parse process. I'm not sure * yethow to tell the difference. * * @param aToken -- next (start)token to be handled * @return PR_TRUE if all went well;PR_FALSE if error occured */ //下面这个方法当在解析过程中遇到了一个结束型节点的时候被调用。如果这个结束型节点符合栈中的某个起始型节点,那么简单地关闭上它即可。否则的话,我们就有了一个错误的情况了。这可能是由于我们遇到了一个没有起始节点的结束节点(用户在编写html的时候写错了)或者也可能由于我们之前的解析过程中某些地方搞砸了。我还不知道如何区分具体是什么情况导致的。 nsresult CNavDTD::HandleEndToken(CToken* aToken) { NS_PRECONDITION(nsnull != aToken, kNullToken); //首先确保aToken不为空 nsresult result = NS_OK; //默认处理结果为成功 eHTMLTags theChildTag =(eHTMLTags)aToken->GetTypeID(); //获取该Token的tag类型 // Begin by dumping any attributes (bug 143512) //首先要抛弃它的所有属性节点(bug 143512) //因为结束型节点中是不应当有属性节点的(但是某些处理可能会在该token后面加上一些属性token) CollectAttributes(nsnull, theChildTag, aToken->GetAttributeCount()); switch (theChildTag) { //根据节点tag类型进行判断 case eHTMLTag_link: case eHTMLTag_meta: break; //如果是link或meta型,直接退出该判断,不用对</meta>,</link>这种节点作任何处理 case eHTMLTag_head: //如果是</head> StripWSFollowingTag(theChildTag,mTokenizer, mTokenAllocator, !mCountLines ? nsnull: &mLineNumber); //先处理掉</head>后面跟随的所有的空字符节点(如换行,空格等) if(mBodyContext->LastOf(eHTMLTag_head) != kNotFound) { //如果此时确实有一个<head>节点被打开了 result = CloseContainersTo(eHTMLTag_head,PR_FALSE); //将<head>和</head>之间的所有节点都关闭掉 } mFlags &= ~NS_DTD_FLAG_HAS_EXPLICIT_HEAD; //设置标示位,表示现在已经没有<head>节点被打开了 break; case eHTMLTag_form: //如果是</form> result = CloseContainer(eHTMLTag_form, PR_FALSE); //我们关闭当前打开的form容器 break; case eHTMLTag_br: { // This is special NAV-QUIRKS code that allowsusers to use </BR>, even // though that isn't a legitimate tag. //这是一个特殊的NAV-QUIRKS代码,允许用户使用</BR>,即使这并不是一个合法的标签 if (eDTDMode_quirks == mDTDMode) { //如果当前解析模式是quirks // Use recycler and pass the token thro'HandleToken() to fix bugs // like 32782. // 使用回收器将token交给HandleToken()以修正类似bug 32782的问题 //创建一个<BR> CToken* theToken = mTokenAllocator->CreateTokenOfType(eToken_start, theChildTag); //并且调用HandleToken处理该<BR> result = HandleToken(theToken); } } break; case eHTMLTag_body: case eHTMLTag_html: //如果是body或html,则将它们之后跟随的空字符型节点都去掉 StripWSFollowingTag(theChildTag, mTokenizer, mTokenAllocator, !mCountLines ? nsnull: &mLineNumber); break; case eHTMLTag_script: // Note: we don't fall through to the defaultcase because // CloseContainersTo() has the bad habit ofclosing tags that are opened // by document.write(). Fortunately, thetokenizer guarantees that no // actual tags appear between <script>and </script> so we won't be // closing the wrong tag. //注意:这里我们不会到达default的环节,因为CloseContainerTo()常常会将document.write()所打开的节点关闭掉。幸运地是,分词其确保了在<script>和</script>之间不会出现任何的tag,这样我们不会关闭掉错误的节点。 if (mBodyContext->Last() !=eHTMLTag_script) { //如果此时的栈顶元素不是script // Except if we're here, then there's probablya stray script tag. //如果我们到了这里,说明此时可能有一个没有配对的<script>节点 NS_ASSERTION(mBodyContext->LastOf(eHTMLTag_script) == kNotFound, "Mishandlingscripts in CNavDTD"); //确保此时栈内已经没有script节点了 break; //直接退出,不处理 } mBodyContext->Pop(); //从栈中取出该节点 result = CloseContainer(eHTMLTag_script, aToken->IsInError()); //关闭script容器 break; default: { // Now check to see if this token should be omitted,or // if it's gated from closing by the presenceof another tag. //现在检查一下这个token是否应当被omit,或者它被某个其它节点阻止了其关闭 if(gHTMLElements[theChildTag].CanOmitEndTag()) { //一般只有在其不是Container的情况下才能被Omit,或者是该节点有特殊的设置才行 PopStyle(theChildTag); //将该节点的Style弹出 } else { //获取当前栈顶元素 eHTMLTags theParentTag = mBodyContext->Last(); // First open transient styles, so that we seeany autoclosed style // tags. //首先打开transient style栈,这样我们就能看到所有被自动关闭的sylte节点了 if(nsHTMLElement::IsResidualStyleTag(theChildTag)) { //如果该节点是ResidualStyle型节点 result =OpenTransientStyles(theChildTag); //打开相应的transient style容器,这个方法主要是配合style栈进行的,确保当前的上下文style正确 if(NS_FAILED(result)) { returnresult; } } //下面作一个判断,如果我们没有在当前上下文中找到相应的theChildTag节点,或者其同义节点 if (kNotFound == nsHTMLElement::GetIndexOfChildOrSynonym(*mBodyContext, theChildTag)) { // Ref:bug 30487 // Makesure that we don't cross boundaries, of certain elements, // toclose stylistic information. //<fontface="helvetica"><table><tr><td></font></td></tr></table>some text... // In theabove ex. the orphaned FONT tag, inside TD, should cross // TDboundary to close the FONT tag above TABLE. //关联: bug 30487 //确保对于一些特定的元素我们不会越界关闭style相关的信息。 //<font face=”helvetica”><table><tr><td></font></td></tr></table>文本内容 //在上面的这个例子中,这个在TD中单独存在的FONT标签,应当跨越出<TD>的边界,并关闭TABLE之上的这个<font> staticeHTMLTags gBarriers[] = { //设置一下边界标签的类型 eHTMLTag_thead, eHTMLTag_tbody,eHTMLTag_tfoot, eHTMLTag_table }; //下面作一个判断,如果我们没有在gBarriers中找到相应的标签,并且theChildTag是ResidualStyle类型标签 if(!FindTagInSet(theParentTag, gBarriers, NS_ARRAY_LENGTH(gBarriers)) && nsHTMLElement::IsResidualStyleTag(theChildTag)) { // Fixbug 77746 //修正bug 77746 mBodyContext->RemoveStyle(theChildTag); //去掉theChildTag在的style栈中的内容 } // If thebit kHandleStrayTag is set then we automatically open up a //matching start tag (compatibility). Currentlythis bit is set on // Ptag. This also fixes Bug: 22623 //如果kHandleStrayTag被设置了那么我们会自动打开一个匹配的起始型tag(兼容性)。目前这个bit只有<P>标签才有。这同样还修正了Bug: 22623 if(gHTMLElements[theChildTag].HasSpecialProperty(kHandleStrayTag) && mDTDMode !=eDTDMode_full_standards && mDTDMode !=eDTDMode_almost_standards) { //如果有kHandleStrayTag被设置了,且当前的解析模式不是standards(就剩下quirks了吧?) // Ohboy!! we found a "stray" tag. Nav4.x and IE introduce line //break in such cases. So, let's simulate that effect for //compatibility. // Ex.<html><body>Hello</P>There</body></html> //我们找到了一个单独的tag。Nav4.x和IE遇到这种情况时使用了换行符。因此,我们为了兼容性也来模拟这种处理方式吧 //例子:<html><body>Hello</P>There</body></html> PRInt32 theParentContains = -1; if(!CanOmit(theParentTag, theChildTag, theParentContains)) { CToken* theStartToken = mTokenAllocator->CreateTokenOfType(eToken_start, theChildTag); NS_ENSURE_TRUE(theStartToken,NS_ERROR_OUT_OF_MEMORY); //This check for NS_DTD_FLAG_IN_MISPLACED_CONTENT was added // tofix bug 142965. //这个对于NS_DTD_FLAG_IN_MISPLACED_CONTENT标示位的检查是用来修正bug142965的 if (!(mFlags & NS_DTD_FLAG_IN_MISPLACED_CONTENT)) { //We're not handling misplaced content right now, just push //these new tokens back on the stack and handle them in the //regular flow of HandleToken. //我们现在没有在处理错误内容,只需要将新的tokens压回栈里并采用标准的HandleToken()处理即可 IF_HOLD(aToken); mTokenizer->PushTokenFront(aToken); //将该token入栈 mTokenizer->PushTokenFront(theStartToken); //将对应的起始节点token入栈 } else{ //Oops, we're in misplaced content. Handle these tokens //directly instead of trying to push them onto the tokenizer //stack. //哦,我们当前位于错误内容之中。直接对这些token进行处理,不用尝试将他们压入分词器的栈了 result =HandleToken(theStartToken); //处理该起始型token NS_ENSURE_SUCCESS(result,result); IF_HOLD(aToken); result = HandleToken(aToken); //处理该结束型token } } } returnresult; } if (result == NS_OK) { //如果前面的处理没有出错 //调用前面的方法,寻找其对应的起始关闭节点 eHTMLTags theTarget = FindAutoCloseTargetForEndTag(theChildTag, *mBodyContext, mDTDMode); if(eHTMLTag_unknown != theTarget) { //如果查找成功 result =CloseContainersTo(theTarget, PR_FALSE); //直接将这两个节点之间的所有容器关闭 } } } } break; } return result; } /** * This method will be triggered when the endof a table is * encountered. Its primary purpose is to process all the * bad-contents pertaining a particular table.The position * of the table is the token bank ID. * * @update harishd 03/24/99 * @param aTag - This ought to be a table tag * */ //这个方法会在遇到一个table的结尾时被调用。它的主要目的是处理附属于某个table的所有错误内容。Table的位置是就是token的bankID nsresult CNavDTD::HandleSavedTokens(PRInt32anIndex) { NS_PRECONDITION(mBodyContext != nsnull &&mBodyContext->GetCount() > 0,"invalidcontext"); nsresult result = NS_OK; if (mSink && (anIndex > kNotFound)) { PRInt32theBadTokenCount = mMisplacedContent.GetSize(); //获取当前错误内容的数量 if (theBadTokenCount > 0) { mFlags |= NS_DTD_FLAG_IN_MISPLACED_CONTENT; //打开当前的错误内容标示位 if (!mTempContext &&!(mTempContext =new nsDTDContext())) { //尝试创建临时上下文,如果当前的临时上下文不存在,且进行创建 return NS_ERROR_OUT_OF_MEMORY; //若创建失败,则说明没内存了 } CToken* theToken; eHTMLTags theTag; PRInt32 attrCount; PRInt32 theTopIndex = anIndex + 1; PRInt32 theTagCount =mBodyContext->GetCount(); PRBool formWasOnStack =mSink->IsFormOnStack(); if (formWasOnStack) { // Do this to synchronize dtd stack and thesink stack. // Note: FORM is never on the dtd stackbecause its always // considered as a leaf. However, in the sinkFORM can either // be a container or a leaf. Therefore, wehave to check // with the sink -- Ref: Bug 20087. //下面这样做以使得dtd栈和sink栈同步。 //注意:FORM从来不存在于dtd的栈上,因为它总是被被当作一个叶节点来处理。然而,在sink中FORM即可以是一个容器也可以是一个叶节点。因此,我们需要检查一下sink。--参考:Bug 20087 ++anIndex; } STOP_TIMER() MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:CNavDTD::HandleSavedTokensAbove(), this=%p\n",this)); // Pause the main context and switch to thenew context. //暂停主要的上下文,并切换到新的上下文 //直接调用mSink的BeginContext启动新的sinkcontext result = mSink->BeginContext(anIndex); MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::HandleSavedTokensAbove(), this=%p\n",this)); START_TIMER() NS_ENSURE_SUCCESS(result, result); // The body context should contain contentsonly upto the marked position. // body上下文应当只包含到标记位置为止的内容 //下面这个方法,将mBodyContext栈中自栈顶开始theTagCount – theTopIndex数量的节点放置到mTempContext中 mBodyContext->MoveEntries(*mTempContext, theTagCount - theTopIndex); // Now flush out all the bad contents. //现在抛弃掉所有错误内容的节点 while (theBadTokenCount-- > 0){ theToken = (CToken*)mMisplacedContent.PopFront(); //取出栈顶的元素 if (theToken) { theTag =(eHTMLTags)theToken->GetTypeID(); //获取该节点的类型 attrCount =theToken->GetAttributeCount(); //获取属性的数量 // Put back attributes, which once got poppedout, into the // tokenizer. Make sure we preserve their ordering, however! // XXXbz would it be faster to get the tokensout with ObjectAt and // put them in the tokenizer and then PopFrontthem all from // mMisplacedContent? //将刚刚被取出的属性节点放回分词器中。但是要注意确保他们的顺序不变! //XXXbz如果用ObjectAt将这些tokens取出,并且将他们放回分词器,并使用PopFront将他们全部从mMisplacedContent中取出,这样会不会更快? nsDeque temp; //设置一个临时队列 for (PRInt32 j = 0; j < attrCount;++j) { //根据属性节点的数量 CToken* theAttrToken =(CToken*)mMisplacedContent.PopFront(); //将这些属性节点取出 if(theAttrToken) { temp.Push(theAttrToken); //如果取出成功,则将他们放到临时队列中 } theBadTokenCount--; } mTokenizer->PrependTokens(temp); //之后将这个队列重新放置到分词器中 if(eToken_end == theToken->GetTokenType()) { //如果当前节点类型为结束型节点 // Ref:Bug 25202 // Makesure that the BeginContext() is ended only by the call to //EndContext(). Ex: <center><table><a></center>. // In theEx. above </center> should not close <center> above table. // Doingso will cause the current context to get closed prematurely. // 相关:Bug25202 // 确保只有调用EndContext()才能终止BeginContext()。例子:<center><table><a></center>。 // 在这个例子中。上面的</center>不应当关闭<table>之外的<center>。 // 这样做会导致当前的上下文过早地被关闭。 eHTMLTags closed =FindAutoCloseTargetForEndTag(theTag, *mBodyContext, mDTDMode); //确保获取的关闭目标节点不为空 PRInt32 theIndex = closed !=eHTMLTag_unknown ?mBodyContext->LastOf(closed) : kNotFound; if(theIndex != kNotFound && theIndex <=mBodyContext->mContextTopIndex) { //注意到,这里并没有对其做任何处理,如前面的例子中,就是为了不让其关闭其不应当关闭的节点 //释放theToken,并跳过对其进行处理,直接继续下一次循环 IF_FREE(theToken,mTokenAllocator); continue; } } // XXX This should go away, with this call, itbecomes extremely // difficult to handle misplaced style andlink tags, since it's hard // to propagate the block return all the wayup and then re-enter this // method. //XXX这个应当取消掉,通过这个调用,使得其非常难以处理错误的style和link节点,因为很难以从block的返回值进行“延展”后又重新进入这个方法。 result = HandleToken(theToken); } } if (theTopIndex !=mBodyContext->GetCount()) { // CloseContainersTo does not close any formswe might have opened while // handling saved tokens, because the parserdoes not track forms on its // mBodyContext stack. // CloseContainersTo()方法并不能够关闭任何我们可能在处理saved tokens时打开的form节点,因为此时的parser并没有在它的mBodyContext栈上对form元素们进行追踪 //forms本身就不存在于mBodyContext栈上 //对容器进行关闭 CloseContainersTo(theTopIndex, mBodyContext->TagAt(theTopIndex), PR_TRUE); } if (!formWasOnStack &&mSink->IsFormOnStack()) { //双重确认 // If a form has appeared on the sink contextstack since the beginning of // HandleSavedTokens, have the sink close it: //如果在handleSavedTokens的开始时一个form就出现在了sink的上下文中,我们让sink去关闭它 mSink->CloseContainer(eHTMLTag_form); //关闭该form } // Bad-contents were successfully processed.Now, itz time to get // back to the original body context state. // 错误内容被成功的处理了。现在,是时候恢复原始的body上下文了。 mTempContext->MoveEntries(*mBodyContext, theTagCount - theTopIndex); STOP_TIMER() MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:CNavDTD::HandleSavedTokensAbove(), this=%p\n",this)); // Terminate the new context and switch backto the main context mSink->EndContext(anIndex); //关闭新的context并切换至新的主体上下文 MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::HandleSavedTokensAbove(), this=%p\n",this)); START_TIMER() mFlags &= ~NS_DTD_FLAG_IN_MISPLACED_CONTENT; } } return result; } /** * Thismethod gets called when an entity token has been * encountered in the parse process. * * @update gess 3/25/98 * @param aToken -- next (start)token to be handled * @return PR_TRUE if all went well;PR_FALSE if error occured */ //这个方法是在解析过程中遇到entity词条所进行调用的 nsresult CNavDTD::HandleEntityToken(CToken*aToken) { NS_PRECONDITION(nsnull != aToken, kNullToken); nsresult result = NS_OK; const nsSubstring& theStr =aToken->GetStringValue(); //获取该token的字符串值 if (kHashsign != theStr.First() && -1 == nsHTMLEntities::EntityToUnicode(theStr)) { //进行一下判断,如果此时该entity的开头字符不为#,且无法将其转换为字符Unicode // If you're here we have a bogus entity. // Convert it into a text token. //如果程序运行到了这里,说明我们遇到了一个假冒的entity //我们直接 将其转换为文本token CToken*theToken; nsAutoString entityName; entityName.AssignLiteral("&"); //将’&’赋值给entityName entityName.Append(theStr); //将theStr附加到entityName后面 //创建一个文本类型的token,其字符串值就是刚刚生成的entityName theToken= mTokenAllocator->CreateTokenOfType(eToken_text, eHTMLTag_text, entityName); NS_ENSURE_TRUE(theToken,NS_ERROR_OUT_OF_MEMORY); //确保分配正常,内存没有错误 // theToken should get recycled automagically... //该token应该会自动被释放 return HandleToken(theToken); //然后我们将新生成的文本型token直接放入HandleToken进行正常的处理 } //运行到这里说明我们遇到的确实是entity型的token eHTMLTags theParentTag = mBodyContext->Last(); //获取当前上下文的最后一个节点 nsCParserNode* theNode = mNodeAllocator.CreateNode(aToken,mTokenAllocator); //为aToken创建一个aToken的parserNode节点 NS_ENSURE_TRUE(theNode, NS_ERROR_OUT_OF_MEMORY); PRInt32 theParentContains = -1; //下面的处理方法就很简单了,判断该entity节点是否可以被omit到父节点中,如果不能则将其作为叶节点而添加 if (CanOmit(theParentTag, eHTMLTag_entity, theParentContains)){ eHTMLTags theCurrTag = (eHTMLTags)aToken->GetTypeID(); HandleOmittedTag(aToken, theCurrTag, theParentTag, theNode); } else { result = AddLeaf(theNode); //将其作为叶节点添加 } IF_FREE(theNode, &mNodeAllocator); return result; } /** * Thismethod gets called when a comment token has been * encountered in the parse process. After making sure * we'resomewhere in the body, we handle the comment * inthe same code that we use for text. * * @update gess 3/25/98 * @param aToken -- next (start) token to be handled * @return PR_TRUE if all went well;PR_FALSE if error occured */ //这个方法会在解析过程中遇到一个注释型token的时候被触发。在确信了我们此时处于body中的某处后,我们将这个注释型节点当做文本节点一样进行处理。 //处理注释型节点 nsresult CNavDTD::HandleCommentToken(CToken* aToken) { NS_PRECONDITION(nsnull != aToken, kNullToken); //首先必然是需要为aToken创建一个相应类型的ParserNode nsCParserNode* theNode = mNodeAllocator.CreateNode(aToken,mTokenAllocator); NS_ENSURE_TRUE(theNode, NS_ERROR_OUT_OF_MEMORY); STOP_TIMER(); MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:CNavDTD::HandleCommentToken(), this=%p\n",this)); //调用mSink的AddComment方法进行添加,当然要首先确保mSink存在,否则直接返回NS_OK(虽然不添加了,但是可以确保程序不出错) nsresult result = mSink ? mSink->AddComment(*theNode) : NS_OK; IF_FREE(theNode, &mNodeAllocator); //释放该节点 MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::HandleCommentToken(), this=%p\n",this)); START_TIMER(); return result; } /** * Thismethod gets called when an attribute token has been * encountered in the parse process. This is an error, since * allattributes should have been accounted for in the prior * startor end tokens * * @update gess 3/25/98 * @param aToken -- next (start)token to be handled * @return PR_TRUE if all went well;PR_FALSE if error occured */ //下面这个方法会在解析过程中遇到一个属性token时被调用。这实际上是一个错误,因为所有的属性都应当在处理开始和结束型token之前被处理。 nsresult CNavDTD::HandleAttributeToken(CToken*aToken) { NS_ERROR("attribute encountered -- thisshouldn't happen."); return NS_OK; } /** * Thismethod gets called when an "instruction" token has been * encountered in the parse process. * * @update gess 3/25/98 * @param aToken -- next (start)token to be handled * @return PR_TRUE if all went well;PR_FALSE if error occured */ //这个方法会在解析过程中遇到一个”instruction”型节点的时候被调用 nsresult CNavDTD::HandleProcessingInstructionToken(CToken*aToken) { NS_PRECONDITION(nsnull != aToken, kNullToken); //首先创建parserNode nsCParserNode* theNode = mNodeAllocator.CreateNode(aToken,mTokenAllocator); NS_ENSURE_TRUE(theNode, NS_ERROR_OUT_OF_MEMORY); STOP_TIMER(); MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:CNavDTD::HandleProcessingInstructionToken(), this=%p\n",this)); //直接调用ContentSink的相应方法AddProcessingInstruction对其进行处理 nsresult result = mSink ? mSink->AddProcessingInstruction(*theNode) :NS_OK; IF_FREE(theNode, &mNodeAllocator); MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::HandleProcessingInstructionToken(), this=%p\n",this)); START_TIMER(); return result; } /** * Thismethod gets called when a DOCTYPE token has been * encountered in the parse process. * * @update harishd 09/02/99 * @param aToken -- The very firsttoken to be handled * @return PR_TRUE if all went well;PR_FALSE if error occured */ //下面这个方法会在解析过程中遇到一个DOCTYPE标签时被调用 nsresult CNavDTD::HandleDocTypeDeclToken(CToken*aToken) { NS_PRECONDITION(nsnull != aToken, kNullToken); //首先尝试将aToken强制转换成CDoctypeDeclToken类型的token CDoctypeDeclToken* theToken = static_cast<CDoctypeDeclToken*>(aToken); //之后获取该token的string值 nsAutoString docTypeStr(theToken->GetStringValue()); // XXX Doesn't this count the newlines twice? // XXX 作者注:这里不会重复计算了两次新行数么? if (mCountLines) { mLineNumber += docTypeStr.CountChar(kNewLine); } PRInt32 len = docTypeStr.Length(); //获得当前docTypeStr的长度 PRInt32 pos = docTypeStr.RFindChar(kGreaterThan); //获取’>’的位置 if (pos != kNotFound) { // First remove '>' from the end. //首先从结尾的位置将’>’符号移除 docTypeStr.Cut(pos, len - pos); } // Now remove "<!" from the begining //现在在起始处除掉”<!”字符(必然是0~1位置的这两个字符) docTypeStr.Cut(0, 2); theToken->SetStringValue(docTypeStr); //重新将修改后的字符串传递给theToken //依旧首先创建一个ParserNode nsCParserNode* theNode = mNodeAllocator.CreateNode(aToken,mTokenAllocator); NS_ENSURE_TRUE(theNode, NS_ERROR_OUT_OF_MEMORY); STOP_TIMER(); MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:CNavDTD::HandleDocTypeDeclToken(), this=%p\n",this)); nsresult result = mSink ? mSink->AddDocTypeDecl(*theNode) : NS_OK; IF_FREE(theNode, &mNodeAllocator); MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::HandleDocTypeDeclToken(), this=%p\n",this)); START_TIMER(); return result; } /** * Retrieve the attributes for this node, andadd then into * the node. * * @update gess4/22/98 * @param aNode is the node you want to collect attributes for * @param aCount is the # of attributes you're expecting * @return error code (should be 0) */ //获取这个node相关的所有属性节点,然后再将他们添加到node中去 nsresult CNavDTD::CollectAttributes(nsIParserNode*aNode, eHTMLTags aTag, PRInt32 aCount) { int attr = 0; nsresult result = NS_OK; int theAvailTokenCount = mTokenizer->GetCount(); if (aCount <= theAvailTokenCount) { //首先得确保属性节点的数量少于剩余可用节点的数量 CToken* theToken; for (attr = 0; attr < aCount; ++attr){ //循环遍历所有属性节点 theToken = mTokenizer->PopToken(); //出栈一个节点 if(theToken) { //如果该节点存在 eHTMLTokenTypes theType = eHTMLTokenTypes(theToken->GetTokenType()); //获取该token的类型 if (theType != eToken_attribute) { //如果当前节点的类型不为属性节点 // If you're here then it means that the tokendoes not //belong to this node. Put the token back into the tokenizer // and let it go thro' the regular path. Bug:59189. //如果程序运行到了这里说明该token并不属于这个node。将这个token重新放回tokenizer中,让其回归到正常的处理流程中去 //这种情况应该只有在aCount出错的情况下发生 mTokenizer->PushTokenFront(theToken); break; } if (mCountLines) { mLineNumber += theToken->GetNewlineCount(); //将新增的行数计算进去 } if (aNode) { //如果节点不为空 // If the key is empty, the attribute isunusable, so we should not // add it to the node. //如果该属性的键为空,那么该属性是无效的,因此我们不应当将其加入到节点中 if(!((CAttributeToken*)theToken)->GetKey().IsEmpty()) { aNode->AddAttribute(theToken); //不为空则将其加入到aNode中 } else { IF_FREE(theToken, mTokenAllocator); //为空则直接释放掉该无用节点 } } else { //如果此时aNode为空,我们无法对其进行属性添加,直接释放掉该属性节点 IF_FREE(theToken, mTokenAllocator); } } } } else { //说明此时theToken为空,基本可以说明已经到了文档末尾 result = kEOF; //设置结果为kEOF } return result; //返回结果 } /** * Thismethod is called to determine whether or not a tag * ofone type can contain a tag of another type. * * @update gess 4/8/98 * @param aParent -- tag enum ofparent container * @param aChild -- tag enum ofchild container * @return PR_TRUE if parent cancontain child */ //这个方法用来判断一个tag下能否包含另一个tag NS_IMETHODIMP_(PRBool) CNavDTD::CanContain(PRInt32 aParent,PRInt32 aChild) const { //首先调用父节点的CanContain方法,并结合当前的DTD模式和子节点类型进行判断,得到初步判断结果:即原则上该类型的父节点能否包含该类型的子节点 PRBoolresult = gHTMLElements[aParent].CanContain((eHTMLTags)aChild, mDTDMode); //除此之外,我们还需要对该子节点的类型进行判断,确保其不是nobr型tag,并且确保其父节点不是inline型元素,并且当前上下文中没有nobr型tag处于打开状态 if (eHTMLTag_nobr == aChild && IsInlineElement(aParent, aParent) && HasOpenContainer(eHTMLTag_nobr)) { return PR_FALSE; //否则就返回false } return result; } /** * Thismethod is called to determine whether or not * thegiven childtag is a block element. * * @update gess 6June2000 * @param aChildID -- tag id of child * @param aParentID -- tag id ofparent (or eHTMLTag_unknown) * @return PR_TRUE if this tag is ablock tag */ //这个方法被调用来判断一个给定的childTag是否是一个block级元素 PRBool CNavDTD::IsBlockElement(PRInt32 aTagID,PRInt32 aParentID) const { eHTMLTags theTag = (eHTMLTags)aTagID; //其实很简单,直接判断通过IsMemberOf方法来判断其相应的标示位即可了,因为特定的tag都会已经预先定义好了标示位 return (theTag > eHTMLTag_unknown &&theTag < eHTMLTag_userdefined) && (gHTMLElements[theTag].IsMemberOf(kBlock) || gHTMLElements[theTag].IsMemberOf(kBlockEntity) || gHTMLElements[theTag].IsMemberOf(kHeading) || gHTMLElements[theTag].IsMemberOf(kPreformatted) || gHTMLElements[theTag].IsMemberOf(kList)); } /** * Thismethod is called to determine whether or not * thegiven childtag is an inline element. * * @update gess 6June2000 * @param aChildID -- tag id ofchild * @param aParentID -- tag id ofparent (or eHTMLTag_unknown) * @return PR_TRUE if this tag is aninline tag */ //这个方法被用来判断给定的childTag是否是一个inline级元素 PRBool CNavDTD::IsInlineElement(PRInt32 aTagID,PRInt32 aParentID) const { eHTMLTags theTag = (eHTMLTags)aTagID; return (theTag > eHTMLTag_unknown &&theTag < eHTMLTag_userdefined) && (gHTMLElements[theTag].IsMemberOf(kInlineEntity) || gHTMLElements[theTag].IsMemberOf(kFontStyle) || gHTMLElements[theTag].IsMemberOf(kPhrase) || gHTMLElements[theTag].IsMemberOf(kSpecial) || gHTMLElements[theTag].IsMemberOf(kFormControl)); } /** * Thismethod is called to determine whether or not * thenecessary intermediate tags should be propagated * between the given parent and given child. * * @update gess 4/8/98 * @param aParent -- tag enum ofparent container * @param aChild -- tag enum ofchild container * @return PR_TRUE if propagationshould occur */ //这个方法被用来判断给定的aParent和aChild之间,是否应当对它们之间的tags进行延展 //这个方法可能不好理解:比如我们遇到了这样的序列<table><td>,那么我们就可以判断从<td>到<table>之间可以通过加入一个<tr>而变成<table><tr><td> PRBool CNavDTD::CanPropagate(eHTMLTags aParent,eHTMLTags aChild, PRInt32 aParentContains) { PRBool result = PR_FALSE; if (aParentContains == -1) { //如果此时的aParentContains为-1 aParentContains = CanContain(aParent, aChild); //我们重新判断其aParent是否能够包含aChild } if (aParent == aChild) { //如果此时aParent和aChild相等 return result; //那必然返回false了 } if (nsHTMLElement::IsContainer(aChild)) { //如果aChild是一个容器类型 mScratch.Truncate(); //清除mScratch,注意我们这里需要用到这个临时变量来记录过程了 if (!gHTMLElements[aChild].HasSpecialProperty(kNoPropagate)){ //首先aChild不能够拥有kNoPropagate这种显式禁止其进行延展的属性 if(nsHTMLElement::IsBlockParent(aParent) || //其次aParent必须属于block级的元素或aParent拥有SpecialChildren列表 gHTMLElements[aParent].GetSpecialChildren()) { //首先我们需要从Parent采用正向的延展,对aChild进行延展尝试,并将过程记录到mScratch中 result = ForwardPropagate(mScratch,aParent, aChild); if(!result) { //如果正向延展结果失败了 if(eHTMLTag_unknown != aParent) { //判断该aParent不为unknown的情况下 result = BackwardPropagate(mScratch,aParent, aChild); //尝试从aChild进行逆向延展,并将过程记录到mScratch中 } else { result =BackwardPropagate(mScratch, eHTMLTag_html, aChild); //如果aParent此时为unknown,那么我们就从aChild尝试去向最根部的<html>标签进行延展,以确定是否可以进行延展 } } } } //此时mScratch中应当记录的是延展所带来的路径 //如果此时的mScratch长度大于我们所能进行延展的最大距离 if (mScratch.Length() - 1 >gHTMLElements[aParent].mPropagateRange) { result = PR_FALSE; //设置结果为失败 } } else { result = !!aParentContains; //否则结果取决于父节点是否同意包含该子节点 } return result; } /** * Thismethod gets called to determine whether a given * tagcan be omitted from opening. Most cannot. * * @param aParent * @param aChild * @param aParentContains * @return PR_TRUE if given tag cancontain other tags */ //这个方法用来判断一个tag能否忽略容器打开的步骤。大部分都不能。 //这个方法就判断了在aParent下能否omit aChild节点 PRBool CNavDTD::CanOmit(eHTMLTags aParent,eHTMLTags aChild, PRInt32& aParentContains) { //首先获取aChild所含的ExcludingAncestor,这里面包含aChild所对应的一些特殊的父节点,这类父节点会阻止aChild打开 eHTMLTags theAncestor = gHTMLElements[aChild].mExcludingAncestor; //下面这个判断是在theAcestor不为unknow的情况下,并且当前上下文中存在一个该theAncestor类型的节点处于打开状态,也就是说我们目前位于这个节点之下 if (eHTMLTag_unknown != theAncestor &&HasOpenContainer(theAncestor)) { return PR_TRUE; //直接返回true,说明此时可以Omit该aChild节点 } //下面获取aChild所含的mRequiredAncestor,这里面包含一些aChild所必须要的祖先节点,比如<td>只能存在于<table>之下,那么<table>就是<td>的mRequiredAncestor theAncestor = gHTMLElements[aChild].mRequiredAncestor; if (eHTMLTag_unknown != theAncestor) { // If there's a required ancestor, we onlyomit if it isn't open and we // can't get to it through propagation. // 如果这里有一个必须的祖先节点,我们只能够在当前上下文中该祖先节点没有打开的情况下,并且我们无法通过延展到达它的情况下才进行omit return !HasOpenContainer(theAncestor)&& !CanPropagate(aParent, aChild,aParentContains); } if (gHTMLElements[aParent].CanExclude(aChild)) { return PR_TRUE; //如果该aParent能够 } // Now the obvious test: if the parent can contain thechild, don't omit. //下面是一个明显的测试条件:如果parent这里可以包含child节点,不要进行omit if (-1 == aParentContains) { aParentContains = CanContain(aParent, aChild); } if (aParentContains || aChild == aParent) { return PR_FALSE; } //下面判断,如果父节点是Block级元素,且child是inline级元素 if (gHTMLElements[aParent].IsBlockEntity() && nsHTMLElement::IsInlineEntity(aChild)) { // Feel free to drop inlines that a blockdoesn't contain. //无论block是否可以包含该inlines元素,都可进行进行投放 return PR_TRUE; } if(gHTMLElements[aParent].HasSpecialProperty(kBadContentWatch)) { // We can only omit this child if it does nothave the kBadContentWatch // special property. //我们只有在这个这个child没有kBadContentWatch特殊属性的情况下才能够进行Omit(具体哪些元素有或者没有,我们会在之后的nsIElementTable中进行解析和说明) return!gHTMLElements[aChild].HasSpecialProperty(kBadContentWatch); } //如果aParent拥有kSaveMisplaced属性,则直接返回True if(gHTMLElements[aParent].HasSpecialProperty(kSaveMisplaced)) { return PR_TRUE; } if (aParent == eHTMLTag_body) { // There are very few tags that the body doesnot contain. If we get here // the best thing to do is just drop them. //很少有BODY节点不能够包含的节点。如果程序到达了此处,我们直接将他们放在这里就可 //如果程序运行到了此处,且父节点是<body>节点,那么直接进行包含即可。 return PR_TRUE; } return PR_FALSE; } /** * Thismethod gets called to determine whether a given * tagis itself a container * * @update gess 4/8/98 * @param aTag -- tag to test as acontainer * @return PR_TRUE if given tag cancontain other tags */ //这个方法用来确定一个给定的tag是否是容器类型的tag NS_IMETHODIMP_(PRBool) CNavDTD::IsContainer(PRInt32 aTag) const { return nsHTMLElement::IsContainer((eHTMLTags)aTag); //直接调用该tag的isContainer方法进行判断即可 } //这个是正向的延展方法,即从aParent开始,我们看看能否从aChild延展到aParent。并且要注意的是,如果我们此时的aParent是<TR>,那么我们就要看看能否直接从<TD>开始,而不是从aChild开始 PRBool CNavDTD::ForwardPropagate(nsString&aSequence, eHTMLTags aParent, eHTMLTags aChild) { PRBool result = PR_FALSE; switch(aParent) { //首先根据父节点的类型进行选择判断 case eHTMLTag_table: //如果父节点是table类型 if (eHTMLTag_tr == aChild || eHTMLTag_td== aChild) { //且此时aChild恰好是td或者tf return BackwardPropagate(aSequence,aParent, aChild); //注意我们还是需要使用逆向的BackwardPropagate对其进行延展 } // Otherwise, intentionally fall through... //其他情况下,我们有意地跳过这段代码 case eHTMLTag_tr: //如果父节点是tr类型 if (CanContain(eHTMLTag_td, aChild)) { //如果td可以包含aChild类型 aSequence.Append((PRUnichar)eHTMLTag_td); //将td加入到aSequence中(作为起点) result = BackwardPropagate(aSequence, aParent, eHTMLTag_td); //我们尝试从td开始对aParent进行延展 } break; default: break; } return result; } //逆向延展 PRBool CNavDTD::BackwardPropagate(nsString&aSequence, eHTMLTags aParent, eHTMLTags aChild) const { eHTMLTags theParent = aParent; //下面这个循环,就是通过依次寻找从childTag开始的各个tag的第一个root tags,这些roottags是预先定义好的,我们会在后面的nsElementTable中进行解析和说明,这里我们只需要这样循环遍历,直到我们找到aParent或者为空为止,并且记录下这之间所经过的中间tag,形成一个序列(或者说一条路径) do { const TagList* theRootTags =gHTMLElements[aChild].GetRootTags(); //获取aChild节点的RootTags if (!theRootTags) { //如果theRootTags为空 break; //退出循环 } theParent = theRootTags->mTags[0]; //获取aChild的第一个mRootTags NS_ASSERTION(CanContain(theParent, aChild), "Childrenmust be contained by their root tags"); aChild = theParent; //将aChild赋值成theParent aSequence.Append((PRUnichar)theParent); //将theParent赋值到aSequence中去 } while(theParent != eHTMLTag_unknown && theParent != aParent); //在theParent不为aParent且theParent不为eHTMLTag_unknow时继续循环 return aParent == theParent; //我们只有在找到了aParent的情况下才能够算成功,此时aSequence中存放的就是我们进行延展所需要的所有中间节点了,我们举个例子,比如<table><td>,我们对<td>进行延展,最终通过两次访问mRootTags,会得到<table><tbody><tr><td> } //这个方法是用来判断当前上下文中是否存在aContainer类型的容器处于打开状态 PRBoolCNavDTD::HasOpenContainer(eHTMLTags aContainer) const { switch (aContainer) { //首先我们处理两种特殊的节点,一种是<form>,一种是<map>,这两种标签一旦打开,会在上下文中设置相应的标示位或变量,因此我们对相应的标示位进行判断即可 case eHTMLTag_form: return !(~mFlags &NS_DTD_FLAG_HAS_OPEN_FORM); //直接通过标示位来判断即可 case eHTMLTag_map: return mOpenMapCount > 0; //同样通过当前上下文的一个变量来进行判断 default: //其他类型的节点,采用默认方法处理 returnmBodyContext->HasOpenContainer(aContainer); //需要调用dtdContext的 } } //这个方法其实就是在当前的body上下文中寻找aTagSet中的元素,如果找到则返回True,否则返回False,很简单的一个方法 PRBool CNavDTD::HasOpenContainer(const eHTMLTags aTagSet[], PRInt32 aCount)const { int theIndex; int theTopIndex = mBodyContext->GetCount() - 1; //获取栈顶元素的编号 for (theIndex = theTopIndex; theIndex > 0;--theIndex) { if(FindTagInSet((*mBodyContext)[theIndex], aTagSet, aCount)) { return PR_TRUE; } } return PR_FALSE; } //下面这个方法用来获取当前body上下文的栈顶元素 eHTMLTags CNavDTD::GetTopNode() const { return mBodyContext->Last(); } /** * It is with great trepidation that I offerthis method (privately of course). * The gets called whenever a container getsopened. This methods job is to * take a look at the (transient) style stack,and open any style containers that * are there. Of course, we shouldn't bother toopen styles that are incompatible * with our parent container. * * @update gess6/4/98 * @param tag of the container just opened * @return 0 (for now) */ //我带着极大的忐忑提供出这个方法(当然只是个人想法)。这个方法会在一个容器被打开的时候被调用。这个方法的任务是看一下(transient)style栈,并且打开该处的所有style容器。当然,我们不用费心去打开和我们父容器不兼容的style。 //下面这个方法目前可能不太好理解,最好能够结合整体的解析过程去理解这个方法。该方法主要是对style栈进行的操作,其作用是对一些错误的style进行处理的一个流程。每当我们打开一个新的节点之时,首先要对当前的style栈进行判断,关闭错误的style并打开正确的style。 nsresult CNavDTD::OpenTransientStyles(eHTMLTagsaChildTag, PRBool aCloseInvalid) { nsresult result = NS_OK; // No need to open transient styles in head context - Fixfor 41427 // 不需要打开位于head上下文中的transientsytle ---为了修复41427bug if ((mFlags & NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE)&& //判断当前是处于打开RESIDUAL STYLE的模式 eHTMLTag_newline != aChildTag && //当前的aChildTag不为换行符 !(mFlags & NS_DTD_FLAG_HAS_OPEN_HEAD)) { //且当前不处于Head上下文中 if (CanContain(eHTMLTag_font,aChildTag)) { //如果<font>能够包含当前aChildTag PRUint32 theCount = mBodyContext->GetCount(); //获取当前body上下文的节点数量 PRUint32 theLevel = theCount; // This first loop is used to determine howfar up the containment // hierarchy we go looking for residualstyles. // 第一个循环用来确定我们寻找residual style的深度到底多远 while (1 < theLevel) { //从栈顶元素开始逐渐下行 eHTMLTags theParentTag = mBodyContext->TagAt(--theLevel); //判断当前元素是否有kNoStyleLeaksIn这个标示位 if(gHTMLElements[theParentTag].HasSpecialProperty(kNoStyleLeaksIn)) { //如果有则跳出循环,此时的level就是我们想要的深度,否则循环会一直持续到2位置,并且在跳出最后一次循环的时候level的值为1 break; } } mFlags &= ~NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE; //下面一个循环,就是从level的元素开始,逐渐上行直到栈顶元素为止 for (; theLevel < theCount;++theLevel) { //首先我们获取当前位置的节点的style栈 nsEntryStack* theStack = mBodyContext->GetStylesAt(theLevel); if (theStack) { //如果栈存在 // Don't open transient styles if it makes thestack deep, bug 58917. //如果该栈太深了,则不要打开transient style栈,修正bug 58917 if (theCount + theStack->mCount >=FONTSTYLE_IGNORE_DEPTH) { break; //此处直接跳出外层循环 } PRInt32 sindex = 0; nsTagEntry *theEntry = theStack->mEntries; //获取当前栈(底)的指针 PRBool isHeadingOpen = HasOpenTagOfType(kHeading, *mBodyContext); //获取当前是否位于head节点中 //下面这个循环,实际上是遍历从栈底开始遍历theStack的元素,并且每次遍历都会利用该theStack中的栈元素所对应的节点元素(nsCParserNode)进行判断,如果该节点元素可以包含当前的aChildTag,则对该元素进行打开,否则 for (sindex = 0; sindex <theStack->mCount; ++sindex) { //我们首先获取当前栈元素所对应的节点元素 nsCParserNode* theNode =(nsCParserNode*)theEntry->mNode; if(1 == theNode->mUseCount) { //判断mUseCount,确保当前元素有人在使用 //首先获取该节点的节点类型 eHTMLTags theNodeTag =(eHTMLTags)theNode->GetNodeType(); if(gHTMLElements[theNodeTag].CanContain(aChildTag, mDTDMode)) { // XXXThe following comment is entirely incoherent. // Wedo this too, because this entry differs from the new one //we're pushing. //XXX 下面这段注释完全不连贯 //我们同样也这样做,因为这个entry和我们所要压栈的新entry不一样 theEntry->mParent =theStack; if(isHeadingOpen) { //Bug 77352 //The style system needs to identify residual style tags //within heading tags so that heading tags' size can take //precedence over the residual style tags' size info.. //*Note: Make sure that this attribute is transient since it //should not get carried over to cases other than heading. // Bug 77352 // style系统需要识别位于<head>节点中的residual style标签,这样才能使<head>中的节点数量优先于residual sytle栈的信息被使用。注意:确保这个属性只是暂时的,因为它不应当跑到除了<head>之内节点的其他节点中去。 CAttributeTokentheAttrToken(NS_LITERAL_STRING("_moz-rs-heading"), EmptyString()); //为其添加一个临时的属性”_moz-rs-heading” theNode->AddAttribute(&theAttrToken); //添加到节点中去 result =OpenContainer(theNode, theNodeTag, theStack); //打开相应的容器 theNode->PopAttributeToken(); //同时把刚刚添加的这个属性去掉 } else{ //其他情况下,也就是说此时不位于head中 result =OpenContainer(theNode, theNodeTag, theStack); //直接打开容器即可 } } elseif (aCloseInvalid) { //判断此时CanContain判断失败了 // Ifthe node tag can't contain the child tag, then remove the //child tag from the style stack // 如果该node不能够包含child tag,那么要将child tag从style栈中移除 nsCParserNode* node =theStack->Remove(sindex, theNodeTag); //从栈中删除sindex位置的theNodeTag IF_FREE(node,&mNodeAllocator); --theEntry; //因为删掉了一个node,所以将指针减一 } } ++theEntry; //继续寻找下一个sytle栈中的entry } } } mFlags |= NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE; //将标示位置为启用residual_style } } return result; //返回结果 } /** * This method gets called when an explicitstyle close-tag is encountered. * It results in the style tag id being poppedfrom our internal style stack. * * @update gess6/4/98 * @param * @return 0 if all went well (which it always does) */ //这个方法会在一个显式style的关闭标签被遇到的时候进行调用。它会导致该style标签被从我们的style栈中取出来。 void CNavDTD::PopStyle(eHTMLTags aTag) { if ((mFlags & NS_DTD_FLAG_ENABLE_RESIDUAL_STYLE)&& nsHTMLElement::IsResidualStyleTag(aTag)) { //首先进行一下与判断,必须是启用了RS模式,并且该tag确实是一个RS标签的情况下才行 nsCParserNode* node = mBodyContext->PopStyle(aTag); //调用当前mBodyContext中的取style方法进行弹出,注意aTag此时必须是栈顶元素才会进行弹出 IF_FREE(node, &mNodeAllocator); //释放相应的资源 } } /** * This method does two things: 1st, helpconstruct * our own internal model of the content-stack;and * 2nd, pass this message on to the sink. * * @update gess4/22/98 * @param aNode -- next node to be added to model */ //此注释过于重复就不翻译 //下面这个方法是用来在遇到<html>标签的时候,做一些打开HTML标签相应容器的工作的 nsresult CNavDTD::OpenHTML(const nsCParserNode *aNode) { NS_PRECONDITION(mBodyContext->GetCount() >= 0,kInvalidTagStackPos); //首先确保当前body上下文栈存在并且正确 STOP_TIMER(); MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:CNavDTD::OpenHTML(), this=%p\n",this)); //然后直接调用ContentSink的OpenContainer方法对该aNode进行打开即可,但如果此时mSink还没有进行初始化,则直接返回OK即可 nsresult result = mSink ? mSink->OpenContainer(*aNode) : NS_OK; MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::OpenHTML(), this=%p\n",this)); START_TIMER(); // Don't push more than one HTML tag into the stack. //不要将多于一个的HTML节点压入栈中 if (mBodyContext->GetCount() == 0) { //只有在当前没有节点的时候才能够将该html压入栈中,否则我们不做任何操作,可以考虑一下,此处主要是针对多余的html标签进行去噪处理的 mBodyContext->Push(const_cast<nsCParserNode*>(aNode),0, PR_FALSE); } return result; } /** * This method does two things: 1st, helpconstruct * our own internal model of the content-stack;and * 2nd, pass this message on to the sink. * @update gess4/6/98 * @param aNode -- next node to be added to model * @return TRUE if ok, FALSE if error */ //这段注释过于重复就不翻译了 //下面这个方法和刚才的openhtml类似,主要是用来对<body>节点进行打开处理的 nsresult CNavDTD::OpenBody(const nsCParserNode *aNode) { NS_PRECONDITION(mBodyContext->GetCount() >= 0,kInvalidTagStackPos); nsresult result = NS_OK; if (!(mFlags & NS_DTD_FLAG_HAD_FRAMESET)) { //如果当前不是位于Frameset中 mFlags |= NS_DTD_FLAG_HAD_BODY; //那么必然是位于body中了,我们直接将相应的标示位置为true STOP_TIMER(); MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:CNavDTD::OpenBody(), this=%p\n",this)); // Make sure the head is closed by the timethe body is opened. //确保head节点在body节点打开之时已经关闭 CloseContainer(eHTMLTag_head, PR_FALSE); //打开body之前,我们首先要关闭head // Now we can open the body. //现在我们可以打开body节点了 result = mSink ? mSink->OpenContainer(*aNode) : NS_OK; //直接调用contentsink的OpenContainer对其进行打开 MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::OpenBody(), this=%p\n",this)); START_TIMER(); //下面我们要确定,目前没有body节点被打开的情况下,我们才能够将该节点压入上下文栈中 if (!HasOpenContainer(eHTMLTag_body)) { mBodyContext->Push(const_cast<nsCParserNode*>(aNode), 0,PR_FALSE); mTokenizer->PrependTokens(mMisplacedContent); //此处需要注意的一点是,如果此时没有body被打开,且我们新打开了一个body,那么此时要首先把错误的节点内容放置到分词器的最前面,比如我们此时可能在<body>和<head>之间遇到了一些<p>节点,它们都被放到了错误内容栈中,那么此时我们就要尝试将这些节点放置到<body>中去(不一定放得进去) } } return result; } /** * This method does two things: 1st, helpconstruct * our own internal model of the content-stack;and * 2nd, pass this message on to the sink. * @update gess4/6/98 * @param aNode -- next node to be added to model * @param aClosedByStartTag -- ONLY TRUE if the container is being closed byopening of another container. * @return TRUE if ok, FALSE if error */ //这段注释过于重复就不翻译了 //下面这个方法是重要的打开容器的方法,我们需要调用这个方法对所有类型的节点进行打开,但需要注意的是,这个方法主要调用了mSink的OpenContainer方法进行实际的操作,另外,它主要对一些特殊的标签做了一些事先的预处理工作 nsresult CNavDTD::OpenContainer(const nsCParserNode *aNode, eHTMLTags aTag, nsEntryStack*aStyleStack) { //首先确保当前栈的正确性 NS_PRECONDITION(mBodyContext->GetCount() >= 0,kInvalidTagStackPos); //设置几个初始变量 nsresult result = NS_OK; PRBool done = PR_TRUE; PRBool rs_tag =nsHTMLElement::IsResidualStyleTag(aTag); //判断一下该tag是否是RS类型节点 // We need to open transient styles to encompass the<li> so that the bullets // inherit the proper colors. //我们需要打开临时的style来包含<li>,因此该排版节点能够正确继承合适的颜色 PRBool li_tag = aTag ==eHTMLTag_li; //判断该节点是否是<li>节点 if (rs_tag || li_tag) { //如果当前节点是RS类型节点,或者<li>节点 /* * Here's an interesting problem: * * Ifthere's an <a> on the RS-stack, and you're trying to open * another <a>, the one on theRS-stack should be discarded. * * I'm updating OpenTransientStyles tothrow old <a>'s away. */ //这里有一个有趣的问题: //如果此时在RS栈上有一个<a>标签,并且你又尝试去打开一个新的<a>标签,那么在RS栈中的那个应当被抛弃掉。 //我更新了OpenTransientStyle以丢弃旧的<a>节点 OpenTransientStyles(aTag, !li_tag); } switch (aTag) { //根据aTag的类型进行选择 case eHTMLTag_html: //如果是HTML标签 result = OpenHTML(aNode); //直接调用刚才的OpenHTML方法进行处理 break; case eHTMLTag_head: //如果是head标签 if (!(mFlags &NS_DTD_FLAG_HAS_OPEN_HEAD)) { //如果此时没有打开的head节点 mFlags |= NS_DTD_FLAG_HAS_OPEN_HEAD; //设置标示位,表明已经打开head节点 done = PR_FALSE; //设置done为false,以便后面继续进行处理 } break; case eHTMLTag_body: //如果是body标签 { eHTMLTags theParent = mBodyContext->Last(); //首先获取当前上下文的栈顶元素 if(!gHTMLElements[aTag].IsSpecialParent(theParent)) { //如果该节点不是body的特殊父节点 mFlags |= NS_DTD_FLAG_HAS_OPEN_BODY; //设置标示位,打开body节点 result = OpenBody(aNode); //调用之前的OpenBody方法对Body节点进行打开 } else { done = PR_FALSE; //否则我们将done设置为false } } break; case eHTMLTag_map: //如果是<map>节点 ++mOpenMapCount; //代表当前所打开的map标签数量,将其加一 done = PR_FALSE; break; case eHTMLTag_form: // Discard nested forms - bug 72639 // 抛弃嵌入的form - 修正 bug 72639 if (!(mFlags &NS_DTD_FLAG_HAS_OPEN_FORM)) { //如果当前没有打开的form mFlags |= NS_DTD_FLAG_HAS_OPEN_FORM; //设置form已经打开 result = mSink ? mSink->OpenContainer(*aNode) : NS_OK; //调用mSink进行处理 } break; case eHTMLTag_frameset: // Make sure that the head is closed before wetry to open this frameset. //确保我们在打开这个frameset之前先关闭head标签 CloseContainer(eHTMLTag_head, PR_FALSE); // Now that the head is closed, continue onwith opening this frameset. //现在head已经被关闭了,我们继续打开这个frameset mFlags|= NS_DTD_FLAG_HAD_FRAMESET; //首先设置标示位,表示已经有Frameset被打开了 done = PR_FALSE; //设置done为false,以便后面继续处理 break; caseeHTMLTag_noembed: //如果是<noembed>标签 // <noembed> is unconditionallyalternate content. // <noembed>是一个无条件的替代内容 done = PR_FALSE; mFlags |= NS_DTD_FLAG_ALTERNATE_CONTENT; //设置替代内容标示位(noscript,noframe之类都有) break; case eHTMLTag_noscript: //<noscript>标签 // We want to make sure that OpenContainergets called below since we're // not doing it here // 我们希望确保OpenContainer在后面被调用了,因为我们此处并不调用它 done = PR_FALSE; //因此设置done为false,以便后面继续进行处理 if (mFlags & NS_IPARSER_FLAG_SCRIPT_ENABLED){ // XXX This flag doesn't currently do anything(and would be // insufficient if it did). //XXX 这个标示位目前没有任何用处(并且有用了,它也无法准确地表示我们想要的内容) mFlags |= NS_DTD_FLAG_ALTERNATE_CONTENT; } break; //下面是对<iframe>和<noframes>的统一处理 case eHTMLTag_iframe: // Bug 84491 case eHTMLTag_noframes: done = PR_FALSE; //设置done为false,方便后面才进行处理 if (mFlags &NS_IPARSER_FLAG_FRAMES_ENABLED) { //如果此时启用了frame mFlags |= NS_DTD_FLAG_ALTERNATE_CONTENT; //直接设置一下替代内容标示位 } break; default: done = PR_FALSE; //默认情况下,其他的节点我们都用后面的方法进行处理 break; } if (!done) { STOP_TIMER(); MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:CNavDTD::OpenContainer(), this=%p\n",this)); result = mSink ? mSink->OpenContainer(*aNode) : NS_OK; //直接调用ContentSink的方法进行处理 MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::OpenContainer(), this=%p\n",this)); START_TIMER(); // For residual style tags rs_tag will be trueand hence // the body context will hold an extrareference to the node. // 对于residual style类型的几点,rs_tag会被设为true,因此body上下文会额外保持一个对该节点的关联 mBodyContext->Push(const_cast<nsCParserNode*>(aNode),aStyleStack, rs_tag); } return result; } nsresult CNavDTD::CloseResidualStyleTags(const eHTMLTags aTag, PRBoolaClosedByStartTag) { const PRInt32 count = mBodyContext->GetCount(); PRInt32 pos = count; while(nsHTMLElement::IsResidualStyleTag(mBodyContext->TagAt(pos - 1))) --pos; if (pos < count) return CloseContainersTo(pos, aTag,aClosedByStartTag); return NS_OK; } //下面这个方法顾名思义,是用来关闭RS节点的 nsresult CNavDTD::CloseResidualStyleTags(const eHTMLTags aTag, PRBoolaClosedByStartTag) { const PRInt32 count = mBodyContext->GetCount(); //获取当前节点总数 PRInt32 pos = count; while(nsHTMLElement::IsResidualStyleTag(mBodyContext->TagAt(pos - 1))) //从栈顶节点开始依次遍历下行,判断该节点是否都是RS类型节点 //程序进入到这里,说明该节点是RS型节点 --pos; if (pos < count) return CloseContainersTo(pos, aTag,aClosedByStartTag); //我们调用后面的CloseContainerTo方法将相应节点都关闭掉 return NS_OK; } /** * This method does two things: 1st, helpconstruct * our own internal model of the content-stack;and * 2nd, pass this message on to the sink. * @update gess4/6/98 * @param aTag -- id of tag to be closed * @return TRUE if ok, FALSE if error */ //注释过于重复不做翻译 //下面这个方法是用来关闭某个特定容器aTag的 nsresult CNavDTD::CloseContainer(const eHTMLTags aTag, PRBool aMalformed) { nsresult result = NS_OK; PRBool done =PR_TRUE; switch (aTag) { //根据aTag进行判断 case eHTMLTag_head: //如果是head节点 if (mFlags &NS_DTD_FLAG_HAS_OPEN_HEAD) { //如果此时我们处于head中 mFlags &= ~NS_DTD_FLAG_HAS_OPEN_HEAD; //我们此时已经不在head中了,设置标示位以进行表示 if (mBodyContext->Last() ==eHTMLTag_head) { //如果此时的栈顶元素是<head> mBodyContext->Pop(); //直接将其弹出即可 } else { // This else can happen because CloseContaineris called both directly // and from CloseContainersTo.CloseContainersTo pops the current tag // off of the stack before callingCloseContainer. //这个else的情况可能发生在CloseContainer同时被直接地和间接地从CloseContainersTo被调用。CloseContainersTo会先将当前的tag从栈中移除而后再调用CloseContainer。 NS_ASSERTION(mBodyContext->LastOf(eHTMLTag_head) == kNotFound, "Closingthe wrong tag"); //判断如果此时上下文中已经没有了head,则报错 } done = PR_FALSE; //设置done = false,以便后面继续处理 } break; case eHTMLTag_map: //关闭map节点 if (mOpenMapCount) { mOpenMapCount--; //将当前打开的map数减一 done = PR_FALSE; //以便后面继续处理 } break; case eHTMLTag_form: //如果是form节点 if (mFlags &NS_DTD_FLAG_HAS_OPEN_FORM) { mFlags &= ~NS_DTD_FLAG_HAS_OPEN_FORM; //关闭form,设置标示位 done = PR_FALSE; // If we neglect to close these tags, the sinkwill refuse to close the // form because the form will not be on thetop of the SinkContext stack. // See HTMLContentSink::CloseForm. (XXX do this in other cases?) //如果我们忘记了去关闭这些节点,sink会拒绝关闭form节点,因为form此时不在Sink上下文的栈顶。 //请查阅HTMLContentSink::CloseForm方法(XXX 在其他情况下也这样处理?) CloseResidualStyleTags(eHTMLTag_form, PR_FALSE); //关闭form的RS节点 } break; case eHTMLTag_iframe: case eHTMLTag_noembed: case eHTMLTag_noscript: case eHTMLTag_noframes: // Switch from alternate content state toregular state. //从替代内容转换为了主内容 //此时,我们关闭了替代内容型标签,因此从替代内容转换为了主内容,设置一下标示位 mFlags &= ~NS_DTD_FLAG_ALTERNATE_CONTENT; // falling thro' intentionally.... //我们主动地运行到下面 default: done = PR_FALSE; //默认情况下,即其他的标签,直接将done设置为false,并交由后面的代码进行处理 } if (!done) { //判断如果done为false STOP_TIMER(); MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:CNavDTD::CloseContainer(), this=%p\n",this)); if (mSink) { //如果此时mSink不为空 result = !aMalformed //如果此时处于错误内容中,我们调用contentSink的CloseMalformedContainer方法,否则调用正常的CloseContainer方法进行处理 ? mSink->CloseContainer(aTag) :mSink->CloseMalformedContainer(aTag); } // If we were dealing with a head container inthe body, make sure to // close the head context now, so that bodycontent doesn't get sucked // into the head. //如果我们正在处理位于body中的一个head容器,要确保现在就关闭head上下文,这样以确保body的内容不会放置到head中。 if (mBodyContext->GetCount() ==mHeadContainerPosition) { //如果当前上下文的栈顶元素是head节点 mHeadContainerPosition = -1; //将其head位置设为-1,确保后面的内容不会掉入head中 nsresult headresult = CloseContainer(eHTMLTag_head, PR_FALSE); //关闭head // Note: we could be assigning NS_OK intoNS_OK here, but that's ok. // This test is to avoid a successfulCloseHead result stomping over a // request to block the parser. // 注意:我们可能会将NS_OK赋值给NS_OK,但是那没有什么关系。 // 这个测试用来避免一个成功的CloseHead结果覆盖了一个阻止parser的请求 if (NS_SUCCEEDED(result)) { result = headresult; } } MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::CloseContainer(), this=%p\n",this)); START_TIMER(); } return result; } /** * This method does two things: 1st, helpconstruct * our own internal model of the content-stack;and * 2nd, pass this message on to the sink. * @update gess4/6/98 * @param anIndex * @param aTag * @param aClosedByStartTag -- if TRUE, then we're closing something because astart tag caused it * @return TRUE if ok, FALSE if error */ //这个方法类似于刚才那个CloseContainer,但是这个是批量处理的 nsresult CNavDTD::CloseContainersTo(PRInt32anIndex, eHTMLTags aTarget, PRBoolaClosedByStartTag) { NS_PRECONDITION(mBodyContext->GetCount() > 0,kInvalidTagStackPos); //确保当前body上下文的栈内容正确 nsresult result = NS_OK; if (anIndex < mBodyContext->GetCount()&& anIndex >= 0) { //确保index的值位于0和最大元素数量之间 PRInt32 count = 0; while ((count =mBodyContext->GetCount()) > anIndex) { //循环关闭栈顶到anIndex的元素 nsEntryStack* theChildStyleStack = 0; eHTMLTags theTag = mBodyContext->Last(); //获取栈顶元素 nsCParserNode* theNode = mBodyContext->Pop(theChildStyleStack); //获取当前栈顶元素的styles,放置到theChildStyleStack中,并获得当前栈顶节点,放到theNode中 result = CloseContainer(theTag, PR_FALSE); //关闭theTag PRBool theTagIsStyle = nsHTMLElement::IsResidualStyleTag(theTag); //如果theTag是RS类型节点 // If the current tag cannot leak out then weshouldn't leak out of the target - Fix 40713 // 如果当前的tag不能够泄露style,那么我们就不应该从目标泄露style – 修正bug40713 PRBool theStyleDoesntLeakOut =gHTMLElements[theTag].HasSpecialProperty(kNoStyleLeaksOut); //获取theTag是否能够泄露style if (!theStyleDoesntLeakOut) { //如果可以 theStyleDoesntLeakOut =gHTMLElements[aTarget].HasSpecialProperty(kNoStyleLeaksOut); //那么则获取aTarget是否能够泄露style } // Do not invoke residual style handling whendealing with // alternate content. This fixed bug 25214. // 在处理备用类型的内容的时候不要调用RS处理。这修正了bug 25214。 if (theTagIsStyle && !(mFlags& NS_DTD_FLAG_ALTERNATE_CONTENT)) { //如果该tag是RS类型节点,且当前不是位于备选类型的内容中 NS_ASSERTION(theNode, "residual stylenode should not be null"); if (!theNode) { //如果此时theNode为0,说明栈中已经没有元素了 if (theChildStyleStack) { //如果此时ChildStyleStack不为空 mBodyContext->PushStyles(theChildStyleStack); //将最后一个节点的theChildStyleStack压回当前的body上下文中 } return NS_OK; //返回OK } PRBool theTargetTagIsStyle = nsHTMLElement::IsResidualStyleTag(aTarget); //判断aTarget是否是RS型节点 if (aClosedByStartTag) { //如果参数给的aClosedByStartTag为True,说明此次的关闭操作是由于遇到了起始型节点而触发的 // Handle closure due to new start tag. // The cases we're handing here: // 1. <body><b><DIV> //<b> gets pushed onto<body>.mStyles. // 2. <body><a>text<a> //in this case, the target matches, sodon't push style //因为一个新的起始型节点进行关闭处理 //我们所处理的情况有: //1. <body><b><DIV> //<b>会被压栈到<body>.mStyles中去。 //2. <body><a>text<a> //在这个情况下,两个目标相同,因此不要进行style压栈 if (theNode->mUseCount == 0) { //如果当前栈顶元素的被使用次数为0 if(theTag != aTarget) { //且该栈顶类型的Tag类型和目标节点不一样 if(theChildStyleStack) { //如果theChildStyleStack不为空 theChildStyleStack->PushFront(theNode); //我们对theNode进行压栈操作 } else{ mBodyContext->PushStyle(theNode); //其他情况下,我们将theNode作为style压入到上一个元素中 } } } else if(theTag == aTarget && !gHTMLElements[aTarget].CanContainSelf()) { //此时说明theNode的mUseCount不为0,且目标tag和当前栈顶tag一样,且该tag不能够包含自己 //here'sa case we missed: <a><div>text<a>text</a></div> //The<div> pushes the 1st <a> onto the rs-stack, then the 2nd <a> //popsthe 1st <a> from the rs-stack altogether. //这个是我们忘记说的类型:<a><div>text<a>text</a></div> //这个<div>将第一个<a>放到了rs-stack中,之后第二个<a>将第一个<a>从rs-stack中一起取了出来 //取出上一次压栈的theTag(且theTag此时必须处于栈顶) nsCParserNode* node =mBodyContext->PopStyle(theTag); IF_FREE(node, &mNodeAllocator); //释放 } if (theChildStyleStack) { //如果theChildStyleStack不为空 mBodyContext->PushStyles(theChildStyleStack); //将其压栈 } } else { /* * if you're here, then we're dealingwith the closure of tags * caused by a close tag (as opposedto an open tag). * At a minimum, we should considerpushing residual styles up * up the stack or popping and recyclingdisplaced nodes. * * Known cases: * 1.<body><b><div>text</DIV> * Here the <b> will leak into<div> (see case given above), and * when <div> closes the<b> is dropped since it's already residual. * * 2.<body><div><b>text</div> * Here the <b> will leak outof the <div> and get pushed onto * the RS stack for the <body>,since it originated in the <div>. * * 3.<body><span><b>text</span> * In this case, the the <b>get's pushed onto the style stack. * Later we deal with RS stylesstored on the <span> * * 4.<body><span><b>text</i> * Here we the <b>is closed bya (synonymous) style tag. * In this case, the <b> issimply closed. */ //如果你到了这里,那么我们正在处理由于遇到了一个结束型节点而触发的对tags进行关闭的操作(而不是遇到了一个起始型节点所导致的)。 //最起码的,我们应当考虑将residual style压栈或者将错误放置的节点取出并释放掉。 //已知的情况: //1.<body><b><div>text</div> //这里<b>会渗透到<div>中(请查看上面的例子),并且当<div>关闭的时候,这个<b>就会被抛弃掉,因为它已经是遗漏型的style了。 //2.<body><div><b>text</div> //这里<b>会渗透到<div>之外,并会被压到<body>的RS栈上,因为他原本是在<div>中的。 //3.<body><span><b>text</span> //在这个例子中,<b>标签会被压到style栈上。之后我们会处理储存于<span>上的RS style。 //4.<body<span><b>text</i> //这个<b>被一个(同义的)style节点给关闭了 //在这个例子中,<b>会被关闭 if (theChildStyleStack) { if(!theStyleDoesntLeakOut) { if(theTag != aTarget) { if(theNode->mUseCount == 0) { theChildStyleStack->PushFront(theNode); //在theChildStyleStack不为空,且栈顶元素的Style不应渗透出去,且栈顶元素和目标元素tag类型不一样,且栈顶元素的被引用次数为0,则将栈顶元素压栈到theChildStyleStack中 } } elseif (theNode->mUseCount == 1) { //否则当theNode的引用次数为1时 //This fixes bug 30885,29626. //Make sure that the node, which is about to // getreleased does not stay on the style stack... //Also be sure to remove the correct style off the //style stack. - Ref. bug 94208. // Ex<FONT><B><I></FONT><FONT></B></I></FONT> //Make sure that </B> removes B off the style stack. //这修正了bug30885,29626. //确正准备被释放的改目标node不要停留在style栈上… //同时要确保从style栈中移除正确的style – 源自bug 94208 // 例子:<FONT><B><I></FONT><FONT></B></I></FONT> // 确保</B>正确地将<B>从style栈中移除 mBodyContext->RemoveStyle(theTag); //从当前的上下文中移除theTag(该theTag一定是第一个匹配成功的,位于某个style栈栈顶的tag) } mBodyContext->PushStyles(theChildStyleStack); } else{ IF_DELETE(theChildStyleStack,&mNodeAllocator); } } else if(theNode->mUseCount == 0) { // Theold version of this only pushed if the targettag wasn't //style. But that misses this case:<font><b>text</font>, where // the bshould leak. // 在老版本中这个方法只会在目标tag不是style的情况下将它进行压栈。但是那错过了这种情况:<font><b>text</font>,这里的b应当进行渗透的。 if(aTarget != theTag) { mBodyContext->PushStyle(theNode); //将theNode进行压栈 } } else { // Ah, atlast, the final case. If you're here, then we just popped // astyle tag that got onto that tag stack from a stylestack //somewhere. Pop it from the stylestack ifthe target is also a // styletag. Make sure to remove the matchingstyle. In the //following example: //<FONT><B><I></FONT><FONTcolor=red></B></I></FONT> //make sure that </I> does not remove //<FONT color=red> off the style stack. - bug 94208 //啊,终于,最后一种情况。如果你到了这里,那么我们刚刚从该tag栈上取掉了一个style tag。如果target也是一个style的情况下,将它取出。并确保将相同的style取出。在下面这个例子中: //<FONT><B><I></FONT><FONTcolor=red></B></I></FONT> //确保</I>不会将<FONT color=rd>从style栈中移除。- 修复bug 94208 if(theTargetTagIsStyle && theTag == aTarget) { //当targetTag是style,且theTag和aTarget相等的情况下 mBodyContext->RemoveStyle(theTag); //移除theTag } } } } else { // The tag is not a style tag. // 目标tag不是一个style类型的tag if (theChildStyleStack) { if (theStyleDoesntLeakOut) { //如果theStyleDoesntLeakOut在之前被theTag或aTarget置成了true IF_DELETE(theChildStyleStack,&mNodeAllocator); //直接将该theChildStyleStack删除 } else { //否则 mBodyContext->PushStyles(theChildStyleStack); //将该栈中的全部style压入当前的上下文style栈中(即为了渗漏给后面的节点) } } } IF_FREE(theNode, &mNodeAllocator); //回收theNode指针 } } return result; } /** * This method does two things: 1st, helpconstruct * our own internal model of the content-stack;and * 2nd, pass this message on to the sink. * @update gess4/6/98 * @param aNode -- next node to be added to model * @return error code; 0 means OK */ //注释过于重复就不翻译了 //下面这个方法,就是直接在content model上添加一个叶节点的方法,其实是直接调用了ContentSink相应的add leaf方法 nsresult CNavDTD::AddLeaf(const nsIParserNode *aNode) { nsresult result = NS_OK; if (mSink) { eHTMLTags theTag = (eHTMLTags)aNode->GetNodeType(); //获取该节点的类型 OpenTransientStyles(theTag); //打开相应的style栈 STOP_TIMER(); MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:CNavDTD::AddLeaf(), this=%p\n",this)); result = mSink->AddLeaf(*aNode); //调用mSink的AddLeaf对其进行添加 MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::AddLeaf(), this=%p\n",this)); START_TIMER(); } return result; } /** * Call this method ONLY when you want to writea leaf * into the head container. * * @update gess 03/14/99 * @param aNode -- next node to be added to model * @return error code; 0 means OK */ //只有当你想直接在head容器中填写一个叶节点的时候才进行调用这个方法 nsresult CNavDTD::AddHeadContent(nsIParserNode*aNode) { nsresult result = NS_OK; static eHTMLTags gNoXTags[] = { eHTMLTag_noembed,eHTMLTag_noframes }; eHTMLTags theTag = (eHTMLTags)aNode->GetNodeType(); // XXX - SCRIPT inside NOTAGS should not get executedunless the pref. // says so. Since wedon't have this support yet..lets ignore the // SCRIPT inside NOTAGS. Ref Bug 25880. //XXX – 位于NOTAGS类型的节点(noframe,noembed)中的脚本不应当被执行,除非我们事先定义其可运行才行。但是我们目前还不支持这一功能…我们暂时就忽略位于NOTAGS中的Script节点吧。源自 Bug 25880 if (mSink) { STOP_TIMER(); MOZ_TIMER_DEBUGLOG(("Stop: Parse Time: CNavDTD::AddHeadContent(),this=%p\n",this)); // Make sure the head is opened. // 确保head节点被打开了 if (!(mFlags &NS_DTD_FLAG_HAS_OPEN_HEAD)) { //如果当前没有打开HEAD节点 result = mSink->OpenHead(); //在mSink中打开Head节点 mBodyContext->PushTag(eHTMLTag_head); //手动将head节点放入当前上下文 mFlags |= NS_DTD_FLAG_HAS_OPEN_HEAD; //设置相应的标示位 } // Note: userdefined tags in the head aretreated as leaves. //注意:在head中用户定义的tags会被当做叶子节点来处理 //下面判断,如果theTag不是容器(即其是叶节点),或theTag是用户定义类型的节点 if (!nsHTMLElement::IsContainer(theTag)|| theTag == eHTMLTag_userdefined) { result = mSink->AddLeaf(*aNode); if (mFlags &NS_DTD_FLAG_HAS_MAIN_CONTAINER) { // Close the head now so that body contentdoesn't get sucked into it. // 现在关闭head,这样body中的内容不会跑到head中去 CloseContainer(eHTMLTag_head, PR_FALSE); //关闭head容器 } }else { //其他情况下,说明该节点是容器类型 if ((mFlags &NS_DTD_FLAG_HAS_MAIN_CONTAINER) && mHeadContainerPosition == -1) { // Keep track of this so that we know when toclose the head, when // this tag is done with. // 保持这样的纪录,这样我们就知道合适去关闭head,何时这个tag处理完毕了 mHeadContainerPosition = mBodyContext->GetCount(); } // Note: The head context is already opened. //此时head上下文已经打开了 result = mSink->OpenContainer(*aNode); //调用mSink的OpenContainer打开该节点 //将该node直接添加到当前上下文之中 mBodyContext->Push(static_cast<nsCParserNode*>(aNode),nsnull, PR_FALSE); } MOZ_TIMER_DEBUGLOG(("Start: Parse Time:CNavDTD::AddHeadContent(), this=%p\n",this)); START_TIMER(); } return result; } //下面这个方法,是当遇到可以进行延展的两个元素时候,调用这个方法对他们进行延展,即对当前上下文补充的。 void CNavDTD::CreateContextStackFor(eHTMLTagsaParent, eHTMLTags aChild) { mScratch.Truncate(); // PRBool result =ForwardPropagate(mScratch, aParent, aChild); //首先尝试正向延展 if (!result) { //如果不行,则尝试反向延展 if (eHTMLTag_unknown == aParent) { result = BackwardPropagate(mScratch, eHTMLTag_html, aChild); } else if(aParent != aChild) { // Don't even bother if we're already inside asimilar element... result = BackwardPropagate(mScratch, aParent, aChild); } } // Now, build up the stackaccording to the tags. // 现在,根据tags建立相应的栈 //延展完毕,我们会调用下面的方法,根据延展过程中所记录在mScratch中的路径,依次创建相应的标签,对上下文进行补充 while (theLen) { theTag = (eHTMLTags)mScratch[--theLen]; // Note: These tokens should all wind up oncontextstack, so don't recycle // them. //注意:这些tokens应当全部在context栈上进行收尾,因此不要对他们进行回收 CToken *theToken = mTokenAllocator->CreateTokenOfType(eToken_start,theTag); HandleToken(theToken); //调用HandleToken对theToken进行处理 } }
后续:Mozilla Firefox Gecko内核源代码解析(6.nsElementTable)