I have a Chrome extension (content script) with a popup window. When the user clicks a "Start" button in the popup window, I'd like a new tab to open to a url (say www.test.com), and for the content script to be injected into that tab. Not just executed once, but injected so that it will work on (www.test.com/*) on that same tab. Not in other tabs - just that one.
Here's what I have now:
chrome.tabs.create({
'url': 'http://test.com/shop/new'
}, function(tab) {
chrome.tabs.executeScript(tab.id, {
'file': 'script.js'
});
});
But, chrome.tabs.executeScript is being used, which only executes the script once. The script redirects the page to 'http://test.com/shop/new/xxx', but since the script is only executed once, it stops working when the page changes. Again - how can I make it so that the script is injected into all 'http://test.com/shop/*' pages in that tab?
A good idea is to make a script that is always injected into http://test.com/shop/* (via manifest):
"content_scripts" : [
{
matches: ["http://test.com/shop/*"],
js: ["script.js"]
}
],
Then, in the script, ask the background page if it should be active for this ID:
// script.js
chrome.runtime.sendMessage({shouldIRun : true}, function(response){
if(response) {
// Actually do stuff
}
});
And in the background script, keep a record of tabs that you want it to apply to:
// Background script
var activeTabs = {}; // Slightly more difficult with event pages
// At some point when you enable it, e.g. in a browserAction.onClicked listener
activeTabs[tabId] = true;
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.shouldIRun) {
// Double negation to ensure true/false
sendResponse(!!activeTabs[sender.tab.id]);
}
});
// It's a good idea to clear the stray entries
chrome.tabs.onRemoved.addListener(function(tabId, removeInfo) {
delete activeTabs[tabId];
});
// Sometimes this can also happen
chrome.tabs.onReplaced.addListener(function(addedTabId, removedTabId) {
if(!!activeTabs[removedTabId]) activeTabs[addedTabId] = true;
delete activeTabs[removedTabId];
});
Related
As I understand from Manage Events with Background Scripts and Migrate to Event Driven Background Scripts background script should be activated when events triggered.
background.js
chrome.runtime.onMessage.addListener((message, sender, reply) => {
const json = message.data;
// some code
reply({ result: true })
return true;
});
popup.js
chrome.runtime.sendMessage({ data: [<ArrayWithData>] },
function (response) {
logger.log(response);
}
);
Everything works well, but only in case of active background.
Why background not become active? Can someone explain what is my mistake?
Or how can I activate and execute background.js on click from popup.js?
I know that if I change persistence: true in manifest.json or just remove it, everything will works fine. But I want to keep persistence false and trigger background.js when needed.
You missed this part in the documentation that explains how a background script should be activated from popup.js. After retrieving the background page object, you just need to call any function or even access a field.
I always put this at the top of my popup.js:
// Initialize background page
chrome.runtime.getBackgroundPage(function(backgroundPage) {
console = backgroundPage.console;
})
That way I can also view console logs from the popup together with logs from the background view
I have an extension that injects content scripts when the tab is updated.
Main script:
chrome.browserAction.onClicked.addListener(function(tab) {
"use strict";
const sendScriptToPage = function(tabId, changeInfo, tab) {
if (changeInfo.status === "complete" && tab && tab.url && tab.url.indexOf("http") === 0) {
console.log ("executeScript ");
chrome.tabs.executeScript(tabId, {
file: "content.js", allFrames: true
});
}
};
chrome.tabs.onUpdated.addListener(sendScriptToPage);
});
Content script:
const Content = (function() {
"use strict";
console.log("Content");
const makeRandomColor = function(){
let c = '';
while (c.length < 6) {
c += (Math.random()).toString(16).substr(-6).substr(-1)
}
return '#'+c;
};
document.body.style.backgroundColor = makeRandomColor();
}
)();
It works fine when I reload a tab. However, when a tab is reloaded dynamically, the content script gets reloaded although it is already loaded in the tab. This shows in the log since const can't be re-declared.
Should not content scripts be unloaded when an update occurs? How can I know if a content script is already loaded or not?
A URL that shows this behaviour:
http://www.prisjakt.nu/produkt.php?p=391945#rparams=ss=android
Typing something in the search field triggers the onUpdated event handler, but the content script is already in the page.
A test extension:
https://github.com/hawk-lord/chrome-test
A navigation would wipe the content scripts, but onUpdated can be triggered by many things.
The first that comes to mind is an iframe being loaded. The main page doesn't navigate but you do inject indiscriminately.
Many approaches are possible:
Make the content script more robust by checking for Content being defined before you do anything.
Listen to a different event, for example, various webNavigation API events. They will uniquely identify the frame they refer to.
Don't listen to events - always inject, and change your triggering logic (for instance, keep a flag in chrome.storage).
I'm trying to do something like adblock does. Adblock counts number of "ads" and update badge value. For now, I tried to do something with 'background pages', but they are run only one time and badge value is the same for all tabs. I can't use browser action popup.html, because it triggers only after the click.
So I need something which takes current tab, is able to read current DOM of tab and after all update badge value. But also after I click on different tab I need to compute new badge value.
Thanks in advance
The badge text is stored for each tab independently provided you specify tabId parameter, you don't have to update it manually after the user switches tabs if you already have set the value.
So if your extension processes the pages immediately after loading, call chrome.browserAction.setBadgeText once. You can do it e.g. by sending a message from your content script to your background/event page which will invoke setBadgeText with the sender tab's id (this parameter is what makes the text unique to a tab).
content script:
chrome.runtime.sendMessage({badgeText: "123"});
background/event script:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.badgeText != null) {
chrome.browserAction.setBadgeText({
tabId: sender.tab.id,
text: message.badgeText,
}, () => chrome.runtime.lastError); // ignore errors due to closed/prerendered tabs
}
});
You can listen to the Chrome tab events in your background/event page. The following code helped me to solve the same problem:
// fires when tab is updated
chrome.tabs.onUpdated.addListener(updateBadge);
// fires when active tab changes
chrome.tabs.onActivated.addListener(updateBadge);
function updateBadge() {
// get active tab on current window
chrome.tabs.query({active: true, currentWindow: true}, function(arrayOfTabs) {
// the return value is an array
var activeTab = arrayOfTabs[0];
if (!activeTab) return;
// compute number for badge for current tab's url
var count = getCount(activeTab.url);
chrome.browserAction.setBadgeText({
text: count.toString()
});
});
}
function getCount(currentUrl) {
// your logic, e.g., return 42
}
You can write an "onActiveChanged" listener and call your updateBadge function and pass the tabId. Hope it helped.
chrome.tabs.onActiveChanged.addListener(function (tabId, changeInfo, tab) {
updateBadge(tabId);
});
function updateBadge(tabId) {
...
}
So I'm just trying to get a response every time the extension button is clicked. So like with AdBlock how this comes down
But instead I'm just trying to do a console.log() every time the button is clicked without any visible popups.
I've tried this so far
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
switch (request.directive) {
case "popup-click":
// execute the content script
chrome.tabs.executeScript(null, { // defaults to the current tab
file: "real.js", // script to inject into page and run in sandbox
allFrames: true // This injects script into iframes in the page and doesn't work before 4.0.266.0.
});
sendResponse({}); // sending back empty response to sender
break;
default:
// helps debug when request directive doesn't match
alert("Unmatched request of '" + request + "' from script to background.js from " + sender);
}
}
);
Then my real.js
console.log("Yo");
But sadly I only get a Yo when it launches. Any ideas?
If you don't have a popup (nothing shows when you click the button), then there is an event that will fire when the button is clicked:
chrome.browserAction.onClicked
Fired when a browser action icon is clicked. This event will not fire if the browser action has a popup.
To use:
// In your background script
chrome.browserAction.onClicked.addListener( function() {
// Do stuff
});
If, however, you do have a popup, then, as the docs mention, this event will not fire. Then your code is more appropriate: you just need to send a message from the popup and catch it in the background script whenever it is opened. See a full example in this answer.
I did a simple auto form filler, by sending info from a created html to the background and then the content script, so the injected script can change the info on the form.
I know the content script run once the page is load. I want to know if I can run the content script again, without the need of reloading the page.
I got sendRequest function in the content script, that I use to make sure it gets the info, only when the page is ready. It then add the info to the form, and wait for me to send it.
In the content script, I added a onRequest and it works (it get the info). but, I don't see the changes on the form, unless I am realoding the page.
I want to know if it is possible to do and if it does what subjects should I learn to implent this.
I am new to chrome extentions and I am still learning :)
in 1 of the pages, I use jQuery, so an answer with jQuery would be good too.
i found out that if we create a chrome.tabs.sendRequest from background we can use chrome.extestion.onRequest from content script and it will execute every time becuse they both run allmost in the same time.
so i did from background:
chrome.tabs.query({}, function (tabs) {
for (var i = 0; i < tabs.length; i++) {
chrome.tabs.sendRequest(tabs[i].id, {...requests u want to send }, function (response) {
});
}
});
from content script:
chrome.extension.onRequest.addListener(function (request, sender, sendRespons) {
//get requested info here
//call functions.
sendResponse({}); //send info back to background page.
});
form's target could be an iframe which would avoid page reload. not sure how useful it'd be.
The correct way to execute a content script again is by using the chrome.tabs.executeScript method. It receives two arguments. The first argument is the tabId, which can be obtained in many ways, such as one of the chrome.tabs events. Use null to execute the content script in the currently selected tab (caution: this may also be an active dev tools window!).
Examples:
// Reloads the current tab
chrome.tabs.executeScript(null, {code:'location.reload();'});
// Executes contentscript.js in the current tab
chrome.tabs.executeScript(null, {file:'contentscript.js'});
// Executes contentscript.js in all frames in the current tab
chrome.tabs.executeScript(null, {file:'contentscript.js', allFrames: true});
// Receives message from content script, and execute a content script:
chrome.extension.onMessage.addListener(function(details) {
if (details.message === 'load a content script') {
chrome.tabs.executeScript(details.sender.tab.id, {file: 'a_script.js'});
}
});
// The previous one is activated from a content script, as follows:
chrome.extension.sendMessage('load a content script');
(onMessage and sendMessage have to be used instead of onRequest and sendRequest, since Chrome 20)