I have a script that injects a js file in each tab via
chrome.scripting.executeScript({, world: 'MAIN'})
I do that as i want to access variables of the loaded pages.
In this injected code i want to send a message to background via
chrome.runtime.sendMessage()
though in the page console i get the error:
Uncaught TypeError: Cannot read properties of undefined (reading 'sendMessage')
manifest.json (shrinked)
{
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"permissions": [
"scripting"
]
}
background.js
chrome.scripting.executeScript({
target: {tabId: tabId},
files: [
'./js/asdf.js'
],
world: 'MAIN'
}).then()
./js/asdf.js
console.log("asdfasdf"); # is printed
chrome.runtime.sendMessage({action: '...'}).then()
As soon as i do not run the script with world 'MAIN' i can access the method 'sendMessage'.
How can i send infos from a script injected into world 'MAIN' to the background? :)
For my scenario the following worked:
content script (world: isolated)
window.addEventListener("load", async function () {
//get state from background
const state = await chrome.runtime.sendMessage({});
....
//send state to page
window.postMessage();
});
//receive response from page
window.addEventListener("message", (event) => {
//send info to background
chrome.runtime.sendMessage();
}, false);
background.js (inject code into page)
chrome.scripting.executeScript({
target: {tabId: tabId},
files: [
'classes.js',
'.main.js',
],
world: 'MAIN',
}).then();
classes.js
//receive info from content script
window.addEventListener("message", (event) => {
//do something based on received state
}, false);
//post something back to content script
window.postMessage();
process:
get state from background by sendMessage in content script
based on state info content script sends postMessage to page
postMessage triggers process in page (injected code)
code does something and sends postMessage to content script
content script had listened for postMessage and forwards info to background by sendMessage
Related
I've taken a look at other related SO posts and the solutions haven't helped solve my issue. This is my first chrome extension, so please bear with me!
I'm writing a simple chrome extension that searches for user provided keywords on a webpage. I can't get the content script that returns the DOM content to run. Some of the code, I've taken from an answer in another SO post, but I can't seem to get it to work for me.
I put a console.log("hello world") at the top of the file, and it doesn't show up, so I think it might be the structure of my project.
manifest.json
{
"name": "keyword search",
"version": "0.0.1",
"manifest_version": 2,
"permissions": [ "tabs" , "storage", "activeTab", "<all_urls>"],
"browser_action": {
"default_popup": "html/form.html"
},
"content_scripts": [{
"matches": [ "<all_urls>" ],
"js": [ "js/jquery.min.js", "content_scripts/content_script.js" ]
}],
"homepage_url": "http://google.com/"
}
js/popup.js
function run() {
running = true;
console.log('running');
var url = "https://www.stackoverflow.com/"
// Get KW & category for search
chrome.storage.local.get(["kw"],
function (data) {
kw = data.kw;
console.log("redirecting to find kw: " + kw);
// Send current tab to url
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.update(tabs[0].id, {url: url});
chrome.tabs.sendMessage(tabs[0].id, {type: 'DOM_request'}, searchDOM);
});
}
);
}
function searchDOM(domContent) {
console.log("beginning dom search \n" + domContent);
}
content_scripts/content_script.js
// Listen for messages
console.log("hello world")
chrome.runtime.onMessageExternal.addListener(function (msg, sender, sendResponse) {
// If the received message has the expected format...
if (msg.type === 'DOM_request') {
// Call the specified callback, passing
// the web-page's DOM content as argument
sendResponse(document.all[0].outerHTML);
}
});
console
running
redirecting to find kw: TestKeyword
beginning dom search
undefined
First, onMessageExternal is the wrong event (it's for external messaging):
you should use the standard onMessage.
Second, chrome extensions API is asynchronous so it only registers a job, returns immediately to continue to the next statement in your code without waiting for the job to complete:
chrome.tabs.update enqueues a navigation to a new URL
chrome.tabs.sendMessage enqueues a message sending job
the current page context in the tab gets destroyed along with the running content scripts
the tab starts loading the new URL
the message is delivered into the tab but there are no listeners,
but this step may instead run right after step 2 depending on various factors so the content script running in the old page will receive it which is not what you want
the tab loads the served HTML and emits a DOMContentLoaded event
your content scripts run shortly after that because of the default "run_at": "document_idle"
There are at least three methods to properly time it all:
make your content script emit a message and add an onMessage listener in the popup
use chrome.tabs.onUpdated to wait for the tab to load
use chrome.tabs.onUpdated + chrome.tabs.executeScript to simplify the entire thing
Let's take the executeScript approach.
remove "content_scripts" from manifest.json
instead of chrome.tabs.query (it's not needed) use the following:
chrome.tabs.update({url}, tab => {
chrome.tabs.onUpdated.addListener(function onUpdated(tabId, change, updatedTab) {
if (tabId === tab.id && change.status === 'complete') {
chrome.tabs.onUpdated.removeListener(onUpdated);
chrome.tabs.executeScript(tab.id, {
code: 'document.documentElement.innerHTML',
}, results => {
searchDOM(results[0]);
});
}
});
});
Building a Chrome Extension that adds a devtools panel. That part is working. The dev tools panel needs to be able to grab information from the current page.
The only way I see how to do it, is using a content script. Ideally I don't want to require the "tabs" or "" permissions. I'm fine with them being optional permissions".
My content scripts that I inject only appear to work on the pages that I have specified in my manifest file. I want them to work on any page the user inspects so my panel can display information.
User clicks a button in panel - I request optional permissions for "tabs" and "" in my panel.js
my devtools page .js checks for "tabs" and "" permission.
If permission is granted, I send background.js a message - load this content script.
Recieving the message doesn't seem to matter on Background.js's end as the content script loads automatically regardless on URL's specified in the manifest. It does not however appear on pages you inspect.
CODE
(in order of how it was explained above)
Button in panel.html
<button id="load">Load Dev Info</button>
https://github.com/williamspiro/HubSpot-Developer-Extension/blob/c23b8cad6f1787264bb3e8b3673c594a7db5fe7e/panel.html#L8
Permission request in Panel.js
document.querySelector('#load').addEventListener('click', function(event) {
// Permissions must be requested from inside a user gesture, like a button's
// click handler.
chrome.permissions.request({
permissions: ['tabs'],
origins:['<all_urls>']
}, function(granted) {
// The callback argument will be true if the user granted the permissions.
if (granted) {
console.log("Perm granted");
} else {
console.log("Perm denied");
}
});
});
https://github.com/williamspiro/HubSpot-Developer-Extension/blob/c23b8cad6f1787264bb3e8b3673c594a7db5fe7e/panel.js#L2-L18
permissions check if granted - sendMessage with content script name to background JS and devtools.js(loaded on devtools.html)
name: "devtools-page"
});
backgroundPageConnection.onMessage.addListener(function (message) {
// Handle responses from the background page, if any
});
console.log("devtools.js ran");
chrome.permissions.contains({
permissions: ['tabs'],
origins: ['<all_urls>']
}, function(result) {
if (result) {
// The extension has the permissions.
// Relay the tab ID to the background page
chrome.runtime.sendMessage({
tabId: chrome.devtools.inspectedWindow.tabId,
scriptToInject: "hsInspector.js"
});
} else {
// The extension doesn't have the permissions.
}
});
https://github.com/williamspiro/HubSpot-Developer-Extension/blob/c23b8cad6f1787264bb3e8b3673c594a7db5fe7e/devtools.js#L7-L35
Receive message to background.js - inject content script
chrome.runtime.onConnect.addListener(function(devToolsConnection) {
// assign the listener function to a variable so we can remove it later
var devToolsListener = function(message, sender, sendResponse) {
// Inject a content script into the identified tab
console.log("script:",message.scriptToInject);
chrome.tabs.executeScript(message.tabId,
{ file: message.scriptToInject });
}
// add the listener
devToolsConnection.onMessage.addListener(devToolsListener);
devToolsConnection.onDisconnect.addListener(function() {
devToolsConnection.onMessage.removeListener(devToolsListener);
});
});
https://github.com/williamspiro/HubSpot-Developer-Extension/blob/c23b8cad6f1787264bb3e8b3673c594a7db5fe7e/background.js#L38-L52
It appears the background JS part does not matter, because in manifest I have the content script mentioned in the manifest causing it to get injected on all URL's that match but not inspected pages.
Permissions and content scripts in manifest.json
"permissions": [
"storage",
"activeTab"
],
"optional_permissions": [
"https://app.hubspot.com/*",
"https://app.hubspotqa.com/*",
"tabs",
"<all_urls>"
],
"web_accessible_resources": [
"mac-text-cursor.svg",
"mac-text-cursor.png"
],
"content_scripts": [{
"run_at": "document_idle",
"js": ["jquery-3.2.1.min.js", "design-manager.js","hsInspector.js"],
"css": ["hsDarkIde.css"],
"document_idle": "document_start",
"matches": ["https://app.hubspot.com/*", "https://app.hubspotqa.com/*"]
}],
"content_security_policy": "script-src 'self' https://ssl.google-analytics.com; object-src 'self'",
https://github.com/williamspiro/HubSpot-Developer-Extension/blob/66ee912d56b88be052182a91257af28a4e356558/manifest.json#L25-L46
The content script I am trying to inject from the devtools is hsInspector.js
I only care to have this file get loaded if the devtools injects it. It doesn't need to load on the "matches" pages at all.
EXPECTED AND ACTUAL RESULTS
Expected result is that when inspecting a page, if you go to the extension's dev tools tab, and have the permissions required, the content script will be injected into the page.
[if that were working my goal would be to then make the content script do this]
Content script will be used to grab data from the page, message background.js the required data, background.js messages panel.js the data, panel js would render what it needs to in the panel.
Actual result: content script is not loading into the inspected page, but is instead loading onto the "matches" URLs specified under content_scripts.
If you have questions please ask. I am not an expert at building chrome extensions, so maybe I'm making this way more complicated than it needs to be. Really appreciate any help. Thank you in advance.
i develop my first chrome extension.
I try to call the page from my default_popup.
I try with the chrome.runtime.onMessage.addListener and the chrome.runtime.sendMessage but that do not work.
I read this page https://developer.chrome.com/apps/messaging, but i can't figure out where to place correctly my Listiner.
I need when i open the "default popup", call an event in the page and return something to the "default_popup" came from the page.
More explication :
Actually i have a content.js in this content.js i am able to call the background.js by calling the chrome.runtime.sendMessage but it's call to fast.
The DOM of the page have not enought time to load. My content.js inject some .js file in the webpage to interact with the page.
It's there a way i can call the crhome.extension.sendMessage from the injected page ?
Any suggestion ?
Ok i found it.
We can register in the background.js an event
chrome.runtime.onMessageExternal.addListener(
function (request, sender, sendResponse) {
debugger;
if (sender.url == blacklistedWebsite)
return; // don't allow this web page access
if (request.openUrlInEditor)
openUrl(request.openUrlInEditor);
});
You need to put in the manifest the right rules
"externally_connectable": {
"matches": ["*://*.example.com/*"]
}
After that from your injected page :
chrome.runtime.sendMessage(extensionID, { openUrlInEditor: "test" },
function (response) {
debugger;
if (!response.success)
handleError("est");
});
UPDATE
From what I can tell, it is impossible to send a message from the background script to the content script using the "sendMessage" function. However there is a horrible workaround,
In your content script's window.onload, send a message to the background script:
chrome.runtime.sendMessage( { action: "messaging", window: "app" }, this.listenForFutureMessages );
Also in the content script, have the following function:
listenForFutureMessages: function(someAction)
{
//Take some action based on the message
//If we want the background script to be able to contact
//us again, we need to give them another callback. This
//is because Chrome only allows one use per callback
chrome.runtime.sendMessage( { action: "messaging", window: "app" }, this.listenForFutureMessages );
},
In the background script, have a listener that does something like this:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse)
{
if ( request.action === "messaging" )
{
//Save the callback for later
this.listeners[ request.window ] = sendResponse;
//Tell chrome we will be using the callback later
return true;
}
}
);
When your background script wants to send the content script a message, simply call it like this:
this.listeners[ "app" ]( { someProperty: "some value" } );
This is a stupid way to do this, but it makes this actually possible. Hope this helps anyone else who needs this functionality.
ORIGINAL
I'm unable to send a message from my background script to a content script. When I try to find the tab id, it tells me I don't have permissions even though my app has that permission. And when I receive a message from the content script, and print out the sender object, it shows tab.id = -1. The API to send a message to a content script requires a tab id!
chrome.tabs.sendMessage(integer tabId, any message, function responseCallback)
The error:
chrome.tabs is not available: You do not have permission to access this API. Ensure that the required permission or manifest property is included in your manifest.json.
Error in event handler for 'undefined': Cannot call method 'sendMessage' of undefined TypeError: Cannot call method 'sendMessage' of undefined
at chrome-extension://panoaieakcofaegcjfbmhndaekfgpijh/scripts/background.js:109:16
at Event.dispatchToListener (event_bindings:356:21)
at Event.dispatch_ (event_bindings:342:27)
at Event.dispatch (event_bindings:362:17)
at miscellaneous_bindings:167:33
at Event.dispatchToListener (event_bindings:356:21)
at Event.dispatch_ (event_bindings:342:27)
at Event.dispatch (event_bindings:362:17)
at Object.chromeHidden.Port.dispatchOnMessage (miscellaneous_bindings:253:22)
So how do I contact my content script? (I have multiple windows and need to be able to contact them individually)
My manifest:
{
"manifest_version": 2,
"name": "App",
"description": "App",
"version": "0.75",
"minimum_chrome_version": "27",
"offline_enabled": true,
"icons":
{
"16": "images/icon16.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
},
"app":
{
"background":
{
"scripts":
[
"scripts/background.js"
]
}
},
"permissions":
[
"unlimitedStorage",
"fullscreen",
{
"fileSystem":
[
"write"
]
},
"background",
"<all_urls>",
"tabs"
],
"update_url": "http://192.168.1.121/app.xml"
}
There's not such a thing called "Content scripts" in a Chrome app. Your manifest file looks like a mixture of a Chrome extension. Open chrome://extensions/, enable developer mode, and you would see a warning that the "background" and "tabs" permissions are invalid for a Chrome app.
If you're implementing a Chrome app, just use chrome.runtime.sendMessage and chrome.runtime.onMessage. These messages can be send from and to your event page and the main page. For example:
// event page (aka background page)
chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('main.html');
});
// Later, when you want to notify the app window
chrome.runtime.sendMessage(" ... any message ... ");
<!-- main.html -->
<script src="main.js"></script>
// main.js
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
// Do something with the message
});
I'm a newbie at Chrome extensions, and of course I stuck on every step, but this is specially hard. Maybe it is a silly mistake, but here is what I am trying to do:
Send a simple message from the content script to the background page and handle it as a variable. So I have this in my content script:
$(document).ready(function() {
var d = document.domain;
chrome.extension.sendMessage({dom: d});
});
And in my background script this:
chrome.extension.onMessage.addListener(function(request) {
alert(request.dom);
});
So, the alert works fine. But it "goes" to the page I am browing and not the HTML extension, this means, instead of poping up when clicking on my extension button, it will appear as it was coded into the content script when the page loads.
Please, any help would be appreciated.
My Demo extension is as follows
Files & Roles
a) manifest.json (Documentation)
b) myscript.js (Content Script See Documentation)
c) background.js (Background HTML File See Documentation)
d) popup.html (Browser Action Popup See Documentation)
e) popup.js (Receptor of Modified value from Background Page)
manifest.json
Registered all files to manifest(Viz background,popup,content scripts) with permissions
{
"name":"Communication Demo",
"description":"This demonstrates modes of communication",
"manifest_version":2,
"version":"1",
"permissions":["<all_urls>"],
"background":{
"scripts":["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["myscript.js"]
}
],
"browser_action":{
"default_icon":"screen.png",
"default_popup":"popup.html"
}
}
myscript.js
Used sendMessage() API for communicating with background page
var d = document.domain;
chrome.extension.sendMessage({
dom: d
});
background.js
Added Event Listeners for Content and popup.js using onMessage() and onConnect() Listeners
var modifiedDom;
chrome.extension.onMessage.addListener(function (request) {
modifiedDom = request.dom + "Trivial Info Appending";
});
chrome.extension.onConnect.addListener(function (port) {
port.onMessage.addListener(function (message) {
if (message == "Request Modified Value") {
port.postMessage(modifiedDom);
}
});
});
popup.html
Sample browser action HTML Page registering popup.js to avoid Inline Scripting
<!doctype html>
<html>
<head>
<script src="popup.js"></script>
</head>
<body></body>
</html>
popup.js
Used Port\Long Lived Connection for communicating with background page for fetching results
var port = chrome.extension.connect({
name: "Sample Communication"
});
port.postMessage("Request Modified Value");
port.onMessage.addListener(function (msg) {
console.log("Modified Value recieved is " + msg);
});
Hope this helps, let me know if you need more information