Injected script execute twice in Chrome Extension - javascript

This is my popup.js where it's included in my popup.html, it will trigger when I click on browser action's icon.
chrome.tabs.query({
active: true,
currentWindow: true
}, function (tabs) {
var tabId = tabs[0];
chrome.tabs.update(null, {
url: 'https://example.com/login.php'
}, function () {
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
if (changeInfo.status == 'complete') {
chrome.tabs.executeScript(null, {
file: "jquery.js"
}, function () {
chrome.tabs.executeScript(null, {
file: "setLogin.js"
});
});
}
});
});
I have alert() within my setLogin.js, but I have no idea why it execute twice. I expect it will only trigger once as I have set my execute script within the changeInfo.status == 'complete' block.

Related

Chrome extension - Uncaught TypeError: Cannot read properties of undefined (reading 'sendMessage')

I am trying to send a message from injected code to background.js.
ContentScript.js
var g = document.createElement('script');
g.src = chrome.runtime.getURL('/js/buttonclick.js');
g.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(g);
buttonclick.js
var qsb = document.createElement("button");
document.body.appendChild(qsb);
qsb.addEventListener("click", submitT);
function submitT(e) {
opSubmit('T'); // opSubmit() is a function in a webpage, it is why I injected this code
window.onbeforeunload = null;
window.scrollTo(0, 0);
chrome.runtime.sendMessage({text: "hey"}, function(response) {
});
}
background.js
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
console.log("Received %o from %o, frame", msg, sender.tab, sender.frameId);
sendResponse("Gotcha!");
chrome.action.setBadgeText( { text: 'X', tabId: sender.tab.id} );
chrome.action.setBadgeBackgroundColor( { color: '#ef5350', tabId: sender.tab.id} );
setTimeout(function () {
chrome.tabs.query({currentWindow: true, active: true}, function(tabs){
uu = tabs[0].url;
tt = uu.indexOf(e);
vv = uu.indexOf(c);
if (tt != -1 || vv != -1) {
chrome.scripting.executeScript({
target: {tabId: sender.tab.id, allFrames: true},
files: ['/js/inject.js']
});
chrome.action.setBadgeText( { text: 'O', tabId: sender.tab.id} );
chrome.action.setBadgeBackgroundColor( { color: 'green', tabId: sender.tab.id} );
};
});
}, 4000);
});
However, when I run it, I get this error in console:
Uncaught TypeError: Cannot read properties of undefined (reading 'sendMessage')
Is it because I tried to execute sendMessage in an injected code? Then how can I change my code so that it will properly send Message?
Alternatively, sendMessage works fine if I call it from content script directly. Is there any way conten script can recognize button click from my injected button(qsb)? I tried adding
qsb.id = "qsb1"
in buttonclick.js and
document.getElementid("qsb1")
in contentscript.js but it can't recognize my button and instead returns null.
Thanks!
Okay, I did it with the latter method. I just added window.onload = function() to make my buttons load first and then used getElementid. It recognized my button correctly.

Send message from extension popup script to content script - JS

I'm making a web extension and I want a message to be sent to my content script when the following button of my popup.html is clicked.
<button class="button" type="button" id="stop">Stop</button>
<script src="stopCount.js"></script>
Here's stopCount.js :
let button = document.getElementById('stop');
button.addEventListener('click', function(event){
if (button.textContent == "Stop") {
button.textContent = "Resume";
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(
tabs[0].id,
{msg: "stop"});
});
}
else if (button.textContent == "Resume") {
button.textContent = "Stop";
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(
tabs[0].id,
{msg: "resume"});
});
}
})
And here's the part of my content script that intercepts messages :
chrome.runtime.onMessage.addListener(function(request, sender, response) {
if (request.msg == "stop") {
observer.disconnect();
}
else if (request.msg == "resume") {
observer = new MutationObserver(callback);
observer.observe(targetNode, config);
}
})
My problem is the part of my content script described just above is never reached, even if I click on the button.
Do you know what is the problem ?
Thanks in advance,

Chrome Extension - Change styles.CSS var not working

