From 8ce07187633fccad8cbfeb5f23710ea96a6367f0 Mon Sep 17 00:00:00 2001 From: "rossberg@chromium.org" Date: Thu, 16 May 2013 11:19:37 +0000 Subject: [PATCH] Implement Array.observe and emit splice change records for ArrayPush Review URL: https://codereview.chromium.org/14978007 Patch from Rafael Weinstein . git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@14705 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/array.js | 23 +++++++++++++++ src/object-observe.js | 54 ++++++++++++++++++++++++++++++++-- test/mjsunit/harmony/object-observe.js | 9 ++++++ 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/src/array.js b/src/array.js index 54f0b48..599fd5c 100644 --- a/src/array.js +++ b/src/array.js @@ -416,6 +416,26 @@ function ArrayPop() { } +function ObservedArrayPush() { + var n = TO_UINT32(this.length); + var m = %_ArgumentsLength(); + + EnqueueSpliceRecord(this, n, [], 0, m); + + try { + BeginPerformSplice(this); + + for (var i = 0; i < m; i++) { + this[i+n] = %_Arguments(i); + } + this.length = n + m; + } finally { + EndPerformSplice(this); + } + + return this.length; +} + // Appends the arguments to the end of the array and returns the new // length of the array. See ECMA-262, section 15.4.4.7. function ArrayPush() { @@ -424,6 +444,9 @@ function ArrayPush() { ["Array.prototype.push"]); } + if (%IsObserved(this)) + return ObservedArrayPush.apply(this, arguments); + var n = TO_UINT32(this.length); var m = %_ArgumentsLength(); for (var i = 0; i < m; i++) { diff --git a/src/object-observe.js b/src/object-observe.js index c4c6c14..b28f928 100644 --- a/src/object-observe.js +++ b/src/object-observe.js @@ -166,7 +166,7 @@ function EndPerformChange(objectInfo, type) { objectInfo); } -function ensureObserverRemoved(objectInfo, callback) { +function EnsureObserverRemoved(objectInfo, callback) { function remove(observerList) { for (var i = 0; i < observerList.length; i++) { if (observerList[i].callback === callback) { @@ -219,7 +219,7 @@ function ObjectObserve(object, callback, accept) { if (IS_UNDEFINED(objectInfo)) objectInfo = CreateObjectInfo(object); %SetIsObserved(object, true); - ensureObserverRemoved(objectInfo, callback); + EnsureObserverRemoved(objectInfo, callback); var observer = CreateObserver(callback, accept); if (ObserverIsActive(observer, objectInfo)) @@ -240,7 +240,7 @@ function ObjectUnobserve(object, callback) { if (IS_UNDEFINED(objectInfo)) return object; - ensureObserverRemoved(objectInfo, callback); + EnsureObserverRemoved(objectInfo, callback); if (objectInfo.changeObservers.length === 0 && objectInfo.inactiveObservers.length === 0) { @@ -250,6 +250,17 @@ function ObjectUnobserve(object, callback) { return object; } +function ArrayObserve(object, callback) { + return ObjectObserve(object, callback, ['new', + 'updated', + 'deleted', + 'splice']); +} + +function ArrayUnobserve(object, callback) { + return ObjectUnobserve(object, callback); +} + function EnqueueChangeRecord(changeRecord, observers) { // TODO(rossberg): adjust once there is a story for symbols vs proxies. if (IS_SYMBOL(changeRecord.name)) return; @@ -271,6 +282,39 @@ function EnqueueChangeRecord(changeRecord, observers) { } } +function BeginPerformSplice(array) { + var objectInfo = objectInfoMap.get(array); + if (!IS_UNDEFINED(objectInfo)) + BeginPerformChange(objectInfo, 'splice'); +} + +function EndPerformSplice(array) { + var objectInfo = objectInfoMap.get(array); + if (!IS_UNDEFINED(objectInfo)) + EndPerformChange(objectInfo, 'splice'); +} + +function EnqueueSpliceRecord(array, index, removed, deleteCount, addedCount) { + var objectInfo = objectInfoMap.get(array); + if (IS_UNDEFINED(objectInfo) || objectInfo.changeObservers.length === 0) + return; + + var changeRecord = { + type: 'splice', + object: array, + index: index, + removed: removed, + addedCount: addedCount + }; + + changeRecord.removed.length = deleteCount; + // TODO(rafaelw): This breaks spec-compliance. Re-enable when freezing isn't + // slow. + // ObjectFreeze(changeRecord); + // ObjectFreeze(changeRecord.removed); + EnqueueChangeRecord(changeRecord, objectInfo.changeObservers); +} + function NotifyChange(type, object, name, oldValue) { var objectInfo = objectInfoMap.get(object); if (objectInfo.changeObservers.length === 0) @@ -405,6 +449,10 @@ function SetupObjectObserve() { "observe", ObjectObserve, "unobserve", ObjectUnobserve )); + InstallFunctions($Array, DONT_ENUM, $Array( + "observe", ArrayObserve, + "unobserve", ArrayUnobserve + )); InstallFunctions(notifierPrototype, DONT_ENUM, $Array( "notify", ObjectNotifierNotify, "performChange", ObjectNotifierPerformChange diff --git a/test/mjsunit/harmony/object-observe.js b/test/mjsunit/harmony/object-observe.js index 8200e90..372ffdb 100644 --- a/test/mjsunit/harmony/object-observe.js +++ b/test/mjsunit/harmony/object-observe.js @@ -1068,13 +1068,22 @@ observer.assertCallbackRecords([ reset(); var array = [1, 2]; Object.observe(array, observer.callback); +Array.observe(array, observer2.callback); array.push(3, 4); +array.push(5); Object.deliverChangeRecords(observer.callback); observer.assertCallbackRecords([ { object: array, name: '2', type: 'new' }, { object: array, name: 'length', type: 'updated', oldValue: 2 }, { object: array, name: '3', type: 'new' }, { object: array, name: 'length', type: 'updated', oldValue: 3 }, + { object: array, name: '4', type: 'new' }, + { object: array, name: 'length', type: 'updated', oldValue: 4 }, +]); +Object.deliverChangeRecords(observer2.callback); +observer2.assertCallbackRecords([ + { object: array, type: 'splice', index: 2, removed: [], addedCount: 2 }, + { object: array, type: 'splice', index: 4, removed: [], addedCount: 1 } ]); // Pop -- 2.7.4