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 | |
59 var observationState = %GetObservationState(); | 30 var observationState = %GetObservationState(); |
60 if (IS_UNDEFINED(observationState.callbackInfoMap)) { | 31 if (IS_UNDEFINED(observationState.callbackInfoMap)) { |
61 observationState.callbackInfoMap = %ObservationWeakMapCreate(); | 32 observationState.callbackInfoMap = %ObservationWeakMapCreate(); |
62 observationState.objectInfoMap = %ObservationWeakMapCreate(); | 33 observationState.objectInfoMap = %ObservationWeakMapCreate(); |
63 observationState.notifierObjectInfoMap = %ObservationWeakMapCreate(); | 34 observationState.notifierTargetMap = %ObservationWeakMapCreate(); |
64 observationState.pendingObservers = null; | 35 observationState.pendingObservers = new InternalArray; |
65 observationState.nextCallbackPriority = 0; | 36 observationState.nextCallbackPriority = 0; |
66 } | 37 } |
67 | 38 |
68 function ObservationWeakMap(map) { | 39 function ObservationWeakMap(map) { |
69 this.map_ = map; | 40 this.map_ = map; |
70 } | 41 } |
71 | 42 |
72 ObservationWeakMap.prototype = { | 43 ObservationWeakMap.prototype = { |
73 get: function(key) { | 44 get: function(key) { |
74 key = %UnwrapGlobalProxy(key); | 45 key = %UnwrapGlobalProxy(key); |
75 if (!IS_SPEC_OBJECT(key)) return void 0; | 46 if (!IS_SPEC_OBJECT(key)) return void 0; |
76 return %WeakCollectionGet(this.map_, key); | 47 return %WeakCollectionGet(this.map_, key); |
77 }, | 48 }, |
78 set: function(key, value) { | 49 set: function(key, value) { |
79 key = %UnwrapGlobalProxy(key); | 50 key = %UnwrapGlobalProxy(key); |
80 if (!IS_SPEC_OBJECT(key)) return void 0; | 51 if (!IS_SPEC_OBJECT(key)) return void 0; |
81 %WeakCollectionSet(this.map_, key, value); | 52 %WeakCollectionSet(this.map_, key, value); |
82 }, | 53 }, |
83 has: function(key) { | 54 has: function(key) { |
84 return !IS_UNDEFINED(this.get(key)); | 55 return !IS_UNDEFINED(this.get(key)); |
85 } | 56 } |
86 }; | 57 }; |
87 | 58 |
88 var callbackInfoMap = | 59 var callbackInfoMap = |
89 new ObservationWeakMap(observationState.callbackInfoMap); | 60 new ObservationWeakMap(observationState.callbackInfoMap); |
90 var objectInfoMap = new ObservationWeakMap(observationState.objectInfoMap); | 61 var objectInfoMap = new ObservationWeakMap(observationState.objectInfoMap); |
91 var notifierObjectInfoMap = | 62 var notifierTargetMap = |
92 new ObservationWeakMap(observationState.notifierObjectInfoMap); | 63 new ObservationWeakMap(observationState.notifierTargetMap); |
93 | 64 |
94 function TypeMapCreate() { | 65 function CreateObjectInfo(object) { |
95 return { __proto__: null }; | 66 var info = { |
| 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; |
96 } | 75 } |
97 | 76 |
98 function TypeMapAddType(typeMap, type, ignoreDuplicate) { | 77 var defaultAcceptTypes = { |
99 typeMap[type] = ignoreDuplicate ? 1 : (typeMap[type] || 0) + 1; | 78 __proto__: null, |
| 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; |
100 } | 102 } |
101 | 103 |
102 function TypeMapRemoveType(typeMap, type) { | 104 function ObserverIsActive(observer, objectInfo) { |
103 typeMap[type]--; | 105 if (objectInfo.performingCount === 0) |
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) | |
120 return true; | 106 return true; |
121 | 107 |
122 for (var type in typeMap1) { | 108 var performing = objectInfo.performing; |
123 if (TypeMapHasType(typeMap1, type) && TypeMapHasType(typeMap2, type)) | 109 for (var type in performing) { |
| 110 if (performing[type] > 0 && observer.accept[type]) |
124 return false; | 111 return false; |
125 } | 112 } |
126 | 113 |
127 return true; | 114 return true; |
128 } | 115 } |
129 | 116 |
130 var defaultAcceptTypes = TypeMapCreateFromList([ | 117 function ObserverIsInactive(observer, objectInfo) { |
131 'new', | 118 return !ObserverIsActive(observer, objectInfo); |
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 }; | |
149 } | 119 } |
150 | 120 |
151 function ObserverGetCallback(observer) { | 121 function RemoveNullElements(from) { |
152 return IS_SPEC_FUNCTION(observer) ? observer : observer.callback; | 122 var i = 0; |
| 123 var j = 0; |
| 124 for (; i < from.length; i++) { |
| 125 if (from[i] === null) |
| 126 continue; |
| 127 if (j < i) |
| 128 from[j] = from[i]; |
| 129 j++; |
| 130 } |
| 131 |
| 132 if (i !== j) |
| 133 from.length = from.length - (i - j); |
153 } | 134 } |
154 | 135 |
155 function ObserverGetAcceptTypes(observer) { | 136 function RepartitionObservers(conditionFn, from, to, objectInfo) { |
156 return IS_SPEC_FUNCTION(observer) ? defaultAcceptTypes : observer.accept; | 137 var anyRemoved = false; |
| 138 for (var i = 0; i < from.length; i++) { |
| 139 var observer = from[i]; |
| 140 if (conditionFn(observer, objectInfo)) { |
| 141 anyRemoved = true; |
| 142 from[i] = null; |
| 143 to.push(observer); |
| 144 } |
| 145 } |
| 146 |
| 147 if (anyRemoved) |
| 148 RemoveNullElements(from); |
157 } | 149 } |
158 | 150 |
159 function ObserverIsActive(observer, objectInfo) { | 151 function BeginPerformChange(objectInfo, type) { |
160 return TypeMapIsDisjointFrom(ObjectInfoGetPerformingTypes(objectInfo), | 152 objectInfo.performing[type] = (objectInfo.performing[type] || 0) + 1; |
161 ObserverGetAcceptTypes(observer)); | 153 objectInfo.performingCount++; |
| 154 RepartitionObservers(ObserverIsInactive, |
| 155 objectInfo.changeObservers, |
| 156 objectInfo.inactiveObservers, |
| 157 objectInfo); |
162 } | 158 } |
163 | 159 |
164 function ObjectInfoGet(object) { | 160 function EndPerformChange(objectInfo, type) { |
165 var objectInfo = objectInfoMap.get(object); | 161 objectInfo.performing[type]--; |
166 if (IS_UNDEFINED(objectInfo)) { | 162 objectInfo.performingCount--; |
167 if (!%IsJSProxy(object)) | 163 RepartitionObservers(ObserverIsActive, |
168 %SetIsObserved(object); | 164 objectInfo.inactiveObservers, |
169 | 165 objectInfo.changeObservers, |
170 objectInfo = { | 166 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 } | 167 } |
181 | 168 |
182 function ObjectInfoGetFromNotifier(notifier) { | 169 function EnsureObserverRemoved(objectInfo, callback) { |
183 return notifierObjectInfoMap.get(notifier); | 170 function remove(observerList) { |
184 } | 171 for (var i = 0; i < observerList.length; i++) { |
185 | 172 if (observerList[i].callback === callback) { |
186 function ObjectInfoGetNotifier(objectInfo) { | 173 observerList.splice(i, 1); |
187 if (IS_NULL(objectInfo.notifier)) { | 174 return true; |
188 objectInfo.notifier = { __proto__: notifierPrototype }; | 175 } |
189 notifierObjectInfoMap.set(objectInfo.notifier, objectInfo); | 176 } |
| 177 return false; |
190 } | 178 } |
191 | 179 |
192 return objectInfo.notifier; | 180 if (!remove(objectInfo.changeObservers)) |
193 } | 181 remove(objectInfo.inactiveObservers); |
194 | |
195 function ObjectInfoGetObject(objectInfo) { | |
196 return objectInfo.object; | |
197 } | |
198 | |
199 function ChangeObserversIsOptimized(changeObservers) { | |
200 return typeof changeObservers === 'function' || | |
201 typeof changeObservers.callback === 'function'; | |
202 } | |
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; | |
226 } | |
227 | |
228 ObjectInfoNormalizeChangeObservers(objectInfo); | |
229 var priority = CallbackInfoGetPriority(callbackInfo); | |
230 objectInfo.changeObservers[priority] = observer; | |
231 } | |
232 | |
233 function ObjectInfoRemoveObserver(objectInfo, callback) { | |
234 if (!objectInfo.changeObservers) | |
235 return; | |
236 | |
237 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { | |
238 if (callback === ObserverGetCallback(objectInfo.changeObservers)) | |
239 objectInfo.changeObservers = null; | |
240 return; | |
241 } | |
242 | |
243 var callbackInfo = CallbackInfoGet(callback); | |
244 var priority = CallbackInfoGetPriority(callbackInfo); | |
245 delete objectInfo.changeObservers[priority]; | |
246 } | |
247 | |
248 function ObjectInfoHasActiveObservers(objectInfo) { | |
249 if (IS_UNDEFINED(objectInfo) || !objectInfo.changeObservers) | |
250 return false; | |
251 | |
252 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) | |
253 return ObserverIsActive(objectInfo.changeObservers, objectInfo); | |
254 | |
255 for (var priority in objectInfo.changeObservers) { | |
256 if (ObserverIsActive(objectInfo.changeObservers[priority], objectInfo)) | |
257 return true; | |
258 } | |
259 | |
260 return false; | |
261 } | |
262 | |
263 function ObjectInfoAddPerformingType(objectInfo, type) { | |
264 objectInfo.performing = objectInfo.performing || TypeMapCreate(); | |
265 TypeMapAddType(objectInfo.performing, type); | |
266 objectInfo.performingCount++; | |
267 } | |
268 | |
269 function ObjectInfoRemovePerformingType(objectInfo, type) { | |
270 objectInfo.performingCount--; | |
271 TypeMapRemoveType(objectInfo.performing, type); | |
272 } | |
273 | |
274 function ObjectInfoGetPerformingTypes(objectInfo) { | |
275 return objectInfo.performingCount > 0 ? objectInfo.performing : null; | |
276 } | 182 } |
277 | 183 |
278 function AcceptArgIsValid(arg) { | 184 function AcceptArgIsValid(arg) { |
279 if (IS_UNDEFINED(arg)) | 185 if (IS_UNDEFINED(arg)) |
280 return true; | 186 return true; |
281 | 187 |
282 if (!IS_SPEC_OBJECT(arg) || | 188 if (!IS_SPEC_OBJECT(arg) || |
283 !IS_NUMBER(arg.length) || | 189 !IS_NUMBER(arg.length) || |
284 arg.length < 0) | 190 arg.length < 0) |
285 return false; | 191 return false; |
286 | 192 |
287 var length = arg.length; | 193 var length = arg.length; |
288 for (var i = 0; i < length; i++) { | 194 for (var i = 0; i < length; i++) { |
289 if (!IS_STRING(arg[i])) | 195 if (!IS_STRING(arg[i])) |
290 return false; | 196 return false; |
291 } | 197 } |
292 return true; | 198 return true; |
293 } | 199 } |
294 | 200 |
295 // CallbackInfo's optimized state is just a number which represents its global | 201 function EnsureCallbackPriority(callback) { |
296 // priority. When a change record must be enqueued for the callback, it | 202 if (!callbackInfoMap.has(callback)) |
297 // normalizes. When delivery clears any pending change records, it re-optimizes. | 203 callbackInfoMap.set(callback, observationState.nextCallbackPriority++); |
298 function CallbackInfoGet(callback) { | |
299 return callbackInfoMap.get(callback); | |
300 } | 204 } |
301 | 205 |
302 function CallbackInfoGetOrCreate(callback) { | 206 function NormalizeCallbackInfo(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) { | |
320 var callbackInfo = callbackInfoMap.get(callback); | 207 var callbackInfo = callbackInfoMap.get(callback); |
321 if (IS_NUMBER(callbackInfo)) { | 208 if (IS_NUMBER(callbackInfo)) { |
322 var priority = callbackInfo; | 209 var priority = callbackInfo; |
323 callbackInfo = new InternalArray; | 210 callbackInfo = new InternalArray; |
324 callbackInfo.priority = priority; | 211 callbackInfo.priority = priority; |
325 callbackInfoMap.set(callback, callbackInfo); | 212 callbackInfoMap.set(callback, callbackInfo); |
326 } | 213 } |
327 return callbackInfo; | 214 return callbackInfo; |
328 } | 215 } |
329 | 216 |
330 function ObjectObserve(object, callback, acceptList) { | 217 function ObjectObserve(object, callback, accept) { |
331 if (!IS_SPEC_OBJECT(object)) | 218 if (!IS_SPEC_OBJECT(object)) |
332 throw MakeTypeError("observe_non_object", ["observe"]); | 219 throw MakeTypeError("observe_non_object", ["observe"]); |
333 if (!IS_SPEC_FUNCTION(callback)) | 220 if (!IS_SPEC_FUNCTION(callback)) |
334 throw MakeTypeError("observe_non_function", ["observe"]); | 221 throw MakeTypeError("observe_non_function", ["observe"]); |
335 if (ObjectIsFrozen(callback)) | 222 if (ObjectIsFrozen(callback)) |
336 throw MakeTypeError("observe_callback_frozen"); | 223 throw MakeTypeError("observe_callback_frozen"); |
337 if (!AcceptArgIsValid(acceptList)) | 224 if (!AcceptArgIsValid(accept)) |
338 throw MakeTypeError("observe_accept_invalid"); | 225 throw MakeTypeError("observe_accept_invalid"); |
339 | 226 |
340 var objectInfo = ObjectInfoGet(object); | 227 EnsureCallbackPriority(callback); |
341 ObjectInfoAddObserver(objectInfo, callback, acceptList); | 228 |
| 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 |
342 return object; | 243 return object; |
343 } | 244 } |
344 | 245 |
345 function ObjectUnobserve(object, callback) { | 246 function ObjectUnobserve(object, callback) { |
346 if (!IS_SPEC_OBJECT(object)) | 247 if (!IS_SPEC_OBJECT(object)) |
347 throw MakeTypeError("observe_non_object", ["unobserve"]); | 248 throw MakeTypeError("observe_non_object", ["unobserve"]); |
348 if (!IS_SPEC_FUNCTION(callback)) | 249 if (!IS_SPEC_FUNCTION(callback)) |
349 throw MakeTypeError("observe_non_function", ["unobserve"]); | 250 throw MakeTypeError("observe_non_function", ["unobserve"]); |
350 | 251 |
351 var objectInfo = objectInfoMap.get(object); | 252 var objectInfo = objectInfoMap.get(object); |
352 if (IS_UNDEFINED(objectInfo)) | 253 if (IS_UNDEFINED(objectInfo)) |
353 return object; | 254 return object; |
354 | 255 |
355 ObjectInfoRemoveObserver(objectInfo, callback); | 256 EnsureObserverRemoved(objectInfo, callback); |
356 return object; | 257 return object; |
357 } | 258 } |
358 | 259 |
359 function ArrayObserve(object, callback) { | 260 function ArrayObserve(object, callback) { |
360 return ObjectObserve(object, callback, ['new', | 261 return ObjectObserve(object, callback, ['new', |
361 'updated', | 262 'updated', |
362 'deleted', | 263 'deleted', |
363 'splice']); | 264 'splice']); |
364 } | 265 } |
365 | 266 |
366 function ArrayUnobserve(object, callback) { | 267 function ArrayUnobserve(object, callback) { |
367 return ObjectUnobserve(object, callback); | 268 return ObjectUnobserve(object, callback); |
368 } | 269 } |
369 | 270 |
370 function ObserverEnqueueIfActive(observer, objectInfo, changeRecord) { | 271 function EnqueueToCallback(callback, changeRecord) { |
371 if (!ObserverIsActive(observer, objectInfo) || | 272 var callbackInfo = NormalizeCallbackInfo(callback); |
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 }; | |
380 observationState.pendingObservers[callbackInfo.priority] = callback; | 273 observationState.pendingObservers[callbackInfo.priority] = callback; |
381 callbackInfo.push(changeRecord); | 274 callbackInfo.push(changeRecord); |
382 %SetObserverDeliveryPending(); | 275 %SetObserverDeliveryPending(); |
383 } | 276 } |
384 | 277 |
385 function ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord) { | 278 function EnqueueChangeRecord(changeRecord, observers) { |
386 // TODO(rossberg): adjust once there is a story for symbols vs proxies. | 279 // TODO(rossberg): adjust once there is a story for symbols vs proxies. |
387 if (IS_SYMBOL(changeRecord.name)) return; | 280 if (IS_SYMBOL(changeRecord.name)) return; |
388 | 281 |
389 if (ChangeObserversIsOptimized(objectInfo.changeObservers)) { | 282 for (var i = 0; i < observers.length; i++) { |
390 var observer = objectInfo.changeObservers; | 283 var observer = observers[i]; |
391 ObserverEnqueueIfActive(observer, objectInfo, changeRecord); | 284 if (IS_UNDEFINED(observer.accept[changeRecord.type])) |
392 return; | 285 continue; |
393 } | |
394 | 286 |
395 for (var priority in objectInfo.changeObservers) { | 287 EnqueueToCallback(observer.callback, changeRecord); |
396 var observer = objectInfo.changeObservers[priority]; | |
397 ObserverEnqueueIfActive(observer, objectInfo, changeRecord); | |
398 } | 288 } |
399 } | 289 } |
400 | 290 |
401 function BeginPerformSplice(array) { | 291 function BeginPerformSplice(array) { |
402 var objectInfo = objectInfoMap.get(array); | 292 var objectInfo = objectInfoMap.get(array); |
403 if (!IS_UNDEFINED(objectInfo)) | 293 if (!IS_UNDEFINED(objectInfo)) |
404 ObjectInfoAddPerformingType(objectInfo, 'splice'); | 294 BeginPerformChange(objectInfo, 'splice'); |
405 } | 295 } |
406 | 296 |
407 function EndPerformSplice(array) { | 297 function EndPerformSplice(array) { |
408 var objectInfo = objectInfoMap.get(array); | 298 var objectInfo = objectInfoMap.get(array); |
409 if (!IS_UNDEFINED(objectInfo)) | 299 if (!IS_UNDEFINED(objectInfo)) |
410 ObjectInfoRemovePerformingType(objectInfo, 'splice'); | 300 EndPerformChange(objectInfo, 'splice'); |
411 } | 301 } |
412 | 302 |
413 function EnqueueSpliceRecord(array, index, removed, addedCount) { | 303 function EnqueueSpliceRecord(array, index, removed, addedCount) { |
414 var objectInfo = objectInfoMap.get(array); | 304 var objectInfo = objectInfoMap.get(array); |
415 if (!ObjectInfoHasActiveObservers(objectInfo)) | 305 if (IS_UNDEFINED(objectInfo) || objectInfo.changeObservers.length === 0) |
416 return; | 306 return; |
417 | 307 |
418 var changeRecord = { | 308 var changeRecord = { |
419 type: 'splice', | 309 type: 'splice', |
420 object: array, | 310 object: array, |
421 index: index, | 311 index: index, |
422 removed: removed, | 312 removed: removed, |
423 addedCount: addedCount | 313 addedCount: addedCount |
424 }; | 314 }; |
425 | 315 |
426 ObjectFreeze(changeRecord); | 316 ObjectFreeze(changeRecord); |
427 ObjectFreeze(changeRecord.removed); | 317 ObjectFreeze(changeRecord.removed); |
428 ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord); | 318 EnqueueChangeRecord(changeRecord, objectInfo.changeObservers); |
429 } | 319 } |
430 | 320 |
431 function NotifyChange(type, object, name, oldValue) { | 321 function NotifyChange(type, object, name, oldValue) { |
432 var objectInfo = objectInfoMap.get(object); | 322 var objectInfo = objectInfoMap.get(object); |
433 if (!ObjectInfoHasActiveObservers(objectInfo)) | 323 if (objectInfo.changeObservers.length === 0) |
434 return; | 324 return; |
435 | 325 |
436 var changeRecord = (arguments.length < 4) ? | 326 var changeRecord = (arguments.length < 4) ? |
437 { type: type, object: object, name: name } : | 327 { type: type, object: object, name: name } : |
438 { type: type, object: object, name: name, oldValue: oldValue }; | 328 { type: type, object: object, name: name, oldValue: oldValue }; |
439 ObjectFreeze(changeRecord); | 329 ObjectFreeze(changeRecord); |
440 ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord); | 330 EnqueueChangeRecord(changeRecord, objectInfo.changeObservers); |
441 } | 331 } |
442 | 332 |
443 var notifierPrototype = {}; | 333 var notifierPrototype = {}; |
444 | 334 |
445 function ObjectNotifierNotify(changeRecord) { | 335 function ObjectNotifierNotify(changeRecord) { |
446 if (!IS_SPEC_OBJECT(this)) | 336 if (!IS_SPEC_OBJECT(this)) |
447 throw MakeTypeError("called_on_non_object", ["notify"]); | 337 throw MakeTypeError("called_on_non_object", ["notify"]); |
448 | 338 |
449 var objectInfo = ObjectInfoGetFromNotifier(this); | 339 var target = notifierTargetMap.get(this); |
450 if (IS_UNDEFINED(objectInfo)) | 340 if (IS_UNDEFINED(target)) |
451 throw MakeTypeError("observe_notify_non_notifier"); | 341 throw MakeTypeError("observe_notify_non_notifier"); |
452 if (!IS_STRING(changeRecord.type)) | 342 if (!IS_STRING(changeRecord.type)) |
453 throw MakeTypeError("observe_type_non_string"); | 343 throw MakeTypeError("observe_type_non_string"); |
454 | 344 |
455 if (!ObjectInfoHasActiveObservers(objectInfo)) | 345 var objectInfo = objectInfoMap.get(target); |
| 346 if (IS_UNDEFINED(objectInfo) || objectInfo.changeObservers.length === 0) |
456 return; | 347 return; |
457 | 348 |
458 var newRecord = { object: ObjectInfoGetObject(objectInfo) }; | 349 var newRecord = { object: target }; |
459 for (var prop in changeRecord) { | 350 for (var prop in changeRecord) { |
460 if (prop === 'object') continue; | 351 if (prop === 'object') continue; |
461 %DefineOrRedefineDataProperty(newRecord, prop, changeRecord[prop], | 352 %DefineOrRedefineDataProperty(newRecord, prop, changeRecord[prop], |
462 READ_ONLY + DONT_DELETE); | 353 READ_ONLY + DONT_DELETE); |
463 } | 354 } |
464 ObjectFreeze(newRecord); | 355 ObjectFreeze(newRecord); |
465 | 356 |
466 ObjectInfoEnqueueChangeRecord(objectInfo, newRecord); | 357 EnqueueChangeRecord(newRecord, objectInfo.changeObservers); |
467 } | 358 } |
468 | 359 |
469 function ObjectNotifierPerformChange(changeType, changeFn, receiver) { | 360 function ObjectNotifierPerformChange(changeType, changeFn, receiver) { |
470 if (!IS_SPEC_OBJECT(this)) | 361 if (!IS_SPEC_OBJECT(this)) |
471 throw MakeTypeError("called_on_non_object", ["performChange"]); | 362 throw MakeTypeError("called_on_non_object", ["performChange"]); |
472 | 363 |
473 var objectInfo = ObjectInfoGetFromNotifier(this); | 364 var target = notifierTargetMap.get(this); |
474 | 365 if (IS_UNDEFINED(target)) |
475 if (IS_UNDEFINED(objectInfo)) | |
476 throw MakeTypeError("observe_notify_non_notifier"); | 366 throw MakeTypeError("observe_notify_non_notifier"); |
477 if (!IS_STRING(changeType)) | 367 if (!IS_STRING(changeType)) |
478 throw MakeTypeError("observe_perform_non_string"); | 368 throw MakeTypeError("observe_perform_non_string"); |
479 if (!IS_SPEC_FUNCTION(changeFn)) | 369 if (!IS_SPEC_FUNCTION(changeFn)) |
480 throw MakeTypeError("observe_perform_non_function"); | 370 throw MakeTypeError("observe_perform_non_function"); |
481 | 371 |
482 if (IS_NULL_OR_UNDEFINED(receiver)) { | 372 if (IS_NULL_OR_UNDEFINED(receiver)) { |
483 receiver = %GetDefaultReceiver(changeFn) || receiver; | 373 receiver = %GetDefaultReceiver(changeFn) || receiver; |
484 } else if (!IS_SPEC_OBJECT(receiver) && %IsClassicModeFunction(changeFn)) { | 374 } else if (!IS_SPEC_OBJECT(receiver) && %IsClassicModeFunction(changeFn)) { |
485 receiver = ToObject(receiver); | 375 receiver = ToObject(receiver); |
486 } | 376 } |
487 | 377 |
488 ObjectInfoAddPerformingType(objectInfo, changeType); | 378 var objectInfo = objectInfoMap.get(target); |
| 379 if (IS_UNDEFINED(objectInfo)) |
| 380 return; |
| 381 |
| 382 BeginPerformChange(objectInfo, changeType); |
489 try { | 383 try { |
490 %_CallFunction(receiver, changeFn); | 384 %_CallFunction(receiver, changeFn); |
491 } finally { | 385 } finally { |
492 ObjectInfoRemovePerformingType(objectInfo, changeType); | 386 EndPerformChange(objectInfo, changeType); |
493 } | 387 } |
494 } | 388 } |
495 | 389 |
496 function ObjectGetNotifier(object) { | 390 function ObjectGetNotifier(object) { |
497 if (!IS_SPEC_OBJECT(object)) | 391 if (!IS_SPEC_OBJECT(object)) |
498 throw MakeTypeError("observe_non_object", ["getNotifier"]); | 392 throw MakeTypeError("observe_non_object", ["getNotifier"]); |
499 | 393 |
500 if (ObjectIsFrozen(object)) return null; | 394 if (ObjectIsFrozen(object)) return null; |
501 | 395 |
502 var objectInfo = ObjectInfoGet(object); | 396 var objectInfo = objectInfoMap.get(object); |
503 return ObjectInfoGetNotifier(objectInfo); | 397 if (IS_UNDEFINED(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; |
504 } | 408 } |
505 | 409 |
506 function CallbackDeliverPending(callback) { | 410 function CallbackDeliverPending(callback) { |
507 var callbackInfo = callbackInfoMap.get(callback); | 411 var callbackInfo = callbackInfoMap.get(callback); |
508 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) | 412 if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) |
509 return false; | 413 return false; |
510 | 414 |
511 // Clear the pending change records from callback and return it to its | 415 // Clear the pending change records from callback and return it to its |
512 // "optimized" state. | 416 // "optimized" state. |
513 var priority = callbackInfo.priority; | 417 var priority = callbackInfo.priority; |
514 callbackInfoMap.set(callback, priority); | 418 callbackInfoMap.set(callback, priority); |
515 | 419 |
516 if (observationState.pendingObservers) | 420 delete observationState.pendingObservers[priority]; |
517 delete observationState.pendingObservers[priority]; | |
518 | |
519 var delivered = []; | 421 var delivered = []; |
520 %MoveArrayContents(callbackInfo, delivered); | 422 %MoveArrayContents(callbackInfo, delivered); |
521 | 423 |
522 try { | 424 try { |
523 %Call(void 0, delivered, callback); | 425 %Call(void 0, delivered, callback); |
524 } catch (ex) {} | 426 } catch (ex) {} |
525 return true; | 427 return true; |
526 } | 428 } |
527 | 429 |
528 function ObjectDeliverChangeRecords(callback) { | 430 function ObjectDeliverChangeRecords(callback) { |
529 if (!IS_SPEC_FUNCTION(callback)) | 431 if (!IS_SPEC_FUNCTION(callback)) |
530 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); | 432 throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); |
531 | 433 |
532 while (CallbackDeliverPending(callback)) {} | 434 while (CallbackDeliverPending(callback)) {} |
533 } | 435 } |
534 | 436 |
535 function DeliverChangeRecords() { | 437 function DeliverChangeRecords() { |
536 while (observationState.pendingObservers) { | 438 while (observationState.pendingObservers.length) { |
537 var pendingObservers = observationState.pendingObservers; | 439 var pendingObservers = observationState.pendingObservers; |
538 observationState.pendingObservers = null; | 440 observationState.pendingObservers = new InternalArray; |
539 for (var i in pendingObservers) { | 441 for (var i in pendingObservers) { |
540 CallbackDeliverPending(pendingObservers[i]); | 442 CallbackDeliverPending(pendingObservers[i]); |
541 } | 443 } |
542 } | 444 } |
543 } | 445 } |
544 | 446 |
545 function SetupObjectObserve() { | 447 function SetupObjectObserve() { |
546 %CheckIsBootstrapping(); | 448 %CheckIsBootstrapping(); |
547 InstallFunctions($Object, DONT_ENUM, $Array( | 449 InstallFunctions($Object, DONT_ENUM, $Array( |
548 "deliverChangeRecords", ObjectDeliverChangeRecords, | 450 "deliverChangeRecords", ObjectDeliverChangeRecords, |
549 "getNotifier", ObjectGetNotifier, | 451 "getNotifier", ObjectGetNotifier, |
550 "observe", ObjectObserve, | 452 "observe", ObjectObserve, |
551 "unobserve", ObjectUnobserve | 453 "unobserve", ObjectUnobserve |
552 )); | 454 )); |
553 InstallFunctions($Array, DONT_ENUM, $Array( | 455 InstallFunctions($Array, DONT_ENUM, $Array( |
554 "observe", ArrayObserve, | 456 "observe", ArrayObserve, |
555 "unobserve", ArrayUnobserve | 457 "unobserve", ArrayUnobserve |
556 )); | 458 )); |
557 InstallFunctions(notifierPrototype, DONT_ENUM, $Array( | 459 InstallFunctions(notifierPrototype, DONT_ENUM, $Array( |
558 "notify", ObjectNotifierNotify, | 460 "notify", ObjectNotifierNotify, |
559 "performChange", ObjectNotifierPerformChange | 461 "performChange", ObjectNotifierPerformChange |
560 )); | 462 )); |
561 } | 463 } |
562 | 464 |
563 SetupObjectObserve(); | 465 SetupObjectObserve(); |
OLD | NEW |