I'm building my first Chrome extension and my goal is to fetch the value of a DOM element from the extension popup by the click of a button. I'm basically following the exact documentation on how to send a message but keep getting:
'Error: Could not establish connection. Receiving end does not exist.'
For now, I'm not even trying to return DOM data; just trying to console.log a message triggered by the extension and returned by the content script. Any idea what the issue might be?
Here's my setup:
manifest.jst
{
"name": "Fetch Test",
"description": "Fetch data from DOM",
"version": "1.0",
"manifest_version": 3,
"action": {
"default_popup": "popup.html"
},
"permissions": ["activeTab", "tabs", "scripting"],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_script.js"]
}
]
}
popup.html
<html>
<body>
<div class="container">
<button id="fetch-button">Fetch</button>
</div>
</body>
</html>
<script src="popup.js"></script>
popup.js
document.getElementById("fetch-button").addEventListener("click", function () {
(async () => {
const [tab] = await chrome.tabs.query({
active: true,
lastFocusedWindow: true,
});
const response = await chrome.tabs.sendMessage(tab.id, {
greeting: "hello",
});
console.log(response);
})();
});
content_script.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
console.log(
sender.tab
? "from a content script:" + sender.tab.url
: "from the extension"
);
if (request.greeting === "hello") sendResponse({ farewell: "goodbye" });
});
This sample uses executeScript to get document.title.
manifest.json
{
"name": "Get document.title",
"version": "1.0",
"manifest_version": 3,
"permissions": [
"scripting"
],
"host_permissions": [
"<all_urls>"
],
"action": {
"default_popup": "popup.html"
}
}
popup.html
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
* {
font-size: x-large;
}
</style>
</head>
<body style="min-width:300px">
<div id="title"></div><br>
<script src="popup.js"></script>
</body>
</html>
popup.js
const getTitle = () => {
console.log("getTitle() = " + document.title);
return document.title;
}
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
console.log("Execute Script");
chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: getTitle
}, (result) => {
console.log("Recv result = " + result[0].result);
document.getElementById("title").innerText = result[0].result;
});
});
You need to include the javascript files in your popup.html file like so. Does that help?
<html>
<head>
<script src="popup.js"></script>
<script src="content_script.js"></script>
</head>
<body>
<div class="container">
<button id="fetch-button">Fetch</button>
</div>
</body>
</html>
You can try to do a long-lived message passing.
popup.js
const extensionContentScriptPort = () => {
return (async function () {
const currentTabQuery = {
active: true,
currentWindow: true,
};
const currentTabId = (await chrome?.tabs?.query(currentTabQuery))?.[0].id;
return chrome?.tabs?.connect(currentTabId, {
name: 'EXTENSION_CONTENTSCRIPT',
});
})();
};
// Add Event Listener for receiving messages
extensionContentScriptPort.then(port =>
port.onMessage.addListener((extensionContentScriptMsg) => {
console.log(extensionContentScriptMsg);
});
);
// Post message to content script
document.getElementById("fetch-button").addEventListener("click", function () {
extensionContentScriptPort?.then(port => {
port.postMessage({
greeting: "hello"
})
});
});
content_script.js
chrome?.runtime?.onConnect?.addListener(function (extensionContentScriptPort) {
console.assert(extensionContentScriptPort?.name === 'EXTENSION_CONTENTSCRIPT');
extensionContentScriptPort?.onMessage?.addListener(function (
extensionContentScriptMsg,
) {
if (extensionContentScriptMsg?.greetings === "hello") {
return extensionContentScriptPort?.postMessage({
greetings_reply: "Hey, there!"
})
}
}
}
Related
I am working on a google chrome extension project where content.js script sends message to popup.js. Also trying to get some text from content.js to be turned into the same thing but on popup.js. Here is all my code.
This is my manifest file.
manifest.json
{
"name": "hoko's ext",
"description": "my ext",
"version": "1.0",
"manifest_version": 3,
"action": {
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": ["https://*/*", "http://*/*"],
"js": ["content-script.js"]
}
],
"background": {
"service_worker": "background.js"
},
"permissions": [
"tabs",
"activeTab"
]
}
This is the popup you get when you click on the icon in the toolbar.
popup.html
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>I am the popup</h1>
<p id="extensionpopupcontent"></p>
<script type="text/javascript" src="popup.js"></script>
</body>
</html>
This is my content script called content-script.js
function injectScript(file_path, tag) {
var node = document.getElementsByTagName(tag)[0];
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', file_path);
node.appendChild(script);
}
injectScript(chrome.runtime.getURL('inject.js'), 'body');
chrome.runtime.sendMessage(document.getElementsByTagName('title')[0].innerText);
In background.js, I am receiving the message from content-script.js.
chrome.runtime.onMessage.addListener(function(response, sender, sendResponse) {
function onMessage(request, sender, sendResponse) {
console.log(sender.tab ?
"from a content script:" + sender.tab.url :
"from the extension");
if (request.greeting == "hello") {
sendResponse({farewell: "goodbye"});
}
}
})
In popup.js, it is receiving from background.js to output the code from content-script.js.
window.addEventListener('DOMContentLoaded', () => {
let bg = chrome.extension.getBackgroundPage();
chrome.tabs.query({active: true, currentWindow: true}, tabs => {
chrome.tabs.sendMessage(tabs[0].id, {greeting: "hello"}, function(response) {
document.getElementById('extensionpopupcontent').innerHTML = response;
});
});
});
I believe I need to inject this script that is inject.js.
function click() {
return document.getElementsByTagName('title')[0].innerText;
}
click();
remove background and content_scripts in manifest.json
remove tabs from permissions in manifest.json
remove background.js, content-script.js, and inject.js
rewrite popup.js as follows:
chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
document.getElementById('extensionpopupcontent').textContent = tabs[0]?.title;
});
The Manifest V3 JavaScript Chrome extension only has one button. When the button is clicked the browser should go to multiple pages and perform an action on each page before going to the next one.
What's currently happening, is the browser jumping to the last page and not performing any action.
How to fix my code, so that the extension visits each page one by one (in the same tab), then waits on the page until a button is detected, clicks the button, checks if the button is clicked, and then goes to the next page?
Manifest.json:
{
"name": "Twitch Follower",
"version": "0.1",
"manifest_version": 3,
"description": "Automatically follows Twitch users.",
"action": {
"default_popup": "popup.html"
},
"permissions": [
"activeTab",
"tabs",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"host_permissions": [
"https://www.twitch.tv/*"
]
}
popup.html:
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css#2/out/water.css">
</head>
<body>
<button id="start">Start</button>
<script src="popup.js"></script>
</body>
</html>
popup.js:
start.addEventListener("click", async () => {
chrome.runtime.sendMessage({ start: true });
});
background.js:
const list = ["https://www.twitch.tv/hello1", "https://www.twitch.tv/hello2"];
function clickFollow() {
document.querySelector("button[data-a-target='follow-button']").click();
}
function handleFollowButton(followButton) {
setTimeout(clickFollow, 1000);
}
// Check for a button
async function checkButton() {
const findButton = new Promise((resolve, reject) => {
const observerConfig = { attributes: true, attributeOldValue: true };
const observer = new MutationObserver((mutations) => {
const followButton = document.querySelector("button[data-a-target='follow-button']");
const unfollowButton = document.querySelector("button[data-a-target='unfollow-button']");
// If follow button is found, click it
if (followButton) {
handleFollowButton(followButton);
}
// If unfollow button is found, end observing and continue
if (unfollowButton) {
unfollowButtonFound = True;
observer.disconnect();
resolve;
}
});
window.setTimeout(() => {
if (!unfollowButtonFound) {
reject;
}
}, 5000);
observer.observe(document, {
childList: true,
subtree: true,
});
});
await findButton;
return true;
}
// When the start button is clicked
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.start === true) {
// Loop through every link
for (const link of list) {
console.log(link);
// Open the link and inject the script
chrome.tabs.update({ url: link }, (tab) => {
chrome.scripting.executeScript(
{
target: { tabId: tab.id },
func: checkButton,
},
(result) => {
console.log(results);
}
);
});
}
}
});
I started to learn chrome extensions, I wanna make an extension with a list of phrases to send in twitch chat.
I have no intention of using the twitch api.
The first message is sent as it should, but the next ones nothing happens.
There's a log that shows that messages arrive on the page, I don't know what I'm doing wrong.
This is what I have:
manifest.json
{
"manifest_version": 2,
"name": "Default Messages",
"version": "0.1",
"offline_enabled": true,
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["*://*.twitch.tv/*"],
"js": ["content.js"]
}],
"page_action": {
"default_title": "Default messages",
"default_popup": "popup.html"
}
}
background.js
chrome.runtime.onMessage.addListener((msg, sender) => {
if (msg.from === "twitch" && msg.subject === "chat") {
chrome.pageAction.show(sender.tab.id);
}
});
content.js
chrome.runtime.sendMessage({
from: "twitch",
subject: "chat",
});
chrome.runtime.onMessage.addListener((msg, sender, response) => {
if (msg.from === "defaultMessages" && msg.subject === "newMessage") {
console.log(msg);
let input = document.querySelectorAll("textarea")[0];
let buttons = document.querySelectorAll("button");
input.textContent = msg.content;
input.dispatchEvent(new Event("input", { bubbles: true }));
buttons.forEach((button) => {
if (button?.dataset?.aTarget == "chat-send-button") {
button.dispatchEvent(new Event("click", { bubbles: true }));
}
});
response({
from: "twitch",
subject: "messageSent",
id: msg.id,
});
}
});
popup.js
window.addEventListener("DOMContentLoaded", () => {
let tab_id = undefined;
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
tab_id = tabs[0].id;
});
document.querySelectorAll("button").forEach((button) => {
button.addEventListener("click", function (e) {
if (!e.target.disabled) {
let content = e.target.textContent;
let id = e.target.id;
chrome.tabs.sendMessage(
tab_id,
{
from: "defaultMessages",
subject: "newMessage",
content,
id,
},
(resp) => {
document.getElementById(resp.id).disabled = true;
}
);
}
});
});
});
popup.html
<!DOCTYPE html>
<html lang="en">
<head>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<script type="text/javascript" src="./popup.js"></script>
</head>
<body>
<div class="container">
<div class="card mt-1" style="width: 15rem;">
<div class="card-body">
<div class="btn-group-vertical">
<button type="button" class="btn btn-outline-primary" id="hello">Hello</button>
<button type="button" class="btn btn-outline-primary" id="hi">Hi</button>
<button type="button" class="btn btn-outline-primary" id="how">How are you doing?</button>
</div>
</div>
</div>
</div>
</body>
</html>
Changing from "textContent" to "value" solved the problem, I don't know why.
content.js
chrome.runtime.sendMessage({
from: "twitch",
subject: "chat",
});
chrome.runtime.onMessage.addListener((msg, sender, response) => {
if (msg.from === "defaultMessages" && msg.subject === "newMessage") {
console.log(msg);
let input = document.querySelectorAll("textarea")[0];
let buttons = document.querySelectorAll("button");
input.value = msg.content; // <<<<<<<<<<<<<<<<<<<<<< this
input.dispatchEvent(new Event("input", { bubbles: true }));
buttons.forEach((button) => {
if (button?.dataset?.aTarget == "chat-send-button") {
button.dispatchEvent(new Event("click", { bubbles: true }));
}
});
response({
from: "twitch",
subject: "messageSent",
id: msg.id,
});
}
});
All I am trying to do change the value of popup.html on the basis of Specific URL. So I am sending message to background.js when user click on change ID of H2 from popup.html render the content by using specific JavaScript file
manifest.json
{
"manifest_version": 2,
"name": "Helpfullio",
"version": "0.1",
"content_scripts": [{
"matches": [
"*://google.com/*/*",
],
"js": ["jquery-3.2.1.js"]
}],
"browser_action": {
"default_popup": "popup.html"
},
"background": {
"scripts": ["background.js"]
},
"permissions": ["tabs","http://*/*", "https://*/*"]
}
popup.html
<html>
<head>
</head>
<body>
<h2 id="change">Change ___________</h2>
<script src="popup.js"></script>
</body>
</html>
popup.js
function clickHandler(e) {
chrome.runtime.sendMessage({directive: "popup-click"}, function(response) {
// this.close(); finishes processing request
});
}
document.addEventListener('DOMContentLoaded', function () {
document.getElementById('change').addEventListener('click', clickHandler);
})
background.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
switch (request.directive) {
case "popup-click":
chrome.tabs.query({
'active': true, 'lastFocusedWindow': true
}, function (tabs) {
var url = tabs[0].url;
var result = url.split('/');
var hostname = result[2];
if("bitlock" == hostname){
chrome.tabs.executeScript(null, {
file: "render.js",
// allFrames: true
});
}else{
hrome.tabs.executeScript(null, {
file: "secondrender.js",
// allFrames: true
});
}
sendResponse({});
});
break;
default:
alert("Unmatched request of '" + request + "' from script to background.js from " + sender);
}
}
);;
JavaScript doesn't render the content of popup.html as it is not finding the elementID "change" . SO here how can I give reference of popup.html file in render.js file
render.js
document.getElementById("change").textContent = 'new text';
Im trying to send message to Background, and as well receive back to content script.
This is my content_script.js
init();
function init() {
function notifyBackgroundPage(event) {
chrome.runtime.sendMessage(chromeExtID, {
greeting: "Greeting from the content script"
});
}
window.addEventListener("click", notifyBackgroundPage);
// accept messages from background
// below line addListener is undefined
chrome.runtime.onMessage.addListener(function backgroundListener(request, sender, sendResponse) {
console.log("BgExt.js says: " + request);
});
}
BgExt.js
define([], function GmailExt() {
return {
start: function () {
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
console.log('inside response');
// to send back your response to the current tab
chrome.tabs.query({active: true, currentWindow: true}, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, {farewell: response}, function (response) {
});
});
return true;
}
);
}
};
});
This GmailExt file is loaded inside all.js (while all.js is inserted in index.html)
require([
"base_host/chrome/Server",
"kernel/Kernel",
"hydra/version",
"base_host/chrome/StartupConfig",
"hydra/apps",
"dojo/_base/Deferred",
"talkto_util/common",
"dojo/i18n!base_host/nls/CommonStrings",
"base_host/chrome/BgExt",
"dojo/domReady!"
], function (Server, Kernel, version, StartupConfig, apps, Deferred, talktoUtil, nls, BgExt) {
document.getElementById("logoMessage").innerText = nls.simple_secure_text;
var host = new Server();
//if app and not extension
chrome.browserAction && chrome.browserAction.onClicked.addListener(function() {
Deferred.when(kernel.onInit(), function () {
host.appManager.showShell();
});
return true;
});
BgExt.start();
});
client_base/host/chrome/index.html
<!DOCTYPE HTML>
<html>
<head>
<!-- Background Page for Extension/app -->
<title>Flock</title>
<meta charset="UTF-8">
<!--BUILD_REPLACE_BLOCK_START-->
<!-- This block will be remove by build.
All the files from libs/common/files.txt would be prepended to dojo.js -->
<script type="text/javascript" src="../../../hydra/src/libs/common/dojoConfig.js"></script>
<script type="text/javascript" src="../../../hydra/src/libs/common/underscore.js"></script>
<script type="text/javascript" src="../../../hydra/src/libs/common/underscore-ext.js"></script>
<!--BUILD_REPLACE_BLOCK_END-->
<script type="text/javascript" src="../../../hydra/src/libs/dojotoolkit/dojo/dojo.js"></script>
<script type="text/javascript" src="all.js"></script>
</head>
<body>
</body>
</html>
Manifest.json
{
"manifest_version": 2,
"content_security_policy": "script-src 'self' 'unsafe-eval' https://j.maxmind.com https://ssl.google-analytics.com https://flock-apps.flock.co https://flock-apps.flock-staging.co https://flock-apps.flock.com https://flock-apps.flock-staging.com; object-src 'self'",
"minimum_chrome_version": "22",
"options_page": "client_base/host/chrome/static/crx_browser_actions/index.html?app=preferences",
"name": "__MSG_extName__",
"description": "__MSG_extDescription__",
"background": {
"page": "client_base/host/chrome/index.html",
"persistent": true
},
"browser_action": {
"default_popup": "/gmail_ext/popup.html"
},
"web_accessible_resources": [
"client_base/host/chrome/static/blank.gif",
"gmail_ext/icons.png",
"gmail_ext/jquery-3.2.1.min.js",
"gmail_ext/gmail.js",
"gmail_ext/content_script.js"
],
"permissions": [
"<all_urls>",
"unlimitedStorage",
"notifications",
"idle",
"background",
"tabs",
"activeTab"
],
"optional_permissions": [
"clipboardWrite"
],
"externally_connectable": {
"matches": [
"https://*.google.com/*",
"http://localhost/*",
]
},
"content_scripts": [
{
"matches": [
"*://mail.google.com/*"
],
"css": [
"/gmail_ext/content_script.css"
],
"js": [
"/gmail_ext/loader.js"
],
"run_at": "document_end"
}
],
"version": "1.0"
}
I fixed it. The Solution was since I was using loader.js to load my content script and jquery via chrome.extension.getURL('/gmail_ext/content_script.js');
And was using "content_script.js" as a web_accessible_resources only loader.js had access to chrome object.
And yes, its a Dojo project, you can see the Working Extension here : https://chrome.google.com/webstore/detail/flock-chat-for-teams-and/enfaahabcinohafeakbliimmoholjeip?hl=en
I'm using page events now as showed here : https://developer.chrome.com/extensions/content_scripts#host-page-communication
At the top of loader.js I added below:
function initPort() {
var contentScriptPort,
chromeExtID = "lddaepjihbpbfpegjhjnkffjmmoigphe";
contentScriptPort = chrome.runtime.connect(chromeExtID);
contentScriptPort.onMessage.addListener(function (message) {
// Send data back to content script received from Background.
window.postMessage({type: "FROM_BACKGROUND", emails: message}, "*");
});
window.addEventListener("message", function _postMessage(event) {
// We only accept messages from ourselves
if (event.source != window)
return;
if (event.data.type && (event.data.type == "FROM_PAGE")) {
// console.log("Content script received: " + event.data.emails);
contentScriptPort.postMessage(event.data);
}
}, false);
}
initPort();