Skip to content

refactor: test converting GWT to TS#24486

Draft
Artur- wants to merge 135 commits into
mainfrom
client-to-ts2
Draft

refactor: test converting GWT to TS#24486
Artur- wants to merge 135 commits into
mainfrom
client-to-ts2

Conversation

@Artur-
Copy link
Copy Markdown
Member

@Artur- Artur- commented May 31, 2026

This is not intended to be merged

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 31, 2026

Test Results

 1 400 files   -  30  1 400 suites   - 30   1h 56m 28s ⏱️ + 32m 39s
 9 578 tests  - 474  9 432 ✅  - 552  66 💤  - 2  43 ❌ +43  37 🔥 +37 
10 050 runs   - 474  9 890 ✅  - 565  67 💤  - 2  46 ❌ +46  47 🔥 +47 

For more details on these failures and errors, see this check.

Results for commit 8e73a1e. ± Comparison against base commit 8fb5047.

♻️ This comment has been updated with latest results.

Artur- added 28 commits June 1, 2026 16:11
Replace the GWT JSNI implementation of `Console` with a TypeScript module
published at `window.Vaadin.Flow.internal.client.Console` and reached from
remaining Java code through a `@JsType(isNative = true)` shim. JVM unit tests
still hit a `GWT.isScript()` fallback that prints to `System.out`/`System.err`.

This is the pilot migration. The new `internal/bridge.ts` is a registry
that publishes every TS-migrated implementation under the
`Vaadin.Flow.internal.*` namespace before `FlowClient.init()` runs; it is
re-installed before each init to survive tests that wipe `window.Vaadin`.
`ClientEngineTestBase` installs equivalent pass-through stubs so existing
`Gwt*Test` classes continue to work.

MIGRATION.md documents the proven pattern, the per-PR checklist and the
migration order for the remaining ~106 GWT-compiled classes.
LocationParser had no production callers (only its own JUnit test referenced
it); StorageUtil had no callers at all. Neither needs to be migrated to TS.
Replace the JSNI implementation of `LitUtils.isLitElement` and `whenRendered`
with a TypeScript module published at `window.Vaadin.Flow.internal.client.LitUtils`.
Establishes the callback-bridging pattern: GWT marshals Java `Runnable` through
a sibling `@JsFunction JsRunnable` interface declared on the `NativeLitUtils`
shim, so existing call sites in `SimpleElementBindingStrategy` are unchanged.

LitUtils is GWT-only (no JVM callers), so the Java shim does not need a
`System.out` fallback — it just no-ops outside `GWT.isScript()`.
Replace the JSNI `addReadyCallback` with a TypeScript module published at
`window.Vaadin.Flow.internal.client.ReactUtils`. Reuses the `JsRunnable`
@JsFunction declared on `NativeLitUtils` for callback marshalling.

`ReactUtils.isInitialized` is a plain null check with no browser API to
delegate to and stays in Java.
Move the JSNI accesses to `window.Vaadin.connectionState` and
`window.Vaadin.connectionIndicator` into a TypeScript module published at
`window.Vaadin.Flow.internal.client.ConnectionIndicator`. The Java class keeps
its public `CONNECTED`/`LOADING`/`RECONNECTING`/`CONNECTION_LOST` string
constants (used as keys throughout the codebase) and delegates the six methods
through a `NativeConnectionIndicator` JsType shim.

Pure JVM callers get no-op methods and a `null` `getState()`.
Move `getElementById` and `getElementByName` to a TypeScript module
published at `window.Vaadin.Flow.internal.client.ElementUtil`. `hasTag` is a
pure-Java `instanceof` + name comparison with no browser API to delegate to
and stays in `ElementUtil.java`. JVM tests get a `null` fallback from the
two delegating methods.
Move the 17 JSNI helpers in `com.vaadin.client.WidgetUtil` to a TypeScript
module published at `window.Vaadin.Flow.internal.client.WidgetUtil`. The pure-Java
helpers (`refresh`, `getAbsoluteUrl`, `updateAttribute`, the `toPrettyJson`
wrapper and `equals`) stay in `WidgetUtil.java`. JVM paths preserve their
existing behavior — only the JSNI bodies move.
Move the 13 JSNI helpers in `com.vaadin.client.PolymerUtils` to a TypeScript
module published at `window.Vaadin.Flow.internal.client.PolymerUtils`. The
pure-Java logic (createModelTree, getCustomElement, addReadyListener,
fireReadyEvent, the registered-change-handler wiring, etc.) stays in
PolymerUtils.java. JVM paths keep their existing no-op-when-not-script
behavior.
Move the 3 JSNI helpers (`checkForTouchDevice`, `getBrowserString`, `isIos`)
to a TypeScript module published at
`window.Vaadin.Flow.internal.client.BrowserInfo`. The pure-Java
`BrowserDetails`-backed query methods (`isFirefox`, `isMacOSX`, ...) stay in
`BrowserInfo.java`. JVM tests get an empty user-agent string and `false` for
the touch/iOS probes.
Move the 5 JSNI helpers (`supportsHtmlWhenReady`, `addHtmlImportsReadyHandler`,
`addOnloadHandler`, `getStyleSheetLength`, `runPromiseExpression`) to a
TypeScript module published at
`window.Vaadin.Flow.internal.client.ResourceLoader`.

