| Index: src/object-observe.js
|
| diff --git a/src/object-observe.js b/src/object-observe.js
|
| index 1c147d95e30f9e82d45797d500b2d32bb198b064..c9b1c88363ae65524cfa878424c2feb2f77314d2 100644
|
| --- a/src/object-observe.js
|
| +++ b/src/object-observe.js
|
| @@ -67,51 +67,131 @@ function CreateObjectInfo(object) {
|
| changeObservers: new InternalArray,
|
| notifier: null,
|
| inactiveObservers: new InternalArray,
|
| - performing: { __proto__: null },
|
| + performing: 0,
|
| performingCount: 0,
|
| };
|
| objectInfoMap.set(object, info);
|
| return info;
|
| }
|
|
|
| -var defaultAcceptTypes = {
|
| - __proto__: null,
|
| - 'new': true,
|
| - 'updated': true,
|
| - 'deleted': true,
|
| - 'prototype': true,
|
| - 'reconfigured': true
|
| -};
|
| +var typeConstants = { __proto__: null };
|
| +var nextTypeConstant = 1;
|
| +var MAX_BITFIELD_CONSTANT = 1 << 30;
|
| +
|
| +function NormalizeTypeMap(typeMap) {
|
| + if (IS_NUMBER(typeMap)) {
|
| + var bitfield = typeMap;
|
| + var typeMap = [];
|
| + var constant = 1;
|
| + while (true) {
|
| + if (bitfield & constant)
|
| + typeMap[constant] = 1;
|
| + if (constant == MAX_BITFIELD_CONSTANT)
|
| + break;
|
| + constant = constant << 1;
|
| + }
|
| + }
|
|
|
| -function CreateObserver(callback, accept) {
|
| - var observer = {
|
| - __proto__: null,
|
| - callback: callback,
|
| - accept: defaultAcceptTypes
|
| - };
|
| + return typeMap;
|
| +}
|
|
|
| - if (IS_UNDEFINED(accept))
|
| - return observer;
|
| +function GetTypeConstant(type) {
|
| + var constant = typeConstants[type];
|
| + if (!constant) {
|
| + constant = nextTypeConstant;
|
| + typeConstants[type] = constant;
|
| + nextTypeConstant = nextTypeConstant < MAX_BITFIELD_CONSTANT ?
|
| + nextTypeConstant << 1 : nextTypeConstant + 1;
|
| + }
|
|
|
| - var acceptMap = { __proto__: null };
|
| - for (var i = 0; i < accept.length; i++)
|
| - acceptMap[accept[i]] = true;
|
| + return constant;
|
| +}
|
| +
|
| +function AddToTypeMap(typeMap, type, ignoreDuplicate) {
|
| + var typeConstant = GetTypeConstant(type);
|
| + if (IS_NUMBER(typeMap) &&
|
| + typeConstant <= MAX_BITFIELD_CONSTANT &&
|
| + (ignoreDuplicate || !(typeMap & typeConstant))) {
|
| + typeMap |= typeConstant;
|
| + return typeMap;
|
| + }
|
|
|
| - observer.accept = acceptMap;
|
| - return observer;
|
| + typeMap = NormalizeTypeMap(typeMap);
|
| + var value = ignoreDuplicate ? 1 : (typeMap[typeConstant] || 0) + 1;
|
| + typeMap[typeConstant] = value;
|
| + return typeMap;
|
| }
|
|
|
| -function ObserverIsActive(observer, objectInfo) {
|
| - if (objectInfo.performingCount === 0)
|
| - return true;
|
| +function RemoveFromTypeMap(typeMap, type) {
|
| + var typeConstant = GetTypeConstant(type);
|
| + if (IS_NUMBER(typeMap)) {
|
| + if (typeConstant <= MAX_BITFIELD_CONSTANT && (typeMap & typeConstant))
|
| + typeMap -= typeConstant;
|
| + } else if (typeMap[typeConstant] > 0) {
|
| + typeMap[typeConstant]--;
|
| + }
|
|
|
| - var performing = objectInfo.performing;
|
| - for (var type in performing) {
|
| - if (performing[type] > 0 && observer.accept[type])
|
| - return false;
|
| + return typeMap;
|
| +}
|
| +
|
| +function CreateTypeMap(typeList) {
|
| + var typeMap = 0;
|
| + for (var i = 0; i < typeList.length; i++) {
|
| + typeMap = AddToTypeMap(typeMap, typeList[i], true);
|
| }
|
|
|
| - return true;
|
| + return typeMap;
|
| +}
|
| +
|
| +function TypeMapHasType(typeMap, type) {
|
| + return TypeMapHasConstant(typeMap, GetTypeConstant(type));
|
| +}
|
| +
|
| +function TypeMapHasConstant(typeMap, constant) {
|
| + if (IS_NUMBER(typeMap))
|
| + return constant > MAX_BITFIELD_CONSTANT ? false : typeMap & constant;
|
| + else
|
| + return typeMap[constant] > 0;
|
| +}
|
| +
|
| +function TypeMapsIntersect(typeMap1, typeMap2) {
|
| + if (IS_NUMBER(typeMap1) && IS_NUMBER(typeMap2))
|
| + return typeMap1 & typeMap2;
|
| +
|
| + var checkMap = IS_NUMBER(typeMap1) ? typeMap1 : typeMap2;
|
| + var iterateMap = IS_NUMBER(typeMap1) ? typeMap2 : typeMap1;
|
| +
|
| + for (var constant in iterateMap) {
|
| + if (TypeMapHasConstant(checkMap, constant))
|
| + return true;
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +var defaultAcceptTypes = CreateTypeMap([
|
| + 'new',
|
| + 'updated',
|
| + 'deleted',
|
| + 'prototype',
|
| + 'reconfigured'
|
| +]);
|
| +
|
| +GetTypeConstant('splice');
|
| +
|
| +
|
| +function CreateObserver(callback, acceptList) {
|
| + return {
|
| + __proto__: null,
|
| + callback: callback,
|
| + acceptMap: IS_UNDEFINED(acceptList) ?
|
| + defaultAcceptTypes : CreateTypeMap(acceptList)
|
| + };
|
| +}
|
| +
|
| +function ObserverIsActive(observer, objectInfo) {
|
| + return objectInfo.performingCount === 0 ?
|
| + true : !TypeMapsIntersect(objectInfo.performing, observer.acceptMap);
|
| }
|
|
|
| function ObserverIsInactive(observer, objectInfo) {
|
| @@ -148,22 +228,47 @@ function RepartitionObservers(conditionFn, from, to, objectInfo) {
|
| RemoveNullElements(from);
|
| }
|
|
|
| -function BeginPerformChange(objectInfo, type) {
|
| - objectInfo.performing[type] = (objectInfo.performing[type] || 0) + 1;
|
| +function ObjectAddPerformingType(objectInfo, type) {
|
| + if (!objectInfo.performing) {
|
| + objectInfo.performing = AddToTypeMap(0, type);
|
| + objectInfo.performingCount = 1;
|
| + return true;
|
| + }
|
| +
|
| + var hadType = TypeMapHasType(objectInfo.performing, type);
|
| + objectInfo.performing = AddToTypeMap(objectInfo.performing, type);
|
| objectInfo.performingCount++;
|
| - RepartitionObservers(ObserverIsInactive,
|
| - objectInfo.changeObservers,
|
| - objectInfo.inactiveObservers,
|
| - objectInfo);
|
| + return !hadType;
|
| }
|
|
|
| -function EndPerformChange(objectInfo, type) {
|
| - objectInfo.performing[type]--;
|
| +function ObjectRemovePerformingType(objectInfo, type) {
|
| + if (objectInfo.performingCount == 1) {
|
| + objectInfo.performing = 0;
|
| + objectInfo.performingCount = 0;
|
| + return true;
|
| + }
|
| +
|
| + objectInfo.performing = RemoveFromTypeMap(objectInfo.performing, type);
|
| objectInfo.performingCount--;
|
| - RepartitionObservers(ObserverIsActive,
|
| - objectInfo.inactiveObservers,
|
| - objectInfo.changeObservers,
|
| - objectInfo);
|
| + return !TypeMapHasType(objectInfo.performing, type);
|
| +}
|
| +
|
| +function BeginPerformChange(objectInfo, type) {
|
| + if (ObjectAddPerformingType(objectInfo, type)) {
|
| + RepartitionObservers(ObserverIsInactive,
|
| + objectInfo.changeObservers,
|
| + objectInfo.inactiveObservers,
|
| + objectInfo);
|
| + }
|
| +}
|
| +
|
| +function EndPerformChange(objectInfo, type) {
|
| + if (ObjectRemovePerformingType(objectInfo, type)) {
|
| + RepartitionObservers(ObserverIsActive,
|
| + objectInfo.inactiveObservers,
|
| + objectInfo.changeObservers,
|
| + objectInfo);
|
| + }
|
| }
|
|
|
| function EnsureObserverRemoved(objectInfo, callback) {
|
| @@ -214,14 +319,14 @@ function NormalizeCallbackInfo(callback) {
|
| return callbackInfo;
|
| }
|
|
|
| -function ObjectObserve(object, callback, accept) {
|
| +function ObjectObserve(object, callback, acceptList) {
|
| if (!IS_SPEC_OBJECT(object))
|
| throw MakeTypeError("observe_non_object", ["observe"]);
|
| if (!IS_SPEC_FUNCTION(callback))
|
| throw MakeTypeError("observe_non_function", ["observe"]);
|
| if (ObjectIsFrozen(callback))
|
| throw MakeTypeError("observe_callback_frozen");
|
| - if (!AcceptArgIsValid(accept))
|
| + if (!AcceptArgIsValid(acceptList))
|
| throw MakeTypeError("observe_accept_invalid");
|
|
|
| EnsureCallbackPriority(callback);
|
| @@ -234,7 +339,7 @@ function ObjectObserve(object, callback, accept) {
|
|
|
| EnsureObserverRemoved(objectInfo, callback);
|
|
|
| - var observer = CreateObserver(callback, accept);
|
| + var observer = CreateObserver(callback, acceptList);
|
| if (ObserverIsActive(observer, objectInfo))
|
| objectInfo.changeObservers.push(observer);
|
| else
|
| @@ -275,16 +380,18 @@ function EnqueueToCallback(callback, changeRecord) {
|
| %SetObserverDeliveryPending();
|
| }
|
|
|
| +function ObserverAcceptsType(observer, type) {
|
| + return TypeMapHasType(observer.acceptMap, type);
|
| +}
|
| +
|
| function EnqueueChangeRecord(changeRecord, observers) {
|
| // TODO(rossberg): adjust once there is a story for symbols vs proxies.
|
| if (IS_SYMBOL(changeRecord.name)) return;
|
|
|
| for (var i = 0; i < observers.length; i++) {
|
| var observer = observers[i];
|
| - if (IS_UNDEFINED(observer.accept[changeRecord.type]))
|
| - continue;
|
| -
|
| - EnqueueToCallback(observer.callback, changeRecord);
|
| + if (ObserverAcceptsType(observer, changeRecord.type))
|
| + EnqueueToCallback(observer.callback, changeRecord);
|
| }
|
| }
|
|
|
|
|