今天解決的最有成就感的一個Web前端問題


IE詭異的行為經常讓人捉摸不透。這次遇到的IE問題也很詭異,雖然沒有“透”,但捉摸出了解決方法,而且是在幾乎無望的情形下捉摸出來的,所以說很有成就感。那“最”從何來呢?在解決過的Web前端問題中,這次是“最”讓人興奮的。

這個問題簡單來說就是一個Javascript跨域的問題。那復雜來說呢,請看下面的文字。

<script src="http://common.cnblogs.com/editor/tiny_mce/tiny_mce.js"></script>

博客園博客后台(域名是www.cnblogs.com)用的是TinyMCE編輯器,TinyMCE的js文件的引用是通過 common.cnblogs.com 這個域名(兩個好處:1. 實現不同應用重用TinyMCE;2. 可以針對這個域名進行CDN加速)【注:這里雖然涉及兩個域名,如果僅僅是js文件引用,不存在跨域問題】。

然后點擊“上傳圖片”按鈕,彈出上傳圖片的窗口(通過一個TinyMCE插件實現的),窗口打開的是 upload.cnblogs.com 這個域名【注:這時也不存在跨域問題】。

然后進行圖片上傳操作,上傳成功之后返回圖片地址,並將這個圖片地址寫入到編輯器文本框中(調用tinyMCEPopup.editor.execCommand),然后關閉這個窗口(調用tinyMCEPopup.close),為了調用這兩個TinyMCE函數,必須在上傳頁面(upload.cnblogs.com)中引用 util.cnblogs.com 域名下的tiny_mce_popup.js文件。【注:跨域問題登場了】為了支持跨域操作,必須要在tiny_mce_popup.js文件開頭添加如下的代碼:

以前我們就是這樣解決跨域問題的。

但這次使用了ajax上傳組件Fine Uploader(https://github.com/valums/file-uploader)之后,Chrome, Firefox, IE10標准模式下都沒問題,但在IE10的“IE9標准模式”下(估計IE9及以下版本都存在這個問題),上傳圖片之后,在IE10 develop tools的控制台中卻出現這樣的錯誤(圖片地址也就無法寫入編輯器中):

Error when attempting to access iframe during handling of upload response (Error: Access is denied.) 
Error when attempting to access iframe during handling of upload response (Error: Access is denied.) 

大部分時間都花在找出這個錯誤的來源上。。。

后來試着在tiny_mce_popup.js中將 document.domain = 'cnblogs.com'; 注釋掉,錯誤就消失了。進一步測試,發現只要在Fine Uploader發起請求之前(注意這里的“請求之前”)有 document.domain = 'cnblogs.com'; 這樣的代碼,就會出現這個錯誤。

也就是說在IE9及IE9版本以下(未實際驗證,只在IE10的“IE9標准模式”下驗證過),Fine Uploader與 document.domain = 'cnblogs.com'; 水火不容。但是不在tiny_mce_popup.js中加 document.domain = 'cnblogs.com'; ,就無法將圖片地址添加到TinyMCE文本框(tinyMCEPopup.editor.execCommand)中並關閉圖片上傳窗口(tinyMCEPopup.close)。而錯誤信息恰恰就是來自這兩個操作【這兩個操作是跨域操作,不設置document.domain就無法跨域操作】。

Fine Uploader不允許跨域,而TinyMCE又必須要跨域才能添加內容,本文開頭的“幾乎無望”說的就是這個時候。。。此處省略解決問題過程中最難熬的階段

然后,突然想到:tinyMCEPopup.editor.execCommand與tinyMCEPopup.close是在圖片上傳完成之后fineUploader.complete回調函數中執行的,如果在這里動態加載tiny_mce_popup.js是不是可以解決問題呢?

於是,改為下面的代碼(upload.cnblogs.com域名下的上傳頁面,關鍵代碼是$.getScript):

$('#jquery-wrapped-fine-uploader').fineUploader({})
    .on('complete', function (event, id, fileName, responseJSON) {
    if (responseJSON.success) {
        $.getScript("http://common.cnblogs.com/editor/tiny_mce/tiny_mce_popup.js", function () {
            tinyMCEPopup.editor.execCommand('mceInsertContent', false, responseJSON.message);
            tinyMCEPopup.close();
        });
    }            
});

結果得到的是不同的錯誤信息:

SCRIPT5: Access is denied. SCRIPT5007: Unable to get property 'execCommand' of undefined or null 

對於這個錯誤,當時沒有進行仔細分析,只是猜測可能是$.getScript的跨域問題(有經驗的朋友幫忙分析一下)。接着,將tiny_mce_popup.js文件復制到upload.cnblogs.com域名下,改為在同一個域名下加載tiny_mce_popup.js,代碼如下:

$('#jquery-wrapped-fine-uploader').fineUploader({})
    .on('complete', function (event, id, fileName, responseJSON) {
    if (responseJSON.success) {
        $.getScript("/scripts/tiny_mce_popup.js", function () {
            tinyMCEPopup.editor.execCommand('mceInsertContent', false, responseJSON.message);
            tinyMCEPopup.close();
        });
    }            
});

改為這個代碼后,問題竟奇跡般地解決了。(當然,tiny_mce_popup.js中的 document.domain = 'cnblogs.com'; 不能少)

寫了這么多,不知道沒有把問題表述清楚,跨來跨去,人都被跨暈,何況IE呢?可是為什么Chrome, Firefox沒有暈?

更新

寫了這篇博客之后,一直在思考有沒有更好的解決方法。

把問題精簡一下:

1. 在tiny_mce_popup.js中,初始化tinyMCEPopup需要設置document.domain;

2. 使用fineUploader時不能設置document.domain,但在fineUploader上傳完成之后的回調函數(complete)中可以設置;

3. 初始化tinyMCEPopup的目的就是為了tinyMCEPopup.editor.execCommand與tinyMCEPopup.close的執行。

目前采用的$.getScript方法,是通過異步的方式加載tiny_mce_popup.js(這樣加載有成本),在tiny_mce_popup.js中設置document.domain,初始化tinyMCEPopup,這樣做效率不是很高。

效率更高的做法應該是正常引用tiny_mce_popup.js(通過<script>標簽),在fineUploader的回調函數(complete)中設置document.domain,初始化tinyMCEPopup。

看了一下tiny_mce_popup.js的代碼,的確可以把初始化tinyMCEPopup的操作移出來。於是,有了更好的解決方法:

1. 在tiny_mce_popup.js中移除設置document.domain的代碼。

2. 在tiny_mce_popup.js中移除tinyMCEPopup.init();【注:該代碼在文件最后的位置】

3. 改為如下的代碼:

<script src="/scripts/tiny_mce_popup.js"></script>
<script src="/scripts/jquery.fineuploader-3.1.min.js"></script>
<div id="jquery-wrapped-fine-uploader"></div>
<div id="message">只能上傳單張10M以下的 png、jpg、gif 格式的圖片</div>
<script>
    $(function () {
        $('#jquery-wrapped-fine-uploader').fineUploader({}).
            on('complete', function (event, id, fileName, responseJSON) {
                if (responseJSON.success) {
                    document.domain = 'cnblogs.com'; tinyMCEPopup.init();
                    tinyMCEPopup.editor.execCommand('mceInsertContent',
                        false, '<p><img src="' + responseJSON.message + '" alt=""/>');
                    tinyMCEPopup.close();
                }
            });
    });
</script>

這再次體現了寫博客的作用:幫助自己理清思路,進行更深入的思考。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM