用户脚本的两个实例如何在框架之间进行通信? [英] How can two instances of a userscript communicate between frames?

查看:29
本文介绍了用户脚本的两个实例如何在框架之间进行通信?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

<块引用>

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

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

<块引用>

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

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

<块引用>

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


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

这将是同步实例所必需的.例如,让 iframe 脚本实例仅在网页脚本实例完成后执行其任务,反之亦然.

解决方案

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

<块引用>

这将需要同步实例,例如让 iframe 仅在网页一完成后执行其任务,反之亦然.

这就是其他答案中显示的内容.
但是 Chrome 在显示框架方面存在错误/iframes 到扩展因此,要解决这些错误,您必须注入调用 postMessage() 的代码.

以下脚本显示了如何操作.它:

  • 在 iframe 和包含页面中运行.
  • 处理跨域 iframe.
  • 它演示了具有以下逻辑的脚本间控制:

    1. 容器页面设置为侦听来自 iframe 的消息.
    2. iframe 设置为侦听来自容器页面的消息.
    3. iframe 将第一条消息发送到容器页面.
    4. 当容器页面收到该消息时,它会将另一条消息发送回 iframe.

安装这个脚本(感谢CertainPerformance,多年来目标站点的变化):

//==UserScript==//@name _Cross iframe,跨域,interscript通信//@include http://fiddle.jshell.net/2nmfk5qs/*//@include http://puppylinux.com///@grant 无//==/用户脚本==/* eslint-disable no-multi-spaces */如果(window.top === window.self)返回;console.log("脚本启动...");if (window.location.href.includes('fiddle')) {console.log("用户脚本在主页面中.");//--- 设置处理来自在 iFrame 上运行的 GM 实例的消息:window.addEventListener("消息", receiveMessageFromFrame, false);console.log("等待消息 1,来自 iframe...");}别的 {console.log("用户脚本在 FRAMED 页面中.");//--- 仔细检查这个 iframe 是否在预期的域上:if (/puppylinux.com/i.test (location.host) ) {window.addEventListener("消息", receiveMessageFromContainer, false);//--- 将第一条消息发送到包含页面.sendMessageFromAnIframe (***消息 1,来自 iframe***"、http://fiddle.jshell.net");console.log("等待消息 2,来自包含页面...");}}函数receiveMessageFromFrame(事件){if (event.origin != "http://puppylinux.com") 返回;console.log('容器页面收到消息,"' + event.data + '".');//--- 将消息 2 发送回 iframe.sendMessageToAnIframe ("#testIframe","***消息 2,来自容器页面***",http://puppylinux.com");}函数receiveMessageFromContainer(事件){如果(event.origin!=http://fiddle.jshell.net")返回;console.log('iframe 收到消息,"' + event.data + '".');}/*--- 由于 Chrome 向扩展显示框架的方式存在错误,我们必须注入消息传递代码.请参阅错误 20773 等.frames、top、self.parent、contentWindow 等都未正确定义当我们需要它们时.有关正确行为,请参阅 Firefox 和其他浏览器.*/函数 sendMessageFromAnIframe(消息,targetDomain){var scriptNode = document.createElement ('script');scriptNode.textContent = 'parent.postMessage ("' + 消息+ '", "' + 目标域 + '");';document.body.appendChild (scriptNode);}函数 sendMessageToAnIframe (cssSelector, message, targetDomain) {函数 findIframeAndMessageIt (cssSelector, message, targetDomain) {var targetIframe = document.querySelector (cssSelector)如果(targetIframe){targetIframe.contentWindow.postMessage(消息,targetDomain);}}var scriptNode = document.createElement ('script');scriptNode.textContent = findIframeAndMessageIt.toString ()+ 'findIframeAndMessageIt ("' + cssSelector+ '", "' + 消息+ '", "' + 目标域 + '");';document.body.appendChild (scriptNode);}console.log("脚本结束");


然后访问 jsFiddle 上的这个测试页面.

您将在 javascript 控制台中看到:

<上一页>脚本开始...用户脚本位于 MAIN 页面中.等待消息 1,来自 iframe...脚本结束脚本开始...用户脚本位于 FRAMED 页面中.等待消息 2,来自包含页面...脚本结束容器页面收到消息***Message 1, from iframe***".iframe 收到消息***Message 2, from the container page***".

Refer to the technique of having the same JavaScript to run in both a web page and an iframe, as described in this answer:

For example, suppose you have this page at domain_A.com:

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

If you set your @match directives like this:

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

Then your script will run twice -- once on the main page and once on the iframe as though it were a standalone page.


What are the options to have the two instances of the script to communicate with each other?

This would be needed to sync the instances. For example, have the iframe script-instance execute its task only after the webpage script-instance completed, and vice versa.

解决方案

The two script instances can communicate with each other using postMessage(). Regarding:

This would be needed to sync the instances, so for example to have the iframe one to execute its task only after the webpage one completed, and vice versa.

That's what is shown in this other answer.
But Chrome has bugs in how it presents frames/iframes to extensions So, to work around these bugs, you must inject the code that calls postMessage().

The following script shows how. It:

  • Runs in both an iframe and the containing page.
  • Handles cross domain iframes.
  • It demonstrates inter-script control having the following logic:

    1. The container page sets up to listen for messages from the iframe.
    2. The iframe sets up to listen for messages from the container page.
    3. The iframe sends the first message to the container page.
    4. When the container page receives that message, it sends another message back to the iframe.

Install this script (Updated thanks to CertainPerformance, for changes in the target sites over the years):

// ==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");


Then visit this test page at jsFiddle.

You will see this in the javascript console:

Script start...
Userscript is in the MAIN page.
Waiting for Message 1, from iframe...
Script end
Script start...
Userscript is in the FRAMED page.
Waiting for Message 2, from containing page...
Script end
The container page received the message, "***Message 1, from iframe***".
The iframe received the message, "***Message 2, from the container page***".

这篇关于用户脚本的两个实例如何在框架之间进行通信?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