Make Object.freeze/seal/preventExtensions observable
authorrafaelw@chromium.org <rafaelw@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 5 Nov 2013 12:25:32 +0000 (12:25 +0000)
committerrafaelw@chromium.org <rafaelw@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 5 Nov 2013 12:25:32 +0000 (12:25 +0000)
Note: spec has been updated here: http://wiki.ecmascript.org/doku.php?id=harmony:observe_spec_changes.

R=rossberg@chromium.org, rossberg
BUG=v8:2975,v8:2941

Review URL: https://codereview.chromium.org/47703003

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

src/object-observe.js
src/objects.cc
src/v8natives.js
test/mjsunit/harmony/object-observe.js

index 9fd611a..718920f 100644 (file)
@@ -132,7 +132,8 @@ var defaultAcceptTypes = TypeMapCreateFromList([
   'updated',
   'deleted',
   'prototype',
-  'reconfigured'
+  'reconfigured',
+  'preventExtensions'
 ]);
 
 // An Observer is a registration to observe an object by a callback with
@@ -463,9 +464,20 @@ function NotifyChange(type, object, name, oldValue) {
   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);
   ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord);
 }
index 6f371a8..cc73995 100644 (file)
@@ -2175,11 +2175,13 @@ void JSObject::EnqueueChangeRecord(Handle<JSObject> object,
     object = handle(JSGlobalObject::cast(*object)->global_receiver(), isolate);
   }
   Handle<Object> args[] = { type, object, name, old_value };
+  int argc = name.is_null() ? 2 : old_value->IsTheHole() ? 3 : 4;
   bool threw;
+
   Execution::Call(isolate,
                   Handle<JSFunction>(isolate->observers_notify_change()),
                   isolate->factory()->undefined_value(),
-                  old_value->IsTheHole() ? 3 : 4, args,
+                  argc, args,
                   &threw);
   ASSERT(!threw);
 }
@@ -5442,6 +5444,9 @@ bool JSObject::ReferencesObject(Object* obj) {
 
 Handle<Object> JSObject::PreventExtensions(Handle<JSObject> object) {
   Isolate* isolate = object->GetIsolate();
+
+  if (!object->map()->is_extensible()) return object;
+
   if (object->IsAccessCheckNeeded() &&
       !isolate->MayNamedAccess(*object,
                                isolate->heap()->undefined_value(),
@@ -5484,6 +5489,11 @@ Handle<Object> JSObject::PreventExtensions(Handle<JSObject> object) {
   new_map->set_is_extensible(false);
   object->set_map(*new_map);
   ASSERT(!object->map()->is_extensible());
+
+  if (FLAG_harmony_observation && object->map()->is_observed()) {
+    EnqueueChangeRecord(object, "preventExtensions", Handle<Name>(),
+                        isolate->factory()->the_hole_value());
+  }
   return object;
 }
 
@@ -5512,6 +5522,7 @@ static void FreezeDictionary(Dictionary* dictionary) {
 Handle<Object> JSObject::Freeze(Handle<JSObject> object) {
   // Freezing non-strict arguments should be handled elsewhere.
   ASSERT(!object->HasNonStrictArgumentsElements());
+  ASSERT(!object->map()->is_observed());
 
   if (object->map()->is_frozen()) return object;
 
index c42d5c4..995e7d8 100644 (file)
@@ -1249,7 +1249,7 @@ function ObjectFreeze(obj) {
     throw MakeTypeError("called_on_non_object", ["Object.freeze"]);
   }
   var isProxy = %IsJSProxy(obj);
-  if (isProxy || %HasNonStrictArgumentsElements(obj)) {
+  if (isProxy || %HasNonStrictArgumentsElements(obj) || %IsObserved(obj)) {
     if (isProxy) {
       ProxyFix(obj);
     }
index 5615d9e..fa123e3 100644 (file)
@@ -300,8 +300,103 @@ observer.assertCallbackRecords([
   { object: obj, type: 'deleted', name: '', oldValue: ' ' },
 ]);
 
+// Object.preventExtensions
+reset();
+var obj = { foo: 'bar'};
+Object.observe(obj, observer.callback);
+obj.baz = 'bat';
+Object.preventExtensions(obj);
+
+Object.deliverChangeRecords(observer.callback);
+observer.assertCallbackRecords([
+  { object: obj, type: 'new', name: 'baz' },
+  { object: obj, type: 'preventExtensions' },
+]);
+
+reset();
+var obj = { foo: 'bar'};
+Object.preventExtensions(obj);
+Object.observe(obj, observer.callback);
+Object.preventExtensions(obj);
+Object.deliverChangeRecords(observer.callback);
+observer.assertNotCalled();
+
+// Object.freeze
+reset();
+var obj = { a: 'a' };
+Object.defineProperty(obj, 'b', {
+  writable: false,
+  configurable: true,
+  value: 'b'
+});
+Object.defineProperty(obj, 'c', {
+  writable: true,
+  configurable: false,
+  value: 'c'
+});
+Object.defineProperty(obj, 'd', {
+  writable: false,
+  configurable: false,
+  value: 'd'
+});
+Object.observe(obj, observer.callback);
+Object.freeze(obj);
+
+Object.deliverChangeRecords(observer.callback);
+observer.assertCallbackRecords([
+  { object: obj, type: 'reconfigured', name: 'a' },
+  { object: obj, type: 'reconfigured', name: 'b' },
+  { object: obj, type: 'reconfigured', name: 'c' },
+  { object: obj, type: 'preventExtensions' },
+]);
+
+reset();
+var obj = { foo: 'bar'};
+Object.freeze(obj);
+Object.observe(obj, observer.callback);
+Object.freeze(obj);
+Object.deliverChangeRecords(observer.callback);
+observer.assertNotCalled();
+
+// Object.seal
+reset();
+var obj = { a: 'a' };
+Object.defineProperty(obj, 'b', {
+  writable: false,
+  configurable: true,
+  value: 'b'
+});
+Object.defineProperty(obj, 'c', {
+  writable: true,
+  configurable: false,
+  value: 'c'
+});
+Object.defineProperty(obj, 'd', {
+  writable: false,
+  configurable: false,
+  value: 'd'
+});
+Object.observe(obj, observer.callback);
+Object.seal(obj);
+
+Object.deliverChangeRecords(observer.callback);
+observer.assertCallbackRecords([
+  { object: obj, type: 'reconfigured', name: 'a' },
+  { object: obj, type: 'reconfigured', name: 'b' },
+  { object: obj, type: 'preventExtensions' },
+]);
+
+reset();
+var obj = { foo: 'bar'};
+Object.seal(obj);
+Object.observe(obj, observer.callback);
+Object.seal(obj);
+Object.deliverChangeRecords(observer.callback);
+observer.assertNotCalled();
+
 // Observing a continuous stream of changes, while itermittantly unobserving.
 reset();
+var obj = {};
 Object.observe(obj, observer.callback);
 Object.getNotifier(obj).notify({
   type: 'updated',