发现问题:
1、content.js,原始网页,iframe之间不同作用域,不能互相通讯;
2、chrome.scripting.executeScript在content.js的作用域中执行,操作不到原始网页资源;
3、v3扩展中content.js不能执行eval。
因此通过封装window.postMessage以实现不同域之间通讯,互相调用。
消息封装:
参考chrome.runtime.onMessage.addListener和chrome.runtime.sendMessage方法实现
windowMessageEvent.js
/**
* 基于window.addEventListener和window.postMessage封装为类似chrome.runtime.onMessage.addListener和chrome.runtime.sendMessage的方案。
* 【郑重声明】 该代码著作权归瞌睡虫子所有。其他第三方使用该代码用于商用活动,引起的法律纠纷与作者无关,同时作者保留追责权力。
*
* 2024-12-29
* 原始网页,iframe,content.js之间消息通讯
* ____________________________________________________________________________________________________________________________________
*/
class MessagePort {
constructor(channel) {
this.channel = channel;
this.listeners = [];
this.listenerIdCounter = 0;
this.responseCallbacks = new Map();
this.handleMessage = this.handleMessage.bind(this);
window.addEventListener('message', this.handleMessage);
}
generateId(prefix) {
return `${prefix}_${this.listenerIdCounter++}`;
}
sendMessage(message, toChannels = '*', targetOrigin = '*') {
return new Promise((resolve, reject) => {
const messageId = this.generateId(`${this.channel}_msg`);
this.responseCallbacks.set(messageId, {
resolve,
reject
});
toChannels = typeof(toChannels) == "string" ? [toChannels] : toChannels;
window.postMessage({
id: messageId,
from: this.channel,
to: toChannels || ["*"],
message: message
}, targetOrigin);
setTimeout(() => {
if (this.responseCallbacks.has(messageId)) {
this.responseCallbacks.delete(messageId);
reject(new Error('Message response timeout'));
}
}, 5000); // 设置超时时间,例如5秒
});
}
onMessage(callback) {
const listenerId = this.generateId(`${this.channel}_listener`);
const wrappedCallback = (event, sendResponse) => {
const {
id,
from,
to,
message
} = event.data;
// 通道对应上,而且不是自己发的才处理
if (!(Array.isArray(to) && (to.includes(this.channel) || to.includes("*"))) || from === this.channel)
return false;
const responseSent = callback(message, from, (response, responseToChannels) => {
sendResponse(response, responseToChannels || from);
}, event);
return responseSent;
};
this.listeners.push({
id: listenerId,
callback: wrappedCallback
});
return listenerId;
}
removeMessageListener(listenerId) {
this.listeners = this.listeners.filter(listener => listener.id !== listenerId);
}
sendResponse(response, responseToChannels, event) {
const {
id,
from
} = event.data;
responseToChannels = typeof(responseToChannels) == "string" ? [responseToChannels] : responseToChannels;
const responseMessage = {
id: id,
from: this.channel,
to: responseToChannels || [from],
message: response
};
event.source.postMessage(responseMessage, event.origin);
if (id && this.responseCallbacks.has(id)) {
this.responseCallbacks.get(id).resolve(responseMessage);
this.responseCallbacks.delete(id);
}
}
handleMessage(event) {
const {
id,
from,
to,
message
} = event.data;
// 通道对应上,而且不是自己发的才处理
if (!(Array.isArray(to) && (to.includes(this.channel) || to.includes("*"))) || from === this.channel)
return;
// 如果是自己发出去的消息,别人回的说明是回调消息。直接回调resolve即可
if (id && this.responseCallbacks.has(id)) {
this.responseCallbacks.get(id).resolve(message);
this.responseCallbacks.delete(id);
return;
}
// 说明是别人发过来的消息
this.listeners.forEach(listener => {
const responseSent = listener.callback(event, (response, responseToChannels) => {
this.sendResponse(response, responseToChannels, event);
});
});
}
destroy() {
window.removeEventListener('message', this.handleMessage);
this.listeners = [];
this.responseCallbacks.clear();
}
}
扩展通过inject.js注入windowMessageEvent.js到原始网页。并且注册MessagePort消息,用于通讯
// inject.js 将js脚本注入到原网页运行时,以解决扩展不能运行eval或执行网页js
var script = document.createElement('script');
script.src = chrome.runtime.getURL('js/windowMessageEvent.js');
script.onload = function() {
this.remove();
var script = document.createElement('script');
script.src = chrome.runtime.getURL('js/crawler.js');
script.onload = function() {
this.remove();
window.injested = true;
const contentPort = new MessagePort('content-channel');
// 发送消息到原始页面
contentPort.sendMessage('Hello from content script!').then(response => {
console.log('Content script got response:', response);
});
};
(document.head || document.documentElement).appendChild(script);
};
(document.head || document.documentElement).appendChild(script);
在原始网页注入windowMessageEvent.js完成后注入crawler.js。来注册onMessage消息,监听其他作用域的消息
// 在原始页面中定义 MessagePort
const originalPort = new MessagePort('original-channel');
console.log("1233444545566");
// 监听来自 content script 的消息
originalPort.onMessage((message, fromChannel, sendResponse) => {
console.log('Original page received message:', message, 'from:', fromChannel);
// 同步响应
sendResponse('This is the sync response from the original page');
return false; // 同步处理
});
需要在manifest.json中给js权限
{
"content_scripts": [
{
"all_frames": true,
"exclude_globs": [],
"js": [
"js/inject.js",
"js/windowMessageEvent.js"
],
"matches": [
"https://forum.laiye.com/*"
],
"run_at": "document_idle"
}
],
"web_accessible_resources": [
{
"resources": [
"content.js",
"js/inject.js",
"js/crawler.js",
"js/windowMessageEvent.js"
],
"matches": [
"<all_urls>"
]
}
],
"host_permissions": [
"http://*/*",
"https://*/*"
],
"permissions": [
"tabs",
"scripting",
"activeTab"
],
}