Getting the source HTML of the current page from chrome extension - html

I have a chrome extension. I need to analyse from the HTML source of the current page. I found here all kinds of solutions with background pages and content scripts but none helped me. here is what I have so far:
manifest.json
{
"name": "Extension",
"version": "1.0",
"description": "Extension",
"browser_action": {
"default_icon": "bmarkred.ico",
"popup": "Test.html"
},
"content_scripts": [
{
"matches": ["http://*/*"],
"js": ["content.js"]
}
],
"background": {
"page": "backgroundPage.html"
},
"permissions": [
"cookies",
"tabs",
"http://*/*",
"https://*/*"
]
}
background.html
<html>
<head>
<script type="text/javascript">
try {
chrome.tabs.getSelected(null, function (tab) {
chrome.tabs.sendRequest(tab.id, {action: "getSource"}, function(source) {
alert(source);
});
});
}
catch (ex) {
alert(ex);
}
</script>
</head>
</html>
content.js
chrome.extension.onRequest.addListener(function(request, sender, callback) {
if (request.action == "getSource") {
callback(document.getElementsByTagName('html')[0].innerHTML);
}
});
The alert always alerts undefined. even if i change in the content.js file the callback function to:
callback('hello');
still the same result. What am I doing wrong? maybe I'm going at this the wrong way. What I really need is this: When the user opens the extension popup (and only then), I need HTML of the current page so I can analyse it.

Inject a script into the page you want to get the source from and message it back to the popup....
manifest.json
{
"name": "Get pages source",
"version": "1.1",
"manifest_version": 3,
"description": "Get active tabs or element on that pages source from a popup",
"action": {
"default_title": "Get pages source",
"default_popup": "popup.html"
},
"permissions": [
"scripting",
"activeTab"
]
}
popup.html
function onWindowLoad() {
var message = document.querySelector('#message');
chrome.tabs.query({ active: true, currentWindow: true }).then(function (tabs) {
var activeTab = tabs[0];
var activeTabId = activeTab.id;
return chrome.scripting.executeScript({
target: { tabId: activeTabId },
// injectImmediately: true, // uncomment this to make it execute straight away, other wise it will wait for document_idle
func: DOMtoString,
// args: ['body'] // you can use this to target what element to get the html for
});
}).then(function (results) {
message.innerText = results[0].result;
}).catch(function (error) {
message.innerText = 'There was an error injecting script : \n' + error.message;
});
}
window.onload = onWindowLoad;
function DOMtoString(selector) {
if (selector) {
selector = document.querySelector(selector);
if (!selector) return "ERROR: querySelector failed to find node"
} else {
selector = document.documentElement;
}
return selector.outerHTML;
}

Here is my solution:
chrome.runtime.onMessage.addListener(function(request, sender) {
if (request.action == "getSource") {
this.pageSource = request.source;
var title = this.pageSource.match(/<title[^>]*>([^<]+)<\/title>/)[1];
alert(title)
}
});
chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
chrome.tabs.executeScript(
tabs[0].id,
{ code: 'var s = document.documentElement.outerHTML; chrome.runtime.sendMessage({action: "getSource", source: s});' }
);
});

Related

MV3 declarativeNetRequest and X-Frame-Options DENY