Sorry, I messed that up. I did not update the manifest from the last post and I forgot the content.js, that is how the Change button was sending the value and the page was getting the new style attribute value from the one entered in the text field and then to the content.js, existing content.js file added below.
I have a styles.css that changes the web page and is loaded from the popup but want to change a variable in the styles.css from the data entered at the popup.
I've added a content.js file and I can now pass a value from the Popup and it adds a new style attribute to the page but it's not updating the --ptwidth in the styles.css file. I think I need to have it in the styles.css to give the correct location and add the !important option.
I tried to ask this question before an it was closed\linked to one about webpage DOMs and don't have the reputation to post a comment and not sure I should ask my questions there if I could:
How to access the webpage DOM rather than the extension page DOM?
The styles.css injection works using the Wider button and the --ptwidth var is passed the value given (310) in the styles.CSS, at the very least I'd like to be able to enter a new value in the textbox and then use the existing Wider button to load the updated styles.css but it would be nice to have it auto update and maybe even use the slider.
The change button moves the new value entered in the text field to the content.js file and it then adds the new style attribute but it's not working. The insertCSS on the Wider button works but the info it adds is different.
Content.js adds the Style attribute section and the insertCSS adds the :root section that works.
Added by Content.js No Work:
Added by insertCSS Works and adds these two:
Columns before:
Columns after:
Rule before using the working Wider button with insertCSS:
Rules after the insertCSS injections of styles.css:
Popup:
manifest:
{
"manifest_version": 3,
"name": "Hellper",
"description": "Extension",
"version": "0.1",
"icons": { "16": "logo_16_T.png",
"48": "logo_48_T.png",
"128": "logo_128_T.png"
},
"action": {
"default_icon": "logo_16_T.png",
"default_popup":"popup.html"
},
"permissions": ["scripting", "tabs", "activeTab", "content.js"],
"host_permissions": ["<all_urls>"],
"content_scripts": [{
"js": ["jquery-2.2.0.min.js", "popup.js"],
"matches": ["https://www.google.com/*",
"https://en.wikipedia.org/*",
"https://stackoverflow.com/*"]
}]
}
popup.html:
<!doctype html>
<html>
<head>
<title>Popup</title>
</head>
<body>
<input id="button1" type=button value=clickme>
<button class="format">Wider</button>
<button class="reset">Reset</button>
<script src="jquery-2.2.0.min.js"></script>
<script src="popup.js"></script>
<!--
<h2>Background Color</h2>
<input type="color" id="color-changer" />
<h2>Rotate Page</h2>
<input type="range" min="0" max="360" step="1" value="0" id="rotate" />
-->
<h1>New Width</h1>
<p>
<input type="text" id="newWidth" value="120"/>
<input type="submit" id="btnChange" value="Change"/>
</p>
<div class="form-group">
<lable for="slider">Project/Task Width</lable>
<input type="range" min="0" max="999" step="1" value="160" id="slider" />
</div>
</body>
</html>
styles.css:
:root {
--ptwidth: 310px
}
.quixote .qx-grid .editor_grid tbody tr td input, .quixote .qx-grid .editor_grid tbody tr td .input-group {
/*max-width: 450px !important;
min-width: 450px !important;*/
max-width: var(--ptwidth) !important;
min-width: var(--ptwidth) !important;
popup.js:
$(document).ready(function() {
$('.reset').click(function() {
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
chrome.scripting.removeCSS({
target: { tabId: activeTab.id },
files: ["styles.css"]
});
});
})
$('.format').click(function() {
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
chrome.scripting.insertCSS({
target: { tabId: activeTab.id, allFrames: true },
files: ["styles.css"]
});
/*chrome.tabs.sendMessage(activeTab.id, {"buttonclicked": "wider"});*/
});
})
})
$(function(){
var width = $('#newWidth').val();
$('#newWidth').on("change paste keyup", function(){
width = $(this).val();
});
$('#btnChange').click(function(){
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
chrome.tabs.sendMessage(tabs[0].id, {todo: "changeWidth", sliderWidth: width})
});
});
});
content.js
let root = document.documentElement;
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.todo == "changeWidth"){
var updateWidth = request.sliderWidth;
root.style.setProperty('--ptwidth', updateWidth + "px");
// start();
}
});
It seems like there is still some work to do on this extension. I will try to provide only a solution to what was asked an leave the rest for you to work on.
Issues I will try to address
You shouldn't be reusing popup.js as a content script. You should create a separate file instead of having one script be both injected into the webpage and also being used in the popup window.
You are also sending messages from the popup window without anything listening for the messages. This also would be solved by creating a separate content script that listens for these messages.
Partial solution
I edited the manifest.json and popup.js as well as created a new file content.js.
New manifest.json
changed "popup.js" to "content.js"
{
"manifest_version": 3,
"name": "Hellper",
"description": "Extension",
"version": "0.1",
"icons": { "16": "logo_16_T.png",
"48": "logo_48_T.png",
"128": "logo_128_T.png"
},
"action": {
"default_icon": "logo_16_T.png",
"default_popup":"popup.html"
},
"permissions": ["scripting", "tabs", "activeTab"],
"host_permissions": ["<all_urls>"],
"content_scripts": [{
"js": ["jquery-2.2.0.min.js", "content.js"],
"matches": ["https://www.google.com/*",
"https://en.wikipedia.org/*",
"https://stackoverflow.com/*"]
}]
}
New popup.js
only edited the last function block
added a listener for the slider changes
coupled the values in the text box and slider, so when one changes, the other changes as well
changed the message parameter from "sliderWidth" to "newWidth" to make it more general
moved retrieval of the width value into the listeners so the new changed value can be sent along
I also suggest removing the change button entirely because the other listeners make it unnecessary
// Unchanged
$(document).ready(function () {
$(".reset").click(function () {
chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
var activeTab = tabs[0];
chrome.scripting.removeCSS({
target: { tabId: activeTab.id },
files: ["styles.css"],
});
});
});
$(".format").click(function () {
chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
var activeTab = tabs[0];
chrome.scripting.insertCSS({
target: { tabId: activeTab.id, allFrames: true },
files: ["styles.css"],
});
/*chrome.tabs.sendMessage(activeTab.id, {"buttonclicked": "wider"});*/
});
});
});
// Changed
$(function () {
// text input listener
$("#newWidth").on("change paste keyup", function () {
const width = $(this).val();
// update slider
$("#slider").val(width);
chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, { todo: "changeWidth", newWidth: width });
});
});
// listener for change button press
// button might not be needed anymore because of the text input listener above
$("#btnChange").click(function () {
const width = $("#newWidth").val();
// update slider
$("#slider").val(width);
chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, { todo: "changeWidth", newWidth: width });
});
});
// listener for slider changes
$("#slider").on("input", function () {
const width = $("#slider").val();
// update text box
$("#newWidth").val(width);
chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
chrome.tabs.sendMessage(tabs[0].id, { todo: "changeWidth", newWidth: width });
});
});
});
New content.js
created this script to listen for messages sent from the popup and make the changes to the CSS based on the message
$(document).ready(function () {
// listen for messages sent to the tab the content script is running in
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) {
// check to see if the message received is something that needs to be acted on
if (request.todo === "changeWidth") {
// pull the width data from the message
const newWidth = request.newWidth;
// set the style attribute of :root to override the styles.css value for --ptwidth
document.documentElement.style.setProperty("--ptwidth", newWidth + "px");
}
// send a response to avoid errors in popup.js
sendResponse("Width updated");
});
});
Hopefully that get you on the right track for finishing your extension!
Don't forget to check out the developer documentation for Chrome extensions. There are several useful posts for teaching how to accomplish the various parts of an extension.
[1] Message Passing: https://developer.chrome.com/docs/extensions/mv3/messaging/
[2] Content Scripts: https://developer.chrome.com/docs/extensions/mv3/content_scripts/
I got this working by using the chrome.scripting.insertCSS to replace the --ptwidth value after the styles.css has been injected. So far everything works with just the popup.js and no need for the content.js file.
I use this to load the styles.css with it's default value.
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
chrome.scripting.insertCSS({
target: { tabId: activeTab.id, allFrames: true },
files: ["styles.css"]
});
});
Then this when I get a new value from the textbox or slider.
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
//alert(readsavewidth);
$("#slider").val(readsavewidth);
$("#newWidth").val(readsavewidth);
chrome.scripting.insertCSS({
target: { tabId: activeTab.id, allFrames: true },
css: `:root { --ptwidth: ${readsavewidth + "px"} }`
});
});
});
I also used the chrome.storage.local to set and get the last value used.
$(function () {
// text input listener
$("#newWidth").on("change paste keyup", function () {
width = $(this).val();
//Save it to the localStorage variable which will always remember what you store in it
chrome.storage.local.set({'savewidth': width});
// update slider
$("#slider").val(width);
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
chrome.scripting.insertCSS({
target: { tabId: activeTab.id, allFrames: true },
css: `:root { --ptwidth: ${width + "px"} }`
});
});
});
// listener for slider changes
$("#slider").on("input", function () {
width = $("#slider").val();
// update text box
$("#newWidth").val(width);
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
chrome.scripting.insertCSS({
target: { tabId: activeTab.id, allFrames: true },
css: `:root { --ptwidth: ${width + "px"} }`
});
});
//Pull text from user inputbox
var data = width;
//Save it to the localStorage variable which will always remember what you store in it
chrome.storage.local.set({'savewidth': data});
});
});
Then I get the value when the popup opens and it then overights the --ptwidth value using the chrome.scripting.insertCSS funtion.
chrome.storage.local.get('savewidth', function (result) {
readsavewidth = result.savewidth;
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
var activeTab = tabs[0];
//alert(readsavewidth);
$("#slider").val(readsavewidth);
$("#newWidth").val(readsavewidth);
chrome.scripting.insertCSS({
target: { tabId: activeTab.id, allFrames: true },
css: `:root { --ptwidth: ${readsavewidth + "px"} }`
});
});
});

