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.
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 trying to code a chrome extension. Task:
On my page I have two input type=file fields. When start uploading file from one input (by choosing file from my disk and clicking upload button), I need the other file with the same name, but other folder adding to another input and start uploading too (or post it, for example).
My problem at this moment is blocking get local file by chrome extension to operate with it.
function readFile(_path, _cb){
let txt = '';
let xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function(){
if (xmlhttp.status == 200 && xmlhttp.readyState == 4){
txt = xmlhttp.responseText;
console.log(txt)
}
};
xmlhttp.open("GET", _path, true);
xmlhttp.send();
}
window.onload = function () {
document.getElementById('button').addEventListener('click', function () {
readFile('file://F:/my-file.txt', function(_res){
console.log(_res);
});
});
}
I tried to allow access by different conditions in chrome starting (such as --disable-web-security and -–allow-file-access-from-files), but it doesn't work. The answer is file:///F:/my-file.txt net::ERR_UNKNOWN_URL_SCHEME
My manifest.json:
{
"name": "Stocksy Uploader",
"version": "1.0",
"description": "Parallel upload original video files",
"permissions": ["declarativeContent","storage",{"fileSystem": ["write", "retainEntries", "directory"]},"activeTab","file:///"],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"page_action": {
"default_popup": "popup.html"
},
"content_scripts": [
{
"matches": ["https://testingdomain/*"],
"js": ["contents.js"]
}
],
"manifest_version": 2
}
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'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.
I have an chrome extension, with 2 content script injected by manifest and one background script.
{
"manifest_version": 2,
"name": "Test",
"permissions": [
"tabs", "<all_urls>", "activeTab", "storage"
],
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": [
"content/autofill/lib_generic.js",
"content/autofill/lib.js"],
"run_at": "document_end"
}
],
"web_accessible_resources": [
"content/specific_scripts/*"
],
"background": {
"scripts": ["background.js"],
"persistent": false
}
}
lib_generic.js contains one function named apply_forms(...) (its description is not important). The function is called from lib.js file. But this procedure doesn't work with several pages, so for each such page a I have a special script - also with only one function named apply_forms(...).
I have a function, which takes current domain as input and returns name of desired specific script or false if generic should be used.
There is too many files and it's logic is more complicated, so I can't just list all (url, script) pairs in "content_scripts" directive (I also don't want to inject all specific files as content script).
I've tried something like this in background (note that it's only for demonstration):
var url = ""; //url of current tab
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if(changeInfo.status == "complete") {
var filename = getSpecificFilename(url);
chrome.tabs.executeScript(tabId, {file: filename}, function() {
//script injected
});
}
});
NOTE: getSpecificFilename(...) will always return a name
But I get Unchecked runtime.lastError while running tabs.executeScript: Cannot access a chrome:// URL on the 5th line.
Can anyone help me with this? Is it the good way to "override` function definition dynamically, or should I go different way (which one, then).
Thanks.
This means, probably, that you're getting an onUpdated event on an extension/internals page (popup? options page? detached dev tools?).
One option is to filter by URL:
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
if(changeInfo.status == "complete") {
if(!tab.url.match(/^http/)) { return; } // Wrong scheme
var filename = getSpecificFilename(url);
chrome.tabs.executeScript(tabId, {file: filename}, function() {
//script injected
});
}
});
Another (and probably better) option is to make your content script request this injection:
// content script
chrome.runtime.sendMessage({injectSpecific : true}, function(response) {
// Script injected, we can proceed
if(response.done) { apply_forms(/*...*/); }
else { /* error handling */ }
});
// background script
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.injectSpecific){
var filename = getSpecificFilename(sender.url);
chrome.tabs.executeScript(sender.tab.id, {file: filename}, function() {
sendResponse({ done: true });
});
return true; // Required for async sendResponse()
}
});
This way you know that a content script is injected and initiated this.