web communication -- across documents, worker s, channels

Posted by Silverado_NL on Wed, 26 Jan 2022 06:21:59 +0100

Cross document messaging, Cross worker messaging, channel messaging

MessageEvent

Message event MessageEvent() be used for:

Properties:

attributeexplain
dataContains arbitrary string data, which is sent by the original script
originA string containing the scheme, domain name and port of the original document (such as: http://domain.example:80)
lastEventIdA string containing the unique identifier of the current message event.
sourceReference to the window of the original file. Rather, it is a WindowProxy object.
portsAn array containing any MessagePort Object to send a message.

Cross document communication

The most common example is iframe.

otherWindow.postMessage(message, targetOrigin, [transfer])

window.addEventListener("message", (messageEvent) => {}, false)

targetOrigin: specify which windows can receive message events through the origin attribute of the window. Its value can be string "*" (indicating unlimited) or a URI. If you know exactly which window the message should be sent to, please always provide a targetOrigin with an exact value instead of *. Failure to provide an exact target will lead to data disclosure to any malicious site interested in the data.


Main page

<iframe src="iframe_1.html"></iframe>
<iframe src="iframe_2.html"></iframe>

iframe_1.html

<input id="data" />
<button id="btn">send out</button>

<script>
  let btn = document.querySelector('#btn')
  btn.addEventListener('click', function () {
    let data = document.querySelector('#data')
    window.parent.frames[1].postMessage(data.value, '*')
    return false
  })
</script>

iframe_2.html

Received data:<span id="message"></span>

<script>
  let msg = document.querySelector('#message')
  window.addEventListener('message', (msgEvent) => {
    msg.innerHTML = msgEvent.data
  })
</script>

worker communication

A background task that can be created by a script. It can send and receive information to its creator during task execution.

worker.postMessage(aMessage, transferList)

worker.onmessage = (msgEvent) => {}	// ①
worker.addEventListener('message', (msgEvent) => {}) // ②

Main page

const worker = new Worker('worker.js')
worker.postMessage({
  num1: num1.value,
  num2: num2.value
})
worker.onmessage = function (msgEvent) {
  dom.innerHTML = msgEvent.data
}

worker.js

onmessage = function (msgEvent) {
  let {num1, num2} = msgEvent.data
  postMessage(Number(num1) + Number(num2))
}

This can be done functionally (non file)

function createWorker(workerFunc) {
  if (! (workerFunc instanceof Function)) {
    throw new Error('Argument must be function');
  }
  const src = `(${workerFunc})();`;
  const blob = new Blob([src], {type: 'application/javascript'});
  const url = URL.createObjectURL(blob);
  return new Worker(url);    
}

Channel communication

The MessageChannel interface allows us to create a new message channel through its two MessagePort Property to send data.

Example of implementing the above dual iframe:

Port1[iframe2] <===> Port2[iframe1]

const [ifr1, ifr2] = document.querySelectorAll('iframe')
const ifr1Window = ifr1.contentWindow
const ifr2Window = ifr2.contentWindow

// Monitor [iframe] onmessage information
ifr1Window.addEventListener("message", function (msgEvent) {
  ifr1Window.document.querySelector('#btn').addEventListener('click', function () {
    // Transfer the value according to [port2]
    msgEvent.ports[0].postMessage(ifr1Window.document.querySelector('#data').value)
  })
})

ifr2Window.addEventListener("load", function () {
  // Establish information channel
  const channel = new MessageChannel()
  // Listen to the information of [port1]
  channel.port1.onmessage = function (msgEvent) {
    ifr2Window.document.querySelector('#message').innerHTML = msgEvent.data
  }
  // Send information to iframe1 [carry port2]
  ifr1Window.postMessage('I am ready', '*', [channel.port2])
})

iframe value transfer transformation

It can realize the value transfer between multiple iframe s, and more importantly, it can realize the value transfer between multiple web worker s.

Main page

const channel = new MessageChannel()
const w1 = new Worker('worker1.js')
const w2 = new Worker('worker2.js')

w1.postMessage('index', [channel.port1])
w2.postMessage('', [channel.port2])

w2.onmessage = function (msgEvent) {
  console.log(msgEvent.data)
}

worker1.js

onmessage = function ({data, ports}) {
  const port1 = ports[0]
  port1.postMessage(`${data} => worker1`)
}

worker2.js

onmessage = function({ports}) {
  const port2 = ports[0]
  port2.onmessage = function (msgEvent) {
    postMessage(`${msgEvent.data} => worker2`)
  }
}

index => worker1 => worker2

  • index: ① issue port1/2 through worker1/2; ② Monitor worker2.
  • worker1: ① monitor worker1; ② port1 sends a message.
  • Worker2: ① monitor worker2; ② Monitor port2; ③ The worker sends a message.

comlink

The RPC proxy method adopted by Comlink is not to pass the context (because it is very dangerous, and the function passing will lead to Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'Worker': xxx could not be cloned Error reporting).

In essence, it is still MessagePort message communication, but it encapsulates the "operation judgment" we have a headache and handles it in a more elegant way (Proxy + Promise).

RPC: Remote Procedure Call , remote procedure call refers to calling methods different from the current context. It can usually be different threads, domains and network hosts through the provided interface.

index.html

import * as Comlink from "https://unpkg.com/comlink/dist/esm/comlink.mjs"

const worker = new Worker("worker.js")
const cw = Comlink.wrap(worker)
comput.addEventListener('click', async function () {
  let result = await cw(num1.value, num2.value)
  dom.innerHTML = result
})

worker.js

importScripts("https://unpkg.com/comlink/dist/umd/comlink.js")
function add (num1, num2) {
  return Number(num1) + Number(num2)
}
Comlink.expose(add)

importScripts() synchronously imports one or more scripts into the scope of the worker. Subordinate to: WorkerGlobalScope Interface.

Reference link

  • https://developer.mozilla.org/zh-CN/docs/Web/API/MessageEvent
  • https://www.zhangxinxu.com/wordpress/2012/02/html5-web-messaging-cross-document-messaging-channel-messaging/
  • https://developer.mozilla.org/zh-CN/docs/Web/API/MessageChannel

Topics: Front-end