一 前言

在翻译这篇文章Tasks,microtasks,queues and schedules时,有一个不懂之处:"All windows on the same origin share an event loop as they can synchronously communicate."Google之后就有了这篇文章。
首先解释一下这里的同源是什么意思。千万不要把浏览器的同源策略混起来,这里的同源和那里同域是两回事。
同源就是下文中指的浏览器实例:

我们将一组用相互用script连接的Tabs称为一个浏览实例,它对应于HTML5规范中的“相关的浏览上下文单元”。 该组由一个选项卡和任何其他使用Javascript代码打开的选项卡组成。

例如有ABC三个页面,在A中执行window.open(B),那么AB就是同源,B又打开了C,则ABC就是同源。

二 正文

原文地址:http://hassansin.github.io/sh...

我最近看到一篇文章上说:“来自同一个源的所有窗口共享一个事件循环,它们也可以同步通信。” 照它这么说 -- 如果我在浏览器上打开了多个Tab(因为选项卡与现代浏览器中的窗口基本相同),Tab来自同一主机的不同页面,它们全都将呈现在单个线程中。 但是这没根本不可能吧,因为Chrome在其自己的进程中运行每个Tab。 他们无法共享相同的事件循环。 文章说法有待考证。

谷歌浏览器进程的模型

使用chrome任务管理器的快速测试证明我是正确的。每个具有来自相同域的Tab确实是在单独的进程中运行。 但是当我在Chrome任务管理器进程中进行挖掘时,我注意到一些Tab是在相同进程ID下运行的。 例如:

Tabs甚至不是来自同一个域却在同一个进程里面。 所以这里发生了什么? 谷歌快速搜索后,事实证明,Chrome有一个复杂的流程模型:chromium.org/developers/design-documents/process-models。 默认情况下,Chrome使用process-per-site-instance模型,即:

Chromium会为用户访问的每个站点实例创建一个渲染器进程。 这确保了来自不同站点的页面被独立渲染,并且对同一站点的单独访问也彼此隔离。

因此,一个站点的一个实例中的失败(例如渲染器崩溃)或资源占用率过高不会影响浏览器的其余部分。 

**该模型基于内容的源和相互执行脚本的选项卡之间的关系。** 因此,两个选项卡可能会显示在任务管理器的同一个进程中,而当在已经打开的一个页面的选项卡中导航到跨站页面时,可能会切换选项卡的渲染器进程。

但事实上,我认为实际情况比上述内容更复杂。 Ctrl + click 打开来自同一页面的不同链接有时会在同一个进程中打开这些链接,有时不会 -- 不管它们的域是什么。

不管那些了,我迫切地想测试一下这些Tab是否真的共享相同的Event Loop。 所以我写了一个长时间运行的同步任务。 你猜怎么了! 这只是一个空循环:

function longrunning(){
    for (let i=0; i<10000000000; i++);
}

然后,我需要将其注入到这些tabs-per-process的其中一个中去。 有一个很好的扩展称为Custom JavaScript for websites可以做到这一点。 当我使用此扩展插入脚本并运行它时,它将进程上的所有选项卡都挂起了。 任务完成。 我还从来没有这么高兴地看到了像这样被挂起地页面。

窗口之间同步通信

回到我刚才讨论的第一篇文章。 它也提到这些窗口还可以同步进行相互通信。 所以这些Tab必须以某种方式相互连接。 从关于Chrome进程模型的文章:

我们将一组用相互用script连接的Tabs称为一个浏览实例,它对应于HTML5规范中的“相关的浏览上下文单元”。 该组由一个选项卡和任何其他使用Javascript代码打开的选项卡组成。 

这些选项卡必须在同一个进程中呈现,以允许在它们之间进行Javascript调用(最常见的是来自同一源的页面之间)。

