How to update tab with Background.js and THEN execute Content Script? - javascript

I would like for my Chrome extension to run like this:
User clicks on the icon and that brings up the hard-coded URL, "www.example.com". It does not open a new tab but instead updates the window. I would then like to execute the content script and be able to alert "working" on the updated page.
Here is what i've got so far:
Background.js
chrome.browserAction.onClicked.addListener(function(activeTab)
{
chrome.tabs.query({'active': true, 'lastFocusedWindow': true}, function
(tabs)
{
chrome.tabs.update({
url: "http://www.example.com/"
});
});
function test()
{
chrome.tabs.executeScript(null, {file: "myscript.js"});
}
chrome.tabs.onUpdated.addListener(function(tabid, changeinfo, tab) {
var url = tab.url;
if (url !== undefined && changeinfo.status == "complete") {
test();
}
});
});
Content script
alert('working');
The result is odd. When I click on the icon, it brings up example.com fine, however sometimes the alert works and sometimes it doesn't. Even weirder, it works more often if I double click but if I click it a bunch of times, the alerts add up and i then get many alerts all at once (I just want one).

content.js can set a global variable so you can check it to skip reinjection.
if (window[chrome.runtime.id]) {
alert('Oi! Reinjected.');
} else {
window[chrome.runtime.id] = true;
alert('Oi!');
}
// do something
Depending on what you do in the content script, you can add a message listener which will process requests from your background page instead of re-running the entire code.
background.js checks if the active tab is already navigated to the site of interest (or navigates to it) and injects the content script (comment out inject(); to skip reinjection).
The check itself is simple: inject a content script code that checks that global variable. This code runs in the same context as other content scripts for a given page (the isolated world).
const SITE_URL = 'http://www.example.com';
chrome.browserAction.onClicked.addListener(tab => {
if (tab.url === new URL(SITE_URL).href) {
checkIfInjected(tab).then(tab => {
console.log('already injected in %d, reinjecting anyway', tab.id);
inject(tab);
}).catch(inject);
} else {
updateTabAndWaitForStart(tab.id, {url: SITE_URL})
.then(inject);
}
});
function checkIfInjected(tab) {
return runContentScript(tab.id, {
code: 'window[chrome.runtime.id]',
}).then(results => {
resolve(results[0] ? tab : Promise.reject(tab));
});
}
function inject(tab) {
return runContentScript(tab.id, {
file: 'content.js',
runAt: 'document_end',
allFrames: false,
});
}
function runContentScript(tabId, options) {
return new Promise(resolve => {
chrome.tabs.executeScript(tabId, options, resolve);
});
}
// onUpdated listener waits for our tab to get an URL, detaches on success
function updateTabAndWaitForStart(tabId, options) {
return new Promise(resolve => {
chrome.tabs.update(tabId, options, newTab => {
chrome.tabs.onUpdated.addListener(
function onUpdated(updatedId, info, updatedTab) {
if (updatedId === newTab.id && info.url) {
chrome.tabs.onUpdated.removeListener(onUpdated);
resolve(updatedTab);
}
}
);
});
});
}
manifest.json should contain permissions for the site you navigate to and for the active tab.
Of course, a larger permission like <all_urls> would include these, but the advantage of an exact list is that the installation warning in the extensions web store will show only the site.
* in the site URL permission is optional (works the same as per API design).
The final / is mandatory (it's a path).
{
"name": "test",
"version": "0.0.1",
"manifest_version": 2,
"description": ".............",
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_title": "click me"
},
"permissions": ["activeTab", "http://www.example.com/*"]
}

Related

Host permission issue in a created window - Chrome Extension [duplicate]

