我們現在了解如何開發一個簡易版的瀏覽器了。在本系列的先前文章當中,已經帶大家一步一步完成一個「玩具」等級的瀏覽器,雖然功能很簡陋,很多東西不支援,但卻是貨真價實的瀏覽器!

但我們並不滿足,我們想做的是一個真正的產品,是功能齊全、設計良好、效能超棒的瀏覽器!這麼一個產品當然不可能隨手就做出來,市面上的瀏覽器,哪一個不是開發十年,由無數開發者所堆砌出來的呢?

本系列的進階實戰部分將以 Servo 專案為例,實作一些功能,讓大家了解真正開發瀏覽器是怎麼一回事。

我們都知道,一台機器是由好幾個大組件構成,大組建又是好多小零件所拼起來,小零件可能又是由幾個素材所製造。
好了,所以我們今天講的東西是,HTML > input 元素 > type > value sanitization(檢查數值),這樣講大家可能不清處,究竟 type 是什麼?

input 的 type 就是:

<input type="month">
<input type="week">
<input type="time">
<input type="color">

然後 type 會有 value 就像是:

<input type="month" value="2014-09">
<input type="week" value="2014-W52">
<input type="time" value="17:30:23">
<input type="color" value="#ffffff">

可是問題就來了,誰知道你給的數值是不是正確的呢?
例如:

<input type="month" value="2014-29">

月份最高就到 12,所以上面的當然就是錯的!

所以針對這個,有 WHATWG的文件可以參考。

像是以 week 這個 type 為例:
文件有說明怎樣才是有效的數值:https://html.spec.whatwg.org/multipage/input.html#week-state-(type=week):value-sanitization-algorithm
判斷有效數值的演算法也有文件: https://html.spec.whatwg.org/multipage/#parse-a-week-string

然後我們就可以實作了,這段程式是我寫的,也是照著文件寫,每個步驟都有對應文件上的步驟編號。


首先從 input element 中取得對應的 type 的數值 (Gthub)

InputType::Week => {
    // 取得數值
    let mut textinput = self.textinput.borrow_mut();
    // 判斷是否合理
    if !textinput.single_line_content().is_valid_week_string() {
        // 不是有效的數值就清除成 ""
        *textinput.single_line_content_mut().clear();
    }
}

上面可以看到 is_valid_week_string(),這串是 DOM 的 binding,也就是自身的方法,可以用來檢查數值。

pub fn is_valid_week_string(&self) -> bool {
    // 嘗試解析
    parse_week_string(&*self.0).is_ok()
}

檢查數值的時候會嘗試解析字串,這邊是解析的演算法,解析成功的話就會使上面的 is_valid_week_string 為真,就算通過檢查。(Github)

/// https://html.spec.whatwg.org/multipage/#parse-a-week-string
fn parse_week_string(value: &str) -> Result<(u32, u32), ()> {
    // Step 1, 2, 3 取得年份,為 "-" 切開的第一個字串
    let mut iterator = value.split('-');
    let year = iterator.next().ok_or(())?;

// Step 4 &#x5982;&#x679C;&#x5E74;&#x4EFD;&#x4E0D;&#x662F;&#x56DB;&#x500B;&#x6578;&#x5B57;&#xFF0C;&#x4E14;&#x5F9E;&#x5B57;&#x4E32;&#x8F49;&#x6210;&#x6578;&#x5B57;&#x51FA;&#x932F;&#xFF0C;&#x5C31;&#x56DE;&#x50B3;&#x932F;
let year_int = year.parse::&lt;u32&gt;().map_err(|_| ())?;
if year.len() &lt; 4 || year_int == 0 {
    return Err(());
}

// Step 5, 6 &#x5982;&#x679C;&#x80FD;&#x89E3;&#x6790;&#x4E0B;&#x500B;&#x5B57;&#x4E32;&#xFF0C;&#x4E14;&#x70BA; W &#x624D;&#x901A;&#x904E;&#x6AA2;&#x67E5;
let week = iterator.next().ok_or(())?;
let (week_first, week_last) = week.split_at(1);
if week_first != &quot;W&quot; {
    return Err(());
}

// Step 7 &#x9031;&#x6578;&#x70BA;&#x5169;&#x500B;&#x6578;&#x5B57;
let week_int = week_last.parse::&lt;u32&gt;().map_err(|_| ())?;
if week_last.len() != 2 {
    return Err(());
}

// Step 8 &#x7576;&#x5E74;&#x6700;&#x5927;&#x9031;&#x6578;
let max_week = max_week_in_year(year_int);

// Step 9 &#x9031;&#x6578;&#x5FC5;&#x9808;&#x5728;&#x6700;&#x5927;&#x9031;&#x6578;&#x4EE5;&#x5167;
if week_int &lt; 1 || week_int &gt; max_week {
    return Err(());
}

// Step 10 &#x5982;&#x679C;&#x5B57;&#x4E32;&#x9084;&#x6709;&#x6771;&#x897F;&#xFF0C;&#x90A3;&#x5C31;&#x662F;&#x932F;&#x7684;
if iterator.next().is_some() {
    return Err(());
}

// Step 11 &#x56DE;&#x50B3;&#x89E3;&#x6790;&#x7D50;&#x679C;
Ok((year_int, week_int))

}


以上就是 value sanitization of input type 的示範,事實上瀏覽器中有很多這種小功能必須實作,也就是我一開始舉例的小零件,每個螺絲都很重要,才能完成我們最棒的瀏覽器!

希望有幫到大家,大家明天見!


關於作者

劉安齊

軟體工程師,熱愛寫程式,更喜歡推廣程式讓更多人學會