How to run a chrome extension content script on current tab [duplicate] - google-chrome-extension

I'm messing around (trying to learn) how to make a chrome extension. Right now I'm just making super simple one where it counts instances of a certain word on a page. I have this part working.
What I want to do is send this information to the pop so I can use it to do some other stuff.
Here is what I have so far:
manifest.json
{
"manifest_version": 2,
"name": "WeedKiller",
"description": "Totally serious $100% legit extension",
"version": "0.1",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"permissions":[
"tabs",
"storage"
],
"browser_action": {
"default_icon": "icon.png",
"default_title": "WeedKiller",
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": [
"content.js"
],
"run_at": "document_end"
}
]
}
content.js
var elements = document.getElementsByTagName('*');
var count = 0;
function tokeCounter(){
for (var i = 0; i < elements.length; i++) {
var element = elements[i];
for (var j = 0; j < element.childNodes.length; j++) {
var node = element.childNodes[j];
if (node.nodeType === 3) {
var text = node.nodeValue;
if(text == '420'){
count++;
}
var replacedText = text.replace(/420/, '+1');
if (replacedText !== text) {
element.replaceChild(document.createTextNode(replacedText), node);
}
}
}
}
}
tokeCounter();
So what I want to happen is to send the count variable to the popup so that I can use it there.
I have looked around and found that I need to do something with chrome.runtime.sendMessage.
I have it so I add this line to the end of content.js:
chrome.runtime.sendMessage(count);
and then in background.js:
chrome.runtime.onMessage.addListener(
function(response, sender, sendResponse){
temp = response;
}
);
I'm sort of stuck here as I'm not sure how to send this information to popup and use it.

As you have properly noticed, you can't send data directly to the popup when it's closed. So, you're sending data to the background page.
Then, when you open the popup, you want the data there. So, what are the options?
Please note: this answer will give bad advice first, and then improve on it. Since OP is learning, it's important to show the thought process and the roadbumps.
First solution that comes to mind is the following: ask the background page, using Messaging again. Early warning: this will not work or work poorly
First off, establish that there can be different types of messages. Modifying your current messaging code:
// content.js
chrome.runtime.sendMessage({type: "setCount", count: count});
// background.js
chrome.runtime.onMessage.addListener(
function(message, sender, sendResponse) {
switch(message.type) {
case "setCount":
temp = message.count;
break;
default:
console.error("Unrecognised message: ", message);
}
}
);
And now, you could in theory ask that in the popup:
// popup.js
chrome.runtime.sendMessage({type: "getCount"}, function(count) {
if(typeof count == "undefined") {
// That's kind of bad
} else {
// Use count
}
});
// background.js
chrome.runtime.onMessage.addListener(
function(message, sender, sendResponse) {
switch(message.type) {
case "setCount":
temp = message.count;
break;
case "getCount":
sendResponse(temp);
break;
default:
console.error("Unrecognised message: ", message);
}
}
);
Now, what are the problems with this?
What's the lifetime of temp? You have explicitly stated "persistent": false in your manifest. As a result, the background page can be unloaded at any time, wiping state such as temp.
You could fix it with "persistent": true, but keep reading.
Which tab's count do you expect to see? temp will have the last data written to it, which may very well not be the current tab.
You could fix it with keeping tabs (see what I did there?) on which tab sent the data, e.g. by using:
// background.js
/* ... */
case "setCount":
temp[sender.tab.id] = message.count;
break;
case "getCount":
sendResponse(temp[message.id]);
break;
// popup.js
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
// tabs is a single-element array after this filtering
chrome.runtime.sendMessage({type: "getCount", id: tabs[0].id}, function(count) {
/* ... */
});
});
It's a lot of work though, isn't it? This solution works fine though for non-tab-specific data, after fixing 1.
Next improvement to consider: do we need the background page to store the result for us? After all, chrome.storage is a thing; it's a persistent storage that all extension scripts (including content scripts) can access.
This cuts the background (and Messaging) out of the picture:
// content.js
chrome.storage.local.set({count: count});
// popup.js
chrome.storage.local.get("count", function(data) {
if(typeof data.count == "undefined") {
// That's kind of bad
} else {
// Use data.count
}
});
This looks cleaner, and completely bypasses problem 1 from above, but problem 2 gets trickier. You can't directly set/read something like count[id] in the storage, you'll need to read count out, modify it and write it back. It can get slow and messy.
Add to that that content scripts are not really aware of their tab ID; you'll need to message background just to learn it. Ugh. Not pretty. Again, this is a great solution for non-tab-specific data.
Then the next question to ask: why do we even need a central location to store the (tab-specific) result? The content script's lifetime is the page's lifetime. You can ask the content script directly at any point. Including from the popup.
Wait, wait, didn't you say at the very top you can't send data to the popup? Well, yes, kinda: when you don't know if it's there listening. But if the popup asks, then it must be ready to get a response, no?
So, let's reverse the content script logic. Instead of immediately sending the data, wait and listen for requests:
chrome.runtime.onMessage.addListener(
function(message, sender, sendResponse) {
switch(message.type) {
case "getCount":
sendResponse(count);
break;
default:
console.error("Unrecognised message: ", message);
}
}
);
Then, in the popup, we need to query the tab that contains the content script. It's a different messaging function, and we have to specify the tab ID.
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {type: "getCount"}, function(count) {
/* ... */
});
});
Now that's much cleaner. Problem 2 is solved: we query the tab we want to hear from. Problem 1 seems to be solved: as long as a script counted what we need, it can answer.
Do note, as a final complication, that content scripts are not always injected when you expect them to: they only start to activate on navigation after the extension was (re)loaded. Here's an answer explaining that in great detail. It can be worked around if you wish, but for now just a code path for it:
function(count) {
if(typeof count == "undefined") {
// That's kind of bad
if(chrome.runtime.lastError) {
// We couldn't talk to the content script, probably it's not there
}
} else {
// Use count
}
}

