在翻譯這篇文章Tasks, microtasks, queues and schedules時,有一個不懂之處:"All windows on the same origin share an event loop as they can synchronously communicate."Google以後就有了這篇文章。
首先解釋一下這裏的同源是什麼意思。千萬不要把瀏覽器的同源策略混起來,這裏的同源和那裏同域是兩回事。
同源就是下文中指的瀏覽器實例:javascript
咱們將一組用相互用script鏈接的Tabs稱爲一個瀏覽實例,它對應於HTML5規範中的「相關的瀏覽上下文單元」。 該組由一個選項卡和任何其餘使用Javascript代碼打開的選項卡組成。
例若有ABC三個頁面,在A中執行window.open(B),那麼AB就是同源,B又打開了C,則ABC就是同源。html
原文地址:http://hassansin.github.io/sh...前端
我最近看到一篇文章上說:「來自同一個源的全部窗口共享一個事件循環,它們也能夠同步通訊。」 照它這麼說 -- 若是我在瀏覽器上打開了多個Tab(由於選項卡與現代瀏覽器中的窗口基本相同),Tab來自同一主機的不一樣頁面,它們全都將呈如今單個線程中。 可是這沒根本不可能吧,由於Chrome在其本身的進程中運行每一個Tab。 他們沒法共享相同的事件循環。 文章說法有待考證。java
使用chrome任務管理器的快速測試證實我是正確的。每一個具備來自相同域的Tab確實是在單獨的進程中運行。 可是當我在Chrome任務管理器進程中進行挖掘時,我注意到一些Tab是在相同進程ID下運行的。 例如:git
Tabs甚至不是來自同一個域卻在同一個進程裏面。 因此這裏發生了什麼? 谷歌快速搜索後,事實證實,Chrome有一個複雜的流程模型:chromium.org/developers/design-documents/process-models。 默認狀況下,Chrome使用process-per-site-instance模型,即:github
Chromium會爲用戶訪問的每一個站點實例建立一個渲染器進程。 這確保了來自不一樣站點的頁面被獨立渲染,而且對同一站點的單獨訪問也彼此隔離。 所以,一個站點的一個實例中的失敗(例如渲染器崩潰)或資源佔用率太高不會影響瀏覽器的其他部分。 **該模型基於內容的源和相互執行腳本的選項卡之間的關係。** 所以,兩個選項卡可能會顯示在任務管理器的同一個進程中,而當在已經打開的一個頁面的選項卡中導航到跨站頁面時,可能會切換選項卡的渲染器進程。
但事實上,我認爲實際狀況比上述內容更復雜。 Ctrl + click
打開來自同一頁面的不一樣連接有時會在同一個進程中打開這些連接,有時不會 -- 無論它們的域是什麼。web
無論那些了,我迫切地想測試一下這些Tab是否真的共享相同的Event Loop。 因此我寫了一個長時間運行的同步任務。 你猜怎麼了! 這只是一個空循環:chrome
function longrunning(){ for (let i=0; i<10000000000; i++); }
而後,我須要將其注入到這些tabs-per-process的其中一箇中去。 有一個很好的擴展稱爲Custom JavaScript for websites
能夠作到這一點。 當我使用此擴展插入腳本並運行它時,它將進程上的全部選項卡都掛起了。 任務完成。 我還歷來沒有這麼高興地看到了像這樣被掛起地頁面。segmentfault
回到我剛纔討論的第一篇文章。 它也提到這些窗口還能夠同步進行相互通訊。 因此這些Tab必須以某種方式相互鏈接。 從關於Chrome進程模型的文章:windows
咱們將一組用相互用script鏈接的Tabs稱爲一個瀏覽實例,它對應於HTML5規範中的「相關的瀏覽上下文單元」。 該組由一個選項卡和任何其餘使用Javascript代碼打開的選項卡組成。 這些選項卡必須在同一個進程中呈現,以容許在它們之間進行Javascript調用(最多見的是來自同一源的頁面之間)。
好吧,這意味着咱們須要使用JavaScript打開它們,才能鏈接窗口。 實際上有幾種方法能夠在javascript中執行此操做。 使用 iframe
,window.frames
和window.open
。 而且要相互通訊的話,咱們可使用window.postMessage
web api。 咱們還能夠輕鬆測試使用window.open
打開的選項卡是否共享相同的事件循環。 我準備了這個演示頁面,使用window.open打開一個彈出窗口。 而後,頂部窗口和子窗口都運行一些同步任務,咱們能夠看到它們是如何相互影響的。
演示在這裏demo。 你須要讓瀏覽器容許彈出窗口才能看到效果。
top.html:
<html> <head> <title>Top window</title> <script> function longrunning(){ for(let i=0;i<2000000000;i++); } let t0 let t1 const elapsedTime = () => { if(!t0) { t0 = performance.now() t1 = t0 } else { t1 = performance.now() } return ((t1-t0)/1000).toFixed(2) } window.parentLogger = (str) => { console.log("[%s] TOP: %s", elapsedTime(), str) } window.childLogger = (str) => { console.log("[%s] CHILD: %s", elapsedTime(), str) } parentLogger('before opening popup') const popup = window.open('child.html'); // var popup = window.open('/child.html', '', 'noopener=true'); if(popup) { parentLogger(`after popup opened, popup window url: ${popup.location.href}`) } parentLogger('starting long synchronous process. This will prevent loading and parsing of popup window') longrunning(); parentLogger('finished long synchronous process.') parentLogger('adding 1s timeout.') setTimeout(function(){ parentLogger('timed out') },1000) </script> </head> <body></body> </html>
child.html:
<html> <head> <title>Child window</title> <script> function longrunning(){ for(let i=0;i<2000000000;i++); } window.addEventListener('DOMContentLoaded', e => window.opener.childLogger(`popup initial html loaded, popup window url: ${window.location.href}`)) window.opener.childLogger('starting long synchronous process inside popup window. This will prevent the event loop in top window') longrunning() window.opener.childLogger('finished long synchronous process inside popup window.') // window.close() </script> </head> <body></body> </html>
不過,這裏有top.html中控制檯的輸出:
[0.00] TOP: before opening popup [0.01] TOP: after popup opened, popup window url: about:blank [0.01] TOP: starting long synchronous process. This will prevent loading and parsing of popup window [4.93] TOP: finished long synchronous process. [4.93] TOP: adding 1s timeout. [5.82] CHILD: starting long synchronous process inside popup window. This will prevent the event loop in top window [10.79] CHILD: finished long synchronous process inside popup window. [11.15] CHILD: popup initial html loaded, popup window url: http://localhost:4000/assets/chrome-process-models/child.html [11.18] TOP: timed out
你能夠在每一個事件的方括號中查看以秒計的總時間。 TOP
表示它是從父窗口記錄的,而CHILD
表示它是從彈出窗口記錄的。如下是發生了什麼事情的簡要介紹:
window.open
以後檢查彈出的URL時,它被設置爲about:blank
。實際上URL的獲取被延遲,並在當前腳本塊執行完成後開始因此從彈出窗口中加載內容的時間點以及在頂部窗口中觸發setTimeout回調的時間點能夠清楚地看到,它們都共享相同的事件循環。
那麼咱們如何讓同源窗口在它本身的進程中運行而不影響彼此的事件循環呢? 事實證實,咱們能夠在window.open()
中傳遞一個選項noopener
。 可是使用該選項也會失去對父窗口的引用。 因此咱們沒法使用window.postMessage()
在窗口之間進行通訊。
全部這些行爲在不一樣的瀏覽器中可能會有所不一樣。 這實際上都是特定於瀏覽器的實現。 咱們甚至能夠在Chrome中傳遞不一樣的標誌並選擇不一樣的過程模型。
這篇文章給出了最終答案:來自同一個源的Tabs共享相同的事件循環。
可使用JS調用的方式打開(例如window.open)的Tabs建立同一源,即便這些Tabs不一樣域。
始終強調一點須要注意:全部這些行爲在不一樣的瀏覽器中可能會有所不一樣。
打開chrome的任務管理,能夠看到任務狀況。
例如我在當前頁面控制檯執行了一個
window.open('https://segmentfault.com/a/1190000014833359');
打開了一個新的Tab,可是:
在同一個進程裏。
我又在當前頁面控制檯執行了一個
window.open('https://www.baidu.com');
又打開了一個新的Tab,
嗯,仍是同一個進程。
這裏說明了共享事件循環的可行性。
多個Tabs共享相同的事件循環確定會相互影響,除了使用在window.open()
中傳遞一個選項noopener
的方法外,咱們要注意儘可能減小使用window.open,使用a標籤就不會出現這樣的問題。固然咱們還能夠在適當的時候調用window.close()
關閉不須要的Tab。
文章講到,它們能夠同步通訊。固然,不多使用window.open的方式來相互通訊,可是ifame倒是很經常使用的 -- ifram中能夠加載別的域的頁面。在建立了iframe以後是能夠拿到ifame的實例的。而後就可使用postMessage相互通訊了。
postMessage解決了:
a.) 頁面和其打開的新窗口的數據傳遞 b.) 多窗口之間消息傳遞 c.) 頁面與嵌套的iframe消息傳遞 d.) 上面三個場景的跨域數據傳遞
下面是一個實際的例子:
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe> <script> var iframe = document.getElementById('iframe'); iframe.onload = function() { var data = { name: 'aym' }; // 向domain2傳送跨域數據 iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com'); }; // 接受domain2返回數據 window.addEventListener('message', function(e) { alert('data from domain2 ---> ' + e.data); }, false); </script>
更加全面的跨域知識請看:前端常見跨域解決方案(全)
By default, Chromium creates a renderer process for each instance of a site the user visits. This ensures that pages from different sites are rendered independently, and that separate visits to the same site are also isolated from each other. Thus, failures (e.g., renderer crashes) or heavy resource usage in one instance of a site will not affect the rest of the browser. This model is based on both the origin of the content and relationships between tabs that might script each other. As a result, two tabs may display pages that are rendered in the same process, while navigating to a cross-site page in a given tab may switch the tab's rendering process. (Note that there are important caveats in Chromium's current implementation, discussed in the Caveats section below.)
Concretely, we define a "site" as a registered domain name (e.g., google.com or bbc.co.uk) plus a scheme (e.g., https://). This is similar to the origin defined by the Same Origin Policy, but it groups subdomains (e.g., mail.google.com and docs.google.com) and ports (e.g., http://foo.com:8080) into the same site. This is necessary to allow pages that are in different subdomains or ports of a site to access each other via Javascript, which is permitted by the Same Origin Policy if they set their document.domain variables to be identical.
A"site instance" is a collection of connected pages from the same site. We consider two pages as connected if they can obtain references to each other in script code (e.g., if one page opened the other in a new window using Javascript).