Chrome Extension - Obtaining Active Tab's DOM information through background script - javascript

I know there have been many questions related to this topic but none of them thus far have allowed me to figure this out in V3. When I run the following background.js, I only get undefined.
The goal of my extension, at least for this stage, is to scrape the active tab's DOM and extract the text content of all of the div elements.
My background.js page:
function getDOM() {
let htmlarr = [];
const pageDOM = document.getElementsByTagName('div');
for (i = 0; i < pageDOM.length; i++) {
htmlarr += pageDOM.innerHTML;
}
return Object.assign({}, htmlarr)
}
chrome.tabs.onActivated.addListener(activeInfo => {
let domRes = chrome.scripting.executeScript({
target: { tabId: activeInfo.tabId },
func: getDOM
})
console.log(domRes);
});
my manifest.json:
{
"name": "HTML Sourcer",
"description": "Extract HTML source code",
"version": "1.0",
"minimum_chrome_version": "10.0",
"manifest_version": 3,
"permissions": ["scripting", "tabs", "activeTab"],
"host_permissions": [
"*://*/*"
],
"background": {
"service_worker": "background.js"
}
}
Any help would be much appreciated. Thank you!

Problem 1
Per the documentation this API method returns a Promise when there's no callback parameter.
To get the value of the Promise, add await and mark the function as async:
chrome.tabs.onActivated.addListener(async info => {
let domRes = await chrome.scripting.executeScript({
target: {tabId: info.tabId},
func: getDOM,
}).catch(console.error);
if (!domRes) return;
console.log(domRes);
});
There's a catch because some tabs don't support injection e.g. the start tab or chrome:// tabs.
Problem 2
In JavaScript += doesn't work with arrays, but only with numbers/strings. Use htmlarr.push() instead. There's also no need to convert the array to an object via Object.assign.
Actually, let's rewrite getDOM using Array.from:
function getDOM() {
return Array.from(
document.getElementsByTagName('div'),
el => el.innerHTML
);
}

Related

Javascript Browser WebExtension API Manifest v3 - Service Worker and chrome.storage.local API issue

