Copy to clipboard in chrome extension V3 - javascript

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)

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

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.

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

Google Extension: Access to DOM in iframe of a different domain

So I've found several pages on here, as well as various blog posts that seem to do pretty much exactly what I want to do, but they are all a few years old and seem really easy but don't work.
As the title says, On thisdomain.com there is a iframe from thatdomain.com and I want to get the value in a div in that iframe.
Manifest.json
{
"manifest_version": 1,
"name": "MyExtention",
"version": "1.0",
"description": "Nothing Yet",
"permissions": [
"storage",
"tabs",
"unlimitedStorage",
"webRequest",
"webNavigation",
"*://*.match-both-iframe-and-main-domain.com/*",
"*://*/*"
],
"background": {
"scripts": ["listener.js"],
"persistent": true
},
"content_scripts":
[
{
"matches": ["*://*.matchnothing.shshdjdjffkdj.com/*"],
"js": ["mainscript.js"],
"all_frames": true
}
]
}
The content script url matches nothing because it is fired from a listener (which works). Basically it waits for a request from one of 2 urls before it activates.
listener.js
var chrome = chrome || {};
var callback = function(listenerRes) {
console.log(listenerRes.url);
if (listenerRes.url.indexOf("listenurl1") > -1 ||
listenerRes.url.indexOf("listenurl2") > -1) {
chrome.tabs.get(listenerRes.tabId, function(tab) {
chrome.tabs.executeScript(tab.id, {file: "mainscript.js"});
});
}
};
chrome.webRequest.onBeforeRequest.addListener( callback, {urls: ["*://*.google.com/*"]} );
mainscript.js
var chrome = chrome || {};
... // helper functions and such
var iframe = document.getElementsByid('myiframe');
// Get all data.
var datas = [];
try {
datas = iframe.contentWindow.document.getElementsByClassName('mydata'); // error is here
}catch(e){
console.log(e);
}
... // do stuff with the data
On the commented line it throws a "Blocked a frame with origin URL from accessing a cross-origin frame."
So I am under the impression that some combination of all_frames = true, the chrome.tabs.executeScript, and the domains in the permissions should allow for this to work. But it doesn't.
It might be important to note, the reason for this listener is because the iframe isnt on the page to start.
Please help, Im an experienced web developer but this is my 1st foray into Chrome Extentions.

Message Passing issues in Chrome Extension

I'm trying to pass a message from my content script to my background page. This error occurs when the content script is executed:
Uncaught TypeError: Cannot call method 'sendRequest' of undefined
Content Script:
function injectFunction(func, exec) {
var script = document.createElement("script");
script.textContent = "-" + func + (exec ? "()" : "");
document.body.appendChild(script);
}
function login() {
chrome.extension.sendMessage({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
var d = window.mainFrame.document;
d.getElementsByName("email")[0].value = "I need the response data here";
d.getElementsByName("passwort")[0].value = "Here too.";
d.forms["login"].submit();
}
injectFunction(login, true);
Background:
chrome.extension.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.greeting == "hello")
sendResponse({farewell: "goodbye"});
});
manifest.json:
{
"name": "Sephir Auto-Login",
"version": "1.0",
"manifest_version": 2,
"description": "Contact x#x.com for support or further information.",
"options_page": "options.html",
"icons":{
"128":"icon.png"
},
"background": {
"scripts": ["eventPage.js"]
},
"content_scripts": [
{
"matches": ["https://somewebsite/*"],
"js": ["login.js"]
},
{
"matches": ["somewebsite/*"],
"js": ["changePicture.js"]
}
],
"permissions": [
"storage",
"http://*/*",
"https://*/*",
"tabs"
]
}
Those are the examples on the documentation from google, so they should work.
Any help? I'm completely lost.
The problem is caused by your misunderstanding of the script executing environment. Read Chrome extension code vs Content scripts vs Injected scripts for more information. To be precise, you're using a form of this method to execute code in the context of a web page. Web pages do not have any access to the chrome.extension API.
I suggest to rewrite your code to not use injected scripts, because it's not necessary in this case.
function login() {
chrome.extension.sendRequest({greeting: "hello"}, function(response) {
console.log(response.farewell);
});
var d = document.getElementById('mainFrame').contentDocument;
d.getElementsByName("email")[0].value = "I need the response data here";
d.getElementsByName("passwort")[0].value = "Here too.";
d.forms["login"].submit();
}
login();
* Only works if the frame is located at the same origin. Otherwise, you need this method to execute code correctly.
sendRequest and onRequest are deprecated. You need to use sendMessage and onMessage.
Also, you are injecting function to the DOM, that makes it run outside the content script context so chrome.extension API is no longer available for this function.

Categories