WebKit Inside: CSS 樣式表解碼字符集

chaoguo1234發表於2023-09-30

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) 字符集

 

相關文章