Can't inject JavaScript into new tab from popup.js - google-chrome

I'M creating an extension that autofill's forms on different websites. There is a portion of the script that records the actions of the user with event listeners. After the recording is done the user click the test button that will launch a new tab and autofill's the form. I know launching a new tab for testing is not necessary but this function will be used later for another feature.
This is what I got so far. I had started over several times. I tried sending a message to content script. The chrome.windows,create function only launched from the popup script. I started chrome development a week ago. I'm totally confused.
Popup.js
`(async () =\> { const tab = await chrome.tabs.create({url: 'https://www.listyourself.net/ListYourself/listing.jsp'});
const tabId = tab.id; if (!tab.url)
await onTabUrlUpdated(tabId);
await chrome.scripting.executeScript({
target: {tabId},
files: \['test.js'\],
});`
})();`

I think your extension has two problems.
onTabUrlUpdated is not defined.
Execution of chrome.tabs.create causes the popup to lose focus, so the JavaScript is also terminated.
Here is a sample that fixes it.
manifest.json
{
"manifest_version": 3,
"name": "hoge",
"version": "1.0",
"permissions": [
"scripting"
],
"host_permissions": [
"<all_urls>"
],
"background": {
"service_worker": "background.js"
},
"action": {
"default_title": "hoge"
}
}
background.js
chrome.action.onClicked.addListener(() => {
main();
});
const test = () => {
console.log("test");
}
const main = async () => {
const tab = await chrome.tabs.create({ url: "https://nory-soft.web.app/" });
const tabId = tab.id;
if (!tab.url) {
chrome.tabs.onUpdated.addListener((updateTabId, changeInfo) => {
if ((updateTabId == tabId) && (changeInfo.status == "complete")) {
chrome.scripting.executeScript({
target: { tabId },
func: test
});
}
});
}
}

Related

How can I focus a document programmatically from a Chrome extension (to paste content)

What I try to do is simple:
I am making a chrome extension.
This extension has a shortcut (e.g. alt + o).
When I press this shortcut it switches to a tab,
and should paste clipboard content in the page.
Here's the background script that reacts to the shortcut and inject code in page to simulate paste event:
// --- background.js
chrome.commands.onCommand.addListener((command) => {
switch (command) {
case 'switch_tab_and_paste_content':
switchToTabAndPasteContent()
break;
}
})
async function switchToTabAndPasteContent() {
const tab = (await chrome.tabs.query({})).filter((tab) => tab.title.toLowerCase().includes('my page'))[0]
// focus the window
await chrome.windows.update(tab.windowId, { focused: true })
// and focus the tab
await chrome.tabs.update(tab.id, { active: true })
/* inject paste emulation script */
await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: function () {
pasteContent()
}
})
}
pasteContent() in the code above is a function contained in a content script. It's used to simulate paste event (just like a user would use ctrl + v on some page.)
// --- paste.js
async function pasteContent() {
try {
const wizElement = document.querySelector('c-wiz')
const clipboardItems = await navigator.clipboard.read()
const firstItem = clipboardItems[0]
const blob = await firstItem.getType('image/png')
const file = new File([blob], 'image.png', { type: 'image/png' })
const dataTransfer = new DataTransfer()
dataTransfer.items.add(file)
const pasteEvent = new ClipboardEvent('paste', {
bubbles: true,
cancelable: true,
clipboardData: dataTransfer,
})
wizElement.dispatchEvent(pasteEvent)
}
catch (e) { console.log(e) }
}
and here's the simplified manifest.json
{
"name": "app-connector",
"version": 3,
"permissions": [ "tabs", "scripting", "activeTab" ],
"background": {
"service_worker": "background.js"
},
"content_scripts": [{
"matches": [ ... ],
"js": "paste.js"
}],
"commands": {
"switch_tab_and_paste_content": {
"description": "Switch tab"
}
}
}
However when I press the shortcut on my keyboard, the tab is focused but I get this exception
DOMException: Document is not focused.
I know the reason of that exception (it's because when I leave the tab the document loses focus, chrome.tabs.update(tab.id, { active: true }) activates the tab but it's not enough to focus back on the document)
What I want to know is if there is a way to bypass that (a chrome flag, a command-line argument, etc...).
Of course the ideal would be a native solution in the extension ecosystem but I didn't find/can't think of any solution. I tried opening/closing a popup fast to give focus back to the page but I still get this exception (it seems like it won't work unless I click in the page but it defeats the purpose of making an automated routine behind).
am I missing a permission?

Chrome Extension embedding script in active web page, in MV3?

beautiful people on the internet. I am new to chrome extension not new to writing code though. I have implemented webpack to use external packages. One major one in my application is npm package by the name "mark.js".
My application works like this i want some specific words to be highlighted in active webpage using this package. I have written code for this to achieve the functionality but the problem is with loading the script in a web page. I have performed different aspect of loading script but that doesnot work. The new MV3 version have some strict rules.
I want to achieve anything similar of loading script in an active webpage. Please help.
btn.addEventListener("click", async () => {
console.log("BUTTON IS PRESSED!!");
try {
await chrome.tabs.query(
{ active: true, currentWindow: true },
async function (tabs) {
chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: highlightText,
args: [arr],
});
}
);
} catch (e) {
console.log("ERROR AT CHROME TAB QUERY : ", e);
}
});
async function highlightText(arr) {
console.log(typeof Mark);
try {
var instance2 = new Mark(document.querySelector("body"));
// instance2.mark("is");
var success = [];
// const instance2 = new Mark(document.querySelector("body"));
await Promise.all(
arr.map(async function (obj) {
console.log("OBJECT TEXT : ", obj.text);
instance2.mark(obj.text, {
element: "span",
each: function (ele) {
console.log("STYLING : ");
ele.setAttribute("style", `background-color: ${obj.color};`);
if (obj.title && obj.title != "") {
ele.setAttribute("title", obj.title);
}
ele.innerHTML = obj.text;
success.push({
status: "Success",
text: obj.text,
});
},
});
})
);
console.log("SUCCESS : ", success);
} catch (error) {
console.log(error);
}
}
There's no need to use chrome.runtime.getURL. Since you use executeScript to run your code all you need is to inject mark.js before injecting the function.
Also, don't load popup.js in content_scripts, it's not a content script (these run in web pages), it's a script for your extension page. Actually, you don't need content_scripts at all.
btn.addEventListener('click', async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const target = { tabId: tab.id };
const exec = v => (await chrome.scripting.executeScript({ target, ...v }))[0].result;
if (!await exec({ func: () => !!window.Mark })) {
await exec({files: ['mark.js.min'] });
await exec({func: highlightText, args: [arr] });
}
});
For V3 I assume you will want to use Content Scripts in your manifest to inject the javascript into every webpage it matches. I recently open-sourced TorpedoRead and had to do both V2 and V3, I recommend checking the repo as it sounds like I did something similar to you (Firefox is V2, Chrome is V3).
The code below need to be added to your manifest.json and this will execute on every page based on the matches property. You can read more about content scripts here: https://developer.chrome.com/docs/extensions/mv3/content_scripts/
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["yourscript.js"]
}
],

Why Chrome DevTools Network shows no response?

I've been trying to fetch some url using Devtools 'copy as fetch' but I can see no response. If I try replay xhr, it will work. It's strange since it doesn't work for this particular site, but if I try 'copy as fetch' on some others it does bring a result.
What I'm trying to do is grab the response body and display it some other way (it's a schedling software, and I'm trying to modify the way it displays the calendar view since it displays everything together).
I have an extension that enables me to modify XMLHttpRequest so I can get the response of any XHR, but since the first async executes before I inject the script, then I'm always missing the first one.
I plan on using chrome webRequest to stop the first one and fetch it again.
manifest.json
{
"name": "jobber",
"version": "2.0",
"description": "Build an Extension!",
"manifest_version": 2,
"permissions": [
"webNavigation",
"webRequest",
"*://secure.name.com/*"
],
"content_scripts": [{
"matches": ["*://secure.name.com/calendar*"],
"js": ["contents.js"],
"run-at": "document_start"
}],
"externally_connectable": {
"matches": ["*://secure.name.com/calendar*"]
},
"background": {
"scripts": ["background.js"]
}
}
contents.js
(function () {
'use strict';
let s = document.createElement("script");
s.textContent = overloadXHR();
document.head.insertBefore(s, document.head.children[0]);
s = document.createElement("script");
s.textContent = displayCalendar;
document.head.insertBefore(s, document.head.children[0]);
})();
function overloadXHR() {
const text = `
console.log(\`overriding: \${Date.now()}\`);
const rawOpen = XMLHttpRequest.prototype.open;
let json = [];
(function(){
XMLHttpRequest.prototype.open = function () {
this.addEventListener("readystatechange", e => {
if (/secure.name.com.calendar.*?calendar=true/i.test(this.responseURL)) {
if ((this.status == 200) && (this.readyState == 4)) {
console.log(this.readyState);
try {
json = JSON.parse(this.response);
window.setTimeout(() => (window.displayCalendar({json}))({json}), 1000);
}
catch (e) { console.log(e); }
}
}
});
rawOpen.apply(this, arguments);
}
}())
`;
return text;
}
function displayCalendar({ json }) {
// do something
}
I tried POST requests too. I could see that they would work, but no response given tho.
original request:
copy as fetch
copy as fetch response
copy as fetch timing

