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

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

问题描述

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

<块引用>

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

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

<块引用>

如果你像这样设置你的@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,跨域,跨脚本通信//@include http://fiddle.jshell.net/2nmfk5qs/*//@include http://puppylinux.com///@grant 无//==/用户脚本==/* eslint-disable no-multi-spaces */如果(window.top === window.self)返回;console.log("脚本开始...");如果(window.location.href.includes('小提琴')){console.log ("用户脚本在主页面中.");//--- 设置以处理来自 iFrame 上运行的 GM 实例的消息:window.addEventListener("message", receiveMessageFromFrame, false);console.log ("正在等待消息 1,来自 iframe...");}别的 {console.log ("用户脚本在框架页面中.");//--- 仔细检查此 iframe 是否在预期域中:如果 (/puppylinux.com/i.test (location.host) ) {window.addEventListener("message", 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(事件){if (event.origin != "http://fiddle.jshell.net") 返回;console.log('iframe 收到消息,"' + event.data + '".');}/*--- 由于 Chrome 向扩展程序呈现帧的方式存在错误,我们必须注入消息代码.请参阅错误 20773 等.frame、top、self.parent、contentWindow 等都未正确定义当我们需要它们时.有关正确行为,请参阅 Firefox 和其他浏览器.*/函数 sendMessageFromAnIframe (message, 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.contentWindow.postMessage(消息,目标域);}}var scriptNode = document.createElement('script');scriptNode.textContent = findIframeAndMessageIt.toString()+ 'findIframeAndMessageIt ("' + cssSelector+ '", "' + 消息+ '", "' + 目标域 + '");';document.body.appendChild (scriptNode);}console.log("脚本结束");


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

您将在 javascript 控制台中看到:

<前>脚本开始...用户脚本位于主页面中.正在等待来自 iframe 的消息 1...脚本结束脚本开始...用户脚本位于 FRAMED 页面中.正在等待消息 2,来自包含页面...脚本结束容器页面收到消息***消息 1,来自 iframe***".iframe 收到消息***消息 2,来自容器页面***".

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天全站免登陆