[toc]
一、瀏覽器的常用進程
1.進程和線程的區別
線程可以共享地址空間和數據.而進程獨享地址空間和數據.
線程比進程更輕量.所以他的切換和創建銷毀更快速.
多個線程可以共享公共內存.因此更容易進行通信
線程必須在進程中運行,但是進程和線程是不同的東西,線程擁有自己的程序計數器,寄存器,和一個堆棧.
進程是資源的聚合體.而線程則是CPU上被調度的實體.線程可以訪問進程地址空間中的每一個內存地址,甚至可以訪問其他線程的堆棧,因此線程之間是沒有保護的.
進程中的任意一線程執行出錯,都會導致整個進程的崩潰。
線程之間共享進程中的數據。
當一個進程關閉之后,操作系統會回收進程所占用的內存。
進程之間的內容相互隔離。
2.多進程瀏覽器
以chrome舉例
- 瀏覽器進程:頁面顯示、用戶交互、子進程管理,同時提供存儲等功能
- 渲染進程:解析HTML、CSS、js為用戶可交互的網頁,排版引起blink 和JavaScript引擎V8運行在進程下,默認情況每個網頁Tab都會創建一個渲染進程(同一個根域名下的網站會公用同一個渲染進程)。渲染進程運行在沙箱模式下(獨立作業環境,內部操作不會影響外部系統,只能訪問自己內部目錄和資源,并且與其它進程獨立)
- GPU進程:用了實現3D CSS效果,video播放,只是隨后網頁、Chrome 的 UI 界面都選擇采用 GPU 來繪制,這使得 GPU 成為瀏覽器普遍的需求。
- 網絡進程。主要負責頁面的網絡資源加載。
- 插件進程。主要是負責插件的運行,因插件易崩潰,所以需要通過插件進程來隔離,以保證插件進程崩潰不會對瀏覽器和頁面造成影響。
二、瀏覽器發起http請求流程
1.構建請求
如GET /index.html HTTP1.1,構建好后,瀏覽器準備發起網絡請求
2.查找緩存
先查找瀏覽器中是否有緩存,有的話攔截請求,返回緩存。如果沒有,就進入網絡請求
3.準備ip地址和端口
通過DNS服務獲取網站對應的IP地址,同時host也有緩存:根域名服務器->頂級域名服務器->網絡服務提供商緩存->路由器緩存->系統緩存->瀏覽器緩存
端口號沒有指定的話,默認是80
4.等待TCP隊列
Chrome有個機制,同一個域名同時最多只能建立6個TCP連接,如果在同一個域名下同時有10個請求發生,那么其中4個請求會進入排隊等待狀態,直至進行中的請求完成。如果當前請求數量少于6,會直接進入下一步,建立TCP連接
5.建立TCP鏈接
包括三次握手,如果使用HTTPS,還需要TLS層的四次加密協商。七次握手
6.發送HTTP請求
首先瀏覽器會向服務器發送請求行,它包括了請求方法、請求URI(Uniform Resource Identifier)和HTTP版本協議。然后再發送請求頭(包含瀏覽器的一些基礎信息)和請求體(GET請求不需要)
7.處理http響應
這里 包括處理響應和狀態碼,斷開鏈接,重定向(如果狀態碼是301)三個步驟
如果響應狀態碼是301,則會發送重定向,需要再次請求,并讀取響應頭中的重定向地址作為第二次發請求的地址。
瀏覽器的緩存過程
DNS緩存和頁面資源緩存會被緩存在瀏覽器中,而頁面資源則是由幾個請求頭中的字段決定
包括Cache-Control:Max-age = 2000 表示緩存過期時間2000秒
If-None-Match:"4f80f-13c-3a1xb12a" 增加文件的etag,供后天判斷資源是否是最新
請求流程總結
三、從輸入URL到頁面展示的過程
主要職責劃分
- 瀏覽器進程主要負責用戶交互、子進程管理和文件儲存等功能
- 網絡進程是面向渲染進程和瀏覽器進程等提供網絡下載功能
- 渲染進程的主要職責是把從網絡下載的HTML、JavaScript、CSS、圖片等資源解析為可以顯示和交互的頁面。因為渲染進程所有的內容都是通過網絡獲取的,會存在一些惡意代碼利用瀏覽器漏洞對系統進行攻擊,所以運行在渲染進程里面的代碼是不被信任的,運行在沙箱中
1. 用戶輸入
地址欄判斷輸入的是搜索內容還是URL,如果是內容,會結合默認搜索引擎來組成URL,如果是URL,會加上協議(https\http)形成完整的URL
敲入回車后,瀏覽器給當前頁面發送一個beforeunload的事件,beforeunload事件允許頁面在退出之前執行一些數據清理操作,還可以詢問用戶是否要離開當前頁面,接著進入加載狀態,如下圖,此時頁面內容沒有變化。
2.URL請求過程
瀏覽器進程通過IPC機制把URL請求發送到網絡進程,網絡進程先判斷本地緩存,如果有直接返回緩存,然后進行DNS解析,建立連接,發起網絡請求。等待請求回來的后續處理
2.1重定向
如果響應頭是301或302,需要重定向到其它URL。網絡進程會從響應頭的Location字段里面讀取重定向的地址,然后再發起新的HTTP或者HTTPS請求,一切又重頭開始了。
2.2 響應數據類型
Content-Type是HTTP頭中一個非常重要的字段, 它告訴瀏覽器服務器返回的響應體數據是什么類型,不同Content-Type的后續處理流程也不同,如果是HTML,那么瀏覽器則會繼續進行導航流程。由于Chrome的頁面渲染是運行在渲染進程中的,所以接下來就需要準備渲染進程。
2.3準備渲染進程
打開一個新頁面采用的渲染進程策略就是:
- 通常情況下,打開新的頁面都會使用單獨的渲染進程;
- 如果從A頁面打開B頁面,且A和B都屬于同一站點的話,那么B頁面復用A頁面的渲染進程;如果是其他情況,瀏覽器進程則會為B創建一個新的渲染進程。
- 每個跨站點 iframe 運行在單獨的渲染器進程
2.4 提交文檔
網絡請求由網絡進程發起和接受,但是數據要由渲染進程處理,這里需要借助瀏覽器進程的控制,使網絡進程和渲染進程進行通信。提交文檔就是瀏覽器進程將網絡進程接收到的HTML數據提交給渲染進程,如下:
- 首先當瀏覽器進程接收到網絡進程的響應頭數據之后,便向渲染進程發起“提交文檔”的消息;
- 渲染進程接收到“提交文檔”的消息后,會和網絡進程建立傳輸數據的“管道”(IPC);
- 等文檔數據傳輸完成之后,渲染進程會返回“確認提交”的消息給瀏覽器進程;
- 瀏覽器進程在收到“確認提交”的消息后,會更新瀏覽器界面狀態,包括了安全狀態、地址欄的URL、前進后退的歷史狀態,并更新Web頁面(白屏頁面)
網站通常使用圖像、CSS 和 JavaScript 等外部資源。這些文件需要從網絡或緩存中加載。主線程可以在解析構建DOM的過程中找到它們時一一請求,但為了加快速度,“預加載掃描器”是并發運行的。如果有喜歡的東西<img>
或<link>
在通過HTML解析器生成的標記在HTML文檔中,預緊掃描器掃描并發送請求到在瀏覽器過程中的網線。而當解析到<seript>標簽時,則會暫停HTML文檔的解析,并且必須加載、解析和執行 JavaScript 代碼,因為JavaScript可以通過document.write()改變整個 DOM 結構之類的東西來改變文檔的形狀。
如果您的 JavaScript 不使用document.write()
,您可以添加async
或defer
屬性到<script>
標記,瀏覽器異步加載和運行 JavaScript 代碼,并且不會阻止解析。
2.5渲染階段
一旦文檔被提交,渲染進程便開始頁面解析和子資源加載,等待渲染完成后,渲染進程發消息給瀏覽器進程,瀏覽器進程完成加載動畫,展示新的頁面。
3.渲染流程
渲染流程就是把HTML、CSS、javas經過轉化,轉為頁面的過程。
一個渲染流程會劃分很多子階段,整個處理過程叫渲染流水線,流水線可分為如下幾個子階段:構建DOM樹、樣式計算、布局階段、分層、繪制、分塊、光柵化和合成。每個階段都經過輸入內容 -->處理過程-->輸出內容三個部分。
3.1構建DOM樹
這是因為瀏覽器無法直接理解和使用HTML,所以需要將HTML轉換為瀏覽器能夠理解的結構——DOM樹
每個DOM樹的一個節點就是HTML中的一個標簽,這種轉化其實很常見。DOM是保存在內存中樹狀結構,可以通過JavaScript來查詢或修改其內容。
3.2樣式計算(Recalculate Style)
樣式計算是為了計算Dom節點的每個元素的具體樣式,這里就需要CSS的參與。分為三個階段:
3.2.1 CSS轉化為styleSheets
CSS樣式來源主要有三種:
- 通過link引用的外部CSS文件
- style標記內的 CSS
- 元素的style屬性內嵌的CSS
當渲染引擎接收到CSS文本時,會執行一個轉換操作,將CSS文本轉換為瀏覽器可以理解的結構——StyleSheets。這是一種結構體,渲染引擎會把所有獲取到的CSS轉換為styleSheets結構中的數據,并且具備了查詢和修改功能,這其實是CSSDOM。
3.2.2屬性值標準化
就是把CSS中的屬性值,如2em、blue、bold,這些類型數值不容易被渲染引擎理解,所以需要將所有值轉換為渲染引擎容易理解的、標準化的計算值,這個過程就是屬性值標準化。
- initial value:初始值,我們手動指定的值
- specified value:從 css、父節點、初始值來的默認值
- resolved value:給 CSSOM 用的值
- computed value:inheritance 結束后,計算出一些和布局無關的相對值,從specified value計算得來
- used value:布局計算后,確定的值
- actual value:根據運行環境的限制,然后近似計算后得到 actual value
3.2.3 計算DOM元素的樣式
就是把CSSDom 的數據綁定到DOM樹上,通過Css的繼承規則和層疊規則。CSS繼承就是每個DOM節點都包含有父節點的樣式,也就是所有子節點都繼承了父節點樣式,Chrome中的效果:
第二個規則是樣式層疊,表示如何合并來自多個CSS源的屬性值的算法。這兩步最終的結果是計算每個DOM元素的具體樣式,并保存在ComputedStyle的結構內。
3.3布局階段
我們有了DOM樹和DOM樹種元素的樣式,接著需要計算DOM樹中可見元素的幾何位置,整個過程叫布局。
3.3.1創建布局樹
有些元素可能不會展示出來,所有創建一棵只包含可見元素的布局樹,(display:none,head標簽)不再該樹中,但是visibility: hidden
在布局樹中。
3.3.2計算布局
上一步的布局樹需要遞歸的計算布局,因為一個節點大小的計算通常需要先計算它的子節點的位置、大小信息等。對于布局樹中的每個節點我們稱為RenderObject,因為這些節點是最終需要被渲染出來的節點。
- 首先會判斷RenderObject節點是否需要重新計算,通過檢查相應的標記位、子女是否需要計算布局等來確定
- 其次判斷網頁的寬度和垂直方向的外邊距,通常網頁在垂直方向移動,水平方向盡量步滾動
- 然后遍歷每個子女節點,計算他們的布局,每個元素實現layout函數來計算該元素的布局,如果自己 有寬高,就使用自己的設定,否則根據內容填充
- 節點根據子女節點的大小計算自己的高度,整個過程結束
如果有樣式發生變化,可視區域發生變化,產生動畫,JavaScript修改樣式信息,用戶交互時就需要重新布局。
3.4分層
渲染引擎還需要為特定的節點生成專用的圖層,并生成一棵對應的圖層樹(LayerTree)。正是這些圖層疊加在一起構成了最終的頁面圖像。
layoutTree就是上邊布局后形成的樹,然后又會產生圖層樹,并不是布局樹的每個節點都包含一個圖層,如果一個節點沒有對應的層,那么這個節點就從屬于父節點的圖層。
那些樹會有單獨的一層?
- 擁有層疊上下文屬性的元素會被提升為單獨的一層: 層疊上下文介紹
- 需要剪裁(clip)的地方也會被創建為圖層,剪裁就是節點內容超過節點限制
- 如果出現滾動條,滾動條也會被提升為單獨的層
3.5圖層繪制(tiles)
渲染引擎會把圖層樹中的每個圖層進行分別繪制,并且把一個圖層的繪制拆分成很多小的繪制指令,形成待繪制列表
從圖中可以看出,繪制列表中的指令就是讓其執行一個繪制操作,比如繪制粉色矩形或者黑色的線等。而繪制一個元素通常需要好幾條繪制指令,因為每個元素的背景、前景、邊框都需要單獨的指令去繪制。所以在圖層繪制階段,輸出的內容就是這些待繪制列表
3.6柵格化(raster)操作
主要操作為:
- 提交到合成線程
- 合成線程把圖層分塊
- 轉到柵格化線程來把圖塊轉為位圖
- 柵格化線程通過IPC借助gpu線程完成柵格化
繪制列表只是用來記錄繪制順序和繪制指令的列表,而實際上繪制操作是由渲染引擎中的合成線程來完成的。你可以結合下圖來看下渲染主線程和合成線程之間的關系:
- 當渲染引擎中圖層的繪制列表準備好之后,主線程會把該列表提交(commit)給合成線程。因為有時候圖層的大小超過屏幕上的可見區域(視口Viewport),全部繪制會浪費資源,合成線程會把圖層進行劃分為圖塊(tile),通常圖塊的大小通常是256x256或者512x512。
- 合成線程會按照視口附近的圖塊來優先生成位圖,實際生成位圖的操作是由柵格化來執行的。所謂柵格化,是指將圖塊轉換為位圖。而圖塊是柵格化執行的最小單位。渲染進程維護了一個柵格化的線程池,所有的圖塊柵格化都是在線程池內執行的,也就是由合成線程轉化到了柵格化線程。(此時還在渲染進程中)
- 柵格化過程都會使用GPU來加速生成,使用GPU生成位圖的過程叫快速柵格化,生成的位圖被保存在GPU內存中。GPU操作是運行在GPU進程,最終生成位圖的操作是在GPU中完成的,這就涉及到了跨進程操作。
3.7合成和顯示 (draw quad)
一旦所有圖塊都被光柵化,合成線程就會生成一個繪制圖塊的命令——“DrawQuad”,然后將該命令提交給瀏覽器進程。瀏覽器進程會處理該命令,把頁面繪制到內存中,最后將內存顯示在屏幕上。
一個完整的渲染流程大致可總結為如下:
- 渲染進程將HTML內容轉換為能夠讀懂的DOM樹結構。
- 渲染引擎將CSS樣式表轉化為瀏覽器可以理解的styleSheets(生存CSSDOM樹),計算出DOM節點的樣式。
- 創建布局樹(LayoutTree),并計算元素的布局信息。
- 對布局樹進行分層,并生成分層樹(LayerTree)。
- 為每個圖層生成繪制列表,并將其提交到合成線程。
- 合成線程將圖層分成圖塊,并在光柵化線程池中將圖塊轉換成位圖。
- 合成線程發送繪制圖塊命令DrawQuad給瀏覽器進程。
- 瀏覽器進程根據DrawQuad消息生成頁面,并顯示到顯示器上。
3.9相關概念
重排
通過JavaScript或者CSS修改元素的幾何位置屬性如寬高,會觸發重新布局等一系列子階段,這個過程叫重排。重排需要更新完整的渲染流水線,開銷最大
重繪
更改元素的背景顏色,不會觸發布局解決,直接進入繪制階段等子階段,叫重繪。重繪比重排少了布局和合成階段,效率高一些
直接合成
如果你更改一個既不要布局也不要繪制的屬性,渲染引擎將跳過布局和繪制,只執行后續的合成操作,我們把這個過程叫做合成,如CSS的transform來實現動畫效果,在非主線程進行合成動畫,合成的效率比前兩者高很多。
4.0輸入事件
事件接收
當用戶在屏幕上發生觸摸等手勢時,瀏覽器進程首先接收到該手勢。但是,瀏覽器進程只知道該手勢發生的位置,因為選項卡內的內容由渲染器進程處理。因此瀏覽器進程將事件類型(如touchstart
)及其坐標發送到渲染器進程。渲染器進程通過查找事件目標并運行附加的事件偵聽器來適當地處理事件。
傳遞給主線程
由于運行 JavaScript 是主線程的工作,因此當頁面被合成時,合成器線程將頁面中附加了事件處理程序的區域標記為“非快速滾動區域”。通過擁有這些信息,如果事件發生在該區域,合成器線程可以確保將輸入事件發送到主線程。如果輸入事件來自該區域之外,則合成器線程繼續合成新幀,而無需等待主線程。