Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright 2012 the V8 project authors. All rights reserved. | 1 // Copyright 2012 the V8 project authors. All rights reserved. |
| 2 // Redistribution and use in source and binary forms, with or without | 2 // Redistribution and use in source and binary forms, with or without |
| 3 // modification, are permitted provided that the following conditions are | 3 // modification, are permitted provided that the following conditions are |
| 4 // met: | 4 // met: |
| 5 // | 5 // |
| 6 // * Redistributions of source code must retain the above copyright | 6 // * Redistributions of source code must retain the above copyright |
| 7 // notice, this list of conditions and the following disclaimer. | 7 // notice, this list of conditions and the following disclaimer. |
| 8 // * Redistributions in binary form must reproduce the above | 8 // * Redistributions in binary form must reproduce the above |
| 9 // copyright notice, this list of conditions and the following | 9 // copyright notice, this list of conditions and the following |
| 10 // disclaimer in the documentation and/or other materials provided | 10 // disclaimer in the documentation and/or other materials provided |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 24 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | 25 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 26 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 27 | 27 |
| 28 "use strict"; | 28 "use strict"; |
| 29 | 29 |
| 30 var observationState = %GetObservationState(); | 30 var observationState = %GetObservationState(); |
| 31 if (IS_UNDEFINED(observationState.callbackInfoMap)) { | 31 if (IS_UNDEFINED(observationState.callbackInfoMap)) { |
| 32 observationState.callbackInfoMap = %ObservationWeakMapCreate(); | 32 observationState.callbackInfoMap = %ObservationWeakMapCreate(); |
| 33 observationState.objectInfoMap = %ObservationWeakMapCreate(); | 33 observationState.objectInfoMap = %ObservationWeakMapCreate(); |
| 34 observationState.notifierTargetMap = %ObservationWeakMapCreate(); | 34 observationState.notifierObjectInfoMap = %ObservationWeakMapCreate(); |
| 35 observationState.pendingObservers = new InternalArray; | 35 observationState.pendingObservers = { __proto__: null }; |
| 36 observationState.anyPendingObservers = false; | |
| 36 observationState.nextCallbackPriority = 0; | 37 observationState.nextCallbackPriority = 0; |
| 37 } | 38 } |
| 38 | 39 |
| 39 function ObservationWeakMap(map) { | 40 function ObservationWeakMap(map) { |
| 40 this.map_ = map; | 41 this.map_ = map; |
| 41 } | 42 } |
| 42 | 43 |
| 43 ObservationWeakMap.prototype = { | 44 ObservationWeakMap.prototype = { |
| 44 get: function(key) { | 45 get: function(key) { |
| 45 key = %UnwrapGlobalProxy(key); | 46 key = %UnwrapGlobalProxy(key); |
| 46 if (!IS_SPEC_OBJECT(key)) return void 0; | 47 if (!IS_SPEC_OBJECT(key)) return void 0; |
| 47 return %WeakCollectionGet(this.map_, key); | 48 return %WeakCollectionGet(this.map_, key); |
| 48 }, | 49 }, |
| 49 set: function(key, value) { | 50 set: function(key, value) { |
| 50 key = %UnwrapGlobalProxy(key); | 51 key = %UnwrapGlobalProxy(key); |
| 51 if (!IS_SPEC_OBJECT(key)) return void 0; | 52 if (!IS_SPEC_OBJECT(key)) return void 0; |
| 52 %WeakCollectionSet(this.map_, key, value); | 53 %WeakCollectionSet(this.map_, key, value); |
| 53 }, | 54 }, |
| 54 has: function(key) { | 55 has: function(key) { |
| 55 return !IS_UNDEFINED(this.get(key)); | 56 return !IS_UNDEFINED(this.get(key)); |
| 56 } | 57 } |
| 57 }; | 58 }; |
| 58 | 59 |
| 59 var callbackInfoMap = | 60 var callbackInfoMap = |
| 60 new ObservationWeakMap(observationState.callbackInfoMap); | 61 new ObservationWeakMap(observationState.callbackInfoMap); |
| 61 var objectInfoMap = new ObservationWeakMap(observationState.objectInfoMap); | 62 var objectInfoMap = new ObservationWeakMap(observationState.objectInfoMap); |
| 62 var notifierTargetMap = | 63 var notifierObjectInfoMap = |
| 63 new ObservationWeakMap(observationState.notifierTargetMap); | 64 new ObservationWeakMap(observationState.notifierObjectInfoMap); |
| 64 | 65 |
| 65 function CreateObjectInfo(object) { | 66 function TypeMapCreate() { |
| 66 var info = { | 67 return { __proto__: null }; |
| 67 changeObservers: new InternalArray, | |
| 68 notifier: null, | |
| 69 inactiveObservers: new InternalArray, | |
| 70 performing: { __proto__: null }, | |
| 71 performingCount: 0, | |
| 72 }; | |
| 73 objectInfoMap.set(object, info); | |
| 74 return info; | |
| 75 } | 68 } |
| 76 | 69 |
| 77 var defaultAcceptTypes = { | 70 function TypeMapAddType(typeMap, type, ignoreDuplicate) { |
| 78 __proto__: null, | 71 typeMap[type] = ignoreDuplicate ? 1 : (typeMap[type] || 0) + 1; |
|
rossberg
2013/08/07 13:02:14
Hm, this hack could be avoided if AcceptArgIsValid
rafaelw
2013/08/07 20:36:28
That's not the issue. objectInfo.performing is a t
| |
| 79 'new': true, | |
| 80 'updated': true, | |
| 81 'deleted': true, | |
| 82 'prototype': true, | |
| 83 'reconfigured': true | |
| 84 }; | |
| 85 | |
| 86 function CreateObserver(callback, accept) { | |
| 87 var observer = { | |
| 88 __proto__: null, | |
| 89 callback: callback, | |
| 90 accept: defaultAcceptTypes | |
| 91 }; | |
| 92 | |
| 93 if (IS_UNDEFINED(accept)) | |
| 94 return observer; | |
| 95 | |
| 96 var acceptMap = { __proto__: null }; | |
| 97 for (var i = 0; i < accept.length; i++) | |
| 98 acceptMap[accept[i]] = true; | |
| 99 | |
| 100 observer.accept = acceptMap; | |
| 101 return observer; | |
| 102 } | 72 } |
| 103 | 73 |
| 104 function ObserverIsActive(observer, objectInfo) { | 74 function TypeMapRemoveType(typeMap, type) { |
| 105 if (objectInfo.performingCount === 0) | 75 typeMap[type]--; |
| 76 } | |
| 77 | |
| 78 function TypeMapCreateFromList(typeList) { | |
| 79 var typeMap = TypeMapCreate(); | |
| 80 for (var i = 0; i < typeList.length; i++) { | |
| 81 TypeMapAddType(typeMap, typeList[i], true); | |
| 82 } | |
| 83 return typeMap; | |
| 84 } | |
| 85 | |
| 86 function TypeMapHasType(typeMap, type) { | |
| 87 return typeMap[type]; | |
|
rossberg
2013/08/07 13:02:14
Nit: !!typeMap[type]
rafaelw
2013/08/07 20:36:28
Done.
| |
| 88 } | |
| 89 | |
| 90 function TypeMapIsDisjointFrom(typeMap1, typeMap2) { | |
| 91 if (!typeMap1 || !typeMap2) | |
| 106 return true; | 92 return true; |
| 107 | 93 |
| 108 var performing = objectInfo.performing; | 94 for (var type in typeMap1) { |
| 109 for (var type in performing) { | 95 if (TypeMapHasType(typeMap1, type) && TypeMapHasType(typeMap2, type)) |
| 110 if (performing[type] > 0 && observer.accept[type]) | |
| 111 return false; | 96 return false; |
| 112 } | 97 } |
| 113 | 98 |
| 114 return true; | 99 return true; |
| 115 } | 100 } |
| 116 | 101 |
| 117 function ObserverIsInactive(observer, objectInfo) { | 102 var defaultAcceptTypes = TypeMapCreateFromList([ |
| 118 return !ObserverIsActive(observer, objectInfo); | 103 'new', |
| 104 'updated', | |
| 105 'deleted', | |
| 106 'prototype', | |
| 107 'reconfigured' | |
| 108 ]); | |
| 109 | |
| 110 // An Observer is a registration to observe an object by a callback with a | |
| 111 // a given set of accept types. If the set of accept types is the default | |
|
rossberg
2013/08/07 13:02:14
Nit: duplicate "a"
rafaelw
2013/08/07 20:36:28
Done.
| |
| 112 // set for Object.observe, the observer is represented as a direct reference | |
| 113 // to the callback. An observer never changes its accept types and thus never | |
| 114 // needs to "normalize". | |
| 115 function ObserverCreate(callback, acceptList) { | |
| 116 return IS_UNDEFINED(acceptList) ? callback : { | |
| 117 __proto__: null, | |
| 118 callback: callback, | |
| 119 accept: TypeMapCreateFromList(acceptList) | |
| 120 }; | |
| 119 } | 121 } |
| 120 | 122 |
| 121 function RemoveNullElements(from) { | 123 function ObserverGetCallback(observer) { |
| 122 var i = 0; | 124 return IS_SPEC_FUNCTION(observer) ? observer : observer.callback; |
| 123 var j = 0; | 125 } |
| 124 for (; i < from.length; i++) { | 126 |
| 125 if (from[i] === null) | 127 function ObserverGetAcceptTypes(observer) { |
| 126 continue; | 128 return IS_SPEC_FUNCTION(observer) ? defaultAcceptTypes : observer.accept; |
| 127 if (j < i) | 129 } |
| 128 from[j] = from[i]; | 130 |
| 129 j++; | 131 function ObserverIsActive(observer, objectInfo) { |
| 132 return TypeMapIsDisjointFrom(ObjectInfoGetPerformingTypes(objectInfo), | |
| 133 ObserverGetAcceptTypes(observer)); | |
| 134 } | |
| 135 | |
| 136 function ObjectInfoGetOrCreate(object) { | |
| 137 var objectInfo = objectInfoMap.get(object); | |
| 138 if (IS_UNDEFINED(objectInfo)) { | |
| 139 if (!%IsJSProxy(object)) | |
| 140 %SetIsObserved(object); | |
| 141 | |
| 142 objectInfo = { | |
| 143 object: object, | |
| 144 changeObserver: null, | |
| 145 changeObservers: null, | |
| 146 notifier: null, | |
| 147 performing: null, | |
| 148 performingCount: 0, | |
| 149 }; | |
| 150 objectInfoMap.set(object, objectInfo); | |
| 151 } | |
| 152 return objectInfo; | |
| 153 } | |
| 154 | |
| 155 function ObjectInfoGet(object) { | |
|
rossberg
2013/08/07 13:02:14
Is this still used at all? If not, you could as we
rafaelw
2013/08/07 20:36:28
Done.
| |
| 156 return objectInfoMap.get(object); | |
| 157 } | |
| 158 | |
| 159 function ObjectInfoGetFromNotifier(notifier) { | |
| 160 return notifierObjectInfoMap.get(notifier); | |
| 161 } | |
| 162 | |
| 163 function ObjectInfoGetNotifier(objectInfo) { | |
| 164 if (IS_NULL(objectInfo.notifier)) { | |
| 165 objectInfo.notifier = { __proto__: notifierPrototype }; | |
| 166 notifierObjectInfoMap.set(objectInfo.notifier, objectInfo); | |
| 130 } | 167 } |
| 131 | 168 |
| 132 if (i !== j) | 169 return objectInfo.notifier; |
| 133 from.length = from.length - (i - j); | |
| 134 } | 170 } |
| 135 | 171 |
| 136 function RepartitionObservers(conditionFn, from, to, objectInfo) { | 172 function ObjectInfoGetObject(objectInfo) { |
| 137 var anyRemoved = false; | 173 return objectInfo.object; |
| 138 for (var i = 0; i < from.length; i++) { | 174 } |
| 139 var observer = from[i]; | 175 |
| 140 if (conditionFn(observer, objectInfo)) { | 176 // The set of observers on an object is called 'changeObservers'. The first |
| 141 anyRemoved = true; | 177 // observer is referenced directly via objectInfo.changeObserver. When a second |
|
rossberg
2013/08/07 13:02:14
Wouldn't it be slightly simpler to use only 'chang
rafaelw
2013/08/07 20:36:28
The problem is that an "observer" can be normal (a
rossberg
2013/08/09 11:26:51
I don't feel too strongly about it. But I would ar
rafaelw
2013/08/09 14:14:37
Fair enough. Done.
On 2013/08/09 11:26:51, rossbe
| |
| 142 from[i] = null; | 178 // is added, changeObservers "normalizes" to become a mapping of callback |
| 143 to.push(observer); | 179 // priority -> observer and is then stored on objectInfo.changeObservers. |
| 144 } | 180 function ObjectInfoNormalizeChangeObservers(objectInfo) { |
| 181 var observer = objectInfo.changeObserver; | |
| 182 objectInfo.changeObserver = null; | |
| 183 objectInfo.changeObservers = { __proto__: null }; | |
| 184 var callback = ObserverGetCallback(observer); | |
| 185 var callbackInfo = CallbackInfoGet(callback); | |
| 186 var priority = CallbackInfoGetPriority(callbackInfo); | |
| 187 objectInfo.changeObservers[priority] = observer; | |
| 188 } | |
| 189 | |
| 190 function ObjectInfoAddObserver(objectInfo, callback, acceptList) { | |
| 191 var callbackInfo = CallbackInfoGetOrCreate(callback); | |
| 192 var observer = ObserverCreate(callback, acceptList); | |
| 193 | |
| 194 if (!objectInfo.changeObserver && !objectInfo.changeObservers) { | |
| 195 objectInfo.changeObserver = observer; | |
| 196 return; | |
| 145 } | 197 } |
| 146 | 198 |
| 147 if (anyRemoved) | 199 if (objectInfo.changeObserver) |
| 148 RemoveNullElements(from); | 200 ObjectInfoNormalizeChangeObservers(objectInfo); |
| 201 | |
| 202 var priority = CallbackInfoGetPriority(callbackInfo); | |
| 203 objectInfo.changeObservers[priority] = observer; | |
| 149 } | 204 } |
| 150 | 205 |
| 151 function BeginPerformChange(objectInfo, type) { | 206 function ObjectInfoRemoveObserver(objectInfo, callback) { |
| 152 objectInfo.performing[type] = (objectInfo.performing[type] || 0) + 1; | 207 if (objectInfo.changeObserver) { |
| 153 objectInfo.performingCount++; | 208 if (callback === ObserverGetCallback(objectInfo.changeObserver)) |
| 154 RepartitionObservers(ObserverIsInactive, | 209 objectInfo.changeObserver = null; |
| 155 objectInfo.changeObservers, | 210 |
| 156 objectInfo.inactiveObservers, | 211 return; |
| 157 objectInfo); | 212 } |
| 213 | |
| 214 var callbackInfo = CallbackInfoGet(callback); | |
| 215 var priority = CallbackInfoGetPriority(callbackInfo); | |
| 216 delete objectInfo.changeObservers[priority]; | |
| 158 } | 217 } |
| 159 | 218 |
| 160 function EndPerformChange(objectInfo, type) { | 219 function ObjectInfoHasActiveObservers(objectInfo) { |
| 161 objectInfo.performing[type]--; | 220 if (IS_UNDEFINED(objectInfo)) |
| 162 objectInfo.performingCount--; | 221 return false; |
| 163 RepartitionObservers(ObserverIsActive, | 222 |
| 164 objectInfo.inactiveObservers, | 223 if (objectInfo.changeObserver) |
| 165 objectInfo.changeObservers, | 224 return ObserverIsActive(objectInfo.changeObserver, objectInfo); |
| 166 objectInfo); | 225 |
| 226 for (var priority in objectInfo.changeObservers) { | |
| 227 if (ObserverIsActive(objectInfo.changeObservers[priority], objectInfo)) | |
| 228 return true; | |
| 229 } | |
| 230 | |
| 231 return false; | |
| 167 } | 232 } |
| 168 | 233 |
| 169 function EnsureObserverRemoved(objectInfo, callback) { | 234 function ObjectInfoAddPerformingType(objectInfo, type) { |
| 170 function remove(observerList) { | 235 objectInfo.performing = objectInfo.performing || TypeMapCreate(); |
| 171 for (var i = 0; i < observerList.length; i++) { | 236 TypeMapAddType(objectInfo.performing, type); |
| 172 if (observerList[i].callback === callback) { | 237 objectInfo.performingCount++; |
| 173 observerList.splice(i, 1); | 238 } |
| 174 return true; | |
| 175 } | |
| 176 } | |
| 177 return false; | |
| 178 } | |
| 179 | 239 |
| 180 if (!remove(objectInfo.changeObservers)) | 240 function ObjectInfoRemovePerformingType(objectInfo, type) { |
| 181 remove(objectInfo.inactiveObservers); | 241 objectInfo.performingCount--; |
| 242 TypeMapRemoveType(objectInfo.performing, type); | |
| 243 } | |
| 244 | |
| 245 function ObjectInfoGetPerformingTypes(objectInfo) { | |
| 246 return objectInfo.performingCount > 0 ? objectInfo.performing : null; | |
| 182 } | 247 } |
| 183 | 248 |
| 184 function AcceptArgIsValid(arg) { | 249 function AcceptArgIsValid(arg) { |
| 185 if (IS_UNDEFINED(arg)) | 250 if (IS_UNDEFINED(arg)) |
| 186 return true; | 251 return true; |
| 187 | 252 |
| 188 if (!IS_SPEC_OBJECT(arg) || | 253 if (!IS_SPEC_OBJECT(arg) || |
| 189 !IS_NUMBER(arg.length) || | 254 !IS_NUMBER(arg.length) || |
| 190 arg.length < 0) | 255 arg.length < 0) |
| 191 return false; | 256 return false; |
| 192 | 257 |
| 193 var length = arg.length; | 258 var length = arg.length; |
| 194 for (var i = 0; i < length; i++) { | 259 for (var i = 0; i < length; i++) { |
| 195 if (!IS_STRING(arg[i])) | 260 if (!IS_STRING(arg[i])) |
| 196 return false; | 261 return false; |
| 197 } | 262 } |
| 198 return true; | 263 return true; |
| 199 } | 264 } |
| 200 | 265 |
| 201 function EnsureCallbackPriority(callback) { | 266 function CallbackInfoGet(callback) { |
| 202 if (!callbackInfoMap.has(callback)) | 267 return callbackInfoMap.get(callback); |
| 203 callbackInfoMap.set(callback, observationState.nextCallbackPriority++); | |
| 204 } | 268 } |
| 205 | 269 |
| 206 function NormalizeCallbackInfo(callback) { | 270 function CallbackInfoGetOrCreate(callback) { |
| 271 var callbackInfo = callbackInfoMap.get(callback); | |
| 272 if (!IS_UNDEFINED(callbackInfo)) | |
| 273 return callbackInfo; | |
| 274 | |
| 275 var priority = observationState.nextCallbackPriority++ | |
| 276 callbackInfoMap.set(callback, priority); | |
| 277 return priority; | |
| 278 } | |
| 279 | |
| 280 function CallbackInfoGetPriority(callbackInfo) { | |
| 281 if (IS_NUMBER(callbackInfo)) | |
| 282 return callbackInfo; | |
| 283 else | |
| 284 return callbackInfo.priority; | |
| 285 } | |
| 286 | |
| 287 function CallbackInfoNormalize(callback) { | |
| 207 var callbackInfo = callbackInfoMap.get(callback); | 288 var callbackInfo = callbackInfoMap.get(callback); |
| 208 if (IS_NUMBER(callbackInfo)) { | 289 if (IS_NUMBER(callbackInfo)) { |
| 209 var priority = callbackInfo; | 290 var priority = callbackInfo; |
| 210 callbackInfo = new InternalArray; | 291 callbackInfo = new InternalArray; |
| 211 callbackInfo.priority = priority; | 292 callbackInfo.priority = priority; |
| 212 callbackInfoMap.set(callback, callbackInfo); | 293 callbackInfoMap.set(callback, callbackInfo); |
| 213 } | 294 } |
| 214 return callbackInfo; | 295 return callbackInfo; |
| 215 } | 296 } |
| 216 | 297 |
| 217 function ObjectObserve(object, callback, accept) { | 298 function ObjectObserve(object, callback, acceptList) { |
| 218 if (!IS_SPEC_OBJECT(object)) | 299 if (!IS_SPEC_OBJECT(object)) |
| 219 throw MakeTypeError("observe_non_object", ["observe"]); | 300 throw MakeTypeError("observe_non_object", ["observe"]); |
| 220 if (!IS_SPEC_FUNCTION(callback)) | 301 if (!IS_SPEC_FUNCTION(callback)) |
| 221 throw MakeTypeError("observe_non_function", ["observe"]); | 302 throw MakeTypeError("observe_non_function", ["observe"]); |
| 222 if (ObjectIsFrozen(callback)) | 303 if (ObjectIsFrozen(callback)) |
| 223 throw MakeTypeError("observe_callback_frozen"); | 304 throw MakeTypeError("observe_callback_frozen"); |
| 224 if (!AcceptArgIsValid(accept)) | 305 if (!AcceptArgIsValid(acceptList)) |
| 225 throw MakeTypeError("observe_accept_invalid"); | 306 throw MakeTypeError("observe_accept_invalid"); |
| 226 | 307 |
| 227 EnsureCallbackPriority(callback); | 308 var objectInfo = ObjectInfoGetOrCreate(object); |
| 228 | 309 ObjectInfoAddObserver(objectInfo, callback, acceptList); |
| 229 var objectInfo = objectInfoMap.get(object); | |
| 230 if (IS_UNDEFINED(objectInfo)) { | |
| 231 objectInfo = CreateObjectInfo(object); | |
| 232 %SetIsObserved(object); | |
| 233 } | |
| 234 | |
| 235 EnsureObserverRemoved(objectInfo, callback); | |
| 236 | |
| 237 var observer = CreateObserver(callback, accept); | |
| 238 if (ObserverIsActive(observer, objectInfo)) | |
| 239 objectInfo.changeObservers.push(observer); | |
| 240 else | |
| 241 objectInfo.inactiveObservers.push(observer); | |
| 242 | |
| 243 return object; | 310 return object; |
| 244 } | 311 } |
| 245 | 312 |
| 246 function ObjectUnobserve(object, callback) { | 313 function ObjectUnobserve(object, callback) { |
| 247 if (!IS_SPEC_OBJECT(object)) | 314 if (!IS_SPEC_OBJECT(object)) |
| 248 throw MakeTypeError("observe_non_object", ["unobserve"]); | 315 throw MakeTypeError("observe_non_object", ["unobserve"]); |
| 249 if (!IS_SPEC_FUNCTION(callback)) | 316 if (!IS_SPEC_FUNCTION(callback)) |
| 250 throw MakeTypeError("observe_non_function", ["unobserve"]); | 317 throw MakeTypeError("observe_non_function", ["unobserve"]); |
| 251 | 318 |
| 252 var objectInfo = objectInfoMap.get(object); | 319 var objectInfo = objectInfoMap.get(object); |
| 253 if (IS_UNDEFINED(objectInfo)) | 320 if (IS_UNDEFINED(objectInfo)) |
| 254 return object; | 321 return object; |
| 255 | 322 |
| 256 EnsureObserverRemoved(objectInfo, callback); | 323 ObjectInfoRemoveObserver(objectInfo, callback); |
| 257 return object; | 324 return object; |
| 258 } | 325 } |
| 259 | 326 |
| 260 function ArrayObserve(object, callback) { | 327 function ArrayObserve(object, callback) { |
| 261 return ObjectObserve(object, callback, ['new', | 328 return ObjectObserve(object, callback, ['new', |
| 262 'updated', | 329 'updated', |
| 263 'deleted', | 330 'deleted', |
| 264 'splice']); | 331 'splice']); |
| 265 } | 332 } |
| 266 | 333 |
| 267 function ArrayUnobserve(object, callback) { | 334 function ArrayUnobserve(object, callback) { |
| 268 return ObjectUnobserve(object, callback); | 335 return ObjectUnobserve(object, callback); |
| 269 } | 336 } |
| 270 | 337 |
| 271 function EnqueueToCallback(callback, changeRecord) { | 338 function ObserverEnqueueIfActive(observer, objectInfo, changeRecord) { |
| 272 var callbackInfo = NormalizeCallbackInfo(callback); | 339 if (!ObserverIsActive(observer, objectInfo) || |
| 340 !TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) { | |
| 341 return; | |
| 342 } | |
| 343 | |
| 344 var callback = ObserverGetCallback(observer); | |
| 345 var callbackInfo = CallbackInfoNormalize(callback); | |
| 273 observationState.pendingObservers[callbackInfo.priority] = callback; | 346 observationState.pendingObservers[callbackInfo.priority] = callback; |
| 347 observationState.anyPendingObservers = true; | |
| 274 callbackInfo.push(changeRecord); | 348 callbackInfo.push(changeRecord); |
| 275 %SetObserverDeliveryPending(); | 349 %SetObserverDeliveryPending(); |
| 276 } | 350 } |
| 277 | 351 |
| 278 function EnqueueChangeRecord(changeRecord, observers) { | 352 function ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord) { |
| 279 // TODO(rossberg): adjust once there is a story for symbols vs proxies. | 353 // TODO(rossberg): adjust once there is a story for symbols vs proxies. |
| 280 if (IS_SYMBOL(changeRecord.name)) return; | 354 if (IS_SYMBOL(changeRecord.name)) return; |
| 281 | 355 |
| 282 for (var i = 0; i < observers.length; i++) { | 356 if (objectInfo.changeObserver) { |
| 283 var observer = observers[i]; | 357 var observer = objectInfo.changeObserver; |
| 284 if (IS_UNDEFINED(observer.accept[changeRecord.type])) | 358 ObserverEnqueueIfActive(observer, objectInfo, changeRecord); |
| 285 continue; | 359 return; |
| 360 } | |
| 286 | 361 |
| 287 EnqueueToCallback(observer.callback, changeRecord); | 362 for (var priority in objectInfo.changeObservers) { |
| 363 var observer = objectInfo.changeObservers[priority]; | |
| 364 ObserverEnqueueIfActive(observer, objectInfo, changeRecord); | |
| 288 } | 365 } |
| 289 } | 366 } |
| 290 | 367 |
| 291 function BeginPerformSplice(array) { | 368 function BeginPerformSplice(array) { |
| 292 var objectInfo = objectInfoMap.get(array); | 369 var objectInfo = objectInfoMap.get(array); |
| 293 if (!IS_UNDEFINED(objectInfo)) | 370 if (!IS_UNDEFINED(objectInfo)) |
| 294 BeginPerformChange(objectInfo, 'splice'); | 371 ObjectInfoAddPerformingType(objectInfo, 'splice'); |
| 295 } | 372 } |
| 296 | 373 |
| 297 function EndPerformSplice(array) { | 374 function EndPerformSplice(array) { |
| 298 var objectInfo = objectInfoMap.get(array); | 375 var objectInfo = objectInfoMap.get(array); |
| 299 if (!IS_UNDEFINED(objectInfo)) | 376 if (!IS_UNDEFINED(objectInfo)) |
| 300 EndPerformChange(objectInfo, 'splice'); | 377 ObjectInfoRemovePerformingType(objectInfo, 'splice'); |
| 301 } | 378 } |
| 302 | 379 |
| 303 function EnqueueSpliceRecord(array, index, removed, addedCount) { | 380 function EnqueueSpliceRecord(array, index, removed, addedCount) { |
| 304 var objectInfo = objectInfoMap.get(array); | 381 var objectInfo = objectInfoMap.get(array); |
| 305 if (IS_UNDEFINED(objectInfo) || objectInfo.changeObservers.length === 0) | 382 if (!ObjectInfoHasActiveObservers(objectInfo)) |
| 306 return; | 383 return; |
| 307 | 384 |
| 308 var changeRecord = { | 385 var changeRecord = { |
| 309 type: 'splice', | 386 type: 'splice', |
| 310 object: array, | 387 object: array, |
| 311 index: index, | 388 index: index, |
| 312 removed: removed, | 389 removed: removed, |
| 313 addedCount: addedCount | 390 addedCount: addedCount |
| 314 }; | 391 }; |
| 315 | 392 |
| 316 ObjectFreeze(changeRecord); | 393 ObjectFreeze(changeRecord); |
| 317 ObjectFreeze(changeRecord.removed); | 394 ObjectFreeze(changeRecord.removed); |
| 318 EnqueueChangeRecord(changeRecord, objectInfo.changeObservers); | 395 ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord); |
| 319 } | 396 } |
| 320 | 397 |
| 321 function NotifyChange(type, object, name, oldValue) { | 398 function NotifyChange(type, object, name, oldValue) { |
| 322 var objectInfo = objectInfoMap.get(object); | 399 var objectInfo = objectInfoMap.get(object); |
| 323 if (objectInfo.changeObservers.length === 0) | 400 if (!ObjectInfoHasActiveObservers(objectInfo)) |
| 324 return; | 401 return; |
| 325 | 402 |
| 326 var changeRecord = (arguments.length < 4) ? | 403 var changeRecord = (arguments.length < 4) ? |
| 327 { type: type, object: object, name: name } : | 404 { type: type, object: object, name: name } : |
| 328 { type: type, object: object, name: name, oldValue: oldValue }; | 405 { type: type, object: object, name: name, oldValue: oldValue }; |
| 329 ObjectFreeze(changeRecord); | 406 ObjectFreeze(changeRecord); |
| 330 EnqueueChangeRecord(changeRecord, objectInfo.changeObservers); | 407 ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord); |
| 331 } | 408 } |
| 332 | 409 |
| 333 var notifierPrototype = {}; | 410 var notifierPrototype = {}; |
| 334 | 411 |
| 335 function ObjectNotifierNotify(changeRecord) { | 412 function ObjectNotifierNotify(changeRecord) { |
| 336 if (!IS_SPEC_OBJECT(this)) | 413 if (!IS_SPEC_OBJECT(this)) |
| 337 throw MakeTypeError("called_on_non_object", ["notify"]); | 414 throw MakeTypeError("called_on_non_object", ["notify"]); |
| 338 | 415 |
| 339 var target = notifierTargetMap.get(this); | 416 var objectInfo = ObjectInfoGetFromNotifier(this); |
| 340 if (IS_UNDEFINED(target)) | 417 if (IS_UNDEFINED(objectInfo)) |
| 341 throw MakeTypeError("observe_notify_non_notifier"); | 418 throw MakeTypeError("observe_notify_non_notifier"); |
| 342 if (!IS_STRING(changeRecord.type)) | 419 if (!IS_STRING(changeRecord.type)) |
| 343 throw MakeTypeError("observe_type_non_string"); | 420 throw MakeTypeError("observe_type_non_string"); |
| 344 | 421 |
| 345 var objectInfo = objectInfoMap.get(target); | 422 if (!ObjectInfoHasActiveObservers(objectInfo)) |
| 346 if (IS_UNDEFINED(objectInfo) || objectInfo.changeObservers.length === 0) | |
| 347 return; | 423 return; |
| 348 | 424 |
| 349 var newRecord = { object: target }; | 425 var newRecord = { object: ObjectInfoGetObject(objectInfo) }; |
| 350 for (var prop in changeRecord) { | 426 for (var prop in changeRecord) { |
| 351 if (prop === 'object') continue; | 427 if (prop === 'object') continue; |
| 352 %DefineOrRedefineDataProperty(newRecord, prop, changeRecord[prop], | 428 %DefineOrRedefineDataProperty(newRecord, prop, changeRecord[prop], |
| 353 READ_ONLY + DONT_DELETE); | 429 READ_ONLY + DONT_DELETE); |
| 354 } | 430 } |
| 355 ObjectFreeze(newRecord); | 431 ObjectFreeze(newRecord); |
| 356 | 432 |
| 357 EnqueueChangeRecord(newRecord, objectInfo.changeObservers); | 433 ObjectInfoEnqueueChangeRecord(objectInfo, newRecord); |
| 358 } | 434 } |
| 359 | 435 |
| 360 function ObjectNotifierPerformChange(changeType, changeFn, receiver) { | 436 function ObjectNotifierPerformChange(changeType, changeFn, receiver) { |
| 361 if (!IS_SPEC_OBJECT(this)) | 437 if (!IS_SPEC_OBJECT(this)) |
| 362 throw MakeTypeError("called_on_non_object", ["performChange"]); | 438 throw MakeTypeError("called_on_non_object", ["performChange"]); |
| 363 | 439 |
| 364 var target = notifierTargetMap.get(this); | 440 var objectInfo = ObjectInfoGetFromNotifier(this); |
| 365 if (IS_UNDEFINED(target)) | 441 |
| 442 if (IS_UNDEFINED(objectInfo)) | |
| 366 throw MakeTypeError("observe_notify_non_notifier"); | 443 throw MakeTypeError("observe_notify_non_notifier"); |
| 367 if (!IS_STRING(changeType)) | 444 if (!IS_STRING(changeType)) |
| 368 throw MakeTypeError("observe_perform_non_string"); | 445 throw MakeTypeError("observe_perform_non_string"); |
| 369 if (!IS_SPEC_FUNCTION(changeFn)) | 446 if (!IS_SPEC_FUNCTION(changeFn)) |
| 370 throw MakeTypeError("observe_perform_non_function"); | 447 throw MakeTypeError("observe_perform_non_function"); |
| 371 | 448 |
| 372 if (IS_NULL_OR_UNDEFINED(receiver)) { | 449 if (IS_NULL_OR_UNDEFINED(receiver)) { |
| 373 receiver = %GetDefaultReceiver(changeFn) || receiver; | 450 receiver = %GetDefaultReceiver(changeFn) || receiver; |
| 374 } else if (!IS_SPEC_OBJECT(receiver) && %IsClassicModeFunction(changeFn)) { | 451 } else if (!IS_SPEC_OBJECT(receiver) && %IsClassicModeFunction(changeFn)) { |
| 375 receiver = ToObject(receiver); | 452 receiver = ToObject(receiver); |
| 376 } | 453 } |
| 377 | 454 |
| 378 var objectInfo = objectInfoMap.get(target); | 455 ObjectInfoAddPerformingType(objectInfo, changeType); |
| 379 if (IS_UNDEFINED(objectInfo)) | |
| 380 return; | |
| 381 | |
| 382 BeginPerformChange(objectInfo, changeType); | |
| 383 try { | 456 try { |
| 384 %_CallFunction(receiver, changeFn); | 457 %_CallFunction(receiver, changeFn); |
| 385 } finally { | 458 } finally { |
| 386 EndPerformChange(objectInfo, changeType); | 459 ObjectInfoRemovePerformingType(objectInfo, changeType); |
| 387 } | 460 } |
| 388 } | 461 } |
| 389 | 462 |
| 390 function ObjectGetNotifier(object) { | 463 function ObjectGetNotifier(object) { |
| 391 if (!IS_SPEC_OBJECT(object)) | 464 if (!IS_SPEC_OBJECT(object)) |
| 392 throw MakeTypeError("observe_non_object", ["getNotifier"]); | 465 throw MakeTypeError("observe_non_object", ["getNotifier"]); |
| 393 | 466 |
| 394 if (ObjectIsFrozen(object)) return null; | 467 if (ObjectIsFrozen(object)) return null; |
| 395 | 468 |
| 396 var objectInfo = objectInfoMap.get(object); | 469 var objectInfo = ObjectInfoGetOrCreate(object); |
| 397 if (IS_UNDEFINED(objectInfo)) { | 470 return ObjectInfoGetNotifier(objectInfo); |
| 398 objectInfo = CreateObjectInfo(object); | |
| 399 %SetIsObserved(object); | |
| 400 } | |
| 401 | |
| 402 if (IS_NULL(objectInfo.notifier)) { | |
| 403 objectInfo.notifier = { __proto__: notifierPrototype }; | |
| 404 notifierTargetMap.set(objectInfo.notifier, object); | |
| 405 } | |
| 406 | |
| 407 return objectInfo.notifier; | |
| 408 } | 471 } |
| 409 | 472 |
| 410 function CallbackDeliverPending(callback) { | 473 function CallbackDeliverPending(callback) { |
| 411 var callbackInfo = callbackInfoMap.get(callback); | 474 var callbackInfo = callbackInfoMap.get(callback); |
| 412 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) | 475 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) |
| 413 return false; | 476 return false; |
| 414 | 477 |
| 415 // Clear the pending change records from callback and return it to its | 478 // Clear the pending change records from callback and return it to its |
| 416 // "optimized" state. | 479 // "optimized" state. |
| 417 var priority = callbackInfo.priority; | 480 var priority = callbackInfo.priority; |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 428 } | 491 } |
| 429 | 492 |
| 430 function ObjectDeliverChangeRecords(callback) { | 493 function ObjectDeliverChangeRecords(callback) { |
| 431 if (!IS_SPEC_FUNCTION(callback)) | 494 if (!IS_SPEC_FUNCTION(callback)) |
| 432 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); | 495 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); |
| 433 | 496 |
| 434 while (CallbackDeliverPending(callback)) {} | 497 while (CallbackDeliverPending(callback)) {} |
| 435 } | 498 } |
| 436 | 499 |
| 437 function DeliverChangeRecords() { | 500 function DeliverChangeRecords() { |
| 438 while (observationState.pendingObservers.length) { | 501 while (observationState.anyPendingObservers) { |
|
rossberg
2013/08/07 13:02:14
I don't think you need to make this flag part of t
rafaelw
2013/08/07 20:36:28
I don't understand. observationState.anyPendingObs
rossberg
2013/08/09 11:26:51
What I mean is that all you are really implementin
rafaelw
2013/08/09 14:14:37
I tried & profiled this version, but it made deliv
rossberg
2013/08/13 09:19:25
Hm, that sounds like a lot. Did you try a version
rafaelw
2013/08/19 18:51:24
That one is 3x more expensive.
On 2013/08/13 09:1
| |
| 439 var pendingObservers = observationState.pendingObservers; | 502 var pendingObservers = observationState.pendingObservers; |
| 440 observationState.pendingObservers = new InternalArray; | 503 observationState.pendingObservers = { __proto__: null }; |
| 504 observationState.anyPendingObservers = false | |
| 441 for (var i in pendingObservers) { | 505 for (var i in pendingObservers) { |
| 442 CallbackDeliverPending(pendingObservers[i]); | 506 CallbackDeliverPending(pendingObservers[i]); |
| 443 } | 507 } |
| 444 } | 508 } |
| 445 } | 509 } |
| 446 | 510 |
| 447 function SetupObjectObserve() { | 511 function SetupObjectObserve() { |
| 448 %CheckIsBootstrapping(); | 512 %CheckIsBootstrapping(); |
| 449 InstallFunctions($Object, DONT_ENUM, $Array( | 513 InstallFunctions($Object, DONT_ENUM, $Array( |
| 450 "deliverChangeRecords", ObjectDeliverChangeRecords, | 514 "deliverChangeRecords", ObjectDeliverChangeRecords, |
| 451 "getNotifier", ObjectGetNotifier, | 515 "getNotifier", ObjectGetNotifier, |
| 452 "observe", ObjectObserve, | 516 "observe", ObjectObserve, |
| 453 "unobserve", ObjectUnobserve | 517 "unobserve", ObjectUnobserve |
| 454 )); | 518 )); |
| 455 InstallFunctions($Array, DONT_ENUM, $Array( | 519 InstallFunctions($Array, DONT_ENUM, $Array( |
| 456 "observe", ArrayObserve, | 520 "observe", ArrayObserve, |
| 457 "unobserve", ArrayUnobserve | 521 "unobserve", ArrayUnobserve |
| 458 )); | 522 )); |
| 459 InstallFunctions(notifierPrototype, DONT_ENUM, $Array( | 523 InstallFunctions(notifierPrototype, DONT_ENUM, $Array( |
| 460 "notify", ObjectNotifierNotify, | 524 "notify", ObjectNotifierNotify, |
| 461 "performChange", ObjectNotifierPerformChange | 525 "performChange", ObjectNotifierPerformChange |
| 462 )); | 526 )); |
| 463 } | 527 } |
| 464 | 528 |
| 465 SetupObjectObserve(); | 529 SetupObjectObserve(); |
| OLD | NEW |