| OLD | NEW |
| 1 // Copyright 2012 the V8 project authors. All rights reserved. | 1 // Copyright 2012 the V8 project authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 var $observeNotifyChange; |
| 6 var $observeEnqueueSpliceRecord; |
| 7 var $observeBeginPerformSplice; |
| 8 var $observeEndPerformSplice; |
| 9 var $observeNativeObjectObserve; |
| 10 var $observeNativeObjectGetNotifier; |
| 11 var $observeNativeObjectNotifierPerformChange; |
| 12 |
| 13 (function() { |
| 14 |
| 5 "use strict"; | 15 "use strict"; |
| 6 | 16 |
| 17 %CheckIsBootstrapping(); |
| 18 |
| 19 var GlobalArray = global.Array; |
| 20 var GlobalObject = global.Object; |
| 21 |
| 22 // ------------------------------------------------------------------- |
| 23 |
| 7 // Overview: | 24 // Overview: |
| 8 // | 25 // |
| 9 // This file contains all of the routing and accounting for Object.observe. | 26 // This file contains all of the routing and accounting for Object.observe. |
| 10 // User code will interact with these mechanisms via the Object.observe APIs | 27 // User code will interact with these mechanisms via the Object.observe APIs |
| 11 // and, as a side effect of mutation objects which are observed. The V8 runtime | 28 // and, as a side effect of mutation objects which are observed. The V8 runtime |
| 12 // (both C++ and JS) will interact with these mechanisms primarily by enqueuing | 29 // (both C++ and JS) will interact with these mechanisms primarily by enqueuing |
| 13 // proper change records for objects which were mutated. The Object.observe | 30 // proper change records for objects which were mutated. The Object.observe |
| 14 // routing and accounting consists primarily of three participants | 31 // routing and accounting consists primarily of three participants |
| 15 // | 32 // |
| 16 // 1) ObjectInfo. This represents the observed state of a given object. It | 33 // 1) ObjectInfo. This represents the observed state of a given object. It |
| (...skipping 11 matching lines...) Expand all Loading... |
| 28 // (i.e. not Object.deliverChangeRecords), this is the mechanism by which | 45 // (i.e. not Object.deliverChangeRecords), this is the mechanism by which |
| 29 // callbacks are invoked in the proper order until there are no more | 46 // callbacks are invoked in the proper order until there are no more |
| 30 // change records pending to a callback. | 47 // change records pending to a callback. |
| 31 // | 48 // |
| 32 // Note that in order to reduce allocation and processing costs, the | 49 // Note that in order to reduce allocation and processing costs, the |
| 33 // implementation of (1) and (2) have "optimized" states which represent | 50 // implementation of (1) and (2) have "optimized" states which represent |
| 34 // common cases which can be handled more efficiently. | 51 // common cases which can be handled more efficiently. |
| 35 | 52 |
| 36 var observationState; | 53 var observationState; |
| 37 | 54 |
| 55 var notifierPrototype = {}; |
| 56 |
| 38 // We have to wait until after bootstrapping to grab a reference to the | 57 // We have to wait until after bootstrapping to grab a reference to the |
| 39 // observationState object, since it's not possible to serialize that | 58 // observationState object, since it's not possible to serialize that |
| 40 // reference into the snapshot. | 59 // reference into the snapshot. |
| 41 function GetObservationStateJS() { | 60 function GetObservationStateJS() { |
| 42 if (IS_UNDEFINED(observationState)) { | 61 if (IS_UNDEFINED(observationState)) { |
| 43 observationState = %GetObservationState(); | 62 observationState = %GetObservationState(); |
| 44 } | 63 } |
| 45 | 64 |
| 46 // TODO(adamk): Consider moving this code into heap.cc | 65 // TODO(adamk): Consider moving this code into heap.cc |
| 47 if (IS_UNDEFINED(observationState.callbackInfoMap)) { | 66 if (IS_UNDEFINED(observationState.callbackInfoMap)) { |
| 48 observationState.callbackInfoMap = %ObservationWeakMapCreate(); | 67 observationState.callbackInfoMap = %ObservationWeakMapCreate(); |
| 49 observationState.objectInfoMap = %ObservationWeakMapCreate(); | 68 observationState.objectInfoMap = %ObservationWeakMapCreate(); |
| 50 observationState.notifierObjectInfoMap = %ObservationWeakMapCreate(); | 69 observationState.notifierObjectInfoMap = %ObservationWeakMapCreate(); |
| 51 observationState.pendingObservers = null; | 70 observationState.pendingObservers = null; |
| 52 observationState.nextCallbackPriority = 0; | 71 observationState.nextCallbackPriority = 0; |
| 53 observationState.lastMicrotaskId = 0; | 72 observationState.lastMicrotaskId = 0; |
| 54 } | 73 } |
| 55 | 74 |
| 56 return observationState; | 75 return observationState; |
| 57 } | 76 } |
| 58 | 77 |
| 78 |
| 59 function GetPendingObservers() { | 79 function GetPendingObservers() { |
| 60 return GetObservationStateJS().pendingObservers; | 80 return GetObservationStateJS().pendingObservers; |
| 61 } | 81 } |
| 62 | 82 |
| 83 |
| 63 function SetPendingObservers(pendingObservers) { | 84 function SetPendingObservers(pendingObservers) { |
| 64 GetObservationStateJS().pendingObservers = pendingObservers; | 85 GetObservationStateJS().pendingObservers = pendingObservers; |
| 65 } | 86 } |
| 66 | 87 |
| 88 |
| 67 function GetNextCallbackPriority() { | 89 function GetNextCallbackPriority() { |
| 68 return GetObservationStateJS().nextCallbackPriority++; | 90 return GetObservationStateJS().nextCallbackPriority++; |
| 69 } | 91 } |
| 70 | 92 |
| 93 |
| 71 function nullProtoObject() { | 94 function nullProtoObject() { |
| 72 return { __proto__: null }; | 95 return { __proto__: null }; |
| 73 } | 96 } |
| 74 | 97 |
| 98 |
| 75 function TypeMapCreate() { | 99 function TypeMapCreate() { |
| 76 return nullProtoObject(); | 100 return nullProtoObject(); |
| 77 } | 101 } |
| 78 | 102 |
| 103 |
| 79 function TypeMapAddType(typeMap, type, ignoreDuplicate) { | 104 function TypeMapAddType(typeMap, type, ignoreDuplicate) { |
| 80 typeMap[type] = ignoreDuplicate ? 1 : (typeMap[type] || 0) + 1; | 105 typeMap[type] = ignoreDuplicate ? 1 : (typeMap[type] || 0) + 1; |
| 81 } | 106 } |
| 82 | 107 |
| 108 |
| 83 function TypeMapRemoveType(typeMap, type) { | 109 function TypeMapRemoveType(typeMap, type) { |
| 84 typeMap[type]--; | 110 typeMap[type]--; |
| 85 } | 111 } |
| 86 | 112 |
| 113 |
| 87 function TypeMapCreateFromList(typeList, length) { | 114 function TypeMapCreateFromList(typeList, length) { |
| 88 var typeMap = TypeMapCreate(); | 115 var typeMap = TypeMapCreate(); |
| 89 for (var i = 0; i < length; i++) { | 116 for (var i = 0; i < length; i++) { |
| 90 TypeMapAddType(typeMap, typeList[i], true); | 117 TypeMapAddType(typeMap, typeList[i], true); |
| 91 } | 118 } |
| 92 return typeMap; | 119 return typeMap; |
| 93 } | 120 } |
| 94 | 121 |
| 122 |
| 95 function TypeMapHasType(typeMap, type) { | 123 function TypeMapHasType(typeMap, type) { |
| 96 return !!typeMap[type]; | 124 return !!typeMap[type]; |
| 97 } | 125 } |
| 98 | 126 |
| 127 |
| 99 function TypeMapIsDisjointFrom(typeMap1, typeMap2) { | 128 function TypeMapIsDisjointFrom(typeMap1, typeMap2) { |
| 100 if (!typeMap1 || !typeMap2) | 129 if (!typeMap1 || !typeMap2) |
| 101 return true; | 130 return true; |
| 102 | 131 |
| 103 for (var type in typeMap1) { | 132 for (var type in typeMap1) { |
| 104 if (TypeMapHasType(typeMap1, type) && TypeMapHasType(typeMap2, type)) | 133 if (TypeMapHasType(typeMap1, type) && TypeMapHasType(typeMap2, type)) |
| 105 return false; | 134 return false; |
| 106 } | 135 } |
| 107 | 136 |
| 108 return true; | 137 return true; |
| 109 } | 138 } |
| 110 | 139 |
| 140 |
| 111 var defaultAcceptTypes = (function() { | 141 var defaultAcceptTypes = (function() { |
| 112 var defaultTypes = [ | 142 var defaultTypes = [ |
| 113 'add', | 143 'add', |
| 114 'update', | 144 'update', |
| 115 'delete', | 145 'delete', |
| 116 'setPrototype', | 146 'setPrototype', |
| 117 'reconfigure', | 147 'reconfigure', |
| 118 'preventExtensions' | 148 'preventExtensions' |
| 119 ]; | 149 ]; |
| 120 return TypeMapCreateFromList(defaultTypes, defaultTypes.length); | 150 return TypeMapCreateFromList(defaultTypes, defaultTypes.length); |
| 121 })(); | 151 })(); |
| 122 | 152 |
| 153 |
| 123 // An Observer is a registration to observe an object by a callback with | 154 // An Observer is a registration to observe an object by a callback with |
| 124 // a given set of accept types. If the set of accept types is the default | 155 // a given set of accept types. If the set of accept types is the default |
| 125 // set for Object.observe, the observer is represented as a direct reference | 156 // set for Object.observe, the observer is represented as a direct reference |
| 126 // to the callback. An observer never changes its accept types and thus never | 157 // to the callback. An observer never changes its accept types and thus never |
| 127 // needs to "normalize". | 158 // needs to "normalize". |
| 128 function ObserverCreate(callback, acceptList) { | 159 function ObserverCreate(callback, acceptList) { |
| 129 if (IS_UNDEFINED(acceptList)) | 160 if (IS_UNDEFINED(acceptList)) |
| 130 return callback; | 161 return callback; |
| 131 var observer = nullProtoObject(); | 162 var observer = nullProtoObject(); |
| 132 observer.callback = callback; | 163 observer.callback = callback; |
| 133 observer.accept = acceptList; | 164 observer.accept = acceptList; |
| 134 return observer; | 165 return observer; |
| 135 } | 166 } |
| 136 | 167 |
| 168 |
| 137 function ObserverGetCallback(observer) { | 169 function ObserverGetCallback(observer) { |
| 138 return IS_SPEC_FUNCTION(observer) ? observer : observer.callback; | 170 return IS_SPEC_FUNCTION(observer) ? observer : observer.callback; |
| 139 } | 171 } |
| 140 | 172 |
| 173 |
| 141 function ObserverGetAcceptTypes(observer) { | 174 function ObserverGetAcceptTypes(observer) { |
| 142 return IS_SPEC_FUNCTION(observer) ? defaultAcceptTypes : observer.accept; | 175 return IS_SPEC_FUNCTION(observer) ? defaultAcceptTypes : observer.accept; |
| 143 } | 176 } |
| 144 | 177 |
| 178 |
| 145 function ObserverIsActive(observer, objectInfo) { | 179 function ObserverIsActive(observer, objectInfo) { |
| 146 return TypeMapIsDisjointFrom(ObjectInfoGetPerformingTypes(objectInfo), | 180 return TypeMapIsDisjointFrom(ObjectInfoGetPerformingTypes(objectInfo), |
| 147 ObserverGetAcceptTypes(observer)); | 181 ObserverGetAcceptTypes(observer)); |
| 148 } | 182 } |
| 149 | 183 |
| 184 |
| 150 function ObjectInfoGetOrCreate(object) { | 185 function ObjectInfoGetOrCreate(object) { |
| 151 var objectInfo = ObjectInfoGet(object); | 186 var objectInfo = ObjectInfoGet(object); |
| 152 if (IS_UNDEFINED(objectInfo)) { | 187 if (IS_UNDEFINED(objectInfo)) { |
| 153 if (!%_IsJSProxy(object)) { | 188 if (!%_IsJSProxy(object)) { |
| 154 %SetIsObserved(object); | 189 %SetIsObserved(object); |
| 155 } | 190 } |
| 156 objectInfo = { | 191 objectInfo = { |
| 157 object: object, | 192 object: object, |
| 158 changeObservers: null, | 193 changeObservers: null, |
| 159 notifier: null, | 194 notifier: null, |
| 160 performing: null, | 195 performing: null, |
| 161 performingCount: 0, | 196 performingCount: 0, |
| 162 }; | 197 }; |
| 163 %WeakCollectionSet(GetObservationStateJS().objectInfoMap, | 198 %WeakCollectionSet(GetObservationStateJS().objectInfoMap, |
| 164 object, objectInfo); | 199 object, objectInfo); |
| 165 } | 200 } |
| 166 return objectInfo; | 201 return objectInfo; |
| 167 } | 202 } |
| 168 | 203 |
| 204 |
| 169 function ObjectInfoGet(object) { | 205 function ObjectInfoGet(object) { |
| 170 return %WeakCollectionGet(GetObservationStateJS().objectInfoMap, object); | 206 return %WeakCollectionGet(GetObservationStateJS().objectInfoMap, object); |
| 171 } | 207 } |
| 172 | 208 |
| 209 |
| 173 function ObjectInfoGetFromNotifier(notifier) { | 210 function ObjectInfoGetFromNotifier(notifier) { |
| 174 return %WeakCollectionGet(GetObservationStateJS().notifierObjectInfoMap, | 211 return %WeakCollectionGet(GetObservationStateJS().notifierObjectInfoMap, |
| 175 notifier); | 212 notifier); |
| 176 } | 213 } |
| 177 | 214 |
| 215 |
| 178 function ObjectInfoGetNotifier(objectInfo) { | 216 function ObjectInfoGetNotifier(objectInfo) { |
| 179 if (IS_NULL(objectInfo.notifier)) { | 217 if (IS_NULL(objectInfo.notifier)) { |
| 180 objectInfo.notifier = { __proto__: notifierPrototype }; | 218 objectInfo.notifier = { __proto__: notifierPrototype }; |
| 181 %WeakCollectionSet(GetObservationStateJS().notifierObjectInfoMap, | 219 %WeakCollectionSet(GetObservationStateJS().notifierObjectInfoMap, |
| 182 objectInfo.notifier, objectInfo); | 220 objectInfo.notifier, objectInfo); |
| 183 } | 221 } |
| 184 | 222 |
| 185 return objectInfo.notifier; | 223 return objectInfo.notifier; |
| 186 } | 224 } |
| 187 | 225 |
| 226 |
| 188 function ChangeObserversIsOptimized(changeObservers) { | 227 function ChangeObserversIsOptimized(changeObservers) { |
| 189 return IS_SPEC_FUNCTION(changeObservers) || | 228 return IS_SPEC_FUNCTION(changeObservers) || |
| 190 IS_SPEC_FUNCTION(changeObservers.callback); | 229 IS_SPEC_FUNCTION(changeObservers.callback); |
| 191 } | 230 } |
| 192 | 231 |
| 232 |
| 193 // The set of observers on an object is called 'changeObservers'. The first | 233 // The set of observers on an object is called 'changeObservers'. The first |
| 194 // observer is referenced directly via objectInfo.changeObservers. When a second | 234 // observer is referenced directly via objectInfo.changeObservers. When a second |
| 195 // is added, changeObservers "normalizes" to become a mapping of callback | 235 // is added, changeObservers "normalizes" to become a mapping of callback |
| 196 // priority -> observer and is then stored on objectInfo.changeObservers. | 236 // priority -> observer and is then stored on objectInfo.changeObservers. |
| 197 function ObjectInfoNormalizeChangeObservers(objectInfo) { | 237 function ObjectInfoNormalizeChangeObservers(objectInfo) { |
| 198 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { | 238 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
| 199 var observer = objectInfo.changeObservers; | 239 var observer = objectInfo.changeObservers; |
| 200 var callback = ObserverGetCallback(observer); | 240 var callback = ObserverGetCallback(observer); |
| 201 var callbackInfo = CallbackInfoGet(callback); | 241 var callbackInfo = CallbackInfoGet(callback); |
| 202 var priority = CallbackInfoGetPriority(callbackInfo); | 242 var priority = CallbackInfoGetPriority(callbackInfo); |
| 203 objectInfo.changeObservers = nullProtoObject(); | 243 objectInfo.changeObservers = nullProtoObject(); |
| 204 objectInfo.changeObservers[priority] = observer; | 244 objectInfo.changeObservers[priority] = observer; |
| 205 } | 245 } |
| 206 } | 246 } |
| 207 | 247 |
| 248 |
| 208 function ObjectInfoAddObserver(objectInfo, callback, acceptList) { | 249 function ObjectInfoAddObserver(objectInfo, callback, acceptList) { |
| 209 var callbackInfo = CallbackInfoGetOrCreate(callback); | 250 var callbackInfo = CallbackInfoGetOrCreate(callback); |
| 210 var observer = ObserverCreate(callback, acceptList); | 251 var observer = ObserverCreate(callback, acceptList); |
| 211 | 252 |
| 212 if (!objectInfo.changeObservers) { | 253 if (!objectInfo.changeObservers) { |
| 213 objectInfo.changeObservers = observer; | 254 objectInfo.changeObservers = observer; |
| 214 return; | 255 return; |
| 215 } | 256 } |
| 216 | 257 |
| 217 ObjectInfoNormalizeChangeObservers(objectInfo); | 258 ObjectInfoNormalizeChangeObservers(objectInfo); |
| (...skipping 25 matching lines...) Expand all Loading... |
| 243 | 284 |
| 244 for (var priority in objectInfo.changeObservers) { | 285 for (var priority in objectInfo.changeObservers) { |
| 245 var observer = objectInfo.changeObservers[priority]; | 286 var observer = objectInfo.changeObservers[priority]; |
| 246 if (!IS_NULL(observer) && ObserverIsActive(observer, objectInfo)) | 287 if (!IS_NULL(observer) && ObserverIsActive(observer, objectInfo)) |
| 247 return true; | 288 return true; |
| 248 } | 289 } |
| 249 | 290 |
| 250 return false; | 291 return false; |
| 251 } | 292 } |
| 252 | 293 |
| 294 |
| 253 function ObjectInfoAddPerformingType(objectInfo, type) { | 295 function ObjectInfoAddPerformingType(objectInfo, type) { |
| 254 objectInfo.performing = objectInfo.performing || TypeMapCreate(); | 296 objectInfo.performing = objectInfo.performing || TypeMapCreate(); |
| 255 TypeMapAddType(objectInfo.performing, type); | 297 TypeMapAddType(objectInfo.performing, type); |
| 256 objectInfo.performingCount++; | 298 objectInfo.performingCount++; |
| 257 } | 299 } |
| 258 | 300 |
| 301 |
| 259 function ObjectInfoRemovePerformingType(objectInfo, type) { | 302 function ObjectInfoRemovePerformingType(objectInfo, type) { |
| 260 objectInfo.performingCount--; | 303 objectInfo.performingCount--; |
| 261 TypeMapRemoveType(objectInfo.performing, type); | 304 TypeMapRemoveType(objectInfo.performing, type); |
| 262 } | 305 } |
| 263 | 306 |
| 307 |
| 264 function ObjectInfoGetPerformingTypes(objectInfo) { | 308 function ObjectInfoGetPerformingTypes(objectInfo) { |
| 265 return objectInfo.performingCount > 0 ? objectInfo.performing : null; | 309 return objectInfo.performingCount > 0 ? objectInfo.performing : null; |
| 266 } | 310 } |
| 267 | 311 |
| 312 |
| 268 function ConvertAcceptListToTypeMap(arg) { | 313 function ConvertAcceptListToTypeMap(arg) { |
| 269 // We use undefined as a sentinel for the default accept list. | 314 // We use undefined as a sentinel for the default accept list. |
| 270 if (IS_UNDEFINED(arg)) | 315 if (IS_UNDEFINED(arg)) |
| 271 return arg; | 316 return arg; |
| 272 | 317 |
| 273 if (!IS_SPEC_OBJECT(arg)) | 318 if (!IS_SPEC_OBJECT(arg)) |
| 274 throw MakeTypeError("observe_invalid_accept"); | 319 throw MakeTypeError("observe_invalid_accept"); |
| 275 | 320 |
| 276 var len = ToInteger(arg.length); | 321 var len = ToInteger(arg.length); |
| 277 if (len < 0) len = 0; | 322 if (len < 0) len = 0; |
| 278 | 323 |
| 279 return TypeMapCreateFromList(arg, len); | 324 return TypeMapCreateFromList(arg, len); |
| 280 } | 325 } |
| 281 | 326 |
| 327 |
| 282 // CallbackInfo's optimized state is just a number which represents its global | 328 // CallbackInfo's optimized state is just a number which represents its global |
| 283 // priority. When a change record must be enqueued for the callback, it | 329 // priority. When a change record must be enqueued for the callback, it |
| 284 // normalizes. When delivery clears any pending change records, it re-optimizes. | 330 // normalizes. When delivery clears any pending change records, it re-optimizes. |
| 285 function CallbackInfoGet(callback) { | 331 function CallbackInfoGet(callback) { |
| 286 return %WeakCollectionGet(GetObservationStateJS().callbackInfoMap, callback); | 332 return %WeakCollectionGet(GetObservationStateJS().callbackInfoMap, callback); |
| 287 } | 333 } |
| 288 | 334 |
| 335 |
| 289 function CallbackInfoSet(callback, callbackInfo) { | 336 function CallbackInfoSet(callback, callbackInfo) { |
| 290 %WeakCollectionSet(GetObservationStateJS().callbackInfoMap, | 337 %WeakCollectionSet(GetObservationStateJS().callbackInfoMap, |
| 291 callback, callbackInfo); | 338 callback, callbackInfo); |
| 292 } | 339 } |
| 293 | 340 |
| 341 |
| 294 function CallbackInfoGetOrCreate(callback) { | 342 function CallbackInfoGetOrCreate(callback) { |
| 295 var callbackInfo = CallbackInfoGet(callback); | 343 var callbackInfo = CallbackInfoGet(callback); |
| 296 if (!IS_UNDEFINED(callbackInfo)) | 344 if (!IS_UNDEFINED(callbackInfo)) |
| 297 return callbackInfo; | 345 return callbackInfo; |
| 298 | 346 |
| 299 var priority = GetNextCallbackPriority(); | 347 var priority = GetNextCallbackPriority(); |
| 300 CallbackInfoSet(callback, priority); | 348 CallbackInfoSet(callback, priority); |
| 301 return priority; | 349 return priority; |
| 302 } | 350 } |
| 303 | 351 |
| 352 |
| 304 function CallbackInfoGetPriority(callbackInfo) { | 353 function CallbackInfoGetPriority(callbackInfo) { |
| 305 if (IS_NUMBER(callbackInfo)) | 354 if (IS_NUMBER(callbackInfo)) |
| 306 return callbackInfo; | 355 return callbackInfo; |
| 307 else | 356 else |
| 308 return callbackInfo.priority; | 357 return callbackInfo.priority; |
| 309 } | 358 } |
| 310 | 359 |
| 360 |
| 311 function CallbackInfoNormalize(callback) { | 361 function CallbackInfoNormalize(callback) { |
| 312 var callbackInfo = CallbackInfoGet(callback); | 362 var callbackInfo = CallbackInfoGet(callback); |
| 313 if (IS_NUMBER(callbackInfo)) { | 363 if (IS_NUMBER(callbackInfo)) { |
| 314 var priority = callbackInfo; | 364 var priority = callbackInfo; |
| 315 callbackInfo = new InternalArray; | 365 callbackInfo = new InternalArray; |
| 316 callbackInfo.priority = priority; | 366 callbackInfo.priority = priority; |
| 317 CallbackInfoSet(callback, callbackInfo); | 367 CallbackInfoSet(callback, callbackInfo); |
| 318 } | 368 } |
| 319 return callbackInfo; | 369 return callbackInfo; |
| 320 } | 370 } |
| 321 | 371 |
| 372 |
| 322 function ObjectObserve(object, callback, acceptList) { | 373 function ObjectObserve(object, callback, acceptList) { |
| 323 if (!IS_SPEC_OBJECT(object)) | 374 if (!IS_SPEC_OBJECT(object)) |
| 324 throw MakeTypeError("observe_non_object", ["observe"]); | 375 throw MakeTypeError("observe_non_object", ["observe"]); |
| 325 if (%IsJSGlobalProxy(object)) | 376 if (%IsJSGlobalProxy(object)) |
| 326 throw MakeTypeError("observe_global_proxy", ["observe"]); | 377 throw MakeTypeError("observe_global_proxy", ["observe"]); |
| 327 if (!IS_SPEC_FUNCTION(callback)) | 378 if (!IS_SPEC_FUNCTION(callback)) |
| 328 throw MakeTypeError("observe_non_function", ["observe"]); | 379 throw MakeTypeError("observe_non_function", ["observe"]); |
| 329 if (ObjectIsFrozen(callback)) | 380 if (ObjectIsFrozen(callback)) |
| 330 throw MakeTypeError("observe_callback_frozen"); | 381 throw MakeTypeError("observe_callback_frozen"); |
| 331 | 382 |
| 332 var objectObserveFn = %GetObjectContextObjectObserve(object); | 383 var objectObserveFn = %GetObjectContextObjectObserve(object); |
| 333 return objectObserveFn(object, callback, acceptList); | 384 return objectObserveFn(object, callback, acceptList); |
| 334 } | 385 } |
| 335 | 386 |
| 387 |
| 336 function NativeObjectObserve(object, callback, acceptList) { | 388 function NativeObjectObserve(object, callback, acceptList) { |
| 337 var objectInfo = ObjectInfoGetOrCreate(object); | 389 var objectInfo = ObjectInfoGetOrCreate(object); |
| 338 var typeList = ConvertAcceptListToTypeMap(acceptList); | 390 var typeList = ConvertAcceptListToTypeMap(acceptList); |
| 339 ObjectInfoAddObserver(objectInfo, callback, typeList); | 391 ObjectInfoAddObserver(objectInfo, callback, typeList); |
| 340 return object; | 392 return object; |
| 341 } | 393 } |
| 342 | 394 |
| 395 |
| 343 function ObjectUnobserve(object, callback) { | 396 function ObjectUnobserve(object, callback) { |
| 344 if (!IS_SPEC_OBJECT(object)) | 397 if (!IS_SPEC_OBJECT(object)) |
| 345 throw MakeTypeError("observe_non_object", ["unobserve"]); | 398 throw MakeTypeError("observe_non_object", ["unobserve"]); |
| 346 if (%IsJSGlobalProxy(object)) | 399 if (%IsJSGlobalProxy(object)) |
| 347 throw MakeTypeError("observe_global_proxy", ["unobserve"]); | 400 throw MakeTypeError("observe_global_proxy", ["unobserve"]); |
| 348 if (!IS_SPEC_FUNCTION(callback)) | 401 if (!IS_SPEC_FUNCTION(callback)) |
| 349 throw MakeTypeError("observe_non_function", ["unobserve"]); | 402 throw MakeTypeError("observe_non_function", ["unobserve"]); |
| 350 | 403 |
| 351 var objectInfo = ObjectInfoGet(object); | 404 var objectInfo = ObjectInfoGet(object); |
| 352 if (IS_UNDEFINED(objectInfo)) | 405 if (IS_UNDEFINED(objectInfo)) |
| 353 return object; | 406 return object; |
| 354 | 407 |
| 355 ObjectInfoRemoveObserver(objectInfo, callback); | 408 ObjectInfoRemoveObserver(objectInfo, callback); |
| 356 return object; | 409 return object; |
| 357 } | 410 } |
| 358 | 411 |
| 412 |
| 359 function ArrayObserve(object, callback) { | 413 function ArrayObserve(object, callback) { |
| 360 return ObjectObserve(object, callback, ['add', | 414 return ObjectObserve(object, callback, ['add', |
| 361 'update', | 415 'update', |
| 362 'delete', | 416 'delete', |
| 363 'splice']); | 417 'splice']); |
| 364 } | 418 } |
| 365 | 419 |
| 420 |
| 366 function ArrayUnobserve(object, callback) { | 421 function ArrayUnobserve(object, callback) { |
| 367 return ObjectUnobserve(object, callback); | 422 return ObjectUnobserve(object, callback); |
| 368 } | 423 } |
| 369 | 424 |
| 425 |
| 370 function ObserverEnqueueIfActive(observer, objectInfo, changeRecord) { | 426 function ObserverEnqueueIfActive(observer, objectInfo, changeRecord) { |
| 371 if (!ObserverIsActive(observer, objectInfo) || | 427 if (!ObserverIsActive(observer, objectInfo) || |
| 372 !TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) { | 428 !TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) { |
| 373 return; | 429 return; |
| 374 } | 430 } |
| 375 | 431 |
| 376 var callback = ObserverGetCallback(observer); | 432 var callback = ObserverGetCallback(observer); |
| 377 if (!%ObserverObjectAndRecordHaveSameOrigin(callback, changeRecord.object, | 433 if (!%ObserverObjectAndRecordHaveSameOrigin(callback, changeRecord.object, |
| 378 changeRecord)) { | 434 changeRecord)) { |
| 379 return; | 435 return; |
| (...skipping 12 matching lines...) Expand all Loading... |
| 392 }); | 448 }); |
| 393 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); | 449 %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name }); |
| 394 } else { | 450 } else { |
| 395 %EnqueueMicrotask(ObserveMicrotaskRunner); | 451 %EnqueueMicrotask(ObserveMicrotaskRunner); |
| 396 } | 452 } |
| 397 } | 453 } |
| 398 GetPendingObservers()[callbackInfo.priority] = callback; | 454 GetPendingObservers()[callbackInfo.priority] = callback; |
| 399 callbackInfo.push(changeRecord); | 455 callbackInfo.push(changeRecord); |
| 400 } | 456 } |
| 401 | 457 |
| 458 |
| 402 function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) { | 459 function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) { |
| 403 if (!ObjectInfoHasActiveObservers(objectInfo)) | 460 if (!ObjectInfoHasActiveObservers(objectInfo)) |
| 404 return; | 461 return; |
| 405 | 462 |
| 406 var hasType = !IS_UNDEFINED(type); | 463 var hasType = !IS_UNDEFINED(type); |
| 407 var newRecord = hasType ? | 464 var newRecord = hasType ? |
| 408 { object: objectInfo.object, type: type } : | 465 { object: objectInfo.object, type: type } : |
| 409 { object: objectInfo.object }; | 466 { object: objectInfo.object }; |
| 410 | 467 |
| 411 for (var prop in changeRecord) { | 468 for (var prop in changeRecord) { |
| 412 if (prop === 'object' || (hasType && prop === 'type')) continue; | 469 if (prop === 'object' || (hasType && prop === 'type')) continue; |
| 413 %DefineDataPropertyUnchecked( | 470 %DefineDataPropertyUnchecked( |
| 414 newRecord, prop, changeRecord[prop], READ_ONLY + DONT_DELETE); | 471 newRecord, prop, changeRecord[prop], READ_ONLY + DONT_DELETE); |
| 415 } | 472 } |
| 416 ObjectFreezeJS(newRecord); | 473 ObjectFreezeJS(newRecord); |
| 417 | 474 |
| 418 ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord); | 475 ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord); |
| 419 } | 476 } |
| 420 | 477 |
| 478 |
| 421 function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord) { | 479 function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord) { |
| 422 // TODO(rossberg): adjust once there is a story for symbols vs proxies. | 480 // TODO(rossberg): adjust once there is a story for symbols vs proxies. |
| 423 if (IS_SYMBOL(changeRecord.name)) return; | 481 if (IS_SYMBOL(changeRecord.name)) return; |
| 424 | 482 |
| 425 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { | 483 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
| 426 var observer = objectInfo.changeObservers; | 484 var observer = objectInfo.changeObservers; |
| 427 ObserverEnqueueIfActive(observer, objectInfo, changeRecord); | 485 ObserverEnqueueIfActive(observer, objectInfo, changeRecord); |
| 428 return; | 486 return; |
| 429 } | 487 } |
| 430 | 488 |
| 431 for (var priority in objectInfo.changeObservers) { | 489 for (var priority in objectInfo.changeObservers) { |
| 432 var observer = objectInfo.changeObservers[priority]; | 490 var observer = objectInfo.changeObservers[priority]; |
| 433 if (IS_NULL(observer)) | 491 if (IS_NULL(observer)) |
| 434 continue; | 492 continue; |
| 435 ObserverEnqueueIfActive(observer, objectInfo, changeRecord); | 493 ObserverEnqueueIfActive(observer, objectInfo, changeRecord); |
| 436 } | 494 } |
| 437 } | 495 } |
| 438 | 496 |
| 497 |
| 439 function BeginPerformSplice(array) { | 498 function BeginPerformSplice(array) { |
| 440 var objectInfo = ObjectInfoGet(array); | 499 var objectInfo = ObjectInfoGet(array); |
| 441 if (!IS_UNDEFINED(objectInfo)) | 500 if (!IS_UNDEFINED(objectInfo)) |
| 442 ObjectInfoAddPerformingType(objectInfo, 'splice'); | 501 ObjectInfoAddPerformingType(objectInfo, 'splice'); |
| 443 } | 502 } |
| 444 | 503 |
| 504 |
| 445 function EndPerformSplice(array) { | 505 function EndPerformSplice(array) { |
| 446 var objectInfo = ObjectInfoGet(array); | 506 var objectInfo = ObjectInfoGet(array); |
| 447 if (!IS_UNDEFINED(objectInfo)) | 507 if (!IS_UNDEFINED(objectInfo)) |
| 448 ObjectInfoRemovePerformingType(objectInfo, 'splice'); | 508 ObjectInfoRemovePerformingType(objectInfo, 'splice'); |
| 449 } | 509 } |
| 450 | 510 |
| 511 |
| 451 function EnqueueSpliceRecord(array, index, removed, addedCount) { | 512 function EnqueueSpliceRecord(array, index, removed, addedCount) { |
| 452 var objectInfo = ObjectInfoGet(array); | 513 var objectInfo = ObjectInfoGet(array); |
| 453 if (!ObjectInfoHasActiveObservers(objectInfo)) | 514 if (!ObjectInfoHasActiveObservers(objectInfo)) |
| 454 return; | 515 return; |
| 455 | 516 |
| 456 var changeRecord = { | 517 var changeRecord = { |
| 457 type: 'splice', | 518 type: 'splice', |
| 458 object: array, | 519 object: array, |
| 459 index: index, | 520 index: index, |
| 460 removed: removed, | 521 removed: removed, |
| 461 addedCount: addedCount | 522 addedCount: addedCount |
| 462 }; | 523 }; |
| 463 | 524 |
| 464 ObjectFreezeJS(changeRecord); | 525 ObjectFreezeJS(changeRecord); |
| 465 ObjectFreezeJS(changeRecord.removed); | 526 ObjectFreezeJS(changeRecord.removed); |
| 466 ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord); | 527 ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord); |
| 467 } | 528 } |
| 468 | 529 |
| 530 |
| 469 function NotifyChange(type, object, name, oldValue) { | 531 function NotifyChange(type, object, name, oldValue) { |
| 470 var objectInfo = ObjectInfoGet(object); | 532 var objectInfo = ObjectInfoGet(object); |
| 471 if (!ObjectInfoHasActiveObservers(objectInfo)) | 533 if (!ObjectInfoHasActiveObservers(objectInfo)) |
| 472 return; | 534 return; |
| 473 | 535 |
| 474 var changeRecord; | 536 var changeRecord; |
| 475 if (arguments.length == 2) { | 537 if (arguments.length == 2) { |
| 476 changeRecord = { type: type, object: object }; | 538 changeRecord = { type: type, object: object }; |
| 477 } else if (arguments.length == 3) { | 539 } else if (arguments.length == 3) { |
| 478 changeRecord = { type: type, object: object, name: name }; | 540 changeRecord = { type: type, object: object, name: name }; |
| 479 } else { | 541 } else { |
| 480 changeRecord = { | 542 changeRecord = { |
| 481 type: type, | 543 type: type, |
| 482 object: object, | 544 object: object, |
| 483 name: name, | 545 name: name, |
| 484 oldValue: oldValue | 546 oldValue: oldValue |
| 485 }; | 547 }; |
| 486 } | 548 } |
| 487 | 549 |
| 488 ObjectFreezeJS(changeRecord); | 550 ObjectFreezeJS(changeRecord); |
| 489 ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord); | 551 ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord); |
| 490 } | 552 } |
| 491 | 553 |
| 492 var notifierPrototype = {}; | |
| 493 | 554 |
| 494 function ObjectNotifierNotify(changeRecord) { | 555 function ObjectNotifierNotify(changeRecord) { |
| 495 if (!IS_SPEC_OBJECT(this)) | 556 if (!IS_SPEC_OBJECT(this)) |
| 496 throw MakeTypeError("called_on_non_object", ["notify"]); | 557 throw MakeTypeError("called_on_non_object", ["notify"]); |
| 497 | 558 |
| 498 var objectInfo = ObjectInfoGetFromNotifier(this); | 559 var objectInfo = ObjectInfoGetFromNotifier(this); |
| 499 if (IS_UNDEFINED(objectInfo)) | 560 if (IS_UNDEFINED(objectInfo)) |
| 500 throw MakeTypeError("observe_notify_non_notifier"); | 561 throw MakeTypeError("observe_notify_non_notifier"); |
| 501 if (!IS_STRING(changeRecord.type)) | 562 if (!IS_STRING(changeRecord.type)) |
| 502 throw MakeTypeError("observe_type_non_string"); | 563 throw MakeTypeError("observe_type_non_string"); |
| 503 | 564 |
| 504 ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord); | 565 ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord); |
| 505 } | 566 } |
| 506 | 567 |
| 568 |
| 507 function ObjectNotifierPerformChange(changeType, changeFn) { | 569 function ObjectNotifierPerformChange(changeType, changeFn) { |
| 508 if (!IS_SPEC_OBJECT(this)) | 570 if (!IS_SPEC_OBJECT(this)) |
| 509 throw MakeTypeError("called_on_non_object", ["performChange"]); | 571 throw MakeTypeError("called_on_non_object", ["performChange"]); |
| 510 | 572 |
| 511 var objectInfo = ObjectInfoGetFromNotifier(this); | 573 var objectInfo = ObjectInfoGetFromNotifier(this); |
| 512 if (IS_UNDEFINED(objectInfo)) | 574 if (IS_UNDEFINED(objectInfo)) |
| 513 throw MakeTypeError("observe_notify_non_notifier"); | 575 throw MakeTypeError("observe_notify_non_notifier"); |
| 514 if (!IS_STRING(changeType)) | 576 if (!IS_STRING(changeType)) |
| 515 throw MakeTypeError("observe_perform_non_string"); | 577 throw MakeTypeError("observe_perform_non_string"); |
| 516 if (!IS_SPEC_FUNCTION(changeFn)) | 578 if (!IS_SPEC_FUNCTION(changeFn)) |
| 517 throw MakeTypeError("observe_perform_non_function"); | 579 throw MakeTypeError("observe_perform_non_function"); |
| 518 | 580 |
| 519 var performChangeFn = %GetObjectContextNotifierPerformChange(objectInfo); | 581 var performChangeFn = %GetObjectContextNotifierPerformChange(objectInfo); |
| 520 performChangeFn(objectInfo, changeType, changeFn); | 582 performChangeFn(objectInfo, changeType, changeFn); |
| 521 } | 583 } |
| 522 | 584 |
| 585 |
| 523 function NativeObjectNotifierPerformChange(objectInfo, changeType, changeFn) { | 586 function NativeObjectNotifierPerformChange(objectInfo, changeType, changeFn) { |
| 524 ObjectInfoAddPerformingType(objectInfo, changeType); | 587 ObjectInfoAddPerformingType(objectInfo, changeType); |
| 525 | 588 |
| 526 var changeRecord; | 589 var changeRecord; |
| 527 try { | 590 try { |
| 528 changeRecord = %_CallFunction(UNDEFINED, changeFn); | 591 changeRecord = %_CallFunction(UNDEFINED, changeFn); |
| 529 } finally { | 592 } finally { |
| 530 ObjectInfoRemovePerformingType(objectInfo, changeType); | 593 ObjectInfoRemovePerformingType(objectInfo, changeType); |
| 531 } | 594 } |
| 532 | 595 |
| 533 if (IS_SPEC_OBJECT(changeRecord)) | 596 if (IS_SPEC_OBJECT(changeRecord)) |
| 534 ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, changeType); | 597 ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, changeType); |
| 535 } | 598 } |
| 536 | 599 |
| 600 |
| 537 function ObjectGetNotifier(object) { | 601 function ObjectGetNotifier(object) { |
| 538 if (!IS_SPEC_OBJECT(object)) | 602 if (!IS_SPEC_OBJECT(object)) |
| 539 throw MakeTypeError("observe_non_object", ["getNotifier"]); | 603 throw MakeTypeError("observe_non_object", ["getNotifier"]); |
| 540 if (%IsJSGlobalProxy(object)) | 604 if (%IsJSGlobalProxy(object)) |
| 541 throw MakeTypeError("observe_global_proxy", ["getNotifier"]); | 605 throw MakeTypeError("observe_global_proxy", ["getNotifier"]); |
| 542 | 606 |
| 543 if (ObjectIsFrozen(object)) return null; | 607 if (ObjectIsFrozen(object)) return null; |
| 544 | 608 |
| 545 if (!%ObjectWasCreatedInCurrentOrigin(object)) return null; | 609 if (!%ObjectWasCreatedInCurrentOrigin(object)) return null; |
| 546 | 610 |
| 547 var getNotifierFn = %GetObjectContextObjectGetNotifier(object); | 611 var getNotifierFn = %GetObjectContextObjectGetNotifier(object); |
| 548 return getNotifierFn(object); | 612 return getNotifierFn(object); |
| 549 } | 613 } |
| 550 | 614 |
| 615 |
| 551 function NativeObjectGetNotifier(object) { | 616 function NativeObjectGetNotifier(object) { |
| 552 var objectInfo = ObjectInfoGetOrCreate(object); | 617 var objectInfo = ObjectInfoGetOrCreate(object); |
| 553 return ObjectInfoGetNotifier(objectInfo); | 618 return ObjectInfoGetNotifier(objectInfo); |
| 554 } | 619 } |
| 555 | 620 |
| 621 |
| 556 function CallbackDeliverPending(callback) { | 622 function CallbackDeliverPending(callback) { |
| 557 var callbackInfo = CallbackInfoGet(callback); | 623 var callbackInfo = CallbackInfoGet(callback); |
| 558 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) | 624 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) |
| 559 return false; | 625 return false; |
| 560 | 626 |
| 561 // Clear the pending change records from callback and return it to its | 627 // Clear the pending change records from callback and return it to its |
| 562 // "optimized" state. | 628 // "optimized" state. |
| 563 var priority = callbackInfo.priority; | 629 var priority = callbackInfo.priority; |
| 564 CallbackInfoSet(callback, priority); | 630 CallbackInfoSet(callback, priority); |
| 565 | 631 |
| 566 var pendingObservers = GetPendingObservers(); | 632 var pendingObservers = GetPendingObservers(); |
| 567 if (!IS_NULL(pendingObservers)) | 633 if (!IS_NULL(pendingObservers)) |
| 568 delete pendingObservers[priority]; | 634 delete pendingObservers[priority]; |
| 569 | 635 |
| 570 // TODO: combine the following runtime calls for perf optimization. | 636 // TODO: combine the following runtime calls for perf optimization. |
| 571 var delivered = []; | 637 var delivered = []; |
| 572 %MoveArrayContents(callbackInfo, delivered); | 638 %MoveArrayContents(callbackInfo, delivered); |
| 573 %DeliverObservationChangeRecords(callback, delivered); | 639 %DeliverObservationChangeRecords(callback, delivered); |
| 574 | 640 |
| 575 return true; | 641 return true; |
| 576 } | 642 } |
| 577 | 643 |
| 644 |
| 578 function ObjectDeliverChangeRecords(callback) { | 645 function ObjectDeliverChangeRecords(callback) { |
| 579 if (!IS_SPEC_FUNCTION(callback)) | 646 if (!IS_SPEC_FUNCTION(callback)) |
| 580 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); | 647 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); |
| 581 | 648 |
| 582 while (CallbackDeliverPending(callback)) {} | 649 while (CallbackDeliverPending(callback)) {} |
| 583 } | 650 } |
| 584 | 651 |
| 652 |
| 585 function ObserveMicrotaskRunner() { | 653 function ObserveMicrotaskRunner() { |
| 586 var pendingObservers = GetPendingObservers(); | 654 var pendingObservers = GetPendingObservers(); |
| 587 if (!IS_NULL(pendingObservers)) { | 655 if (!IS_NULL(pendingObservers)) { |
| 588 SetPendingObservers(null); | 656 SetPendingObservers(null); |
| 589 for (var i in pendingObservers) { | 657 for (var i in pendingObservers) { |
| 590 CallbackDeliverPending(pendingObservers[i]); | 658 CallbackDeliverPending(pendingObservers[i]); |
| 591 } | 659 } |
| 592 } | 660 } |
| 593 } | 661 } |
| 594 | 662 |
| 595 function SetupObjectObserve() { | 663 // ------------------------------------------------------------------- |
| 596 %CheckIsBootstrapping(); | |
| 597 InstallFunctions($Object, DONT_ENUM, [ | |
| 598 "deliverChangeRecords", ObjectDeliverChangeRecords, | |
| 599 "getNotifier", ObjectGetNotifier, | |
| 600 "observe", ObjectObserve, | |
| 601 "unobserve", ObjectUnobserve | |
| 602 ]); | |
| 603 InstallFunctions($Array, DONT_ENUM, [ | |
| 604 "observe", ArrayObserve, | |
| 605 "unobserve", ArrayUnobserve | |
| 606 ]); | |
| 607 InstallFunctions(notifierPrototype, DONT_ENUM, [ | |
| 608 "notify", ObjectNotifierNotify, | |
| 609 "performChange", ObjectNotifierPerformChange | |
| 610 ]); | |
| 611 } | |
| 612 | 664 |
| 613 SetupObjectObserve(); | 665 InstallFunctions(GlobalObject, DONT_ENUM, [ |
| 666 "deliverChangeRecords", ObjectDeliverChangeRecords, |
| 667 "getNotifier", ObjectGetNotifier, |
| 668 "observe", ObjectObserve, |
| 669 "unobserve", ObjectUnobserve |
| 670 ]); |
| 671 InstallFunctions(GlobalArray, DONT_ENUM, [ |
| 672 "observe", ArrayObserve, |
| 673 "unobserve", ArrayUnobserve |
| 674 ]); |
| 675 InstallFunctions(notifierPrototype, DONT_ENUM, [ |
| 676 "notify", ObjectNotifierNotify, |
| 677 "performChange", ObjectNotifierPerformChange |
| 678 ]); |
| 679 |
| 680 $observeNotifyChange = NotifyChange; |
| 681 $observeEnqueueSpliceRecord = EnqueueSpliceRecord; |
| 682 $observeBeginPerformSplice = BeginPerformSplice; |
| 683 $observeEndPerformSplice = EndPerformSplice; |
| 684 $observeNativeObjectObserve = NativeObjectObserve; |
| 685 $observeNativeObjectGetNotifier = NativeObjectGetNotifier; |
| 686 $observeNativeObjectNotifierPerformChange = NativeObjectNotifierPerformChange; |
| 687 |
| 688 })(); |
| OLD | NEW |