Related

Using a Chrome extension, open a page, perform an action, open another page, do same (repeat until all pages done)

I am trying to write a Chrome extension that iterates through a list of web pages (the URLs of these pages can be hard coded into some JavaScript array in a .js), perform the following actions...
Take a screenshot
Take the resulting screenshot and either store it locally or send it (the image data) to an API
Move on to the next URL, rinse and repeat
I would like to do this without opening a new tab for each URL.
I tried all manner of things using a script that executes when the extension is called up. It appears that window.open("some URL", "_self") does not work. Using chrome.tab.create, as expected, opens a new tab each time. I was fine with this (for now), but then in the callback function, attempting to call chrome.tab.create again did not work. I then read about background scripts (service workers) and still no luck there. I was hoping in the callback function (newTab) => {...}, I could do something like this newTab.open("https://blah", some_callback) and cause the tab to be updated with the new URL and another callback (perhaps using chrome.tabs.update).
I am using manifest version 3
Thanks,
Matthew
Update: Looks like am lacking some contextual information. Seems that background JS (service workers) appear to be Node.js and, naturally, browser based JS is not. This means they would have two completely different namespaces, so would be curious as to how browser JS interacts with service worker JS (like any other server I assume)
Looks like I figured it out. First here's the manifest (version 3)...
{
"name": "Screenshots from List",
"version": "0.0.1",
"description": "Take screenshots based on a list of URLs",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": [
"desktopCapture",
"downloads",
"tabs",
"activeTab"
],
"host_permissions": ["<all_urls>"],
"action": {
"default_title": "Take Screenshots"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_script.js"]
}
]
}
The content_script.js file is empty, but appears to be needed so the extensions menu contains a clickable entry (given my current, and no doubt limited, understanding of Chrome extensions)
The background.js script pauses after the URL for the tab is set, since in my use case, JavaScript continues to render the tab after the chrome.tabs.onUpdated event has fired...
var m_Clicked = false;
var m_Index = 0;
var m_UrlList = ["URL1", "URL2"];
chrome.action.onClicked.addListener(function (tab) {
console.log("Clicked");
m_Clicked = true;
chrome.tabs.update({"url": m_UrlList[m_Index]});
});
chrome.tabs.onUpdated.addListener(async function (tabId, changeInfo, tab) {
console.log("Clicked : " + m_Clicked);
console.log(changeInfo.status);
if (m_Clicked) {
if (changeInfo.status == 'complete') {
await new Promise(r => setTimeout(r, 2000));
chrome.tabs.captureVisibleTab(null, {
format: "png",
quality: 100
}, function (data) {
chrome.downloads.download({
url: data,
filename: "pic_" + m_Index + ".png"
},
() => {
if (m_Index < m_UrlList.length) {
m_Index++;
chrome.tabs.update({"url": m_UrlList[m_Index]});
}
}
);
});
}
}
});

instagram embed doesn't work at first when dynamically appending into a page, but then works after refresh