I'm implementing a small extension for Copy as cURL feature (as done by the Network tab of DevTools) and I would like to use Manifest v3. According to the documentation and to the contribution of the community, Service Worker at a certain time stops to live so some variables cannot retrieve the needed information from the active tab.
For managing this, I'm using chrome.storage.local.set and .get functions in order to keep the needed information also after the Service Worker stops to live. When I run the extension test, I don't receive any error, but, despite I retrieve the stored variables by the chrome.storage API, sometimes I continue to not retrieve the values anymore also when the Service Worker should be alive. For example:
when I connect to a website, I can retrieve and copy the correct data also in 1 min, then, if I continue to Copy (without refreshing the page), I don't get the parameters (i.e., GET headers).
sometimes, if I open a new tab, insert an address and quickly press Copy as cURL, of my extension, headers are not copied, and I need to refresh the page (not by clicking refresh button of browser but click on URL then ENTER) for getting them.
Maybe the issue is not related to the Time-to-live of the Service Worker because I can keep a page opened for a lot of minutes and it gives me the right parameters. I don't know where my approach is failing. The code of this small implementation is the following:
background.js
"use strict";
/*
Called when the item has been created, or when creation failed due to an error.
We'll just log success/failure here.
*/
function onCreated() {
if (chrome.runtime.lastError) {
console.log(`Error: ${chrome.runtime.lastError}`);
} else {
console.log("Item created successfully");
}
}
/*
Called when the item has been removed.
We'll just log success here.
*/
function onRemoved() {
console.log("Item removed successfully");
}
/*
Called when there was an error.
We'll just log the error here.
*/
function onError(error) {
console.log(`Error: ${error}`);
}
/*
Create all the context menu items.
*/
chrome.contextMenus.create({
id: "tools-copy",
//title: chrome.i18n.getMessage("menuItemToolsCopy"),
title: "Copy",
contexts: ["all"],
}, onCreated);
chrome.contextMenus.create({
id: "tools-copy-curl",
parentId: "tools-copy",
//title: chrome.i18n.getMessage("menuItemToolsCopyAsFFUF"),
title: "Copy as cURL",
contexts: ["all"],
}, onCreated);
const tabData = {};
const getProp = (obj, key) => (obj[key] || (obj[key] = {}));
const encodeBody = body => {
var data = '';
// Read key
for (var key in body.formData) { //body is a JSON object
data += `${key}=${body.formData[key]}&`;
}
data = data.replace(/.$/,"");
var body_data = `'${data}'`;
return body_data;
}
const FILTER = {
types: ['main_frame', 'sub_frame'],
urls: ['<all_urls>'],
};
const TOOLS = {
CURL: 'tools-copy-curl',
};
chrome.webRequest.onBeforeRequest.addListener(e => {
getProp(getProp(tabData, e.tabId), e.frameId).body = e.requestBody;
chrome.storage.local.set({tabData: tabData}, function() {
console.log('HTTP request saved');
});
}, FILTER, ['requestBody']);
chrome.webRequest.onBeforeSendHeaders.addListener(e => {
getProp(getProp(tabData, e.tabId), e.frameId).headers = e.requestHeaders;
chrome.storage.local.set({tabData: tabData}, function() {
console.log('HTTP request saved');
});
}, FILTER, ['requestHeaders']);
chrome.tabs.onRemoved.addListener(tabId => delete tabData[tabId]);
chrome.tabs.onReplaced.addListener((addId, delId) => delete tabData[delId]);
chrome.contextMenus.onClicked.addListener((info, tab) => {
chrome.storage.local.get(["tabData"], function(items) {
const data = items.tabData[tab.id]?.[info.frameId || 0] || {};
if (info.menuItemId === TOOLS.CURL) {
var txt_clip = `curl -u '${info.frameUrl || tab.url}'` +
(data.headers?.map(h => ` -H '${h.name}: ${h.value}'`).join('') || '') +
(data.body? ' --data_raw ' + encodeBody(data.body) : '');
}
chrome.tabs.sendMessage(tab.id,
{
message: "copyText",
textToCopy: txt_clip
}, function(response) {})
});
});
content.js
chrome.runtime.onMessage.addListener(
function(request, sender, sendResponse) {
if (request.message === "copyText") {
navigator.clipboard.writeText(request.textToCopy);
sendResponse({status: true});
}
}
);
manifest.json
{
"manifest_version": 3,
"name": "CopyAsCURL",
"description": "Copy as cURL test example.",
"version": "1.0",
"default_locale": "en",
"background": {
"service_worker": "background.js"
},
"permissions": [
"contextMenus",
"activeTab",
"cookies",
"webRequest",
"tabs",
"clipboardWrite",
"storage"
],
"host_permissions": [
"<all_urls>"
],
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
],
"icons": {
"16": "icons/menu-16.png",
"32": "icons/menu-32.png",
"48": "icons/menu-48.png"
}
}
I want also to thank #wOxxOm for the support on similar topic.

Host permission issue in a created window - Chrome Extension [duplicate]

