How to refresh content script for chrome extension as DOM changes - javascript

I'm having a difficult time re-running some simple JS from a content_script for a chrome extension. Here's basically what I have setup. The following code works, but only once:
manifest.json
{
"content_scripts": [ {
"matches": ["https://www.amazon.com/*"],
"js": ["./content_scraper.js", "./main.js"],
"run_at": "document_end"
} ],
"permissions": ["tabs","webNavigation"]
}
content_scraper.js
function dataUpdater() {
// this function scrapes the page and updates the data var
}
chrome.runtime.onMessage.addListener(
function(message, sender, sendResponse) {
switch(message.type) {
case "getItems":
sendResponse(data)
break;
}
}
);
main.js
function getItemsData() {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, {type: "getItems"}, function(resp) {
resp.map(item => {
// do some things
}
})
})
}
document.addEventListener("DOMContentLoaded", getItemsData)
So, the above code works after DOMContent is loaded. I have experimented with the DOMNodeInserted event and reading the document.readyState to fire scripts again, but I cannot seem to access the dataUpdater function from content_scripts.
I'm simply trying to run the content_scraper.js (which lives in content_scripts) whenever the DOM is updated. Thanks for any help!

You need to look for MutationObserver and observe on subtree of document body, like this:
const observer = new MutationObserver(function(mutations) {
...
});
observer.observe(document.body, { subtree: true });
See the reference: https://developer.mozilla.org/cs/docs/Web/API/MutationObserver

Related

Chrome Extension: Sending message from Injected Script to Content Script

I'm trying to develop a chrome extension, it has to inject the script using OnUpdated
and OnActivated Event Listener.
My script is injecting properly but the problem is that how I can communicate with my background/service_worker script using my injected script
This is image of my injected script which contain some kind of buttons Injected Script
I've tried to access these element into content-script send message to background/service_worker but these elements aren't accessible in my content-script
When I try to access element without injected script these elements are passing message correctly between content-script to background/service-worker Web page
This is the Manifest MV3 Manifest.json
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"css": [
"css/all.min.css",
"css/camera.css"
],
"js": [
"js/content-script.js"
],
"run_at": "document_end",
"all_frames": false
}
],
"web_accessible_resources": [
{
"resources": [
"*"
],
"matches": [
"<all_urls>"
],
"use_dynamic_url": true
}
]
This is my content-script.js
var startRecording = document.getElementById('start-recording');
var stopRecording = document.getElementById('rec-stop');
if(startRecording){
startRecording.addEventListener('click',()=> {
chrome.runtime.sendMessage({recording_started: true}, function(response){
console.log(response.result);
})
})
}
if(stopRecording){
stopRecording.addEventListener('click',()=>{
console.log('im stop')
})
}
startRecording is accessing element from non injected script which is working and stopRecordingis accessing element from injected script which is not working well
and after all this is my service_worker.js which i'm using to listening messages from content script
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log('Service Workder ',message);
if(message.recording_started){
sendResponse({result: 'hello Im going from service worker'});
}
if(message.notification){
sendResponse({result: 'Notification from service worker'});
}
})
Basically my problem is to accessing the element of injected script in content-script and pass message to service_worker.js when injected element is clicked
This is how I'm injecting my script
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if(changeInfo.status == 'complete' && tab.status == 'complete' && tab.url !== 'undefined'){
chrome.tabs.query({currentWindow: true, active: true}, function(tabs){
if(tabs[0].url?.includes('chrome://')) return undefined;
var currentId = tabs[0].id;
chrome.scripting.executeScript({
target: {tabId: currentId, allFrames: false},
files: ['js/camera.js'],
});
});
}
})
Maybe I'm using the wrong method for message passing
You should suggest me a better way to passing message between injected script and content-script
Thanks
Based on the limited code provided and your explanation, if stopRecording is added via the injected script, then it does not appear that your codes actually adds the stopRecording element. Upon the user clicking "startRecording", you should be injecting the stopRecording element first into the DOM before attempting to "getElementId" and assign an onclick event to it. Inside the startRecording onClick event you should be adding code that replaces the startRecording Button with the stopRecording button and then looking for 'rec-stop':
var startRecording = document.getElementById('start-recording');
if(startRecording){
startRecording.addEventListener('click',()=> {
//Replace startRecording Button with stopRecording Button
const stopRecordingElement = document.createElement("div");
stopRecordingElement.id = "rec-stop";
//Add any image or styling to button
startRecording.replaceWith(stopRecordingElement);
//Now that the DOM has the 'rec-stop' element you can call
var stopRecording = document.getElementById('rec-stop');
if(stopRecording){
stopRecording.addEventListener('click',()=>{
console.log('im stop')
})
}
chrome.runtime.sendMessage({recording_started: true},
function(response){
console.log(response.result);
})
})
}
I've tried to access my injected elements into my non-injected js files
They are working properly for me
Thanks everyone for helping me

