How to get and save images from cache - chrome extension - javascript

I would like to be able to download an image that has be cached by chrome to a specified folder using a chrome extension. Currently I am getting the url of the image and passing it to the background.js which then downloads the image. Yet on this specific website (the website this chrome extension is being built around), does not allow you to get the url of the image.
What changes to my code will I need to be able to do this?
(the extension flips to another page, and attempts to download around 50 images per subsection of the site. this site also does not have 1 class for each image, and regularly makes the class name or Id the token of the individual)
popup.js
const scriptCodeCollect =
`(function() {
// collect all images
let images = document.querySelectorAll('img');
let srcArray = Array.from(images).map(function(image) {
return image.currentSrc;
});
chrome.storage.local.get('savedImages', function(result) {
// remove empty images
imagestodownload = [];
for (img of srcArray) {
if (img) {
img.substring("data:image/".length, img.indexOf(";base64"));
console.log(img);
}imagestodownload.push(img);
};
result.savedImages = imagestodownload;
chrome.storage.local.set(result);
console.log("local collection setting success:"+result.savedImages.length);
});
})();`;
const scriptCodeDownload =
`(function() {
chrome.storage.local.get('savedImages', function(result) {
let message = {
"savedImages" : result.savedImages
};
chrome.runtime.sendMessage(message, function(){
console.log("sending success");
});
});
})();`;
function injectTheScript() {
// Gets all tabs that have the specified properties, or all tabs if no properties are specified (in our case we choose current active tab)
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
// Injects JavaScript code into a page
chrome.tabs.executeScript(tabs[0].id, {file: "content_script.js"});
});
}
// adding listener to your button in popup window
document.getElementById('flipbtn').addEventListener('click', injectTheScript);
document.getElementById("startbtn").addEventListener("click", function() {
if((url.includes('http://127.0.0.1:5500/example%20test/physics/'))) {
document.getElementById("success").innerHTML = "<strong><u>In progress<br></u></strong> Currently downloading: <br>" +urlfilename
setInterval(function(){ //Loops download
chrome.tabs.query({active: true, lastFocusedWindow: true}, tabs => {
let urlfilename = tabs[0].url.replace('http://127.0.0.1:5500/example%20test/', '').replace('.html', '').replace('?','');
document.getElementById("success").innerHTML = "<strong><u>In progress<br></u></strong> Currently downloading: <br>" +urlfilename
let bknb= tabs[0].url.replace('http://127.0.0.1:5500/example%20test/', '').replace('.html', '').replace('/',' ').replace('?', '').replace(/\D/g, '');
chrome.storage.local.set({'foo': urlfilename, 'bar': bknb}, function() {
console.log('Settings saved');
console.log(urlfilename);
});
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
sleep(5000).then(() => { //Pauses so URL can write and display correctly
chrome.tabs.executeScript({code : scriptCodeCollect}); //Collects Image (page is saved as image on the website)
let textCollect = document.getElementById('textCollect');
chrome.storage.local.get('savedImages', function(result) {
textCollect.innerHTML = "collected "+ result.savedImages.length + " images";
});
});
chrome.tabs.executeScript({code : scriptCodeDownload}); //Downloads Image
document.getElementById("warning").innerHTML = "test"
sleep(5000).then(() => { //Waits so image can download fully
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
chrome.tabs.executeScript(tabs[0].id, {file: "content_script.js"}); //Injects Script to flip page
});
});
});
}, 10000); //Waits 10s and then loops
background.js
let downloadsArray= [];
let initialState = {
'savedImages': downloadsArray
};
chrome.runtime.onInstalled.addListener(function() {
chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
chrome.declarativeContent.onPageChanged.addRules([{
conditions: [
new chrome.declarativeContent.PageStateMatcher({
css: ['img'],
css: ['img[input[type="image"[aspect-ratio[attr(1000)/attr(1419)]]]]'],
css: ['id[image]']
})],
actions: [ new chrome.declarativeContent.ShowPageAction() ]
}]);
});
chrome.storage.local.set(initialState);
console.log("initialState set");
});
//chrome.downloads.onChanged.addListener(function() {
chrome.storage.local.get(['foo', 'bar'], function(items) {
var urlfilename = items.foo
console.log(urlfilename)
var bkpg= items.bar
// });
chrome.runtime.onMessage.addListener(
function(message, callback) {
console.log("message coming");
console.log(message);
let srcArray = message.savedImages;
var counter = bkpg-1;
for (let src of srcArray) {
console.log(src);
chrome.downloads.download({url:src, filename:urlfilename + ".png"});
};
});
});