Go to:
http://staging2.ju.blog.kylebaker.io/
click hamburger
click 'timeline'
instagram embeds show only the gray logo and don't load fully. embed.js doesn't seem to load when watching in the network tab.
Now, click refresh.
Now, everything loads. embed.js is there.
:/
You can notice that an older version on http://staging.ju.blog.kylebaker.io works fine--this seems to obviously be because it's an entirely new page load (which I want to avoid).
Some potentially relevant code this theme relies on to load this page "into" the page:
L: function(url, f, err) {
if (url == xhrUrl) {
return false;
}
xhrUrl = url;
if (xhr) {
xhr.abort();
}
xhr = $.ajax({
type: 'GET',
url: url,
timeout: 10000,
success: function(data) {
f(data);
xhrUrl = '';
},
error: function(a, b, c) {
if (b == 'abort') {
err && err()
} else {
window.location.href = url;
}
xhrUrl = '';
}
});
},
HS: function(tag, flag) {
var id = tag.data('id') || 0,
url = tag.attr('href'),
title = tag.attr('title') + " - " + $("#config-title").text();
if (!$('#preview').length || !(window.history && history.pushState)) location.href = url;
Diaspora.loading()
var state = {d: id, t: title, u: url};
Diaspora.L(url, function(data) {
if (!$(data).filter('#single').length) {
location.href = url;
return
}
switch (flag) {
case 'push':
history.pushState(state, title, url)
break;
case 'replace':
history.replaceState(state, title, url)
break;
}
document.title = title;
$('#preview').html($(data).filter('#single'))
switch (flag) {
case 'push':
Diaspora.preview()
break;
case 'replace':
window.scrollTo(0, 0)
Diaspora.loaded()
break;
}
setTimeout(function() {
Diaspora.player();
$('#top').show();
comment = $("#gitalk-container");
if (comment.data('ae') == true){
comment.click();
}
}, 0)
})
},
preview: function() {
// preview toggle
$("#preview").one('transitionend webkitTransitionEnd oTransitionEnd otransitionend MSTransitionEnd', function() {
var previewVisible = $('#preview').hasClass('show');
if (!!previewVisible) {
$('#container').hide();
}else{
$('#container').show();
}
Diaspora.loaded();
});
setTimeout(function() {
$('#preview').addClass('show');
$('#container').data('scroll', window.scrollY);
setTimeout(function() {
$('#preview').css({
'position': 'static',
'overflow-y': 'auto'
});
}, 500);
}, 0);
},
(for full file, see: https://github.com/Fechin/hexo-theme-diaspora/blob/master/source/js/diaspora.js)
I see the script tag loaded in the DOM; why isn't it loading? Feels like it must be something simple I'm missing... It's 4am and I've been working non-stop.
(please ignore the dust, this is a small side-project work-in-progress with many small things broken.)
Things I've tried:
adding the code to load embed.js manually in the page. (no change--I then see embed.js is loaded, but it doesn't have an impact on the result)
editing the URL to include "http:" before the "//insta..." url (as
recommended in some answers elsewhere for some people) (no change)
window.instgrm.Embeds.process() it seems the instgrm object no longer exists. (no change)
Seems to be related to however this is being injected via jquery, but I'm a little confused as to specifics, figured I'd ask the world and make a space for the answer to exist for the next poor soul.
Note that because of what I've tried, the answers here do not seem to be helpful or relevant, though maybe it's just me blanking at 4am.
The problem was, indeed, the things I had already addressed, I just missed some rendering details of getting changes to stick. jQuery executing and stripping the script tags seems to have been the source the problem, and calling window.instgrm.Embeds.process() at the appropriate time, after making sure that the embeds.js script was requested in the right place/at the right time, was enough to fix the issue seen above. Part of the confusion was using hexo, which uses ejs in node, which doesn't really seem to allow client-executing inline JS in ejs template files themselves, silently.

How to make tab and window active when result is found?

I want to bring the tab the extension is running on to the front of my window as soon as the match is found, even if I am currently working in a second window.
So far I have this code in my content_script.js, but it doesn't seem to work.
The commented lines were my last failed tries.
The alert gave me -1, which seems to be quite the weird tab id.
if(output == 'found') {
sendResponse({findresult: "yes"});
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
//chrome.tabs.update(sender.tab.id, {selected: true});
//chrome.tabs.update(sender.tab.id, {active: true});
alert(sender.tab.id);
});
}
I've tried some things in the background.html too and all kinds of things already posted here, all with no luck.
What do I need to do to make this work?
EDIT
manifest.json script inclusion
"background": {
"scripts": ["background.js"]
},
"content_scripts": [ {
"all_frames": false,
"js": [ "jquery.js", "content_script.js" ],
"matches": [ "http://*/*", "https://*/*" ],
"run_at": "document_idle"
}
background.js (the alert won't even show up)
alert("here");
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
your_tab_Id = sender.tab.id);
});
chrome.tabs.update(your_tab_Id,{"active":true,"highlighted":true},function (tab){
console.log("Completed updating tab .." + JSON.stringify(tab));
});
content_script.js jQuery change background (sendResponse works, but if I activate the background changing line the script stops working)
if(found === true) {
//$('td:contains('+foundItem+')').css("background", "greenyellow");
sendResponse({findresult: "yes"});
}
jsFiddle I tested the jQuery code in
EDIT 2
extension download
You can not use chrome.tabs API() from content script
References
Content Scripts
tabs API
To get tab id put your code to background.js
chrome.extension.onRequest.addListener(function(request, sender, sendResponse) {
your_tab_Id = sender.tab.id);
});
and add this to background.js page
chrome.tabs.update(your_tab_Id,{"active":true,"highlighted":true},function (tab){
console.log("Completed updating tab .." + JSON.stringify(tab));
});
Use only content script to send messages
chrome.extension.sendMessage("Awesome message");
Edit 1:
Your code do not work because of syntax error
Some point to consider:
Use onMessage instead of onRequest ( onRequest is deprecated in favour of onMessage)
All the used API's are asynchronous so make sure they are called synchronously.
Use sendMessage in content script as well ( sendRequest is deprecated in favour of sendMessage)
After applying above changes your code turns as shown here.
alert("here");
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
your_tab_Id = sender.tab.id;
chrome.tabs.update(your_tab_Id,{"active":true,"highlighted":true},function (tab){
console.log("Completed updating tab .." + JSON.stringify(tab));
});
});
Let me know if you need more information.

JS function no longer applies

Hello everyone.
I am currently trying to develop an extension for the Chrome browser and am having a problem.
My extension works, but the JS function which is integrated only works page load, or when it refresh.
But when browsing, when you return to the page or text should be amended, the JS function no longer applies: cry:
Here is an example to better explain myself;)
Function is to replace text with another text:
manifest.json:
{
"content_scripts": [ {
"js": [ "content.js", "init.js" ],
"matches": [ "http://battlelog.battlefield.com/*" ],
"run_at": "document_end"
} ],
"name": "My Extension",
"author" : "Tesla",
"version": "1.0",
"description": "DESCRIPTION COMING !",
"key": "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALuyMQ/qwguwISXI8lDNIyQQyuQHQIqLRTekDDomL/UlEv2pWvUKtbnj3yMvNsXu/4KGzKZvcDmCD1W2mEXjk8vrm7rEt3AqXsWooSuqRlWJ3vqiB5/mtA+Hac2whiUSqylC5FJH6LbWNjYAAcHyVxif83GRWfxwPVHFiyQTt5hDAgMBAAECgYEAqYwNRZOKNYSkbL1YJiUn2SxSGily47Nqkxhc8yoLqCYVQY352+AQyBpPNjkwARwjMoUR2EZR2aDiuUp3wqoQllRzLHR61jjUtg+0X5RSgQlIbB/GFVns9/7DqXRBcY5RTmMQZ9H53UrIIfZ9fdwZyQ4yOAc2rv14YnwjjZlU/gECQQD6cjUJv7Ps891z2e0lHE2XALIdpaRa4Q5Isx6GairJKlzv722zK2/ftECE1VV+rElwtEw+teCRI7K8Cawx60iHAkEAv9u/9Fz7ZM7j5bgYSkZx043K+PT8yqNTQKtwpwSRDShje8iQkzW9HjLjxlC6qc8uN0sE+H48TaHslCJ/pQntZQJBAM8sDG3NFASuUoGUQ5TQTerc23qk3EmFJHDFIzojttMD5S9hy0hMZVYTYM/BPeD0mifOLcguYd8OPbtI8RW2QR8CQBb9UQIKBkGtHNfQ+HAmAsuzyOeOC6CIc5hjMquAu5TVCx6xCMnq/Y9Zz7tavxNL9SDBB4ZzMeyng364p4zyJJUCQG2GRV0BiZfcxhT7/YV+E+t037pV/NisdXomDgCbSBpkN/gls0wI/6MkhlrU6YmfaZ/hG2QtGs7VCKRJ8fI36Nw=",
"minimum_chrome_version": "14.0"
}
content.js:
var head = document.getElementsByTagName("head")[0];
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = chrome.extension.getURL('main.js');
head.appendChild(script);
var css = document.createElement('link');
css.type = 'text/css';
css.rel = 'stylesheet';
css.href = chrome.extension.getURL('main.css');
head.appendChild(css);
main.js:
var modif = {
change: 'Text-Change'
};
var original = {
change: /Leaderboards/gm,
};
$(document).ready(function() {
$('.base-middle').each(function() {
var $p = $(this);
var html = $p.html();
$p.html(html.replace(original.change, modif.change));
});
});
The function must edit the text:
Leaderboards
in
Text-Change
on this page : http://battlelog.battlefield.com/bf3/leaderboard/
without having to refresh the page every time.
If anyone can help me?
thank you ;)
Tesla
EDIT/
I posted a response after this message
As I mentioned in the comment, your dealing with an AJAX driven page which makes things rather tricky (any advice from anyone on how to deal with an ajax site would surely be appreciated).
One thing I noticed with that page is after it gets the pages contents and renders them to the page it executes Surface.Renderer.invokeUsedComponents so you could hijack that function and then redo your mod if window.location equals the url you want to mod on.
A quick example on how to hijack that function looks like....
(function(old){
Surface.Renderer.invokeUsedComponents = function(a){
// Check window.location and if it matches then do something
alert(a);
old.apply(Surface.Renderer, arguments);
}
})(Surface.Renderer.invokeUsedComponents)
Be aware that I didnt do heaps of testing and if they ever change their api it could break your extension....
Good Luck!
EDIT
I forgot to mention that your going to need to inject that code into the page and have it communicate back to the extension.
Heres some code that should work (I didnt test it, just modded some code I had already)...
// http://code.google.com/chrome/extensions/content_scripts.html#host-page-communication
var comDiv = document.createElement('div');
comDiv.setAttribute("id", "stateChangeDiv");
document.documentElement.appendChild(comDiv);
script = function(old) {
// Create the event that the content script listens for
var stateChangeEvent = document.createEvent('Event');
stateChangeEvent.initEvent('stateChangeEvent', true, true);
// overide the function that gets called after the page is updated
Surface.Renderer.invokeUsedComponents = function(a) {
var hiddenDiv = document.getElementById('stateChangeDiv');
hiddenDiv.dispatchEvent(stateChangeEvent);
//console.debug(c);
old.apply(Surface.Renderer, arguments);
}
}
function override(fn) {
var script = document.createElement('script');
script.setAttribute("type", "application/javascript");
script.textContent = '(' + fn + ')(Surface.Renderer.invokeUsedComponents);';
document.documentElement.appendChild(script); // run the script
document.documentElement.removeChild(script); // clean up
}
override(script);
document.getElementById('stateChangeDiv').addEventListener('stateChangeEvent', function() {
// This will get executed everytime Surface.Renderer.invokeUsedComponents does on the page
alert('page updated, check your window.location and do something');
});

