Remember state chrome extension - javascript

I use a chrome extension to fire two content scripts to inject css. If the user opens the page the contentscript-on.js loads (defined in my manifest.json):
manifest.json
{
"name": "tools",
"version": "1.1",
"description": "tools",
"browser_action": {
"default_icon": "icon-on.png",
"default_title": "tools"
},
"manifest_version": 2,
"content_scripts": [
{
"matches": [ "*://*/*" ],
"include_globs": [ "*://app.example.*/*" ],
"js": ["jquery-1.11.0.min.js", "contentscript-on.js"]
}
],
"background": {
"scripts": ["background.js"],
"persistent": true
},
"permissions": [
"storage",
"https://*.app.example.de/*", "tabs", "webNavigation"
]
}
background.js
function getToggle(callback) { // expects function(value){...}
chrome.storage.local.get('toggle', function(data){
if(data.toggle === undefined) {
callback(true); // default value
} else {
callback(data.toggle);
}
});
}
function setToggle(value, callback){ // expects function(){...}
chrome.storage.local.set({toggle : value}, function(){
if(chrome.runtime.lastError) {
throw Error(chrome.runtime.lastError);
} else {
callback();
}
});
}
chrome.browserAction.onClicked.addListener( function(tab) {
getToggle(function(toggle){
toggle = !toggle;
setToggle(toggle, function(){
if(toggle){
//change the icon after pushed the icon to On
chrome.browserAction.setIcon({path: "icon-on.png", tabId:tab.id});
//start the content script to hide dashboard
chrome.tabs.executeScript({file:"contentscript-on.js"});
}
else{
//change the icon after pushed the icon to Off
chrome.browserAction.setIcon({path: "icon-off.png", tabId:tab.id});
//start the content script to hide dashboard
chrome.tabs.executeScript({file:"contentscript-off.js"});
}
});
});
});
contentscript-on.js
$(document).ready(function() {
chrome.storage.local.get('toggle', function(data) {
if (data.toggle === false) {
return;
} else {
// do some css inject
}
});
});
contentscript-off.js
$(document).ready(function() {
// set css to original
});
Everything works fine, but how can I save the "state" of the icon? If the user close the browser and open it again, the last used contentscript should load.
Thank you very much for your help.

You have two methods (at least), one is "old" and one is "new".
Old: localStorage
Your extension pages share a common localStorage object you can read/write, and it is persistent through browser restarts.
Working with it is synchronous:
var toggle;
if(localStorage.toggle === undefined){
localStorage.toggle = true;
}
toggle = localStorage.toggle;
chrome.browserAction.onClicked.addListener( function(tab) {
var toggle = !toggle;
localStorage.toggle = toggle;
/* The rest of your code; at this point toggle is saved */
});
It's simple to work with, but there are downsides: localStorage context is different for content scripts, so they need to communicate via Messaging to get the values from the background script; also, complications arise if the extension is used in Incognito mode.
New: chrome.storage API
To work with the new method, you need permission "storage" in the manifest (does not generate a warning).
Also, unlike localStorage, working with it is asynchronous, i.e. you will need to use callbacks:
function getToggle(callback) { // expects function(value){...}
chrome.storage.local.get('toggle', function(data){
if(data.toggle === undefined) {
callback(true); // default value
} else {
callback(data.toggle);
}
});
}
function setToggle(value, callback){ // expects function(){...}
chrome.storage.local.set({toggle : value}, function(){
if(chrome.runtime.lastError) {
throw Error(chrome.runtime.lastError);
} else {
callback();
}
});
}
chrome.browserAction.onClicked.addListener( function(tab) {
getToggle(function(toggle){
toggle = !toggle;
setToggle(toggle, function(){
/* The rest of your code; at this point toggle is saved */
});
});
});
Asynchronous code is a bit harder to work with, but you get some advantages. Namely, content scripts can use chrome.storage directly instead of communicating with the parent, you can watch for changes with onChanged, and you can use chrome.storage.sync instead of (or together with) chrome.storage.local to propagate changes to all browsers a user is logged into.
EDIT
I'm including a full solution, since the OP made a mistake of mixing per-tab state and global state.
contentscript.js
$(document).ready(function() {
chrome.storage.local.get('toggle', function(data) {
if (data.toggle === false) {
return;
} else {
/* do some css inject */
}
});
chrome.storage.onChanged.addListener(function(changes, areaName){
if(areaName == "local" && changes.toggle) {
if(changes.toggle.newValue) {
/* do some css inject */
} else {
/* set css to original */
}
}
});
});
background.js:
/* getToggle, setToggle as above */
function setIcon(value){
var path = (value)?"icon-on.png":"icon-off.png";
chrome.browserAction.setIcon({path: path});
}
getToggle(setIcon); // Initial state
chrome.browserAction.onClicked.addListener( function(tab) {
getToggle(function(toggle){
setToggle(!toggle, function(){
setIcon(!toggle);
});
});
});
This way, you only need one content script.

