@odoo/owl 2.0.0-beta-4 → 2.0.0-beta-7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/owl.iife.js CHANGED
@@ -1915,7 +1915,7 @@
1915
1915
  // Maps fibers to thrown errors
1916
1916
  const fibersInError = new WeakMap();
1917
1917
  const nodeErrorHandlers = new WeakMap();
1918
- function _handleError(node, error, isFirstRound = false) {
1918
+ function _handleError(node, error) {
1919
1919
  if (!node) {
1920
1920
  return false;
1921
1921
  }
@@ -1925,23 +1925,19 @@
1925
1925
  }
1926
1926
  const errorHandlers = nodeErrorHandlers.get(node);
1927
1927
  if (errorHandlers) {
1928
- let stopped = false;
1928
+ let handled = false;
1929
1929
  // execute in the opposite order
1930
1930
  for (let i = errorHandlers.length - 1; i >= 0; i--) {
1931
1931
  try {
1932
1932
  errorHandlers[i](error);
1933
- stopped = true;
1933
+ handled = true;
1934
1934
  break;
1935
1935
  }
1936
1936
  catch (e) {
1937
1937
  error = e;
1938
1938
  }
1939
1939
  }
1940
- if (stopped) {
1941
- if (isFirstRound && fiber && fiber.node.fiber) {
1942
- const root = fiber.root;
1943
- root.setCounter(root.counter - 1);
1944
- }
1940
+ if (handled) {
1945
1941
  return true;
1946
1942
  }
1947
1943
  }
@@ -1959,7 +1955,7 @@
1959
1955
  current = current.parent;
1960
1956
  } while (current);
1961
1957
  fibersInError.set(fiber.root, error);
1962
- const handled = _handleError(node, error, true);
1958
+ const handled = _handleError(node, error);
1963
1959
  if (!handled) {
1964
1960
  console.warn(`[Owl] Unhandled error. Destroying the root component`);
1965
1961
  try {
@@ -1976,10 +1972,6 @@
1976
1972
  if (current) {
1977
1973
  cancelFibers(current.children);
1978
1974
  current.root = null;
1979
- if (current instanceof RootFiber && current.delayedRenders.length) {
1980
- let root = parent.root;
1981
- root.delayedRenders = root.delayedRenders.concat(current.delayedRenders);
1982
- }
1983
1975
  }
1984
1976
  return new Fiber(node, parent);
1985
1977
  }
@@ -1987,12 +1979,15 @@
1987
1979
  let current = node.fiber;
1988
1980
  if (current) {
1989
1981
  let root = current.root;
1982
+ // lock root fiber because canceling children fibers may destroy components,
1983
+ // which means any arbitrary code can be run in onWillDestroy, which may
1984
+ // trigger new renderings
1985
+ root.locked = true;
1990
1986
  root.setCounter(root.counter + 1 - cancelFibers(current.children));
1987
+ root.locked = false;
1991
1988
  current.children = [];
1989
+ current.childrenMap = {};
1992
1990
  current.bdom = null;
1993
- if (current === root) {
1994
- root.reachedChildren = new WeakSet();
1995
- }
1996
1991
  if (fibersInError.has(current)) {
1997
1992
  fibersInError.delete(current);
1998
1993
  fibersInError.delete(root);
@@ -2009,13 +2004,21 @@
2009
2004
  }
2010
2005
  return fiber;
2011
2006
  }
2007
+ function throwOnRender() {
2008
+ throw new Error("Attempted to render cancelled fiber");
2009
+ }
2012
2010
  /**
2013
2011
  * @returns number of not-yet rendered fibers cancelled
2014
2012
  */
2015
2013
  function cancelFibers(fibers) {
2016
2014
  let result = 0;
2017
2015
  for (let fiber of fibers) {
2018
- fiber.node.fiber = null;
2016
+ let node = fiber.node;
2017
+ fiber.render = throwOnRender;
2018
+ if (node.status === 0 /* NEW */) {
2019
+ node.destroy();
2020
+ }
2021
+ node.fiber = null;
2019
2022
  if (fiber.bdom) {
2020
2023
  // if fiber has been rendered, this means that the component props have
2021
2024
  // been updated. however, this fiber will not be patched to the dom, so
@@ -2023,7 +2026,7 @@
2023
2026
  // the same props, and skip the render completely. With the next line,
2024
2027
  // we kindly request the component code to force a render, so it works as
2025
2028
  // expected.
2026
- fiber.node.forceNextRender = true;
2029
+ node.forceNextRender = true;
2027
2030
  }
2028
2031
  else {
2029
2032
  result++;
@@ -2038,6 +2041,7 @@
2038
2041
  this.children = [];
2039
2042
  this.appliedToDom = false;
2040
2043
  this.deep = false;
2044
+ this.childrenMap = {};
2041
2045
  this.node = node;
2042
2046
  this.parent = parent;
2043
2047
  if (parent) {
@@ -2054,21 +2058,17 @@
2054
2058
  render() {
2055
2059
  // if some parent has a fiber => register in followup
2056
2060
  let prev = this.root.node;
2061
+ let scheduler = prev.app.scheduler;
2057
2062
  let current = prev.parent;
2058
2063
  while (current) {
2059
2064
  if (current.fiber) {
2060
2065
  let root = current.fiber.root;
2061
- if (root.counter) {
2062
- root.delayedRenders.push(this);
2063
- return;
2066
+ if (root.counter === 0 && prev.parentKey in current.fiber.childrenMap) {
2067
+ current = root.node;
2064
2068
  }
2065
2069
  else {
2066
- if (!root.reachedChildren.has(prev)) {
2067
- // is dead
2068
- this.node.app.scheduler.shouldClear = true;
2069
- return;
2070
- }
2071
- current = root.node;
2070
+ scheduler.delayedRenders.push(this);
2071
+ return;
2072
2072
  }
2073
2073
  }
2074
2074
  prev = current;
@@ -2082,12 +2082,13 @@
2082
2082
  const root = this.root;
2083
2083
  if (root) {
2084
2084
  try {
2085
+ this.bdom = true;
2085
2086
  this.bdom = node.renderFn();
2086
- root.setCounter(root.counter - 1);
2087
2087
  }
2088
2088
  catch (e) {
2089
2089
  handleError({ node, error: e });
2090
2090
  }
2091
+ root.setCounter(root.counter - 1);
2091
2092
  }
2092
2093
  }
2093
2094
  }
@@ -2102,8 +2103,6 @@
2102
2103
  // A fiber is typically locked when it is completing and the patch has not, or is being applied.
2103
2104
  // i.e.: render triggered in onWillUnmount or in willPatch will be delayed
2104
2105
  this.locked = false;
2105
- this.delayedRenders = [];
2106
- this.reachedChildren = new WeakSet();
2107
2106
  }
2108
2107
  complete() {
2109
2108
  const node = this.node;
@@ -2156,14 +2155,6 @@
2156
2155
  setCounter(newValue) {
2157
2156
  this.counter = newValue;
2158
2157
  if (newValue === 0) {
2159
- if (this.delayedRenders.length) {
2160
- for (let f of this.delayedRenders) {
2161
- if (f.root) {
2162
- f.render();
2163
- }
2164
- }
2165
- this.delayedRenders = [];
2166
- }
2167
2158
  this.node.app.scheduler.flush();
2168
2159
  }
2169
2160
  }
@@ -2178,6 +2169,7 @@
2178
2169
  let current = this;
2179
2170
  try {
2180
2171
  const node = this.node;
2172
+ node.children = this.childrenMap;
2181
2173
  node.app.constructor.validateTarget(this.target);
2182
2174
  if (node.bdom) {
2183
2175
  // this is a complicated situation: if we mount a fiber with an existing
@@ -2383,7 +2375,7 @@
2383
2375
  const node = getCurrent();
2384
2376
  let render = batchedRenderFunctions.get(node);
2385
2377
  if (!render) {
2386
- render = batched(node.render.bind(node));
2378
+ render = batched(node.render.bind(node, false));
2387
2379
  batchedRenderFunctions.set(node, render);
2388
2380
  // manual implementation of onWillDestroy to break cyclic dependency
2389
2381
  node.willDestroy.push(clearReactivesForCallback.bind(null, render));
@@ -2401,14 +2393,8 @@
2401
2393
  function component(name, props, key, ctx, parent) {
2402
2394
  let node = ctx.children[key];
2403
2395
  let isDynamic = typeof name !== "string";
2404
- if (node) {
2405
- if (node.status < 1 /* MOUNTED */) {
2406
- node.destroy();
2407
- node = undefined;
2408
- }
2409
- else if (node.status === 2 /* DESTROYED */) {
2410
- node = undefined;
2411
- }
2396
+ if (node && node.status === 2 /* DESTROYED */) {
2397
+ node = undefined;
2412
2398
  }
2413
2399
  if (isDynamic && node && node.component.constructor !== name) {
2414
2400
  node = undefined;
@@ -2439,15 +2425,15 @@
2439
2425
  throw new Error(`Cannot find the definition of component "${name}"`);
2440
2426
  }
2441
2427
  }
2442
- node = new ComponentNode(C, props, ctx.app, ctx);
2428
+ node = new ComponentNode(C, props, ctx.app, ctx, key);
2443
2429
  ctx.children[key] = node;
2444
2430
  node.initiateRender(new Fiber(node, parentFiber));
2445
2431
  }
2446
- parentFiber.root.reachedChildren.add(node);
2432
+ parentFiber.childrenMap[key] = node;
2447
2433
  return node;
2448
2434
  }
2449
2435
  class ComponentNode {
2450
- constructor(C, props, app, parent) {
2436
+ constructor(C, props, app, parent, parentKey) {
2451
2437
  this.fiber = null;
2452
2438
  this.bdom = null;
2453
2439
  this.status = 0 /* NEW */;
@@ -2463,7 +2449,8 @@
2463
2449
  this.willDestroy = [];
2464
2450
  currentNode = this;
2465
2451
  this.app = app;
2466
- this.parent = parent || null;
2452
+ this.parent = parent;
2453
+ this.parentKey = parentKey;
2467
2454
  this.level = parent ? parent.level + 1 : 0;
2468
2455
  applyDefaultProps(props, C);
2469
2456
  const env = (parent && parent.childEnv) || app.env;
@@ -2496,9 +2483,9 @@
2496
2483
  fiber.render();
2497
2484
  }
2498
2485
  }
2499
- async render(deep = false) {
2486
+ async render(deep) {
2500
2487
  let current = this.fiber;
2501
- if (current && current.root.locked) {
2488
+ if (current && (current.root.locked || current.bdom === true)) {
2502
2489
  await Promise.resolve();
2503
2490
  // situation may have changed after the microtask tick
2504
2491
  current = this.fiber;
@@ -2557,8 +2544,15 @@
2557
2544
  for (let child of Object.values(this.children)) {
2558
2545
  child._destroy();
2559
2546
  }
2560
- for (let cb of this.willDestroy) {
2561
- cb.call(component);
2547
+ if (this.willDestroy.length) {
2548
+ try {
2549
+ for (let cb of this.willDestroy) {
2550
+ cb.call(component);
2551
+ }
2552
+ }
2553
+ catch (e) {
2554
+ handleError({ error: e, node: this });
2555
+ }
2562
2556
  }
2563
2557
  this.status = 2 /* DESTROYED */;
2564
2558
  }
@@ -2624,6 +2618,7 @@
2624
2618
  bdom.mount(parent, anchor);
2625
2619
  this.status = 1 /* MOUNTED */;
2626
2620
  this.fiber.appliedToDom = true;
2621
+ this.children = this.fiber.childrenMap;
2627
2622
  this.fiber = null;
2628
2623
  }
2629
2624
  moveBefore(other, afterNode) {
@@ -2639,10 +2634,8 @@
2639
2634
  }
2640
2635
  _patch() {
2641
2636
  const hasChildren = Object.keys(this.children).length > 0;
2637
+ this.children = this.fiber.childrenMap;
2642
2638
  this.bdom.patch(this.fiber.bdom, hasChildren);
2643
- if (hasChildren) {
2644
- this.cleanOutdatedChildren();
2645
- }
2646
2639
  this.fiber.appliedToDom = true;
2647
2640
  this.fiber = null;
2648
2641
  }
@@ -2652,19 +2645,6 @@
2652
2645
  remove() {
2653
2646
  this.bdom.remove();
2654
2647
  }
2655
- cleanOutdatedChildren() {
2656
- const children = this.children;
2657
- for (const key in children) {
2658
- const node = children[key];
2659
- const status = node.status;
2660
- if (status !== 1 /* MOUNTED */) {
2661
- delete children[key];
2662
- if (status !== 2 /* DESTROYED */) {
2663
- node.destroy();
2664
- }
2665
- }
2666
- }
2667
- }
2668
2648
  // ---------------------------------------------------------------------------
2669
2649
  // Some debug helpers
2670
2650
  // ---------------------------------------------------------------------------
@@ -2684,7 +2664,7 @@
2684
2664
  constructor() {
2685
2665
  this.tasks = new Set();
2686
2666
  this.frame = 0;
2687
- this.shouldClear = false;
2667
+ this.delayedRenders = [];
2688
2668
  this.requestAnimationFrame = Scheduler.requestAnimationFrame;
2689
2669
  }
2690
2670
  addFiber(fiber) {
@@ -2695,16 +2675,22 @@
2695
2675
  * Other tasks are left unchanged.
2696
2676
  */
2697
2677
  flush() {
2678
+ if (this.delayedRenders.length) {
2679
+ let renders = this.delayedRenders;
2680
+ this.delayedRenders = [];
2681
+ for (let f of renders) {
2682
+ if (f.root && f.node.status !== 2 /* DESTROYED */ && f.node.fiber === f) {
2683
+ f.render();
2684
+ }
2685
+ }
2686
+ }
2698
2687
  if (this.frame === 0) {
2699
2688
  this.frame = this.requestAnimationFrame(() => {
2700
2689
  this.frame = 0;
2701
2690
  this.tasks.forEach((fiber) => this.processFiber(fiber));
2702
- if (this.shouldClear) {
2703
- this.shouldClear = false;
2704
- for (let task of this.tasks) {
2705
- if (task.node.status === 2 /* DESTROYED */) {
2706
- this.tasks.delete(task);
2707
- }
2691
+ for (let task of this.tasks) {
2692
+ if (task.node.status === 2 /* DESTROYED */) {
2693
+ this.tasks.delete(task);
2708
2694
  }
2709
2695
  }
2710
2696
  });
@@ -4018,8 +4004,11 @@
4018
4004
  let slotStr = [];
4019
4005
  for (let slotName in ast.slots) {
4020
4006
  const slotAst = ast.slots[slotName];
4021
- const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
4022
- const params = [`__render: ${name}, __ctx: ${ctxStr}`];
4007
+ const params = [];
4008
+ if (slotAst.content) {
4009
+ const name = this.compileInNewTarget("slot", slotAst.content, ctx, slotAst.on);
4010
+ params.push(`__render: ${name}, __ctx: ${ctxStr}`);
4011
+ }
4023
4012
  const scope = ast.slots[slotName].scope;
4024
4013
  if (scope) {
4025
4014
  params.push(`__scope: "${scope}"`);
@@ -4121,7 +4110,7 @@
4121
4110
  if (dynamic) {
4122
4111
  let name = this.generateId("slot");
4123
4112
  this.define(name, slotName);
4124
- blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}), ${dynamic}, ${scope})`;
4113
+ blockString = `toggler(${name}, callSlot(ctx, node, key, ${name}, ${dynamic}, ${scope}))`;
4125
4114
  }
4126
4115
  else {
4127
4116
  blockString = `callSlot(ctx, node, key, ${slotName}, ${dynamic}, ${scope})`;
@@ -4662,28 +4651,26 @@
4662
4651
  slotNode.removeAttribute("t-set-slot");
4663
4652
  slotNode.remove();
4664
4653
  const slotAst = parseNode(slotNode, ctx);
4665
- if (slotAst) {
4666
- let on = null;
4667
- let attrs = null;
4668
- let scope = null;
4669
- for (let attributeName of slotNode.getAttributeNames()) {
4670
- const value = slotNode.getAttribute(attributeName);
4671
- if (attributeName === "t-slot-scope") {
4672
- scope = value;
4673
- continue;
4674
- }
4675
- else if (attributeName.startsWith("t-on-")) {
4676
- on = on || {};
4677
- on[attributeName.slice(5)] = value;
4678
- }
4679
- else {
4680
- attrs = attrs || {};
4681
- attrs[attributeName] = value;
4682
- }
4654
+ let on = null;
4655
+ let attrs = null;
4656
+ let scope = null;
4657
+ for (let attributeName of slotNode.getAttributeNames()) {
4658
+ const value = slotNode.getAttribute(attributeName);
4659
+ if (attributeName === "t-slot-scope") {
4660
+ scope = value;
4661
+ continue;
4662
+ }
4663
+ else if (attributeName.startsWith("t-on-")) {
4664
+ on = on || {};
4665
+ on[attributeName.slice(5)] = value;
4666
+ }
4667
+ else {
4668
+ attrs = attrs || {};
4669
+ attrs[attributeName] = value;
4683
4670
  }
4684
- slots = slots || {};
4685
- slots[name] = { content: slotAst, on, attrs, scope };
4686
4671
  }
4672
+ slots = slots || {};
4673
+ slots[name] = { content: slotAst, on, attrs, scope };
4687
4674
  }
4688
4675
  // default slot
4689
4676
  const defaultContent = parseChildNodes(clone, ctx);
@@ -5029,7 +5016,7 @@
5029
5016
  }
5030
5017
  setup() { }
5031
5018
  render(deep = false) {
5032
- this.__owl__.render(deep);
5019
+ this.__owl__.render(deep === true);
5033
5020
  }
5034
5021
  }
5035
5022
  Component.template = "";
@@ -5326,13 +5313,19 @@
5326
5313
  }
5327
5314
  this.helpers = makeHelpers(this.getTemplate.bind(this));
5328
5315
  }
5329
- addTemplate(name, template, options = {}) {
5330
- if (name in this.rawTemplates && !options.allowDuplicate) {
5331
- throw new Error(`Template ${name} already defined`);
5316
+ addTemplate(name, template) {
5317
+ if (name in this.rawTemplates) {
5318
+ const rawTemplate = this.rawTemplates[name];
5319
+ const currentAsString = typeof rawTemplate === "string" ? rawTemplate : rawTemplate.outerHTML;
5320
+ const newAsString = typeof template === "string" ? template : template.outerHTML;
5321
+ if (currentAsString === newAsString) {
5322
+ return;
5323
+ }
5324
+ throw new Error(`Template ${name} already defined with different content`);
5332
5325
  }
5333
5326
  this.rawTemplates[name] = template;
5334
5327
  }
5335
- addTemplates(xml, options = {}) {
5328
+ addTemplates(xml) {
5336
5329
  if (!xml) {
5337
5330
  // empty string
5338
5331
  return;
@@ -5340,7 +5333,7 @@
5340
5333
  xml = xml instanceof Document ? xml : parseXML(xml);
5341
5334
  for (const template of xml.querySelectorAll("[t-name]")) {
5342
5335
  const name = template.getAttribute("t-name");
5343
- this.addTemplate(name, template, options);
5336
+ this.addTemplate(name, template);
5344
5337
  }
5345
5338
  }
5346
5339
  getTemplate(name) {
@@ -5411,7 +5404,7 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
5411
5404
  return prom;
5412
5405
  }
5413
5406
  makeNode(Component, props) {
5414
- return new ComponentNode(Component, props, this);
5407
+ return new ComponentNode(Component, props, this, null, null);
5415
5408
  }
5416
5409
  mountNode(node, target, options) {
5417
5410
  const promise = new Promise((resolve, reject) => {
@@ -5619,9 +5612,9 @@ See https://github.com/odoo/owl/blob/${hash}/doc/reference/app.md#configuration
5619
5612
  Object.defineProperty(exports, '__esModule', { value: true });
5620
5613
 
5621
5614
 
5622
- __info__.version = '2.0.0-beta-4';
5623
- __info__.date = '2022-03-29T13:50:04.545Z';
5624
- __info__.hash = '55dbc01';
5615
+ __info__.version = '2.0.0-beta-7';
5616
+ __info__.date = '2022-04-27T09:08:40.703Z';
5617
+ __info__.hash = '0cd66c8';
5625
5618
  __info__.url = 'https://github.com/odoo/owl';
5626
5619
 
5627
5620