从will-a-browser-give-an-iframe-a-separate-thread-for-javascript发现一个有趣东西
Chrome对于不同domain的域名是使用不同的UI线程,也就是iframe不会阻塞宿主,那么意味着iframe也可以当作worker使用了
那么使用zstd decoder
简单验证和对比web worker。
宿主网页HTML, host在 127.0.0.1:8080
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>iframe worker</title>
</head>
<body>
<div id="count"></div>
<script>
let i = 0;
let div = document.getElementById('count');
setInterval(() => {
div.innerText = i++;
}, 100);
// 测试通讯数据传递速度是多少
</script>
<!-- <iframe src="http://192.168.1.3:8081/index.html" frameborder="0"></iframe> -->
<iframe src="http://127.0.0.1:8081/index.html" frameborder="0"></iframe>
</body>
</html>
iframe host在127.0.0.1:8081
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>iframe worker</title>
</head>
<body>
<button id="spin">spin</button>
<script>
const spin = document.getElementById('spin');
spin.addEventListener('click', () => {
const start = Date.now();
while (Date.now() - start < 1000) {}
});
</script>
</body>
</html>
在chrome 91上点击spin按钮宿主网页会卡顿1s,怎么和文中的描述不一致呢?同源策略不是protocol+域名+port相同才算同源么?这里一个端口8080,另一个是8081,难道是127.0.0.1特殊ip导致的?所以分别测试一下组合
宿主:8080 | iframe:8081 | |
---|---|---|
127.0.0.1 | 127.0.0.1 | 阻塞 |
127.0.0.1 | 192.168.1.3 | 不阻塞 |
192.168.1.3 | 127.0.0.1 | 不阻塞 |
192.168.1.3 | 192.168.1.3 | 阻塞 |
这就奇怪了,难道 Chrome 在这里的同源策略检测有Bug? 熟悉C++大佬们可以帮忙翻翻Chromium源码 html_iframe_element.cc帮忙查找原因
既然确定了有OOPIF的策略,那么说明是iframe当作worker是可行的,接下来是实现zstd decode in iframe worker
宿主依然在8080端口,但是iframe使用ip是192.168.1.3
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>iframe worker</title>
</head>
<body>
<div id="count"></div>
<script>
let i = 0;
let div = document.getElementById('count');
setInterval(() => {
div.innerText = i++;
}, 100);
</script>
<iframe src="http://192.168.1.3:8081/index.html" frameborder="0"></iframe>
<!-- <iframe src="http://127.0.0.1:8081/index.html" frameborder="0"></iframe> -->
<script type="module">
import { ZSTDDecoderWorker } from './ZSTDDecoderWorker.js';
/** @type HTMLIFrameElement */
const $worker = document.getElementsByTagName('iframe')[0];
const channel = new MessageChannel();
const decoder = new ZSTDDecoderWorker(1);
Promise.all([
fetch('./group1-shard1of2.bin.zst').then(i => i.arrayBuffer()),
new Promise(resolve => ($worker.onload = resolve)),
decoder.init(),
]).then(([zstBuffer]) => {
console.log(zstBuffer);
const data = zstBuffer.slice(0);
$worker.contentWindow.postMessage({ time: Date.now() }, '*', [
channel.port2,
]);
const dataSendTime = Date.now();
channel.port1.postMessage({ data, time: dataSendTime }, [data]);
channel.port1.onmessage = async e => {
const { data, time } = e.data;
const dataReceivedTime = Date.now();
console.log('data from msg channel', dataReceivedTime - time);
console.log('iframe worker decode time', dataReceivedTime - dataSendTime);
const dataInput = zstBuffer.slice(0);
const decodeStartTime = Date.now();
const output = await decoder.decode(new Uint8Array(dataInput));
const decodeEndTime = Date.now();
console.log('worker decode time', decodeEndTime - decodeStartTime);
};
// 经测试无法直接把buffer传递到iframe,需要使用MessageChannel的postMessage
// $worker.contentWindow.postMessage(
// { zstBuffer, time: Date.now() },
// '*',
// [zstBuffer],
// );
});
</script>
</body>
</html>
iframe依然在8081端口
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>iframe worker</title>
</head>
<body>
<button id="spin">spin</button>
<script>
const spin = document.getElementById('spin');
spin.addEventListener('click', () => {
const start = Date.now();
while (Date.now() - start < 1000) {}
});
</script>
<script type="module">
import { ZSTDDecoder } from './zstddec.module.js';
const zstdDecoder = new ZSTDDecoder();
zstdDecoder.init();
let msgChannelPort2;
window.addEventListener('message', e => {
console.log('msg from main frame', Date.now() - e.data.time);
msgChannelPort2 = e.ports[0];
msgChannelPort2.onmessage = async e => {
const { data, time } = e.data;
const msgReceivedTime = Date.now();
console.log('msg from msg channel', msgReceivedTime - time);
await zstdDecoder.init();
const timeDecoderInit = Date.now();
console.log('zstd init time', timeDecoderInit - msgReceivedTime);
const output = zstdDecoder.decode(new Uint8Array(data));
const decodeEndTime = Date.now();
console.log('zstd decode cost', decodeEndTime - msgReceivedTime);
msgChannelPort2.postMessage({ output, time: decodeEndTime }, [
output.buffer,
]);
};
});
</script>
</body>
</html>
性能结果如下图,iframe worker在OOPIF下传递消息耗时比web worker下大的多
一下是同源的iframe测试结果,同源iframe在同一个线程,msg channel是不耗时的
可clone github仓库 iframe-worker-demo,自行探索下