Function inside "DOMContentLoaded" event does not get triggered - javascript

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);
}
}

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

Copy to clipboard in chrome extension V3

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)

chrome extension inject javascript at top of head before any other js

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);

How to refresh content script for chrome extension as DOM changes

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

How to get notified on window resize in chrome browser

I am writing an extension for the Chrome browser where I want to add an event listener for the window resize event. My method is being executed for the window load event, but not being executed for the resize event.
Below is the code for my manifest.json file
{
"name": "A browser action",
"version": "1.0",
"background": { "scripts": ["background.js"] },
"permissions": [
"tabs", "http://*/*"
],
"manifest_version": 2
}
Below is the code for my background.js file.
var myExtension =
{
init: function()
{
// The event can be DOMContentLoaded, pageshow, pagehide, load or unload.
alert("ASHSIH");
window.addEventListener("resize", this.onmyPageResize, false);
},
onmyPageResize: function(aEvent)
{
alert("RESIZED");
}
}
window.addEventListener("load", function load(event){
window.removeEventListener("load", load, false); //remove listener, no longer needed
myExtension.init();
},false);
Chrome-o-Tile is one example of an extension which listens resize in its content script.
In manifest.json:
"content_scripts": [
{
"js": ["contentscript.js"],
"run_at": "document_start",
"matches": [
"<all_urls>"
]
}
],
In contentscript.js:
'use strict';
var timeoutId = 0;
window.addEventListener('resize', function() {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(function() {
chrome.runtime.sendMessage({method: 'resize'});
timeoutId = 0;
}, 100);
}, false);
In background.js:
chrome.runtime.onMessage.addListener(function requested(request) {
if (request.method === 'resize') {
...
}
)};
There is also an open issue to implement chrome.windows.onResize event for Chrome extensions.
The background.js file cannot capture resize events in the browser. You would need to inject a content script for that.
For future readers of this question, the chrome.windows API provides an onBoundsChanged event:
onBoundsChanged (Since Chrome 86)
Fired when a window has been resized; this event is only dispatched when the new bounds are committed, and not for in-progress changes.
https://developer.chrome.com/extensions/windows#event-onBoundsChanged
This is how the event can be used: chrome.windows.onBoundsChanged.addListener( callback() )
(Make sure the manifest.json file declares the tabs permission)
{
"name": "My extension",
...
"permissions": ["tabs"],
...
}
You can use jQuery's bind() function.. http://api.jquery.com/bind/
$(window).bind('resize', function () {
//do something here
});

Categories