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
Related
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);
}
}
I created an extension, where I placed a script into a page markup and performs some actions according to the following article:
var s = document.createElement('script');
s.src = chrome.runtime.getURL('code.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
I also displayed a checkbox in "default_popup" which should indicate whether to execute a part of methods in script from "code.js" (web_accessible_resources) or not.
However, I have no idea how to interact between the script from "content_scripts" (which has access to "default_popup") and the script from "web_accessible_resources".
Could you suggest something?
I understand that I can completely replace the "web_accessible_resources" script, but this does not seem to be the best practice.
Thank you.
This is a sample of communication between popup and tab.
manifest.json
{
"name": "content_scripts + popup",
"version": "1.0",
"manifest_version": 3,
"content_scripts": [
{
"js": [
"matches.js"
],
"matches": [
"<all_urls>"
]
}
],
"host_permissions": [
"<all_urls>"
],
"action": {
"default_popup": "popup.html"
}
}
popup.js
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
console.log("Send");
chrome.tabs.sendMessage(tabs[0].id, "message", (response) => {
console.log("Recv response = " + response.title);
document.getElementById("title").innerText = response.title;
});
});
matches.js
console.log("matches.js");
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
console.log("Recv. Send response = " + document.title);
sendResponse({ title: document.title });
return true;
});
popup.html
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
* {
font-size: x-large;
}
</style>
</head>
<body style="min-width:300px">
<div id="title"></div><br>
<script src="popup.js"></script>
</body>
</html>
I'm not sure exactly what you're asking but I'm assuming you have 3 things: (1) a pop up, (2) a content script, and (3) a script injected into the page, running in page context. Firstly, to send messages to the popup, you can run chrome.runtime.sendMessage() and have a listener in your popup's script.js file with chrome.runtime.onMessage(). To get the data from the page script is different, it cannot communicate with your browser extension directly. So, from your page context script, you can create an element with a unique ID and store some data in an attribute. Use a modification of a MutationObserver to run a waitForElm() function (see this thread - How to wait until an element exists?). In your content script, you'll wait for the element to be created, collect data from the attribute you set, and then run chrome.runtime.sendMessage() as mentioned earlier to get that data to your pop up.
I am developing a chrome extension V3. I want to copy content to clipboard in my JS file.
The manifest.json as below,
"background" :{
"service_worker" :"eventPage.js"
},
"permissions" : [
"contextMenus",
"clipboardWrite"
]
I have try 2 solution for copy feature.
Solution 1:
const el = document.createElement('textarea');
el.value = str;
el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
The result:
Error in event handler: ReferenceError: document is not defined at copyToClipboard
Solution 2:
navigator.clipboard.writeText(str);
The result:
Error in event handler: TypeError: Cannot read properties of undefined (reading 'writeText')
The chrome extension is run as a service worker. So it seems I can't access DOM document and have no grant of writeText. Does anyone have another suggestion?
Thanks.
I'll follow the excellent suggestion wOxxOm gave you, elaborating it in a concrete example. What you want to do is have a ContentScript.js that runs on any active tab with a web page, since you can't access the DOM from the backGround.js, and then send a message to this script, from where you would copy to the clipboard.
manifest.json
"background" :{
"service_worker" :"eventPage.js"
},
"permissions" : [
"contextMenus",
"clipboardWrite"
],
"content_scripts": [ // this is what you need to add
{
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
From the background.js, you would send a message, that will be handled in the ContentScript.js
background.js
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id,
{
message: "copyText",
textToCopy: "some text"
}, function(response) {})
})
In the contentScript.js, you would catch the message and copy it to the clipboard.
content.js
chrome.runtime.onMessage.addListener( // this is the message listener
function(request, sender, sendResponse) {
if (request.message === "copyText")
copyToTheClipboard(request.textToCopy);
}
);
async function copyToTheClipboard(textToCopy){
const el = document.createElement('textarea');
el.value = textToCopy;
el.setAttribute('readonly', '');
el.style.position = 'absolute';
el.style.left = '-9999px';
document.body.appendChild(el);
el.select();
document.execCommand('copy');
document.body.removeChild(el);
}
I'm not an expert in Chrome addons, but I believe that requesting <all_urls> is not best practice.
Instead, I got something to work by request activeTab instead, ditching the content scripts and injecting code to the active tab instead.
Manifest:
...
"permissions": [
"clipboardWrite"
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
...
Background:
// To be injected to the active tab
function contentCopy(text) {
navigator.clipboard.writeText(text);
}
async function copyLink(text, tab) {
// Format as a whatsapp link
const link = await getWhatsAppLink(text);
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: contentCopy,
args: [link],
});
}
So contentCopy is injected to the active tab and executed with the text I want to copy to clipboard.
Works well, and saves on problematic permissions (according to Google)
What I want to do.
Using a Chrome extension I need to inject a script into the page context in such a way that the injected script runs before any other javascript on the page.
Why do I need this?
I need to hijack all console commands for a specific page so my extension can listen to the messages.
My current issue
Currently I am catching some of the messages logged by the page but not all of them, specifically, all messages from web-directory-132a3f16cf1ea31e167fdf5294387073.js are not being caught. After some digging I discovered that the web-directory-132a3f16cf1ea31e167fdf5294387073.js is also hijacking console but doing so before my script has a chance to.
As a visual, if I look at the network tab after loading the page, I see this:
My injected script is consoleInterceptor.js. It correctly captures the output of the js files that are loaded here accept web-directory-132a3f16cf1ea31e167fdf5294387073.js
Inside of web-directory-132a3f16cf1ea31e167fdf5294387073.js is some code something like this:
....
_originalLogger: t.default.Logger,
...
// do stuff with logging ....
this._originalLogger[e].apply(this._originalLogger, s),
What I think the problem is
It seems to me that, web-directory-132a3f16cf1ea31e167fdf5294387073.js is grabbing the standard console functions and storing them internally before my script has had a chance to replace them with my own versions. So even though my script works, the web-directory-132a3f16cf1ea31e167fdf5294387073.js still uses the original standard console functions it saved.
Note that web-directory-132a3f16cf1ea31e167fdf5294387073.js is an ember application and I dont see any simple way to hook into that code to overwrite those functions too but Im open to that as a solution.
My current code:
manifest.js
...
"web_accessible_resources": [
"js/ajaxInterceptor.js",
"js/consoleInterceptor.js"
],
"version" : "5.2",
"manifest_version": 2,
"permissions": [
"<all_urls>",
"tabs",
"activeTab",
"storage",
"webNavigation",
"unlimitedStorage",
"notifications",
"clipboardWrite",
"downloads",
"tabCapture",
"cookies",
"browsingData",
"webRequest",
"*://*/*",
"gcm",
"contextMenus",
"management"
],
"externally_connectable": {
"matches": ["*://apps.mypurecloud.com/*","*://*.cloudfront.net/*"]
},
...
background.js
var options = {url: [{hostContains: 'apps.mypurecloud.com'}]};
chrome.webNavigation.onCommitted.addListener(function(details) {
// first inject the chrome extension's id
chrome.tabs.executeScript(details.tabId, {
code: "var chromeExtensionId = " + JSON.stringify(chrome.runtime.id)
});
// then inject the script which will use the dynamically added extension id
// to talk to the extension
chrome.tabs.executeScript(details.tabId, {
file: 'js/injectConsoleInterceptor.js'
});
},
options
);
chrome.runtime.onMessageExternal.addListener(
function(msg, sender, sendResponse) {
if(msg.action === 'console_intercepted'){
_this.processConsoleMessage(sender, msg.details.method, msg.details.arguments);
}
});
injectConsoleInterceptor.js
var interceptorScript = document.createElement('script');
interceptorScript.src = chrome.extension.getURL('js/consoleInterceptor.js');
interceptorScript.onload = function(){this.remove();};
(document.head || document.documentElement).prepend(interceptorScript);
consoleInterceptor.js
if(!window.hasConsoleInterceptor){
window.hasConsoleInterceptor = true;
console.log('overriding console functions');
var originals ={};
var console = window.console;
if (console){
function interceptConsoleMethod(method){
originals[method] = console[method];
console[method] = function(){
// send the data to the extension
// chromeExtensionId should be injected into the page separately and before this script
var data = {
action: 'console_intercepted',
details: {
method: method,
arguments: arguments
}
};
chrome.runtime.sendMessage(chromeExtensionId, data);
originals[method].apply(console, arguments)
}
}
// an array of the methods we want to observe
var methods = ['assert', 'count', 'debug', 'dir', 'dirxml', 'error', 'group','groupCollapsed','groupEnd','info','log', 'profile', 'profileEnd','time','timeEnd','timeStamp','trace','warn','table'];
for (var i = 0; i < methods.length; i++){
interceptConsoleMethod(methods[i])
}
console.log('Successfully overridden console functions: '+methods.join(','));
}
}
My question
What can I do to make consoleInterceptor.js run before web-directory-132a3f16cf1ea31e167fdf5294387073.js loads so that web-directory-132a3f16cf1ea31e167fdf5294387073.js uses my modified console functions rather than the default browser console funcitons?
You can try this.In manifest.json file:
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["script/inject.js"],
"run_at":"document_start"
}
]
In inject.js:
var ss = document.createElement("script");
ss.innerHTML= "xxx";
document.documentElement.appendChild(ss);
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