关闭

WebKit之文本资源解码

277人阅读 评论(0) 收藏 举报
分类:
本文描述了Webkit文本资源解码时,编码格式的选择问题。这里的文本资源是指HTML/XML、CSS,以及JS文件等。如果没有明确说明,本文提到的“文本解码器”均特指TextResourceDecoder类。
文本资源解码器由TextResourceDecoder类表示。该类主要是对文本资源进行编码检测,以确定最终的编码格式;而进行实际的文本解码是m_codec成员,其类型为TextCodec的。下面是该类的主要属性字段:
ContentType m_contentType;          //文本资源类型:PlainText, HTML, XML, CSS
TextEncoding m_encoding;              //规范化后的编码格式,如UTF8
OwnPtr<TextCodec> m_codec;        //文本解码器,进行实际的文本编解码
EncodingSource m_source;               //编码格式来源
const char* m_hintEncoding;            //线索编码,用于自动检测
bool m_usesEncodingDetector;          //是否使用编码检测器

TextResourceDecoder决定编码格式时,其来源由EncodingSource表示:

  1. enum EncodingSource {  
  2.     DefaultEncoding,                         //默认编码  
  3.     AutoDetectedEncoding,               //自动检测编码  
  4.     EncodingFromXMLHeader,        //来自于XML头部  
  5.     EncodingFromMetaTag,               //来自于Meta标签  
  6.     EncodingFromCSSCharset,          //来自于CSS的@charset  
  7.     EncodingFromHTTPHeader,        //来自于HTTP响应头  
  8.     UserChosenEncoding,                  //用户选择编码  
  9.     EncodingFromParentFrame          //来自于父Frame  
  10. };  


下面分别介绍这些类型。
1  默认编码
创建TextResourceDecoder对象时,其构造函数需要三个参数,分别为文本资源的MIME类型,指定的编码格式,以及是否使用编码检测器。该构造函数根据文本的MIME类型和指定的编码格式设置一个默认的编码(参考:函数defaultEncoding),编码来源设置为DefaultEncoding。默认的编码的选择规则如下:
1.      如果文本资源类型为XML,那么设置默认编码为UTF8
2.      如果传入的编码名规范化后无效,那么设置默认编码为Latin1
3.      将传入的编码作为默认编码
 
下面详细介绍三种文文本类型的文件创建文本解码器时,传入的默认编码参数。
1.1  HTML/XML文件
从网络接收到数据后(参考:DecodedDataDocumentParser::appendBytes),调用函数DocumentWriter::createDecoderIfNeeded()创建文本解码器。
创建TextResourceDecoder对象时,构造函数的参数设置如下:
1.      从HTTP响应头获取MIME类型参数
2.      如果Settings对象不为空,那么从Settings中获取默认的文本编码,以及是否使用编码检测器参数;否则编码名设置为空,是否使用编码检测器的参数使用默认参数(false);
此外,如果Settings对象不为空,那么设置该对象的线索编码(m_hintEncoding)为其父Frame的编码格式。(前提是,当前Frame与其父Frame具有相同的security origin)
1.2  CSS文件
解析HTML页面过程中,请求CSS外部资源时(HTMLLinkElement::process()),会传入编码参数,以便CachedCSSStyleSheet对象创建文本解码器。传入文本解码器构造函数的参数设置如下:
1.      MIME类型参数为:"text/css"
2.      如果link标签的Charset属性不为空,那么传入构造函数的编码名为该属性值;否则传入该文档所在Frame的编码格式(DocumentWriter::encoding)
3.      是否使用编码检测器:使用默认参数(false)
1.3  JS文件
与CSS文件类似,请求JS资源时(ScriptElementData::requestScript),生成CachedScript对象,便会创建文本解码器。传入的文本解码器的参数设置如下:
1.      MIME类型参数为:"application/javascript"
2.      如果Script标签的Charset属性不为空,那么传入构造函数的编码名为该属性值;否则传入该文档所在Frame的编码格式(DocumentWriter::encoding)
3.      是否使用编码检测器:使用默认参数(false)
1.4  小结
创建文本资源解码器时,「默认编码」的设置:
1.      HTML/XML:如果是XML,那么默认编码为UTF8;否则,优先考虑Settings中设置的默认编码,如某些浏览器中设置的GBK
2.      CSS/JS:优先考虑所在标签指定的编码,其次是文档所在Frame的编码格式
3.      在传入编码规范化后无效的情况下,设置默认编码为Latin1
4.      编码来源为:DefaultEncoding
PS:文档所在Frame的编码格式获取如下:
  1. String DocumentWriter::encoding() const  
  2. {  
  3.     if (m_encodingWasChosenByUser && !m_encoding.isEmpty())  
  4.         return m_encoding;  
  5.     if (m_decoder && m_decoder->encoding().isValid())  
  6.         return m_decoder->encoding().name();  
  7.     Settings* settings = m_frame->settings();  
  8.     return settings ? settings->defaultTextEncodingName() : String();  
  9. }  

 
