版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)
文檔簡介
理解WEBKIT和CHROMIUM目錄\h基礎(chǔ)篇\hWebKit,WebKit2,Chromium和Chrome介紹\hWebKit和Blink\hWebKit和Chromium代碼目錄結(jié)構(gòu)介紹\hWebKit和Chromium功能模塊\hChromium界面(UI)\hChromium多進(jìn)程模型\hChromium的多線程機制\h消息循環(huán)\h頁面渲染的基本過程\hHTML解析和DOM\hCSS基礎(chǔ)\hWebKit渲染基礎(chǔ)\h渲染主循環(huán)(mainloop)和requestAnimationFrame\hWebKit資源加載機制\hChromium的多進(jìn)程資源加載機制\hChromium網(wǎng)絡(luò)棧\hChromium資源磁盤緩存\hChromium插件和擴展基礎(chǔ)\h高級篇\hChromium軟件渲染\hChromium的GPU硬件加速\hChromium硬件加速合成\h硬件加速之RenderLayer樹到合成樹\hCanvas2D及其實現(xiàn)\hWebGL及其實現(xiàn)\hWebKit的CSS實現(xiàn)\hWebKit布局(Layout)\h插件機制(NPAPIPlugin)\hContentAPI和CEF3\hChromium移動版(ChromiumforMobile:Android&iOS)\hChromiumforAndroid\h基于Chromium內(nèi)核的AndroidWebView\hWeb應(yīng)用和Web運行環(huán)境
基礎(chǔ)篇根據(jù)這些專題所涉及的內(nèi)容,大概把它們分為三個部分,第一個部分是基礎(chǔ)篇,第二部分是高級篇,第三部分是開放篇。WebKit,WebKit2,Chromium和Chrome介紹概述在介紹本系列各個專題之前,有必要先解釋一下極其容易混淆的幾個概念,它們是WebKit,WebKit2,Chromium和Chrome。首先來了解WebKit。廣義上來說,WebKit是一個開源的項目,其前身是來源于KDE的KHTML和KJS。該項目專注于網(wǎng)頁內(nèi)容的展示,開發(fā)出一流的網(wǎng)頁渲染引擎。它不是瀏覽器,而且也不想成為瀏覽器。該項目包含兩個部分,第一是WebCore,其中包含了對HTML,CSS等很多W3C規(guī)范的實現(xiàn);第二部分就是狹義上的WebKit,它主要是各個平臺的移植并提供相對應(yīng)的Web接口,也就是WebView或者類似WebView,這些接口提供操作和顯示網(wǎng)頁的能力。目前使用WebKit的主流的瀏覽器或者WebView包括Chrome,Safari,QtWebKit,AndroidBrowser以及眾多的移動平臺的瀏覽器。WebKit2相對于狹義上的WebKit而言,它不是WebKit簡單的第二個版本,它是一個新的API層,其最主要的變化在于將網(wǎng)頁的渲染置于單獨的進(jìn)程,而接口層則在另外一個進(jìn)程,它們之間通過IPC來通訊。對于接口的調(diào)用者來說,中間的IPC和底下的實現(xiàn)是透明的,這樣做的好處有很多,一個很明顯的好處是,當(dāng)網(wǎng)頁的渲染出現(xiàn)問題時,不會阻礙Web接口的調(diào)用者進(jìn)程,這會在很大程度上解決或者幫助解決瀏覽器或者這些調(diào)用者的穩(wěn)定性和安全性等問題。Chromium是一個建立在WebKit之上的瀏覽器開源項目,由Google發(fā)起的。該項目被創(chuàng)建以來發(fā)展迅速,很多先進(jìn)的技術(shù)被采用,如跨進(jìn)程模型,沙箱模型等等。同時,很多新的規(guī)范被支持,例如WebGL,Canvas2D,CSS3以及其他很多的HTML5特性,基本上每天你都可以看到它的變化,它的版本升級很快。在性能方面,其也備受稱贊,包括快速啟動,網(wǎng)頁加載迅速等。Chrome是Google公司的瀏覽器產(chǎn)品,它基于chromium開源項目,一般選擇穩(wěn)定的版本作為它的基礎(chǔ),它和chromium的不同點在于chromium是開源試驗場,會嘗試很多新的東西,當(dāng)這些東西穩(wěn)定之后,chrome才會集成進(jìn)來,這也就是說chrome的版本會落后于chromium。另外一個就是,chrome里面會加入一些私有的codec,這些僅在chrome中才會出現(xiàn)。再次,chrome還會整合Google的很多服務(wù),最后chrome還會有自動更新的功能,這也是chromium所沒有的。參考文獻(xiàn)//wiki/WebKit2WebKit和Blink關(guān)注Web和HTML5領(lǐng)域的人最近應(yīng)該都有了解WebKit項目的重磅消息,那就是Google退出WebKit項目,創(chuàng)建自己的渲染引擎Blink。這其實不能說完全沒有先兆,合合分分,純屬正常。其實,之前關(guān)于WebKit2,雙方的爭論就非常的大。Apple希望它可以隨便加入和刪除代碼而無需擔(dān)心它會破壞其它Ports的代碼,這遭到很多人的反對和不滿。同時,另一方面,Google有很多新的功能希望加入WebKit中,但是WebKit可能并不認(rèn)可他們。雙方分歧越來越多,終于分道揚鑣。這里面有個誤區(qū),就是Google的Blink是一個全新的引擎。其實不是這樣,Blink目前就是從WebKit直接復(fù)制出一個版本出來,然后將與chromium無關(guān)的Ports全部移除掉,將代碼結(jié)構(gòu)重新整理,就目前而言,Blink的渲染和WebKit是一樣,但是,以后兩者將各自走不同的路。這有點類似于之前WebKit從KHTML中復(fù)制出來一樣,歷史總是驚人的相似。目前參與Blink和Chromium大致一樣,擁有Chromium的commit權(quán)限對Blink也適用。原來一些WebKit的committer和reviewer也開始成為blink的committer。它的提交代碼流程,review流程等都是chromium的風(fēng)格,這對chromium的開發(fā)者來說非常熟悉。Blink從WebKit繼承而來,那么未來它會在哪些方面做改變呢?根據(jù)chromium官方的說法,目前大概有兩個比較大的,后面應(yīng)該有更多的改變:跨進(jìn)程的iframe(out-of-processiframes):為iframes內(nèi)容創(chuàng)建單獨的沙箱進(jìn)程來渲染它們將DOM移入JavaScript中,這樣JavaScript可以更快的訪問DOM今后,Blink會和WebKit差別越來越大,對Web標(biāo)準(zhǔn)支持也不盡相同,未來的發(fā)展如何,讓我們拭目以待吧。順便插一句,以后可能要改這個系列的標(biāo)題了,呵呵。參考資料/blinkWebKit和Chromium代碼目錄結(jié)構(gòu)介紹WebKit和Chromium的代碼量很大(這兩個項目都是幾百萬行代碼的級別,不包括它們依賴的第三方庫),讀起來是相當(dāng)?shù)牟蝗菀住5橇己玫拇a組織結(jié)構(gòu)很好的幫助了開發(fā)者和學(xué)習(xí)者們,下面大致介紹一下它們的目錄結(jié)構(gòu)及其用處,方便了解和學(xué)習(xí),進(jìn)而快速地理解整個項目。因為目錄實在太多,所以這里介紹其中主要的部分。先來看看WebKit。(WebKit項目在chromium中的目錄是src/third_party/WebKit)再來看看Chromium。WebKit和Chromium功能模塊在“WebKit,WebKit2,Chromium和Chrome介紹”中,大致了解了WebKit是一個渲染引擎,Chromium是一個瀏覽器,它們那么分別包含哪些不同的功能模塊?它們是如何劃分地?本章節(jié)來為大家詳細(xì)解讀一下。WebKit:HTML解析:負(fù)責(zé)HTML語言的解析CSS解析:負(fù)責(zé)CSS的解析工作圖片解碼:支持不同編碼格式的圖片JavaScript引擎:JavaScript語言的解析引擎,缺省的是JavaScriptCore,但是目前Google的V8JavaScript被廣泛使用正則表達(dá)式布局:負(fù)責(zé)布局(layout)的計算和更新工作文檔對象模型(DOM):DOM是W3C定義的對象模型,該部分負(fù)責(zé)DOM樹及其相應(yīng)的接口渲染:與渲染相關(guān)的基礎(chǔ)設(shè)施,例如渲染樹,渲染層次樹等等SVG:對SVG的支持XML解析:XML語言的解析XSLT:XSLT語言的解析執(zhí)行URL解析器:URL規(guī)范的解析Unicode編解碼器:各種編碼解碼工作移植:WebKit中比較大的一部分,因為WebKit要工作需要不同平臺上有具體的實現(xiàn),因而不同的移植有不同的實現(xiàn)。chromium的移植很復(fù)雜,因為其支持跨平臺,所以它的移植需要在windows,linux和mac上工作。由上面的模塊大致可以WebKit主要是跟網(wǎng)頁的解析和渲染相關(guān)的工作,其不涉及瀏覽器的歷史,書簽,下載,cookie管理等等方面的工作。Chromium:Cookie管理器:cookie生命周期的管理歷史管理器:歷史記錄的管理密碼管理器:網(wǎng)頁中密碼登錄信息管理窗口管理:多個Tab窗口的管理和切換地址欄:地址欄功能,智能地址填充與書簽的協(xié)同工作安全瀏覽黑名單管理:安全瀏覽機制網(wǎng)絡(luò)棧:與網(wǎng)絡(luò)傳輸相關(guān)的工作,其有很多創(chuàng)新的東西SSL/TLS:網(wǎng)絡(luò)傳輸安全磁盤緩存:磁盤緩存頁面及其替換策略等生命周期的管理下載管理器:管理下載相關(guān)粘帖板:clipboard的功能書簽管理:書簽的組織和管理URL解析器:同WebKitUnicode編解碼器:同WebKitChromium主要是實現(xiàn)瀏覽器相關(guān)的功能,如上面中的網(wǎng)絡(luò)棧等等。其實以上只是一些瀏覽器基本功能,chromium實現(xiàn)的遠(yuǎn)不止這些,這其中包含沙箱模型,NaCl,擴展機制,硬件加速架構(gòu)等等。這些我們將在之后的章節(jié)中逐一介紹它們。URL解析器和Unicode編解碼器在兩者中都存在是因為它們都要使用到。Chromium界面(UI)Chromium的界面相當(dāng)簡潔,這是她的設(shè)計理念。大體上可以把界面分成兩個主要部分:網(wǎng)頁內(nèi)容和外邊的修飾控件(例如,tab管理,工具欄,設(shè)置按鈕等)。整個chromium瀏覽器是個頂層窗口。每個tab都對應(yīng)一個頂層窗口的子窗口,每個網(wǎng)頁內(nèi)容都會繪制在一個子窗口中。當(dāng)然這個是現(xiàn)有的窗口結(jié)構(gòu),但在新的views框架中,窗口將會被移除,詳細(xì)的后面有專門介紹。Chromium界面另一個主要的控件是設(shè)置按鈕,里面包含了所有有關(guān)chromium屬性設(shè)置的部分。值得一提的是,里面有很多設(shè)置界面都是由HTML來撰寫的,而不是傳統(tǒng)的語言,例如c/c++。這很大程度上得益于chromium的擴展機制及其提供的API,這會在擴展章節(jié)詳細(xì)介紹。大家可能會覺得chromium界面簡潔,用戶或者能看到的瀏覽器信息有限,其實不然。嘗試在地址欄里輸入chrome://chrome-urls/,你會看到很多的chrome地址。這些地址提供給用戶或者開發(fā)者關(guān)于瀏覽器的豐富的信息,可以說是包羅萬象,你能想象的信息基本都能從這里看到。這些信息其實非常的有用,特別對于理解chromium的內(nèi)部機制非常有幫助,很多細(xì)節(jié)我們會在后面的章節(jié)中逐一揭露。下面節(jié)選自chrome://chrome-urls/的輸出:ListofChromeURLschrome://appcache-internalschrome://blob-internalschrome://bookmarkschrome://cachechrome://chrome-urlschrome://crasheschrome://creditschrome://dnschrome://downloadschrome://extensionschrome://flagschrome://flash......Chromium多進(jìn)程模型概述相信你一定有這樣的經(jīng)歷:打開很多個頁面,不幸的是其中某個頁面不響應(yīng)了或者崩潰了,隨之而來的是更不幸的事,所有頁面都不響應(yīng)或者都崩潰了。最讓人崩潰的是其中一些頁面還有未保存或者未發(fā)送的信息!這絕對是不堪回首的過去。但是,現(xiàn)在好了,現(xiàn)代瀏覽器很多都支持多進(jìn)程模型,這個模型可以很好地避免上面的問題,雖然它很復(fù)雜而且也有自身的問題,例如更多的資源消耗,但是它的優(yōu)勢也是非常明顯地。chromium的多進(jìn)程架構(gòu)至少帶來三點好處,其一是避免單個頁面的不響應(yīng)或者奔潰影響整個瀏覽器的穩(wěn)定性;其二是當(dāng)?shù)谌讲寮紳r候不會影響頁面或者瀏覽器的穩(wěn)定性;其三是方便了安全模型的實施,也就是說沙箱模型是基于多進(jìn)程架構(gòu)的。其實,這很大程度上也是WebKit2產(chǎn)生的原因。那么,這是怎么做到的呢?下圖給出了缺省的chromium瀏覽器的進(jìn)程模型。方框代表進(jìn)程,連接線代表IPC進(jìn)程間通信。通常來講,chromium瀏覽器包括以下主要進(jìn)程類型:Browser進(jìn)程:瀏覽器的主進(jìn)程,負(fù)責(zé)瀏覽器界面的顯示,各個頁面的管理,其他各種進(jìn)程的管理;Render進(jìn)程:頁面的渲染進(jìn)程,負(fù)責(zé)頁面的渲染工作,WebKit的工作主要在這個進(jìn)程中完成;NPAPI插件進(jìn)程:每種類型的插件只會有一個進(jìn)程,每個插件進(jìn)程可以被多個Render進(jìn)程共享;GPU進(jìn)程:最多只有一個,當(dāng)且僅當(dāng)GPU硬件加速打開的時候才會被創(chuàng)建,主要用于對3D加速調(diào)用的實現(xiàn);Pepper插件進(jìn)程:同NPAPI插件進(jìn)程,不同的是為Pepper插件而創(chuàng)建的進(jìn)程Chromium瀏覽器的進(jìn)程模型,包括以下特征:browser進(jìn)程和頁面是分開的,這保證了頁面的奔潰不會導(dǎo)致瀏覽器主界面的奔潰;每個頁面是獨立的進(jìn)程,這保證了頁面之間相互不影響;插件進(jìn)程也是獨立的,插件的問題不會影響瀏覽器主界面和頁面;GPU硬件加速進(jìn)程也是獨立的。因為這么多的進(jìn)程,開發(fā)者通常需要知道進(jìn)程列表中的進(jìn)程類別,這很簡單,可以通過進(jìn)程的命令行參數(shù)"--type"來識別。有趣的是,就在我寫下上面這段文字的時候,我的chrome瀏覽器的flash插件崩潰了,幸運的是其他一切都很好,感謝chrome的多進(jìn)程模型!模型的類型其實介紹了進(jìn)程模型,其實Chromium支持多種進(jìn)程模型,特別是對頁面而言,下面簡單的介紹以下模型的類型:Process-per-site-instance該類型的含義是對同一個域的實例都會創(chuàng)建獨立的進(jìn)程。舉個例子來講,例如,用戶訪問了milado_nju的CSDN博客(我的博客),然后從個人主頁打開多篇文章時,每篇文章的頁面都是該域的一個實例,因而它們都共享同一個的進(jìn)程。如果新打開CSDN博客的主頁,那么就是另一個實例,會重新創(chuàng)建進(jìn)程來渲染它。這帶來的好處是每個頁面互不影響,壞處自然是資源的巨大浪費。Process-per-site該類型的含義是不同一個域會創(chuàng)建獨立的進(jìn)程,同一域的不同實例共享同一個進(jìn)程。好處是對于不同的域可以共享,相對較小的內(nèi)存消耗,壞處是可能會有特別大的Renderer進(jìn)程??梢栽诿钚屑尤?yún)?shù)--process-per-site來嘗試它。Process-per-tab該類型的含義是為每個標(biāo)簽頁創(chuàng)建一個獨立的進(jìn)程,這也是chrome/chromium的缺省行為Singleprocess該類型的含義是不為頁面創(chuàng)建任何獨立的進(jìn)程,所有渲染工作都在browser進(jìn)程中。但是這個類型只是實驗性質(zhì)的,不穩(wěn)定,因而不推薦使用,只有在比較單進(jìn)程和多進(jìn)程時候比較有用,可以在命令行加入?yún)?shù)--single-process來嘗試它。沙箱模型在頁面的多進(jìn)程模型中,頁面的渲染是運行在沙箱模型中的Render進(jìn)程中實現(xiàn)的,這些渲染引擎沒有訪問本地資源的能力(例如文件系統(tǒng),窗口系統(tǒng),等等),這可以保護(hù)渲染引擎被入侵。參考文獻(xiàn)/developers/design-documents/process-modelsChromium的多線程機制概述前面我們介紹過Chromium是基于多進(jìn)程模型的架構(gòu)設(shè)計,那么各個進(jìn)程內(nèi)的情況呢?事實是每個進(jìn)程都有很多的線程,特別是browser進(jìn)程,因而它也基于多線程模型的。介紹多線程機制之前,先來看一下殘酷的現(xiàn)實吧,下面是各個進(jìn)程的線程信息情況(基于Linux平臺,其它平臺的可能略有不同),相信保證讓你頭大。是的,你需要泡杯茶,然后靜下心來了解一下它們:為什么這么多的線程呢?Chromium的官方說法告訴我們,主要目的就是為了保持UI的高響應(yīng)度,保證UI線程(chrome線程,主線程)不會被任何其它費時的操作阻礙從而影響了對用戶的響應(yīng)。這些其它的操作很多,例如本地文件讀寫,socket讀寫,數(shù)據(jù)庫操作等等。既然它們會阻礙其它操作,那好,把它們放在單獨的線程里自己忙或者等待去吧,所以你就看到那么多與這些相關(guān)的線程(線程名顯然也暴露了這一切)。問題來了,它們之間如何通信和同步呢?這是多線程的一個非常難纏的問題,因為這會造成死鎖或者競爭沖突等問題。Chromium精心設(shè)計了一套機制來處理它們,那就是絕大多數(shù)的場景使用事件和一種chromium新創(chuàng)建的任務(wù)傳遞機制,僅在非用不可的情況下使用鎖或者線程安全對象,這有嚴(yán)格的要求,詳細(xì)的情況請查看以下鏈接以便作近一步的了解:/developers/lock-and-condition-variable。問題又來了,那么每個線程內(nèi)部是如何處理這些事件和任務(wù)的呢?答案是MessageLoop。每個線程會有一個自己的MessageLoop,它們用來處理這些事件和任務(wù)。通常的MessageLoop只是處理事件,Chromium中的MessageLoop可以同時處理事件和任務(wù)。MessageLoop也是值得研究的,詳細(xì)情況我們將在以后的一章加以介紹。任務(wù)和MessageLoop的基本原理如下圖所示。任務(wù)被派發(fā)到進(jìn)程的某個線程的MessageLoop的隊列中,MessageLoop會調(diào)度執(zhí)行這些Task。關(guān)于上面這些線程,多數(shù)可以通過它們的名字猜出用途,這里鑒于篇幅和噪音考慮,不一一介紹,下面說明幾個重要和詭異的線程:chrome線程:進(jìn)程的主線程,browser進(jìn)程重要主要是負(fù)責(zé)UI,當(dāng)然也是管家;Renderer進(jìn)程中則是管家兼處理WebKit渲染的;gpu進(jìn)程中則是負(fù)責(zé)處理處理繪圖請求并調(diào)用openGL進(jìn)行繪制工作的。Chrome_IOThread/Chrome_ChildIOThread線程:用來接受來自其它進(jìn)程的IPC消息和派發(fā)自身消息到其它進(jìn)程。SignalSender線程:V8JavaScript引擎中用于處理Linux信號的線程。任務(wù)(task)Chromium的特色就是在事件的基礎(chǔ)上,加入了一個新的機制-任務(wù)。當(dāng)需要執(zhí)行某個操作時候,可以把該操作封裝成一個任務(wù),由任務(wù)派發(fā)機制傳遞給相應(yīng)的進(jìn)程的MessageLoop。下面看看線程內(nèi)和線程間分別是如何操作的。首先看線程內(nèi)部是如何進(jìn)行的。但你需要進(jìn)行費時的操作時候,可以派發(fā)一個事件和回調(diào)函數(shù)給自身線程的MessageLoop,然后MessageLoop會調(diào)度該回調(diào)函數(shù)以執(zhí)行其操作。問題是這有必要嗎?直接調(diào)用不就可以嗎?答案是不可以,或者說是最好不要這么做,其原因在于,如果當(dāng)前的MessageLoop里面有優(yōu)先級更高的事件和任務(wù)需要處理時,你這樣做會阻礙它們的執(zhí)行。其次看一看線程間通信。假如一個線程A需要把任務(wù)傳遞給一個另外的線程B,大致有三個階段:首先,線程A把該任務(wù)傳遞給線程B;其次,線程B調(diào)度執(zhí)行該任務(wù);最后,線程B執(zhí)行完任務(wù)后回復(fù)A。很多情況下,線程A不需要回復(fù)。下圖描述的是一個帶回復(fù)的典型的任務(wù)傳遞過程。如果不需要回復(fù),那么上圖中的‘ReplyTask2’就不需要了。當(dāng)需要回復(fù)時候,chromium的做法是新建一個新的任務(wù),該任務(wù)封裝原來的任務(wù),新任務(wù)被放入線程B,B執(zhí)行新任務(wù)時候調(diào)用其Run方法,里面首先執(zhí)行原來的任務(wù),然后派發(fā)Reply任務(wù)給線程A,操作完成。后面會有一個具體的例子來描述上面這個過程,下面了解一下chromium支持Task所涉及的幾個主要類。Callback:回調(diào)類,其本質(zhì)是封裝了一個由調(diào)用者(例如線程A)設(shè)置的回調(diào)函數(shù)。包含一個run方法,當(dāng)MessageLoop調(diào)度執(zhí)行該Task時候,運行該方法,該方法調(diào)用回調(diào)函數(shù)Closure:定義為Callback<void(void)>tracked_objects:一系列的類用于追蹤Task生成位置,編譯調(diào)試Task:包含一個Closure,追蹤信息,表示一個任務(wù)一個例子下面用一個實際的例子來理解線程間是如何傳遞任務(wù)的。該例子是chromium中非常常見的一個場景:browser進(jìn)程接收到來自renderer進(jìn)程的IPC消息,它由IO線程接收管理,然后派發(fā)給chrome線程處理,具體過程如下圖所示:上面的圖基本上對應(yīng)了前面的關(guān)于任務(wù)派發(fā)過程的圖,只是少了回復(fù)環(huán)節(jié),這是因為IO線程并不需要回復(fù)?;镜牟襟E是,當(dāng)IO線程收到消息后,其派發(fā)一個任務(wù),該任務(wù)將ChannelProxy::Context::OnDispatchMessage設(shè)置會回調(diào)函數(shù),這個任務(wù)保存在chrome線程的輸入任務(wù)隊列中。chrome線程將輸入隊列拷貝到工作隊列后,執(zhí)行該任務(wù)的run方法,該方法會調(diào)用回調(diào)函數(shù)ChannelProxy::Context::OnDispatchMessage,該函數(shù)會調(diào)用事件的處理函數(shù),這里也就是RenderProcessHostImpl::OnMessageReceived,這個函數(shù)實際上會根據(jù)事件類型來調(diào)用各個相應(yīng)函數(shù)。源文件目錄base/threading/
線程相關(guān)的基礎(chǔ)類
參考文獻(xiàn)/developers/design-documents/threading/archives/478/developers/lock-and-condition-variable消息循環(huán)概述前面介紹了線程間如何傳遞chromium自定義任務(wù)(task),那么在線程內(nèi),消息循環(huán)(messageloop)是如何處理這所有的消息和任務(wù)呢?本章節(jié)重點介紹消息循環(huán)的工作原理。在Chromium里,需要處理三種類型的消息:chromium自定義的任務(wù),Socket或者文件等IO操作以及用戶界面(UI)的消息。這里面,chromium自定義任務(wù)是平臺無關(guān)的,而后面兩種類型的消息是平臺相關(guān)的?;貞浺幌虑懊娑嗑€程模型章節(jié)中列舉的眾多線程,例如主線程(UI線程)需要處理UI相關(guān)的消息和自定義任務(wù);IO線程則需要處理Socket和自定義任務(wù);History線程則只需要處理自定義任務(wù);其它線程需要處理消息類型不會超出以上三個線程。根據(jù)三個組合,chromium定義和實現(xiàn)三種相對應(yīng)的類來支持它們,下面讓我們來看看具體的實現(xiàn)吧。Chromium中主要的類這是本章中最重要的三個基類,依次介紹它們:類RunLoop:一個輔助類,主要封裝消息循環(huán)MessageLoop類,其本身沒有特別的功能,主要提供一組公共接口被調(diào)用,其實質(zhì)是調(diào)用MessageLoop類的接口和實現(xiàn)。類MessageLoop:主消息循環(huán),原理上講,它應(yīng)該可以處理三種類型的消息,包括支持不同平臺的消息。事實上,如果讓它處理所有這些消息,這會讓其代碼結(jié)構(gòu)復(fù)雜不清難以理解。因此,根據(jù)上面對各個線程的分析,消息循環(huán)也只需要三種類型,一種僅能處理自定義任務(wù),一種能處理自定義任務(wù)和IO操作,一種是能處理自定義任務(wù)和UI消息。很自然地,Chromium定義一個基類MessageLoop用于處理自定義任務(wù),兩個子類對應(yīng)于第二和第三種類型。如下圖所示。對于第二和第三種MessageLoop類型,它們除了要處理任務(wù)外,還要處理平臺相關(guān)的消息,為了結(jié)構(gòu)清晰,chromium定義一個新的基類及其子類來負(fù)責(zé)處理它們,這就是MessagePump。MessagePump的每個子類針對不同平臺和不同的消息類型。事實上,不僅如此,消息處理的主循環(huán)也在MessagePump中,這有些令人意外,后面會詳細(xì)介紹。因此,MessageLoop通過實現(xiàn)MessagePumpDelegate的接口來負(fù)責(zé)處理Chromium自定義任務(wù)。如下圖所示。類MessagePump:一個抽象出來的基類,可以用來處理第二和第三種消息類型。對于每個平臺,它們有不同的MessagePump的子類來對應(yīng),這些子類被包含在MessageLoopForUI和MessageLoopForIO類中。結(jié)合上面的圖,針對三種類型的消息循環(huán),三種典型的線程在不同平臺所使用的類如下:對于所有平臺來說,History線程所使用的類是一樣的;UI線程和IO線程分別對應(yīng)不同的MessagePump。相信大家注意到了,最后一個平臺Android跟其它的稍有不同,那就是它的UI線程,原因在于主循環(huán)在Java層,用戶界面的事件派發(fā)機制都在Java代碼來處理,因而需要在Android的消息循環(huán)機制中加入對自定義任務(wù)的處理,后面會作介紹。無限循環(huán)這里面還有一個重要的部分需要介紹,那就是消息循環(huán)的主邏輯。該循環(huán)本質(zhì)就是一個無限循環(huán),不停的處理消息循環(huán)接收到的任務(wù)和消息,直到需要推出為止。如前面所述,主消息循環(huán)的邏輯在MessagePump中,而MessageLoop只是負(fù)責(zé)處理自定義的任務(wù),MessagePump通過調(diào)用MessagePumpDelegate來達(dá)到該目的。下面是IO線程的消息處理主邏輯的時序圖。前面章節(jié)我們介紹過MessageLoop有任務(wù)隊列來保存需要處理的任務(wù),這些任務(wù)可能有不同的優(yōu)先級,例如需要即時處理,或者延遲處理,或者Idle時處理。上圖中,當(dāng)調(diào)用DoWork時候,首先將出入任務(wù)隊列(incomingtask)拷貝到工作任務(wù)隊列中,然后依次執(zhí)行該隊列中的任務(wù)。之后,同理處理調(diào)用DoDelayWork和DoIdleWork。當(dāng)這些處理完后,它會阻塞和等待在IO操作。對于Android的UI線程來說,情況稍有不同,因為主循環(huán)邏輯是由Java層的Android.os.Looper來控制的,所以MessagePumpAndroid其實是被Looper調(diào)用的。當(dāng)它被調(diào)用時,其會把執(zhí)行任務(wù)的工作交給MessagePumpDelegate,這里也就是MessageLoopForUI來完成,如下圖所示的UI線程的消息循環(huán)主邏輯。Java層的SystemMessageHandler和C++層的MessagePumpAndroid其實都是輔助Looper調(diào)用執(zhí)行chromium的自定義類的。最后還有一個問題,就是如何等待自定義的任務(wù)。假設(shè)現(xiàn)在MessageLoop沒有任務(wù)和消息需要處理,它應(yīng)該等待Socket或者IO或者任務(wù),OS系統(tǒng)支持Socket和IO喚醒它,問題是如何等待自定義任務(wù)呢?總不能忙式的檢查吧,那樣太耗費資源了。Chromium設(shè)計了一個巧妙的方法來解決該問題,以MessagePumpLibEvent為例:在Linux平臺上,該類創(chuàng)建一個管道,它等待讀取這個管道的內(nèi)容,當(dāng)有自定義的新任務(wù)到來時,寫入一個字節(jié)到這個管道,從而MessageLoop被喚醒,非常地簡單和直接。源文件目錄base/message_loop.h|cc
base/message_pump.h|cc
消息循環(huán)相關(guān)的眾多類基本上都位于該目錄中,文件名以”message_”開頭
base/android/java/src/org/chromium/base/SystemMessageHandler.java
Android平臺下支持消息循環(huán)的輔助類
參考文獻(xiàn)/archives/478頁面渲染的基本過程概述前面介紹了一些渲染引擎的功能,包括網(wǎng)絡(luò),資源加載,DOM樹,RenderObject樹等等,但是,給人以零亂的感覺,因為沒有一個整體的過程描述它們在這個過程中的位置,它們只是整個渲染引擎工作的一個或者多個步驟而已。渲染引擎的主要目的就是從一個網(wǎng)頁的URL開始,經(jīng)過一系列的復(fù)雜處理過程之后,變成一個可視化的結(jié)果,這一過程就是這里所說的頁面渲染的基本過程。所謂的渲染,就是根據(jù)描述或者定義構(gòu)建數(shù)學(xué)模型,通過模型生成圖像的過程。瀏覽器的渲染引擎就是能夠?qū)TML/CSS/JavaScript轉(zhuǎn)換成圖像結(jié)果的模塊,如下圖所示,輸入是URL對應(yīng)的各種資源,輸出是可視化的圖像。從這里看,非常的簡單和容易理解。渲染模塊那么渲染引擎提供了哪些功能模塊來支持頁面渲染的呢?下圖是一個渲染引擎所包含的基本功能和它們依賴的一些第三方庫。從圖中大致可以看出,一個渲染引擎大致包括HTML解釋器,CSS解釋器,布局和JavaScript引擎。下面依次來描述它們:HTML解釋器:解釋HTML語言的解釋器,本質(zhì)是將HTML文本解釋成DOM(文檔對象模型)樹。CSS解釋器:解釋樣式表的解釋器,其作用是將DOM中的各個元素對象加上樣式信息,從而為計算最后結(jié)果的布局提供依據(jù)。布局:DOM之后,需要將其中的元素對象同樣式信息結(jié)合起來,計算它們的大小位置等布局信息,形成一個能夠表示這所有信息的內(nèi)部表示模型。JavaScript引擎:JavaScript可以修改網(wǎng)頁的內(nèi)容,也能修改CSS的信息,JavaScript引擎解釋JavaScript代碼并把代碼的邏輯和對DOM和CSS的改動信息應(yīng)用到布局中去,從而改變渲染的結(jié)果。這些模塊依賴很多其他的基礎(chǔ)模塊,這其中包括網(wǎng)絡(luò),存儲,2D/3D圖形,音頻視頻和圖片解碼器等。實際上,渲染引擎中還應(yīng)該包括如何使用這些依賴模塊的部分,這部分的工作其實并不少,因為需要使用它們來高效的渲染網(wǎng)頁。例如,利用2D/3D圖形庫來實現(xiàn)高性能的網(wǎng)頁繪制和網(wǎng)頁的3D渲染,這個實現(xiàn)非常非常的復(fù)雜。最后,當(dāng)然,在最下面,依然少不了操作系統(tǒng)的支持,例如線程支持,文件支持等等?;具^程了解模塊之后,下面就是這些模塊如何組織以達(dá)成渲染過程的。一般地,一個典型的渲染過程下圖所示,這是渲染引擎的核心過程,一切都是圍繞著它來的。下面逐個從左至右來解釋上圖中的這一過程。這一過程的先后關(guān)系由圖中的實線箭頭表示。左上角開始,首先是網(wǎng)頁內(nèi)容,送到HTML解釋器。HTML解釋器在解釋它后形成DOM樹,中間如果遇到JavaScript代碼則交給JavaScript引擎去處理。如果頁面包含CSS,則交給CSS解釋器去解析。當(dāng)DOM建立的時候,接受來自CSS解釋的樣式信息,構(gòu)建一個新的內(nèi)部繪圖模型。該模型由布局模塊計算模型內(nèi)部的各個元素的位置和大小信息,最后由繪圖模塊完成從該模型到圖像的繪制。最后解釋圖中虛線箭頭的指向含義。它們表示在渲染過程中,每個階段可能使用到的其他模塊。在網(wǎng)頁內(nèi)容的下載中,需要使用到網(wǎng)絡(luò)和存儲,這個是顯而易見地。但計算布局和繪圖的時候,需要使用2D/3D的圖形模塊,同時因為要生成最后的可視化結(jié)果,這時候需要開始解碼音頻視頻和圖片,同其它內(nèi)容一起繪制到最后的圖像中。在渲染完成之后,用戶可能需要跟渲染的結(jié)果進(jìn)行交互,或者網(wǎng)頁自身有動畫,一般而言,這會持續(xù)的重新渲染過程。這個過程跟上面類似,不再贅述。參考資料/2013/webkit-for-developers/HTML解析和DOM概述前面介紹了很多眼花繚亂的新技術(shù),關(guān)于渲染,關(guān)于硬件加速,關(guān)于布局,關(guān)于其他很多,同大家一樣,我也花了很多時間來消化它們。本章介紹稍微基礎(chǔ)些的話題(本系列的寫作順序完全是隨心所欲地),就是在渲染整個過程的初始階段HTML解析。不過這不表示它簡單,其實這里是非常繞人的。在前面描述渲染過程,其實也是回避了這些方面的很多細(xì)節(jié),原因也很簡單,我自己也沒有完全仔細(xì)地了解清楚.:-(現(xiàn)在又重新閱讀和debug一下代碼,因此,借用本章節(jié),想完整的描述一下從HTML及其資源(例如圖片等)下載后到DOM樹建立好這一段過程的細(xì)節(jié)及其WebKit所涉及的這一切的一切。大體地,本章內(nèi)容主要包括網(wǎng)頁結(jié)構(gòu)描述,WebKit對網(wǎng)頁結(jié)構(gòu)的表示,WebKit解析HTML的基礎(chǔ)設(shè)施,一般過程,DOM規(guī)范和DOM樹。按照慣例,這里給出一個例子。個人崇尚簡單,只要能說明問題就行,如下所示,后面的解釋和過程是基于此例子展開地。讓我們首先了解網(wǎng)頁的基本結(jié)構(gòu),這樣便于后面理解WebKit的相關(guān)設(shè)施。其實,它并不復(fù)雜,如下圖所示。整個是一個網(wǎng)頁,這里稱之為Page。每個Page都有一個主框(MainFrame),該框通常包含一個HTMLDocument,主框也可能包含子框(subframe),如圖所示,這就是網(wǎng)頁的多框結(jié)構(gòu),雖然這一結(jié)構(gòu)飽受詬病,但現(xiàn)實還是這樣?,F(xiàn)在,更多的網(wǎng)頁僅有主框,這樣便于搜索引擎的檢索和處理。本章的例子是一個僅有主框的最簡單的網(wǎng)頁結(jié)構(gòu)。這些框構(gòu)成一個樹型結(jié)構(gòu),以主框為根節(jié)點,每個框也可能包含自己的HTMLDocument,它是一顆DOM樹。WebKit設(shè)施介紹了網(wǎng)頁的基本結(jié)構(gòu),那么WebKit的設(shè)施也相對容易理解,下圖是WebKit相對應(yīng)的類,和網(wǎng)頁的結(jié)構(gòu)是一一對應(yīng)的。這其中WebView是網(wǎng)頁對外的接口。在介紹WebKit中其他類之前,有必要先介紹一個WebKit普遍使用的設(shè)計模式。首先看一個例子。下圖是Chrome和ChromeClient,這兩個類非常重要。此Chrome非彼Chrome,這里的Chrome是WebKit的一個類,不是Google的瀏覽器產(chǎn)品Chrome。類Chrome后面會介紹,這里強調(diào)的是因為WebKit有很多種移植(port),所以不同移植中的Chrome需要有不同的實現(xiàn),所以Chrome類需要滿足兩類要求:1)Chrome需要具備有獲取各個平臺資源的能力,例如WebKit可以調(diào)用Chrome來創(chuàng)建一個新窗口;2)Chrome需要把WebKit的狀態(tài)和進(jìn)度等信息分發(fā)給外部的調(diào)用者或者說是WebKit的使用者;WebKit內(nèi)部跟這兩類要求相關(guān)的需求都是通過Chrome的接口來完成,這時候有個問題,那就是如何讓W(xué)ebKit和外部調(diào)用者既不緊密耦合,而能方便支持不同的平臺?WebKit使用ChromeClient抽象類來實現(xiàn)。每個port實現(xiàn)類ChromeClient,一方面監(jiān)聽WebKit狀態(tài),一方面返回WebKit所需要的資源和信息。WebKit直接調(diào)用Chrome的接口,Chrome調(diào)用ChromeClient的接口,而ChromeClient的實現(xiàn)由各個移植來完成。在這一部分中,很多都是該模式的類組合,例如FrameLoader和FrameLoaderClient,ImageLoader和ImageLoaderClient,ContextMenu和ContextMenuClient等等。這些類比較容易理解,不再闡述,這里簡單介紹幾組類的關(guān)系(很多其他的組類似):Frame和FrameLoader:Frame表示的是頁面框和框的加載器,一個負(fù)責(zé)頁面的表示,一個負(fù)責(zé)加載需要的接口及實現(xiàn),還有很多類似的組合類,例如Document和DocumentLoader,CachedImage和ImageLoader等。WebView和Page:二者一一對應(yīng),Page是WebKit內(nèi)部表示網(wǎng)頁的類,WebView是WebKit對外表示網(wǎng)頁的類,Page只有一個實現(xiàn),WebView在不同的移植中有不同的實現(xiàn)。其他類似的組合類如WebFrame和Frame。最后接上面介紹的類Chrome,它是一個非常重要的類,是WebKit與它的使用者之間的橋梁,主要負(fù)責(zé)UI和渲染相關(guān)的需要用到平臺相關(guān)接口。以下是它的一些主要功能:跟UI和渲染顯示相關(guān)的需要移植實現(xiàn)的接口集合類;繼承自HostWindow(宿主窗口),其包含一系列接口,用來通知重繪或者更新相應(yīng)整個窗口,滾動窗口等等;窗口相關(guān)操作,例如顯示,隱藏等;顯示/隱藏窗口中的toolbar,statusbar,scroolbar等;顯示JavaScript相關(guān)的窗口,例如JavaScript的Alert,confirm,prompt窗口等;其他一些跟顯示相關(guān)的,例如colorchooser等。HTML解析的一般過程首先介紹主要對象的創(chuàng)建順序,如下圖所示,簡單明了。下面是解析和創(chuàng)建DOM樹的一般過程,依舊是用圖說話,為簡單起見,省略其中的若干類和步驟。上述圖中的順序,簡單來講是,先接受服務(wù)器發(fā)送過來的數(shù)據(jù),之后解析成一系列的Tokens,然后在加載結(jié)束后建立DOM樹,這還是比較容易理解地,不再詳細(xì)描述。下圖是解析完例子后所創(chuàng)建的DOM樹。簡單吧?我也覺得:-)DOM標(biāo)準(zhǔn)DOM定義的是一組平臺無關(guān)和語言無關(guān)的接口,該接口允許編程語言動態(tài)訪問和更改結(jié)構(gòu)化文檔。本人(:-))見的比較多的應(yīng)用對象是XML和HTML。W3C標(biāo)準(zhǔn)化組織定義一系列DOM接口,隨著時間的發(fā)展,目前包括DOMlevel1,2,3。每個新版本是對以前版本的補充和新功能的加入。下面是對這三個版本的簡單介紹:DOMlevel1:1.Core:一組底層的接口,其接口可以表示任何結(jié)構(gòu)化的文檔,同時也允許對其進(jìn)行擴展,典型的例子是支持XML文檔。2.HTML:一組基于Core定義的接口的上層接口,主要是為了方便HTML文檔的訪問。DOMlevel2:1.Core:對DOMlevel1中core部分的擴展,其中著名的就是getElementById(沒用過的請舉手),還有很多跟名空間(namespace)相關(guān)的接口;2.Views:書上說views是“允許動態(tài)訪問和修改文檔內(nèi)容的表示,主要是兩個接口AbstractView和DocumentView”,沒接觸過加不懂,誰來救救我?3.Events:這個很重要,引入了對時間的處理,個人覺得是個重要的變化,主要有EventTarget,Mouseevents等接口,但不支持Keyboard,這個在DOMlevel3才被加入;4.Style(CSS):加入接口可以修改樣式屬性;5.Traversalandrange:這個容易理解,就是遍歷樹(NodeIterator和TreeWalker)加上對制定范圍的文檔修改刪除等操作;6.HTML:擴充DOMlevel1的HTML部分,允許動態(tài)訪問和修改HTML文檔。DOMlevel3:1.Core:加入了新的adoptNode()和textContent支持;2.Loadandsave:動態(tài)加載和序列化DOM表示;3.Validation:根據(jù)scheme驗證文檔的有效性;4.Events:主要擴展對keyboard的支持。HTML5和觸屏技術(shù)如火如荼,所以對Touch的支持很快就會進(jìn)入規(guī)范;5.XPath:使用XPath1.0來訪問DOM樹,XPath是一種簡單直觀的檢索DOM樹節(jié)點的方式,具體見W3CXPath標(biāo)準(zhǔn)。DOM標(biāo)準(zhǔn)對具體的表示方法沒有任何限制,只是定義了接口,因此它在現(xiàn)實中有很多種實現(xiàn)。DOM的樹狀表示是其中比較普遍的方式,還可以是二進(jìn)制表示,其有很多的優(yōu)點(例如binaryxml),詳情請google之。好了,該結(jié)束本章了,但這不是全部,這部分還有很多的細(xì)節(jié)值得研究和探討,例如個人比較關(guān)注的是DOM樹上的事件(Event)分發(fā)機制。有興趣的話,可以自行閱讀以下目錄中的WebKit代碼,相信你會獲益非淺。源文件目錄third_party/WebKit/Source/WebCore/loader
加載相關(guān)的類,例如負(fù)責(zé)加載Document,F(xiàn)rame,image等
third_party/WebKit/Source/WebCore/page
表示頁面的類,例如page,frame等,及頁面相關(guān)的設(shè)施,例如contextmenu,JavaScript內(nèi)置的對象,例如,window,console,navigator,dom等。
third_party/WebKit/Source/WebCore/dom
DOM相關(guān)的類,包括節(jié)點定義類
third_party/WebKit/Source/WebCore/html
html標(biāo)注的相應(yīng)的DOM節(jié)點類定義
third_party/WebKit/Source/WebCore/html/parser
HTML解析器相關(guān)類
參考文獻(xiàn)/DOM//wiki/Document_Object_Model/TR/DOM-Level-3-Events//TR/DOM-Level-3-Core/core.html/htmldom/default.asp/en-US/docs/DOM_Levels/developerworks/cn/xml/x-keydom//developerworks/cn/xml/x-keydom2/index.html/view/7fa3ad6e58fafab069dc02b8.htmlCSS基礎(chǔ)CSS初探概述先談?wù)凥TML網(wǎng)頁的開發(fā)者們所遭遇地痛苦和悲慘的經(jīng)歷。在CSS出現(xiàn)前或者出現(xiàn)早期,HTML因為要設(shè)計不同風(fēng)格和樣式的元素,所以在不停地加入很多新的元素來表示,例如p,span。然后,問題還是存在,那就是大量的使用表格(Table)元素來排列網(wǎng)頁中的元素,這導(dǎo)致一些不好的問題,其一,Table經(jīng)常嵌Table,導(dǎo)致網(wǎng)頁較大,消耗帶寬,其二,被搜索引擎解析后,其內(nèi)容變得雜亂無章。慶幸地是,CSS的出現(xiàn)極大地解決了這些問題。CSS的全稱是CascadingStyleSheet,中文名是級聯(lián)樣式表,是用來控制網(wǎng)頁顯示風(fēng)格的,被廣泛地使用在網(wǎng)頁中,現(xiàn)在基本所有的瀏覽器支持它。其有一個比較重要的特征就是將網(wǎng)頁的內(nèi)容和展示的方式分離開,這很重要。另一個重要的特征是它很強大,不是一般的強大,特別是新的CSS3標(biāo)準(zhǔn),不僅能提供對頁面各個元素的精準(zhǔn)控制,同時提供豐富多彩的樣式。簡而言之,CSS是一種非常出色的文本展示語言。本章將簡單介紹CSS的一些基本功能,讓你對它有個大概地認(rèn)識,后面的一章中,我們將會介紹它們?nèi)绾卧赪ebKit和chormium中獲得支持地。下面給出一個雖然簡單但是卻展示了CSS眾多特征的示例,CSS的主要部分在包含元素Style中,也就是下例中從第3行到第16行,同時JavaScript中也有使用部分對樣式的操作,后面的部分會對它們逐一加以解釋。CSS功能選擇器CSS的選擇器是一組模式,用來匹配相應(yīng)的HTML元素。但選擇器匹配相應(yīng)的元素時候,該選擇器包含的各種樣式的設(shè)置就會作用在選中的元素上。通過選擇器,CSS能夠精準(zhǔn)地控制HTML頁面中的任意一個或者多個元素的屬性??瓷厦娴睦又械谒男?。該行中的’div’就是一個選擇器,它屬于元素選擇器,其含義是選擇該頁面中的所有’div’元素。因為僅有第20行包含一個’div’元素,所以,該選擇的選擇結(jié)果就是該元素。那么,div下面所設(shè)置的樣式等屬性(花括號內(nèi))都會作用在該元素,從第6行到第16行。示例中的選擇器僅是眾多選擇器類型中的一種,從CSS1到CSS3,規(guī)范陸續(xù)地加入了多達(dá)42種選擇器,極大地方便了開發(fā)者,下面介紹其中一些主要的選擇器:標(biāo)簽選擇器:根據(jù)元素的名稱來選擇,例如例子中的選擇器,可以選擇一個或者多個類選擇器:根據(jù)類別信息來選擇目標(biāo)元素,可以選擇一個或者多個,例子中選擇div元素也可以-使用類選擇器,方法是”.aclass”;ID選擇器:根據(jù)ID來選擇目標(biāo)元素,僅能選擇一個,例子中選擇div元素也可以根據(jù)屬性選擇器,方法可以是”#adiv”屬性選擇器:根據(jù)屬性來選擇目標(biāo)元素,可以選擇一個或者多個,例子中選擇div元素也可以使用屬性選擇器,方法是”div[id]”,”div[id=’adiv’]”,”div[id~=’di’]”,”div[id|=’ad’]”;后代選擇器:選擇某元素包含的后代元素,可以選擇一個或者多個,例子中選擇div元素也可以使用后代選擇器,方法是”bodydiv”;子女選擇器:選擇某元素包含的子女元素,可以選擇一個或者多個,例子中選擇div元素也可以使用后代選擇器,方法是”body>div”;相鄰?fù)x擇器:根據(jù)相鄰?fù)畔泶_定選擇的元素,可以選擇一個或者多個,例子中選擇div元素也可以使用相鄰?fù)x擇器,方法是”p+div”;還有很多類型的選擇器,例如偽類選擇器,通用選擇器,群組選擇器,根選擇器等等,這里不再一一作介紹。介紹了選擇器之后,后面還有個重要的問題,那就是優(yōu)先級。因為多個選擇器可能作用于同一個元素,它們設(shè)置的樣式屬性可能不一樣,這種情況下,應(yīng)該怎么確定使用哪種樣式。一般而言,選擇器越特殊,它的優(yōu)先級越高,也就是所選擇器指向的越準(zhǔn)確,它的優(yōu)先級就越高。例如,如果用1表示標(biāo)簽選擇器的優(yōu)先級,那么類選擇器優(yōu)先級是10,id選擇器就是100,數(shù)值越大表示優(yōu)先級別越高。所以,盡量使用精確控制的選擇器,使用合理優(yōu)先級的選擇。各種屬性從第6行到第16行設(shè)置選擇的元素的樣式屬性值,大致把這些屬性分成以下類別:背景:通常有兩種方式設(shè)置,一個是設(shè)置背景顏色(例子中的background-color),另外一種設(shè)置背景圖片。文本:設(shè)置文本縮進(jìn),對齊,單詞間隔,字母間隔,字符轉(zhuǎn)換,裝飾,空白字符等字體:設(shè)置字體屬性,可以是內(nèi)嵌的,也可以是自定義的方式,另外還可以設(shè)置加粗,變形等等。列表:設(shè)置列表類型,可以以字母,希臘字母,數(shù)字等變好列表表格:通過設(shè)置邊框來達(dá)到表格的目的,設(shè)置是否把表格邊框合并為單一的邊框,設(shè)置分隔單元格邊框的距離,設(shè)置表格標(biāo)題的位置,設(shè)置是否顯示表格中的空單元格,設(shè)置顯示單元、行和列的算法等??蚰P?boxmodel):框模型定義了元素框處理元素內(nèi)容,內(nèi)邊框,邊框和外邊距處理方式。在示例中包含屬性’border’,’padding’分別表示邊框和內(nèi)邊距。定位:CSS提供相對,絕對地位和浮動定位。示例使用了絕對定位,參見第6到第8行。從示例中相信你可以看到,CSS的基本單元就是選擇器加上它所包含的各個屬性設(shè)置,參看示例中第4行到第17行,CSS就是由多個這樣的基本單元所組成。在編寫CSS的時候,通過選擇器來精確控制需要選擇的元素,然后通過設(shè)置屬性值來讓這些元素展示出不同的顯示效果。CSS3新增功能選擇器:上面介紹屬性選擇器就是CSS3新加入的,除此之外,還加入了精確控制的選擇器用來選擇特定位置的子女,特定元素標(biāo)簽的子女等等。樣式:增加了一些比較好的功能,例如自定義字體,圓角屬性,邊框顏色等等變換,過渡和動畫(transform,transition,animation):CSS3提供令人驚奇的變好,轉(zhuǎn)變和動畫功能,另其更加的賞心悅目。規(guī)范的草案中定義了2D的變換,更為吃驚的是WebKit提供了3D的變換。變換有三種類型,平移,旋轉(zhuǎn)和縮放。同2D不同的是,3D增加了繞Z軸的平移旋轉(zhuǎn)和縮放。有一點頗令人遺憾,那就是各個不同的瀏覽器對這些屬性的名字定義不一致,例如標(biāo)準(zhǔn)對變換的定義屬性名是’transform’,而webkit的是’-webkit-transform’,如例子中第15行所示,IE的’-ms-transform’,firefox的則是’-moz-transform’,opera的是’-o-transform’,這不免令人心煩意亂。過渡(transition)描述了屬性從一個值過渡到另一個值的過程,定義了過程的時間,啟動過程的延遲時間等等。但是,這些標(biāo)準(zhǔn)草案中的定義還不足以描述更精確的變化過程,所以引入了更為靈活的方式,這就是CSS動畫(animation)。通過動畫,你能夠定義不同的keyframes來控制中間變化過程而不僅僅是開始和結(jié)束。你可以這么理解,過渡是一種較為簡單和常見的動畫。CSS和JavaScript在新的瀏覽器中,Javascript也可以方便且簡單地來操作設(shè)置css的值,看看例子第24行到34行的代碼。在函數(shù)’loop’中,但得到元素’adiv’后,可以通過設(shè)置它的style屬性來設(shè)置各個CSS的屬性值,例如本例中是要設(shè)置變換的不同角度,含義是通過不斷地改變’webkitTransform’的值來讓’adiv’元素繞Z軸旋轉(zhuǎn)起來,效果相當(dāng)酷。另外一個非常不錯的功能是,規(guī)范中引入了兩個新的JavaScript接口:querySelector和querySelectorAll。這兩個接口讓CSS定義的所有的選擇器都可以作為參數(shù)傳給這兩個接口,從而獲取到相應(yīng)的HTML頁面中的元素,這非常的有用,你值得試試。chromium,safari和Firefox都支持它。在這一節(jié)結(jié)束前,強烈建議你將該示例在瀏覽器中嘗試(最好是chrome或者safari,如果是其它瀏覽器,可能需要做相應(yīng)的屬性名修改),同時,逐一注釋掉每個屬性,看看它怎么影響最終的顯示效果。參考文獻(xiàn)/projects/layout/index.html/wiki/Tableless_web_design/cssref/css_selectors.aspWebKit渲染基礎(chǔ)概述WebKit是一個渲染引擎,而不是一個瀏覽器,它專注于網(wǎng)頁內(nèi)容展示,其中渲染是其中核心的部分之一。本章著重于對渲染部分的基礎(chǔ)進(jìn)行一定程度的了解和認(rèn)識,主要理解基于DOM樹來介紹Render樹和RenderLayer樹的構(gòu)建由來和方式。那么什么是DOM?簡單來說,DOM是對HTML或者XML等文檔的一種結(jié)構(gòu)化表示方法,通過這種方式,用戶可以通過提供標(biāo)準(zhǔn)的接口來訪問HTML頁面中的任何元素的相關(guān)屬性,并可對DOM進(jìn)行相應(yīng)的添加、刪除和更新操作等。相關(guān)信息可查閱W3C的文檔,這里不再贅述?;贒OM樹的一些可視(visual)的節(jié)點,WebKit來根據(jù)需要來創(chuàng)建相應(yīng)的RenderObject節(jié)點,這些節(jié)點也構(gòu)成了一顆樹,稱之為Render樹。基于Render樹,WebKit也會根據(jù)需要來為它們中的某些節(jié)點創(chuàng)建新的RenderLayer節(jié)點,從而形成一棵RenderLayer樹。Render樹和RenderLayer樹是WebKit支持渲染所提供的基礎(chǔ)但是卻非常重要的設(shè)施。這是因為WebKit的布局計算依賴它們,瀏覽器的渲染和GPU硬件加速也都依賴于它們。幸運地是,得益于它們接口定義的靈活性,不同的瀏覽器可以很方便地來實現(xiàn)自己的渲染和加速機制。為了直觀了解這三種樹,下圖給出了這三種樹及其它們之間的對應(yīng)關(guān)系,后面會詳細(xì)介紹里面的細(xì)節(jié)。Render樹Render樹的建立Render樹是基于DOM樹建立起來的一顆新的樹,是布局和渲染等機制的基礎(chǔ)設(shè)施。Render樹節(jié)點和DOM樹節(jié)點不是一一對應(yīng)關(guān)系,那么哪些情況下需要建立新的Render節(jié)點呢?a)DOM樹的document節(jié)點;b)DOM樹中的可視化節(jié)點,例如HTML,BODY,DIV等,非可視化節(jié)點不會建立Render樹節(jié)點,例如HEAD,META,SCRIPT等;c)某些情況下需要建立匿名的Render節(jié)點,該節(jié)點不對應(yīng)于DOM樹中的任何節(jié)點;RenderObject對象在DOM樹創(chuàng)建的同時也會被創(chuàng)建,當(dāng)然,如果DOM中有動態(tài)加入元素時,也可能會相應(yīng)地創(chuàng)建RenderObject對象。下圖示例的是RenderObject對象被創(chuàng)建的函數(shù)調(diào)用過程。Render樹建立之后,布局運算會計算出相關(guān)的屬性,這其中有位置,大小,是否浮動等。有了這些信息之后,渲染引擎才只知道在何處以及如何畫這些元素。RenderObject類及其子類RenderObject是Render樹的節(jié)點基礎(chǔ)類,提供了一組公共的接口。它有很多的子類,這些子類可能對應(yīng)一些DOM樹中的節(jié)點,例如RenderText,有些則是容器類,例如RenderBlock。下圖給出了一些常用的類的繼承關(guān)系圖,這其中RenderBlock是一個非常重要的類。匿名RenderBlock對象CSS中有塊級元素和內(nèi)嵌(inline)元素之分。內(nèi)嵌元素表現(xiàn)的是行布局形式,就是說這些元素以行進(jìn)行顯示。以’div’元素為例,如果設(shè)置屬性’style’為’display:inline’時,則那是內(nèi)嵌元素,那么它可能與前面的元素在同一行;如果該元素沒有設(shè)置這個屬性時,則是塊級元素,那么在新的行里顯示。RenderBlock用來是用來表示塊級元素,為了處理上的方便,某些情況下需要建立匿名的RenderBlock對象,因為RenderBlock的子女必須都是內(nèi)嵌的元素或者都是非內(nèi)嵌的元素。所以,當(dāng)它包含兩種元素的時候,那么它會為相鄰的內(nèi)嵌元素創(chuàng)建一個塊級RenderBlock節(jié)點,然后設(shè)置該節(jié)點為自己的子女并且設(shè)置這些內(nèi)嵌元素為它的子女。RenderLayer樹RenderLayer樹是基于Render樹建立起來的一顆新的樹。同樣,RenderLayer節(jié)點和Render節(jié)點不是一一對應(yīng)關(guān)系,而是一對多的關(guān)系。那么哪些情況下的RenderObject節(jié)點需要建立新的RenderLayer節(jié)點呢?a)DOM樹的document節(jié)點對應(yīng)的RenderView節(jié)點b)DOM樹中的document的子女節(jié)點,也就是HTML節(jié)點對應(yīng)的RenderBlock節(jié)點c)顯式的CSS位置d)有透明效果的對象e)節(jié)點有溢出(overflow),alpha或者反射等效果的f)Canvas2D和3D(WebGL)g)Video節(jié)點對應(yīng)的RenderObject對象一個RenderLayer被建立之后,其所在的RenderObject對象的所有后代均包含在該RenderLayer,除非這些后代需要建立自己的RenderLayer。而后代的RenderLayer的父親就是自己最近的祖先所在的不同的RenderLayer,這樣,它們也構(gòu)成了一顆RenderLayer樹。每個RenderLayer對應(yīng)的Render節(jié)點內(nèi)容均會繪制在該RenderLayer所對應(yīng)的層次上(或者內(nèi)部存儲結(jié)構(gòu)上)。不同的RenderLayer可以共享同一個內(nèi)部存儲結(jié)構(gòu),也可以有各自的后端存儲,這取決于不同的移植。在軟件渲染下,通常各個RenderLayer的內(nèi)容都繪制在同一塊后端存儲上。在GPU硬件加速的下,某些RenderLayer可能有自己獨立的后端存儲,而后通過合成器來把這些不同的后端合成在一起,最終形成網(wǎng)頁的可視化內(nèi)容。RenderLayer在創(chuàng)建RenderObject對象的時候,如果需要,也會同時被創(chuàng)建,當(dāng)然也有可能在執(zhí)行JavaScript代碼時,更新頁面的樣式從而需要新創(chuàng)建一個RenderLayer。##一個例子以上說了那么多,一堆堆的類,一個個的建立規(guī)則,聽起來太抽象,不太容易理解,那么一個具體的Render樹和RenderLayer樹到底是怎么樣的呢?為了可視化理解Render樹和RenderLayer樹,下面給出一個具體的例子來加以解釋和說明。首先,讓我們來看一個簡單的HTML網(wǎng)頁,源代碼如下所示。上面的代碼結(jié)構(gòu)很簡單,就是一些HTML的基本元素組成的,例如HTML,HEAD,DIV,A,IMG,TABLE等等,然后包含一個特別的HTML5元素CANVAS,而且還有一小段JavaScript代碼。照顧到一些沒有相關(guān)背景的讀者,簡單解釋一下這段代碼的含義。這段代碼是對CANVAS元素創(chuàng)建一個WebGL的Context,有了這個context,就可以在canvas元素上繪制3D的內(nèi)容。這個類似于OpenGL或者OpenGLES的context,具體canvas和WebGL會有另外專門的章節(jié)來介紹。這段HTML源代碼被WebKit解析后會生成一顆DOM樹。這段代碼的DOM樹主要結(jié)構(gòu)如本章第一幅圖中的‘DOM樹’部分所示。當(dāng)DOM樹生成時,WebKit同時建立了一顆Render樹,如上所說,代碼的Render樹被打印成如下圖所示的文本信息。首先,上圖中的“l(fā)ayerat(x,x)”表示不同的RenderLayer節(jié)點,下面的所有RenderObject均屬于該RenderLayer。以第一個layer為例,它對應(yīng)于DOM中的Document節(jié)點。后面的(0,0)表示該節(jié)點在坐標(biāo)系中的位置,最后的1028x683表示該節(jié)點的大小。它包含的RenderView節(jié)點后面的信息也是同樣的意思。其次,看其中最大的部分-第二個layer,包含了HTML中的絕大部分元素。這里有三點需要解釋一下:第一,Head元素沒有相應(yīng)的RenderObject,如上面所解釋的,因為它不是一個可視的元素。第二,Canvas元素不在其中,而是在第三個layer中(RenderHTMLCanvas)。但是它仍然是RenderBody節(jié)點的子女。第三,匿名的RenderBlock節(jié)點,它包含了RenderText,RenderInline等節(jié)點,原因如前所說。再次,來看一下第三個layer。因為從Canvas創(chuàng)建了一個WebGL3DContext對象,這里需要重新生成一個新的layer。最后,來說明一下三個layer的創(chuàng)建時間。第一和第二個layer在創(chuàng)建DOM樹后,會觸發(fā)創(chuàng)建;第三個layer測試資源加載解析好之后,執(zhí)行后面的JavaScript代碼后所創(chuàng)建。基于上面的描述,相信大家已經(jīng)對Render樹和RenderLayer樹有了一定的了解?,F(xiàn)在讓我們回憶一下本章的第一幅圖。該圖其實是給出了示例的HTML網(wǎng)頁在WebKit中三種樹的對應(yīng)關(guān)系(僅選取其中重要的部分),相信現(xiàn)在你已經(jīng)了解它們的含義了。源代碼目錄WebKit/Source/WebCore/rendering
WebKit為支持渲染所涉及的各個類
WebKit/Source/WebCore/dom/
DOM樹的相關(guān)文件,包括一些基礎(chǔ)類及其接口定義
WebKit/Source/WebCore/html/
為HTML網(wǎng)頁的元素所定義的相關(guān)的基礎(chǔ)類,它們基于DOM的Node類
參考文獻(xiàn)Googledesigndocuments:/developers/design-documents/gpu-accelerated-compositing-in-chrome渲染主循環(huán)(mainloop)和requestAnimationFrame概述曾經(jīng)寫過一段JavaScript代碼,因為涉及到需要循環(huán)調(diào)用某個函數(shù)來實現(xiàn)動畫的功能,很自然地,我想到了使用setInterval函數(shù)(或者setTimeout,大家是否有類似經(jīng)歷呢?),然后心滿意足地很快的搞定。結(jié)束后,朋友幫忙閱讀了一下代碼,他提醒我是不是可以考慮使用requestAnimationFrame。之前一直知道這個函數(shù),也知道一些它的一些優(yōu)點,問題是為什么呢?本著追究到底的精神,決定還是去閱讀一下WebKit相關(guān)代碼和一些相關(guān)文檔,了解它們背后的故事。好吧,本章我將和大家一起來學(xué)習(xí)和探討這背后的故事…背景接觸過JavaScript的讀者應(yīng)該有過了解或者使用setTimeout或者setInterval的經(jīng)歷,其功能是在每個時間間隔之后一次性或者重復(fù)多次執(zhí)行一段JavaScript代碼(稱為回調(diào)函數(shù)),以完成特定的動畫要求。但是,這里面有還有些疑問:時間間隔應(yīng)該設(shè)置為多少才合適呢?跟屏幕的分辨率有關(guān)系嗎?設(shè)置的時間間隔會按照預(yù)想的執(zhí)行嗎?動畫會被平滑地顯示出效果嗎?回調(diào)函數(shù)是復(fù)雜的好還是簡單的好呢?應(yīng)該如何編寫才能效率高呢?與平臺和瀏覽器相關(guān)嗎?如何適應(yīng)不同平臺呢?這對setTimeout和setInterval來說很重要。如果對mainloop機制和渲染機制有一定了解的讀者來說,上面這幾條其實是非常難做到地,哪怕是較為接近理想的結(jié)果。幸運地是,總是有聰明的人來幫助大家解決難題。對問題提出一個漂亮解決方案的是mozilla的RobertO’Callahan。他的靈感和依據(jù)來源于CSS。CSS是知道動畫什么時候發(fā)生,所以能夠較為準(zhǔn)確的知道什么時候刷新UI。對于JavaScript來說,是不是也可以根據(jù)類似的機制呢?答案是肯定地。其做法是增加一個新的方法requestAnimationFrame,該方法告訴瀏覽器JavaScript想發(fā)起一個動畫幀,然后在動畫幀繪制之前,需要做一些動作,這樣瀏覽器可以根據(jù)需要來優(yōu)化自己的mainloop機制和調(diào)用時間點,以達(dá)到較好地平衡效果。好吧,下面來看看mainloop機制及其工作原理。渲染mainloop因為chromium是多進(jìn)程的結(jié)構(gòu)(參看Chromium多進(jìn)程架構(gòu)篇),所以,跟一般瀏覽器不一樣的是,Browser進(jìn)程UI用戶界面的mainloop和Renderer進(jìn)程的主線程的mainloop不是同一個,分別位于兩個不同的進(jìn)程,所以UI和渲染可以互相不影響,聽起來這好像很不錯,是的,但是問題依然存在,那就是Renderer進(jìn)程的渲染工作和JavaScript的執(zhí)行工作都在其主線程中,由mainloop來負(fù)責(zé)調(diào)度完成,所以競爭依然存在。大致過程是一個大的循環(huán)加上一個事件隊列,具體的過程,如下圖所示。當(dāng)隊列中有事件時,從隊列中取出第一個事件,設(shè)置相應(yīng)的狀態(tài)信息,處理該事件及其對應(yīng)的處理函數(shù),直到該函數(shù)處理完后,才重新檢查隊列中是否有事件。如果有,繼續(xù)處理;如果沒有,則繼續(xù)等待。這其中可以看出,如果隊列中事件多的時候,那么很多事件可能來不及處理,從而造成比較大的延時,因而事件的平均等待時間會比較長。同時,如果事件的處理函數(shù)需要的時間很長,就會造成后面的事件一直在等待,同樣會增加事件的平均等待時間。而當(dāng)隊列比較空閑時或者事件的處理函數(shù)需要的時間比較短,則事件的平均等待時間會相對小很多。WebKit和Chromium中的實現(xiàn)理解了mainloop之后,下面來看一看setTimeout和setInterval的實現(xiàn)。來看一下它們的實現(xiàn):WebKit中setTimeout和setInterval的實現(xiàn)機制是類似的,區(qū)別在于后者是重復(fù)性的,見下圖所示的類圖關(guān)系。WebKit會為DOM中的每個setTimeout和setInterval的調(diào)用創(chuàng)建一個DOMTimer,而后該對象會由存儲TLS(threadlocalstorage)中的ThreadTimers負(fù)責(zé)管理,其內(nèi)部其實是一個最小堆,每次取timeout時間最小的,同時,時間相同的Timer可以合并。當(dāng)Timer超時后,Chromium清除該Timer對象,同時調(diào)用相應(yīng)的回調(diào)函數(shù),回調(diào)函數(shù)通常會更新頁面的樣式和布局,這會觸發(fā)relayout,從而觸發(fā)立即重新繪制一個新幀。結(jié)合上面的描述,我們大致地總結(jié)setTimeout和setInterval主要不足就是:setTimeout和setInterval從不考慮瀏覽器內(nèi)部發(fā)生了其他什么事,它只要求瀏覽器在某個時間之后調(diào)用它的回調(diào)函數(shù),無論瀏覽器很繁忙或者頁面被隱藏(雖然某些瀏覽器做了這方面的優(yōu)化,例如chromium);setTimeout和setInterval只要求瀏覽器做什么,而不管瀏覽器能不能做到(例如mainloop有很多事件需要處理),這有點強人所難,而且會帶來極大的資源浪費。舉個例子,例如屏幕的刷新率是60HZ,但是設(shè)置的時間間隔是5ms,其實對用戶來說根本看不到這些變化,但是額外需要消耗更多的CPU資源,太不環(huán)保了…setTimeout和setInterval可能是編程風(fēng)格方面的考慮。如果每一幀可能在不同的代碼出需要設(shè)置回調(diào)函數(shù),一個方法是統(tǒng)一到一個地方,但是這有點勉為其難,另一個方法是分別用setInterval設(shè)置它們,這個方法的問題是,瀏覽器可能需要計算更多次,刷新更多次的屏幕,唉?,F(xiàn)在再來看看requestAnimationFrame的實現(xiàn),看看其如何解決這些不足之處的。其原理就是其會申請繪制下一幀,至于什么時候不知道,由瀏覽器決定,只需要瀏覽器在繪制下一幀前執(zhí)行其設(shè)置的回調(diào)函數(shù),完成JavaScript對動畫所做的設(shè)置和邏輯即可?;具^程是這樣的:JavaScript調(diào)用requestAnimationFrame,因而相應(yīng)的webkit和chromium會調(diào)度一個需要繪制下一證的事件,該事件會將requestAnimationFrame的調(diào)用上下文和回調(diào)函數(shù)記錄下來;上面的請求會觸發(fā)Chromium更新頁面內(nèi)容的事件,該事件被mainloop調(diào)度處理后,會檢查是否需要調(diào)用動畫的相關(guān)處理,因為有動畫需要處理,所以會依次調(diào)用那些回調(diào)函數(shù),JavaScript引擎會更新相應(yīng)的CSS屬性或者DOM樹修改;Chromium觸發(fā)重新計算layout(參看layout章節(jié)),更新自己的Renderer樹(參看webkit渲染基礎(chǔ)章節(jié)),而后繪制,完成一幀的渲染。下圖是一個上述過程對應(yīng)的狀態(tài)轉(zhuǎn)換圖,來源于chromium的官方網(wǎng)站,看著的確比較饒人,可以先理解一下其中幾個主要的概念:Floortime:指的是繪制下一幀之前需要等待的事件間隔Invalidation:觸發(fā)重新繪制請求的操作;scheduleAnimation:JavaScript調(diào)用requestAnimationFrame所引起的WebKit內(nèi)部請求調(diào)度動畫的操作;這些狀態(tài)的轉(zhuǎn)換倒是說明了,requestAnimationFrame可以很好地和Chromium內(nèi)部的繪制過程結(jié)合,從而達(dá)到比較好的性能。為了實現(xiàn)更好的性能,chromium中對requestAnimationFrame有三個設(shè)計原則1.當(dāng)頁面不可見時,其回調(diào)函數(shù)不會被調(diào)用,這可以減少CPU和GPU的使用率,更環(huán)保嘛;2.其最大調(diào)用頻率不會超過60hz,無論屏幕的刷新率是多少,因而回
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 中國石油大學(xué)(北京)《籃球》2023-2024學(xué)年第一學(xué)期期末試卷
- 鄭州升達(dá)經(jīng)貿(mào)管理學(xué)院《園林景觀快題設(shè)計》2023-2024學(xué)年第一學(xué)期期末試卷
- 小學(xué)新課程標(biāo)準(zhǔn)培訓(xùn)方案
- 長春工業(yè)大學(xué)《葡萄酒品嘗學(xué)》2023-2024學(xué)年第一學(xué)期期末試卷
- 生態(tài)恢復(fù)技術(shù)在退化土地上應(yīng)用
- 餐飲業(yè)年度報告模板
- AI生活助手新品發(fā)布模板
- 碩士論文答辯報告
- 生醫(yī)年報展望模板
- 房地產(chǎn)交易制度政策-《房地產(chǎn)基本制度與政策》全真模擬試卷4
- 2025年中國AI AGENT(人工智能體)行業(yè)市場動態(tài)分析、發(fā)展方向及投資前景分析報告
- 家居建材行業(yè)綠色材料應(yīng)用及節(jié)能設(shè)計方
- 農(nóng)副產(chǎn)品安全培訓(xùn)
- 2024年中國玩具工程車市場調(diào)查研究報告
- 2025-2030年中國電動三輪車市場發(fā)展現(xiàn)狀及前景趨勢分析報告
- TCABEE 063-2024 建筑光儲直柔系統(tǒng)變換器 通 用技術(shù)要求
- 【9化期末】合肥市廬陽區(qū)2023-2024學(xué)年九年級上學(xué)期期末化學(xué)試題
- 高一下學(xué)期生物人教版必修二:3.4 基因通常是有遺傳效應(yīng)的DNA片段課件
- 下屬企業(yè)考核報告范文
- 修車補胎合同范例
- 2024年基金應(yīng)知應(yīng)會考試試題
評論
0/150
提交評論