[IMP] update owl to v2.0.0-beta-7

This commit is contained in:
Samuel Degueldre
2022-04-27 11:08:59 +02:00
parent db41b28d9c
commit 97fb38eea5
+233 -148
View File
@@ -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);
@@ -1974,13 +2004,21 @@
}
return fiber;
}
function throwOnRender() {
throw new Error("Attempted to render cancelled fiber");
}
/**
* @returns number of not-yet rendered fibers cancelled
*/
function cancelFibers(fibers) {
let result = 0;
for (let fiber of fibers) {
fiber.node.fiber = null;
let node = fiber.node;
fiber.render = throwOnRender;
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 +2026,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 +2041,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 +2055,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 +2152,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 +2169,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 +2375,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 +2393,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 +2425,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 +2449,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 +2480,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 +2524,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 +2544,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 +2571,7 @@
return;
}
component.props = props;
this._render(fiber);
fiber.render();
const parentRoot = parentFiber.root;
if (this.willPatch.length) {
parentRoot.willPatch.push(fiber);
@@ -2549,6 +2618,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 +2634,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 +2645,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 +2663,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.node.fiber === f) {
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 +4004,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 +4110,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 +4651,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 +4901,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 +5016,7 @@
}
setup() { }
render(deep = false) {
this.__owl__.render(deep);
this.__owl__.render(deep === true);
}
}
Component.template = "";
@@ -5233,13 +5313,19 @@
}
this.helpers = makeHelpers(this.getTemplate.bind(this));
}
addTemplate(name, template, options = {}) {
if (name in this.rawTemplates && !options.allowDuplicate) {
throw new Error(`Template ${name} already defined`);
addTemplate(name, template) {
if (name in this.rawTemplates) {
const rawTemplate = this.rawTemplates[name];
const currentAsString = typeof rawTemplate === "string" ? rawTemplate : rawTemplate.outerHTML;
const newAsString = typeof template === "string" ? template : template.outerHTML;
if (currentAsString === newAsString) {
return;
}
throw new Error(`Template ${name} already defined with different content`);
}
this.rawTemplates[name] = template;
}
addTemplates(xml, options = {}) {
addTemplates(xml) {
if (!xml) {
// empty string
return;
@@ -5247,7 +5333,7 @@
xml = xml instanceof Document ? xml : parseXML(xml);
for (const template of xml.querySelectorAll("[t-name]")) {
const name = template.getAttribute("t-name");
this.addTemplate(name, template, options);
this.addTemplate(name, template);
}
}
getTemplate(name) {
@@ -5318,7 +5404,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 +5436,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 +5502,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 +5519,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 +5612,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-7';
__info__.date = '2022-04-27T09:08:50.320Z';
__info__.hash = '0cd66c8';
__info__.url = 'https://github.com/odoo/owl';