2  HTTP响应头
HTTP响应头以Content-Type来指定所请求资源的MIME类型和编码格式,例如:Content-Type:text/html;charset=gb2312。通常,HTTP响应头指定的编码格式的优先级高于页面内指定的编码格式,低于用户选择的编码格式。因为网关可能会对页面进行重新编码,并将编码格式以响应头返回。但是,HTTP响应头并不总是会返回编码格式。
2.1  请求HTML/XML页面
  当Webkit接收到页面数据后,会调用DocumentWriter的setEncoding函数进行编码格式设置(参考:FrameLoaderClientUC::receivedData),该设置包括编码名(m_encoding)和是否是用户显示指定编码格式(m_encodingWasChosenByUser)。其中,用户显示指定的编码格式的优先级高于HTTP响应头的编码格式。
PS: 接收到页面文档数据后函数receivedData先于createDecoderIfNeeded被调用!
在DocumentWriter::createDecoderIfNeeded()中:
1.      如果m_encoding为空,即用户没有显示指定编码格式,并且HTTP响应头也没有返回编码格式,那么将当前的文本解码器设置为父Frame的实际解码格式(具体代码为:parentFrame->document()->inputEncoding()),同时设置编码来源为EncodingFromParentFrame;
2.      否则,将m_encoding值设置为当前文本解码器的编码格式;同时,如果参数m_encodingWasChosenByUser为真,那么设置编码来源为UserChosenEncoding,否则设置为EncodingFromHTTPHeader;
3.      此外,将当前的文本解码器设置为对应Document的解码器
2.2  请求CSS文件
当网络层接收到响应头后,如果响应头返回了编码格式,那么调用CSS缓存对象的setEncoding函数替换默认的编码格式,设置EncodingFromHTTPHeader作为编码来源(具体参见:CachedCSSStyleSheet::setEncoding)。
2.3  请求JS文件
与CSS类似,调用JS缓存对象(CachedScript)的setEncoding函数替换默认的编码格式,设置编码来源为EncodingFromHTTPHeader。
2.4  小结
当接收到HTTP响应头后:
1.      CSS/JS: HTTP响应头返回编码的优先级比默认编码高
2.      HTML /XML:用户显示指定的编码格式高于HTTP响应头返回的编码格式,父Frame编码格式的优先级最低
3  页内编码
这里的页内编码是指,文本资源文件中指定的编码格式,比如,XML 声明中encoding属性、HTML文件的meta标签指定的编码,以及CSS的@charset等。此外,JS文件只会检测BOM(Byte Order Mark)头部。
在调用文本解码器对数据解码时(TextResourceDecoder::decode),会对页内编码进行检测。
3.1  检测BOM
TextResourceDecoder::checkForBOM函数在资源文件的开头检测是否有UTF-16/32或者UTF-8的BOM标记。如果检测到BOM标记存在,那么更改当前的编码格式,并设置编码来源为AutoDetectedEncoding。在Webkit中BOM的优先级比用户选择的编码格式高。
3.2  检测CSS编码                                                  
如果文本资源文件是CSS,并且当前解码器的编码来源是DefaultEncoding或者是EncodingFromParentFrame(也就是说,如果检测到BOM的存在,便不会再进行CSS字符集的查找),那么将会对CSS文件进行页内编码检测,其实现逻辑在TextResourceDecoder::checkForCSSCharset函数中。如果检测到CSS文件中指定了某种编码格式,那么更改当前解码器的编码格式,并且设置编码来源为EncodingFromCSSCharset。
3.3  检测头部编码
如果文本资源文件是HTML或者XML,并且当前解码器的编码来源是DefaultEncoding或者是EncodingFromParentFrame(也就是说,如果检测到BOM的存在,便不会再进行头部编码检测),那么将会对该文件进行页内头部编码检测,其实现逻辑在TextResourceDecoder:: checkForHeadCharset函数中。其处理逻辑如下:
1.      文件开头是XML的声明
a)        如果是XML声明,并且包含有encoding属性,那么更改当前解码器的编码格式,并且设置编码来源为EncodingFromXMLHeader
b)       根据字符串“<?xml”占用的字节和顺序,确定编码为UTF16/UTF32的大端/小端编码
2.      否则,检测meta标签中的charset字符串
a)        如果是XML文件,则返回(Webkit中注释:在XHTML中http-equiv无效)
b)       如果包含有meta标签,并且指定由某种编码格式,那么那么更改当前解码器的编码格式,并且设置编码来源为EncodingFromMetaTag
PS: 检测文本前1024字节,直到遇到head中不被允许的标签为止,而不是遇到head的结束标签就停止(在head中允许嵌套的标签有如下几个:SCRIPT|STYLE|META|LINK|OBJECT|TITLE|BASE)。此外,忽略<title>, <script> 和<noscript>这样的标签。
3.4  编码检测器
使用编码检测器必须同时满足下面条件(shouldAutoDetect()):
1.      m_usesEncodingDetector为真;对于HTML/XML文件,该条件是否为真,取决于settings是否设置为真;而对于CSS/JS,该条件总是为假
2.      编码来源为DefaultEncoding,或者编码来源为EncodingFromParentFrame且线索编码不为空
PS:shouldAutoDetect()函数的注释如下:
//   We use the encoding detector in two cases:
//   1. Encoding detector is turned ON and no other encoding source is
//      available (that is, it's DefaultEncoding).
//   2. Encoding detector is turned ON and the encoding is set to
//      the encoding of the parent frame, which is also auto-detected.
//   Note that condition #2 is NOT satisfied unless parent-child frame
//   relationship is compliant to the same-origin policy. If they're from
//   different domains, |m_source| would not be set to EncodingFromParentFrame
//   in the first place.
如果shouldAutoDetect()条件满足:
1.      当前解码器的编码为日文
a)        调用detectJapaneseEncoding函数检查是否需要设置具体的日文编码,并设置编码来源为AutoDetectedEncoding
2.      否则,调用detectTextEncoding函数,并将线索编码作为参数传入;该函数以统计学的方法检测给定文本可能的编码格式。
3.5  小结
对于页内编码检测:
1.      文件的BOM标记总是会被检测,优先级最高
2.      CSS/HTML/XML的页内编码是在一定条件下才会被检测,条件是:
a)        编码来源为DefaultEncoding
b)       或者,编码来源为EncodingFromParentFrame
3.      CSS/JS默认不允许使用编码检测器,HTML/XML在一定条件下允许使用
a)        Settings被设置为允许使用编码检测器
b)       编码来源为DefaultEncoding,或者编码来源为EncodingFromParentFrame且线索编码不为空
4  总结
Webkit编码选择优先级如下:
1.      BOM标记
2.      用户选择编码
3.      HTTP响应头返回编码
4.      页面内编码
5.      编码检测器
6.      父Frmae的编码

7.      默认编码

0
0

猜你在找
【直播】机器学习&数据挖掘7周实训--韦玮
【套餐】系统集成项目管理工程师顺利通关--徐朋
【直播】3小时掌握Docker最佳实战-徐西宁
【套餐】机器学习系列套餐(算法+实战)--唐宇迪
【直播】计算机视觉原理及实战--屈教授
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之矩阵--黄博士
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之凸优化--马博士
【套餐】Javascript 设计模式实战--曾亮
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:128055次
    • 积分:3836
    • 等级:
    • 排名:第8291名
    • 原创:243篇
    • 转载:159篇
    • 译文:0篇
    • 评论:2条
    文章分类
    最新评论