1. 程式人生 > 其它 >課外加餐:1 | 瀏覽上下文組:如何計算 Chrome 中渲染程序的個數?

課外加餐:1 | 瀏覽上下文組:如何計算 Chrome 中渲染程序的個數?

  前言:該篇說明:請見 說明 —— 瀏覽器工作原理與實踐 目錄

 

  在前面《04 | 導航流程》中講過,在預設情況下,如果開啟一個標籤頁,那麼瀏覽器會預設為其建立一個渲染程序。不過還介紹了同一站點的概念,如果從一個標籤頁中打開了另一個新標籤頁,當新標籤頁和當前標籤頁屬於同一站點的話,那麼新標籤頁會複用當前標籤頁的渲染程序。

  具體地講,如果從極客邦 (www.geekbang.org) 的標籤頁中開啟新的極客時間 (time.geekbang.org) 標籤頁,由於這兩個標籤頁屬於同一站點(相同協議、相同根域名),所以它們會共用同一個渲染程序。參考下圖 Chrome 的工作管理員截圖:

 

 

多個標籤頁執行在同一個渲染程序

  從上圖中可以看出,極客邦官網和極客時間標籤頁都共用同一個渲染程序,該程序 ID 是 84748。

  不過如果分別開啟這兩個標籤頁,比如先開啟極客邦的標籤頁,然後再新建一個標籤頁,再在這個標籤頁中開啟極客時間,這時候可以看到這兩個標籤頁分別使用了兩個不同的渲染程序,參考下圖:

 

多個標籤頁執行在不同的渲染程序中

  這時候你一定很好奇,既然都是同一站點,為什麼從 A 標籤頁中開啟 B 標籤頁,就會使用同一個渲染程序,而分別開啟這兩個標籤頁,又會分別使用不同的渲染程序?

 

標籤頁之間的連線

  要搞清楚這個問題,就先來分析下瀏覽器標籤頁之間的連線關係。

  我們知道,瀏覽器標籤頁之間是可以通過 JS 指令碼來連線的,通常情況下有如下幾種連線方式:

  第一種是通過<a>標籤來和新標籤建立連線,這種方式我們最熟悉,如下:

<a href="https://time.geekbang.org/" target="_blank" class="">極客時間</a>

 

  點選該連結會開啟新的標籤頁,新標籤頁中的 window.opener 的值就是指向極客邦標籤頁中的 window,這樣就可以在新的極客時間標籤頁中通過 opener 來操作上個極客邦的標籤頁了。這樣可以說,這兩個標籤頁是有連線的。

  另外,還可以通過 JS 中的 window.open 方法來和新標籤頁建立連線

,如下:

new_window = window.open("http://time.geekbang.org")

  通過上面這種方式,可以在當前標籤頁中通過 new_window 來控制新標籤頁,還可以在新標籤頁中通過 window.opener 來控制當前標籤頁。所以也可以說,如果從 A 標籤頁中通過 window.open 的方式開啟 B 標籤頁,那  A 和 B 標籤頁也是有連線的。

  其實通過上述兩種方式開啟的新標籤頁,不論這兩個標籤頁是否屬於同一站點,它們之間都能通過 opener 來建立連線,所以它們之間是有聯絡的。在 WhatWG 規範中,把這一類具有相互連線關係的標籤頁稱為瀏覽上下文組(browsing context group)。

  既然提到瀏覽上下文組,就有必要提下瀏覽上下文,通常情況下,把一個標籤頁所包含的內容,諸如 window 物件,歷史記錄,滾動條位置等資訊稱為瀏覽上下文。這些通過指令碼相互連線起來的瀏覽上下文就是瀏覽上下文組。規範文件

  也就是說,如果從一個標籤頁A 中,通過連結打開了多個新的標籤頁,不管這幾個新的標籤頁是否是同一站點,它們都和標籤頁A 構成了瀏覽下文組,因為這些標籤頁中的 opener 都指向了 標籤頁 A。

  Chrome 瀏覽器會將瀏覽上下文組中屬於同一站點的標籤分配到同一個渲染程序中,這是因為如果一組標籤頁,既在同一個瀏覽上下文組中,又屬於同一站點,那麼它們可能需要在對方的標籤頁中執行指令碼。因此,它們必須執行在同一渲染程序中。

  現在清楚了瀏覽器是怎麼分配渲染程序的了,接下來就可以來分析文章開頭提的那個問題了:

