mirror of
https://github.com/odoo/owl.git
synced 2025-10-06 19:59:41 +07:00
[FIX] qweb: protect scope in t-call writing recursively
Have something like
```xml
<div t-name="Parent">
<t t-call="nodeTemplate">
<t t-set="recursive_idx" t-value="1"/>
<t t-set="node" t-value="root"/>
</t>
</div>
<div t-name="nodeTemplate">
<t t-set="recursive_idx" t-value="recursive_idx + 1"/>
<p><t t-esc="node.val"/> <t t-esc="recursive_idx"/></p>
<t t-foreach="node.children or []" t-as="subtree">
<t t-call="nodeTemplate">
<t t-set="node" t-value="subtree"/>
</t>
</t>
</div>
```
Where we want to propagate a recursion index through recursive t-calls
Before this commit, it did not work as we protected the scope in order
to not leak, in the wronf manner. Namely the protected scope only took
firt level prototype properties of the original scope.
After this commit, this case works as we mark the scope as read only
solves #672
This commit is contained in:
@@ -196,11 +196,12 @@ export class CompilationContext {
|
||||
const protectID = this.generateID();
|
||||
this.rootContext.protectedScopeNumber++;
|
||||
this.rootContext.shouldDefineScope = true;
|
||||
const scopeExpr = codeBlock
|
||||
? `Object.create(scope);`
|
||||
: `Object.assign(Object.create(context), scope);`;
|
||||
const scopeExpr = `Object.create(scope);`;
|
||||
this.addLine(`let _origScope${protectID} = scope;`);
|
||||
this.addLine(`scope = ${scopeExpr}`);
|
||||
if (!codeBlock) {
|
||||
this.addLine(`scope.__access_mode__ = 'ro';`);
|
||||
}
|
||||
return protectID;
|
||||
}
|
||||
stopProtectScope(protectID: number) {
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ const UTILS: Utils = {
|
||||
},
|
||||
getScope(obj, property: string) {
|
||||
const obj0 = obj;
|
||||
while (obj && !obj.hasOwnProperty(property)) {
|
||||
while (obj && !obj.hasOwnProperty(property) && !(obj.hasOwnProperty('__access_mode__') && obj.__access_mode__ === 'ro')) {
|
||||
const newObj = obj.__proto__;
|
||||
if (!newObj || isComponent(newObj)) {
|
||||
return obj0;
|
||||
|
||||
@@ -1282,7 +1282,8 @@ exports[`other directives with t-component t-set can't alter in t-call body 1`]
|
||||
}
|
||||
{
|
||||
let _origScope4 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'iter').iter = 'inCall';
|
||||
|
||||
@@ -774,12 +774,14 @@ exports[`misc global 1`] = `
|
||||
}
|
||||
{
|
||||
let _origScope10 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
{
|
||||
let _origScope13 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'foo').foo = 'aaa';
|
||||
@@ -1150,7 +1152,8 @@ exports[`t-call (template calling call with several sub nodes on same line 1`] =
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
let c4 = [], p4 = {key:4};
|
||||
@@ -1198,7 +1201,8 @@ exports[`t-call (template calling cascading t-call t-raw='0' 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope12 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
let c13 = [], p13 = {key:13};
|
||||
@@ -1288,7 +1292,8 @@ exports[`t-call (template calling recursive template, part 2 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope11 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'node').node = scope['root'];
|
||||
@@ -1339,7 +1344,8 @@ exports[`t-call (template calling recursive template, part 2 2`] = `
|
||||
let key1 = i1;
|
||||
{
|
||||
let _origScope9 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'node').node = scope['subtree'];
|
||||
@@ -1365,7 +1371,8 @@ exports[`t-call (template calling recursive template, part 3 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope11 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'node').node = scope['root'];
|
||||
@@ -1416,7 +1423,93 @@ exports[`t-call (template calling recursive template, part 3 2`] = `
|
||||
let key1 = i1;
|
||||
{
|
||||
let _origScope9 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'node').node = scope['subtree'];
|
||||
scope[utils.zero] = c__0;
|
||||
}
|
||||
let k10 = \`__10__\${key0}__\${key1}__\`;
|
||||
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c2, parent: utils.getComponent(context), key: k10}));
|
||||
scope = _origScope9;
|
||||
}
|
||||
}
|
||||
scope = _origScope8;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling recursive template, part 4: with t-set recursive index 1`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"Parent\\"
|
||||
let utils = this.constructor.utils;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = [], p1 = {key:1};
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope11 = scope;
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'recursive_idx').recursive_idx = 1;
|
||||
utils.getScope(scope, 'node').node = scope['root'];
|
||||
scope[utils.zero] = c__0;
|
||||
}
|
||||
this.subTemplates['nodeTemplate'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context), key: '__12__'}));
|
||||
scope = _origScope11;
|
||||
}
|
||||
return vn1;
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`t-call (template calling recursive template, part 4: with t-set recursive index 2`] = `
|
||||
"function anonymous(context, extra
|
||||
) {
|
||||
// Template name: \\"nodeTemplate\\"
|
||||
let utils = this.constructor.utils;
|
||||
let scope = Object.create(context);
|
||||
let h = this.h;
|
||||
let c1 = extra.parentNode;
|
||||
let key0 = extra.key || \\"\\";
|
||||
let c2 = [], p2 = {key:\`\${key0}_2\`};
|
||||
let vn2 = h('div', p2, c2);
|
||||
c1.push(vn2);
|
||||
scope.recursive_idx = scope.recursive_idx+1;
|
||||
let c3 = [], p3 = {key:\`\${key0}_3\`};
|
||||
let vn3 = h('p', p3, c3);
|
||||
c2.push(vn3);
|
||||
let _4 = scope['node'].val;
|
||||
if (_4 != null) {
|
||||
c3.push({text: _4});
|
||||
}
|
||||
c3.push({text: \` \`});
|
||||
if (scope.recursive_idx != null) {
|
||||
c3.push({text: scope.recursive_idx});
|
||||
}
|
||||
let _5 = scope['node'].children||[];
|
||||
if (!_5) { throw new Error('QWeb error: Invalid loop expression')}
|
||||
let _6 = _7 = _5;
|
||||
if (!(_5 instanceof Array)) {
|
||||
_6 = Object.keys(_5);
|
||||
_7 = Object.values(_5);
|
||||
}
|
||||
let _length6 = _6.length;
|
||||
let _origScope8 = scope;
|
||||
scope = Object.create(scope);
|
||||
for (let i1 = 0; i1 < _length6; i1++) {
|
||||
scope.subtree_first = i1 === 0
|
||||
scope.subtree_last = i1 === _length6 - 1
|
||||
scope.subtree_index = i1
|
||||
scope.subtree = _6[i1]
|
||||
scope.subtree_value = _7[i1]
|
||||
let key1 = i1;
|
||||
{
|
||||
let _origScope9 = scope;
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'node').node = scope['subtree'];
|
||||
@@ -1442,7 +1535,8 @@ exports[`t-call (template calling scoped parameters 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope2 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'foo').foo = 42;
|
||||
@@ -1517,7 +1611,8 @@ exports[`t-call (template calling t-call with t-set inside and outside 1`] = `
|
||||
utils.getScope(scope, 'val').val = scope['v'].val;
|
||||
{
|
||||
let _origScope8 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'val3').val3 = scope.val*3;
|
||||
@@ -1564,7 +1659,8 @@ exports[`t-call (template calling t-call, conditional and t-set in t-call body 1
|
||||
else if (scope.v1==='elif') {
|
||||
{
|
||||
let _origScope6 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'v').v = 'success';
|
||||
@@ -1602,7 +1698,8 @@ exports[`t-call (template calling with unused body 1`] = `
|
||||
let h = this.h;
|
||||
{
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
c__0.push({text: \`WHEEE\`});
|
||||
@@ -1627,7 +1724,8 @@ exports[`t-call (template calling with unused setbody 1`] = `
|
||||
let h = this.h;
|
||||
{
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'qux').qux = 3;
|
||||
@@ -1652,7 +1750,8 @@ exports[`t-call (template calling with used body 1`] = `
|
||||
let h = this.h;
|
||||
{
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
c__0.push({text: \`ok\`});
|
||||
@@ -1678,7 +1777,8 @@ exports[`t-call (template calling with used set body 1`] = `
|
||||
let vn1 = h('span', p1, c1);
|
||||
{
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
utils.getScope(scope, 'foo').foo = 'ok';
|
||||
@@ -1839,7 +1939,8 @@ exports[`t-esc t-esc is escaped 1`] = `
|
||||
scope.var = c2
|
||||
if (scope.var != null) {
|
||||
let _origScope4 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.var = scope.var instanceof utils.VDomArray ? utils.vDomToString(scope.var) : scope.var;
|
||||
c1.push({text: scope.var});
|
||||
scope = _origScope4;
|
||||
@@ -1875,7 +1976,8 @@ exports[`t-esc t-esc=0 is escaped 1`] = `
|
||||
let vn1 = h('div', p1, c1);
|
||||
{
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
{
|
||||
let c__0 = [];
|
||||
let c4 = [], p4 = {key:4};
|
||||
@@ -2948,7 +3050,8 @@ exports[`t-set set from body literal 1`] = `
|
||||
scope.value = c1
|
||||
if (scope.value != null) {
|
||||
let _origScope2 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.value = scope.value instanceof utils.VDomArray ? utils.vDomToString(scope.value) : scope.value;
|
||||
let vn3 = {text: scope.value};
|
||||
result = vn3
|
||||
@@ -2975,7 +3078,8 @@ exports[`t-set set from body lookup 1`] = `
|
||||
scope.stuff = c2
|
||||
if (scope.stuff != null) {
|
||||
let _origScope4 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.stuff = scope.stuff instanceof utils.VDomArray ? utils.vDomToString(scope.stuff) : scope.stuff;
|
||||
c1.push({text: scope.stuff});
|
||||
scope = _origScope4;
|
||||
@@ -3123,7 +3227,8 @@ exports[`t-set t-set with content and sub t-esc 1`] = `
|
||||
scope.setvar = c2
|
||||
if (scope.setvar != null) {
|
||||
let _origScope4 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.setvar = scope.setvar instanceof utils.VDomArray ? utils.vDomToString(scope.setvar) : scope.setvar;
|
||||
c1.push({text: scope.setvar});
|
||||
scope = _origScope4;
|
||||
@@ -3215,7 +3320,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 1`] = `
|
||||
}
|
||||
if (scope.ourvar != null) {
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar;
|
||||
c1.push({text: scope.ourvar});
|
||||
scope = _origScope3;
|
||||
@@ -3243,7 +3349,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 1 2`] = `
|
||||
}
|
||||
if (scope.ourvar != null) {
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar;
|
||||
c1.push({text: scope.ourvar});
|
||||
scope = _origScope3;
|
||||
@@ -3271,7 +3378,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 1`] = `
|
||||
}
|
||||
if (scope.ourvar != null) {
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar;
|
||||
c1.push({text: scope.ourvar});
|
||||
scope = _origScope3;
|
||||
@@ -3299,7 +3407,8 @@ exports[`t-set t-set, t-if, and mix of expression/body lookup, 2 2`] = `
|
||||
}
|
||||
if (scope.ourvar != null) {
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.ourvar = scope.ourvar instanceof utils.VDomArray ? utils.vDomToString(scope.ourvar) : scope.ourvar;
|
||||
c1.push({text: scope.ourvar});
|
||||
scope = _origScope3;
|
||||
@@ -3325,7 +3434,8 @@ exports[`t-set value priority 1`] = `
|
||||
}
|
||||
if (scope.value != null) {
|
||||
let _origScope3 = scope;
|
||||
scope = Object.assign(Object.create(context), scope);
|
||||
scope = Object.create(scope);
|
||||
scope.__access_mode__ = 'ro';
|
||||
scope.value = scope.value instanceof utils.VDomArray ? utils.vDomToString(scope.value) : scope.value;
|
||||
c1.push({text: scope.value});
|
||||
scope = _origScope3;
|
||||
|
||||
@@ -944,6 +944,35 @@ describe("t-call (template calling", () => {
|
||||
expect(recursiveFn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("recursive template, part 4: with t-set recursive index", () => {
|
||||
qweb.addTemplates(`
|
||||
<templates>
|
||||
<div t-name="Parent">
|
||||
<t t-call="nodeTemplate">
|
||||
<t t-set="recursive_idx" t-value="1"/>
|
||||
<t t-set="node" t-value="root"/>
|
||||
</t>
|
||||
</div>
|
||||
<div t-name="nodeTemplate">
|
||||
<t t-set="recursive_idx" t-value="recursive_idx + 1"/>
|
||||
<p><t t-esc="node.val"/> <t t-esc="recursive_idx"/></p>
|
||||
<t t-foreach="node.children or []" t-as="subtree">
|
||||
<t t-call="nodeTemplate">
|
||||
<t t-set="node" t-value="subtree"/>
|
||||
</t>
|
||||
</t>
|
||||
</div>
|
||||
|
||||
</templates>
|
||||
`);
|
||||
const root = { val: "a", children: [{ val: "b", children: [{ val: "c" , children: [{val: "d"}] }] }] };
|
||||
const expected =
|
||||
"<div><div><p>a 2</p><div><p>b 3</p><div><p>c 4</p><div><p>d 5</p></div></div></div></div></div>";
|
||||
expect(renderToString(qweb, "Parent", { root })).toBe(expected);
|
||||
const recursiveFn = Object.values(qweb.subTemplates)[0] as any;
|
||||
expect(recursiveFn.toString()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test("t-call, global templates", () => {
|
||||
QWeb.registerTemplate("abcd", '<div><t t-call="john"/></div>');
|
||||
qweb.addTemplate("john", `<span>desk</span>`);
|
||||
|
||||
Reference in New Issue
Block a user