Chrome Extension -> ExecuteScript: function call doesn' work - javascript

He is my manifest.json:
{
"name": "Page Redder",
"description": "Make the current page red",
"version": "2.0",
"permissions": [
"activeTab","*://*/*"
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_icon" : "icon.png",
"default_title": "Make this page red"
},
"manifest_version": 2
}
Here is background.js that works (the page becomes red):
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, {code:'document.body.style.backgroundColor="red";'} );
});
If I change background.js in the following way, it fails to work:
function changeColor() {
document.body.style.backgroundColor="red";
}
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, {code:';'}, function() {
changeColor();
});
});
Chrome build: 38.0.2125.111
The question: what am I doing wrong here? Why calling a function in executeScript doesn't work?
Thanks,
Racoon

You are not calling a function in executeScript.
You are calling the function in its callback, that runs in the original (background) page. It's a function describing "what to do when executeScript finishes", not the code to run.
The code that actually runs in the page you're injecting code to is ";" (which obviously does nothing).
You can run a function defined in your code with executeScript by properly converting it into a string. But note that it will not have any access to variables defined outside the function.
I think what you're trying to do is to make the code accept a parameter (color). Instead of crafting custom code each time, you should consider using Messaging to pass a command.
Example: add a file content.js with the following:
// Include guard: only execute once
if (!injected) {
injected = true;
init();
}
function init() {
// Get ready to receive a command
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.action == "colorBackground") {
document.body.style.backgroundColor = message.color;
}
});
}
And in your background, you can do this:
chrome.tabs.executeScript(null, {file: "content.js"}, function() {
// File executed, it's ready for the message
chrome.tabs.sendMessage(null, { action: "backgroundColor", color: "green" });
}

Related

Cant fire `alert` from `service_worker` (previously background) from Chrome Extension (v3 manifest) [duplicate]

I am attempting to display an alarm that pops up in my browser from background.js in Manifest v3. However, using the code implementation that is described in the Manifest v3 page does not produce an alarm.
Manifest.js:
{
"name": "Focus-Bot",
"description": "A bot meant to help you focus",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["storage", "activeTab", "scripting"],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
}
},
"icons": {
"16": "/images/get_started16.png",
"32": "/images/get_started32.png",
"48": "/images/get_started48.png",
"128": "/images/get_started128.png"
},
"options_page": "options.html"
}
Background.js:
chrome.runtime.onInstalled.addListener(() => {
chrome.scripting.executeScript({
function: showAlert
})
});
function showAlert(){
alert('object input for later');
}
This version of background.js returns the following error
TypeError: Error in invocation of scripting.executeScript(scripting.ScriptInjection injection, optional function callback): Error at parameter 'injection': Missing required property 'target'.
The example code of a working Chrome Extension (the green background button) uses chrome.tabs in a popup.js file to get a target and inject javascript, but when background.js runs the same code like this:
Background.js (tabs):
chrome.runtime.onInstalled.addListener(() => {
let [tab] = await chrome.tabs.query(queryOptions);
console.log(tab)
chrome.scripting.executeScript({
function: showAlert
})
});
function showAlert(){
alert('object input for later');
}
Background.js seems to crash with "Service worker registration failed", with no error logs.
How do I display an alarm for the current active page from background.js?
As the error message says you need to add target to executeScript's parameters. Always look up the exact usage of API methods in the documentation.
Your code uses await but the function isn't declared with async which is a syntax error that causes the service worker to fail the registration. Currently ManifestV3 is riddled with bugs so it doesn't even show the cause of the failure so you'll have to use try/catch manually.
try {
chrome.runtime.onInstalled.addListener(async () => {
const [tab] = await chrome.tabs.query(queryOptions);
chrome.scripting.executeScript({
target: {tabId: tab.id},
function: showAlert,
});
});
} catch (e) {
console.error(e);
}
An arguably better/cleaner approach would be to use two files: the main code in bg.js and the try-catch wrapper in bg-loader.js that imports bg.js, see this example.
Note that the active tab may be un-injectable e.g. a default start page or a chrome:// page (settings, bookmarks, etc.) or a chrome-extension:// page. Instead you can open a small new window:
alert({html: 'Foo <b>bar</b><ul><li>bla<li>bla</ul>'})
.then(() => console.log('alert closed'));
async function alert({
html,
title = chrome.runtime.getManifest().name,
width = 300,
height = 150,
left,
top,
}) {
const w = left == null && top == null && await chrome.windows.getCurrent();
const w2 = await chrome.windows.create({
url: `data:text/html,<title>${title}</title>${html}`.replace(/#/g, '%23'),
type: 'popup',
left: left ?? Math.floor(w.left + (w.width - width) / 2),
top: top ?? Math.floor(w.top + (w.height - height) / 2),
height,
width,
});
return new Promise(resolve => {
chrome.windows.onRemoved.addListener(onRemoved, {windowTypes: ['popup']});
function onRemoved(id) {
if (id === w2.id) {
chrome.windows.onRemoved.removeListener(onRemoved);
resolve();
}
}
});
}

How To Call Chrome Extension Function After Page Redirect?

I am working on building a Javascript (in-browser) Instagram bot. However, I ran into a problem.
If you run this script, the first function will be called and the page will be redirected to "https://www.instagram.com/explore/tags/samplehashtag/" and the second function will be called immediately after (on the previous URL before the page changes to the new URL). Is there a way to make the second function be called after this second URL has been loaded completely?
I have tried setting it to a Window setInterval() Method for an extended time period, window.onload and a couple of other methods. However, I can't seem to get anything to work. Any chance someone has a solution?
This is my first chrome extension and my first real project, so I may be missing something simple..
manifest.json
{
"name": "Inject Me",
"version": "1.0",
"manifest_version": 2,
"description": "Injecting stuff",
"homepage_url": "http://danharper.me",
"background": {
"scripts": [
"background.js"
],
"persistent": true
},
"browser_action": {
"default_title": "Inject!"
},
"permissions": [
"https://*/*",
"http://*/*",
"tabs"
]
}
inject.js
(function() {
let findUrl = () => {
let hashtag = "explore/tags/samplehashtag/";
location.replace("https://www.instagram.com/" + hashtag);
}
findUrl();
})();
background.js
// this is the background code...
// listen for our browerAction to be clicked
chrome.browserAction.onClicked.addListener(function(tab) {
// for the current tab, inject the "inject.js" file & execute it
chrome.tabs.executeScript(tab.ib, {
file: 'inject.js'
});
});
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
chrome.tabs.executeScript(tab.ib, {
file: 'inject2.js'
});
});
inject2.js
(function() {
if (window.location.href.indexOf("https://www.instagram.com/explore/tags/samplehashtag/") != -1){
let likeAndRepeat = () => {
let counter = 0;
let grabPhoto = document.querySelector('._9AhH0');
grabPhoto.click();
let likeAndSkip = function() {
let heart = document.querySelector('.glyphsSpriteHeart__outline__24__grey_9.u-__7');
let arrow = document.querySelector('a.coreSpriteRightPaginationArrow');
if (heart) {
heart.click();
counter++;
console.log(`You have liked ${counter} photographs`)
}
arrow.click();
}
setInterval(likeAndSkip, 3000);
//alert('likeAndRepeat Inserted');
};
likeAndRepeat();
}
})();
It is not clear from the question and the example, when you want to run your function. But in chrome extension there is something called Message Passing
https://developer.chrome.com/extensions/messaging
With message passing you can pass messages from one file to another, and similarly listen for messages.
So as it looks from your use case, you can listen for a particular message and then fire your method.
For example
background.js
chrome.runtime.sendMessage({message: "FIRE_SOME_METHOD"})
popup.js
chrome.runtime.onMessage.addListener(
function(request) {
if (request.message == "FIRE_SOME_METHOD")
someMethod();
});
EDIT
Also if you want to listen for the URL changes, you can simply put a listener provided as in the documentation.
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
console.log('updated tab');
});

