diff --git a/packages/core/src/render3/assert.ts b/packages/core/src/render3/assert.ts index a05546be2c..1547afac00 100644 --- a/packages/core/src/render3/assert.ts +++ b/packages/core/src/render3/assert.ts @@ -11,6 +11,7 @@ import {assertDefined, assertEqual, throwError} from '../util/assert'; import {getComponentDef, getNgModuleDef} from './definition'; import {TNode} from './interfaces/node'; import {LView} from './interfaces/view'; +import {isLContainer, isLView} from './util'; export function assertComponentType( @@ -44,3 +45,21 @@ export function assertDataNext(lView: LView, index: number, arr?: any[]) { assertEqual( arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`); } + +export function assertLContainerOrUndefined(value: any): void { + value && assertEqual(isLContainer(value), true, 'Expecting LContainer or undefined or null'); +} + +export function assertLContainer(value: any): void { + assertDefined(value, 'LContainer must be defined'); + assertEqual(isLContainer(value), true, 'Expecting LContainer'); +} + +export function assertLViewOrUndefined(value: any): void { + value && assertEqual(isLView(value), true, 'Expecting LView or undefined or null'); +} + +export function assertLView(value: any) { + assertDefined(value, 'LView must be defined'); + assertEqual(isLView(value), true, 'Expecting LView'); +} diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index d586a2640e..087516d321 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -134,7 +134,7 @@ export function renderComponent( component = createRootComponent( componentView, componentDef, rootView, rootContext, opts.hostFeatures || null); - addToViewTree(rootView, HEADER_OFFSET, componentView); + addToViewTree(rootView, componentView); refreshDescendantViews(rootView); // creation mode pass rootView[FLAGS] &= ~LViewFlags.CreationMode; diff --git a/packages/core/src/render3/discovery_utils.ts b/packages/core/src/render3/discovery_utils.ts index a24f83be4b..a64bb4b5fa 100644 --- a/packages/core/src/render3/discovery_utils.ts +++ b/packages/core/src/render3/discovery_utils.ts @@ -8,13 +8,14 @@ import {Injector} from '../di/injector'; +import {assertLView} from './assert'; import {discoverLocalRefs, getComponentAtNodeIndex, getDirectivesAtNodeIndex, getLContext} from './context_discovery'; import {NodeInjector} from './di'; import {LContext} from './interfaces/context'; import {DirectiveDef} from './interfaces/definition'; import {TElementNode, TNode, TNodeProviderIndexes} from './interfaces/node'; -import {CLEANUP, CONTEXT, FLAGS, HOST, LView, LViewFlags, PARENT, RootContext, TVIEW} from './interfaces/view'; -import {getRootView, readElementValue, renderStringify} from './util'; +import {CLEANUP, CONTEXT, FLAGS, HOST, LView, LViewFlags, TVIEW} from './interfaces/view'; +import {getLViewParent, getRootContext, readElementValue, renderStringify} from './util'; @@ -95,28 +96,18 @@ export function getContext(element: Element): T|null { */ export function getViewComponent(element: Element | {}): T|null { const context = loadLContext(element) !; - let lView: LView = context.lView; - while (lView[PARENT] && lView[HOST] === null) { + let lView = context.lView; + let parent: LView|null; + ngDevMode && assertLView(lView); + while (lView[HOST] === null && (parent = getLViewParent(lView) !)) { // As long as lView[HOST] is null we know we are part of sub-template such as `*ngIf` - lView = lView[PARENT] !; + if (parent) { + lView = parent; + } } - return lView[FLAGS] & LViewFlags.IsRoot ? null : lView[CONTEXT] as T; } - - -/** - * Returns the `RootContext` instance that is associated with - * the application where the target is situated. - * - */ -export function getRootContext(target: LView | {}): RootContext { - const lViewData = Array.isArray(target) ? target : loadLContext(target) !.lView; - const rootLView = getRootView(lViewData); - return rootLView[CONTEXT] as RootContext; -} - /** * Retrieve all root components. * diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 40fd1912bd..b2ed0c1b89 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -14,11 +14,11 @@ import {CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA, SchemaMetadata} from '../metad import {validateAgainstEventAttributes, validateAgainstEventProperties} from '../sanitization/sanitization'; import {Sanitizer} from '../sanitization/security'; import {StyleSanitizeFn} from '../sanitization/style_sanitizer'; -import {assertDataInRange, assertDefined, assertEqual, assertLessThan, assertNotEqual} from '../util/assert'; +import {assertDataInRange, assertDefined, assertDomNode, assertEqual, assertLessThan, assertNotEqual} from '../util/assert'; import {isObservable} from '../util/lang'; import {normalizeDebugBindingName, normalizeDebugBindingValue} from '../util/ng_reflect'; -import {assertHasParent, assertPreviousIsParent} from './assert'; +import {assertHasParent, assertLContainerOrUndefined, assertLView, assertPreviousIsParent} from './assert'; import {bindingUpdated, bindingUpdated2, bindingUpdated3, bindingUpdated4} from './bindings'; import {attachPatchData, getComponentViewByInstance} from './context_discovery'; import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di'; @@ -33,7 +33,8 @@ import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection' import {LQueries} from './interfaces/query'; import {GlobalTargetResolver, ProceduralRenderer3, RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from './interfaces/renderer'; import {SanitizerFn} from './interfaces/sanitization'; -import {BINDING_INDEX, CLEANUP, CONTAINER_INDEX, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TAIL, TData, TVIEW, TView, T_HOST} from './interfaces/view'; +import {StylingContext} from './interfaces/styling'; +import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, INJECTOR, InitPhaseState, LView, LViewFlags, NEXT, OpaqueViewState, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, RootContext, RootContextFlags, SANITIZER, TData, TVIEW, TView, T_HOST} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {appendChild, appendProjectedNode, createTextNode, getLViewChild, insertView, removeView} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; @@ -42,7 +43,7 @@ import {getInitialClassNameValue, getInitialStyleStringValue, initializeStaticCo import {BoundPlayerFactory} from './styling/player_factory'; import {ANIMATION_PROP_PREFIX, allocateDirectiveIntoContext, createEmptyStylingContext, forceClassesAsString, forceStylesAsString, getStylingContext, hasClassInput, hasStyleInput, hasStyling, isAnimationProp} from './styling/util'; import {NO_CHANGE} from './tokens'; -import {INTERPOLATION_DELIMITER, applyOnCreateInstructions, findComponentView, getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readElementValue, readPatchedLView, renderStringify} from './util'; +import {INTERPOLATION_DELIMITER, applyOnCreateInstructions, findComponentView, getComponentViewByIndex, getLViewParent, getNativeByIndex, getNativeByTNode, getRootContext, getRootView, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readElementValue, readPatchedLView, renderStringify} from './util'; @@ -816,7 +817,6 @@ function createViewBlueprint(bindingStartIndex: number, initialViewLength: numbe const blueprint = new Array(initialViewLength) .fill(null, 0, bindingStartIndex) .fill(NO_CHANGE, bindingStartIndex) as LView; - blueprint[CONTAINER_INDEX] = -1; blueprint[BINDING_INDEX] = bindingStartIndex; return blueprint; } @@ -2098,11 +2098,10 @@ function addComponentLogic( // accessed through their containers because they may be removed / re-added later. const rendererFactory = lView[RENDERER_FACTORY]; const componentView = addToViewTree( - lView, previousOrParentTNode.index as number, - createLView( - lView, tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, - lView[previousOrParentTNode.index], previousOrParentTNode as TElementNode, - rendererFactory, lView[RENDERER_FACTORY].createRenderer(native as RElement, def))); + lView, createLView( + lView, tView, null, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, + lView[previousOrParentTNode.index], previousOrParentTNode as TElementNode, + rendererFactory, lView[RENDERER_FACTORY].createRenderer(native as RElement, def))); componentView[T_HOST] = previousOrParentTNode as TElementNode; @@ -2208,8 +2207,10 @@ function generateInitialInputs( * @returns LContainer */ export function createLContainer( - hostNative: RElement | RComment, currentView: LView, native: RComment, + hostNative: RElement | RComment | StylingContext | LView, currentView: LView, native: RComment, isForViewContainerRef?: boolean): LContainer { + ngDevMode && assertDomNode(native); + ngDevMode && assertLView(currentView); return [ isForViewContainerRef ? -1 : 0, // active index [], // views @@ -2295,7 +2296,7 @@ function containerInternal( // Containers are added to the current view tree instead of their embedded views // because views can be removed and re-inserted. - addToViewTree(lView, index + HEADER_OFFSET, lContainer); + addToViewTree(lView, lContainer); ngDevMode && assertNodeType(getPreviousOrParentTNode(), TNodeType.Container); return tNode; @@ -2457,7 +2458,7 @@ export function embeddedViewStart(viewBlockId: number, consts: number, vars: num if (lContainer) { if (isCreationMode(viewToRender)) { // it is a new view, insert it into collection of views for a given container - insertView(viewToRender, lContainer, lView, lContainer[ACTIVE_INDEX] !, -1); + insertView(viewToRender, lContainer, lContainer[ACTIVE_INDEX] !); } lContainer[ACTIVE_INDEX] !++; } @@ -2502,7 +2503,9 @@ export function embeddedViewEnd(): void { lView[FLAGS] &= ~LViewFlags.CreationMode; } refreshDescendantViews(lView); // update mode pass - leaveView(lView[PARENT] !); + const lContainer = lView[PARENT] as LContainer; + ngDevMode && assertLContainerOrUndefined(lContainer); + leaveView(lContainer[PARENT] !); setPreviousOrParentTNode(viewHost !); setIsParent(false); } @@ -2648,7 +2651,8 @@ export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?: const componentView = findComponentView(lView); const componentNode = componentView[T_HOST] as TElementNode; let nodeToProject = (componentNode.projection as(TNode | null)[])[selectorIndex]; - let projectedView = componentView[PARENT] !; + let projectedView = componentView[PARENT] !as LView; + ngDevMode && assertLView(projectedView); let projectionNodeIndex = -1; if (Array.isArray(nodeToProject)) { @@ -2670,7 +2674,7 @@ export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?: projectionNodeStack[++projectionNodeIndex] = projectedView; nodeToProject = firstProjectedNode; - projectedView = currentComponentView[PARENT] !; + projectedView = getLViewParent(currentComponentView) !; continue; } } @@ -2703,16 +2707,17 @@ export function projection(nodeIndex: number, selectorIndex: number = 0, attrs?: * @param state The LView or LContainer to add to the view tree * @returns The state passed in */ -export function addToViewTree( - lView: LView, adjustedHostIndex: number, state: T): T { - const tView = lView[TVIEW]; - if (lView[TAIL]) { - lView[TAIL] ![NEXT] = state; - } else if (tView.firstTemplatePass) { - tView.childIndex = adjustedHostIndex; +export function addToViewTree(lView: LView, lViewOrLContainer: T): T { + // TODO(benlesh/misko): This implementation is incorrect, because it always adds the LContainer to + // the end of the queue, which means if the developer asks for the LContainers out of order, the + // change detection will run out of order. + if (lView[CHILD_HEAD]) { + lView[CHILD_TAIL] ![NEXT] = lViewOrLContainer; + } else { + lView[CHILD_HEAD] = lViewOrLContainer; } - lView[TAIL] = state; - return state; + lView[CHILD_TAIL] = lViewOrLContainer; + return lViewOrLContainer; } /////////////////////////////// @@ -2721,6 +2726,7 @@ export function addToViewTree( /** If node is an OnPush component, marks its LView dirty. */ function markDirtyIfOnPush(lView: LView, viewIndex: number): void { + ngDevMode && assertLView(lView); const childComponentLView = getComponentViewByIndex(viewIndex, lView); if (!(childComponentLView[FLAGS] & LViewFlags.CheckAlways)) { childComponentLView[FLAGS] |= LViewFlags.Dirty; @@ -2780,12 +2786,13 @@ function wrapListener( export function markViewDirty(lView: LView): LView|null { while (lView) { lView[FLAGS] |= LViewFlags.Dirty; + const parent = getLViewParent(lView); // Stop traversing up as soon as you find a root view that wasn't attached to any container - if (isRootView(lView) && lView[CONTAINER_INDEX] === -1) { + if (isRootView(lView) && !parent) { return lView; } // continue otherwise - lView = lView[PARENT] !; + lView = parent !; } return null; } diff --git a/packages/core/src/render3/interfaces/container.ts b/packages/core/src/render3/interfaces/container.ts index 5978bb6e69..7584b93412 100644 --- a/packages/core/src/render3/interfaces/container.ts +++ b/packages/core/src/render3/interfaces/container.ts @@ -62,7 +62,7 @@ export interface LContainer extends Array { * Access to the parent view is necessary so we can propagate back * up from inside a container to parent[NEXT]. */ - [PARENT]: LView|null; + [PARENT]: LView; /** * This allows us to jump from a container to a sibling container or component @@ -85,10 +85,10 @@ export interface LContainer extends Array { * It could also be a styling context if this is a node with a style/class * binding. */ - [HOST]: RElement|RComment|StylingContext|LView; + readonly[HOST]: RElement|RComment|StylingContext|LView; /** The comment element that serves as an anchor for this LContainer. */ - [NATIVE]: RComment; + readonly[NATIVE]: RComment; } // Note: This hack is necessary so we don't erroneously get a circular dependency diff --git a/packages/core/src/render3/interfaces/renderer.ts b/packages/core/src/render3/interfaces/renderer.ts index a247204bc6..71c9599fda 100644 --- a/packages/core/src/render3/interfaces/renderer.ts +++ b/packages/core/src/render3/interfaces/renderer.ts @@ -110,11 +110,27 @@ export const domRendererFactory3: RendererFactory3 = { /** Subset of API needed for appending elements and text nodes. */ export interface RNode { + /** + * Returns the parent Element, Document, or DocumentFragment + */ parentNode: RNode|null; + + /** + * Returns the parent Element if there is one + */ + parentElement: RElement|null; + + /** + * Gets the Node immediately following this one in the parent's childNodes + */ nextSibling: RNode|null; - removeChild(oldChild: RNode): void; + /** + * Removes a child from the current node and returns the removed node + * @param oldChild the child node to remove + */ + removeChild(oldChild: RNode): RNode; /** * Insert a child node. diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 589f48a44e..d38af4e2d4 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -41,8 +41,8 @@ export const INJECTOR = 10; export const RENDERER_FACTORY = 11; export const RENDERER = 12; export const SANITIZER = 13; -export const TAIL = 14; -export const CONTAINER_INDEX = 15; +export const CHILD_HEAD = 14; +export const CHILD_TAIL = 15; export const CONTENT_QUERIES = 16; export const DECLARATION_VIEW = 17; /** Size of LView's header. Necessary to adjust for it when setting slots. */ @@ -86,7 +86,7 @@ export interface LView extends Array { * This is the "insertion" view for embedded views. This allows us to properly * destroy embedded views. */ - [PARENT]: LView|null; + [PARENT]: LView|LContainer|null; /** * @@ -168,17 +168,7 @@ export interface LView extends Array { * The tail allows us to quickly add a new state to the end of the view list * without having to propagate starting from the first child. */ - [TAIL]: LView|LContainer|null; - - /** - * The index of the parent container's host node. Applicable only to embedded views that - * have been inserted dynamically. Will be -1 for component views and inline views. - * - * This is necessary to jump from dynamically created embedded views to their parent - * containers because their parent cannot be stored on the TViewNode (views may be inserted - * in multiple containers, so the parent cannot be shared between view instances). - */ - [CONTAINER_INDEX]: number; + [CHILD_TAIL]: LView|LContainer|null; /** * Stores QueryLists associated with content queries of a directive. This data structure is diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index b7a54b693d..8da441efc2 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -7,29 +7,33 @@ */ import {ViewEncapsulation} from '../metadata/view'; +import {assertDefined} from '../util/assert'; +import {assertLContainer, assertLView} from './assert'; import {attachPatchData} from './context_discovery'; import {LContainer, NATIVE, VIEWS, unusedValueExportToPlacateAjd as unused1} from './interfaces/container'; import {ComponentDef} from './interfaces/definition'; import {NodeInjectorFactory} from './interfaces/injector'; -import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; +import {TElementNode, TNode, TNodeFlags, TNodeType, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; -import {CLEANUP, CONTAINER_INDEX, FLAGS, HEADER_OFFSET, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; +import {CHILD_HEAD, CLEANUP, FLAGS, HEADER_OFFSET, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; import {assertNodeType} from './node_assert'; -import {findComponentView, getNativeByTNode, isComponent, isLContainer, isRootView, readElementValue, renderStringify} from './util'; +import {findComponentView, getLViewParent, getNativeByTNode, isComponent, isLContainer, isLView, isRootView, readElementValue, renderStringify} from './util'; const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5; export function getLContainer(tNode: TViewNode, embeddedView: LView): LContainer|null { + ngDevMode && assertLView(embeddedView); + const container = embeddedView[PARENT] as LContainer; if (tNode.index === -1) { // This is a dynamically created view inside a dynamic container. - // If the host index is -1, the view has not yet been inserted, so it has no parent. - const containerHostIndex = embeddedView[CONTAINER_INDEX]; - return containerHostIndex > -1 ? embeddedView[PARENT] ![containerHostIndex] : null; + // The parent isn't an LContainer if the embedded view hasn't been attached yet. + return isLContainer(container) ? container : null; } else { + ngDevMode && assertLContainer(container); // This is a inline view node (e.g. embeddedViewStart) - return embeddedView[PARENT] ![tNode.parent !.index] as LContainer; + return container; } } @@ -123,7 +127,7 @@ function walkTNodeTree( projectionNodeStack[++projectionNodeIndex] = tNode; projectionNodeStack[++projectionNodeIndex] = currentView !; if (head) { - currentView = componentView[PARENT] !; + currentView = componentView[PARENT] !as LView; nextTNode = currentView[TVIEW].data[head.index] as TNode; } } @@ -156,7 +160,7 @@ function walkTNodeTree( // When exiting a container, the beforeNode must be restored to the previous value if (tNode.type === TNodeType.Container) { - currentView = currentView[PARENT] !; + currentView = getLViewParent(currentView) !; beforeNode = currentView[tNode.index][NATIVE]; } @@ -252,35 +256,35 @@ export function addRemoveViewFromContainer( */ export function destroyViewTree(rootView: LView): void { // If the view has no children, we can clean it up and return early. - if (rootView[TVIEW].childIndex === -1) { + let lViewOrLContainer = rootView[CHILD_HEAD]; + if (!lViewOrLContainer) { return cleanUpView(rootView); } - let viewOrContainer: LView|LContainer|null = getLViewChild(rootView); - while (viewOrContainer) { + while (lViewOrLContainer) { let next: LView|LContainer|null = null; - if (viewOrContainer.length >= HEADER_OFFSET) { + if (isLView(lViewOrLContainer)) { // If LView, traverse down to child. - const view = viewOrContainer as LView; - if (view[TVIEW].childIndex > -1) next = getLViewChild(view); + next = lViewOrLContainer[CHILD_HEAD]; } else { + ngDevMode && assertLContainer(lViewOrLContainer); // If container, traverse down to its first LView. - const container = viewOrContainer as LContainer; - if (container[VIEWS].length) next = container[VIEWS][0]; + const views = lViewOrLContainer[VIEWS] as LView[]; + if (views.length > 0) next = views[0]; } - if (next == null) { + if (!next) { // Only clean up view when moving to the side or up, as destroy hooks // should be called in order from the bottom up. - while (viewOrContainer && !viewOrContainer ![NEXT] && viewOrContainer !== rootView) { - cleanUpView(viewOrContainer); - viewOrContainer = getParentState(viewOrContainer, rootView); + while (lViewOrLContainer && !lViewOrLContainer ![NEXT] && lViewOrLContainer !== rootView) { + cleanUpView(lViewOrLContainer); + lViewOrLContainer = getParentState(lViewOrLContainer, rootView); } - cleanUpView(viewOrContainer || rootView); - next = viewOrContainer && viewOrContainer ![NEXT]; + cleanUpView(lViewOrLContainer || rootView); + next = lViewOrLContainer && lViewOrLContainer ![NEXT]; } - viewOrContainer = next; + lViewOrLContainer = next; } } @@ -294,15 +298,13 @@ export function destroyViewTree(rootView: LView): void { * * @param lView The view to insert * @param lContainer The container into which the view should be inserted - * @param parentView The new parent of the inserted view - * @param index The index at which to insert the view - * @param containerIndex The index of the container node, if dynamic + * @param index Which index in the container to insert the child view into */ -export function insertView( - lView: LView, lContainer: LContainer, parentView: LView, index: number, - containerIndex: number) { +export function insertView(lView: LView, lContainer: LContainer, index: number) { + ngDevMode && assertLView(lView); + ngDevMode && assertLContainer(lContainer); const views = lContainer[VIEWS]; - + ngDevMode && assertDefined(views, 'Container must have views'); if (index > 0) { // This is a new view, we need to add it to the children. views[index - 1][NEXT] = lView; @@ -316,12 +318,7 @@ export function insertView( lView[NEXT] = null; } - // Dynamically inserted views need a reference to their parent container's host so it's - // possible to jump from a view to its container's next when walking the node tree. - if (containerIndex > -1) { - lView[CONTAINER_INDEX] = containerIndex; - lView[PARENT] = parentView; - } + lView[PARENT] = lContainer; // Notify query that a new view has been added if (lView[QUERIES]) { @@ -354,7 +351,6 @@ export function detachView(lContainer: LContainer, removeIndex: number): LView { if (viewToDetach[QUERIES]) { viewToDetach[QUERIES] !.removeView(); } - viewToDetach[CONTAINER_INDEX] = -1; viewToDetach[PARENT] = null; // Unsets the attached flag viewToDetach[FLAGS] &= ~LViewFlags.Attached; @@ -404,20 +400,21 @@ export function destroyLView(view: LView) { * embedded views, the container (which is the view node's parent, but not the * LView's parent) needs to be checked for a possible next property. * - * @param state The LViewOrLContainer for which we need a parent state + * @param lViewOrLContainer The LViewOrLContainer for which we need a parent state * @param rootView The rootView, so we don't propagate too far up the view tree * @returns The correct parent LViewOrLContainer */ -export function getParentState(state: LView | LContainer, rootView: LView): LView|LContainer|null { +export function getParentState(lViewOrLContainer: LView | LContainer, rootView: LView): LView| + LContainer|null { let tNode; - if (state.length >= HEADER_OFFSET && (tNode = (state as LView) ![T_HOST]) && + if (isLView(lViewOrLContainer) && (tNode = lViewOrLContainer[T_HOST]) && tNode.type === TNodeType.View) { // if it's an embedded view, the state needs to go up to the container, in case the // container has a next - return getLContainer(tNode as TViewNode, state as LView) as LContainer; + return getLContainer(tNode as TViewNode, lViewOrLContainer); } else { // otherwise, use parent view for containers or component views - return state[PARENT] === rootView ? null : state[PARENT]; + return lViewOrLContainer[PARENT] === rootView ? null : lViewOrLContainer[PARENT]; } } @@ -576,9 +573,10 @@ function getRenderParent(tNode: TNode, currentView: LView): RElement|null { * a host element. */ function getHostNative(currentView: LView): RElement|null { + ngDevMode && assertLView(currentView); const hostTNode = currentView[T_HOST]; return hostTNode && hostTNode.type === TNodeType.Element ? - (getNativeByTNode(hostTNode, currentView[PARENT] !) as RElement) : + (getNativeByTNode(hostTNode, getLViewParent(currentView) !) as RElement) : null; } diff --git a/packages/core/src/render3/players.ts b/packages/core/src/render3/players.ts index 438b24cb5e..a221938d52 100644 --- a/packages/core/src/render3/players.ts +++ b/packages/core/src/render3/players.ts @@ -8,11 +8,11 @@ import '../util/ng_dev_mode'; import {getLContext} from './context_discovery'; -import {getRootContext} from './discovery_utils'; import {scheduleTick} from './instructions'; import {ComponentInstance, DirectiveInstance, Player} from './interfaces/player'; -import {HEADER_OFFSET, RootContextFlags} from './interfaces/view'; +import {RootContextFlags} from './interfaces/view'; import {addPlayerInternal, getOrCreatePlayerContext, getPlayerContext, getPlayersInternal, getStylingContext, throwInvalidRefError} from './styling/util'; +import {getRootContext} from './util'; /** * Adds a player to an element, directive or component instance that will later be diff --git a/packages/core/src/render3/state.ts b/packages/core/src/render3/state.ts index 8e4b292b1a..19764f20ce 100644 --- a/packages/core/src/render3/state.ts +++ b/packages/core/src/render3/state.ts @@ -8,12 +8,14 @@ import {assertDefined} from '../util/assert'; +import {assertLViewOrUndefined} from './assert'; import {executeHooks} from './hooks'; import {ComponentDef, DirectiveDef} from './interfaces/definition'; import {TElementNode, TNode, TViewNode} from './interfaces/node'; import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, TVIEW} from './interfaces/view'; + /** * Store the element depth count. This is used to identify the root elements of the template * so that we can than attach `LView` to only those elements. @@ -142,6 +144,7 @@ export function setPreviousOrParentTNode(tNode: TNode) { } export function setTNodeAndViewData(tNode: TNode, view: LView) { + ngDevMode && assertLViewOrUndefined(view); previousOrParentTNode = tNode; lView = view; } @@ -249,6 +252,7 @@ export function setCurrentQueryIndex(value: number): void { * @returns the previous state; */ export function enterView(newView: LView, hostTNode: TElementNode | TViewNode | null): LView { + ngDevMode && assertLViewOrUndefined(newView); const oldView = lView; if (newView) { const tView = newView[TVIEW]; diff --git a/packages/core/src/render3/util.ts b/packages/core/src/render3/util.ts index fd9fd5969b..d9d02c1aad 100644 --- a/packages/core/src/render3/util.ts +++ b/packages/core/src/render3/util.ts @@ -9,17 +9,36 @@ import {assertDataInRange, assertDefined, assertGreaterThan, assertLessThan} from '../util/assert'; import {global} from '../util/global'; +import {assertLView} from './assert'; import {LCONTAINER_LENGTH, LContainer} from './interfaces/container'; import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context'; import {ComponentDef, DirectiveDef} from './interfaces/definition'; import {NO_PARENT_INJECTOR, RelativeInjectorLocation, RelativeInjectorLocationFlags} from './interfaces/injector'; import {TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node'; import {RComment, RElement, RText} from './interfaces/renderer'; -import {StylingContext} from './interfaces/styling'; import {CONTEXT, DECLARATION_VIEW, FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, RootContext, TData, TVIEW, T_HOST} from './interfaces/view'; +/** + * Gets the parent LView of the passed LView, if the PARENT is an LContainer, will get the parent of + * that LContainer, which is an LView + * @param lView the lView whose parent to get + */ +export function getLViewParent(lView: LView): LView|null { + ngDevMode && assertLView(lView); + const parent = lView[PARENT]; + return isLContainer(parent) ? parent[PARENT] ! : parent; +} + +/** + * Returns true if the value is an {@link LView} + * @param value the value to check + */ +export function isLView(value: any): value is LView { + return Array.isArray(value) && value.length >= HEADER_OFFSET; +} + /** * Returns whether the values are different from a change detection stand point. * @@ -85,7 +104,7 @@ export function loadInternal(view: LView | TData, index: number): T { * * @param value The initial value in `LView` */ -export function readElementValue(value: RElement | StylingContext | LContainer | LView): RElement { +export function readElementValue(value: any): RElement { while (Array.isArray(value)) { value = value[HOST] as any; } @@ -113,7 +132,9 @@ export function getTNode(index: number, view: LView): TNode { export function getComponentViewByIndex(nodeIndex: number, hostView: LView): LView { // Could be an LView or an LContainer. If LContainer, unwrap to find LView. const slotValue = hostView[nodeIndex]; - return slotValue.length >= HEADER_OFFSET ? slotValue : slotValue[HOST]; + const lView = isLView(slotValue) ? slotValue : slotValue[HOST]; + ngDevMode && assertLView(lView); + return lView; } export function isContentQueryHost(tNode: TNode): boolean { @@ -128,7 +149,7 @@ export function isComponentDef(def: DirectiveDef): def is ComponentDef return (def as ComponentDef).template !== null; } -export function isLContainer(value: RElement | RComment | LContainer | StylingContext): boolean { +export function isLContainer(value: any): value is LContainer { // Styling contexts are also arrays, but their first index contains an element node return Array.isArray(value) && value.length === LCONTAINER_LENGTH; } @@ -138,20 +159,27 @@ export function isRootView(target: LView): boolean { } /** - * Retrieve the root view from any component by walking the parent `LView` until + * Retrieve the root view from any component or `LView` by walking the parent `LView` until * reaching the root `LView`. * - * @param component any component + * @param componentOrLView any component or `LView` */ -export function getRootView(target: LView | {}): LView { - ngDevMode && assertDefined(target, 'component'); - let lView = Array.isArray(target) ? (target as LView) : readPatchedLView(target) !; +export function getRootView(componentOrLView: LView | {}): LView { + ngDevMode && assertDefined(componentOrLView, 'component'); + let lView = isLView(componentOrLView) ? componentOrLView : readPatchedLView(componentOrLView) !; while (lView && !(lView[FLAGS] & LViewFlags.IsRoot)) { - lView = lView[PARENT] !; + lView = getLViewParent(lView) !; } + ngDevMode && assertLView(lView); return lView; } - +/** + * Returns the `RootContext` instance that is associated with + * the application where the target is situated. It does this by walking the parent views until it + * gets to the root view, then getting the context off of that. + * + * @param viewOrComponent the `LView` or component to get the root context for. + */ export function getRootContext(viewOrComponent: LView | {}): RootContext { const rootView = getRootView(viewOrComponent); ngDevMode && @@ -279,6 +307,7 @@ export function findComponentView(lView: LView): LView { rootTNode = lView[T_HOST]; } + ngDevMode && assertLView(lView); return lView; } diff --git a/packages/core/src/render3/view_engine_compatibility.ts b/packages/core/src/render3/view_engine_compatibility.ts index 6c5b68af97..d8fff606c3 100644 --- a/packages/core/src/render3/view_engine_compatibility.ts +++ b/packages/core/src/render3/view_engine_compatibility.ts @@ -22,7 +22,7 @@ import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderEmbedd import {ACTIVE_INDEX, LContainer, NATIVE, VIEWS} from './interfaces/container'; import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; import {RComment, RElement, isProceduralRenderer} from './interfaces/renderer'; -import {CONTAINER_INDEX, CONTEXT, LView, QUERIES, RENDERER, TView, T_HOST} from './interfaces/view'; +import {CONTEXT, LView, QUERIES, RENDERER, TView, T_HOST} from './interfaces/view'; import {assertNodeOfPossibleTypes} from './node_assert'; import {addRemoveViewFromContainer, appendChild, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode, removeView} from './node_manipulation'; import {getLView, getPreviousOrParentTNode} from './state'; @@ -101,15 +101,13 @@ export function createTemplateRef( super(); } - createEmbeddedView( - context: T, container?: LContainer, - hostTNode?: TElementNode|TContainerNode|TElementContainerNode, hostView?: LView, - index?: number): viewEngine_EmbeddedViewRef { + createEmbeddedView(context: T, container?: LContainer, index?: number): + viewEngine_EmbeddedViewRef { const lView = createEmbeddedViewAndNode( this._tView, context, this._declarationParentView, this._hostLContainer[QUERIES], this._injectorIndex); if (container) { - insertView(lView, container, hostView !, index !, hostTNode !.index); + insertView(lView, container, index !); } renderEmbeddedTemplate(lView, this._tView, context); const viewRef = new ViewRef(lView, context, -1); @@ -207,9 +205,7 @@ export function createContainerRef( viewEngine_EmbeddedViewRef { const adjustedIdx = this._adjustIndex(index); const viewRef = (templateRef as any) - .createEmbeddedView( - context || {}, this._lContainer, this._hostTNode, - this._hostView, adjustedIdx); + .createEmbeddedView(context || {}, this._lContainer, adjustedIdx); (viewRef as ViewRef).attachToViewContainerRef(this); this._viewRefs.splice(adjustedIdx, 0, viewRef); return viewRef; @@ -237,7 +233,7 @@ export function createContainerRef( const lView = (viewRef as ViewRef)._lView !; const adjustedIdx = this._adjustIndex(index); - insertView(lView, this._lContainer, this._hostView, adjustedIdx, this._hostTNode.index); + insertView(lView, this._lContainer, adjustedIdx); const beforeNode = getBeforeNodeForView(adjustedIdx, this._lContainer[VIEWS], this._lContainer[NATIVE]); @@ -271,7 +267,7 @@ export function createContainerRef( const adjustedIdx = this._adjustIndex(index, -1); const view = detachView(this._lContainer, adjustedIdx); const wasDetached = this._viewRefs.splice(adjustedIdx, 1)[0] != null; - return wasDetached ? new ViewRef(view, view[CONTEXT], view[CONTAINER_INDEX]) : null; + return wasDetached ? new ViewRef(view, view[CONTEXT], -1) : null; } private _adjustIndex(index?: number, shift: number = 0) { diff --git a/packages/core/src/render3/view_ref.ts b/packages/core/src/render3/view_ref.ts index 9130e87385..810e75401a 100644 --- a/packages/core/src/render3/view_ref.ts +++ b/packages/core/src/render3/view_ref.ts @@ -15,7 +15,7 @@ import {checkNoChangesInRootView, checkNoChangesInternal, detectChangesInRootVie import {TNode, TNodeType, TViewNode} from './interfaces/node'; import {FLAGS, HOST, LView, LViewFlags, PARENT, T_HOST} from './interfaces/view'; import {destroyLView} from './node_manipulation'; -import {getNativeByTNode} from './util'; +import {getLViewParent, getNativeByTNode} from './util'; @@ -271,7 +271,7 @@ export class ViewRef implements viewEngine_EmbeddedViewRef, viewEngine_Int } private _lookUpContext(): T { - return this._context = this._lView[PARENT] ![this._componentIndex] as T; + return this._context = getLViewParent(this._lView) ![this._componentIndex] as T; } } diff --git a/packages/core/src/util/assert.ts b/packages/core/src/util/assert.ts index 16a2466040..1f1aeedf8e 100644 --- a/packages/core/src/util/assert.ts +++ b/packages/core/src/util/assert.ts @@ -65,7 +65,10 @@ export function throwError(msg: string): never { } export function assertDomNode(node: any) { - assertEqual(node instanceof Node, true, 'The provided value must be an instance of a DOM Node'); + assertEqual( + node instanceof Node || + (typeof node === 'object' && node.constructor.name === 'WebWorkerRenderNode'), + true, 'The provided value must be an instance of a DOM Node'); } diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index a18848355a..464e2b2a82 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -12,10 +12,13 @@ "name": "BLOOM_MASK" }, { - "name": "CLEAN_PROMISE" + "name": "CHILD_HEAD" }, { - "name": "CONTAINER_INDEX" + "name": "CHILD_TAIL" + }, + { + "name": "CLEAN_PROMISE" }, { "name": "CONTEXT" @@ -119,9 +122,6 @@ { "name": "SANITIZER" }, - { - "name": "TAIL" - }, { "name": "TVIEW" }, @@ -351,7 +351,7 @@ "name": "getLView" }, { - "name": "getLViewChild" + "name": "getLViewParent" }, { "name": "getNativeAnchorNode" @@ -470,6 +470,12 @@ { "name": "isFactory" }, + { + "name": "isLContainer" + }, + { + "name": "isLView" + }, { "name": "isNodeMatchingSelector" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index d68730da4c..0766039f98 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -9,10 +9,13 @@ "name": "BLOOM_MASK" }, { - "name": "CLEAN_PROMISE" + "name": "CHILD_HEAD" }, { - "name": "CONTAINER_INDEX" + "name": "CHILD_TAIL" + }, + { + "name": "CLEAN_PROMISE" }, { "name": "CONTEXT" @@ -50,6 +53,9 @@ { "name": "INJECTOR_BLOOM_PARENT_SIZE" }, + { + "name": "LCONTAINER_LENGTH" + }, { "name": "MONKEY_PATCH_KEY_NAME" }, @@ -98,9 +104,6 @@ { "name": "SANITIZER" }, - { - "name": "TAIL" - }, { "name": "TVIEW" }, @@ -258,7 +261,7 @@ "name": "getLView" }, { - "name": "getLViewChild" + "name": "getLViewParent" }, { "name": "getNativeAnchorNode" @@ -335,6 +338,12 @@ { "name": "isFactory" }, + { + "name": "isLContainer" + }, + { + "name": "isLView" + }, { "name": "isProceduralRenderer" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 08225ab963..fd36edb1c4 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -17,15 +17,18 @@ { "name": "BoundPlayerFactory" }, + { + "name": "CHILD_HEAD" + }, + { + "name": "CHILD_TAIL" + }, { "name": "CLEANUP" }, { "name": "CLEAN_PROMISE" }, - { - "name": "CONTAINER_INDEX" - }, { "name": "CONTEXT" }, @@ -209,9 +212,6 @@ { "name": "SkipSelf" }, - { - "name": "TAIL" - }, { "name": "TNODE" }, @@ -720,7 +720,7 @@ "name": "getLView" }, { - "name": "getLViewChild" + "name": "getLViewParent" }, { "name": "getMatchingBindingIndex" @@ -965,6 +965,9 @@ { "name": "isLContainer" }, + { + "name": "isLView" + }, { "name": "isListLikeIterable" }, diff --git a/packages/core/test/linker/query_integration_spec.ts b/packages/core/test/linker/query_integration_spec.ts index a4f11a21b7..1fd7bbc11c 100644 --- a/packages/core/test/linker/query_integration_spec.ts +++ b/packages/core/test/linker/query_integration_spec.ts @@ -319,7 +319,7 @@ describe('Query API', () => { const template = `
-
+
`; const view = createTestCmpAndDetectChanges(MyComp0, template); @@ -333,7 +333,7 @@ describe('Query API', () => { const template = `
-
+
`; const view = createTestCmpAndDetectChanges(MyComp0, template); @@ -358,7 +358,7 @@ describe('Query API', () => { const template = `
-
+
`; const view = createTestCmpAndDetectChanges(MyComp0, template); diff --git a/packages/core/test/sanitization/sanatization_spec.ts b/packages/core/test/sanitization/sanatization_spec.ts index 5d538b6305..7fea17a5e2 100644 --- a/packages/core/test/sanitization/sanatization_spec.ts +++ b/packages/core/test/sanitization/sanatization_spec.ts @@ -8,14 +8,19 @@ */ import {SECURITY_SCHEMA} from '@angular/compiler/src/schema/dom_security_schema'; +import {HEADER_OFFSET, LView} from '@angular/core/src/render3/interfaces/view'; import {setTNodeAndViewData} from '@angular/core/src/render3/state'; import {bypassSanitizationTrustHtml, bypassSanitizationTrustResourceUrl, bypassSanitizationTrustScript, bypassSanitizationTrustStyle, bypassSanitizationTrustUrl} from '../../src/sanitization/bypass'; import {getUrlSanitizer, sanitizeHtml, sanitizeResourceUrl, sanitizeScript, sanitizeStyle, sanitizeUrl, sanitizeUrlOrResourceUrl} from '../../src/sanitization/sanitization'; import {SecurityContext} from '../../src/sanitization/security'; +function fakeLView(): LView { + return Array.from({length: HEADER_OFFSET}) as LView; +} + describe('sanitization', () => { - beforeEach(() => setTNodeAndViewData(null !, [] as any)); + beforeEach(() => setTNodeAndViewData(null !, fakeLView())); afterEach(() => setTNodeAndViewData(null !, null !)); class Wrap { constructor(private value: string) {}