简体   繁体   中英

Chrome extension contextmenus sendResponse doesn't work on some pages

I want to build a extension which is able to get the English word selected by users when reading some English articles and get the whole sentence at the same time.

↓ This is my background.js file. I use getSelection function to send a message to content.js to request a response which contains selection info.

//background.js

chrome.runtime.onInstalled.addListener((tableId, changeInfo, tab) => {
    chrome.contextMenus.create({
        id: 'addWords',
        title: "Send \"%s\" to background",
        contexts: ['all']
    })

    function getSelection(info, tab) {
        if (info.menuItemId === "addWords") {
            chrome.tabs.sendMessage(tab.id, {action: "getSelection"}, (response) => {
                console.log(response);
            });
        }
    }

    chrome.contextMenus.onClicked.addListener(getSelection);
});

↓ This is my content.js file. I use onMessage to respond to the background's request.

//content.js
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
    if(request.action === "getSelection"){
        let selection = window.getSelection();
        if(selection.toString() !== ""){
            let arr = selection.anchorNode.data.split(".");
            let word = selection.toString();
            let sentence = arr.find(str => str.includes(word))
            alert(word);
            sendResponse({word: word, sentence: sentence});
        }
    }
})

↓ This is my manifest.json file

{
    "manifest_version": 3,
    "name": "Words Repeater",
    "description": "Repeat words what you want to memorize",
    "version": "1.0.0",
    "permissions": ["contextMenus", "activeTab", "tabs"],
    "action": {
        "default_popup": "popup.html"
    },
    "background": {
        "service_worker": "./js/background.js"
    },
    "content_scripts": [
        {
          "matches": ["http://*/*", "https://*/*"],
          "js": ["./js/content.js"]
        }
    ]
}

The extension works correctly initially, but it fails after changing a website and I got a error "Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist."

How to make the context menu run correctly on every tabs? Really appreicate your help!

  • Your onClicked event listener is misplaced inside onInstalled listener, so it will work only for less than a minute immediately after the installation/update until the background script is auto-terminated, then it will be ignored like it doesn't exist. Move it outside to properly re-register it every time the background script starts on an event like the context menu click.

  • When the extension is installed/updated its new content script won't be automatically injected into the currently opened tabs in Chrome/ium, so you'll have to do it explicitly yourself as shown here , but there's a much better alternative in cases like yours where the access to the page is necessary only on demand after the user invoked the extension: programmatic injection via executeScript in tandem with the activeTab permission.

    • remove content_scripts from manifest.json - now your extension won't require broad host permissions ie there'll be no installation warning in the web store.

    • remove "tabs" from "permissions" - it's not necessary and now there'll be no warning about observing the browser history.

    • add "scripting" to "permissions".

  • Consider limiting the contexts to "selection" to show it only when text is selected, and not show in the wrong contexts like the built-in menu of the extension's icon in the toolbar.

  • Parameters of chrome.runtime.onInstalled were incorrectly copied from chrome.tabs.onUpdated listener, but since they are unused you can remove them.

chrome.runtime.onInstalled.addListener(() => {
  chrome.contextMenus.create({
    id: 'addWords',
    title: 'Send "%s" to background',
    contexts: ['selection'],
  });
});

chrome.contextMenus.onClicked.addListener(async (info, tab) => {
  if (info.menuItemId === 'addWords') {
    let word = info.selectionText.trim();
    let sentence;
    try {
      [{ result: sentence }] = await chrome.scripting.executeScript({
        target: {
          tabId: tab.id,
          frameIds: [info.frameId],
        },
        func: () => {
          const selection = window.getSelection();
          const arr = selection.anchorNode.data.split('.');
          const word = selection.toString();
          const sentence = arr.find(str => str.includes(word));
          return sentence;
        },
      });
    } catch (e) {} // invoked on a page that doesn't allow injection
    console.log(word, sentence); // this prints in background console
  }
});

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM