In the chrome app I have some js/files.js that are being loading in the index.html and others html.
I was using:
"app": {
"launch": {
"local_path": "index.html",
"width":800,
"height":800
}
},
but it is deprecated and going to be removed from webstore chrome this month.
I've changed as it recommend, with :
"app": { "background": { "scripts": ["main.js"] } },
and there it is calling the index.html
when I've made this change , javascript did not work anymore. Can't figure it out what is happening.
I've tried and read every kind of issue with chrome app and javascript files, but cant run a simple function like:
var index = {
initialize: function () {
setTimeout(function () {
var user = null;
if (user == null) {
window.location.href = "login.html";
} else {
//do something more
}
}, 500);
}
};
index.initialize();
from this js file
There is an unbalanced(extra) } in this snippet
"app": { "background": { "scripts": ["main.js"] } } },
You cannot manipulate window.location from App pages anymore, among other things.
Basically the idea is to have to use a single-page applications, or as a workaround you can open new windows with chrome.app.window as needed.
Related
I'm new to extension creation and have a problem, which I've already been able to find various ways to solve, but which are all different from mine and/or fixed with manifest V2 instead of V3 which I need.
Also, some fixes found work on their end, but not on mine, so I really don't understand the problem.
Here is my problem:
I want to make a chrome extension to take screenshots of my browser and apps
I found an online tutorial that seemed correct to me (by the way, the only tutorial that uses AND the screenshots AND the V3 manifest, so perfect!)
Following the tutorial, I got the following error: Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist.
I looked for various ways, but nothing worked, I ended up downloading the git code of the tutorial, but it does not change anything, the error is still present
From what I understand, the error is in the following line:
chrome.action.onClicked.addListener(function (tab) {
chrome.desktopCapture.chooseDesktopMedia(
["screen", "window", "tab"],
tab,
(streamId) => {
if (streamId && streamId.length) {
setTimeout(() => {
chrome.tabs.sendMessage(
tab.id,
{ name: "stream", streamId },
(response) => console.log("received user data", response) // error is here, response is undefined
);
}, 200);
}
}
);
});
I get undefined instead of the response, and I think it's from there that it's a problem, because it never goes on and therefore never activates the onMessage function, nor the content_script
Here is the full background.js code :
chrome.action.onClicked.addListener(function (tab) {
chrome.desktopCapture.chooseDesktopMedia(
["screen", "window", "tab"],
tab,
(streamId) => {
if (streamId && streamId.length) {
setTimeout(() => {
chrome.tabs.sendMessage(
tab.id,
{ name: "stream", streamId },
(response) => console.log("received user data", response)
);
}, 200);
}
}
);
});
chrome.runtime.onMessage.addListener((message, sender, senderResponse) => {
if (message.name === "download" && message.url) {
chrome.downloads.download(
{
filename: "screenshot.png",
url: message.url,
},
(downloadId) => {
senderResponse({ success: true });
}
);
return true;
}
});
Content_script
chrome.runtime.onMessage.addListener((message, sender, senderResponse) => {
if (message.name === 'stream' && message.streamId) {
let track, canvas
navigator.mediaDevices.getUserMedia({
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: message.streamId
},
}
}).then((stream) => {
track = stream.getVideoTracks()[0]
const imageCapture = new ImageCapture(track)
return imageCapture.grabFrame()
}).then((bitmap) => {
track.stop()
canvas = document.createElement('canvas');
canvas.width = bitmap.width; //if not set, the width will default to 200px
canvas.height = bitmap.height;//if not set, the height will default to 200px
let context = canvas.getContext('2d');
context.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height)
return canvas.toDataURL();
}).then((url) => {
chrome.runtime.sendMessage({name: 'download', url}, (response) => {
if (response.success) {
alert("Screenshot saved");
} else {
alert("Could not save screenshot")
}
canvas.remove()
senderResponse({success: true})
})
}).catch((err) => {
alert("Could not take screenshot")
senderResponse({success: false, message: err})
})
return true;
}
})
manifest v3
{
"name": "Screenshots",
"version": "0.0.1",
"description": "Take screenshots",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["desktopCapture", "downloads", "tabs", "nativeMessaging"],
"action": {
"default_title": "Take a Screenshot"
},
"icons": {
"16": "/assets/icon-16.png",
"32": "/assets/icon-32.png",
"48": "/assets/icon-48.png",
"128": "/assets/icon-128.png"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content_script.js"]
}
]
}
I tried several things after various research like
Disable my extensions (which makes no sense, but you never know)
Add a timeout for the response, I tried up to 20 seconds delay, but without success
Added breakpoints everywhere to see if it crosses the line or not
Here is an implementation without service worker and content scripts.
manifest.json
{
"name": "desktopCapture",
"version": "1.0",
"manifest_version": 3,
"permissions": [
"desktopCapture",
"tabs",
"downloads"
],
"action": {
"default_popup": "popup.html"
}
}
popup.html
<html>
<body>
<script src="popup.js"></script>
</body>
</html>
popup.js
const createDate = {
url: "desktopCaptuer.html",
type: "popup",
width: 800,
height: 600
};
chrome.windows.create(createDate);
desktopCaptuer.html
<html>
<body>
<input type="button" id="captuer" value="Captuer">
<script src="desktopCaptuer.js"></script>
</body>
</html>
desktopCaptuer.js
chrome.windows.getCurrent({}, w => {
chrome.windows.update(w.id, { focused: true }, () => {
document.getElementById("captuer").onclick = () => {
const sources = ["screen", "window", "tab"];
chrome.tabs.getCurrent((tab) => {
chrome.desktopCapture.chooseDesktopMedia(sources, tab, (streamId) => {
let track, canvas;
navigator.mediaDevices.getUserMedia({
video: {
mandatory: {
chromeMediaSource: "desktop",
chromeMediaSourceId: streamId
},
}
}).then((stream) => {
track = stream.getVideoTracks()[0];
const imageCapture = new ImageCapture(track);
return imageCapture.grabFrame();
}).then((bitmap) => {
track.stop();
canvas = document.createElement("canvas");
canvas.width = bitmap.width;
canvas.height = bitmap.height;
let context = canvas.getContext("2d");
context.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height);
return canvas.toDataURL();
}).then((url) => {
chrome.downloads.download({
filename: "screenshot.png",
url: url,
}, () => {
canvas.remove();
});
}).catch((err) => {
console.log(err);
alert("Could not take screenshot");
})
});
});
}
});
});
Works for me, using Chromium 107.0.5304.121 (Official. Build) Arch Linux (64-Bit).
Go to https://stackoverflow.com/
Click on the extension icon.
A new window opens, with the text "Select what you want to share. Screenshots wants to share the contents of your screen with stackoverflow.com"
Click on one of the tabs: Entire Screen, Window, Chromium Tab
Click on a screenshot preview or tab title
Click "Share"
The browser displays an alert with the text "Screenshot saved", and a file named "Screenshot.png" is created in the default downloads directory.
So, #Norio Yamamoto 's solution suits me perfectly, because I then need to make a popup to give a name and do other processing on my screen, so thanks to your help, I'm already moving on by starting to understand it HTML popups on extensions! Thanks !
For the problem itself, I was able to "fix" it in the end by reinstalling chrome, and it works as #Thomas Muller tells me... not sure why, maybe I had to break something with many tests, so the app was already working
But I noticed a problem on the version of the tutorial compared to the one with popup, the tutorial version does not work on: non-reload pages (thanks #wOxxOm for the tip by the way), nor on chrome home pages, nor on the extension page, so I really prefer the popup version, but I need to dig more to improve that
Thanks again !
All of the information I can find on this is pretty old. Like the title says I am trying to make a global variable in one script and access it from another. The purpose of the extension is to search for a class named "page-title" and then return the innerHTML of that HTML element. Once I get the code working I will specify the URL I want the extension to run on so it's not constantly running.
After a couple iterations trying to accomplish this in different ways I followed the method explained in this answer but my needs have different requirements and I am receiving the error "Unchecked runtime.lastError: Could not establish connection. Receiving end does not exist." tied to the popup.html.
I tried the Unchecked runtime error solution found here but it's been awhile (~ 7 years) since I've dived into any coding and I'm not sure I implemented it correctly.
I've also tried to pass the value between JS documents is the HTML injection method, but without overriding security defaults in the manifest that doesn't really work. It also seemed super bootstrappy and I wanted to pass the information in a more conventional way. I tried creating a global variable by simply declaring the variable outside of a function/class/if statement and loading that .js file first, but that was unsuccessful as well.
Manifest
"name": "P.P. to Sharepoint",
"version": "1.0.0",
"description": "Open P.P. client folder in sharepoint",
"manifest_version": 3,
"author": "Zach Morris",
"action":{
"default_popup": "popup.html",
"default_title": "Open Sharepoint Folder"
},
"background": {
"service_worker": "background.js"
},
"permissions": [
"activeTab",
"tabs",
"scripting",
"notifications"
],
"content_scripts": [{
"js": ["contentScript.js"],
"matches": ["<all_urls>"]
}]
}
popup.html
My popup.html is super simple and really just has a button to press. I included all the .js files in the order I thought necessary
<script src="globalVariable.js"></script>
<script src="contentScript.js"></script>
<script src="popup.js"></script>
<script src="script.js"></script>
<script src="background.js"></script>
globalVariable.js
This one is straight forward. I need to pull the client's name out of the HTML of the page then use it in an API call when I click the button in popup.js This initializes the variable and uses it as place holder.
var clientInfo = {
name: 'test name'
};
ContentScript.js
I only want to run this if importScripts is not undefined. So I threw it in the if statement. Then I make sure I pulled a client name from the page. If not I throw an error message saying no client was found.
if( 'function' === typeof importScripts) {
importScripts('globalVariable.js');
addEventListener('message', onMessage);
function onMessage(e) {
if(b[0]) {
clientInfo.name = b[0].innerHTML;
alert(clientInfo.name + ' was assigned!');
} else {
alert('There is no client on this screen ' + 'b[0] is ' + b[0] + " clientInfo = " + clientInfo.name);
};
};
} else {
console.log("Your stupid code didn't work. ");
}
popup.js
This one pulls up the globalVariable.js to use the clientInfo. and makes a call to the button in background.js
if( 'function' === typeof importScripts) {
importScripts('globalVariable.js');
addEventListener('message', onMessage);
function onMessage(e) {
const text = clientInfo.name;
const notify = document.getElementById( 'myButton' );
notify.addEventListener( 'click', () => {
chrome.runtime.sendMessage( '', {
type: 'notification',
message: text });
} );
}
}
background.js
Same thing here. I import the globalVariable script to use the global variable. The notification will eventually be replaced with the API call when the rest of the code is working properly. I probably don't need to import the script here to access the variable because I can mass it with the event listener in popup.js, but I put it in here out of desperation.
if( 'function' === typeof importScripts) {
importScripts('globalVariable.js');
addEventListener('message', onMessage);
function onMessage(e) {
// do some work here
chrome.runtime.onMessage.addListener( data => {
if ( data.type === 'notification' ) {
chrome.notifications.create(
'',
{
type: 'basic',
title: 'Notify!',
message: data.message || 'Notify!',
iconUrl: 'notify.png',
}
);
console.log("sent notification");
};
});
}
}
You can have the popup.js listen for a button click and content.js handle all the logic of finding the correct element.
popup.js
document.querySelector('#btn').addEventListener('click', () => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) =>
chrome.tabs.sendMessage(tabs[0].id, { command: 'getClientName' })
);
});
content.js
chrome.runtime.onMessage.addListener((msg, sender, response) => {
if (msg.command === 'getClientName')
findClientName(document.querySelectorAll('h3.page-title'));
});
Example of findClientName function:
const findClientName = async (element) => {
let clientName;
if (element.length > 0) {
element.length === 1
? (clientName = setClientName(element[0]))
: handleMultipleElements(element);
} else {
handleNoClientNameFound();
}
clientName ? await makeAPIRequest(clientName) : null;
};
Try this method instead maybe?
{
var x = 2;
}
so:
{
var clientInfo = {
name: 'test name'
};
}
Not very good at this language, so I thought maybe you're missing the brackets?
I am working on building a Javascript (in-browser) Instagram bot. However, I ran into a problem.
If you run this script, the first function will be called and the page will be redirected to "https://www.instagram.com/explore/tags/samplehashtag/" and the second function will be called immediately after (on the previous URL before the page changes to the new URL). Is there a way to make the second function be called after this second URL has been loaded completely?
I have tried setting it to a Window setInterval() Method for an extended time period, window.onload and a couple of other methods. However, I can't seem to get anything to work. Any chance someone has a solution?
This is my first chrome extension and my first real project, so I may be missing something simple..
manifest.json
{
"name": "Inject Me",
"version": "1.0",
"manifest_version": 2,
"description": "Injecting stuff",
"homepage_url": "http://danharper.me",
"background": {
"scripts": [
"background.js"
],
"persistent": true
},
"browser_action": {
"default_title": "Inject!"
},
"permissions": [
"https://*/*",
"http://*/*",
"tabs"
]
}
inject.js
(function() {
let findUrl = () => {
let hashtag = "explore/tags/samplehashtag/";
location.replace("https://www.instagram.com/" + hashtag);
}
findUrl();
})();
background.js
// this is the background code...
// listen for our browerAction to be clicked
chrome.browserAction.onClicked.addListener(function(tab) {
// for the current tab, inject the "inject.js" file & execute it
chrome.tabs.executeScript(tab.ib, {
file: 'inject.js'
});
});
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
chrome.tabs.executeScript(tab.ib, {
file: 'inject2.js'
});
});
inject2.js
(function() {
if (window.location.href.indexOf("https://www.instagram.com/explore/tags/samplehashtag/") != -1){
let likeAndRepeat = () => {
let counter = 0;
let grabPhoto = document.querySelector('._9AhH0');
grabPhoto.click();
let likeAndSkip = function() {
let heart = document.querySelector('.glyphsSpriteHeart__outline__24__grey_9.u-__7');
let arrow = document.querySelector('a.coreSpriteRightPaginationArrow');
if (heart) {
heart.click();
counter++;
console.log(`You have liked ${counter} photographs`)
}
arrow.click();
}
setInterval(likeAndSkip, 3000);
//alert('likeAndRepeat Inserted');
};
likeAndRepeat();
}
})();
It is not clear from the question and the example, when you want to run your function. But in chrome extension there is something called Message Passing
https://developer.chrome.com/extensions/messaging
With message passing you can pass messages from one file to another, and similarly listen for messages.
So as it looks from your use case, you can listen for a particular message and then fire your method.
For example
background.js
chrome.runtime.sendMessage({message: "FIRE_SOME_METHOD"})
popup.js
chrome.runtime.onMessage.addListener(
function(request) {
if (request.message == "FIRE_SOME_METHOD")
someMethod();
});
EDIT
Also if you want to listen for the URL changes, you can simply put a listener provided as in the documentation.
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab) {
console.log('updated tab');
});
He is my manifest.json:
{
"name": "Page Redder",
"description": "Make the current page red",
"version": "2.0",
"permissions": [
"activeTab","*://*/*"
],
"background": {
"scripts": ["background.js"],
"persistent": false
},
"browser_action": {
"default_icon" : "icon.png",
"default_title": "Make this page red"
},
"manifest_version": 2
}
Here is background.js that works (the page becomes red):
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, {code:'document.body.style.backgroundColor="red";'} );
});
If I change background.js in the following way, it fails to work:
function changeColor() {
document.body.style.backgroundColor="red";
}
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.tabs.executeScript(null, {code:';'}, function() {
changeColor();
});
});
Chrome build: 38.0.2125.111
The question: what am I doing wrong here? Why calling a function in executeScript doesn't work?
Thanks,
Racoon
You are not calling a function in executeScript.
You are calling the function in its callback, that runs in the original (background) page. It's a function describing "what to do when executeScript finishes", not the code to run.
The code that actually runs in the page you're injecting code to is ";" (which obviously does nothing).
You can run a function defined in your code with executeScript by properly converting it into a string. But note that it will not have any access to variables defined outside the function.
I think what you're trying to do is to make the code accept a parameter (color). Instead of crafting custom code each time, you should consider using Messaging to pass a command.
Example: add a file content.js with the following:
// Include guard: only execute once
if (!injected) {
injected = true;
init();
}
function init() {
// Get ready to receive a command
chrome.runtime.onMessage.addListener(function(message, sender, sendResponse) {
if(message.action == "colorBackground") {
document.body.style.backgroundColor = message.color;
}
});
}
And in your background, you can do this:
chrome.tabs.executeScript(null, {file: "content.js"}, function() {
// File executed, it's ready for the message
chrome.tabs.sendMessage(null, { action: "backgroundColor", color: "green" });
}
I use a chrome extension to fire two content scripts to inject css. If the user opens the page the contentscript-on.js loads (defined in my manifest.json):
manifest.json
{
"name": "tools",
"version": "1.1",
"description": "tools",
"browser_action": {
"default_icon": "icon-on.png",
"default_title": "tools"
},
"manifest_version": 2,
"content_scripts": [
{
"matches": [ "*://*/*" ],
"include_globs": [ "*://app.example.*/*" ],
"js": ["jquery-1.11.0.min.js", "contentscript-on.js"]
}
],
"background": {
"scripts": ["background.js"],
"persistent": true
},
"permissions": [
"storage",
"https://*.app.example.de/*", "tabs", "webNavigation"
]
}
background.js
function getToggle(callback) { // expects function(value){...}
chrome.storage.local.get('toggle', function(data){
if(data.toggle === undefined) {
callback(true); // default value
} else {
callback(data.toggle);
}
});
}
function setToggle(value, callback){ // expects function(){...}
chrome.storage.local.set({toggle : value}, function(){
if(chrome.runtime.lastError) {
throw Error(chrome.runtime.lastError);
} else {
callback();
}
});
}
chrome.browserAction.onClicked.addListener( function(tab) {
getToggle(function(toggle){
toggle = !toggle;
setToggle(toggle, function(){
if(toggle){
//change the icon after pushed the icon to On
chrome.browserAction.setIcon({path: "icon-on.png", tabId:tab.id});
//start the content script to hide dashboard
chrome.tabs.executeScript({file:"contentscript-on.js"});
}
else{
//change the icon after pushed the icon to Off
chrome.browserAction.setIcon({path: "icon-off.png", tabId:tab.id});
//start the content script to hide dashboard
chrome.tabs.executeScript({file:"contentscript-off.js"});
}
});
});
});
contentscript-on.js
$(document).ready(function() {
chrome.storage.local.get('toggle', function(data) {
if (data.toggle === false) {
return;
} else {
// do some css inject
}
});
});
contentscript-off.js
$(document).ready(function() {
// set css to original
});
Everything works fine, but how can I save the "state" of the icon? If the user close the browser and open it again, the last used contentscript should load.
Thank you very much for your help.
You have two methods (at least), one is "old" and one is "new".
Old: localStorage
Your extension pages share a common localStorage object you can read/write, and it is persistent through browser restarts.
Working with it is synchronous:
var toggle;
if(localStorage.toggle === undefined){
localStorage.toggle = true;
}
toggle = localStorage.toggle;
chrome.browserAction.onClicked.addListener( function(tab) {
var toggle = !toggle;
localStorage.toggle = toggle;
/* The rest of your code; at this point toggle is saved */
});
It's simple to work with, but there are downsides: localStorage context is different for content scripts, so they need to communicate via Messaging to get the values from the background script; also, complications arise if the extension is used in Incognito mode.
New: chrome.storage API
To work with the new method, you need permission "storage" in the manifest (does not generate a warning).
Also, unlike localStorage, working with it is asynchronous, i.e. you will need to use callbacks:
function getToggle(callback) { // expects function(value){...}
chrome.storage.local.get('toggle', function(data){
if(data.toggle === undefined) {
callback(true); // default value
} else {
callback(data.toggle);
}
});
}
function setToggle(value, callback){ // expects function(){...}
chrome.storage.local.set({toggle : value}, function(){
if(chrome.runtime.lastError) {
throw Error(chrome.runtime.lastError);
} else {
callback();
}
});
}
chrome.browserAction.onClicked.addListener( function(tab) {
getToggle(function(toggle){
toggle = !toggle;
setToggle(toggle, function(){
/* The rest of your code; at this point toggle is saved */
});
});
});
Asynchronous code is a bit harder to work with, but you get some advantages. Namely, content scripts can use chrome.storage directly instead of communicating with the parent, you can watch for changes with onChanged, and you can use chrome.storage.sync instead of (or together with) chrome.storage.local to propagate changes to all browsers a user is logged into.
EDIT
I'm including a full solution, since the OP made a mistake of mixing per-tab state and global state.
contentscript.js
$(document).ready(function() {
chrome.storage.local.get('toggle', function(data) {
if (data.toggle === false) {
return;
} else {
/* do some css inject */
}
});
chrome.storage.onChanged.addListener(function(changes, areaName){
if(areaName == "local" && changes.toggle) {
if(changes.toggle.newValue) {
/* do some css inject */
} else {
/* set css to original */
}
}
});
});
background.js:
/* getToggle, setToggle as above */
function setIcon(value){
var path = (value)?"icon-on.png":"icon-off.png";
chrome.browserAction.setIcon({path: path});
}
getToggle(setIcon); // Initial state
chrome.browserAction.onClicked.addListener( function(tab) {
getToggle(function(toggle){
setToggle(!toggle, function(){
setIcon(!toggle);
});
});
});
This way, you only need one content script.