`addOnloadHandler` no longer crosses the boundary with a Java listener +
event object; the Java shim builds two `JsRunnable` closures that capture
the event so the TS side only sees two `() => void` callbacks. Likewise
`runPromiseExpression` accepts a `JsSupplier` (new `@JsFunction` declared on
`NativeResourceLoader`) for the promise factory.
Move the 3 JSNI helpers (`recreateNodes`, `showPopover`, `getShadowRootElement`)
to a TypeScript module published at
`window.Vaadin.Flow.internal.client.SystemErrorHandler`. The error UI rendering
logic stays in `SystemErrorHandler.java`.
…resendRequest to TS

Both classes had a single JSNI helper each; the JS bodies move to
`window.Vaadin.Flow.internal.client.communication.{MessageSender,XhrConnection}`.
The surrounding Java orchestration (request queues, retry timers, XHR
plumbing) stays in place.
Move the 5 JSNI helpers (`removeStylesheetByIdFromDom`,
`callAfterServerUpdates`, `calculateBootstrapTime`, `parseJSONResponse`,
`getFetchStartTime`) to a TypeScript module published at
`window.Vaadin.Flow.internal.client.communication.MessageHandler`. The UIDL
processing orchestration stays in `MessageHandler.java`.
Move the 5 JSNI helpers (`getJsoConfiguration`, `vaadinBootstrapLoaded`,
`deferStartApplication`, `startApplicationImmediately`, `registerCallback`)
to a TypeScript module published at
`window.Vaadin.Flow.internal.client.bootstrap.Bootstrapper`. Callbacks taking
`String` use a new `JsStringConsumer` `@JsFunction` on `NativeBootstrapper`.
…nCodec JSNI

Each had one JSNI helper:

- `ExecuteJavaScriptElementUtils.isPropertyDefined` checks for a Polymer-style
  property descriptor on the element's constructor; moved to
  `window.Vaadin.Flow.internal.client.ExecuteJavaScriptElementUtils`.

- `ClientJsonCodec.createReturnChannelCallback` returns a JS function that
  forwards its variadic args to a Java consumer. Moved to
  `window.Vaadin.Flow.internal.client.flow.util.ClientJsonCodec`; the Java
  shim wraps `(nodeId, channelId, ServerConnector::sendReturnChannelMessage)`
  into a new `JsArgsConsumer` `@JsFunction` so the TS side stays
  ServerConnector-agnostic.
…+ status

- Document the `@JsFunction` interfaces established for callback marshalling
  (JsRunnable, JsSupplier, JsStringConsumer, JsArgsConsumer) and the
  "split-listener" trick used for ResourceLoader.addOnloadHandler.
- Record migration status: which classes have shipped, which are deferred
  because their JSNI is a bidirectional `this`-bound API surface rather than
  a browser-API helper, and which need no JSNI bridge.
… bridge

The JSNI body of `getContextExecutionObject` built the `this` object exposed
to user JS executed by `executeJs`: a `getNode(element)` lookup, the cleaned
`$appId` string, the `registry` handle, three element-utility invocations
that auto-resolve elements to StateNodes, and a `stopApplication()` callback.

Move the object construction to
`window.Vaadin.Flow.internal.client.flow.ExecuteJavaScriptProcessor`. The
Java shim now does the `$appId` cleanup, passes the `JsMap<Object, Object>`
node-parameter map straight through, and supplies three `@JsFunction`
callbacks for the element-utility dispatch.

`JsRunnable` moved to a top-level `com.vaadin.client.JsRunnable` since it
is now used from three packages.
…ia bridge

Move the two JSNI bodies in
`com.vaadin.client.flow.binding.SimpleElementBindingStrategy`:

- `bindPolymerModelProperties` checks `PolymerUtils.isPolymerElement` and the
  `customElements.whenDefined` + 1s race, then calls the Java `hookUpPolymer`
  hand-off through a `JsRunnable`. TS imports `PolymerUtils` from the
  already-migrated module rather than going through the bridge namespace.

- `hookUpPolymerElement` replaces the element's `_propertiesChanged` and
  `ready` methods, and on `ready` patches the `dom-repeat` prototype's
  `_propertiesChanged` to forward per-item changes back to the server. The
  Java shim provides three callbacks: handlePropertiesChanged (consumes the
  changed-props JS object), fireReadyEvent (no-arg), and
  handleListItemPropertyChange (nodeId, host, propertyName, value), each
  closing over the right Java context (node / element / tree).
…ridge

