用户脚本的两个实例如何在帧之间进行通信?

IT技术 javascript iframe synchronization cross-domain userscripts
2021-03-02 09:42:45

请参阅在网页和 iframe 中运行相同的 JavaScript 的技术,如本答案所述

例如,假设您在 domain_A.com 上有这个页面:

<html>
<body>
    <iframe src="http://domain_B.com/SomePage.htm"></iframe>
</body>
</html>

如果您像这样设置@match 指令:

// @match http://domain_A.com/*
// @match http://domain_B.com/*

然后您的脚本将运行两次——一次在主页面上,一次在 iframe 上,就好像它是一个独立的页面一样。


有哪些选项可以让脚本的两个实例相互通信?

这将需要同步实例。例如,让 iframe 脚本实例在网页脚本实例完成后才执行其任务,反之亦然。

1个回答

这两个脚本实例可以使用postMessage(). 关于:

这将需要同步实例,例如,只有在网页完成后才让 iframe 执行其任务,反之亦然。

这就是另一个答案中显示的内容
但是 Chrome 在向扩展程序呈现框架/iframe 的方式上存在错误 因此,要解决这些错误,您必须注入调用postMessage().

以下脚本显示了如何。它:

  • 在 iframe 和包含页面中运行。
  • 处理跨域 iframe。
  • 它演示了具有以下逻辑的脚本间控制:
    1. 容器页面设置为侦听来自 iframe 的消息。
    2. iframe 设置为侦听来自容器页面的消息。
    3. iframe 将第一条消息发送到容器页面。
    4. 当容器页面收到该消息时,它会将另一条消息发送回 iframe。

安装此脚本(由于多年来目标站点的变化而更新,感谢CertainPerformance):

// ==UserScript==
// @name        _Cross iframe, cross-domain, interscript communication
// @include     http://fiddle.jshell.net/2nmfk5qs/*
// @include     http://puppylinux.com/
// @grant       none
// ==/UserScript==
/* eslint-disable no-multi-spaces */

if (window.top === window.self) return;
console.log ("Script start...");
if (window.location.href.includes('fiddle')) {
    console.log ("Userscript is in the MAIN page.");

    //--- Setup to process messages from the GM instance running on the iFrame:
    window.addEventListener ("message", receiveMessageFromFrame, false);
    console.log ("Waiting for Message 1, from iframe...");
}
else {
    console.log ("Userscript is in the FRAMED page.");

    //--- Double-check that this iframe is on the expected domain:
    if (/puppylinux\.com/i.test (location.host) ) {
        window.addEventListener ("message", receiveMessageFromContainer, false);

        //--- Send the first message to the containing page.
        sendMessageFromAnIframe (
            "***Message 1, from iframe***", "http://fiddle.jshell.net"
        );
        console.log ("Waiting for Message 2, from containing page...");
    }
}

function receiveMessageFromFrame (event) {
    if (event.origin != "http://puppylinux.com")    return;

    console.log ('The container page received the message, "' + event.data + '".');

    //--- Send message 2, back to the iframe.
    sendMessageToAnIframe (
        "#testIframe",
        "***Message 2, from the container page***",
        "http://puppylinux.com"
    );
}

function receiveMessageFromContainer (event) {
    if (event.origin != "http://fiddle.jshell.net")    return;

    console.log ('The iframe received the message, "' + event.data + '".');
}

/*--- Because of bugs in how Chrome presents frames to extensions, we must inject
    the messaging code. See bug 20773 and others.
    frames, top, self.parent, contentWindow, etc. are all improperly undefined
    when we need them.  See Firefox and other browsers for the correct behavior.
*/
function sendMessageFromAnIframe (message, targetDomain) {
    var scriptNode          = document.createElement ('script');
    scriptNode.textContent  = 'parent.postMessage ("' + message
                            + '", "' + targetDomain + '");'
                            ;
    document.body.appendChild (scriptNode);
}

function sendMessageToAnIframe (cssSelector, message, targetDomain) {
    function findIframeAndMessageIt (cssSelector, message, targetDomain) {
        var targetIframe    = document.querySelector (cssSelector)
        if (targetIframe) {
            targetIframe.contentWindow.postMessage (message, targetDomain);
        }
    }
    var scriptNode          = document.createElement ('script');
    scriptNode.textContent  = findIframeAndMessageIt.toString ()
                            + 'findIframeAndMessageIt ("' + cssSelector
                            + '", "' + message
                            + '", "' + targetDomain + '");'
                            ;
    document.body.appendChild (scriptNode);
}

console.log ("Script end");


然后在 jsFiddle访问这个测试页面

您将在 javascript 控制台中看到:

脚本开始...
用户脚本位于主页面中。
正在等待来自 iframe 的消息 1...
脚本结束
脚本开始...
用户脚本位于 FRAMED 页面中。
正在等待消息 2,来自包含页面...
脚本结束
容器页面收到消息“***消息 1,来自 iframe***”。
iframe 收到消息“***消息 2,来自容器页面***”。
2021-04-20 09:42:45
1) 到目前为止,postMessage 是否可以在 chrome 中使用?您提到需要注入代码,因为 chrome 扩展存在问题。现在还是这样吗?2) 作为对用户脚本中 postMessage 使用的原始问题的扩展,网站本身是否有可能拦截用户脚本中 postMessage 的使用?
2021-04-27 09:42:45
@Edge:是的,是的(但如果您在 Tampermonkey 和 set 中使用脚本,则可能不需要@grant none注入)。是的,在很多情况下,网页可以拦截 postMessages。不过,我还没有看到一个真正的网站,它是那种肛门/偏执狂。
2021-04-27 09:42:45
该示例不再起作用 - 看起来 JSFiddle 更改了他们的演示文稿,以便正确的代码无论如何都在 iframe 中(因此即使会.../show/light导致页面中包含...一个嵌入的 iframe 链接到.../show/light),因此window.top测试行为不符合预期。puppylinux 也不再使用 www。
2021-05-01 09:42:45
布洛克,我刚放假回来,看到了你的回答。非常感谢,我会详细研究一下,并尝试应用到我的案例中。
2021-05-03 09:42:45