Improvements #14550 ยป wt-handler-factory-no-minified.patch
| src/web/DomElement.C | ||
|---|---|---|
|
// events on the dom root container are events received by the whole
|
||
|
// document when no element has focus
|
||
|
// Fast path: most exposed-signal handlers are produced by setEvent() from a
|
||
|
// small set of deterministic templates. Recognize the three common shapes
|
||
|
// (simple update, guarded update, anchor-guarded update) and emit a call
|
||
|
// into a shared closure factory in Wt.js instead of a unique per-element
|
||
|
// `function fNNN(event) { ... }` declaration. The factory closures
|
||
|
// reproduce the inlined handler body exactly; this is a payload-size
|
||
|
// optimization, not a behavior change. Other handler shapes fall through
|
||
|
// to the unchanged per-element function emission below.
|
||
|
if (!globalUnfocused_ && !handler.signalName.empty()) {
|
||
|
static const std::string prelude = "var e=event||window.event,o=this;";
|
||
|
const std::string& body = handler.jsCode;
|
||
|
if (body.size() > prelude.size()
|
||
|
&& body.compare(0, prelude.size(), prelude) == 0) {
|
||
|
static const std::string anchorOpen =
|
||
|
"if(e.ctrlKey||e.metaKey||e.shiftKey||(" WT_CLASS ".button(e) > 1))"
|
||
|
"return true;else{";
|
||
|
std::size_t bodyStart = prelude.size();
|
||
|
std::size_t bodyEnd = body.size();
|
||
|
bool anchor = false;
|
||
|
if (body.size() >= bodyStart + anchorOpen.size() + 1
|
||
|
&& body.compare(bodyStart, anchorOpen.size(), anchorOpen) == 0
|
||
|
&& body.back() == '}') {
|
||
|
anchor = true;
|
||
|
bodyStart += anchorOpen.size();
|
||
|
--bodyEnd;
|
||
|
}
|
||
|
// Keep the "Wt-no-default" string and the cancelEvent/preventDefault
|
||
|
// structure in sync with the HandlerChecks built by
|
||
|
// WInteractWidget::updateSignalConnection() and with the same string
|
||
|
// in the _mkgu / _mkau factories in src/web/skeleton/Wt.{,min.}js.
|
||
|
// Detection deliberately uses the literal here rather than the
|
||
|
// WInteractWidget::noDefault constant so that a future rename of the
|
||
|
// constant fails closed (handlers fall through to per-element
|
||
|
// function emission, payload regresses) rather than silently
|
||
|
// running a factory whose Wt.js body still checks the old class.
|
||
|
const std::string handlerChecks =
|
||
|
"if(o.classList.contains('" + app->theme()->disabledClass() + "')){"
|
||
|
WT_CLASS ".cancelEvent(e);return;}"
|
||
|
"if(o.classList.contains('Wt-no-default')){e.preventDefault();}";
|
||
|
bool guarded = false;
|
||
|
if (bodyEnd >= bodyStart + handlerChecks.size()
|
||
|
&& body.compare(bodyStart, handlerChecks.size(), handlerChecks) == 0) {
|
||
|
guarded = true;
|
||
|
bodyStart += handlerChecks.size();
|
||
|
}
|
||
|
// The anchor-without-guard shape is reachable only from direct
|
||
|
// DomElement::setEvent callers on a raw <a> element (stock Wt anchor
|
||
|
// clicks always go through WInteractWidget, which injects HandlerChecks
|
||
|
// before setEvent runs). Skip optimizing that case; the AnchorGuarded
|
||
|
// factory adds a disabled-class check the original handler did not run,
|
||
|
// which would be observable behavior change.
|
||
|
if (anchor && !guarded) {
|
||
|
// fall through to per-element function emission
|
||
|
} else {
|
||
|
const std::string updateCall =
|
||
|
app->javaScriptClass() + "._p_.update(o,'" + handler.signalName +
|
||
|
"',e,true);";
|
||
|
if (bodyEnd - bodyStart == updateCall.size()
|
||
|
&& body.compare(bodyStart, updateCall.size(), updateCall) == 0) {
|
||
|
// Factory contract: _mksu(app, sig) takes two arguments;
|
||
|
// _mkgu(app, dc, sig) and _mkau(app, dc, sig) take three. Keep
|
||
|
// emission below in sync with these signatures.
|
||
|
const char *factory =
|
||
|
anchor ? "._mkau(" :
|
||
|
guarded ? "._mkgu(" :
|
||
|
"._mksu(";
|
||
|
const bool wheelOnIE =
|
||
|
(eventName == WInteractWidget::WHEEL_SIGNAL &&
|
||
|
app->environment().agentIsIE() &&
|
||
|
static_cast<unsigned int>(app->environment().agent()) >=
|
||
|
static_cast<unsigned int>(UserAgent::IE9));
|
||
|
declare(out);
|
||
|
if (wheelOnIE)
|
||
|
out << var_ << ".addEventListener('wheel',";
|
||
|
else
|
||
|
out << var_ << ".on" << eventName << '=';
|
||
|
out << WT_CLASS << factory << app->javaScriptClass();
|
||
|
if (anchor || guarded)
|
||
|
out << ",'" << app->theme()->disabledClass() << '\'';
|
||
|
out << ",'" << handler.signalName << "')";
|
||
|
if (wheelOnIE)
|
||
|
out << ",false)";
|
||
|
out << ";\n";
|
||
|
return;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
unsigned fid = nextId_++;
|
||
|
out << "function f" << fid << "(event) { ";
|
||
| src/web/skeleton/Wt.js | ||
|---|---|---|
|
}
|
||
|
};
|
||
|
// Closure factories used by DomElement::setJavaScriptEvent() to avoid
|
||
|
// emitting a unique `function fNNN(event){...}` declaration for every
|
||
|
// exposed-signal event handler. Each factory returns a per-element
|
||
|
// closure whose body reproduces, byte-for-byte at runtime, the inlined
|
||
|
// handler that DomElement::setEvent() would otherwise have built.
|
||
|
//
|
||
|
// _mksu โ simple update: just the signal dispatch.
|
||
|
// _mkgu โ guarded update: disabled-class + no-default-class checks,
|
||
|
// then signal dispatch (non-anchor click pattern).
|
||
|
// _mkau โ anchor-guarded update: ctrl/meta/shift / non-primary-button
|
||
|
// bypass, then guarded update (anchor click pattern).
|
||
|
//
|
||
|
// The literal "Wt-no-default" below must match WInteractWidget::noDefault
|
||
|
// (src/Wt/WInteractWidget.C) and the matching literal in
|
||
|
// DomElement::setJavaScriptEvent(). All three sites must be updated
|
||
|
// together if that class name ever changes.
|
||
|
this._mksu = function(app, sig) {
|
||
|
return function(event) {
|
||
|
const e = event || window.event, o = this;
|
||
|
app._p_.update(o, sig, e, true);
|
||
|
};
|
||
|
};
|
||
|
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);
|
||
|
};
|
||
|
};
|
||
|
this._mkau = function(app, dc, sig) {
|
||
|
return function(event) {
|
||
|
const e = event || window.event, o = this;
|
||
|
if (e.ctrlKey || e.metaKey || e.shiftKey || (WT.button(e) > 1)) {
|
||
|
return true;
|
||
|
}
|
||
|
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);
|
||
|
};
|
||
|
};
|
||
|
this.getElement = function(id) {
|
||
|
let el = document.getElementById(id);
|
||
|
if (!el) {
|
||