Why is Input.dispatchMouseEvent not dispatching an event?

I'm trying to dispatch an IsTrusted event that simulates the user clicking a certain place on the screen. I'm trying this out through a Chrome Extension, although I'm not having much luck. There are no errors in the console, and my giant screen-wide button isn't being clicked. Here is my background.js:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var activeTab = tabs[0];
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.message === "clickElement" ) {
chrome.debugger.attach({tabId:tab.id}, "1.2", function(debugg) {
chrome.debugger.sendCommand(
{tabId:tab.id}, "Debugger.enable", {},
function() {
chrome.debugger.sendCommand({tabId:tab.id}, "Input.dispatchMouseEvent",
{
type:"mousePressed",
x:parseFloat(request.x),
y:parseFloat(request.y)
})
})
})
}
}
);
chrome.tabs.sendMessage(activeTab.id, {"message": "runbot"});
});
});
And my content.js just sends the clickElement message with the coordinates of the button.
Any ideas?
What you need to do is remove this nonsense:
chrome.debugger.sendCommand(
{tabId:tab.id}, "Debugger.enable", {},
And add button: "left" prop to the parameters object:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var activeTab = tabs[0];
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if( request.message === "clickElement" ) {
chrome.debugger.attach({tabId:tab.id}, "1.2", function(debugg) {
chrome.debugger.sendCommand({tabId:tab.id}, "Input.dispatchMouseEvent",
{
type:"mousePressed",
button: "left",
x:parseFloat(request.x),
y:parseFloat(request.y)
})
})
}
}
);
chrome.tabs.sendMessage(activeTab.id, {"message": "runbot"});
});
});
Also make sure you have the "debugger" permission in you manifest.json.
ps. When debugger attaches you should see this bar appear just below the address bar saying smth like '${extn. name} is executing browser debug':

