Javascript TamperMonkey-不同子域上脚本之间的消息

Javascript TamperMonkey-不同子域上脚本之间的消息,javascript,jquery,greasemonkey,tampermonkey,Javascript,Jquery,Greasemonkey,Tampermonkey,我有两个脚本。每个都在我们公司的不同子域上运行“Example.com”。 相同的域,不同的子域 当某个特定元素出现在house.example.com上时,我需要向运行在bob.fred.example.com 由于Google扩展可以在扩展之间交换消息,因此必须有一种使用TamperMonkey的方法在同一扩展内、脚本之间交换消息——特别是如果它们运行在同一个二级域上 谁能给我指出正确的方向吗?一两个例子是值得的黄金重量 更新:尽管Gothdo提到包含了对这个问题的答案,但他没有考虑到涉及

我有两个脚本。每个都在我们公司的不同子域上运行
“Example.com”。

相同的域,不同的子域

当某个特定元素出现在
house.example.com
上时,我需要向运行在
bob.fred.example.com

由于Google扩展可以在扩展之间交换消息,因此必须有一种使用TamperMonkey的方法在同一扩展内、脚本之间交换消息——特别是如果它们运行在同一个二级域上

谁能给我指出正确的方向吗?一两个例子是值得的黄金重量



更新:尽管Gothdo提到包含了对这个问题的答案,但他没有考虑到涉及的跨原产地政策该参考问题中的任何答案都不能为跨来源浏览器选项卡通信提供清晰的答案,这是该问题的要点。我现在已经研究并解决了这个问题,从许多SO和非SO来源获得了想法。如果这个问题被重新打开,我将发布我的解决方案。

您可以使用
GM\u getValue
GM\u setValue
&
GM\u addvaluechangeelistener
实现跨选项卡用户脚本通信

function GM_onMessage(label, callback) {
  GM_addValueChangeListener(label, function() {
    callback.apply(undefined, arguments[2]);
  });
}

function GM_sendMessage(label) {
  GM_setValue(label, Array.from(arguments).slice(1));
}
在用户脚本标题中添加以下行

// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addValueChangeListener
下面几行粗略的代码将简化跨选项卡用户脚本通信

function GM_onMessage(label, callback) {
  GM_addValueChangeListener(label, function() {
    callback.apply(undefined, arguments[2]);
  });
}

function GM_sendMessage(label) {
  GM_setValue(label, Array.from(arguments).slice(1));
}
因此,您只需执行以下操作即可发送和接收消息

GM_onMessage('_.unique.name.greetings', function(src, message) {
  console.log('[onMessage]', src, '=>', message);
});
GM_sendMessage('_.unique.name.greetings', 'hello', window.location.href);

注意如果发送的消息与以前相同,则发送消息可能不会触发回调。这是因为
GM\u addValueChangeListener
没有触发,因为该值没有更改,即,即使调用了
GM\u setValue
,该值仍与以前相同。

我最终用于同一域上的子域之间的tab-to-tab通信的方法是通过javascript cookies传递信息。(我也尝试过使用localStorage,但在子域之间不起作用。)

场景:子域A上的选项卡A将向子域B上的选项卡B发送消息:

代码如下所示:

function getCookie(cooVal) {
    var cname = cooVal+ '=';
    var ca = document.cookie.split(';');
    for (var i=0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(cname) === 0) {
            return c.substring(cname.length, c.length);
        }
    }
    return null;
} //END getcookie()
document.cookie="acciq=5; domain=.example.com; path=/";
document.cookie="jdlwc=fubar; domain=.example.com; path=/";
(function foreverloop(i) {
    //Do all my stuff - send/receive the cookies, do stuff with the values, etc
    setTimeout(function() {
        foreverloop(++i);
    },2000);
}(0)); //END foreverloop
// ==UserScript==
// @namespace    abcd.tops.example.com
// @match        *://abcd.tops.example.*/*
// @grant        none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// ==/UserScript==
and
// ==UserScript==
// @namespace    http://mysubdomain.example.com/callcenter/
// @match        *://*.example.com/callcenter/
// @grant        none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// ==/UserScript==
TabA将向Tab B发送消息,如下所示:

