Chrome Extension: Uncaught ReferenceError: function is not defined - javascript

I am writing a Chrome Extension and I have this page:
<html>
<body>
<button id="changeColor"></button>
<script src="popup.js"></script>
</body>
</html>
With this JS (popup.js):
let changeColor = document.getElementById("changeColor");
chrome.storage.sync.get("color", ({ color }) => {
changeColor.style.backgroundColor = color;
});
changeColor.addEventListener("click", async () => {
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
chrome.scripting.executeScript({
target: { tabId: tab.id },
function: setPageBackgroundColor,
});
});
function setPageBackgroundColor() {
chrome.storage.sync.get("color", ({ color }) => {
document.body.style.backgroundColor = color;
});
// Here, it says: Uncaught ReferenceError: getElementByXpath is not defined
console.log(getElementByXpath("xpath").textContent);
}
function getElementByXpath(path) {
return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}
Why?

Problem: the code of outer functions isn't injected.
Here's what executeScript does:
it takes the function's code as plain text IIFE i.e. (function foo() { ... })(),
it transfers the text to the web page,
it selects the "isolated world" environment where all content scripts of your extension run,
it executes that text as JavaScript code.
Solution: put all necessary code and functions inside the function you inject.
In your case getElementByXpath definition should be moved inside setPageBackgroundColor.
P.S. Naturally, the injected code can also use global variables/functions of previously injected content scripts via manifest.json's content_scripts (assuming their run_at already occurred) or executeScript.

Related

Html script src files that are linked to each other

