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