Add PWA to home screen not working on chrome mobile

I recently got into front end developpement to make an interface for a nodejs server hosted on a raspberry pi.
I heard of progressive web app and wanted the user to be able to install it on his phone.
So here are the manifest and the service worker script.
Manifest:
{
"name": "Philips D6330",
"short_name": "Philips D6330",
"description": "A control app for the Philips D6330",
"icons": [
{
"src": "https://192.168.1.26/cdn/favicon.png",
"sizes": "512x512",
"type": "image/png"
}
],
"start_url": "https://192.168.1.26",
"display": "fullscreen",
"orientation": "portrait",
"theme_color": "#333333",
"background_color": "#333333",
"scope": "/"
}
Service Worker:
const CACHE_NAME = 'cache';
const CACHE = [
'/',
'/cdn/offline.html',
'/cdn/style.css',
'/cdn/favicon.png',
'/cdn/linetoB.ttf',
'/cdn/linetoL.ttf',
'/cdn/neon.ttf',
'/cdn/not.png',
'/cdn/next.png',
'/cdn/pause.png',
'/cdn/play.png',
'/cdn/previous.png',
'/cdn/dots.png',
'/cdn/rfid.png'
];
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
return cache.addAll(CACHE)
})
.then(self.skipWaiting())
)
})
self.addEventListener('fetch', function(event) {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request)
.catch(() => {
return caches.open(CACHE_NAME)
.then((cache) => {
return cache.match('/cdn/offline.html');
})
})
);
}
else {
event.respondWith(
fetch(event.request)
.catch(() => {
return caches.open(CACHE_NAME)
.then((cache) => {
return cache.match(event.request)
})
})
);
}
})
self.addEventListener('activate', function(event) {
event.waitUntil(
caches.keys()
.then((keyList) => {
return Promise.all(keyList.map((key) => {
if (key !== CACHE_NAME) {
console.log('[ServiceWorker] Removing old cache', key)
return caches.delete(key)
}
}))
})
.then(() => self.clients.claim())
)
})
I think also relevant to tell you that all of this happens on my local network so my node server uses https with a self-signed certificate made using this tutorial : https://medium.com/#tbusser/creating-a-browser-trusted-self-signed-ssl-certificate-2709ce43fd15
But even tho it's a self-signed certificate, the service worker seem to registers well on firefox and chrome, as it stores the files and displays the offline page when offline.
Here is my problem :
when I want to install it from the desktop version of chrome I can but not chrome mobile (or samsung internet)...
here is the piece of code i use to make it instalable :
<script defer>
window.addEventListener('beforeinstallprompt', (event) => {
console.log('👍', 'beforeinstallprompt', event);
// Stash the event so it can be triggered later.
window.deferredPrompt = event;
});
butInstall.addEventListener('click', () => {
console.log('👍', 'butInstall-clicked');
const promptEvent = window.deferredPrompt;
if (!promptEvent) {
// The deferred prompt isn't available.
return;
}
// Show the install prompt.
promptEvent.prompt();
// Log the result
promptEvent.userChoice.then((result) => {
console.log('👍', 'userChoice', result);
// Reset the deferred prompt variable, since
// prompt() can only be called once.
window.deferredPrompt = null;
});
});
window.addEventListener('appinstalled', (event) => {
console.log('👍', 'appinstalled', event);
});
</script>
It comes from here https://web.dev/codelab-make-installable/
Here is a screenshot of the before install prompt event with the lighthouse report if it can help (by the way the plus sign in the url shows it's working):
console log infos
But on mobile the plus sign doesn't show up and nothing happens when I click the button... And as I don't have acces to the console I can't see any errors...
------ Edit ------
After using alert to log what the console says, I think the problem comes from the service worker does register well because I get this :
"ServiceWorker registration failed: SecurityError: Failed to register a ServiceWorker for scope ('https://192.168.1.26/') with script ('https://192.168.1.26/sw.js'): An SSL certificate error occurred when fetching the script.".
Is there a way to make the browser trust my self-singed certificate ?
Any help, suggestion or comment is welcome ^^
Its a .webmanifest and you dont match the criteria. You need an 512x512 AND an 192x192 icon.
As start_url i would just use /?source=pwa
"icons": [
{
"src": "icon.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "big_icon.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": "/?source=pwa",
After that you need to link your webmanifest with <link>
<link rel="manifest" href="/manifest.webmanifest">
Check if the paths are all correct.
Here is my own little "library" to abstract things: https://github.com/ilijanovic/serviceHelper (its under development tho)
If you met all criteria, then you can make your app installable
Here its how it works with my library. You instantiate the class and pass the path to the service worker.
var sh = new ServiceHelper("./sw.js");
sh.init().then(response => {
console.log("Initialized");
})
sh.on("notinstalled", (e) => {
console.log("App is not installed");
//do some stuff. For example enable some button
//button.classList.remove("disabled");
})
butInstall.addEventListener('click', () => {
sh.installApp().then(e => {
// e = user choice event
console.log(e);
}).catch(err => {
// error if the app is already installed
console.log(err);
})

Chrome extension sendResponse not working

There are numerous versions of this question, and I have reviewed them and tried many things to the best of my ability without success. I am new to chrome extensions, and I could be missing many things, but I don't know what.
First, I want to point out that I have copied several claimed working examples from various answers, and none of them work for me. I am using Chrome Version 58.0.3029.81 on Windows 7 Professional.
Whether it's my code, or examples I have copied, the code in the content script executes, and calls sendResponse with the correct response. Beyond that, nothing - the specified callback function is never executed.
Here is my current code:
manifest.json
{
"manifest_version": 2,
"name": "TestExtension",
"description": "...",
"version": "1.0",
"content_scripts": [
{
"matches": [
"http://*/*",
"https://*/*"
],
"js": [ "content.js" ]
}
],
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html",
"default_title": "Send a checkpoint request"
},
"permissions": [
"activeTab",
"tabs",
"http://*/*", // allow any host
"https://*/*" // allow any secure host
]
}
popup.js
document.addEventListener('DOMContentLoaded', function ()
{
document.getElementById('extension_form').onsubmit = function ()
{
var elementName = "DataAccessURL";
chrome.tabs.query(
{
active: true,
currentWindow: true
},
function (tabs)
{
chrome.tabs.sendMessage(
tabs[0].id,
{ Method: "GetElement", data: elementName },
function (response)
{
debugger;
console.log("response:Element = " + response.Element);
alert(response.Element);
});
});
};
});
content.js
chrome.runtime.onMessage.addListener(function (request, sender, sendResponse)
{
console.log("In Listener");
if (request.Method && request.Method == "GetElement")
{
var result = "";
var element = request.data;
console.log(element);
var e = document.getElementById(element);
if (e) result = e.innerHTML;
console.log(result);
sendResponse({ Element: result });
return true; // <-- the presence or absence of this line makes no difference
}
});
Any clues would be appreciated. I simply do not understand why my callback is not being called.
User wOxxOm provided this answer in his comments.
In the popup.js example, I am attaching to the form 'onsubmit' event to trigger my code.
I had previously read that if the popup gets closed, it's code would no longer be there to be run. I could not visually tell in my case that, as wOxxOm pointed out, that the submit event reloads the popup. That was disconnecting my code.
In order to prevent the popup from reloading, I needed to prevent the default action on the submit event. I added an 'evt' parameter to accept the event argument to the submit, and called preventDefault as shown below.
document.getElementById('extension_form').onsubmit = function (evt)
{
evt.preventDefault();
...
This allowed the sample to work as expected.
Thanks wOxxOm!