I have a MV2 extension with chrome.webRequest that works perfectly but fail on MV3 declarativeNetRequest getting around iframes.
The extension is like a multi-messenger that opens multiple iframes for various sites to merge in a single extension all popular messengers.
So I have a domain "example.com" and there I open multiple iframes, for example open an iframe with Twitter.com or Telegram.org.
Since twitter.com or telegram.org set the X-Frame-Options to DENY those iframes don't show anything.
With MV2 we could run chrome.webRequest and remove those headers:
chrome.webRequest.onHeadersReceived.addListener(
function (details)
{
if (details.tabId && (details.tabId === tabId || details.tabId === -1 || tabMultiId.includes(details.tabId))) {
var b = details.responseHeaders.filter((details) => !['x-frame-options', 'content-security-policy', 'x-content-security-policy', 'strict-transport-security', 'frame-ancestors'].includes(details.name.toLowerCase()));
b.forEach(function(e){
"set-cookie" === e.name && -1 !== e.value.indexOf("Secure") && (-1 !== e.value.indexOf("SameSite=Strict") ?
(e.value = e.value.replace(/SameSite=Strict/g, "SameSite=None"))
: -1 !== e.value.indexOf("SameSite=Lax")
? (e.value = e.value.replace(/SameSite=Lax/g, "SameSite=None"))
: (e.value = e.value.replace(/; Secure/g, "; SameSite=None; Secure")));
});
return {
responseHeaders: b
}
}
},
{
urls: [ "<all_urls>" ],
tabId: tabId
},
["blocking", "responseHeaders", "extraHeaders"]
);
I have tried to do exactly the same with MV3 but keep failing.
My 2 attemps:
async function NetRequest() {
var blockUrls = ["*://*.twitter.com/*","*://*.telegram.org/*"];
var tabId = await getObjectFromLocalStorage('tabId');
var tabMultiId = [];
tabMultiId = JSON.parse(await getObjectFromLocalStorage('tabMultiId'));
tabMultiId.push(tabId);
blockUrls.forEach((domain, index) => {
let id = index + 1;
chrome.declarativeNetRequest.updateSessionRules({
addRules:[
{
"id": id,
"priority": 1,
"action": { "type": "modifyHeaders",
"responseHeaders": [
{ "header": "X-Frame-Options", "operation": "remove" },
{ "header": "Frame-Options", "operation": "remove" },
{ "header": "content-security-policy", "operation": "remove" },
{ "header": "content-security-policy-report-only", "operation": "remove" },
{ "header": "x-content-security-policy", "operation": "remove" },
{ "header": "strict-transport-security", "operation": "remove" },
{ "header": "frame-ancestors", "operation": "remove" },
{ "header": "set-cookie", "operation": "set", "value": "SameSite=None; Secure" }
]
},
"condition": {"urlFilter": domain, "resourceTypes": ["image","media","main_frame","sub_frame","stylesheet","script","font","xmlhttprequest","ping","websocket","other"],
"tabIds" : tabMultiId }
}
],
removeRuleIds: [id]
});
});
}
async function launchWindow(newURL, windowDimensions, urlWindow, isIncognitoWindow, windowType) {
chrome.windows.create({ url: newURL, type: windowType, incognito: isIncognitoWindow, width: windowDimensions.width, height: windowDimensions.height, left: windowDimensions.left, top: windowDimensions.top },
async function (chromeWindow) {
if (urlWindow != "install" || urlWindow != "update") {
chrome.storage.local.set({ 'extensionWindowId': chromeWindow.id }, function () { });
chrome.storage.local.set({ 'tabId': chromeWindow.tabs[0].id }, function () { });
NetRequest();
}
});
}
Also tried:
const iframeHosts = [
'twitter.com', 'telegram.org'
];
const RULE = {
id: 1,
condition: {
initiatorDomains: ['example.com'],
requestDomains: iframeHosts,
resourceTypes: ['sub_frame', 'main_frame'],
},
action: {
type: 'modifyHeaders',
responseHeaders: [
{header: 'X-Frame-Options', operation: 'remove'},
{header: 'Frame-Options', operation: 'remove'},
],
},
};
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [RULE.id],
addRules: [RULE],
});
Permissions:
"permissions": [
"system.display",
"scripting",
"activeTab",
"notifications",
"contextMenus",
"unlimitedStorage",
"storage",
"declarativeNetRequestWithHostAccess",
"webNavigation",
"alarms"
],
"host_permissions": [
"<all_urls>"
],
Any of this attempts worked.
Greetings and thank you very much for anyone that try to help.
You need to unregister service worker for the site and clear its cache using chrome.browsingData API.
Syntax for urlFilter is different, so your "*://*.twitter.com/*" is incorrect and should be "||twitter.com/", however a better solution is to use requestDomains because it allows specifying multiple sites in just one rule.
// manifest.json
"permissions": ["browsingData", "declarativeNetRequest"],
"host_permissions": ["*://*.twitter.com/", "*://*.telegram.org/"],
// extension script
async function configureNetRequest(tabId) {
const domains = [
'twitter.com',
'telegram.org',
];
const headers = [
'X-Frame-Options',
'Frame-Options',
];
await chrome.declarativeNetRequest.updateSessionRules({
removeRuleIds: [1],
addRules: [{
id: 1,
action: {
type: 'modifyHeaders',
responseHeaders: headers.map(h => ({ header: h, operation: 'remove'})),
},
condition: {
requestDomains: domains,
resourceTypes: ['sub_frame'],
tabIds: [tabId],
},
}],
});
await chrome.browsingData.remove({
origins: domains.map(d => `https://${d}`),
}, {
cacheStorage: true,
serviceWorkers: true,
});
}
// Usage
chrome.windows.create({ url: 'about:blank' }, async w => {
await configureNetRequest(w.tabs[0].id);
await chrome.tabs.update(w.tabs[0].id, { url: 'https://some.real.url/' });
});