I am struggling to get this simple f-ty working... My scenario is:
get current URL
modify it
navigate/redirect to it
execute custom JS code there
The most problems I have is with 4)
manifest.json
{
"name": "Hello, World!",
"description": "Navigate and execute custom js script",
"version": "1.0",
"manifest_version": 3,
"permissions": [
"tabs",
"activeTab",
"scripting"
],
"background": {
"service_worker": "background.js"
},
"action": {}
}
background.js
function myCustomScript() {
alert('myCustomScript test ok!');
console.log('myCustomScript test ok!');
}
chrome.action.onClicked.addListener((tab) => {
chrome.tabs.update({url: "https://example.com"}, myCustomScript);
});
The page got redirected but my js function is not executed! Do you know why and how to fix it?
P.S: this is my first time I am creating my chrome extension, maybe I am doing something wrong...
To execute custom code, use chrome.scripting API. For this scenario you'll need:
"scripting" added to "permissions", which you already have,
"https://example.com/" added to "host_permissions" in manifest.json.
Note that activeTab permission won't apply to the tab after it's navigated to a URL with a different origin because this permission only applies to the currently shown origin.
Due to a bug in Chrome, you need to wait for the URL to be set before executing the script.
The bug is fixed in Chrome 100.
chrome.action.onClicked.addListener(async tab => {
await chrome.tabs.update(tab.id, {url: "https://example.com"});
// Creating a tab needs the same workaround
// tab = await chrome.tabs.create({url: "https://example.com"});
await onTabUrlUpdated(tab.id);
const results = await chrome.scripting.executeScript({
target: {tabId: tab.id},
files: ['content.js'],
});
// do something with results
});
function onTabUrlUpdated(tabId) {
return new Promise((resolve, reject) => {
const onUpdated = (id, info) => id === tabId && info.url && done(true);
const onRemoved = id => id === tabId && done(false);
chrome.tabs.onUpdated.addListener(onUpdated);
chrome.tabs.onRemoved.addListener(onRemoved);
function done(ok) {
chrome.tabs.onUpdated.removeListener(onUpdated);
chrome.tabs.onRemoved.removeListener(onRemoved);
(ok ? resolve : reject)();
}
});
}
P.S. alert can't be used in a service worker. Instead, you should look at devtools console of the background script or use chrome.notifications API.

Getting current browser URL in JS [duplicate]