function getCookie(cooVal) {
    var cname = cooVal+ '=';
    var ca = document.cookie.split(';');
    for (var i=0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(cname) === 0) {
            return c.substring(cname.length, c.length);
        }
    }
    return null;
} //END getcookie()
document.cookie="acciq=5; domain=.example.com; path=/";
document.cookie="jdlwc=fubar; domain=.example.com; path=/";
(function foreverloop(i) {
    //Do all my stuff - send/receive the cookies, do stuff with the values, etc
    setTimeout(function() {
        foreverloop(++i);
    },2000);
}(0)); //END foreverloop
// ==UserScript==
// @namespace    abcd.tops.example.com
// @match        *://abcd.tops.example.*/*
// @grant        none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// ==/UserScript==
and
// ==UserScript==
// @namespace    http://mysubdomain.example.com/callcenter/
// @match        *://*.example.com/callcenter/
// @grant        none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// ==/UserScript==

对于任何想知道,是的,子域可以互相发送消息-这不仅仅是单向通信。只需在另一个方向重复相同的场景即可

当然,在这两个选项卡上,消息传递系统都位于javascript循环中,如下所示:

function getCookie(cooVal) {
    var cname = cooVal+ '=';
    var ca = document.cookie.split(';');
    for (var i=0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(cname) === 0) {
            return c.substring(cname.length, c.length);
        }
    }
    return null;
} //END getcookie()
document.cookie="acciq=5; domain=.example.com; path=/";
document.cookie="jdlwc=fubar; domain=.example.com; path=/";
(function foreverloop(i) {
    //Do all my stuff - send/receive the cookies, do stuff with the values, etc
    setTimeout(function() {
        foreverloop(++i);
    },2000);
}(0)); //END foreverloop
// ==UserScript==
// @namespace    abcd.tops.example.com
// @match        *://abcd.tops.example.*/*
// @grant        none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// ==/UserScript==
and
// ==UserScript==
// @namespace    http://mysubdomain.example.com/callcenter/
// @match        *://*.example.com/callcenter/
// @grant        none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// ==/UserScript==

两个选项卡上的TM标题如下所示:

function getCookie(cooVal) {
    var cname = cooVal+ '=';
    var ca = document.cookie.split(';');
    for (var i=0; i < ca.length; i++) {
        var c = ca[i];
        while (c.charAt(0)==' ') c = c.substring(1,c.length);
        if (c.indexOf(cname) === 0) {
            return c.substring(cname.length, c.length);
        }
    }
    return null;
} //END getcookie()
document.cookie="acciq=5; domain=.example.com; path=/";
document.cookie="jdlwc=fubar; domain=.example.com; path=/";
(function foreverloop(i) {
    //Do all my stuff - send/receive the cookies, do stuff with the values, etc
    setTimeout(function() {
        foreverloop(++i);
    },2000);
}(0)); //END foreverloop
// ==UserScript==
// @namespace    abcd.tops.example.com
// @match        *://abcd.tops.example.*/*
// @grant        none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// ==/UserScript==
and
// ==UserScript==
// @namespace    http://mysubdomain.example.com/callcenter/
// @match        *://*.example.com/callcenter/
// @grant        none
// @require     http://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js
// ==/UserScript==

对于延迟发布此解决方案,向所有人致歉。在问题被错误地标记为重复之后,它花了很长时间才被重新打开,生活就这样继续着

使用
@grant
启用沙箱,在尝试与Greasemonkey上的复杂页面对象进行交互时,有时会遇到困难

如果您不想使用
@grant
启用沙箱,另一个选项是让用户脚本创建一个到其他域的iframe,然后向其发布消息。在另一个域的iframe中,侦听消息。当收到消息时,使用将消息发送到其他域上的每个其他选项卡,运行userscript的其他选项卡可以打开同一广播频道并侦听消息

GM_onMessage('_.unique.name.greetings', function(src, message) {
  console.log('[onMessage]', src, '=>', message);
});
GM_sendMessage('_.unique.name.greetings', 'hello', window.location.href);
例如,要在
stackoverflow.com
上创建一个用户脚本,该脚本可以向运行在
example.com
上不同选项卡中的用户脚本发送消息:

