JS: Backport³ and more additions & fixes #3961
Merged
Task list completed / task-list-completed
succeeded
Oct 31, 2024 in 0s
3 / 3 tasks completed
All tasks have been completed
Details
Required Tasks
Task | Status |
---|---|
backport (here) of backported missing things (there) of great backport & rework (over yonder) xD | Incomplete |
i separated into individual commits that make sense on their own, might be easier to look there instead of whole diff | Incomplete |
original authors/contributors credited with co-author where relevant | Incomplete |
numpad keys support | Incomplete |
keyboard layout support | Incomplete |
altPrint() and altPrintln() support | Incomplete |
quit() function to unlock usb profile (original intent was to alternate badusb profile and mass storage profile, we have this in momentum and other cfw as usbdisk module, but outside scope of this pr, can backport this module later on if you're interested) | Incomplete |
readAny() to avoid starving read loops with many small reads or hinder usability with few large reads | Incomplete |
end() to release serial handle allowing new initialization in same script, maybe for gpio usage or different serial port or settings | Incomplete |
expansion service is automatically disabled to avoid conflict | Incomplete |
added simple example script we had previously, ported to new remade storage module | Incomplete |
fixed null ptr crash when user did not pass max len prop. logic of props is from what i can see, always configure view for minimum usable state, props should configure things on top as optional | Incomplete |
defaultText and defaultTextClear props | Incomplete |
byte_input | Incomplete |
there is no file picker based on View object, so this is only gui/* module that is not based on view_factory | Incomplete |
instead it uses usual dialogs app file picker in sync manner | Incomplete |
viewDispatcher.currentView to allow users to have extra logic in navigation callbacks for example | Incomplete |
updated gui example script with showcase of all additions above | Incomplete |
toString() works correctly on negative numbers, toString(-42) used to give "0" | Incomplete |
parseInt() | Incomplete |
toUpperCase() and toLowerCase() | Incomplete |
example for string functions | Incomplete |
__filepath and __dirpath to allow relative path contextualization, for example for load() or storage.open() | Incomplete |
example interactive.js repl script, allows running js in limited capacity without editing files | Incomplete |
many additions to typedefs that were missing or incomplete, still not everything is documented i believe but getting closer | Incomplete |
toString(), toUpperCase(), toLowerCase() could all be moved to respective classes i think, for strings there is getprop_builtin_string() in mjs source, can add there, for number there is no such function but may be possible to add another if branch in getprop_builtin() for number type | Incomplete |
load() accepts a custom scope parameter. with custom scope, assignment in child script is confined to custom scope, lookup is recursive to parent scopes. with no custom scope, code is executed in current scope, so everything is shared like code was inlined | Incomplete |
for custom scope, we can get custom scope object passed by user and inject __filepath and __dirpath into it, easy | Incomplete |
for no custom scope, i dont see any good solution: one way is saving current __dirpath and __filepath values from scope, changing them, running load(), then resetting to previous values. might work fine in most cases but if child script runs load() of its own and uses callbacks or things like this, child2 might run a function defined in child1 and then its screwed up. this is a rare edge case of course, if no function callback is passed from child1 to child2 then no code from child1 could run while child2, and second instance of mjs_load() would restore child1's __dirpath and __filepath correctly | Incomplete |
unit tests | Incomplete |
example scripts | Incomplete |
PR has description of feature/bug or link to Confluence/Jira task | Completed |
Description contains actions to verify feature/bugfix | Completed |
I've built this code, uploaded it to the device and verified feature/bugfix | Completed |
We want to maintain a base JS API that is consistent across all firmware flavors. | Incomplete |
We want JS app developers to be able to leverage additional functionality found in custom firmware editions. | Incomplete |
We want users not to think about cross-FW compatibility of the scripts that they find online too much. | Incomplete |
A JS app that works on OFW should also work on a CFW. | Incomplete |
A JS app that works on a CFW and leverages its extended features may or may not work on OFW. | Incomplete |
A JS app that does not leverage any additional CFW functionality is guaranteed to work across all firmware editions. | Incomplete |
A JS app that requires additional CFW functionality will not work on OFW. | Incomplete |
One can write JS apps that can make use of but do not require additional CFW functionality. | Incomplete |
CFWs are free to introduce additional JS APIs. | Incomplete |
CFWs are discouraged from breaking the base JS API found in OFW. | Incomplete |
CFWs are encouraged to backport any of their extended features into OFW, assuming they are compatible with OFW's vision. | Incomplete |
checkSdkCompatibility(expectedMajor, expectedMinor) : checks if the JS API version is compatible with the one that the script expects (i.e. expectedMajor == actualMajor && expectedMinor >= actualMinor ), and if it doesn't, warns the user and asks whether they want to continue executing the script anyways. This is the simplest of the three checks, and it will be automatically inserted in the beginning of the output file unless the developer explicitly asks our JS "toolchain" (SDK) not to do so. By asserting compatibility by default, we guarantee that the user receives a clear warning if their firmware is incompatible with the script instead of cryptic error messages like calling non-callable . |
Incomplete |
isSdkCompatible(expectedMajor, expectedMinor) : performs the same check, but just returns the result as a boolean. |
Incomplete |
sdkCompatibilityStatus(expectedMajor, expectedMinor) : performs the same check, but returns a more detailed description ("compatible" , "firmwareTooOld" , "firmwareTooNew" ) |
Incomplete |
checkSdkVendor(expectedVendor) : checks if the JS API vendor matches the one that the script expects, and if it doesn't, warns the user and asks whether they want to continue executing the script anyways. This is the simplest of the two checks for cases where an app requires a feature found in a CFW. This check will not be automatically inserted by our SDK (unlike checkSdkCompatibility ). |
Incomplete |
sdkVendor : a string containing the SDK vendor (e.g. "flipperdevices" or "momentum" ). A JS app that is able to work without additional features on a best-effort basis may query this global constant to find out whether it can use those features. |
Incomplete |
Both OFW and Momentum are at JS SDK version 1.0. | Incomplete |
Momentum introduces a feature called "text-input-regex" and bumps its JS SDK version to 1.1. |
Incomplete |
The feature is backported into OFW, bumping its JS SDK version to 1.1 as well. | Incomplete |
Both OFW and Momentum continue to report true for doesSdkSupport("text-input-regex") until their JS SDK versions are bumped up to 2.0 |
Incomplete |
Starting with JS SDK 2.0, doesSdkSupport("text-input-regex") starts reporting false and checkSdkFeatures(["text-input-regex"]) starts warning the user that a feature is unsupported. I assert that the negative effect from these incorrect results is alleviated thanks to SDK version checks, which will fail if the script expects v1.x but is actually running on 2.x. |
Incomplete |
changing the examples to show view.set("defaultText", "text"); after the first makeWith() call | Incomplete |
changing behavior of defaultText and defaultData assign to resize the buffer to atleast the size specified by the default provided. for example, default is 4 bytes for byteinput, default data is 10 long so enlarge to 10, then we also have length prop provided, so enlarge or shrink as requested. this brings up another problem however: what if the user provides a defaultdata longer than the length they specify? if length is assigned first, then it will be resized later by default data. if defaultdata is assigned first, then it will be shrunk later by length. and we cannot solve this by enforcing the size specified by length, because thats the original issue: if defaultdata is assigned before than user provided length, then length is still default of 4 and default data is cutoff despire the user specifying otherwise. | Incomplete |
MJS_TAG_STRING_I and MJS_TAG_STRING_5 : embedded in the lower 5 bytes of term. The tag is stored in the upper 2 bytes, leaving a byte in the middle which will be 0 b/c terms are zero-initialized, at least in mjs_mk_string . |
Incomplete |
MJS_TAG_STRING_O : GC'ed owned strings, which are always stored with the terminator at runtime. The parser does store them without the terminator in the same giant buffer with all the other strings, but we never use raw internally referenced strings from the parser. The bytecode interpreter does, but it converts them into nice null-terminated strings with mjs_mk_string for us. |
Incomplete |
MJS_TAG_STRING_F : foreign strings, not managed by mJS at all. The main use case is to avoid wasting RAM for strings that you already have in .rodata . One could sneak a non-null-terminated string into mjs_mk_string(/*...*/, false) , but I'd just call that irresponsible, honestly. Either way, nothing in our codebase does that; not us, not mJS itself. |
Incomplete |
MJS_TAG_STRING_C : seems to be used internally during garbage collection, never seen by us. |
Incomplete |
MJS_TAG_STRING_D : all uses are commented out, the symbols in the commented out code are no longer there, I can't tell. |
Incomplete |
I don't like how with the max length thing the fields have to be arranged in a specific way for the thing to work. Field ordering is absolutely not something that the ECMAScript specification guarantees. This is especially important with the new build pipeline underway. | Incomplete |
Not an issue with this particular PR, just a thing to note in regards to #3963: .d.ts files would have to be moved, and the new declarations would have to get @version doc comments. |
Incomplete |
I don't like that the file browser is not a view. But that's an issue for later, I don't think that this PR needs to change this. | Incomplete |
API versioning in JS got different nature, not like api versioning in API symbols, it should be much easier to maintain it | Incomplete |
We'll have checkSdk() but as an optional canary | Incomplete |
If you call it we'll check version and ask user what to do if version doesn't match | Incomplete |
If your script doesn't contain it, then no checks is done and you'll crash later | Incomplete |
I'd like to see all of us cooperating on JS API: keep it consistent across all forks, simplifying portability between different firmware flavors. It make sense to expose additional API to query which firmware flavor we are running at | Incomplete |
Loading