I'm trying to send new tab URL from background.js to content.js. The .sendMessage() performs but doesn't get to the content.js
background.js:
chrome.tabs.onUpdated.addListener(
function (tabId, changeInfo, tab) {
if (changeInfo.url) {
chrome.tabs.sendMessage(tabId, {
url: changeInfo.url
})
}
}
);
content.js:
chrome.runtime.onMessage.addListener(function(request, sender, callback) {
console.log('here');
});
manifest.json:
{
"manifest_version": 2,
"name": "Url tracker",
"description": "Track your latest visited URLs",
"version": "0.0.1",
"icons": {
"16": "logo-small.png",
"48": "logo-small.png",
"128": "logo-small.png"
},
"permissions": [
"activeTab",
"tabs"
],
"background": {
"scripts":["background.js"],
"persistent": false
},
"content_scripts": [{
"matches": ["<all_urls>"],
"all_frames": true,
"js": ["content.js"]
}]
}
Content scripts run after DOMContentLoaded by default (it can be changed) but the URL is reported to onUpdated when the tab starts loading i.e. before the content script runs.
The solution is to add a check to skip the first update because it's not needed: an instance of the content script runs in each matching web page and it already knows location.href of its page.
if (changeInfo.url && tab.status === 'complete') {
After you reload your extension on chrome://extensions page (or update it from the web store), all its content scripts get "orphanized" and can't receive messages.
The solution is to re-inject them explicitly.
Related
I know there are similar questions and I've tried almost everything in them. I'm trying to build a chrome extension and it needs to pass a message from content.js to background.js.
The code:
background.js
var xstext;
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
xstext=request.stext;
});
var wtf = "https://www.google.com/search?q="+xstext;
chrome.commands.onCommand.addListener(function(command) {
if(command=="ntab"){
chrome.tabs.create({ url: wtf});
}
});
content.js
var text = window.getSelection();
var stext=text.toString();
chrome.runtime.sendMessage({stext: stext});
manifest.json
"manifest_version": 2,
"name": "vind",
"version": "1.0",
"description": "Search stuff easily!",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [
{
"all_frames": true,
"run_at": "document_start",
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"browser_action": {
"default_icon": {
"16": "images/icon16.png",
"32": "images/icon32.png"
},
"default_popup": "popup.html"
},
"commands": {
"ntab": {
"suggested_key": {
"default": "Alt+G",
"windows": "Alt+G",
"mac": "Alt+G",
"chromeos": "Alt+G",
"linux": "Alt+G"
},
"description": "New tab for the query"
}
}
}
I want to pass the selected text from content.js to background.js, I have tried adding return: true; in the listener to no avail. I'm getting 'undefined' added to the main string, so nothing seems to get passed. what should I do?
This approach won't work because 1) your content script is saving the text at the moment it runs which happens just one time at page load and 2) since the background script is not persistent it'll unload after receiving a message and xstext will disappear.
Do the reverse: ask the content script when the hotkey is pressed.
background.js, entire contents:
chrome.commands.onCommand.addListener(command => {
if (command === 'ntab') {
chrome.tabs.query({active: true, lastFocusedWindow: true}, ([tab]) => {
if (!tab) return;
chrome.tabs.sendMessage(tab.id, 'getText', text => {
chrome.tabs.create({
url: 'https://www.google.com/search?q=' + encodeURIComponent(text),
});
});
});
}
});
content.js, entire contents:
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg === 'getText' && document.hasFocus()
&& (!document.activeElement || !/^I?FRAME$/.test(document.activeElement.tagName))) {
sendResponse(getSelection().toString());
}
});
P.S. An advanced solution would be to run the content script on demand (using chrome.tabs.executeScript in background.js onCommand listener) so you can remove content_scripts section from manifest.json and use activeTab permission instead.
I'm trying to access the activeTab DOM content from my popup. Here is my manifest:
{
"manifest_version": 2,
"name": "Test",
"description": "Test script",
"version": "0.1",
"permissions": [
"activeTab",
"https://api.domain.com/"
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"browser_action": {
"default_icon": "icon.png",
"default_title": "Chrome Extension test",
"default_popup": "index.html"
}
}
I'm really confused whether background scripts (event pages with persistence: false) or content_scripts are the way to go. I've read all the documentation and other SO posts and it still makes no sense to me.
Can someone explain why I might use one over the other.
Here is the background.js that I've been trying:
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
// LOG THE CONTENTS HERE
console.log(request.content);
}
);
And I'm just executing this from the popup console:
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, { }, function(response) {
console.log(response);
});
});
I'm getting:
Port: Could not establish connection. Receiving end does not exist.
UPDATE:
{
"manifest_version": 2,
"name": "test",
"description": "test",
"version": "0.1",
"permissions": [
"tabs",
"activeTab",
"https://api.domain.com/"
],
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'",
"browser_action": {
"default_icon": "icon.png",
"default_title": "Test",
"default_popup": "index.html"
}
}
content.js
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.text && (request.text == "getDOM")) {
sendResponse({ dom: document.body.innerHTML });
}
}
);
popup.html
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, { action: "getDOM" }, function(response) {
console.log(response);
});
});
When I run it, I still get the same error:
undefined
Port: Could not establish connection. Receiving end does not exist. lastError:30
undefined
The terms "background page", "popup", "content script" are still confusing you; I strongly suggest a more in-depth look at the Google Chrome Extensions Documentation.
Regarding your question if content scripts or background pages are the way to go:
Content scripts: Definitely
Content scripts are the only component of an extension that has access to the web-page's DOM.
Background page / Popup: Maybe (probably max. 1 of the two)
You may need to have the content script pass the DOM content to either a background page or the popup for further processing.
Let me repeat that I strongly recommend a more careful study of the available documentation!
That said, here is a sample extension that retrieves the DOM content on StackOverflow pages and sends it to the background page, which in turn prints it in the console:
background.js:
// Regex-pattern to check URLs against.
// It matches URLs like: http[s]://[...]stackoverflow.com[...]
var urlRegex = /^https?:\/\/(?:[^./?#]+\.)?stackoverflow\.com/;
// A function to use as callback
function doStuffWithDom(domContent) {
console.log('I received the following DOM content:\n' + domContent);
}
// When the browser-action button is clicked...
chrome.browserAction.onClicked.addListener(function (tab) {
// ...check the URL of the active tab against our pattern and...
if (urlRegex.test(tab.url)) {
// ...if it matches, send a message specifying a callback too
chrome.tabs.sendMessage(tab.id, {text: 'report_back'}, doStuffWithDom);
}
});
content.js:
// Listen for messages
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
// If the received message has the expected format...
if (msg.text === 'report_back') {
// Call the specified callback, passing
// the web-page's DOM content as argument
sendResponse(document.all[0].outerHTML);
}
});
manifest.json:
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
...
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["*://*.stackoverflow.com/*"],
"js": ["content.js"]
}],
"browser_action": {
"default_title": "Test Extension"
},
"permissions": ["activeTab"]
}
Update for manifest v3
chrome.tabs.executeScript doesn't work in manifest v3, as noted in the comments of this answer. Instead, use chrome.scripting. You can specify a separate script to run instead of a function, or specify a function (without having to stringify it!).
Remember that your manifest.json will need to include
...
"manifest_version": 3,
"permissions": ["scripting"],
...
You don't have to use the message passing to obtain or modify DOM. I used chrome.tabs.executeScriptinstead. In my example I am using only activeTab permission, therefore the script is executed only on the active tab.
part of manifest.json
"browser_action": {
"default_title": "Test",
"default_popup": "index.html"
},
"permissions": [
"activeTab",
"<all_urls>"
]
index.html
<!DOCTYPE html>
<html>
<head></head>
<body>
<button id="test">TEST!</button>
<script src="test.js"></script>
</body>
</html>
test.js
document.getElementById("test").addEventListener('click', () => {
console.log("Popup DOM fully loaded and parsed");
function modifyDOM() {
//You can play with your DOM here or check URL against your regex
console.log('Tab script:');
console.log(document.body);
return document.body.innerHTML;
}
//We have permission to access the activeTab, so we can call chrome.tabs.executeScript:
chrome.tabs.executeScript({
code: '(' + modifyDOM + ')();' //argument here is a string but function.toString() returns function's code
}, (results) => {
//Here we have just the innerHTML and not DOM structure
console.log('Popup script:')
console.log(results[0]);
});
});
For those who tried gkalpak answer and it did not work,
be aware that chrome will add the content script to a needed page only when your extension enabled during chrome launch and also a good idea restart browser after making these changes
I am writing a Google Chrome extension to automate some common tasks. The functionality I want is as follows:
Create a new tab and navigate to my webmail
enter username and password
click "submit" button
Wait until the webmail page appears, and choose the "roundcube" client.
I have completed steps 1,2,and 3 and they work. I am having a lot of trouble trying to listen for the url change after my credentials are submitted so that the function that selects roundcube client can run
I know I can run a script when client selection page appears by adding to my manifest but I want to use "chrome.tabs.executeScript" instead so that roundcube is chosen only if I run the script from the chrome extension and not if I go to client selection page manually.
Here is my manifest.json:
{
"manifest_version": 2,
"name" : "Chrome Autobot",
"description": "This extension will run various automation scripts for google chrome",
"version" : "1.0",
"browser_action" : {
"default_icon" : "icon.png",
"default_popup": "index.html"
},
"permissions": [
"activeTab",
"webNavigation",
"tabs",
"http://*/*",
"https://*/*"
]
}
Here is my chrome script:
jQuery(function($) {
"Use Strict";
var openWebmail = function() {
chrome.tabs.create({
url: 'http://mywebmaillogin.com:2095/'
}, function() {
chrome.tabs.executeScript(null, {file: "scripts/openEmail.js"});
});
chrome.tabs.onUpdated.addListener(function(){
chrome.tabs.executeScript(null, {file: "scripts/openEmail.js"});
alert('i work');
});
};
var init = $('.script-init');
init.on('click', function() {
openWebmail();
});
});
and here is the content script to be executed as a callback of tab creation (when the email login page is fetched and the DOM has loaded), and also when the email credentials are submitted and the client selection page's DOM has loaded (which is not working right now)
var openEmail = function() {
var loc = window.location.href;
if(loc === 'http://mywebmaillogin.com:2095/') {
var submit = document.getElementById('login_submit');
user.value = 'myusername';
pass.value = 'mypassword';
if(user.value === 'myusername' && pass.value === 'mypassword') {
submit.click();
}
else {
openEmail();
}
}
if(loc.indexOf('http://mywebmaillogin:2095/') > -1 && loc.indexOf('login=1') > -1) {
alert('I work');
}
}()
any help would be appreciated... thanks!
As mentioned by #NycCompSci, you cannot call the chrome api from content scripts. I was able to pass api data to content scripts with message passing though, so thought I'd share that here. First call onUpdated in background.js:
Manifest
{
"name": "My test extension",
"version": "1",
"manifest_version": 2,
"background": {
"scripts":["background.js"]
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["contentscript.js"]
}
],
"permissions": [
"tabs"
]
}
background.js
chrome.tabs.onUpdated.addListener(function
(tabId, changeInfo, tab) {
// read changeInfo data and do something with it (like read the url)
if (changeInfo.url) {
// do something here
}
}
);
Then you can expand that script to send data (including the new url and other chrome.tabs.onUpdated info) from background.js to your content script like this:
background.js
chrome.tabs.onUpdated.addListener(
function(tabId, changeInfo, tab) {
// read changeInfo data and do something with it
// like send the new url to contentscripts.js
if (changeInfo.url) {
chrome.tabs.sendMessage( tabId, {
message: 'hello!',
url: changeInfo.url
})
}
}
);
Now you just need to listen for that data in your content script:
contentscript.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
// listen for messages sent from background.js
if (request.message === 'hello!') {
console.log(request.url) // new url is now in content scripts!
}
});
use chrome.tabs.onUpdated
Maifest.json
{
"name": "My test extension",
"version": "1",
"manifest_version": 2,
"background": {
"scripts":["background.js"]
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["contentscript.js"]
}
],
"permissions": [
"tabs"
]
}
contentscript.js
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
alert('updated from contentscript');
});
background.js
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
alert('updated from background');
});
Based on / Thanks to #ztrat4dkyle's answer, what works for me:
manifest.json
{
...
"background": {
"scripts":["background.js"]
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["content.js"]
}
],
"permissions": [
"tabs"
]
}
background.js
chrome.runtime.onInstalled.addListener(function() {
// ...
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
// changeInfo object: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/onUpdated#changeInfo
// status is more reliable (in my case)
// use "alert(JSON.stringify(changeInfo))" to check what's available and works in your case
if (changeInfo.status === 'complete') {
chrome.tabs.sendMessage(tabId, {
message: 'TabUpdated'
});
}
})
});
content.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
if (request.message === 'TabUpdated') {
console.log(document.location.href);
}
})
Hello, I am writing a simple chrome extension which will be used to:
1. open new webpage
2. fill timesheet form based on pasted string array
3. submit timesheet (just clicking "ok" button in the form)
4. open new webpage
For this to work my extension needs to contain:
1. popup.html Browser Action popup, with input textfield for string array, and submit button.
2. timesheet.js - javascript file to add logic to popup.html
3. background.js - background page to take care of filling form, after clicking submit button
4. content_script.js - to access newly opened webpage DOM, to fill the form.
For now, I made a simplified version, which is supposed to:
1. open www.google.com in new tab
2. wait few seconds (optionally, wait or page to finish loading)
3. change background color
Everything seems to be woring fine, except for content_script.js listener doesn't react to message send by background.js
Here is the code:
manifest.json:
{
"manifest_version": 2,
"name": "Timesheet Filler",
"description": "Description.",
"version": "1.0",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["http://www.google.com/*"],
"js": ["content_script.js"]
}],
"browser_action": {
"default_title": "Timesheet Filler",
"default_popup": "popup.html"
},
"permissions": [
"tabs",
"activeTab",
"http://www.google.com/*"
]
}
popup.html:
<!DOCTYPE html>
<html>
<body>
<button id="btn" >Click Me!</button>
<script src="timesheet.js"></script>
</body>
</html>
timesheet.js:
document.addEventListener('DOMContentLoaded', function(){
init();
});
function init(){
var btn = document.getElementById('btn');
btn.onclick = function() { onBtnClick(); }
}
function onBtnClick(){
chrome.runtime.sendMessage({action:"btnClick"}, btnClickCallback);
}
function btnClickCallback(any){
alert(any);
}
background.js
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.action == "btnClick"){
chrome.tabs.create({url: "http://www.google.com", active:true});
setTimeout(function(){ delayed(); }, 3000);
}
});
function delayed(){
chrome.tabs.query({active:true}, queryCallback);
}
function queryCallback(arr){
var tabId = arr[0].id;
console.log("message shown 3 second after clicking button") // THIS IS WORKING
chrome.tabs.sendMessage(tabId, {action:"doSomething"}); // CONTENT SCRIPT DOESNT REACT TO THIS
}
function contentScriptCallback(any){
alert(any);
}
content_script.js:
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if( message.action == "doSomething"){
document.body.style.backgroundColor='#000000';
alert("do something");
}
});
Download all files in one ZIP here
How to make content_script.js react to to message and change webpage bg color?
SOLUTION: (thanks to #wOxxOm)
Content script wasn't called since matches in content_scripts section as well as permisions of manifest.json was defined wrong.
Easiest fix was to change URL range to: <all_urls>
(for precise urls matches see this link)
Fixed manifest.json:
{
"manifest_version": 2,
"name": "Timesheet Filler",
"description": "Description.",
"version": "1.0",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["<all_urls>"],
"js": ["content_script.js"]
}],
"browser_action": {
"default_title": "Timesheet Filler",
"default_popup": "popup.html"
},
"permissions": [
"tabs",
"activeTab",
"<all_urls>"
]
}
Here is my scenario: By clicking the browser icon, I create a sidebar (html and css) next to the whole page, thus creating two columns (one is my sidebar, the other one is the actual page).
What I to achieve is having the sidebar stay when I reload the page or navigate to another page WITHIN the same domain. What I have right now is just the creation of the sidebar, but I have to click the browser action every time I navigate or reload the web page.
Manifest:
{
"name": "apdrop",
"version": "0.1",
"manifest_version": 2,
"description": "first prototype for apdrop extension",
"icons": {
"16": "icons/icon16.png",
"48": "icons/icon48.png",
"128": "icons/icon128.png"
},"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_icon": "icons/icon19.png",
"default_title": "apdrop"
},
"permissions": [
"background",
"tabs",
"http://*/*/",
"https://*/*/"
]
}
Background.js
function injectedScript(tab, method){
chrome.tabs.insertCSS(tab.id, {file:"style.css"});
//chrome.tabs.insertCSS(tab.id, {file:"bootstrap.css"});
chrome.tabs.executeScript(tab.id, { file: 'jquery-2.1.1.min.js'});
//chrome.tabs.executeScript(tab.id, { file: 'bootstrap.min.js'});
chrome.tabs.executeScript(tab.id, { file: 'inject.js'});
}
function click(tab){
console.log("browser action clicked");
injectedScript(tab, 'click');
//alert("action button was clicked");
}
chrome.browserAction.onClicked.addListener(click);
Inject.js
var ev = $("body > *");
if (!document.getElementById('contentxf343487d32'))
{
ev.wrapAll("<div id='insidecontent65675f526567'>");
$("#insidecontent65675f526567").wrapAll("<div id='contentxf343487d32'>");
$("<div id='sidebar343gf87897fh'><div id='insidesidebar87678bbbb'><p>this is my name</p></div></div>").insertBefore("#contentxf343487d32");
}
else
{
$("#sidebar343gf87897fh").remove();
$("#insidecontent65675f526567").unwrap();
$("#insidecontent65675f526567 > div").unwrap();
}
Hope this helps clarify a bit more.
The simplest strategy would be to save state in domain's sessionStorage and have a "detector" script that re-injects your UI.
Add setting the state in your content script:
// inject.js
if (!document.getElementById('contentxf343487d32'))
{
// ...
sessionStorage["contentxf343487d32"] = true;
}
else
{
// ...
sessionStorage["contentxf343487d32"] = false;
}
Add a "detector" script:
// detect.js
if(sessionStorage["contentxf343487d32"])
{
chrome.runtime.sendMessage({injectSidebar: true});
}
Always inject the script on page load, via the manifest (and change to a better permission):
"content_scripts" : [
{
"matches": ["<all_urls>"],
"js": ["detect.js"]
}
],
"permissions": [
"background",
"tabs",
"<all_urls>"
]
In the background, inject the script upon message:
// background.js
chrome.runtime.onMessage.addListener( function (message, sender, sendResponse){
if(message.injectSidebar)
{
click(sender.tab);
}
});
If you need more persistence than sessionStorage provides, use localStorage. If you need a different logic, you can still use this skeleton of a detector signalling the background.