[IMP] web: update owl to last version
This owl update is significant: it introduces a change in the way components are rendered: now, child components check if there is a current rendering somewhere in their ancestors. If this is the case, the rendering will be delayed. This prevents lots of issues where a child components may be in some kind of corrupted state after some state changes. closes odoo/odoo#88572 X-original-commit: 93710d082bf860d0ea576fe99db9506af9bb42b0 Related: odoo/enterprise#26135 Signed-off-by: Samuel Degueldre <sad@odoo.com> Signed-off-by: Géry Debongnie <ged@odoo.com>
This commit is contained in:
+218
-143
@@ -212,7 +212,8 @@
|
||||
}
|
||||
function makePropSetter(name) {
|
||||
return function setProp(value) {
|
||||
this[name] = value || "";
|
||||
// support 0, fallback to empty string for other falsy values
|
||||
this[name] = value === 0 ? 0 : value || "";
|
||||
};
|
||||
}
|
||||
function isProp(tag, key) {
|
||||
@@ -518,7 +519,7 @@
|
||||
const characterDataSetData = getDescriptor$1(characterDataProto, "data").set;
|
||||
const nodeGetFirstChild = getDescriptor$1(nodeProto$2, "firstChild").get;
|
||||
const nodeGetNextSibling = getDescriptor$1(nodeProto$2, "nextSibling").get;
|
||||
const NO_OP$1 = () => { };
|
||||
const NO_OP = () => { };
|
||||
const cache$1 = {};
|
||||
/**
|
||||
* Compiling blocks is a multi-step process:
|
||||
@@ -822,7 +823,7 @@
|
||||
idx: info.idx,
|
||||
refIdx: info.refIdx,
|
||||
setData: makeRefSetter(index, ctx.refList),
|
||||
updateData: NO_OP$1,
|
||||
updateData: NO_OP,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1623,6 +1624,11 @@
|
||||
if (key === TARGET) {
|
||||
return target;
|
||||
}
|
||||
// non-writable non-configurable properties cannot be made reactive
|
||||
const desc = Object.getOwnPropertyDescriptor(target, key);
|
||||
if (desc && !desc.writable && !desc.configurable) {
|
||||
return Reflect.get(target, key, proxy);
|
||||
}
|
||||
observeTargetKey(target, key, callback);
|
||||
return possiblyReactive(Reflect.get(target, key, proxy), callback);
|
||||
},
|
||||
@@ -1695,6 +1701,23 @@
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Creates a forEach function that will delegate to forEach on the underlying
|
||||
* collection while observing key changes, and keys as they're iterated over,
|
||||
* and making the passed keys/values reactive.
|
||||
*
|
||||
* @param target @see reactive
|
||||
* @param callback @see reactive
|
||||
*/
|
||||
function makeForEachObserver(target, callback) {
|
||||
return function forEach(forEachCb, thisArg) {
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
target.forEach(function (val, key, targetObj) {
|
||||
observeTargetKey(target, key, callback);
|
||||
forEachCb.call(thisArg, possiblyReactive(val, callback), possiblyReactive(key, callback), possiblyReactive(targetObj, callback));
|
||||
}, thisArg);
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Creates a function that will delegate to an underlying method, and check if
|
||||
* that method has modified the presence or value of a key, and notify the
|
||||
@@ -1753,6 +1776,7 @@
|
||||
values: makeIteratorObserver("values", target, callback),
|
||||
entries: makeIteratorObserver("entries", target, callback),
|
||||
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
||||
forEach: makeForEachObserver(target, callback),
|
||||
clear: makeClearNotifier(target),
|
||||
get size() {
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
@@ -1768,6 +1792,7 @@
|
||||
values: makeIteratorObserver("values", target, callback),
|
||||
entries: makeIteratorObserver("entries", target, callback),
|
||||
[Symbol.iterator]: makeIteratorObserver(Symbol.iterator, target, callback),
|
||||
forEach: makeForEachObserver(target, callback),
|
||||
clear: makeClearNotifier(target),
|
||||
get size() {
|
||||
observeTargetKey(target, KEYCHANGES, callback);
|
||||
@@ -1821,11 +1846,13 @@
|
||||
await Promise.resolve();
|
||||
if (!called) {
|
||||
called = true;
|
||||
callback();
|
||||
// wait for all calls in this microtick to fall through before resetting "called"
|
||||
// so that only the first call to the batched function calls the original callback
|
||||
await Promise.resolve();
|
||||
called = false;
|
||||
// so that only the first call to the batched function calls the original callback.
|
||||
// Schedule this before calling the callback so that calls to the batched function
|
||||
// within the callback will proceed only after resetting called to false, and have
|
||||
// a chance to execute the callback again
|
||||
Promise.resolve().then(() => (called = false));
|
||||
callback();
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1888,7 +1915,7 @@
|
||||
// Maps fibers to thrown errors
|
||||
const fibersInError = new WeakMap();
|
||||
const nodeErrorHandlers = new WeakMap();
|
||||
function _handleError(node, error, isFirstRound = false) {
|
||||
function _handleError(node, error) {
|
||||
if (!node) {
|
||||
return false;
|
||||
}
|
||||
@@ -1898,22 +1925,19 @@
|
||||
}
|
||||
const errorHandlers = nodeErrorHandlers.get(node);
|
||||
if (errorHandlers) {
|
||||
let stopped = false;
|
||||
let handled = false;
|
||||
// execute in the opposite order
|
||||
for (let i = errorHandlers.length - 1; i >= 0; i--) {
|
||||
try {
|
||||
errorHandlers[i](error);
|
||||
stopped = true;
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
if (stopped) {
|
||||
if (isFirstRound && fiber && fiber.node.fiber) {
|
||||
fiber.root.counter--;
|
||||
}
|
||||
if (handled) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1931,7 +1955,7 @@
|
||||
current = current.parent;
|
||||
} while (current);
|
||||
fibersInError.set(fiber.root, error);
|
||||
const handled = _handleError(node, error, true);
|
||||
const handled = _handleError(node, error);
|
||||
if (!handled) {
|
||||
console.warn(`[Owl] Unhandled error. Destroying the root component`);
|
||||
try {
|
||||
@@ -1955,8 +1979,14 @@
|
||||
let current = node.fiber;
|
||||
if (current) {
|
||||
let root = current.root;
|
||||
root.counter = root.counter + 1 - cancelFibers(current.children);
|
||||
// lock root fiber because canceling children fibers may destroy components,
|
||||
// which means any arbitrary code can be run in onWillDestroy, which may
|
||||
// trigger new renderings
|
||||
root.locked = true;
|
||||
root.setCounter(root.counter + 1 - cancelFibers(current.children));
|
||||
root.locked = false;
|
||||
current.children = [];
|
||||
current.childrenMap = {};
|
||||
current.bdom = null;
|
||||
if (fibersInError.has(current)) {
|
||||
fibersInError.delete(current);
|
||||
@@ -1980,7 +2010,11 @@
|
||||
function cancelFibers(fibers) {
|
||||
let result = 0;
|
||||
for (let fiber of fibers) {
|
||||
fiber.node.fiber = null;
|
||||
let node = fiber.node;
|
||||
if (node.status === 0 /* NEW */) {
|
||||
node.destroy();
|
||||
}
|
||||
node.fiber = null;
|
||||
if (fiber.bdom) {
|
||||
// if fiber has been rendered, this means that the component props have
|
||||
// been updated. however, this fiber will not be patched to the dom, so
|
||||
@@ -1988,7 +2022,7 @@
|
||||
// the same props, and skip the render completely. With the next line,
|
||||
// we kindly request the component code to force a render, so it works as
|
||||
// expected.
|
||||
fiber.node.forceNextRender = true;
|
||||
node.forceNextRender = true;
|
||||
}
|
||||
else {
|
||||
result++;
|
||||
@@ -2003,12 +2037,13 @@
|
||||
this.children = [];
|
||||
this.appliedToDom = false;
|
||||
this.deep = false;
|
||||
this.childrenMap = {};
|
||||
this.node = node;
|
||||
this.parent = parent;
|
||||
if (parent) {
|
||||
this.deep = parent.deep;
|
||||
const root = parent.root;
|
||||
root.counter++;
|
||||
root.setCounter(root.counter + 1);
|
||||
this.root = root;
|
||||
parent.children.push(this);
|
||||
}
|
||||
@@ -2016,6 +2051,42 @@
|
||||
this.root = this;
|
||||
}
|
||||
}
|
||||
render() {
|
||||
// if some parent has a fiber => register in followup
|
||||
let prev = this.root.node;
|
||||
let scheduler = prev.app.scheduler;
|
||||
let current = prev.parent;
|
||||
while (current) {
|
||||
if (current.fiber) {
|
||||
let root = current.fiber.root;
|
||||
if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
|
||||
current = root.node;
|
||||
}
|
||||
else {
|
||||
scheduler.delayedRenders.push(this);
|
||||
return;
|
||||
}
|
||||
}
|
||||
prev = current;
|
||||
current = current.parent;
|
||||
}
|
||||
// there are no current rendering from above => we can render
|
||||
this._render();
|
||||
}
|
||||
_render() {
|
||||
const node = this.node;
|
||||
const root = this.root;
|
||||
if (root) {
|
||||
try {
|
||||
this.bdom = true;
|
||||
this.bdom = node.renderFn();
|
||||
}
|
||||
catch (e) {
|
||||
handleError({ node, error: e });
|
||||
}
|
||||
root.setCounter(root.counter - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
class RootFiber extends Fiber {
|
||||
constructor() {
|
||||
@@ -2077,6 +2148,12 @@
|
||||
handleError({ fiber: current || this, error: e });
|
||||
}
|
||||
}
|
||||
setCounter(newValue) {
|
||||
this.counter = newValue;
|
||||
if (newValue === 0) {
|
||||
this.node.app.scheduler.flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
class MountFiber extends RootFiber {
|
||||
constructor(node, target, options = {}) {
|
||||
@@ -2088,6 +2165,7 @@
|
||||
let current = this;
|
||||
try {
|
||||
const node = this.node;
|
||||
node.children = this.childrenMap;
|
||||
node.app.constructor.validateTarget(this.target);
|
||||
if (node.bdom) {
|
||||
// this is a complicated situation: if we mount a fiber with an existing
|
||||
@@ -2293,17 +2371,10 @@
|
||||
const node = getCurrent();
|
||||
let render = batchedRenderFunctions.get(node);
|
||||
if (!render) {
|
||||
render = batched(node.render.bind(node));
|
||||
render = batched(node.render.bind(node, false));
|
||||
batchedRenderFunctions.set(node, render);
|
||||
// manual implementation of onWillDestroy to break cyclic dependency
|
||||
node.willDestroy.push(clearReactivesForCallback.bind(null, render));
|
||||
if (node.app.dev) {
|
||||
Object.defineProperty(node, "subscriptions", {
|
||||
get() {
|
||||
return getSubscriptions(render);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
return reactive(state, render);
|
||||
}
|
||||
@@ -2318,14 +2389,8 @@
|
||||
function component(name, props, key, ctx, parent) {
|
||||
let node = ctx.children[key];
|
||||
let isDynamic = typeof name !== "string";
|
||||
if (node) {
|
||||
if (node.status < 1 /* MOUNTED */) {
|
||||
node.destroy();
|
||||
node = undefined;
|
||||
}
|
||||
else if (node.status === 2 /* DESTROYED */) {
|
||||
node = undefined;
|
||||
}
|
||||
if (node && node.status === 2 /* DESTROYED */) {
|
||||
node = undefined;
|
||||
}
|
||||
if (isDynamic && node && node.component.constructor !== name) {
|
||||
node = undefined;
|
||||
@@ -2356,14 +2421,15 @@
|
||||
throw new Error(`Cannot find the definition of component "${name}"`);
|
||||
}
|
||||
}
|
||||
node = new ComponentNode(C, props, ctx.app, ctx);
|
||||
node = new ComponentNode(C, props, ctx.app, ctx, key);
|
||||
ctx.children[key] = node;
|
||||
node.initiateRender(new Fiber(node, parentFiber));
|
||||
}
|
||||
parentFiber.childrenMap[key] = node;
|
||||
return node;
|
||||
}
|
||||
class ComponentNode {
|
||||
constructor(C, props, app, parent) {
|
||||
constructor(C, props, app, parent, parentKey) {
|
||||
this.fiber = null;
|
||||
this.bdom = null;
|
||||
this.status = 0 /* NEW */;
|
||||
@@ -2379,7 +2445,8 @@
|
||||
this.willDestroy = [];
|
||||
currentNode = this;
|
||||
this.app = app;
|
||||
this.parent = parent || null;
|
||||
this.parent = parent;
|
||||
this.parentKey = parentKey;
|
||||
this.level = parent ? parent.level + 1 : 0;
|
||||
applyDefaultProps(props, C);
|
||||
const env = (parent && parent.childEnv) || app.env;
|
||||
@@ -2409,12 +2476,12 @@
|
||||
return;
|
||||
}
|
||||
if (this.status === 0 /* NEW */ && this.fiber === fiber) {
|
||||
this._render(fiber);
|
||||
fiber.render();
|
||||
}
|
||||
}
|
||||
async render(deep = false) {
|
||||
async render(deep) {
|
||||
let current = this.fiber;
|
||||
if (current && current.root.locked) {
|
||||
if (current && (current.root.locked || current.bdom === true)) {
|
||||
await Promise.resolve();
|
||||
// situation may have changed after the microtask tick
|
||||
current = this.fiber;
|
||||
@@ -2453,16 +2520,7 @@
|
||||
// embedded in a rendering coming from above, so the fiber will be rendered
|
||||
// in the next microtick anyway, so we should not render it again.
|
||||
if (this.fiber === fiber && (current || !fiber.parent)) {
|
||||
this._render(fiber);
|
||||
}
|
||||
}
|
||||
_render(fiber) {
|
||||
try {
|
||||
fiber.bdom = this.renderFn();
|
||||
fiber.root.counter--;
|
||||
}
|
||||
catch (e) {
|
||||
handleError({ node: this, error: e });
|
||||
fiber.render();
|
||||
}
|
||||
}
|
||||
destroy() {
|
||||
@@ -2482,8 +2540,15 @@
|
||||
for (let child of Object.values(this.children)) {
|
||||
child._destroy();
|
||||
}
|
||||
for (let cb of this.willDestroy) {
|
||||
cb.call(component);
|
||||
if (this.willDestroy.length) {
|
||||
try {
|
||||
for (let cb of this.willDestroy) {
|
||||
cb.call(component);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
handleError({ error: e, node: this });
|
||||
}
|
||||
}
|
||||
this.status = 2 /* DESTROYED */;
|
||||
}
|
||||
@@ -2502,7 +2567,7 @@
|
||||
return;
|
||||
}
|
||||
component.props = props;
|
||||
this._render(fiber);
|
||||
fiber.render();
|
||||
const parentRoot = parentFiber.root;
|
||||
if (this.willPatch.length) {
|
||||
parentRoot.willPatch.push(fiber);
|
||||
@@ -2549,6 +2614,7 @@
|
||||
bdom.mount(parent, anchor);
|
||||
this.status = 1 /* MOUNTED */;
|
||||
this.fiber.appliedToDom = true;
|
||||
this.children = this.fiber.childrenMap;
|
||||
this.fiber = null;
|
||||
}
|
||||
moveBefore(other, afterNode) {
|
||||
@@ -2564,10 +2630,8 @@
|
||||
}
|
||||
_patch() {
|
||||
const hasChildren = Object.keys(this.children).length > 0;
|
||||
this.children = this.fiber.childrenMap;
|
||||
this.bdom.patch(this.fiber.bdom, hasChildren);
|
||||
if (hasChildren) {
|
||||
this.cleanOutdatedChildren();
|
||||
}
|
||||
this.fiber.appliedToDom = true;
|
||||
this.fiber = null;
|
||||
}
|
||||
@@ -2577,18 +2641,15 @@
|
||||
remove() {
|
||||
this.bdom.remove();
|
||||
}
|
||||
cleanOutdatedChildren() {
|
||||
const children = this.children;
|
||||
for (const key in children) {
|
||||
const node = children[key];
|
||||
const status = node.status;
|
||||
if (status !== 1 /* MOUNTED */) {
|
||||
delete children[key];
|
||||
if (status !== 2 /* DESTROYED */) {
|
||||
node.destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
// ---------------------------------------------------------------------------
|
||||
// Some debug helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
get name() {
|
||||
return this.component.constructor.name;
|
||||
}
|
||||
get subscriptions() {
|
||||
const render = batchedRenderFunctions.get(this);
|
||||
return render ? getSubscriptions(render) : [];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2598,59 +2659,59 @@
|
||||
class Scheduler {
|
||||
constructor() {
|
||||
this.tasks = new Set();
|
||||
this.isRunning = false;
|
||||
this.frame = 0;
|
||||
this.delayedRenders = [];
|
||||
this.requestAnimationFrame = Scheduler.requestAnimationFrame;
|
||||
}
|
||||
start() {
|
||||
this.isRunning = true;
|
||||
this.scheduleTasks();
|
||||
}
|
||||
stop() {
|
||||
this.isRunning = false;
|
||||
}
|
||||
addFiber(fiber) {
|
||||
this.tasks.add(fiber.root);
|
||||
if (!this.isRunning) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Process all current tasks. This only applies to the fibers that are ready.
|
||||
* Other tasks are left unchanged.
|
||||
*/
|
||||
flush() {
|
||||
this.tasks.forEach((fiber) => {
|
||||
if (fiber.root !== fiber) {
|
||||
this.tasks.delete(fiber);
|
||||
return;
|
||||
}
|
||||
const hasError = fibersInError.has(fiber);
|
||||
if (hasError && fiber.counter !== 0) {
|
||||
this.tasks.delete(fiber);
|
||||
return;
|
||||
}
|
||||
if (fiber.node.status === 2 /* DESTROYED */) {
|
||||
this.tasks.delete(fiber);
|
||||
return;
|
||||
}
|
||||
if (fiber.counter === 0) {
|
||||
if (!hasError) {
|
||||
fiber.complete();
|
||||
if (this.delayedRenders.length) {
|
||||
let renders = this.delayedRenders;
|
||||
this.delayedRenders = [];
|
||||
for (let f of renders) {
|
||||
if (f.root && f.node.status !== 2 /* DESTROYED */) {
|
||||
f.render();
|
||||
}
|
||||
this.tasks.delete(fiber);
|
||||
}
|
||||
});
|
||||
if (this.tasks.size === 0) {
|
||||
this.stop();
|
||||
}
|
||||
if (this.frame === 0) {
|
||||
this.frame = this.requestAnimationFrame(() => {
|
||||
this.frame = 0;
|
||||
this.tasks.forEach((fiber) => this.processFiber(fiber));
|
||||
for (let task of this.tasks) {
|
||||
if (task.node.status === 2 /* DESTROYED */) {
|
||||
this.tasks.delete(task);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
scheduleTasks() {
|
||||
this.requestAnimationFrame(() => {
|
||||
this.flush();
|
||||
if (this.isRunning) {
|
||||
this.scheduleTasks();
|
||||
processFiber(fiber) {
|
||||
if (fiber.root !== fiber) {
|
||||
this.tasks.delete(fiber);
|
||||
return;
|
||||
}
|
||||
const hasError = fibersInError.has(fiber);
|
||||
if (hasError && fiber.counter !== 0) {
|
||||
this.tasks.delete(fiber);
|
||||
return;
|
||||
}
|
||||
if (fiber.node.status === 2 /* DESTROYED */) {
|
||||
this.tasks.delete(fiber);
|
||||
return;
|
||||
}
|
||||
if (fiber.counter === 0) {
|
||||
if (!hasError) {
|
||||
fiber.complete();
|
||||
}
|
||||
});
|
||||
this.tasks.delete(fiber);
|
||||
}
|
||||
}
|
||||
}
|
||||
// capture the value of requestAnimationFrame as soon as possible, to avoid
|
||||
@@ -3939,8 +4000,11 @@
|
||||
let slotStr = [];
|
||||
for (let slotName in ast.slots) {
|
||||
const slotAst = ast.slots[slotName];
|
||||
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
|
||||
const params = [`__render: ${name}, __ctx: ${ctxStr}`];
|
||||
const params = [];
|
||||
if (slotAst.content) {
|
||||
const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
|
||||
params.push(`__render: ${name}, __ctx: ${ctxStr}`);
|
||||
}
|
||||
const scope = ast.slots[slotName].scope;
|
||||
if (scope) {
|
||||
params.push(`__scope: "${scope}"`);
|
||||
@@ -4042,7 +4106,7 @@
|
||||
if (dynamic) {
|
||||
let name = this.generateId("slot");
|
||||
this.define(name, slotName);
|
||||
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}), ${dynamic}, ${scope})`;
|
||||
blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
|
||||
}
|
||||
else {
|
||||
blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
|
||||
@@ -4583,28 +4647,26 @@
|
||||
slotNode.removeAttribute("t-set-slot");
|
||||
slotNode.remove();
|
||||
const slotAst = parseNode(slotNode, ctx);
|
||||
if (slotAst) {
|
||||
let on = null;
|
||||
let attrs = null;
|
||||
let scope = null;
|
||||
for (let attributeName of slotNode.getAttributeNames()) {
|
||||
const value = slotNode.getAttribute(attributeName);
|
||||
if (attributeName === "t-slot-scope") {
|
||||
scope = value;
|
||||
continue;
|
||||
}
|
||||
else if (attributeName.startsWith("t-on-")) {
|
||||
on = on || {};
|
||||
on[attributeName.slice(5)] = value;
|
||||
}
|
||||
else {
|
||||
attrs = attrs || {};
|
||||
attrs[attributeName] = value;
|
||||
}
|
||||
let on = null;
|
||||
let attrs = null;
|
||||
let scope = null;
|
||||
for (let attributeName of slotNode.getAttributeNames()) {
|
||||
const value = slotNode.getAttribute(attributeName);
|
||||
if (attributeName === "t-slot-scope") {
|
||||
scope = value;
|
||||
continue;
|
||||
}
|
||||
else if (attributeName.startsWith("t-on-")) {
|
||||
on = on || {};
|
||||
on[attributeName.slice(5)] = value;
|
||||
}
|
||||
else {
|
||||
attrs = attrs || {};
|
||||
attrs[attributeName] = value;
|
||||
}
|
||||
slots = slots || {};
|
||||
slots[name] = { content: slotAst, on, attrs, scope };
|
||||
}
|
||||
slots = slots || {};
|
||||
slots[name] = { content: slotAst, on, attrs, scope };
|
||||
}
|
||||
// default slot
|
||||
const defaultContent = parseChildNodes(clone, ctx);
|
||||
@@ -4835,12 +4897,26 @@
|
||||
return new Function("bdom, helpers", code);
|
||||
}
|
||||
|
||||
const TIMEOUT = Symbol("timeout");
|
||||
function wrapError(fn, hookName) {
|
||||
const error = new Error(`The following error occurred in ${hookName}: `);
|
||||
const timeoutError = new Error(`${hookName}'s promise hasn't resolved after 3 seconds`);
|
||||
const node = getCurrent();
|
||||
return (...args) => {
|
||||
try {
|
||||
const result = fn(...args);
|
||||
if (result instanceof Promise) {
|
||||
if (hookName === "onWillStart" || hookName === "onWillUpdateProps") {
|
||||
const fiber = node.fiber;
|
||||
Promise.race([
|
||||
result,
|
||||
new Promise((resolve) => setTimeout(() => resolve(TIMEOUT), 3000)),
|
||||
]).then((res) => {
|
||||
if (res === TIMEOUT && node.fiber === fiber) {
|
||||
console.warn(timeoutError);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result.catch((cause) => {
|
||||
error.cause = cause;
|
||||
if (cause instanceof Error) {
|
||||
@@ -4936,7 +5012,7 @@
|
||||
}
|
||||
setup() { }
|
||||
render(deep = false) {
|
||||
this.__owl__.render(deep);
|
||||
this.__owl__.render(deep === true);
|
||||
}
|
||||
}
|
||||
Component.template = "";
|
||||
@@ -5318,7 +5394,7 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
||||
return prom;
|
||||
}
|
||||
makeNode(Component, props) {
|
||||
return new ComponentNode(Component, props, this);
|
||||
return new ComponentNode(Component, props, this, null, null);
|
||||
}
|
||||
mountNode(node, target, options) {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
@@ -5350,6 +5426,7 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
||||
}
|
||||
destroy() {
|
||||
if (this.root) {
|
||||
this.scheduler.flush();
|
||||
this.root.destroy();
|
||||
}
|
||||
}
|
||||
@@ -5415,10 +5492,6 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
||||
const node = getCurrent();
|
||||
node.childEnv = extendEnv(node.childEnv, envExtension);
|
||||
}
|
||||
// -----------------------------------------------------------------------------
|
||||
// useEffect
|
||||
// -----------------------------------------------------------------------------
|
||||
const NO_OP = () => { };
|
||||
/**
|
||||
* This hook will run a callback when a component is mounted and patched, and
|
||||
* will run a cleanup function before patching and before unmounting the
|
||||
@@ -5436,18 +5509,20 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
||||
let dependencies;
|
||||
onMounted(() => {
|
||||
dependencies = computeDependencies();
|
||||
cleanup = effect(...dependencies) || NO_OP;
|
||||
cleanup = effect(...dependencies);
|
||||
});
|
||||
onPatched(() => {
|
||||
const newDeps = computeDependencies();
|
||||
const shouldReapply = newDeps.some((val, i) => val !== dependencies[i]);
|
||||
if (shouldReapply) {
|
||||
dependencies = newDeps;
|
||||
cleanup();
|
||||
cleanup = effect(...dependencies) || NO_OP;
|
||||
if (cleanup) {
|
||||
cleanup();
|
||||
}
|
||||
cleanup = effect(...dependencies);
|
||||
}
|
||||
});
|
||||
onWillUnmount(() => cleanup());
|
||||
onWillUnmount(() => cleanup && cleanup());
|
||||
}
|
||||
// -----------------------------------------------------------------------------
|
||||
// useExternalListener
|
||||
@@ -5527,9 +5602,9 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
|
||||
Object.defineProperty(exports, '__esModule', { value: true });
|
||||
|
||||
|
||||
__info__.version = '2.0.0-beta.3';
|
||||
__info__.date = '2022-03-08T11:47:57.414Z';
|
||||
__info__.hash = 'c356351';
|
||||
__info__.version = '2.0.0-beta-6';
|
||||
__info__.date = '2022-04-11T09:02:17.726Z';
|
||||
__info__.hash = '41344ef';
|
||||
__info__.url = 'https://github.com/odoo/owl';
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/** @odoo-module **/
|
||||
|
||||
const { Component, onMounted, onWillStart, onWillUpdateProps, useRef, useState } = owl;
|
||||
import { useBus } from "@web/core/utils/hooks";
|
||||
|
||||
const { Component, onMounted, onWillStart, useRef, useState } = owl;
|
||||
|
||||
/**
|
||||
* Search panel
|
||||
@@ -23,6 +25,12 @@ export class SearchPanel extends Component {
|
||||
|
||||
this.importState(this.props.importedState);
|
||||
|
||||
useBus(this.env.searchModel, "update", async () => {
|
||||
await this.env.searchModel.sectionsPromise;
|
||||
this.updateActiveValues();
|
||||
this.render();
|
||||
});
|
||||
|
||||
onWillStart(async () => {
|
||||
await this.env.searchModel.sectionsPromise;
|
||||
this.expandDefaultValue();
|
||||
@@ -35,11 +43,6 @@ export class SearchPanel extends Component {
|
||||
this.root.el.scroll({ top: this.scrollTop });
|
||||
}
|
||||
});
|
||||
|
||||
onWillUpdateProps(async () => {
|
||||
await this.env.searchModel.sectionsPromise;
|
||||
this.updateActiveValues();
|
||||
});
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
@@ -121,7 +124,6 @@ export class SearchPanel extends Component {
|
||||
}
|
||||
}
|
||||
if (category.activeValueId !== value.id) {
|
||||
this.state.active[category.id] = value.id;
|
||||
this.env.searchModel.toggleCategoryValue(category.id, value.id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,6 +98,7 @@ export function useModel(ModelClass, params, options = {}) {
|
||||
const orm = model.orm;
|
||||
let sampleORM = globalState.sampleORM;
|
||||
const user = useService("user");
|
||||
let started = false;
|
||||
async function load(props) {
|
||||
model.orm = orm;
|
||||
const searchParams = getSearchParams(props);
|
||||
@@ -111,13 +112,17 @@ export function useModel(ModelClass, params, options = {}) {
|
||||
useSampleModel = false;
|
||||
}
|
||||
model.useSampleModel = useSampleModel;
|
||||
if (started) {
|
||||
model.notify();
|
||||
}
|
||||
}
|
||||
onWillStart(() => {
|
||||
return load(component.props);
|
||||
onWillStart(async () => {
|
||||
await load(component.props);
|
||||
started = true;
|
||||
});
|
||||
onWillUpdateProps((nextProps) => {
|
||||
useSampleModel = false;
|
||||
return load(nextProps);
|
||||
load(nextProps);
|
||||
});
|
||||
|
||||
useSetupView({
|
||||
|
||||
@@ -5,15 +5,16 @@
|
||||
const schedulers = new Set();
|
||||
|
||||
function hookIntoScheduler(scheduler) {
|
||||
const { stop: originalStop } = scheduler;
|
||||
scheduler.stop = function stop() {
|
||||
const wasRunning = this.isRunning;
|
||||
originalStop.call(this);
|
||||
if (wasRunning) {
|
||||
while (stopPromises.length) {
|
||||
stopPromises.pop().resolve();
|
||||
const { flush: originalFlush } = scheduler;
|
||||
scheduler.flush = function flush() {
|
||||
originalFlush.call(this, ...arguments);
|
||||
window.requestAnimationFrame(() => {
|
||||
if ([...schedulers].every(({ tasks }) => tasks.size === 0)) {
|
||||
while (stopPromises.length) {
|
||||
stopPromises.pop().resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@@ -49,10 +50,15 @@
|
||||
const timeoutProm = new Promise((resolve, reject) => {
|
||||
timeoutNoRender = setTimeout(() => {
|
||||
let error = startError;
|
||||
if ([...schedulers].some(({ isRunning }) => isRunning)) {
|
||||
const runningSchedulers = [...schedulers].filter(({ tasks }) => tasks.size > 0);
|
||||
if (runningSchedulers.length) {
|
||||
error = stopError;
|
||||
}
|
||||
console.error(error);
|
||||
console.error(
|
||||
error,
|
||||
runningSchedulers.map((s) => [...s.tasks].map((f) => [f, f.node.status])),
|
||||
runningSchedulers
|
||||
);
|
||||
reject(error);
|
||||
}, timeoutDelay);
|
||||
});
|
||||
@@ -76,7 +82,7 @@
|
||||
await new Promise(function (resolve) {
|
||||
setTimeout(() => window.requestAnimationFrame(() => resolve()));
|
||||
});
|
||||
if ([...schedulers].some(({ isRunning }) => isRunning)) {
|
||||
if ([...schedulers].some(({ tasks }) => tasks.size > 0)) {
|
||||
await afterNextRender(() => {}, timeoutDelay);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -990,9 +990,9 @@ QUnit.module("Search", (hooks) => {
|
||||
const asustekPromise = promise;
|
||||
await click(getCategory(target, 1));
|
||||
|
||||
// 'asustek' should be selected, but there should still be 3 records
|
||||
// 'asustek' should not be selected yet, and there should still be 3 records
|
||||
assert.containsOnce(target, ".o_search_panel_category_value .active");
|
||||
assert.containsOnce(target, ".o_search_panel_category_value:nth(1) .active");
|
||||
assert.containsOnce(target, ".o_search_panel_category_value:first .active");
|
||||
|
||||
assert.deepEqual(getDomain(), [["bar", "=", true]]);
|
||||
|
||||
@@ -1001,9 +1001,9 @@ QUnit.module("Search", (hooks) => {
|
||||
const agrolaitPromise = promise;
|
||||
await click(getCategory(target, 2));
|
||||
|
||||
// 'agrolait' should be selected, but there should still be 3 records
|
||||
// 'agrolait' should not be selected yet, and there should still be 3 records
|
||||
assert.containsOnce(target, ".o_search_panel_category_value .active");
|
||||
assert.containsOnce(target, ".o_search_panel_category_value:nth(2) .active");
|
||||
assert.containsOnce(target, ".o_search_panel_category_value:first .active");
|
||||
|
||||
assert.deepEqual(getDomain(), [["bar", "=", true]]);
|
||||
|
||||
@@ -1012,7 +1012,7 @@ QUnit.module("Search", (hooks) => {
|
||||
await nextTick();
|
||||
|
||||
assert.containsOnce(target, ".o_search_panel_category_value .active");
|
||||
assert.containsOnce(target, ".o_search_panel_category_value:nth(2) .active");
|
||||
assert.containsOnce(target, ".o_search_panel_category_value:first .active");
|
||||
|
||||
assert.deepEqual(getDomain(), ["&", ["bar", "=", true], ["company_id", "child_of", 3]]);
|
||||
|
||||
@@ -2251,7 +2251,7 @@ QUnit.module("Search", (hooks) => {
|
||||
|
||||
const webclient = await createWebClient({
|
||||
serverData,
|
||||
async mockRPC(route, {method}) {
|
||||
async mockRPC(route, { method }) {
|
||||
if (/search_panel_/.test(method || route)) {
|
||||
assert.step(method || route);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user