RealRaven2000 / FiltaQuilla

Adds many new mail filter actions to Thunderbird
http://quickfilters.quickfolders.org/filtaquilla.html
GNU General Public License v3.0
85 stars 15 forks source link

Question: Scope of JavaScript when called from filters? #274

Open TonyGravagno opened 1 month ago

TonyGravagno commented 1 month ago

If we execute JavaScript from a filter, exit that filter and proceed to another, and execute more script there, are variables still in-scope?

Reasons:

1) Set flags that get used later so that multiple filters can contribute to determining the disposition of a mail item. 2) Avoid recreating objects, just re-use objects that have remained in-scope. 3) Avoid duplicating functions that have already been created further up in the filter list. 4) Avoid duplicating functions that have already been defined by a filter in another account. 5) Cross-account sharing of functions and data.

Yes, some of those cross-over to others and the practical use of some can be argued. My point here is about what is possible ... then we can consider what's reasonable or useful.

Thanks!

RealRaven2000 commented 1 month ago

If we execute JavaScript from a filter, exit that filter and proceed to another, and execute more script there, are variables still in-scope?

no the whole context gets destroyed, I don't think there is a way to do this. If you wanted to store something I could imagine using the configuration database or Add-ons private storage but I may need to expose that to the script somehow. We first need to determine whether API functions are even accessible from the script. I think currently it is very limited, but I would be open to patches to make it more useful (within reason)

RealRaven2000 commented 1 month ago
  • Avoid duplicating functions that have already been created further up in the filter list.

  • Avoid duplicating functions that have already been defined by a filter in another account.

that's a separate thought - access to previous filters would be interesting, or at least global filter access (so that other filters could be triggered manually). But there is a big problem with the filter architecture; we cannot identify a single filter because they have no unique identifier, and you can have multiple filters with the same name. I think we really need Thunderbird to make a patch that allows adding custom attributes to filters to make them useful beyond what is offered out of the box.

The other thing I cannot know if that if filters start some asynchronous operation then the next filter will not know when this is finished - because at the moment filter actions are still created as synchronous. So this is also a restriction in the Thunderbird architecture that must be changed first.

TonyGravagno commented 1 month ago

Thanks for your thoughts!!

There are at least two concepts here:

Storage of scripts attached to a specific filter

As I understand this, filters are saved and updated through the filter service. It does whatever it does, so we can't refer to other filters from within a filter. However, at runtime we do have access to the script.
(Warning, this is going to get ugly.) In our scripts, we can add a unique identifier at the top.
At runtime, get that ID and check preferences to see if it exists.
If not, save the script or some parsed version of it into a preference. (Ugh!)
If it does exist, append the "preference" value into the top of the script before doing the eval().

That is just a rough draft of the concept. Yes, details need to be added to make this work properly.

Runtime exchange of data

It seems there should be a place for "memory" which is used for communication between scripts. I know nothing about Tb internals but there must be some service that stores common runtime data, where preferences are for permanent storage. The code linked above shows that JavaScripts executed as an action already return a return value:
return eval(actionValue);
But the applyAction that calls the eval does not use it. The presents an opportunity, without breaking existing code, for new code to return JSON that holds state data. That should be stored in that runtime data buffer, or if that doesn't exist (Ugly performance impact alert!) in a preference keyed to the account and unique filter ID noted above. For each execution of that applyAction, the current JSON value is injected into the current script along with the common functions. This gives every JavaScript action a complete view of the current code and data.

Summary

What this describes is a scenario where we save a single filter somewhere, and run it just to get functions saved into preferences. In normal runtime filtering, that code is pulled from preferences, appended to the current filter script, and the result is exactly the same as if we had done an import from a common library. Each filter can now use a return value, a JSON object with values that are meaningful to other scripts. At runtime, just after the common functions are appended, this object can be inserted into each filter run to make that data available to every other filter. For performance it would be ideal to keep the current common script and the current JSON data in active memory rather than constantly pulling from preferences and re-saving the live JSON data back to preferences.

Sample Code filter: Description: Common Functions : Manual Run Only ```js // uniqueID: CommonFunctions function concatenate(a,b) { return a+b; } ``` filter: Description: Some JS custom filter and action : Run for all new items ```js // uniqueID: CustomAction1 const strings = concatenate("hello","world"); return { currentString: strings } // saving for subsequent references to TbCommon ``` filter: Description: Another JS custom filter and action : Run for all new items ```js // uniqueID: CustomAction2 console.log( TbCommon.currentString } // assumes TbCommon is available // TbCommon can be modifed and returned here ``` filtaQuilla.js self.javascriptAction.applyAction : ```js const commonScript = getScriptFromPrefs(); // local function const currentJSON = getRuntimeData(); // from prefs or Tb runtime let newScript = commonScript newScript += "\n" + "let TbCommon = " + currentJSON; // runtime data newScript += "\n" + actionValue; // current filter const result = eval(newScript); // new JSON runtime data! return result // ... const newRuntimeData = this.applyAction(msgHdrs, aActionValue, aListener, aType, aMsgWindow); saveRuntimeData(newRuntimeData); // to Tb runtime or prefs ```

This might later be expanded with a defined object in TbCommon which carries "state" for how other filters are expected to function. For example, if one filter returns a "Done" flag then all other filters might avoid an eval process because TbCommon already has that flag set. Once we have "state", like preferences, we might find helpful reasons to reach for that resource.

Something like this could also be used by users to create their own custom filters, eliminating the need for filtaQuilla (as a project) to have to consider the worthiness of individual functionalities being added to the core of this addon. For example, and I'm not sure if this will work as I think ... since a custom action is executed with eval, it can probably include any code that is valid in an addon. That makes it possible to wrap new some addon functionality within this addon, like defining new filters that are saved and later executed on a new self._init().

Yeah, this can be ugly, scary, and very cool, all at the same time.

It would be really nice to get feedback from John on this too.