Chrome extension problem getting tab url

I'm not good at JS and I'm having some -I hope- stupid problem I'm not seeing on my code... if you guys could help me out, I'd really appreciate it.
My extension does some stuff with the current tab's URL. It worked ok using the onUpdate event on my background page, setting the tab's URL on a variable and then I used it on a pop-up.
The thing is that if the user starts, selecting different tabs, without updating the URLs my event won't be triggered again... so I'm now also listening to the onSelectionChanged event.
The thing is that there's no "tab" object within the onSelectionChanged event's parameters, so I cannot ask for the tab.url property.
I tried to use the chrome.tabs.getCurrent() method, but obviously I'm doing something wrong... and I reached the limit of my -very little- knowledge.
Here's the code, if you guys could take a look and point me in the right direction, I'll really appreciate it.
<script>
var tabURL = '';
var defaultURLRecognition = [ "test" ];
// Called when the url of a tab changes.
function checkForValidUrl(tabId, changeInfo, tab) {
//THIS IS WHAT'S NOT WORKING, I SUPPOSE
if (tab==undefined) {
chrome.tabs.getCurrent(function(tabAux) {
test = tabAux;
});
}
//
// If there's no URLRecognition value, I set the default one
if (localStorage["URLRecognition"]==undefined) {
localStorage["URLRecognition"] = defaultURLRecognition;
};
// Look for URLRecognition value within the tab's URL
if (tab.url.indexOf(localStorage["URLRecognition"]) > -1) {
// ... show the page action.
chrome.pageAction.show(tabId);
tabURL = tab.url;
}
};
// Listen for any changes to the URL of any tab.
chrome.tabs.onUpdated.addListener(checkForValidUrl);
// Listen for tab selection changes
chrome.tabs.onSelectionChanged.addListener(checkForValidUrl);
</script>
I would do something like this:
function checkForValidUrl(tab) {
//...
}
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab){
if(changeInfo.status == "loading") {
checkForValidUrl(tab);
}
});
chrome.tabs.onSelectionChanged.addListener(function(tabId, selectInfo){
chrome.tabs.getSelected(null, function(tab){
checkForValidUrl(tab);
});
});

Resources