tabs.executeScript - how can I tell if content script was being injected? - firefox-addon

I inject content scripts into websites programmatically using browser.tabs.executeScript. This will return a Promise, but in case of its rejection there seems to be no way of differentiating between the following 2 cases:
The content script couldn't be injected (i.e. for missing host
permission)
An error occured on script updateparsingend:update(but the script was being injected)
I'm only interested in whether the script was being injected or not.
The argument passed to the catch is an Error object.
catch(e => console.log(e.toString()) will output the error message, which can either be a reason for an injection failure (i.e. Missing host permission) or an error that occurred updatereading the scriptend:update.
browser.tabs.executeScript(tabId, {
file: '../path/to/content-script.js',
frameId: 0,
runAt: 'document_idle'
})
.catch(e => console.log(e.toString()));
So, for example if the content script is as follows:
window.document.body.addEventListener('click', e => console.log('clicked body'), false);
bla.bla();
then the Promise is being rejected, since bla.bla is undefined - but the script was being injected successfully.
In case the content script couldn't be injected I'd like to notify the user with the corresponding error message.
But when an error occurred updatethat is unrelated to whether the script could be injectedend:update, while the script was being injected, I don't want to notify the user, but handle it silently.
Is there a way to differentiate between those 2 cases?
EDIT: I came up with an indirect solution: In case the returned Promise was rejected I try to send a message to the content script. If this fails then the background script "knows" that no content script was being injected -> notify user.

This is how the Promise works....
browser.tabs.executeScript(tabId, {
file: '../path/to/content-script.js',
frameId: 0,
runAt: 'document_idle'
})
.catch(e => console.log(e.toString()));
catch in above will catch errors if the tabs.executeScript failed to inject. It may also show some errors when parsing the file in order to inject, if the JS file has parsing errors (invalid JS). It has nothing to do with what '../path/to/content-script.js' will be doing afterwards.
So once it was injected, then above Promise is fulfilled.
If the injected script has a sync return, then it can be received by the tabs.executeScript via then() e.g.
browser.tabs.executeScript(tabId, {
file: '../path/to/content-script.js',
frameId: 0,
runAt: 'document_idle'
})
.then(result => {})
.catch(e => console.log(e.toString()));
In case of async functions such as .addEventListener which will happen later, then is nothing returned to tabs.executeScript
To catch errors in the content scripts, you can generate the error message within the content script or send a message to background script i.e. sendMessage & onMessage.addListener
tabs.executeScript()
A Promise that will be fulfilled with an array of objects,
representing the result of the script in every injected frame.
The result of the script is the last evaluated statement, which is
similar to what would be output (the results, not any console.log()
output) if you executed the script in the Web Console. For example,
consider a script like this:
var foo='my result';foo;
browser.tabs.executeScript(tabId, {
file: '../path/to/content-script.js',
frameId: 0,
runAt: 'document_idle'
})
.then(result => {
// result is returned by the Promise
if (result === []) {
// it was fine but there was nothing to return
}
else if (result[0]) {
// result[0] is return from the promise
}
})
.catch(e => console.log(e.toString()));
Now if you want a return (it must be sync or else you have to tie it to another Promise), return something from '../path/to/content-script.js'

Related

Tarantool blocks for operations, if error occurs inside box.begin()...box.commit() block

If error occurs inside box.begin()...box.commit() block, Tarantool box blocks for operations untill you manually execute box.commit()
For example, I provide incorrect data without one of fields to my function:
replace = function(space, data)
local id = data[1][1]
box.begin()
my_module.delete(space, id)
local response = my_module.insert(space, data)
box.commit()
return response
end
Inside of my_module.insert() it will encount an error, so execution of replace() will be aborted before execution of box.commit().
Later, if i call replace() again with another data, it won`t be able to execute box.begin() because of previously open transaction.
pcall(my_module.insert()) wasn`t able to catch an error.
There's a handy function for it - box.atomic
I suggest the following implementation:
replace = function(space, data)
return box.atomic(function()
local id = data[1][1]
my_module.delete(space, id)
return my_module.insert(space, data)
end)
end
pcall(my_module.insert()) wasn't able to catch an error.
pcall catches all errors. The error isn't caught probably because it occurs earlier on the delete operation.
By the way, Tarantool has inline operations for replace:
https://www.tarantool.io/en/doc/latest/reference/reference_lua/box_space_index/#box-space-replace

Difference between .then() and .whenCompleted() methods when working with Futures?

I'm exploring Futures in Dart, and I'm confused about these two methods that Future offers, .then() and .whenCompleted(). What's the main difference between them?
Lets say I want to read a .txt using .readAsString(), I would do it like this:
void main(){
File file = new File('text.txt');
Future content = file.readAsString();
content.then((data) {
print(content);
});
}
So .then() is like a callback that fires a function once the Future is completed.
But I see there is also .whenComplete() that can also fire a function once Future completes. Something like this :
void main(){
File file = new File('text.txt');
Future content = file.readAsString();
content.whenComplete(() {
print("Completed");
});
}
The difference I see here is that .then() has access to data that was returned!
What is .whenCompleted() used for? When should we choose one over the other?
.whenComplete will fire a function either when the Future completes with an error or not, instead .then will fire a function after the Future completes without an error.
Quote from the .whenComplete API DOC
This is the asynchronous equivalent of a "finally" block.
then runs if the future completes successfully.
catchError runs if the future fails.
whenComplete runs regardless of the future completed with a value or with an error.
Here's the basic flow:
someFuture().then((value) {
print('Future finished successfully i.e. without error');
}).catchError((error) {
print('Future finished with error');
}).whenComplete(() {
print('Either of then or catchError has run at this point');
});
.whenComplete = The function inside .whenComplete is called when this future completes, whether it does so with a value or with an error.
.then = Returns a new Future which is completed with the result of the call to onValue (if this future completes with a value) or to onError (if this future completes with an error)
Read detail on API DOC
whenComplete then

Transaction Block has wrong Return [duplicate]

I have a problem with transactions. The data in the transaction is always null and the update handler is called only singe once. The documentation says :
To accomplish this, you pass transaction() an update function which is
used to transform the current value into a new value. If another
client writes to the location before your new value is successfully
written, your update function will be called again with the new
current value, and the write will be retried. This will happen
repeatedly until your write succeeds without conflict or you abort the
transaction by not returning a value from your update function
Now I know that there is no other client accessing the location right now. Secondly if I read the documentation correctly the updateCounters function should be called multiple times should it fail to retrieve and update data.
The other thing - if I take out the condition if (counters === null) the execution will fail as counters is null but on a subsequent attempt the transaction finishes fine - retrieves data and does the update.
simple once - set on this location work just fine but it is not safe.
Please what do I miss?
here is the code
self.myRef.child('counters')
.transaction(function updateCounters(counters){
if (counters === null) {
return;
}
else {
console.log('in transaction counters:', counters);
counters.comments = counters.comments + 1;
return counters;
}
}, function(error, committed, ss){
if (error) {
console.log('transaction aborted');
// TODO error handling
} else if (!committed){
console.log('counters are null - why?');
} else {
console.log('counter increased',ss.val());
}
}, true);
here is the data in the location
counters:{
comments: 1,
alerts: 3,
...
}
By returning undefined in your if( ... === null ) block, you are aborting the transaction. Thus it never sends an attempt to the server, never realizes the locally cached value is not the same as remote, and never retries with the updated value (the actual value from the server).
This is confirmed by the fact that committed is false and the error is null in your success function, which occurs if the transaction is aborted.
Transactions work as follows:
pass the locally cached value into the processing function, if you have never fetched this data from the server, then the locally cached value is null (the most likely remote value for that path)
get the return value from the processing function, if that value is undefined abort the transaction, otherwise, create a hash of the current value (null) and pass that and the new value (returned by processing function) to the server
if the local hash matches the server's current hash, the change is applied and the server returns a success result
if the server transaction is not applied, server returns the new value, client then calls the processing function again with the updated value from the server until successful
when ultimately successful, and unrecoverable error occurs, or the transaction is aborted (by returning undefined from the processing function) then the success method is called with the results.
So to make this work, obviously you can't abort the transaction on the first returned value.
One workaround to accomplish the same result--although it is coupled and not as performant or appropriate as just using the transactions as designed--would be to wrap the transaction in a once('value', ...) callback, which would ensure it's cached locally before running the transaction.

How do I fail a script running in Bitbucket Pipelines?

When a pipeline runs a node series of commands, how can I trigger a fail within the pipeline?
I have tried the following:
const failBuild = function(message) {
console.error('Deploy failed: ', message)
throw new Error('Deploy failed')
}
I see the "Deploy failed" message, but the pipeline still says "Success".
Bb Pipelines fail when a command exits with a non-zero exit code. So, if you want the pipeline to fail, you have to make sure the code is not 0.
In your case (note for people reading this later: see comments), you get 0 as exit status, because the throw is executed in a promise, but then catched in the promise’s catch() function – which does neither stop execution nor have any influence on the exit code.
Solution: explicitly throw an error in the catch() function.
For anyone else who might be struggling with this...
You need to return a non zero as already mentioned, I find the easiest way to do this is by passing a negative integer to PHP's exit() function.
https://php.net/manual/en/function.exit.php
if($condition == true)
{
// Whatever we were doing, it worked YAY!!
exit();
}
else
{
// Something went wrong so fail the step in the pipeline
exit(-1);
}
The accepted answer states:
Solution: explicitly throw an error in the catch() function.
So if I understand that correctly, it suggests you should write the script as:
async function main() { throw "err"; }
main().catch(e => { throw e; });
However, this does not work: the exit code is still 0, and the console displays a nasty warning:
> node "main.js"
(node:32996) UnhandledPromiseRejectionWarning: err
(node:32996) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 2)
(node:32996) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
> $?
0
The correct way to bubble up the error to the node process is:
process.on('unhandledRejection', up => { throw up });
async function main() { throw "err"; }
main();
This way, you get teh following result:
> node "main.js"
test2.js:1
process.on('unhandledRejection', up => { throw up });
^
err
> $?
1
Which is a little bit better (except the stacktrace is not very clear).