既然都是同一站點,為什麼從 A 標籤頁中開啟 B 標籤頁,就會使用同一個渲染程序?而分別開啟這兩個標籤頁,又會分別使用不同的渲染程序?

  首先來看第一種,在極客邦標籤頁內部通過連結開啟極客時間標籤頁,那麼極客時間標籤頁和極客邦標籤頁屬於同一個瀏覽上下文組,且它們屬於同一站點,所以瀏覽器會將它們分配到同一個渲染程序之中。

  而第二種情況就簡單多了,因為第二個標籤頁中並沒有第一個標籤頁中的任何資訊,第一個標籤頁也不包含任何第二個標籤頁中的資訊,所以它們不屬於同一個瀏覽上下文組,因此即便它們屬於同一站點,也不會執行在同一個渲染程序之中。參考下圖的計算標籤頁的流程圖:

 

計算標籤頁使用的渲染程序數目

一個“例外”

  現在清楚了 Chrome 瀏覽器為標籤頁分配渲染程序的策略了:

  1. 如果兩個標籤頁都位於同一個瀏覽上下文組,且屬於同一站點,那這兩個標籤頁會被瀏覽器分配到同一個渲染程序中。

  2. 如果這兩個條件不能同時滿足,那這兩個標籤頁會分別使用不同的渲染程序來渲染。

  

  現在你可以想下,如果從 A 標籤頁中開啟 B 標籤頁,那能肯定 A 標籤頁和 B 標籤頁屬於同一瀏覽上下文組嗎?

  答案是“不能”,下面來看下這個問題:

https://linkmarket.aliyun.com 內新開的標籤頁都是新開一個渲染程序,能幫忙解釋下嗎?

  我們先來複現下所描述的現象,首先開啟 linkmarket.aliyun.com 這個標籤頁,再在這個標籤頁中隨便點選兩個連結,然後就打開了兩個新的標籤頁了,如下所示:

 

“例外”情況

  通過 A 標籤頁中的連結打開了兩個新標籤頁,B 和 C,而且也可以看出來,A、B、C 三個標籤頁都屬於同一站點,正常情況下,它們應該共用同一個渲染程序,不過通過上圖可以看出,A、B、C 三個標籤頁分別使用了三個不同的渲染程序。

  既然屬於同一站點,又不在同一個渲染程序中,所以可以推斷這三個標籤頁不屬於同一個瀏覽上下文組,那麼接下來的分析思路就很清晰了:

  1. 首先驗證這三個標籤頁是不是真的不在同一個瀏覽上下文組中;

  2. 然後再分析它們為什麼不在同一瀏覽上下文組。

  為了驗證猜測,可以通過通知臺,來看看 B 標籤頁和 C 標籤頁的 opener 的值,結果發現這兩個標籤頁中的 opener 的值都是 null,這就確定了 B、C 標籤頁和 A 標籤頁沒有連線關係,當然也就不屬於同一瀏覽上下文組了。

  驗證了猜測,接下來就來查查,阿里的這個站點是不是採用了什麼特別的手段,移除了這兩個標籤頁之間的連線關係。

  可以看看實現連結的 HTML 檔案,如下所示:

 

連結使用了 rel = noopener

  通過上圖可以發現, a 連結的 rel 屬性值都使用了 noopener 和 noreferrer,通過 noopener 能猜測到這兩個值是讓被連結的標籤頁和當前標籤頁不要產生連線關係。

  通常,將 noopener 的值引入 rel 屬性中,就是告訴瀏覽器通過這個連結開啟的標籤頁中的 opener 值設定為 null,引入 noreferrer 是告訴瀏覽器,新開啟的標籤頁不要有引入關係。

  到這裡就知道了,通過 linkmarket.aliyun.com 標籤頁開啟新的標籤頁要使用單獨的一個程序,是因為使用了 rel=noopener 的屬性,所以新開啟的標籤頁和現在的標籤頁就沒有了引用關係,當然它們也就不屬於同一瀏覽上下文組了。這也就解答了上面的問題。

 