Function inside "DOMContentLoaded" event does not get triggered

I was trying to create a chrome extension for youtube.com, where the injected script should load right after the page has finished loading. However, the script creates an element using an id which is available only once the page fully loads and the videos have loaded. Current method used:
document.addEventListener('DOMContentLoaded', () => {
const wrapper = document.getElementById(<id>);
const btn = document.createElement('button');
wrapper.appendChild(btn);
btn.addEventListener('click', () => {
const el = document.getElementById(<id>).children;
});
});
In your manifest.json file.
Add this line inside content_scripts
"run_at": "document_end"
example
"content_scripts": [
{
"matches": ["<all_urls>"],
"all_frames": true,
"js": ["contents/content.js"],
"run_at": "document_end"
}
],
Remove DOMcontentLoaded eventlistener.
You got error because:
your event callback function set after "DOMContentLoaded" event triggered.
Most likely because your code run too late, after DOMContentLoaded has already fired. if you're using jQuery you can do
$(document).ready(()=>{...})
and in vanilla-js you can do
{
const cb = () => {...};
if (document.readyState === "interactive" || document.readyState === "complete") {
cb();
} else {
document.addEventListener('DOMContentLoaded', cb);
}
}

How can I get the current Crome URL?

I'm writing Windows Forms application on C# and trying to get current URL from Chrome in this application. I find active process, and if it's name is "chrome" I try anything, but unsuccessfully. All solutions i've found works only for previous versions of Chrome. As far as I understand, it's better to use Google Chrome Extention for this (but I have never write any of them and I'm superficially familiar with JS).
So, I tried to write an Extension:
manifest.js:
{
"manifest_version": 3,
"name": "URL collector",
"version": "0.11",
"description": "Собирает URL посещенных ресурсов",
"permissions": [
"tabs",
"activeTab",
"webNavigation",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": [
"app.js"
]
}
]}
background.js:
chrome.runtime.onInstalled.addListener(() => {
async function getCurrentTab() {
let queryOptions = { active: true, lastFocusedWindow: true };
let [tab] = await chrome.tabs.query(queryOptions);
return tab;
}
alert(window.getCurrentTab());});
app.js:
chrome.tabs.query({active: true, lastFocusedWindow: true}, function(tabs) {
var tab = tabs[0];
console.log(tab.url);
alert(tab.url);});
Also I've tried such code:
;(function() {
var pushState = history.pushState;
var replaceState = history.replaceState;
history.pushState = function() {
pushState.apply(history, arguments);
window.dispatchEvent(new Event('pushstate'));
window.dispatchEvent(new Event('locationchange'));
};
history.replaceState = function() {
replaceState.apply(history, arguments);
window.dispatchEvent(new Event('replacestate'));
window.dispatchEvent(new Event('locationchange'));
};
window.addEventListener('popstate', function() {
window.dispatchEvent(new Event('locationchange'))
});})();
window.addEventListener('locationchange', function(){
console.log('onlocationchange event occurred!');
alert(chrome.tabs.getCurrent(tab => tab.url));})
Everything I have tried in app.js I also tried in background.js and vice versa. And either I did not understand how to track the Extension triggering, or it does not work (probably the second). In general, I tried to catch URL change events, for example, switching tabs or following a link. Well, so far nothing has come of it. Actually, the first question is this: how to make such Extension?
It seems, I totally do not understand the topic, therefore I will also be extremely grateful for links to materials on it.
Well, and the second question is, what is the best way to pass a URL to a Windows Forms application?
Sorry for my English, it seems to be really bad.
in my app.js, I'm using current.Window rather than lastFocusedWindow
button_element.addEventListener('click', () => {
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
arr.push(tabs[0].url)
localStorage.setItem("example", JSON.stringify(arr) )
render(arr)
})
})
That works for pushing the current tab to local storage

chrome extensions: How to send a message to the newly created tab?

