Index: src/object-observe.js |
diff --git a/src/object-observe.js b/src/object-observe.js |
index 1d23085fd362af21d0ea181016c776b75a47cdec..5e41c1ef511917afb42e0001e58e676ef7a0b223 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; |
+ |
+})(); |