Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5f3d922
Initial work on the new JavaScript port
shai-almog Mar 31, 2026
0e523b8
Added improved JavaScript port coverage to CI and fixed port security…
shai-almog Mar 31, 2026
042e3bc
Adding initial placeholder facade for the full port
shai-almog Mar 31, 2026
8844c0f
HTML5Implementation now delegates bootstrap main-class binding, stora…
shai-almog Apr 1, 2026
10077e1
Added some event handling and delegation
shai-almog Apr 1, 2026
9c4da78
Refactored events into a more generic session state
shai-almog Apr 1, 2026
5564e63
Improved keyboard event handling
shai-almog Apr 1, 2026
1464e68
Initial work on rendering layer
shai-almog Apr 1, 2026
38b13a4
Additional HTML Graphics support
shai-almog Apr 1, 2026
0a47a02
Incorporated clipping and drawing logic
shai-almog Apr 1, 2026
a8aaf54
Additional fixes for the rendering code and updated status file
shai-almog Apr 1, 2026
b96f495
Started working on CI support and running the screenshot tests
shai-almog Apr 2, 2026
5110f0d
Fixed CI failure
shai-almog Apr 2, 2026
5f0f918
Fixed CI and got the intial bootstrap running
shai-almog Apr 2, 2026
7425f92
Fixed CSS build issue
shai-almog Apr 2, 2026
360da81
Removing TeaVM from build
shai-almog Apr 2, 2026
db6111b
Added bootstrap code
shai-almog Apr 2, 2026
3b224a0
Fixed initialization loop and some thread issues
shai-almog Apr 3, 2026
b847afa
Fixed additional CI pipeline failures
shai-almog Apr 3, 2026
27255f7
Fixed merge of String.format() change
shai-almog Apr 3, 2026
658c9bb
Added TeaVM stubs to get CI working with the new port
shai-almog Apr 3, 2026
8f402dc
Removed TeaVM dependency and replaced it with similar code
shai-almog Apr 3, 2026
cbf34ba
Cleaned up port and defined boundry with VM
shai-almog Apr 3, 2026
392975a
Added an initial JSBody implementation
shai-almog Apr 4, 2026
445ede9
Trying to native function calls
shai-almog Apr 4, 2026
a71c3eb
Fixed the Cn1ssDeviceRunner lambda bridge recursion
shai-almog Apr 4, 2026
418da37
Fixed JS tests so they run (albeit all fail)
shai-almog Apr 5, 2026
9126c3c
Fixed several blockers
shai-almog Apr 5, 2026
4a97abd
Screenshot test should now produce screenshots
shai-almog Apr 5, 2026
5d03146
Fixed another CI failure with array clone
shai-almog Apr 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/parparvm-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ on:
pull_request:
paths:
- 'vm/**'
- 'Ports/JavaScriptPort/**'
- '!vm/**/README.md'
- '!vm/**/readme.md'
- '!vm/**/docs/**'
push:
branches: [ master, main ]
paths:
- 'vm/**'
- 'Ports/JavaScriptPort/**'
- '!vm/**/README.md'
- '!vm/**/readme.md'
- '!vm/**/docs/**'
Expand Down Expand Up @@ -79,6 +81,11 @@ jobs:
java-version: '8'
cache: 'maven'

- name: Set up Node
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Run ParparVM JVM tests
working-directory: vm
run: mvn -B clean package -pl JavaAPI -am -DskipTests && mvn -B test -pl tests -am -DexcludedGroups=benchmark
Expand Down
9 changes: 9 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ jobs:
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-m2
- name: Set up Node
if: ${{ matrix.java-version == 8 }}
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Run Unit Tests
run: |
MVN_ARGS=""
Expand Down Expand Up @@ -110,6 +115,10 @@ jobs:
- name: Run SpotBugs for ByteCodeTranslator
if: ${{ matrix.java-version == 8 }}
run: mvn -B -DskipTests=true -f vm/ByteCodeTranslator/pom.xml verify
- name: Run JavaScript Port smoke integration
if: ${{ matrix.java-version == 8 }}
working-directory: vm
run: mvn -B test -pl tests -am -DfailIfNoTests=false -Dsurefire.failIfNoSpecifiedTests=false -Dtest=JavaScriptPortSmokeIntegrationTest
- name: Generate static analysis HTML summaries
if: ${{ always() && matrix.java-version == 8 }}
env:
Expand Down
136 changes: 136 additions & 0 deletions .github/workflows/scripts-javascript.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
name: Test JavaScript screenshot scripts