站點隔離

  上面都是基於標籤頁來分析渲染程序的,不過在《35 | 安全沙箱》中介紹過了,目前 Chrome 瀏覽器已經預設實現了站點隔離的功能,這意味著標籤頁中的 iframe 也會遵守同一站點的分配原則,如果標籤頁中的 iframe 和標籤頁是同一站點,並且有連線關係,那麼標籤頁依然會和當前標籤頁執行在同一個渲染程序中,如果 iframe 和標籤頁不屬於同一站點,那麼 iframe 會執行在單獨的渲染程序中。

  先來看下面這個例子:

<html>
  <head>
      <title>站點隔離:demo</title>
      <style>
          iframe {
              width: 800px;
              height: 300px;
          }
      </style>
  </head>

  <body>
      <div><iframe src="iframe.html"></iframe></div>
      <div><iframe src="https://www.infoq.cn/"></iframe></div>
      <div><iframe src="https://time.geekbang.org/"></iframe></div>
      <div><iframe src="https://www.geekbang.org/"></iframe></div>
  </body>
</html>

 

  在 Chrome 瀏覽器中開啟上面這個標籤頁,然後觀察 Chrome 的任務管理,會發現這個標籤頁使用了四個渲染程序,如下圖:

 

iframe 使用單獨的渲染程序

  結合上圖和 HTML 程式碼可以發現,由於 InfoQ、極客邦兩個 iframe 與 父標籤頁不屬於同一站點,所以它們會被分配到不同的渲染程序中,而 iframe.html 和源標籤頁屬於同一站點,所以它會和源標籤頁執行在同一個渲染程序中。參考下圖的渲染程序數目的流程圖:

 

計算 iframe 所使用的渲染程序數目

 

總結

  本文的主要內容:

  首先使用了兩種不同的方式開啟兩個標籤頁,第一種是從 A 標籤頁中通過連結打開了 B 標籤頁,第二種是分別開啟 A 和 B 標籤頁,這兩種情況下的 A 和 B 都屬於同一站點。

  通過 Chrome 的工作管理員發現,雖然 A 標籤頁 和 B 標籤頁都屬於同一站點,不過通過第一種方式開啟的 A 標籤頁 和 B 標籤頁 會共用同一個渲染程序,而通過第二種方式開啟的兩個標籤頁卻分別使用了兩個不同的渲染程序。

  這是因為,使用同一個渲染程序需要滿足兩個條件:首先 A 標籤頁 和 B 標籤頁屬於同一站點,其次 A 標籤頁 和 B 標籤頁需要有連線關係。

  接著分析了一個“例外”,如果在連結中加入了 rel=noopener 屬性,那麼通過連結開啟的新標籤頁和源標籤頁之間就不會建立連線關係了。

  最後還分析了站點隔離對渲染程序個數的影響,如果 A 標籤頁中的 iframe 和 A 標籤頁屬於同一站點,那麼該 iframe 和 A 標籤頁會共用同一個渲染程序,如果不是,則該 iframe 會使用單獨的渲染程序。

  現在應該會計算渲染程序的個數了。

  在最後還要補充下同源策略對同一站點的限制,雖然 Chrome 會讓有連線且屬於同一站點的標籤頁執行在同一個渲染程序中,不過如果 A 標籤頁 和 B 標籤頁屬於同一站點,卻不屬於同源站點,那麼依然無法通過 opener 來操作父標籤頁中的 DOM,這依然會受到同源策略的限制。

  簡單地講,極客邦和極客時間屬於同一站點,但是它們並不是同源的,因為同源是需要相同域名的,雖然根域名 geekbang.org 相同,但是域名卻是不相同的,一個是 time.geekbang.org,一個是 www.geekbang.org,因此瀏覽器判斷它們不是同源的,所以依然無法通過 time.geekbang.org 標籤頁中的 opener 來操作 www.geekbang.org 中的 DOM。

 

思考題

你認為 Chrome 為什麼使用同一站點劃分渲染程序,而不是使用同源策略來劃分渲染程序?

 

記錄

1、阿里為什麼要把同一站點的 tab 籤做成無連線的,會避免什麼安全隱患?

作者回復:如果多個標籤在同一個程序中,那麼一個標籤淪陷了,其它標籤都會淪陷的

 

2、同源要求協議、域名以及埠均一樣才行;同一站點只要求協議,根域名相同即可。也就是同源的要求太嚴格,導致複用同一渲染程序的條件比較難滿足,所有條件放寬至同一站點?

作者回復:第一原因是通常同一站點安全性是有保障的,第二個原因就是你提到的資源的複用了。