// implementation of (1) and (2) have "optimized" states which represent
// common cases which can be handled more efficiently.
-var observationState = %GetObservationState();
-if (IS_UNDEFINED(observationState.callbackInfoMap)) {
- observationState.callbackInfoMap = %ObservationWeakMapCreate();
- observationState.objectInfoMap = %ObservationWeakMapCreate();
- observationState.notifierObjectInfoMap = %ObservationWeakMapCreate();
- observationState.pendingObservers = null;
- observationState.nextCallbackPriority = 0;
-}
-
-function ObservationWeakMap(map) {
- this.map_ = map;
-}
-
-ObservationWeakMap.prototype = {
- get: function(key) {
- key = %UnwrapGlobalProxy(key);
- if (!IS_SPEC_OBJECT(key)) return UNDEFINED;
- return %WeakCollectionGet(this.map_, key);
- },
- set: function(key, value) {
- key = %UnwrapGlobalProxy(key);
- if (!IS_SPEC_OBJECT(key)) return UNDEFINED;
- %WeakCollectionSet(this.map_, key, value);
- },
- has: function(key) {
- return !IS_UNDEFINED(this.get(key));
+var observationState;
+
+function GetObservationState() {
+ if (IS_UNDEFINED(observationState))
+ observationState = %GetObservationState();
+
+ if (IS_UNDEFINED(observationState.callbackInfoMap)) {
+ observationState.callbackInfoMap = %ObservationWeakMapCreate();
+ observationState.objectInfoMap = %ObservationWeakMapCreate();
+ observationState.notifierObjectInfoMap = %ObservationWeakMapCreate();
+ observationState.pendingObservers = null;
+ observationState.nextCallbackPriority = 0;
}
-};
-var callbackInfoMap =
- new ObservationWeakMap(observationState.callbackInfoMap);
-var objectInfoMap = new ObservationWeakMap(observationState.objectInfoMap);
-var notifierObjectInfoMap =
- new ObservationWeakMap(observationState.notifierObjectInfoMap);
+ return observationState;
+}
-function TypeMapCreate() {
+function GetWeakMapWrapper() {
+ function MapWrapper(map) {
+ this.map_ = map;
+ };
+
+ MapWrapper.prototype = {
+ get: function(key) {
+ key = %UnwrapGlobalProxy(key);
+ if (!IS_SPEC_OBJECT(key)) return UNDEFINED;
+ return %WeakCollectionGet(this.map_, key);
+ },
+ set: function(key, value) {
+ key = %UnwrapGlobalProxy(key);
+ if (!IS_SPEC_OBJECT(key)) return UNDEFINED;
+ %WeakCollectionSet(this.map_, key, value);
+ },
+ has: function(key) {
+ return !IS_UNDEFINED(this.get(key));
+ }
+ };
+
+ return MapWrapper;
+}
+
+var contextMaps;
+
+function GetContextMaps() {
+ if (IS_UNDEFINED(contextMaps)) {
+ var map = GetWeakMapWrapper();
+ var observationState = GetObservationState();
+ contextMaps = {
+ callbackInfoMap: new map(observationState.callbackInfoMap),
+ objectInfoMap: new map(observationState.objectInfoMap),
+ notifierObjectInfoMap: new map(observationState.notifierObjectInfoMap)
+ };
+ }
+
+ return contextMaps;
+}
+
+function GetCallbackInfoMap() {
+ return GetContextMaps().callbackInfoMap;
+}
+
+function GetObjectInfoMap() {
+ return GetContextMaps().objectInfoMap;
+}
+
+function GetNotifierObjectInfoMap() {
+ return GetContextMaps().notifierObjectInfoMap;
+}
+
+function GetPendingObservers() {
+ return GetObservationState().pendingObservers;
+}
+
+function SetPendingObservers(pendingObservers) {
+ GetObservationState().pendingObservers = pendingObservers;
+}
+
+function GetNextCallbackPriority() {
+ return GetObservationState().nextCallbackPriority++;
+}
+
+function nullProtoObject() {
return { __proto__: null };
}
+function TypeMapCreate() {
+ return nullProtoObject();
+}
+
function TypeMapAddType(typeMap, type, ignoreDuplicate) {
typeMap[type] = ignoreDuplicate ? 1 : (typeMap[type] || 0) + 1;
}
}
var defaultAcceptTypes = TypeMapCreateFromList([
- 'new',
- 'updated',
- 'deleted',
- 'prototype',
- 'reconfigured'
+ 'add',
+ 'update',
+ 'delete',
+ 'setPrototype',
+ 'reconfigure',
+ 'preventExtensions'
]);
// An Observer is a registration to observe an object by a callback with
// to the callback. An observer never changes its accept types and thus never
// needs to "normalize".
function ObserverCreate(callback, acceptList) {
- return IS_UNDEFINED(acceptList) ? callback : {
- __proto__: null,
- callback: callback,
- accept: TypeMapCreateFromList(acceptList)
- };
+ if (IS_UNDEFINED(acceptList))
+ return callback;
+ var observer = nullProtoObject();
+ observer.callback = callback;
+ observer.accept = TypeMapCreateFromList(acceptList);
+ return observer;
}
function ObserverGetCallback(observer) {
ObserverGetAcceptTypes(observer));
}
-function ObjectInfoGet(object) {
- var objectInfo = objectInfoMap.get(object);
+function ObjectInfoGetOrCreate(object) {
+ var objectInfo = ObjectInfoGet(object);
if (IS_UNDEFINED(objectInfo)) {
if (!%IsJSProxy(object))
%SetIsObserved(object);
performing: null,
performingCount: 0,
};
- objectInfoMap.set(object, objectInfo);
+ GetObjectInfoMap().set(object, objectInfo);
}
return objectInfo;
}
+function ObjectInfoGet(object) {
+ return GetObjectInfoMap().get(object);
+}
+
function ObjectInfoGetFromNotifier(notifier) {
- return notifierObjectInfoMap.get(notifier);
+ return GetNotifierObjectInfoMap().get(notifier);
}
function ObjectInfoGetNotifier(objectInfo) {
if (IS_NULL(objectInfo.notifier)) {
objectInfo.notifier = { __proto__: notifierPrototype };
- notifierObjectInfoMap.set(objectInfo.notifier, objectInfo);
+ GetNotifierObjectInfoMap().set(objectInfo.notifier, objectInfo);
}
return objectInfo.notifier;
var callback = ObserverGetCallback(observer);
var callbackInfo = CallbackInfoGet(callback);
var priority = CallbackInfoGetPriority(callbackInfo);
- objectInfo.changeObservers = { __proto__: null };
+ objectInfo.changeObservers = nullProtoObject();
objectInfo.changeObservers[priority] = observer;
}
}
var callbackInfo = CallbackInfoGet(callback);
var priority = CallbackInfoGetPriority(callbackInfo);
- delete objectInfo.changeObservers[priority];
+ objectInfo.changeObservers[priority] = null;
}
function ObjectInfoHasActiveObservers(objectInfo) {
return ObserverIsActive(objectInfo.changeObservers, objectInfo);
for (var priority in objectInfo.changeObservers) {
- if (ObserverIsActive(objectInfo.changeObservers[priority], objectInfo))
+ var observer = objectInfo.changeObservers[priority];
+ if (!IS_NULL(observer) && ObserverIsActive(observer, objectInfo))
return true;
}
// priority. When a change record must be enqueued for the callback, it
// normalizes. When delivery clears any pending change records, it re-optimizes.
function CallbackInfoGet(callback) {
- return callbackInfoMap.get(callback);
+ return GetCallbackInfoMap().get(callback);
}
function CallbackInfoGetOrCreate(callback) {
- var callbackInfo = callbackInfoMap.get(callback);
+ var callbackInfo = GetCallbackInfoMap().get(callback);
if (!IS_UNDEFINED(callbackInfo))
return callbackInfo;
- var priority = observationState.nextCallbackPriority++
- callbackInfoMap.set(callback, priority);
+ var priority = GetNextCallbackPriority();
+ GetCallbackInfoMap().set(callback, priority);
return priority;
}
}
function CallbackInfoNormalize(callback) {
- var callbackInfo = callbackInfoMap.get(callback);
+ var callbackInfo = GetCallbackInfoMap().get(callback);
if (IS_NUMBER(callbackInfo)) {
var priority = callbackInfo;
callbackInfo = new InternalArray;
callbackInfo.priority = priority;
- callbackInfoMap.set(callback, callbackInfo);
+ GetCallbackInfoMap().set(callback, callbackInfo);
}
return callbackInfo;
}
if (!AcceptArgIsValid(acceptList))
throw MakeTypeError("observe_accept_invalid");
- var objectInfo = ObjectInfoGet(object);
+ var objectInfo = ObjectInfoGetOrCreate(object);
ObjectInfoAddObserver(objectInfo, callback, acceptList);
return object;
}
if (!IS_SPEC_FUNCTION(callback))
throw MakeTypeError("observe_non_function", ["unobserve"]);
- var objectInfo = objectInfoMap.get(object);
+ var objectInfo = ObjectInfoGet(object);
if (IS_UNDEFINED(objectInfo))
return object;
}
function ArrayObserve(object, callback) {
- return ObjectObserve(object, callback, ['new',
- 'updated',
- 'deleted',
+ return ObjectObserve(object, callback, ['add',
+ 'update',
+ 'delete',
'splice']);
}
}
var callbackInfo = CallbackInfoNormalize(callback);
- if (!observationState.pendingObservers)
- observationState.pendingObservers = { __proto__: null };
- observationState.pendingObservers[callbackInfo.priority] = callback;
+ if (IS_NULL(GetPendingObservers())) {
+ SetPendingObservers(nullProtoObject())
+ GetMicrotaskQueue().push(ObserveMicrotaskRunner);
+ %SetMicrotaskPending(true);
+ }
+ GetPendingObservers()[callbackInfo.priority] = callback;
callbackInfo.push(changeRecord);
- %SetObserverDeliveryPending();
}
-function ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord,
- skipAccessCheck) {
+function ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, type) {
+ if (!ObjectInfoHasActiveObservers(objectInfo))
+ return;
+
+ var hasType = !IS_UNDEFINED(type);
+ var newRecord = hasType ?
+ { object: ObjectInfoGetObject(objectInfo), type: type } :
+ { object: ObjectInfoGetObject(objectInfo) };
+
+ for (var prop in changeRecord) {
+ if (prop === 'object' || (hasType && prop === 'type')) continue;
+ %DefineOrRedefineDataProperty(newRecord, prop, changeRecord[prop],
+ READ_ONLY + DONT_DELETE);
+ }
+ ObjectFreeze(newRecord);
+
+ ObjectInfoEnqueueInternalChangeRecord(objectInfo, newRecord,
+ true /* skip access check */);
+}
+
+function ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord,
+ skipAccessCheck) {
// TODO(rossberg): adjust once there is a story for symbols vs proxies.
if (IS_SYMBOL(changeRecord.name)) return;
for (var priority in objectInfo.changeObservers) {
var observer = objectInfo.changeObservers[priority];
+ if (IS_NULL(observer))
+ continue;
ObserverEnqueueIfActive(observer, objectInfo, changeRecord,
needsAccessCheck);
}
}
function BeginPerformSplice(array) {
- var objectInfo = objectInfoMap.get(array);
+ var objectInfo = ObjectInfoGet(array);
if (!IS_UNDEFINED(objectInfo))
ObjectInfoAddPerformingType(objectInfo, 'splice');
}
function EndPerformSplice(array) {
- var objectInfo = objectInfoMap.get(array);
+ var objectInfo = ObjectInfoGet(array);
if (!IS_UNDEFINED(objectInfo))
ObjectInfoRemovePerformingType(objectInfo, 'splice');
}
function EnqueueSpliceRecord(array, index, removed, addedCount) {
- var objectInfo = objectInfoMap.get(array);
+ var objectInfo = ObjectInfoGet(array);
if (!ObjectInfoHasActiveObservers(objectInfo))
return;
ObjectFreeze(changeRecord);
ObjectFreeze(changeRecord.removed);
- ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord);
+ ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord);
}
function NotifyChange(type, object, name, oldValue) {
- var objectInfo = objectInfoMap.get(object);
+ var objectInfo = ObjectInfoGet(object);
if (!ObjectInfoHasActiveObservers(objectInfo))
return;
- var changeRecord = (arguments.length < 4) ?
- { type: type, object: object, name: name } :
- { type: type, object: object, name: name, oldValue: oldValue };
+ var changeRecord;
+ if (arguments.length == 2) {
+ changeRecord = { type: type, object: object };
+ } else if (arguments.length == 3) {
+ changeRecord = { type: type, object: object, name: name };
+ } else {
+ changeRecord = {
+ type: type,
+ object: object,
+ name: name,
+ oldValue: oldValue
+ };
+ }
+
ObjectFreeze(changeRecord);
- ObjectInfoEnqueueChangeRecord(objectInfo, changeRecord);
+ ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord);
}
var notifierPrototype = {};
if (!IS_STRING(changeRecord.type))
throw MakeTypeError("observe_type_non_string");
- if (!ObjectInfoHasActiveObservers(objectInfo))
- return;
-
- var newRecord = { object: ObjectInfoGetObject(objectInfo) };
- for (var prop in changeRecord) {
- if (prop === 'object') continue;
- %DefineOrRedefineDataProperty(newRecord, prop, changeRecord[prop],
- READ_ONLY + DONT_DELETE);
- }
- ObjectFreeze(newRecord);
-
- ObjectInfoEnqueueChangeRecord(objectInfo, newRecord,
- true /* skip access check */);
+ ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord);
}
function ObjectNotifierPerformChange(changeType, changeFn) {
throw MakeTypeError("observe_perform_non_function");
ObjectInfoAddPerformingType(objectInfo, changeType);
+
+ var changeRecord;
try {
- %_CallFunction(UNDEFINED, changeFn);
+ changeRecord = %_CallFunction(UNDEFINED, changeFn);
} finally {
ObjectInfoRemovePerformingType(objectInfo, changeType);
}
+
+ if (IS_SPEC_OBJECT(changeRecord))
+ ObjectInfoEnqueueExternalChangeRecord(objectInfo, changeRecord, changeType);
}
function ObjectGetNotifier(object) {
if (ObjectIsFrozen(object)) return null;
- var objectInfo = ObjectInfoGet(object);
+ var objectInfo = ObjectInfoGetOrCreate(object);
return ObjectInfoGetNotifier(objectInfo);
}
function CallbackDeliverPending(callback) {
- var callbackInfo = callbackInfoMap.get(callback);
+ var callbackInfo = GetCallbackInfoMap().get(callback);
if (IS_UNDEFINED(callbackInfo) || IS_NUMBER(callbackInfo))
return false;
// Clear the pending change records from callback and return it to its
// "optimized" state.
var priority = callbackInfo.priority;
- callbackInfoMap.set(callback, priority);
+ GetCallbackInfoMap().set(callback, priority);
- if (observationState.pendingObservers)
- delete observationState.pendingObservers[priority];
+ if (GetPendingObservers())
+ delete GetPendingObservers()[priority];
var delivered = [];
%MoveArrayContents(callbackInfo, delivered);
try {
%_CallFunction(UNDEFINED, delivered, callback);
- } catch (ex) {}
+ } catch (ex) {} // TODO(rossberg): perhaps log uncaught exceptions.
return true;
}
while (CallbackDeliverPending(callback)) {}
}
-function DeliverChangeRecords() {
- while (observationState.pendingObservers) {
- var pendingObservers = observationState.pendingObservers;
- observationState.pendingObservers = null;
+function ObserveMicrotaskRunner() {
+ var pendingObservers = GetPendingObservers();
+ if (pendingObservers) {
+ SetPendingObservers(null);
for (var i in pendingObservers) {
CallbackDeliverPending(pendingObservers[i]);
}
));
}
-SetupObjectObserve();
+// Disable Object.observe API for M35.
+// SetupObjectObserve();