扩展content.js与原始网页间消息通讯

发现问题:
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"
    ],
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容