How to gracefully abort yeoman generator on error?

I'm writing a yeoman generator and want to check some prerequisites, for example a git being installed. I can easily check this using .exec, but how do i gracefully abort generator and report error to user? I searched docs, but it seems that i'm missing some obvious way to do it. Any hints?
Throwing exception will of course abort generator, but is it a best way? Maybe something more user friendly? Not all yeoman users are able to read js exceptions.
The current state of error handling in the popular generators is quite diverse:
in the most cases they just log the error and return from the action and let the subsequnt actions run and return 0 status code:
generator-karma's setupTravis method:
if (err) {
this.log.error('Could not open package.json for reading.', err);
done();
return;
}
or set a custom abort property on error and skip further actions with cheking on the abort property but still return 0 status code:
generator-jhipster's CloudFoundryGenerator:
CloudFoundryGenerator.prototype.checkInstallation = function checkInstallation() {
if(this.abort) return;
var done = this.async();
exec('cf --version', function (err) {
if (err) {
this.log.error('cloudfoundry\'s cf command line interface is not available. ' +
'You can install it via https://github.com/cloudfoundry/cli/releases');
this.abort = true;
}
done();
}.bind(this));
};
or manually end the process with process.exit:
generator-mobile's configuringmethod:
if (err) {
self.log.error(err);
process.exit(1);
}
However none of these methods provide a good way to signal to the environment that something went wrong except the last one but directly calling process.exit is a design smell.
Throwing an exception is also an option but this presents also the stackstrace to the user which is not always a good idea.
The best option would be use the Environment.error method, which has some nice advantages:
the Environment is exposed thorough the env property of the yeoman.generators.Base
an error event is emitted which is handled by the yo cli code
the execution will result in a non zero (error) status code which is override-able
by default yo will display only the message and no stacktrace
the stacktrace can be optionally displayed with providing the --debug built-in option when re-running the generator.
With using this technique your action method would look like this:
module.exports = generators.Base.extend({
method1: function () {
console.log('method 1 just ran');
this.env.error("something bad is happened");
console.log('this won't be executed');
},
method2: function () {
console.log('this won't be executed');
}
});

Resources