Previously, if a reactive was observing the presence of an item in a
set that was originally not present, it would get notified when that
item was "deleted" from the set (even though there was nothing to delete
and the set did not change). The same applied to the key already being
present and then being "added". The same thing occured with Map, both
for presence but also for values (setting a key to the value it was
already set to would notify).
This commit fixes that.
When attempting to create a reactive object, we first check if the
target can be made reactive, this is done with Object.toString, which
internally reads the Symbol.toStringTag on the underlying object. When
trying to make a reactive object from another, for example when
reobserving a reactive or when reading a reactive object from the
context of another, this would read the subscribe the original object to
the Symbol.toStringTag property.
This commit fixes that by calling Object.toString on the underlying
target object where applicable.
Previously, when there was a reactive object in the prototype chain of a
different object, it would get notified of the first write to a property
that existed on the reactive object but did not yet exist on the other
object, despite the value of that property not actually getting written
to the reactive and hence the value not getting changed.
This was caused by the fact that we assumed that Reflect.set would set
the value on the target, when in fact, the value is set on the object
that underlies the `receiver`. When a reactive object is part of the
prototype chain, the first write on a key triggers the set trap but the
receiver is not the reactive object, and so Reflect.set doesn't modify
the target, but adds the new key to the object that's lower in the
prototype chain.
This commit fixes that by actually reading the value from the target
instead of assuming that it was changed by Reflect.set
This commit also removes the special symbols SKIP and TARGET, as there
is no reliable way to check whether these keys are present on the object
itself or on its prototype chain, which can cause issue when trying to
create reactive objects from objects with other reactive objects in
their prototype chain, or to create reactive objects from objects with
non-reactive objects in their prototype chain. To solve this problem, we
simply use a WeakMap that maps reactives to their targets, and a WeakSet
that contains all skipped objects.
Previously, calling the batched function from within the callback being
batched would fail as it would be treated as part of the same batch.
This commit fixes that by scheduling the reset of the "called" flag
before calling the callback. This means that all microtasks that were
already in the microtask queue when a batch is about to run are treated
as part of the batch, and all microtasks that will be added by the
callback are not.
This crash was caused by the fact that Proxies *must* return the value of the
property on the target when that property is non-writeable and
non-configurable. Since the reactivity system always attempts to proxify the
value from the target, this crashes.
This commit fixes that by not proxifying such values. This however means that
from that point on, we have escaped the reactivity system and will not
subscribe to any changes in that object or its children.
With this commit, component only render child
components if they have different props (shallow
equality). Otherwise, we trust the reactivity
system to make sure that all impacted components
are updated
This commit makes the reactivity system more fine grained and makes it
more eager to stop observing keys or objects when they are modified,
this results in fewer "false positive" notifications.
The deletion of a key in an observed object did clear the observers of
that key but did not clear the atoms created (if any) when the key existed
(e.g. on the key value if it was trackable). This can lead to a growing
set of atoms that are useless but kept in memory if a lot of keys are
added/deleted. The same thing can happen if a key value is changed many
times and the values are trackable.
Here we fix the problem by
- keeping tracks of the objects that have been observed by an external call
to the method "atom" (we call them seeds).
- remove all observer atoms that are not seeds for observers of at least
one key deletion or key value change.
Note the fix is in some sense partial: a user could use the "atom" primitive
making the new system uncapable of avoiding a leak (see test "atom on an
object with a getter 3" where a getter is used in a weird way in an
observed object).
Aim to replace the abstraction "Context" from OWL 1 with the new primitives
"atom" and "useState":
- notification is done only after a batch of modifications.
- observers are notified at most once for a batch.
- an observer of type component is notified (and rerendered)
only if it does not have an ancestor that has to be notified for the
same batch of operations (anywhere in the web of references!).
- notification of components is done on all levels "simultaneously".
Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Géry Debongnie <ged@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>
fine grained reactivity:
existing key in source changes --> only observer having read the key are notified
add/delete key in source changes --> all source observers are notified
Co-authored-by: Aaron Bohy <aab@odoo.com>
Co-authored-by: Géry Debongnie <ged@odoo.com>
Co-authored-by: Mathieu Duckerts-Antoine <dam@odoo.com>