Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(295)

Side by Side Diff: src/object-observe.js

Issue 19541010: Implement optimized objectInfo structure (Closed) Base URL: https://github.com/v8/v8.git@bleeding_edge
Patch Set: fix cctest Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | test/cctest/test-object-observe.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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();
OLDNEW
« no previous file with comments | « no previous file | test/cctest/test-object-observe.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698