【问题标题】:Chrome Extension message passing: Unchecked runtime.lastError: Could not establish connection. Receiving end does not existChrome 扩展消息传递:未选中 runtime.lastError:无法建立连接。接收端不存在
【发布时间】:2019-06-08 10:54:11
【问题描述】:

我的 chrome 扩展有以下两个 javascript:

background.js,作为后台脚本运行:

chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
    if (message.data == "takeScreenshot") {
        var resp = sendResponse;
        chrome.tabs.captureVisibleTab(function(screenshotUrl) {
            resp({
                screenshot: screenshotUrl
            });
        });
        return true; // Return true to tell that the response is sent asynchronously
    } else {
        return "TestReply";
    }
});

api.js,作为网络可访问资源运行:

window.takeScreenshot = (function() {
    var isTakingScreenshot = false; // Semaphore
    return function() {
        if(isTakingScreenshot) return Promise.reject();
        isTakingScreenshot = true;
        return new Promise(function(resolve, reject) {
            chrome.runtime.sendMessage("eomfljlchjpefnempfimgminjnegpjod", "takeScreenshot", function(response) {
                console.log(response);
                isTakingScreenshot = false;
                resolve(response.screenshot);
            });
        });
    }
})()
window.test = (function() {
    return function() {
        return new Promise(function(resolve, reject) {
            chrome.runtime.sendMessage("eomfljlchjpefnempfimgminjnegpjod", "test", function(response) {
                console.log(response);
                resolve(response.length);
            });         
        });
    }
})();

当我在选项卡的控制台中执行任一功能时(自动完成功能知道它们,因此它们可用),我收到错误:

未经检查的 runtime.lastError:无法建立连接。接收端不存在。

并且返回的响应是未定义的。

我检查了sendMessage 中的 id 是否与清单和 chrome://extensions 页面中的相同,并且我已经打开了扩展程序的后台页面 DevTools 并在那里手动添加了相同的侦听器来制作确保侦听器确实已注册。

我的搜索发现这个错误意味着监听器没有正确注册,但我没有找到根本原因。您知道导致此错误的原因吗?

【问题讨论】:

  • 由于您使用的是 web_accessible_resource,因此您必须将其作为无法访问扩展环境的页面脚本运行 - 您需要通过 externally_connectable 键公开消息。
  • @wOxxOm 你能解释一下externally_connectable 与一般permissions 有何不同吗?在不了解externally_connectable 设置的情况下,我似乎能够多年来毫无问题地使用chrome.runtime.sendMessage()chrome.runtime.onMessage.addListener()
  • @Atav32,我看不出你的评论与问题和我的评论有何关联,所以我无法回答,但我猜你没有运行页面脚本。
  • 我们遇到了同样的错误,我们没有尝试与外部扩展通信。我在下面有一个非最佳解决方案。也许@wOxxOm 有更好的主意。在调用 connect 之前,没有其他 api 可以检查端口是否已经被监听。
  • 嗨@wOxxOm - 我做了一个最小的扩展来向你展示这个问题。如果您只是加载此扩展程序,您将收到错误“未检查的 runtime.lastError:无法建立连接。接收端不存在。”但是如果您打开弹出窗口然后重新加载内容脚本,问题就会消失。 github.com/ddehghan/bugReproExtension 。内容脚本如何确保连接前有监听器。这似乎是一个错误。

标签: google-chrome-extension


【解决方案1】:

好的。我发现了问题所在。我猜这是自 72 年以来 chrome 行为的变化。问题是,如果您在后台或弹出页面中打开另一端的频道之前尝试调用 chrome.runtime.connect(),那么您将收到该错误。

Chrome 文档说您可以立即发送消息。在过去,这只会起作用,消息要么被传递,要么被丢弃。但现在它失败了。

chrome docs: 在调用 tabs.connect、runtime.connect 或 runtime.connectNative 时,会创建一个端口。此端口可立即用于通过 postMessage 向另一端发送消息。

所以我们的解决方法是通过延迟 connect() 调用来确保在调用 connect() 之前首先设置连接侦听器:

chrome.runtime.onConnect.addListener(port => {
  console.log('connected ', port);

  if (port.name === 'hi') {
    port.onMessage.addListener(this.processMessage);
  }
});

如果您在内容脚本端为断开连接事件设置了一个侦听器,那么当您尝试 chrome.runtime.connect 并且您在另一端没有任何东西侦听时,它实际上会被调用。根据Port lifetime

,这是正确的行为
port = chrome.runtime.connect(null, {name: 'hi'});      
port.onDisconnect.addListener(obj => {
  console.log('disconnected port');
})

我不知道是否有其他方法可以避免这种情况,除了使用 setTimeout 并尝试在调用 chrome.runtime.onConnect.addListener 之后让 chrome.runtime.connect 出现。这不是一个好的解决方案,因为它会导致时序错误。也许另一种解决方法是反转通道的方向。并从弹出窗口而不是 contentscript 启动连接。

更新:我为这个问题创建了a minimum repro extension

【讨论】:

  • 我为测试做了一个最小的复制扩展。
  • @DavidDehghan 这绝对是一个错误!你发过bug report吗?
  • 我没有。你可以继续这样做并使用我的 repro git。 Chrome 团队在查看此类回归问题方面非常缓慢。他们最早可能需要 6 个月的时间。如果你在这里创建一个错误链接,我们都可以给它加星标。
  • 进一步思考,我认为这种行为是故意的。由于端口会立即触发onDisconnect,它不会再发出任何事件。换句话说,它已经死了。
  • 不,这是一个错误。医生说它应该可以立即发送。这意味着在内部它应该阻止调用,直到建立连接。这是一个糟糕的 api 设计,而且实现起来更糟糕。
