Custom User Agent with Iframes - javascript

Yes, I have read Load iframe content with different user agent but I do not understand how to implement it correctly/it does not work
I am trying to create a Iframe with a custom user agent inside a chrome extension, and this is my current code (from other tutorials/answers)
function setUserAgent(window, userAgent) {
if (window.navigator.userAgent != userAgent) {
var userAgentProp = { get: function () { return userAgent; } };
try {
Object.defineProperty(window.navigator, 'userAgent', userAgentProp);
} catch (e) {
window.navigator = Object.create(navigator, {
userAgent: userAgentProp
});
}
}
}
window.addEventListener("load", function() {
document.querySelector('iframe').contentWindow, 'Test User Agent';
});
<title>Title</title>
<link rel="icon" href="favicon.ico" type="image/ico" sizes="16x16">
<iframe src="https://www.whatsmyua.info/" frameborder="0" style="overflow:hidden;height:100%;width:100%" height="100%" width="100%"></iframe>
<script src='index.js'>
</script>
For Some reason the user agent is not working, as shown by the page that loads.

You need to insert that code into the page context using a content script.
1a. Inject a content script
It can be injected programmatically from your extension page into the iframe.
manifest.json
"permissions": ["webNavigation"]
index.js:
chrome.tabs.getCurrent(tab => {
chrome.webNavigation.onCommitted.addListener(function onCommitted(info) {
if (info.tabId === tab.id) {
chrome.webNavigation.onCommitted.removeListener(onCommitted);
chrome.tabs.executeScript({
frameId: info.frameId,
file: 'content.js',
runAt: 'document_start',
});
}
}, {
url: [{urlEquals: document.querySelector('iframe').src}],
});
});
1b. Declare a content script
Note that the previous approach may theoretically allow some of the iframe's inline scripts in its <head> to run first. In that case you'll have to add a dummy URL parameter to the iframe src (for example https://example.org/page/page?foobar) and use declarative matching in manifest.json:
"content_scripts": [{
"matches": ["*://example.org/*?foobar*"],
"all_frames": true,
"run_at": "document_start",
"js": ["content.js"]
}],
2. Add a page script
To run our code immediately let's run it via textContent. We're not using a separate file for page code because that would put it into a queue where it could run after the currently pending page scripts.
content.js:
var script = document.createElement('script');
script.textContent = '(' + function() {
Object.defineProperty(navigator, 'userAgent', {
get() {
return 'Test User Agent';
},
});
} + ')();';
(document.head||document.documentElement).appendChild(script);
script.remove();

Related

Display chrome extension in separate tab, open a new tab, scrape page content and display it on the extension page

So far I've always been patient enough to search SO until I found a working solution. However I am really stuck on this one.
I am trying to achieve the following:
Display a chrome extension in a new tab instead of a popup
Once a search button is clicked a new page should be opened
A content script should read the DOM and return it
The content should be displayed on the extension page
Whenever I click the search button a new page is opened.
However, I never get back any response but an error telling me "Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.".
Screenshot of error message
I assume there is an issue with the registration of the listener. I've seen questions where the issue was that a message has been sent before a listener has been registered. I can imagine something similar here but I have not yet been able to resolve it by searching answers on SO.
If anyone would have the patience to help some chrome extension newbie with this issue any help will be really appreciated!
manifest.json
{
"manifest_version": 3,
"name": "Test",
"version": "1",
"icons": {
"32": "images/favicon.png"
},
"action":
{
"default_title":"Test",
"default_popup": "ui/main.html"
},
"content_security_policy": {
"extension_pages": "script-src 'self'; object-src 'self'; script-src-elem 'self' 'unsafe-inline' http://code.jquery.com/jquery-latest.min.js;"
},
"background": {
"service_worker": "js/background.js"
},
"content_scripts": [{
"matches": ["*://*/*"],
"js": ["js/content.js"]
}],
"permissions": [
"activeTab"
]
}
main.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="description" content="Test" />
<meta charset="utf-8">
<title>Trade Monitoring</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="author" content="">
<link rel="stylesheet" href="../css/style.css">
<script src="../js/main.js"></script>
</head>
<body>
<center>
<div class="main">
<center>
<button id="search">Search</button>
<div id="result">
</div>
</center>
</div>
</center>
</body>
</html>
main.js
var link= "https://www.google.com";
var extensionTab = 0;
var pageTab = 0;
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var currTab = tabs[0];
if (currTab) { // Sanity check
extensionTab = currTab.id;
}
});
document.addEventListener('DOMContentLoaded', function () {
var btn = document.getElementById('search');
btn.addEventListener('click', function() {
search();
});
});
function search(){
scrape();
}
async function scrape(){
await openPage();
getContent();
}
function openPage(){
return new Promise((resolve, reject) => {
try{
chrome.tabs.create({ url: link }, function (newTab){
pageTab = newTab.id;
resolve(pageTab);
});
}catch(e){
reject(e);
}
})
}
function getContent(){
chrome.tabs.sendMessage(pageTab, {method: 'get_dom'}, function (response) {
console.log(response);
});
//TODO: Display content on extension page
}
content.js
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
alert("here");
if (request.name == "name") {
payload = request.payload;
}
});
background.js
chrome.tabs.create({url: '../ui/main.html'})
chrome.tabs.create() returns to your code immediately, before the tab actually starts loading, so your message won't be delivered because messaging doesn't wait for future listeners.
You can either repeat sendMessage in 100ms intervals until it succeeds or you can reverse the direction of the communication and let the new tab's content script be an initiator:
main.js
async function scrape(url) {
const tab = await chrome.tabs.create({ url });
return new Promise(resolve => {
chrome.runtime.onMessage.addListener(function onMessage(msg, sender) {
if (msg.method === 'dom' && sender.tab.id === tab.id) {
resolve(msg.data);
chrome.runtime.onMessage.removeListener(onMessage);
}
});
});
}
content.js:
// analyze DOM
// ..........
chrome.runtime.sendMessage({ method: 'dom', data: ['whatever'] });
The returned data must be JSON-compatible (number, string, boolean, null, and objects/arrays consisting of these types). Attempting to return a DOM element or a Promise or Map/Set will produce an empty {}.