I am struggling to get this simple f-ty working... My scenario is:
get current URL
modify it
navigate/redirect to it
execute custom JS code there
The most problems I have is with 4)
manifest.json
{
"name": "Hello, World!",
"description": "Navigate and execute custom js script",
"version": "1.0",
"manifest_version": 3,
"permissions": [
"tabs",
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {}
}
background.js
function myCustomScript() {
alert('myCustomScript test ok!');
console.log('myCustomScript test ok!');
}
chrome.action.onClicked.addListener((tab) => {
chrome.tabs.update({url: "https://example.com"}, myCustomScript);
});
The page got redirected but my js function is not executed! Do you know why and how to fix it?
P.S: this is my first time I am creating my chrome extension, maybe I am doing something wrong...
To execute custom code, use chrome.scripting API. For this scenario you'll need:
"scripting" added to "permissions", which you already have,
"https://example.com/" added to "host_permissions" in manifest.json.
Note that activeTab permission won't apply to the tab after it's navigated to a URL with a different origin because this permission only applies to the currently shown origin.
Due to a bug in Chrome, you need to wait for the URL to be set before executing the script.
The bug is fixed in Chrome 100.
chrome.action.onClicked.addListener(async tab => {
await chrome.tabs.update(tab.id, {url: "https://example.com"});
// Creating a tab needs the same workaround
// tab = await chrome.tabs.create({url: "https://example.com"});
await onTabUrlUpdated(tab.id);
const results = await chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ['content.js'],
});
// do something with results
});
function onTabUrlUpdated(tabId) {
return new Promise((resolve, reject) => {
const onUpdated = (id, info) => id === tabId && info.url && done(true);
const onRemoved = id => id === tabId && done(false);
chrome.tabs.onUpdated.addListener(onUpdated);
chrome.tabs.onRemoved.addListener(onRemoved);
function done(ok) {
chrome.tabs.onUpdated.removeListener(onUpdated);
chrome.tabs.onRemoved.removeListener(onRemoved);
(ok ? resolve : reject)();
}
});
}
P.S. alert can't be used in a service worker. Instead, you should look at devtools console of the background script or use chrome.notifications API.

CHROME-EXTENSIONS: How can I run a function for different tabs at the same time?

I am working on a project that creates a google chrome extension. I have a function. I want that if new tab is onUpdated or onActivated this function works. Also, I want it works every tab simultaneously. How can I do that? Here is my code:
chrome.tabs.onActivated.addListener((activeInfo) => {
chrome.tabs.query({ active: true }, (tabs) => {
Process()
})
})
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
chrome.tabs.query({ currentWindow: true }, (tabs) => {
Process()
})
})
This example will show how to apply same function on tab events onActivated and onActivated and to apply the function to all tabs on these events. You will want to do this in background service worker.
Create an extension manifest specifying the service worker:
{
"name": "Example",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
}
}
Next, in background.js script (the service worker), you will want to handle these events. The most basic implementation could look like this, and will call Process function on every tab, each time any tab is activated or updated:
function Process(tab) {
// now do something with each tab
console.log(`Process tab id ${tab.id}`);
}
function ProcessAllTabs() {
// do not specify query options to get
// all tabs in all windows for current user
chrome.tabs.query({},
(tabs) => tabs.map(Process));
}
// register the event listeners
chrome.tabs.onActivated.addListener(ProcessAllTabs);
chrome.tabs.onUpdated.addListener(ProcessAllTabs);
If you debug this behavior, you will notice the tab update will call the even handler twice as the tab undergoes different update statuses. You may want to be more specific, and perhaps only call Process after tab has finished updating, or have more information about which event caused Process to be called. In that case, a more detailed implementation could look like this:
// name the events
const EventsEnum = {UPDATE: 'update', ACTIVATE: 'activate'};
function Process(tab, eventName) {
// now do something with each tab:
console.log(`Process tab id ${tab.id} on ${eventName}`);
}
function ProcessAllTabs(eventName) {
// do not specify query options to get
// all tabs in all windows for current user
chrome.tabs.query({},
// pass more arguments to Process function
(tabs) => tabs.map(tab => Process(tab, eventName)));
}
// register the event listeners
chrome.tabs.onActivated.addListener(
// include event name
() => ProcessAllTabs(EventsEnum.ACTIVATE));
chrome.tabs.onUpdated.addListener((tab, changeInfo) => {
// require that tab update is complete before proceeding
if (changeInfo.status === "complete") {
ProcessAllTabs(EventsEnum.UPDATE)
}
});

Write a chrome extension to console.log the active tab URL whenever a new page is loaded

I want to write a chrome extension which records the current active tab URL every time a new site is loaded and send it to a server for further use. So far I have managed to write the following code:
manifest.json
{
"manifest_version": 2,
"name": "Currenturl",
"description": "Fetches current tab url.",
"version": "0.1",
"author": "Tarun Khare",
"browser_action": {
"default_icon": "icon.png",
"default_title": "Just observing your current url."
},
"permissions": ["tabs", "activeTab"],
"background": {
"scripts": ["content.js"],
"persistent": false
}
}
content.js
chrome.tabs.query({'active': true, 'lastFocusedWindow': true}, function (tabs) {
var url = tabs[0].url;
console.log("hello: "+url);
});
I am using background scripts since chrome.tabs doesn't work in content scripts. But this extension is not printing anything in chrome console. What is the issue?
Rename content.js to background.js since this is a background script
Use chrome.tabs.onUpdated listener
Look at the correct console: Where to read console messages from background.js?
chrome.tabs.onUpdated.addListener((tabId, change, tab) => {
if (change.url) {
console.log(change.url);
}
});
It'll report the URL changes in all tabs.
You can also limit the processing to only the active tab by adding a check for tab.active property.
i try to write code based on the information you provide.
const tabUpdatelistenerFun = (tabid, changeInfo, tab) => {
const url = changeInfo.url;
if (!url || ['chrome://', 'about://'].some(p => url.startsWith(p))) return false;
const { index, active, highlighted, windowId } = tab;
if (!active) return false;
chrome.tabs.query({ index, highlighted, windowId, lastFocusedWindow: true }, () => {
console.log(url);
})
}
chrome.tabs.onUpdated.addListener(tabUpdatelistenerFun);
i think this is what you want.
You can count and extract the URL with full detail in background.js
here is the main code from below GihHub repository:
chrome.windows.getAll({ populate: true }, function (windows) {
windows.forEach(function (window) {
window.tabs.forEach(function (tab) {
//i++
collect all of the urls here, I will just log them instead
console.log("tab.ur[![enter image description here][1]][1]l aaaaakahari 2");
console.log(tab.url);
});
});
});
There is a GitHub repository of chrome Extension below here, you can find in the background.js there are methods to count and extract the open URL, and there is and even section that console it imminently when the user open or close any tab.
https://github.com/Farbod29/extract-and-find-the-new-tab-frome-the-browser-with-chrome-extention

Chrome extension background message listener fires up twice

