Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 404x 404x 404x 404x 404x 404x 404x 404x 404x 404x 404x 404x 404x 404x 404x 404x 404x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 34x 404x 404x 404x 404x 404x 404x 404x 404x 404x 677x 677x 677x 677x 677x 404x 404x 404x 404x 404x 404x 404x 404x 404x 85x 4x 4x 81x 81x 81x 81x 81x 81x 85x 85x 85x 85x 85x 85x 85x 85x 85x 85x 85x 85x 85x 85x 85x 85x 85x 85x 85x 85x 85x 81x 29x 29x 81x 85x 4x 4x 4x 4x 4x 4x 4x 4x 85x 404x 404x 404x 404x 404x 195x 195x 404x | import { logger } from '../runtime/AvenxLogger.js';
import { AvenxErrorCodes, formatMessage } from '../runtime/AvenxError.js';
import { tracer } from '../trace/tracer.js';
import { TraceNodeType } from '../trace/schema.js';
import { describeNode } from '../trace/dom.js';
/**
* Builds the trace node for a dispatched event.
*
* The handler source is recorded verbatim. Avenx keeps template expressions as
* source text right through to evaluation, so the trace can name the exact code
* that ran rather than a compiled closure with no identity.
* @param {string} source - The handler source from the template.
* @param {Event|null} event - The dispatched event.
* @returns {object} The event node fields.
*/
export function buildEventNode(source, event) {
const target = event && event.target;
const node = {
eventType: (event && event.type) || 'unknown',
handler: source,
};
const ref = describeNode(target);
if (ref) {
node.target = { selector: ref.selector, nth: ref.nth };
if (ref.component) {
node.component = ref.component;
node.uid = ref.uid;
}
}
// Input values are what replay has to reproduce for a form interaction, so a
// typed value is part of the event rather than an observation of it.
if (target && typeof target.value === 'string' && target.value !== '') {
node.value = target.value;
}
if (target && typeof target.checked === 'boolean') {
node.checked = target.checked;
}
if (event && typeof event.key === 'string') {
node.key = event.key;
}
return node;
}
/**
* Handles the execution of event handlers.
*/
export class EventExecutor {
/**
* @param {Function} runHandler - Function that executes the event logic.
*/
constructor(runHandler) {
/**
* @type {Function}
*/
this.runHandler = runHandler;
}
/**
* Executes the event handler for a given source.
* @param {string} source - The source code or identifier for the event handler.
* @param {Event|null} [event] - The event object, if any.
* @param {object|null} [slotScope] - The slot scope context, if any.
* @returns {any} The result of the event handler execution.
*/
execute(source, event = null, slotScope = null) {
if (!this.runHandler) {
throw new TypeError('Handler is not configured or has been torn down.');
}
try {
// A DOM event is the one thing in an Avenx application that genuinely
// starts a causal chain, so it opens the outermost node. Everything the
// handler goes on to do — the action, its writes, the watchers those
// wake, the DOM patches those produce — hangs off this node.
const token = tracer.on ? tracer.enter(TraceNodeType.EVENT, buildEventNode(source, event)) : -1;
try {
// The *source* is handed on, not a compiled function.
//
// This used to build `new Function("with(state){with(methods){...}}")`
// for every handler and pass that down, which meant every inline
// handler in every application took the legacy proxy sandbox and no
// handler ever reached the AST evaluator. Two consequences, neither
// intended: an application needed 'unsafe-eval' as soon as it had a
// single @click, and the evaluator's guarantees — no reaching the
// Function constructor, no `constructor` / `__proto__` / `prototype`
// however the key is spelled — did not hold on the busiest path in the
// framework.
//
// `executeStatement` already knows how to make this decision. Given
// source it parses first and only falls back to `new Function` for
// bodies that are real statements (`if`, `for`, `return`, a
// declaration), which is what the deployment guide has always
// described. Given a function it had no choice.
return this.runHandler(source, event, slotScope);
} finally {
if (token >= 0) {
tracer.leave(token);
}
}
} catch (error) {
const compContext = event?.target?.__avenx_comp_instance?.$logContext || {};
const elTag = event?.target?.tagName || 'UNKNOWN';
const eType = event?.type || 'unknown';
const msg = formatMessage(AvenxErrorCodes.EVENT_HANDLER_ERROR, source, error);
const extendedMsg = `${msg} \n[Context] Element: <${elTag}>, Event: '${eType}'`;
logger.error(extendedMsg, compContext);
throw error;
}
}
/**
* Cleans up the run handler closure reference to prevent parent scope memory retention.
*/
teardown() {
this.runHandler = null;
}
}
|