javascript - Chrome extension: Communication between content.js and background.js on load

Edit: Modified code using https://developer.chrome.com/extensions/devtools#evaluated-scripts-to-devtools as reference. Still no luck.
I'm trying to code a chrome-extension which uses chrome.* API call and save portions of the result in a file. I want to automate everything from the loading of the page to the text file download and hence, I don't want to use the browser.onclick() event.
My current attempt has no effect.
What changes would I need to make?
https://stackoverflow.com/a/16720024
Using the above answer as reference, I attempted the following:
manifest.json
{
"name":"Test Extension",
"version":"0.0.1",
"manifest_version": 2,
"description":"Description",
"permissions":["tabs"],
"background": {
"scripts": ["background.js"]
},
"devtools_page": "devtools.html"
}
background.js
// Background page -- background.js
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
chrome.tabs.executeScript(message.tabId,
{ file: message.scriptToInject });
}
// add the listener
devToolsConnection.onMessage.addListener(devToolsListener);
devToolsConnection.onDisconnect.addListener(function() {
devToolsConnection.onMessage.removeListener(devToolsListener);
});
}
devtools.js
var backgroundPageConnection = chrome.runtime.connect({
name: "devtools-page"
});
backgroundPageConnection.onMessage.addListener(function (message) {
// Handle responses from the background page, if any
});
chrome.devtools.network.onRequestFinished.addListener(
function(request) {
chrome.runtime.sendMessage({
string: "Hi",
tabId: chrome.devtools.inspectedWindow.tabId,
scriptToInject: "content.js"
});
}
);
chrome.runtime.sendMessage({
string: "Hi",
tabId: chrome.devtools.inspectedWindow.tabId,
scriptToInject: "content.js"
});
content.js
alert("Hello");

messaging between content script and background page in a chrome extension is not working as it is supposed to be

I post the code below:
manifest.json
{
"manifest_version": 2,
"name": "Demo",
"description": "all_frames test",
"version": "1.0",
"background": {
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["*://*/*"],
"js": ["content.js"],
"all_frames": true
}],
"permissions": [
"tabs",
"*://*/*"
]
}
background.js
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
var tabStatus = changeInfo.status;
if (tabStatus == 'complete') {
function return_msg_callback() {
console.log('Got a msg from cs...')
}
chrome.tabs.sendMessage(tabId, {
text: 'hey_cs'
}, return_msg_callback);
}
});
content.js
/* Listen for messages */
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
/* If the received message has the expected format... */
if (msg.text && (msg.text == 'hey_cs')) {
console.log('Received a msg from bp...')
sendResponse('hey_bp');
}
});
Then, if I go to a site that includes multiples cross-origin iFrames, e.g., http://www.sport.es/ you would see that all the iFrames within the page receive the message from the background page but only one of them is able to response back. Is this a normal behavior?
Thanks in advance for your answer.
You send just one message with a direct callback so naturally Chrome can use this response callback just one time (it's a one-time connection to one entity, be it a page or an iframe).
Solution 1: send multiple messages to each iframe explicitly:
manifest.json, additional permissions:
"permissions": [
"webNavigation"
],
background.js
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
.............
// before Chrome 49 it was chrome.webNavigation.getAllFrames(tabId, .....
// starting with Chrome 49 tabId is passed inside an object
chrome.webNavigation.getAllFrames({tabId: tabId}, function(details) {
details.forEach(function(frame) {
chrome.tabs.sendMessage(
tabId,
{text: 'hey_cs'},
{frameId: frame.frameId},
function(response) { console.log(response) }
);
});
});
});
Solution 2: rework your background script logic so that the content script is the lead in communication and let it send the message once it's loaded.
content.js
chrome.runtime.sendMessage({text: "hey"}, function(response) {
console.log("Response: ", response);
});
background.js
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
console.log("Received %o from %o, frame", msg, sender.tab, sender.frameId);
sendResponse("Gotcha!");
});
Communicating between a content script and the background page in a Chrome extension
Content script to background page
Send info to background page
chrome.extension.sendRequest({message: contentScriptMessage});
Receive info from content script
chrome.extension.onRequest.addListener(function(request, sender) {
console.log(request.message);
});
Background page to content script
Send info to content script
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, { message: "TEST" });
});
Receive info from background page
chrome.runtime.onMessage.addListener(function(request, sender) {
console.log(request.message);
});
Instead of messaging, you can use executeScript for your purposes. While the callback's argument is rarely used (and I don't think many know how it works), it's perfect here:
chrome.tabs.executeScript(tabId, {file: "script.js"}, function(results) {
// Whichever is returned by the last executed statement of script.js
// is considered a result.
// "results" is an Array of all results - collected from all frames
})
You can make sure, for instance, that the last executed statement is something like
// script.js
/* ... */
result = { someFrameIdentifier: ..., data: ...};
// Note: you shouldn't do a "return" statement - it'll be an error,
// since it's not a function call. It just needs to evaluate to what you want.
Make sure you make script.js able to execute more than once on the same context.
For a frame identifier, you can devise your own algorithm. Perhaps a URL is enough, perhaps you can use the frame's position in the hierarchy.

