Problem stopping CORB from blocking requests - javascript

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.

Related

Creating and accessing global variable in google chrome extension

All of the information I can find on this is pretty old. Like the title says I am trying to make a global variable in one script and access it from another. The purpose of the extension is to search for a class named "page-title" and then return the innerHTML of that HTML element. Once I get the code working I will specify the URL I want the extension to run on so it's not constantly running.
After a couple iterations trying to accomplish this in different ways I followed the method explained in this answer but my needs have different requirements and I am receiving the error "Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist." tied to the popup.html.
I tried the Unchecked runtime error solution found here but it's been awhile (~ 7 years) since I've dived into any coding and I'm not sure I implemented it correctly.
I've also tried to pass the value between JS documents is the HTML injection method, but without overriding security defaults in the manifest that doesn't really work. It also seemed super bootstrappy and I wanted to pass the information in a more conventional way. I tried creating a global variable by simply declaring the variable outside of a function/class/if statement and loading that .js file first, but that was unsuccessful as well.
Manifest
"name": "P.P. to Sharepoint",
"version": "1.0.0",
"description": "Open P.P. client folder in sharepoint",
"manifest_version": 3,
"author": "Zach Morris",
"action":{
"default_popup": "popup.html",
"default_title": "Open Sharepoint Folder"
},
"background": {
"service_worker": "background.js"
},
"permissions": [
"activeTab",
"tabs",
"scripting",
"notifications"
],
"content_scripts": [{
"js": ["contentScript.js"],
"matches": ["<all_urls>"]
}]
}
popup.html
My popup.html is super simple and really just has a button to press. I included all the .js files in the order I thought necessary
<script src="globalVariable.js"></script>
<script src="contentScript.js"></script>
<script src="popup.js"></script>
<script src="script.js"></script>
<script src="background.js"></script>
globalVariable.js
This one is straight forward. I need to pull the client's name out of the HTML of the page then use it in an API call when I click the button in popup.js This initializes the variable and uses it as place holder.
var clientInfo = {
name: 'test name'
};
ContentScript.js
I only want to run this if importScripts is not undefined. So I threw it in the if statement. Then I make sure I pulled a client name from the page. If not I throw an error message saying no client was found.
if( 'function' === typeof importScripts) {
importScripts('globalVariable.js');
addEventListener('message', onMessage);
function onMessage(e) {
if(b[0]) {
clientInfo.name = b[0].innerHTML;
alert(clientInfo.name + ' was assigned!');
} else {
alert('There is no client on this screen ' + 'b[0] is ' + b[0] + " clientInfo = " + clientInfo.name);
};
};
} else {
console.log("Your stupid code didn't work. ");
}
popup.js
This one pulls up the globalVariable.js to use the clientInfo. and makes a call to the button in background.js
if( 'function' === typeof importScripts) {
importScripts('globalVariable.js');
addEventListener('message', onMessage);
function onMessage(e) {
const text = clientInfo.name;
const notify = document.getElementById( 'myButton' );
notify.addEventListener( 'click', () => {
chrome.runtime.sendMessage( '', {
type: 'notification',
message: text });
} );
}
}
background.js
Same thing here. I import the globalVariable script to use the global variable. The notification will eventually be replaced with the API call when the rest of the code is working properly. I probably don't need to import the script here to access the variable because I can mass it with the event listener in popup.js, but I put it in here out of desperation.
if( 'function' === typeof importScripts) {
importScripts('globalVariable.js');
addEventListener('message', onMessage);
function onMessage(e) {
// do some work here
chrome.runtime.onMessage.addListener( data => {
if ( data.type === 'notification' ) {
chrome.notifications.create(
'',
{
type: 'basic',
title: 'Notify!',
message: data.message || 'Notify!',
iconUrl: 'notify.png',
}
);
console.log("sent notification");
};
});
}
}
You can have the popup.js listen for a button click and content.js handle all the logic of finding the correct element.
popup.js
document.querySelector('#btn').addEventListener('click', () => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) =>
chrome.tabs.sendMessage(tabs[0].id, { command: 'getClientName' })
);
});
content.js
chrome.runtime.onMessage.addListener((msg, sender, response) => {
if (msg.command === 'getClientName')
findClientName(document.querySelectorAll('h3.page-title'));
});
Example of findClientName function:
const findClientName = async (element) => {
let clientName;
if (element.length > 0) {
element.length === 1
? (clientName = setClientName(element[0]))
: handleMultipleElements(element);
} else {
handleNoClientNameFound();
}
clientName ? await makeAPIRequest(clientName) : null;
};
Try this method instead maybe?
{
var x = 2;
}
so:
{
var clientInfo = {
name: 'test name'
};
}
Not very good at this language, so I thought maybe you're missing the brackets?

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 to send data from background script to popup script?