Related

Add or remove content script on click of a toggle button inside a chrome popup?

I am developing my first chrome extension, I want to have a toggle button on my popup which allows users to enable or disable the extension for that site, though not completely disable the extension but technically control the execution of the content.js for that particular domain.
Something similar to
popup.js
const toggleBtn = document.querySelector(".toggle");
toggleBtn.addEventListener("click", function(){
if(toggleBtn.checked === true){
console.log('inject content.js');
}else{
console.log('remove content.js');
}
});
I can see there is a way to inject it but there is no way to remove it.
chrome.scripting.executeScript({
target: {tabId},
files: ['content.js'],
});
Note, I don't want to reload the page but want to disable the content.js. How can I achieve this functionality inside my chrome extension. I see there are many extensions doing this like Adblocker and Wordtune.
I went through few SO threads like this one but most of them are for Manifest V2 and few packages that are recommended are obsolate now. I also could not find a complete example anywhere.
Todo
I should be able to turn the extension on/off for the current domain from within the Popup.
It should store the state inside a chrome.storage.sync so that next time I installed the extension I should not again turn on/off for the selected websites.
As soon as I toggle the button, the changes should be visible if I have opened the same domain in another tab.
The extension's icon should be changed in real time as well e.g grey icon for disabled status
If I close and open a website, the icon and the toggle button should be restored their on/off status and update the icon accordingly.
My Attempt (Solves #toDo 1,2,3,4,5)
background.js
const icons = {
enabled: {
'16': 'icon-16.png',
'19': 'icon-19.png',
'38': 'icon-38.png',
'48': 'icon-48.png',
'128': 'icon-128.png'
},
disabled: {
'16': 'icon-16-off.png',
'19': 'icon-19-off.png',
'38': 'icon-38-off.png',
'48': 'icon-48-off.png',
'128': 'icon-128-off.png'
}
};
const getExtensionStatus = host => new Promise((resolve, reject) => {
chrome.storage.sync.get(host, result => {
if (result[host] !== undefined) {
resolve(result[host]);
} else {
setExtensionStatus(host, true);
}
});
});
const setExtensionStatus = (host, toggleBtnStatus) => {
const data = { [host]: toggleBtnStatus };
chrome.storage.sync.set(data, () => {});
};
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === "install") {
chrome.action.setIcon({ path: icons.enabled });
}
});
const init = async tab => {
const url = new URL(tab.url);
if (url.protocol !== "chrome-extension:") {
const host = url.host;
const status = await getExtensionStatus(host);
const icon = status ? icons.enabled : icons.disabled;
chrome.action.setIcon({ path: icon });
}
};
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === "complete") init(tab);
});
chrome.tabs.onActivated.addListener(info => {
chrome.tabs.get(info.tabId, init);
});
chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {
if (request.type === "getExtensionStatus") {
const status = await getExtensionStatus(request.host);
sendResponse({ status });
return true;
} else if (request.type === "setExtensionStatus") {
setExtensionStatus(request.host, request.status);
const icon = request.status ? icons.enabled : icons.disabled;
chrome.action.setIcon({ path: icon });
}
});
popup.js
document.addEventListener("DOMContentLoaded", function () {
const toggleBtn = document.querySelector(".toggle");
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
const host = new URL(tabs[0].url).host;
chrome.runtime.sendMessage({ type: "getExtensionStatus", host }, (response) => {
toggleBtn.checked = response.status;
});
toggleBtn.addEventListener("click", () => {
chrome.runtime.sendMessage({ type: "setExtensionStatus", host, status: toggleBtn.checked });
});
});
});
What left?
The content.js is not available after some time of inactiveness of the tab. How can I inject the script again and avoid running event multiple times?
content.js
document.addEventListener("keydown", listenEnterkey);
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === "setExtensionStatus") {
console.log(request.status);
if (request.status) {
// Add event listener here
document.addEventListener("keydown", listenEnterkey);
} else {
// Remove event listener here
document.removeEventListener("keydown", listenEnterkey);
}
}
});
For example, if you add a button with a script, I don't think the added button will disappear even if you remove the script.
Therefore, the ON/OFF function is built into the script, and it is operated by the message.
manifest.json
{
"name": "content_scripts + popup",
"version": "1.0",
"manifest_version": 3,
"content_scripts": [
{
"js": [
"matches.js"
],
"matches": [
"<all_urls>"
]
}
],
"host_permissions": [
"<all_urls>"
],
"action": {
"default_popup": "popup.html"
}
}
matches.js
console.log("matches.js");
let isEnabled = true;
console.log(isEnabled);
chrome.runtime.onMessage.addListener((message) => {
switch (message) {
case "on":
isEnabled = true;
break;
default:
isEnabled = false;
break;
}
console.log(isEnabled);
});
popup.js
const send = (s) => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
chrome.tabs.sendMessage(tabs[0].id, s);
});
}
document.getElementById("on").onclick = () => {
send("on");
}
document.getElementById("off").onclick = () => {
send("off");
}
popup.html
<html>
<body>
<button id="on">on</button>
<button id="off">off</button>
<script src="popup.js"></script>
</body>
</html>