(simplification) I have two javascript files I want to include. They inter-link each other.
Problem: If I just include them the following there is an error because source1.js needs something from source2.js and vice-versa.
How can I include inter-linking source files properly in HTML, without merging them? (Imaging various already-large files)
<head>
<script src="source1.js"></script>
<script src="source2.js"></script>
source1.js
function filtersomething() {
...
othersource.somefunction();
}
source2.js
var columns = {
text: filtersomething()
}
Added more to a working snippet. I'd use that code over some of the examples here. it deals more with arguments, promises, etc. Have to run, hope this helps.
You can place an event listener within each JS file, which would not call any functions until the dom is loaded. Doing this allows both JS files to load in and see the others functions available.
// script.js
window.addEventListener('DOMContentLoaded', (event) => {
filtersomething();
});
function filtersomething() {
...
othersource.somefunction();
}
Because these are loaded after script.js, script2.js always sees what script.JS has available. So, script.JS does not see what script2.JS has until after it is loaded
// script2.js
window.addEventListener('DOMContentLoaded', (event) => {
var columns = {
text: filtersomething()
}
});
We can also watch for a pointer, as suggested. This is useful when waiting for jQuery to load as well. So within your script files, watch for a property to be set, then execute.
<head>
<script>
function deferRun(thisMethod, scriptNum) {
if (window[scriptNum])
return thisMethod();
// No property found, set timeout of 50ms and try again
setTimeout(function() { defer(thisMethod, scriptNum) }, 50);
}
</script>
<script src="source1.js"></script>
<script src="source2.js"></script>
</head>
// script2.JS
// wait until script.js is available, then return result
var columns = {
text: deferRun(filtersomething, 'script1')
}
// Set our window property saying script loaded
window.script2 = true;
//script.js
function filtersomething() {
...
deferRun(othersource.somefunction, 'script2');
}
// Set our window property saying script loaded
window.script1 = true;
// Think of script1/script2 as <script> tags.
window.script1 = {
// script.js
filtersomething: () => {
return deferRun('somefunction', 'script2', 'message to use');
}
};
// Now "load" script2.js
window.script2 = {
somefunction: (msg) => {
return `msg response is ${msg}`
},
columns: {
// wait until script.js is available, then return result
text: deferRun('filtersomething', 'script1')
},
render: async() => {
console.log(await window.script2.columns.text);
}
};
(async() => {
await window.script2.render();
})();
<head>
<script>
// this is available to all and before either script 1 or 2 loads
function deferRun(thisMethod, property, argument = null) {
if (window[property])
return window[property][thisMethod](argument);
// No property found, set timeout of 50ms and try again
return setTimeout(function() {
deferRun(thisMethod, property, argument)
}, 50);
}
</script>
</head>
You can place an event listener within each JS file, which would not call any functions until the dom is loaded. Doing this allows both JS files to load in and see the others functions available.
<pre>
// script.js
window.addEventListener('DOMContentLoaded', (event) => {
filtersomething();
});
function filtersomething() {
...
othersource.somefunction();
}
```
Because these are loaded after script.js, script2.js always sees what script.JS has available. So, script.JS does not see what script2.JS has until after it is loaded
```js
// script2.js
window.addEventListener('DOMContentLoaded', (event) => {
var columns = {
text: filtersomething()
}
});
```
We can also watch for a pointer, as suggested. This is useful when waiting for jQuery to load as well. So within your script files, watch for a property to be set, then execute.
</pre>

fingerprintjs not loading in chrome extension

I am trying to load fingerprintjs in my chrome extension and for some reason it is not loading.
The documentation says to use this approach, which works fine in standalone web sites, just not in chrome extensions
<script>
function initFingerprintJS() {
FingerprintJS.load().then(fp => {
// The FingerprintJS agent is ready.
// Get a visitor identifier when you'd like to.
fp.get().then(result => {
// This is the visitor identifier:
const visitorId = result.visitorId;
console.log("visitorId", visitorId);
});
});
}
</script>
<script async src="fp.min.js" onload="initFingerprintJS();"></script>
For chrome extension I added created a JS file initFingerprint.js that holds the init code like so:
var visitorId = null; //will hold the actual fingerprint
function initFingerprintJS() {
console.log("inside initFingerprintJS");
FingerprintJS.load().then(fp => {
console.log("loaded initFingerprintJS");
fp.get().then(result => {
console.log("initFingerprintJS got result", result)
visitorId = result.visitorId; // This is the visitor identifier
console.log("visitorId", visitorId);
});
});
}
initFingerprintJS();
In background.html, I added this:
<script async src="fp.min.js"></script>
<script async src="initFingerprint.js"></script>
I have tried with async in there and not in there, but still no luck. This line will print, but none of the other lines below it.
inside initFingerprintJS
What am I doing wrong? I appreciate any help. thank you
Remove <script> tag for fp.min.js from the html and create the script element yourself in initFingerprint.js so you can use the onload event directly:
loadScript('fp.min.js').then(initFingerprintJS);
function loadScript(url) {
return new Promise((resolve, reject) => {
const el = document.createElement('script');
el.onload = resolve;
el.onerror = reject;
el.src = url;
document.documentElement.appendChild(el);
});
}
async function initFingerprintJS() {
visitorId = await (await FingerprintJS.load()).get();
console.log('visitorId', visitorId);
}

chrome.runtime.sendMessage not working on the 1st click when running normally. it works while debugging though

I have a function in the context.js which loads a panel and sends a message to panel.js at the last. The panel.js function updates the ui on receiving that msg. But it is not working for the first click i.e. it just loads normal ui, not the one that is expected that is updated one after the msg is received. while debugging it works fine.
manifest.json
"background": {
"scripts": ["background.js"],
"persistent": false
},
"content_scripts": [{
"all_frames": false,
"matches": ["<all_urls>"],
"js":["context.js"]
}],
"permissions": ["activeTab","<all_urls>", "storage","tabs"],
"web_accessible_resources":
"panel.html",
"panel.js"
]
context.js - code
fillUI (){
var iframeNode = document.createElement('iframe');
iframeNode.id = "panel"
iframeNode.style.height = "100%";
iframeNode.style.width = "400px";
iframeNode.style.position = "fixed";
iframeNode.style.top = "0px";
iframeNode.style.left = "0px";
iframeNode.style.zIndex = "9000000000000000000";
iframeNode.frameBorder = "none";
iframeNode.src = chrome.extension.getURL("panel.html")
document.body.appendChild(iframeNode);
var dataForUI = "some string data"
chrome.runtime.sendMessage({action: "update UI", results: dataForUI},
(response)=> {
console.log(response.message)
})
}
}
panel.js - code
var handleRequest = function(request, sender, cb) {
console.log(request.results)
if (request.action === 'update Not UI') {
//do something
} else if (request.action === 'update UI') {
document.getElementById("displayContent").value = request.results
}
};
chrome.runtime.onMessage.addListener(handleRequest);
background.js
chrome.runtime.onMessage.addListener((request,sender,sendResponse) => {
chrome.tabs.sendMessage(sender.tab.id,request,function(response){
console.log(response)`
});
});
panel.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="panel.css" />
</head>
<body>
<textarea id="displayContent" rows="10" cols="40"></textarea>
</body>
</html>
Any suggestions on what I am doing wrong or what can I do instead?
An iframe with a real URL loads asynchronously so its code runs after the embedding code finishes - hence, your message is sent too early and is lost. The URL in your case points to an extension resource so it's a real URL. For reference, a synchronously loading iframe would have a dummy URL e.g. no src at all (or an empty string) or it would be something like about:blank or javascript:/*some code here*/, possibly srcdoc as well.
Solution 1: send a message in iframe's onload event
Possible disadvantage: all extension frames in all tabs will receive it, including the background script and any other open extension pages such the popup, options, if they also have an onMessage listener.
iframeNode.onload = () => {
chrome.runtime.sendMessage('foo', res => { console.log(res); });
};
document.body.appendChild(iframeNode);
Solution 2: let iframe send a message to its embedder
Possible disadvantage: wrong data may be sent in case you add several such extension frames in one tab and for example the 2nd one loads earlier than the 1st one due to a bug or an optimization in the browser - in this case you may have to use direct DOM messaging (solution 3).
iframe script (panel.js):
chrome.tabs.getCurrent(ownTab => {
chrome.tabs.sendMessage(ownTab.id, 'getData', data => {
console.log('frame got data');
// process data here
});
});
content script (context.js):
document.body.appendChild(iframeNode);
chrome.runtime.onMessage.addListener(
function onMessage(msg, sender, sendResponse) {
if (msg === 'getData') {
chrome.runtime.onMessage.removeListener(onMessage)
sendResponse({ action: 'update UI', results: 'foo' });
}
});
Solution 3: direct messaging via postMessage
Use in case of multiple extension frames in one tab.
Disadvantage: no way to tell if the message was forged by the page or by another extension's content script.
The iframe script declares a one-time listener for message event:
window.addEventListener('message', function onMessage(e) {
if (typeof e.data === 'string' && e.data.startsWith(chrome.runtime.id)) {
window.removeEventListener('message', onMessage);
const data = JSON.parse(e.data.slice(chrome.runtime.id.length));
// process data here
}
});
Then, additionally, use one of the following:
if content script is the initiator
iframeNode.onload = () => {
iframeNode.contentWindow.postMessage(
chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
};
document.body.appendChild(iframeNode);
if iframe is the initiator
iframe script:
parent.postMessage('getData', '*');
content script:
document.body.appendChild(iframeNode);
window.addEventListener('message', function onMessage(e) {
if (e.source === iframeNode) {
window.removeEventListener('message', onMessage);
e.source.postMessage(chrome.runtime.id + JSON.stringify({foo: 'data'}), '*');
}
});
one possible way that worked for me is by using functionality in setTimeout() method.
in context.js
setTimeout(() => {
chrome.runtime.sendMessage({action: "update UI", results: dataForUI},
(response)=> {
console.log(response.message)
}
)
}, 100);
But I am not sure if this is the best way.

How to call a javascript function from .js file on page load?

I really can't figure out how to do it. I need to call somefunc() from file.js file on page load.
My file.js contains:
function somefunc() {
pc.somefunc(gotLocalDescription,
function(error) {
console.log(error)
}, {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true
}
});
}
// Socket.io
var socket = io.connect('', {
port: 1234
});
function sendCall(call) {
socket.emit('call', call);
}
socket.on('call', function(call) {
if (call.type === 'offer') {
pc.setRemoteDescription(new SessionDescription(call));
createAnswer();
} else if (call.type === 'answer') {
console.log('10--if call type is answer');
pc.setRemoteDescription(new SessionDescription(call));
} else if (call.type === 'candidate') {
var candidate = new IceCandidate({
sdpMLineIndex: call.label,
candidate: call.candidate
});
pc.addIceCandidate(candidate);
}
});
consider using
Trigger
instead
You can simple call the function which is in another file.
Have created a plunker.Also note it has a seperate file file.js. If you using name spacing please take care of that.
Click Me
WORKING COPY
You can use this:
click
But first you must include file.js in you html
<script type="text/javascript" src="file.js">
Using window.onload (pure javascript), you can call your somefunc() of file.js on page load, as following:
function somefunc() {
alert('somefunc() of file.js called!');
/*
* Your logic goes here.
*/
}
window.onload = somefunc();
DEMO
While, if you want to use jQuery, then include jquery source first and then your custom script file containing your method and DOM ready call, as following:
function somefunc() {
alert('somefunc() of file.js called!');
/*
* Your logic goes here.
*/
}
jQuery(document).ready(function(){
somefunc();
});
// OR
jQuery(function({
somefunc();
});
First make sure you have included your file.js to head of your html and the location to file is correct.

How to apply class to the active tab page from chrome extension

I am developing a chrome extension in which one can select a color scheme from list given in popup and apply it to the open (highlighted) tab. From one of code snippet I comes to know that using code : "document.body.style.backgroundColor='red'" in chrome.tabs.executeScript change the background color. but there is only one line in code.
What my steps are
select the color scheme from popup
get the class name of the selected li
apply that class to the DOM document
Please see the code below
popup.js
document.addEventListener('DOMContentLoaded', function () {
var li = document.querySelectorAll('li');
for (var i = 0; i < li.length; i++) {
li[i].addEventListener('click', click);
}
});
function click(e) {
// console.log(e.target.className); // gives correct value
chrome.tabs.executeScript(null, {
code : "var scriptOptions = { param1: e.target.className} ;"}, function(e){
console.log('clicked class');
console.info(param1); // gives nothing
document.body.setAttribute('class', e.target.className);
});
window.close();
}
How to get e.target.className inside function(e) ?
again If I use jquery. it changed the that popup background color only, see the code
$(function(){
console.log('jQuery added');
$(document).on ('click', 'li', function(){
var cl = this.className;
$('body').removeClass().addClass(cl);
});
});
Please tell me
What is the proper way to accomplish this in both javascript and jQuery
How to get e.target.className inside function(e) ?
Let's look at the following sample code:
var a = 1;
function f(a) {
alert(a);
}
f(2);
This is a simplified version of your problem. There is a variable a in the global scope, but by naming your function parameter a you're essentially making a local variable of the same name.
In your code:
function click(e) {
// e is now from click(e)
chrome.tabs.executeScript(null, {
code : "var scriptOptions = { param1: e.target.className} ;"}, function(e){
// e is now from function(e)
});
}
The solution is simple: you're not using the parameter of the callback of executeScript, so just use function() { /* ... */ } as a callback.
If I use jQuery, it changes the popup background color only
Your code operates in the context of your popup; $('body') refers to popup's body. Same with document.body - the callback of executeScript executes in the popup.
To change the active tab, this needs to be done from the content script in that tab.
What is the proper way to accomplish this
While you could just inject code, it's better to make a content script that waits for a command.
// content.js
if(!injected) { // Make sure it's only executed once
injected = true;
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.action == "bodyClass") {
document.body.setAttribute('class', message.class);
}
});
}
Then from the popup, you inject this script then message it:
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
// requires only activeTab permission
chrome.tabs.executeScript(tabs[0].id, {file: "content.js"}, function() {
// This code executes in the popup after the content script code executes
// so it is ready for the message
chrome.tabs.sendMessage(tabs[0].id, {action: "bodyClass", class: "example"});
});
});
If you need jQuery, you need to inject it first:
chrome.tabs.executeScript(tabs[0].id, {file: "jquery.js"}, function() {
chrome.tabs.executeScript(tabs[0].id, {file: "content.js"}, function() {
/* content script ready */
}
}
Alternatively, you can define the script in the manifest and not inject it every time, but this potentially drains memory as it is injected in tabs where it is not needed.
There is bug in chromium and in chrome I need to use JSON.stringify(e.target.className) the before sending via code
code : "var scriptOptions = { selectedClass: " + JSON.stringify(cl) + " }"
from chorme.sendMessage documentation
Sending a request from the extension to a content script looks very
similar, except that you need to specify which tab to send it to.
function click(e) {
var cl = e.target.className; // both gives the same result that is OK.
chrome.tabs.query({ active: true, highlighted: true, currentWindow: true }, function(htab) {
// console.log(JSON.stringify(htab, ['active', 'id', 'index', 'windowId', 'title', 'url'], 4));
chrome.tabs.executeScript(htab[0].id, {
code : "var scriptOptions = { selectedClass:" + JSON.stringify(cl) + " }" }, function() {
chrome.tabs.executeScript(htab[0].id, { file: "js/script.js" }, function(){
console.log('Inside script file');
chrome.tabs.sendMessage(htab[0].id, { action: "bodyColor" }, function(resp) {
console.log('response aaya');
});
});
});
});
}

Categories