How do you get the current tab in a popup window created with chrome.windows.create [duplicate]

I am going to create a chrome extension that will pop up a new window, by doing this
chrome.windows.create({
url: chrome.runtime.getURL("popup.html"),
type: "popup",
});
my problem is from "pop up window" I want to access the active Tab from the "main window", for example I want to change the background of the DOM from the active tab from where I clicked the extension that shows the popup window. I hope the question is not confusing. :)
manifest.json
{
"name": "test",
"description": "test",
"version": "1.0.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["storage", "activeTab", "scripting", "tabs"],
"action": {
"default_icon": {
"16": "Icon-16.png",
"32": "Icon-32.png",
"48": "Icon-48.png",
"128": "Icon-128.png"
}
},
"icons": {
"16": "Icon-16.png",
"32": "Icon-32.png",
"48": "Icon-48.png",
"128": "Icon-128.png"
}
}
on the pop up window, i have this onclick function
const onClick = async (e) => {
if (e && e.preventDefault) e.preventDefault();
const [tab] = await chrome.tabs.query({
active: true,
currentWindow: true,
});
chrome.scripting.executeScript({
target: { tabId: tab.id },
function: () => {
chrome.storage.sync.get("color", ({ color }) => {
document.body.style.backgroundColor = color;
});
},
});
};
Get the active tab before creating the window and pass the id as a URL parameter.
extension script:
async function openPopup() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
await chrome.windows.create({
url: `popup.html?${new URLSearchParams({
tabId: tab.id,
})}` ,
type: 'popup',
});
}
popup.html:
<script src="popup.js"></script>
popup.js:
const activeTabId = Number(new URLSearchParams(location.search).get('tabId'));
chrome.scripting.executeScript({ target: { tabId: activeTabId }, function: foo });
function foo() {
console.log('injected foo');
}
async function getCurrentTab() {
let queryOptions = { active: true, currentWindow: true };
let [tab] = await chrome.tabs.query(queryOptions);
return tab;
}
const tab = getCurrentTab();
you can use this from documentation, returning promises object
https://developer.chrome.com/docs/extensions/reference/tabs/

Override HTTP responses from a Chrome extension

