下午同事前來報案,一個MultiView包在UpdatePanel中,使用IE9檢視,以AJAX方式動態切換ActiveView後,其中的<ol><li>的自動跳號數字忽然全變成0。將IE9切換成IE8或IE7相容模式,則序號顯示正常。這個小小問題,揭開了前後近六小時的射茶包歷程。

由於原本的問題出現在一個十分複雜的後台專案網頁中,執行時涉及身分認證、Oracle帳務資料才能正確顯示,因此當務之急是設法用最少的Code重現Bug,最好只需單一ASPX,放在任何IIS執行就可以執行重現問題。要實踐這個理想沒什麼捷徑,就是一點一點刪去網頁上的元素,每刪掉一部分就重測一次,確認Bug還在,直到再刪掉任何一樣元素Bug就消失為止。最後歸納出 "ScriptManager + UpdatePanel ( Button + <ol><ul> )" 的最精簡組合如下:

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>IE9 Ordered List Issue</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager runat="server"></asp:ScriptManager>
        <asp:UpdatePanel ID="UpdatePanel" runat="server">
            <ContentTemplate>
                <asp:Button ID="btnPostBack" runat="server" Text="PostBack" />
                <ol>
                    <li>One</li>
                    <li>Two</li>
                    <li>Three</li>
                </ol>
            </ContentTemplate>
        </asp:UpdatePanel>
    </form>
</body>
</html>

以上程式,原本的1. 2. 3. 在按下【PostBack】鈕後會變成 0. 0. 0. 。而這只有在IE9標準模式會出問題,開相容模式或使用FireFox、Chrome檢視都是正常的,應足以推論是IE9的Bug。

理論上UpdatePanel+Button組合已是很純的濃縮茶包,可送交客服或RD進一步化驗;但射手熱血一旦湧起,豈能輕易罷休,心想,若是IE9 DOM呈現問題,肯定可以只用HTML + Javascript就模擬出來,不追根究底豈不負了茶包射手的名聲。為了賭這口氣,一轉眼,又再賠上好幾個小時... orz

由於前述ASP.NET程式已經被簡化到只有ScriptManager跟UpdatePanel加Button,所涉及的都是ASP.NET內建的JavaScript及HTML元素,不像自己寫的Script可以任意調整測試。所以我初步使用IE9 Dev Tools進行偵錯,追到MicrosoftAjaxWebForms.debug.js中的執行順序是:

  • Sys$WebForms$PageRequestManager$_scriptIncludesLoadComplete 呼叫this._updatePanel(updatePanelElement, node.content)
  • _updatePanel() 透過 updatePanelElement.innerHTML = rendering; 置換成Ajax呼叫所得到的HTML內容。

但_scriptIncludesLoadComplete()中還有一系列的操作,巧妙地使用IE9 Dev Tools的偵錯功能,在_scriptIncludesLoadComplete函數一開始設中斷點,中斷時以Console輸入指令,指定
    document.getElementById(“UpdatePanel”).innerHTML = “<ol><li>One</li><li>Two</li></ol>”;
並偷改this._request,讓函數在下一列指令
    if (data.executor.get_webRequest() !== this._request) {  return;  }
強迫結束,就可避開後續一連串動件,在此測試下,<ol>序號是正常的,這個實驗可以推估凶手就藏在後續一連串動作中的某處。而要找出是哪一段程式惹禍,還是只能依賴最原始的地毯式搜索法,把後嬻可疑動作一點一點加回去,當加入某段後開始出錯,凶手便在其中。

問題來了,MicrosoftAjaxWebForms.js是ScriptManager自動帶出來的,要怎麼去調整JavaScript,控制_scriptIncludesLoadComplete()函數中要執行哪些片段?? 此時要使用一項密技出: 先用Dev Tools把側錄下來的MicrosoftAjaxWebForms.debug.js存成檔案,再利用<asp:ScriptReference>將其指向我們另存的檔案(Path不用加.debug,ScriptManager會自動加),如此即可用改寫版本取代內建版本。

<asp:ScriptManager runat="server" >
<Scripts>
<asp:ScriptReference Name="MicrosoftAjax.js" 
     Path="~/Folder/MicrosoftAjax.js" />
<asp:ScriptReference Name="MicrosoftAjaxWebForms.js" 
     Path="~/Folder/MicrosoftAjaxWebForms.js" />
</Scripts>
</asp:ScriptManager>

取得MicrosoftAjaxWebForms.debug.js的修改權,問題就單純了,純粹都是苦工。一點一點加入_scriptIncludesLoadComplete()原本要執行的程式片段,最後發現炸彈在這裡:

    if (!container) {
        container = document.createElement('span');
        container.style.cssText = "display:none !important";
        this._form.appendChild(container);
    }

只要不執行this._form.appendChild(container),序號就正常,一執行序號就變0!! 鐵證如山,人肯定就是你殺的,豈容狡辯! (威~~武~~~~)

再經過一番組合嘗試,終於做一個 純HTML/Javascript Bug重現包:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>IE9 Bug</title>
    <script type="text/javascript">
            setTimeout(function () {
                document.getElementById("dv1").innerHTML = 
                    "<ol><li>XXX</li><li>YYY</li></ol>";
                var container = document.createElement('span');
                container.style.cssText = "display:none";
                document.getElementById("dv2").appendChild(container);
            }, 1000);
    </script>
</head>
<body>
<div id="dv1">
<ol><li>AAA</li><li>BBB</li></ol>
</div>
<div id="dv2"></div>
</body>
</html>

推敲問題的發生條件是: 1) 網頁原本就有<ol>  2) 使用innerHTML動態加入<ol>元素  3) 動態在DOM中加入style=”display:none”;元素。三者缺一不可,感覺上要剛好湊足這些條件並不容易,但偏偏同事就遇到了,只能說一切都是冥冥之中自有安排呀~~~ 打算下週把這個Bug提報給微軟,有結果再向大家說明。

以上就是近六個小時的射茶包心得,報告完畢!!


Comments

# by 小熊子

太威了~~

# by Bibby

光是看完,我就累了..辛苦了!!

# by 91

威~~~武~~~

# by ChrisTorng

不能使用二分搜尋法嗎? 我的意思是,刪掉一半比較不可能的段落看看是否有影響,沒有影響就再刪一半,若有影響就恢復被刪的,改刪另一半...

# by Jeffrey

to ChrisTorng, 理論上可以用二分法,不過因為粗估切分程式段落不超過十幾處,要去精準地把有長有短的程式碼分割成多段,並計算從哪裡切算一半,刪掉若沒影響還要還原;直覺上不如先在第一段後方放return看結果,若OK再把return移到第二段後方,再OK再把return放在第三段後方來得單純,這是我第一時間決定用地毯式的原因,供你參考。

Post a comment