55import { EmptyObject , lookupDescriptor , symbol } from 'ember-utils' ;
66import isEnabled from './features' ;
77import { protoMethods as listenerMethods } from './meta_listeners' ;
8- import { runInDebug } from './debug' ;
8+ import { runInDebug , assert } from './debug' ;
9+ import {
10+ removeChainWatcher
11+ } from './chains' ;
912
1013let counters = {
1114 peekCalls : 0 ,
@@ -55,6 +58,10 @@ let members = {
5558 tag : ownCustomObject
5659} ;
5760
61+ const SOURCE_DESTROYING = 1 << 1 ;
62+ const SOURCE_DESTROYED = 1 << 2 ;
63+ const META_DESTROYED = 1 << 3 ;
64+
5865if ( isEnabled ( 'ember-glimmer-detect-backtracking-rerender' ) ||
5966 isEnabled ( 'ember-glimmer-allow-backtracking-rerender' ) ) {
6067 members . lastRendered = ownMap ;
@@ -78,6 +85,10 @@ export function Meta(obj, parentMeta) {
7885 this . _chains = undefined ;
7986 this . _tag = undefined ;
8087
88+ // initial value for all flags right now is false
89+ // see FLAGS const for detailed list of flags used
90+ this . _flags = 0 ;
91+
8192 // used only internally
8293 this . source = obj ;
8394
@@ -104,11 +115,79 @@ Meta.prototype.isInitialized = function(obj) {
104115 return this . proto !== obj ;
105116} ;
106117
118+ const NODE_STACK = [ ] ;
119+
120+ Meta . prototype . destroy = function ( ) {
121+ if ( this . isMetaDestroyed ( ) ) { return ; }
122+
123+ // remove chainWatchers to remove circular references that would prevent GC
124+ let node , nodes , key , nodeObject ;
125+ node = this . readableChains ( ) ;
126+ if ( node ) {
127+ NODE_STACK . push ( node ) ;
128+ // process tree
129+ while ( NODE_STACK . length > 0 ) {
130+ node = NODE_STACK . pop ( ) ;
131+ // push children
132+ nodes = node . _chains ;
133+ if ( nodes ) {
134+ for ( key in nodes ) {
135+ if ( nodes [ key ] !== undefined ) {
136+ NODE_STACK . push ( nodes [ key ] ) ;
137+ }
138+ }
139+ }
140+
141+ // remove chainWatcher in node object
142+ if ( node . _watching ) {
143+ nodeObject = node . _object ;
144+ if ( nodeObject ) {
145+ let foreignMeta = peekMeta ( nodeObject ) ;
146+ // avoid cleaning up chain watchers when both current and
147+ // foreign objects are being destroyed
148+ // if both are being destroyed manual cleanup is not needed
149+ // as they will be GC'ed and no non-destroyed references will
150+ // be remaining
151+ if ( foreignMeta && ! foreignMeta . isSourceDestroying ( ) ) {
152+ removeChainWatcher ( nodeObject , node . _key , node , foreignMeta ) ;
153+ }
154+ }
155+ }
156+ }
157+ }
158+
159+ this . setMetaDestroyed ( ) ;
160+ } ;
161+
107162for ( let name in listenerMethods ) {
108163 Meta . prototype [ name ] = listenerMethods [ name ] ;
109164}
110165memberNames . forEach ( name => members [ name ] ( name , Meta ) ) ;
111166
167+ Meta . prototype . isSourceDestroying = function isSourceDestroying ( ) {
168+ return ( this . _flags & SOURCE_DESTROYING ) !== 0 ;
169+ } ;
170+
171+ Meta . prototype . setSourceDestroying = function setSourceDestroying ( ) {
172+ this . _flags |= SOURCE_DESTROYING ;
173+ } ;
174+
175+ Meta . prototype . isSourceDestroyed = function isSourceDestroyed ( ) {
176+ return ( this . _flags & SOURCE_DESTROYED ) !== 0 ;
177+ } ;
178+
179+ Meta . prototype . setSourceDestroyed = function setSourceDestroyed ( ) {
180+ this . _flags |= SOURCE_DESTROYED ;
181+ } ;
182+
183+ Meta . prototype . isMetaDestroyed = function isMetaDestroyed ( ) {
184+ return ( this . _flags & META_DESTROYED ) !== 0 ;
185+ } ;
186+
187+ Meta . prototype . setMetaDestroyed = function setMetaDestroyed ( ) {
188+ this . _flags |= META_DESTROYED ;
189+ } ;
190+
112191// Implements a member that is a lazily created, non-inheritable
113192// POJO.
114193function ownMap ( name , Meta ) {
@@ -135,6 +214,8 @@ function inheritedMap(name, Meta) {
135214 let capitalized = capitalize ( name ) ;
136215
137216 Meta . prototype [ 'write' + capitalized ] = function ( subkey , value ) {
217+ assert ( `Cannot call write${ capitalized } after the object is destroyed.` , ! this . isMetaDestroyed ( ) ) ;
218+
138219 let map = this . _getOrCreateOwnMap ( key ) ;
139220 map [ subkey ] = value ;
140221 } ;
@@ -161,6 +242,8 @@ function inheritedMap(name, Meta) {
161242 } ;
162243
163244 Meta . prototype [ 'clear' + capitalized ] = function ( ) {
245+ assert ( `Cannot call clear${ capitalized } after the object is destroyed.` , ! this . isMetaDestroyed ( ) ) ;
246+
164247 this [ key ] = undefined ;
165248 } ;
166249
@@ -206,6 +289,8 @@ function inheritedMapOfMaps(name, Meta) {
206289 let capitalized = capitalize ( name ) ;
207290
208291 Meta . prototype [ 'write' + capitalized ] = function ( subkey , itemkey , value ) {
292+ assert ( `Cannot call write${ capitalized } after the object is destroyed.` , ! this . isMetaDestroyed ( ) ) ;
293+
209294 let outerMap = this . _getOrCreateOwnMap ( key ) ;
210295 let innerMap = outerMap [ subkey ] ;
211296 if ( ! innerMap ) {
@@ -277,6 +362,8 @@ function ownCustomObject(name, Meta) {
277362 let key = memberProperty ( name ) ;
278363 let capitalized = capitalize ( name ) ;
279364 Meta . prototype [ 'writable' + capitalized ] = function ( create ) {
365+ assert ( `Cannot call writable${ capitalized } after the object is destroyed.` , ! this . isMetaDestroyed ( ) ) ;
366+
280367 let ret = this [ key ] ;
281368 if ( ! ret ) {
282369 ret = this [ key ] = create ( this . source ) ;
@@ -295,6 +382,8 @@ function inheritedCustomObject(name, Meta) {
295382 let key = memberProperty ( name ) ;
296383 let capitalized = capitalize ( name ) ;
297384 Meta . prototype [ 'writable' + capitalized ] = function ( create ) {
385+ assert ( `Cannot call writable${ capitalized } after the object is destroyed.` , ! this . isMetaDestroyed ( ) ) ;
386+
298387 let ret = this [ key ] ;
299388 if ( ! ret ) {
300389 if ( this . parent ) {
@@ -376,7 +465,7 @@ const HAS_NATIVE_WEAKMAP = (function() {
376465 return Object . prototype . toString . call ( instance ) === '[object WeakMap]' ;
377466} ) ( ) ;
378467
379- let setMeta , peekMeta , deleteMeta ;
468+ let setMeta , peekMeta ;
380469
381470// choose the one appropriate for given platform
382471if ( HAS_NATIVE_WEAKMAP ) {
@@ -412,14 +501,6 @@ if (HAS_NATIVE_WEAKMAP) {
412501 runInDebug ( ( ) => counters . peakPrototypeWalks ++ ) ;
413502 }
414503 } ;
415-
416- deleteMeta = function WeakMap_deleteMeta ( obj ) {
417- runInDebug ( ( ) => counters . deleteCalls ++ ) ;
418-
419- // set value to `null` so that we can detect
420- // a deleted meta in peekMeta later
421- metaStore . set ( obj , null ) ;
422- } ;
423504} else {
424505 setMeta = function Fallback_setMeta ( obj , meta ) {
425506 // if `null` already, just set it to the new value
@@ -438,13 +519,15 @@ if (HAS_NATIVE_WEAKMAP) {
438519 peekMeta = function Fallback_peekMeta ( obj ) {
439520 return obj [ META_FIELD ] ;
440521 } ;
522+ }
441523
442- deleteMeta = function Fallback_deleteMeta ( obj ) {
443- if ( typeof obj [ META_FIELD ] !== 'object' ) {
444- return ;
445- }
446- obj [ META_FIELD ] = null ;
447- } ;
524+ export function deleteMeta ( obj ) {
525+ runInDebug ( ( ) => counters . deleteCalls ++ ) ;
526+
527+ let meta = peekMeta ( obj ) ;
528+ if ( meta ) {
529+ meta . destroy ( ) ;
530+ }
448531}
449532
450533/**
@@ -487,6 +570,5 @@ export function meta(obj) {
487570export {
488571 peekMeta ,
489572 setMeta ,
490- deleteMeta ,
491573 counters
492574} ;
0 commit comments