diff --git a/src/array.js b/src/array.js index b2e26e0305..b69554c214 100644 --- a/src/array.js +++ b/src/array.js @@ -407,12 +407,12 @@ function ObservedArrayPop(n) { var value = this[n]; try { - BeginPerformSplice(this); + $observeBeginPerformSplice(this); delete this[n]; this.length = n; } finally { - EndPerformSplice(this); - EnqueueSpliceRecord(this, n, [value], 0); + $observeEndPerformSplice(this); + $observeEnqueueSpliceRecord(this, n, [value], 0); } return value; @@ -446,15 +446,15 @@ function ObservedArrayPush() { var m = %_ArgumentsLength(); try { - BeginPerformSplice(this); + $observeBeginPerformSplice(this); for (var i = 0; i < m; i++) { this[i+n] = %_Arguments(i); } var new_length = n + m; this.length = new_length; } finally { - EndPerformSplice(this); - EnqueueSpliceRecord(this, n, [], m); + $observeEndPerformSplice(this); + $observeEnqueueSpliceRecord(this, n, [], m); } return new_length; @@ -584,12 +584,12 @@ function ObservedArrayShift(len) { var first = this[0]; try { - BeginPerformSplice(this); + $observeBeginPerformSplice(this); SimpleMove(this, 0, 1, len, 0); this.length = len - 1; } finally { - EndPerformSplice(this); - EnqueueSpliceRecord(this, 0, [first], 0); + $observeEndPerformSplice(this); + $observeEnqueueSpliceRecord(this, 0, [first], 0); } return first; @@ -632,7 +632,7 @@ function ObservedArrayUnshift() { var num_arguments = %_ArgumentsLength(); try { - BeginPerformSplice(this); + $observeBeginPerformSplice(this); SimpleMove(this, 0, 0, len, num_arguments); for (var i = 0; i < num_arguments; i++) { this[i] = %_Arguments(i); @@ -640,8 +640,8 @@ function ObservedArrayUnshift() { var new_length = len + num_arguments; this.length = new_length; } finally { - EndPerformSplice(this); - EnqueueSpliceRecord(this, 0, [], num_arguments); + $observeEndPerformSplice(this); + $observeEnqueueSpliceRecord(this, 0, [], num_arguments); } return new_length; @@ -758,7 +758,7 @@ function ObservedArraySplice(start, delete_count) { var num_elements_to_add = num_arguments > 2 ? num_arguments - 2 : 0; try { - BeginPerformSplice(this); + $observeBeginPerformSplice(this); SimpleSlice(this, start_i, del_count, len, deleted_elements); SimpleMove(this, start_i, del_count, len, num_elements_to_add); @@ -774,12 +774,12 @@ function ObservedArraySplice(start, delete_count) { this.length = len - del_count + num_elements_to_add; } finally { - EndPerformSplice(this); + $observeEndPerformSplice(this); if (deleted_elements.length || num_elements_to_add) { - EnqueueSpliceRecord(this, - start_i, - deleted_elements.slice(), - num_elements_to_add); + $observeEnqueueSpliceRecord(this, + start_i, + deleted_elements.slice(), + num_elements_to_add); } } diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc index 61017bfa2e..2246f6e1a7 100644 --- a/src/bootstrapper.cc +++ b/src/bootstrapper.cc @@ -1565,17 +1565,18 @@ void Genesis::InstallNativeFunctions() { INSTALL_NATIVE(JSFunction, "PromiseCatch", promise_catch); INSTALL_NATIVE(JSFunction, "PromiseThen", promise_then); - INSTALL_NATIVE(JSFunction, "NotifyChange", observers_notify_change); - INSTALL_NATIVE(JSFunction, "EnqueueSpliceRecord", observers_enqueue_splice); - INSTALL_NATIVE(JSFunction, "BeginPerformSplice", + INSTALL_NATIVE(JSFunction, "$observeNotifyChange", observers_notify_change); + INSTALL_NATIVE(JSFunction, "$observeEnqueueSpliceRecord", + observers_enqueue_splice); + INSTALL_NATIVE(JSFunction, "$observeBeginPerformSplice", observers_begin_perform_splice); - INSTALL_NATIVE(JSFunction, "EndPerformSplice", + INSTALL_NATIVE(JSFunction, "$observeEndPerformSplice", observers_end_perform_splice); - INSTALL_NATIVE(JSFunction, "NativeObjectObserve", + INSTALL_NATIVE(JSFunction, "$observeNativeObjectObserve", native_object_observe); - INSTALL_NATIVE(JSFunction, "NativeObjectGetNotifier", + INSTALL_NATIVE(JSFunction, "$observeNativeObjectGetNotifier", native_object_get_notifier); - INSTALL_NATIVE(JSFunction, "NativeObjectNotifierPerformChange", + INSTALL_NATIVE(JSFunction, "$observeNativeObjectNotifierPerformChange", native_object_notifier_perform_change); INSTALL_NATIVE(JSFunction, "$arrayValues", array_values_iterator); } diff --git a/src/object-observe.js b/src/object-observe.js index 1d23085fd3..5e41c1ef51 100644 --- a/src/object-observe.js +++ b/src/object-observe.js @@ -2,8 +2,25 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +var $observeNotifyChange; +var $observeEnqueueSpliceRecord; +var $observeBeginPerformSplice; +var $observeEndPerformSplice; +var $observeNativeObjectObserve; +var $observeNativeObjectGetNotifier; +var $observeNativeObjectNotifierPerformChange; + +(function() { + "use strict"; +%CheckIsBootstrapping(); + +var GlobalArray = global.Array; +var GlobalObject = global.Object; + +// ------------------------------------------------------------------- + // Overview: // // This file contains all of the routing and accounting for Object.observe. @@ -35,6 +52,8 @@ var observationState; +var notifierPrototype = {}; + // We have to wait until after bootstrapping to grab a reference to the // observationState object, since it's not possible to serialize that // reference into the snapshot. @@ -56,34 +75,42 @@ function GetObservationStateJS() { return observationState; } + function GetPendingObservers() { return GetObservationStateJS().pendingObservers; } + function SetPendingObservers(pendingObservers) { GetObservationStateJS().pendingObservers = pendingObservers; } + function GetNextCallbackPriority() { return GetObservationStateJS().nextCallbackPriority++; } + function nullProtoObject() { return { __proto__: null }; } + function TypeMapCreate() { return nullProtoObject(); } + function TypeMapAddType(typeMap, type, ignoreDuplicate) { typeMap[type] = ignoreDuplicate ? 1 : (typeMap[type] || 0) + 1; } + function TypeMapRemoveType(typeMap, type) { typeMap[type]--; } + function TypeMapCreateFromList(typeList, length) { var typeMap = TypeMapCreate(); for (var i = 0; i < length; i++) { @@ -92,10 +119,12 @@ function TypeMapCreateFromList(typeList, length) { return typeMap; } + function TypeMapHasType(typeMap, type) { return !!typeMap[type]; } + function TypeMapIsDisjointFrom(typeMap1, typeMap2) { if (!typeMap1 || !typeMap2) return true; @@ -108,6 +137,7 @@ function TypeMapIsDisjointFrom(typeMap1, typeMap2) { return true; } + var defaultAcceptTypes = (function() { var defaultTypes = [ 'add', @@ -120,6 +150,7 @@ var defaultAcceptTypes = (function() { return TypeMapCreateFromList(defaultTypes, defaultTypes.length); })(); + // An Observer is a registration to observe an object by a callback with // a given set of accept types. If the set of accept types is the default // set for Object.observe, the observer is represented as a direct reference @@ -134,19 +165,23 @@ function ObserverCreate(callback, acceptList) { return observer; } + function ObserverGetCallback(observer) { return IS_SPEC_FUNCTION(observer) ? observer : observer.callback; } + function ObserverGetAcceptTypes(observer) { return IS_SPEC_FUNCTION(observer) ? defaultAcceptTypes : observer.accept; } + function ObserverIsActive(observer, objectInfo) { return TypeMapIsDisjointFrom(ObjectInfoGetPerformingTypes(objectInfo), ObserverGetAcceptTypes(observer)); } + function ObjectInfoGetOrCreate(object) { var objectInfo = ObjectInfoGet(object); if (IS_UNDEFINED(objectInfo)) { @@ -166,15 +201,18 @@ function ObjectInfoGetOrCreate(object) { return objectInfo; } + function ObjectInfoGet(object) { return %WeakCollectionGet(GetObservationStateJS().objectInfoMap, object); } + function ObjectInfoGetFromNotifier(notifier) { return %WeakCollectionGet(GetObservationStateJS().notifierObjectInfoMap, notifier); } + function ObjectInfoGetNotifier(objectInfo) { if (IS_NULL(objectInfo.notifier)) { objectInfo.notifier = { __proto__: notifierPrototype }; @@ -185,11 +223,13 @@ function ObjectInfoGetNotifier(objectInfo) { return objectInfo.notifier; } + function ChangeObserversIsOptimized(changeObservers) { return IS_SPEC_FUNCTION(changeObservers) || IS_SPEC_FUNCTION(changeObservers.callback); } + // The set of observers on an object is called 'changeObservers'. The first // observer is referenced directly via objectInfo.changeObservers. When a second // is added, changeObservers "normalizes" to become a mapping of callback @@ -205,6 +245,7 @@ function ObjectInfoNormalizeChangeObservers(objectInfo) { } } + function ObjectInfoAddObserver(objectInfo, callback, acceptList) { var callbackInfo = CallbackInfoGetOrCreate(callback); var observer = ObserverCreate(callback, acceptList); @@ -250,21 +291,25 @@ function ObjectInfoHasActiveObservers(objectInfo) { return false; } + function ObjectInfoAddPerformingType(objectInfo, type) { objectInfo.performing = objectInfo.performing || TypeMapCreate(); TypeMapAddType(objectInfo.performing, type); objectInfo.performingCount++; } + function ObjectInfoRemovePerformingType(objectInfo, type) { objectInfo.performingCount--; TypeMapRemoveType(objectInfo.performing, type); } + function ObjectInfoGetPerformingTypes(objectInfo) { return objectInfo.performingCount > 0 ? objectInfo.performing : null; } + function ConvertAcceptListToTypeMap(arg) { // We use undefined as a sentinel for the default accept list. if (IS_UNDEFINED(arg)) @@ -279,6 +324,7 @@ function ConvertAcceptListToTypeMap(arg) { return TypeMapCreateFromList(arg, len); } + // CallbackInfo's optimized state is just a number which represents its global // priority. When a change record must be enqueued for the callback, it // normalizes. When delivery clears any pending change records, it re-optimizes. @@ -286,11 +332,13 @@ function CallbackInfoGet(callback) { return %WeakCollectionGet(GetObservationStateJS().callbackInfoMap, callback); } + function CallbackInfoSet(callback, callbackInfo) { %WeakCollectionSet(GetObservationStateJS().callbackInfoMap, callback, callbackInfo); } + function CallbackInfoGetOrCreate(callback) { var callbackInfo = CallbackInfoGet(callback); if (!IS_UNDEFINED(callbackInfo)) @@ -301,6 +349,7 @@ function CallbackInfoGetOrCreate(callback) { return priority; } + function CallbackInfoGetPriority(callbackInfo) { if (IS_NUMBER(callbackInfo)) return callbackInfo; @@ -308,6 +357,7 @@ function CallbackInfoGetPriority(callbackInfo) { return callbackInfo.priority; } + function CallbackInfoNormalize(callback) { var callbackInfo = CallbackInfoGet(callback); if (IS_NUMBER(callbackInfo)) { @@ -319,6 +369,7 @@ function CallbackInfoNormalize(callback) { return callbackInfo; } + function ObjectObserve(object, callback, acceptList) { if (!IS_SPEC_OBJECT(object)) throw MakeTypeError("observe_non_object", ["observe"]); @@ -333,6 +384,7 @@ function ObjectObserve(object, callback, acceptList) { return objectObserveFn(object, callback, acceptList); } + function NativeObjectObserve(object, callback, acceptList) { var objectInfo = ObjectInfoGetOrCreate(object); var typeList = ConvertAcceptListToTypeMap(acceptList); @@ -340,6 +392,7 @@ function NativeObjectObserve(object, callback, acceptList) { return object; } + function ObjectUnobserve(object, callback) { if (!IS_SPEC_OBJECT(object)) throw MakeTypeError("observe_non_object", ["unobserve"]); @@ -356,6 +409,7 @@ function ObjectUnobserve(object, callback) { return object; } + function ArrayObserve(object, callback) { return ObjectObserve(object, callback, ['add', 'update', @@ -363,10 +417,12 @@ function ArrayObserve(object, callback) { 'splice']); } + function ArrayUnobserve(object, callback) { return ObjectUnobserve(object, callback); } + function ObserverEnqueueIfActive(observer, objectInfo, changeRecord) { if (!ObserverIsActive(observer, objectInfo) || !TypeMapHasType(ObserverGetAcceptTypes(observer), changeRecord.type)) { @@ -399,6 +455,7 @@ function ObserverEnqueueIfActive(observer, objectInfo, changeRecord) { callbackInfo.push(changeRecord); } + function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) { if (!ObjectInfoHasActiveObservers(objectInfo)) return; @@ -418,6 +475,7 @@ function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) { ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord); } + function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord) { // TODO(rossberg): adjust once there is a story for symbols vs proxies. if (IS_SYMBOL(changeRecord.name)) return; @@ -436,18 +494,21 @@ function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord) { } } + function BeginPerformSplice(array) { var objectInfo = ObjectInfoGet(array); if (!IS_UNDEFINED(objectInfo)) ObjectInfoAddPerformingType(objectInfo, 'splice'); } + function EndPerformSplice(array) { var objectInfo = ObjectInfoGet(array); if (!IS_UNDEFINED(objectInfo)) ObjectInfoRemovePerformingType(objectInfo, 'splice'); } + function EnqueueSpliceRecord(array, index, removed, addedCount) { var objectInfo = ObjectInfoGet(array); if (!ObjectInfoHasActiveObservers(objectInfo)) @@ -466,6 +527,7 @@ function EnqueueSpliceRecord(array, index, removed, addedCount) { ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord); } + function NotifyChange(type, object, name, oldValue) { var objectInfo = ObjectInfoGet(object); if (!ObjectInfoHasActiveObservers(objectInfo)) @@ -489,7 +551,6 @@ function NotifyChange(type, object, name, oldValue) { ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord); } -var notifierPrototype = {}; function ObjectNotifierNotify(changeRecord) { if (!IS_SPEC_OBJECT(this)) @@ -504,6 +565,7 @@ function ObjectNotifierNotify(changeRecord) { ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord); } + function ObjectNotifierPerformChange(changeType, changeFn) { if (!IS_SPEC_OBJECT(this)) throw MakeTypeError("called_on_non_object", ["performChange"]); @@ -520,6 +582,7 @@ function ObjectNotifierPerformChange(changeType, changeFn) { performChangeFn(objectInfo, changeType, changeFn); } + function NativeObjectNotifierPerformChange(objectInfo, changeType, changeFn) { ObjectInfoAddPerformingType(objectInfo, changeType); @@ -534,6 +597,7 @@ function NativeObjectNotifierPerformChange(objectInfo, changeType, changeFn) { ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, changeType); } + function ObjectGetNotifier(object) { if (!IS_SPEC_OBJECT(object)) throw MakeTypeError("observe_non_object", ["getNotifier"]); @@ -548,11 +612,13 @@ function ObjectGetNotifier(object) { return getNotifierFn(object); } + function NativeObjectGetNotifier(object) { var objectInfo = ObjectInfoGetOrCreate(object); return ObjectInfoGetNotifier(objectInfo); } + function CallbackDeliverPending(callback) { var callbackInfo = CallbackInfoGet(callback); if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo)) @@ -575,6 +641,7 @@ function CallbackDeliverPending(callback) { return true; } + function ObjectDeliverChangeRecords(callback) { if (!IS_SPEC_FUNCTION(callback)) throw MakeTypeError("observe_non_function", ["deliverChangeRecords"]); @@ -582,6 +649,7 @@ function ObjectDeliverChangeRecords(callback) { while (CallbackDeliverPending(callback)) {} } + function ObserveMicrotaskRunner() { var pendingObservers = GetPendingObservers(); if (!IS_NULL(pendingObservers)) { @@ -592,22 +660,29 @@ function ObserveMicrotaskRunner() { } } -function SetupObjectObserve() { - %CheckIsBootstrapping(); - InstallFunctions($Object, DONT_ENUM, [ - "deliverChangeRecords", ObjectDeliverChangeRecords, - "getNotifier", ObjectGetNotifier, - "observe", ObjectObserve, - "unobserve", ObjectUnobserve - ]); - InstallFunctions($Array, DONT_ENUM, [ - "observe", ArrayObserve, - "unobserve", ArrayUnobserve - ]); - InstallFunctions(notifierPrototype, DONT_ENUM, [ - "notify", ObjectNotifierNotify, - "performChange", ObjectNotifierPerformChange - ]); -} +// ------------------------------------------------------------------- -SetupObjectObserve(); +InstallFunctions(GlobalObject, DONT_ENUM, [ + "deliverChangeRecords", ObjectDeliverChangeRecords, + "getNotifier", ObjectGetNotifier, + "observe", ObjectObserve, + "unobserve", ObjectUnobserve +]); +InstallFunctions(GlobalArray, DONT_ENUM, [ + "observe", ArrayObserve, + "unobserve", ArrayUnobserve +]); +InstallFunctions(notifierPrototype, DONT_ENUM, [ + "notify", ObjectNotifierNotify, + "performChange", ObjectNotifierPerformChange +]); + +$observeNotifyChange = NotifyChange; +$observeEnqueueSpliceRecord = EnqueueSpliceRecord; +$observeBeginPerformSplice = BeginPerformSplice; +$observeEndPerformSplice = EndPerformSplice; +$observeNativeObjectObserve = NativeObjectObserve; +$observeNativeObjectGetNotifier = NativeObjectGetNotifier; +$observeNativeObjectNotifierPerformChange = NativeObjectNotifierPerformChange; + +})(); diff --git a/src/v8natives.js b/src/v8natives.js index d050a9e2c4..8c9732ea2f 100644 --- a/src/v8natives.js +++ b/src/v8natives.js @@ -909,7 +909,7 @@ function DefineArrayProperty(obj, p, desc, should_throw) { var emit_splice = %IsObserved(obj) && new_length !== old_length; var removed; if (emit_splice) { - BeginPerformSplice(obj); + $observeBeginPerformSplice(obj); removed = []; if (new_length < old_length) removed.length = old_length - new_length; @@ -930,8 +930,8 @@ function DefineArrayProperty(obj, p, desc, should_throw) { } threw = !DefineObjectProperty(obj, "length", desc, should_throw) || threw; if (emit_splice) { - EndPerformSplice(obj); - EnqueueSpliceRecord(obj, + $observeEndPerformSplice(obj); + $observeEnqueueSpliceRecord(obj, new_length < old_length ? new_length : old_length, removed, new_length > old_length ? new_length - old_length : 0); @@ -954,14 +954,14 @@ function DefineArrayProperty(obj, p, desc, should_throw) { var length = obj.length; if (index >= length && %IsObserved(obj)) { emit_splice = true; - BeginPerformSplice(obj); + $observeBeginPerformSplice(obj); } var length_desc = GetOwnPropertyJS(obj, "length"); if ((index >= length && !length_desc.isWritable()) || !DefineObjectProperty(obj, p, desc, true)) { if (emit_splice) - EndPerformSplice(obj); + $observeEndPerformSplice(obj); if (should_throw) { throw MakeTypeError("define_disallowed", [p]); } else { @@ -972,8 +972,8 @@ function DefineArrayProperty(obj, p, desc, should_throw) { obj.length = index + 1; } if (emit_splice) { - EndPerformSplice(obj); - EnqueueSpliceRecord(obj, length, [], index + 1 - length); + $observeEndPerformSplice(obj); + $observeEnqueueSpliceRecord(obj, length, [], index + 1 - length); } return true; }