I'm having fun with Google Chrome extension, and I just want to know how can I store the URL of the current tab in a variable?
Use chrome.tabs.query() like this:
chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => {
let url = tabs[0].url;
// use `url` here inside the callback because it's asynchronous!
});
This requires that you request access to the chrome.tabs API in your extension manifest:
"permissions": [ ...
"tabs"
]
It's important to note that the definition of your "current tab" may differ depending on your extension's needs.
Setting lastFocusedWindow: true in the query is appropriate when you want to access the current tab in the user's focused window (typically the topmost window).
Setting currentWindow: true allows you to get the current tab in the window where your extension's code is currently executing. For example, this might be useful if your extension creates a new window / popup (changing focus), but still wants to access tab information from the window where the extension was run.
I chose to use lastFocusedWindow: true in this example, because Google calls out cases in which currentWindow may not always be present.
You are free to further refine your tab query using any of the properties defined here: chrome.tabs.query
Warning! chrome.tabs.getSelected is deprecated. Please use chrome.tabs.query as shown in the other answers.
First, you've to set the permissions for the API in manifest.json:
"permissions": [
"tabs"
]
And to store the URL :
chrome.tabs.getSelected(null,function(tab) {
var tablink = tab.url;
});
Other answers assume you want to know it from a popup or background script.
In case you want to know the current URL from a content script, the standard JS way applies:
window.location.toString()
You can use properties of window.location to access individual parts of the URL, such as host, protocol or path.
The problem is that chrome.tabs.getSelected is asynchronous. This code below will generally not work as expected. The value of 'tablink' will still be undefined when it is written to the console because getSelected has not yet invoked the callback that resets the value:
var tablink;
chrome.tabs.getSelected(null,function(tab) {
tablink = tab.url;
});
console.log(tablink);
The solution is to wrap the code where you will be using the value in a function and have that invoked by getSelected. In this way you are guaranteed to always have a value set, because your code will have to wait for the value to be provided before it is executed.
Try something like:
chrome.tabs.getSelected(null, function(tab) {
myFunction(tab.url);
});
function myFunction(tablink) {
// do stuff here
console.log(tablink);
}
This is a pretty simple way
window.location.toString();
You probaly have to do this is the content script because it has all the functions that a js file on a wepage can have and more.
Hi here is an Google Chrome Sample which emails the current Site to an friend. The Basic idea behind is what you want...first of all it fetches the content of the page (not interessting for you)...afterwards it gets the URL (<-- good part)
Additionally it is a nice working code example, which i prefer motstly over reading Documents.
Can be found here:
Email this page
This Solution is already TESTED.
set permissions for API in manifest.json
"permissions": [ ...
"tabs",
"activeTab",
"<all_urls>"
]
On first load call function. https://developer.chrome.com/extensions/tabs#event-onActivated
chrome.tabs.onActivated.addListener((activeInfo) => {
sendCurrentUrl()
})
On change call function. https://developer.chrome.com/extensions/tabs#event-onSelectionChanged
chrome.tabs.onSelectionChanged.addListener(() => {
sendCurrentUrl()
})
the function to get the URL
function sendCurrentUrl() {
chrome.tabs.getSelected(null, function(tab) {
var tablink = tab.url
console.log(tablink)
})
async function getCurrentTabUrl () {
const tabs = await chrome.tabs.query({ active: true })
return tabs[0].url
}
You'll need to add "permissions": ["tabs"] in your manifest.
For those using the context menu api, the docs are not immediately clear on how to obtain tab information.
chrome.contextMenus.onClicked.addListener(function(info, tab) {
console.log(info);
return console.log(tab);
});
https://developer.chrome.com/extensions/contextMenus
You have to check on this.
HTML
<button id="saveActionId"> Save </button>
manifest.json
"permissions": [
"activeTab",
"tabs"
]
JavaScript
The below code will save all the urls of active window into JSON object as part of button click.
var saveActionButton = document.getElementById('saveActionId');
saveActionButton.addEventListener('click', function() {
myArray = [];
chrome.tabs.query({"currentWindow": true}, //{"windowId": targetWindow.id, "index": tabPosition});
function (array_of_Tabs) { //Tab tab
arrayLength = array_of_Tabs.length;
//alert(arrayLength);
for (var i = 0; i < arrayLength; i++) {
myArray.push(array_of_Tabs[i].url);
}
obj = JSON.parse(JSON.stringify(myArray));
});
}, false);
If you want the full extension that store the URLs that opened or seen by the use via chrome extension:
use this option in your background:
openOptionsPage = function (hash) {
chrome.tabs.query({ url: options_url }, function (tabs) {
if (tabs.length > 0) {
chrome.tabs.update(
tabs[0].id,
{ active: true, highlighted: true, currentWindow: true },
function (current_tab) {
chrome.windows.update(current_tab.windowId, { focused: true });
}
);
} else {
window.addEventListener(hash, function () {
//url hash # has changed
console.log(" //url hash # has changed 3");
});
chrome.tabs.create({
url: hash !== undefined ? options_url + "#" + hash : options_url,
});
}
});
};
you need index.html file also. which you can find in the this Github
the manifest file should be like this:
{
"manifest_version": 2,
"name": "ind count the Open Tabs in browser ",
"version": "0.3.2",
"description": "Show open tabs",
"homepage_url": "https://github.com/sylouuu/chrome-open-tabs",
"browser_action": {},
"content_security_policy": "script-src 'self' https://ajax.googleapis.com https://www.google-analytics.com; object-src 'self'",
"options_page": "options.html",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"background": {
"scripts": ["background.js"]
},
"web_accessible_resources": ["img/*.png"],
"permissions": ["tabs", "storage"]
}
The full version of simple app can be found here on this Github:
https://github.com/Farbod29/extract-and-find-the-new-tab-from-the-browser-with-chrome-extention

Chrome extension – "Cannot read property 'onMessage' of undefined" [duplicate]

I'm having fun with Google Chrome extension, and I just want to know how can I store the URL of the current tab in a variable?
Use chrome.tabs.query() like this:
chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => {
let url = tabs[0].url;
// use `url` here inside the callback because it's asynchronous!
});
This requires that you request access to the chrome.tabs API in your extension manifest:
"permissions": [ ...
"tabs"
]
It's important to note that the definition of your "current tab" may differ depending on your extension's needs.
Setting lastFocusedWindow: true in the query is appropriate when you want to access the current tab in the user's focused window (typically the topmost window).
Setting currentWindow: true allows you to get the current tab in the window where your extension's code is currently executing. For example, this might be useful if your extension creates a new window / popup (changing focus), but still wants to access tab information from the window where the extension was run.
I chose to use lastFocusedWindow: true in this example, because Google calls out cases in which currentWindow may not always be present.
You are free to further refine your tab query using any of the properties defined here: chrome.tabs.query
Warning! chrome.tabs.getSelected is deprecated. Please use chrome.tabs.query as shown in the other answers.
First, you've to set the permissions for the API in manifest.json:
"permissions": [
"tabs"
]
And to store the URL :
chrome.tabs.getSelected(null,function(tab) {
var tablink = tab.url;
});
Other answers assume you want to know it from a popup or background script.
In case you want to know the current URL from a content script, the standard JS way applies:
window.location.toString()
You can use properties of window.location to access individual parts of the URL, such as host, protocol or path.
The problem is that chrome.tabs.getSelected is asynchronous. This code below will generally not work as expected. The value of 'tablink' will still be undefined when it is written to the console because getSelected has not yet invoked the callback that resets the value:
var tablink;
chrome.tabs.getSelected(null,function(tab) {
tablink = tab.url;
});
console.log(tablink);
The solution is to wrap the code where you will be using the value in a function and have that invoked by getSelected. In this way you are guaranteed to always have a value set, because your code will have to wait for the value to be provided before it is executed.
Try something like:
chrome.tabs.getSelected(null, function(tab) {
myFunction(tab.url);
});
function myFunction(tablink) {
// do stuff here
console.log(tablink);
}
This is a pretty simple way
window.location.toString();
You probaly have to do this is the content script because it has all the functions that a js file on a wepage can have and more.
Hi here is an Google Chrome Sample which emails the current Site to an friend. The Basic idea behind is what you want...first of all it fetches the content of the page (not interessting for you)...afterwards it gets the URL (<-- good part)
Additionally it is a nice working code example, which i prefer motstly over reading Documents.
Can be found here:
Email this page
This Solution is already TESTED.
set permissions for API in manifest.json
"permissions": [ ...
"tabs",
"activeTab",
"<all_urls>"
]
On first load call function. https://developer.chrome.com/extensions/tabs#event-onActivated
chrome.tabs.onActivated.addListener((activeInfo) => {
sendCurrentUrl()
})
On change call function. https://developer.chrome.com/extensions/tabs#event-onSelectionChanged
chrome.tabs.onSelectionChanged.addListener(() => {
sendCurrentUrl()
})
the function to get the URL
function sendCurrentUrl() {
chrome.tabs.getSelected(null, function(tab) {
var tablink = tab.url
console.log(tablink)
})
async function getCurrentTabUrl () {
const tabs = await chrome.tabs.query({ active: true })
return tabs[0].url
}
You'll need to add "permissions": ["tabs"] in your manifest.
For those using the context menu api, the docs are not immediately clear on how to obtain tab information.
chrome.contextMenus.onClicked.addListener(function(info, tab) {
console.log(info);
return console.log(tab);
});
https://developer.chrome.com/extensions/contextMenus
You have to check on this.
HTML
<button id="saveActionId"> Save </button>
manifest.json
"permissions": [
"activeTab",
"tabs"
]
JavaScript
The below code will save all the urls of active window into JSON object as part of button click.
var saveActionButton = document.getElementById('saveActionId');
saveActionButton.addEventListener('click', function() {
myArray = [];
chrome.tabs.query({"currentWindow": true}, //{"windowId": targetWindow.id, "index": tabPosition});
function (array_of_Tabs) { //Tab tab
arrayLength = array_of_Tabs.length;
//alert(arrayLength);
for (var i = 0; i < arrayLength; i++) {
myArray.push(array_of_Tabs[i].url);
}
obj = JSON.parse(JSON.stringify(myArray));
});
}, false);
If you want the full extension that store the URLs that opened or seen by the use via chrome extension:
use this option in your background:
openOptionsPage = function (hash) {
chrome.tabs.query({ url: options_url }, function (tabs) {
if (tabs.length > 0) {
chrome.tabs.update(
tabs[0].id,
{ active: true, highlighted: true, currentWindow: true },
function (current_tab) {
chrome.windows.update(current_tab.windowId, { focused: true });
}
);
} else {
window.addEventListener(hash, function () {
//url hash # has changed
console.log(" //url hash # has changed 3");
});
chrome.tabs.create({
url: hash !== undefined ? options_url + "#" + hash : options_url,
});
}
});
};
you need index.html file also. which you can find in the this Github
the manifest file should be like this:
{
"manifest_version": 2,
"name": "ind count the Open Tabs in browser ",
"version": "0.3.2",
"description": "Show open tabs",
"homepage_url": "https://github.com/sylouuu/chrome-open-tabs",
"browser_action": {},
"content_security_policy": "script-src 'self' https://ajax.googleapis.com https://www.google-analytics.com; object-src 'self'",
"options_page": "options.html",
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
],
"background": {
"scripts": ["background.js"]
},
"web_accessible_resources": ["img/*.png"],
"permissions": ["tabs", "storage"]
}
The full version of simple app can be found here on this Github:
https://github.com/Farbod29/extract-and-find-the-new-tab-from-the-browser-with-chrome-extention

