How to tell if a script is run as content script or background script? - google-chrome-extension

In a Chrome extension, a script may be included as a content script or background script.
Most stuff it does is the same, but there are some would vary according to different context.
The question is, how could a script tell which context it is being run at?
Thank you.

I think this is a fairly robust version that worked in my initial tests and does not require a slower try catch, and it identifies at least the three primary contexts of a chrome extension, and should let you know if you are on the base page as well.
av = {};
av.Env = {
isChromeExt: function(){
return !!(window['chrome'] && window['chrome']['extension'])
},
getContext: function(){
var loc = window.location.href;
if(!!(window['chrome'] && window['chrome']['extension'])){
if(/^chrome/.test(loc)){
if(window == chrome.extension.getBackgroundPage()){
return 'background';
}else{
return 'extension';
}
}else if( /^https?/.test(loc) ){
return 'content';
}
}else{
return window.location.protocol.replace(':','');
}
}
};

Well I managed to work out this:
var scriptContext = function() {
try {
if (chrome.bookmarks) {
return "background";
}
else {
return "content";
}
}
catch (e) {
return "content";
}
}
It's because an exception would be thrown if the content script tries to access the chrome.* parts except chrome.extension.
Reference: http://code.google.com/chrome/extensions/content_scripts.html

The best solution I've found to this problem comes from over here.
const isBackground = () => location.protocol === 'chrome-extension:'

The background service worker at Manifest v3 does not contain a window.
I use this as part of my extension error handling which reloads the content scripts, when i receive an Extension context invalidated error:
...
if (!self.window) {
console.warn('Background error: \n', error);
} else {
location.reload();
}
...

Related

chrome.devtools.inspectedWindow.eval - frameURL

I am trying to get the selected element to the sidebar pane in my chrome extension.
It's working fine if the page has no frames when the element is in the frame, it's not working.
As per the document I have to pass the frameURL, but how do I get the frame or Iframe URL?
Thank you.
Note: This issue is duplicate that was opened in 3 years ago, but still no solution there, so re-opening it again.
In devtools.js
chrome.devtools.panels.elements.createSidebarPane(name, (panel) => {
// listen for the elements changes
function updatePanel() {
chrome.devtools.inspectedWindow.eval("parseDOM($0)", {
frameURL: // how to pass dynamic
useContentScriptContext: true
}, (result, exceptipon) => {
if (result) {
console.log(result)
}
if (exceptipon) {
console.log(exceptipon)
}
});
}
chrome.devtools.panels.elements.onSelectionChanged.addListener(updatePanel);
});
I ran into this as well. I ended up needing to add a content_script on each page/iframe and a background page to help pass messages between devtools and content scripts.
The key bit is that in the devtools page, we should ask the content_scripts to send back what their current url is. For every content script that was registered, we can then call chrome.devtools.inspectedWindow.eval("setSelectedElement($0)", { useContentScriptContext: true, frameURL: msg.iframe } );
Or in full:
chrome.devtools.panels.elements.createSidebarPane( "example", function( sidebar ) {
const port = chrome.extension.connect({ name: "example-name" });
// announce to content scripts that they should message back with their frame urls
port.postMessage( 'SIDEBAR_INIT' );
port.onMessage.addListener(function ( msg) {
if ( msg.iframe ) {
// register with the correct frame url
chrome.devtools.panels.elements.onSelectionChanged.addListener(
() => {
chrome.devtools.inspectedWindow.eval("setSelectedElement($0)", { useContentScriptContext: true, frameURL: msg.iframe } );
}
);
} else {
// otherwise assume other messages from content scripts should update the sidebar
sidebar.setObject( msg );
}
} );
}
);
Then in the content_script, we should only process the event if we notice that the last selected element ($0) is different, since each frame on the page will also handle this.
let lastElement;
function setSelectedElement( element ) {
// if the selected element is the same, let handlers in other iframe contexts handle it instead.
if ( element !== lastElement ) {
lastElement = element;
// Pass back the object we'd like to set on the sidebar
chrome.extension.sendMessage( nextSidebarObject( element ) );
}
}
There's a bit of setup, including manifest changes, so see this PR for a full example:
https://github.com/gwwar/z-context/pull/21
You can found url of the frame this way:
document.querySelectorAll('iframe')[0].src
Assuming there is at lease one iframe.
Please note, you cannot use useContentScriptContext: true, as it will make the script execute as a context page (per documentation) and it will be in a separate sandboxed environment.
I had a slightly different problem, but it might be helpful for your case too, I was dynamically inserting an iframe to a page, and then tried to eval a script in it. Here the code that worked:
let win = chrome.devtools.inspectedWindow
let code = `
(function () {
let doc = window.document
let insertFrm = doc.createElement('IFRAME')
insertFrm.src = 'about:runner'
body.appendChild(insertFrm)
})()`
win.eval(code, function (result, error) {
if (error) {
console.log('Eror in insertFrame(), result:', result)
console.error(error)
} else {
let code = `
(function () {
let doc = window.document
let sc = doc.createElement('script')
sc.src = '${chrome.runtime.getURL('views/index.js')}'
doc.head.appendChild(sc)
})()`
win.eval(code, { frameURL: 'about:bela-runner' }, function (result, error) {
if (error) {
console.log('Eror in insertFrame(), result:', result)
console.error(error)
}
})
}
})

