i am trying to program an extension to intercept downloads and rename them
manifest.json:
{
"name": " Ebooks Downloader",
"description": "Automatically rename ebooks downloaded from gutenberg.org",
"version": "1.0",
"author": "",
"manifest_version": 2,
"content_scripts": [
{
"matches": ["https://gutenberg.org/ebooks/*"],
"js": ["content_script.js"]
}
],
"permissions": [
"https://gutenberg.org/*",
"storage"
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"permissions": [
"downloads"
]
}
content_script.js :
// Get the content of the h1 title
var nameProp = document.querySelector('[itemprop=name]').textContent;
// Set everything to lower case, remove special characters and standardize format
nameProp = nameProp.toLowerCase().replace(/[^a-z0-9 ]/gi, '');
var filename = nameProp.replace(' by ', ' - ');
// use the storage API
chrome.storage.local.set({[document.URL]: filename}, function() {
console.log('Book filename is stored as: ' + filename);
});
background.js:
chrome.downloads.onDeterminingFilename.addListener(function(item, suggest) {
if (item.referrer.search("gutenberg.org") == -1) {
// If the file does not come from gutenberg.org, suggest nothing new.
suggest({filename: item.filename});
} else {
// Otherwise, fetch the book's title in storage...
chrome.storage.local.get([item.referrer], function(result) {
if (result[item.referrer] == null) {
// ...and if we find don't find it, suggest nothing new.
suggest({filename: item.filename});
console.log('Nothing done.');
}
else {
// ...if we find it, suggest it.
fileExt = item.filename.split('.').pop();
var newFilename = "gutenberg/" + result[item.referrer] + "." + fileExt;
suggest({filename: newFilename});
console.log('New filename: ' + newFilename);
}
});
// Storage API is asynchronous so we need to return true
return true;
}
});
I have two problems:
the console gives two errors particularly at chrome.storage.local.set and chrome.storage.local.get it says Uncaught TypeError: Cannot read properties of undefined (reading 'local') i tried running the code only with chrome.storage.local.set({[document.URL]: "hi"}) in console and still gave error
i know that i used suggest but i want the extension to just rename the file without having me to press the pop-up
Moving my observation to an answer, since it worked:
I just noticed that you have 2 "permissions" sections in your manifest.json. It's possible the 2nd one is overriding the first one. Try combining them and see if that works.
Related
I'm trying to create a simple extension for Chrome and Firefox which just gets some content from the DOM of certain pages and adds other content.
But the issue is that it only works the first time I load it as a temporary Add-on for testing on both Chrome and Firefox, or when I hit the extension's reload button on about:debugging on Firefox.
My manifest.json only contains the following information:
{
"manifest_version": 2,
"name": "ft_blackhole",
"version": "0.1",
"description": "Shows how many days you have left before you get absorbed by the Blackhole.",
"icons": {
"48": "icons/blackhole.png"
},
"content_scripts": [
{
"matches": [
"https://profile.intra.42.fr/",
"https://profile.intra.42.fr/users/*",
"https://profile.intra.42.fr/users/*/"
],
"js": [
"ft_blackhole.js"
]
}
],
"browser_specific_settings": {
"gecko": {
"id": "ft_blackhole-0.1#intra.42.fr"
}
}
}
I use almost the same exact manifest file (same exact matches) for another extension that works and depends on the same URLs. That extension works fine. The difference between it and this one is that while they both insert content in the DOM, my older extension does it after it fetches data from an API, but this one just gets some text from the current page's DOM:
ft_blackhole.js:
console.log("Hello World");
let blackholeDiv = document
.getElementById("bh")
.getElementsByClassName("emote-bh")[0];
let daysLeft = blackholeDiv.getAttribute("data-original-title");
let daysNum = daysLeft.split(" ")[0];
let status = (() => {
if (daysNum <= 14)
return {cat: "😿", color: "#D8636F"};
else if (daysNum <= 42)
return {cat: "🙀", color: "#F0AD4E"};
else
return {cat: "😸", color: "#5CB85C"};
})();
let daysLeftDiv = document.createElement("div");
daysLeftDiv.innerText = daysLeft + ' ' + status["cat"];
daysLeftDiv.style.color = status["color"];
daysLeftDiv.style.fontSize = "0.7em";
daysLeftDiv.style.fontWeight = "400";
blackholeDiv
.children[1]
.appendChild(daysLeftDiv);
I searched all over the internet, I couldn't understand what is causing it to run only the first time the extension is installed, but then when I refresh the page, it doesn't add anything to the page anymore, until I reload the extension again from about:debugging.
I hope someone could help.
Edit:
I also noticed that it also works after a hard reload of the page on Chrome, but not after a normal reload.
Edit 2:
When I console.log() something in the content script, it works all the time, always logs when I reload the page (normal reload), but the other code for DOM manipulation doesn't...
Edit 3:
I have uploaded a static copy of the profile page if anyone wants to take a look at the HTML content: https://haddi.me/intra-example/intra.html
Edit 4:
It seems the issue is caused after everything is loaded in the DOM, by some javascript that runs after that and makes changes to it, I used Mutation Observer on the target node to log those changes, and there were indeed some few ones, what I can't figure out is how am I supposed to run my code only after that last change (Which is their modification of span#bh-date's style attribute)? This the code I added:
const blackholeDiv = document
.getElementById("bh")
.getElementsByClassName("emote-bh")[0];
// Callback function to execute when mutations are observed
• const callback = function (mutationsList, observer) {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
console.log('A child node has been added or removed: ', mutation);
}
else if (mutation.type === 'attributes') {
console.log('The ' + mutation.attributeName + ' attribute was modified: ', mutation);
}
}
};
const config = {attributes: true, childList: true, subtree: true};
const observer = new MutationObserver(callback);
observer.observe(blackholeDiv, config);
And these are the logs:
Screenshot from Chrome's Devtools Console
In your manifest.json file, I do not see a "run_at", to make sure the DOM is ready and then immediately inject the script. Use the "document_end" string in your code.
Source:
https://developer.chrome.com/docs/extensions/mv3/content_scripts/#run_time
manifest.json
{
"name": "Test Extenstion",
"description": "Content test!",
"version": "1.0",
"manifest_version": 2,
"content_scripts": [
{
"matches": ["https://haddi.me/*"],
"js": ["content.js"],
"run_at": "document_end"
}
],
"permissions": ["activeTab"]
}
Tested in Google Chrome web browser version 99.0.4844.83.
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]);
}
Basically what I am trying to do is send a message from the popup.js by grabbing values from the input fields, and onMessage in the content_script to assign those values from that local scope to the global scope. I can't manage to do it! I can close the whole code in the onMessage function, but then to get to next function of the code I have to keep clicking save on the chrome extension. If someone knows this way better than I do with chrome extensions and what not please review my code and guide me to progress. I have been on this for like a week and my head is about to expload!
I have tried enclosing all the code in the onMessage function in the content_script. I have tried flipping which .js files are sending the message and trying to send the input fields from the extension as a response. I have tried to exclude the sendMessage out of the .click DOM function. Reviewed Messaging API on Google Chrome Extension and im puzzled.
content.js file
chrome.runtime.onMessage.addListener (
function(request) {
item_name = request.name;
item_color = request.color;
item_category = request.category;
item_size = request.size;
console.log(item_name);
console.log(item_color);
console.log(item_category);
console.log(item_size);
});
var url = window.location.href;
var i;
var item_category;
var item_name;
var item_color;
var item_size;
console.log(item_name);
console.log(item_color);
console.log(item_category);
console.log(item_size);
popup.js file
document.getElementById("save_input").onclick = function() {
var item_name_save = document.getElementById("item_name_save").value;
var item_color_save = document.getElementById("item_color_save").value;
var item_category_save = document.getElementById("item_category_save").value;
var item_size_save = document.getElementById("item_size_save").value;
chrome.storage.sync.set({'item_name_save' : item_name_save}, function() {
console.log('Value is set to ' + item_name_save);
});
chrome.storage.sync.set({'item_color_save' : item_color_save}, function() {
console.log('Value is set to ' + item_color_save);
});
chrome.storage.sync.set({'item_category_save' : item_category_save}, function() {
console.log('Value is set to ' + item_category_save);
});
chrome.storage.sync.set({'item_size_save' : item_size_save}, function() {
console.log('Value is set to ' + item_size_save);
});
const msg = { name: item_name_save,
color: item_color_save,
category: item_category_save,
size: item_size_save
};
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, msg, function() {
});
});
location.reload();
// window.close();
}
manifest.json
{
"manifest_version": 2,
"name": "Supreme bot",
"description": "A bot for Supreme",
"version": "1.0",
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_icon": "icon.png",
"default_popup": "popup.html",
"default_title": "Supreme bot"
},
"content_scripts": [
{
"matches": ["http://*/*", "https://*/*"],
"js": ["bot-functionallity.js"]
}
],
"permissions": [
"activeTab",
"tabs",
"storage",
"tabs"
]
}
(When all code is in the onMessage function, works but not as intended)
The Incorrect Functionality
I basically want the content.js to NOT be all inside of the onMessage function because that doesn't work properly, and have it assign values in the global scope from the onMessage function. I just want that info to be sent 1 time from the popup.js to the content.js.
I just set the value with chrome.storage.sync.set and use chrome.storage.sync.get in the other file...
I'm new to Chrome Apps, however I did successfully get my app to work with GCM. I'm so happy! What I would like the app to do however, is open a popup window when the user gets a notification. I'm making it a video chat app. Please Any Help at all would be greatly appreciated! This code that I have is currently not working for popups, tabs or anything of the sort... :(
background.js
// Returns a new notification ID used in the notification.
function getNotificationId() {
var id = Math.floor(Math.random() * 9007199254740992) + 1;
return id.toString();
}
function messageReceived(message) {
// A message is an object with a data property that
// consists of key-value pairs.
// Concatenate all key-value pairs to form a display string.
var messageString = "";
for (var key in message.data) {
if (messageString != "")
messageString += ", "
messageString += key + ":" + message.data[key];
}
console.log("Message received: " + messageString);
// Pop up a notification to show the GCM message.
chrome.notifications.create(getNotificationId(), {
title: 'GCM Message',
iconUrl: 'gcm_128.png',
type: 'basic',
message: messageString
}, function() {});
chrome.tabs.create({url:"http://www.google.com"});
}
var registerWindowCreated = false;
function firstTimeRegistration() {
chrome.storage.local.get("registered", function(result) {
// If already registered, bail out.
if (result["registered"])
return;
registerWindowCreated = true;
chrome.app.window.create(
"register.html",
{ width: 500,
height: 400,
frame: 'chrome'
},
function(appWin) {}
);
});
}
// Set up a listener for GCM message event.
chrome.gcm.onMessage.addListener(messageReceived);
// Set up listeners to trigger the first time registration.
chrome.runtime.onInstalled.addListener(firstTimeRegistration);
chrome.runtime.onStartup.addListener(firstTimeRegistration);
Manifest.json
{
"name": "GCM Notifications",
"description": "Chrome platform app.",
"manifest_version": 2,
"version": "0.3",
"app": {
"background": {
"scripts": ["background.js"]
}
},
"permissions": ["gcm", "storage", "notifications", "tabs", "<all_urls>"],
"icons": { "128": "gcm_128.png" }
}
chrome.notifications.create should appear outside the browser. I can't see anything wrong with the call you made, though. We need to determine if you're not breaking the Content Security Policy
In Chrome Apps, due to a strict Content Security Policy these URLs must point to a local resource or use a blob or data URL. Use a 3:2 ratio for your image; otherwise a black border frames the image.
Additional references you can look at is the official notification sample repo as well as how to integrate notification with GCM.
I have a Chrome extension that I'm working on that redirects users of a non-HTTPS site to the HTTPS version automatically.
However, the current problem with this is that the user must activate this redirection manually.
This would be an easy feat to accomplish using content_scripts in manifest.json, however, according to the Chrome documentation, content scripts "Cannot...Use chrome.* APIs (except for parts of chrome.extension)".
So, here's the manifest file for my extension:
{
"name": "SSL Redirect",
"version": "1.0",
"manifest_version": 2,
"description": "Redirects plain HTTP domain.com to the encrypted, HTTPS secured version.",
"permissions": [ "tabs", "http://*/*", "https://*/*" ],
"background" : {
"page": "body.html"
},
"browser_action": {
"default_icon": "icon.png"
},
"content_scripts": [
{
"matches": ["http://www.domain.com/*"],
"js": ["redirect.js"]
}
]
}
And here's the js:
var domain = /domain.com\//;
var ssldomain = "ssl.domain.com\/";
function updateUrl(tab){
if(tab.url.match(ssldomain)) {
alert("You're already using the SSL site. :)")
throw { name: 'Error', message: 'Stopped running, already in SSL mode.' };
}
if(tab.url.match(domain)) {
var newurl = tab.url.replace(domain, ssldomain);
newurl = newurl.replace(/^http:/, 'https:');
newurl = newurl.replace("www.", "");
chrome.tabs.update(tab.id, {url: newurl});
}
if(!(tab.url.match(domain))) {
alert("This extension only works on domain.com.")
throw { name: 'Error', message: 'Stopped running, not on domain.com.' };
}
}
chrome.browserAction.onClicked.addListener(function(tab) {updateUrl(tab);});
My ultimate goal is to get this to run automatically on any page matching domain.com, without user interaction.
I'm a little stuck. Any ideas?
1) From within the content script, you can use standard methods of changing URL, since you are running in the context of the page. I.e.:
var oldUrl = location.href;
/* construct newUrl */
if(newUrl != oldUrl) location.replace(newUrl);
2) Scrap what you've written already and read about chrome.webRequest API.
This will achieve what you need without a content script or tab manipulation.
Example:
chrome.webRequest.onBeforeRequest.addListener(
function(details) {
var url = details.url.replace(/^http/, "https");
return {redirectUrl: url};
},
{urls: ["http://domain.com/*"]},
["blocking"]
);
Note: you need host permissions for "*://domain.com/*"