Implement Array.observe and emit splice change records for ArrayPush
authorrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 16 May 2013 11:19:37 +0000 (11:19 +0000)
committerrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Thu, 16 May 2013 11:19:37 +0000 (11:19 +0000)
Review URL: https://codereview.chromium.org/14978007

Patch from Rafael Weinstein <rafaelw@chromium.org>.

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@14705 ce2b1a6d-e550-0410-aec6-3dcde31c8c00

src/array.js
src/object-observe.js
test/mjsunit/harmony/object-observe.js

index 54f0b48..599fd5c 100644 (file)
@@ -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++) {
index c4c6c14..b28f928 100644 (file)
@@ -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
index 8200e90..372ffdb 100644 (file)
@@ -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