Previously, when trying to mount a portal into a target that would be
mounted in the same render as the portal itself, the portal content
could not be mounted correctly. In a previous attempt to fix it, a
mistake was made while writing the test causing us to incorrectly
believe in now worked, when in fact, it would just move the portal
content to the end of its parent but without changing it.
This commit fixes the issue by making the `moveBeforeDOMNode` method of
VNodes accept a second optional parameter which is the parent in which
the element should be moved, defaulting to the current parent. This
results in identical behaviour when a parent is not specified, but when
it is, the VNode is "reparented" to the passed parent, which is what is
now done by the Portal
Currently, the `moveBefore` method on VNodes assumes that the `other`
VNode it receives is of the same type, and that the entire VNode tree
below that other VNode has the exact same structure. While this is
correct in most cases, it breaks down when there is a VToggler somewhere
in the VNode tree, as the structure below a VToggler can be very
different from the structure below another VToggler that was created
from the same compiled code. For example, two iterations of a t-foreach
that contains a <t t-component="..."/> may spawn different components,
and different components obviously have different structures.
One way to fix this is to remove the assumption that the structure of
the `this` block tree in moveBefore is the same as the structure
of the `other` block tree, and instead, always give the concrete DOM
node before which we want to move the current VNode instead of giving it
a VNode and an afterNode as a fallback. One problem with this solution
is that it degrades performance in the "standard" case, where a
t-foreach contains no VToggler anywhere in its block tree, as retrieving
the first concrete DOM node requires calling firstNode() which
recursively traverses the entire tree.
To avoid this performance penalty in the standard case, we opt to only
go down this route whenever we encounter a VToggler when calling
`moveBefore`. This requires that we maintain two separate methods, one
to move a VNode before another VNode of assumed similar structure, which
is basically the current implementation of `moveBefore` for all VNode
types except VToggler, and one implementation that moves a VNode before
a concrete DOM node. This method needs to be implemented for all VNode
types, as all VNode types can be descendants of a VToggler. This method
will only be called from one place: the `moveBeforeVNode` method of the
toggler, which is the point where we realize that the assumption of
identical structure breaks down.
Co-authored-by: Bruno Boi <boi@odoo.com>
Currently, if a component has a default slot defined with t-set-slot,
and also content that compiles to something (eg, text or even a comment
node), the content takes priority over the t-set-slot. As t-set-slot is
more explicity, it should have priority.
Currently when checking for duplicate keys, we insert the value of the
key as is in a set then check for unicity against those. When the key is
an object, we check for duplicates based on object identity, whereas the
keys are used by owl as strings, and so using objects can cause
duplicate key errors that do not throw correctly but crash in the owl
internals.
This commit fixes that by making the duplicate checking code serialize
the key to string before insertion and when comparing against existing
keys.
The t-out directive is compiled internally into a LazyValue, which
represents a value that may or may not be created sometimes in the
future. It can also be reused more than once, and this is where there
may be an issue: if a component is contained in the lazyvalue, it needs
a unique key (coming from the t-foreach) to be properly indexed in the
parent children map. However, the LazyValue does not keep the key
information, so it is not able to provide it to its content.
The fix is then quite clear: the LazyValue class should store the key
information, and provides it to its content. This allows the LazyValue
to be used multiple times, in any place in a template.
closes#1270
Before this commit, the template compiler would guess the next block id
that will be generated when compiling the body of a tcall. This is
correct IF there are not nested t-call, but otherwise wrong, because the
next block id could be mixed up: the first t-call would save the next
block id (let's say n), then the inner t-call would also save the same
block id (so, n), will then generate its own block (n+1), then the outer
t-call would use the block n index instead of n+1
The best fix, in my opinion, is to make sure we get the next block var
name, so we do not have to guess. To do that, each compile block type
function needs to properly return the information.
closes#1267
A recent commit fixes the props validation code to make it work
regardless of the rendering context (important with the recent
t-call-context directive). Unfortunately, it then breaks props
validation through slots, because it assumed that the parent node in the
virtual node was the parent of the component, but it is not necessarily
true.
To fix this, we can use a simple property of the template functions:
they are bound to the current instance of the component, so we can
simply use "this"
Before this commit, the portal wouldn't work when its target is created
after the portal content, since it wouldn't be able to mount the dom at
the correct location.
With this commit, we work around the issue by mounting the portal
content at the portal location, then when the Portal component is
mounted, moving it to its correct location.
The big downside with that approach is that the portal content is
(sometimes) rendered and mounted at a location, THEN mounted in another
location. I think that it is most of the time not an issue, but one
could argue that it is inconsistent: some specific code could work at
one point, then fail in a different very similar situation (for example,
iframes don't support very well being moved around). On the flip side,
having the portal work as expected is very useful, and may be worth the
tradeoff.
closes#1250
The code for props validation assumed that the rendering context was a
component. This was actually true when it was written, but is no longer
true since t-call-context was introduced.
Because of that, it would crash when trying to access the internals of
the component, such as the static components object.
The fix is simple: instead of passing the context to the props
validation code, which can now be anything, we pass the component node,
which is guaranteed to give a reference to the component (and also to
the app). This also make the code slightly simpler.
closes#1261
This commit makes it so that when an error occurs in an owl app and none
of the registered error handlers are able to handle it, we rethrow the
error instead of just logging it to the console and swallowing it. This
allows users of owl to handle errors that happen in owl applications by
using event listeners for error and unhandledrejection events on the
window.
Previously, event handlers would not work when an app was mounted in an
iframe, this is caused by a guard in the event handler that checks that
the target element is still in the document, but it doesn't check
against the correct document in the case of an iframe.
This commit changes the check to check against the target's
ownerDocument.
Before this commit, a crash could occur when a component with no props
is defined in a slot, and that slot is conditionally displayed in
multiple locations.
The reason for that is that the key provided to the callSlot function
was identical, so from the perspective of the component function, it was
not possible to make the difference between a component located in
either places. With this commit, we make sure that a unique key is used
when a slot is reused in a template (or if it is dynamic, because in
that case, we have no idea at compile time if it will be unique or not)
closes#1246
Previously, attempting to mount an app in an iframe would crash, saying
that the target is not a valid DOM element, this is because instanceof
checks do not work cross-frame as global objects do not have the same
identity in frames as with the main window. This commit fixes that by
making sure the target is an instance of HTMLElement of the
corresponding window, and checks that the corresponding document body
contains it.
Recently, we made it so that when a component is rendered, it always
updates the property values for computed properties. This was done by
wrapping the value in a String or Boolean object. One issue with this is
that wrapping a falsy value in a String doesn't yield an empty string,
but a string containing the value as text (eg new String(undefined) ->
"undefined"), which causes the value to not remain empty as per the
spec. This commit fixes that by adding a fallback to the empty string
for falsy values before converting to a String object.
closes: #1236
Previously, when wrapping errors in wrapError, if the error was not an
actual error object, we wouldn't set the cause property on the wrapping
error correctly. The "instanceof Error" check is simply there so that we
can know whether we can add the original errors message to the wrapping
error, but the line that sets the error's cause was mistakenly moved
into that condition.
This commit also fixes the wrapping error's message in the case of
non-Error objects, to avoid having "the following error occurred in
hookname:" with nothing after the colon which is confusing/misleading.
This commit makes all errors thrown in owl use a custom error class. The
main point of this is to always wrap user-code errors that happen during
the owl lifecycle so that they can be treated uniformly in onError by
checking the cause property, and also allows user code to differenciate
owl errors from non-owl errors reliably at runtime.
Before this commit, wrapping an error occurring in async code
would result in an unhandledpromise exception, because we created
another promise, that would be rejected and that we didn't catch.
Before this commit, owl would incorrectly skip patching html properties
when the new value is the same as the value used in the previous render.
However, this is incorrect, since the user may have changed the value by
clicking on a checkbox, or changing some text in an input.
So, to be more correct, owl has to force the update whenever it
encounters a prop. This is however hard to do without impacting the
main update loop, so we kind of work around the problem by using String
and Boolean instance, instead of primitive values.
Before this commit, whenever Owl encounter a t-if, it generates an
anchor (a "hole") in the current block being compiled. However, in some
cases, the content of the t-if may not have any content at all, and the
anchor is then useless. Worse, the code generating the anchor generates
an index based on the number of sub blocks, but if there is no content,
the next anchor being created will have the same index, which then may
cause weird bugs.
A possible way to fix this could be to make sure we increment properly
the anchor index, but we could even do better: not having an anchor at
all.
Before this commit, the generated code for the component directive was
using the current context as the place to look for static informations
(such as the sub components). However, it is not entirely correct, since
the current context may be different than the current component (which
is easily accessed by using the this variable).
Also, while doing this, we fix some issues in the t-set directive, which
as calling lazy values with the wrong this.
Previously, having a leading or trailing space caused
HTMLElement.classList.add to be called with an empty string (because we
are splitting on whitespace), which is not allowed and caused a crash.
This commit fixes that by trimming the keys of the class object in the
same way that we already do it for class strings.
Before this commit, the generated code crashed if an attribute in
the template contained backticks. The compiler output something
like
```js
let block1 = createBlock(`<div foo="`bar`"/>`);
```
The backticks are now properly escaped.
This commit add supports for value types in prop validation. To describe
a value V type, one has to simply write {value: V}.
Note that this commit also improves the validation typing.
closes#1198closes#910
Before this commit, using t-on on t-component was not correctly
implemented by owl: when more than one component shared the same
parentelement and have an handler with the same name, the second handler
would override the first (using an internal key). With this commit, we
simply recreate event handler for each instance of a catcher block. Note
that it means that using t-on on components is slightly slower now.
Also, this commit fixes an additional issue that was noticed: the
catcher block would not update its internal handler properly, so it
would not behave properly after being patched.
closes#1199
Before this commit, Owl had a bad interaction with the static
defaultProps code. The props comparison would be done with the new props
object (unmodified) and the current props object (with default props
applied), so the comparison would always return false, which in turn,
causes additional useless renderings.
This commit modifies component node to keep a reference to the
(unmodified) props object so we can compare it as expected.
The goal is to get closer to the possibility of using Owl without the
compiler. This commit removes Portal dependency on the compiler, and
opens the way to register pre-compiled template functions.
This commit reworks the props validation code in order to extract a
generic validate utility function. The validation should be more robust,
with better error messages, and at the same time, it supports `*` in a
shape object.
And as a bonus, it is now typesafe, and the static props object is now
typed.
closes#1190
Previously, the support for the "in" operator was iffy, and "new" was
not supported at all, "in" would fail when checking if a nested property
was in something else, as the leading space would be stripped, and "new"
would fail because the following space would be stripped.
This commit fixes that by preserving the significant whitespace where
necessary.
Previously, components would automatically call useState on their props,
so that changes deeply within props would automatically cause the
component to be rendered. This can be useful when passing a piece of
state to children or descendants.
One problem with this is that all props implicitly become reactive, even
if the object that was passed as a props was not. The problem with that
being that since the original object is not reactive, any change made by
the parent will not go through the reactivity system and the children
won't be notified of the change, in essence, this reactive object is
essentially useless, while having a real cost: traversing reactive
objects creates more reactive objects, and those objects are all
proxies. This is expensive for basically no benefit, while also making
it more difficult to debug code that involves those objects.
This commit fixes that by only calling useState on objects that are
already reactive, allowing the usecase described in the first paragraph
without the drawbacks described in the second.
With this commit, props validation error messages will include a more
developer-friendly error message, avoiding the need to investigate in
the Developer Tools why a complex props structure is invalid.
If you declare a child component which is not actually a Component,
the error message is not very friendly and not very helpfull to find
what happens and which child component is wrong.
```
const ChildComponent = "not a component constructor";
class MyComponent extends Component {
static components = { ChildComponent };
}
```
This commit improves the type declaration for those working with Typescript
and adds a runtime check for javascript codebases
Before this commit, there were 2 different ways of generating variable
identifiers. And it was possible to have a situation with two different
variable in a template with the same identifier, which caused a crash.
This commit ensures that we go through a unique helper method, so this
cannot occur anymore. Also, the code is slightly simpler.