轉載:https://github.com/skyline75489/what-happens-when-zh_CN/blob/master/README.rst
當···時發生了什么?
這個倉庫試圖回答一個古老的面試問題:當你在瀏覽器中輸入 google.com 并且按下回車之后發生了什么?
不過我們不再局限于平常的回答,而是想辦法回答地盡可能具體,不遺漏任何細節。
這將是一個協作的過程,所以深入挖掘吧,并且幫助我們一起完善它。仍然有大量的細節等待著你來添加,歡迎向我們發送 Pull Requset!
這些內容使用 Creative Commons Zero
_ 協議發布。
目錄
.. contents::
:backlinks: none
:local:
按下"g"鍵
接下來的內容介紹了物理鍵盤和系統中斷的工作原理,但是有一部分內容卻沒有涉及。當你按下“g”鍵,瀏覽器接收到這個消息之后,會觸發自動完成機制。瀏覽器根據自己的算法,以及你是否處于隱私瀏覽模式,會在瀏覽器的地址框下方給出輸入建議。大部分算法會優先考慮根據你的搜索歷史和書簽等內容給出建議。你打算輸入 "google.com",因此給出的建議并不匹配。但是輸入過程中仍然有大量的代碼在后臺運行,你的每一次按鍵都會使得給出的建議更加準確。甚至有可能在你輸入之前,瀏覽器就將 "google.com" 建議給你。
回車鍵按下
為了從零開始,我們選擇鍵盤上的回車鍵被按到最低處作為起點。在這個時刻,一個專用于回車鍵的電流回路被直接地或者通過電容器間接地閉合了,使得少量的電流進入了鍵盤的邏輯電路系統。這個系統會掃描每個鍵的狀態,對于按鍵開關的電位彈跳變化進行噪音消除(debounce),并將其轉化為鍵盤碼值。在這里,回車的碼值是13。鍵盤控制器在得到碼值之后,將其編碼,用于之后的傳輸?,F在這個傳輸過程幾乎都是通過通用串行總線(USB)或者藍牙(Bluetooth)來進行的,以前是通過PS/2或者ADB連接進行。
USB鍵盤:
鍵盤的USB元件通過計算機上的USB接口與USB控制器相連接,USB接口中的第一號針為它提供了5V的電壓
鍵碼值存儲在鍵盤內部電路一個叫做"endpoint"的寄存器內
USB控制器大概每隔10ms便查詢一次"endpoint"以得到存儲的鍵碼值數據,這個最短時間間隔由鍵盤提供
鍵值碼值通過USB串行接口引擎被轉換成一個或者多個遵循低層USB協議的USB數據包
這些數據包通過D+針或者D-針(中間的兩個針),以最高1.5Mb/s的速度從鍵盤傳輸至計算機。速度限制是因為人機交互設備總是被聲明成"低速設備"(USB 2.0 compliance)
這個串行信號在計算機的USB控制器處被解碼,然后被人機交互設備通用鍵盤驅動進行進一步解釋。之后按鍵的碼值被傳輸到操作系統的硬件抽象層
虛擬鍵盤(觸屏設備):
在現代電容屏上,當用戶把手指放在屏幕上時,一小部分電流從傳導層的靜電域經過手指傳導,形成了一個回路,使得屏幕上觸控的那一點電壓下降,屏幕控制器產生一個中斷,報告這次“點擊”的坐標
然后移動操作系統通知當前活躍的應用,有一個點擊事件發生在它的某個GUI部件上了,現在這個部件是虛擬鍵盤的按鈕
虛擬鍵盤引發一個軟中斷,返回給OS一個“按鍵按下”消息
這個消息又返回來向當前活躍的應用通知一個“按鍵按下”事件
產生中斷[非USB鍵盤]
鍵盤在它的中斷請求線(IRQ)上發送信號,信號會被中斷控制器映射到一個中斷向量,實際上就是一個整型數 。CPU使用中斷描述符表(IDT)把中斷向量映射到對應函數,這些函數被稱為中斷處理器,它們由操作系統內核提供。當一個中斷到達時,CPU根據IDT和中斷向量索引到對應的中斷處理器,然后操作系統內核出場了。
(Windows)一個 WM_KEYDOWN
消息被發往應用程序
HID把鍵盤按下的事件傳送給 KBDHID.sys
驅動,把HID的信號轉換成一個掃描碼(Scancode),這里回車的掃描碼是 VK_RETURN(0x0d)
。 KBDHID.sys
驅動和 KBDCLASS.sys
(鍵盤類驅動,keyboard class driver)進行交互,這個驅動負責安全地處理所有鍵盤和小鍵盤的輸入事件。之后它又去調用 Win32K.sys
,在這之前有可能把消息傳遞給安裝的第三方鍵盤過濾器。這些都是發生在內核模式。
Win32K.sys
通過 GetForegroundWindow()
API函數找到當前哪個窗口是活躍的。這個API函數提供了當前瀏覽器的地址欄的句柄。Windows系統的"message pump"機制調用 SendMessage(hWnd, WM_KEYDOWN, VK_RETURN, lParam)
函數, lParam
是一個用來指示這個按鍵的更多信息的掩碼,這些信息包括按鍵重復次數(這里是0),實際掃描碼(可能依賴于OEM廠商,不過通常不會是 VK_RETURN
),功能鍵(alt, shift, ctrl)是否被按下(在這里沒有),以及一些其他狀態。
Windows的 SendMessage
API直接將消息添加到特定窗口句柄 hWnd
的消息隊列中,之后賦給 hWnd
的主要消息處理函數 WindowProc
將會被調用,用于處理隊列中的消息。
當前活躍的句柄 hWnd
實際上是一個edit control控件,這種情況下,WindowProc
有一個用于處理 WM_KEYDOWN
消息的處理器,這段代碼會查看 SendMessage
傳入的第三個參數 wParam
,因為這個參數是 VK_RETURN
,于是它知道用戶按下了回車鍵。
(Mac OS X)一個 KeyDown
NSEvent被發往應用程序
中斷信號引發了I/O Kit Kext鍵盤驅動的中斷處理事件,驅動把信號翻譯成鍵碼值,然后傳給OS X的 WindowServer
進程。然后, WindowServer
將這個事件通過Mach端口分發給合適的(活躍的,或者正在監聽的)應用程序,這個信號會被放到應用程序的消息隊列里。隊列中的消息可以被擁有足夠高權限的線程使用 mach_ipc_dispatch
函數讀取到。這個過程通常是由 NSApplication
主事件循環產生并且處理的,通過 NSEventType
為 KeyDown
的 NSEvent
。
(GNU/Linux)Xorg 服務器監聽鍵碼值
當使用圖形化的 X Server 時,X Server 會按照特定的規則把鍵碼值再一次映射,映射成掃描碼。當這個映射過程完成之后, X Server 把這個按鍵字符發送給窗口管理器(DWM,metacity, i3等等),窗口管理器再把字符發送給當前窗口。當前窗口使用有關圖形API把文字打印在輸入框內。
解析URL
-
瀏覽器通過 URL 能夠知道下面的信息:
-
Protocol
"http"
使用HTTP協議 -
Resource
"/"
請求的資源是主頁(index)
-
輸入的是 URL 還是搜索的關鍵字?
當協議或主機名不合法時,瀏覽器會將地址欄中輸入的文字傳給默認的搜索引擎。大部分情況下,在把文字傳遞給搜索引擎的時候,URL會帶有特定的一串字符,用來告訴搜索引擎這次搜索來自這個特定瀏覽器。
轉換非 ASCII 的 Unicode 字符
- 瀏覽器檢查輸入是否含有不是
a-z
,A-Z
,0-9
,-
或者.
的字符 - 這里主機名是
google.com
,所以沒有非ASCII的字符;如果有的話,瀏覽器會對主機名部分使用Punycode
_ 編碼
檢查 HSTS 列表···
- 瀏覽器檢查自帶的“預加載 HSTS(HTTP嚴格傳輸安全)”列表,這個列表里包含了那些請求瀏覽器只使用HTTPS進行連接的網站
- 如果網站在這個列表里,瀏覽器會使用 HTTPS 而不是 HTTP 協議,否則,最初的請求會使用HTTP協議發送
- 注意,一個網站哪怕不在 HSTS 列表里,也可以要求瀏覽器對自己使用 HSTS 政策進行訪問。瀏覽器向網站發出第一個 HTTP 請求之后,網站會返回瀏覽器一個響應,請求瀏覽器只使用 HTTPS 發送請求。然而,就是這第一個 HTTP 請求,卻可能會使用戶收到
downgrade attack
_ 的威脅,這也是為什么現代瀏覽器都預置了 HSTS 列表。
DNS 查詢···
瀏覽器檢查域名是否在緩存當中(要查看 Chrome 當中的緩存, 打開
chrome://net-internals/#dns <chrome://net-internals/#dns>
_)。如果緩存中沒有,就去調用
gethostbyname
庫函數(操作系統不同函數也不同)進行查詢。gethostbyname
函數在試圖進行DNS解析之前首先檢查域名是否在本地 Hosts 里,Hosts 的位置不同的操作系統有所不同
_如果
gethostbyname
沒有這個域名的緩存記錄,也沒有在hosts
里找到,它將會向 DNS 服務器發送一條 DNS 查詢請求。DNS 服務器是由網絡通信棧提供的,通常是本地路由器或者 ISP 的緩存 DNS 服務器。查詢本地 DNS 服務器
如果 DNS 服務器和我們的主機在同一個子網內,系統會按照下面的 ARP 過程對 DNS 服務器進行 ARP查詢
如果 DNS 服務器和我們的主機在不同的子網,系統會按照下面的 ARP 過程對默認網關進行查詢
ARP 過程
要想發送 ARP(地址解析協議)廣播,我們需要有一個目標 IP 地址,同時還需要知道用于發送 ARP 廣播的接口的 MAC 地址。
- 首先查詢 ARP 緩存,如果緩存命中,我們返回結果:目標 IP = MAC
如果緩存沒有命中:
- 查看路由表,看看目標 IP 地址是不是在本地路由表中的某個子網內。是的話,使用跟那個子網相連的接口,否則使用與默認網關相連的接口。
- 查詢選擇的網絡接口的 MAC 地址
- 我們發送一個二層(
OSI 模型
_ 中的數據鏈路層)ARP 請求:
ARP Request
::
Sender MAC: interface:mac:address:here
Sender IP: interface.ip.goes.here
Target MAC: FF:FF:FF:FF:FF:FF (Broadcast)
Target IP: target.ip.goes.here
根據連接主機和路由器的硬件類型不同,可以分為以下幾種情況:
直連:
- 如果我們和路由器是直接連接的,路由器會返回一個
ARP Reply
(見下面)。
集線器:
- 如果我們連接到一個集線器,集線器會把 ARP 請求向所有其它端口廣播,如果路由器也“連接”在其中,它會返回一個
ARP Reply
。
交換機:
- 如果我們連接到了一個交換機,交換機會檢查本地 CAM/MAC 表,看看哪個端口有我們要找的那個 MAC 地址,如果沒有找到,交換機會向所有其它端口廣播這個 ARP 請求。
- 如果交換機的 MAC/CAM 表中有對應的條目,交換機會向有我們想要查詢的 MAC 地址的那個端口發送 ARP 請求
- 如果路由器也“連接”在其中,它會返回一個
ARP Reply
ARP Reply
::
Sender MAC: target:mac:address:here
Sender IP: target.ip.goes.here
Target MAC: interface:mac:address:here
Target IP: interface.ip.goes.here
現在我們有了 DNS 服務器或者默認網關的 IP 地址,我們可以繼續 DNS 請求了:
- 使用 53 端口向 DNS 服務器發送 UDP 請求包,如果響應包太大,會使用 TCP 協議
- 如果本地/ISP DNS 服務器沒有找到結果,它會發送一個遞歸查詢請求,一層一層向高層 DNS 服務器做查詢,直到查詢到起始授權機構,如果找到會把結果返回
使用套接字
當瀏覽器得到了目標服務器的 IP 地址,以及 URL 中給出來端口號(http 協議默認端口號是 80, https 默認端口號是 443),它會調用系統庫函數 socket
,請求一個
TCP流套接字,對應的參數是 AF_INET/AF_INET6
和 SOCK_STREAM
。
- 這個請求首先被交給傳輸層,在傳輸層請求被封裝成 TCP segment。目標端口會被加入頭部,源端口會在系統內核的動態端口范圍內選?。↙inux下是ip_local_port_range)
- TCP segment 被送往網絡層,網絡層會在其中再加入一個 IP 頭部,里面包含了目標服務器的IP地址以及本機的IP地址,把它封裝成一個TCP packet。
- 這個 TCP packet 接下來會進入鏈路層,鏈路層會在封包中加入 frame頭 部,里面包含了本地內置網卡的MAC地址以及網關(本地路由器)的 MAC 地址。像前面說的一樣,如果內核不知道網關的 MAC 地址,它必須進行 ARP 廣播來查詢其地址。
到了現在,TCP 封包已經準備好了,可以使用下面的方式進行傳輸:
-
以太網
_ -
WiFi
_ -
蜂窩數據網絡
_
對于大部分家庭網絡和小型企業網絡來說,封包會從本地計算機出發,經過本地網絡,再通過調制解調器把數字信號轉換成模擬信號,使其適于在電話線路,有線電視光纜和無線電話線路上傳輸。在傳輸線路的另一端,是另外一個調制解調器,它把模擬信號轉換回數字信號,交由下一個 網絡節點
_ 處理。節點的目標地址和源地址將在后面討論。
大型企業和比較新的住宅通常使用光纖或直接以太網連接,這種情況下信號一直是數字的,會被直接傳到下一個 網絡節點
_ 進行處理。
最終封包會到達管理本地子網的路由器。在那里出發,它會繼續經過自治區域(autonomous system, 縮寫 AS)的邊界路由器,其他自治區域,最終到達目標服務器。一路上經過的這些路由器會從IP數據報頭部里提取出目標地址,并將封包正確地路由到下一個目的地。IP數據報頭部 time to live (TTL) 域的值每經過一個路由器就減1,如果封包的TTL變為0,或者路由器由于網絡擁堵等原因封包隊列滿了,那么這個包會被路由器丟棄。
上面的發送和接受過程在 TCP 連接期間會發生很多次:
- 客戶端選擇一個初始序列號(ISN),將設置了 SYN 位的封包發送給服務器端,表明自己要建立連接并設置了初始序列號
- 服務器端接收到 SYN 包,如果它可以建立連接:
- 服務器端選擇它自己的初始序列號
- 服務器端設置 SYN 位,表明自己選擇了一個初始序列號
- 服務器端把 (客戶端ISN + 1) 復制到 ACK 域,并且設置 ACK 位,表明自己接收到了客戶端的第一個封包
- 客戶端通過發送下面一個封包來確認這次連接:
- 自己的序列號+1
- 接收端 ACK+1
- 設置 ACK 位
- 數據通過下面的方式傳輸:
- 當一方發送了N個 Bytes 的數據之后,將自己的 SEQ 序列號也增加N
- 另一方確認接收到這個數據包(或者一系列數據包)之后,它發送一個 ACK 包,ACK 的值設置為接收到的數據包的最后一個序列號
- 關閉連接時:
- 要關閉連接的一方發送一個 FIN 包
- 另一方確認這個 FIN 包,并且發送自己的 FIN 包
- 要關閉的一方使用 ACK 包來確認接收到了 FIN
UDP 數據包
TLS 握手
--------
* 客戶端發送一個 ``Client hello`` 消息到服務器端,消息中同時包含了它的 Transport Layer Security (TLS) 版本,可用的加密算法和壓縮算法。
* 服務器端向客戶端返回一個 ``Server hello`` 消息,消息中包含了服務器端的TLS版本,服務器選擇了哪個加密和壓縮算法,以及服務器的公開證書,證書中包含了公鑰??蛻舳藭褂眠@個公鑰加密接下來的握手過程,直到協商生成一個新的對稱密鑰
* 客戶端根據自己的信任CA列表,驗證服務器端的證書是否有效。如果有效,客戶端會生成一串偽隨機數,使用服務器的公鑰加密它。這串隨機數會被用于生成新的對稱密鑰
* 服務器端使用自己的私鑰解密上面提到的隨機數,然后使用這串隨機數生成自己的對稱主密鑰
* 客戶端發送一個 ``Finished`` 消息給服務器端,使用對稱密鑰加密這次通訊的一個散列值
* 服務器端生成自己的 hash 值,然后解密客戶端發送來的信息,檢查這兩個值是否對應。如果對應,就向客戶端發送一個 ``Finished`` 消息,也使用協商好的對稱密鑰加密
* 從現在開始,接下來整個 TLS 會話都使用對稱秘鑰進行加密,傳輸應用層(HTTP)內容
TCP 數據包
HTTP 協議···
如果瀏覽器是 Google 出品的,它不會使用 HTTP 協議來獲取頁面信息,而是會與服務器端發送請求,商討使用 SPDY 協議。
如果瀏覽器使用 HTTP 協議,它會向服務器發送這樣的一個請求::
GET / HTTP/1.1
Host: google.com
[其他頭部]
“其他頭部”包含了一系列的由冒號分割開的鍵值對,它們的格式符合HTTP協議標準,它們之間由一個換行符分割開來。這里我們假設瀏覽器沒有違反HTTP協議標準的bug,同時瀏覽器使用 HTTP/1.1
協議,不然的話頭部可能不包含 Host
字段,同時 GET
請求中的版本號會變成 HTTP/1.0
或者 HTTP/0.9
。
HTTP/1.1 定義了“關閉連接”的選項 "close",發送者使用這個選項指示這次連接在響應結束之后會斷開::
Connection:close
不支持持久連接的 HTTP/1.1 必須在每條消息中都包含 "close" 選項。
在發送完這些請求和頭部之后,瀏覽器發送一個換行符,表示要發送的內容已經結束了。
服務器端返回一個響應碼,指示這次請求的狀態,響應的形式是這樣的::
200 OK
[響應頭部]
然后是一個換行,接下來有效載荷(payload),也就是 www.google.com
的HTML內容。服務器下面可能會關閉連接,如果客戶端請求保持連接的話,服務器端會保持連接打開,以供以后的請求重用。
如果瀏覽器發送的HTTP頭部包含了足夠多的信息(例如包含了 Etag 頭部,以至于服務器可以判斷出,瀏覽器緩存的文件版本自從上次獲取之后沒有再更改過,服務器可能會返回這樣的響應::
304 Not Modified
[響應頭部]
這個響應沒有有效載荷,瀏覽器會從自己的緩存中取出想要的內容。
在解析完 HTML 之后,瀏覽器和客戶端會重復上面的過程,直到HTML頁面引入的所有資源(圖片,CSS,favicon.ico等等)全部都獲取完畢,區別只是頭部的 GET / HTTP/1.1
會變成 GET /$(相對www.google.com的URL) HTTP/1.1
。
如果HTML引入了 www.google.com
域名之外的資源,瀏覽器會回到上面解析域名那一步,按照下面的步驟往下一步一步執行,請求中的 Host
頭部會變成另外的域名。
HTTP 服務器請求處理
HTTPD(HTTP Daemon)在服務器端處理請求/響應。最常見的 HTTPD 有 Linux 上常用的 Apache 和 nginx,以及 Windows 上的 IIS。
- HTTPD 接收請求
- 服務器把請求拆分為以下幾個參數:
- HTTP 請求方法(
GET
,POST
,HEAD
,PUT
,DELETE
,CONNECT
,OPTIONS
, 或者TRACE
)。直接在地址欄中輸入 URL 這種情況下,使用的是 GET 方法 - 域名:google.com
- 請求路徑/頁面:/ (我們沒有請求google.com下的指定的頁面,因此 / 是默認的路徑)
- HTTP 請求方法(
- 服務器驗證其上已經配置了 google.com 的虛擬主機
- 服務器驗證 google.com 接受 GET 方法
- 服務器驗證該用戶可以使用 GET 方法(根據 IP 地址,身份信息等)
- 如果服務器安裝了 URL 重寫模塊(例如 Apache 的 mod_rewrite 和 IIS 的 URL Rewrite),服務器會嘗試匹配重寫規則,如果匹配上的話,服務器會按照規則重寫這個請求
- 服務器根據請求信息獲取相應的響應內容,這種情況下由于訪問路徑是 "/" ,會訪問首頁文件(你可以重寫這個規則,但是這個是最常用的)。
- 服務器會使用指定的處理程序分析處理這個文件,假如 Google 使用 PHP,服務器會使用 PHP 解析 index 文件,并捕獲輸出,把 PHP 的輸出結果返回給請求者
瀏覽器背后的故事
當服務器提供了資源之后(HTML,CSS,JS,圖片等),瀏覽器會執行下面的操作:
- 解析 —— HTML,CSS,JS
- 渲染 —— 構建 DOM 樹 -> 渲染 -> 布局 -> 繪制
瀏覽器
瀏覽器的功能是從服務器上取回你想要的資源,然后展示在瀏覽器窗口當中。資源通常是 HTML 文件,也可能是 PDF,圖片,或者其他類型的內容。資源的位置通過用戶提供的 URI(Uniform Resource Identifier) 來確定。
瀏覽器解釋和展示 HTML 文件的方法,在 HTML 和 CSS 的標準中有詳細介紹。這些標準由 Web 標準組織 W3C(World Wide Web Consortium) 維護。
不同瀏覽器的用戶界面大都十分接近,有很多共同的 UI 元素:
- 一個地址欄
- 后退和前進按鈕
- 書簽選項
- 刷新和停止按鈕
- 主頁按鈕
瀏覽器高層架構
組成瀏覽器的組件有:
- 用戶界面 用戶界面包含了地址欄,前進后退按鈕,書簽菜單等等,除了請求頁面之外所有你看到的內容都是用戶界面的一部分
- 瀏覽器引擎 瀏覽器引擎負責讓 UI 和渲染引擎協調工作
- 渲染引擎 渲染引擎負責展示請求內容。如果請求的內容是 HTML,渲染引擎會解析 HTML 和 CSS,然后將內容展示在屏幕上
- 網絡組件 網絡組件負責網絡調用,例如 HTTP 請求等,使用一個平臺無關接口,下層是針對不同平臺的具體實現
- UI后端 UI 后端用于繪制基本 UI 組件,例如下拉列表框和窗口。UI 后端暴露一個統一的平臺無關的接口,下層使用操作系統的 UI 方法實現
- Javascript 引擎 Javascript 引擎用于解析和執行 Javascript 代碼
- 數據存儲 數據存儲組件是一個持久層。瀏覽器可能需要在本地存儲各種各樣的數據,例如 Cookie 等。瀏覽器也需要支持諸如 localStorage,IndexedDB,WebSQL 和 FileSystem 之類的存儲機制
HTML 解析
瀏覽器渲染引擎從網絡層取得請求的文檔,一般情況下文檔會分成8kB大小的分塊傳輸。
HTML 解析器的主要工作是對 HTML 文檔進行解析,生成解析樹。
解析樹是以 DOM 元素以及屬性為節點的樹。DOM是文檔對象模型(Document Object Model)的縮寫,它是 HTML 文檔的對象表示,同時也是 HTML 元素面向外部(如Javascript)的接口。樹的根部是"Document"對象。整個 DOM 和 HTML 文檔幾乎是一對一的關系。
解析算法
HTML不能使用常見的自頂向下或自底向上方法來進行分析。主要原因有以下幾點:
- 語言本身的“寬容”特性
- HTML 本身可能是殘缺的,對于常見的殘缺,瀏覽器需要有傳統的容錯機制來支持它們
- 解析過程需要反復。對于其他語言來說,源碼不會在解析過程中發生變化,但是對于 HTML 來說,動態代碼,例如腳本元素中包含的
document.write()
方法會在源碼中添加內容,也就是說,解析過程實際上會改變輸入的內容
由于不能使用常用的解析技術,瀏覽器創造了專門用于解析 HTML 的解析器。解析算法在 HTML5 標準規范中有詳細介紹,算法主要包含了兩個階段:標記化(tokenization)和樹的構建。
解析結束之后
瀏覽器開始加載網頁的外部資源(CSS,圖像,Javascript 文件等)。
此時瀏覽器把文檔標記為“可交互的”,瀏覽器開始解析處于“推遲”模式的腳本,也就是那些需要在文檔解析完畢之后再執行的腳本。之后文檔的狀態會變為“完成”,瀏覽器會進行“加載”事件。
注意解析 HTML 網頁時永遠不會出現“語法錯誤”,瀏覽器會修復所有錯誤,然后繼續解析。
執行同步 Javascript 代碼。
CSS 解析
- 根據
CSS詞法和句法
_ 分析CSS文件和<style>
標簽包含的內容 - 每個CSS文件都被解析成一個樣式表對象,這個對象里包含了帶有選擇器的CSS規則,和對應CSS語法的對象
- CSS解析器可能是自頂向下的,也可能是使用解析器生成器生成的自底向上的解析器
頁面渲染
- 通過遍歷DOM節點樹創建一個“Frame 樹”或“渲染樹”,并計算每個節點的各個CSS樣式值
- 通過累加子節點的寬度,該節點的水平內邊距(padding)、邊框(border)和外邊距(margin),自底向上的計算"Frame 樹"中每個節點首的選(preferred)寬度
- 通過自頂向下的給每個節點的子節點分配可行寬度,計算每個節點的實際寬度
- 通過應用文字折行、累加子節點的高度和此節點的內邊距(padding)、邊框(border)和外邊距(margin),自底向上的計算每個節點的高度
- 使用上面的計算結果構建每個節點的坐標
- 當存在元素使用
floated
,位置有absolutely
或relatively
屬性的時候,會有更多復雜的計算,詳見http://dev.w3.org/csswg/css2/ 和 http://www.w3.org/Style/CSS/current-work - 創建layer(層)來表示頁面中的哪些部分可以成組的被繪制,而不用被重新柵格化處理。每個幀對象都被分配給一個層
- 頁面上的每個層都被分配了紋理(?)
- 每個層的幀對象都會被遍歷,計算機執行繪圖命令繪制各個層,此過程可能由CPU執行柵格化處理,或者直接通過D2D/SkiaGL在GPU上繪制
- 上面所有步驟都可能利用到最近一次頁面渲染時計算出來的各個值,這樣可以減少不少計算量
- 計算出各個層的最終位置,一組命令由 Direct3D/OpenGL發出,GPU命令緩沖區清空,命令傳至GPU并異步渲染,幀被送到Window Server。
GPU 渲染
- 在渲染過程中,圖形處理層可能使用通用用途的 CPU,也可能使用圖形處理器 GPU
- 當使用 GPU 用于圖形渲染時,圖形驅動軟件會把任務分成多個部分,這樣可以充分利用 GPU 強大的并行計算能力,用于在渲染過程中進行大量的浮點計算。
Window Server
后期渲染與用戶引發的處理
渲染結束后,瀏覽器根據某些時間機制運行JavaScript代碼(比如Google Doodle動畫)或與用戶交互(在搜索欄輸入關鍵字獲得搜索建議)。類似Flash和Java的插件也會運行,盡管Google主頁里沒有。這些腳本可以觸發網絡請求,也可能改變網頁的內容和布局,產生又一輪渲染與繪制。
.. _Creative Commons Zero
: https://creativecommons.org/publicdomain/zero/1.0/
.. _CSS詞法和句法
: http://www.w3.org/TR/CSS2/grammar.html
.. _Punycode
: https://en.wikipedia.org/wiki/Punycode
.. _以太網
: http://en.wikipedia.org/wiki/IEEE_802.3
.. _WiFi
: https://en.wikipedia.org/wiki/IEEE_802.11
.. _蜂窩數據網絡
: https://en.wikipedia.org/wiki/Cellular_data_communication_protocol
.. _analog-to-digital converter
: https://en.wikipedia.org/wiki/Analog-to-digital_converter
.. _網絡節點
: https://en.wikipedia.org/wiki/Computer_network#Network_nodes
.. 不同的操作系統有所不同
: https://en.wikipedia.org/wiki/Hosts%28file%29#Location_in_the_file_system
.. _downgrade attack
: http://en.wikipedia.org/wiki/SSL_stripping
.. _OSI 模型
: https://en.wikipedia.org/wiki/OSI_model