Refreshing background.js with info from popup.js - Google Chrome Extension - javascript

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);
}
});

Related

Can't access chrome.extension.getBackgroundPage() when creating actual popup

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;
}
}

Passing data (tabs) from background.js to popup.js

I am currently trying to make a chrome extension that lists all of the open tabs in its popup window. With more functionality to be added later, such as closing a specific tab through the popup, opening up a new tab with a specific URL etc.
manifest.json
{
"manifest_version": 2,
"name": "List your tabs!",
"version": "1.0.0",
"description": "This extension only lists all of your tabs, for now.",
"background": {
"persistent": true,
"scripts": [
"js/background.js"
]
},
"permissions": [
"contextMenus",
"activeTab",
"tabs"
],
"browser_action": {
"default_popup": "popup.html"
}
}
background.js
const tabStorage = {};
(function() {
getTabs();
chrome.tabs.onRemoved.addListener((tab) => {
getTabs();
});
chrome.tabs.onUpdated.addListener((tab) => {
getTabs();
});
}());
function getTabs() {
console.clear();
chrome.windows.getAll({populate:true},function(windows){
windows.forEach(function(window){
window.tabs.forEach(function(tab){
//collect all of the urls here, I will just log them instead
tabStorage.tabUrl = tab.url;
console.log(tabStorage.tabUrl);
});
});
});
chrome.runtime.sendMessage({
msg: "current_tabs",
data: {
subject: "Tabs",
content: tabStorage
}
});
}
popup.js
(function() {
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.msg === "current_tabs") {
// To do something
console.log(request.data.subject)
console.log(request.data.content)
}
}
);
}());
From my understanding, since you're supposed to have listeners in background.js for any changes to your tabs. Then when those occur, you can send a message to popup.js
As you can see, for now I'm simply trying to log my tabs in the console to make sure it works, before appending it to a div or something in my popup.html. This does not work, however, because in my popup.html I'm getting the following error in the console:
popup.js:3 Uncaught TypeError: Cannot read property 'sendMessage' of undefined
so I'm... kind of understanding that I can't use onMessage in popup.js due to certain restrictions, but I also have no clue, then, on how to achieve what I'm trying to do.
Any help would be appreciated.
The Google's documentation about the background script is a bit vague. The important thing for your use case is that the popup runs only when it's shown, it doesn't run when hidden, so you don't need background.js at all, just put everything in popup.js which will run every time your popup is shown, here's your popup.html:
<script src="popup.js"></script>
The error message implies you were opening the html file directly from disk as a file:// page, but it should be opened by clicking the extension icon or via its own URL chrome-extension://id/popup.html where id is your extension's id. This happens automatically when you click the extension icon - the popup is a separate page with that URL, with its own DOM, document, window, and so on.
The popup has its own devtools, see this answer that shows how to invoke it (in Chrome it's by right-clicking inside the popup, then clicking "inspect").
Extension API is asynchronous so the callbacks run at a later point in the future, after the outer code has already completed, which is why you can't use tabStorage outside the callback like you do currently. More info: Why is my variable unaltered after I modify it inside of a function? - Asynchronous code reference.
There should be no need to enumerate all the tabs in onRemoved and onUpdated because it may be really slow when there's a hundred of tabs open. Instead you can modify your tabStorage using the parameters provided to the listeners of these events, see the documentation for details. That requires tabStorage to hold the id of each tab so it would make sense to simply keep the entire response from the API as is. Here's a simplified example:
let allTabs = [];
chrome.tabs.query({}, tabs => {
allTabs = tabs;
displayTabs();
});
function displayTabs() {
document.body.appendChild(document.createElement('ul'))
.append(...allTabs.map(createTabElement));
}
function createTabElement(tab) {
const el = document.createElement('li');
el.textContent = tab.id + ': ' + tab.url;
return el;
}

Reading specific DOM items from a website and putting the content into HTML items with a google extension

I setup this layout for an extension I am trying to build to make my work easier.
manifest.json
{
"manifest_version": 2,
"name": "Work Order Dispatcher",
"version": "0.1",
"description": "Work order dispatcher for BeHome/v12",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [{
"matches": ["*://*.v12.instantsoftware.com/*"],
//"matches": ["www"],
"js": ["content.js"]
}],
"browser_action": {
"default_title": "Work Order Dispatcher"
},
"permissions": [
"activeTab"
]
}
Background.js
// Regex-pattern to check URLs against.
// It matches URLs like
//var urlRegex = /^https?:\/\/(?:[^./?#]+\.)?v12.instantsoftware\.com/;
var urlRegex = /^https?:\/\/(?:[^./?#]+\.)?v12\.instantsoftware\.com/;
// A function to use as callback
function doStuffWithDom(domContent) {
console.log('I received the following DOM content:\n' + domContent);
}
// When the browser-action button is clicked...
chrome.browserAction.onClicked.addListener(function (tab) {
// ...check the URL of the active tab against our pattern and...
if (urlRegex.test(tab.url)) {
// ...if it matches, send a message specifying a callback too
chrome.tabs.sendMessage(tab.id, {text: 'report_back'}, doStuffWithDom);
}
});
content.js
// Listen for messages
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
// If the received message has the expected format...
if (msg.text === 'report_back') {
// Call the specified callback, passing
// the web-page's DOM content as argument
sendResponse(document.getElementsByTagName('body'));
}
});
I am trying to pull data from a text field on a website and insert it in another text field on a different website. I use the two sites side by side. In my console I get:
4background.js:8 I received the following DOM content:
undefined
I think it is only pulling the background DOM of the extension itself?
I guess the confusion comes in at would I be able to store the content of a dom item and place it into an HTML text area of my own (which I have yet to build) and then be able to send that off to my other website so I can dispatch my guys. Let me know if you need more details about what I am trying to accomplish.
Thanks in advance.

Irregular message detection

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.

Port error: Could not establish connection. Receiving end does not exist

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.

Categories