validating lack of parameters in feathresjs

I've read the feathersjs documentation, but after doing a find method in a service I realized that if I don't give any query parameters, the service returns all the data, which is something I don't want. How can I define a hook to validate that there are at least one query parameter in order to proceed; otherwise, send back a 403 error (bad request).?
I have doubts in the way to do it I tried this:
app.service('myService')
.before(function(hook) {
if (hook.params.query.name === undefined){
console.log('There is no name, throw an error!');
}
})
.find({
query: {
$sort: {
year: -1
}
}
})
And I tried in hook file on hooks this (that seemed really desperate & | stupid):
function noparams (hook) {
if (hook.params.query.name === undefined){
console.log('There is no name, throw an error!');
}
}
module.exports = {
before: {
find: [ noparams(this) ] ...
}
}
but it does not compile (I don't know what to send as a parameter there), and the examples seemed to be for pre 2.0 version and on top of that the code I found seemed to be in the app.js, but all is differently coded using feathers-cli, so the examples, even in the book, aren't against the scaffolded version, which is confusing because they shows the code in a different file were should be.
Thanks.
I ended using a before hook, so the code used is this:
const errors = require('feathers-errors');
module.exports = function () {
return function (hook) {
if(hook.method === 'find'){
if (hook.params.query.name === undefined || hook.params.query.length == 0){
throw new errors.BadRequest('Invalid Parameters');
}else{
return hook;
}
}
}
};
If have used feathers-cli to generate your application (feathers v2.x) you don't need to do anything else. If is an earlier version you maybe need to add the Express error handler and it is pointed out in the documentation|Errors|REST.
Thanks.

Electron 4 Windows -> electron-builder -> auto-update: custom solution

I'm building an app for windows using Electron. To package and distribute it I'm using electron-builder. Electron-builder relies on many packages, and for auto-updates it uses Squirrel-windows.
I've been battling with auto-update on Windows for 3 days and at the end I've come up with a working solution that seems to give no problems.
I wont' go into the details of what I've tried, and failed. Instead, I'll post here the solution with which I've come up.
I'm sharing it with you guys, to see if you may point out to me any flaws that will make my system fail, or, if it truly is a solid solution, to help those who are struggling as I was. For this latter reason, I'm posting some more code than it would be necessary, hoping it will help others.
The logic is as follows:
if the sub-folder fullupdate inside the path of the current executable does not exists (see later, it will be clarified), we connect with an online server and check if there is an update by sending the current app version;
if there is no update, do nothing.
if there is an update, we instruct the server to return a json string that contains the url from which we can download of the .exe installer produced by electron-builder. NB: not the .nupkg (server code not provided :-)).
we download the file and save it inside a sub folder fullupdate in the local folder in which our app is currently saved. This should be "safe" as electron-builder saves the app in the current user folder AppData, so we should not have permissions issues.
at the end of the download, we create a new file update inside the folder fullupdate to be sure the download has finished successfully. We could also rename the file, but I prefer this way.
next time the app opens:
if the folder fullupdate exists we check if the file update exists. If it does not exists, the download was not finished, so we delete the folder fullupdate and call the remote server again to start all over again.
else, if the file update exists, we launch the .exe file we have downloaded, and return true. This will prevent the app from opening the main window. The cool thing is that the updater will delete the whole old version of the app saved in AppData (while leaving local user data) and replace it with the new version. In this way we will get rid also of the folder fullupdate.
Now the code:
// we want to run this only on windows
var handleStartupEvent = function() {
if (process.platform !== 'win32') {
return false;
}
/////////////////
// MANUAL UPDATER
/////////////////
var appFolder = 'app-' + appVersion;
var pathApp = path.dirname(process.execPath);
var pathUpdate = pathApp + '\\fullupdate';
var checkupdateurl = 'https://api.mysite.com/getjson/' + appVersion.split('.').join('-');
function checkIfDownloaded(){
if (!fs.existsSync(pathUpdate)) checkUpdate();
else return checkIfInstallLocal();
}
function checkIfInstallLocal(){
if(fileExists('fullupdate\\update')) return installLocal();
else {
deleteFolderRecursive(pathUpdate);
checkUpdate();
}
}
function installLocal(){
cp.exec('fullupdate\\Update.exe', function( error, stdout, stderr){
if ( error != null ) {
console.log(stderr);
}
});
return true;
}
// from http://www.geedew.com/remove-a-directory-that-is-not-empty-in-nodejs/
var deleteFolderRecursive = function(path) {
if( fs.existsSync(path) ) {
fs.readdirSync(path).forEach(function(file,index){
var curPath = path + "/" + file;
if(fs.lstatSync(curPath).isDirectory()) deleteFolderRecursive(curPath);
else fs.unlinkSync(curPath);
});
fs.rmdirSync(path);
}
};
// from http://stackoverflow.com/questions/4482686/check-synchronously-if-file-directory-exists-in-node-js
function fileExists(path) {
try {
return fs.statSync(path).isFile();
}
catch (e) {
if (e.code == 'ENOENT') { // no such file or directory. File really does not exist
return false;
}
throw e; // something else went wrong, we don't have rights, ...
}
}
function checkUpdate(){
https.get('https://api.mysite.com/getjson/' + app.getVersion().split('.').join('-'), (res) => {
res.setEncoding('utf8');
res.on('data', function(chunk) {
if(chunk) thereIsUpdate(chunk);
});
}).on('error', (e) => {
console.log(e);
});
}
function thereIsUpdate(chunk){
var data = JSON.parse(chunk);
if(data && data.url) getNewUpdate(data.urlsetup);
}
function getNewUpdate(url){
fs.mkdirSync(pathUpdate);
var file = fs.createWriteStream(pathUpdate + '/Update.exe');
var responseSent = false; // flag to make sure that response is sent only once.
var request = https.get(url, function(response) {
response.pipe(file);
file.on('finish', () =>{
file.close(() => {
if(responseSent) return;
responseSent = true;
});
fs.closeSync(fs.openSync(pathUpdate + '/update', 'w'));
});
});
}
if(checkIfDownloaded()) return true;
/////////////////////////
// SQUIRREL EVENTS HANDLER
//////////////////////////
// see http://stackoverflow.com/questions/30105150/handle-squirrels-event-on-an-electron-app
};
// here we call the function. It is before the opening of the window, so that we prevent the opening if we are updating, or if there is a Squirrel event going on (see SO question, link above)
if (handleStartupEvent()) {
return;
}