// ==UserScript==
// @name             0 Cross-tab example
// @include          /^https://example\.com\/$/
// @include          /^https://stackoverflow\.com\/$/
// @grant            none
// ==/UserScript==

if (window.location.href === 'https://example.com/') {
  const broadcastChannel = new BroadcastChannel('exampleUserscript');
  if (window.top !== window) {
    // We're in the iframe:
    window.addEventListener('message', (e) => {
      if (e.origin === 'https://stackoverflow.com') {
        broadcastChannel.postMessage(e.data);
      }
    });
  } else {
    // We're on a top-level tab:
    broadcastChannel.addEventListener('message', (e) => {
      console.log('Got message', e.data);
    });
  }
} else {
  // We're on Stack Overflow:
  const iframe = document.body.appendChild(document.createElement('iframe'));
  iframe.style.display = 'none';
  iframe.src = 'https://example.com';

  setTimeout(() => {
    iframe.contentWindow.postMessage('Sending message from Stack Overflow', '*');
  }, 2000);
}
这导致:

如果您想要双向通信,而不仅仅是单向通信,请让两个父页面创建一个子iframe到单个目标域(例如,到
example.com
)。要与其他选项卡通信,请向子iframe发布消息。让子iframe侦听消息,当看到消息时,发布广播频道消息以与所有其他iframe通信。当iframe接收到BroadcastChannel消息时,使用
postMessage
将其中继到父窗口

// ==UserScript==
// @name             0 Cross-tab example
// @include          /^https://example\.com\/$/
// @include          /^https://(?:stackoverflow|stackexchange)\.com\/$/
// @grant            none
// ==/UserScript==

if (window.location.href === 'https://example.com/') {
  const broadcastChannel = new BroadcastChannel('exampleUserscript');
  if (window.top !== window) {
    // We're in an iframe:
    window.addEventListener('message', (e) => {
      console.log('iframe received message from top window');
      if (e.origin === 'https://stackoverflow.com' || e.origin === 'https://stackexchange.com') {
        broadcastChannel.postMessage(e.data);
      }
    });
    broadcastChannel.addEventListener('message', (e) => {
      console.log('iframe received message from BroadcastChannel');
      window.top.postMessage(e.data, '*');
    });
  }
} else {
  // We're on Stack Overflow or Stack Exchange
  const iframe = document.body.appendChild(document.createElement('iframe'));
  iframe.style.display = 'none';
  iframe.src = 'https://example.com';
  window.addEventListener('message', (e) => {
    if (e.origin === 'https://example.com') {
      console.log(`Top window ${window.origin} received message from iframe:`, e.data);
    }
  });
  if (window.location.href === 'https://stackoverflow.com/') {
    setTimeout(() => {
      console.log('stackoverflow posting message to iframe');
      iframe.contentWindow.postMessage('Message from stackoverflow', '*');
    }, 2000);
  }
}
在上面的代码中,堆栈溢出选项卡向堆栈交换选项卡发送消息。结果截图:


这对我来说不是重复的。OP询问的是TamperMonkey中的消息,类似于@nicovank,TamperMonkey中也是如此。Tampermonkey脚本没有Chrome扩展等特权;它们与常规脚本非常相似。@Gothdo尽管如此,我认为适当的做法是提供一个答案,说明没有这样的API,但可以使用常规web消息传递,并提供链接。这不是重复,即使答案是相似的特别是因为如果脚本在同一页面上运行,那么答案可以通过演示如何使用来增强。@胡言乱语这篇论坛帖子展示了如何使用GM_setValue和GM_getValue在选项卡之间发送消息,以实现选项卡级别锁定@哥多,你上面的断言是错误的;Tampermonkey脚本可以做一些常规脚本做不到的事情,而这种重复不适用于OP的场景。尽管这个问题可能是一些油腻的问题的重复(这个主题肯定已经在GM/TM场景中讨论过了),但我现在没有时间搜索更多内容……对该脚本的快速测试为我确认,使用这些方法进行幕后通信确实可以跨选项卡/窗口,甚至跨不同的顶级域,只要您使用适当的元标记,如//@match https://*/*