How to share a unique tab ID between content and background scripts without an asynchronous delay?

I have built a chrome extension and I'm getting hit by a race condition that I need help with.
If you see the answer chrome extension: sharing an object between content scripts and background script it tells us that you cannot share a variable between content and background scripts.
My goal is to generate a unique ID per-browser tab and then share it in between the content.js and the background.js. Then I need to use this value in a content injected javascript as explained in this answer: In Chrome extensions, can you force some javascript to be injected before everything?
The only way I have been able to figure out how to do this is by doing the following async code then I just use the tab ID as the unique ID:
content.js
await pingBackground();
async function pingBackground() {
var info;
await new Promise(function (resolve, reject) {
chrome.runtime.sendMessage({ type: 1 }, function (response) {
if (response !== undefined) {
info = response;
resolve();
}
else {
reject();
}
});
});
console.log("Id is " + info.id);
}
background.js
chrome.runtime.onMessage.addListener(messageHandler);
function messageHandler(message, sender, reply) {
switch (message.type) {
case 1:
reply({ 'id': sender['tab'].id, 'active': true });
break;
}
}
manifest.json
{
"name": "oogi",
"version": "0.1",
"manifest_version": 2,
"background": {
"scripts": [
"common.js",
"background.js"
],
"persistent": true
},
"content_scripts": [
{
"matches": ["*://*/*"],
"js": ["content.js"],
"run_at": "document_start"
}
],
"permissions": [
"contentSettings",
"webRequest",
"webRequestBlocking",
"*://*/*"
]
}
But the problem with this is by the time I get the tab ID from background js, the script's content has already been loaded.
Is there some way to make it so this variable can be asynchronously shared between background.js and content.js? Or is this simply impossible?
Can I switch it around and have background.js load a variable from content.js asynchronously?
UPDATE:
A terrible hack which works is to do this in the foreground of the content.js:
var sleepScript = document.createElement('script');
var sleepCode = `function sleep (ms) {
var start = new Date().getTime();
while (new Date() < start + ms) {}
return 0;
}
sleep(500);`;
sleepScript.textContent = sleepCode;
(document.head || document.documentElement).appendChild(sleepScript);
This will force the page to wait for a bit giving the time to query the background before running the inline dom.
It works but that's awful.
This question was already answered previously, although it is hard to tell that this is the same issue at first glance.
https://stackoverflow.com/a/45105934
The answer is pretty descriptive so give it a read.
Here is the script changes that make it work:
// background.js
function addSeedCookie(details) {
details.responseHeaders.push({
name: "Set-Cookie",
value: `tab_id=${details.tabId}; Max-Age=2`
});
return {
responseHeaders: details.responseHeaders
};
}
chrome.webRequest.onHeadersReceived.addListener(
addSeedCookie, {urls: ["<all_urls>"]}, ["blocking", "responseHeaders"]
);
// inject.js
function getCookie(cookie) { // https://stackoverflow.com/a/19971550/934239
return document.cookie.split(';').reduce(function(prev, c) {
var arr = c.split('=');
return (arr[0].trim() === cookie) ? arr[1] : prev;
}, undefined);
}
var tabId = getCookie("tab_id");

Categories