Execute Chrome Developer Console Commands though Chrome Extension [duplicate]

I am trying to create an extension that will have a side panel. This side panel will have buttons that will perform actions based on the host page state.
I followed this example to inject the side panel and I am able to wire up a button onClick listener. However, I am unable to access the global js variable. In developer console, in the scope of the host page I am able to see the variable (name of variable - config) that I am after. but when I which to the context of the sidepanel (popup.html) I get the following error -
VM523:1 Uncaught ReferenceError: config is not defined. It seems like popup.html also runs in a separate thread.
How can I access the global js variable for the onClick handler of my button?
My code:
manifest.json
{
"manifest_version": 2,
"name": "Hello World",
"description": "This extension to test html injection",
"version": "1.0",
"content_scripts": [{
"run_at": "document_end",
"matches": [
"https://*/*",
"http://*/*"
],
"js": ["content-script.js"]
}],
"browser_action": {
"default_icon": "icon.png"
},
"background": {
"scripts":["background.js"]
},
"permissions": [
"activeTab"
],
"web_accessible_resources": [
"popup.html",
"popup.js"
]
}
background.js
chrome.browserAction.onClicked.addListener(function(){
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
chrome.tabs.sendMessage(tabs[0].id,"toggle");
})
});
content-script.js
chrome.runtime.onMessage.addListener(function(msg, sender){
if(msg == "toggle"){
toggle();
}
})
var iframe = document.createElement('iframe');
iframe.style.background = "green";
iframe.style.height = "100%";
iframe.style.width = "0px";
iframe.style.position = "fixed";
iframe.style.top = "0px";
iframe.style.right = "0px";
iframe.style.zIndex = "9000000000000000000";
iframe.frameBorder = "none";
iframe.src = chrome.extension.getURL("popup.html")
document.body.appendChild(iframe);
function toggle(){
if(iframe.style.width == "0px"){
iframe.style.width="400px";
}
else{
iframe.style.width="0px";
}
}
popup.html
<head>
<script src="popup.js"> </script>
</head>
<body>
<h1>Hello World</h1>
<button name="toggle" id="toggle" >on</button>
</body>
popup.js
document.addEventListener('DOMContentLoaded', function() {
document.getElementById("toggle").addEventListener("click", handler);
});
function handler() {
console.log("Hello");
console.log(config);
}
Since content scripts run in an "isolated world" the JS variables of the page cannot be directly accessed from an extension, you need to run code in page's main world.
WARNING! DOM element cannot be extracted as an element so just send its innerHTML or another attribute. Only JSON-compatible data types can be extracted (string, number, boolean, null, and arrays/objects of these types), no circular references.
1. ManifestV3 in modern Chrome 95 or newer
This is the entire code in your extension popup/background script:
async function getPageVar(name, tabId) {
const [{result}] = await chrome.scripting.executeScript({
func: name => window[name],
args: [name],
target: {
tabId: tabId ??
(await chrome.tabs.query({active: true, currentWindow: true}))[0].id
},
world: 'MAIN',
});
return result;
}
Usage:
(async () => {
const v = await getPageVar('foo');
console.log(v);
})();
See also how to open correct devtools console.
2. ManifestV3 in old Chrome and ManifestV2
We'll extract the variable and send it into the content script via DOM messaging. Then the content script can relay the message to the extension script in iframe or popup/background pages.
ManifestV3 for Chrome 94 or older needs two separate files
content script:
const evtToPage = chrome.runtime.id;
const evtFromPage = chrome.runtime.id + '-response';
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg === 'getConfig') {
// DOM messaging is synchronous so we don't need `return true` in onMessage
addEventListener(evtFromPage, e => {
sendResponse(JSON.parse(e.detail));
}, {once: true});
dispatchEvent(new Event(evtToPage));
}
});
// Run the script in page context and pass event names
const script = document.createElement('script');
script.src = chrome.runtime.getURL('page-context.js');
script.dataset.args = JSON.stringify({evtToPage, evtFromPage});
document.documentElement.appendChild(script);
page-context.js should be exposed in manifest.json's web_accessible_resources, example.
// This script runs in page context and registers a listener.
// Note that the page may override/hook things like addEventListener...
(() => {
const el = document.currentScript;
const {evtToPage, evtFromPage} = JSON.parse(el.dataset.args);
el.remove();
addEventListener(evtToPage, () => {
dispatchEvent(new CustomEvent(evtFromPage, {
// stringifying strips nontranferable things like functions or DOM elements
detail: JSON.stringify(window.config),
}));
});
})();
ManifestV2 content script:
const evtToPage = chrome.runtime.id;
const evtFromPage = chrome.runtime.id + '-response';
// this creates a script element with the function's code and passes event names
const script = document.createElement('script');
script.textContent = `(${inPageContext})("${evtToPage}", "${evtFromPage}")`;
document.documentElement.appendChild(script);
script.remove();
// this function runs in page context and registers a listener
function inPageContext(listenTo, respondWith) {
addEventListener(listenTo, () => {
dispatchEvent(new CustomEvent(respondWith, {
detail: window.config,
}));
});
}
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg === 'getConfig') {
// DOM messaging is synchronous so we don't need `return true` in onMessage
addEventListener(evtFromPage, e => sendResponse(e.detail), {once: true});
dispatchEvent(new Event(evtToPage));
}
});
usage example for extension iframe script in the same tab:
function handler() {
chrome.tabs.getCurrent(tab => {
chrome.tabs.sendMessage(tab.id, 'getConfig', config => {
console.log(config);
// do something with config
});
});
}
usage example for popup script or background script:
function handler() {
chrome.tabs.query({active: true, currentWindow: true}, tabs => {
chrome.tabs.sendMessage(tabs[0].id, 'getConfig', config => {
console.log(config);
// do something with config
});
});
}
So, basically:
the iframe script gets its own tab id (or the popup/background script gets the active tab id) and sends a message to the content script
the content script sends a DOM message to a previously inserted page script
the page script listens to that DOM message and sends another DOM message back to the content script
the content script sends it in a response back to the extension script.

What's the best way to call a content scripts' function from the background script in a Firefox extension?

I want to call a function that is implemented in the content script of an extension, that gets the selected text from webpages, from a function in the background script that will be later called in a listener connected to a menu item.
Is that possible and what would be the shortest way to do it?
Here are the relevant code snippets:
manifest.json
"background": {
"scripts": ["background.js"]
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
}
]
content.js
var text = "";
function highlightedText() {
text = content.getSelection();
}
background.js
function listenerFunction() {
highlightedText();
/* Doing various stuff that have to use the text variable */
}
browser.menus.onClicked.addListener((info, tab) => {
highlightedText();
});
Obviously, the above code is not working as the "highlighted" function is now visible from the background script.
So, what's the quickest / shortest way to make the code work?
OK. I'm having to crib this from one of my own private extensions but the gist is this:
In the background script set up the menu, and assign a function to the onclick prop:
browser.menus.create({
id: 'images',
title: 'imageDownload',
contexts: ['all'],
onclick: downloadImages
}, onCreated);
Still in the same script get the current tab information, and send a message to the content script.
function getCurrentTab() {
return browser.tabs.query({ currentWindow: true, active: true });
}
async function downloadImages() {
const tabInfo = await getCurrentTab();
const [{ id: tabId }] = tabInfo;
browser.tabs.sendMessage(tabId, { trigger: 'downloadImages' });
}
The content script listens for the message:
browser.runtime.onMessage.addListener(data => {
const { trigger } = data;
if (trigger === 'downloadImages') doSomething();
});
And once the processing is done pass a new message back to the background script.
function doSomething() {
const data = [1, 2, 3];
browser.runtime.sendMessage({ trigger: 'downloadImages', data });
}
And in a separate background script I have the something like the following:
browser.runtime.onMessage.addListener(data => {
const { trigger } = data;
if (trigger === 'downloadImages') ...
});

chrome.webRequest.onBeforeRequest acting unpredictably

I'm trying to make a web filtering chrome extension that will block certain sites and replace their html with a block page included in the extension.
let bans = ["*://*.amazon.com/*", "*://*.youtube.com/*", "*://*.netflix.com/*","*://*.facebook.com/*", "*://*.twitter.com/*"];
chrome.webRequest.onBeforeRequest.addListener(
function(details){
try{
chrome.tabs.executeScript(null, {file: "content.js"});
}
catch(error){
console.error(error);
}
return {cancel: true};
},
{urls: bans},
["blocking"]
);
This should mean that if I try to visit any site on that banned list the content script should replace the page with my own block page. However for some reason some sites never load the block page, other sites don't get blocked at all and some sites seem to work perfectly. Even stranger the listener seems to be triggered on sites not listed in the bans array at all. I can't figure out any sort of pattern between these behaviors.
I don't believe they are the source of the problem but here are the permissions in my manifest (manifest v2)
"web_accessible_resources": [
"certBlockPage.html",
"blockPageLight.html"
],
"incognito": "split",
"permissions": [
"webNavigation",
"webRequest",
"webRequestBlocking",
"tabs",
"windows",
"identity",
"http://*/*",
"https://*/*",
"<all_urls>"
]
and here is the content.js file
window.onload = function(){
fetch(chrome.runtime.getURL('certBlockPage.html'))
.then(r => r.text())
.then(html => {
document.open();
document.write(html);
document.close;
});
}
There are several problems.
You didn't specify runAt: 'document_start' in executeScript's options so it will wait in case you navigated to a banned site while the old page in this tab was still loading.
executeScript is asynchronous so it can easily run after load event was already fired in the tab so your window.onload will never run. Don't use onload, just run the code immediately.
You always run executeScript in the currently focused tab (the term is active) but the tab may be non-focused (inactive). You need to use details.tabId and details.frameId.
The user may have opened a new tab and typed the blocked url or clicked its link, which is blocked by your {cancel: true}, but then executeScript will fail because the newtab page, which is currently shown in this tab, can't run content scripts. Same for any other chrome:// or chrome-extension:// tab or when a network error is displayed inside the tab.
If you call onBeforeRequest.addListener another time without removing the previous registration, both will be active.
document.write will fail on sites with strict CSP
Solution 1: webRequest + executeScript
background script:
updateListener(['amazon.com', 'youtube.com', 'netflix.com']);
function updateListener(hosts) {
chrome.webRequest.onBeforeRequest.removeListener(onBeforeRequest);
chrome.webRequest.onBeforeRequest.addListener(
onBeforeRequest, {
types: ['main_frame', 'sub_frame'],
urls: hosts.map(h => `*://*.${h}/*`),
}, [
'blocking',
]);
}
function onBeforeRequest(details) {
const {tabId, frameId} = details;
chrome.tabs.executeScript(tabId, {
file: 'content.js',
runAt: 'document_start',
matchAboutBlank: true,
frameId,
}, () => chrome.runtime.lastError && redirectTab(tabId));
// Cancel the navigation without showing that it was canceled
return {redirectUrl: 'javascript:void 0'};
}
function redirectTab(tabId) {
chrome.tabs.update(tabId, {url: 'certBlockPage.html'});
}
content.js:
fetch(chrome.runtime.getURL('certBlockPage.html'))
.then(r => r.text())
.then(html => {
try {
document.open();
document.write(html);
document.close();
} catch (e) {
location.href = chrome.runtime.getURL('certBlockPage.html');
}
});
Solution 2: webRequest + redirection
No need for content scripts.
const bannedHosts = ['amazon.com', 'youtube.com', 'netflix.com'];
chrome.webRequest.onBeforeRequest.addListener(
details => ({
redirectUrl: chrome.runtime.getURL('certBlockPage.html'),
}), {
types: ['main_frame', 'sub_frame'],
urls: bannedHosts.map(h => `*://*.${h}/*`),
}, [
'blocking',
]);
Solution 3: declarativeNetRequest + redirection
This is the fastest method but it's limited to a very simple predefined set of actions. See the documentation and don't forget to add "declarativeNetRequest" to "permissions" in manifest.json, and "<all_urls>" to host_permissions (ManifestV3 still doesn't support optional permissions).
const bannedHosts = ['amazon.com', 'youtube.com', 'netflix.com'];
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: bannedHosts.map((h, i) => i + 1),
addRules: bannedHosts.map((h, i) => ({
id: i + 1,
action: {type: 'redirect', redirect: {extensionPath: '/certBlockPage.html'}},
condition: {urlFilter: `||${h}/`, resourceTypes: ['main_frame', 'sub_frame']},
})),
});