Can I do tabs.sendMessage to specific tab?

For example, I have 3 tabs opened and the third tab is active. Can I "sendMessage" from the background script to the 1st tab(inactive)?
Also, how to do this, ".tabs.update"
I can do ".tabs.update" to the active tab by this,
browser.tabs.sendMessage(tabs[0].id, {
stat: "check"
}, function (response) {
browser.tabs.update(tabs[0].id, {
url: response.url
});
});
This sample sends to previously updated tabs every time a tab is updated.
const tabIds = {};
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === "complete") {
tabIds[tabId] = "";
for (const tabId in tabIds) {
console.log("sendMessage tabId=", tabId);
chrome.tabs.sendMessage(parseInt(tabId, 10), "hoge");
}
}
});

Why does chrome.storage.sync.set not load user setting instantly?

For my chrome extension's options page I have two radio buttons to switch between showing and not showing the buttons on a page. The function works but it takes me a few reloads of the web page before the changes take effect. Does it take some time to save to chrome.storage before the changes take effect/can be used?
here is the JS for my options page:
const saveBtn = document.getElementById('save-btn');
saveBtn.addEventListener('click', () => {
if(document.getElementById('show_btns').checked){
console.log('true');
chrome.storage.sync.set({ btn_disp: true });
} else {
console.log('false');
chrome.storage.sync.set({ btn_disp: false });
}
});
Background script waits for page to load then injects content scripts and css and sends user data with message.
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
//inject the script into page
//only inject on complete load
//listening for the 'complete' message from tabs
if(changeInfo.status === 'complete' && /^http/.test(tab.url)){
chrome.scripting.executeScript({
target: { tabId: tabId },
files: ['./src/js/foreground.js']
})
.then(() => {
console.log('INJECTED');
})
.catch(err => console.log(err));
chrome.scripting.insertCSS({
target: {tabId: tabId},
files: ['./src/css/page_btn_styles.css']
})
.then(() => {
console.log('page_btn_styles.css injected');
sendToForeground();
})
.catch(err => console.log(err));
}
});
//page loaded -> load buttons
function sendToForeground() {
chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
chrome.tabs.sendMessage(tabs[0].id, { message: 'page_loaded', data: user_data }, (response) => {
console.log('page_loaded message sent');
});
});
}
My content script listens for a message from the background script that the page has loaded. The background script sends the user data object with the message.
//message from background.js that page has loaded
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if(request.message === 'page_loaded'){
const allElements = document.querySelectorAll('*');
for(let i = 0; i < allElements.length; i++) {
if(allElements[i].className.toString().match(/ingredient/g)){
console.log(request.data);
loadPageBtns(request.data);
break;
}
}
}
sendResponse({});
});
JS for loading page buttons in the content script
function loadPageBtns(user_data) {
if(user_data.btn_disp){
//container for buttons on page
let container = document.createElement('div');
container.id = 'btns-container';
//ingredients button
let ingredientsBtn = document.createElement('button');
ingredientsBtn.id = 'ingredients-btn';
ingredientsBtn.classList.add('page-btns');
ingredientsBtn.classList.add('animated');
ingredientsBtn.classList.add('bounceInLeft');
ingredientsBtn.innerText = 'Get Ingredients';
ingredientsBtn.addEventListener('click', () => {
scrollToIngredients();
});
ingredientsBtn.addEventListener('mouseover', () => {
ingredientsBtn.classList.remove('animated');
});
//load event handler for click on recipe
//recipe button
let recipesBtn = document.createElement('button');
recipesBtn.id = 'recipes-btn';
recipesBtn.classList.add('page-btns');
recipesBtn.classList.add('animated');
recipesBtn.classList.add('bounceInLeft');
recipesBtn.innerText = 'Get Recipe';
recipesBtn.addEventListener('click', () => {
scrollToDirections();
});
recipesBtn.addEventListener('mouseover', () => {
recipesBtn.classList.remove('animated');
});
container.appendChild(ingredientsBtn);
container.appendChild(recipesBtn);
document.body.appendChild(container);
}
}
Chrome storage does not seem to save anything when the data is false, i.e. chrome.storage.sync.set({ btn_disp: false });
does not store anything. If it is true then it does store the item.
This can be a problem depending on your default value. If you have a default of TRUE then you end up never being able to store a FALSE value.
I resolved this by storing text true or false rather then boolean.
I can't see your retrieve and default logic so I'm not sure if this is the root of your problem.

