chrome.runtime.sendMessage 在 运行 正常时第一次点击不工作。它在调试时有效

chrome.runtime.sendMessage not working on the 1st click when running normally. it works while debugging though

我在 context.js 中有一个函数,它加载面板并在最后向 panel.js 发送消息。 panel.js 函数在收到该消息时更新 ui。但它不适用于第一次点击,即它只是正常加载 ui,而不是预期的在收到消息后更新的那个。调试时它工作正常。

manifest.json

"background": {
    "scripts": ["background.js"],
    "persistent": false
  },
"content_scripts": [{
    "all_frames": false,
    "matches": ["<all_urls>"],
    "js":["context.js"]
  }],
"permissions": ["activeTab","<all_urls>", "storage","tabs"],
  "web_accessible_resources": 
    "panel.html",
    "panel.js"
  ]

context.js - 代码


fillUI (){
    var iframeNode = document.createElement('iframe');
    iframeNode.id = "panel"
    iframeNode.style.height = "100%";
    iframeNode.style.width = "400px";
    iframeNode.style.position = "fixed";
    iframeNode.style.top = "0px";
    iframeNode.style.left = "0px";
    iframeNode.style.zIndex = "9000000000000000000";
    iframeNode.frameBorder = "none"; 
    iframeNode.src = chrome.extension.getURL("panel.html")
    document.body.appendChild(iframeNode);
    var dataForUI = "some string data"
    chrome.runtime.sendMessage({action: "update UI", results: dataForUI}, 
        (response)=> {
          console.log(response.message)
        })
     }
}

panel.js - 代码

var handleRequest = function(request, sender, cb) {
  console.log(request.results)
  if (request.action === 'update Not UI') {
    //do something
  } else if (request.action === 'update UI') {
    document.getElementById("displayContent").value = request.results
  }
};

chrome.runtime.onMessage.addListener(handleRequest);

background.js

chrome.runtime.onMessage.addListener((request,sender,sendResponse) => {
    chrome.tabs.sendMessage(sender.tab.id,request,function(response){
        console.log(response)`
    });
});

panel.html

<!DOCTYPE html>

<html>

<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="panel.css" />
</head>

<body>
  <textarea id="displayContent" rows="10" cols="40"></textarea>
</body>
</html>

关于我做错了什么或我可以做什么的任何建议?

一种对我有用的可能方法是使用 setTimeout() 方法中的功能。

context.js

setTimeout(() => {
    chrome.runtime.sendMessage({action: "update UI", results: dataForUI}, 
        (response)=> {
          console.log(response.message)
        }
    )
}, 100);

但我不确定这是否是最好的方法。

具有真实 URL 的 iframe 异步加载,因此其代码在嵌入代码完成后运行 - 因此,您的消息发送得太早而丢失。 URL 在你的例子中指向一个扩展资源,所以它是一个真正的 URL。作为参考,同步加载的 iframe 将有一个虚拟 URL 例如根本没有 src(或空字符串),或者它会像 about:blankjavascript:/*some code here*/,也可能是 srcdoc

解决方案 1:在 iframe 的 onload 事件中发送消息

可能的缺点:所有选项卡中的所有扩展框架都会收到它,包括后台脚本和任何其他打开的扩展页面,如弹出窗口、选项,如果它们也有一个 onMessage 侦听器。

iframeNode.onload = () => {
  chrome.runtime.sendMessage('foo', res => { console.log(res); });
};
document.body.appendChild(iframeNode);



解决方案 2:让 iframe 向其嵌入器发送消息

可能的缺点:如果您在一个选项卡中添加多个这样的扩展框架,例如由于浏览器中的错误或优化,第二个比第一个加载得早,可能会发送错误的数据 - 在这种情况下您可能必须使用直接 DOM 消息传递(解决方案 3)。

iframe 脚本 (panel.js):

chrome.tabs.getCurrent(ownTab => {
  chrome.tabs.sendMessage(ownTab.id, 'getData', data => {
    console.log('frame got data');
    // process data here
  });
});

内容脚本(context.js):

document.body.appendChild(iframeNode);
chrome.runtime.onMessage.addListener(
  function onMessage(msg, sender, sendResponse) {
    if (msg === 'getData') {
      chrome.runtime.onMessage.removeListener(onMessage)
      sendResponse({ action: 'update UI', results: 'foo' });
    }
  });



解决方案 3:通过 postMessage

直接发送消息

在一个选项卡中有多个扩展框架的情况下使用。

缺点:无法判断消息是由页面还是由其他扩展程序的内容脚本伪造的。

iframe 脚本为 message 事件声明了一个 one-time 侦听器:

window.addEventListener('message', function onMessage(e) {
  if (typeof e.data === 'string' && e.data.startsWith(chrome.runtime.id)) {
    window.removeEventListener('message', onMessage);
    const data = JSON.parse(e.data.slice(chrome.runtime.id.length));
    // process data here
  }
});

然后,另外,使用以下之一:

  • 如果内容脚本是发起者

    iframeNode.onload = () => {
      iframeNode.contentWindow.postMessage(
        chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
    };
    document.body.appendChild(iframeNode);
    
  • 如果 iframe 是发起者

    iframe 脚本:

    parent.postMessage('getData', '*');
    

    内容脚本:

    document.body.appendChild(iframeNode);
    window.addEventListener('message', function onMessage(e) {
      if (e.source === iframeNode) {
        window.removeEventListener('message', onMessage);
        e.source.postMessage(chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
      }
    });