I'm trying to make a chrome extension to modify the HTML of a page, but I can't seem to get it to work. I have four files:
manifest.json:
{
"manifest_version": 2,
"name": "Agor.aio h3cks scrubz lel",
"description": "Dis sextenzion iz wery guud",
"version": "1.0",
"background": {
"page": "test.html"
},
"content_scripts": [
{
"matches": ["http://agar.io/*"],
"js": ["contentscript.js"],
"run_at": "document_end"
}
],
"web_accessible_resources": ["script.js", "test.html"]
}
contentscript.js:
var s = document.createElement('script');
s.src = chrome.extension.getURL('script.js');
s.onload = function() {
this.parentNode.removeChild(this);
};
(document.head||document.documentElement).appendChild(s);
script.js:
$('#overlays').load('test.html');
var changeRegion = function(region) {
if(region === "Other") {
$('#changeip').toggle();
} else {
setRegion(region);
}
}
var changeIP = function(ip){
try {
connect("ws://" + ip);
} catch(err) {
console.log(err);
setRegion(document.getElementById('region').value);
}
}
var playGame = function(nick) {
setNick(nick);
setShowMass(true);
}
I don't know how to load the test.html file from within the script.js. All help is appreciated. Thank you!
You can access web-accessible resources with a url like chrome:///test.html.
If you don't know it, the id of your extensions can be found in the chrome://extensions tab. Just put it into developer mode, and the extensions id will show up under each extension. This id doesn't change, it's tied to the key you generated in your pem file.
Related
I want to extract the HTML of a webpage so that I can analyze it and send a notification to my chrome extension. Sort of like how an adblocker does it when analyzing a web page for ads and then tell the extension how many possible ads there are.
I am trying to use the document object in content-scripts to get the HTML, however, I always seem to get the HTML of my popup file instead. Can anybody help?
content-script.js
chrome.tabs.onActivated.addListener(function(activeInfo) {
chrome.tabs.get(activeInfo.tabId, function(tab) {
console.log("[content.js] onActivated");
chrome.tabs.sendMessage(
activeInfo.tabId,
{
content: document.all[0].innerText,
type: "from_content_script",
url: tab.url
},
{},
function(response) {
console.log("[content.js]" + window.document.all[0].innerText);
}
);
});
});
chrome.tabs.onUpdated.addListener((tabId, change, tab) => {
if (tab.active && change.url) {
console.log("[content.js] onUpdated");
chrome.tabs.sendMessage(
tabId,
{
content: document.all[0].innerText,
type: "from_content_script",
url: change.url
},
{},
function(response) {
console.log("[content.js]" + window.document.all[0].innerText);
}
);
}
});
background.js
let messageObj = {};
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// Arbitrary string allowing the background to distinguish
// message types. You might also be able to determine this
// from the `sender`.
if (message.type === "from_content_script") {
messageObj = message;
} else if (message.type === "from_popup") {
sendResponse(messageObj);
}
});
manifest.json
{
"short_name": "Extension",
"version": "1.0.0",
"manifest_version": 3,
"name": "My Extension",
"description": "My Extension Description",
"permissions": ["identity", "activeTab", "tabs"],
"icons": {
"16": "logo-16.png",
"48": "logo-48.png",
"128": "logo-128.png"
},
"action": {
"default_icon": "ogo_alt-16.png",
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["./static/js/content-script.js"],
"run_at": "document_end"
}
],
"background": {
"service_worker": "./static/js/background.js"
}
}
Your current content script is nonfunctional because content scripts cannot access chrome.tabs API. If it kinda worked for you, the only explanation is that you loaded it in the popup, which is wrong because the popup is not a web page, it's a separate page with a chrome-extension:// URL.
For your current goal, there's no need for the background script at all because you can simply send a message from the popup to the content script directly to get the data. Since you're showing the info on demand there's also no need to run the content scripts all the time in all the sites i.e. you can remove content_scripts from manifest.json and inject the code on demand from the popup.
TL;DR. Remove content_scripts and background from manifest.json, remove background.js and content-script.js files.
manifest.json:
"permissions": ["activeTab", "scripting"],
popup.html:
<body>
your UI
<script src=popup.js></script>
</body>
popup.js:
(async () => {
const [tab] = await chrome.tabs.query({active: true, currentWindow: true});
let result;
try {
[{result}] = await chrome.scripting.executeScript({
target: {tabId: tab.id},
func: () => document.documentElement.innerText,
});
} catch (e) {
document.body.textContent = 'Cannot access page';
return;
}
// process the result
document.body.textContent = result;
})();
If you want to to analyze the page automatically and display some number on the icon then you will need the background script and possibly content_scripts in manifest.json, but that's a different task.
This question already has answers here:
What is the difference between a function call and function reference?
(6 answers)
Closed 1 year ago.
So I'm trying to make an extension that allows you to write custom JS to be injected on any page for a given domain. My popup loads the saved JS code, and when clicking save, the JS is evaluated, and that works just fine. I can't figure out how to get the code to evaluate on page load, though.
Here is what I have so far.
//Content.js
//Globals
var entries = {"test": "test"}; //Entries dictionary "domain": "js code"
var url = window.location.href; //Full URL of the tab
var parts = url.split("/"); //URL split by '/' character
var domain = parts[2] + ''; //Just the domain (global)
loadChanges();
chrome.runtime.onMessage.addListener(listener);
window.onload=eval(entries[domain]); //doesn't work
function listener (request, sender, sendResponse) {
console.log("Manipulating data for: " + domain);
if (request == "LOAD"){
if(entries.hasOwnProperty(domain)){
console.log("PE - Loaded Value: " + entries[domain].toString());
sendResponse(entries[domain]);
} else {
console.log("Nothing to load");
sendResponse('');
}
} else {
entries[domain] = request;
console.log(entries[domain]);
saveChanges();
eval(request); //This one DOES work
}
}
//Load saved code (on startup)
function loadChanges() {
chrome.storage.local.get(['PE'], function (data){
console.log(data.PE);
if (data.PE == null){
return;
}
entries=data.PE;
});
if(entries.hasOwnProperty(domain)){
eval(entries[domain]); //doesn't work
}
}
//Save changes to code (on button press)
function saveChanges() {
chrome.storage.local.set({PE: entries}, function(data){
console.log("Saved Value: " + entries[domain])
});
}
Note the "doesn't work" comments in there.
manifest.json
{
"name": "PersistEdit",
"version": "0.1.1",
"manifest_version": 2,
"content_scripts":[
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"run_at": "document_end",
"persistent": false
}
],
"background": {
"scripts": [
"background.js"
],
"persistent": false
},
"browser_action": {
"default_popup": "popup.html",
"default_title": "PersistEdit"
},
"permissions": [
"storage"
]
}
document.addEventListener('DOMContentLoaded', onload, false);
function onload(){
chrome.tabs.query({currentWindow: true, active: true}, function (tabs){
chrome.tabs.sendMessage(tabs[0].id, "LOAD");
});
}
Didn't include my popup.html or popup.js because those parts of it work as intended, but I can include them if necessary. I'm not sure what I'm missing here, any guidance would be appreciated.
window.onload is supposed to be a function.
Here window.onload=eval(entries[domain]); you are just assigning the result of eval to onload(which happens immediately during the assignment). It's possible that entries isn't properly populated at that time.
Try the following code
window.onload=function () {
eval(entries[domain]);
}
I'm writing my first Chrome extension and have hit a brick wall when it comes to setting and using "options." I've used Google's documentation to learn how to set up an options page and have opted to set it as my default popup in the extension.
Here's my manifest for reference:
{
"manifest_version": 2,
"name": "MyExtension",
"description": "MyDescription",
"version": "0.0",
"options_page": "options.html",
"browser_action": {
"default_icon": "on.png",
"default_popup": "options.html",
"default_title": "Manage Tools!"
},
"permissions": [
"storage",
"tabs",
"activeTab",
"https://ajax.googleapis.com/"
],
"content_scripts": [{
"matches": ["specialURL.com*"],
"js": ["jquery-3.1.1.min.js", "content.js"]
}],
"web_accessible_resources": [
"script.js"
],
"background": {
"scripts": ["background.js"]
}
}
My content.js page contains the following:
var s = document.createElement('script');
s.src = chrome.extension.getURL('script.js');
s.onload = function() {
this.remove();
};
(document.head || document.documentElement).appendChild(s);
which loads my script file (script.js). Inside of script.js there are two methods
function foo() { -code- }
function bar() { -code- }
options.js:
function save_options() {
var alltoggle = document.getElementById('alltoggle').checked;
var footoggle = document.getElementById('footoggle').checked;
var bartoggle = document.getElementById('bartoggle').checked;
chrome.storage.sync.set({
allsetting: alltoggle,
foosetting: footoggle,
barsetting: bartoggle
}, function () {
// Update status to let user know options were saved.
var status = document.getElementById('status');
status.textContent = 'Options saved.';
setTimeout(function () {
status.textContent = '';
}, 750);
});
}
// Restores select box and checkbox state using the preferences
// stored in chrome.storage.
function restore_options() {
// Default to true
chrome.storage.sync.get({
allsetting: true,
foosetting: true,
barsetting: true
}, function (items) {
document.getElementById('alltoggle').checked = items.allsetting;
document.getElementById('footoggle').checked = items.foosetting;
document.getElementById('bartoggle').checked = items.barsetting;
});
}
document.addEventListener('DOMContentLoaded', restore_options);
document.getElementById('save').addEventListener('click', save_options);
document.addEventListener('DOMContentLoaded', function () {
document.querySelector('#alltoggle').addEventListener('change', allHandler);
// Turn on/off all features
function allHandler() {
$("input:checkbox").prop('checked', $(this).prop("checked"));
}
});
The problem comes in when I try to load settings back and apply them to my content scripts. Specifically, I can't find how to do that anywhere.
Solved
At first I thought this was a messages issue, but it wasn't. By adding the following to content.js I was able to check storage for my settings and execute code from there.
var fooON;
chrome.storage.sync.get("foosetting", function(result) {
fooON = result.foosetting;
//confirm
console.log(result.foosetting);
});
var barON;
chrome.storage.sync.get("barsetting", function(result) {
barON = result.barsetting;
//confirm
console.log(result.barsetting);
});
Then, by separating foo() and bar() into two scripts I could make the script injection in content.js selective by adding if(fooOn){-inject foo()-} etc.
For others facing similar issues
You can access options saved to storage.sync by using the chrome.storage.sync.get() API call in your content scripts.
var yourSetting;
chrome.storage.sync.get("yourSetting", function(result) {
yourSetting = result.yourSetting;
});
Question
I tried to make a plugin to help modify the navigator.platform, in the main page(the web browser requested page) is well, but I found that if there is a iframe in the page, iframe in the page will not be modified by my content_scripts.js, although I have to set up in the manifest.json file all_frames: true`. This is why?
manifest.json
{
"name": "Platform Modifier",
"version": "1.0.0.0",
"manifest_version":2,
"default_locale": "en",
"permissions": ["tabs", "webRequest", "webRequestBlocking", "<all_urls>"],
"background":{
"persistent":true,
"scripts":["bg.js"]
},
"browser_action": {
"default_icon": "icon.png" ,
"default_title": "Platform Modifier",
"default_popup": "popup.html"
},
"content_scripts": [{
"matches": ["*://*/*"],
"all_frames": true,
"js": ["content_scripts.js"],
"run_at":"document_start"
}],
"web_accessible_resources":[
"insert-script.js"
]
}
content_scripts.js
var xmlhttp = null;
var url = chrome.extension.getURL("insert-script.js");
if (window.XMLHttpRequest){
xmlhttp=new XMLHttpRequest();
}
if(xmlhttp == null){
console.log("not support XMLHTTP")
}else{
xmlhttp.onreadystatechange=state_Change;
xmlhttp.open("GET",url,true);
xmlhttp.send(null);
}
function state_Change(){
if(xmlhttp.status == 200){
chrome.extension.sendRequest({op: "getAll"}, function(response) {
var replaceList = {
"TAG_PlatForm":response.value.platform,
"TAG_UserAgent":response.value.userAgent
};
var sc=document.createElement("script");
sc.type="text/javascript";
sc.innerHTML= replaceText(xmlhttp.responseText,replaceList);
var html=document.getElementsByTagName("html");
html[0].appendChild(sc);
});
}
}
function replaceText(str,regexp){
for(var key in regexp){
str = str.replace(key,regexp[key]);
}
return str;
}
insert-script.js
var myPlatForm = function() {
return 'TAG_PlatForm';
};
var myUserAgent = function() {
return 'TAG_UserAgent';
};
if (Object.defineProperty) {
Object.defineProperty(navigator, 'platform', {
get: myPlatForm
});
Object.defineProperty(navigator, 'userAgent', {
get: myUserAgent
});
} else if (Object.prototype.__defineGetter__) {
navigator.__defineGetter__('platform', myPlatForm);
navigator.__defineGetter__('userAgent', myPlatForm);
}
Complete file
Download Link, this file contains two parts: extensions.zip is the Chrome extension; testPages.zip is the HTML file for testing. In the test file, open the main.html
The value is changed, just later than it is displayed in <body onload, you can check it in devtools:
The errors in the console are fixed by using the correct condition in XHR (the code was injected multiple times for each XHR loading stage, the copies tried to redefine a non-configurable property):
if (xmlhttp.readyState == 4) {
As for reducing the delay:
Currently you're waiting for XHR to fetch the injected script and waiting for the message request to the background page.
Instead put the injected script code as a literal string in your content script and use chrome.storage.local and access the values directly in the content script and the popup page.
Hi I've written the following extension for our company machines that should in practice remove any numbers from a list that is held on our webserver.
Whilst this works in it dramatically slows down loading of medium/webpages and chrome appears to look through the list of numbers (the favicon for example refreshes whilst this happens).
Any help or guidance for a better way of achieving the same result would be immensely appreciated.
[highlight.js]
// This array will all of the numbers to highlight
var numberArray = [''];
var UPDATE_INTERVAL = 57600; // Update after 1 Minute
// Retrieve script from storage
chrome.storage.local.get({
lastUpdated: 0,
code: ''
}, function(items) {
if (Date.now() - items.lastUpdated > UPDATE_INTERVAL) {
// Get updated file, and if found, save it.
get('http://webaddress/highlight.js', function(code) {
if (!code) return;
chrome.storage.local.set({lastUpdated: Date.now(), code: code});
});
}
if (items.code) // Cached script is available, use it
execute(items.code);
else // No cached version yet. Load from extension
get(chrome.extension.getURL('highlight.js'), execute);
});
//
function execute(code) {
try { window.eval(code); } catch (e) { console.error(e); }
// Run number replacement.
numberArray.forEach(function(v){
var number = v;
ve = new RegExp(number, "g");
document.body.innerHTML = document.body.innerHTML.replace(ve,"DO-NOT-CALL");
});
}
function get(url, callback) {
var x = new XMLHttpRequest();
x.onload = x.onerror = function() { callback(x.responseText); };
x.open('GET', url);
x.send();
}
[manifest.json]
{
"background": {
},
"content_scripts": [ {
"js": [ "highlight.js"],
"run_at" : "document_idle",
"matches": [ "http://*/*", "https://*/*" ],
"css": [ "style.css" ]
} ],
"content_security_policy": "script-src 'self' 'unsafe-eval' https://ssl.google-analytics.com; object-src 'self'",
"description": "Detect telephone numbers and remove blocked numbers.",
"icons": {
"128": "icon_128.png",
"16": "icon_16.png",
"32": "icon_32.png",
"48": "icon_48.png"
},
"manifest_version": 2,
"name": "CLS Call Bar",
"permissions": [ "tabs", "storage", "http://*/*", "https://*/*" ],
"version": "1.0.0"
}
You are replacing innerHTML of whole document for every number which seems highly inefficient
document.body.innerHTML = document.body.innerHTML.replace(ve,"DO-NOT-CALL");
Instead you can use numbers in their immediate parent id/class as identifier like so
<span id="number-holder-{{number}}">{{number}}</span>
( it might already be like this as it is a common templating practice )
and in your content script do something like
numberArray.forEach(function(v){
document.getElementById("number-holder-"+v).innerHTML = "DO-NOT-CALL"
}
If changing the HTML is not an option then do the regex replace on a temporary variable with document.body.innerHTML as initial value and assign it back after the loop.
I've managed to solve this with thanks to Aman Verma for pointing out my replacing the whole innerHTML I've changed the following and its now very efficient...
From
numberArray.forEach(function(v){
var number = v;
ve = new RegExp(number, "g");
document.body.innerHTML = document.body.innerHTML.replace(ve,"DO-NOT-CALL");
});
To
numberArray.forEach(function(v){
var treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT),
textNode;
while(textNode = treeWalker.nextNode()) {
if(textNode.parentElement.tagName !== 'SCRIPT') {
textNode.nodeValue = textNode.nodeValue.replace(v, "DO NOT CALL THIS NUMBER");
}
}
});