I need to send some data to the newly created tab. I found some answers here to implement the listener first and then send a message. My event listener isn't working and can't catch the message.
manifest:
{
"manifest_version": 2,
"name": "My Cool Extension",
"version": "0.1",
"permissions": ["tabs",
"http://*/*",
"https://*/*",
"activeTab"
],
"browser_action": {
"default_icon": "icon.png"
},
"background": {
"scripts": ["background.js"]
}
}
background:
chrome.browserAction.onClicked.addListener((tab)=>{
chrome.tabs.query({active: true, currentWindow: true}, tabs => {
if(tabs.length === 1 ){
chrome.tabs.create({url:"https://www.youtube.com/", active: true}, (tab)=>{
chrome.tabs.executeScript(tab.id, {file:"content.js"},tab=>{
chrome.tabs.sendMessage(tab.id, {"Active Objects": "elo"})
})
})
}
else{
alert("wrong page")
}
});
});
content:
chrome.runtime.onUpdate.addListener(
(request, sender, sendResponse)=>{
alert("elo")
}
);
I've finally got it to work. I didn't actually tested it the first time.
In addition to replacing onUpdate with onMessage in the content script you
might want to add setTimeout functions in the background script to delay the executions of
chrome.tabs.executeScript and chrome.tabs.sendMessage. Otherwise, you might
get (as I have) a runtime.lastError: The tab was closed.
And also you should avoid having multiple function callback arguments named
tab. Otherwise, they get overriden.
I simplified the background script to get it to work. You don't actually need
to query the current tab to create a tab and execute a script inside it. So,
I've simply kept chrome.tabs.create.
background.js:
chrome.browserAction.onClicked.addListener(function (_) {
chrome.tabs.create({url: "https://www.youtube.com/", active: true},
function (yt_tab) {
setTimeout(function () {
chrome.tabs.executeScript(yt_tab.id, {file: "content.js"});
setTimeout(function () {
chrome.tabs.sendMessage(yt_tab.id, {"Active Objects":"elo"});
}, 1000);
}, 1000);
});
});
content.js:
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
console.log(request);
}
);

Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist. Chrome Extension

I am trying to receive some info from the content page to the popup page in chrome extension.
Here is my manifest.json:
{
"name": " Downloader",
"description": "history ",
"version": "1.0",
"permissions": [
"activeTab",
"notifications"
],
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"content_scripts": [
{
"all_frames": false,
"matches": ["<all_urls>"],
"exclude_matches": [],
"js": [
"/src/jquery.js",
"/src/sheet-min.js",
"/src/file-saver-min.js"
]
// "css": [
// "js/content/page.css"
// ]
}
],
"content_scripts": [{
"matches": ["*://*.ebay.com/*"],
"js": ["content.js"],
"run_at": "document_idle",
"all_frames": false
}],
"browser_action": {
"default_title": "Download History.",
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"manifest_version": 2
}
background.js
chrome.runtime.onMessage.addListener((msg, sender) => {
// First, validate the message's structure.
if ((msg.from === 'content') && (msg.subject === 'showPageAction')) {
// Enable the page-action for the requesting tab.
chrome.browserAction.show(sender.tab.id);
}
});
content.js
// Inform the background page that
// this tab should have a page-action.
function ping() {
chrome.runtime.sendMessage('ping', response => {
if(chrome.runtime.lastError) {
setTimeout(ping, 1000);
} else {
chrome.runtime.sendMessage({
from: 'content',
subject: 'showPageAction',
});
}
});
}
ping();
// Listen for messages from the popup.
chrome.runtime.onMessage.addListener((msg, sender, response) => {
// First, validate the message's structure.
if ((msg.from === 'popup') && (msg.subject === 'DOMInfo')) {
// Collect the necessary data.
// (For your specific requirements `document.querySelectorAll(...)`
// should be equivalent to jquery's `$(...)`.)
var domInfo = {
total: document.querySelectorAll('*').length,
inputs: document.querySelectorAll('input').length,
buttons: document.querySelectorAll('button').length,
};
// Directly respond to the sender (popup),
// through the specified callback.
response(domInfo);
}
});
popup.js
const setDOMInfo = info => {
console.log(info)
};
window.addEventListener('DOMContentLoaded', () => {
// ...query for the active tab...
chrome.tabs.query({
active: true,
currentWindow: true
}, tabs => {
// ...and send a request for the DOM info...
chrome.tabs.sendMessage(
tabs[0].id,
{from: 'popup', subject: 'DOMInfo'},
// ...also specifying a callback to be called
// from the receiving end (content script).
setDOMInfo);
});
});
I know that this error occurs when the content script sends message to the background script but the background script is not ready to receive the message. After looking for a solution on stackoverflow I decided to use the ping function but as you can see above but it still gives me the same error message.
There's no chrome.browserAction.show as you can see in the documentation so the listener in background.js throws and aborts the execution. The messaging cycle never completes so to the sender it looks just like an absence of any receiver.
Each part of an extension has its own devtools.
Open devtools for the background script and you'll see the error.
There's no need for the background script here.
No need for showPageAction message either because browser_action is enabled by default.
P.S. the entire code can be simplified by switching to programmatic injection (example) so you can remove content_scripts, background script, and messaging.

Categories