Related

What's the best way to call a content scripts' function from the background script in a Firefox extension?

I want to call a function that is implemented in the content script of an extension, that gets the selected text from webpages, from a function in the background script that will be later called in a listener connected to a menu item.
Is that possible and what would be the shortest way to do it?
Here are the relevant code snippets:
manifest.json
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
]
content.js
var text = "";
function highlightedText() {
text = content.getSelection();
}
background.js
function listenerFunction() {
highlightedText();
/* Doing various stuff that have to use the text variable */
}
browser.menus.onClicked.addListener((info, tab) => {
highlightedText();
});
Obviously, the above code is not working as the "highlighted" function is now visible from the background script.
So, what's the quickest / shortest way to make the code work?
OK. I'm having to crib this from one of my own private extensions but the gist is this:
In the background script set up the menu, and assign a function to the onclick prop:
browser.menus.create({
id: 'images',
title: 'imageDownload',
contexts: ['all'],
onclick: downloadImages
}, onCreated);
Still in the same script get the current tab information, and send a message to the content script.
function getCurrentTab() {
return browser.tabs.query({ currentWindow: true, active: true });
}
async function downloadImages() {
const tabInfo = await getCurrentTab();
const [{ id: tabId }] = tabInfo;
browser.tabs.sendMessage(tabId, { trigger: 'downloadImages' });
}
The content script listens for the message:
browser.runtime.onMessage.addListener(data => {
const { trigger } = data;
if (trigger === 'downloadImages') doSomething();
});
And once the processing is done pass a new message back to the background script.
function doSomething() {
const data = [1, 2, 3];
browser.runtime.sendMessage({ trigger: 'downloadImages', data });
}
And in a separate background script I have the something like the following:
browser.runtime.onMessage.addListener(data => {
const { trigger } = data;
if (trigger === 'downloadImages') ...
});

How can I make extension disabled when the link is not matched with content_script?

"content_scripts": [
{
"matches": [
"https://www.google.com/*"
],
"js": ["contentScript.bundle.js"],
"css": ["styles.css"]
}
],
How can I make extension turned off or not clickable if its not matched with content_script link?
You can implement something like this in the background file,
Steps
Create an Array of domains which has been specified in your content script.
Add a listener on the tab selection changed and tab updated.
Checkout the currently active tab is in your array or not.
Set a flag value true false using the above check.
On extension click now, check if the flag is true then perform the task else ignore the click.
let activeExtension = false;
const contentScriptMatchArray = ['https://www.google.com', 'https://www.youtube.com'];
chrome.tabs.onActivated.addListener(handleExtensionActiveState);
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
handleExtensionActiveState(tab);
});
function handleExtensionActiveState(tabs) {
if (tabs.tabId) {
chrome.tabs.get(tabs.tabId, (tab) => {
activeExtension = checkWhiteListedOrigin(tab);
});
} else {
activeExtension = checkWhiteListedOrigin(tabs);
}
}
function checkWhiteListedOrigin(tab) {
const tabURl = new URL(tab.url);
return contentScriptMatchArray.includes(tabURl.origin);
}
chrome.browserAction.onClicked.addListener(function () {
if (activeExtension) {
console.log('Clicked');
}
});
Note
you can also change your extension icon, so your extension will look like it's actually disabled
References
https://developer.chrome.com/docs/extensions/reference/tabs/#event-onActiveChanged

