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");
}
}
});
Related
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;
});
I am trying to fire a notification whenever I double click on a word/select it. Found several examples online but I still cannot get my example working:
Manifest:
{
"name": "hh",
"description": "hh!",
"manifest_version": 2,
"version": "0.0.0.1",
"content_scripts": [
{
"matches": [ "http://*/*", "https://*/*" ],
"js": [ "background.js" ],
"all_frames": true,
"run_at": "document_end"
}
],
"permissions": ["storage", "notifications"],
"icons": { "128": "neoprice.png" }
}
background.js
var listener = function(evt) {
var selection = window.getSelection();
if (selection.rangeCount > 0) {
displayPrice();
var range = selection.getRangeAt(0);
var text = range.cloneContents().textContent;
console.log(text);
}
};
document.addEventListener('dblclick', listener);
function displayPrice(){
chrome.notifications.create(getNotificationId(), {
title: "message.data.name",
iconUrl: 'hh.png',
type: 'basic',
message: "message.data.prompt"
}, function() {});
}
// Returns a new notification ID used in the notification.
function getNotificationId() {
var id = Math.floor(Math.random() * 9007199254740992) + 1;
return id.toString();
}
I was earlier adding the following but I saw people weren't using it, so I removed it
"app": {
"background": {
"scripts": ["background.js", "assets/jquery.min.js"]
}
},
What I am trying to achieve: Whenever they go to ANY page on selecting a word, it fires the function. Later, I wish to use this for a specific page. :)
Tried: How to keep the eventlistener real time for chrome extensions?
Chrome extension double click on a word
https://github.com/max99x/inline-search-chrome-ext
Both don't really work as I want them too. :(
Solution
It seems you are confused with background page and content script. Your background.js is a content script in fact, though its name is "background". While chrome.notifications api can be only called in background page, trying commenting displayPrice function will make your code work.
Next step
Take a look at above tutorials, wdblclick event triggers, use Message Passing to communicate with background page and call chrome.notications api in background page.
What's more
The following code is used in chrome apps rather than chrome extension.
"app": {
"background": {
"scripts": ["background.js", "assets/jquery.min.js"]
}
},
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.
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.