Home | History | Annotate | Download | only in src
      1 // 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 
     36 var observationState;
     37 
     38 function GetObservationStateJS() {
     39   if (IS_UNDEFINED(observationState))
     40     observationState = %GetObservationState();
     41 
     42   if (IS_UNDEFINED(observationState.callbackInfoMap)) {
     43     observationState.callbackInfoMap = %ObservationWeakMapCreate();
     44     observationState.objectInfoMap = %ObservationWeakMapCreate();
     45     observationState.notifierObjectInfoMap = %ObservationWeakMapCreate();
     46     observationState.pendingObservers = null;
     47     observationState.nextCallbackPriority = 0;
     48     observationState.lastMicrotaskId = 0;
     49   }
     50 
     51   return observationState;
     52 }
     53 
     54 function GetWeakMapWrapper() {
     55   function MapWrapper(map) {
     56     this.map_ = map;
     57   };
     58 
     59   MapWrapper.prototype = {
     60     __proto__: null,
     61     get: function(key) {
     62       return %WeakCollectionGet(this.map_, key);
     63     },
     64     set: function(key, value) {
     65       %WeakCollectionSet(this.map_, key, value);
     66     },
     67     has: function(key) {
     68       return !IS_UNDEFINED(this.get(key));
     69     }
     70   };
     71 
     72   return MapWrapper;
     73 }
     74 
     75 var contextMaps;
     76 
     77 function GetContextMaps() {
     78   if (IS_UNDEFINED(contextMaps)) {
     79     var map = GetWeakMapWrapper();
     80     var observationState = GetObservationStateJS();
     81     contextMaps = {
     82       callbackInfoMap: new map(observationState.callbackInfoMap),
     83       objectInfoMap: new map(observationState.objectInfoMap),
     84       notifierObjectInfoMap: new map(observationState.notifierObjectInfoMap)
     85     };
     86   }
     87 
     88   return contextMaps;
     89 }
     90 
     91 function GetCallbackInfoMap() {
     92   return GetContextMaps().callbackInfoMap;
     93 }
     94 
     95 function GetObjectInfoMap() {
     96   return GetContextMaps().objectInfoMap;
     97 }
     98 
     99 function GetNotifierObjectInfoMap() {
    100   return GetContextMaps().notifierObjectInfoMap;
    101 }
    102 
    103 function GetPendingObservers() {
    104   return GetObservationStateJS().pendingObservers;
    105 }
    106 
    107 function SetPendingObservers(pendingObservers) {
    108   GetObservationStateJS().pendingObservers = pendingObservers;
    109 }
    110 
    111 function GetNextCallbackPriority() {
    112   return GetObservationStateJS().nextCallbackPriority++;
    113 }
    114 
    115 function nullProtoObject() {
    116   return { __proto__: null };
    117 }
    118 
    119 function TypeMapCreate() {
    120   return nullProtoObject();
    121 }
    122 
    123 function TypeMapAddType(typeMap, type, ignoreDuplicate) {
    124   typeMap[type] = ignoreDuplicate ? 1 : (typeMap[type] || 0) + 1;
    125 }
    126 
    127 function TypeMapRemoveType(typeMap, type) {
    128   typeMap[type]--;
    129 }
    130 
    131 function TypeMapCreateFromList(typeList, length) {
    132   var typeMap = TypeMapCreate();
    133   for (var i = 0; i < length; i++) {
    134     TypeMapAddType(typeMap, typeList[i], true);
    135   }
    136   return typeMap;
    137 }
    138 
    139 function TypeMapHasType(typeMap, type) {
    140   return !!typeMap[type];
    141 }
    142 
    143 function TypeMapIsDisjointFrom(typeMap1, typeMap2) {
    144   if (!typeMap1 || !typeMap2)
    145     return true;
    146 
    147   for (var type in typeMap1) {
    148     if (TypeMapHasType(typeMap1, type) && TypeMapHasType(typeMap2, type))
    149       return false;
    150   }
    151 
    152   return true;
    153 }
    154 
    155 var defaultAcceptTypes = (function() {
    156   var defaultTypes = [
    157     'add',
    158     'update',
    159     'delete',
    160     'setPrototype',
    161     'reconfigure',
    162     'preventExtensions'
    163   ];
    164   return TypeMapCreateFromList(defaultTypes, defaultTypes.length);
    165 })();
    166 
    167 // An Observer is a registration to observe an object by a callback with
    168 // a given set of accept types. If the set of accept types is the default
    169 // set for Object.observe, the observer is represented as a direct reference
    170 // to the callback. An observer never changes its accept types and thus never
    171 // needs to "normalize".
    172 function ObserverCreate(callback, acceptList) {
    173   if (IS_UNDEFINED(acceptList))
    174     return callback;
    175   var observer = nullProtoObject();
    176   observer.callback = callback;
    177   observer.accept = acceptList;
    178   return observer;
    179 }
    180 
    181 function ObserverGetCallback(observer) {
    182   return IS_SPEC_FUNCTION(observer) ? observer : observer.callback;
    183 }
    184 
    185 function ObserverGetAcceptTypes(observer) {
    186   return IS_SPEC_FUNCTION(observer) ? defaultAcceptTypes : observer.accept;
    187 }
    188 
    189 function ObserverIsActive(observer, objectInfo) {
    190   return TypeMapIsDisjointFrom(ObjectInfoGetPerformingTypes(objectInfo),
    191                                ObserverGetAcceptTypes(observer));
    192 }
    193 
    194 function ObjectInfoGetOrCreate(object) {
    195   var objectInfo = ObjectInfoGet(object);
    196   if (IS_UNDEFINED(objectInfo)) {
    197     if (!%IsJSProxy(object))
    198       %SetIsObserved(object);
    199 
    200     objectInfo = {
    201       object: object,
    202       changeObservers: null,
    203       notifier: null,
    204       performing: null,
    205       performingCount: 0,
    206     };
    207     GetObjectInfoMap().set(object, objectInfo);
    208   }
    209   return objectInfo;
    210 }
    211 
    212 function ObjectInfoGet(object) {
    213   return GetObjectInfoMap().get(object);
    214 }
    215 
    216 function ObjectInfoGetFromNotifier(notifier) {
    217   return GetNotifierObjectInfoMap().get(notifier);
    218 }
    219 
    220 function ObjectInfoGetNotifier(objectInfo) {
    221   if (IS_NULL(objectInfo.notifier)) {
    222     objectInfo.notifier = { __proto__: notifierPrototype };
    223     GetNotifierObjectInfoMap().set(objectInfo.notifier, objectInfo);
    224   }
    225 
    226   return objectInfo.notifier;
    227 }
    228 
    229 function ObjectInfoGetObject(objectInfo) {
    230   return objectInfo.object;
    231 }
    232 
    233 function ChangeObserversIsOptimized(changeObservers) {
    234   return typeof changeObservers === 'function' ||
    235          typeof changeObservers.callback === 'function';
    236 }
    237 
    238 // The set of observers on an object is called 'changeObservers'. The first
    239 // observer is referenced directly via objectInfo.changeObservers. When a second
    240 // is added, changeObservers "normalizes" to become a mapping of callback
    241 // priority -> observer and is then stored on objectInfo.changeObservers.
    242 function ObjectInfoNormalizeChangeObservers(objectInfo) {
    243   if (ChangeObserversIsOptimized(objectInfo.changeObservers)) {
    244     var observer = objectInfo.changeObservers;
    245     var callback = ObserverGetCallback(observer);
    246     var callbackInfo = CallbackInfoGet(callback);
    247     var priority = CallbackInfoGetPriority(callbackInfo);
    248     objectInfo.changeObservers = nullProtoObject();
    249     objectInfo.changeObservers[priority] = observer;
    250   }
    251 }
    252 
    253 function ObjectInfoAddObserver(objectInfo, callback, acceptList) {
    254   var callbackInfo = CallbackInfoGetOrCreate(callback);
    255   var observer = ObserverCreate(callback, acceptList);
    256 
    257   if (!objectInfo.changeObservers) {
    258     objectInfo.changeObservers = observer;
    259     return;
    260   }
    261 
    262   ObjectInfoNormalizeChangeObservers(objectInfo);
    263   var priority = CallbackInfoGetPriority(callbackInfo);
    264   objectInfo.changeObservers[priority] = observer;
    265 }
    266 
    267 function ObjectInfoRemoveObserver(objectInfo, callback) {
    268   if (!objectInfo.changeObservers)
    269     return;
    270 
    271   if (ChangeObserversIsOptimized(objectInfo.changeObservers)) {
    272     if (callback === ObserverGetCallback(objectInfo.changeObservers))
    273       objectInfo.changeObservers = null;
    274     return;
    275   }
    276 
    277   var callbackInfo = CallbackInfoGet(callback);
    278   var priority = CallbackInfoGetPriority(callbackInfo);
    279   objectInfo.changeObservers[priority] = null;
    280 }
    281 
    282 function ObjectInfoHasActiveObservers(objectInfo) {
    283   if (IS_UNDEFINED(objectInfo) || !objectInfo.changeObservers)
    284     return false;
    285 
    286   if (ChangeObserversIsOptimized(objectInfo.changeObservers))
    287     return ObserverIsActive(objectInfo.changeObservers, objectInfo);
    288 
    289   for (var priority in objectInfo.changeObservers) {
    290     var observer = objectInfo.changeObservers[priority];
    291     if (!IS_NULL(observer) && ObserverIsActive(observer, objectInfo))
    292       return true;
    293   }
    294 
    295   return false;
    296 }
    297 
    298 function ObjectInfoAddPerformingType(objectInfo, type) {
    299   objectInfo.performing = objectInfo.performing || TypeMapCreate();
    300   TypeMapAddType(objectInfo.performing, type);
    301   objectInfo.performingCount++;
    302 }
    303 
    304 function ObjectInfoRemovePerformingType(objectInfo, type) {
    305   objectInfo.performingCount--;
    306   TypeMapRemoveType(objectInfo.performing, type);
    307 }
    308 
    309 function ObjectInfoGetPerformingTypes(objectInfo) {
    310   return objectInfo.performingCount > 0 ? objectInfo.performing : null;
    311 }
    312 
    313 function ConvertAcceptListToTypeMap(arg) {
    314   // We use undefined as a sentinel for the default accept list.
    315   if (IS_UNDEFINED(arg))
    316     return arg;
    317 
    318   if (!IS_SPEC_OBJECT(arg))
    319     throw MakeTypeError("observe_accept_invalid");
    320 
    321   var len = ToInteger(arg.length);
    322   if (len < 0) len = 0;
    323 
    324   return TypeMapCreateFromList(arg, len);
    325 }
    326 
    327 // CallbackInfo's optimized state is just a number which represents its global
    328 // priority. When a change record must be enqueued for the callback, it
    329 // normalizes. When delivery clears any pending change records, it re-optimizes.
    330 function CallbackInfoGet(callback) {
    331   return GetCallbackInfoMap().get(callback);
    332 }
    333 
    334 function CallbackInfoGetOrCreate(callback) {
    335   var callbackInfo = GetCallbackInfoMap().get(callback);
    336   if (!IS_UNDEFINED(callbackInfo))
    337     return callbackInfo;
    338 
    339   var priority =  GetNextCallbackPriority();
    340   GetCallbackInfoMap().set(callback, priority);
    341   return priority;
    342 }
    343 
    344 function CallbackInfoGetPriority(callbackInfo) {
    345   if (IS_NUMBER(callbackInfo))
    346     return callbackInfo;
    347   else
    348     return callbackInfo.priority;
    349 }
    350 
    351 function CallbackInfoNormalize(callback) {
    352   var callbackInfo = GetCallbackInfoMap().get(callback);
    353   if (IS_NUMBER(callbackInfo)) {
    354     var priority = callbackInfo;
    355     callbackInfo = new InternalArray;
    356     callbackInfo.priority = priority;
    357     GetCallbackInfoMap().set(callback, callbackInfo);
    358   }
    359   return callbackInfo;
    360 }
    361 
    362 function ObjectObserve(object, callback, acceptList) {
    363   if (!IS_SPEC_OBJECT(object))
    364     throw MakeTypeError("observe_non_object", ["observe"]);
    365   if (%IsJSGlobalProxy(object))
    366     throw MakeTypeError("observe_global_proxy", ["observe"]);
    367   if (!IS_SPEC_FUNCTION(callback))
    368     throw MakeTypeError("observe_non_function", ["observe"]);
    369   if (ObjectIsFrozen(callback))
    370     throw MakeTypeError("observe_callback_frozen");
    371 
    372   var objectObserveFn = %GetObjectContextObjectObserve(object);
    373   return objectObserveFn(object, callback, acceptList);
    374 }
    375 
    376 function NativeObjectObserve(object, callback, acceptList) {
    377   var objectInfo = ObjectInfoGetOrCreate(object);
    378   var typeList = ConvertAcceptListToTypeMap(acceptList);
    379   ObjectInfoAddObserver(objectInfo, callback, typeList);
    380   return object;
    381 }
    382 
    383 function ObjectUnobserve(object, callback) {
    384   if (!IS_SPEC_OBJECT(object))
    385     throw MakeTypeError("observe_non_object", ["unobserve"]);
    386   if (%IsJSGlobalProxy(object))
    387     throw MakeTypeError("observe_global_proxy", ["unobserve"]);
    388   if (!IS_SPEC_FUNCTION(callback))
    389     throw MakeTypeError("observe_non_function", ["unobserve"]);
    390 
    391   var objectInfo = ObjectInfoGet(object);
    392   if (IS_UNDEFINED(objectInfo))
    393     return object;
    394 
    395   ObjectInfoRemoveObserver(objectInfo, callback);
    396   return object;
    397 }
    398 
    399 function ArrayObserve(object, callback) {
    400   return ObjectObserve(object, callback, ['add',
    401                                           'update',
    402                                           'delete',
    403                                           'splice']);
    404 }
    405 
    406 function ArrayUnobserve(object, callback) {
    407   return ObjectUnobserve(object, callback);
    408 }
    409 
    410 function ObserverEnqueueIfActive(observer, objectInfo, changeRecord) {
    411   if (!ObserverIsActive(observer, objectInfo) ||
    412       !TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) {
    413     return;
    414   }
    415 
    416   var callback = ObserverGetCallback(observer);
    417   if (!%ObserverObjectAndRecordHaveSameOrigin(callback, changeRecord.object,
    418                                               changeRecord)) {
    419     return;
    420   }
    421 
    422   var callbackInfo = CallbackInfoNormalize(callback);
    423   if (IS_NULL(GetPendingObservers())) {
    424     SetPendingObservers(nullProtoObject());
    425     if (DEBUG_IS_ACTIVE) {
    426       var id = ++GetObservationStateJS().lastMicrotaskId;
    427       var name = "Object.observe";
    428       %EnqueueMicrotask(function() {
    429         %DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
    430         ObserveMicrotaskRunner();
    431         %DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
    432       });
    433       %DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
    434     } else {
    435       %EnqueueMicrotask(ObserveMicrotaskRunner);
    436     }
    437   }
    438   GetPendingObservers()[callbackInfo.priority] = callback;
    439   callbackInfo.push(changeRecord);
    440 }
    441 
    442 function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) {
    443   if (!ObjectInfoHasActiveObservers(objectInfo))
    444     return;
    445 
    446   var hasType = !IS_UNDEFINED(type);
    447   var newRecord = hasType ?
    448       { object: ObjectInfoGetObject(objectInfo), type: type } :
    449       { object: ObjectInfoGetObject(objectInfo) };
    450 
    451   for (var prop in changeRecord) {
    452     if (prop === 'object' || (hasType && prop === 'type')) continue;
    453     %DefineDataPropertyUnchecked(
    454         newRecord, prop, changeRecord[prop], READ_ONLY + DONT_DELETE);
    455   }
    456   ObjectFreezeJS(newRecord);
    457 
    458   ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord);
    459 }
    460 
    461 function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord) {
    462   // TODO(rossberg): adjust once there is a story for symbols vs proxies.
    463   if (IS_SYMBOL(changeRecord.name)) return;
    464 
    465   if (ChangeObserversIsOptimized(objectInfo.changeObservers)) {
    466     var observer = objectInfo.changeObservers;
    467     ObserverEnqueueIfActive(observer, objectInfo, changeRecord);
    468     return;
    469   }
    470 
    471   for (var priority in objectInfo.changeObservers) {
    472     var observer = objectInfo.changeObservers[priority];
    473     if (IS_NULL(observer))
    474       continue;
    475     ObserverEnqueueIfActive(observer, objectInfo, changeRecord);
    476   }
    477 }
    478 
    479 function BeginPerformSplice(array) {
    480   var objectInfo = ObjectInfoGet(array);
    481   if (!IS_UNDEFINED(objectInfo))
    482     ObjectInfoAddPerformingType(objectInfo, 'splice');
    483 }
    484 
    485 function EndPerformSplice(array) {
    486   var objectInfo = ObjectInfoGet(array);
    487   if (!IS_UNDEFINED(objectInfo))
    488     ObjectInfoRemovePerformingType(objectInfo, 'splice');
    489 }
    490 
    491 function EnqueueSpliceRecord(array, index, removed, addedCount) {
    492   var objectInfo = ObjectInfoGet(array);
    493   if (!ObjectInfoHasActiveObservers(objectInfo))
    494     return;
    495 
    496   var changeRecord = {
    497     type: 'splice',
    498     object: array,
    499     index: index,
    500     removed: removed,
    501     addedCount: addedCount
    502   };
    503 
    504   ObjectFreezeJS(changeRecord);
    505   ObjectFreezeJS(changeRecord.removed);
    506   ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord);
    507 }
    508 
    509 function NotifyChange(type, object, name, oldValue) {
    510   var objectInfo = ObjectInfoGet(object);
    511   if (!ObjectInfoHasActiveObservers(objectInfo))
    512     return;
    513 
    514   var changeRecord;
    515   if (arguments.length == 2) {
    516     changeRecord = { type: type, object: object };
    517   } else if (arguments.length == 3) {
    518     changeRecord = { type: type, object: object, name: name };
    519   } else {
    520     changeRecord = {
    521       type: type,
    522       object: object,
    523       name: name,
    524       oldValue: oldValue
    525     };
    526   }
    527 
    528   ObjectFreezeJS(changeRecord);
    529   ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord);
    530 }
    531 
    532 var notifierPrototype = {};
    533 
    534 function ObjectNotifierNotify(changeRecord) {
    535   if (!IS_SPEC_OBJECT(this))
    536     throw MakeTypeError("called_on_non_object", ["notify"]);
    537 
    538   var objectInfo = ObjectInfoGetFromNotifier(this);
    539   if (IS_UNDEFINED(objectInfo))
    540     throw MakeTypeError("observe_notify_non_notifier");
    541   if (!IS_STRING(changeRecord.type))
    542     throw MakeTypeError("observe_type_non_string");
    543 
    544   ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord);
    545 }
    546 
    547 function ObjectNotifierPerformChange(changeType, changeFn) {
    548   if (!IS_SPEC_OBJECT(this))
    549     throw MakeTypeError("called_on_non_object", ["performChange"]);
    550 
    551   var objectInfo = ObjectInfoGetFromNotifier(this);
    552   if (IS_UNDEFINED(objectInfo))
    553     throw MakeTypeError("observe_notify_non_notifier");
    554   if (!IS_STRING(changeType))
    555     throw MakeTypeError("observe_perform_non_string");
    556   if (!IS_SPEC_FUNCTION(changeFn))
    557     throw MakeTypeError("observe_perform_non_function");
    558 
    559   var performChangeFn = %GetObjectContextNotifierPerformChange(objectInfo);
    560   performChangeFn(objectInfo, changeType, changeFn);
    561 }
    562 
    563 function NativeObjectNotifierPerformChange(objectInfo, changeType, changeFn) {
    564   ObjectInfoAddPerformingType(objectInfo, changeType);
    565 
    566   var changeRecord;
    567   try {
    568     changeRecord = %_CallFunction(UNDEFINED, changeFn);
    569   } finally {
    570     ObjectInfoRemovePerformingType(objectInfo, changeType);
    571   }
    572 
    573   if (IS_SPEC_OBJECT(changeRecord))
    574     ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, changeType);
    575 }
    576 
    577 function ObjectGetNotifier(object) {
    578   if (!IS_SPEC_OBJECT(object))
    579     throw MakeTypeError("observe_non_object", ["getNotifier"]);
    580   if (%IsJSGlobalProxy(object))
    581     throw MakeTypeError("observe_global_proxy", ["getNotifier"]);
    582 
    583   if (ObjectIsFrozen(object)) return null;
    584 
    585   if (!%ObjectWasCreatedInCurrentOrigin(object)) return null;
    586 
    587   var getNotifierFn = %GetObjectContextObjectGetNotifier(object);
    588   return getNotifierFn(object);
    589 }
    590 
    591 function NativeObjectGetNotifier(object) {
    592   var objectInfo = ObjectInfoGetOrCreate(object);
    593   return ObjectInfoGetNotifier(objectInfo);
    594 }
    595 
    596 function CallbackDeliverPending(callback) {
    597   var callbackInfo = GetCallbackInfoMap().get(callback);
    598   if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo))
    599     return false;
    600 
    601   // Clear the pending change records from callback and return it to its
    602   // "optimized" state.
    603   var priority = callbackInfo.priority;
    604   GetCallbackInfoMap().set(callback, priority);
    605 
    606   if (GetPendingObservers())
    607     delete GetPendingObservers()[priority];
    608 
    609   var delivered = [];
    610   %MoveArrayContents(callbackInfo, delivered);
    611 
    612   try {
    613     %_CallFunction(UNDEFINED, delivered, callback);
    614   } catch (ex) {}  // TODO(rossberg): perhaps log uncaught exceptions.
    615   return true;
    616 }
    617 
    618 function ObjectDeliverChangeRecords(callback) {
    619   if (!IS_SPEC_FUNCTION(callback))
    620     throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]);
    621 
    622   while (CallbackDeliverPending(callback)) {}
    623 }
    624 
    625 function ObserveMicrotaskRunner() {
    626   var pendingObservers = GetPendingObservers();
    627   if (pendingObservers) {
    628     SetPendingObservers(null);
    629     for (var i in pendingObservers) {
    630       CallbackDeliverPending(pendingObservers[i]);
    631     }
    632   }
    633 }
    634 
    635 function SetupObjectObserve() {
    636   %CheckIsBootstrapping();
    637   InstallFunctions($Object, DONT_ENUM, $Array(
    638     "deliverChangeRecords", ObjectDeliverChangeRecords,
    639     "getNotifier", ObjectGetNotifier,
    640     "observe", ObjectObserve,
    641     "unobserve", ObjectUnobserve
    642   ));
    643   InstallFunctions($Array, DONT_ENUM, $Array(
    644     "observe", ArrayObserve,
    645     "unobserve", ArrayUnobserve
    646   ));
    647   InstallFunctions(notifierPrototype, DONT_ENUM, $Array(
    648     "notify", ObjectNotifierNotify,
    649     "performChange", ObjectNotifierPerformChange
    650   ));
    651 }
    652 
    653 SetupObjectObserve();
    654