I'm well aware that there are older questions regarding this issue, but I havent't found a solution from them.
My extension is supposed to detect a copy event in contentScript.js and pass the information, that event has been detected to oncopy.js. After that oncopy.js is supposed to copy users clipboard contents and pass them to popup.js, where the content is stored using Googles storage API, and set to the input fields value in popup.html.
The copy detection works perfectly, but I don't know what to do after that. This is my first extension, so I'm still trying to get hang of things.
Here are my manifest.jsons relevant parts:
"permissions": [
"activeTab",
"storage",
"clipboardRead"
],
"background": {
"scripts": ["oncopy.js"],
"persistent": false
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["contentScript.js"]
}
]
contentScript.js:
// Fires when copy event is detected
document.addEventListener("copy", () => {
chrome.runtime.sendMessage({event: "copy"}, msg => console.log(msg))
})
oncopy.js e.g. the background script:
console.log("oncopy.js background scipt is running...");
// When copy event is detected and message of it is received, this starts to run
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
chrome.runtime.sendMessage({ event: "new clipboard" }, () => {
// This is supposed to get the clipboard contents from user
bg = chrome.extension.getBackgroundPage();
bg.document.body.innerHTML = "";
var helperdiv = bg.document.createElement("div");
document.body.appendChild(helperdiv);
helperdiv.contentEditable = true;
var range = document.createRange();
range.selectNode(helperdiv);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
helperdiv.focus();
bg.document.execCommand("Paste");
var clipboardContents = helperdiv.innerHTML;
});
sendResponse("Message has been processed by background page");
});
popup.js:
// This is supposed to set all of the clipboards to input fields values
document.body.onload = () => {
chrome.storage.sync.get("clipboards", (clipboards) => {
if (!chrome.runtime.error) {
document.getElementById("clipboard1").value = clipboards[0];
}
});
};
// The function that gets clipboard contents in oncopy.js is supposed to pass the contents here
// addClipboard is supposed to handle multiple clipboards, but for the sake of simplicity I'm using one as an example
function addClipboard(clipboard) {
chrome.storage.get("clipboards", (clipboards) => {
clipboards[0] = clipboard;
chrome.storage.sync.set({'clipboards': clipboards}, () => {
message('Clipboard saved');
});
document.getElementById("clipboard1").value = clipboards[0];
});
}
and finally the popup.html:
<h2>Clipboards</h2>
<form>
<input type="text" id="clipboard1" value="Empty" readonly>
</form>

javascript - Chrome extension: Communication between content.js and background.js on load

Edit: Modified code using https://developer.chrome.com/extensions/devtools#evaluated-scripts-to-devtools as reference. Still no luck.
I'm trying to code a chrome-extension which uses chrome.* API call and save portions of the result in a file. I want to automate everything from the loading of the page to the text file download and hence, I don't want to use the browser.onclick() event.
My current attempt has no effect.
What changes would I need to make?
https://stackoverflow.com/a/16720024
Using the above answer as reference, I attempted the following:
manifest.json
{
"name":"Test Extension",
"version":"0.0.1",
"manifest_version": 2,
"description":"Description",
"permissions":["tabs"],
"background": {
"scripts": ["background.js"]
},
"devtools_page": "devtools.html"
}
background.js
// Background page -- background.js
chrome.runtime.onConnect.addListener(function(devToolsConnection) {
// assign the listener function to a variable so we can remove it later
var devToolsListener = function(message, sender, sendResponse) {
// Inject a content script into the identified tab
chrome.tabs.executeScript(message.tabId,
{ file: message.scriptToInject });
}
// add the listener
devToolsConnection.onMessage.addListener(devToolsListener);
devToolsConnection.onDisconnect.addListener(function() {
devToolsConnection.onMessage.removeListener(devToolsListener);
});
}
devtools.js
var backgroundPageConnection = chrome.runtime.connect({
name: "devtools-page"
});
backgroundPageConnection.onMessage.addListener(function (message) {
// Handle responses from the background page, if any
});
chrome.devtools.network.onRequestFinished.addListener(
function(request) {
chrome.runtime.sendMessage({
string: "Hi",
tabId: chrome.devtools.inspectedWindow.tabId,
scriptToInject: "content.js"
});
}
);
chrome.runtime.sendMessage({
string: "Hi",
tabId: chrome.devtools.inspectedWindow.tabId,
scriptToInject: "content.js"
});
content.js
alert("Hello");

Remember state chrome extension

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.

Categories

Resources