- 浏览: 895996 次
-
文章分类
最新评论
-
netkongjian:
不错CAD控件知识,感谢分享[deyi]
适合任何CAD版本的CAD开发技巧 -
xz1017347332:
楼主,我试着照着你的方法做。开始,除了最后一个在状态栏显示坐标 ...
Winform中的Google地图操作 -
xglla_1129:
有视频吗 发我一份 谢谢啊 xglla_1129@163 ...
软考路上 -
jj7jj7jj:
架构不错,多谢群主分享
分享下曾经做的一个JS小游戏——《Battle City》 -
zinss26914:
case只需要4种情况就行了,怎么还搞出8种?
C语言的一个简单算法: 26个字母随机步生成
Mozilla FireFox Gecko内核源代码解析(1.nsParser)
Mozilla FireFox Gecko内核源代码解析
(1.nsParser)
中科院计算技术研究所网络数据科学与工程研究中心-信息抽取小组
耿耘
前言:
在Web信息抽取的工作过程中,我们主要处理的都是经过各种处理HTML格式文档,而无论是DOM方式还是视觉方式的信息抽取,都需要对HTML进行解析,而最标准的解析器莫过于浏览器内核引擎,因此,对于浏览器内核进行研究会对我们的工作和学习带来很大的帮助。
Mozilla FireFox浏览器的内核Gecko是一款非常成功的开源浏览器内核引擎,但其公认的弊病是XPCOM和XUL复杂的体系让许多开发人员望而却步,本系列文档主要针对Gecko内核工作原理和工作方式进行了逐行代码的详细解析,从工作流程上来讲就是从负责HTML分析的代码开始,直到渲染视觉模型模块为止。
本文档单纯地对源代码进行了注释型解释,并在适当位置加入了一些说明信息,其中不包括浏览器的整体结构等信息,在阅读本文档前,读者可以先对MDN,Bugzilla等网站上的文档进行阅读和调研,大体掌握Gecko以及FireFox浏览器的工作原理,以及一些基本的XPCOM组件知识(类似于微软的COM),这样会对理解本文档带来很大帮助。同时本文档的编码中使用了大量的类型名都是经过重定义的,如nsresult实际上是int,以及Int32,nsCOMPtr等,我在相应的地方加上了一些定义它们的.h文件的名称,希望能帮助大家理解。
请注意本文档只包括Gecko代码的解析,不包括:网络通讯模块Necko,浏览器界面生成组件XULRunner,构件支持模块XPCOM,JS引擎SpiderMonkey等非内核模块等。
本系列文档代码针对Mozilla 1.8.2版本。
如果您在阅读过程中发现了什么问题,请您联系gengyun@sohu.com,十分感谢。
简介:
在Gecko中,包含了一个对于HTML文档进行解析并生成DOM树(Gecko中称为内容模型ContentModel)的模块,这个模块可以统称为nsHTMLParser,它由多个组件构成,如负责字符串扫描的nsScanner,负责分词的nsTokenizer,负责语法检查的DTD,以及负责建立DOM树的ContentSink等。我们这篇文档首先针对其主要的流程控制文件nsParser.h(.cpp)进行解析。
读者在刚开始上手理解HtmlParser的时候,可能会比较困难,因为它和其他的模块进行了很密切的交互和耦合,这篇文档希望能够帮助读者更加容易地理解parser的结构和行为,当读者了解了其他模块后,再去理解这个模块可能就会容易很多。
在阅读Mozilla源代码的时候,需要注意它为了跨硬件跨平台的考虑,重新定义了许多数据类型,如32位机器下的int,会被定义为PRInt32等。以及一些对变量进行Bool结果判断的NS_FAILED(),NS_ASSERTION()等。这些函数最好的了解方式是去看.h文件中的声明,如prtype.h,nscore.h等。
Mozilla FireFox的前身是Netscape浏览器,大部分核心代码都是Netscape的代码,因此大部分代码前面都有ns字样,而接口类型的类则一般声明为nsI字样。
源代码解析:
如果想快速地了解Parser的使用流程,可以查看parser/htmlparser/tests/html下的TestParser.cpp文件。这个文件实际上是用来测试Parser模块功能的。
该模块通过用户输入的参数,单纯地读取某个Html文件并进行解析。需要注意的是,标准的Html解析并不是仅仅打开一个文件或者获取一个输入流并进行解析这么简单,不过这个我们放在后面进行解释。
这里我们先通过分析这部分源代码进行一下大体了解。首先从其主函数入手:
nsresult ParseData(char* anInputStream,char*anOutputStream) { NS_ENSURE_ARG_POINTER(anInputStream); //确保anInputStream参数正确 NS_ENSURE_ARG_POINTER(anOutputStream); //确保anOutputStream参数正确
nsresult result = NS_OK; //nsresult数据类型和NS_OK数据类型都是ns中自定义的数据类型,请参考nscore.h // Create a parser nsCOMPtr<nsIParser> parser(do_CreateInstance(kParserCID,&result)); //创建一个parser if (NS_FAILED(result)) { //如果创建Parser失败 printf("\nUnable to create aparser\n"); //弹出错误信息 return result; } // Create a sink nsCOMPtr<nsILoggingSink> sink(do_CreateInstance(kLoggingSinkCID,&result)); //创建一个LoggingSink if (NS_FAILED(result)) { //如果创建Sink失败 printf("\nUnable to create asink\n"); //弹出错误信息 return result; } int main(int argc, char**argv) { if (argc < 3) { //如果参数数量小于3,说明输入错误 printf("\nUsage: <inputfile><outputfile>\n"); //提示用户参数输入方式 return -1; } nsresult rv = NS_InitXPCOM2(nsnull, nsnull, nsnull); //这里测试一下NS的组件机制是否能够正确初始化 if (NS_FAILED(rv)) { //如果不能 printf("NS_InitXPCOM2 failed\n"); //报错 return -1; } ParseData(argv[1],argv[2]); //这是解析函数的主体,并将用户输入的第一个和第二个参数传递给函数。 return 0; } PRFileDesc* out = PR_Open(anOutputStream, PR_CREATE_FILE|PR_TRUNCATE|PR_RDWR, 0777); if (!out) { //如果无法打开输出流 printf("\nUnableto open output file - %s\n", anOutputStream); //则报错 returnresult; } nsString stream; charbuffer[1024] = {0}; // XXX Yikes! //用来存放读取的Html流 PRBool done = PR_FALSE; PRInt32 length = 0; while(!done){ //循环地将html字段都写入stream中,每次只读1024字节,可能为了模拟缓冲区大小吧 length = PR_Read(in, buffer, sizeof(buffer)); //读取参数 if (length!= 0) { //如果确实读进来了字节 stream.Append(NS_ConvertUTF8toUTF16(buffer, length)); // } else { //如果读进来的是空内容 done=PR_TRUE; //说明全部读取完毕,退出循环 } } sink->SetOutputStream(out); //设置输出流 parser->SetContentSink(sink); //为parser设置配合其工作的contentsink result = parser->Parse(stream, 0,NS_LITERAL_CSTRING("text/html"),PR_TRUE); //这句就是调用Parser::Parse()方法执行解析的语句了,具体方法我们放在后面进行分析。 PR_Close(in); //关闭输入流 PR_Close(out); //关闭输出流 returnresult; } |
因为每一个HtmlParser都要有一个ContentSink来接收输出,这里创建的LoggingSink实际上就是ContentSink,只不过将ContentSink的输出改为直接输出消息到输出流中,而不是标准地输入到后面的模块,这是专门为测试而建立的Sink,具体可见nsILoggingSink.h的代码说明。
下面我们分析重要的htmlparser部分代码。
打开htmlparser文件夹,可以看到很清晰的三个文件夹:public,src,tests。其中public中包含的大部分是公用的一些头文件,以及一些parser所引用的其他模块的头文件,如nsIContentSink.h等。而tests中则是一些测试用的相关内容,包括了一个随机的html文件生成器,一些html测试用例页面,以及一些测试结果等。开源代码的作者很有意思,自己的很多工作痕迹都上传在SVN上,我们可以利用这些结果去帮助我们进行分析。
Parser类说明:
首先我们可以看一下nsParser.h的开头注释,可知Parser类主要提供两项主要功能:
1)它遍历在分词过程(tokenization process)中产生的词条(tokens),识别出各个元素的起始和结束(进行验证和标准化)。
2)它控制并协调一个IContentSink的接口,来产生内容模型(content model)。
这个类在解析Html的时候,不会默认Html文档是有结构的(即不会认为Html文档一定包含BODY,HEAD等模块内容),因此也就不包含一些类似DoBody(),DoHead()之类的方法。
另外,为了让我们的解析过程能够自后向前兼容(即是说和Html流的顺序无关),我们必须扫描每个Token并且实施以下一些基本操作:
1)确定每个Token的类型(这个很简单,因为每个Token中就包含了这个信息)
2)确定每个Token所应当处在Html文档中的哪个位置(是在BODY,HEAD,还是FRAMESET等)
3)将解析好的Content通过ContentSink插入到Document的合适位置。
4)对于属于BODY部分的tags,我们必须确保通过Document的状态能够确定出正确的解析上下文。即是说,比如我们看到了一个<TR>标签,那么我们必须确定我们的Document中包含了一个table,能够让该<TR>正确地插入进去。这潜在地起到了“容器”的作用,即保证我们的Html是结构正确的。
我们首先来分析nsParser.h(.cpp)。该类是解析器的主体类。
#ifndefNS_PARSER__ #defineNS_PARSER__ #include "nsIParser.h" #include "nsDeque.h" #include "nsParserNode.h" #include "nsIURL.h" #include "CParserContext.h" #include "nsParserCIID.h" #include "nsITokenizer.h" #include "nsHTMLTags.h" #include "nsDTDUtils.h" #include "nsTimer.h" #include "nsThreadUtils.h" #include "nsIContentSink.h" #include "nsIParserFilter.h" #include "nsCOMArray.h" #include "nsIUnicharStreamListener.h" #include "nsCycleCollectionParticipant.h" classnsICharsetConverterManager; classnsICharsetAlias; classnsIDTD; classnsScanner; classnsSpeculativeScriptThread; classnsIThreadPool; #ifdef_MSC_VER #pragma warning( disable :4275 ) #endif //这段代码主要是一些头文件包含声明,以及一些前置声明。我们跳过这段代码直接看后面的 classnsParser : public nsIParser, publicnsIStreamListener { //nsParser继承自两个基类:nsIParser,nsIStreamListener,前者是基本接口,而后者则是为了和Necko进行通讯所用的基类。 public: /** * Called on module init */ static nsresult Init(); //初始化的方法 /** * Called on module shutdown */ static void Shutdown(); //关闭方法 NS_DECL_CYCLE_COLLECTING_ISUPPORTS //这两个是在前面的nsISupportImpl.h中#Define过的,主要定义了一些接口 NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsParser, nsIParser) /** * default constructor * @update gess5/11/98 */ nsParser(); //构造方法 /** * Destructor * @update gess5/11/98 */ virtual ~nsParser(); //析构方法 /** * Select given content sink into parserfor parser output * @update gess5/11/98 * @paramaSink is the new sink to be used by parser * @returnold sink, or NULL */ NS_IMETHOD_(void)SetContentSink(nsIContentSink* aSink); //为该Parser设置对应的ContentSink,ContentSink就是用来建立DOM树所用到的模块 /** * retrive the sink set into the parser * @update gess5/11/98 * @paramaSink is the new sink to be used by parser * @returnold sink, or NULL */ NS_IMETHOD_(nsIContentSink*)GetContentSink(void); //获取该Parser所对应的ContentSink /** *Call this method once you've created a parser, and want to instruct it *about the command which caused the parser to be constructed. Forexample, *this allows us to select a DTD which can do, say, view-source. * *@update gess 3/25/98 *@param aCommand -- ptrs tostring that contains command *@return nada */ NS_IMETHOD_(void)GetCommand(nsCString& aCommand); //获取当前Parser的指令方式 NS_IMETHOD_(void) SetCommand(const char*aCommand); //为当前的Parser进行指令设置 NS_IMETHOD_(void)SetCommand(eParserCommands aParserCommand); //同上,形参不同 //根据程序注释,这里主要是设定Parser的工作方式,解析器有多种工作模式,HTML模式,查看源代码模式,这里可以对其进行设置,还有可以对后面我们会用到的DTD进行设置,Parser在对不同的Html文档进行解析时需要进行不同的操作,这些我们后面再进行解释。 /** *Call this method once you've created a parser, and want to instruct it *about what charset to load * *@update ftang 4/23/99 *@param aCharset- the charset ofa document *@param aCharsetSource- thesource of the charset *@return nada */ NS_IMETHOD_(void) SetDocumentCharset(const nsACString& aCharset, PRInt32 aSource); //设置Parser进行文档解析时使用的字符集 NS_IMETHOD_(void) GetDocumentCharset(nsACString& aCharset,PRInt32& aSource) //获取Parser进行文档解析时使用的字符集 { aCharset = mCharset; aSource = mCharsetSource; } NS_IMETHOD_(void) SetParserFilter(nsIParserFilter* aFilter); //为Parser设置过滤器 /** * Cause parser to parse input from givenURL * @update gess5/11/98 * @paramaURL is a descriptor for source document * @paramaListener is a listener to forward notifications to * @returnTRUE if all went well -- FALSE otherwise */ NS_IMETHOD Parse(nsIURI* aURL, nsIRequestObserver*aListener = nsnull, void*aKey = 0, nsDTDMode aMode =eDTDMode_autodetect); //这个方法能够从给定的URL参数中,获取Html文档并进行解析 /** * @update gess5/11/98 * @paramanHTMLString contains a string-full of real HTML * @paramappendTokens tells us whether we should insert tokens inline, or appendthem. * @returnTRUE if all went well -- FALSE otherwise */ NS_IMETHOD Parse(const nsAString&aSourceBuffer, void*aKey, constnsACString& aContentType, PRBool aLastCall, nsDTDMode aMode =eDTDMode_autodetect); //这个方法能够从给定的aSourceBuffer中获取Html文档并进行解析 NS_IMETHOD_(void *) GetRootContextKey(); //获取位于根部的ParseContext的Key,ParserContext是解析上下文,在解析的过程中为解析提供支持所用的 //以上两个Parser方法有很大不同,虽然都是对Html流进行解析,但是还是有很多区别,这个在对该方法进行解析的时候会进行说明。而对于GetRootContextKey()方法,由于我们的ParserContext们采用的是栈式数据结构,并且用链表方式进行存储,且每个Context都有一个唯一的Key,这个GetRootContextKey()主要是为了获取栈底元素的Key值。 /** * This method needs documentation */ NS_IMETHOD ParseFragment(constnsAString& aSourceBuffer, void* aKey, nsTArray<nsString>& aTagStack, PRBool aXMLMode, const nsACString& aContentType, nsDTDMode aMode =eDTDMode_autodetect); NS_IMETHOD ParseFragment(constnsAString& aSourceBuffer, nsISupports*aTargetNode, nsIAtom*aContextLocalName, PRInt32aContextNamespace, PRBool aQuirks); //上面这两个方法是主要针对HTML FRAGMENT进行解析的,也就是进行一些简单的HTML TO DOM的解析。其中,第一个方法还可以用来解析XML文档,而第二个方法在目前版本的FireFox里还没有实现。 /** * This method gets called when the tokenshave been consumed, and it's time * to build the model via the content sink. * @update gess5/11/98 * @returnYES if model building went well -- NO otherwise. */ NS_IMETHOD BuildModel(void); //上面这个方法是在分词过程结束后,需要调用ContentSink进行输出和建立Content Model的时候调用的方法。 /** *Call this when you want control whether or not the parser will parse *and tokenize input (TRUE), or whether it just caches input to be *parsed later (FALSE). * *@update gess 9/1/98 *@param aState determines whetherwe parse/tokenize or just cache. *@return current state */ NS_IMETHODContinueParsing(); //让parser继续工作 NS_IMETHODContinueInterruptedParsing(); //让被打断的Parser继续工作 NS_IMETHOD_(void) BlockParser(); //阻塞parser的工作 NS_IMETHOD_(void) UnblockParser(); //解除parser的阻塞 NS_IMETHOD Terminate(void); //结束parser工作 //这几个方法主要是对Parser进行控制的,从字面就很好理解他们的作用。其中parser的阻塞原因可能有很多种,如时间过长等 /** * Call this to query whether the parser isenabled or not. * *@update vidur 4/12/99 *@return current state */ NS_IMETHOD_(PRBool) IsParserEnabled(); //返回paser是否当前可用 /** * Call this to query whether the parserthinks it's done with parsing. * *@update rickg 5/12/01 *@return complete state */ NS_IMETHOD_(PRBool) IsComplete(); //返回paser是否认为自己完成了工作 //需要注意的是,IsComplete()返回的只是从parser本身出发认为自己是否完成了工作。 /** *This rather arcane method (hack) is used as a signal between the *DTD and the parser. It allows the DTD to tell the parser that content *that comes through (parser::parser(string)) but not consumed should *propagate into the next string based parse call. * *@update gess 9/1/98 * @paramaState determines whether we propagate unused string content. *@return current state */ void SetUnusedInput(nsString&aBuffer); //这个方法主要是设置一个字符串,该字符串中存放的是当前还未处理的字符流,这些字符流只有在下一个parser的调用中才能够被解析 /** * This method gets called (automatically)during incremental parsing * @update gess5/11/98 * @returnTRUE if all went well, otherwise FALSE */ virtual nsresult ResumeParse(PRBoolallowIteration = PR_TRUE, PRBool aIsFinalChunk = PR_FALSE, PRBoolaCanInterrupt = PR_TRUE); //这个方法是在进行增量式解析的时候自动被调用的(其实在其他地方也有调用)。 //********************************************* // These methods are callback methods used by // net lib to let us know about ourinputstream. //********************************************* // nsIRequestObserver methods: NS_DECL_NSIREQUESTOBSERVER // nsIStreamListener methods: NS_DECL_NSISTREAMLISTENER //以上两个方法是预先#define好的,用来提供parser的输入用的,让Necko可以通过调用这两个模块来提醒parser有新的输入流了。 void PushContext(CParserContext&aContext); //将Context压栈 CParserContext* PopContext(); //将Context出栈 CParserContext* PeekContext() {return mParserContext;} //查看栈顶的Context //这三个方法很显然是对栈进行操作,而栈中的元素则是Context,我们前面提到过ParserContext是以栈的形式存放的,用来对解析的过程进行支持。 /** * Get the channel associated with thisparser * @update harishd,gagan 07/17/01 * @param aChannel out param that willcontain the result * @return NS_OK if successful */ NS_IMETHOD GetChannel(nsIChannel** aChannel); //获取该Parser的数据通道,这个方法主要是获取和该Parser相连的Channel,该Channel是parser获取输入流的来源。 /** * Get the DTD associated with this parser * @update vidur 9/29/99 * @param aDTD out param that will containthe result * @return NS_OK if successful,NS_ERROR_FAILURE for runtime error */ NS_IMETHOD GetDTD(nsIDTD** aDTD); //获取该Parser的DTD。 /** * Detects the existence of a META tag withcharset information in * the given buffer. */ PRBool DetectMetaTag(const char* aBytes, PRInt32 aLen, nsCString&oCharset, PRInt32&oCharsetSource); //在给定的缓冲字符串中寻找<META>标签,返回是否找到 void SetSinkCharset(nsACString&aCharset); //为Sink设置让其使用的字符集 /** *Removes continue parsing events *@update kmcclusk 5/18/98 */ NS_IMETHODIMP CancelParsingEvents(); //删除解析结束时所触发的事件(其实就是清空当前parser里mContinueEvent的值) /** *Indicates whether the parser is in a state where it *can be interrupted. *@return PR_TRUE if parser can be interrupted, PR_FALSE if it can not beinterrupted. *@update kmcclusk 5/18/98 */ virtual PRBool CanInterrupt(); //返回该parser在解析的时候是否能够被外来事件打断。返回TRUE表示能,返回FALSE表示不能。 /** *Set to parser state to indicate whether parsing tokens can beinterrupted *@param aCanInterrupt PR_TRUE if parser can be interrupted, PR_FALSE ifit can not be interrupted. *@update kmcclusk 5/18/98 */ voidSetCanInterrupt(PRBool aCanInterrupt); //设置该parser在进行解析的时候能否被外来事件打断。 /** * This is called when the final chunk hasbeen * passed to the parser and the contentsink has * interrupted token processing. Itschedules * a ParserContinue PL_Event which will askthe parser * to HandleParserContinueEvent when it ishandled. * @update kmcclusk6/1/2001 */ nsresult PostContinueEvent(); //触发让parser继续的Event //需要注意的是,上面PostContinueEvent()只能在两种情况下被调用,一个是当所有的数据都输入完毕的时候,还有就是在Parser已经被ContentSink因为处理时间过长而阻塞的时候。 /** *Fired when the continue parse event is triggered. *@update kmcclusk 5/18/98 */ voidHandleParserContinueEvent(classnsParserContinueEvent *); //这个是在上面那个nsContinueEvent被触发的时候进行调用的,具体请见nsContinueEvent的类定义 /** * Called by top-level scanners when datafrom necko is added to * the scanner. * //下面这些代码是为了给高层的扫描器提供一个借口,当数据从necko传输到扫描器的时候被调用 nsresultDataAdded(const nsSubstring& aData,nsIRequest *aRequest); //aData是数据,aRequest是数据的请求 staticnsCOMArray<nsIUnicharStreamListener> *sParserDataListeners; //建立一组数据监听器 static nsICharsetAlias*GetCharsetAliasService() { return sCharsetAliasService; } //获取字符集编码设置等值 staticnsICharsetConverterManager* GetCharsetConverterManager() { return sCharsetConverterManager; } //获取字符集编码转换等功能的服务器 virtual voidReset() { Cleanup(); Initialize(); } //通过调用Cleanup()来清除解析器状态,并通过调用Initialize()来初始化解析器,用来重设解析器的值 nsIThreadPool* ThreadPool() { return sSpeculativeThreadPool; } //这个SpeculativeThread是用来进行预读取用的线程,当Gecko的Html解析被打断时,这个线程会自动地并行去读取HTML文档中以src = URL形式给出的一些应当会用到的CSS,脚本语言文件等数据,这样来提高运行效率 PRBool IsScriptExecuting() { return mSink &&mSink->IsScriptExecuting(); } //通过调用当前解析器所属的ContentSink的IsScriptExecuting()方法来判断是否该ContentSink是否正在进行脚本解析 //下面是protected的一些方法: protected: void Initialize(PRBoolaConstructor = PR_FALSE); //初始化方法 void Cleanup(); //清除解析器状态的方法 /** * * @update gess5/18/98 * @param * @return */ nsresult WillBuildModel(nsString& aFilename); //在解析器即将调用ContentSink进行ContentModel建模之前进行调用,做一些准备工作,Mozilla中经常可见这种三部曲式的代码,即以WillDoSomething-DoSomething-DidDoSomething的形式和顺序出现,用来进行运行准备,运行,运行收尾的三步工作。 /** * * @update gess5/18/98 * @param * @return */ nsresult DidBuildModel(nsresult anErrorCode); //调用ContentSink进行ContentModel的建立。 void SpeculativelyParse(); //并行进行读取解析 //下面是一些private的分词(tokenization)方法: private: /******************************************* These are the tokenization methods... *******************************************/ /** *Part of the code sandwich, this gets called right before *the tokenization process begins. The main reason for *this call is to allow the delegate to do initialization. * *@update gess 3/25/98 *@param *@return TRUE if it's ok toproceed */ PRBool WillTokenize(PRBool aIsFinalChunk = PR_FALSE); //这个是在进行分词之前进行准备工作的方法 /** *This is the primary control routine. It iteratively *consumes tokens until an error occurs or you run out *of data. * *@update gess 3/25/98 *@return error code */ nsresult Tokenize(PRBool aIsFinalChunk = PR_FALSE); //这个就是进行分词的操作,它会不断地对tokens进行处理,直到出错或者处理完毕 /** *This is the tail-end of the code sandwich for the *tokenization process. It gets called once tokenziation *has completed. * *@update gess 3/25/98 *@param *@return TRUE if all went well */ PRBoolDidTokenize(PRBool aIsFinalChunk = PR_FALSE); //这个是在tokenize处理之后进行收尾的操作 //最后,我们来看一下parser的全部数据成员,对这些数据成员的理解可以帮助我们去分析parser的结构。 protected: //********************************************* // And now, some data members... //********************************************* CParserContext* mParserContext; //用来存放解析的上下文,注意这些上下文之间是以链表的方式进行存储的 nsCOMPtr<nsIDTD> mDTD; //用来存放一个指向当前所用DTD对象的指针 nsCOMPtr<nsIRequestObserver>mObserver; //用来观察并接收nsIRequest的监听器 nsCOMPtr<nsIContentSink> mSink; //当前parser所用的ContentSink nsIRunnable*mContinueEvent; // weak ref //设置一个指向nsIRunnable类型的指针,该指针指向的函数就是当解析结束的时候所要执行的函数。 nsRefPtr<nsSpeculativeScriptThread>mSpeculativeScriptThread; //当前负责进行资源预读取的线程 nsCOMPtr<nsIParserFilter>mParserFilter; //设置一个指针,指向当前解析器的Filter nsTokenAllocatormTokenAllocator; //当前解析器的Token分配器 eParserCommands mCommand; //当前解析器的指令 nsresultmInternalState; //当前解析器的(内部)状态 PRInt32 mStreamStatus; //当前解析器解析流的状态 PRInt32mCharsetSource; //当前的字符集类型(来源) PRUint16 mFlags; //用于对解析器进行一些设置的标志位,如是否启用了Observer等,在后面的函数中会用到,主要是进行一些bit位操作,注意是PRUint16,该类型不同机器下不一样,一般使用unsigned short,也就是占2个字节,16位。 nsString mUnusedInput; //未解析的字符串 nsCString mCharset; //当前解析器的字符集 nsCString mCommandStr; //当前解析器的指令字符 static nsICharsetAlias* sCharsetAliasService; //解析器所用的字符集 static nsICharsetConverterManager*sCharsetConverterManager; //解析器所用的字符集类型转换器 static nsIThreadPool* sSpeculativeThreadPool; //并行预读取资源线程的线程池 enum { kSpeculativeThreadLimit = 15, //设置线程池的上限 kIdleThreadLimit = 0, //设置空闲线程的上限 kIdleThreadTimeout = 50 //设置空闲线程超时的上限阈值 }; public: //设置几个计时器,因为Mozilla Firefox是一款注重人机交互的软件,它非常注重程序的响应时间,因此设置了一些计时器 MOZ_TIMER_DECLARE(mParseTime) //用来测量解析时间 MOZ_TIMER_DECLARE(mDTDTime) //用来测量DTD的处理时间 MOZ_TIMER_DECLARE(mTokenizeTime) //用来测量Tokenize分词过程的处理时间 }; |
以上就是nsParser.h的代码,下面我们来看nsParser.cpp的代码。
//我们省略它的#include部分 #defineNS_PARSER_FLAG_PARSER_ENABLED0x00000002 #defineNS_PARSER_FLAG_OBSERVERS_ENABLED0x00000004 #defineNS_PARSER_FLAG_PENDING_CONTINUE_EVENT 0x00000008 #defineNS_PARSER_FLAG_CAN_INTERRUPT0x00000010 #defineNS_PARSER_FLAG_FLUSH_TOKENS0x00000020 #defineNS_PARSER_FLAG_CAN_TOKENIZE0x00000040 //首先它定义了几个全局用的值,仔细看可以发现,前三个分别是二进制的第1,2,3位为1,其他位为零,也就是说这几个值不会互相干涉,这也是一种常用的比特标志位赋值方法,用它就可以对我们前面的mFlag标志位进行标示,来标示parser的一些基本状态。而至于这几个16进制值,读者可以自己观察他们的特点和之间的关系。 staticNS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); staticNS_DEFINE_CID(kCParserCID, NS_PARSER_CID); staticNS_DEFINE_IID(kIParserIID, NS_IPARSER_IID); //以上这三个方法是在nsID.h中定义的多重#DEFINE的方法,读者可以自己去看一下很简单,另外需要注意NS_ISUPPORTS_IID,NS_PARSER_CID和NS_IPARSER_IID的值的特点。 //------------------------------------------------------------------- nsCOMArray<nsIUnicharStreamListener>*nsParser::sParserDataListeners; //这个方法声明了Parser的sParserDataListener指向一个流监听器类型 //源文件中接下来有一段很长的关于nsParser的注释说明,介绍了Parser工作原理的特点。这里对其进行翻译并加以解释一下: //Parser可以被在执行BuildModel()方法时所返回的NS_ERROR_HTMLPARSER_INTERRUPTED值所打断。这会使得Parser停止对当前内容的解析并返回到原先的事件循环中去。此时,Parser中所剩下的未解析的字符串则会被保留下来,直到下一次网络模块的OnDataAvailable()(即有新的数据被接收到时)被调用时再继续解析。然而,如果当所有的Html数据流都已经被接收到,那么则不会再产生新的OnDataAvailable()事件(此时如果parser被打断且还有剩下的未处理数据则会出现问题),因此Parser会设置一个nsParserContinueEvent,这个事件将会在Parser被打断并返回原先的时间循环后被再次调用(使得Parser能够继续处理未处理的数据),而如果此时Parser再次被打断,则他会再给自己加一个nsParserContinueEvent。这一过程会一直持续,直到以下两个情况之一发生为止: // 1)所有剩下的数据能够不被打断地处理到结束 // 2) Parser被撤销
//这一功能目前在CNavDTD和nsHTMLContentSink中所使用。当新的数据块到达并需要进行处理的时候,nsHTMLSink是由CNavDTD进行通知的。当开始进行处理时,nsHTML content sink会记录下开始处理的时间,并且如果处理的时间超过了一个叫做最大tokenizing时间的阈值的话,则会返回一个NS_ERROR_HTMLPARSER_INTERRUPTED的错误。这将允许content sink对一个chunk中一次处理多少数据进行限定,从而也就限定了在事件循环之外的处理最多能耗费多少时间。处理小数据块同样可以减少在低层的reflows(浏览器回流操作,后面会介绍)操作的时间耗费。 //这一功能在读取大文件的时候作用尤其明显。如果最大tokenizing时间设置的足够小,那么浏览器在处理文档时候就能够始终保持和用户的可交互性。 //然而这一功能的一个副作用就是:当最后一部分数据传输到OnDataAvailable()的时候,文件读取工作还没有结束,因为parser可能在最后一部分数据传输到的时候被打断。文档只有在所有的token都被处理过,并且也没有等待处理的nsParserContinueEvents时才算被处理完毕。如果一些应用程序认为它能够通过监视文档的读取请求来判断文档是否读取结束的话,会造成不小的问题。这种问题在Mozilla里就会发生。当所有的文档读取请求都已经被满足时,文档就被认为已经全部读取完毕了。为了拖延文档读取直到所有的解析工作完毕,nsHTMLContentSink加入了一个很笨的解析器读取请求,这个请求始终不会被满足,直到nsHTMLContentSink的DidBuildModel方法被调用了为止。而CNavDTD则能够保证直到最后一块数据通过OnDataAvailable()被传输到解析器中,并且没有任何等待满足的nsParserContinueEvent时,才会去调用DidBuildModel。 //目前Parser在处理script的时候会屏蔽所有中断的请求。这是因为JavaScript修改DOM树的document.write()方法如果被打断,则可能会出现一些错误。 //如果想得到更多的信息,请访问bugzilla76772。 //下面,我们就开始分析nsParser代码的逻辑实体部分,首先是声明前面提到过的nsParserContinueEvent。 classnsParserContinueEvent : public nsRunnable { public: nsRefPtr<nsParser> mParser; //关联性指针,指向该Event所关联的Parser。 nsParserContinueEvent(nsParser* aParser) //初始化方法,将mParser赋值 : mParser(aParser) {} NS_IMETHOD Run() //该Event的运行方法 { mParser->HandleParserContinueEvent(this); //调用关联的Parser的方法进行处理 return NS_OK; //返回成功的正确值 } }; //下面是个模板类Holder,用来存放classType的,并提供了一个GET方法返回存放的值,其析构方法就是使用Reaper定义的值进行替代,很简单的实现,主要为主函数提供支持。 template<class Type> classHolder { public: typedef void(*Reaper)(Type *); //定义一个函数指针类型reaper,该类型指针指向void function(Type *)类型的函数 Holder(Reaper aReaper) //构造方法 : mHoldee(nsnull), mReaper(aReaper) //将mReaper赋初值 { } ~Holder() { //析构方法 if (mHoldee) { mReaper(mHoldee); //将mReaper里赋上mHoldee的值,即用mHoldee的值去替换当前mReaper所指向的值 } } Type *get() { return mHoldee; //返回mHoldee的值 } const Holder &operator=(Type *aHoldee) { //重载操作符 if (mHoldee && aHoldee !=mHoldee) { //如果mHoldee不为空且和新的Holdee不同 mReaper(mHoldee); //则mReaper赋值为原先的Holdee } mHoldee = aHoldee; //用新的aHoldee代替原来的mHoldee return *this; //将本Holder返回 } private: //前面用到的两个数据成员 Type *mHoldee; Reaper mReaper; }; //下面,是预读取资源的解析线程的类声明部分: classnsSpeculativeScriptThread : public nsIRunnable{ //注意它也是继承自nsIRunnable public: nsSpeculativeScriptThread() //构造方法,将各个数据成员赋初值 : mLock(nsAutoLock::DestroyLock), mCVar(PR_DestroyCondVar), mKeepParsing(PR_FALSE), mCurrentlyParsing(PR_FALSE), mNumConsumed(0), mContext(nsnull), mTerminated(PR_FALSE) { } ~nsSpeculativeScriptThread() { //析构方法 NS_ASSERTION(NS_IsMainThread() || !mDocument, //确保不是主线程或解析文档为空 "Destroyingthe document on the wrong thread"); } NS_DECL_ISUPPORTS //详见nsISupportsImpl.h文件 NS_DECL_NSIRUNNABLE nsresult StartParsing(nsParser *aParser); //开始进行解析 void StopParsing(PRBool aFromDocWrite); //停止进行解析 enum PrefetchType { NONE, SCRIPT, STYLESHEET, IMAGE};//枚举类型,定义预读取数据的类型 struct PrefetchEntry { //定义一个结构体,为解析过程使用,主要是为了存放一些需要预先读取的数据内容 PrefetchType type; nsString uri; nsString charset; nsString elementType; }; nsIDocument *GetDocument() { //私有成员变量访问接口,获取该Thread所关联的Document NS_ASSERTION(NS_IsMainThread(), "Potentialthreadsafety hazard"); return mDocument; } PRBool Parsing() { //私有成员变量访问接口,获取该Thread是否正在Parsing return mCurrentlyParsing; } CParserContext *Context() { //私有成员变量访问接口,获取该Thread的ParserContext return mContext; } typedef nsDataHashtable<nsCStringHashKey,PRBool> PreloadedType; //定义一个Hashtable类型 PreloadedType& GetPreloadedURIs() { //定义一个获取该Hashtable的GET方法 return mPreloadedURIs; //返回mPreloadedURIs } void Terminate() { //销毁Thread mTerminated = PR_TRUE; //设置销毁标志位 StopParsing(PR_FALSE); //停止解析 } PRBool Terminated() { //获取当前Thread是否处于销毁状态 return mTerminated; } //下面是私有部分 private: void ProcessToken(CToken *aToken); //处理Token void AddToPrefetchList(constnsAString &src, //将一个新的需要进行预读取的URL添加到mURLs中去 const nsAString &charset, const nsAString &elementType, PrefetchType type); void FlushURIs(); //将当前缓冲区内的所有的URI数据传输到主线程中进行处理 // These members are only accessed on the speculativelyparsing thread. nsTokenAllocator mTokenAllocator; //私有数据成员,当前线程的TokenAllocator // The following members are shared across the main threadand the // speculatively parsing thread. //以下这些函数会在主线程和次级线程中进行使用 Holder<PRLock>mLock; //同步锁 Holder<PRCondVar> mCVar; //条件变量 volatile PRBool mKeepParsing; //BOOL变量,标示是否继续进行解析 volatile PRBool mCurrentlyParsing; //BOOL变量,标示当前是否正在解析 nsRefPtr<nsHTMLTokenizer> mTokenizer; //当前线程的HTML分词器 nsAutoPtr<nsScanner> mScanner; //当前线程的HTML扫描器 enum { kBatchPrefetchURIs = 5 }; //枚举类型,设置缓冲区的大小 nsAutoTArray<PrefetchEntry, kBatchPrefetchURIs> mURIs; //以数组形式存放当前所有的URIs // Number of characters consumed by the last speculativeparse. //用来存放上一次解析时消耗的字符数 PRUint32 mNumConsumed; // These members are only accessed on the main thread. //下面这些数据成员只有在主线程中才会被调用 nsCOMPtr<nsIDocument>mDocument; //当前线程的nsIDocument对象 CParserContext *mContext; //当前线程的解析上下文mContext。 PreloadedType mPreloadedURIs; //Hash数据表,存放当前解析线程所有的URI PRBool mTerminated; //标示该线程是否被销毁 }; //下面这个CSSLoaderObserver是用来注册一个监听器来接收CSS信息,但是实际上并没有什么作用,只在没有CSS的时候才用到。(即没有CSS文件的时候,同样要实现一个CSSLoaderOberserver接口) /** * Used if we need to pass annsICSSLoaderObserver as parameter, * but don't really need its services */ classnsDummyCSSLoaderObserver : publicnsICSSLoaderObserver { public: NS_IMETHOD StyleSheetLoaded(nsICSSStyleSheet* aSheet, PRBool aWasAlternate,nsresult aStatus) { return NS_OK; } NS_DECL_ISUPPORTS //事先#define了一些接口的定义,参见nsISupportImpl.h }; //下面这个方法是一个很有意思的方法,通过一个定义好的构件模板,可以为某个内部类添加一个新的方法。具体方法暂不在这篇文档中介绍了,有兴趣的可以去看nsISupportImpl.h文档。 NS_IMPL_ISUPPORTS1(nsDummyCSSLoaderObserver,nsICSSLoaderObserver) //下面是对nsPreloadURIs的定义: classnsPreloadURIs : public nsIRunnable { public: //构造方法,用构造参数aURIs和aSriptThread对两个数据成员进行赋值 nsPreloadURIs(nsAutoTArray<nsSpeculativeScriptThread::PrefetchEntry,5> &aURIs, nsSpeculativeScriptThread*aScriptThread) : mURIs(aURIs), mScriptThread(aScriptThread) { } NS_DECL_ISUPPORTS //事先#define了一些接口的定义,参见nsISupportImpl.h NS_DECL_NSIRUNNABLE static voidPreloadURIs(const nsAutoTArray<nsSpeculativeScriptThread::PrefetchEntry,5> &aURIs, nsSpeculativeScriptThread *aScriptThread); //读取URIs private: nsAutoTArray<nsSpeculativeScriptThread::PrefetchEntry, 5> mURIs;//数组,存放mURIs nsRefPtr<nsSpeculativeScriptThread> mScriptThread; //指针,指向当前解析线程 }; //下面是利用预先#define好的语句添加线程安全性的支持 NS_IMPL_THREADSAFE_ISUPPORTS1(nsPreloadURIs,nsIRunnable) //之后的Run()方法很简单,就是直接调用内部的PreloadURIs()方法。 NS_IMETHODIMP nsPreloadURIs::Run() //运行方法 { PreloadURIs(mURIs, mScriptThread); //直接调用PreloadURIs,将本身的两个成员变量作为参数传递过去 return NS_OK; //返回成功的值 } //下面我们就来看它本体调用的方法,PreloadURIs()的具体执行方法。 void nsPreloadURIs::PreloadURIs(constnsAutoTArray<nsSpeculativeScriptThread::PrefetchEntry, 5> &aURIs, nsSpeculativeScriptThread *aScriptThread) { //首先判断是否是主线程 NS_ASSERTION(NS_IsMainThread(), "Touchingnon-threadsafe objects off thread"); if (aScriptThread->Terminated()) { return; //如果当前线程已经处于销毁状态,则什么事情都不作,直接返回。 } //获取当前线程所对应得nsIDocument对象 nsIDocument *doc = aScriptThread->GetDocument(); //如果读取失败,即doc对象为空则报错 NS_ASSERTION(doc, "We shouldn't havestarted preloading without a document"); // Note: Per the code in the HTML content sink, we shouldbe keeping track // of each <base href> as it comes. However, becausewe do our speculative // parsing off the main thread, this is hard to emulate.For now, just load // the URIs using the document's base URI at the potentialcost of being // wrong and having to re-load a given relative URI later. //对于HTMLcontent sink中的每一个节点代码,我们都应当跟踪所有的<base href>标签,确保基地址的正确。然后,由于我们在主线程之外进行多线程的解析,因此这个跟踪很难实现。目前,我们暂时先用文档的base URI进行其他URIs的读取,虽然这样做可能会出错,我们只能够在出错之后重新再读取相应的相对URI地址。 //首先获取当前doc的BaseURI nsIURI *base = doc->GetBaseURI(); //获取文档的编码集 const nsCString &charset = doc->GetDocumentCharacterSet(); //用一个指针的形式,获取当前线程的PreloadedURIs的地址 nsSpeculativeScriptThread::PreloadedType &alreadyPreloaded = aScriptThread->GetPreloadedURIs(); //获取需要preload的URI //对于每一个参数传递进来的URIs进行处理 for (PRUint32 i = 0, e = aURIs.Length(); i < e;++i) { //获取该数据类型的起始地址 constnsSpeculativeScriptThread::PrefetchEntry &pe = aURIs[i]; //一个指向nsIURI类型的指针 nsCOMPtr<nsIURI>uri; //建立一个新的uri,并调用IO模块去进行读取 nsresult rv = NS_NewURI(getter_AddRefs(uri), pe.uri, charset.get(),base); if (NS_FAILED(rv)) { //如果读取失败 NS_WARNING("Failed to create aURI"); //则报错 continue; //执行下一个循环 } nsCAutoString spec; //新申请一个字符串 uri->GetSpec(spec); //调用GetSpec,获取其URL scheme并将其添加至spec字符串之前 PRBoolanswer; //从当前的alreadyPreloaded的hash列表中查找该spec的URI,是否已经被读取了,如果是则不用再次读取(比如页面上有两张一样的图片,那么只需要读取一次) if (alreadyPreloaded.Get(spec,&answer)) { // Already preloaded. Don't preload again. continue; } //将spec放入已经读取的hash表中,记录其已经被读取 alreadyPreloaded.Put(spec, PR_TRUE); //根据pe的类型,进行不同的读取 switch (pe.type) { case nsSpeculativeScriptThread::SCRIPT: //如果类型是SCRIPT doc->ScriptLoader()->PreloadURI(uri, pe.charset, pe.elementType); //调用doc对象的ScriptLoader对其进行读取 break; case nsSpeculativeScriptThread::IMAGE: //如果是IMAGE doc->MaybePreLoadImage(uri); //调用MaybePreLoadImage(怪不得图像不一定显示出来呢) break; casensSpeculativeScriptThread::STYLESHEET: { //如果是STYLESHEET类型 nsCOMPtr<nsICSSLoaderObserver> obs = newnsDummyCSSLoaderObserver(); //还记得前面的nsDummyCSSLoaderOberver么? doc->CSSLoader()->LoadSheet(uri, doc->NodePrincipal(), NS_LossyConvertUTF16toASCII(pe.charset), obs); //调用doc对象的CSSLoader对该uri进行读取 break; } case nsSpeculativeScriptThread::NONE: //如果是空类型(这种情况不应当发生) NS_NOTREACHED("Uninitialized preloadentry?"); //则报错 break; } } } //以上代码主要用来对文档中需要进行预读取的图像,Script代码,CSS格式表等URL进行读取的处理函数。下面,我们来看nsSpeculativeScriptThread的一些具体方法。首先是调用构件的方法,为该线程提供一些线程安全的支持。 NS_IMPL_THREADSAFE_ISUPPORTS1(nsSpeculativeScriptThread,nsIRunnable) //之后是该线程的运行方法Run() NS_IMETHODIMP nsSpeculativeScriptThread::Run() { //判断,预读取行为不能够在主线程上进行 NS_ASSERTION(!NS_IsMainThread(), "Speculativeparsing on the main thread?"); //初始化当前已经解析的数目为0 mNumConsumed = 0; //调用mTokenizer的初始化方法,进行一些初始化 mTokenizer->WillTokenize(PR_FALSE, &mTokenAllocator); //通过对mKeepParsing进行判断,逐步地进行分词,也就是说通过设置这个变量可以打断分词的过程 while (mKeepParsing) { PRBool flushTokens = PR_FALSE; //设置一个布尔变量 nsresult rv = mTokenizer->ConsumeToken(*mScanner, flushTokens); //调用ConsumeToken对词条进行读取处理,注意传递进去的参数,一个为使用的扫描器,一个为刚刚设置的FALSE的变量 if (NS_FAILED(rv)) { //如果分词失败 break; //则跳出while循环 } mNumConsumed += mScanner->Mark(); //获取当前已经分词过的词条数 // TODO Don't pop the tokens. CToken*token; //当mKeepParsing为真并且 while (mKeepParsing && (token =mTokenizer->PopToken())) { //逐token读取 ProcessToken(token); //对token进行处理,后面有详细解析 } } mTokenizer->DidTokenize(PR_FALSE); //分词结束,调用DidTokenize进行一些收尾工作 if (mKeepParsing) { // Ran out of room in this part of thedocument -- flush out the URIs we // gathered so far so we don't end up waitingfor the parser's current // load to finish. //Doucment的当前这部分已经没有剩余空间了---将我们所收集来的URIs传递出去,以便我们不用一直等到parser的当前读取过程完成。 if (!mURIs.IsEmpty()) { //如果当前URIs不为空 FlushURIs(); //传递出去URIs } } { nsAutoLock al(mLock.get()); //获取互斥锁 mCurrentlyParsing = PR_FALSE; //设置标示当前正在处理的变量为FALSE PR_NotifyCondVar(mCVar.get()); //通知正在当前条件变量上等待的线程 } return NS_OK; } //下面是开始进行处理的函数start()方法: nsresult nsSpeculativeScriptThread::StartParsing(nsParser*aParser) { //判断当前线程是否是主要线程,如果是则报错,因为这是次级线程 NS_ASSERTION(NS_IsMainThread(), "Calledon the wrong thread"); //判断当前线程是否已经开始处理,如果是则报错 NS_ASSERTION(!mCurrentlyParsing, "Badrace happening"); if (!aParser->ThreadPool()) { //如果作为参数传递进来的parser根本没有线程池 return NS_OK; //则返回 } //获取参数传递进来的parser的contentSink nsIContentSink *sink = aParser->GetContentSink(); if (!sink) { //如果获取不到 return NS_OK; //则返回 } nsCOMPtr<nsIDocument> doc =do_QueryInterface(sink->GetTarget()); //获取该sink的所关联的文档对象mDoucment if (!doc) { //如果获取不到 return NS_OK; //则返回 } nsAutoString toScan; CParserContext *context = aParser->PeekContext(); //获取当前parser中位于栈顶的context if (!mLock.get()) { //如果当前没有获取到异步锁,应该说明没有其他线程正在解析 mLock = nsAutoLock::NewLock("nsSpeculativeScriptThread::mLock"); //则申请一个锁 if (!mLock.get()) { //如果申请失败 return NS_ERROR_OUT_OF_MEMORY; //估计是没内存了 } mCVar = PR_NewCondVar(mLock.get()); //申请一个新的条件变量,锁和条件变量需配合使用 if (!mCVar.get()) { //如果申请失败 return NS_ERROR_OUT_OF_MEMORY; } //估计是没内存了 if (!mPreloadedURIs.Init(15)) { //尝试初始化一下mPreloadedURIs的hashtable return NS_ERROR_OUT_OF_MEMORY; //失败估计是没有内存了 } //申请一个新的分词器,使用当前的Context的数据作为初始化参数 mTokenizer = newnsHTMLTokenizer(context->mDTDMode, context->mDocType, context->mParserCommand, 0); if (!mTokenizer) { //如果申请失败 return NS_ERROR_OUT_OF_MEMORY; //估计是没有内存了 } mTokenizer->CopyState(context->mTokenizer); //将该context所对应的Tokenizer中的mFlag,即状态标示变量拷贝过来,拷到现在所新申请的Tokenizer中 context->mScanner->CopyUnusedData(toScan); //并且将改context中未解析完的数据拷贝到toScan变量中 if (toScan.IsEmpty()) { //如果toScan为空,说明要么拷贝失败,要么已经没有未拷贝数据 return NS_OK; } } else if(context == mContext) { //如果获取到了锁,并且当前线程的context等于parser栈顶的Context // Don't parse the same part of the document twice. //避免重复解析 nsScannerIteratorend; context->mScanner->EndReading(end); //获取当前Scanner的结尾位置,并赋值给end nsScannerIterator start; context->mScanner->CurrentPosition(start); //获取当前Scanner的当前位置,并赋值给start if (mNumConsumed >context->mNumConsumed) { // We consumed more the last time we triedspeculatively parsing than we // did the last time we actually parsed. //如果判定成功,说明上次我们读取的数据多于我们上次实际解析了的数据 PRUint32distance = Distance(start, end); //计算start和end之间的距离,并放到distance中 start.advance(PR_MIN(mNumConsumed - context->mNumConsumed,distance)); //将start前进一段距离,这个距离取distance和上次读取数据和上次解析数据之差的最小值 } if (start == end) { //如果start和end相等,说明现在已经解析完毕了,返回即可 // We're at the end of this context's buffer,nothing else to do. return NS_OK; } //将start和end之间的这段数据拷贝至toScan字符串 CopyUnicodeTo(start, end, toScan); }else { // Grab all of the context. //将mScanner中所有未使用的数据拷贝至toScan中 context->mScanner->CopyUnusedData(toScan); if (toScan.IsEmpty()) { // Nothing to parse, don't do anything. //如果此时toScan还为空,那么说明待解析的内容一点也没有了,直接返回 return NS_OK; } } nsCAutoString charset; PRInt32 source; aParser->GetDocumentCharset(charset, source); //获取doucment的字符集 mScanner = new nsScanner(toScan,charset, source); //申请一个新的scanner if (!mScanner) { //如果失败 return NS_ERROR_OUT_OF_MEMORY; //估计是内存不够 } mScanner->SetIncremental(PR_TRUE); //将mScaaner的mIncremental的值设置为TRUE,增量式扫描 mDocument.swap(doc); //交换指针,将doc的值赋值给mDocument mKeepParsing = PR_TRUE; //设置持续解析为TRUE mCurrentlyParsing = PR_TRUE; //设置当前正在解析为TRUE mContext = context; //设置当前线程的mContext为context return aParser->ThreadPool()->Dispatch(this, NS_DISPATCH_NORMAL); //调用组件,将当前线程放入解析器的线程池 } //下面是让解析线程停止的StopParsing()方法。 void nsSpeculativeScriptThread::StopParsing(PRBool/*aFromDocWrite*/) { //判断是否是主线程,如果是则报错 NS_ASSERTION(NS_IsMainThread(), "Can'tstop parsing from another thread"); //如果获取不到当前的锁变量 if (!mLock.get()) { // If we bailed early out of StartParsing,don't do anything. return; //直接返回 } { nsAutoLock al(mLock.get()); //获取锁 mKeepParsing = PR_FALSE; //设置继续解析位为FALSE if (mCurrentlyParsing) { //如果当前正在解析 PR_WaitCondVar(mCVar.get(), PR_INTERVAL_NO_TIMEOUT); //在条件变量上等待 NS_ASSERTION(!mCurrentlyParsing, "Didn'tactually stop parsing?"); //如果当前不是正在进行解析,则报错 } } // The thread is now idle. if (mTerminated) { //如果设置了销毁标示位 // If we're terminated, then we need to ensurethat we release our document // and tokenizer here on the main thread sothat our last reference to them // isn't our alter-ego rescheduled on another thread. //如果销毁了,我们必须要清空我们的分词器,文档引用对象,这样我们就不会错误地引用他们。 mDocument = nsnull; mTokenizer = nsnull; mScanner = nsnull; } else if(mURIs.Length()) { //如果mURIs的长度不为空 // Note: Don't do this if we're terminated. //读取当前已经解析出来的URIs nsPreloadURIs::PreloadURIs(mURIs,this); //清空当前已经解析出来的URIs mURIs.Clear(); } // Note: Currently, we pop the tokens off (see the commentin Run) so this // isn't a problem. If and when we actually use the tokenscreated // off-thread, we'll need to use aFromDocWrite for real. //因为目前我们是将词条们采用出栈的方式进行处理,因此目前这样做(指以上的操作)不会产生什么问题。但是如果我们想使用多线程情况下产生的tokens的话,就需要使用aFromDocWrite了。 } //下面我们来看一下前面用到过的,对词条进行处理的ProcessToken()方法。 void nsSpeculativeScriptThread::ProcessToken(CToken*aToken) { // Only called on the speculative script thread. //这个方法只会在非主线程中被调用 CHTMLToken *token = static_cast<CHTMLToken*>(aToken); //首先将该词条进行一个强制类型转换,转换为HTML词条类型 //之后获取该词条的类型,同样需要进行一下强制类型转换 switch (static_cast<eHTMLTokenTypes>(token->GetTokenType())){ case eToken_start: { //如果是开始型词条,比如<div><li>等,结束型为</div></li>,为其设置一个指针 CStartToken *start = static_cast<CStartToken*>(aToken); //获取该词条的类型ID nsHTMLTag tag = static_cast<nsHTMLTag>(start->GetTypeID()); //获取该词条的属性总数 PRInt16 attrs = start->GetAttributeCount(); PRInt16 i = 0; //申请几个字符串变量,从变量名应该就能看出是用来存放什么的 nsAutoString src; nsAutoString elementType; nsAutoString charset; nsAutoString href; nsAutoString rel; //申请一个prefetchType类型 PrefetchType ptype = NONE; //下面根据tagID进行判断 switch (tag) { case eHTMLTag_link: //如果是eHTMLTag_link ptype = STYLESHEET; //将前面申请的ptype设置为STYLESHEET,即样式表 break; case eHTMLTag_img: //如果是Tag_img ptype = IMAGE; //将ptype设置为IMAGE break; case eHTMLTag_script: //如果是Tag_script ptype = SCRIPT; //将ptype设置为SCRIPT break; default: //其他情况下不需做这种prefetch的处理 break; } // We currently handle the followingelement/attribute combos : //<link rel="stylesheet" href= charset= type> //<script src= charset= type=> //目前我们只处理如下这些元素/属性的集合: // <linkrel="stylesheet" href= charset= type> // <script src= charset=type=> //也就是说只能够识别以上这些形式的超链接 if (ptype != NONE) { //如果ptype不为空,说明可能需要进行prefetch // loopover all attributes to extract relevant info //循环遍历所有的属性,来取到相关的信息 for(; i < attrs ; ++i) { CAttributeToken *attr = static_cast<CAttributeToken*>(mTokenizer->PopToken()); //首先判断token_type,如果不为属性类型的token,则报错 NS_ASSERTION(attr->GetTokenType()== eToken_attribute, "Weird token"); //如下的语句就是分别对attr的类型进行判断,并且获取其属性值,如果没有该属性,那么该属性所对应的变量就为空 if(attr->GetKey().EqualsLiteral("src")){ src.Assign(attr->GetValue()); } elseif (attr->GetKey().EqualsLiteral("href")) { href.Assign(attr->GetValue()); } elseif (attr->GetKey().EqualsLiteral("rel")) { rel.Assign(attr->GetValue()); } elseif (attr->GetKey().EqualsLiteral("charset")) { charset.Assign(attr->GetValue()); } elseif (attr->GetKey().EqualsLiteral("type")) { elementType.Assign(attr->GetValue()); } IF_FREE(attr,&mTokenAllocator); //前面#define的方法,用来回收attr指针 } // ensurewe have the right kind if it's a link-element if(ptype == STYLESHEET) { //如果是STYLESHEET类型,我们还需要对其进行一下验证和特殊处理,确保后面的操作能够顺利进行 if(rel.EqualsLiteral("stylesheet")){ //判断如果rel的值为stylesheet //将href的值赋值给src,因为src是后面将会用到的一个很重要的变量 src = href; // src is the important variable below } else{ //其他情况下,清空src的值 src.Truncate(); // clear src if wrong kind of link } } // add tolist if we have a valid src //如果我们的src是正确的,那么就将其加入到prefetch的list中 if(!src.IsEmpty()) { //判断src不为空 //将其添加到prefetchlist中 AddToPrefetchList(src, charset,elementType, ptype); } } else { //Irrelevant tag, but pop and free all its attributes in any case //可能是无关的tag,为了保险起见要将其所有的属性值取出 for(; i < attrs ; ++i) { CToken *attr =mTokenizer->PopToken(); //取出Token IF_FREE(attr,&mTokenAllocator); //回收该attr指针 } } break; } default: break; } IF_FREE(aToken, &mTokenAllocator); //回收aToken } //下面就是我们前面所用到的AddToPrefetchList()了,即将需要预载入的URI放入一个清单中。 void nsSpeculativeScriptThread::AddToPrefetchList(const nsAString &src, const nsAString &charset, const nsAString &elementType, PrefetchType type) {//注意传递的参数及类型 PrefetchEntry *pe = mURIs.AppendElement(); //在mURIs中新增一个元素,并返回指向该元素的一个指针 pe->type = type; //用参数type为其赋值 pe->uri = src; //用参数src为其赋值 pe->charset = charset; //用参数charset为其赋值 pe->elementType = elementType; //用参数elementType为其赋值 if (mURIs.Length() == kBatchPrefetchURIs) { //如果mURIs的列表长度到达了我们定义的阈值(目前为5),我们就会调用下面的方法将URIs抛出 FlushURIs(); //清空URIs } } //下面我们看一下上面这个FlushURIs()方法。 void nsSpeculativeScriptThread::FlushURIs() { nsCOMPtr<nsIRunnable> r = newnsPreloadURIs(mURIs, this); //首先用当前线程申请一个新的PreloadURIs对象,用来对mURIs中的各个元素分别进行调用读取,因为其有Run()方法,因此可以用一个nsIRunnable指针指向它 if (!r) { //如果申请失败 return; //则返回 } mURIs.Clear(); //清空mURIs中的数据 NS_DispatchToMainThread(r, NS_DISPATCH_NORMAL); //将r放到主线程(应该是线程池)中去 } //上面就是一些和Parser有关的支持函数了,下面我们来看解析器真正的主类部分: //首先初始化并清空几个值,前两个是字符集转换用的,后一个方前面次级线程用的线程池 nsICharsetAlias*nsParser::sCharsetAliasService = nsnull; nsICharsetConverterManager*nsParser::sCharsetConverterManager = nsnull; nsIThreadPool*nsParser::sSpeculativeThreadPool = nsnull; //首先是nsParser的一个初始化方法,在构造方法中被调用。 /** * Thisgets called when the htmlparser module is initialized. */ // static //这段方法在htmlparser模块进行初始化的时候被调用 nsresult nsParser::Init() { nsresult rv; nsCOMPtr<nsICategoryManager> cm = //获取一个目录支持的服务,一个类似通过key查找value的hash表 do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); //判断是否获取成功 nsCOMPtr<nsISimpleEnumerator> e; //一个遍历器,用来遍历parser的数据 rv = cm->EnumerateCategory("Parserdata listener", getter_AddRefs(e)); //获取到名为”Parser data listener”的Category,并将e作为其遍历器,此处尚不清楚该目录服务是怎样注册的 NS_ENSURE_SUCCESS(rv, rv); //确保操作成功 //下面申请几个变量 nsCAutoString categoryEntry; nsXPIDLCString contractId; nsCOMPtr<nsISupports> entry; //用e进行遍历,并将查询到的key放入entry while(NS_SUCCEEDED(e->GetNext(getter_AddRefs(entry)))) { nsCOMPtr<nsISupportsCString> category(do_QueryInterface(entry)); //使用entry进行查询 if (!category) { //如果查询失败,则推出本次循环,继续进行下一个循环 NS_WARNING("Category entry not annsISupportsCString!"); continue; } //通过category获取其对应的Data,并放到categoryEntry中 rv = category->GetData(categoryEntry); NS_ENSURE_SUCCESS(rv, rv); //通过categoryEntry,获取其对应的contractID rv = cm->GetCategoryEntry("Parserdata listener", categoryEntry.get(), getter_Copies(contractId)); NS_ENSURE_SUCCESS(rv, rv); //通过contractID,创建一个新的字节流监听器listener nsCOMPtr<nsIUnicharStreamListener> listener = do_CreateInstance(contractId.get()); if (listener) { //如果成功创建了listener if (!sParserDataListeners) { //初始化的时候,这个变量应该为空 //创建一个新的数组,存放nsIUnicharStreamListener类型的监听器 sParserDataListeners = newnsCOMArray<nsIUnicharStreamListener>(); if (!sParserDataListeners) //如果创建数组失败 return NS_ERROR_OUT_OF_MEMORY; //说明内存不够了 } sParserDataListeners->AppendObject(listener); //将新的listener加入到sParserDataListeners数组中去 } } //可见,listener的数量有很多,并且会通过一个数组来对其进行管理。 nsCOMPtr<nsICharsetAlias> charsetAlias = //获取字符集服务 do_GetService(NS_CHARSETALIAS_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); //确保获取操作成功 nsCOMPtr<nsICharsetConverterManager> charsetConverter = //获取字符集转换服务 do_GetService(NS_CHARSETCONVERTERMANAGER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); //确保获取操作成功 //使用swap操作,将新申请的两个服务赋值给当前parser的两个相应数据成员中去 charsetAlias.swap(sCharsetAliasService); charsetConverter.swap(sCharsetConverterManager); nsCOMPtr<nsIThreadPool> threadPool = //申请一个新的线程池 do_CreateInstance(NS_THREADPOOL_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); //确保操作成功 rv = threadPool->SetThreadLimit(kSpeculativeThreadLimit); //设置该线程池的最大线程数量(默认15) NS_ENSURE_SUCCESS(rv, rv); //确保操作成功 rv = threadPool->SetIdleThreadLimit(kIdleThreadLimit); //设置该线程池空闲线程的最大数目(默认为0) NS_ENSURE_SUCCESS(rv, rv); //确保操作成功 rv = threadPool->SetIdleThreadTimeout(kIdleThreadTimeout); //设置该线程池空闲线程的超时时间限制(默认为50) NS_ENSURE_SUCCESS(rv, rv); //确保操作成功 threadPool.swap(sSpeculativeThreadPool); //同样通过swap操作,用新申请的变量对本parser的数据成员进行赋值 return NS_OK; } //下面是shutdown()方法,用来关闭parser。 /** * Thisgets called when the htmlparser module is shutdown. */ // static voidnsParser::Shutdown() //这个方法会在parser关闭的时候被调用 { delete sParserDataListeners; //删除相应的sParserDataListeners数组,回收其内存空间 sParserDataListeners = nsnull; //设置本parser的指针为空 NS_IF_RELEASE(sCharsetAliasService); //构件回收方法 NS_IF_RELEASE(sCharsetConverterManager); //构件回收方法 if (sSpeculativeThreadPool) { //如果线程池存在 sSpeculativeThreadPool->Shutdown(); //关闭 NS_RELEASE(sSpeculativeThreadPool); //回收线程池 } } //下面是默认的构造方法,很简单,直接调用写好的方法 /** *default constructor */ nsParser::nsParser() //构造方法 { Initialize(PR_TRUE); //调用Initialize } nsParser::~nsParser() //析构方法 { Cleanup(); //调用Cleanup() } //接下来我们看一下上面构造方法中调用的Initialize(PR_TRUE)方法的实现。 void nsParser::Initialize(PRBool aConstructor) //参数为布尔型的aConstructor { #ifdefNS_DEBUG //如果是调试模式,输出调试信息 if (!gDumpContent) { gDumpContent =PR_GetEnv("PARSER_DUMP_CONTENT") != nsnull; } #endif if (aConstructor) { // Raw pointer //说明用的是普通指针 mParserContext = 0; //设置当前的ParserContext为0,即没有解析上下文 } else { //否则说明用的是nsCOMPtr构件指针 mObserver = nsnull; //初始化mOberver变量 mParserFilter = nsnull; mUnusedInput.Truncate(); //清空待处理的数据 } mContinueEvent = nsnull; //初始化解析结束时触发的时间,默认为空 mCharsetSource = kCharsetUninitialized; //设置字符集来源设置,此处的默认值即为0,在头文件中声明的 mCharset.AssignLiteral("ISO-8859-1"); //为mCharset mInternalState = NS_OK; //目前运行状态,默认为TRUE即正常 mStreamStatus = 0; mCommand = eViewNormal; //注意这里,默认初始化时是ViewNormal模式 mFlags = NS_PARSER_FLAG_OBSERVERS_ENABLED | //位或操作 NS_PARSER_FLAG_PARSER_ENABLED | //结果值应为0x0000000E NS_PARSER_FLAG_CAN_TOKENIZE; MOZ_TIMER_DEBUGLOG(("Reset: Parse Time:nsParser::nsParser(), this=%p\n", this)); MOZ_TIMER_RESET(mParseTime); //重设时间,具体请参考nsTimer.h,以及stopwatch.h MOZ_TIMER_RESET(mDTDTime); //主要作用就是计时,因为Mozilla FireFox是一款注重 MOZ_TIMER_RESET(mTokenizeTime); //人机交互的浏览器,对于任何响应时间都要进行计算 } //下面,是用来清除Context的parser::CleanUp()方法 void nsParser::Cleanup() { #ifdefNS_DEBUG //这些调试的输出信息就不解释了 if (gDumpContent) { if (mSink) { // Sink (HTMLContentSink at this time)supports nsIDebugDumpContent // interface. We can get to the contentmodel through the sink. nsresult result = NS_OK; nsCOMPtr<nsIDebugDumpContent>trigger = do_QueryInterface(mSink, &result); if (NS_SUCCEEDED(result)) { trigger->DumpContentModel(); } } } #endif #ifdefDEBUG if (mParserContext &&mParserContext->mPrevContext) { NS_WARNING("Extra parser contextsstill on the parser stack"); } #endif while (mParserContext) { //如果当前parserContext不为空,则循环进行清除,直到为空为止 CParserContext *pc = mParserContext->mPrevContext; //获取前一个Context delete mParserContext; //删除当前 mParserContext = pc; //将当前的Context赋为前一个Context } // It should not be possible for this flag to be set whenwe are getting // destroyed since this flag implies a pendingnsParserContinueEvent, which // has an owning reference to |this|. //当我们正在被销毁的时候,下面这个判断是不应当发生的,因为下面这个标示位表示有一个nsParserContinueEvent被挂起,并且有一个关联到当前parser的关系。 //位操作,很好理解 NS_ASSERTION(!(mFlags & NS_PARSER_FLAG_PENDING_CONTINUE_EVENT), "bad"); if (mSpeculativeScriptThread) { //如果有当前的次级线程存在 mSpeculativeScriptThread->Terminate(); //销毁该线程 mSpeculativeScriptThread = nsnull; //并将关联指针赋为空 } } //下面有一大段代码,全部使用之前#define的方法进行了定义,是一种很有意思的代码声明方式。这段代码我们这里暂且不进行解析,(有点麻烦,写完后面再回来写这部分吧)有兴趣的读者可以自己去跟踪源代码,很有意思,也是一种统一地复用化写代码的方法。 NS_IMPL_CYCLE_COLLECTION_CLASS(nsParser) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsParser) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDTD) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mSink) NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mObserver) NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsParser) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDTD) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mSink) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mObserver) CParserContext *pc = tmp->mParserContext; while (pc) { cb.NoteXPCOMChild(pc->mTokenizer); pc = pc->mPrevContext; } NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTING_ADDREF_AMBIGUOUS(nsParser,nsIParser) NS_IMPL_CYCLE_COLLECTING_RELEASE_AMBIGUOUS(nsParser,nsIParser) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsParser) NS_INTERFACE_MAP_ENTRY(nsIStreamListener) NS_INTERFACE_MAP_ENTRY(nsIParser) NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIParser) NS_INTERFACE_MAP_END //下面我们继续看后面的代码,如果有印象的读者会记得,前面说过,下面这个PostContinueEvent是在解析结束后触发的事件,且只有可能在两种情况下出现。我们来看一下它的具体实现: // The parser continue eventis posted only if // all of the data to parsehas been passed to ::OnDataAvailable // and the parser has beeninterrupted by the content sink // because the processing oftokens took too long. //parser的触发事件只有在所有的数据都传递到OnDataAvailable时,或者由于token的处理使劲过长导致parser被contentsink打断时才会发生。 nsresult nsParser::PostContinueEvent() { if (!(mFlags &NS_PARSER_FLAG_PENDING_CONTINUE_EVENT)) { //位与操作,即判断二进制低位起第3个bit位是否被设置,如果没设置,那么就不应当出现continueEvent,报错 // If this flag isn't set, then thereshouldn't be a live continue event! NS_ASSERTION(!mContinueEvent, "bad"); // This creates a reference cycle between thisand the event that is // broken when the event fires. //下面这将会创建一段代码,主要是注册事件用的,其中会用到前面解析过的nsParserContinueEvent,该Event中提供了Run()方法,Run方法中又调用了Parser::HandleParserContinueEvent,后面会进行解析 nsCOMPtr<nsIRunnable>event = newnsParserContinueEvent(this); //创建事件 if(NS_FAILED(NS_DispatchToCurrentThread(event))){ //将事件分发至主线程 NS_WARNING("failed to dispatch parsercontinuation event"); } else { //成功分发后 mFlags |= NS_PARSER_FLAG_PENDING_CONTINUE_EVENT; //设置Event挂起位 mContinueEvent = event; //将本parser的相应变量mContinueEvent设置为该event } } return NS_OK; } //接下来是两个小方法,用来设置成员变量 NS_IMETHODIMP_(void) nsParser::SetParserFilter(nsIParserFilter* aFilter) //为Parser设置过滤器 { mParserFilter = aFilter; //具体的filter解析我们放在对ParserFilter文件解析时再进行 } NS_IMETHODIMP_(void) nsParser::GetCommand(nsCString&aCommand) //获取Parser的Command { aCommand = mCommandStr; //用当前的command去赋值参数传递过来的地址 } /** * Callthis method once you've created a parser, and want to instruct it * aboutthe command which caused the parser to be constructed. For example, * thisallows us to select a DTD which can do, say, view-source. * *@param aCommand the commandstring to set */ //一旦你创建了parser后,就可以调用这个方法来指示它用某种命令模式进行构建了。比如,我们可以通过这个方法来设置DTD对象的权限和功能,比如view-source模式。 NS_IMETHODIMP_(void) nsParser::SetCommand(const char* aCommand) { mCommandStr.Assign(aCommand); //将该Command赋值给 if (mCommandStr.Equals(kViewSourceCommand)) { //判断是否是”view-source” mCommand = eViewSource; //设置相应命令 } else if(mCommandStr.Equals(kViewFragmentCommand)) { //判断是否是”view-fragment” mCommand = eViewFragment; //设置相应命令 } else { mCommand = eViewNormal; //其他情况设置为默认值 } } //下面这个方法作用和上面这个一样,只不过参数换成了eParserCommand类型而已 /** * Callthis method once you've created a parser, and want to instruct it * aboutthe command which caused the parser to be constructed. For example, * thisallows us to select a DTD which can do, say, view-source. * *@param aParserCommand the commandto set */ NS_IMETHODIMP_(void) nsParser::SetCommand(eParserCommandsaParserCommand) { mCommand = aParserCommand; //因为参数已经是aParserCommands,直接赋值就行了 } 下面的方法,是用来进行一些变量值设置的。 /** * Callthis method once you've created a parser, and want to instruct it * aboutwhat charset to load * *@param aCharset- the charset ofa document *@param aCharsetSource- thesource of the charset */ //一旦你创建了parser之后,并且希望为它设置所要使用的字符集时可以调用这个方法 NS_IMETHODIMP_(void) nsParser::SetDocumentCharset(const nsACString& aCharset, PRInt32aCharsetSource) { mCharset = aCharset; //设置当前的charset mCharsetSource = aCharsetSource; //设置当前的charsetSource if (mParserContext &&mParserContext->mScanner) { //如果当前的解析上下文存在并且对应的扫描器也存在 mParserContext->mScanner->SetDocumentCharset(aCharset,aCharsetSource); //那么需要为扫描器也设置一下字符集charset } } //下面这个方法是设置本parser相应的contentSink的字符集的,很简单不多做解释了 void nsParser::SetSinkCharset(nsACString&aCharset) { if (mSink) { mSink->SetDocumentCharset(aCharset); } } /** * Thismethod gets called in order to set the content * sinkfor this parser to dump nodes to. * *@param nsIContentSink interfacefor node receiver */ NS_IMETHODIMP_(void) //这个方法就是为该parser设置其输出所用的contentSink的 nsParser::SetContentSink(nsIContentSink*aSink) { NS_PRECONDITION(aSink, "sink cannot benull!"); //判断参数给的contentSink不为空 mSink = aSink; //用参数的sink赋值给当前parser的mSink if (mSink) { //如果mSink不为空 mSink->SetParser(this); //同时需要设置一下,确保该sink的对应parser是自己 } } //下面这个方法用来获取本parser所对应的contentsink /** * retrieve the sink set into the parser * @returncurrent sink */ NS_IMETHODIMP_(nsIContentSink*) nsParser::GetContentSink() { return mSink; //只需要返回msink的值 } //下面的这些方法需要注意一下,主要因为其重要性比较高。 //该方法主要用来确定应当使用哪种DTD来进行解析。因为DTD对象才是真正进行文法比对的对象。对于不同格式的HTML,需要使用不同的DTD,或者说需要对DTD进行不同的设置。因此确定应当使用哪种DTD就显得格外重要。 //首先我们来看一下它代码中的注释部分所说的: //确定为这份文档使用哪种DTD模式(同时也就确定了使用哪种layout兼容模式)主要是基于从网络部分所接收的第一块数据块(每一个解析上下文parsercontext都可以拥有它自己的DTD)。目前这还不是最优的解决方案,我们在接收到DOCTYPE之前其实都不需要对其做太多的考虑,并且如果本parser能够设计得更加方便进行正则表达式的判定的话,这个过程可能可以设计得简单得多。 //下面我们就来看看它的代码,首先是一个支持用的方法ParsePS() // Parse the PS productionin the SGML spec (excluding the part dealing // with entity references)starting at theIndex into theBuffer, and // return the first indexafter the end of the production. //这段代码的输入是一个字符串的地址,外带一个整形的地址,实际上就是从参数aIndex的位置开始,在aBuffer中寻找SGML定义语言的起始部分,其中要跳过所有的空格以及\t \n \r等字符,并且跳过所有-- ….--形式的内容(即注释)并返回第一个不符合以上条件这样字符的位置,相当于一个复杂一点的string.trim()函数 staticPRInt32 ParsePS(constnsString& aBuffer, PRInt32 aIndex) { for (;;) { //无限制循环 PRUnichar ch = aBuffer.CharAt(aIndex); //首先找到位于aIndex的字符并进行判断 if ((ch == PRUnichar(' ')) || (ch == PRUnichar('\t'))|| (ch == PRUnichar('\n')) || (ch ==PRUnichar('\r'))) { //如果为以上这四种字符 ++aIndex; //那么将aIndex加一 } else if(ch == PRUnichar('-')) { //否则判断是否为’-’ PRInt32 tmpIndex; if (aBuffer.CharAt(aIndex+1) ==PRUnichar('-') && //如果是的话则在其之后再寻找结尾的--符 kNotFound != (tmpIndex=aBuffer.Find("--",PR_FALSE,aIndex+2,-1))){ aIndex = tmpIndex + 2; } else { return aIndex; //其他情况下则返回该aIndex的值 } } else { returnaIndex; //同样返回aIndex的值 } } } //之后定义了几个需要用到的标示位,这次不用十六进制表示了,采取的是移位的方法使其互不冲突。从其定义的名称就可以大概看出其作用。 #definePARSE_DTD_HAVE_DOCTYPE(1<<0) #definePARSE_DTD_HAVE_PUBLIC_ID(1<<1) #definePARSE_DTD_HAVE_SYSTEM_ID(1<<2) #definePARSE_DTD_HAVE_INTERNAL_SUBSET(1<<3) //下面的方法,就是通过解析相应字段,来判断DTD的类型的代码: // return PR_TRUE on success(includes not present), PR_FALSE on failure staticPRBool ParseDocTypeDecl(const nsString &aBuffer, PRInt32 *aResultFlags, nsString &aPublicID, nsString &aSystemID) { PRBool haveDoctype = PR_FALSE; //初始化设置是否有DTD设置项为FALSE *aResultFlags = 0; //初始化Flag,为空 // Skip through any comments and processing instructions // The PI-skipping is a bit of a hack. PRInt32 theIndex = 0; //初始化解析位置 do { //本循环是跳过所有的注释部分即<!--类型的代码 theIndex = aBuffer.FindChar('<',theIndex); //首先找到’<’的位置 if (theIndex == kNotFound) break; //如果没找到,直接退出循环 PRUnichar nextChar = aBuffer.CharAt(theIndex+1); //取该位置的下一个位置的字符 if (nextChar == PRUnichar('!')) { //判断是否是’!’ PRInt32 tmpIndex = theIndex + 2; //如果是,那么继续判断’!’之后的下一个字符 if (kNotFound != //从该位置之后紧邻的位置开始找”DOCTYPE”字样 (theIndex=aBuffer.Find("DOCTYPE",PR_TRUE, tmpIndex, 0))) { haveDoctype = PR_TRUE; //如果找到 theIndex += 7; // skip "DOCTYPE" //说明有DOCTYPE,跳过这个DOCTYPE字段 break; //退出循环 } theIndex = ParsePS(aBuffer, tmpIndex); //调用前面的ParsePS方法 theIndex = aBuffer.FindChar('>',theIndex); //从返回的位置其实开始找’>’ } else if(nextChar == PRUnichar('?')) { //注意nextChar的值,始终是theIndex+1的位置的字符值,这个判断即使说如果碰到’<?’的话 theIndex = aBuffer.FindChar('>',theIndex); //那么在之后直接找’>’,即跳过’<?’至’>’这一部分 } else { break; //其他情况下,直接退出循环 }while (theIndex != kNotFound); //这个循环直到进行到theIndex=-1,即找不到为止 if (!haveDoctype) //如果前面的循环中没有找到DOCTYPE的话 return PR_TRUE; //直接返回 *aResultFlags |= PARSE_DTD_HAVE_DOCTYPE; //否则就将相应的标示位置为1 theIndex = ParsePS(aBuffer, theIndex); //之后我们从找到DOCTYPE的之后开始,先进行ParsePS处理 theIndex = aBuffer.Find("HTML",PR_TRUE, theIndex, 0); //而后从得到的位置之后找HTML字样,PR_TRUE说明区分大小写 if (kNotFound == theIndex) //如果没找到 return PR_FALSE; //那么返回错误 theIndex = ParsePS(aBuffer, theIndex+4); //从HTML字样之后开始找,先运行ParsePS函数 PRInt32 tmpIndex = aBuffer.Find("PUBLIC",PR_TRUE, theIndex, 0); //之后开始找PUBLIC字样 if (kNotFound != tmpIndex) { //如果找到了 theIndex = ParsePS(aBuffer, tmpIndex+6); //那么从PUBLIC字样之后运行ParsePS函数 // We get here only if we've read <!DOCTYPEHTML PUBLIC // (not case sensitive) possibly with commentswithin. // Now find the beginning and end of thepublic identifier // and the system identifier (if present). //如果函数运行到了这里的话,说明我们已经找到了<!DOCTYPE HTML PUBLIC字样,并不区分大小写,并且其中可能跳过了一些注释字段,下面我们就开始寻找public identifier和system identifier的开始和结束位置了 PRUnichar lit = aBuffer.CharAt(theIndex); if ((lit != PRUnichar('\"')) && (lit != PRUnichar('\''))) //如果紧接着出现不符合规范的字符 return PR_FALSE; //直接报错 // Start is the first character, excluding thequote, and End is // the final quote, so there are (end-start)characters. //带Start名称的变量指向的是第一个字符,不包括引号,而带End名称的变量指向的是最后一个引号,这样一共有(end - start)个字符 PRInt32 PublicIDStart = theIndex + 1; //start指向第一个字符 PRInt32 PublicIDEnd = aBuffer.FindChar(lit, PublicIDStart); //从start开始往后找第一个引号,并且赋值给end if (kNotFound == PublicIDEnd) //如果没找到 return PR_FALSE; //直接报错 theIndex = ParsePS(aBuffer, PublicIDEnd + 1); //从end开始,运行ParsePS函数 PRUnichar next = aBuffer.CharAt(theIndex); //获取该字符 if (next == PRUnichar('>')) { //如果是’>’,说明只有public标示符,但是没有system标示符,因此什么也不做,进行这个判断主要是为了避免结尾的地方还有什么其他东西 // There was a public identifier, but nosystem // identifier, // so do nothing. // This is needed to avoid the else at theend, and it's // also the most common case. } else if((next == PRUnichar('\"')) || //如果接下来是”或者’符号 (next == PRUnichar('\''))) { // We found a system identifier. //说明我们找到了system identifier *aResultFlags |= PARSE_DTD_HAVE_SYSTEM_ID; //将相应的标示位设置为1 PRInt32 SystemIDStart = theIndex + 1; //将start设置为该位置加1,即指向第一个字符 PRInt32 SystemIDEnd = aBuffer.FindChar(next, SystemIDStart); //从start位置开始,寻找相应的end位置的单引号或者双引号 if (kNotFound == SystemIDEnd) //如果没找到 return PR_FALSE; //返回错误 aSystemID = //截取该system identifier字符串并赋值给参数aSystemID的地址 Substring(aBuffer, SystemIDStart, SystemIDEnd - SystemIDStart); } else if(next == PRUnichar('[')) { //如果下一个字符是’[’ // We found an internal subset. //找到了一个内部subset *aResultFlags |= PARSE_DTD_HAVE_INTERNAL_SUBSET; //设置相应的标志位 } else { //其他情况,说明出错了 // Something's wrong. return PR_FALSE; } // Since a public ID is a minimum literal, wemust trim // and collapse whitespace //因为public ID是由紧凑字符组成,因此我们必须消除一些空格字符 //获取aPublicID aPublicID= Substring(aBuffer, PublicIDStart, PublicIDEnd - PublicIDStart); //压缩该aPublicID,消除空格 aPublicID.CompressWhitespace(PR_TRUE,PR_TRUE); //设置相应的标志位 *aResultFlags |= PARSE_DTD_HAVE_PUBLIC_ID; } else { //如果没有找到public ID,我们就尝试只去找SYSTEM ID tmpIndex=aBuffer.Find("SYSTEM",PR_TRUE, theIndex, 0); if (kNotFound != tmpIndex) { //如果找到 // DOCTYPES with system ID but no Public ID //说明DOCTYPES里只有sytem ID,但是没有Public ID //设置相应的标示位 *aResultFlags |= PARSE_DTD_HAVE_SYSTEM_ID; //从SYSTEM字段之后,调用ParsePS函数 theIndex = ParsePS(aBuffer, tmpIndex+6); //获取之后获得的theIndex所对应的字符 PRUnichar next = aBuffer.CharAt(theIndex); //如果不是规定的单引号或双引号 if (next != PRUnichar('\"') && next != PRUnichar('\'')) returnPR_FALSE; //直接报错返回 PRInt32 SystemIDStart = theIndex + 1; //start指向第一个字符 PRInt32 SystemIDEnd = aBuffer.FindChar(next,SystemIDStart); //获取end,即最后一个引号 if (kNotFound == SystemIDEnd) //如果没找到 return PR_FALSE; //返回错误 aSystemID = //截取字符串,获取SystemID Substring(aBuffer, SystemIDStart, SystemIDEnd - SystemIDStart); theIndex = ParsePS(aBuffer, SystemIDEnd + 1); //并对end之后进行一下parsePS } PRUnichar nextChar = aBuffer.CharAt(theIndex); //获取DOCTYPE声明部分最后的结尾字符 if (nextChar == PRUnichar('[')) //如果是’[’ *aResultFlags |= PARSE_DTD_HAVE_INTERNAL_SUBSET; //设置标示位,内部子集 else if(nextChar != PRUnichar('>')) //如果该字符不是’>’ returnPR_FALSE; //报错 } return PR_TRUE; //都成功的情况下,说明解析成功,返回PR_TRUE } //下面针对上面解析中的public ID进行了几种类型的定义,通过声明了一个数据类型来进行存储这些定义。 structPubIDInfo { enum eMode { //类型声明,枚举了三种模式 eQuirks, /* always quirks mode, unless there's an internal subset */ eAlmostStandards,/* eCompatibility_AlmostStandards*/ eFullStandards /*eCompatibility_FullStandards */ /* * public IDs that should trigger strictmode are not listed * since we want all future public IDs totrigger strict mode as * well */ //对于那些没有列出的public ID,默认都应当使用严格模式 }; const char* name; //名字 eMode mode_if_no_sysid; //如果没有SYSTEM ID情况下的eMode eMode mode_if_sysid; //如果有SYSTEM ID情况下的eMode }; //下面用#define设置了一个ELEMENTS_OF(array_)类型,可以看到其作用就是通过给定一个数组计算:数组大小/第一个元素的大小。 #defineELEMENTS_OF(array_) (sizeof(array_)/sizeof(array_[0])) //之后这个静态变量数组kPublicIDs,里面存放的元素类型就是前面声明了的PubIDInfo,我们可以看到,基本所有Mozilla所支持的DTD类型都放在了这里面。 static const PubIDInfo kPublicIDs[] = { {"+//silmaril//dtd html pro v0r1119970101//en" /* "+//Silmaril//dtdhtml Pro v0r11 19970101//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//advasoft ltd//dtd html 3.0 aswedit+ extensions//en" /* "-//AdvaSoftLtd//DTD HTML 3.0 asWedit + extensions//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//as//dtd html 3.0 aswedit +extensions//en" /* "-//AS//DTD HTML3.0 asWedit + extensions//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//ietf//dtd html 2.0 level1//en" /* "-//IETF//DTD HTML 2.0Level 1//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html 2.0 level2//en" /* "-//IETF//DTD HTML 2.0Level 2//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html 2.0 strict level1//en" /* "-//IETF//DTD HTML 2.0 StrictLevel 1//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html 2.0 strict level2//en" /* "-//IETF//DTD HTML 2.0Strict Level 2//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html 2.0strict//en" /* "-//IETF//DTD HTML2.0 Strict//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html 2.0//en"/* "-//IETF//DTD HTML 2.0//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html 2.1e//en"/* "-//IETF//DTD HTML 2.1E//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html 3.0//en"/* "-//IETF//DTD HTML 3.0//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html 3.0//en//"/* "-//IETF//DTD HTML 3.0//EN//" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html 3.2 final//en"/* "-//IETF//DTD HTML 3.2 Final//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html 3.2//en"/* "-//IETF//DTD HTML 3.2//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html 3//en" /* "-//IETF//DTD HTML 3//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html level 0//en"/* "-//IETF//DTD HTML Level 0//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html level0//en//2.0" /* "-//IETF//DTD HTMLLevel 0//EN//2.0" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html level 1//en"/* "-//IETF//DTD HTML Level 1//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html level1//en//2.0" /* "-//IETF//DTD HTMLLevel 1//EN//2.0" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html level 2//en"/* "-//IETF//DTD HTML Level 2//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html level2//en//2.0" /* "-//IETF//DTD HTMLLevel 2//EN//2.0" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html level 3//en"/* "-//IETF//DTD HTML Level 3//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html level3//en//3.0" /* "-//IETF//DTD HTMLLevel 3//EN//3.0" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html strict level0//en" /* "-//IETF//DTD HTML StrictLevel 0//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html strict level0//en//2.0" /* "-//IETF//DTD HTMLStrict Level 0//EN//2.0" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html strict level1//en" /* "-//IETF//DTD HTML StrictLevel 1//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html strict level1//en//2.0" /* "-//IETF//DTD HTMLStrict Level 1//EN//2.0" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html strict level2//en" /* "-//IETF//DTD HTML StrictLevel 2//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html strict level2//en//2.0" /* "-//IETF//DTD HTMLStrict Level 2//EN//2.0" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//ietf//dtd html strict level3//en" /* "-//IETF//DTD HTML StrictLevel 3//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html strict level3//en//3.0" /* "-//IETF//DTD HTMLStrict Level 3//EN//3.0" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//ietf//dtd html strict//en"/* "-//IETF//DTD HTML Strict//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd htmlstrict//en//2.0" /* "-//IETF//DTDHTML Strict//EN//2.0" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd htmlstrict//en//3.0" /* "-//IETF//DTDHTML Strict//EN//3.0" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html//en" /* "-//IETF//DTD HTML//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html//en//2.0"/* "-//IETF//DTD HTML//EN//2.0" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//ietf//dtd html//en//3.0"/* "-//IETF//DTD HTML//EN//3.0" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//metrius//dtd metriuspresentational//en" /*"-//Metrius//DTD Metrius Presentational//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//microsoft//dtd internet explorer2.0 html strict//en" /*"-//Microsoft//DTD Internet Explorer 2.0 HTML Strict//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//microsoft//dtd internet explorer2.0 html//en" /* "-//Microsoft//DTDInternet Explorer 2.0 HTML//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//microsoft//dtd internet explorer2.0 tables//en" /*"-//Microsoft//DTD Internet Explorer 2.0 Tables//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//microsoft//dtd internet explorer3.0 html strict//en" /*"-//Microsoft//DTD Internet Explorer 3.0 HTML Strict//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//microsoft//dtd internet explorer3.0 html//en" /* "-//Microsoft//DTDInternet Explorer 3.0 HTML//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//microsoft//dtd internet explorer3.0 tables//en" /*"-//Microsoft//DTD Internet Explorer 3.0 Tables//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//netscape comm. corp.//dtdhtml//en" /* "-//Netscape Comm.Corp.//DTD HTML//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//netscape comm. corp.//dtd stricthtml//en" /* "-//Netscape Comm.Corp.//DTD Strict HTML//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//o'reilly and associates//dtd html2.0//en" /* "-//O'Reilly andAssociates//DTD HTML 2.0//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//o'reilly and associates//dtd htmlextended 1.0//en" /* "-//O'Reillyand Associates//DTD HTML Extended 1.0//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//o'reilly and associates//dtd htmlextended relaxed 1.0//en" /*"-//O'Reilly and Associates//DTD HTML Extended Relaxed 1.0//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//softquad software//dtd hotmetalpro 6.0::19990601::extensions to html 4.0//en" /* "-//SoftQuad Software//DTD HoTMetaL PRO6.0::19990601::extensions to HTML 4.0//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//softquad//dtd hotmetal pro4.0::19971010::extensions to html 4.0//en" /*"-//SoftQuad//DTD HoTMetaL PRO 4.0::19971010::extensions to HTML4.0//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//spyglass//dtd html 2.0extended//en" /* "-//Spyglass//DTDHTML 2.0 Extended//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//sq//dtd html 2.0 hotmetal +extensions//en" /* "-//SQ//DTD HTML2.0 HoTMetaL + extensions//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//sun microsystems corp.//dtdhotjava html//en" /* "-//SunMicrosystems Corp.//DTD HotJava HTML//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//sun microsystems corp.//dtdhotjava strict html//en" /* "-//SunMicrosystems Corp.//DTD HotJava Strict HTML//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//w3c//dtd html 31995-03-24//en" /* "-//W3C//DTD HTML3 1995-03-24//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//w3c//dtd html 3.2 draft//en"/* "-//W3C//DTD HTML 3.2 Draft//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//w3c//dtd html 3.2 final//en"/* "-//W3C//DTD HTML 3.2 Final//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//w3c//dtd html 3.2//en"/* "-//W3C//DTD HTML 3.2//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//w3c//dtd html 3.2s draft//en"/* "-//W3C//DTD HTML 3.2S Draft//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//w3c//dtd html 4.0frameset//en" /* "-//W3C//DTD HTML4.0 Frameset//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//w3c//dtd html 4.0transitional//en" /* "-//W3C//DTDHTML 4.0 Transitional//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//w3c//dtd html 4.01frameset//en" /* "-//W3C//DTD HTML4.01 Frameset//EN" */, PubIDInfo::eQuirks,PubIDInfo::eAlmostStandards}, {"-//w3c//dtd html 4.01transitional//en" /* "-//W3C//DTDHTML 4.01 Transitional//EN" */, PubIDInfo::eQuirks,PubIDInfo::eAlmostStandards}, {"-//w3c//dtd html experimental19960712//en" /* "-//W3C//DTD HTMLExperimental 19960712//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//w3c//dtd html experimental970421//en" /* "-//W3C//DTD HTMLExperimental 970421//EN" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, {"-//w3c//dtd w3 html//en" /* "-//W3C//DTD W3 HTML//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//w3c//dtd xhtml 1.0 frameset//en"/* "-//W3C//DTD XHTML 1.0 Frameset//EN" */,PubIDInfo::eAlmostStandards, PubIDInfo::eAlmostStandards}, {"-//w3c//dtd xhtml 1.0transitional//en" /* "-//W3C//DTDXHTML 1.0 Transitional//EN" */, PubIDInfo::eAlmostStandards,PubIDInfo::eAlmostStandards}, {"-//w3o//dtd w3 html 3.0//en"/* "-//W3O//DTD W3 HTML 3.0//EN" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//w3o//dtd w3 html 3.0//en//"/* "-//W3O//DTD W3 HTML 3.0//EN//" */,PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//w3o//dtd w3 html strict3.0//en//" /* "-//W3O//DTD W3 HTMLStrict 3.0//EN//" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//webtechs//dtd mozilla html2.0//en" /* "-//WebTechs//DTDMozilla HTML 2.0//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-//webtechs//dtd mozillahtml//en" /* "-//WebTechs//DTDMozilla HTML//EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"-/w3c/dtd html 4.0transitional/en" /* "-/W3C/DTD HTML4.0 Transitional/EN" */, PubIDInfo::eQuirks, PubIDInfo::eQuirks}, {"html" /* "HTML" */, PubIDInfo::eQuirks,PubIDInfo::eQuirks}, }; //可以看到,其中大部分内容都是使用eQuirks模式。不同的模式,取决了解析器怎样对文档进行解析。比如XHTML 1.0提供了三种DTD类型可供选择:过渡型,严格型,框架型。有一些如<br>之类的标签,在过渡型的DTD里可以进行使用,但在严格型的DTD里是不允许使用的。//刚才那段代码之后还有一段调试代码,主要用来验证public ID的正确性的,暂不解释。 //下面,我们就来看看浏览器如何决定HTML解析模式的代码: static void DetermineHTMLParseMode(const nsString& aBuffer, nsDTDMode&aParseMode, eParserDocType&aDocType) { #ifdefDEBUG //调试语句 VerifyPublicIDs(); #endif PRInt32 resultFlags; //结果标示位 nsAutoString publicIDUCS2, sysIDUCS2; //两个字符串用来存放publicID和systemID if (ParseDocTypeDecl(aBuffer, &resultFlags,publicIDUCS2, sysIDUCS2)) { //调用前面的ParseDocTypeDecl方法,对aBuffer进行解析,并返回在resultFlags,publicIDUCS2,以及sysIDUCS2等三个变量中 if (!(resultFlags &PARSE_DTD_HAVE_DOCTYPE)) { //如果没有DOCTYPE // no DOCTYPE aParseMode = eDTDMode_quirks; //默认的DTD就是使用quirks模式 aDocType = eHTML_Quirks; //默认HTML doc type也使用quirks模式 } else if((resultFlags & PARSE_DTD_HAVE_INTERNAL_SUBSET) || !(resultFlags &PARSE_DTD_HAVE_PUBLIC_ID)) { //其他情况下,判断PARSE_DTD_HAVE_INTERNAL_SUBSET和PARSE_DTD_HAVE_PUBLIC_ID这两个标示位是否存在,如果存在,说明要么有internal subset或者是没有public ID,如果是这两种情况,那么都要使用full_standards模式 // A doctype with an internal subset is alwaysfull_standards. // A doctype without a public ID is alwaysfull_standards. aDocType = eHTML_Strict; //doctype声明为eHTML_Strict模式 aParseMode = eDTDMode_full_standards;//DTD的解析模式设置为full_standards模式 // Special hack for IBM's custom DOCTYPE. //这里还专门为IBM的某个DOCTYPE设置了一个单独的判断,来确定其解析模式 if (!(resultFlags &PARSE_DTD_HAVE_INTERNAL_SUBSET) && sysIDUCS2 == NS_LITERAL_STRING( "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd")){ //全部使用quirks模式 aParseMode = eDTDMode_quirks; aDocType = eHTML_Quirks; } } else { // We have to check our list of public IDs tosee what to do. // Yes, we want UCS2 to ASCII lossyconversion. //对于其他情况下的Public DI,我们就需要去刚才那个列表中去寻找我们的publicID //首先需要将其从UCS2转换为ASCII编码 nsCAutoString publicID; publicID.AssignWithConversion(publicIDUCS2); //进行转换 // See comment above definition of kPublicIDsabout case // sensitivity. ToLowerCase(publicID); //因为我们的列表中都是小写的,所以我们需要将其转换为小写字符,之后我们遍历整个列表去寻找对应的public ID值是否在列表中 // Binary search to see if we can find thecorrect public ID // These must be signed since maximum can gobelow zero and we'll // crash if it's unsigned. PRInt32 minimum = 0; PRInt32 maximum = ELEMENTS_OF(kPublicIDs) - 1; PRInt32 index; for (;;) { //循环直到主动退出 index = (minimum + maximum) / 2; //折半查找,提高效率 PRInt32 comparison = nsCRT::strcmp(publicID.get(),kPublicIDs[index].name); if (comparison == 0) break; if (comparison < 0) maximum = index - 1; else minimum = index + 1; if (maximum < minimum) { //如果到了这里,说明我们的列表中没有相应的publicID,我们需要退出 // The DOCTYPE is not in our list, so it mustbe full_standards. //对于这种没有定义过的DOCTYPE,我们统一采用full_standards的模式对其进行解析 aParseMode = eDTDMode_full_standards; aDocType = eHTML_Strict; return; } } switch ((resultFlags &PARSE_DTD_HAVE_SYSTEM_ID) //根据是否有SYSTEM ID选择刚才的两种eMode之一 ?kPublicIDs[index].mode_if_sysid :kPublicIDs[index].mode_if_no_sysid) { //下面就是根据刚才列表中的值,对相应的aParseMode和aDocType进行赋值了 case PubIDInfo::eQuirks: aParseMode = eDTDMode_quirks; aDocType = eHTML_Quirks; break; case PubIDInfo::eAlmostStandards: aParseMode = eDTDMode_almost_standards; aDocType = eHTML_Strict; break; case PubIDInfo::eFullStandards: aParseMode = eDTDMode_full_standards; aDocType = eHTML_Strict; break; default: NS_NOTREACHED("no other cases!"); //不可能出现的情况 } //在此之后,还有一小部分代码,主要是为了保险起见,所有那些无法识别,或者没有DOCTYPE的情况,都一律将模式设置为quirks。 } } else { //其他情况,一律设置为quirks解析模式 // badly formed DOCTYPE -> quirks aParseMode = eDTDMode_quirks; aDocType = eHTML_Quirks; } } //下面这个方法也是确定解析模式的,但是不一定是针对HTML的解析。是否针对HTML进行解析,主要是通过对aMimeType变量的判断来确定的。 static void DetermineParseMode(const nsString& aBuffer, nsDTDMode&aParseMode, eParserDocType&aDocType, const nsACString& aMimeType) { if (aMimeType.EqualsLiteral(kHTMLTextContentType)) { //如果是HTML模式 DetermineHTMLParseMode(aBuffer,aParseMode, aDocType); //则使用前面的DetermineHTMLParseMode方法对其进行解析 //其他情况下如果是以下这几种模式的内容,我们都将其认为是PlainText来进行解析,并且解析模式设置为quirks模式 } else if(aMimeType.EqualsLiteral(kPlainTextContentType) || aMimeType.EqualsLiteral(kTextCSSContentType)|| aMimeType.EqualsLiteral(kApplicationJSContentType) || aMimeType.EqualsLiteral(kApplicationXJSContentType) || aMimeType.EqualsLiteral(kTextECMAScriptContentType) || aMimeType.EqualsLiteral(kApplicationECMAScriptContentType) || aMimeType.EqualsLiteral(kTextJSContentType)) { aDocType = ePlainText; aParseMode = eDTDMode_quirks; } else { //Some form of XML //如果需要解析的内容不是以上任何一种内容,那么我们将其认为是普通XML格式的文档来进行解析 aDocType = eXML; //设置文档类型为aDocType aParseMode = eDTDMode_full_standards; //采用full_standards解析模式 } } //下面这个方法,就是为该parserContext确定合适的DTD或者其他解析器,以便对其进行相应的解析。整个代码很简单,主要就分了两种情况,(如果算上viewSource的话就是三种),分别使用三种不同的解析方式即可。 staticnsIDTD* FindSuitableDTD(CParserContext&aParserContext) { // We always find a DTD. //我们总会找到一个DTD,首先设置该Context的自动检测状态 aParserContext.mAutoDetectStatus = ePrimaryDetect; //如果定义了支持观看源代码 #ifdefMOZ_VIEW_SOURCE // Quick check for view source. if (aParserContext.mParserCommand ==eViewSource) { //设定为观看源代码模式 return new CViewSourceHTML(); //返回一个源代码查看器 } #endif // Now see if we're parsing HTML (which, as far as we'reconcerned, simply // means "not XML"). //判断我们是不是正在HTML,目前来说,只要我们认定不是在解析XML,那么肯定是在解析HTML if (aParserContext.mDocType != eXML) { //判断是否在解析XML return newCNavDTD(); //返回一个DTD对象,用来解析HTML } // If we're here, then we'd better be parsing XML. //其他情况下,就是解析XML,首先判断是否是XML,如果不是则报错 NS_ASSERTION(aParserContext.mDocType == eXML, "Whatare you trying to send me, here?"); return newnsExpatDriver(); //返回一个nsExpatDriver对象,用来解析XML } //还记得我们前面有个Parser的ContinueEvent吗?即解析结束后要触发的事件,下面这个方法就是取消这个等待触发的事件。 NS_IMETHODIMP nsParser::CancelParsingEvents() //撤销解析事件 { if (mFlags &NS_PARSER_FLAG_PENDING_CONTINUE_EVENT) { //判断是否有等待触发的事件 NS_ASSERTION(mContinueEvent, "mContinueEventis null"); //确保一下 // Revoke the pending continue parsing event //设置当前的等待触发时间为空 mContinueEvent= nsnull; //并且取消当前拥有等待事件的标示位 mFlags &= ~NS_PARSER_FLAG_PENDING_CONTINUE_EVENT; } return NS_OK; //返回成功 } //下面的代码#Define了一段代码结构,这段代码的作用比较抽象,作者给出了一大段注释来解释这段代码的作用,现在我们来看看: //这段代码主要是为了按照顺序评估表达式EXPR1和表达式EXPR2各一次。如果EXPR2表达式为假(或失败),那么将EXPR2的结果存放到RV中,否则就将EXPR1的结果存放到RV中去(EXPR1的结果即可能是成果也可能是失败)。 //为了理解这段代码结构的动机,考虑如下可能的两个方法: * nsresultnsSomething::DoThatThing(nsIWhatever* obj) { *nsresult rv = NS_OK; *... *return obj->DoThatThing(); * NS_ENSURE_SUCCESS(rv,rv); *... *return rv; * } * * voidnsCaller::MakeThingsHappen() { *return mSomething->DoThatThing(mWhatever); * } //也许我们因为某种原因,希望把调用mWatever->DoThatThing()的责任从nsSomething::DoThatThing()移交给nsCaller::MakeThingsHappen()。我们可能需要像如下这样重写这段代码: * nsresult nsSomething::DoThatThing() { *nsresult rv = NS_OK; *... *... *return rv; * } * * voidnsCaller::MakeThingsHappen() { *nsresult rv; *PREFER_LATTER_ERROR_CODE(mSomething->DoThatThing(), *mWhatever->DoThatThing(), * rv); *return rv; * } //这其中可能的原因有:nsCaller不希望让mSomething访问mWhatever,nsCaller希望保证mWhatever->DoSomething()不管nsSomething::DoThatThing()的结果如何都会被调用。 #definePREFER_LATTER_ERROR_CODE(EXPR1, EXPR2, RV) { \ nsresult RV##__temp = EXPR1;\ RV = EXPR2;\ if (NS_FAILED(RV)) {\ RV = RV##__temp;\ }\ } //以上这个方法读者可以仔细去理解和思考一下,其思路有值得借鉴的地方。下面我们来看看解析结束,进行Content Model建立的时候需要调用的方法WillBuildModel()。 nsresult nsParser::WillBuildModel(nsString&aFilename) //即将建立Content Model { if (!mParserContext) //首先为了保险,判断一下当前Parser的Context是否存在 return kInvalidParserContext; //如果不存在则返回错误的标示码 if (eUnknownDetect !=mParserContext->mAutoDetectStatus) //前面的寻找DTD的代码中提到过这个mParserContext->mAutoDetectStatus,这里判断其是否为未知的检测类型,如果不等于则说明正确 return NS_OK; //返回正确值 //否则说明我们目前的parserContext中还没有确定DTD类型 if (eDTDMode_unknown == mParserContext->mDTDMode|| //如果当前的DTDMode检测状态为eDTDMode_unknown,即未知类型。或者为autodetect,即自动检测。 eDTDMode_autodetect == mParserContext->mDTDMode) { PRUnichar buf[1025]; //设置一个1025字节的数组 nsFixedString theBuffer(buf, 1024, 0);//设置一个定长字符串,截取buf的前1024字节,并且需要注意的是buf的改变可能会影响theBuffer的值 // Grab 1024 characters, starting at the firstnon-whitespace // character, to look for the doctype in. //下面这段代码主要是调用Peek从待解析的字符流中的第一个非空白字符处开始,获取1024个字符,并且放到theBuffer中 mParserContext->mScanner->Peek(theBuffer,1024, mParserContext->mScanner->FirstNonWhitespacePosition()); //之后我们就在这个theBuffer中去寻找DOCTYPE以确定解析模式即可 //思考:DOCTYPE一定位于前1024个字节中吗? DetermineParseMode(theBuffer, mParserContext->mDTDMode, mParserContext->mDocType, mParserContext->mMimeType); } //判断mDTD是否存在,以及mParserContext是否是根context NS_ASSERTION(!mDTD|| !mParserContext->mPrevContext, "ClobberingDTD for non-root parser context!"); //为该context寻找一个合适的DTD mDTD = FindSuitableDTD(*mParserContext); //确保DTD建立成功 NS_ENSURE_TRUE(mDTD, NS_ERROR_OUT_OF_MEMORY); //申请一个新的分词器指针,并指向当前的ParserContext的分词器 nsITokenizer* tokenizer; nsresult rv = mParserContext->GetTokenizer(mDTD, mSink, tokenizer); //确保申请成功 NS_ENSURE_SUCCESS(rv, rv); //调用mDTD对象的WillBuildModel进行建立content model的准备,具体的我们分析DTD对象之时再解释 rv = mDTD->WillBuildModel(*mParserContext, tokenizer, mSink); //同样还需要调用mSink的WillBuildModel进行建立Content Model的准备 nsresult sinkResult = mSink->WillBuildModel(mDTD->GetMode()); // nsIDTD::WillBuildModel used to be responsible forcalling // nsIContentSink::WillBuildModel, but that obligationisn't expressible // in the nsIDTD interface itself, so it's sounder andsimpler to give that // responsibility back to the parser. The former behaviorof the DTD was to // NS_ENSURE_SUCCESS the sink WillBuildModel call, so ifthe sink returns // failure we should use sinkResult instead of rv, topreserve the old error // handling behavior of the DTD: //以前nsIContentSink::WillBuildModel原来是在nsIDTD::WillBuildModel中进行调用的,后来觉得这样做不太合适,于是就把它放回到Parser中进行调用了。原来的DTD主要是要对sink的WillBuildModel调用进行结果判定,如果sink返回了错误值,我们应当使用sinkResult而不是rv,这主要是为了保持过去的DTD对象的错误处理行为。 //返回处理结果 return NS_FAILED(sinkResult) ? sinkResult : rv; } //上面这个方法,是在建立ContentModel之前进行的,下面这个方法DidBuildModel则是在建立完Content Model之后进行的。 /** * This gets called when the parser is donewith its input. * Note that the parser may have been calledrecursively, so we * have to check for a prev. context beforeclosing out the DTD/sink. */ nsresult nsParser::DidBuildModel(nsresultanErrorCode) { nsresult result = anErrorCode; if (IsComplete()) { //如果当前的解析完成的话 if (mParserContext &&!mParserContext->mPrevContext) { // Let sink know if we're about to end loadbecause we've been terminated. // In that case we don't want it to rundeferred scripts. //在我们解析结束的时候,需要告诉Content sink知道我们马上就要结束读取了,让sink不要再去响应一些延迟的脚本处理 //设置terminated位 PRBool terminated = mInternalState == NS_ERROR_HTMLPARSER_STOPPARSING; if (mDTD&& mSink) { //如果DTD对象和Sink对象都存在的话,那么还需要分别去调用这两个对象的DidBuildModel() nsresult dtdResult =mDTD->DidBuildModel(anErrorCode), sinkResult =mSink->DidBuildModel(terminated); // nsIDTD::DidBuildModel used to beresponsible for calling // nsIContentSink::DidBuildModel, but thatobligation isn't expressible // in the nsIDTD interface itself, so it'ssounder and simpler to give // that responsibility back to the parser. Theformer behavior of the // DTDwas to NS_ENSURE_SUCCESS the sink DidBuildModel call, so if the // sink returns failure we should usesinkResult instead of dtdResult, // to preserve the old error handling behaviorof the DTD: result = NS_FAILED(sinkResult) ? sinkResult : dtdResult; } //Ref. to bug 61462. //其实Firefox有很多bug,细心点就能发现 mParserContext->mRequest = 0; //设置ParseContext的mRequest为空 if (mSpeculativeScriptThread) { //如果有解析线程 mSpeculativeScriptThread->Terminate(); //调用解析线程的销毁方法 mSpeculativeScriptThread = nsnull; //将解析线程的指针指向空 } } } return result; } //下面我们来看Speculative解析线程的主要运行方法parse()。 void nsParser::SpeculativelyParse() { if (mParserContext->mParserCommand == eViewNormal&& //如果当前的命令为ViewnNormal且当前的MimeType模式(前面提到过,区分XML解析和HTML解析)为HTML !mParserContext->mMimeType.EqualsLiteral("text/html")){ return; } if (!mSpeculativeScriptThread) { //如果当前解析线程不存在 mSpeculativeScriptThread = newnsSpeculativeScriptThread(); //创建一个新的 if (!mSpeculativeScriptThread) { //如果创建失败 return; //则返回 } } nsresult rv = mSpeculativeScriptThread->StartParsing(this); //调用该线程的StartParsing()方法开始解析 if (NS_FAILED(rv)) { //如果解析结果异常 mSpeculativeScriptThread = nsnull; //则删除该解析线程(这样可以容错) } } //前面我们提到过,ParseContext实际上是以一个栈式的链表进行存储的,他们之间通过一个mPrevContext的指针类型数据成员变量进行关联,下面这个方法则是,将一个新的ParseContext压入栈中。 /** * This method adds a new parser context to thelist, * pushing the current one to the nextposition. * * @paramptr to new context */ void nsParser::PushContext(CParserContext&aContext) { //首先进行判断,需要对压入的Context进行判断,如果该Context的mPrevContext是否是等于mParserContext,如果不是则说明插入操作的前提不成立,不能进行 NS_ASSERTION(aContext.mPrevContext == mParserContext, "Tryingto push a context whose previous context differs from " "thecurrent parser context."); mParserContext = &aContext; //设置当前的mParserContext,指向新Context } /** * This method pops the topmost context off thestack, * returning it to the user. The nextcontext (if any) * becomes the current context. * @update gess7/22/98 * @returnprev. context */ //下面这个操作和上面那个操作相反,主要是将当前栈顶的Context出栈,并且将下一个Context设置为当前的Context(即栈顶Context) CParserContext* nsParser::PopContext() { CParserContext* oldContext = mParserContext; //设置一个指针,指向当前Context if (oldContext) { //如果当前Context存在 mParserContext = oldContext->mPrevContext; //设置该指针指向当前Context的下一个Context if (mParserContext) { //如果下一个Context也存在 // If the old context was blocked, propagatethe blocked state // back to the new one. Also, propagate thestream listener state // but don't override onStop state toguarantee the call to DidBuildModel(). //这里需要注意的是,如果原始的(即出栈的)Context被阻塞了,需要将该阻塞状态传递回新的(即当前栈顶的)Context。同时,也要传递流监听器的状态,但是如果新context的状态是onStop值则不要修改,以便后续context能够运行DidBuildModel()方法。 if(mParserContext->mStreamListenerState != eOnStop) { //如果当前ParserContext的流监听器状态不等于eOnStop //修改其mStreamListnerState的值为出栈的Context的值 mParserContext->mStreamListenerState =oldContext->mStreamListenerState; } // Update the current context's tokenizer toany information gleaned // while parsing document.write() calls (suchas "a plaintext tag was // found") //更新当前context的分词器,通知其所有在处理document.write()调用时收集到的信息(如“找到plaintext标签”事件等) if (mParserContext->mTokenizer) { //如果当前Context的分词器存在 mParserContext->mTokenizer->CopyState(oldContext->mTokenizer); //同样拷贝其内容 } } } return oldContext; //返回出栈的context } /** * Callthis when you want control whether or not the parser will parse * andtokenize input (TRUE), or whether it just caches input to be *parsed later (FALSE). * *@param aState determines whetherwe parse/tokenize or just cache. *@return current state */ //调用这个方法,这个方法其实就是将参数aBuffer的内容存放起来,等待将来进行解析 void nsParser::SetUnusedInput(nsString&aBuffer) { mUnusedInput = aBuffer; } //这个方法很简单,就是获取位于栈底(即链表结尾,根节点等)的Context的Key值,很简单,就不多解释了 NS_IMETHODIMP_(void*) nsParser::GetRootContextKey() { CParserContext* pc = mParserContext; if (!pc) { return nsnull; } while (pc->mPrevContext) { //一直往前找,直到根节点 pc = pc->mPrevContext; } return pc->mKey; //返回该节点的Key } //下面的方法是nsParser::Terminate(),从字面的意思能看出,肯定是停止Parser用的方法。 /** * Callthis when you want to *force* the parser to terminate the *parsing process altogether. This is binary -- so once you terminate * youcan't resume without restarting altogether. */ //当你需要强制销毁全部解析处理的过程的时候,可以调用这个方法。这个方法是强制型的—因此一旦你进行了销毁就无法再继续进行解析了,除非重新启动解析器 NS_IMETHODIMP nsParser::Terminate(void) { // We should only call DidBuildModel once, so don't doanything if this is // the second time that Terminate has been called. //我们应当仅调用DidBuildModel一次,因此注意如果这是第二次被Terminate的时候,不要做任何事情 if (mInternalState == NS_ERROR_HTMLPARSER_STOPPARSING){ //如果当前状态为异常而中止解析 return NS_OK; //直接返回NS_OK } nsresult result = NS_OK; //设置result为NS_OK // XXX - [ until we figure out a way to break parser-sinkcircularity ] // Hack - Hold a reference until we are completely done... nsCOMPtr<nsIParser> kungFuDeathGrip(this); //这个是指向自己的一个指针,用来销毁自己,在我们完全运行完毕之前,需要一直保持这个指针 mInternalState = result = NS_ERROR_HTMLPARSER_STOPPARSING; // CancelParsingEvents must be called to avoid leaking thensParser object // @see bug 108049 // If NS_PARSER_FLAG_PENDING_CONTINUE_EVENT is set thenCancelParsingEvents // will reset it so DidBuildModel will call DidBuildModelon the DTD. Note: // The IsComplete() call inside of DidBuildModel looks atthe pendingContinueEvents flag. //必须调用一下CancelParsingEvents()方法,以避免漏掉nsParser对象 //这是过去mozilla的一个bug //如果NS_PARSER_FLAG_PENDING_CONTINUE_EVENT标示位被设置了,那么CancelParsingEvents方法将会重设该值,这样DidBuildModel方法就会调用DTD对象中的DidBuildModel方法。注意:在DidBuildModel方法中的IsComplete()方法会检查pendingContinueEvents标示位的值。 CancelParsingEvents(); if (mSpeculativeScriptThread) { //如果有次级解析线程 mSpeculativeScriptThread->Terminate(); //那么直接调用其Terminate方法 mSpeculativeScriptThread = nsnull; //设置次级解析线程指针为空 } // If we got interrupted in the middle of a document.write,then we might // have more than one parser context on our parsercontextstack. This has // the effect of making DidBuildModel a no-op, meaning thatwe never call // our sink's DidBuildModel and break the reference cycle,causing a leak. // Since we're getting terminated, we manually clean up ourcontext stack. //如果我们在一个document.write的操作过程中被打断了,那么在我们的parsercontext栈上可能会有多于一个的parser context。这时的销毁会导致DidBuildModel操作无法进行,也就是说我们永远不会调用我们sink的DidBuildModel,这会破坏整个调用循环,导致遗漏一些数据。 //手动循环删除整个context栈中的所有元素 while (mParserContext &&mParserContext->mPrevContext) { CParserContext *prev = mParserContext->mPrevContext; delete mParserContext; mParserContext = prev; } //如果当前parser有DTD对象,还需要删除DTD对象 if (mDTD) { mDTD->Terminate(); //调用DTD对象的Terminate() DidBuildModel(result); //这里需要注意,还需要手动调用DidBuildModel() } else if(mSink) { //其他情况下,说明我们没有Parser context或者DTD对象(也就是说我们在获取了任何数据之前就被销毁了)。手动去打断和sink之间的连接。 // We have no parser context or no DTD yet (sowe got terminated before we // got any data).Manually break the reference cycle with the sink. //注意,需要手动调用mSink的DidBuildModel result= mSink->DidBuildModel(PR_TRUE); //确保成功 NS_ENSURE_SUCCESS(result, result); } return NS_OK; //返回成功值 } //从上面这段代码中可以看出,其实Mozilla解析的过程是一个连锁式的处理,因此一旦在某个环节的处理进行了改动,还需要考虑对其他环节的一些修改和调用。下面这个方法,主要是用来继续处于中断状态的解析器的。 NS_IMETHODIMP nsParser::ContinueInterruptedParsing() { // If there are scripts executing, then the content sink isjumping the gun // (probably due to a synchronous XMLHttpRequest) and willre-enable us // later, see bug 460706. // 如果有脚本正在执行,那么content sink可能提前开始了一些操作(很可能是由于一个同步的XMLHttpRequest)并且会在之后重新启动我们。 if (IsScriptExecuting()) { //判断是否有脚本正在执行 return NS_OK; //不做任何操作,直接返回 } // If the stream has already finished, there's a goodchance // that we might start closing things down when the parser // is reenabled. To make sure that we're not deleted across // the reenabling process, hold a reference to ourselves. //如果stream的传输已经结束了,那么有很大的可能在parser重新开始的时候,我们会开始关闭一些东西。为了确保我们没有在重新开始的过程中被删除掉,我们保留一个指向自己的句柄。 nsresult result=NS_OK; nsCOMPtr<nsIParser> kungFuDeathGrip(this); //指向自己的句柄 #ifdefDEBUG //debug信息 if (!(mFlags &NS_PARSER_FLAG_PARSER_ENABLED)) { NS_WARNING("Don't callContinueInterruptedParsing on a blocked parser."); } #endif if (mSpeculativeScriptThread) { //如果有次级解析线程 mSpeculativeScriptThread->StopParsing(PR_FALSE); //则让其停止解析,并且通知其这不是由于doucment.write()导致的 } //通过mParserContext的mStreamListenerState位的值来判断是否是FinalChunk,并设置相应标示位 PRBool isFinalChunk = mParserContext && mParserContext->mStreamListenerState == eOnStop; //如果有content sink if (mSink) { mSink->WillParse(); //调用content sink的willParser(),通知其即将解析 } //调用resumeParser继续进行解析 result = ResumeParse(PR_TRUE, isFinalChunk); //Ref. bug 57999 if (result != NS_OK) { //如果当前的result不为NS_OK result=mInternalState; //设置result为当前的内部状态 } return result; //并将result返回 } //下面这个方法是用来暂停解析过程的。 /** * Stopsparsing temporarily. That's it will prevent the *parser from building up content model. */ //暂停解析过程。这会通过设置相应的标示位,让该解析器暂停建立一个新的content model等。 NS_IMETHODIMP_(void) nsParser::BlockParser() { mFlags &= ~NS_PARSER_FLAG_PARSER_ENABLED; //取消Paser enabled标示位 //记录停止的时间 MOZ_TIMER_DEBUGLOG(("Stop: Parse Time:nsParser::BlockParser(), this=%p\n", this)); //停止解析时间计时器 MOZ_TIMER_STOP(mParseTime); } //下面这个方法是和上面这个方法对应的方法,即让解析器重新解锁,注意只是重新解锁,但是并不是让parser继续解析。 /** * Openup the parser for tokenization, building up content * model..etc. However, this method does notresume parsing *automatically. It's the callers' responsibility to restart * theparsing engine. */ //解锁parser,以便让其进行分词操作,建立content model等操作。然而这个操作不会重启parsing过程。该重启解析引擎操作是调用者所应当进行的。 NS_IMETHODIMP_(void) nsParser::UnblockParser() { if (!(mFlags & NS_PARSER_FLAG_PARSER_ENABLED)) { //首先判断当前parser是否被阻塞 mFlags|= NS_PARSER_FLAG_PARSER_ENABLED; //重新设置相应的阻塞位 //记录开始的时间 MOZ_TIMER_DEBUGLOG(("Start: Parse Time: nsParser::UnblockParser(),this=%p\n", this)); //重新开始解析计时 MOZ_TIMER_START(mParseTime); } else { NS_WARNING("Trying to unblock anunblocked parser."); } } //下面这个方法是判断parser是否正在启用isEnable(),以及是否完成的isComplete方法,都是通过mFlag的相应标示位进行判断。 /** * Call this to query whether the parser isenabled or not. */ //判断Parser是否是Enabled的 NS_IMETHODIMP_(PRBool) nsParser::IsParserEnabled() { return (mFlags & NS_PARSER_FLAG_PARSER_ENABLED)!= 0; } /** * Call this to query whether the parser thinksit's done with parsing. */ //判断Parser是否已经完成 NS_IMETHODIMP_(PRBool) nsParser::IsComplete() { return !(mFlags &NS_PARSER_FLAG_PENDING_CONTINUE_EVENT); } //接下来的方法,就是我们前面提到过的HandleParserContinueEvent()方法,主要是用来处理解析之后的事件的方法。 voidnsParser::HandleParserContinueEvent(nsParserContinueEvent *ev) { // Ignore any revoked continue events... //首先判断continueEvent是否等于目前的mContinueEvent if (mContinueEvent != ev) //如果不等 return; //则忽略该请求,直接返回 mFlags &= ~NS_PARSER_FLAG_PENDING_CONTINUE_EVENT; //取消当前有挂起事件的标示位 mContinueEvent = nsnull; //设置当前的ContinueEvent事件为空 NS_ASSERTION(!IsScriptExecuting(), "Interruptedin the middle of a script?"); //判断是否是有脚本正在执行,如果有则报错 ContinueInterruptedParsing(); //重启被中断的解析过程 } //下面的DataAdded()方法,是用来让网络接收模块的相应函数进行调用的,主要作用就是通知Parser有了新的数据到达,让其准备对该数据进行解析。 nsresult nsParser::DataAdded(const nsSubstring& aData, nsIRequest *aRequest) { //首先判断sParserDataListeners是否存在 NS_ASSERTION(sParserDataListeners, "Don'tcall this with no parser data listeners!"); //如果没有mSink或者aRequest为空 if (!mSink || !aRequest) { return NS_OK; //直接返回 } nsISupports *ctx = mSink->GetTarget(); //调用mSink的GetTarget() PRInt32 count = sParserDataListeners->Count(); //获取DataListeners的数量 nsresult rv = NS_OK; PRBool canceled = PR_FALSE; while (count--) { //这部分需要看完网络模块才能回来看,主要目的就是调用组件方法,对sParserDataListeners中的每一个数组元素调用OnUnicharDataAvailable方法 rv |= sParserDataListeners->ObjectAt(count)-> OnUnicharDataAvailable(aRequest, ctx, aData); if (NS_FAILED(rv) && !canceled){ aRequest->Cancel(rv); canceled = PR_TRUE; } } return rv; } //下面这两个方法,主要是为了判断和设置nsParser能否被打断 PRBool nsParser::CanInterrupt() //判断nsParser能否被打断 { return (mFlags & NS_PARSER_FLAG_CAN_INTERRUPT) !=0; //获取相应的标示位 } void nsParser::SetCanInterrupt(PRBoolaCanInterrupt) //设置nsParser能否被打断 { if (aCanInterrupt) { //如果设为能被打断 mFlags |= NS_PARSER_FLAG_CAN_INTERRUPT; //则设置相应标示位为能打断 } else { //否则 mFlags &= ~NS_PARSER_FLAG_CAN_INTERRUPT; //设置为不能打断 } } //下面这两个方法很重要,是Parser的主要解析方法Parser,也是可以供其他模块调用的方法,其控制了整个解析操作的步骤,这两个方法的区别在于,一个是通过某个URI进行解析,一个是对一段String类型的HTML流进行解析,下面我们来详细看一下这两个方法。 /** * Thisis the main controlling routine in the parsing process. * Notethat it may get called multiple times for the same scanner, * sincethis is a pushed based system, and all the tokens may * nothave been consumed by the scanner during a given invocation * ofthis method. */ //下面这个方法是解析过程的主要控制流程,注意到它可能会被同一个scanner调用多次,因为它是一个基于压栈操作的系统,并且所有的词条可能没有在scanner对它的一次调用中全部由scanner读取完毕。 NS_IMETHODIMP nsParser::Parse(nsIURI* aURL, //解析操作 nsIRequestObserver* aListener, void*aKey, nsDTDMode aMode) { NS_PRECONDITION(aURL, "Error: Null URLgiven"); //判断aURL是否为空 NS_ASSERTION(!mSpeculativeScriptThread, "Can't reuse a parser like this"); //判断是否有次级解析线程 nsresult result=kBadURL; //首先将结果标示位默认设置为bad URL mObserver = aListener; //获取参数传递来的aListener设置自己的mObserver if (aURL) { //判断aURL不为空的情况下(前面已经判断过了) nsCAutoString spec; //设置字符串变量spec nsresult rv = aURL->GetSpec(spec); //获取该URL对象的地址字符串 if (rv != NS_OK) { //如果获取失败 return rv; //直接返回错误 } NS_ConvertUTF8toUTF16 theName(spec); //转换一下spec的编码 //建立一个新的扫描器scanner,使用前面的参数 nsScanner* theScanner = newnsScanner(theName, PR_FALSE, mCharset, mCharsetSource); //建立一个新的CParserContext CParserContext* pc = newCParserContext(mParserContext, theScanner, aKey, mCommand, aListener); if (pc && theScanner) { //如果这两个对象都建立成功,我们需要对其的一些成员变量进行一些设置 pc->mMultipart = PR_TRUE; pc->mContextType = CParserContext::eCTURL; //设置该context的类型为枚举类型之一,具体的我们放到对context的解析中再进行解析 pc->mDTDMode = aMode; //设置DTD的模式 PushContext(*pc); //将该context压栈 // Here, and only here, hand this parser offto the scanner. We // only want to do that here since the onlyreason the scanner // needs the parser is to call DataAdded() onit, and that's // only ever wanted when parsing from an URI. //仅仅在此,才将这个parser提交给scanner,因为这样做的唯一原因就是希望scanner调用我们的DataAdded()方法,并且这只有在我们从URI进行解析的时候才会进行 theScanner->SetParser(this); //将Scanner的parser设置为自身 result = NS_OK; }else { //设置result的mInternalState状态为NS_ERROR_HTMLPARSER_BADCONTEXT result = mInternalState = NS_ERROR_HTMLPARSER_BADCONTEXT; } } return result; } //下面这个Parse方法和上面的区别就在于,刚才那个Parser方法是从URI进行读取的,并且其驱动的根源是通过让Scanner调用本Parser的DataAdded()方法来进行的,而下面这个Parse方法则是直接对一串字符串进行解析。 //理解这点会对我们了解Parser的解析工作流程有很大帮助。 /** * Call this method if all you want to do isparse 1 string full of HTML text. * In particular, this method should be calledby the DOM when it has an HTML * string to feed to the parser in real-time. * * @paramaSourceBuffer contains a string-full of real content * @paramaMimeType tells us what type of content to expect in the given string */ //当你希望对一整段String类型的HTML文本进行解析的时候,可以调用这个方法。一般来讲,这个方法应当被DOM结构体,在其拥有一段HTML字符串希望实时传递给parser的时候进行调用。 NS_IMETHODIMP nsParser::Parse(constnsAString& aSourceBuffer, void*aKey, constnsACString& aMimeType, PRBool aLastCall, nsDTDMode aMode) { nsresult result = NS_OK; //设置结果result为NS_OK // Don't bother if we're never going to parse this. if (mInternalState ==NS_ERROR_HTMLPARSER_STOPPARSING) { //如果当前的内部状态是NS_ERROR_HTMLPARSER_STOPPARSING return result; //直接返回,不做任何解析 } if (!aLastCall && aSourceBuffer.IsEmpty()) { // Nothing is being passed to the parser soreturn // immediately. mUnusedInput will getprocessed when // some data is actually passed in. // But if this is the last call, make sure tofinish up // stuff correctly. //没有任何东西被传递给parser,因此直接返回。mUnusedInput会在有数据传递进来的时候被处理。但是如果这是last call,需要注意将结果正确进行处理。 return result; } if (mSpeculativeScriptThread) { //如果有次级解析线程 mSpeculativeScriptThread->StopParsing(PR_TRUE); //将其停止 } // Hack to pass on to the dtd the caller's desire to // parse a fragment without worrying about containmentrules if (aMode == eDTDMode_fragment) //如果是eDTDMode_frament模式 mCommand= eViewFragment; //设置当前命令为eViewFragment,该类型命令就是把HTML当做普通的XML进行处理,不需要担心相应的HTML语法规则 // Maintain a reference to ourselves so we don't go away // till we're completely done. //设置一个链接到自己本身的链接直到我们全部结束为止,这样我们就不会担心一些其他操作的影响了 nsCOMPtr<nsIParser> kungFuDeathGrip(this); if (aLastCall || !aSourceBuffer.IsEmpty() ||!mUnusedInput.IsEmpty()) { //Note: The following code will always find the parser context associated // with the given key, even if that contexthas been suspended (e.g., for // another document.write call). This doesn'tappear to be exactly what IE // does in the case where this happens, but this makes moresense. //注意:下面的代码将会寻找到一个关联到给定的Key的parser context,即使该context已经被闲置了(比如,为了另一个document.write调用)。这可能看起来和IE所作的不一样,但是这样做从道理上更正确一点。 CParserContext* pc = mParserContext; //设置一个指针pc,指向mParserContext while (pc && pc->mKey !=aKey) { //一直向前寻找,直到找到一个key和给定key相同的parsercontext为止 pc = pc->mPrevContext; } if (!pc) { //如果pc不存在 // Only make a new context if we don't haveone, OR if we do, but has a // different context key. //只有在我们没有一个context的情况下才建立一个新的context,或者是我们有context,但是该context有一个不同的context key //建立一个Scanner nsScanner* theScanner = newnsScanner(mUnusedInput, mCharset, mCharsetSource); //判断该Scanner是否建立成功,否则报没有内存错误 NS_ENSURE_TRUE(theScanner, NS_ERROR_OUT_OF_MEMORY); //设置一个theStatus,用来记录监测到的文档类型,默认为UnknowDectect eAutoDetectResult theStatus = eUnknownDetect; //如果mParserContext存在且mParserContext的mMimeType等于当前的aMimeType if (mParserContext &&mParserContext->mMimeType == aMimeType) { // Ref. Bug 90379 //判断mDTD是否为空,这里是为了修复一个bug NS_ASSERTION(mDTD, "How come the DTD isnull?"); //判断如果mParserContext存在 if (mParserContext) { //设置theStatue为当前ParserContext的检测类型 theStatus = mParserContext->mAutoDetectStatus; // Added this to fix bug 32022. } } //根据以上内容,我们新建一个CParserContext pc = new CParserContext(mParserContext,theScanner, aKey, mCommand, 0, theStatus,aLastCall); //确保pc创建成功 NS_ENSURE_TRUE(pc, NS_ERROR_OUT_OF_MEMORY); //将其压入当前的Context栈中 PushContext(*pc); //设置pc的mMultipart,默认如果是lastcall了后面就不会有多个部分了 pc->mMultipart = !aLastCall; // By default if (pc->mPrevContext) { //如果pc不是唯一的Context pc->mMultipart |= pc->mPrevContext->mMultipart; //那么沿用pc之前的Context的mMultipart作为pc的mMultipart的值 } //下面这段代码是对一个bug的修复,其作用未知,应该是某种特殊情况下的处理 // Start fix bug 40143 if (pc->mMultipart) { pc->mStreamListenerState = eOnDataAvail; if (pc->mScanner) { pc->mScanner->SetIncremental(PR_TRUE); } } else { pc->mStreamListenerState = eOnStop; if (pc->mScanner) { pc->mScanner->SetIncremental(PR_FALSE); } } // end fix for 40143 pc->mContextType=CParserContext::eCTString; //设置ContextType为String pc->SetMimeType(aMimeType); //设置MimeType为传递过来的MimeType //下面需要保存上一个Context的DTD模式 if (pc->mPrevContext && aMode== eDTDMode_autodetect) { // Preserve the DTD mode from the lastcontext, bug 265814. pc->mDTDMode = pc->mPrevContext->mDTDMode; } else { pc->mDTDMode = aMode; //否则设置为传递过来的aMode } //对mUnusedInput进行一下调整 mUnusedInput.Truncate(); //将传递过来的字符串传递给mScanner pc->mScanner->Append(aSourceBuffer); if (!pc->mPrevContext) { //如果pc之前没有Context,也就是说pc是唯一的一个context // Set stream listener state to eOnStop, onthe final context - Fix 68160, // to guarantee DidBuildModel() call - Fix36148 //最后一个context,将stream listener的状态设置为eOnStop,来确保DidBuildModel()的调用 if (aLastCall) { //如果是last call,需要设置两个数据成员 pc->mStreamListenerState = eOnStop; pc->mScanner->SetIncremental(PR_FALSE); } if (pc == mParserContext) { //如果pc是mParserContext // If pc is not mParserContext, then this callto ResumeParse would // do the wrong thing and try to continueparsing using // mParserContext. We need to wait to actuallyresume parsing on pc. //如果pc不是mParserContext,那么这个ResumeParse的调用会进行一些错误操作,它会使用mParserContext继续进行解析。我们需要等到在pc上继续进行解析。 ResumeParse(PR_FALSE,PR_FALSE, PR_FALSE); //调用ResumeParser继续解析,这个方法在后面我们会进行解析 } } } } return result; } //下面这个ParseFragment()方法,主要是进行无语法式的解析,即对一些没有上下文,或者不考虑上下文的HTML标签进行解析,如XML。 NS_IMETHODIMP nsParser::ParseFragment(const nsAString& aSourceBuffer, void* aKey, nsTArray<nsString>& aTagStack, PRBool aXMLMode, const nsACString& aMimeType, nsDTDMode aMode) { nsresult result = NS_OK; //设置result nsAutoString theContext; //设置一个String类型变量 PRUint32 theCount = aTagStack.Length(); //获取aTagStack的长度 PRUint32 theIndex = 0; //设置整型index变量 // Disable observers for fragments //在对fragments进行解析的时候需要禁用obervers mFlags &= ~NS_PARSER_FLAG_OBSERVERS_ENABLED; //判断是否拥有mSpeculativeScriptThread NS_ASSERTION(!mSpeculativeScriptThread,"Can't reuse a parser like this"); //首先根据aTagStack的内容,将这些Tag转换为一个字符串的类型,存放到theContext中 for (theIndex = 0; theIndex < theCount;theIndex++) { theContext.AppendLiteral("<"); theContext.Append(aTagStack[theCount - theIndex - 1]); theContext.AppendLiteral(">"); } if (theCount == 0) { //如果tagStack的长度为0 // Ensure that the buffer is not empty.Because none of the DTDs care // about leading whitespace, this doesn't change the result. //我们需要确保该buffer不为空。因为没有DTD会关心开头的空白字符,所以这不会造成什么问题 theContext.AssignLiteral(" "); } // First, parse the context to build up the DTD's tagstack. Note that we // pass PR_FALSE for the aLastCall parameter. //首先,解析上下文来建立DTD的Tag栈。注意到我们传递PR_FALSE作为aLastCall参数,注意这里我们调用了前面对一段String类型字符串进行解析的Parse方法,需要理解的是我们把theContext的地址作为aKey赋值过去,保证了Context的不重复,并且aLastcall为PR_FALSE result = Parse(theContext, (void*)&theContext,aMimeType, PR_FALSE, aMode); if (NS_FAILED(result)) { //如果解析失败 mFlags |= NS_PARSER_FLAG_OBSERVERS_ENABLED; //设置相应的标示位 return result; //返回错误结果 } if (!mSink) { //如果mSink不存在 // Parse must have failed in the XML case and so the sink waskilled. //说明刚才的Parse操作肯定在XML方式的解析中失败了,因此导致Sink被销毁 NS_ASSERTION(aXMLMode, "Unexpected!"); //判断是否是aXMLMode return NS_ERROR_HTMLPARSER_STOPPARSING; //返回异常 } //设置一个新的指针,指向mSink(这里可能还涉及到组件类型正确性判断的问题) nsCOMPtr<nsIFragmentContentSink> fragSink =do_QueryInterface(mSink); //判断fragSink是否创建成功 NS_ASSERTION(fragSink, "ParseFragmentrequires a fragment content sink"); //如果当前不是XML模式,并且tagStack不为空 if (!aXMLMode && theCount) { // First, we have to flush any tags that don'tbelong in the head if there // was no <body> in the context. // XXX This is extremely ugly. Maybe CNavDTD should haveFlushMisplaced()? //首先,如果context里没有<body>标签,我们需要处理任何不属于head的tags //作者认为这里还需要进行修改 //首先进行判断mParserContext不为空 NS_ASSERTION(mParserContext, "Parsingdidn't create a parser context?"); //设置将mDTD经过两次强制类型转换为CNavDTD类型,并设置一个指针dtd指向它 CNavDTD* dtd = static_cast<CNavDTD*> (static_cast<nsIDTD*>(mDTD)); //判断该dtd不为空 NS_ASSERTION(dtd, "How did we parseanything without a dtd?"); //首先建立一个新的Token,手动将其设置为BODY类型标签 CStartTokenbodyToken(NS_LITERAL_STRING("BODY"),eHTMLTag_body); //继而建立一个bodynode,通过刚才建立的Token将类型设置好 nsCParserNode bodyNode(&bodyToken, 0); //下面这句详细作用未知,需要等带DTD解析完后再写 dtd->OpenContainer(&bodyNode, eHTMLTag_body); // Now parse the flushed out tags. result = BuildModel(); //调用BuildModel将tag全部解析 if (NS_FAILED(result)) { //判断结果是否成功 mFlags |= NS_PARSER_FLAG_OBSERVERS_ENABLED; return result; } // Now that we've flushed all of the tags outof the body, we have to make // sure that there aren't any context tags left in thescanner. //现在我们处理了body中所有的tags,我们需要确保在scanner中没有遗留的context tags NS_ASSERTION(mParserContext->mScanner, "Where'd the scanner go?"); PRUnicharnext; //如果mScanner中有内容 if(NS_SUCCEEDED(mParserContext->mScanner->Peek(next))) { // Uh, oh. This must mean that the contextstack has a special tag on // it, such as <textarea> or<title> that requires its end tag before it // will be consumed. Tell the content sinkthat it will be coming. // Note: For now, we can assume that there isonly one such tag. //运行到这里,说明context栈中有一个特殊的tag,比如<textarea>或者<title>需要它的结尾tag才能够被处理。这里只需告诉context sink它很快回来就行了。目前,我们假设只会有一个这中ta NS_ASSERTION(next == '<', "The tokenizer failed to consume a token"); fragSink->IgnoreFirstContainer(); //调用fragment sink,忽略这个tag } } //调用fragSink的WillBuildContent(),让其进行一些准备工作 fragSink->WillBuildContent(); // Now, parse the actual content. Note that this is thelast call // for HTML content, but for XML, we will want to build andparse // the end tags.However, if tagStack is empty, it's the last call // for XML as well. //现在,解析真正的内容。注意到这对HTML内容来说是last call,但是对XML来说,我们希望建立并解析结束型标签。然而,如果tagStack是空的话,这对于XML来说也是last call if (!aXMLMode || (theCount == 0)) { result = Parse(aSourceBuffer, &theContext, aMimeType, //注意PR_TRUE PR_TRUE, aMode); fragSink->DidBuildContent(); //调用fragSink的DidBuildContent进行善后处理 } else { //其他情况下 // Add an end tag chunk, so expat will readthe whole source buffer, // and not worry about ']]' etc. //添加一个结束型tag块,因此expat会读取整个字符串,并不用担心’]]’之类的 //直接在SourceBuffer后面加一个’</’字符串 result = Parse(aSourceBuffer + NS_LITERAL_STRING("</"), &theContext, aMimeType,PR_FALSE, aMode); fragSink->DidBuildContent(); //调用fragSink的DidBuildContent进行善后处理 if (NS_SUCCEEDED(result)) { //如果result判断为成功 nsAutoString endContext; //设置string字符串endContext for (theIndex = 0; theIndex <theCount; theIndex++) { // we already added an end tag chunk above //我们之前已经添加了一个结束行的tag块 if (theIndex > 0) { //如果theIndex大于0 endContext.AppendLiteral("</"); //在end后面加一个’</’,即有几个tag就要加几个’</’ } nsString& thisTag = aTagStack[theIndex]; //获取当前数组下标的tag // was there an xmlns=? //下面这段代码就是要把tag名复制到endContext中去,这样和’</’凑整为一个整齐的结束型tag PRInt32 endOfTag = thisTag.FindChar(PRUnichar('')); if (endOfTag == -1) { endContext.Append(thisTag); } else { endContext.Append(Substring(thisTag,0,endOfTag)); } //不要忘记了在最后面为结束型tag加上’>’ endContext.AppendLiteral(">"); } //之后就调用Parse进行aLastCall为TRUE的解析 result = Parse(endContext, &theContext, aMimeType, PR_TRUE, aMode); } } //设置相应的标志位 mFlags |= NS_PARSER_FLAG_OBSERVERS_ENABLED; //返回解析结果 return result; } //接下来的这个ResumeParse()方法非常重要,它是真正意义上启动解析的方法。它带有一大段注释,我们先来看看它的注释://这个方法被调用来让parser继续解析它的解析数据流。这个调用会允许解析过程以数据块的方式运行,这也恰好应正了内容是以推送的方式到达的,我们需要按块进行解析。 //Parser被使用的流程中,有一个有趣的改动,让我们为这个处理过程添加了一个额外的处理方法。这个情况发生在当parser在某一个context中被阻塞,并在另一个context中被parse(string)调用之时。这种情况下,这些parserContext是被链接到一起的,这没有问题。 //问题在于parse(string)在运行的时候会认为它自己可以无限制地运行,但是如果这个parser已经被阻塞,那么这个假设就失效了。因此我们需要在这里添加一个机制,能够让parser继续进行处理(弹出或释放)context,直到1)它再次地被阻塞 2)它没有context可以继续运行了。 nsresult nsParser::ResumeParse(PRBoolallowIteration, PRBool aIsFinalChunk, PRBool aCanInterrupt) { nsresult result = NS_OK; if ((mFlags & NS_PARSER_FLAG_PARSER_ENABLED)&& mInternalState != NS_ERROR_HTMLPARSER_STOPPARSING) { MOZ_TIMER_DEBUGLOG(("Start: Parse Time:nsParser::ResumeParse(), this=%p\n", this)); //输出开始时间 MOZ_TIMER_START(mParseTime); //开始解析计时器 //如果没有次级线程,或者次级线程没有在解析 NS_ASSERTION(!mSpeculativeScriptThread ||!mSpeculativeScriptThread->Parsing(), "Badraces happening, expect to crash!"); //调用前面的willbuildmodel方法,但这个filename似乎没什么用? result= WillBuildModel(mParserContext->mScanner->GetFilename()); //如果result判断失败 if (NS_FAILED(result)) { mFlags &= ~NS_PARSER_FLAG_CAN_TOKENIZE; //清除CAN_TOKENIZE位 return result; } if (mDTD) { //做个前提判断,如果mDTD对象存在 mSink->WillResume(); //调用mSink的WillResume()方法 PRBool theIterationIsOk = PR_TRUE; //设置theIterationIsOk为TRUE while (result == NS_OK &&theIterationIsOk) { //当result为OK及theIterationIsOk的时候,无限进行循环 //如果当前存放未解析字符的变量不为空,且mScanner存在的情况下 if (!mUnusedInput.IsEmpty() &&mParserContext->mScanner) { // -- Ref: Bug# 22485 -- // Insert the unused input into the sourcebuffer // as if it was read from the input stream. // Adding UngetReadable() per vidur!! //将上次解析时未使用的字符串插入到source buffer中,把它看作为新到达的字节流 mParserContext->mScanner->UngetReadable(mUnusedInput); //对该字符进行处理 mUnusedInput.Truncate(0); } // Only allow parsing to be interrupted in thesubsequent call to // build model. //只允许解析过程在子过程调用中进行build model的时候才能够被打断 SetCanInterrupt(aCanInterrupt); //根据标示位判断能否分词,如果能则首先进行分词操作,将结果存放到theTokenizerResult中 nsresult theTokenizerResult = (mFlags & NS_PARSER_FLAG_CAN_TOKENIZE) ?Tokenize(aIsFinalChunk) : NS_OK; //调用BuildModel()进行分词后的处理操作,后面会详细介绍这个方法 result = BuildModel(); //如果结果是被打断并且是最后一块数据块了,则运行前面介绍过的PostContinueEvent()方法,为了处理最后到达的一些数据,这是一个循环的链接,请读者仔细看nsParser的说明,并按照调用关系去跟踪理解一下 if (result ==NS_ERROR_HTMLPARSER_INTERRUPTED && aIsFinalChunk) { PostContinueEvent(); } //设置解析器为不能被打断 SetCanInterrupt(PR_FALSE); //设置循环的判断条件theIterationIsOk成真的结果为:分词结果不应为文档结尾,且result不为NS_ERROR_HTMLPARSER_INTERRUPTED theIterationIsOk = theTokenizerResult != kEOF && result !=NS_ERROR_HTMLPARSER_INTERRUPTED; // Make sure not to stop parsing too early.Therefore, before shutting // down the parser, it's important to checkwhether the input buffer // has been scanned to completion(theTokenizerResult should be kEOF). // kEOF -> End of buffer.
// If we're told to block the parser, wedisable all further parsing // (and cache any data coming in) until theparser is re-enabled. //注意确保不要太早地结束解析过程。因此,在关闭parser之前,很重要的一点就是检查输入字节流已经被完整地扫描了一遍(theTokenizerResult应当为kEOF) //如果我们被告知要阻塞parser,我们就需要停止所有接下来的解析(并且用缓冲区存放下后面到来的数据),直到parser重新被启用为止 if (NS_ERROR_HTMLPARSER_BLOCK == result){ //如果result中存放的是NS_ERROR_HTMLPARSER_BLOCK,说明被阻塞 mSink->WillInterrupt(); //调用mSink的WillInterrupt,为中断做准备 if (mFlags &NS_PARSER_FLAG_PARSER_ENABLED) { //如果我们被一个递归的调用所阻塞,不要重新再次进行阻塞 // If wewere blocked by a recursive invocation, don't re-block. BlockParser(); //阻塞Parser(),其实就是通过设置标示位阻止其建立content model等 SpeculativelyParse(); //使用次级线程进行解析 } return NS_OK; //返回 } |
后续:Mozilla FireFox Gecko内核源代码解析(2.nsHTMLTokenizer)
相关推荐
在本项目中,标题"JAVA制作火狐内核浏览器源代码"揭示了主要知识点是使用Java编程语言构建一个基于火狐浏览器内核的定制化浏览器。这个项目可能涉及到对Mozilla Firefox浏览器内核的理解,以及如何利用Java进行...
Java编写的Gecko内核浏览器源码是一种独特的技术实践,它展示了如何利用Java语言来实现浏览器功能,并结合不同的渲染引擎,如火狐的Gecko内核和IE内核,为用户提供浏览网页的能力。这一项目主要涉及到以下几个核心...
1. **获取源代码**:首先,你需要从Mozilla的官方仓库获取Gecko内核的源代码,这通常通过Git等版本控制工具完成。 2. **编译环境准备**:安装必要的编译工具,如Microsoft Visual Studio或MinGW,以及Python、CMake...
1. **性能提升**:Firefox 15.0 在速度方面有了显著提升,页面加载更快,JavaScript执行效率更高,这得益于其内核Gecko的持续优化。同时,对于HTML5的支持也更为完善,使得现代网页应用的运行更加流畅。 2. **静默...
Firefox是由Mozilla基金会维护的开源浏览器,其内核名为Gecko,也由C++编写。Firefox以其对隐私保护的重视和强大的拓展支持而著名。Firefox源代码可以在Mozilla的官方Git仓库中获取,这使得开发者能够研究Firefox的...
**GeckoFX** 是一个开源项目,它提供了一个.NET框架,允许开发者在C#等.NET语言中集成Mozilla的Firefox浏览器引擎,即Gecko内核。这个项目的主要目标是为Windows应用提供一个支持现代Web标准(如HTML5)的浏览器控件...
Firefox是Mozilla公司开发的一款开放源代码网页浏览器,其内核名为Gecko,因此以Gecko为名的驱动程序能够与Firefox进行通信。 标签同样重申了关键信息:Firefox浏览器、GeckoDriver以及64位Linux环境。这些标签有助...
在Selenium 3版本之后,由于Firefox浏览器升级到Gecko内核,原有的Selenium与Firefox的交互方式不再适用,这就引出了GeckoDriver。GeckoDriver,全称Mozilla Gecko Driver,是Mozilla开发的一个接口,用于在Selenium...
火狐浏览器(Firefox)是由Mozilla基金会及其子公司开发的,它与大多数使用Internet Explorer或Chromium内核的浏览器不同,Firefox采用的是自研的Gecko内核。Gecko内核以开放源代码的形式发布,这使得开发者可以自由...
Firefox是最知名的采用Gecko内核的浏览器,此外还有Netscape 6及更高版本、SeaMonkey等。Gecko内核支持多个操作系统,如Windows、Linux、Mac OS X。 3. WebKit内核:源于KDE项目的KHTML和KJS,是一个开放源代码的...
Firefox OS 是由Mozilla主导开发的开源移动操作系统,使用Linux内核和基于Gecko的运行环境。该系统的核心理念是所有用户可接触的应用都应该是基于Web的应用。开发方式完全采用开放源代码,应用程序主要基于HTML5、...
这个SDK使得开发者能够利用Gecko内核的强大功能,创建与Firefox浏览器内核兼容的自定义应用和扩展。 **XULRunner** 是一个运行时环境,它支持基于XUL的应用程序运行。XUL是一种XML语言,设计用于创建跨平台的用户...
1. **开源特性**:Firefox基于Mozilla基金会开发的Gecko内核,遵循开放源代码原则,这意味着用户可以查看和修改其源代码,确保了透明度和安全性。开源也为开发者提供了自由定制和扩展浏览器功能的机会。 2. **安全...
7. **授权方式变化**:自2021年1月3日起,Firefox的源代码授权方式从原来的GPL/LGPL/MPL三重授权转变为兼容GPL的MPL 2.0,这反映了Mozilla对开源社区规则的适应和尊重。 总的来说,Firefox 40.0.2的更新着重在于...
常见的浏览器内核有WebKit(苹果Safari和Chrome早期使用的)、Gecko(Mozilla Firefox的内核)、Blink(Chrome和Opera的内核)以及Trident(微软Internet Explorer的内核)。而网狐内核,通常指的是国内一些基于开源...
1. **Firefox的核心技术**: Firefox基于Gecko渲染引擎,这是一种开放源代码的网页排版引擎,能够解析和显示HTML、XML及其各种衍生格式,如CSS、SVG和JavaScript。这种强大的内核使得Firefox在处理复杂网页和Web应用...
《基于Firefox内核的Windows窗体程序开发:深入解析GeckoFx-Windows-33.0-0.8.zip》 在IT行业中,为实现高效且功能强大的网页浏览功能,开发者常常面临选择合适的浏览器内核问题。传统的WebBrowser控件在某些场景下...
Firefox火狐浏览器是一款深受用户喜爱的开源网络浏览器,由Mozilla公司开发并维护。这款74.0b2-mac版本是专为Mac OS操作系统设计的,提供了强大的浏览体验和高度的用户隐私保护。在了解这个特定版本之前,我们先来...
同时,掌握如何查看源代码和使用开发者工具,如Chrome DevTools或Firefox Developer Tools,是提升开发效率的关键技能。这些工具可以帮助调试CSS、JavaScript,检查网络请求,分析性能,甚至模拟不同设备和浏览器...
总的来说,Firefox火狐浏览器76.0b6-win64版本是一个面向Windows 64位用户的先进浏览器,其稳定性和性能都经过了测试,同时保持了Firefox一贯的开放源代码和用户隐私保护原则。通过下载并安装"Firefox Setup 76.0b6....