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 i.e. 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
}
});