on:
pull_request:
paths:
- '.github/workflows/scripts-javascript.yml'
- 'scripts/run-javascript-browser-tests.sh'
- 'scripts/run-javascript-screenshot-tests.sh'
- 'scripts/run-javascript-headless-browser.mjs'
- 'scripts/build-javascript-port-hellocodenameone.sh'
- 'scripts/javascript_browser_harness.py'
- 'scripts/javascript/screenshots/**'
- 'scripts/lib/cn1ss.sh'
- 'scripts/common/java/**'
- 'scripts/hellocodenameone/**'
- 'Ports/JavaScriptPort/**'
- 'vm/**'
- 'maven/**'
- '!maven/core-unittests/**'
- '!docs/**'
push:
branches: [ master ]
paths:
- '.github/workflows/scripts-javascript.yml'
- 'scripts/run-javascript-browser-tests.sh'
- 'scripts/run-javascript-screenshot-tests.sh'
- 'scripts/run-javascript-headless-browser.mjs'
- 'scripts/build-javascript-port-hellocodenameone.sh'
- 'scripts/javascript_browser_harness.py'
- 'scripts/javascript/screenshots/**'
- 'scripts/lib/cn1ss.sh'
- 'scripts/common/java/**'
- 'scripts/hellocodenameone/**'
- 'Ports/JavaScriptPort/**'
- 'vm/**'
- 'maven/**'
- '!maven/core-unittests/**'
- '!docs/**'

jobs:
javascript-screenshots:
permissions:
contents: read
pull-requests: write
issues: write
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
GH_TOKEN: ${{ secrets.CN1SS_GH_TOKEN }}
ARTIFACTS_DIR: ${{ github.workspace }}/artifacts/javascript-ui-tests
CN1_JS_TIMEOUT_SECONDS: "180"
CN1_JS_BROWSER_LIFETIME_SECONDS: "150"
CN1SS_SKIP_COVERAGE: "1"
BROWSER_CMD: "node \"$GITHUB_WORKSPACE/scripts/run-javascript-headless-browser.mjs\""
steps:
- uses: actions/checkout@v4

- name: Set up Java 8 for ParparVM
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '8'
cache: 'maven'

- name: Prepare Codename One binaries for Maven plugin
run: |
mkdir -p ~/.codenameone
cp maven/UpdateCodenameOne.jar ~/.codenameone/

- name: Build ParparVM compiler bundle
run: mvn -B -f maven/pom.xml -pl parparvm -am -DskipTests -Dmaven.javadoc.skip=true package

- name: Set up Java 17
uses: actions/setup-java@v4
with:
distribution: 'zulu'
java-version: '17'
cache: 'maven'

- name: Set up Node 20
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Install Playwright Chromium
run: |
cd scripts
npm init -y 2>/dev/null || true
npm install playwright
npx playwright install --with-deps chromium

- name: Install Xvfb for headless Java AWT
run: sudo apt-get update && sudo apt-get install -y xvfb

- name: Setup workspace
run: xvfb-run ./scripts/setup-workspace.sh -q -DskipTests

- name: Build HelloCodenameOne JavaScript port bundle
run: |
# Use JAVA_HOME set by actions/setup-java for Java 17
# (JAVA_HOME_17_X64 is always available after setup-java@v4)
export JAVA_HOME="${JAVA_HOME_17_X64}"
export PATH="$JAVA_HOME/bin:$PATH"
echo "Using JAVA_HOME=$JAVA_HOME"
java -version

# xvfb-run is required for CSS compilation which uses java.awt
SKIP_PARPARVM_BUILD=1 xvfb-run ./scripts/build-javascript-port-hellocodenameone.sh "${GITHUB_WORKSPACE}/artifacts/javascript-ui-tests/hellocodenameone-javascript-port.zip"