I'm making an extension that will be able to modify request responses. I did some research, found this answer https://stackoverflow.com/a/51594799/16661157 and implemented it into my extension.
When debugging this script, it detects the majority of requests but not all, unfortunately the most important for me are not. So my question is: what could be causing this?
manifest.json
{
"manifest_version": 3,
"permissions": [
"tabs"
],
"web_accessible_resources": [{
"resources": ["script.js"],
"matches": [ "<all_urls>" ]
}],
"action": {
"default_popup": "index.html"
},
"content_scripts": [
{
"matches": [ "<all_urls>" ],
"js": ["jquery-3.6.0.min.js", "contentscript.js"],
"run_at": "document_start"
}
]
}
contentscript.js
var s = document.createElement('script');
s.src = chrome.runtime.getURL('script.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
script.js
var _open = XMLHttpRequest.prototype.open;
window.XMLHttpRequest.prototype.open = function (method, URL) {
var _onreadystatechange = this.onreadystatechange,
_this = this;
_this.onreadystatechange = function () {
// catch only completed 'api/search/universal' requests
try {
console.log('Caught! :)', method, URL/*, _this.responseText*/);
} catch (e) {}
if (_this.readyState === 4 && _this.status === 200 && ~URL.indexOf('api/search/universal')) {
try {
//////////////////////////////////////
// THIS IS ACTIONS FOR YOUR REQUEST //
// EXAMPLE: //
//////////////////////////////////////
var data = JSON.parse(_this.responseText); // {"fields": ["a","b"]}
if (data.fields) {
data.fields.push('c','d');
}
// rewrite responseText
Object.defineProperty(_this, 'responseText', {value: JSON.stringify(data)});
/////////////// END //////////////////
} catch (e) {}
}
// call original callback
if (_onreadystatechange) _onreadystatechange.apply(this, arguments);
};
// detect any onreadystatechange changing
Object.defineProperty(this, "onreadystatechange", {
get: function () {
return _onreadystatechange;
},
set: function (value) {
_onreadystatechange = value;
}
});
return _open.apply(_this, arguments);
};

We made a listener in background.js in chrome extension but it does not run

I am creating a simple chrome extension that prevents me from connecting to the Google domain. But in the background, javascript kept working and created the background.js and the on-updated listener. But it doesn't work. Why? When I clicked on the icon in Main.js, it was OK.
manifest.json
{
"manifest_version": 2,
"name": "불법 토토사이트 감지기",
"description": "불법 토토사이트를 감지해주는 사이트입니다.",
"version": "1.0",
"content_security_policy": "script-src 'self' https://ajax.googleapis.com; object-src 'self'",
"background": {
"scripts": ["background.js"],
"persistent" : false
},
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html"
},
"permissions": [
"tabs",
"activeTab"
]
}
background.js
chrome.tabs.onUpdated.addListener(function () {
var domain = document.URL;
var google = 'https://www.google.co.kr/';
if (domain == google) {
chrome.tabs.executeScript({
code: 'alert("불법 사이트입니다"); history.go(-1);'
})
}
// $.ajax({
// type: 'POST',
// url: 'http://207.148.88.110:3000/',
// data: {
// url: domain
// },
// dataType: 'json',
// success: function(Object){
// if (Object.success == '통과') {
// document.getElementById('url').innerHTML = Object.success;
// }
// else {
// chrome.tabs.executeScript({
// code: 'alert("불법 사이트입니다"); history.go(-1);'
// })
// }
// },
// error: function(error) {
// console.log(error);
// }
// })
})
main.js
chrome.tabs.executeScript({
code: 'document.URL;'
}, function (domain) {
var google = 'https://www.google.co.kr/';
if (domain == google) {
chrome.tabs.executeScript({
code: 'alert("불법 사이트입니다"); history.go(-1);'
})
}
$.ajax({
type: 'POST',
url: 'http://207.148.88.110:3000/',
data: {
url: domain
},
dataType: 'json',
success: function(Object){
if (Object.success == '통과') {
document.getElementById('url').innerHTML = Object.success;
}
else {
chrome.tabs.executeScript({
code: 'alert("불법 사이트입니다"); history.go(-1);'
})
}
},
error: function(error) {
console.log(error);
}
})
})
chrome.tabs.onUpdated.addListener(function () {
var domain = document.URL;
/* ... */
At this point in your code, the document is the background page. So the URL will be something like chrome-extension://your-extension-id/_generated_background_page.html and will never match.
You need to check the details that are provided to onUpdated listeners:
chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
var domain = changeInfo.url; // Or tab.url
/* ... */

Simple chrome extension opening new tab and showing data from page

I try to write a chrome extension, reading data from the window object of the current page, opening a new tab and showing the data there.
Do you know a simple example for that?
My current problem is that I can't write to the opended tab.
manifest.json
{
"manifest_version": 2,
"name": "Inspector",
"version": "0.1",
"permissions" : ["tabs"],
"content_scripts": [{
"matches": [
"<all_urls>"
],
"js": ["jquery-3.2.1.min.js", "content.js"]
}],
"browser_action": {
"default_icon": "icon.png",
"default_title": "Inspector"
},
"background": {
"scripts": ["background.js"]
}
}
content.js
Send a message with the document title to background.
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.message === "clicked_browser_action") {
chrome.runtime.sendMessage({
"message": "open_new_tab",
"title": document.title
});
}
}
);
background.js
Receives the title and try to write it to opended tab.
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.tabs.query({
active: true,
currentWindow: true
}, function (tabs) {
var activeTab = tabs[0];
chrome.tabs.sendMessage(activeTab.id, {
"message": "clicked_browser_action"
});
});
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.message === "open_new_tab") {
chrome.tabs.create({
"url": "inspector.html"
}, (tab) => {
setTimeout(() => {
//use your message data here.
chrome.tabs.executeScript(tab.id, {code: "document.title = request.title"})
}, 3000)
})
}
}
)
});
inspector.html
Empty page.
<html>
<head></head>
<body></body>
</html>
You would need 2 scripts for this - Content Script, Background Script, apart from manifest.json
Process would be :-
1)Fetch the data from current page using content script.
2)Notify Background with the your data and pass as a message from content script.
3)Receive a new message in background script and create new tab with the message data.
For e.g. - Pass title from current page, and set in new Tab.
Content Script -
//send current window title to background js.
chrome.runtime.sendMessage({'title': window.title})
Background Script -
//receive message
chrome.runtime.onMessage((message, sender) => {
chrome.tabs.create({url: 'NEW URL'}, (tab) => {
setTimeout(() => {
//use your message data here.
chrome.tabs.executeScript(tab.id, {code: "document.title = message.title"})
}, 3000);
})
});