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 |
11 // with the distribution. | 11 // with the distribution. |
12 // * Neither the name of Google Inc. nor the names of its | 12 // * Neither the name of Google Inc. nor the names of its |
13 // contributors may be used to endorse or promote products derived | 13 // contributors may be used to endorse or promote products derived |
14 // from this software without specific prior written permission. | 14 // from this software without specific prior written permission. |
15 // | 15 // |
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | 16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | 17 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | 18 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | 19 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | 20 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | 21 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | 22 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | 23 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
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 // Overview: |
| 31 // |
| 32 // This file contains all of the routing and accounting for Object.observe. |
| 33 // User code will interact with these mechanisms via the Object.observe APIs |
| 34 // and, as a side effect of mutation objects which are observed. The V8 runtime |
| 35 // (both C++ and JS) will interact with these mechanisms primarily by enqueuing |
| 36 // proper change records for objects which were mutated. The Object.observe |
| 37 // routing and accounting consists primarily of three participants |
| 38 // |
| 39 // 1) ObjectInfo. This represents the observed state of a given object. It |
| 40 // records what callbacks are observing the object, with what options, and |
| 41 // what "change types" are in progress on the object (i.e. via |
| 42 // notifier.performChange). |
| 43 // |
| 44 // 2) CallbackInfo. This represents a callback used for observation. It holds |
| 45 // the records which must be delivered to the callback, as well as the global |
| 46 // priority of the callback (which determines delivery order between |
| 47 // callbacks). |
| 48 // |
| 49 // 3) observationState.pendingObservers. This is the set of observers which |
| 50 // have change records which must be delivered. During "normal" delivery |
| 51 // (i.e. not Object.deliverChangeRecords), this is the mechanism by which |
| 52 // callbacks are invoked in the proper order until there are no more |
| 53 // change records pending to a callback. |
| 54 // |
| 55 // Note that in order to reduce allocation and processing costs, the |
| 56 // implementation of (1) and (2) have "optimized" states which represent |
| 57 // common cases which can be handled more efficiently. |
| 58 |
30 var observationState = %GetObservationState(); | 59 var observationState = %GetObservationState(); |
31 if (IS_UNDEFINED(observationState.callbackInfoMap)) { | 60 if (IS_UNDEFINED(observationState.callbackInfoMap)) { |
32 observationState.callbackInfoMap = %ObservationWeakMapCreate(); | 61 observationState.callbackInfoMap = %ObservationWeakMapCreate(); |
33 observationState.objectInfoMap = %ObservationWeakMapCreate(); | 62 observationState.objectInfoMap = %ObservationWeakMapCreate(); |
34 observationState.notifierTargetMap = %ObservationWeakMapCreate(); | 63 observationState.notifierObjectInfoMap = %ObservationWeakMapCreate(); |
35 observationState.pendingObservers = new InternalArray; | 64 observationState.pendingObservers = null; |
36 observationState.nextCallbackPriority = 0; | 65 observationState.nextCallbackPriority = 0; |
37 } | 66 } |
38 | 67 |
39 function ObservationWeakMap(map) { | 68 function ObservationWeakMap(map) { |
40 this.map_ = map; | 69 this.map_ = map; |
41 } | 70 } |
42 | 71 |
43 ObservationWeakMap.prototype = { | 72 ObservationWeakMap.prototype = { |
44 get: function(key) { | 73 get: function(key) { |
45 key = %UnwrapGlobalProxy(key); | 74 key = %UnwrapGlobalProxy(key); |
46 if (!IS_SPEC_OBJECT(key)) return void 0; | 75 if (!IS_SPEC_OBJECT(key)) return void 0; |
47 return %WeakCollectionGet(this.map_, key); | 76 return %WeakCollectionGet(this.map_, key); |
48 }, | 77 }, |
49 set: function(key, value) { | 78 set: function(key, value) { |
50 key = %UnwrapGlobalProxy(key); | 79 key = %UnwrapGlobalProxy(key); |
51 if (!IS_SPEC_OBJECT(key)) return void 0; | 80 if (!IS_SPEC_OBJECT(key)) return void 0; |
52 %WeakCollectionSet(this.map_, key, value); | 81 %WeakCollectionSet(this.map_, key, value); |
53 }, | 82 }, |
54 has: function(key) { | 83 has: function(key) { |
55 return !IS_UNDEFINED(this.get(key)); | 84 return !IS_UNDEFINED(this.get(key)); |
56 } | 85 } |
57 }; | 86 }; |
58 | 87 |
59 var callbackInfoMap = | 88 var callbackInfoMap = |
60 new ObservationWeakMap(observationState.callbackInfoMap); | 89 new ObservationWeakMap(observationState.callbackInfoMap); |
61 var objectInfoMap = new ObservationWeakMap(observationState.objectInfoMap); | 90 var objectInfoMap = new ObservationWeakMap(observationState.objectInfoMap); |
62 var notifierTargetMap = | 91 var notifierObjectInfoMap = |
63 new ObservationWeakMap(observationState.notifierTargetMap); | 92 new ObservationWeakMap(observationState.notifierObjectInfoMap); |
64 | 93 |
65 function CreateObjectInfo(object) { | 94 function TypeMapCreate() { |
66 var info = { | 95 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 } | 96 } |
76 | 97 |
77 var defaultAcceptTypes = { | 98 function TypeMapAddType(typeMap, type, ignoreDuplicate) { |
78 __proto__: null, | 99 typeMap[type] = ignoreDuplicate ? 1 : (typeMap[type] || 0) + 1; |
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 } | 100 } |
103 | 101 |
104 function ObserverIsActive(observer, objectInfo) { | 102 function TypeMapRemoveType(typeMap, type) { |
105 if (objectInfo.performingCount === 0) | 103 typeMap[type]--; |
| 104 } |
| 105 |
| 106 function TypeMapCreateFromList(typeList) { |
| 107 var typeMap = TypeMapCreate(); |
| 108 for (var i = 0; i < typeList.length; i++) { |
| 109 TypeMapAddType(typeMap, typeList[i], true); |
| 110 } |
| 111 return typeMap; |
| 112 } |
| 113 |
| 114 function TypeMapHasType(typeMap, type) { |
| 115 return !!typeMap[type]; |
| 116 } |
| 117 |
| 118 function TypeMapIsDisjointFrom(typeMap1, typeMap2) { |
| 119 if (!typeMap1 || !typeMap2) |
106 return true; | 120 return true; |
107 | 121 |
108 var performing = objectInfo.performing; | 122 for (var type in typeMap1) { |
109 for (var type in performing) { | 123 if (TypeMapHasType(typeMap1, type) && TypeMapHasType(typeMap2, type)) |
110 if (performing[type] > 0 && observer.accept[type]) | |
111 return false; | 124 return false; |
112 } | 125 } |
113 | 126 |
114 return true; | 127 return true; |
115 } | 128 } |
116 | 129 |
117 function ObserverIsInactive(observer, objectInfo) { | 130 var defaultAcceptTypes = TypeMapCreateFromList([ |
118 return !ObserverIsActive(observer, objectInfo); | 131 'new', |
| 132 'updated', |
| 133 'deleted', |
| 134 'prototype', |
| 135 'reconfigured' |
| 136 ]); |
| 137 |
| 138 // An Observer is a registration to observe an object by a callback with |
| 139 // a given set of accept types. If the set of accept types is the default |
| 140 // set for Object.observe, the observer is represented as a direct reference |
| 141 // to the callback. An observer never changes its accept types and thus never |
| 142 // needs to "normalize". |
| 143 function ObserverCreate(callback, acceptList) { |
| 144 return IS_UNDEFINED(acceptList) ? callback : { |
| 145 __proto__: null, |
| 146 callback: callback, |
| 147 accept: TypeMapCreateFromList(acceptList) |
| 148 }; |
119 } | 149 } |
120 | 150 |
121 function RemoveNullElements(from) { | 151 function ObserverGetCallback(observer) { |
122 var i = 0; | 152 return IS_SPEC_FUNCTION(observer) ? observer : observer.callback; |
123 var j = 0; | 153 } |
124 for (; i < from.length; i++) { | 154 |
125 if (from[i] === null) | 155 function ObserverGetAcceptTypes(observer) { |
126 continue; | 156 return IS_SPEC_FUNCTION(observer) ? defaultAcceptTypes : observer.accept; |
127 if (j < i) | 157 } |
128 from[j] = from[i]; | 158 |
129 j++; | 159 function ObserverIsActive(observer, objectInfo) { |
| 160 return TypeMapIsDisjointFrom(ObjectInfoGetPerformingTypes(objectInfo), |
| 161 ObserverGetAcceptTypes(observer)); |
| 162 } |
| 163 |
| 164 function ObjectInfoGet(object) { |
| 165 var objectInfo = objectInfoMap.get(object); |
| 166 if (IS_UNDEFINED(objectInfo)) { |
| 167 if (!%IsJSProxy(object)) |
| 168 %SetIsObserved(object); |
| 169 |
| 170 objectInfo = { |
| 171 object: object, |
| 172 changeObservers: null, |
| 173 notifier: null, |
| 174 performing: null, |
| 175 performingCount: 0, |
| 176 }; |
| 177 objectInfoMap.set(object, objectInfo); |
| 178 } |
| 179 return objectInfo; |
| 180 } |
| 181 |
| 182 function ObjectInfoGetFromNotifier(notifier) { |
| 183 return notifierObjectInfoMap.get(notifier); |
| 184 } |
| 185 |
| 186 function ObjectInfoGetNotifier(objectInfo) { |
| 187 if (IS_NULL(objectInfo.notifier)) { |
| 188 objectInfo.notifier = { __proto__: notifierPrototype }; |
| 189 notifierObjectInfoMap.set(objectInfo.notifier, objectInfo); |
130 } | 190 } |
131 | 191 |
132 if (i !== j) | 192 return objectInfo.notifier; |
133 from.length = from.length - (i - j); | |
134 } | 193 } |
135 | 194 |
136 function RepartitionObservers(conditionFn, from, to, objectInfo) { | 195 function ObjectInfoGetObject(objectInfo) { |
137 var anyRemoved = false; | 196 return objectInfo.object; |
138 for (var i = 0; i < from.length; i++) { | 197 } |
139 var observer = from[i]; | 198 |
140 if (conditionFn(observer, objectInfo)) { | 199 function ChangeObserversIsOptimized(changeObservers) { |
141 anyRemoved = true; | 200 return typeof changeObservers === 'function' || |
142 from[i] = null; | 201 typeof changeObservers.callback === 'function'; |
143 to.push(observer); | 202 } |
144 } | 203 |
| 204 // The set of observers on an object is called 'changeObservers'. The first |
| 205 // observer is referenced directly via objectInfo.changeObservers. When a second |
| 206 // is added, changeObservers "normalizes" to become a mapping of callback |
| 207 // priority -> observer and is then stored on objectInfo.changeObservers. |
| 208 function ObjectInfoNormalizeChangeObservers(objectInfo) { |
| 209 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
| 210 var observer = objectInfo.changeObservers; |
| 211 var callback = ObserverGetCallback(observer); |
| 212 var callbackInfo = CallbackInfoGet(callback); |
| 213 var priority = CallbackInfoGetPriority(callbackInfo); |
| 214 objectInfo.changeObservers = { __proto__: null }; |
| 215 objectInfo.changeObservers[priority] = observer; |
| 216 } |
| 217 } |
| 218 |
| 219 function ObjectInfoAddObserver(objectInfo, callback, acceptList) { |
| 220 var callbackInfo = CallbackInfoGetOrCreate(callback); |
| 221 var observer = ObserverCreate(callback, acceptList); |
| 222 |
| 223 if (!objectInfo.changeObservers) { |
| 224 objectInfo.changeObservers = observer; |
| 225 return; |
145 } | 226 } |
146 | 227 |
147 if (anyRemoved) | 228 ObjectInfoNormalizeChangeObservers(objectInfo); |
148 RemoveNullElements(from); | 229 var priority = CallbackInfoGetPriority(callbackInfo); |
| 230 objectInfo.changeObservers[priority] = observer; |
149 } | 231 } |
150 | 232 |
151 function BeginPerformChange(objectInfo, type) { | 233 function ObjectInfoRemoveObserver(objectInfo, callback) { |
152 objectInfo.performing[type] = (objectInfo.performing[type] || 0) + 1; | 234 if (!objectInfo.changeObservers) |
153 objectInfo.performingCount++; | 235 return; |
154 RepartitionObservers(ObserverIsInactive, | 236 |
155 objectInfo.changeObservers, | 237 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
156 objectInfo.inactiveObservers, | 238 if (callback === ObserverGetCallback(objectInfo.changeObservers)) |
157 objectInfo); | 239 objectInfo.changeObservers = null; |
| 240 return; |
| 241 } |
| 242 |
| 243 var callbackInfo = CallbackInfoGet(callback); |
| 244 var priority = CallbackInfoGetPriority(callbackInfo); |
| 245 delete objectInfo.changeObservers[priority]; |
158 } | 246 } |
159 | 247 |
160 function EndPerformChange(objectInfo, type) { | 248 function ObjectInfoHasActiveObservers(objectInfo) { |
161 objectInfo.performing[type]--; | 249 if (IS_UNDEFINED(objectInfo) || !objectInfo.changeObservers) |
162 objectInfo.performingCount--; | 250 return false; |
163 RepartitionObservers(ObserverIsActive, | 251 |
164 objectInfo.inactiveObservers, | 252 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) |
165 objectInfo.changeObservers, | 253 return ObserverIsActive(objectInfo.changeObservers, objectInfo); |
166 objectInfo); | 254 |
| 255 for (var priority in objectInfo.changeObservers) { |
| 256 if (ObserverIsActive(objectInfo.changeObservers[priority], objectInfo)) |
| 257 return true; |
| 258 } |
| 259 |
| 260 return false; |
167 } | 261 } |
168 | 262 |
169 function EnsureObserverRemoved(objectInfo, callback) { | 263 function ObjectInfoAddPerformingType(objectInfo, type) { |
170 function remove(observerList) { | 264 objectInfo.performing = objectInfo.performing || TypeMapCreate(); |
171 for (var i = 0; i < observerList.length; i++) { | 265 TypeMapAddType(objectInfo.performing, type); |
172 if (observerList[i].callback === callback) { | 266 objectInfo.performingCount++; |
173 observerList.splice(i, 1); | 267 } |
174 return true; | |
175 } | |
176 } | |
177 return false; | |
178 } | |
179 | 268 |
180 if (!remove(objectInfo.changeObservers)) | 269 function ObjectInfoRemovePerformingType(objectInfo, type) { |
181 remove(objectInfo.inactiveObservers); | 270 objectInfo.performingCount--; |
| 271 TypeMapRemoveType(objectInfo.performing, type); |
| 272 } |
| 273 |
| 274 function ObjectInfoGetPerformingTypes(objectInfo) { |
| 275 return objectInfo.performingCount > 0 ? objectInfo.performing : null; |
182 } | 276 } |
183 | 277 |
184 function AcceptArgIsValid(arg) { | 278 function AcceptArgIsValid(arg) { |
185 if (IS_UNDEFINED(arg)) | 279 if (IS_UNDEFINED(arg)) |
186 return true; | 280 return true; |
187 | 281 |
188 if (!IS_SPEC_OBJECT(arg) || | 282 if (!IS_SPEC_OBJECT(arg) || |
189 !IS_NUMBER(arg.length) || | 283 !IS_NUMBER(arg.length) || |
190 arg.length < 0) | 284 arg.length < 0) |
191 return false; | 285 return false; |
192 | 286 |
193 var length = arg.length; | 287 var length = arg.length; |
194 for (var i = 0; i < length; i++) { | 288 for (var i = 0; i < length; i++) { |
195 if (!IS_STRING(arg[i])) | 289 if (!IS_STRING(arg[i])) |
196 return false; | 290 return false; |
197 } | 291 } |
198 return true; | 292 return true; |
199 } | 293 } |
200 | 294 |
201 function EnsureCallbackPriority(callback) { | 295 // CallbackInfo's optimized state is just a number which represents its global |
202 if (!callbackInfoMap.has(callback)) | 296 // priority. When a change record must be enqueued for the callback, it |
203 callbackInfoMap.set(callback, observationState.nextCallbackPriority++); | 297 // normalizes. When delivery clears any pending change records, it re-optimizes. |
| 298 function CallbackInfoGet(callback) { |
| 299 return callbackInfoMap.get(callback); |
204 } | 300 } |
205 | 301 |
206 function NormalizeCallbackInfo(callback) { | 302 function CallbackInfoGetOrCreate(callback) { |
| 303 var callbackInfo = callbackInfoMap.get(callback); |
| 304 if (!IS_UNDEFINED(callbackInfo)) |
| 305 return callbackInfo; |
| 306 |
| 307 var priority = observationState.nextCallbackPriority++ |
| 308 callbackInfoMap.set(callback, priority); |
| 309 return priority; |
| 310 } |
| 311 |
| 312 function CallbackInfoGetPriority(callbackInfo) { |
| 313 if (IS_NUMBER(callbackInfo)) |
| 314 return callbackInfo; |
| 315 else |
| 316 return callbackInfo.priority; |
| 317 } |
| 318 |
| 319 function CallbackInfoNormalize(callback) { |
207 var callbackInfo = callbackInfoMap.get(callback); | 320 var callbackInfo = callbackInfoMap.get(callback); |
208 if (IS_NUMBER(callbackInfo)) { | 321 if (IS_NUMBER(callbackInfo)) { |
209 var priority = callbackInfo; | 322 var priority = callbackInfo; |
210 callbackInfo = new InternalArray; | 323 callbackInfo = new InternalArray; |
211 callbackInfo.priority = priority; | 324 callbackInfo.priority = priority; |
212 callbackInfoMap.set(callback, callbackInfo); | 325 callbackInfoMap.set(callback, callbackInfo); |
213 } | 326 } |
214 return callbackInfo; | 327 return callbackInfo; |
215 } | 328 } |
216 | 329 |
217 function ObjectObserve(object, callback, accept) { | 330 function ObjectObserve(object, callback, acceptList) { |
218 if (!IS_SPEC_OBJECT(object)) | 331 if (!IS_SPEC_OBJECT(object)) |
219 throw MakeTypeError("observe_non_object", ["observe"]); | 332 throw MakeTypeError("observe_non_object", ["observe"]); |
220 if (!IS_SPEC_FUNCTION(callback)) | 333 if (!IS_SPEC_FUNCTION(callback)) |
221 throw MakeTypeError("observe_non_function", ["observe"]); | 334 throw MakeTypeError("observe_non_function", ["observe"]); |
222 if (ObjectIsFrozen(callback)) | 335 if (ObjectIsFrozen(callback)) |
223 throw MakeTypeError("observe_callback_frozen"); | 336 throw MakeTypeError("observe_callback_frozen"); |
224 if (!AcceptArgIsValid(accept)) | 337 if (!AcceptArgIsValid(acceptList)) |
225 throw MakeTypeError("observe_accept_invalid"); | 338 throw MakeTypeError("observe_accept_invalid"); |
226 | 339 |
227 EnsureCallbackPriority(callback); | 340 var objectInfo = ObjectInfoGet(object); |
228 | 341 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; | 342 return object; |
244 } | 343 } |
245 | 344 |
246 function ObjectUnobserve(object, callback) { | 345 function ObjectUnobserve(object, callback) { |
247 if (!IS_SPEC_OBJECT(object)) | 346 if (!IS_SPEC_OBJECT(object)) |
248 throw MakeTypeError("observe_non_object", ["unobserve"]); | 347 throw MakeTypeError("observe_non_object", ["unobserve"]); |
249 if (!IS_SPEC_FUNCTION(callback)) | 348 if (!IS_SPEC_FUNCTION(callback)) |
250 throw MakeTypeError("observe_non_function", ["unobserve"]); | 349 throw MakeTypeError("observe_non_function", ["unobserve"]); |
251 | 350 |
252 var objectInfo = objectInfoMap.get(object); | 351 var objectInfo = objectInfoMap.get(object); |
253 if (IS_UNDEFINED(objectInfo)) | 352 if (IS_UNDEFINED(objectInfo)) |
254 return object; | 353 return object; |
255 | 354 |
256 EnsureObserverRemoved(objectInfo, callback); | 355 ObjectInfoRemoveObserver(objectInfo, callback); |
257 return object; | 356 return object; |
258 } | 357 } |
259 | 358 |
260 function ArrayObserve(object, callback) { | 359 function ArrayObserve(object, callback) { |
261 return ObjectObserve(object, callback, ['new', | 360 return ObjectObserve(object, callback, ['new', |
262 'updated', | 361 'updated', |
263 'deleted', | 362 'deleted', |
264 'splice']); | 363 'splice']); |
265 } | 364 } |
266 | 365 |
267 function ArrayUnobserve(object, callback) { | 366 function ArrayUnobserve(object, callback) { |
268 return ObjectUnobserve(object, callback); | 367 return ObjectUnobserve(object, callback); |
269 } | 368 } |
270 | 369 |
271 function EnqueueToCallback(callback, changeRecord) { | 370 function ObserverEnqueueIfActive(observer, objectInfo, changeRecord) { |
272 var callbackInfo = NormalizeCallbackInfo(callback); | 371 if (!ObserverIsActive(observer, objectInfo) || |
| 372 !TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) { |
| 373 return; |
| 374 } |
| 375 |
| 376 var callback = ObserverGetCallback(observer); |
| 377 var callbackInfo = CallbackInfoNormalize(callback); |
| 378 if (!observationState.pendingObservers) |
| 379 observationState.pendingObservers = { __proto__: null }; |
273 observationState.pendingObservers[callbackInfo.priority] = callback; | 380 observationState.pendingObservers[callbackInfo.priority] = callback; |
274 callbackInfo.push(changeRecord); | 381 callbackInfo.push(changeRecord); |
275 %SetObserverDeliveryPending(); | 382 %SetObserverDeliveryPending(); |
276 } | 383 } |
277 | 384 |
278 function EnqueueChangeRecord(changeRecord, observers) { | 385 function ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord) { |
279 // TODO(rossberg): adjust once there is a story for symbols vs proxies. | 386 // TODO(rossberg): adjust once there is a story for symbols vs proxies. |
280 if (IS_SYMBOL(changeRecord.name)) return; | 387 if (IS_SYMBOL(changeRecord.name)) return; |
281 | 388 |
282 for (var i = 0; i < observers.length; i++) { | 389 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { |
283 var observer = observers[i]; | 390 var observer = objectInfo.changeObservers; |
284 if (IS_UNDEFINED(observer.accept[changeRecord.type])) | 391 ObserverEnqueueIfActive(observer, objectInfo, changeRecord); |
285 continue; | 392 return; |
| 393 } |
286 | 394 |
287 EnqueueToCallback(observer.callback, changeRecord); | 395 for (var priority in objectInfo.changeObservers) { |
| 396 var observer = objectInfo.changeObservers[priority]; |
| 397 ObserverEnqueueIfActive(observer, objectInfo, changeRecord); |
288 } | 398 } |
289 } | 399 } |
290 | 400 |
291 function BeginPerformSplice(array) { | 401 function BeginPerformSplice(array) { |
292 var objectInfo = objectInfoMap.get(array); | 402 var objectInfo = objectInfoMap.get(array); |
293 if (!IS_UNDEFINED(objectInfo)) | 403 if (!IS_UNDEFINED(objectInfo)) |
294 BeginPerformChange(objectInfo, 'splice'); | 404 ObjectInfoAddPerformingType(objectInfo, 'splice'); |
295 } | 405 } |
296 | 406 |
297 function EndPerformSplice(array) { | 407 function EndPerformSplice(array) { |
298 var objectInfo = objectInfoMap.get(array); | 408 var objectInfo = objectInfoMap.get(array); |
299 if (!IS_UNDEFINED(objectInfo)) | 409 if (!IS_UNDEFINED(objectInfo)) |
300 EndPerformChange(objectInfo, 'splice'); | 410 ObjectInfoRemovePerformingType(objectInfo, 'splice'); |
301 } | 411 } |
302 | 412 |
303 function EnqueueSpliceRecord(array, index, removed, addedCount) { | 413 function EnqueueSpliceRecord(array, index, removed, addedCount) { |
304 var objectInfo = objectInfoMap.get(array); | 414 var objectInfo = objectInfoMap.get(array); |
305 if (IS_UNDEFINED(objectInfo) || objectInfo.changeObservers.length === 0) | 415 if (!ObjectInfoHasActiveObservers(objectInfo)) |
306 return; | 416 return; |
307 | 417 |
308 var changeRecord = { | 418 var changeRecord = { |
309 type: 'splice', | 419 type: 'splice', |
310 object: array, | 420 object: array, |
311 index: index, | 421 index: index, |
312 removed: removed, | 422 removed: removed, |
313 addedCount: addedCount | 423 addedCount: addedCount |
314 }; | 424 }; |
315 | 425 |
316 ObjectFreeze(changeRecord); | 426 ObjectFreeze(changeRecord); |
317 ObjectFreeze(changeRecord.removed); | 427 ObjectFreeze(changeRecord.removed); |
318 EnqueueChangeRecord(changeRecord, objectInfo.changeObservers); | 428 ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord); |
319 } | 429 } |
320 | 430 |
321 function NotifyChange(type, object, name, oldValue) { | 431 function NotifyChange(type, object, name, oldValue) { |
322 var objectInfo = objectInfoMap.get(object); | 432 var objectInfo = objectInfoMap.get(object); |
323 if (objectInfo.changeObservers.length === 0) | 433 if (!ObjectInfoHasActiveObservers(objectInfo)) |
324 return; | 434 return; |
325 | 435 |
326 var changeRecord = (arguments.length < 4) ? | 436 var changeRecord = (arguments.length < 4) ? |
327 { type: type, object: object, name: name } : | 437 { type: type, object: object, name: name } : |
328 { type: type, object: object, name: name, oldValue: oldValue }; | 438 { type: type, object: object, name: name, oldValue: oldValue }; |
329 ObjectFreeze(changeRecord); | 439 ObjectFreeze(changeRecord); |
330 EnqueueChangeRecord(changeRecord, objectInfo.changeObservers); | 440 ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord); |
331 } | 441 } |
332 | 442 |
333 var notifierPrototype = {}; | 443 var notifierPrototype = {}; |
334 | 444 |
335 function ObjectNotifierNotify(changeRecord) { | 445 function ObjectNotifierNotify(changeRecord) { |
336 if (!IS_SPEC_OBJECT(this)) | 446 if (!IS_SPEC_OBJECT(this)) |
337 throw MakeTypeError("called_on_non_object", ["notify"]); | 447 throw MakeTypeError("called_on_non_object", ["notify"]); |
338 | 448 |
339 var target = notifierTargetMap.get(this); | 449 var objectInfo = ObjectInfoGetFromNotifier(this); |
340 if (IS_UNDEFINED(target)) | 450 if (IS_UNDEFINED(objectInfo)) |
341 throw MakeTypeError("observe_notify_non_notifier"); | 451 throw MakeTypeError("observe_notify_non_notifier"); |
342 if (!IS_STRING(changeRecord.type)) | 452 if (!IS_STRING(changeRecord.type)) |
343 throw MakeTypeError("observe_type_non_string"); | 453 throw MakeTypeError("observe_type_non_string"); |
344 | 454 |
345 var objectInfo = objectInfoMap.get(target); | 455 if (!ObjectInfoHasActiveObservers(objectInfo)) |
346 if (IS_UNDEFINED(objectInfo) || objectInfo.changeObservers.length === 0) | |
347 return; | 456 return; |
348 | 457 |
349 var newRecord = { object: target }; | 458 var newRecord = { object: ObjectInfoGetObject(objectInfo) }; |
350 for (var prop in changeRecord) { | 459 for (var prop in changeRecord) { |
351 if (prop === 'object') continue; | 460 if (prop === 'object') continue; |
352 %DefineOrRedefineDataProperty(newRecord, prop, changeRecord[prop], | 461 %DefineOrRedefineDataProperty(newRecord, prop, changeRecord[prop], |
353 READ_ONLY + DONT_DELETE); | 462 READ_ONLY + DONT_DELETE); |
354 } | 463 } |
355 ObjectFreeze(newRecord); | 464 ObjectFreeze(newRecord); |
356 | 465 |
357 EnqueueChangeRecord(newRecord, objectInfo.changeObservers); | 466 ObjectInfoEnqueueChangeRecord(objectInfo, newRecord); |
358 } | 467 } |
359 | 468 |
360 function ObjectNotifierPerformChange(changeType, changeFn, receiver) { | 469 function ObjectNotifierPerformChange(changeType, changeFn, receiver) { |
361 if (!IS_SPEC_OBJECT(this)) | 470 if (!IS_SPEC_OBJECT(this)) |
362 throw MakeTypeError("called_on_non_object", ["performChange"]); | 471 throw MakeTypeError("called_on_non_object", ["performChange"]); |
363 | 472 |
364 var target = notifierTargetMap.get(this); | 473 var objectInfo = ObjectInfoGetFromNotifier(this); |
365 if (IS_UNDEFINED(target)) | 474 |
| 475 if (IS_UNDEFINED(objectInfo)) |
366 throw MakeTypeError("observe_notify_non_notifier"); | 476 throw MakeTypeError("observe_notify_non_notifier"); |
367 if (!IS_STRING(changeType)) | 477 if (!IS_STRING(changeType)) |
368 throw MakeTypeError("observe_perform_non_string"); | 478 throw MakeTypeError("observe_perform_non_string"); |
369 if (!IS_SPEC_FUNCTION(changeFn)) | 479 if (!IS_SPEC_FUNCTION(changeFn)) |
370 throw MakeTypeError("observe_perform_non_function"); | 480 throw MakeTypeError("observe_perform_non_function"); |
371 | 481 |
372 if (IS_NULL_OR_UNDEFINED(receiver)) { | 482 if (IS_NULL_OR_UNDEFINED(receiver)) { |
373 receiver = %GetDefaultReceiver(changeFn) || receiver; | 483 receiver = %GetDefaultReceiver(changeFn) || receiver; |
374 } else if (!IS_SPEC_OBJECT(receiver) && %IsClassicModeFunction(changeFn)) { | 484 } else if (!IS_SPEC_OBJECT(receiver) && %IsClassicModeFunction(changeFn)) { |
375 receiver = ToObject(receiver); | 485 receiver = ToObject(receiver); |
376 } | 486 } |
377 | 487 |
378 var objectInfo = objectInfoMap.get(target); | 488 ObjectInfoAddPerformingType(objectInfo, changeType); |
379 if (IS_UNDEFINED(objectInfo)) | |
380 return; | |
381 | |
382 BeginPerformChange(objectInfo, changeType); | |
383 try { | 489 try { |
384 %_CallFunction(receiver, changeFn); | 490 %_CallFunction(receiver, changeFn); |
385 } finally { | 491 } finally { |
386 EndPerformChange(objectInfo, changeType); | 492 ObjectInfoRemovePerformingType(objectInfo, changeType); |
387 } | 493 } |
388 } | 494 } |
389 | 495 |
390 function ObjectGetNotifier(object) { | 496 function ObjectGetNotifier(object) { |
391 if (!IS_SPEC_OBJECT(object)) | 497 if (!IS_SPEC_OBJECT(object)) |
392 throw MakeTypeError("observe_non_object", ["getNotifier"]); | 498 throw MakeTypeError("observe_non_object", ["getNotifier"]); |
393 | 499 |
394 if (ObjectIsFrozen(object)) return null; | 500 if (ObjectIsFrozen(object)) return null; |
395 | 501 |
396 var objectInfo = objectInfoMap.get(object); | 502 var objectInfo = ObjectInfoGet(object); |
397 if (IS_UNDEFINED(objectInfo)) { | 503 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 } | 504 } |
409 | 505 |
410 function CallbackDeliverPending(callback) { | 506 function CallbackDeliverPending(callback) { |
411 var callbackInfo = callbackInfoMap.get(callback); | 507 var callbackInfo = callbackInfoMap.get(callback); |
412 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) | 508 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) |
413 return false; | 509 return false; |
414 | 510 |
415 // Clear the pending change records from callback and return it to its | 511 // Clear the pending change records from callback and return it to its |
416 // "optimized" state. | 512 // "optimized" state. |
417 var priority = callbackInfo.priority; | 513 var priority = callbackInfo.priority; |
418 callbackInfoMap.set(callback, priority); | 514 callbackInfoMap.set(callback, priority); |
419 | 515 |
420 delete observationState.pendingObservers[priority]; | 516 if (observationState.pendingObservers) |
| 517 delete observationState.pendingObservers[priority]; |
| 518 |
421 var delivered = []; | 519 var delivered = []; |
422 %MoveArrayContents(callbackInfo, delivered); | 520 %MoveArrayContents(callbackInfo, delivered); |
423 | 521 |
424 try { | 522 try { |
425 %Call(void 0, delivered, callback); | 523 %Call(void 0, delivered, callback); |
426 } catch (ex) {} | 524 } catch (ex) {} |
427 return true; | 525 return true; |
428 } | 526 } |
429 | 527 |
430 function ObjectDeliverChangeRecords(callback) { | 528 function ObjectDeliverChangeRecords(callback) { |
431 if (!IS_SPEC_FUNCTION(callback)) | 529 if (!IS_SPEC_FUNCTION(callback)) |
432 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); | 530 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); |
433 | 531 |
434 while (CallbackDeliverPending(callback)) {} | 532 while (CallbackDeliverPending(callback)) {} |
435 } | 533 } |
436 | 534 |
437 function DeliverChangeRecords() { | 535 function DeliverChangeRecords() { |
438 while (observationState.pendingObservers.length) { | 536 while (observationState.pendingObservers) { |
439 var pendingObservers = observationState.pendingObservers; | 537 var pendingObservers = observationState.pendingObservers; |
440 observationState.pendingObservers = new InternalArray; | 538 observationState.pendingObservers = null; |
441 for (var i in pendingObservers) { | 539 for (var i in pendingObservers) { |
442 CallbackDeliverPending(pendingObservers[i]); | 540 CallbackDeliverPending(pendingObservers[i]); |
443 } | 541 } |
444 } | 542 } |
445 } | 543 } |
446 | 544 |
447 function SetupObjectObserve() { | 545 function SetupObjectObserve() { |
448 %CheckIsBootstrapping(); | 546 %CheckIsBootstrapping(); |
449 InstallFunctions($Object, DONT_ENUM, $Array( | 547 InstallFunctions($Object, DONT_ENUM, $Array( |
450 "deliverChangeRecords", ObjectDeliverChangeRecords, | 548 "deliverChangeRecords", ObjectDeliverChangeRecords, |
451 "getNotifier", ObjectGetNotifier, | 549 "getNotifier", ObjectGetNotifier, |
452 "observe", ObjectObserve, | 550 "observe", ObjectObserve, |
453 "unobserve", ObjectUnobserve | 551 "unobserve", ObjectUnobserve |
454 )); | 552 )); |
455 InstallFunctions($Array, DONT_ENUM, $Array( | 553 InstallFunctions($Array, DONT_ENUM, $Array( |
456 "observe", ArrayObserve, | 554 "observe", ArrayObserve, |
457 "unobserve", ArrayUnobserve | 555 "unobserve", ArrayUnobserve |
458 )); | 556 )); |
459 InstallFunctions(notifierPrototype, DONT_ENUM, $Array( | 557 InstallFunctions(notifierPrototype, DONT_ENUM, $Array( |
460 "notify", ObjectNotifierNotify, | 558 "notify", ObjectNotifierNotify, |
461 "performChange", ObjectNotifierPerformChange | 559 "performChange", ObjectNotifierPerformChange |
462 )); | 560 )); |
463 } | 561 } |
464 | 562 |
465 SetupObjectObserve(); | 563 SetupObjectObserve(); |
OLD | NEW |