Project

General

Profile

Actions

Improvements #14550

open

Reduce dynamically generated JS for event handlers

Added by Will Johnson 2 days ago.

Status:
New
Priority:
Normal
Assignee:
-
Target version:
-
Start date:
05/20/2026
Due date:
% Done:

0%

Estimated time:

Description

In the process of updating a large application from Wt3 to Wt4 I noticed the JS payload dramatically increased, despite the removal of jQuery.

Almost half of the JS payload turned out to be repeated near-identical per-element handler function declarations that DomElement::setJavaScriptEvent emits — things that look like:

function f19737(event) {
  var e = event||window.event, o = this;
  if (o.classList.contains('Wt-disabled')){Wt4_13_2.cancelEvent(e); return;}
  if (o.classList.contains('Wt-no-default')){e.preventDefault();}
  Wt._p_.update(o, 'sa7d9', e, true);
}
j19737.onclick = f19737;

If instead we define this structure once in Wt.js:

this._mkgu = function(app, dc, sig) {
  return function(event) {
    const e = event || window.event, o = this;
    if (o.classList.contains(dc)) { WT.cancelEvent(e); return; }
    if (o.classList.contains("Wt-no-default")) { e.preventDefault(); }
    app._p_.update(o, sig, e, true);
  };
};

and then in src/web/DomElement.C dynamically detect handlers of this shape and emit:

j19737.onclick = Wt4_13_2._mkgu(Wt, 'Wt-disabled', 'sa7d9');

we save considerably on JS whenever there are many elements of the same shape, with no behavior change.

In my testing I saw three variants of this kind of function:

  • Bare EventSignal dispatch — by far the most common (2,731 occurrences in my app, mostly onfocus/onblur/onchange): js var e=event||window.event, o=this; Wt._p_.update(o,'sXX',e,true);
  • Disabled / no-default-class guard — the shape shown above (~108 occurrences).
  • Anchor click wrapper (~725 occurrences): js if (e.ctrlKey||e.metaKey||e.shiftKey||(Wt4_13_2.button(e)>1)) return true; else { /* guard-pattern body */ }

If DomElement::setJavaScriptEvent recognizes these three shapes and emits a factory call, the JS payload for my application is reduced by ~420 KB (35%).

Anything that doesn't exactly match one of the three shapes — handlers with custom JSlots, double-click dispatch, preventDefault/stopPropagation on the signal, drag tracking, etc. — takes the existing per-element function path entirely unchanged, so the worst case of a missed match is just "no savings for that handler".

The attached patch touches only the body of setJavaScriptEvent and adds three methods to the Wt.js IIFE — no public-header, API, or ABI changes — and the factory-call form elem.onXXX = Wt._mkXX(...) preserves the CSP-only-JS intent of #13501.

The attached patch, wt-handler-factory-no-minified.patch, applies against master commit e8dd034d21409847be135b048641a32e214d7ff1. I exercised the patched build interactively (menus, dialogs, tabs, anchor clicks, simple/guarded button clicks, opening/closing widgets) and saw no observable difference against the unpatched build, and was also unable to come up with any code paths or examples where this would change observable behaviors. Wt.min.js will need to be regenerated after the patch.

AI was used to help generate and verify the patch, but investigation and verification were performed carefully and manually.


Files

No data to display

Actions

Also available in: Atom PDF