好吧,这意味着我们需要使用JavaScript打开它们,才能连接窗口。 实际上有几种方法可以在javascript中执行此操作。 使用 iframewindow.frameswindow.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",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表示它是从弹出窗口记录的。以下是发生了什么事情的简要介绍:

  1. 打开弹出窗口是同步的,但弹出窗口中的内容是异步加载的。这就是为什么当我们在window.open之后检查弹出的URL时,它被设置为about:blank。实际上URL的获取被延迟,并在当前脚本块执行完成后开始
  2. 接下来,我们在顶部窗口中运行长时间运行的任务。这会阻止事件循环和任何pedding的回调。因此,在同步过程完成之前,弹出窗口中的内容将无法加载。
  3. 然后我们在顶部窗口中添加1秒的超时时间。这将完成顶部窗口中的当前脚本块。这意味着现在弹出窗口将有机会加载其内容。
  4. 弹出窗口将开始加载内容并执行它看到的任何JavaScript代码。在弹出窗口内容的顶部,我们再次开始一个长时间运行的任务。只要它正在运行,它就会阻止任何待执行的回调。这意味着我们在顶部窗口的1秒超时也会延迟。
  5. 接下来我们看到DOMContentLoaded事件是针对弹出窗口触发的。当初始HTML文档已被完全加载和解析时,此事件被触发,而无需等待样式表,图像和子帧完成加载。
  6. 最后我们看到在6秒后约1秒的超时回调被触发。

所以从弹出窗口中加载内容的时间点以及在顶部窗口中触发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>

更加全面的跨域知识请看:前端常见跨域解决方案(全)

三 附录

Process-per-site-instance

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).

Shared Event-loop for Same-Origin Windows(译)的更多相关文章

  1. 详解Html5页面实现下载文件(apk、txt等)的三种方式

    这篇文章主要介绍了详解Html5页面实现下载文件(apk、txt等)的三种方式,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. 详解使用postMessage解决iframe跨域通信问题

    这篇文章主要介绍了详解使用postMessage解决iframe跨域通信问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  3. 基于HTML十秒做出淘宝页面

    十分钟做出一个网页,看似不可思议,下面小编给大家带来了基于HTML十秒做出淘宝页面,只分为两步,代码超级简单,需要的朋友参考下吧

  4. html5唤起app的方法

    这篇文章主要介绍了html5唤起app的方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  5. 跨域修改iframe页面内容详解

    这篇文章主要介绍了跨域修改iframe页面内容详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  6. 使用postMessage让 iframe自适应高度的方法示例

    这篇文章主要介绍了使用postMessage让 iframe自适应高度的方法示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

  7. 使用layui框架实现点击左侧导航切换右侧内容且右侧选项卡跟随变化的效果

    这篇文章主要介绍了基于HTML5实现点击左侧导航切换右侧内容且右侧选项卡跟随变化的效果,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  8. ios – 如何调整标签栏徽章位置?

    我在标签栏上显示徽章,但是当数字增加时,它会转到标签栏项目,如图所示我想稍微向左移动徽章视图,使其适合选定的选项卡image.i尝试如here所述,但没有运气.那么有没有办法调整徽章视图位置?任何帮助将不胜感激.解决方法我发现Kateryna的答案对于让我走上正轨非常有用,但我必须稍微更新一下:请注意,选项卡整数不是零索引,因此第一个选项卡将是数字1,第二个选项卡将是2,等等.

  9. ios – UITabBarController – Child(Tab)ViewControllers的不正确和不一致的边界

    我有一个带有两个选项卡的UITabBarController.每个选项卡都是UITableViewController.当UITabBarController出现时,两个选项卡视图都有不正确的边界.第一个选项卡正确位于导航栏下方,但延伸到底部的选项卡栏下方.第二个选项卡是另一种方式,从导航栏下方开始,但在底部的选项卡栏之前正确停止.我正在创建和呈现TabBarController,如下所示:然后在

  10. xcode – 隐藏或丢失构建阶段选项卡

    ..构建阶段选项卡.如何使用工具栏中的“构建阶段”选项卡显示布局,并将其保存以用于我的项目?顺便说一句,我使用XCode3.2可能是版本限制?解决方法听起来这些教程适用于Xcode4.对于您的版本,如果您在侧边栏中打开目标,则应该有一些组.这些是你的构建阶段.只需将库拖到类似“LinkExecutable”之类的库中,或单击复选框将其添加到目标,它应该自动进入.

