在翻译这篇文章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).