- name: Locate JavaScript bundle
id: locate_bundle
run: |
set -euo pipefail
bundle="$(ls -1 \
artifacts/javascript-ui-tests/hellocodenameone-javascript-port.zip \
scripts/hellocodenameone/parparvm/target/hellocodenameone-javascript-port.zip \
2>/dev/null | head -n1 || true)"
if [ -z "$bundle" ]; then
echo "Unable to locate HelloCodenameOne JavaScript-port bundle" >&2
exit 1
fi
echo "bundle=$bundle" >> "$GITHUB_OUTPUT"

- name: Run JavaScript screenshot browser tests
run: |
mkdir -p "${ARTIFACTS_DIR}"
./scripts/run-javascript-browser-tests.sh "${{ steps.locate_bundle.outputs.bundle }}" "${GITHUB_WORKSPACE}/scripts/javascript/screenshots"

- name: Upload JavaScript screenshot artifacts
if: always()
uses: actions/upload-artifact@v4
with:
name: javascript-ui-tests
path: artifacts/javascript-ui-tests
if-no-files-found: warn
retention-days: 14
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,6 @@ dependency-reduced-pom.xml
/docs/website/.venv-video-refresh/
/docs/website/.youtube-oauth-token.json
/docs/website/client_secret.json
/.playwright-cli
package-lock.json
package.json
6 changes: 6 additions & 0 deletions Ports/JavaScriptPort/LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
JavaScriptPort and its dedicated fixtures are licensed under the PolyForm Noncommercial License 1.0.0.