Message Passing receiving end

Work algorithm of extension
For example, I am on site C, I see pageAction, click on it, script parsing needed information, then opens site A, script add all that information in textarea.
backround.js ---> c.js (signal to start parsing)
c.js ----> backround.js (message with information)
backround.js ----> a.js (that message, add in textarea) [Here I have problem]
manifest.json
{
"name": "test",
"version": "0.0.1",
"manifest_version": 2,
"description": "test",
"icons": { "16": "16.png",
"48": "48.png",
"128": "128.png"
},
"page_action" :
{
"default_icon" : "icon19.png",
"default_title" : "TEST"
},
"background": {
"page": "html/background.html"
},
"content_scripts": [
{
"matches": ["http://A_SITE"],
"js": ["js/jquery.js", "js/a.js"]
},
{
"matches": ["http://C_SITE"],
"js": ["js/jquery.js", "js/c.js"]
},
],
"minimum_chrome_version":"31.0",
"offline_enabled": true,
"permissions": ["tabs", "http://C_SITE/*", "http://A_SITE/*"]
}
c.js
$(document).ready(function(){
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.c_go == "go"){
//parsing here
chrome.runtime.sendMessage({ some obj }); // HERE I SEND MESSAGE TO BACKGROUND
}
});
});
a.js
$(document).ready(function(){
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request){
console.log(request); // NOTHING IN CONSOLE
}
});
});
background.js
function checkForValidUrl(tabId, changeInfo, tab) {
if(changeInfo.status === "loading") {
if (tab.url.indexOf('C_SITE') > -1) {
chrome.pageAction.show(tabId);
}
}
};
chrome.tabs.onUpdated.addListener(checkForValidUrl);
chrome.pageAction.onClicked.addListener(function(tab){
if (tab.url.indexOf('C_SITE') > -1){
// HERE I SEND MESSAGE TO c.js TO PARSING
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tab.id, {c_go: "go"});
});
// OPENS SITE_A
chrome.tabs.create({url: "SITE_A", "active":true}, function(tab){
// REQUEST FROM c.js
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {
// SEND REQUEST TO TAB WITH SITE_A
chrome.tabs.query({active: true, currentWindow: true}, function(tab) {
chrome.tabs.sendMessage(tab[0].id, request);
});
});
});
}
});
So, in console on SITE_A I see nothing in console. Its very strange, because I use the same code in c.js chrome.runtime.onMessage.addListener.
How to fix it? Thanks.
chrome.runtime.onMessage is an event listener so it needs to be defined before said event occurs in order to listen for it. Right now you have it sending a message background -> c.js then send a new message c.js -> background.js. This turns it into a race condition as your background page opens up the new tab and creates the event handler at the same time that c.js is trying to send a message. Instead, try to make it all one flow.
c.js
chrome.runtime.onMessage.addListener(function(request,sender,sendResponse){
if(request.c_go == "go"){
//parsing here
sendResponse({ some obj }); // HERE I SEND MESSAGE TO BACKGROUND
}
});
This changes the message c -> background to a response to the first message.
background.js
chrome.pageAction.onClicked.addListener(function(tab){
chrome.tabs.sendMessage(tab.id,{c_go:"go"},function(response){
chrome.tabs.create({url: "SITE_A", "active":true}, function(newTab){
chrome.tabs.executeScript(newTab.id, {file:"a.js"},function(){
chrome.tabs.sendMessage(newTab.id, response);
});
});
});
});
This makes is so that Site A isn't opened until you get the info from Site C.
This is what happens on page-action click:
You send message to C_SITE.
You create new tab with A_SITE.
You register an onMessage listener to receive messages from C_SITE.
You forward any messages from C_SITE to A_SITE.
Unfortunately, somewhere between 1 and 3 (before the listener is registered), C_SITE sends its message (which goes unnoticed).
You should always make sure your events are fired after the corresponding listeners are properly set-up.
See my comment above:
* You use chrome.tabs.query() unnecessarily and risk breaking your extension's behaviour (e.g. if another widnow happens to become unexpectedly active).
* You unnecessarily use $(document).ready().

Categories

Resources