mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
Fix: Hydratation of empty lists next to components. (#3630)
* fix: hydration of empty VLists an additional (empty) VText was inserted in the second reconciliation pass, which tried to insert itself in an invalid position. This internal error was masked by a "fix" of the internal slot of components, which should have still been trapped to signal that the second fixup render was not yet run. * fixup: new lints from clippy
This commit is contained in:
parent
46ebdc8d4b
commit
95c29cc684
87
Cargo.lock
generated
87
Cargo.lock
generated
@ -2112,6 +2112,16 @@ dependencies = [
|
|||||||
"yew",
|
"yew",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nu-ansi-term"
|
||||||
|
version = "0.46.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
|
||||||
|
dependencies = [
|
||||||
|
"overload",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "num-traits"
|
name = "num-traits"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
@ -2197,6 +2207,12 @@ dependencies = [
|
|||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "overload"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "papergrid"
|
name = "papergrid"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
@ -2771,6 +2787,15 @@ dependencies = [
|
|||||||
"digest",
|
"digest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sharded-slab"
|
||||||
|
version = "0.1.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "signal-hook-registry"
|
name = "signal-hook-registry"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
@ -2787,14 +2812,14 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"clap",
|
"clap",
|
||||||
"futures 0.3.29",
|
"futures 0.3.29",
|
||||||
"log",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
|
"time",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tracing-subscriber",
|
||||||
|
"tracing-web",
|
||||||
"uuid",
|
"uuid",
|
||||||
"warp",
|
"warp",
|
||||||
"wasm-bindgen-futures",
|
|
||||||
"wasm-logger",
|
|
||||||
"yew",
|
"yew",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3033,6 +3058,16 @@ dependencies = [
|
|||||||
"syn 2.0.38",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread_local"
|
||||||
|
version = "1.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
@ -3040,6 +3075,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
|
checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
|
"js-sys",
|
||||||
"powerfmt",
|
"powerfmt",
|
||||||
"serde",
|
"serde",
|
||||||
"time-core",
|
"time-core",
|
||||||
@ -3274,6 +3310,45 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
"valuable",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-log"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"once_cell",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-subscriber"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
|
||||||
|
dependencies = [
|
||||||
|
"nu-ansi-term",
|
||||||
|
"sharded-slab",
|
||||||
|
"smallvec",
|
||||||
|
"thread_local",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-log",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-web"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b9e6a141feebd51f8d91ebfd785af50fca223c570b86852166caa3b141defe7c"
|
||||||
|
dependencies = [
|
||||||
|
"js-sys",
|
||||||
|
"tracing-core",
|
||||||
|
"tracing-subscriber",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"web-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3410,6 +3485,12 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "valuable"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "vcpkg"
|
name = "vcpkg"
|
||||||
version = "0.2.15"
|
version = "0.2.15"
|
||||||
|
|||||||
@ -25,7 +25,9 @@ macro_rules! generate_callback_impls {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::vtable_address_comparisons)]
|
// We are okay with comparisons from different compilation units to result in false
|
||||||
|
// not-equal results. This should only lead in the worst-case to some unneeded re-renders.
|
||||||
|
#[allow(ambiguous_wide_pointer_comparisons)]
|
||||||
impl<IN, OUT> PartialEq for $callback<IN, OUT> {
|
impl<IN, OUT> PartialEq for $callback<IN, OUT> {
|
||||||
fn eq(&self, other: &$callback<IN, OUT>) -> bool {
|
fn eq(&self, other: &$callback<IN, OUT>) -> bool {
|
||||||
let ($callback { cb }, $callback { cb: rhs_cb }) = (self, other);
|
let ($callback { cb }, $callback { cb: rhs_cb }) = (self, other);
|
||||||
|
|||||||
@ -11,7 +11,7 @@ use super::{test_log, BNode, BSubtree, DomSlot};
|
|||||||
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
|
use crate::dom_bundle::{Reconcilable, ReconcileTarget};
|
||||||
use crate::html::AnyScope;
|
use crate::html::AnyScope;
|
||||||
use crate::utils::RcExt;
|
use crate::utils::RcExt;
|
||||||
use crate::virtual_dom::{Key, VList, VNode, VText};
|
use crate::virtual_dom::{Key, VList, VNode};
|
||||||
|
|
||||||
/// This struct represents a mounted [VList]
|
/// This struct represents a mounted [VList]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -427,15 +427,7 @@ impl Reconcilable for VList {
|
|||||||
// The left items are known since we want to insert them
|
// The left items are known since we want to insert them
|
||||||
// (self.children). For the right ones, we will look at the bundle,
|
// (self.children). For the right ones, we will look at the bundle,
|
||||||
// i.e. the current DOM list element that we want to replace with self.
|
// i.e. the current DOM list element that we want to replace with self.
|
||||||
let (key, mut fully_keyed, mut lefts) = self.split_for_blist();
|
let (key, fully_keyed, lefts) = self.split_for_blist();
|
||||||
|
|
||||||
if lefts.is_empty() {
|
|
||||||
// Without a placeholder the next element becomes first
|
|
||||||
// and corrupts the order of rendering
|
|
||||||
// We use empty text element to stake out a place
|
|
||||||
lefts.push(VText::new("").into());
|
|
||||||
fully_keyed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let rights = &mut blist.rev_children;
|
let rights = &mut blist.rev_children;
|
||||||
test_log!("lefts: {:?}", lefts);
|
test_log!("lefts: {:?}", lefts);
|
||||||
|
|||||||
@ -50,6 +50,10 @@ mod feat_hydration {
|
|||||||
|
|
||||||
#[cfg(feature = "hydration")]
|
#[cfg(feature = "hydration")]
|
||||||
pub(super) use feat_hydration::*;
|
pub(super) use feat_hydration::*;
|
||||||
|
#[cfg(test)]
|
||||||
|
// this is needed because clippy doesn't like the import not being used
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
pub(super) use tests::*;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
@ -87,8 +91,3 @@ mod tests {
|
|||||||
(root, scope, parent, sibling)
|
(root, scope, parent, sibling)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
// this is needed because clippy doesn't like the import not being used
|
|
||||||
#[allow(unused_imports)]
|
|
||||||
pub(super) use tests::*;
|
|
||||||
|
|||||||
@ -130,7 +130,10 @@ where
|
|||||||
T: Reducible,
|
T: Reducible,
|
||||||
{
|
{
|
||||||
fn eq(&self, rhs: &Self) -> bool {
|
fn eq(&self, rhs: &Self) -> bool {
|
||||||
#[allow(clippy::vtable_address_comparisons)]
|
// We are okay with comparisons from different compilation units to result in false
|
||||||
|
// not-equal results. This should only lead in the worst-case to some unneeded
|
||||||
|
// re-renders.
|
||||||
|
#[allow(ambiguous_wide_pointer_comparisons)]
|
||||||
Rc::ptr_eq(&self.dispatch, &rhs.dispatch)
|
Rc::ptr_eq(&self.dispatch, &rhs.dispatch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -430,7 +430,9 @@ impl ComponentState {
|
|||||||
fields(component.id = self.comp_id)
|
fields(component.id = self.comp_id)
|
||||||
)]
|
)]
|
||||||
fn render(&mut self, shared_state: &Shared<Option<ComponentState>>) {
|
fn render(&mut self, shared_state: &Shared<Option<ComponentState>>) {
|
||||||
match self.inner.view() {
|
let view = self.inner.view();
|
||||||
|
tracing::trace!(?view, "render result");
|
||||||
|
match view {
|
||||||
Ok(vnode) => self.commit_render(shared_state, vnode),
|
Ok(vnode) => self.commit_render(shared_state, vnode),
|
||||||
Err(RenderError::Suspended(susp)) => self.suspend(shared_state, susp),
|
Err(RenderError::Suspended(susp)) => self.suspend(shared_state, susp),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -642,7 +642,7 @@ mod feat_hydration {
|
|||||||
use web_sys::{Element, HtmlScriptElement};
|
use web_sys::{Element, HtmlScriptElement};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dom_bundle::{BSubtree, DomSlot, DynamicDomSlot, Fragment};
|
use crate::dom_bundle::{BSubtree, DynamicDomSlot, Fragment};
|
||||||
use crate::html::component::lifecycle::{ComponentRenderState, CreateRunner, RenderRunner};
|
use crate::html::component::lifecycle::{ComponentRenderState, CreateRunner, RenderRunner};
|
||||||
use crate::scheduler;
|
use crate::scheduler;
|
||||||
use crate::virtual_dom::Collectable;
|
use crate::virtual_dom::Collectable;
|
||||||
@ -679,12 +679,6 @@ mod feat_hydration {
|
|||||||
let collectable = Collectable::for_component::<COMP>();
|
let collectable = Collectable::for_component::<COMP>();
|
||||||
|
|
||||||
let mut fragment = Fragment::collect_between(fragment, &collectable, &parent);
|
let mut fragment = Fragment::collect_between(fragment, &collectable, &parent);
|
||||||
let next_sibling = if let Some(n) = fragment.front() {
|
|
||||||
Some(n.clone())
|
|
||||||
} else {
|
|
||||||
fragment.sibling_at_end().cloned()
|
|
||||||
};
|
|
||||||
internal_ref.reassign(DomSlot::create(next_sibling));
|
|
||||||
|
|
||||||
let prepared_state = match fragment
|
let prepared_state = match fragment
|
||||||
.back()
|
.back()
|
||||||
|
|||||||
@ -105,7 +105,7 @@ impl Future for Suspension {
|
|||||||
|
|
||||||
let waker = cx.waker().clone();
|
let waker = cx.waker().clone();
|
||||||
self.listen(Callback::from(move |_| {
|
self.listen(Callback::from(move |_| {
|
||||||
waker.clone().wake();
|
waker.wake_by_ref();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Poll::Pending
|
Poll::Pending
|
||||||
|
|||||||
@ -187,9 +187,11 @@ impl PartialEq for Listeners {
|
|||||||
lhs.iter()
|
lhs.iter()
|
||||||
.zip(rhs.iter())
|
.zip(rhs.iter())
|
||||||
.all(|(lhs, rhs)| match (lhs, rhs) {
|
.all(|(lhs, rhs)| match (lhs, rhs) {
|
||||||
(Some(lhs), Some(rhs)) =>
|
(Some(lhs), Some(rhs)) => {
|
||||||
{
|
// We are okay with comparisons from different compilation units to
|
||||||
#[allow(clippy::vtable_address_comparisons)]
|
// result in false not-equal results. This should only lead in the
|
||||||
|
// worst-case to some unneeded re-renders.
|
||||||
|
#[allow(ambiguous_wide_pointer_comparisons)]
|
||||||
Rc::ptr_eq(lhs, rhs)
|
Rc::ptr_eq(lhs, rhs)
|
||||||
}
|
}
|
||||||
(None, None) => true,
|
(None, None) => true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user