Get current URL from within a chrome.contextMenus.onClicked listener

I'm creating my first Chrome extension and I need some help. I I think everything is working except the fact that I can't get the current URL of the tab.
var menu = chrome.contextMenus.create({
"title": "extension",
"contexts": ["all"]
});
chrome.tabs.query({'active': true, 'lastFocusedWindow': true}, function (tabs) {
var siteUrl = tabs[0].url;
});
chrome.contextMenus.onClicked.addListener(function(activeTab)
{
chrome.tabs.query({'active': true, 'lastFocusedWindow': true}, function (tabs) {
var siteUrl = tabs[0].url;
});
var finalUrl = "http://example.com/";
finalUrl += encodeURI(siteUrl);
// Open the page up.
chrome.tabs.create(
{
"url" : finalUrl
}
);
});
Can anyone please help me? Thanks.
EDIT:
Thank you for your replies. I got it working by moving
var finalUrl = "http://example.com/";
finalUrl += encodeURI(siteUrl);
// Open the page up.
chrome.tabs.create(
{
"url" : finalUrl
}
Inside
chrome.tabs.query({'active': true, 'lastFocusedWindow': true}, function (tabs) {
var siteUrl = tabs[0].url;
});
chrome.tabs.getCurrent(function(tab){
alert(tab.url);
});
OR if you're in a content script,
alert(document.location.href);
The info you require are provided to you already in the callback of the onClicked listener.
chrome.contextMenus.onClicked.addListener(function(info, tab) {
// The URL of the tab (if any)
var tabURL = tab && tab.url;
// The URL of the page (if the menu wasn't triggered in a frame)
var pageURL = info.pageUrl;
// The URL of the frame (if the menu was triggered in a frame)
var frameURL = info.frameUrl;
E.g. you could achieve what you want like this:
manifest.json:
{
"manifest_version": 2,
"name": "Test Extension",
"version": "0.0",
"background": {
"persistent": false,
"scripts": ["background.js"]
},
"permissions": ["contextMenus"]
}
background.js:
var baseURL = 'http://example.com/';
chrome.contextMenus.create({
id: 'myMenu', // <-- event-pages require an ID
title: 'Do cool stuff',
contexts: ['all']
}, function () {
/* It is always a good idea to look for errors */
if (chrome.runtime.lastError) {
alert('ERROR: ' + chrome.runtime.lastError.message);
}
});
chrome.contextMenus.onClicked.addListener(function(info, tab) {
/* Check which context-menu was triggered */
if (info.menuItemId === 'myMenu') {
/* Get the URL of the frame or (if none) the page */
var currentURL = info.frameUrl || info.pageUrl;
/* Open a new tab */
chrome.tabs.create({
url: baseURL + encodeURI(currentURL)
});
}
});
If you are using content script you can use
document.location.href
document.location is Object and can provide set of useful chunks in the URL
document.location.host returns domain name ex: "http://www.google.com/"
document.location.path returns the part after the domain name
document.location.hash returns whatever after # symbol in the url
Function:
function getCurrentUrl(callBackFuntion){
//you are in content scripts
if(null == chrome.tabs || null == chrome.tabs.query){
callBackFuntion(document.location.href);
}else{
//you are in popup
var queryInfo = {
active: true,
currentWindow: true
};
chrome.tabs.query(queryInfo, function(tabs) {
var tab = tabs[0];
callBackFuntion(tab.url);
});
}
}
Function call:
function alertUrl(url){
console.log("currentUrl : " + url);
}
getCurrentUrl(alertUrl);

Backbone.js - History is creating two entries

I'm not sure how to express this in code, as I can't seem to locate the problem, but my issue is that Backbone.history seems to be recording two items when a user clicks on a list item in my app.
This is not consistent.
My app has a 4 item navigation at the bottom that links to 4 main sections (the first one being home - routed to '/'). If I load up the app, go to one of the other navigation pages, then click the 'Home' button again and then click one of the navigation options I get a list of items to choose from. If I then choose one two entries are added - Firstly, for some reason, a reference to the home route with /# at the end and then the route for the item I clicked.
The end result is that 'back' then inexplicably takes me to the home page.
If it helps, my router looks like this...
var siansplanRouter = Backbone.Router.extend({
initialize: function () {
var that = this;
this.routesHit = 0;
//keep count of number of routes handled by your application
Backbone.history.on('route', function() { that.routesHit++; }, this);
window.SiansPlanApp.render();
window.SiansPlanApp.router = this;
},
routes: {
'': 'showHome',
'home': 'showHome',
'hub': 'showHome',
'samples': 'showJqmSamples',
'mealplanner': 'showCurrentMealPlanner',
'mealplanner/:planId': 'showMealPlanner',
'recipes': 'showRecipeSearch',
'recipes/:recipeId': 'showRecipe',
'settings': 'showSettings',
'versioninfo': 'showVersionInfo',
'*other': 'showHome'
},
routesHit: 0,
back: function() {
if(this.routesHit > 1) {
window.history.back();
} else {
//otherwise go to the home page. Use replaceState if available so
//the navigation doesn't create an extra history entry
this.navigate('/', { trigger: true, replace: true });
}
},
showHome: function () {
SiansPlanApp.renderHome();
},
showJqmSamples: function () {
SiansPlanApp.renderView(new SiansPlanApp.views.Hub.Samples());
},
showMealPlanner: function (planId) {
SiansPlanApp.renderView(new SiansPlanApp.views.Planner.MealPlanner({ id: planId }));
},
showCurrentMealPlanner: function () {
SiansPlanApp.renderView(new SiansPlanApp.views.Planner.MealPlanner({ current: true }));
},
showRecipeSearch: function () {
SiansPlanApp.renderView(new SiansPlanApp.views.Recipes.Search());
},
showRecipe: function (recipeId) {
SiansPlanApp.renderView(new SiansPlanApp.views.Recipes.Recipe({ id: recipeId }));
},
showSettings: function () {
SiansPlanApp.renderView(new SiansPlanApp.views.System.Settings());
},
showVersionInfo: function () {
SiansPlanApp.renderView(new SiansPlanApp.views.About.VersionInfo.ListView());
}
});
I've got some basic elements in a kick off file too here...
define(['router', 'regions/r-app', 'jquery', 'domReady'],
function (SiansPlanRouter, AppRegion) {
var run = function () {
// Global click event handler to pass through links to navigate
$(document).on("click", "a:not([data-bypass])", function (e) {
var href = { prop: $(this).prop("href"), attr: $(this).attr("href") };
var root = location.protocol + "//" + location.host + SiansPlanApp.root;
if (href.prop && href.prop.slice(0, root.length) === root) {
e.preventDefault();
Backbone.history.navigate(href.attr, true);
}
});
$.ajaxPrefilter(function (options, originalOptions, jqXhr) {
//options.url = '/api' + options.url;
});
// Create the global namespace region object.
window.SiansPlanApp = new AppRegion();
// Adds the authorization header to all of the API requests.
$(document).ajaxSend(function (e, xhr, options) {
xhr.setRequestHeader("Authorization", 'SiansPlan ' + SiansPlanApp.cookies.getSessionData());
});
// Load up session data if any is present yet - this can't happen until the XHR headers are set up.
SiansPlanApp.session.loadSession();
// Instantiate the router.
window.SiansPlanApp.router = new SiansPlanRouter();
// Boot up the app:
Backbone.history.start();
};
return {
run: run
};
});

Categories