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,12 @@ let members = {
5558 tag : ownCustomObject
5659} ;
5760
61+ const FLAGS = {
62+ sourceDestroying : 1 << 1 ,
63+ sourceDestroyed : 1 << 2 ,
64+ metaDestroyed : 1 << 3
65+ } ;
66+
5867if ( isEnabled ( 'ember-glimmer-detect-backtracking-rerender' ) ||
5968 isEnabled ( 'ember-glimmer-allow-backtracking-rerender' ) ) {
6069 members . lastRendered = ownMap ;
@@ -78,6 +87,10 @@ export function Meta(obj, parentMeta) {
7887 this . _chains = undefined ;
7988 this . _tag = undefined ;
8089
90+ // initial value for all flags right now is false
91+ // see FLAGS const for detailed list of flags used
92+ this . _flags = 0 ;
93+
8194 // used only internally
8295 this . source = obj ;
8396
@@ -104,11 +117,70 @@ Meta.prototype.isInitialized = function(obj) {
104117 return this . proto !== obj ;
105118} ;
106119
120+ const NODE_STACK = [ ] ;
121+
122+ Meta . prototype . destroy = function ( ) {
123+ // remove chainWatchers to remove circular references that would prevent GC
124+
125+ let node , nodes , key , nodeObject ;
126+ node = this . readableChains ( ) ;
127+ if ( node ) {
128+ NODE_STACK . push ( node ) ;
129+ // process tree
130+ while ( NODE_STACK . length > 0 ) {
131+ node = NODE_STACK . pop ( ) ;
132+ // push children
133+ nodes = node . _chains ;
134+ if ( nodes ) {
135+ for ( key in nodes ) {
136+ if ( nodes [ key ] !== undefined ) {
137+ NODE_STACK . push ( nodes [ key ] ) ;
138+ }
139+ }
140+ }
141+
142+ // remove chainWatcher in node object
143+ if ( node . _watching ) {
144+ nodeObject = node . _object ;
145+ if ( nodeObject ) {
146+ let foreignMeta = peekMeta ( nodeObject ) ;
147+ // avoid cleaning up chain watchers when both current and
148+ // foreign objects are being destroyed
149+ if ( foreignMeta && ! foreignMeta . isSourceDestroying ( ) ) {
150+ removeChainWatcher ( nodeObject , node . _key , node , foreignMeta ) ;
151+ }
152+ }
153+ }
154+ }
155+ }
156+
157+ this . setMetaDestroyed ( ) ;
158+ } ;
159+
107160for ( let name in listenerMethods ) {
108161 Meta . prototype [ name ] = listenerMethods [ name ] ;
109162}
110163memberNames . forEach ( name => members [ name ] ( name , Meta ) ) ;
111164
165+ for ( let name in FLAGS ) {
166+ buildFlagAccessors ( name , FLAGS [ name ] , Meta ) ;
167+ }
168+
169+ function buildFlagAccessors ( name , flag , Meta ) {
170+ let capitalized = capitalize ( name ) ;
171+ Meta . prototype [ 'is' + capitalized ] = function ( ) {
172+ return ( this . _flags & flag ) !== 0 ;
173+ } ;
174+
175+ Meta . prototype [ 'set' + capitalized ] = function ( ) {
176+ this . _flags |= flag ;
177+ } ;
178+
179+ Meta . prototype [ 'unset' + capitalized ] = function ( ) {
180+ this . _flags &= ~ flag ;
181+ } ;
182+ }
183+
112184// Implements a member that is a lazily created, non-inheritable
113185// POJO.
114186function ownMap ( name , Meta ) {
@@ -135,6 +207,8 @@ function inheritedMap(name, Meta) {
135207 let capitalized = capitalize ( name ) ;
136208
137209 Meta . prototype [ 'write' + capitalized ] = function ( subkey , value ) {
210+ assert ( `Cannot call write${ capitalized } after the object is destroyed.` , ! this . isMetaDestroyed ( ) ) ;
211+
138212 let map = this . _getOrCreateOwnMap ( key ) ;
139213 map [ subkey ] = value ;
140214 } ;
@@ -161,6 +235,8 @@ function inheritedMap(name, Meta) {
161235 } ;
162236
163237 Meta . prototype [ 'clear' + capitalized ] = function ( ) {
238+ assert ( `Cannot call clear${ capitalized } after the object is destroyed.` , ! this . isMetaDestroyed ( ) ) ;
239+
164240 this [ key ] = undefined ;
165241 } ;
166242
@@ -206,6 +282,8 @@ function inheritedMapOfMaps(name, Meta) {
206282 let capitalized = capitalize ( name ) ;
207283
208284 Meta . prototype [ 'write' + capitalized ] = function ( subkey , itemkey , value ) {
285+ assert ( `Cannot call write${ capitalized } after the object is destroyed.` , ! this . isMetaDestroyed ( ) ) ;
286+
209287 let outerMap = this . _getOrCreateOwnMap ( key ) ;
210288 let innerMap = outerMap [ subkey ] ;
211289 if ( ! innerMap ) {
@@ -277,6 +355,8 @@ function ownCustomObject(name, Meta) {
277355 let key = memberProperty ( name ) ;
278356 let capitalized = capitalize ( name ) ;
279357 Meta . prototype [ 'writable' + capitalized ] = function ( create ) {
358+ assert ( `Cannot call writable${ capitalized } after the object is destroyed.` , ! this . isMetaDestroyed ( ) ) ;
359+
280360 let ret = this [ key ] ;
281361 if ( ! ret ) {
282362 ret = this [ key ] = create ( this . source ) ;
@@ -295,6 +375,8 @@ function inheritedCustomObject(name, Meta) {
295375 let key = memberProperty ( name ) ;
296376 let capitalized = capitalize ( name ) ;
297377 Meta . prototype [ 'writable' + capitalized ] = function ( create ) {
378+ assert ( `Cannot call writable${ capitalized } after the object is destroyed.` , ! this . isMetaDestroyed ( ) ) ;
379+
298380 let ret = this [ key ] ;
299381 if ( ! ret ) {
300382 if ( this . parent ) {
@@ -376,7 +458,7 @@ const HAS_NATIVE_WEAKMAP = (function() {
376458 return Object . prototype . toString . call ( instance ) === '[object WeakMap]' ;
377459} ) ( ) ;
378460
379- let setMeta , peekMeta , deleteMeta ;
461+ let setMeta , peekMeta ;
380462
381463// choose the one appropriate for given platform
382464if ( HAS_NATIVE_WEAKMAP ) {
@@ -412,14 +494,6 @@ if (HAS_NATIVE_WEAKMAP) {
412494 runInDebug ( ( ) => counters . peakPrototypeWalks ++ ) ;
413495 }
414496 } ;
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- } ;
423497} else {
424498 setMeta = function Fallback_setMeta ( obj , meta ) {
425499 // if `null` already, just set it to the new value
@@ -438,13 +512,15 @@ if (HAS_NATIVE_WEAKMAP) {
438512 peekMeta = function Fallback_peekMeta ( obj ) {
439513 return obj [ META_FIELD ] ;
440514 } ;
515+ }
441516
442- deleteMeta = function Fallback_deleteMeta ( obj ) {
443- if ( typeof obj [ META_FIELD ] !== 'object' ) {
444- return ;
445- }
446- obj [ META_FIELD ] = null ;
447- } ;
517+ export function deleteMeta ( obj ) {
518+ runInDebug ( ( ) => counters . deleteCalls ++ ) ;
519+
520+ let meta = peekMeta ( obj ) ;
521+ if ( meta ) {
522+ meta . destroy ( ) ;
523+ }
448524}
449525
450526/**
@@ -487,6 +563,5 @@ export function meta(obj) {
487563export {
488564 peekMeta ,
489565 setMeta ,
490- deleteMeta ,
491566 counters
492567} ;
0 commit comments