[IMP] qweb: turn handlers into function expressions only

For the sake of consistency with vanilla JS, and to allow some things
that were previously not possible.
This commit is contained in:
Samuel Degueldre
2021-11-12 12:56:07 +01:00
committed by Aaron Bohy
parent 756d32daa0
commit bca6afeb90
33 changed files with 280 additions and 227 deletions
+1 -1
View File
@@ -93,7 +93,7 @@ export class ComponentNode<T extends typeof Component = any> implements VNode<Co
this.level = parent ? parent.level + 1 : 0;
applyDefaultProps(props, C);
this.component = new C(props, app.env, this) as any;
this.renderFn = app.getTemplate(C.template).bind(null, this.component, this);
this.renderFn = app.getTemplate(C.template).bind(this.component, this.component, this);
if (C.style) {
applyStyles(C);
}
+5 -6
View File
@@ -26,12 +26,11 @@ export const mainEventHandler = (data: any, ev: Event, currentTarget?: EventTarg
}
}
}
if (typeof data[0] === "function") {
data[0](ev);
} else if (data[0].__owl__) {
const method = data[1];
const args = data[2] || [];
data[0].__owl__.component[method](...args, ev);
// If handler is empty, the array slot 0 will also be empty, and data will not have the property 0
// We check this rather than data[0] being truthy (or typeof function) so that it crashes
// as expected when there is a handler expression that evaluates to a falsy value
if (Object.hasOwnProperty.call(data, 0)) {
data[0].call(data[1] ? data[1].__owl__.component : null, ev);
}
return stopped;
};
+6 -24
View File
@@ -118,7 +118,6 @@ class BlockDescription {
// -----------------------------------------------------------------------------
// Compiler code
// -----------------------------------------------------------------------------
const FNAMEREGEXP = /^[$A-Z_][0-9A-Z_$]*$/i;
interface Context {
block: BlockDescription | null;
@@ -372,7 +371,7 @@ export class QWebCompiler {
const mapping = new Map<string, string>();
return tokens
.map((tok) => {
if (tok.varName) {
if (tok.varName && !tok.isLocal) {
if (!mapping.has(tok.varName)) {
const varId = this.generateId("v");
mapping.set(tok.varName, varId);
@@ -490,11 +489,6 @@ export class QWebCompiler {
}
generateHandlerCode(rawEvent: string, handler: string): string {
let args: string = "";
const name: string = handler.replace(/\(.*\)/, function (_args) {
args = _args.slice(1, -1);
return "";
});
const modifiers = rawEvent
.split(".")
.slice(1)
@@ -503,22 +497,7 @@ export class QWebCompiler {
if (modifiers.length) {
modifiersCode = `${modifiers.join(",")}, `;
}
const isMethodCall = name.match(FNAMEREGEXP);
if (isMethodCall) {
let handlerFn: string;
if (args) {
const argId = this.generateId("arg");
this.addLine(`const ${argId} = [${compileExpr(args)}];`);
handlerFn = `'${name}', ${argId}`;
} else {
handlerFn = `'${name}'`;
}
return `[${modifiersCode}ctx, ${handlerFn!}]`;
} else {
let code = this.captureExpression(handler);
code = `{const res = (() => { return ${code} })(); if (typeof res === 'function') { res(e) }}`;
return `[${modifiersCode}(e) => ${code}]`;
}
return `[${modifiersCode}${this.captureExpression(handler)}, ctx]`;
}
compileTDomNode(ast: ASTDomNode, ctx: Context) {
@@ -919,7 +898,10 @@ export class QWebCompiler {
const id = this.generateId(`callTemplate_`);
this.staticCalls.push({ id, template: subTemplate });
block = this.createBlock(block, "multi", ctx);
this.insertBlock(`${id}(ctx, node, ${key})`, block!, { ...ctx, forceNewBlock: !block });
this.insertBlock(`${id}.call(this, ctx, node, ${key})`, block!, {
...ctx,
forceNewBlock: !block,
});
}
if (ast.body && !ctx.isLast) {
this.addLine(`ctx = ctx.__proto__;`);
+8
View File
@@ -70,6 +70,7 @@ interface Token {
size?: number;
varName?: string;
replace?: Function;
isLocal?: boolean;
}
const STATIC_TOKEN_MAP: { [key: string]: TKind } = Object.assign(Object.create(null), {
@@ -325,6 +326,13 @@ export function compileExprToArray(expr: string): Token[] {
}
i++;
}
// Mark all variables that have been used locally.
// This assumes the expression has only one scope (incorrect but "good enough for now")
for (const token of tokens) {
if (token.type === "SYMBOL" && localVars.has(token.value)) {
token.isLocal = true;
}
}
return tokens;
}