From f1d3be3dee62964f45931fc6baa5d1ff8d61322e Mon Sep 17 00:00:00 2001 From: Andrew Kushnir Date: Wed, 22 Mar 2023 12:51:48 -0700 Subject: [PATCH] refactor(core): only annotate disconnected nodes in content projection (#49549) Previously, we've annotated all disconnected DOM nodes, even if they are not used in content projection. However, this situation is only possible in content projection and if it happens in other cases (for example, when a node was removed using direct DOM manipulations), this should be a mismatch error. PR Close #49549 --- packages/core/src/hydration/annotate.ts | 21 +++++++++++++++++-- .../platform-server/test/hydration_spec.ts | 4 +--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/core/src/hydration/annotate.ts b/packages/core/src/hydration/annotate.ts index 94ab7de715f..f61f6006db1 100644 --- a/packages/core/src/hydration/annotate.ts +++ b/packages/core/src/hydration/annotate.ts @@ -12,7 +12,7 @@ import {CONTAINER_HEADER_OFFSET, LContainer} from '../render3/interfaces/contain import {TI18n} from '../render3/interfaces/i18n'; import {TNode, TNodeType} from '../render3/interfaces/node'; import {RElement} from '../render3/interfaces/renderer_dom'; -import {isLContainer, isProjectionTNode, isRootView} from '../render3/interfaces/type_checks'; +import {isComponentHost, isLContainer, isProjectionTNode, isRootView} from '../render3/interfaces/type_checks'; import {HEADER_OFFSET, HOST, LView, RENDERER, TView, TVIEW, TViewType} from '../render3/interfaces/view'; import {unwrapRNode} from '../render3/util/view_utils'; import {TransferState} from '../transfer_state'; @@ -240,7 +240,7 @@ function serializeLView(lView: LView, context: HydrationContext): SerializedView // layer (in Domino) for now. Longer-term solution should not rely on the DOM emulation and // only use internal data structures and state to compute this information. if (!(tNode.type & TNodeType.Projection) && !!lView[i] && - !(unwrapRNode(lView[i]) as Node).isConnected) { + !(unwrapRNode(lView[i]) as Node).isConnected && isContentProjectedNode(tNode)) { ngh[DISCONNECTED_NODES] ??= []; ngh[DISCONNECTED_NODES].push(noOffsetIndex); continue; @@ -413,3 +413,20 @@ function insertCorruptedTextNodeMarkers( textNode.after(doc.createComment(marker)); } } + +/** + * Detects whether a given TNode represents a node that + * is being content projected. + */ +function isContentProjectedNode(tNode: TNode): boolean { + let currentTNode = tNode; + while (currentTNode != null) { + // If we come across a component host node in parent nodes - + // this TNode is in the content projection section. + if (isComponentHost(currentTNode)) { + return true; + } + currentTNode = currentTNode.parent as TNode; + } + return false; +} diff --git a/packages/platform-server/test/hydration_spec.ts b/packages/platform-server/test/hydration_spec.ts index d33de16f8f2..445c06fa975 100644 --- a/packages/platform-server/test/hydration_spec.ts +++ b/packages/platform-server/test/hydration_spec.ts @@ -3173,9 +3173,7 @@ describe('platform-server integration', () => { }); }); - // TODO(akushnir): we should only mark nodes within a content projection block as - // "disconnected" (avoid marking other disconnected nodes in a template) - xit('should handle element node mismatch', async () => { + it('should handle element node mismatch', async () => { @Component({ standalone: true, selector: 'app',