Move the 5 outer JSNI helpers in
`com.vaadin.client.communication.AtmospherePushConnection`:

- `createConfig` returns the Atmosphere config literal.
- `doConnect` wires Atmosphere's callbacks to nine Java methods bundled
  into a new `AtmosphereConnectCallbacks` @jstype POJO (three new JsFunction
  shapes declared as siblings: response/reconnect/object-supplier).
- `doPush`, `doDisconnect`, `isAtmosphereLoaded` are trivial wrappers.

The six JSO accessors on the inner `AtmosphereConfiguration`
(`getStringValue`, `setStringValue`, …) stay Java — they are property
accessors on a JS instance and don't fit a separate TS module.
Move the two `publishJavascriptMethods` and `publishDevelopmentModeJavascriptMethods`
JSNI bodies in `com.vaadin.client.ApplicationConnection` to a TypeScript
module published at `window.Vaadin.Flow.internal.client.ApplicationConnection`.

Both methods build the `window.Vaadin.Flow.clients[appId]` API surface from
positional `@JsFunction` callbacks passed by the Java shim — method
references into ApplicationConnection, Poller, Registry, ServerConnector,
StateTree, StateNode and URIResolver — replacing the 12 cross-class
`@JsType::method` JSNI calls.

Added a public `MessageHandler.getProfilingData()` helper so the cross-
package access to its package-private timing fields can stay Java instead
of having to be reached via @-syntax JSNI.

`ApplicationConnection.Styles.set` (JSO property accessor) stays Java.
Move the 8 outer static + 2 relative-time JSNI helpers in
`com.vaadin.client.Profiler` to a TypeScript module published at
`window.Vaadin.Flow.internal.client.Profiler`. The Java shim passes the
`EVT_GROUP` constant and `GWT.getModuleName()` value as explicit args to
`logGwtEvent`, replacing the JSNI's `@EVT_GROUP` / `@GWT::getModuleName()`
field-syntax references. JVM fallbacks use `System.currentTimeMillis`.

The inner `GwtStatsEvent` JSO accessors (`getEvtGroup`, `getMillis`, ...)
stay Java — they sit on a JS instance and don't fit a separate TS module.
All structurally-migratable JSNI bodies are now published via the bridge:
17 classes across client, client.bootstrap, client.communication,
client.flow, client.flow.binding, client.flow.util.

