diff --git a/src/qweb/base_directives.ts b/src/qweb/base_directives.ts index a91fa1e4..c13f75b5 100644 --- a/src/qweb/base_directives.ts +++ b/src/qweb/base_directives.ts @@ -128,7 +128,12 @@ QWeb.addDirective({ qwebvar.expr = `scope.${variable}`; if (value) { const formattedValue = ctx.formatExpression(value); - ctx.addLine(`${qwebvar.expr} = ${formattedValue};`); + let scopeExpr = `scope`; + if (ctx.protectedScopeNumber) { + ctx.rootContext.shouldDefineUtils = true; + scopeExpr = `utils.getScope(scope, '${variable}')`; + } + ctx.addLine(`${scopeExpr}.${variable} = ${formattedValue};`); qwebvar.value = formattedValue; } @@ -313,7 +318,7 @@ QWeb.addDirective({ ctx.addLine(`_${valuesID} = Object.values(_${arrayID});`); ctx.closeIf(); ctx.addLine(`let _length${keysID} = _${keysID}.length;`); - let varsID = ctx.startProtectScope(); + let varsID = ctx.startProtectScope(true); const loopVar = `i${ctx.loopNumber}`; ctx.addLine(`for (let ${loopVar} = 0; ${loopVar} < _length${keysID}; ${loopVar}++) {`); ctx.indent(); diff --git a/src/qweb/compilation_context.ts b/src/qweb/compilation_context.ts index 0abceb5e..625a3f71 100644 --- a/src/qweb/compilation_context.ts +++ b/src/qweb/compilation_context.ts @@ -17,6 +17,7 @@ export class CompilationContext { rootContext: CompilationContext; shouldDefineParent: boolean = false; shouldDefineScope: boolean = false; + protectedScopeNumber: number = 0; shouldDefineQWeb: boolean = false; shouldDefineUtils: boolean = false; shouldDefineRefs: boolean = false; @@ -173,14 +174,17 @@ export class CompilationContext { let r = s.replace(/\{\{.*?\}\}/g, s => "${" + this.formatExpression(s.slice(2, -2)) + "}"); return "`" + r + "`"; } - startProtectScope(): number { + startProtectScope(codeBlock?: boolean): number { const protectID = this.generateID(); + this.rootContext.protectedScopeNumber++; this.rootContext.shouldDefineScope = true; + const scopeExpr = codeBlock ? `Object.create(scope);` : `Object.assign(Object.create(context), scope);`; this.addLine(`let _origScope${protectID} = scope;`); - this.addLine(`scope = Object.assign(Object.create(context), scope);`); + this.addLine(`scope = ${scopeExpr}`); return protectID; } stopProtectScope(protectID: number) { + this.rootContext.protectedScopeNumber--; this.addLine(`scope = _origScope${protectID};`); } -} +} \ No newline at end of file diff --git a/src/qweb/qweb.ts b/src/qweb/qweb.ts index f95d1c58..ec32ef81 100644 --- a/src/qweb/qweb.ts +++ b/src/qweb/qweb.ts @@ -86,6 +86,10 @@ interface Utils { [key: string]: any; } +function isComponent(obj) { + return obj && obj.hasOwnProperty("__owl__"); +} + const UTILS: Utils = { zero: Symbol("zero"), toObj(expr) { @@ -122,10 +126,21 @@ const UTILS: Utils = { .join(""); }, getComponent(obj) { - while (obj && !obj.hasOwnProperty("__owl__")) { + while (obj && !isComponent(obj)) { obj = obj.__proto__; } return obj; + }, + getScope(obj, property: string) { + const obj0 = obj; + while (obj && !obj.hasOwnProperty(property)) { + const newObj = obj.__proto__; + if (!newObj || isComponent(newObj)) { + return obj0; + } + obj = newObj; + } + return obj; } }; diff --git a/tests/component/__snapshots__/component.test.ts.snap b/tests/component/__snapshots__/component.test.ts.snap index be30c7c3..d78d0b70 100644 --- a/tests/component/__snapshots__/component.test.ts.snap +++ b/tests/component/__snapshots__/component.test.ts.snap @@ -58,7 +58,7 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for } let _length3 = _3.length; let _origScope5 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); for (let i1 = 0; i1 < _length3; i1++) { scope.section_first = i1 === 0 scope.section_last = i1 === _length3 - 1 @@ -75,7 +75,7 @@ exports[`basic widget properties reconciliation alg works for t-foreach in t-for } let _length7 = _7.length; let _origScope9 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); for (let i2 = 0; i2 < _length7; i2++) { scope.blip_first = i2 === 0 scope.blip_last = i2 === _length7 - 1 @@ -432,7 +432,7 @@ exports[`composition sub components with some state rendered in a loop 1`] = ` } let _length3 = _3.length; let _origScope5 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); for (let i1 = 0; i1 < _length3; i1++) { scope.number_first = i1 === 0 scope.number_last = i1 === _length3 - 1 @@ -587,6 +587,58 @@ exports[`dynamic t-props basic use 1`] = ` }" `; +exports[`other directives with t-component slot setted value (with t-set) not accessible with t-esc 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"__template__2\\" + let utils = this.constructor.utils; + let QWeb = this.constructor; + let parent = context; + let scope = Object.create(context); + let h = this.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('div', p1, c1); + scope.iter = 'source'; + let c2 = [], p2 = {key:2}; + let vn2 = h('p', p2, c2); + c1.push(vn2); + if (scope.iter != null) { + c2.push({text: scope.iter}); + } + // Component 'ChildWidget' + let w3 = '__4__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__4__']] : false; + let props3 = {}; + if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) { + w3.destroy(); + w3 = false; + } + if (w3) { + w3.__updateProps(props3, extra.fiber, Object.assign(Object.create(context), scope)); + let pvnode = w3.__owl__.pvnode; + c1.push(pvnode); + } else { + let componentKey3 = \`ChildWidget\`; + let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['ChildWidget']; + if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')} + w3 = new W3(parent, props3); + parent.__owl__.cmap['__4__'] = w3.__owl__.id; + w3.__owl__.slotId = 1; + let fiber = w3.__prepare(extra.fiber, Object.assign(Object.create(context), scope), () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}}); + c1.push(pvnode); + w3.__owl__.pvnode = pvnode; + } + w3.__owl__.parentLastFiberId = extra.fiber.id; + let c5 = [], p5 = {key:5}; + let vn5 = h('p', p5, c5); + c1.push(vn5); + if (scope.iter != null) { + c5.push({text: scope.iter}); + } + return vn1; +}" +`; + exports[`other directives with t-component t-on with getter as handler 1`] = ` "function anonymous(context, extra ) { @@ -1017,6 +1069,195 @@ exports[`other directives with t-component t-on with stop and/or prevent modifie }" `; +exports[`other directives with t-component t-set can't alter component 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"__template__1\\" + let scope = Object.create(context); + let h = this.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('div', p1, c1); + let c2 = [], p2 = {key:2}; + let vn2 = h('p', p2, c2); + c1.push(vn2); + let _3 = scope['iter']; + if (_3 != null) { + c2.push({text: _3}); + } + scope.iter = 5; + let c4 = [], p4 = {key:4}; + let vn4 = h('p', p4, c4); + c1.push(vn4); + if (scope.iter != null) { + c4.push({text: scope.iter}); + } + return vn1; +}" +`; + +exports[`other directives with t-component t-set can't alter from within callee 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"__template__1\\" + 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); + scope.iter = 'source'; + let c2 = [], p2 = {key:2}; + let vn2 = h('p', p2, c2); + c1.push(vn2); + if (scope.iter != null) { + c2.push({text: scope.iter}); + } + this.subTemplates['ChildWidget'].call(this, Object.assign(Object.create(context), scope), Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); + let c4 = [], p4 = {key:4}; + let vn4 = h('p', p4, c4); + c1.push(vn4); + if (scope.iter != null) { + c4.push({text: scope.iter}); + } + return vn1; +}" +`; + +exports[`other directives with t-component t-set can't alter in t-call body 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"__template__1\\" + 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); + scope.iter = 'source'; + let c2 = [], p2 = {key:2}; + let vn2 = h('p', p2, c2); + c1.push(vn2); + if (scope.iter != null) { + c2.push({text: scope.iter}); + } + { + let _origScope4 = scope; + scope = Object.assign(Object.create(context), scope); + { + let c__0 = []; + utils.getScope(scope, 'iter').iter = 'inCall'; + scope[utils.zero] = c__0; + } + this.subTemplates['ChildWidget'].call(this, scope, Object.assign({}, extra, {parentNode: c1, parent: utils.getComponent(context)})); + scope = _origScope4; + } + let c5 = [], p5 = {key:5}; + let vn5 = h('p', p5, c5); + c1.push(vn5); + if (scope.iter != null) { + c5.push({text: scope.iter}); + } + return vn1; +}" +`; + +exports[`other directives with t-component t-set not altered by child widget 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"__template__2\\" + let utils = this.constructor.utils; + let QWeb = this.constructor; + let parent = context; + let scope = Object.create(context); + let h = this.h; + let c1 = [], p1 = {key:1}; + let vn1 = h('div', p1, c1); + scope.iter = 'source'; + let c2 = [], p2 = {key:2}; + let vn2 = h('p', p2, c2); + c1.push(vn2); + if (scope.iter != null) { + c2.push({text: scope.iter}); + } + // Component 'ChildWidget' + let w3 = '__4__' in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap['__4__']] : false; + let props3 = {}; + if (w3 && w3.__owl__.currentFiber && !w3.__owl__.vnode) { + w3.destroy(); + w3 = false; + } + if (w3) { + w3.__updateProps(props3, extra.fiber, undefined); + let pvnode = w3.__owl__.pvnode; + c1.push(pvnode); + } else { + let componentKey3 = \`ChildWidget\`; + let W3 = context.constructor.components[componentKey3] || QWeb.components[componentKey3]|| scope['ChildWidget']; + if (!W3) {throw new Error('Cannot find the definition of component \\"' + componentKey3 + '\\"')} + w3 = new W3(parent, props3); + parent.__owl__.cmap['__4__'] = w3.__owl__.id; + let fiber = w3.__prepare(extra.fiber, undefined, () => { const vnode = fiber.vnode; pvnode.sel = vnode.sel; }); + let pvnode = h('dummy', {key: '__4__', hook: {remove() {},destroy(vn) {w3.destroy();}}}); + c1.push(pvnode); + w3.__owl__.pvnode = pvnode; + } + w3.__owl__.parentLastFiberId = extra.fiber.id; + let c5 = [], p5 = {key:5}; + let vn5 = h('p', p5, c5); + c1.push(vn5); + if (scope.iter != null) { + c5.push({text: scope.iter}); + } + return vn1; +}" +`; + +exports[`other directives with t-component t-set outside modified in t-foreach 1`] = ` +"function anonymous(context, extra +) { + // Template name: \\"__template__1\\" + 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); + scope.iter = 0; + let _2 = scope['state'].values; + if (!_2) { throw new Error('QWeb error: Invalid loop expression')} + let _3 = _4 = _2; + if (!(_2 instanceof Array)) { + _3 = Object.keys(_2); + _4 = Object.values(_2); + } + let _length3 = _3.length; + let _origScope5 = scope; + scope = Object.create(scope); + for (let i1 = 0; i1 < _length3; i1++) { + scope.val_first = i1 === 0 + scope.val_last = i1 === _length3 - 1 + scope.val_index = i1 + scope.val = _3[i1] + scope.val_value = _4[i1] + let key1 = scope['val']; + let c6 = [], p6 = {key:\`\${key1}_6\`}; + let vn6 = h('p', p6, c6); + c1.push(vn6); + c6.push({text: \`InLoop: \`}); + if (scope.iter != null) { + c6.push({text: scope.iter}); + } + utils.getScope(scope, 'iter').iter = scope.iter+1; + } + scope = _origScope5; + let c7 = [], p7 = {key:7}; + let vn7 = h('p', p7, c7); + c1.push(vn7); + c7.push({text: \`EndLoop: \`}); + if (scope.iter != null) { + c7.push({text: scope.iter}); + } + return vn1; +}" +`; + exports[`random stuff/miscellaneous can inject values in tagged templates 1`] = ` "function anonymous(context, extra ) { @@ -1093,7 +1334,7 @@ exports[`random stuff/miscellaneous t-on with handler bound to dynamic argument } let _length3 = _3.length; let _origScope5 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); for (let i1 = 0; i1 < _length3; i1++) { scope.item_first = i1 === 0 scope.item_last = i1 === _length3 - 1 @@ -1342,7 +1583,7 @@ exports[`t-model directive in a t-foreach 1`] = ` } let _length3 = _3.length; let _origScope5 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); for (let i1 = 0; i1 < _length3; i1++) { scope.thing_first = i1 === 0 scope.thing_last = i1 === _length3 - 1 diff --git a/tests/component/__snapshots__/slots.test.ts.snap b/tests/component/__snapshots__/slots.test.ts.snap index fe1edc2c..e020d99b 100644 --- a/tests/component/__snapshots__/slots.test.ts.snap +++ b/tests/component/__snapshots__/slots.test.ts.snap @@ -263,7 +263,7 @@ exports[`t-slot directive slots are rendered with proper context, part 2 2`] = ` } let _length4 = _4.length; let _origScope6 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); for (let i1 = 0; i1 < _length4; i1++) { scope.user_first = i1 === 0 scope.user_last = i1 === _length4 - 1 @@ -360,7 +360,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = ` } let _length4 = _4.length; let _origScope6 = scope; - scope = Object.assign(Object.create(context), scope); + scope = Object.create(scope); for (let i1 = 0; i1 < _length4; i1++) { scope.user_first = i1 === 0 scope.user_last = i1 === _length4 - 1 @@ -371,7 +371,7 @@ exports[`t-slot directive slots are rendered with proper context, part 3 2`] = ` let c7 = [], p7 = {key:\`\${key1}_7\`}; let vn7 = h('li', p7, c7); c2.push(vn7); - scope.userdescr = 'User '+scope['user'].name; + utils.getScope(scope, 'userdescr').userdescr = 'User '+scope['user'].name; // Component 'Link' let k9 = \`__9__\${key1}__\`; let w8 = k9 in parent.__owl__.cmap ? parent.__owl__.children[parent.__owl__.cmap[k9]] : false; diff --git a/tests/component/component.test.ts b/tests/component/component.test.ts index 52ca2a67..c7d2b960 100644 --- a/tests/component/component.test.ts +++ b/tests/component/component.test.ts @@ -2670,6 +2670,238 @@ describe("other directives with t-component", () => { await nextTick(); // wait for changes triggered in mounted to be applied expect(fixture.innerHTML).toBe("
InLoop:
EndLoop:
InLoop: 0
InLoop: 1
EndLoop: 2
2
3
4
2
3
4
1
5
source
source
source
source
source
source
source
source
InLoop:
EndLoop:
InLoop: 0
InLoop: 1
EndLoop: 0
InLoop:
EndLoop:
InLoop: 0
InLoop: 1
EndLoop: 1