Preface
Page previews in low-code editors often have to use iframes for external link url introduction, which involves the problem of data communication between the preview page and the editor page. The frequently used solution is to pass the value of postMessage, and postMessage It is also a macro task in eventloop, which will involve the problem of browser message queue processing. This article aims to summarize the related pit practice of postMessage in the project, and also provide some avoidance for children who want to use postMessage to transfer data. Pit ideas.
Scenes
The large screen of the private network self-service project is deployed on another url, so the ui that needs to be previewed has to be nested using iframe, and here a series of information such as token needs to be passed to the large screen, here postMessage is used to transfer the value
Case study
[bug description] In the process of passing postMessage, data cannot be passed by simulating a click event
[bug analysis] postMessage is a macro task. The trigger mechanism will be placed in the browser's message queue first, and then processed. Vue and react will implement their own event mechanism by themselves, instead of triggering the actual browser event mechanism.
[Solution] Use setTimeout processing, put the event processing in the browser idle stage to trigger the processing of the callback function, and you need to pay attention to the size of the message passed
recurrent
Referenced address
Use express to start a static service, the page in the iframe
<!DOCTYPE html>
<html lang="en">
<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>3000</title>
</head>
<body>
<h1>
这是一个前端BFF应用
</h1>
<div id="oDiv">
</div>
<script>
console.log('name', window.name)
oDiv.innerHTML = window.name;
// window.addEventListener('message', function(e){
// console.log('data', e.data)
// oDiv.innerHTML = e.data;
// })
</script>
</body>
</html>
When the message comes back, it will be displayed on the page
vue application
vue-cli launched a simple file to import iframe pages
<template>
<div id="container">
<iframe
ref="ifr"
id="ifr"
:name="name"
:allowfullscreen="full"
:width="width"
:height="height"
:src="src"
frameborder="0"
>
<p>你的浏览器不支持iframes</p >
</iframe>
</div>
</template>
<script>
export default {
props: {
src: {
type: String,
default: '',
},
width: {
type: String | Number,
},
height: {
type: String | Number,
},
id: {
type: String,
default: '',
},
content: {
type: String,
default: '',
},
full: {
type: Boolean,
default: false,
},
name: {
type: String,
default: '',
},
},
mounted() {
// this.postMessage()
this.createPost()
},
methods: {
createPost() {
const btn = document.createElement('a')
btn.setAttribute('herf', 'javascript:;')
btn.setAttribute(
'onclick',
"document.getElementById('ifr').contentWindow.postMessage('123', '*')"
)
btn.innerHTML = 'postMessage'
document.getElementById('container').appendChild(btn)
btn.click()
// document.getElementById('container').removeChild(btn)
},
postMessage() {
document.getElementById('ifr').contentWindow.postMessage('123', '*')
}
},
}
</script>
<style>
</style>
react app
Use create-react-app to start a react application, and try it through functional components and class components.
Functional component
// 函数式组件
import { useRef, useEffect } from 'react'
const createBtn = () => {
const btn = document.createElement('a')
btn.setAttribute('herf', 'javascript:;')
btn.setAttribute(
'onclick',
"document.getElementById('ifr').contentWindow.postMessage('123', '*')",
)
btn.innerHTML = 'postMessage'
document.getElementById('container').appendChild(btn)
btn.click()
// document.getElementById('container').removeChild(btn)
}
const Frame = (props) => {
const { name, full, width, height, src } = props
const ifr = useRef(null)
useEffect(() => {
createBtn()
}, [])
return (
<div id="container">
<iframe
id="ifr"
width="100%"
height="540px"
src="http://localhost:3000"
frameBorder="0"
>
<p>你的浏览器不支持iframes</p >
</iframe>
</div>
)
}
export default Frame
Class component
// 类组件
import React from 'react'
const createBtn = () => {
const btn = document.createElement('a')
btn.setAttribute('herf', 'javascript:;')
btn.setAttribute(
'onclick',
"document.getElementById('ifr').contentWindow.postMessage('123', '*')",
)
btn.innerHTML = 'postMessage'
document.getElementById('container').appendChild(btn)
btn.click()
// document.getElementById('container').removeChild(btn)
}
class OtherFrame extends React.Component {
constructor(props) {
super(props)
}
componentDidMount() {
createBtn()
}
render() {
return (
<div id="container">
<iframe
id="ifr"
width="100%"
height="540px"
src="http://localhost:3000"
frameBorder="0"
>
<p>你的浏览器不支持iframes</p >
</iframe>
</div>
)
}
}
export default OtherFrame
Native application
Using native js to write, you can bind events by creating buttons and binding events by a tag, which has no effect
<!DOCTYPE html>
<html lang="en">
<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>原生js</title>
<script>
</script>
</head>
<body>
<iframe id="ifr" src="http://localhost:3000" width="100%" height="540px" frameborder="0"></iframe>
<!-- <script>
window.onload = function() {
const btn = document.createElement('button');
btn.innerHTML = 'postMessge'
btn.addEventListener('click', function() {
ifr.contentWindow.postMessage('123', "*")
})
document.body.appendChild(btn)
btn.click()
// document.body.removeChild(btn)
}
</script> -->
<script>
window.onload = function () {
const btn = document.createElement('a')
btn.setAttribute('herf', 'javascript:;')
btn.setAttribute(
'onclick',
"document.getElementById('ifr').contentWindow.postMessage('123', '*')"
)
btn.innerHTML = 'postMessage'
document.body.appendChild(btn)
btn.click()
// document.body.removeChild(btn)
}
</script>
</body>
</html>
Source code
The above several examples use simulated click events in order to clearly display the label. It is found that the message information can be obtained through the page click event (by way of the page handle), but both vue and react proxy the event, so that the attachEvent cannot be used. Carry out self-generated label adding event
vue
// on实现原理
// v-on是一个指令,vue中通过wrapListeners进行了一个包裹,而wrapListeners的本质是一个bindObjectListeners的renderHelper方法,将事件名称放在了一个listeners监听器中
Vue.prototype.$on = function(event, fn) {
if(Array.isArray(event)) {
for(let i=0, l=event.length; i < l; i++) {
this.$on(event[i], fn)
}
} else {
(this._events[event] || this._events[event] = []).push(fn)
}
return this;
}
Vue.prototype.$off = function (event, fn) {
// all
if (!arguments.length) {
this._events = Object.create(null)
return this
}
// array of events
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$off(event[i], fn)
}
return this
}
// specific event
const cbs = this._events[event]
if (!cbs) {
return this
}
if (!fn) {
this._events[event] = null
return this
}
// specific handler
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return this
}
Vue.prototype.$emit = function (event) {
let cbs = this._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
const args = toArray(arguments, 1)
}
return this
}
react
// 合成事件
function createSyntheticEvent(Interface: EventInterfaceType) {
function SyntheticBaseEvent(
reactName: string | null,
reactEventType: string,
targetInst: Fiber,
nativeEvent: {[propName: string]: mixed},
nativeEventTarget: null | EventTarget,
) {
this._reactName = reactName;
this._targetInst = targetInst;
this.type = reactEventType;
this.nativeEvent = nativeEvent;
this.target = nativeEventTarget;
this.currentTarget = null;
for (const propName in Interface) {
if (!Interface.hasOwnProperty(propName)) {
continue;
}
const normalize = Interface[propName];
if (normalize) {
this[propName] = normalize(nativeEvent);
} else {
this[propName] = nativeEvent[propName];
}
}
const defaultPrevented =
nativeEvent.defaultPrevented != null
? nativeEvent.defaultPrevented
: nativeEvent.returnValue === false;
if (defaultPrevented) {
this.isDefaultPrevented = functionThatReturnsTrue;
} else {
this.isDefaultPrevented = functionThatReturnsFalse;
}
this.isPropagationStopped = functionThatReturnsFalse;
return this;
}
Object.assign(SyntheticBaseEvent.prototype, {
preventDefault: function() {
this.defaultPrevented = true;
const event = this.nativeEvent;
if (!event) {
return;
}
if (event.preventDefault) {
event.preventDefault();
} else if (typeof event.returnValue !== 'unknown') {
event.returnValue = false;
}
this.isDefaultPrevented = functionThatReturnsTrue;
},
stopPropagation: function() {
const event = this.nativeEvent;
if (!event) {
return;
}
if (event.stopPropagation) {
event.stopPropagation();
} else if (typeof event.cancelBubble !== 'unknown') {
event.cancelBubble = true;
}
this.isPropagationStopped = functionThatReturnsTrue;
},
persist: function() {
/
},
isPersistent: functionThatReturnsTrue,
});
return SyntheticBaseEvent;
}
chromium
The implementation of postmessage in chromium mainly realizes the monitoring and distribution of messages through the message in the cast
#include "components/cast/message_port/cast_core/message_port_core_with_task_runner.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/sequence_checker.h"
#include "base/threading/sequenced_task_runner_handle.h"
namespace cast_api_bindings {
namespace {
static uint32_t GenerateChannelId() {
// Should theoretically start at a random number to lower collision chance if
// ports are created in multiple places, but in practice this does not happen
static std::atomic<uint32_t> channel_id = {0x8000000};
return ++channel_id;
}
} // namespace
std::pair<MessagePortCoreWithTaskRunner, MessagePortCoreWithTaskRunner>
MessagePortCoreWithTaskRunner::CreatePair() {
auto channel_id = GenerateChannelId();
auto pair = std::make_pair(MessagePortCoreWithTaskRunner(channel_id),
MessagePortCoreWithTaskRunner(channel_id));
pair.first.SetPeer(&pair.second);
pair.second.SetPeer(&pair.first);
return pair;
}
MessagePortCoreWithTaskRunner::MessagePortCoreWithTaskRunner(
uint32_t channel_id)
: MessagePortCore(channel_id) {}
MessagePortCoreWithTaskRunner::MessagePortCoreWithTaskRunner(
MessagePortCoreWithTaskRunner&& other)
: MessagePortCore(std::move(other)) {
task_runner_ = std::exchange(other.task_runner_, nullptr);
}
MessagePortCoreWithTaskRunner::~MessagePortCoreWithTaskRunner() = default;
MessagePortCoreWithTaskRunner& MessagePortCoreWithTaskRunner::operator=(
MessagePortCoreWithTaskRunner&& other) {
task_runner_ = std::exchange(other.task_runner_, nullptr);
Assign(std::move(other));
return *this;
}
void MessagePortCoreWithTaskRunner::SetTaskRunner() {
task_runner_ = base::SequencedTaskRunnerHandle::Get();
}
void MessagePortCoreWithTaskRunner::AcceptOnSequence(Message message) {
DCHECK(task_runner_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&MessagePortCoreWithTaskRunner::AcceptInternal,
weak_factory_.GetWeakPtr(), std::move(message)));
}
void MessagePortCoreWithTaskRunner::AcceptResultOnSequence(bool result) {
DCHECK(task_runner_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&MessagePortCoreWithTaskRunner::AcceptResultInternal,
weak_factory_.GetWeakPtr(), result));
}
void MessagePortCoreWithTaskRunner::CheckPeerStartedOnSequence() {
DCHECK(task_runner_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&MessagePortCoreWithTaskRunner::CheckPeerStartedInternal,
weak_factory_.GetWeakPtr()));
}
void MessagePortCoreWithTaskRunner::StartOnSequence() {
DCHECK(task_runner_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
task_runner_->PostTask(FROM_HERE,
base::BindOnce(&MessagePortCoreWithTaskRunner::Start,
weak_factory_.GetWeakPtr()));
}
void MessagePortCoreWithTaskRunner::PostMessageOnSequence(Message message) {
DCHECK(task_runner_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&MessagePortCoreWithTaskRunner::PostMessageInternal,
weak_factory_.GetWeakPtr(), std::move(message)));
}
void MessagePortCoreWithTaskRunner::OnPipeErrorOnSequence() {
DCHECK(task_runner_);
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_);
task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&MessagePortCoreWithTaskRunner::OnPipeErrorInternal,
weak_factory_.GetWeakPtr()));
}
bool MessagePortCoreWithTaskRunner::HasTaskRunner() const {
return !!task_runner_;
}
}
Summarize
postMessage seems simple, but in fact it contains the browser’s event loop mechanism and the difference in event processing methods of different VM frameworks. Event processing is a problem worthy of further investigation for the front-end, starting from the single-threaded non-blocking asynchronous paradigm of js The event proxy to the VM framework and various js event libraries (such as EventEmitter, co, etc.) have been running through all aspects of the front-end. The stepping in the project can’t just seek to solve the problem, and more importantly, we can step on To gain a better understanding of the entire programming idea, learn the processing modes of different bigwigs, and use them flexibly, can you improve your technical strength and code elegance, and encourage each other! ! !
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。