CSS 樣式表引入有3種方式: 外部樣式表、內部樣式表、行內樣式,不同的引入方式,解碼樣式表的字符集原理不一樣。
外部樣式表
外部樣式表由 link 標籤引入,當 WebKit 解析到 link 標籤時就會構造 CachedCSSStyleSheet 物件。這個物件持有 CachedResourceRequest 物件和 TextResourceDecoder 物件。CachedResourceRequest 物件負責傳送請求,TextResourceDecoder 物件負責對下載回來的 CSS 資料進行解碼。TextResourceDecoder 物件裡面的 m_encoding 屬性,就是儲存著解碼 CSS 資料使用的字元編碼資訊。相關類圖如下所示:
在建立 TextResourceDecoder 物件時,會傳入 CachedResourceRequest 使用的字符集資訊,程式碼如下所示:
CachedCSSStyleSheet::CachedCSSStyleSheet(CachedResourceRequest&& request, PAL::SessionID sessionID, const CookieJar* cookieJar) : CachedResource(WTFMove(request), Type::CSSStyleSheet, sessionID, cookieJar) // 1. 建立 TextResourceDecoder,傳入 CachedResourceRqeuest 字符集 , m_decoder(TextResourceDecoder::create(cssContentTypeAtom(), request.charset())) { }
在上面程式碼註釋 1 處,就是 CachedCSSStyleSheet 建立 TextResourceDecoder 物件,並且傳入了 CachedResourceReqeust 的字符集用來初始化 TextResourceDecoder 內部的 m_encoding 屬性,程式碼如下:
inline TextResourceDecoder::TextResourceDecoder(const String& mimeType, const PAL::TextEncoding& specifiedDefaultEncoding, bool usesEncodingDetector) : m_contentType(determineContentType(mimeType)) // 1. specifiedDefaultEncoding 就是傳入的 CachedResourceRequest 的字符集 // m_contentType 此時是 CSS , m_encoding(defaultEncoding(m_contentType, specifiedDefaultEncoding)) , m_usesEncodingDetector(usesEncodingDetector) { }
在上面程式碼註釋 1 處初始化 TextResourceDecoder 的 m_encoding 屬性,m_contentType 此時的值是 CSS,specifiedDefaultEncoding 就是 CachedResourceRequest 的字符集。但是在初始化 m_encoding 時,並不是直接將 m_encoding 直接賦值為 specifiedDefaultEncoding,而是呼叫了 TextResourceDecoder::defaultEncoding 函式,相關程式碼如下:
const PAL::TextEncoding& TextResourceDecoder::defaultEncoding(ContentType contentType, const PAL::TextEncoding& specifiedDefaultEncoding) { // Despite 8.5 "Text/xml with Omitted Charset" of RFC 3023, we assume UTF-8 instead of US-ASCII // for text/xml. This matches Firefox. // 1. 如果解碼的是 XML ,那麼無論傳入的 specifiedDefaultEncoding 是什麼值,都使用 UTF-8 作為預設解碼字符集 if (contentType == XML) return PAL::UTF8Encoding(); // 2. 對於其他資源型別,如果 specifiedDefaultEncoding 使用的字符集名為 null,就預設使用 Latin-1 字符集, // 也就是 ISO-8859-1 if (!specifiedDefaultEncoding.isValid()) return PAL::Latin1Encoding(); // 3. 將 specifiedDefaultEncoding 作為 TextResourceDecoder 的預設解碼字符集 return specifiedDefaultEncoding; }
上面程式碼註釋 1 處可以看出,TextResourceDecoder 不僅用來解碼 CSS,還可以用來解碼其他資源型別。如果解碼的資源型別是 XML,那麼預設的解碼字符集就是 UTF-8。
從註釋 2 處可以看到,如果不是 XML 資源型別,並且 specifiedDefaultEncoding 不合法,也就是 specifiedDefaultEncoding 內部程式碼字符集名的 m_name 屬性為 null,那麼就使用 Latin-1(也就是 ISO-8859-1) 作為 TextResourceDecoder 的預設解碼字符集。
從註釋 3 可以看到,對於非 XML 資源型別,specifiedDefaultEncoding 就會作為 TextResourceDecoder 的預設解碼字符集。也就是會說,解碼 CSS 的預設字符集和 CacheResourceRequest 使用同一個字符集。
那麼,CacheResourceRequest 的字符集是如何來的呢?相關程式碼如下:
void HTMLLinkElement::process() { ... if (m_disabledState != Disabled && treatAsStyleSheet && document().frame() && m_url.isValid()) { // 1. 解析 link 標籤的 charset 屬性值 String charset = attributeWithoutSynchronization(charsetAttr); if (!PAL::TextEncoding { charset }.isValid()) // 2. 獲取文件 Document 使用的字符集 charset = document().charset(); ... // 3. 設定 CachedResourceRequest 的字符集 request.setCharset(WTFMove(charset)); ... return; } ... }
上面程式碼註釋 1 處首先解析 link 標籤的 charset 屬性,如果解析到就使用這個字符集作為 CachedResourceRequest 的字符集,如 註釋 3所示。如果解析不到,就使用文件 Document 的字符集作為 CachedResourceReqeust 的字符集,如註釋 2 所示。文件 Document 的字符集由 <meta> 標籤指定,如果沒有 <meta> 標籤,那麼文件 Document 預設使用 Latin-1 字符集(ISO-8859-1)。
但是,解碼 CSS 使用的字符集此時還並沒有完全確定,因為 CSS 樣式表本身支援 @charset at-rule,它可以指定解碼 CSS 樣式表需要使用什麼字符集。因此,當從網路上下載到 CSS 樣式表之後,還需要檢測 CSS 樣式表裡面是否有 @charset at-rule,相關程式碼如下:
String TextResourceDecoder::decode(const char* data, size_t length) { ... if (m_contentType == CSS && !m_checkedForCSSCharset) // 1. 當下載完 CSS 樣式表,這裡呼叫 checkForCSSCharset 函式,檢測樣式表裡是否有 @charset if (!checkForCSSCharset(data, length, movedDataToBuffer)) return emptyString(); ... }
上面程式碼註釋 1處,當下載完 CSS 樣式表使用 TextResourceDecoder 進行解碼時,會呼叫 TextResourceDecoder::checkForCSSCharset 檢測 CSS 樣式表裡面是否有 @charset at-rule。如果有 @charset at-rule,那麼就將 TextResourceDecoder 的 m_encoding 屬性值設定為 @charset 指定的字符集。
綜上所述,對於外部引入的樣式表,解碼字符集的優先順序為:
1 如果樣式表有 @charset at-rule,就優先使用 @charset 指定的字符集;
2 否則,就看 link 標籤有沒有 charset 屬性,有的話就優先使用 link 標籤 charset 屬性指定的字符集;
3 否則,就使用文件 Document 的字符集。
內部樣式表與行內樣式
由於內部樣式表和行內樣式本身就在 HTML 檔案裡面,因此對於它們的解碼就使用 HTML 字符集。HTML 解碼字符集確認規則如下:
1 如果有 <meta> 標籤,就使用 <meta> 標籤使用的字符集;
2 否則,就預設使用 Latin-1(ISO-8859-1) 字符集