Official license text:
[https://polyformproject.org/licenses/noncommercial/1.0.0/](https://polyformproject.org/licenses/noncommercial/1.0.0/)

This license boundary applies to files inside `Ports/JavaScriptPort/**` unless a file states otherwise.
3 changes: 3 additions & 0 deletions Ports/JavaScriptPort/NOTICE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
This subtree is intentionally licensed separately from the parent Codename One repository.

Files in `Ports/JavaScriptPort/**` are licensed under PolyForm Noncommercial 1.0.0 and must not inherit the parent repository's standard source-file license headers.
12 changes: 12 additions & 0 deletions Ports/JavaScriptPort/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
JavaScriptPort is the browser-runtime work area for the new Codename One JavaScript port.

Scope of this bootstrap:
- imported HTML5/runtime code from the existing proprietary browser port as the behavioral/source baseline
- PolyForm-licensed port boundary under `Ports/JavaScriptPort/**`
- ParparVM-oriented smoke fixtures under `Ports/JavaScriptPort/tests/**`
- executable translator/runtime coverage through the local ParparVM test suite in `vm/tests`

License boundary:
- `Ports/JavaScriptPort/**` is licensed under PolyForm Noncommercial 1.0.0
- dedicated smoke fixtures under `Ports/JavaScriptPort/tests/**` use the same license boundary
- the rest of the repository remains under its existing licensing
106 changes: 106 additions & 0 deletions Ports/JavaScriptPort/STATUS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
JavaScript Port Status
======================

Implemented
-----------

- [x] PolyForm Noncommercial 1.0.0 license boundary for `Ports/JavaScriptPort/**`
- [x] Imported browser-port baseline into the repository as a working reference subtree
- [x] ParparVM-side production host bridge in [JavaScriptPortHost.java](/Users/shai/dev/cn1/Ports/JavaScriptPort/src/main/java/com/codename1/impl/platform/js/JavaScriptPortHost.java)
- [x] Native method bindings resolved at runtime via `bindNative()` in `parparvm_runtime.js` and `port.js` (no hardcoded registry needed)
- [x] Browser bundle bootstrap shell and host bridge in `vm/ByteCodeTranslator`
- [x] JSO interfaces created under `com.codename1.html5.js.*` for browser DOM APIs
- [x] @JSBody annotation processing implemented in ByteCodeTranslator for inline JavaScript generation
- [x] Wrapped static method body generation fixed - `__impl` functions now properly generated

Current Blocker
---------------

**FIXED**: All three critical bugs have been resolved:

### Bug 1: @JSBody Inline Script Syntax Error ✅ FIXED
The `@JSBody` annotation scripts contain `return` statements but were being assigned directly:
```javascript
// WRONG
const __jsBodyResult = return evt.source;

// FIXED
const __jsBodyResult = (function() { return evt.source; }).call(this);
```

### Bug 2: Wrapped Static Methods Missing `__impl` Body ✅ FIXED
Static methods were generating only the wrapper but skipping the method body generation due to an early return.

**Root cause**: In `appendMethod()`, wrapped static methods returned after generating the wrapper, skipping lines 253-277 that generate the actual bytecode body.

**Fix**: Removed the early return - now the `__impl` function body is generated before the wrapper.

**Verification**: After fix, generated code shows both functions:
```javascript
// __impl function with actual bytecode
function* main__impl(args) {
// Actual method body
}
// Wrapper that calls __impl
function* main(args) {
jvm.ensureClassInitialized("...");
return yield* main__impl(args);
}
```

### Bug 3: Playwright Not Loadable in CI ✅ FIXED
Playwright installed globally (`npm install -g`) is not accessible via ES module imports.

**Fix**: Install locally in `scripts/` directory:
```yaml
- name: Install Playwright Chromium
run: |
cd scripts
npm init -y 2>/dev/null || true
npm install playwright
npx playwright install --with-deps chromium
```

Next Steps
----------

1. Push changes and run CI to verify fixes work end-to-end
2. If browser tests pass, the JavaScript port is functional

Files Modified
--------------

1. **vm/ByteCodeTranslator/src/com/codename1/tools/translator/BytecodeMethod.java**:
- Added `jsBodyScript`, `jsBodyParams` fields
- Added getters/setters for JSBody annotation data

2. **vm/ByteCodeTranslator/src/com/codename1/tools/translator/ByteCodeMethodArg.java**:
- Added `getTypeName()` and `getPrimitiveType()` getters

3. **vm/ByteCodeTranslator/src/com/codename1/tools/translator/Parser.java**:
- Added `JSBodyAnnotationVisitor` inner class
- Modified `visitAnnotation()` to capture @JSBody data

4. **vm/ByteCodeTranslator/src/com/codename1/tools/translator/JavascriptMethodGenerator.java**:
- Added `appendJsBodyMethod()` for @JSBody inline JavaScript generation
- Fixed @JSBody script wrapping in IIFE for proper return value handling
- **Critical fix**: Removed early return in `appendMethod()` that was skipping `__impl` body generation for wrapped static methods

5. **scripts/run-javascript-headless-browser.mjs**:
- Improved error messages for Playwright import failures

6. **.github/workflows/scripts-javascript.yml**:
- Changed Playwright installation from global to local `scripts/` directory

Verification
------------

After fixes, the generated JavaScript bundle correctly shows:

```bash
$ unzip -p /tmp/test-fix.zip HelloCodenameOne-js/translated_app.js | grep -A15 "function\* main__impl"
# Shows __impl function with actual bytecode (object creation, method calls)
# Shows wrapper function that ensures class init and calls __impl
```

The bytecode translation now correctly generates both the `__impl` function body and the wrapper for all static methods.
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright (c) 2026 Codename One and contributors.
* Licensed under the PolyForm Noncommercial License 1.0.0.
*/
package com.codename1.html5.interop;

/**
* Callback interface for async operations.
* This is a replacement for TeaVM's AsyncCallback.
*/
public interface AsyncCallback<T> {
void complete(T result);
void error(Throwable e);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2026 Codename One and contributors.
* Licensed under the PolyForm Noncommercial License 1.0.0.
*/
package com.codename1.html5.interop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation to suppress synchronization errors.
* This is a no-op stub for ParparVM builds.
* TeaVM uses this for its async/sync transformation.
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface SuppressSyncErrors {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2026 Codename One and contributors.
* Licensed under the PolyForm Noncommercial License 1.0.0.
*/
package com.codename1.html5.js;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks a method as implemented by inline JavaScript.
* For ParparVM, the script is executed via native binding mechanism.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JSBody {
String[] params() default {};
String script() default "";
}
Loading
Loading