I got an chrome extension that works like this:
Background every few seconds checks site x for changes, if they occur background opens new tab with page Y and does some page automation (fills survey in ajax - based page). Page automation is done by content script. Content script needs to ask Background for permission to start working. It does that by sending message and waiting for reply. When content script finishes it's job it sends message to Background with information about success or not. When failure tab should be updated with new link, when succes - tab should be closed. The problem is, sometimes - not always, bacground get's messages twice. Once - when tab fails it's job, and second time just after tab is updated with new link. I wish i could know why...
Here is script:
Background.js listener creation
function create_listerner(){
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
switch(request.greeting){
case "site_opened":
if (sender.tab.id == tabId) {
sendResponse({
farewell : "go"
});
} else {
sendResponse({
farewell : "stop"
});
}
break;
case "imDoneWithSuccess":
//update tab logic
break;
case "imDoneWithFail":
//close tab logic
actNgo(url,arr);
break;
}
});
}
Listener call is made once, when user starts script from context menu: background.js
chrome.contextMenus.create({
title : "Start",
contexts : ["browser_action"],
onclick : function () {
create_listerner();
}
});
tab update looks simple: background.js
function actNgo(url,arr){
chrome.storage.local.set({
array : arr,
ndz : 1
}, function () {
chrome.tabs.update(
tabId,
{
"url" : url,
"active" : false
}, function (tab) {
console.log("tabId "+tabId);
});
});
}
content script is injected into website, site doesn't reload as is ajax based, and even if - messages cannot be send without proper conditions met
content.js
function SR(st) {
var status ;
if(st){
status = "imDoneWithSuccess";
} else {
status = "imDoneWithFail";
}
chrome.runtime.sendMessage({
greeting : status
}, function (response) {
});
}
content script should work only on tabs that bacground.js opens
function askForPermission() {
chrome.runtime.sendMessage({
greeting : "site_opened"
}, function (response) {
if (response.farewell == 'go') {
start();
} else {
console.log("bad tab");
}
});
}
again - there is no possibility for them to fire up by themselves.
Log file looks like this:
Background: tab created;
content script: askForPermission(); <-sends message asking for permission to start
Bacground: go <- allows.
content script: (content script logs, working as intended)
Background listener: imDoneWithFail;
Background update tab;
background listener: imDoneWithFail; <- shouldn't happen, doesn't look like it conten't script even started to work as it didn't ask for permission. Looks like listener is fired up twice...
Background update tab;
content script: askForPermission(); <-sends message asking for permission to start
Bacground: go <- allows.
edit: manifest
{
"name": "Test script",
"version": "0.0.78",
"manifest_version": 2,
"background": {
"scripts": ["src/background/background.js"]
},
"icons": {
"19": "icons/icon19.png"
},
"default_locale": "en",
"options_ui": {
"page": "src/options/index.html",
"chrome_style": true
},
"browser_action": {
"default_icon": "icons/icon19.png",
"default_title": "Test",
"default_popup": "src/browser_action/browser_action.html"
},
"permissions": [
"http://localhost/survey/*",
"storage",
"contextMenus",
"tabs",
"webRequest"
],
"content_scripts": [
{
"matches": [
"http://localhost/survey/*",
"https://localhost/survey/*"
],
"js": [
"js/jquery/jquery.min.js",
"src/inject/content.js"
]
}
]
}
Before someone ask - I tried to use long-live connections, but i had same issue. Why does it happen?
Ok i found a answer, the problem is with function changing url of tab.
function actNgo(url,arr){
chrome.storage.local.set({
array : arr,
ndz : 1
}, function () {
chrome.tabs.update(
tabId,
{
"url" : url,
"active" : false
}, function (tab) {
console.log("tabId "+tabId);
});
});
}
Function, when given same url as current didn't refreshed tab, and content script couldn't fire up as new... Still don't know why listener fired up messages twice, but since my change it has stopped.
I don't know if this fix will help anyone, but i fixed this by first changing tab url to blank, and then to new one.
chrome.tabs.update(
tabId,
{
"url" : "about:blank",
"active" : false
}, function (tab) {
console.log("bcgr: aktualizujStroneIObstaw: pusta: tabId "+tabId);
chrome.tabs.update(
tabId,
{
"url" : url_beta,
"active" : false
}, function (tab) {
console.log("bcgr: aktualizujStroneIObstaw: wlasciwa: tabId "+tabId);
}
);

Have chrome extension display on certain page using page action

I'm trying to make a chrome extension for the Pinterest.
I followed the examples I found from the Chrome extension sample (the one with displaying icon in the omnibox when there is a 'g' in the url) and changed the file a bit to make it display the icon when the site has "pinterest.com" in it. Here is the code:
manifest.json:
"permissions": [
"tabs",
"http://*.pinterest.com/"
]
background.js, I copied most of the code from the example online:
function showPinterestAction(tabId, ChangeInfo, tab) {
if(tab.url.indexOf('pinterest.com') > -1){
chrome.pageAction.show(tabId);
}
/* This doesn't work. tab.url return undefine to me :( */
};
chrome.tabs.onUpdated.addListener(function(tabId, change, tab) {
if (change.status == "complete") {
showPinterestAction(tabId);
}
});
chrome.tabs.onActivated.addListener(function(tabId, info) {
selectedId = tabId;
showPinterestAction(tabId);
});
// Ensure the current selected tab is set up.
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
alert(tabs[0].id);
showPinterestAction(tabs[0].id);
});
It is not displaying the icon at the right page. If I try to alert(tab.url) it gives me undefined. Can someone please tell me what's wrong with my code?
Well, you're only ever calling showPinterestAction with one parameter, tabId.
No surprises, therefore, that tab parameter is simply undefined. The signature of showPinterestAction follows the tab update callback, but you're not using it like one.
You can modify showPinterestAction to pull the data it needs:
function showPinterestAction(tabId) {
chrome.tabs.get(tabId, function(tab){
if(tab.url.indexOf('pinterest.com') > -1){
chrome.pageAction.show(tabId);
}
});
};
You also probably want to make your match pattern more general: "*://*.pinterest.com/*" should cover your use case.
Alternatively, instead of latching on to multiple tabs events, you can use declarativeContent API - it was created for this.
var rule = {
conditions: [
new chrome.declarativeContent.PageStateMatcher({
pageUrl: { hostSuffix: 'pinterest.com' }
})
],
actions: [ new chrome.declarativeContent.ShowPageAction() ]
};
chrome.runtime.onInstalled.addListener(function(details) {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
chrome.declarativeContent.onPageChanged.addRules([rule]);
});
});
In this case you will not need "heavy" permissions like "tabs" or host permissions. Your manifest only needs
"permissions": [
"declarativeContent",
"activeTab"
]
for this to work.

Categories