From 649c037ab0341ef4fbe54c6a626bc3f7cc19b11d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?G=C3=A9ry=20Debongnie?= Date: Wed, 23 Feb 2022 07:57:43 +0000 Subject: [PATCH] [IMP] web: update owl to last version closes odoo/odoo#85197 Signed-off-by: Aaron Bohy (aab) --- addons/web/static/lib/owl/owl.js | 134 ++++++++++++------ .../static/src/core/errors/error_service.js | 16 ++- .../static/src/legacy/js/promise_extension.js | 3 +- addons/web/static/src/legacy/utils.js | 3 +- .../tests/legacy/owl_compatibility_tests.js | 5 +- addons/web/static/tests/qunit.js | 7 +- 6 files changed, 115 insertions(+), 53 deletions(-) diff --git a/addons/web/static/lib/owl/owl.js b/addons/web/static/lib/owl/owl.js index 87ee11a2658..e75015bef58 100644 --- a/addons/web/static/lib/owl/owl.js +++ b/addons/web/static/lib/owl/owl.js @@ -212,7 +212,7 @@ } function makePropSetter(name) { return function setProp(value) { - this[name] = value; + this[name] = value || ""; }; } function isProp(tag, key) { @@ -1778,8 +1778,7 @@ function makeChildFiber(node, parent) { let current = node.fiber; if (current) { - let root = parent.root; - cancelFibers(root, current.children); + cancelFibers(current.children); current.root = null; } return new Fiber(node, parent); @@ -1788,7 +1787,7 @@ let current = node.fiber; if (current) { let root = current.root; - root.counter -= cancelFibers(root, current.children); + root.counter -= cancelFibers(current.children); current.children = []; root.counter++; current.bdom = null; @@ -1811,15 +1810,14 @@ /** * @returns number of not-yet rendered fibers cancelled */ - function cancelFibers(root, fibers) { + function cancelFibers(fibers) { let result = 0; for (let fiber of fibers) { fiber.node.fiber = null; - fiber.root = root; if (!fiber.bdom) { result++; } - result += cancelFibers(root, fiber.children); + result += cancelFibers(fiber.children); } return result; } @@ -2206,53 +2204,86 @@ } } + function wrapError(fn, hookName) { + const error = new Error(`The following error occurred in ${hookName}: `); + return (...args) => { + try { + const result = fn(...args); + if (result instanceof Promise) { + return result.catch((cause) => { + error.cause = cause; + if (cause instanceof Error) { + error.message += `"${cause.message}"`; + } + throw error; + }); + } + return result; + } + catch (cause) { + if (cause instanceof Error) { + error.message += `"${cause.message}"`; + } + throw error; + } + }; + } // ----------------------------------------------------------------------------- // hooks // ----------------------------------------------------------------------------- function onWillStart(fn) { const node = getCurrent(); - node.willStart.push(fn.bind(node.component)); + const decorate = node.app.dev ? wrapError : (fn) => fn; + node.willStart.push(decorate(fn.bind(node.component), "onWillStart")); } function onWillUpdateProps(fn) { const node = getCurrent(); - node.willUpdateProps.push(fn.bind(node.component)); + const decorate = node.app.dev ? wrapError : (fn) => fn; + node.willUpdateProps.push(decorate(fn.bind(node.component), "onWillUpdateProps")); } function onMounted(fn) { const node = getCurrent(); - node.mounted.push(fn.bind(node.component)); + const decorate = node.app.dev ? wrapError : (fn) => fn; + node.mounted.push(decorate(fn.bind(node.component), "onMounted")); } function onWillPatch(fn) { const node = getCurrent(); - node.willPatch.unshift(fn.bind(node.component)); + const decorate = node.app.dev ? wrapError : (fn) => fn; + node.willPatch.unshift(decorate(fn.bind(node.component), "onWillPatch")); } function onPatched(fn) { const node = getCurrent(); - node.patched.push(fn.bind(node.component)); + const decorate = node.app.dev ? wrapError : (fn) => fn; + node.patched.push(decorate(fn.bind(node.component), "onPatched")); } function onWillUnmount(fn) { const node = getCurrent(); - node.willUnmount.unshift(fn.bind(node.component)); + const decorate = node.app.dev ? wrapError : (fn) => fn; + node.willUnmount.unshift(decorate(fn.bind(node.component), "onWillUnmount")); } function onWillDestroy(fn) { const node = getCurrent(); - node.willDestroy.push(fn.bind(node.component)); + const decorate = node.app.dev ? wrapError : (fn) => fn; + node.willDestroy.push(decorate(fn.bind(node.component), "onWillDestroy")); } function onWillRender(fn) { const node = getCurrent(); const renderFn = node.renderFn; - node.renderFn = () => { + const decorate = node.app.dev ? wrapError : (fn) => fn; + node.renderFn = decorate(() => { fn.call(node.component); return renderFn(); - }; + }, "onWillRender"); } function onRendered(fn) { const node = getCurrent(); const renderFn = node.renderFn; - node.renderFn = () => { + const decorate = node.app.dev ? wrapError : (fn) => fn; + node.renderFn = decorate(() => { const result = renderFn(); fn.call(node.component); return result; - }; + }, "onRendered"); } function onError(callback) { const node = getCurrent(); @@ -2859,16 +2890,16 @@ const mapping = new Map(); return tokens .map((tok) => { - if (tok.varName && !tok.isLocal) { - if (!mapping.has(tok.varName)) { - const varId = this.generateId("v"); - mapping.set(tok.varName, varId); - this.addLine(`const ${varId} = ${tok.value};`); - } - tok.value = mapping.get(tok.varName); + if (tok.varName && !tok.isLocal) { + if (!mapping.has(tok.varName)) { + const varId = this.generateId("v"); + mapping.set(tok.varName, varId); + this.addLine(`const ${varId} = ${tok.value};`); } - return tok.value; - }) + tok.value = mapping.get(tok.varName); + } + return tok.value; + }) .join(""); } compileAST(ast, ctx) { @@ -2979,11 +3010,11 @@ .split(".") .slice(1) .map((m) => { - if (!MODS.has(m)) { - throw new Error(`Unknown event modifier: '${m}'`); - } - return `"${m}"`; - }); + if (!MODS.has(m)) { + throw new Error(`Unknown event modifier: '${m}'`); + } + return `"${m}"`; + }); let modifiersCode = ""; if (modifiers.length) { modifiersCode = `${modifiers.join(",")}, `; @@ -3768,6 +3799,9 @@ if (tagName === "t" && !dynamicTag) { return null; } + if (tagName.startsWith("block-")) { + throw new Error(`Invalid tag name: '${tagName}'`); + } ctx = Object.assign({}, ctx); if (tagName === "pre") { ctx.inPreTag = true; @@ -3833,6 +3867,9 @@ ctx.tModelInfo = model; } } + else if (attr.startsWith("block-")) { + throw new Error(`Invalid attribute: '${attr}'`); + } else if (attr !== "t-name") { if (attr.startsWith("t-") && !attr.startsWith("t-att")) { throw new Error(`Unknown QWeb directive: '${attr}'`); @@ -4357,7 +4394,7 @@ if (line[columnIndex]) { msg += `\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` + - `${line}\n${"-".repeat(columnIndex - 1)}^`; + `${line}\n${"-".repeat(columnIndex - 1)}^`; } } } @@ -4402,7 +4439,7 @@ if (line[columnIndex]) { msg += `\nThe error might be located at xml line ${lineNumber} column ${columnIndex}\n` + - `${line}\n${"-".repeat(columnIndex - 1)}^`; + `${line}\n${"-".repeat(columnIndex - 1)}^`; } } } @@ -4450,7 +4487,13 @@ if (!(name in this.templates)) { const rawTemplate = this.rawTemplates[name]; if (rawTemplate === undefined) { - throw new Error(`Missing template: "${name}"`); + let extraInfo = ""; + try { + const componentName = getCurrent().component.constructor.name; + extraInfo = ` (for component "${componentName}")`; + } + catch { } + throw new Error(`Missing template: "${name}"${extraInfo}`); } const templateFn = this._compileTemplate(name, rawTemplate); // first add a function to lazily get the template, in case there is a @@ -4553,7 +4596,7 @@ }); } } - Portal.template = xml``; + Portal.template = xml ``; Portal.props = { target: { type: String, @@ -4626,10 +4669,13 @@ // interactions with other code, such as test frameworks that override them Scheduler.requestAnimationFrame = window.requestAnimationFrame.bind(window); - const DEV_MSG = `Owl is running in 'dev' mode. + const DEV_MSG = () => { + const hash = window.owl ? window.owl.__info__.hash : "master"; + return `Owl is running in 'dev' mode. This is not suitable for production use. -See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for more information.`; +See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration for more information.`; + }; class App extends TemplateSet { constructor(Root, config = {}) { super(config); @@ -4640,7 +4686,7 @@ See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for mor this.dev = true; } if (this.dev && !config.test) { - console.info(DEV_MSG); + console.info(DEV_MSG()); } const descrs = Object.getOwnPropertyDescriptors(config.env || {}); this.env = Object.freeze(Object.defineProperties({}, descrs)); @@ -4732,7 +4778,7 @@ See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for mor }; } } - Memo.template = xml``; + Memo.template = xml ``; /** * we assume that each object have the same set of keys */ @@ -4785,7 +4831,7 @@ See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for mor * @returns the underlying value */ function toRaw(value) { - return value[TARGET]; + return value[TARGET] || value; } const targetToKeysToCallbacks = new WeakMap(); /** @@ -5129,9 +5175,9 @@ See https://github.com/odoo/owl/blob/master/doc/reference/config.md#mode for mor Object.defineProperty(exports, '__esModule', { value: true }); - __info__.version = '2.0.0-alpha.1'; - __info__.date = '2022-02-09T09:24:08.644Z'; - __info__.hash = '67fe89f'; + __info__.version = '2.0.0-alpha.2'; + __info__.date = '2022-02-23T07:56:30.264Z'; + __info__.hash = '0dbd2bd'; __info__.url = 'https://github.com/odoo/owl'; diff --git a/addons/web/static/src/core/errors/error_service.js b/addons/web/static/src/core/errors/error_service.js index bbf2486e7a5..85bb8c9d572 100644 --- a/addons/web/static/src/core/errors/error_service.js +++ b/addons/web/static/src/core/errors/error_service.js @@ -101,8 +101,8 @@ export const errorService = { // Ignore some unnecessary "ResizeObserver loop limit exceeded" error in Firefox. "ResizeObserver loop completed with undelivered notifications.", // ignore Chrome video internal error: https://crbug.com/809574 - "ResizeObserver loop limit exceeded" - ] + "ResizeObserver loop limit exceeded", + ]; if (!originalError && errorsToIgnore.includes(message)) { return; } @@ -123,7 +123,17 @@ export const errorService = { }); browser.addEventListener("unhandledrejection", async (ev) => { - const originalError = ev.reason; + let originalError = ev.reason; + // note: unwrapping error like this means that the error handlers will + // only be aware of the cause, not of the current error (which may + // show more interesting information, such as the owl lifecycle + // method that caused the error. But doing it in the best way (traceback + // from error, and handled by correct error handler requires a small + // scale refactoring of the error handling code. + originalError = + originalError instanceof Error && "cause" in originalError + ? originalError.cause + : originalError; const uncaughtError = new UncaughtPromiseError(); uncaughtError.unhandledRejectionEvent = ev; if (originalError instanceof Error) { diff --git a/addons/web/static/src/legacy/js/promise_extension.js b/addons/web/static/src/legacy/js/promise_extension.js index 4758975c712..9e12e1cc713 100644 --- a/addons/web/static/src/legacy/js/promise_extension.js +++ b/addons/web/static/src/legacy/js/promise_extension.js @@ -11,7 +11,8 @@ var _catch = Promise.prototype.catch; Promise.prototype.guardedCatch = function (onRejected) { return _catch.call(this, function (reason) { - if (!reason || !(reason instanceof Error)) { + const error = (reason instanceof Error && "cause" in reason) ? reason.cause : reason; + if (!error || !(error instanceof Error)) { if (onRejected) { onRejected.call(this, reason); } diff --git a/addons/web/static/src/legacy/utils.js b/addons/web/static/src/legacy/utils.js index b2074208f33..4f888affb44 100644 --- a/addons/web/static/src/legacy/utils.js +++ b/addons/web/static/src/legacy/utils.js @@ -295,7 +295,8 @@ export function wrapSuccessOrFail(promise, { on_success, on_fail } = {}) { if (on_fail) { alreadyThrown = on_fail(reason) === "alreadyThrown"; } - if (reason instanceof Error && !alreadyThrown) { + const error = reason instanceof Error && "cause" in reason ? reason.cause : reason; + if (error instanceof Error && !alreadyThrown) { throw reason; } }); diff --git a/addons/web/static/tests/legacy/owl_compatibility_tests.js b/addons/web/static/tests/legacy/owl_compatibility_tests.js index b1eaff5bd8a..dd10fa648df 100644 --- a/addons/web/static/tests/legacy/owl_compatibility_tests.js +++ b/addons/web/static/tests/legacy/owl_compatibility_tests.js @@ -120,11 +120,12 @@ odoo.define('web.OwlCompatibilityTests', function (require) { onError((e) => { assert.strictEqual( e.toString(), - `Error: ComponentAdapter has more than 1 argument, 'widgetArgs' must be overriden.` + // eslint-disable-next-line no-useless-escape + `Error: The following error occurred in onWillStart: \"ComponentAdapter has more than 1 argument, 'widgetArgs' must be overriden.\"` ); this.error = true; this.render(); - }) + }); } } Parent.template = xml` diff --git a/addons/web/static/tests/qunit.js b/addons/web/static/tests/qunit.js index bf266554c8d..8451d76ef9d 100644 --- a/addons/web/static/tests/qunit.js +++ b/addons/web/static/tests/qunit.js @@ -318,7 +318,8 @@ // Do not really crash on non-errors rejections const qunitUnhandledReject = QUnit.onUnhandledRejection; QUnit.onUnhandledRejection = (reason) => { - if (reason instanceof Error) { + const error = reason instanceof Error && "cause" in reason ? reason.cause : reason; + if (error instanceof Error) { qunitUnhandledReject(reason); } }; @@ -327,7 +328,9 @@ // not due to an actual error const windowUnhandledReject = window.onunhandledrejection; window.onunhandledrejection = (ev) => { - if (!(ev.reason instanceof Error)) { + const error = + ev.reason instanceof Error && "cause" in ev.reason ? ev.reason.cause : ev.reason; + if (!(error instanceof Error)) { ev.stopImmediatePropagation(); ev.preventDefault(); } else if (windowUnhandledReject) {