http://blog.sina.com.cn/s/blog_71b5e2520100s1cr.html
Webkit文本資源編碼選擇
目錄
1 默認編碼....................................................................................2
1.1 HTML/XML文件..............................................................................3
1.2 CSS文件...................................................................................3
1.3 JS文件....................................................................................3
1.4 小結......................................................................................4
2 HTTP響應頭..................................................................................4
2.1 請求HTML/XML頁面..........................................................................4
2.2 請求CSS文件...............................................................................5
2.3 請求JS文件................................................................................5
2.4 小結......................................................................................5
3 頁內編碼....................................................................................5
3.1 檢測BOM...................................................................................6
3.2 檢測CSS編碼...............................................................................6
3.3 檢測頭部編碼..............................................................................6
3.4 編碼檢測器................................................................................7
3.5 小結...................... ......... ......... ..........................................7
4 總結........................................................................................8
本文描述了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表示:
enum EncodingSource {
DefaultEncoding, //默認編碼
AutoDetectedEncoding, //自動檢測編碼
EncodingFromXMLHeader, //來自於XML頭部
EncodingFromMetaTag, //來自於Meta標籤
EncodingFromCSSCharset, //來自於CSS的@charset
EncodingFromHTTPHeader, //來自於HTTP響應頭
UserChosenEncoding, //用戶選擇編碼
EncodingFromParentFrame //來自於父Frame
};
下面分別介紹這些類型。
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的編碼格式獲取如下:
String DocumentWriter::encoding() const
{
if (m_encodingWasChosenByUser &&!m_encoding.isEmpty())
return m_encoding;
if (m_decoder &&m_decoder->encoding().isValid())
return m_decoder->encoding().name();
Settings* settings = m_frame->settings();
return settings ?settings->defaultTextEncodingName() :String();
}
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 sourceis
// available (that is, it's DefaultEncoding).
// 2.Encoding detector is turned ON and the encoding is setto
// the encoding of the parent frame, which is alsoauto-detected.
// Notethat condition #2 is NOT satisfied unless parent-childframe
// relationship is compliant to the same-origin policy. If they'refrom
// different domains, |m_source| would not be set toEncodingFromParentFrame
// inthe 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. 默認編碼