Assertion failed in chrome extension and duplication

In background.js:
function checkForValidUrl(tabId, changeInfo, tab) {
if(changeInfo.status === "loading") {
if (tab.url.indexOf('google.com') > -1 || tab.url.indexOf('amazon.com') > -1) {
chrome.pageAction.show(tabId);
}
}
};
chrome.tabs.onUpdated.addListener(checkForValidUrl);
chrome.pageAction.onClicked.addListener(function(tab){
if (tab.url.indexOf('google.com') > -1){
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tab.id, {google_go: "go"});
});
chrome.tabs.create({url: "http://facebook.com", "active":true});
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {
chrome.runtime.onConnect.addListener(function(port) {
console.assert(port.name == "facebook_chanel");
port.onMessage.addListener(function(msg) {
if (msg.facebook_go == "go")
port.postMessage(request);
});
});
});
}
In facebook.js:
$(document).ready(function(){
var port = chrome.runtime.connect({name: "facebook_chanel"});
port.postMessage({facebook_go: "go"});
port.onMessage.addListener(function(msg) {
console.log(msg);
});
});
I go too google. Press on pageAction, I see tab with facebook, see one Object in console. In html/background.html console (Chrome) I see the error Assertion failed: in
console.assert(port.name == "facebook_chanel");
I go to google again, press pageAction, and I see at new facebook page one old and 2 new Objects.
How to fix it? Thanks.
UPDATE
in background.js
chrome.tabs.create({url: "http://facebook.com", "active":true}, function(tab){
chrome.runtime.onMessage.addListener( function(request, sender, sendResponse) {
chrome.tabs.query({active: true, currentWindow: true}, function(tab) {
chrome.tabs.sendMessage(tab[0].id, request);
});
});
});
in facebook.js:
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
console.log(request);
});
Its right way? But I have clear console, whats wrong?
It is still not very clear what you are trying to achieve and why you chose to use porsistent ports (instead of single messaging) and page-actions, but from what I did understand, context-menus could be a better/cleaner approach.
So, below is the source code of a sample extension that registers a context-menu for when some text is selected (i.e. select/highlight some text and right click) and then opens an Facebook tab and sends the selected text for further processing (by the FB tab's content script).
If you still need a page-action instead of a context-menu, change the code like this:
Add listener for chrome.pageAction.onClicked in background-page.
Inject some code into the original tab (e.g. google.com) to retrieve ad return the current selection (using chrome.tabs.executeScript()).
Handle the acquired selected text as demonstrated in the code below.
manifest.json:
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
"offline_enabled": false,
"background": {
"persistent": false,
"scripts": [
"background.js"
]
},
"content_scripts": [{
"matches": ["*://*.facebook.com/"],
"js": ["content.js"],
"run_at": "document_end",
"all_frames": false
}],
"permissions": [
"contextMenus"
]
}
content.js:
chrome.runtime.onMessage.addListener(function(msg) {
/* Input validation */
if (!msg.source || !msg.text) {
console.error('Bad message format: ', msg);
return;
}
/* Handle the selected text */
switch (msg.source) {
case 'Amazon':
case 'Google':
alert('Selected on ' + msg.source + ':\n' + msg.text);
break;
default:
alert('Unknown source: ' + msg.source);
break;
}
});
background.js:
var contextMenuID = 'copyToFB';
var googleRegex = /^https?:\/\/(?:[^\.]+\.)?google\..+/;
var amazonRegex = /^https?:\/\/(?:[^\.]+\.)?amazon\..+/;
/* `onUpdated` listener factory (for when FB has loaded) */
var listenerFactory = function(trgTabID, msgSrc, selectedText) {
return function(tabId, info, tab) {
if ((trgTabID === tabId) && (info.status === 'complete')) {
chrome.tabs.onUpdated.removeListener(arguments.callee);
chrome.tabs.sendMessage(trgTabID, {
source: msgSrc,
text: selectedText
});
}
}
};
chrome.contextMenus.create({
type: 'normal',
id: contextMenuID,
title: 'Copy to Facebook',
contexts: ['selection'],
// For some reason documentsUrlPatterns
// does not seem to work (at least on Windows).
// Theoratically, you should be able to limit the
// context-menu on specific sites only
//documentUrlPatterns: [
//'*://*.google.*/*',
//'*://*.amazon.*/*'
//],
enabled: true
});
chrome.contextMenus.onClicked.addListener(function(info, tab) {
console.log('Context menu clicked: ', info.menuItemId);
if (info.menuItemId === contextMenuID) {
var selectedText = info.selectionText;
console.log('Selected text: ', selectedText);
if (!selectedText) {
alert('Nothing to copy to Facebook !');
return;
}
var url = info.frameUrl ? info.frameUrl : info.pageUrl;
if (googleRegex.test(url)) {
console.log('Google.com URL: ', url);
/* Handle text selected on `google.com` */
chrome.tabs.create({
url: 'http://facebook.com/',
active: true
}, function(tab) {
chrome.tabs.onUpdated.addListener(
listenerFactory(tab.id, 'Google', selectedText));
});
} else if (amazonRegex.test(url)) {
console.log('Amazon.com URL: ', url);
/* Handle text selected on `amazon.com` */
chrome.tabs.create({
url: 'http://facebook.com/',
active: true
}, function(tab) {
chrome.tabs.onUpdated.addListener(
listenerFactory(tab.id, 'Amazon', selectedText));
});
} else {
console.log('Non-matching URL: ', url);
}
}
});
Final notes:
Because documentUrlPatterns does not seem to work (at least on Windows), the context-menu is shown on every page (when you select some text). You could add extra listeners (e.g. for chrome.tabs.onActivated etc) to remove or disable the context menu when the user is not on one of the permitted URLs.
It might be a good idea to keep track of an ope FB tab and not create a new one every time. (Or you could also look for an already opened (e.g. by the user) FB tab.)

Categories

Resources