How To Call Chrome Extension Function After Page Redirect?

I am working on building a Javascript (in-browser) Instagram bot. However, I ran into a problem.
If you run this script, the first function will be called and the page will be redirected to "https://www.instagram.com/explore/tags/samplehashtag/" and the second function will be called immediately after (on the previous URL before the page changes to the new URL). Is there a way to make the second function be called after this second URL has been loaded completely?
I have tried setting it to a Window setInterval() Method for an extended time period, window.onload and a couple of other methods. However, I can't seem to get anything to work. Any chance someone has a solution?
This is my first chrome extension and my first real project, so I may be missing something simple..
manifest.json
{
"name": "Inject Me",
"version": "1.0",
"manifest_version": 2,
"description": "Injecting stuff",
"homepage_url": "http://danharper.me",
"background": {
"scripts": [
"background.js"
],
"persistent": true
},
"browser_action": {
"default_title": "Inject!"
},
"permissions": [
"https://*/*",
"http://*/*",
"tabs"
]
}
inject.js
(function() {
let findUrl = () => {
let hashtag = "explore/tags/samplehashtag/";
location.replace("https://www.instagram.com/" + hashtag);
}
findUrl();
})();
background.js
// this is the background code...
// listen for our browerAction to be clicked
chrome.browserAction.onClicked.addListener(function(tab) {
// for the current tab, inject the "inject.js" file & execute it
chrome.tabs.executeScript(tab.ib, {
file: 'inject.js'
});
});
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
chrome.tabs.executeScript(tab.ib, {
file: 'inject2.js'
});
});
inject2.js
(function() {
if (window.location.href.indexOf("https://www.instagram.com/explore/tags/samplehashtag/") != -1){
let likeAndRepeat = () => {
let counter = 0;
let grabPhoto = document.querySelector('._9AhH0');
grabPhoto.click();
let likeAndSkip = function() {
let heart = document.querySelector('.glyphsSpriteHeart__outline__24__grey_9.u-__7');
let arrow = document.querySelector('a.coreSpriteRightPaginationArrow');
if (heart) {
heart.click();
counter++;
console.log(`You have liked ${counter} photographs`)
}
arrow.click();
}
setInterval(likeAndSkip, 3000);
//alert('likeAndRepeat Inserted');
};
likeAndRepeat();
}
})();
It is not clear from the question and the example, when you want to run your function. But in chrome extension there is something called Message Passing
https://developer.chrome.com/extensions/messaging
With message passing you can pass messages from one file to another, and similarly listen for messages.
So as it looks from your use case, you can listen for a particular message and then fire your method.
For example
background.js
chrome.runtime.sendMessage({message: "FIRE_SOME_METHOD"})
popup.js
chrome.runtime.onMessage.addListener(
function(request) {
if (request.message == "FIRE_SOME_METHOD")
someMethod();
});
EDIT
Also if you want to listen for the URL changes, you can simply put a listener provided as in the documentation.
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
console.log('updated tab');
});

Problem stopping CORB from blocking requests

