I want to update extension from manifest v2 to manifest v3. previously I was using tabs api now I have to use scripting api. The problem is I am executing multiple script but in scripting api I have to create multiple files for code or I have to create multiple functions. so is there any better way to do this?
This is what I have in mv2 and I have 7-8 scripts like this in my code
chrome.tabs.executeScript(tab.id, {
code: 'document.querySelector("#recv_address > span").textContent'
}, display_location);
I have tried below code and it is working fine but is there any better way to do this because I want to do same thing for 7-8 scripts
function passScript() {
let passQuery = document.querySelector("#recv_address > span").textContent;
return passQuery;
}
chrome.scripting.executeScript(
{
target: { tabId: tab.id },
func: passScript,
},
display_location
);
If you have many files. You can do it like this in your service worker.
Background.js
const scriptList = ["js/a.js", "js/b.js", "js/c.js"];
scriptList.forEach((script) => {
chrome.scripting.executeScript({
target: {tabId: tabId},
files: [`${script}`],
injectImmediately: true
}, () => void chrome.runtime.lastError);
});
Manifest.json
"permissions": ["scripting"],
The API you're using is correct, but know that you can also pass arguments to func, for example:
function passScript(selector) {
let passQuery = document.querySelector(selector).textContent;
return passQuery;
}
chrome.scripting.executeScript(
{
target: { tabId: tab.id },
func: passScript,
args: ["#recv_address > span"]
},
display_location
);
Related
I have a logging mechanism in place that saves the logs into an array. And I need a way to download the logs into a file.
I had this previously working (on manifest v2) with
const url = URL.createObjectURL(new Blob(reallyLongString, { type: 'text/plain' }));
const filename = 'logs.txt';
chrome.downloads.download({url, filename});
Now I am migrating to manifest v3 and since manifest v3 does not have URL.createObjectURL, you cannot create a url to pass to chrome.downloads.download
Instead it is possible to create a Blob URL using something like
const url = `data:text/plain,${reallyLongString}`;
const filename = 'logs.txt';
chrome.downloads.download({url, filename});
The problem is that chrome.downloads.download seems to have a limit on the number of characters passed in the url argument, and the downloaded file only contains a small part of the string.
So what would be a way to overcome this limitation?
Hopefully, a way to download Blob directly in service worker will be implemented in https://crbug.com/1224027.
Workaround via an extension page
Here's the algorithm:
Use an already opened page such as popup or options
Otherwise, inject an iframe into any page that we have access to
Otherwise, open a new minimized window
async function downloadBlob(blob, name, destroyBlob = true) {
// When `destroyBlob` parameter is true, the blob is transferred instantly,
// but it's unusable in SW afterwards, which is fine as we made it only to download
const send = async (dst, close) => {
dst.postMessage({blob, name, close}, destroyBlob ? [await blob.arrayBuffer()] : []);
};
// try an existing page/frame
const [client] = await self.clients.matchAll({type: 'window'});
if (client) return send(client);
const WAR = chrome.runtime.getManifest().web_accessible_resources;
const tab = WAR?.some(r => r.resources?.includes('downloader.html'))
&& (await chrome.tabs.query({url: '*://*/*'})).find(t => t.url);
if (tab) {
chrome.scripting.executeScript({
target: {tabId: tab.id},
func: () => {
const iframe = document.createElement('iframe');
iframe.src = chrome.runtime.getURL('downloader.html');
iframe.style.cssText = 'display:none!important';
document.body.appendChild(iframe);
}
});
} else {
chrome.windows.create({url: 'downloader.html', state: 'minimized'});
}
self.addEventListener('message', function onMsg(e) {
if (e.data === 'sendBlob') {
self.removeEventListener('message', onMsg);
send(e.source, !tab);
}
});
}
downloader.html:
<script src=downloader.js></script>
downloader.js, popup.js, options.js, and other scripts for extension pages (not content scripts):
navigator.serviceWorker.ready.then(swr => swr.active.postMessage('sendBlob'));
navigator.serviceWorker.onmessage = async e => {
if (e.data.blob) {
await chrome.downloads.download({
url: URL.createObjectURL(e.data.blob),
filename: e.data.name,
});
}
if (e.data.close) {
window.close();
}
}
manifest.json:
"web_accessible_resources": [{
"matches": ["<all_urls>"],
"resources": ["downloader.html"],
"use_dynamic_url": true
}]
Warning! Since "use_dynamic_url": true is not yet implemented don't add web_accessible_resources if you don't want to make your extension detectable by web pages.
Workaround via Offscreen document
Soon there'll be another workaround: chrome.offscreen.createDocument instead of chrome.windows.create to start an invisible DOM page where we can call URL.createObjectURL, pass the result back to SW that will use it for chrome.downloads.download.
My Playwright scripts are configured to run in parallel (default) on Chrome, FF and Safari.
import { devices, PlaywrightTestConfig } from '#playwright/test';
const config: PlaywrightTestConfig = {
...
workers: 3,
fullyParallel: false,
},
projects: [
{
name: 'Chrome',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'Firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'Safari',
use: { ...devices['Desktop Safari'] },
},
],
One of my spec files has the below code
test.describe('Home Tests', () => {
let page: Page;
let homeView: HomeView;
test.beforeEach(async ({ page }) => {
homeView = new HomeView(page);
});
test('01', async () => {
await dashboard.login(false, ownerEmail);
...
});
test('02', async () => {
await dashboard.login(false, ownerEmail);
...
});
.
.
.
When I run this test, test 01 is taken up by all 3 browsers and executed in parallel. Due to technical login restrictions of the app, only one test passes and the rest fails. I want test 1 to run in Chrome first and once it is completed it should run on FF second and Safari next (which browser first doesn't matter)
I do not want to change the settings globally or make this possible on the CLI level. I want to handle this at the same spec file level. How can I achieve this?
try to use "test.describe.serial"
Dynamics CRM has its own XRM JS APIs, Which I'm trying to execute in a chrome extension that I'm working on. I'm using the below code.
chrome.tabs.query({ currentWindow: true, active: true }, function(tabs) {
chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: () => {
Xrm.Utility.alertDialog("Hello world", () => { });
}
});
});
Xrm.Utility.alertDialog("Hello world", () => { });
This code just shows a message box on the Dynamics CRM screen using this Dynamics API method.
If I run this code in the chrome console, it shows the message properly. If I just put alert("Hello world")", that code also runs which confirms that executeScript is working fine, but somehow Xrm isn't available.
Manifest.json
After overflowing the stack a few times, I learned that the script injection needs to happen by explicitly creating a script tag and injecting it on the page body. similar to the code below.
function runOnXrmPage() {
// create a script tag
var s = document.createElement('script');
s.src = chrome.runtime.getURL('webscript.js');
s.onload = function () {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
}
chrome.tabs.query({ currentWindow: true, active: true }, function (tabs) {
chrome.scripting.executeScript({
target: { tabId: tabs[0].id },
func: () => {
runOnXrmPage();
}
});
});
All the Xrm code was placed in webscript.js file and manifest was updated as pasted below.
"web_accessible_resources": [
{
"resources": [ "webscript.js" ],
"matches": [ "https://*.crm.dynamics.com/*" ]
}
]
I am developing a chrome-extension, where I am trying to inject a function into the page. Injecting a regular function works without problems.
But as soon as I try to inject anything with async-functions, I get an error similar to this:
Uncaught ReferenceError: g is not defined
For example, injecting an async-function directly.
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: async () => {
console.log("Does it work?");
}
});
Or even if I inject a regular function, but have an async function inside:
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => {
(async function () {
console.log("Does it work?");
})();
}
});
It can't be right, that I am forced to write and inject synchronous code, making me unable to use the async/await functionality... Can it? What am I doing wrong?
EDIT: ---------------
Upon further investigation, I can see that this function
async () => { console.log("Does it work?"); };
Get translated to this
function(){return e.apply(this,arguments)}
Which explains the ReferenceError but still leaves me clueless as to why this happens.
EDIT2: ---------------
So I tried to resort to "synchronous" code for now. But hoped I at least could use promises without async/await - But also no...
chrome.storage.local.set({ someArray: [] })
.then(() => console.log("It worked?"));
The code above results in the following error:
Cannot read properties of undefined (reading 'then')
So no promises and no async/await for any injected code... Is this really the case?
Injecting async function should not cause a problem. I have included a minimal example below to demonstrate.
manifest.json
{
"name": "TEST",
"version": "0.0.1",
"manifest_version": 3,
"permissions": [
"scripting",
"activeTab"
],
"host_permissions": [
"<all_urls>"
],
"background": {
"service_worker": "background.js"
}
}
background.js
function inject(tab) {
chrome.scripting.executeScript({
target: {tabId: tab.id},
func: async () => {
window.alert("I'm here!");
}
})
}
// first open tab with some web page -> try inject there
chrome.tabs.query({
url: "https://*/*"
}, function (tabs) {
inject(tabs.shift())
});
Regarding OP and each edit:
Uncaught ReferenceError: g is not defined - identifier g does not exist: check what it is and where is it supposed to be defined
EDIT 1: if using a compiler/bundler, renaming may be the source of a reference error
EDIT 2: chrome.storage.local.set docs on usage indicate it needs a callback . There is no return value, i.e. it is undefined, cannot chain a then after it.
I am trying to use chrome.scripting.executeScript for a chrome extension I'm building in ManifestV3 and following the Google documentation here (see image below):
I have added an 'arguments' property to pass in the title of the current tab to my function. However, I am getting the following error message:
TypeError: Error in invocation of scripting.executeScript(scripting.ScriptInjection injection, optional function callback): Error at parameter 'injection': Unexpected property: 'arguments'.
Here is my code:
chrome.tabs.query({ active: true }, function (tabs) {
let tab = tabs[0];
chrome.scripting.executeScript(
{
target: { tabId: tab.id },
function: myFunction,
arguments: [tab.title],
},
(injectionResults) => displaySearch(injectionResults[0].result)
);
});
Any help would be appreciated, thanks!
The property name being used is incorrect. The new API uses the property "args".
For example:
function greet(greeting) {
console.log(`${greeting}, World!`);
}
chrome.scripting.executeScript({
target: {tabId: tab.id},
function: greet,
args: ['Hello']
});
Output: Hello, World!
You can find more information here.
There is a workaround for this that you can use until they implement the feature using chrome.storage. What you need to do is first save the argument with chrome.storage.sync.set(), then retrieve it inside the function you're injecting using chrome.storage.sync.get().
chrome.storage.sync.set({ myVariable: valueOfVariable });
chrome.tabs.query({ active: true }, function (tabs) {
let tab = tabs[0];
chrome.scripting.executeScript(
{
target: { tabId: tab.id },
function: myFunction,
}
);
});
function myFunction() {
chrome.storage.sync.get(["myVariable"], ({ myVariable }) => {
// Do stuff
});
}