【解决方案2】:

这是我用来解决这个问题的一个模式。内容脚本尝试访问后台脚本。如果后台脚本尚不可用,那么我们可以从 chrome.runtime.lastError 中看到它正在设置(而不是未定义)。在这种情况下,请在 1000 毫秒后重试。

contentScript.js

function ping() {
  chrome.runtime.sendMessage('ping', response => {
    if(chrome.runtime.lastError) {
      setTimeout(ping, 1000);
    } else {
      // Do whatever you want, background script is ready now
    }
  });
}

ping();

backgroundScript.js

chrome.runtime.onConnect.addListener(port => {
  port.onMessage.addListener(msg => {
    // Handle message however you want
  });
});

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => sendResponse('pong'));

【讨论】:

  • 虽然此代码 sn-p 可能是解决方案,但 including an explanation 确实有助于提高您的帖子质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。
  • 我想补充一点,建议的模式在“简单的一次性请求”的情况下非常有用。对于长寿命的连接,“端口”机制是要走的路。见developer.chrome.com/extensions/messaging
  • 其实它使用“简单的一次性请求”来建立一个长寿命的端口连接
  • 谢谢,找了一周的解决办法!!
  • 这很棒。我想我已经切换了 background.js 和 content.js 脚本。
【解决方案3】:

chrome 中的一些安全更改似乎在内容脚本和后台脚本之间进行消息传递时必须采取稍微不同的方法。

要使用的调用是

  1. 来自后台页面:

chrome.runtime.onMessageExternal.addListener 注意 外部部分。

  1. manifest.json 需要额外的权限
      "externally_connectable": {
        "ids": ["abcdefghijklmnopqrstuvwxyzabcdef"],
        "matches": ["https://example.com/*"],
        "accepts_tls_channel_id": false
      },

"abcdefghijklmnopqrstuvwxyzabcdef" 是您的分机 ID。

"https://example.com/*" 是内容脚本运行所在的域。

  1. 来自内容脚本:

    chrome.runtime.sendMessage / chrome.runtime.connectextensionId 作为第一个参数。

在这里阅读更多 https://developer.chrome.com/extensions/manifest/externally_connectable

【讨论】:

  • 最佳答案。您可以使用 chrome.runtime.id 将 Id 注入消息发送者中,以避免在发送消息时出现静态变量
  • 完美运行。这正是我所需要的。
【解决方案4】:

如果您在开发 Chrome 扩展程序时收到此错误消息,请务必禁用您在浏览器中安装的其他 Chrome 扩展程序。特别是“AdBlock”扩展似乎会干扰消息传递并导致我的扩展抛出此错误。禁用 AdBlock 为我解决了这个问题。

【讨论】:

  • 不一定。 Adblocks 不是此错误的原因。这主要是因为最近 chrome 浏览器(v 77)的变化
【解决方案5】:

在研究了我的扩展程序的更多解决方案后,我考虑将Long-lived connection 更改为Simple one-time requests,严重的是,我花了两天时间寻找解决方案,但在我的扩展程序中找不到解决此问题的方法。弹出窗口是用 ReactJs 制作的。删除所有runtime.connectruntime.onConnect后,更改api并使用onMessage,并且只使用addListner的回调来处理消息,问题消失了。我这样做了,因为我不需要更多的实时更改并仅使用请求进行更改,请考虑一下,如果您不需要Long-lived connection,请将其更改为Simple one-time requestsfor more information。我知道这不是一个解决方案,但对于需要加速或完成开发的每个人来说,这就是我所做的。

我的解决方案:

// content_script.js
if (!chrome.runtime.onMessage.hasListeners()) {
  window.chrome.runtime.onMessage.addListener(
    (request, sender, sendResponse) => {
      if (request?.type === 'load_titles') {
        sendResponse({
          type: 'loaded_titles',
          titles,
        });
      }
      if (request?.type === 'anything_else') {
        // do anything else
      }
    },
  );
}

如果你需要使用 sendResponse asyncronous 在另一个函数中使用,你需要在 onMessage.addListner 的回调中使用 return true

负责发送和接收消息的函数:

 // used in app.js on popup
 export function messager(data, callback) {
  window.chrome?.tabs?.query({ url: 'https://example.com/' }, tabs => {
    const tabId = tabs[0].id;
    window.chrome.tabs.sendMessage(tabId, data, callback);
  });
}

 // app.js
 const loadTitles = useCallback(() => {
    messager({ type: 'load_titles' }, response => {
      if (response?.type === 'loaded_titles') {
         console.log(response.titles)
        // do anything you want
      }
    });
  }, []);

【讨论】:

  • 我在使用“简单的一次性请求”时遇到了同样的错误 :)
  • 相同。使用简单的一次性请求,没有连接,尝试返回 true 与否,但仍然得到 Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist. - Chromium: 92.0.4515.131
【解决方案6】:

我在这里添加这个是因为我遇到了这个错误,而不是一个 StackOverflow 帖子解决了我的问题。我的问题是因为我的 background.js 向我的内容脚本发送了一堆消息,只需要访问一次网页。一旦我添加了 sender.status 检查,它只会在选项卡更改“完成”时发送消息,并且我的错误消失了。

chrome.tabs.onUpdated.addListener(function(activeInfo, sender, sendResponse) {
    // conditional check prevents sending multiple messages per refresh
    if(sender.status ===  "complete") {
      // do stuff here
    }
});

【讨论】:

    【解决方案7】:

    我在开发 Chrome 扩展程序时遇到了这个错误。对我来说,问题是不同的扩展 ID。如果您需要在代码中使用您的扩展 ID(例如,在向后台工作人员发送消息时),请确保您的扩展 ID 与浏览器中的 ID 匹配。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-02
      • 2022-08-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多