馮博文 汲如意 于佳耕
1(中國科學(xué)院大學(xué) 北京 100190)
2(中國科學(xué)院軟件研究所互聯(lián)網(wǎng)軟件技術(shù)實驗室 北京 100190)
Linux中瀏覽器兼容ActiveX控件機制的設(shè)計與實現(xiàn)
馮博文1,2汲如意1,2于佳耕2
1(中國科學(xué)院大學(xué) 北京 100190)
2(中國科學(xué)院軟件研究所互聯(lián)網(wǎng)軟件技術(shù)實驗室 北京 100190)
目前,瀏覽器已經(jīng)成為計算機系統(tǒng)中不可缺少的組成部分。然而,全球市場份額最高的Internet Explorer瀏覽器,其廣泛應(yīng)用的ActiveX控件卻無法運行在Linux操作系統(tǒng)的瀏覽器中。因此,研究了兩種瀏覽器插件框架,NPAPI和ActiveX,提出了一種兼容機制。該機制為中間件解決方案,一端面向NPAPI,另一端面向Wine中經(jīng)封裝的ActiveX控件,實現(xiàn)NPAPI和ActiveX控件間的消息傳輸和轉(zhuǎn)換。實驗結(jié)果表明,兼容機制實現(xiàn)了ActiveX控件運行在Linux操作系統(tǒng)的瀏覽器中,且加載時間、運行效率和內(nèi)存占用均在可接受的范圍內(nèi),為ActiveX控件跨平臺技術(shù)提供了一種新的方法。
ActiveX控件 跨平臺 Linux NPAPI WebKit
瀏覽器為在因特網(wǎng)檢索、呈現(xiàn)和瀏覽信息的一種軟件,它可以顯示網(wǎng)頁內(nèi)容并允許用戶與之交互。瀏覽器插件則為本地二進制代碼,當(dāng)瀏覽器自身的功能無法滿足網(wǎng)頁需求時,瀏覽器將加載插件,并借助插件實現(xiàn)的功能。目前,瀏覽器作為因特網(wǎng)世界的入口,已經(jīng)成為各大軟件廠商的必爭之地。其中,Internet Explorer瀏覽器(簡稱IE瀏覽器)在全球的市場份額最高[1]。IE瀏覽器為微軟開發(fā)的瀏覽器,憑借Windows操作系統(tǒng)的壟斷地位,在國內(nèi)的電子支付、在線辦公等領(lǐng)域仍然具有重要的地位[2],但至今尚不能運行在Linux操作系統(tǒng)的瀏覽器中。
隨著發(fā)行版用戶體驗的逐步提升,Linux 操作系統(tǒng)在桌面領(lǐng)域也存在大量用戶。如果能夠使ActiveX控件運行在Linux操作系統(tǒng)的瀏覽器中,無論對重復(fù)利用現(xiàn)有的系統(tǒng)(如網(wǎng)上銀行、企事業(yè)單位在線辦公網(wǎng)站等),節(jié)約重復(fù)開發(fā)的成本,或者對Linux操作系統(tǒng)的推廣,都具有重要的現(xiàn)實意義。
目前,針對跨平臺運行二進制代碼的技術(shù)已經(jīng)存在一些研究成果,主要分為兩個方向:系統(tǒng)級虛擬化和庫級虛擬化[3-4]。系統(tǒng)級虛擬化即虛擬機技術(shù),指在一臺物理計算機上,虛擬出一個或多個邏輯計算機,邏輯計算機相對獨立,可以運行不同的操作系統(tǒng)和應(yīng)用程序,其代表軟件為VirtualBox和VMWare等。用戶可以在Linux 操作系統(tǒng)中創(chuàng)建Windows操作系統(tǒng)的虛擬機,但虛擬機需要較高的硬件資源,特別需要支持VT-x或AMD-V技術(shù)的處理器才能具有較好的效果。僅因為需要瀏覽含有ActiveX控件的網(wǎng)頁而讓用戶配置啟動復(fù)雜的虛擬機,十分不方便,并且采用虛擬機仍然沒有擺脫Windows操作系統(tǒng)。庫級虛擬化的代表為開源項目Wine,Wine為POSIX操作系統(tǒng)中轉(zhuǎn)換Windows API的兼容層[5]。Wine沒有模擬一個完整的計算機,而是將二進制代碼中對Windows API的調(diào)用轉(zhuǎn)換為POSIX調(diào)用,同時啟動wineserver守護進程模擬Windows內(nèi)核,為在Wine中的程序提供調(diào)度、Windows消息機制等內(nèi)核功能。若將IE瀏覽器整體移植至Wine中,技術(shù)難度較大。瀏覽器內(nèi)核代碼量級為百萬行級,其復(fù)雜度不亞于操作系統(tǒng)內(nèi)核,且IE瀏覽器與Windows操作系統(tǒng)高度耦合。開源項目IEs 4 Linux嘗試將IE瀏覽器移植至Wine中,主要面向Web開發(fā)者測試網(wǎng)頁在IE瀏覽器中的布局渲染效果,僅支持IE瀏覽器的舊版本,于08年停止維護。
此外,開源項目FireBreath專門針對跨平臺瀏覽器插件提供了開發(fā)框架[6]。若基于FireBreath提供的開發(fā)框架,則需要獲取ActiveX控件的源代碼并做二次開發(fā)。然而,在目前的條件下無法獲取ActiveX控件的源代碼,并且針對ActiveX控件的二次開發(fā)代價較大。
采用現(xiàn)有的解決方案具有諸多的缺點,因此,本文提出了一種在Linux操作系統(tǒng)中瀏覽器兼容ActiveX控件的機制。該機制一端面向NPAPI,一端面向Wine中經(jīng)封裝的ActiveX控件,實現(xiàn)了NPAPI和ActiveX控件間的消息傳輸和轉(zhuǎn)換,使ActiveX控件運行在Linux操作系統(tǒng)的瀏覽器中。
網(wǎng)景插件應(yīng)用程序編程接口NPAPI(Netscape Plugin Application Programming Interface)為網(wǎng)景公司開發(fā)的瀏覽器插件框架,也為大部分非IE瀏覽器共同支持的插件框架[7]。NPAPI為一個簡潔的跨平臺開發(fā)框架,它定義了幾十個函數(shù),分為兩組,一組由瀏覽器提供給插件調(diào)用,名稱以NPN開頭。一組由插件提供給瀏覽器調(diào)用,名稱以NP和NPP開頭[8]。NPAPI插件在Linux操作系統(tǒng)中為so文件,在網(wǎng)頁中的形式如下:
var plugin = document.getElementById(″plugin″);
plugin.foo();
變量plugin為JavaScript可編程對象,與NPAPI的NPObject對象對應(yīng),foo函數(shù)由插件代碼實現(xiàn)。NPObject含有NPClass函數(shù)指針表,JavaScript可編程對象的函數(shù)調(diào)用和數(shù)據(jù)訪問都映射至NPObject。瀏覽器調(diào)用NPHasMethod函數(shù)查詢foo函數(shù)是否存在,若存在,則緊接著調(diào)用NPInvoke函數(shù),由NPObject完成對插件代碼的調(diào)用。數(shù)據(jù)成員訪問流程與函數(shù)調(diào)用流程相似。同時,NPAPI定義了變體類型NPVariant作為JavaScript數(shù)據(jù)類型與C++數(shù)據(jù)類型的映射。
ActiveX為微軟推出的組件技術(shù),為OLE(Object Linking and Embedding,對象鏈接與嵌入)針對互聯(lián)網(wǎng)的延伸,本質(zhì)為COM(Component Object Model,組件對象模型)組件[9]。因此,ActiveX控件可以嵌入任何支持COM組件的程序中,但主要應(yīng)用于互聯(lián)網(wǎng),在網(wǎng)頁中的形式如下:
classid=″CLSID:6B5FACBE-A8C1-4DCE-8D0F-B8E1C454234A″ codebase=″https://demo.com/control.cab″>
classid為GUID(Globally Unique Identifier,全局唯一標(biāo)識符),GUID標(biāo)識了需要加載的ActiveX控件,利用注冊表和GUID可以直接找到相應(yīng)的dll或ocx文件。codebase為ActiveX控件的下載地址。ActiveX控件與COM組件具有完全一樣的生命周期,IE瀏覽器調(diào)用Windows API獲取控件的IClassFactory指針,然后向IClassFactory指針請求COM對象。所有COM對象都實現(xiàn)了IUnknown接口,ActiveX控件的函數(shù)調(diào)用和數(shù)據(jù)成員訪問都經(jīng)IUnknown接口映射至COM對象。
若要實現(xiàn)ActiveX控件運行在Linux操作系統(tǒng)的瀏覽器中,需要解決三個關(guān)鍵問題:
(1) 瀏覽器如何獲取和識別ActiveX控件標(biāo)簽:服務(wù)器可能會識別UA(User Agent,用戶代理)直接拒絕不兼容瀏覽器的訪問。同時,NPAPI插件和ActiveX控件在網(wǎng)頁中的表現(xiàn)形式不同。
(2) ActiveX控件如何運行在Linux操作系統(tǒng)中:ActiveX控件為Windows操作系統(tǒng)的二進制代碼,無法直接運行在Linux操作系統(tǒng)中。同時,我們無法改動ActiveX控件。
(3) 瀏覽器如何與ActiveX控件交互:瀏覽器具有NPAPI接口,ActiveX控件具有COM組件接口,需要在其間設(shè)計一個合理的消息機制,消除NPAPI和ActiveX控件的差異。
兼容機制采取了中間件的解決方案,即在瀏覽器與ActiveX控件間構(gòu)建一個中間件,由AxPlugin和AxLoader兩個部分組成,負責(zé)傳輸消息和轉(zhuǎn)換消息。同時,需要在瀏覽器中實現(xiàn)UA偽裝和JavaScript代碼注入。兼容機制方案設(shè)計如圖1所示。
圖1 兼容機制方案設(shè)計
UA為一個特殊的字符串,在HTTP頭中的user-agent字段中,用于服務(wù)器獲取瀏覽器與操作系統(tǒng)的信息,如“Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv: 11.0) like Gecko”,Mozilla和like Gecko為習(xí)慣性字段,Windows NT 6.1為操作系統(tǒng)類型,Trident為IE瀏覽器內(nèi)核,版本號為7.0。在向服務(wù)器請求網(wǎng)頁時,根據(jù)網(wǎng)頁地址偽裝瀏覽器的UA,可以防止服務(wù)器拒絕訪問。同時,瀏覽器無法直接識別ActiveX控件標(biāo)簽,需要在解析網(wǎng)頁時注入JavaScript代碼,將ActiveX控件標(biāo)簽改變?yōu)闉g覽器可識別的NPAPI插件標(biāo)簽,使瀏覽器加載AxPlugin。UA偽裝和JavaScript代碼注入在瀏覽器中解決了如何獲取和識別ActiveX控件標(biāo)簽,小幅增加了瀏覽器的負擔(dān),但具有無需改動現(xiàn)有在線系統(tǒng)的優(yōu)點。
在Linux操作系統(tǒng)中支持Windows應(yīng)用程序幾乎都離不開Wine。由于Windows操作系統(tǒng)的閉源性質(zhì),Wine開發(fā)者只能推測Windows API的內(nèi)部實現(xiàn),所以Wine沒有完全兼容Windows應(yīng)用程序,但目前PE格式加載、注冊表和COM技術(shù)已經(jīng)得到Wine的支持。ActiveX控件本質(zhì)為COM組件,因此將ActiveX控件的運行環(huán)境由Wine負責(zé)[10]。既充分利用了Wine的支持能力,無需改動ActiveX控件,又避免了整體移植IE瀏覽器的復(fù)雜性。
消息機制基于Linux的管道通信技術(shù),由AxPlugin和AxLoader負責(zé)。AxPlugin為NPAPI插件,負責(zé)啟動Wine和AxLoader,并在瀏覽器與AxLoader間傳輸消息。AxLoader為exe可執(zhí)行文件,負責(zé)轉(zhuǎn)換消息并加載ActiveX控件,同時與AxPlugin傳輸消息。所以,AxLoader又需要具有Linux操作系統(tǒng)的IPC能力。Wine為在Linux操作系統(tǒng)中編譯Windows應(yīng)用程序提供了Winelib和WineGCC。Winelib含有Windows API的實現(xiàn)和Windows兼容頭文件。WineGCC為GCC的封裝,用于編譯基于Winelib的C/C++應(yīng)用程序。Winelib和WineGCC實現(xiàn)了一種Windows和Linux混合編程的技術(shù),基于Winelib和WineGCC編譯的Windows應(yīng)用程序獲得了訪問Unix API的能力[11]。因此,AxLoader可以基于Winelib和WineGCC實現(xiàn)。
基于上述方案設(shè)計,瀏覽器根據(jù)網(wǎng)頁地址偽裝UA向服務(wù)器請求網(wǎng)頁,并在解析網(wǎng)頁時注入JavaScript代碼,改變ActiveX控件標(biāo)簽,使瀏覽器加載AxPlugin。AxPlugin將啟動Wine和AxLoader,負責(zé)在瀏覽器和AxLoader間傳輸消息。在Wine中,AxLoader加載GUID標(biāo)識的ActiveX控件,負責(zé)在AxPlugin與ActiveX控件間轉(zhuǎn)換和傳輸消息。
UA偽裝和JavaScript代碼注入需要瀏覽器的支持,本文以MiniBrowser瀏覽器作為實驗瀏覽器。MiniBrowser瀏覽器為在WebKitGTK+上實現(xiàn)的簡單瀏覽器,WebKitGTK+為WebKit內(nèi)核的GTK+移植(port)。
UA偽裝需要記錄網(wǎng)頁地址與UA的映射關(guān)系,因此采取了白名單機制。白名單格式為JSON,每條記錄含有兩個值,分別為網(wǎng)頁地址與UA。白名單中的網(wǎng)頁地址的形式如“https://*.demo.com/*”,支持通配符。白名單中的UA為不同版本IE瀏覽器的UA。在WebKit內(nèi)核中,WebView為網(wǎng)頁視圖,站在用戶的角度,WebView相當(dāng)于瀏覽器中的標(biāo)簽(tab)。在WebView向服務(wù)器請求網(wǎng)頁前,若用戶訪問的網(wǎng)頁地址與白名單中的網(wǎng)頁地址匹配,則獲取WebView的WebKitSettings,將其UA設(shè)置為白名單中相應(yīng)的UA,從而實現(xiàn)UA偽裝,流程如下:
WebKitSettings *settings = webkit_web_view_get_settings(web_view);
if (address matched)
//地址匹配,偽裝UA
webkit_settings_set_user_agent(settings, user_agent in whitelist);
else
//地址不匹配,還原為默認UA
webkit_settings_set_user_agent(settings, default user_agent);
webkit_web_view_load_uri(web_view, address);
//向服務(wù)器請求網(wǎng)頁
WebKit內(nèi)核具有JavaScript代碼注入接口,主要為瀏覽器擴展提供支持。注意,擴展(extension)與插件(plugin)不同,擴展為HTML與JavaScript代碼的集合,它可以包含插件[12]。在WebKitUserContentManager中設(shè)置WebKitUserScript,并在WebView初始化時傳入,WebKitUserScript含有的JavaScript代碼將在網(wǎng)頁解析時執(zhí)行,從而實現(xiàn)JavaScript代碼注入。注入的JavaScript代碼如下:
function listener(event) {
//事件監(jiān)聽函數(shù)
var node = event.target;
if (node.nodeName == ″OBJECT″) {
//若為
set type to ″application/x-axplugin″
set clsid to GUID
remove classid
remove codebase
}
}
window.addEventListener(″beforeload″, listener, true);
//為beforeload事件設(shè)置監(jiān)聽函數(shù)
上述代碼利用WebKit內(nèi)核支持的beforeload事件來改變ActiveX控件標(biāo)簽,使瀏覽器加載AxPlugin。網(wǎng)頁中每一個標(biāo)簽加載前將觸發(fā)beforeload事件。在beforeload事件監(jiān)聽函數(shù)中,向ActiveX控件標(biāo)簽添加type,其值為AxPlugin的MIME,設(shè)置為“application/x-axplugin”。classid在非IE瀏覽器中具有其他含義,因此將classid轉(zhuǎn)換為clsid,移除codebase。JavaScript代碼注入仍然需要白名單機制,防止JavaScript代碼注入影響其他網(wǎng)頁或者帶來不必要的性能開銷。
消息傳輸分為數(shù)據(jù)傳輸和函數(shù)調(diào)用轉(zhuǎn)發(fā)兩個方面的工作。AxPlugin加載后初始化管道并創(chuàng)建子進程,子進程將調(diào)用execvp轉(zhuǎn)而執(zhí)行AxLoader。在消息機制中,實現(xiàn)了Read和Write兩組函數(shù)來負責(zé)AxPlugin和AxLoader間的數(shù)據(jù)傳輸,分別用于從管道中讀出或?qū)懭隒++數(shù)據(jù)類型以及NPAPI數(shù)據(jù)類型。如字符串“Hello”,編碼后向管道中寫入的字節(jié)流如圖2所示。
圖2 “Hello”字符串編碼后的字節(jié)流
第一個字節(jié)代表命令類型,由Command枚舉值定義,圖中為Command::PushString的數(shù)值。其后三個字節(jié)為傳輸數(shù)據(jù)的長度,最大值為0xFFFFFF,圖中為“Hello”(含‘ 古浪县| 临清市| 原阳县| 肇源县| 镇雄县| 青田县| 富顺县| 什邡市| 毕节市| 泾源县| 井陉县| 东光县| 漳浦县| 滨海县| 榆社县| 前郭尔| 太和县| 调兵山市| 开化县| 澎湖县| 习水县| 南澳县| 肇州县| 定西市| 西峡县| 马公市| 聊城市| 财经| 双桥区| 遵化市| 枝江市| 定边县| 安新县| 芦溪县| 兴业县| 西吉县| 西盟| 顺平县| 斗六市| 宾阳县| 磐安县|