Image.fromURL error callback

I am trying to load an image using the fromURL. The issue is that I'd like it to be able to load a default icon if it is not able to reach the Image server to download the image. Looking at the docs I did not see an error callback for the fromURL function. How are we supposed to catch that the call was not successful and therefore do the appropriate thing? It does not seem that the callback gets called at all when image load was unsuccessful.
You can use fabric.util.loadImage() method instead of fabric.Image.fromURL().
If you look at the fromURL() method implementation, internally it uses the loadImage().
The following code may help you:
fabric.util.loadImage('https://s3-eu-west-1.amazonaws.com/kienzle.dev.cors/img/image2.png', function(img) {
if(img == null) {
alert("Error!");
}else {
var image = new fabric.Image(img);
canvas.add(image).setActiveObject(image);
canvas.renderAll();
}
}, { crossOrigin: 'anonymous' });
Here is the fiddle: http://jsfiddle.net/k7moorthi/30kmn5kL/
once you have done the function, even if theres a mistake the callback keeps running, then you could check for the element (as other said) in this way:
let fabricBackgroundInstance = new fabric.Image.fromURL(imageToUse, (oImg) => {
if(oImg._element == null){
console.error('oImg', oImg._element);
return;
}
You could use getElement() to check this error.
fabric.Image.fromURL('/foo.jpg', (img) => {
if (img.getElement() === undefined) {
console.log('Failed to load image!');
return;
}
// do something on success
}
You can add the second argument isError to your callback function.
fabric.Image.fromURL("your image URL", (img, isError) => {
if (isError) {
console.log('Something Wrong with loading image');
return;
}
// do something on success
}
Also check fabric.js source code of Image.fromURL http://fabricjs.com/docs/fabric.js.html#line21471

Chrome Extension: Tab Listener onSelectionChanged

Got some problems with the chrome.tabs.onSelectionChanged.addListener. Everything works good, till the whole window is closed. Then this listener is triggered...for whatsoever reason. For me it's kinda buggy, but anyway:
On this listener, I'm working with the chrome.tabs.get function. And this function is throwing an error in the moment of windows close:
Error during tabs.get: No tab with id: 70.
This makes sense. The tab does no longer exist in this moment. Anyone did already had a way to work around this? One possible reason would be to remove the listener on window close. But sadly, the removeListener doesnt work (if anyone knows how to remove, I'm grateful).
Best
EDIT 1.1: Modified function from serg's approach (thx for that on this way):
First i tried to only catch the tabs of the actual window with: chrome.windows.getCurrent. But this function doesn't return the windows.tabs array. So I first read out the current windows.id and only loop through the tabs of this window.
function ensureTabExists(tabId, callback) {
chrome.windows.getCurrent(function(windows) {
var exists = false;
windowsId=windows.id;
chrome.windows.getAll({populate: true}, function(windows){
loop:
for(var w=0;w<windows.length;++w) {
if (windows[w].id == windowsId) {
for(var t=0;t<windows[w].tabs.length;++t){
if(windows[w].tabs[t].id == tabId) {
exists = true;
break loop;
}
}
}
}
if(exists && callback) {
callback();
}
});
});
}
You can loop through all tabs in all windows and check if it still exists:
function ensureTabExists(tabId, callback) {
chrome.windows.getAll({populate: true}, function(windows){
var exists = false;
loop:
for(w=0;w<windows.length;w++) {
for(t=0;t<windows[w].tabs.length;t++){
if(windows[w].tabs[t].id == tabId) {
exists = true;
break loop;
}
}
}
if(exists && callback) {
callback();
}
});
}
//usage
chrome.tabs.onSelectionChanged.addListener(function(tabId, selectInfo) {
ensureTabExists(tabId, function(){
//this code will run only if tab exists
});
});
Use the chrome.window.onRemoved API to track when windows have closed. That way you can handle the window closing case more gracefully.

Resources