Document why each remaining JSNI stays in Java (JavaScriptObject subclasses
whose methods sit on a JS instance, already-native `@JsType` collections,
and vendored gwt/** code) and outline the tear-down phase that will drop
src/main/java, src/test, src/test-gwt, the GWT plugin and the bridge
registry once the pure-Java logic has been ported to TS.
First non-JSNI migration. Demonstrates the pattern for stateful classes
that are instantiated by JVM unit tests:

- The TS class owns the real logic.
- The Java public class becomes a thin facade that delegates to a
  `NativeExistingElementMap` JsType under GWT, and keeps a parallel
  `HashMap`/`ArrayList`-backed implementation for the JVM path so
  `ExistingElementMapTest` (and any other JUnit code that calls
  `new ExistingElementMap()`) keeps passing unchanged.

`ExistingElementMapTest` (JVM, 2 tests) still passes.
…pattern)

Second instance of the stateful pattern (a class instantiated by Java code
that needs to keep working under both GWT and JVM): the public Java class
keeps a `HashSet`-backed JVM implementation alongside the
`NativeUpdatableModelProperties` JsType binding to the TS implementation.

Used by `SimpleElementBindingStrategy.handlePropertiesChanged` and
`ExecuteJavaScriptElementUtils.populateModelProperty`; both keep calling
the same Java public API.
Add the recipe used by `ExistingElementMap` and
`UpdatableModelProperties` for migrating stateful classes that are
instantiated by JUnit tests: TS class for the real implementation,
`Native<Name>` JsType binding for the constructor + instance methods,
public Java class becomes a facade with a JVM fallback (HashMap/HashSet/
ArrayList) so existing JUnit tests keep passing. The fallback goes away
together with the JUnit suite in the tear-down phase.
…java

Companion to MIGRATION.md. MIGRATION.md describes the per-class patterns;
this file says what to do in what order, in PRs small enough to review,
with the tests that prove each step is safe.

Strategy: drop JUnit one-by-one as target code migrates, port their
coverage to mocha first, each Java class becomes a pure `@JsType` native
shim (no JVM fallback) — facades would just be make-work that gets
deleted in tear-down anyway.

13 tiers, leaves up: reactive → nodefeature → dom → state tree → binding
→ application kernel → communication (3 sub-tiers) → ApplicationConnection
→ tear-down. Plus three pre-work commits (P1 inventory, P2 mocha harness,
P3 confirm GwtTest CI status). Estimated 21-30 days of focused work
across ~25-40 commits.
…TION.md

Reframe the recommended migration pattern to match MIGRATION_PLAN.md:

- The pure `@JsType(isNative = true)` declaration (no JVM branch, no
  Native<Name> sibling unless callback bundling demands one) is the
  recommended shape for new migrations.
- The `GWT.isScript()` shim + JVM fallback pattern used by the 24
  in-flight migrations is now framed as transitional. Per the plan,
  these shims collapse to the recommended form during Tier 7
  (top-level utilities) and the matching tiers for communication and
  bootstrap classes.
- Per-PR checklist updated: tests get ported to mocha *first*, JUnit
  and GwtTest get deleted in the same commit as the migration. The
  GwtTest stub map shrinks rather than grows.
- JsRunnable noted as the shared top-level callback shape; the
  `JsArrayLength` table now reflects its move from NativeLitUtils to
  com.vaadin.client.
- "PR size and order" defers the ordering specifics to
  MIGRATION_PLAN.md to keep one source of truth.
Classifies every test method in src/test/java and src/test-gwt as
PORT (port to mocha before its class migrates), DROP (tests JVM
fallback / GWT-build quirks / JS-engine-guaranteed behaviour), or
INTEGRATION (already covered by flow-tests at the right level).

176 methods across 49 test files:
- ~130 PORT — protocol-critical state-tree, binding, communication
- ~40 DROP — Jre* collections, JsArray/JsMap/JsSet/JsWeakMap (already
  native), GWT-specific quirks (assertion flag, gwt generics), GWT
  bundle size budget
- ~6 INTEGRATION — reconnect flow, web-component navigation,
  unrecoverable-error UI; flow-tests covers these adequately

Each PORT row maps to a tier in MIGRATION_PLAN.md; that's where the
mocha port lands alongside the class migration. The biggest single
ports are GwtBasicElementBinderTest (71 tests) and GwtPolymerModelTest
(21 tests), both in T5.

Open questions captured at the bottom: bundle-size budget for T13,
splitting binding tests across mocha files, eslint encoding of
DomApiAbstractionUsage intent, real-CI status of GwtTest (P3).
Artur- added 27 commits June 1, 2026 16:11
…cript

Replace the @JsOverlay sendEventToServer / sendNodePropertySyncToServer /
sendTemplateEventToServer / sendExistingElementAttachToServer /
sendExistingElementWithIdAttachToServer Java methods with native
bindings to TS-side equivalents on StateTree.

The TS class now holds a serverConnector reference (wired by
DefaultRegistry via setServerConnector after construction, same
install-after-construct pattern used for InitialPropertiesHandler).
sendNodePropertySyncToServer also consults the InitialPropertiesHandler
to suppress sync messages that the handler will replay; the IPH type
binding gains the handlePropertyUpdate signature accordingly.

Tests: 89/89 mocha pass.
Replace the Java @JsOverlay registerNode / unregisterNode methods with
native bindings to TS-side methods on StateTree. The TS versions take
over the assertions (now thrown as Error) and the
InitialPropertiesHandler.nodeRegistered call when an update is in
progress -- the IPH binding type gains the nodeRegistered signature.

The @JSmethod aliases registerNodeImpl / unregisterNodeImpl are dropped
as the same method names register cleanly across the bridge.

Tests: 89/89 mocha pass.
Replace the @JsOverlay prepareForResync / clearLists / assertValidNode
/ isValidNode helpers with TS-side equivalents. The Java side becomes
a single native prepareForResync binding.

The ServerEventObject.getIfPresent + rejectPromises JSNI pair is
inlined in TS as direct `$server` property reads and a promise-array
iteration that rejects each pending @ClientCallable promise. The
RPC_PROMISE_CALLBACK_NAME literal is mirrored as a TS constant so the
module doesn't depend on Java's JsonConstants.

Drops the forEachNodeImpl @JSmethod alias and JsForEachNodeCallback
inner interface (no longer referenced), plus six now-unused imports.

Tests: 89/89 mocha pass.
Convert WidgetUtil from a static-method wrapper that forwarded to
NativeWidgetUtil into a pure @jstype(isNative=true) facade onto the
TS WidgetUtil module. The Java-only helpers (refresh, getAbsoluteUrl,
isAbsoluteUrl, crazyJsCast, crazyJsoCast, toPrettyJson, updateAttribute,
createJsonObject, equals) are added to the TS module so the facade can
cover all callers.

Drops NativeWidgetUtil.java -- the same name no longer needs a separate
binding class.

Tests: 89/89 mocha pass.
Inline the case-insensitive tag-name comparison in the TS module so
Java's @jstype native facade can declare hasTag native rather than
keep it as Java-side @JsOverlay logic.

Tests: 89/89 mocha pass.
…o TS

Replace MapProperty's @JsOverlay syncToServer / getSyncToServerCommand
with TS-side equivalents. The new TS methods reach into the existing
StateTree.sendNodePropertySyncToServer dispatch (already in TS) so the
whole sync-command chain is now JS-native.

The isServerUpdate / markServerUpdate / doSetValue @JSmethod aliases
are dropped now that callers go through the TS methods directly. The
Objects.equals null-safe comparison becomes a small private TS helper
(non-null GWT objects collapse to reference equality anyway, which
matches === on the TS side).

getPreviousDomValue stays Java-side as @JsOverlay because the Java
Optional<Object> shape doesn't survive a JS-only port.

Tests: 89/89 mocha pass.
…ta API

Promote setNodeData(String, Object) / getNodeData(String) /
clearNodeData(String) to public native methods on the @jstype native
StateNode facade. The previous @JsOverlay versions keyed on
Class<T>.getName() are kept as Java-only convenience overloads, but
cross-language call sites can now use an explicit string-key constant
that survives a TypeScript port.

Adds NODE_DATA_KEY = "UpdatableModelProperties" on
UpdatableModelProperties, and updates ExecuteJavaScriptElementUtils to
use the constant instead of UpdatableModelProperties.class -- preparation
for migrating those @JsOverlay methods to TS.

Tests: 89/89 mocha pass.
… to TS

Move attachExistingElement, populateModelProperties, and
registerUpdatableModelProperties from @JsOverlay Java helpers into the
TS ExecuteJavaScriptElementUtils module. The Java side becomes a pure
@jstype(isNative=true) facade.

PolymerUtils.getTag is inlined as a direct ELEMENT_DATA/TAG lookup so
the TS module doesn't need to add a PolymerUtils method. The
UpdatableModelProperties lookup uses the explicit
UPDATABLE_MODEL_PROPERTIES_KEY string (mirroring
UpdatableModelProperties.NODE_DATA_KEY) so cross-language storage hits
the same slot.

registerUpdatableModelProperties resolves the constructor through
window.Vaadin.Flow.internal.client.flow.model.UpdatableModelProperties
since the existing JsType native facade publishes it at that path.

Drops ExecuteJavaScriptCallbacks.java -- the adapter is no longer
needed now that the TS processor calls TS utilities directly. The
ExecuteJavaScriptProcessor constructor parameter list shrinks back to
just (registry).

Tests: 89/89 mocha pass.
Move the remaining Java-only logic (createModelTree, registerChangeHandlers
and the property/list-notification machinery, addReadyListener /
fireReadyEvent, getCustomElement, getTag, hasTag, getChildIgnoringStyles)
to the TS PolymerUtils module. The Java class becomes a pure
@jstype(isNative=true) facade.

Drops NativePolymerUtils.java -- the JsInterop binding name is now the
public PolymerUtils class itself.

The static readyListeners JsWeakMap migrates to a module-level
WeakMap<Element, Set<() => void>>. createModelTree's recursion uses
the convert(...) hooks already exposed on the TS NodeMap/NodeList and
registers change/splice listeners through the existing event-router
surface; getNotificationPath / getPropertiesNotificationPath /
getListNotificationPath are ported verbatim.

Tests: 89/89 mocha pass.
Remove the @JsOverlay setNodeData(T) / getNodeData(Class<T>) /
clearNodeData(T) Java-only convenience overloads now that all call
sites have been switched to the explicit string-keyed natives.

Updates the three remaining Class<T> sites in
SimpleElementBindingStrategy to use string-key constants:
UpdatableModelProperties.NODE_DATA_KEY for the model-property lookup
in handlePropertyChange, and a new
InitialPropertyUpdate.NODE_DATA_KEY private constant for the inner
class's setNodeData / getNodeData / clearNodeData triplet.

The native string-keyed methods are the only public surface left on
StateNode for node-data storage; cross-language callers (Java + TS)
agree on the key strings without depending on GWT's Class.getName()
runtime output.

Tests: 89/89 mocha pass.
Move the implementation to
src/main/frontend/internal/client/communication/RequestResponseTracker.ts;
the Java class becomes a pure @jstype(isNative=true) facade.

The GWT EventBus is replaced with simple per-event-type Set<Handler>
queues, and the four Event<H> subclasses (RequestStartingEvent,
ResponseHandlingStartedEvent, ResponseHandlingEndedEvent,
ReconnectionAttemptEvent) are eliminated. Handlers become JsRunnable
or JsIntConsumer @JsFunction callbacks (a new JsIntConsumer is added
alongside JsRunnable / JsBooleanSupplier). The Java facade still
returns GWT's HandlerRegistration, which structurally matches the TS
{ removeHandler(): void } shape.

The endRequest "should we flush queued invocations now?" decision is
hoisted into a JsRunnable constructor argument, wired by
DefaultRegistry to the same UILifecycle / ServerRpcQueue / MessageSender
checks the original Java version performed inline. MessageSender's
getResynchronizationState becomes public so the wiring lambda can read
it from outside the package.

Java callers updated:
- XhrConnection drops the unused event arg from the response-ended handler
- MessageSender takes the int attempt number directly instead of ev.getAttempt()
- MessageHandler.fireEvent(new ResponseHandlingStartedEvent()) -> fireResponseHandlingStarted()
- DefaultConnectionStateHandler.fireEvent(new ReconnectionAttemptEvent(n)) -> fireReconnectionAttempt(n)

Tests: 89/89 mocha pass.
Move the implementation (defineMethod / removeMethod / getMethods /
rejectPromises / initPromiseHandler / event-data resolution /
expression cache) to
src/main/frontend/internal/client/flow/binding/ServerEventObject.ts;
the Java class becomes a pure @jstype(isNative=true) facade.

JSNI patterns convert to direct JS: `this[name] = function(...) {...}`
for defineMethod, `Object.defineProperty(this, PROMISE_CALLBACK_NAME, ...)`
for initPromiseHandler, `new Function('event', 'element', body)` for
the data-expression cache.

ServerEventObject no longer extends JavaScriptObject, so
SimpleElementBindingStrategy's `WidgetUtil.crazyJsoCast` cast switches
to the generic-typed `crazyJsCast<ServerEventObject>` (identity-cast in
both forms; the difference was just Java type-checking).

MessageSender.getResynchronizationState is promoted to public so
DefaultRegistry's RequestResponseTracker wiring lambda can reach it
from outside the package -- already needed by the previous RRT commit.

Tests: 89/89 mocha pass.
Move bindServerEventHandlerNames (long form) to
src/main/frontend/internal/client/flow/binding/ServerEventHandlerBinder.ts;
the Java class becomes a pure @jstype(isNative=true) facade.

The original `Supplier<ServerEventObject>` parameter becomes a new
inner @JsFunction ServerEventObjectProvider so Java lambdas can be
passed across the JS boundary. The two-arg convenience overload
(Element, StateNode) stays Java-side as an @JsOverlay delegating to
the long form with the CLIENT_DELEGATE_HANDLERS feature id.

Tests: 89/89 mocha pass.
The original Profiler collected timings via the GWT `__gwtStatsEvent` hook,
and only when the `vaadin.profiler` GWT compile flag was set; production
builds always used the disabled no-op base class. Both the compile flag and
the stats-event stream disappear with the GWT runtime, so the aggregation,
Node/RelativeTimeSupplier types, pretty-printer, and ProfilerResultConsumer
plumbing become unreachable.

Profiler.ts keeps the same public surface as a thin no-op: isEnabled returns
false, enter/leave/reset/initialize/logTimings/logBootstrapTimings are
no-ops, and the time helpers wrap performance.now(). Existing Java guards
(`if (Profiler.isEnabled())`) keep linking but never execute their bodies.
Profiler.java becomes a @jstype(isNative=true) facade over the TS module;
NativeProfiler.java is removed since its functions were only used by the
old aggregation paths that no longer exist.
The replace-with referenced Profiler.EnabledProfiler, removed in the prior
commit when Profiler became a TS no-op facade. The compile-time switch was
gated on vaadin.profiler=true and the default value was false, so no
production build was affected.
Move the error-routing, rendering, and resync flow into
SystemErrorHandler.ts. Construction takes a new SystemErrorHandlerCallbacks
adapter so the TS class does not depend on the Java Registry facade; the
adapter is built in DefaultRegistry where the wiring lives and is added
last so its callbacks reach already-built Heartbeat / MessageSender /
PushConfiguration / MessageHandler.

The Java SystemErrorHandler becomes a @jstype(isNative=true) facade. The
Throwable overload of handleError() stays so GWT.setUncaughtExceptionHandler
can still take it as a method reference; the body unwraps UmbrellaException
and forwards a string to TS so the TS side doesn't need to model GWT
throwables. Xhr.getWithCredentials is replaced by XMLHttpRequest with
withCredentials; Scheduler.scheduleDeferred is replaced by setTimeout(fn, 0)
which matches its semantics. NativeSystemErrorHandler is removed since its
helpers are inlined into the TS module.

Registry.getSystemErrorHandler is keyed by an explicit "SystemErrorHandler"
string rather than Class.getName() — GWT collapses every native @jstype to
JavaScriptObject so a second Class-keyed registration would collide with
the existing Heartbeat one.
Replace the BrowserDetails-backed Java BrowserInfo with a TS class that
inlines a minimal UA-string parser covering the six methods used by
external Java callers (isChrome, isIE, isEdge, isSafariOrIOS, isOpera,
isWebkit) plus the original public surface (isFirefox, isSafari, isGecko,
isAndroid, isTouchDevice, isAndroidWithBrokenScrollTop, version getters).
The TS class is wired through Vaadin.Flow.internal.client.BrowserInfo so
Java callers keep the BrowserInfo.get().isXxx() pattern unchanged — both
the static .get() factory and the instance method names survive
GWT-OBF compilation because the call sites address the JS namespace
verbatim.

Static helpers getBrowserString / checkForTouchDevice / isIos remain on
the same namespace so the existing GWT-compiled call sites keep linking.
NativeBrowserInfo is removed since its helpers are now part of the
migrated class.
The vendored SimpleEventBus (a copy of com.google.web.bindery.event.shared.SimpleEventBus)
has no callers in flow-client. The original EventBus that the four
request/response trackers used was inlined into the TS-side
RequestResponseTracker migration, so the supporting class became dead code.
Move BindingStrategy / BinderContext interfaces, TextBindingStrategy, and
the 1500-LOC SimpleElementBindingStrategy from Java into TS. Cross-strategy
references become TS-to-TS, sidestepping the GWT-OBF callback name-mangling
that blocked earlier callback-adapter migrations. The Java side keeps Binder
as a thin @jstype(isNative=true) facade with just static bind(StateNode,
Node) so ApplicationConnection's single call site at line 71 keeps working
through the namespace path Vaadin.Flow.internal.client.flow.binding.Binder.bind.
Port the script / stylesheet / dynamic-import / HTML-imports-readiness
subsystem to TS. ResourceLoader and DependencyLoader move together so the
internal listener plumbing (ResourceLoadListener with onLoad/onError) stays
TS-to-TS; at the Java->TS boundary, @JsOverlay shims unwrap the listener
into two @JsFunction callbacks (OnLoad, OnError) before crossing, sidestepping
the GWT-OBF method-name mangling that affects TS calling methods on Java
anonymous interface implementations.

ResourceLoader's only Registry dependency (SystemErrorHandler::handleError
for "could not load X" messages) is now passed directly to the constructor
as an ErrorHandler @JsFunction. DependencyLoader takes URIResolver and
ResourceLoader directly. Neither receives the Java Registry instance, which
would have required calling Registry.getXxx() from TS — those Java instance-
method names are mangled by GWT OBF.

NativeResourceLoader is removed; its helpers (getStyleSheetLength,
runScriptElement, etc.) are inlined into ResourceLoader.ts.
…ler to TS

Port the communication tier (MessageHandler, MessageSender,
ConnectionStateHandler interface, DefaultConnectionStateHandler) as one
cluster so cyclic dependencies become TS-to-TS. The Java side keeps thin
@jstype(isNative=true) facades preserving the public method names that
ApplicationConnection, XhrConnection, AtmospherePushConnection, Heartbeat,
and DefaultRegistry call by name. Cross-boundary Java->TS callbacks where
TS needs to reach still-Java services (Registry.getXxx(), XhrConnection.send,
etc.) are passed as @JsFunction lambdas at construction time through a
@jstype(isNative=true, namespace=GLOBAL, name=Object) struct with explicit
@JsProperty(name=...) setters. That setter-with-explicit-name pattern is the
proven workaround for the GWT-OBF method-name mangling that affects Java
instance methods and anonymous-class @jstype interface implementations.
…n, XhrConnection) to TS

Port the AtmospherePushConnection and XhrConnection transports along with
the vendored Xhr helper as one cluster. With MessageSender, MessageHandler,
and ConnectionStateHandler already TS, the transports' cross-references
become TS-to-TS. Java side keeps thin @jstype(isNative=true) facades so
the public API (MessageSender's send/push paths, DefaultRegistry's wiring)
keeps working through the JS namespace where method names are preserved.

PushConnectionFactory's GWT.create() deferred-binding indirection is
removed; DefaultRegistry now constructs AtmospherePushConnection directly
through the MessageSenderCallbacks.createPushConnection lambda, since the
single AtmospherePushConnection$Factory implementation has no other
consumers. PushConnection itself becomes a native @jstype interface so
the now-native AtmospherePushConnection can declare it as an implemented
interface.
Port the last Java bootstrap pieces (Bootstrapper EntryPoint,
ApplicationConnection orchestrator, Registry/DefaultRegistry DI container,
JsoConfiguration view) to TypeScript. With the EntryPoint gone there is no
longer anything to feed the GWT compile.

Remove gwt-maven-plugin and the gwt-* dependencies from pom.xml, delete the
ClientEngine.gwt.xml / ClientEngineXSI.gwt.xml module descriptors, the
ClientEngineLinker single-script template and TrackingScheduler. The
hand-written FlowClient.js entry point now drives Bootstrapper.initModule
directly through the window.Vaadin.Flow.internal.client.bootstrap.Bootstrapper
namespace that bridge.ts exposes.

All migrated facade Java files (Console, Profiler, WidgetUtil, MessageHandler,
StateTree, Reactive, ...) become unreferenced once ApplicationConnection /
DefaultRegistry are gone and are deleted in the same commit. src/main/java
ends up empty; the produced jar is content-only (META-INF/frontend/*.js).
The GWT-compiled Java is gone, so all the indirections that existed for
it can go too:

- Delete the window.Vaadin.Flow.internal.* bridge (bridge.ts +
  registry.ts) and the FlowClient.js wrapper. Flow.ts now imports
  Bootstrapper directly via dynamic import. The only remaining bridge
  read (ExecuteJavaScriptElementUtils -> UpdatableModelProperties) is
  replaced with a normal ES import.
- Strip ResourceLoader's 5 "retained for legacy GWT call sites"
  statics (zero callers) and MessageSender's getResynchronizationStateName
  Java-enum alias.
- Move BrowserInfo.checkForTouchDevice + isIos to module-local helpers
  and delete the unused getBrowserString static.
- Shrink Profiler to just getRelativeTimeMillis / getRelativeTimeString;
  the rest were no-ops kept "so existing Java guards keep linking" and
  the guards are gone. Drop the now-dead Profiler.enter/leave/initialize
  callsites in Bootstrapper.ts and MessageHandler.ts.
- Trim Bootstrapper public surface to initModule(); the
  getRunningApplications / getJsoConfiguration / startApplicationImmediately
  / deferStartApplication / registerCallback / vaadinBootstrapLoaded
  exports were only there for bridge consumers.
- Sweep stale comments referencing @jstype native facades, @JsOverlay
  helpers, NativeX shims, JSNI bodies, and "Reached from GWT-compiled
  code" across 20+ files. Kept the GWT Scheduler.scheduleDeferred-style
  comments that explain why setTimeout(0) is used.
- Drop gwt-unitCache/ from .gitignore.

FlowTests.ts pre-bundling import updated to point at the new entry
module; this also unblocked 25 previously failing tests (64 -> 89
passing).

Net diff: -589/+70 across 31 files.
The previous cleanup commit ("drop GWT bridge + dead helpers
post-migration") deleted FlowClient.js because the only in-tree caller
(Flow.ts) had been switched to import Bootstrapper directly. That missed
an out-of-tree caller: flow-build-tools' TaskGenerateWebComponentBootstrap
generates a web-component bootstrap that does

    import { init } from 'Frontend/generated/jar-resources/FlowClient.js';
    init();

and flow-server's vite.generated.ts lists the same path. Both reference
the file by name in production builds, so FlowClient.js is part of the
public API of the flow-client jar.

Restore it. The new body skips the deleted window.Vaadin.Flow.internal
bridge lookup and statically imports Bootstrapper instead. FlowClient.d.ts
already declares the same `init` signature so no consumer change is
needed.

Caught by 15/15 it-tests failures on the PR build with
'[UNLOADABLE_DEPENDENCY] Could not load src/main/frontend/generated/jar-resources/FlowClient.js'.
Found by running test-eager-bootstrap locally and watching the browser
console. Each bug was a Java→TS porting miss where the new code retained
Java method-call syntax against a JS object that has no methods, or kept
a stale Java type assumption in a TS callback wrapper.

1. ConstantPool.importFromJson called `json.keys()` and `json.get(key)`
   on a plain JS object (Java's `elemental.json.JsonObject.keys()` does
   not exist on `Object`). Use `Object.keys(json)` and bracket access.

2. NodeMap / NodeList / MapProperty wired their ReactiveEventRouter wrap
   callback to return a *function* `(event) => listener.onValueChange(event)`,
   but the matching dispatchFn calls `listener.onPropertyAdd(event)` /
   `listener.onSplice(event)` / `listener.onPropertyChange(event)`. The
   stored listener has to be an object with the right method name (Java's
   `wrap()` returned a functional-interface instance, which has the
   method). Return `{ onPropertyAdd: ... }` / `{ onSplice: ... }` /
   `{ onPropertyChange: ... }` so the dispatch finds the method.

3. addPropertyAddListener / addSpliceListener / addChangeListener accept
   raw `(event) => void` lambdas (Java was a functional interface, the
   TS port type-signature still allows it). Calls to dispatchFn then
   crash because `function.onPropertyAdd` etc. don't exist. Normalize
   the input: if it's a bare function, wrap it in the expected object
   shape before storing.

4. ExecuteJavaScriptProcessor + URIResolver called Java-style
   `appConfig.getApplicationId()`, `appConfig.isProductionMode()`,
   `appConfig.getContextRootUrl()` on `ApplicationConfiguration`, which
   was migrated as a plain-fields TS class (no methods). Switch to
   field access (`applicationId`, `productionMode`, `contextRootUrl`)
   and update the local `RegistryLike` types to match.

After these four fixes, test-eager-bootstrap renders RootView and
HelloView with 0 console errors.
`client.getProfilingData()` is contract-tested by
ExportedJSFunctionIT.profilingInfoAvailableInDevelopmentMode, which
asserts the array has exactly 5 entries:

  [lastProcessingTime, totalProcessingTime, serverTime0, serverTime1, bootstrapTime]

The TS port pushed `this.serverTimingInfo` as a single entry (the raw
object) when present, dropping to 4 total. Spread it into its two
numeric slots like the Java original did.
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 1, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant