Execute Chrome Developer Console Commands though Chrome Extension [duplicate] - javascript

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.

Related

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') ...
});

Custom User Agent with Iframes

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();

How To Call Chrome Extension Function After Page Redirect?

I am working on building a Javascript (in-browser) Instagram bot. However, I ran into a problem.
If you run this script, the first function will be called and the page will be redirected to "https://www.instagram.com/explore/tags/samplehashtag/" and the second function will be called immediately after (on the previous URL before the page changes to the new URL). Is there a way to make the second function be called after this second URL has been loaded completely?
I have tried setting it to a Window setInterval() Method for an extended time period, window.onload and a couple of other methods. However, I can't seem to get anything to work. Any chance someone has a solution?
This is my first chrome extension and my first real project, so I may be missing something simple..
manifest.json
{
"name": "Inject Me",
"version": "1.0",
"manifest_version": 2,
"description": "Injecting stuff",
"homepage_url": "http://danharper.me",
"background": {
"scripts": [
"background.js"
],
"persistent": true
},
"browser_action": {
"default_title": "Inject!"
},
"permissions": [
"https://*/*",
"http://*/*",
"tabs"
]
}
inject.js
(function() {
let findUrl = () => {
let hashtag = "explore/tags/samplehashtag/";
location.replace("https://www.instagram.com/" + hashtag);
}
findUrl();
})();
background.js
// this is the background code...
// listen for our browerAction to be clicked
chrome.browserAction.onClicked.addListener(function(tab) {
// for the current tab, inject the "inject.js" file & execute it
chrome.tabs.executeScript(tab.ib, {
file: 'inject.js'
});
});
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
chrome.tabs.executeScript(tab.ib, {
file: 'inject2.js'
});
});
inject2.js
(function() {
if (window.location.href.indexOf("https://www.instagram.com/explore/tags/samplehashtag/") != -1){
let likeAndRepeat = () => {
let counter = 0;
let grabPhoto = document.querySelector('._9AhH0');
grabPhoto.click();
let likeAndSkip = function() {
let heart = document.querySelector('.glyphsSpriteHeart__outline__24__grey_9.u-__7');
let arrow = document.querySelector('a.coreSpriteRightPaginationArrow');
if (heart) {
heart.click();
counter++;
console.log(`You have liked ${counter} photographs`)
}
arrow.click();
}
setInterval(likeAndSkip, 3000);
//alert('likeAndRepeat Inserted');
};
likeAndRepeat();
}
})();
It is not clear from the question and the example, when you want to run your function. But in chrome extension there is something called Message Passing
https://developer.chrome.com/extensions/messaging
With message passing you can pass messages from one file to another, and similarly listen for messages.
So as it looks from your use case, you can listen for a particular message and then fire your method.
For example
background.js
chrome.runtime.sendMessage({message: "FIRE_SOME_METHOD"})
popup.js
chrome.runtime.onMessage.addListener(
function(request) {
if (request.message == "FIRE_SOME_METHOD")
someMethod();
});
EDIT
Also if you want to listen for the URL changes, you can simply put a listener provided as in the documentation.
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
console.log('updated tab');
});

How to send data from background script to popup script?

I'm well aware that there are older questions regarding this issue, but I havent't found a solution from them.
My extension is supposed to detect a copy event in contentScript.js and pass the information, that event has been detected to oncopy.js. After that oncopy.js is supposed to copy users clipboard contents and pass them to popup.js, where the content is stored using Googles storage API, and set to the input fields value in popup.html.
The copy detection works perfectly, but I don't know what to do after that. This is my first extension, so I'm still trying to get hang of things.
Here are my manifest.jsons relevant parts:
"permissions": [
"activeTab",
"storage",
"clipboardRead"
],
"background": {
"scripts": ["oncopy.js"],
"persistent": false
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["contentScript.js"]
}
]
contentScript.js:
// Fires when copy event is detected
document.addEventListener("copy", () => {
chrome.runtime.sendMessage({event: "copy"}, msg => console.log(msg))
})
oncopy.js e.g. the background script:
console.log("oncopy.js background scipt is running...");
// When copy event is detected and message of it is received, this starts to run
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
chrome.runtime.sendMessage({ event: "new clipboard" }, () => {
// This is supposed to get the clipboard contents from user
bg = chrome.extension.getBackgroundPage();
bg.document.body.innerHTML = "";
var helperdiv = bg.document.createElement("div");
document.body.appendChild(helperdiv);
helperdiv.contentEditable = true;
var range = document.createRange();
range.selectNode(helperdiv);
window.getSelection().removeAllRanges();
window.getSelection().addRange(range);
helperdiv.focus();
bg.document.execCommand("Paste");
var clipboardContents = helperdiv.innerHTML;
});
sendResponse("Message has been processed by background page");
});
popup.js:
// This is supposed to set all of the clipboards to input fields values
document.body.onload = () => {
chrome.storage.sync.get("clipboards", (clipboards) => {
if (!chrome.runtime.error) {
document.getElementById("clipboard1").value = clipboards[0];
}
});
};
// The function that gets clipboard contents in oncopy.js is supposed to pass the contents here
// addClipboard is supposed to handle multiple clipboards, but for the sake of simplicity I'm using one as an example
function addClipboard(clipboard) {
chrome.storage.get("clipboards", (clipboards) => {
clipboards[0] = clipboard;
chrome.storage.sync.set({'clipboards': clipboards}, () => {
message('Clipboard saved');
});
document.getElementById("clipboard1").value = clipboards[0];
});
}
and finally the popup.html:
<h2>Clipboards</h2>
<form>
<input type="text" id="clipboard1" value="Empty" readonly>
</form>

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