-
Notifications
You must be signed in to change notification settings - Fork 26.2k
/
Copy pathcleanup.ts
163 lines (151 loc) Β· 5.55 KB
/
cleanup.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ApplicationRef} from '../application/application_ref';
import {DehydratedDeferBlock} from '../defer/interfaces';
import {DehydratedBlockRegistry} from '../defer/registry';
import {
CONTAINER_HEADER_OFFSET,
DEHYDRATED_VIEWS,
LContainer,
} from '../render3/interfaces/container';
import {Renderer} from '../render3/interfaces/renderer';
import {RNode} from '../render3/interfaces/renderer_dom';
import {isLContainer, isLView} from '../render3/interfaces/type_checks';
import {HEADER_OFFSET, HOST, LView, PARENT, RENDERER, TVIEW} from '../render3/interfaces/view';
import {nativeRemoveNode} from '../render3/dom_node_manipulation';
import {validateSiblingNodeExists} from './error_handling';
import {cleanupI18nHydrationData} from './i18n';
import {DEFER_BLOCK_ID, DehydratedContainerView, NUM_ROOT_NODES} from './interfaces';
import {getLNodeForHydration} from './utils';
/**
* Removes all dehydrated views from a given LContainer:
* both in internal data structure, as well as removing
* corresponding DOM nodes that belong to that dehydrated view.
*/
export function removeDehydratedViews(lContainer: LContainer) {
const views = lContainer[DEHYDRATED_VIEWS] ?? [];
const parentLView = lContainer[PARENT];
const renderer = parentLView[RENDERER];
const retainedViews = [];
for (const view of views) {
// Do not clean up contents of `@defer` blocks.
// The cleanup for this content would happen once a given block
// is triggered and hydrated.
if (view.data[DEFER_BLOCK_ID] !== undefined) {
retainedViews.push(view);
} else {
removeDehydratedView(view, renderer);
ngDevMode && ngDevMode.dehydratedViewsRemoved++;
}
}
// Reset the value to an array to indicate that no
// further processing of dehydrated views is needed for
// this view container (i.e. do not trigger the lookup process
// once again in case a `ViewContainerRef` is created later).
lContainer[DEHYDRATED_VIEWS] = retainedViews;
}
export function removeDehydratedViewList(deferBlock: DehydratedDeferBlock) {
const {lContainer} = deferBlock;
const dehydratedViews = lContainer[DEHYDRATED_VIEWS];
if (dehydratedViews === null) return;
const parentLView = lContainer[PARENT];
const renderer = parentLView[RENDERER];
for (const view of dehydratedViews) {
removeDehydratedView(view, renderer);
ngDevMode && ngDevMode.dehydratedViewsRemoved++;
}
}
/**
* Helper function to remove all nodes from a dehydrated view.
*/
function removeDehydratedView(dehydratedView: DehydratedContainerView, renderer: Renderer) {
let nodesRemoved = 0;
let currentRNode = dehydratedView.firstChild;
if (currentRNode) {
const numNodes = dehydratedView.data[NUM_ROOT_NODES];
while (nodesRemoved < numNodes) {
ngDevMode && validateSiblingNodeExists(currentRNode);
const nextSibling: RNode = currentRNode.nextSibling!;
nativeRemoveNode(renderer, currentRNode, false);
currentRNode = nextSibling;
nodesRemoved++;
}
}
}
/**
* Walks over all views within this LContainer invokes dehydrated views
* cleanup function for each one.
*/
export function cleanupLContainer(lContainer: LContainer) {
removeDehydratedViews(lContainer);
// The host could be an LView if this container is on a component node.
// In this case, descend into host LView for further cleanup. See also
// LContainer[HOST] docs for additional information.
const hostLView = lContainer[HOST];
if (isLView(hostLView)) {
cleanupLView(hostLView);
}
for (let i = CONTAINER_HEADER_OFFSET; i < lContainer.length; i++) {
cleanupLView(lContainer[i] as LView);
}
}
/**
* Walks over `LContainer`s and components registered within
* this LView and invokes dehydrated views cleanup function for each one.
*/
function cleanupLView(lView: LView) {
cleanupI18nHydrationData(lView);
const tView = lView[TVIEW];
for (let i = HEADER_OFFSET; i < tView.bindingStartIndex; i++) {
if (isLContainer(lView[i])) {
const lContainer = lView[i];
cleanupLContainer(lContainer);
} else if (isLView(lView[i])) {
// This is a component, enter the `cleanupLView` recursively.
cleanupLView(lView[i]);
}
}
}
/**
* Walks over all views registered within the ApplicationRef and removes
* all dehydrated views from all `LContainer`s along the way.
*/
export function cleanupDehydratedViews(appRef: ApplicationRef) {
const viewRefs = appRef._views;
for (const viewRef of viewRefs) {
const lNode = getLNodeForHydration(viewRef);
// An `lView` might be `null` if a `ViewRef` represents
// an embedded view (not a component view).
if (lNode !== null && lNode[HOST] !== null) {
if (isLView(lNode)) {
cleanupLView(lNode);
} else {
// Cleanup in all views within this view container
cleanupLContainer(lNode);
}
ngDevMode && ngDevMode.dehydratedViewsCleanupRuns++;
}
}
}
/**
* post hydration cleanup handling for defer blocks that were incrementally
* hydrated. This removes all the jsaction attributes, timers, observers,
* dehydrated views and containers
*/
export function cleanupHydratedDeferBlocks(
deferBlock: DehydratedDeferBlock | null,
hydratedBlocks: string[],
registry: DehydratedBlockRegistry,
appRef: ApplicationRef,
): void {
if (deferBlock !== null) {
registry.cleanup(hydratedBlocks);
cleanupLContainer(deferBlock.lContainer);
cleanupDehydratedViews(appRef);
}
}