From 03415f08f476009c7bbbe72299d117aca85e95c6 Mon Sep 17 00:00:00 2001 From: Julien Mougenot Date: Mon, 27 Apr 2020 14:31:04 +0000 Subject: [PATCH 1/2] [IMP] web: update Owl to v1.0.7 --- addons/web/static/lib/owl/owl.js | 86 ++++++++++++++++++++++++-------- 1 file changed, 65 insertions(+), 21 deletions(-) diff --git a/addons/web/static/lib/owl/owl.js b/addons/web/static/lib/owl/owl.js index 9ca5b3739d5..b2987372701 100644 --- a/addons/web/static/lib/owl/owl.js +++ b/addons/web/static/lib/owl/owl.js @@ -1222,11 +1222,12 @@ 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) { @@ -1236,6 +1237,18 @@ } CompilationContext.nextID = 1; + const browser = { + setTimeout: window.setTimeout.bind(window), + clearTimeout: window.clearTimeout.bind(window), + setInterval: window.setInterval.bind(window), + clearInterval: window.clearInterval.bind(window), + requestAnimationFrame: window.requestAnimationFrame.bind(window), + random: Math.random, + Date: window.Date, + fetch: (window.fetch || (() => { })).bind(window), + localStorage: window.localStorage + }; + /** * Owl Utils * @@ -1279,7 +1292,7 @@ return promise; } async function loadFile(url) { - const result = await fetch(url); + const result = await browser.fetch(url); if (!result.ok) { throw new Error("Error while fetching xml templates"); } @@ -1316,8 +1329,8 @@ } } const callNow = immediate && !timeout; - clearTimeout(timeout); - timeout = setTimeout(later, wait); + browser.clearTimeout(timeout); + timeout = browser.setTimeout(later, wait); if (callNow) { func.apply(context, args); } @@ -1403,7 +1416,9 @@ }, getScope(obj, property) { 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; @@ -3090,15 +3105,34 @@ ctx.addLine(`parent.__owl__.cmap[${templateKey}] = w${componentID}.__owl__.id;`); if (hasSlots) { const clone = node.cloneNode(true); - const slotNodes = clone.querySelectorAll("[t-set]"); + const slotNodes = Array.from(clone.querySelectorAll("[t-set-slot]")); + // The next code is a fallback for compatibility reason. It accepts t-set + // elements that are direct children with a non empty body as nodes defining + // the content of a slot. + // + // This is wrong, but is necessary to prevent breaking all existing Owl + // code using slots. This will be removed in v2.0 someday. Meanwhile, + // please use t-set-slot everywhere you need to set the content of a + // slot. + for (let el of clone.children) { + if (el.getAttribute("t-set") && el.hasChildNodes()) { + slotNodes.push(el); + } + } const slotId = QWeb.nextSlotId++; ctx.addLine(`w${componentID}.__owl__.slotId = ${slotId};`); if (slotNodes.length) { for (let i = 0, length = slotNodes.length; i < length; i++) { const slotNode = slotNodes[i]; slotNode.parentElement.removeChild(slotNode); - const key = slotNode.getAttribute("t-set"); - slotNode.removeAttribute("t-set"); + let key = slotNode.getAttribute("t-set-slot"); + slotNode.removeAttribute("t-set-slot"); + // here again, this code should be removed when we stop supporting + // using t-set to define the content of named slots. + if (!key) { + key = slotNode.getAttribute("t-set"); + slotNode.removeAttribute("t-set"); + } const slotFn = qweb._compile(`slot_${key}_template`, slotNode, ctx); QWeb.slots[`${slotId}_${key}`] = slotFn; } @@ -3217,8 +3251,7 @@ }); } } - const raf = window.requestAnimationFrame.bind(window); - const scheduler = new Scheduler(raf); + const scheduler = new Scheduler(browser.requestAnimationFrame); /** * Owl Fiber Class @@ -3403,6 +3436,12 @@ if (target.tagName.toLowerCase() !== fiber.vnode.sel) { throw new Error(`Cannot attach '${component.constructor.name}' to target node (not same tag name)`); } + // In self mode, we *know* we are to take possession of the target + // Hence we manually create the corresponding VNode and copy the "key" in data + const selfVnodeData = fiber.vnode.data ? { key: fiber.vnode.data.key } : {}; + const selfVnode = h(fiber.vnode.sel, selfVnodeData); + selfVnode.elm = target; + target = selfVnode; } else { target = component.__owl__.vnode || document.createElement(fiber.vnode.sel); @@ -3719,6 +3758,9 @@ if (!this.env.qweb) { this.env.qweb = new QWeb(); } + if (!this.env.browser) { + this.env.browser = browser; + } this.env.qweb.on("update", this, () => { if (this.__owl__.isMounted) { this.render(true); @@ -3908,14 +3950,15 @@ */ async render(force = false) { const __owl__ = this.__owl__; - if (!__owl__.isMounted && !__owl__.currentFiber) { + const currentFiber = __owl__.currentFiber; + if (!__owl__.isMounted && !currentFiber) { // if we get here, this means that the component was either never mounted, // or was unmounted and some state change triggered a render. Either way, // we do not want to actually render anything in this case. return; } - if (__owl__.currentFiber && !__owl__.currentFiber.isRendered) { - return scheduler.addFiber(__owl__.currentFiber.root); + if (currentFiber && !currentFiber.isRendered && !currentFiber.isCompleted) { + return scheduler.addFiber(currentFiber.root); } // if we aren't mounted at this point, it implies that there is a // currentFiber that is already rendered (isRendered is true), so we are @@ -4143,9 +4186,10 @@ // here, the component and none of its superclasses defines a static `template` // key. So we fall back on looking for a template matching its name (or // one of its subclass). - let template; - while ((template = p.name) && !(template in qweb.templates) && p !== Component) { + let template = p.name; + while (!(template in qweb.templates) && p !== Component) { p = p.__proto__; + template = p.name; } if (p === Component) { throw new Error(`Could not find template for component "${this.constructor.name}"`); @@ -4452,7 +4496,7 @@ if (component.__owl__[method]) { const current = component.__owl__[method]; component.__owl__[method] = function (...args) { - return Promise.all[(current.call(component, ...args), cb.call(component, ...args))]; + return Promise.all([current.call(component, ...args), cb.call(component, ...args)]); }; } else { @@ -5134,9 +5178,9 @@ exports.useState = useState$1; exports.utils = utils; - exports.__info__.version = '1.0.5'; - exports.__info__.date = '2020-02-21T08:48:04.397Z'; - exports.__info__.hash = 'fd6327b'; + exports.__info__.version = '1.0.7'; + exports.__info__.date = '2020-04-17T13:53:00.270Z'; + exports.__info__.hash = '23ce19e'; exports.__info__.url = 'https://github.com/odoo/owl'; }(this.owl = this.owl || {})); From 6def725c6ef4faee72f7000e7e22f1b495ea0b52 Mon Sep 17 00:00:00 2001 From: "Lucas Perais (lpe)" Date: Thu, 2 Apr 2020 13:25:54 +0000 Subject: [PATCH 2/2] [FIX] web: Owl compatibility: remount on same node Keep a reference to the 'mount' method arguments to ensure that components will be mounted on the same node and in the same way they were originally mounted. This fix is related to the following changes in the Owl framework: https://github.com/odoo/owl/commit/b4ad14edc03d4fb2d7a4622c6c1f63f6df5b4541 Co-authored-by: Julien Mougenot --- addons/web/static/src/js/owl_compatibility.js | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/addons/web/static/src/js/owl_compatibility.js b/addons/web/static/src/js/owl_compatibility.js index 01deee7a4a3..45f387c3e41 100644 --- a/addons/web/static/src/js/owl_compatibility.js +++ b/addons/web/static/src/js/owl_compatibility.js @@ -445,11 +445,9 @@ odoo.define('web.OwlCompatibility', function () { // we may not be in the DOM, but actually want to be redrawn // (e.g. we were detached from the DOM, and now we're going to // be re-attached, but we need to be reloaded first). In this - // case, we have to fool Owl as it would skip the rendering if - // we simply call render. - const tmpEl = document.createElement('div'); - this.el.parentElement.replaceChild(tmpEl, this.el); - prom = this.mount(tmpEl, { position: 'self' }); + // case, we have to call 'mount' as Owl would skip the rendering + // if we simply call render. + prom = this.mount(...this._mountArgs); } return prom; } @@ -494,6 +492,22 @@ odoo.define('web.OwlCompatibility', function () { } parentChildren.push(this); } + /** + * Stores mount target and position at first mount. That way, when updating + * while out of DOM, we know where and how to remount. + * @see update() + * @override + */ + async mount(target, options) { + if (options && options.position === 'self') { + throw new Error( + 'Unsupported position: "self" is not allowed for wrapper components. ' + + 'Contact the JS Framework team or open an issue if your use case is relevant.' + ); + } + this._mountArgs = arguments; + return super.mount(...arguments); + } } ComponentWrapper.template = xml``;