随机推荐

  1. static – 在页面之间共享数据的最佳实践

    我想知道在UWP的页面之间发送像’selectedItem’等变量的最佳做法是什么?创建一个每个页面都知道的静态全局变量类是一个好主意吗?

  2. .net – 为Windows窗体控件提供百分比宽度/高度

    WindowsForm开发的新手,但在Web开发方面经验丰富.有没有办法为Windows窗体控件指定百分比宽度/高度,以便在用户调整窗口大小时扩展/缩小?当窗口调整大小时,可以编写代码来改变控件的宽度/高度,但我希望有更好的方法,比如在HTML/CSS中.在那儿?

  3. 使用Windows Azure查询表存储数据

    我需要使用特定帐户吗?>将应用程序部署到Azure服务后,如何查询数据?GoogleAppEngine有一个数据查看器/查询工具,Azure有类似的东西吗?>您可以看到的sqlExpressintance仅在开发结构中,并且一旦您表示没有等效,所以请小心使用它.>您可以尝试使用Linqpad查询表格.看看JamieThomson的thispost.

  4. windows – SetupDiGetClassDevs是否与文档中的设备实例ID一起使用?

    有没有更好的方法可以使用DBT_DEVICEARRIVAL事件中的数据获取设备的更多信息?您似乎必须指定DIGCF_ALLCLASSES标志以查找与给定设备实例ID匹配的所有类,或者指定ClassGuid并使用DIGCF_DEFAULT标志.这对我有用:带输出:

  5. Windows Live ID是OpenID提供商吗?

    不,WindowsLiveID不是OpenID提供商.他们使用专有协议.自从他们的“测试版”期结束以来,他们从未宣布计划继续它.

  6. 如果我在代码中进行了更改,是否需要重新安装Windows服务?

    我写了一个Windows服务并安装它.现在我对代码进行了一些更改并重新构建了解决方案.我还应该重新安装服务吗?不,只需停止它,替换文件,然后重新启动它.

  7. 带有双引号的字符串回显使用Windows批处理输出文件

    我正在尝试使用Windows批处理文件重写配置文件.我循环遍历文件的行并查找我想要用指定的新行替换的行.我有一个’函数’将行写入文件问题是%Text%是一个嵌入双引号的字符串.然后失败了.可能还有其他角色也会导致失败.如何才能使用配置文件中的所有文本?尝试将所有“在文本中替换为^”.^是转义字符,因此“将被视为常规字符你可以尝试以下方法:其他可能导致错误的字符是:

  8. .net – 将控制台应用程序转换为服务?

    我正在寻找不同的优势/劣势,将我们长期使用的控制台应用程序转换为Windows服务.我们为ActiveMQ使用了一个叫做java服务包装器的东西,我相信人们告诉我你可以用它包装任何东西.这并不是说你应该用它包装任何东西;我们遇到了这个问题.控制台应用程序是一个.NET控制台应用程序,默认情况下会将大量信息记录到控制台,尽管这是可配置的.任何推荐?我们应该在VisualStudio中将其重建为服务吗?我使用“-install”/“-uninstall”开关执行此操作.例如,seehere.

  9. windows – 捕获外部程序的STDOUT和STDERR *同时*它正在执行(Ruby)

    哦,我在Windows上:-(实际上,它比我想象的要简单,这看起来很完美:…是的,它适用于Windows!

  10. windows – 当我试图批量打印变量时,为什么我得到“Echo is on”

    我想要执行一个简单的批处理文件脚本:当我在XP中运行时,它给了我预期的输出,但是当我在Vista或Windows7中运行它时,我在尝试打印值时得到“EchoisOn”.以下是程序的输出:摆脱集合表达式中的空格.等号(=)的两侧可以并且应该没有空格BTW:我通常在@echo关闭的情况下启动所有批处理文件,并以@echo结束它们,所以我可以避免将代码与批处理文件的输出混合.它只是使您的批处理文件输出更好,更清洁.

返回
顶部