javascript - Chrome extension: Communication between content.js and background.js on load

Edit: Modified code using https://developer.chrome.com/extensions/devtools#evaluated-scripts-to-devtools as reference. Still no luck.
I'm trying to code a chrome-extension which uses chrome.* API call and save portions of the result in a file. I want to automate everything from the loading of the page to the text file download and hence, I don't want to use the browser.onclick() event.
My current attempt has no effect.
What changes would I need to make?
https://stackoverflow.com/a/16720024
Using the above answer as reference, I attempted the following:
manifest.json
{
"name":"Test Extension",
"version":"0.0.1",
"manifest_version": 2,
"description":"Description",
"permissions":["tabs"],
"background": {
"scripts": ["background.js"]
},
"devtools_page": "devtools.html"
}
background.js
// Background page -- background.js
chrome.runtime.onConnect.addListener(function(devToolsConnection) {
// assign the listener function to a variable so we can remove it later
var devToolsListener = function(message, sender, sendResponse) {
// Inject a content script into the identified tab
chrome.tabs.executeScript(message.tabId,
{ file: message.scriptToInject });
}
// add the listener
devToolsConnection.onMessage.addListener(devToolsListener);
devToolsConnection.onDisconnect.addListener(function() {
devToolsConnection.onMessage.removeListener(devToolsListener);
});
}
devtools.js
var backgroundPageConnection = chrome.runtime.connect({
name: "devtools-page"
});
backgroundPageConnection.onMessage.addListener(function (message) {
// Handle responses from the background page, if any
});
chrome.devtools.network.onRequestFinished.addListener(
function(request) {
chrome.runtime.sendMessage({
string: "Hi",
tabId: chrome.devtools.inspectedWindow.tabId,
scriptToInject: "content.js"
});
}
);
chrome.runtime.sendMessage({
string: "Hi",
tabId: chrome.devtools.inspectedWindow.tabId,
scriptToInject: "content.js"
});
content.js
alert("Hello");

Categories

Resources