I have a Chrome extension that scrapes some values from a webpage based on some query selectors that are provided via an API call.
Relevant portion of manifest.json:
"background": {
"scripts": ["js/background.js"],
"persistent": false
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["js/jquery.min.js"]
}
],
"permissions": [
"<all_urls>",
"storage",
"activeTab"
]
}
js/background.js:
The idea here is that if a user has entered an atsmap value on their options page, we should perform an API call.
chrome.storage.sync.get(['atsmap'], function(result) {
if (result.atsmap) {
var url = "https://myurl.com/AtsMapping.aspx?AtsCode=" +
encodeURIComponent(result.atsmap)
fetch(url).then(r => r.text()).then(text => {
console.log(text);
response = JSON.stringify(text);
chrome.storage.sync.set({"fieldmapping": response}, function() {
console.log('Fieldmapping is set to ' + response);
});
})
}
return true;
});
This portion appears to be working properly, here is the console from the background page:
In popup.js (which is included at the bottom of popup.html), I call an inject.js script after the DOM is loaded:
// DOM Ready
$(() => {
'use strict';
chrome.tabs.executeScript({file: 'js/inject.js'}, () => {
// We don't need to inject code everwhere
// for example on chrome:// URIs so we just
// catch the error and log it as a warning.
if (chrome.runtime.lastError) {
console.warn(chrome.runtime.lastError.message);
}
});
// injected code will send an event with the parsed data
chrome.runtime.onMessage.addListener(handleInjectResults);
});
And finally, in js/inject.js, I get the value of fieldmapping from storage and attempt to use it:
(function () {
'use strict';
let fieldmap;
let message;
console.log("test");
chrome.storage.sync.get(['atsmap'], function(result) {
if (result.atsmap) {
chrome.storage.sync.get(['fieldmapping'], function(result) {
console.log('Value currently is ' + result.fieldmapping);
fieldmap = JSON.parse(result.fieldmapping);
console.log(fieldmap);
// <key> : { // ID of input on popup.js
// selector: <selector> // DOM selector of value in page
// value: <value> // value to use in popup.js
// }
if(fieldmap.AtsMapping[4].atsMapNotes == 'John Smith (2)') {
message = {
txtLName: {
selector: fieldmap.AtsMapping[6].lastName,
value: null
},
When I go to a demo page that I've setup for the scraping, then click my extension icon, rather than scraping the page for the form values, I get the following in the console:
I don't understand how, on inject.js line 32, I can console.log(fieldmap); and get what appears to be the proper response, and yet on inject.js line 39, the same fieldmap is undefined.
Any suggestions would be helpful as I'm completely lost here.

How do I stop content script execution in the current tab?

I'm working on a Google Chrome Extension, and I'm encountering a bug I can't solve on my own.
It works as expected switching to Youtube's Dark Mode on a single Youtube tab, but if you're on Youtube and Ctrl/Cmd click a link (open in a new tab), content.js is triggered again and the current tab is turned white and the new tab is dark.
If you are in a dark tab, a "child" tab should automatically be dark.
manifest.json:
{
"manifest_version": 2,
"permissions": [
"https://www.youtube.com/*",
"activeTab"
],
"background": {
"scripts": ["background.js"],
"persistant": false
},
"browser_action": {
"default_title": "Engage Youtube Dark Mode."
},
"content_scripts": [
{
"matches": ["https://www.youtube.com/*"],
"js": ["content.js"]
}]
}
background.js:
//var alreadyTriggered = false;
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, {file: "clicker.js"});
});
/*
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
alreadyTriggered = false;
});*/
chrome.runtime.onMessage.addListener(function(response, sender, sendResponse) {
//if (!alreadyTriggered) {
chrome.tabs.executeScript(null, {file: "clicker.js"});
//alreadyTriggered = true;
//};
return true;
});
content.js:
var myDate = new Date();
if ((myDate.getHours() <= 7) || (myDate.getHours() >= 19))
{
var darkMode = document.body.getAttribute("dark");
if (!darkMode) {
chrome.runtime.sendMessage(window.location.href, function(result) {});
};
};
I'm guessing that I'm using activeTab incorrectly. Any help would be greatly appreciated. Thanks!
clicker.js:
stepOne();
function stepOne() {
try {
var buttons = document.querySelectorAll("button.ytd-topbar-menu-button-renderer");
buttons[0].click();
stepTwo();
}
catch(error) {
setTimeout(stepOne, 250);
}
}
function stepTwo() {
try {
buttons = document.querySelectorAll("paper-item.ytd-account-settings");
buttons[0].click();
stepThree();
}
catch(error) {
setTimeout(stepTwo, 100);
}
}
function stepThree() {
try {
buttons = document.querySelectorAll("paper-toggle-button.style-scope.ytd-account-settings");
buttons[0].click();
document.body.click();
}
catch(error) {
setTimeout(stepThree, 100);
}
}
What ended up working for me was to use a combination of:
using window.onload = doStuff();
And to make sure that the value for darkMode was null, not undefined.
Hopefully this helps someone who's been endlessly tweaking their code.

Categories

Resources