I'm working on a simple extension that, based on certain user actions in browser, sends messages to a popup script which then in turn calls functions in a background script. I'm new to developing Chrome extensions so bear with me.
Currently, I have a setup that detects user actions in-browser with a content script, sends a message to a popup script, and that calls a function in the detected background page (or so I believe, I haven't gotten alerts or logs to display anywhere from the background.js).
My question is: why aren't messages being detected when sent from the background script, and is the function in my background script being called at all?
manifest.json
{
...
"browser_action": {
"default_icon": "gamify.png",
"default_popup": "user_stats.html"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["jquery.min.js", "contentscript.js"],
"run_at": "document_end"
}
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"permissions": [
"storage"
]
}
contentscript.js
$(document).ready(function() {
$("#page-container").click(function() {
chrome.runtime.sendMessage({
action: "Load"
});
});
});
//Popup script
$(document).ready(function() {
var bg = chrome.extension.getBackgroundPage();
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
var action = request.action;
if (action == "Load") {
bg.initialize();
}
});
});
background.js
function initialize() {
chrome.runtime.sendMessage({
action: "Start"
});
chrome.storage.sync.get("initialized", function(data) {
alert("BS: Get initialized: " data);
//Do stuff here
});
}
Why are you doing this in a roundabout way?
Popup page only exists as long as it's shown; if the popup is not open, nothing will listen to your message and it will be lost. Since it is very fragile, it's not a good candidate for message routing.
So, step 1: remove the message routing from the popup. Ideologically, the background page is "always there", and handles most of the operations. You can control the display of the popup by, say, listening to chrome.storage.onChanged, or just different messages that make sense only to the popup.
However, you also have declared that the background page has "persistent" : false, i.e. it's an Event page. That means it's not always always there.
This will, by the way, cause chrome.extension.getBackgroundPage to fail from time to time if the page is unloaded.
You have two options:
Remove "persistent": false. Event pages are harder to deal with, so if you're new, you might want to skip it.
Read the Event page documentation carefully. It lists limitations you have to deal with.
Related
I'm creating a Chrome Extension, with a content-script.js, a background.js, and a popup.html. It runs a sentiment analysis on editable areas like #textarea.
To communicate between the content-script.js and the background.js, I'm using chrome.runtime.sendMessage(message), and running the analyses on background.js and saving the results to window.text. If there is a negative sentiment, I send a message back to the page to show an image of a stop sign.
Then on the popup.html (which is really a .vue file), so
mounted(){
this.bgpage = chrome.extension.getBackgroundPage()
this.text = this.bgpage.text
}
and displaying the {{text}} in the template.
It worked perfectly. But I didn't love that users would have to click the button on the browser_action to engage with the popup.
So I added this listener and function to the content-script.js:
document.addEventListener("click", clickStop)
function clickStop(event) {
let target = event.target.getAttribute('id');
if (target == "stop"){
let message = {
stop: true
}
chrome.runtime.sendMessage(message)
}
and this to the background.js
function receiver(request) {
if (request.stop == true){
window.open("popup.html", "default_title", "width=375,height=600,titlebar=no,scrollbars=yes,resizable=no,top=0,left=0");
}
and this works as well. Now I have a popup when a user clicks on the stop sign.
The problem is that it seems that the new popup window doesn't access the chrome.extension.getBackgroundPage(), which means that the {{text}} doesn't show up, like it would if the user would click on the actual browser_action button. (chrome.runtime.getBackgroundPage() crashes everything for some reason.)
I've added the permissions for background to to the manifest.json:
"permissions": [
"activeTab",
"<all_urls>",
"*://*/*",
"tabs",
"background"
],
"background": {
"scripts": ["js/background.js"],
"persistent" : true
},
"browser_action": {
"default_popup": "popup.html",
"default_title": "default_popup",
"default_icon": {
"19": "icons/stopsign.png",
"38": "icons/stopsign.png"
}
Basically I'm trying to figure out how to get the literal new popup window to work just like the browser_action.
The problem lay in a part of the code that I didn't share, because I hadn't thought it was relevant, and I was wrong.
Basically, I had two different chrome.runtime.sendMessage(message); on my content-script.js: one had an if statement on the receiver and the other did not. So every time I sent the request.stop message, the request.text was undefined and therefore caused window.text to become undefined.
Therefore, it worked on my browser-action popup, because there was no request.stop message sent, so window.text was still defined by the last message.
Once I added the second conditional statement, that would not run when the request.stop message was received.
window.text = "there is no text";
function receiver(request) {
if (request.stop == true){
window.open("popup.html", "default_title", "width=375,height=600,titlebar=no,scrollbars=yes,resizable=no,top=0,left=0");
}
//removed this
//window.text = request.text;
//added this - if statement
if (request.text){
window.text = request.text;
}
}
Every once in a while my chrome extension's background.js page freezes, i have no idea what is causing it.
When the background.js file has frozen, it no longer responds to messages from the content script, and when I try to open the background page via the extensions manager to inspect it, the window pops up but it stays blank, and no interface appears.
The only things im doing in the background page are message passing and retrieving localstorage variables.
I cant figure out what is causing this, the bug only seems to have happened since i transitioned to the new chrome.runtime api, from the chrome.extension api
Can anyone tell me what is going wrong here? or help me figure it out? Thanks!
Heres the background.js file's code in its entirety
if (!chrome.runtime) {
// Chrome 20-21
chrome.runtime = chrome.extension;
} else if(!chrome.runtime.onMessage) {
// Chrome 22-25
chrome.runtime.onMessage = chrome.extension.onMessage;
chrome.runtime.sendMessage = chrome.extension.sendMessage;
}
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
if (request.method == "getLocalStorage")
sendResponse({data: localStorage[request.key]}); // decodeURIComponent
else if (request.method == "setLocalStorage")
sendResponse({data: localStorage[request.key]=request.value});
else
sendResponse({}); // send empty response
});
Is it possible a deadlock situation is occurring that is freezing the page? It doesnt cause the CPU to go mad, so im guessing its not an endless loop.
Update
here is the manifest.json as requested
{
"manifest_version": 2,
"content_scripts": [ {
"exclude_globs": [ "http://*.facebook.com/ajax/*", "https://*.facebook.com/ajax/*" , "http://www.facebook.com/ai.php?*", "https://www.facebook.com/ai.php?*", "http://www.facebook.com/ajax/*", "https://www.facebook.com/ajax/*"],
"include_globs": [ "http://*.facebook.com/*", "https://*.facebook.com/*" ],
"js": [ "script.js" ],
"matches": [ "http://*.facebook.com/*", "https://*.facebook.com/*" ],
"run_at": "document_start"
} ],
"converted_from_user_script": true,
"background": {"scripts": ["background.js"],
"persistent": false},
"icons": {
"128": "ET-128x128.png",
"48": "ET-48x48.png"
},
"key": "xxxxxxxxxxxxxxxxxxxx",
"name": "Extension Test",
"short_name": "ET",
"description": "ET Does this and that, but doesnt phone home",
"version": "999",
"homepage_url": "http://www.etphonehome.com"
}
Only disabling and re-enabling the extension get it to start working again, once the background page has frozen
Below is a screenshot of the frozen background page inspection window:
The localStorage API is problematic because in chrome it is a synchronous API to an inherently asynchronous operation (called from a renderer process, which must then communicate with the browser process that reads from / writes to a backing store in the filesystem and possibly replies back to the renderer process). While it should not in theory be possible to cause deadlocks from webpage or extension code, it's possible there are bugs in chrome's implementation.
One thing you might try is switching from localStorage to chrome.storage.local. It is a little more work to use since it has an asynchronous API, but does not suffer from the same implementation complexity as localStorage.
E.g.
sendResponse({data: localStorage[request.key]});
becomes
chrome.storage.local.get(request.key, function(storageResult) {
sendResponse(storageResult[request.key]);
});
I'm working on some code that takes an input from a text box in a browser popup and then relays that input to the background.js in order to filter a webpage using that input.
If I hard code the filtering on background.js at the start then it works (because background.js runs once at the start), but if I put the filtering inside a function that receives the input from the text box in popup.js it doesn't work.
popup.js
$(document).ready(function(){
$('#zim').click(function(){
// Get User Input in Text Box
var author = document.getElementById('author').value;
// Pass author variable to background.js
chrome.extension.getBackgroundPage().StartFiltering(author);
});
});
background.js
function StartFiltering(person){
console.log("Starting Filtering");
console.log("person: " +person);
$( "a:contains('" + person + "')" ).closest('.post-wrapper.js_post-wrapper.wide.postlist-dense').remove();
$( ".text-upper:contains('" + person + "')" ).closest('.post-wrapper.js_post-wrapper.wide.postlist-dense').remove();
$( "a:contains('" + person + "')" ).closest('.post-wrapper.js_post-wrapper.postlist-dense').remove();
};
manifest
{
"name": "Filter",
"description": "Filter out authors on homepage",
"version": "2.0",
"permissions": [
"activeTab"
],
"background": {
"scripts": ["jquery.js","background.js"],
"persistent": false
},
"icons": {
"128": "128.png"
},
"browser_action": {
"default_title": "Filter",
"default_icon": "filter.png",
"default_popup": "popup.html"
},
"manifest_version": 2,
"content_scripts": [
{
"js": ["jquery.js","background.js"],
"matches": [ "http://example.com/*"]
}
]
}
On background.js if I put the 3 lines of jQuery outside the function, and hard code in the "person" variable and reload the extension then it will filter the website correctly. StartFiltering definitely runs, it definitely gets the "author" input from the user, but I think because background.js is only run at the start, it doesn't know to update the file? I'm not sure! Fairly new to JS and coding in general!
Have searched around on here but I can't find anyone with the same problem! Thanks in advance for any help!
EDIT - SOLVED!
So here's how I got this working...
After ExpertSystem pointed out that I was using background.js twice I cleaned up my manifest and file system so that I had 4 files: background.js, content.js, popup.js and the manifest. In my content_scripts section of the manifest I had jquery.js and content.js instead of background.js as before.
I had popup.js send a message to background.js whenever a user entered a value in the popup text box, this was done very simply as:
popup.js
chrome.runtime.sendMessage({type: "new author", author:author});
I had background.js listen in for the message, then if the message type matched the one sent from popup.js then it would store the value of the blocked author in an array (because eventually I planned to keep a list of authors to filter), and send the array as a message:
background.js
chrome.runtime.onMessage.addListener(
function(request,sender,sendResponse) {
if(request.type == "new author") {
blocked.push(request.author)
}
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
chrome.tabs.sendMessage(tabs[0].id,{filter:"author",blocked:blocked})
});
});
Then I had content.js listen in for the message:
content.js
chrome.runtime.onMessage.addListener(function(msg,sender){
if (msg.filter == "author"){
for (var i=0;i<msg.blocked.length;i++){
startFiltering(msg.blocked[i]);
}
}
});
Still needs tweaking but I have all 3 pages communicating now!
The problem
You are using background.js both as your background-page (actually event-page) and as your content-script. So, when you visit http://example.com/* there are in fact two separate instances of background.js: One injected into the webpage and one in your generated background-page. (By the way, you can access your background-page at chrome://extensions/ once you enable the developer mode.)
The solution
Don't use the same file as background-page and content script.
From popup.html pass a message to the content script injected into the webpage.
Have the content script do its magic (a.k.a. filtering).
Example (~ untested code alert ~):
// In popup
var author = ...;
chrome.tabs.getCurrent(function(currentTab) {
chrome.tabs.sendMessage(currentTab.id, {
action: 'filter',
person: author
});
});
.
// In content script
function filter(person) {
$('a:contains(' + person + ')').
closest('.post-wrapper.js_post-wrapper.wide.postlist-dense').
remove();
...
}
chrome.runtime.onMessage.addListener(function(msg) {
if ((msg.action === 'filter') && msg.person) {
filter(msg.person);
}
});
I'm toying around with Chrome trying to create my first extension. Basically, I want to create a script that does some DOM manipulation on a particular domain. Furthermore, I want the user to be able to toggle the script through an icon displayed in the address bar, when visiting that particular domain.
So far, I've got this manifest.json:
{
"manifest_version": 2,
"name": "Ekstrafri",
"description": "Removes annoying boxes for paid articles.",
"version": "1.0",
"page_action": {
"default_title": "Foobar"
},
"content_scripts": [
{
"matches": ["http://ekstrabladet.dk/*"],
"js": ["jquery.min.js", "cleaner.js"],
"run_at": "document_end"
}
]
}
cleaner.js contains a couple of jQuery DOM selectors that removes some stuff.
The current setup works, but the context script is injected all the time. I want the user to be able to toggle, which should trigger a confirmation prompt in which the user accepts or rejects a page reload.
Anyway, page_action doesn't seem to display any icon. According to the documentation, it should display an icon in the address bar.
I have two questions:
How do I display this page_action icon on the matched content?
How do I bind an event to that icon?
One thing you could do here is get and set a variable in a background.js page using the message passing framework. Essentially when the content-script runs you can contact the background script to check the state of a boolean variable. You can then determine whether or not to execute the rest of the script.
chrome.extension.sendMessage({ cmd: "runScript" }, function (response) {
if (response == true) {
//Run the rest of your script inside here...
}
});
You would also use this initial call to bind some UI on the page so you can toggle this state (i.e. switch the content script on/off). In this example I'm using a checkbox.
$("chkOnOff").click(function(){
var scriptOn = ($(this).is(":checked");
chrome.extension.sendMessage({ cmd: "updateRunScriptState", data: { "scriptOn" : scriptOn } }, function (response) {
//Update confirmed
});
});
The background.js page would look something like this:
//Global variable in background.js
var scriptOn = true;
chrome.extension.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.cmd == "runScript") {
sendResponse(scriptOn);
}
if (request.cmd == "updateRunScriptState") {
//here we can update the state
scriptOn = request.data.scriptOn;
}
});
And don't forget to register the background.js page in the manifest.
..
"background": {
"scripts" : ["background.js"]
},
..
I've been googling around extensively trying to remedy this problem but can't seem to find a solution. I'm trying to do the simple task of setting up a listener and sender in my Chrome extension.
My manifest
{
"manifest_version": 2,
"name": "my app",
"description": "text",
"version": "0.1",
"background":{
"scripts":["background.js"]
},
"content_scripts": [
{
// http://developer.chrome.com/extensions/match_patterns.html
"matches": ["http://myurl.com/*"],
"js": ["jquery-1.9.1.min.js", "myapp.js"],
"all_frames": true
}
],
"browser_action": {
"default_icon": "/icons/icon-mini.png",
"default_popup": "popup.html"
}
}
In my background JS
chrome.tabs.getSelected(null, function(tab) {
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) {
console.log(response.farewell);
});
});
In my popup.js (rendered by coffeescript, please forgive the sort of strange syntax)
(function() {
$(function() {});
chrome.extension.onMessage.addListener(function(request, sender, sendResponse) {
if (console.log(sender.tab)) {
"from a content script:" + sender.tab.url;
} else {
"from the extension";
}
if (request.greeting === "hello") {
return sendResponse({
farewell: "goodbye"
});
}
});
}).call(this);
In my myapp.js
chrome.extension.sendMessage({
greeting: "hello"
}, function(response) {
return console.log(response.farewell);
});
I've followed the tutorial. Not sure why this isn't working. I'm pretty decent with JS, very unclear as to why this is behaving strangely. Any help would be hugely appreciated!
There is more than one problem with this code so let me break it down.
From what I see you are trying to send a message from your content script to your popup and there is a background page not doing anything.
Problem #1
The code in the popup.js, besides being strangely convoluted, is not a background page. It only runs when the popup is open, so it will not be able to listen for the message.
Problem #2
The code in the background page is using the depreciated getSelected method to send a message to the content script. The content script has no listener.
The result of these two things is this:
Background page -> content script (no listener)
Content Script -> extension pages (no listener)
I suggest making your background page the hub of your communications. If you need to communicate between your popup and content script make it popup -> content script and use sendResponse() to reply.
Edit: Here is an example of the message passing you would want. Just replace with your variables.
Content Script
...
//get all of your info ready here
chrome.extension.onMessage.addListener(function(message,sender,sendResponse){
//this will fire when asked for info by the popup
sendResponse(arrayWithAllTheInfoInIt);
});
Popup
...
chrome.tabs.query({'active': true,'currentWindow':true},function(tab){
//Be aware 'tab' is an array of tabs even though it only has 1 tab in it
chrome.tabs.sendMessage(tab[0].id,"stuff", function(response){
//response will be the arrayWithAllTheInfoInIt that we sent back
//you can do whatever you want with it here
//I will just output it in console
console.log(JSON.stringify(response));
});
});
I had a similar problem in a background page and my solution was to ensure that the tab had completed loading before trying to send it a message.
If the tab has not fully loaded, the content script will not have started and will not be waiting for messages yet.
Here's some code:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if (changeInfo.status === 'complete') {
// can send message to this tab now as it has finished loading
}
}
So if you want to send a message to the active tab, you can make sure it has completed loading first.