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

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

Related

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

How can javascript be executed in background without opening popup.html in Chrome extension?

I'm developing an extension for Google Chrome that requires a Firebase database. It works fine, "popup.html" is currently open. But when it closes, my background.js stops working.
I need a solution that will allow background.js to work in the background, even when the popup.html is closed, or the browser is minimized.
Here is my background.js:
var db = firebase.database().ref();
var clipText;
setInterval(function() {
document.addEventListener('paste', function(event) {
clipText = event.clipboardData.getData('Text');
if (clipText != null) {
db.child('data').set(clipText);
} else {
console.log('Cliptext is null...');
}
});
document.execCommand('paste');
}, 3000)
As you see, it uses "setInterval" function to do some actions in background every 3 seconds.
Based on your comment you are declaring your background.js in the content script.
You need to declare it inside your manifest.json like this for example:
"background": {
"persistent": true,
"scripts": ["lib/jquery-3.1.1.min.js", "background.js"]
},
You can read about Chrome Extensions architecture here.

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.

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

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

Toggle injection of context_script

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"]
},
..

Categories