GeezerVison is showing github.com — fix(core): remove application from the testability registry when the … · angular/angular@3680ad1
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: goldens/size-tracking/integration-payloads.json +1-1Lines changed: 1 addition & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
| 39 | 39 | "master": { | 40 | 40 | "uncompressed": { | 41 | 41 | "runtime-es2015": 2285, | 42 | | - "main-es2015": 241875, | | 42 | + "main-es2015": 242075, | 43 | 43 | "polyfills-es2015": 36709, | 44 | 44 | "5-es2015": 745 | 45 | 45 | } | |
Collapse file Copy file name to clipboardExpand all lines: packages/core/src/application_ref.ts +14-12Lines changed: 14 additions & 12 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -570,6 +570,7 @@ export class ApplicationRef { | 570 | 570 | private _runningTick: boolean = false; | 571 | 571 | private _enforceNoNewChanges: boolean = false; | 572 | 572 | private _stable = true; | | 573 | + private _onMicrotaskEmptySubscription: Subscription; | 573 | 574 |
| 574 | 575 | /** | 575 | 576 | * Get a list of component types registered to this application. | @@ -598,7 +599,7 @@ export class ApplicationRef { | 598 | 599 | private _initStatus: ApplicationInitStatus) { | 599 | 600 | this._enforceNoNewChanges = isDevMode(); | 600 | 601 |
| 601 | | - this._zone.onMicrotaskEmpty.subscribe({ | | 602 | + this._onMicrotaskEmptySubscription = this._zone.onMicrotaskEmpty.subscribe({ | 602 | 603 | next: () => { | 603 | 604 | this._zone.run(() => { | 604 | 605 | this.tick(); | @@ -691,15 +692,20 @@ export class ApplicationRef { | 691 | 692 | isBoundToModule(componentFactory) ? undefined : this._injector.get(NgModuleRef); | 692 | 693 | const selectorOrNode = rootSelectorOrNode || componentFactory.selector; | 693 | 694 | const compRef = componentFactory.create(Injector.NULL, [], selectorOrNode, ngModule); | | 695 | + const nativeElement = compRef.location.nativeElement; | | 696 | + const testability = compRef.injector.get(Testability, null); | | 697 | + const testabilityRegistry = testability && compRef.injector.get(TestabilityRegistry); | | 698 | + if (testability && testabilityRegistry) { | | 699 | + testabilityRegistry.registerApplication(nativeElement, testability); | | 700 | + } | 694 | 701 |
| 695 | 702 | compRef.onDestroy(() => { | 696 | | - this._unloadComponent(compRef); | | 703 | + this.detachView(compRef.hostView); | | 704 | + remove(this.components, compRef); | | 705 | + if (testabilityRegistry) { | | 706 | + testabilityRegistry.unregisterApplication(nativeElement); | | 707 | + } | 697 | 708 | }); | 698 | | - const testability = compRef.injector.get(Testability, null); | 699 | | - if (testability) { | 700 | | - compRef.injector.get(TestabilityRegistry) | 701 | | - .registerApplication(compRef.location.nativeElement, testability); | 702 | | - } | 703 | 709 |
| 704 | 710 | this._loadComponent(compRef); | 705 | 711 | if (isDevMode()) { | @@ -772,14 +778,10 @@ export class ApplicationRef { | 772 | 778 | listeners.forEach((listener) => listener(componentRef)); | 773 | 779 | } | 774 | 780 |
| 775 | | - private _unloadComponent(componentRef: ComponentRef<any>): void { | 776 | | - this.detachView(componentRef.hostView); | 777 | | - remove(this.components, componentRef); | 778 | | - } | 779 | | - | 780 | 781 | /** @internal */ | 781 | 782 | ngOnDestroy() { | 782 | 783 | this._views.slice().forEach((view) => view.destroy()); | | 784 | + this._onMicrotaskEmptySubscription.unsubscribe(); | 783 | 785 | } | 784 | 786 |
| 785 | 787 | /** | |
Collapse file Copy file name to clipboardExpand all lines: packages/core/src/render3/component_ref.ts +2-9Lines changed: 2 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -246,7 +246,6 @@ export function injectComponentFactoryResolver(): viewEngine_ComponentFactoryRes | 246 | 246 | * | 247 | 247 | */ | 248 | 248 | export class ComponentRef<T> extends viewEngine_ComponentRef<T> { | 249 | | - destroyCbs: (() => void)[]|null = []; | 250 | 249 | instance: T; | 251 | 250 | hostView: ViewRef<T>; | 252 | 251 | changeDetectorRef: ViewEngine_ChangeDetectorRef; | @@ -267,16 +266,10 @@ export class ComponentRef<T> extends viewEngine_ComponentRef<T> { | 267 | 266 | } | 268 | 267 |
| 269 | 268 | destroy(): void { | 270 | | - if (this.destroyCbs) { | 271 | | - this.destroyCbs.forEach(fn => fn()); | 272 | | - this.destroyCbs = null; | 273 | | - !this.hostView.destroyed && this.hostView.destroy(); | 274 | | - } | | 269 | + this.hostView.destroy(); | 275 | 270 | } | 276 | 271 |
| 277 | 272 | onDestroy(callback: () => void): void { | 278 | | - if (this.destroyCbs) { | 279 | | - this.destroyCbs.push(callback); | 280 | | - } | | 273 | + this.hostView.onDestroy(callback); | 281 | 274 | } | 282 | 275 | } |
Collapse file Copy file name to clipboardExpand all lines: packages/core/src/render3/instructions/listener.ts +2-2Lines changed: 2 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -19,7 +19,7 @@ import {assertTNodeType} from '../node_assert'; | 19 | 19 | import {getCurrentDirectiveDef, getCurrentTNode, getLView, getTView} from '../state'; | 20 | 20 | import {getComponentLViewByIndex, getNativeByTNode, unwrapRNode} from '../util/view_utils'; | 21 | 21 |
| 22 | | -import {getLCleanup, handleError, loadComponentRenderer, markViewDirty} from './shared'; | | 22 | +import {getLCleanup, getTViewCleanup, handleError, loadComponentRenderer, markViewDirty} from './shared'; | 23 | 23 |
| 24 | 24 |
| 25 | 25 |
| @@ -120,7 +120,7 @@ function listenerInternal( | 120 | 120 | eventTargetResolver?: GlobalTargetResolver): void { | 121 | 121 | const isTNodeDirectiveHost = isDirectiveHost(tNode); | 122 | 122 | const firstCreatePass = tView.firstCreatePass; | 123 | | - const tCleanup: false|any[] = firstCreatePass && (tView.cleanup || (tView.cleanup = [])); | | 123 | + const tCleanup: false|any[] = firstCreatePass && getTViewCleanup(tView); | 124 | 124 |
| 125 | 125 | // When the ɵɵlistener instruction was generated and is executed we know that there is either a | 126 | 126 | // native listener or a directive output on this element. As such we we know that we will have to | |
Collapse file Copy file name to clipboardExpand all lines: packages/core/src/render3/instructions/shared.ts +16-4Lines changed: 16 additions & 4 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -752,14 +752,26 @@ export function locateHostElement( | 752 | 752 | * On the first template pass, saves in TView: | 753 | 753 | * - Cleanup function | 754 | 754 | * - Index of context we just saved in LView.cleanupInstances | | 755 | + * | | 756 | + * This function can also be used to store instance specific cleanup fns. In that case the `context` | | 757 | + * is `null` and the function is store in `LView` (rather than it `TView`). | 755 | 758 | */ | 756 | 759 | export function storeCleanupWithContext( | 757 | 760 | tView: TView, lView: LView, context: any, cleanupFn: Function): void { | 758 | 761 | const lCleanup = getLCleanup(lView); | 759 | | - lCleanup.push(context); | | 762 | + if (context === null) { | | 763 | + // If context is null that this is instance specific callback. These callbacks can only be | | 764 | + // inserted after template shared instances. For this reason in ngDevMode we freeze the TView. | | 765 | + if (ngDevMode) { | | 766 | + Object.freeze(getTViewCleanup(tView)); | | 767 | + } | | 768 | + lCleanup.push(cleanupFn); | | 769 | + } else { | | 770 | + lCleanup.push(context); | 760 | 771 |
| 761 | | - if (tView.firstCreatePass) { | 762 | | - getTViewCleanup(tView).push(cleanupFn, lCleanup.length - 1); | | 772 | + if (tView.firstCreatePass) { | | 773 | + getTViewCleanup(tView).push(cleanupFn, lCleanup.length - 1); | | 774 | + } | 763 | 775 | } | 764 | 776 | } | 765 | 777 |
| @@ -1997,7 +2009,7 @@ export function getLCleanup(view: LView): any[] { | 1997 | 2009 | return view[CLEANUP] || (view[CLEANUP] = ngDevMode ? new LCleanup() : []); | 1998 | 2010 | } | 1999 | 2011 |
| 2000 | | -function getTViewCleanup(tView: TView): any[] { | | 2012 | +export function getTViewCleanup(tView: TView): any[] { | 2001 | 2013 | return tView.cleanup || (tView.cleanup = ngDevMode ? new TCleanup() : []); | 2002 | 2014 | } | 2003 | 2015 |
| |
Collapse file Copy file name to clipboardExpand all lines: packages/core/src/render3/interfaces/view.ts +4-1Lines changed: 4 additions & 1 deletion | Original file line number | Diff line number | Diff line change |
|---|
@@ -163,8 +163,11 @@ export interface LView extends Array<any> { | 163 | 163 | * | 164 | 164 | * These change per LView instance, so they cannot be stored on TView. Instead, | 165 | 165 | * TView.cleanup saves an index to the necessary context in this array. | | 166 | + * | | 167 | + * After `LView` is created it is possible to attach additional instance specific functions at the | | 168 | + * end of the `lView[CLENUP]` because we know that no more `T` level cleanup functions will be | | 169 | + * addeded here. | 166 | 170 | */ | 167 | | - // TODO: flatten into LView[] | 168 | 171 | [CLEANUP]: any[]|null; | 169 | 172 |
| 170 | 173 | /** | |
Collapse file Copy file name to clipboardExpand all lines: packages/core/src/render3/node_manipulation.ts +19-8Lines changed: 19 additions & 8 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -10,7 +10,7 @@ import {ViewEncapsulation} from '../metadata/view'; | 10 | 10 | import {Renderer2} from '../render/api'; | 11 | 11 | import {RendererStyleFlags2} from '../render/api_flags'; | 12 | 12 | import {addToArray, removeFromArray} from '../util/array_utils'; | 13 | | -import {assertDefined, assertDomNode, assertEqual, assertString} from '../util/assert'; | | 13 | +import {assertDefined, assertDomNode, assertEqual, assertFunction, assertString} from '../util/assert'; | 14 | 14 | import {assertLContainer, assertLView, assertTNodeForLView} from './assert'; | 15 | 15 | import {attachPatchData} from './context_discovery'; | 16 | 16 | import {icuContainerIterate} from './i18n/i18n_tree_shaking'; | @@ -418,7 +418,7 @@ function cleanUpView(tView: TView, lView: LView): void { | 418 | 418 | lView[FLAGS] |= LViewFlags.Destroyed; | 419 | 419 |
| 420 | 420 | executeOnDestroys(tView, lView); | 421 | | - removeListeners(tView, lView); | | 421 | + processCleanups(tView, lView); | 422 | 422 | // For component views only, the local renderer is destroyed at clean up time. | 423 | 423 | if (lView[TVIEW].type === TViewType.Component && isProceduralRenderer(lView[RENDERER])) { | 424 | 424 | ngDevMode && ngDevMode.rendererDestroy++; | @@ -443,38 +443,49 @@ function cleanUpView(tView: TView, lView: LView): void { | 443 | 443 | } | 444 | 444 |
| 445 | 445 | /** Removes listeners and unsubscribes from output subscriptions */ | 446 | | -function removeListeners(tView: TView, lView: LView): void { | | 446 | +function processCleanups(tView: TView, lView: LView): void { | 447 | 447 | const tCleanup = tView.cleanup; | | 448 | + const lCleanup = lView[CLEANUP]!; | | 449 | + // `LCleanup` contains both share information with `TCleanup` as well as instance specific | | 450 | + // information appended at the end. We need to know where the end of the `TCleanup` information | | 451 | + // is, and we track this with `lastLCleanupIndex`. | | 452 | + let lastLCleanupIndex = -1; | 448 | 453 | if (tCleanup !== null) { | 449 | | - const lCleanup = lView[CLEANUP]!; | 450 | 454 | for (let i = 0; i < tCleanup.length - 1; i += 2) { | 451 | 455 | if (typeof tCleanup[i] === 'string') { | 452 | 456 | // This is a native DOM listener | 453 | 457 | const idxOrTargetGetter = tCleanup[i + 1]; | 454 | 458 | const target = typeof idxOrTargetGetter === 'function' ? | 455 | 459 | idxOrTargetGetter(lView) : | 456 | 460 | unwrapRNode(lView[idxOrTargetGetter]); | 457 | | - const listener = lCleanup[tCleanup[i + 2]]; | | 461 | + const listener = lCleanup[lastLCleanupIndex = tCleanup[i + 2]]; | 458 | 462 | const useCaptureOrSubIdx = tCleanup[i + 3]; | 459 | 463 | if (typeof useCaptureOrSubIdx === 'boolean') { | 460 | 464 | // native DOM listener registered with Renderer3 | 461 | 465 | target.removeEventListener(tCleanup[i], listener, useCaptureOrSubIdx); | 462 | 466 | } else { | 463 | 467 | if (useCaptureOrSubIdx >= 0) { | 464 | 468 | // unregister | 465 | | - lCleanup[useCaptureOrSubIdx](); | | 469 | + lCleanup[lastLCleanupIndex = useCaptureOrSubIdx](); | 466 | 470 | } else { | 467 | 471 | // Subscription | 468 | | - lCleanup[-useCaptureOrSubIdx].unsubscribe(); | | 472 | + lCleanup[lastLCleanupIndex = -useCaptureOrSubIdx].unsubscribe(); | 469 | 473 | } | 470 | 474 | } | 471 | 475 | i += 2; | 472 | 476 | } else { | 473 | 477 | // This is a cleanup function that is grouped with the index of its context | 474 | | - const context = lCleanup[tCleanup[i + 1]]; | | 478 | + const context = lCleanup[lastLCleanupIndex = tCleanup[i + 1]]; | 475 | 479 | tCleanup[i].call(context); | 476 | 480 | } | 477 | 481 | } | | 482 | + if (lCleanup !== null) { | | 483 | + for (let i = lastLCleanupIndex + 1; i < lCleanup.length; i++) { | | 484 | + const instanceCleanupFn = lCleanup[i]; | | 485 | + ngDevMode && assertFunction(instanceCleanupFn, 'Expecting instance cleanup function.'); | | 486 | + instanceCleanupFn(); | | 487 | + } | | 488 | + } | 478 | 489 | lView[CLEANUP] = null; | 479 | 490 | } | 480 | 491 | } | |
Collapse file Copy file name to clipboardExpand all lines: packages/core/src/util/assert.ts +6Lines changed: 6 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -31,6 +31,12 @@ export function assertString(actual: any, msg: string): asserts actual is string | 31 | 31 | } | 32 | 32 | } | 33 | 33 |
| | 34 | +export function assertFunction(actual: any, msg: string): asserts actual is Function { | | 35 | + if (!(typeof actual === 'function')) { | | 36 | + throwError(msg, actual === null ? 'null' : typeof actual, 'function', '==='); | | 37 | + } | | 38 | +} | | 39 | + | 34 | 40 | export function assertEqual<T>(actual: T, expected: T, msg: string) { | 35 | 41 | if (!(actual == expected)) { | 36 | 42 | throwError(msg, actual, expected, '=='); | |
Collapse file Copy file name to clipboardExpand all lines: packages/core/test/bundling/router/bundle.golden_symbols.json +3Lines changed: 3 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1436 | 1436 | { | 1437 | 1437 | "name": "getTView" | 1438 | 1438 | }, | | 1439 | + { | | 1440 | + "name": "getTViewCleanup" | | 1441 | + }, | 1439 | 1442 | { | 1440 | 1443 | "name": "getToken" | 1441 | 1444 | }, | |
|