Object.observe: generate change records for named properties.
authorrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 6 Nov 2012 12:32:36 +0000 (12:32 +0000)
committerrossberg@chromium.org <rossberg@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Tue, 6 Nov 2012 12:32:36 +0000 (12:32 +0000)
In more detail:
- Set observation bit for observed objects (and make NormalizedMapCache respect it).
- Mutation of observed objects is always delegated from ICs to runtime.
- Introduce JS runtime function for notifying generated changes.
- Invoke this function in the appropriate places (including some local refactoring).
- Inclusion of oldValue field is not yet implemented, nor element properties.

Also, shortened flag to --harmony-observation.

R=verwaest@chromium.org
BUG=

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

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

src/bootstrapper.cc
src/contexts.h
src/flag-definitions.h
src/handles.h
src/ic.cc
src/object-observe.js
src/objects.cc
src/objects.h
src/runtime.cc
src/v8natives.js
test/mjsunit/harmony/object-observe.js

index 5d246e0..61e32c2 100644 (file)
@@ -1416,6 +1416,9 @@ void Genesis::InstallExperimentalNativeFunctions() {
     INSTALL_NATIVE(JSFunction, "DerivedSetTrap", derived_set_trap);
     INSTALL_NATIVE(JSFunction, "ProxyEnumerate", proxy_enumerate);
   }
+  if (FLAG_harmony_observation) {
+    INSTALL_NATIVE(JSFunction, "NotifyChange", observers_notify_change);
+  }
 }
 
 #undef INSTALL_NATIVE
@@ -1829,7 +1832,7 @@ bool Genesis::InstallExperimentalNatives() {
                "native collection.js") == 0) {
       if (!CompileExperimentalBuiltin(isolate(), i)) return false;
     }
-    if (FLAG_harmony_object_observe &&
+    if (FLAG_harmony_observation &&
         strcmp(ExperimentalNatives::GetScriptName(i).start(),
                "native object-observe.js") == 0) {
       if (!CompileExperimentalBuiltin(isolate(), i)) return false;
index 53a7c2d..91de672 100644 (file)
@@ -161,7 +161,8 @@ enum BindingFlags {
   V(DERIVED_HAS_TRAP_INDEX, JSFunction, derived_has_trap) \
   V(DERIVED_GET_TRAP_INDEX, JSFunction, derived_get_trap) \
   V(DERIVED_SET_TRAP_INDEX, JSFunction, derived_set_trap) \
-  V(PROXY_ENUMERATE, JSFunction, proxy_enumerate) \
+  V(PROXY_ENUMERATE_INDEX, JSFunction, proxy_enumerate) \
+  V(OBSERVERS_NOTIFY_CHANGE_INDEX, JSFunction, observers_notify_change) \
   V(RANDOM_SEED_INDEX, ByteArray, random_seed)
 
 // JSFunctions are pairs (context, function code), sometimes also called
@@ -288,7 +289,8 @@ class Context: public FixedArray {
     DERIVED_HAS_TRAP_INDEX,
     DERIVED_GET_TRAP_INDEX,
     DERIVED_SET_TRAP_INDEX,
-    PROXY_ENUMERATE,
+    PROXY_ENUMERATE_INDEX,
+    OBSERVERS_NOTIFY_CHANGE_INDEX,
     RANDOM_SEED_INDEX,
 
     // Properties from here are treated as weak references by the full GC.
index 325bd4b..77e84ff 100644 (file)
@@ -144,16 +144,16 @@ DEFINE_bool(harmony_modules, false,
 DEFINE_bool(harmony_proxies, false, "enable harmony proxies")
 DEFINE_bool(harmony_collections, false,
             "enable harmony collections (sets, maps, and weak maps)")
-DEFINE_bool(harmony_object_observe, false,
+DEFINE_bool(harmony_observation, false,
             "enable harmony object observation (implies harmony collections")
 DEFINE_bool(harmony, false, "enable all harmony features (except typeof)")
 DEFINE_implication(harmony, harmony_scoping)
 DEFINE_implication(harmony, harmony_modules)
 DEFINE_implication(harmony, harmony_proxies)
 DEFINE_implication(harmony, harmony_collections)
-DEFINE_implication(harmony, harmony_object_observe)
+DEFINE_implication(harmony, harmony_observation)
 DEFINE_implication(harmony_modules, harmony_scoping)
-DEFINE_implication(harmony_object_observe, harmony_collections)
+DEFINE_implication(harmony_observation, harmony_collections)
 
 // Flags for experimental implementation features.
 DEFINE_bool(packed_arrays, true, "optimizes arrays that have no holes")
index a1d88c2..77aebf7 100644 (file)
@@ -95,6 +95,13 @@ class Handle {
 };
 
 
+// Convenience wrapper.
+template<class T>
+inline Handle<T> handle(T* t) {
+  return Handle<T>(t);
+}
+
+
 class DeferredHandles;
 class HandleScopeImplementer;
 
index dd0bb10..50a3a27 100644 (file)
--- a/src/ic.cc
+++ b/src/ic.cc
@@ -1377,6 +1377,11 @@ MaybeObject* StoreIC::Store(State state,
     return *value;
   }
 
+  // Observed objects are always modified through the runtime.
+  if (FLAG_harmony_observation && receiver->map()->is_observed()) {
+    return receiver->SetProperty(*name, *value, NONE, strict_mode);
+  }
+
   // Use specialized code for setting the length of arrays with fast
   // properties.  Slow properties might indicate redefinition of the
   // length property.
@@ -1902,7 +1907,8 @@ MaybeObject* KeyedStoreIC::Store(State state,
     }
 
     // Update inline cache and stub cache.
-    if (FLAG_use_ic && !receiver->IsJSGlobalProxy()) {
+    if (FLAG_use_ic && !receiver->IsJSGlobalProxy() &&
+        !(FLAG_harmony_observation && receiver->map()->is_observed())) {
       LookupResult lookup(isolate());
       if (LookupForWrite(receiver, name, &lookup)) {
         UpdateCaches(&lookup, state, strict_mode, receiver, name, value);
index dcf98d8..41d7f3c 100644 (file)
@@ -68,6 +68,7 @@ function ObjectObserve(object, callback) {
       changeObservers: new InternalArray(callback)
     };
     objectInfoMap.set(object, objectInfo);
+    %SetIsObserved(object, true);
     return;
   }
 
@@ -109,6 +110,15 @@ function EnqueueChangeRecord(changeRecord, observers) {
   }
 }
 
+function NotifyChange(type, object, name, oldValue) {
+  var objectInfo = objectInfoMap.get(object);
+  var changeRecord = (arguments.length < 4) ?
+      { type: type, object: object, name: name } :
+      { type: type, object: object, name: name, oldValue: oldValue };
+  InternalObjectFreeze(changeRecord);
+  EnqueueChangeRecord(changeRecord, objectInfo.changeObservers);
+}
+
 function ObjectNotify(object, changeRecord) {
   // TODO: notifier needs to be [[THIS]]
   if (!IS_STRING(changeRecord.type))
@@ -119,7 +129,7 @@ function ObjectNotify(object, changeRecord) {
     return;
 
   var newRecord = {
-    object: object  // TODO: Needs to be 'object' retreived from notifier
+    object: object  // TODO: Needs to be 'object' retrieved from notifier
   };
   for (var prop in changeRecord) {
     if (prop === 'object')
@@ -161,4 +171,4 @@ function SetupObjectObserve() {
   ));
 }
 
-SetupObjectObserve();
\ No newline at end of file
+SetupObjectObserve();
index d0f8ac2..3ecbf22 100644 (file)
@@ -1677,6 +1677,7 @@ MaybeObject* JSObject::AddProperty(String* name,
   ASSERT(!IsJSGlobalProxy());
   Map* map_of_this = map();
   Heap* heap = GetHeap();
+  MaybeObject* result;
   if (extensibility_check == PERFORM_EXTENSIBILITY_CHECK &&
       !map_of_this->is_extensible()) {
     if (strict_mode == kNonStrictMode) {
@@ -1688,28 +1689,55 @@ MaybeObject* JSObject::AddProperty(String* name,
                                  HandleVector(args, 1)));
     }
   }
+
   if (HasFastProperties()) {
     // Ensure the descriptor array does not get too big.
     if (map_of_this->NumberOfOwnDescriptors() <
         DescriptorArray::kMaxNumberOfDescriptors) {
       if (value->IsJSFunction()) {
-        return AddConstantFunctionProperty(name,
-                                           JSFunction::cast(value),
-                                           attributes);
+        result = AddConstantFunctionProperty(name,
+                                             JSFunction::cast(value),
+                                             attributes);
       } else {
-        return AddFastProperty(name, value, attributes, store_mode);
+        result = AddFastProperty(name, value, attributes, store_mode);
       }
     } else {
       // Normalize the object to prevent very large instance descriptors.
       // This eliminates unwanted N^2 allocation and lookup behavior.
       Object* obj;
-      { MaybeObject* maybe_obj =
-            NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0);
-        if (!maybe_obj->ToObject(&obj)) return maybe_obj;
-      }
+      MaybeObject* maybe = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0);
+      if (!maybe->To(&obj)) return maybe;
+      result = AddSlowProperty(name, value, attributes);
     }
+  } else {
+    result = AddSlowProperty(name, value, attributes);
+  }
+
+  Handle<Object> hresult;
+  if (!result->ToHandle(&hresult)) return result;
+
+  if (FLAG_harmony_observation && map()->is_observed()) {
+    this->EnqueueChangeRecord(
+        "new", handle(name), handle(heap->the_hole_value()));
   }
-  return AddSlowProperty(name, value, attributes);
+
+  return *hresult;
+}
+
+
+void JSObject::EnqueueChangeRecord(
+    const char* type_str, Handle<String> name, Handle<Object> old_value) {
+  Isolate* isolate = GetIsolate();
+  HandleScope scope;
+  Handle<String> type = isolate->factory()->LookupAsciiSymbol(type_str);
+  Handle<JSObject> object(this);
+  Handle<Object> args[] = { type, object, name, old_value };
+  bool threw;
+  Execution::Call(Handle<JSFunction>(isolate->observers_notify_change()),
+                  Handle<Object>(isolate->heap()->undefined_value()),
+                  old_value->IsTheHole() ? 3 : 4, args,
+                  &threw);
+  ASSERT(!threw);
 }
 
 
@@ -2802,7 +2830,7 @@ void JSObject::AddFastPropertyUsingMap(Handle<JSObject> object,
 }
 
 
-MaybeObject* JSObject::SetPropertyForResult(LookupResult* result,
+MaybeObject* JSObject::SetPropertyForResult(LookupResult* lookup,
                                             String* name_raw,
                                             Object* value_raw,
                                             PropertyAttributes attributes,
@@ -2829,7 +2857,7 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* result,
   if (IsAccessCheckNeeded()) {
     if (!heap->isolate()->MayNamedAccess(this, name_raw, v8::ACCESS_SET)) {
       return SetPropertyWithFailedAccessCheck(
-          result, name_raw, value_raw, true, strict_mode);
+          lookup, name_raw, value_raw, true, strict_mode);
     }
   }
 
@@ -2838,7 +2866,7 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* result,
     if (proto->IsNull()) return value_raw;
     ASSERT(proto->IsJSGlobalObject());
     return JSObject::cast(proto)->SetPropertyForResult(
-        result, name_raw, value_raw, attributes, strict_mode, store_mode);
+        lookup, name_raw, value_raw, attributes, strict_mode, store_mode);
   }
 
   // From this point on everything needs to be handlified, because
@@ -2848,19 +2876,20 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* result,
   Handle<String> name(name_raw);
   Handle<Object> value(value_raw);
 
-  if (!result->IsProperty() && !self->IsJSContextExtensionObject()) {
+  if (!lookup->IsProperty() && !self->IsJSContextExtensionObject()) {
     bool done = false;
     MaybeObject* result_object = self->SetPropertyViaPrototypes(
         *name, *value, attributes, strict_mode, &done);
     if (done) return result_object;
   }
 
-  if (!result->IsFound()) {
+  if (!lookup->IsFound()) {
     // Neither properties nor transitions found.
     return self->AddProperty(
         *name, *value, attributes, strict_mode, store_mode);
   }
-  if (result->IsProperty() && result->IsReadOnly()) {
+
+  if (lookup->IsProperty() && lookup->IsReadOnly()) {
     if (strict_mode == kStrictMode) {
       Handle<Object> args[] = { name, self };
       return heap->isolate()->Throw(*heap->isolate()->factory()->NewTypeError(
@@ -2870,34 +2899,44 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* result,
     }
   }
 
+  Handle<Object> old_value(heap->the_hole_value());
+  if (FLAG_harmony_observation && map()->is_observed()) {
+    // TODO(observe): save oldValue
+  }
+
   // This is a real property that is not read-only, or it is a
   // transition or null descriptor and there are no setters in the prototypes.
-  switch (result->type()) {
+  MaybeObject* result = *value;
+  switch (lookup->type()) {
     case NORMAL:
-      return self->SetNormalizedProperty(result, *value);
+      result = self->SetNormalizedProperty(lookup, *value);
+      break;
     case FIELD:
-      return self->FastPropertyAtPut(result->GetFieldIndex(), *value);
+      result = self->FastPropertyAtPut(lookup->GetFieldIndex(), *value);
+      break;
     case CONSTANT_FUNCTION:
       // Only replace the function if necessary.
-      if (*value == result->GetConstantFunction()) return *value;
+      if (*value == lookup->GetConstantFunction()) return *value;
       // Preserve the attributes of this existing property.
-      attributes = result->GetAttributes();
-      return self->ConvertDescriptorToField(*name, *value, attributes);
+      attributes = lookup->GetAttributes();
+      result = self->ConvertDescriptorToField(*name, *value, attributes);
+      break;
     case CALLBACKS: {
-      Object* callback_object = result->GetCallbackObject();
+      Object* callback_object = lookup->GetCallbackObject();
       return self->SetPropertyWithCallback(callback_object,
                                            *name,
                                            *value,
-                                           result->holder(),
+                                           lookup->holder(),
                                            strict_mode);
     }
     case INTERCEPTOR:
-      return self->SetPropertyWithInterceptor(*name,
-                                              *value,
-                                              attributes,
-                                              strict_mode);
+      result = self->SetPropertyWithInterceptor(*name,
+                                                *value,
+                                                attributes,
+                                                strict_mode);
+      break;
     case TRANSITION: {
-      Map* transition_map = result->GetTransitionTarget();
+      Map* transition_map = lookup->GetTransitionTarget();
       int descriptor = transition_map->LastAdded();
 
       DescriptorArray* descriptors = transition_map->instance_descriptors();
@@ -2906,37 +2945,46 @@ MaybeObject* JSObject::SetPropertyForResult(LookupResult* result,
       if (details.type() == FIELD) {
         if (attributes == details.attributes()) {
           int field_index = descriptors->GetFieldIndex(descriptor);
-          return self->AddFastPropertyUsingMap(transition_map,
-                                               *name,
-                                               *value,
-                                               field_index);
+          result = self->AddFastPropertyUsingMap(transition_map,
+                                                 *name,
+                                                 *value,
+                                                 field_index);
+        } else {
+          result = self->ConvertDescriptorToField(*name, *value, attributes);
         }
-        return self->ConvertDescriptorToField(*name, *value, attributes);
       } else if (details.type() == CALLBACKS) {
-        return ConvertDescriptorToField(*name, *value, attributes);
-      }
-
-      ASSERT(details.type() == CONSTANT_FUNCTION);
-
-      Object* constant_function = descriptors->GetValue(descriptor);
-      // If the same constant function is being added we can simply
-      // transition to the target map.
-      if (constant_function == *value) {
-        self->set_map(transition_map);
-        return constant_function;
+        result = ConvertDescriptorToField(*name, *value, attributes);
+      } else {
+        ASSERT(details.type() == CONSTANT_FUNCTION);
+
+        Object* constant_function = descriptors->GetValue(descriptor);
+        if (constant_function == *value) {
+          // If the same constant function is being added we can simply
+          // transition to the target map.
+          self->set_map(transition_map);
+          result = constant_function;
+        } else {
+          // Otherwise, replace with a map transition to a new map with a FIELD,
+          // even if the value is a constant function.
+          result = ConvertTransitionToMapTransition(
+              lookup->GetTransitionIndex(), *name, *value, attributes);
+        }
       }
-      // Otherwise, replace with a map transition to a new map with a FIELD,
-      // even if the value is a constant function.
-      return ConvertTransitionToMapTransition(
-          result->GetTransitionIndex(), *name, *value, attributes);
+      break;
     }
     case HANDLER:
     case NONEXISTENT:
       UNREACHABLE();
-      return *value;
   }
-  UNREACHABLE();  // keep the compiler happy
-  return *value;
+
+  Handle<Object> hresult;
+  if (!result->ToHandle(&hresult)) return result;
+
+  if (FLAG_harmony_observation && map()->is_observed()) {
+    this->EnqueueChangeRecord("updated", name, old_value);
+  }
+
+  return *hresult;
 }
 
 
@@ -2969,13 +3017,13 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
   // interceptor calls.
   AssertNoContextChange ncc;
   Isolate* isolate = GetIsolate();
-  LookupResult result(isolate);
-  LocalLookup(name, &result);
-  if (!result.IsFound()) map()->LookupTransition(this, name, &result);
+  LookupResult lookup(isolate);
+  LocalLookup(name, &lookup);
+  if (!lookup.IsFound()) map()->LookupTransition(this, name, &lookup);
   // Check access rights if needed.
   if (IsAccessCheckNeeded()) {
     if (!isolate->MayNamedAccess(this, name, v8::ACCESS_SET)) {
-      return SetPropertyWithFailedAccessCheck(&result,
+      return SetPropertyWithFailedAccessCheck(&lookup,
                                               name,
                                               value,
                                               false,
@@ -2994,31 +3042,41 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
   }
 
   // Check for accessor in prototype chain removed here in clone.
-  if (!result.IsFound()) {
+  if (!lookup.IsFound()) {
     // Neither properties nor transitions found.
     return AddProperty(name, value, attributes, kNonStrictMode);
   }
 
+  Handle<Object> old_value(isolate->heap()->the_hole_value());
+  if (FLAG_harmony_observation && map()->is_observed()) {
+    // TODO(observe): save oldValue
+  }
+
   // Check of IsReadOnly removed from here in clone.
-  switch (result.type()) {
+  MaybeObject* result = value;
+  switch (lookup.type()) {
     case NORMAL: {
       PropertyDetails details = PropertyDetails(attributes, NORMAL);
-      return SetNormalizedProperty(name, value, details);
+      result = SetNormalizedProperty(name, value, details);
+      break;
     }
     case FIELD:
-      return FastPropertyAtPut(result.GetFieldIndex(), value);
+      result = FastPropertyAtPut(lookup.GetFieldIndex(), value);
+      break;
     case CONSTANT_FUNCTION:
       // Only replace the function if necessary.
-      if (value == result.GetConstantFunction()) return value;
+      if (value == lookup.GetConstantFunction()) return value;
       // Preserve the attributes of this existing property.
-      attributes = result.GetAttributes();
-      return ConvertDescriptorToField(name, value, attributes);
+      attributes = lookup.GetAttributes();
+      result = ConvertDescriptorToField(name, value, attributes);
+      break;
     case CALLBACKS:
     case INTERCEPTOR:
       // Override callback in clone
-      return ConvertDescriptorToField(name, value, attributes);
+      result = ConvertDescriptorToField(name, value, attributes);
+      break;
     case TRANSITION: {
-      Map* transition_map = result.GetTransitionTarget();
+      Map* transition_map = lookup.GetTransitionTarget();
       int descriptor = transition_map->LastAdded();
 
       DescriptorArray* descriptors = transition_map->instance_descriptors();
@@ -3027,29 +3085,40 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
       if (details.type() == FIELD) {
         if (attributes == details.attributes()) {
           int field_index = descriptors->GetFieldIndex(descriptor);
-          return AddFastPropertyUsingMap(transition_map,
-                                         name,
-                                         value,
-                                         field_index);
+          result = AddFastPropertyUsingMap(transition_map,
+                                           name,
+                                           value,
+                                           field_index);
+        } else {
+          result = ConvertDescriptorToField(name, value, attributes);
         }
-        return ConvertDescriptorToField(name, value, attributes);
       } else if (details.type() == CALLBACKS) {
-        return ConvertDescriptorToField(name, value, attributes);
-      }
-
-      ASSERT(details.type() == CONSTANT_FUNCTION);
+        result = ConvertDescriptorToField(name, value, attributes);
+      } else {
+        ASSERT(details.type() == CONSTANT_FUNCTION);
 
-      // Replace transition to CONSTANT FUNCTION with a map transition to a new
-      // map with a FIELD, even if the value is a function.
-      return ConvertTransitionToMapTransition(
-          result.GetTransitionIndex(), name, value, attributes);
+        // Replace transition to CONSTANT FUNCTION with a map transition to a
+        // new map with a FIELD, even if the value is a function.
+        result = ConvertTransitionToMapTransition(
+            lookup.GetTransitionIndex(), name, value, attributes);
+      }
+      break;
     }
     case HANDLER:
     case NONEXISTENT:
       UNREACHABLE();
   }
-  UNREACHABLE();  // keep the compiler happy
-  return value;
+
+  Handle<Object> hresult;
+  if (!result->ToHandle(&hresult)) return result;
+
+  if (FLAG_harmony_observation && map()->is_observed()) {
+    const char* type =
+        attributes == lookup.GetAttributes() ? "updated" : "reconfigured";
+    this->EnqueueChangeRecord(type, handle(name), old_value);
+  }
+
+  return *hresult;
 }
 
 
@@ -3953,38 +4022,55 @@ MaybeObject* JSObject::DeleteProperty(String* name, DeleteMode mode) {
   uint32_t index = 0;
   if (name->AsArrayIndex(&index)) {
     return DeleteElement(index, mode);
-  } else {
-    LookupResult result(isolate);
-    LocalLookup(name, &result);
-    if (!result.IsFound()) return isolate->heap()->true_value();
-    // Ignore attributes if forcing a deletion.
-    if (result.IsDontDelete() && mode != FORCE_DELETION) {
-      if (mode == STRICT_DELETION) {
-        // Deleting a non-configurable property in strict mode.
-        HandleScope scope(isolate);
-        Handle<Object> args[2] = { Handle<Object>(name), Handle<Object>(this) };
-        return isolate->Throw(*isolate->factory()->NewTypeError(
-            "strict_delete_property", HandleVector(args, 2)));
-      }
-      return isolate->heap()->false_value();
-    }
-    // Check for interceptor.
-    if (result.IsInterceptor()) {
-      // Skip interceptor if forcing a deletion.
-      if (mode == FORCE_DELETION) {
-        return DeletePropertyPostInterceptor(name, mode);
-      }
-      return DeletePropertyWithInterceptor(name);
+  }
+
+  LookupResult lookup(isolate);
+  LocalLookup(name, &lookup);
+  if (!lookup.IsFound()) return isolate->heap()->true_value();
+  // Ignore attributes if forcing a deletion.
+  if (lookup.IsDontDelete() && mode != FORCE_DELETION) {
+    if (mode == STRICT_DELETION) {
+      // Deleting a non-configurable property in strict mode.
+      HandleScope scope(isolate);
+      Handle<Object> args[2] = { Handle<Object>(name), Handle<Object>(this) };
+      return isolate->Throw(*isolate->factory()->NewTypeError(
+          "strict_delete_property", HandleVector(args, 2)));
+    }
+    return isolate->heap()->false_value();
+  }
+
+  HandleScope scope(isolate);
+  Handle<Object> old_value(isolate->heap()->the_hole_value());
+  if (FLAG_harmony_observation && map()->is_observed()) {
+    // TODO(observe): save oldValue
+  }
+  MaybeObject* result;
+
+  // Check for interceptor.
+  if (lookup.IsInterceptor()) {
+    // Skip interceptor if forcing a deletion.
+    if (mode == FORCE_DELETION) {
+      result = DeletePropertyPostInterceptor(name, mode);
+    } else {
+      result = DeletePropertyWithInterceptor(name);
     }
+  } else {
     // Normalize object if needed.
     Object* obj;
-    { MaybeObject* maybe_obj =
-          NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0);
-      if (!maybe_obj->ToObject(&obj)) return maybe_obj;
-    }
+    result = NormalizeProperties(CLEAR_INOBJECT_PROPERTIES, 0);
+    if (!result->ToObject(&obj)) return result;
     // Make sure the properties are normalized before removing the entry.
-    return DeleteNormalizedProperty(name, mode);
+    result = DeleteNormalizedProperty(name, mode);
   }
+
+  Handle<Object> hresult;
+  if (!result->ToHandle(&hresult)) return result;
+
+  if (FLAG_harmony_observation && map()->is_observed()) {
+    this->EnqueueChangeRecord("deleted", handle(name), old_value);
+  }
+
+  return *hresult;
 }
 
 
@@ -4612,10 +4698,29 @@ MaybeObject* JSObject::DefineAccessor(String* name,
 
   if (!CanSetCallback(name)) return isolate->heap()->undefined_value();
 
+  Handle<Object> old_value(isolate->heap()->the_hole_value());
+  bool preexists = false;
+  if (FLAG_harmony_observation && map()->is_observed()) {
+    LookupResult result(isolate);
+    LocalLookup(name, &result);
+    preexists = result.IsFound();
+    // TODO(observe): save oldValue
+  }
+
   uint32_t index = 0;
-  return name->AsArrayIndex(&index) ?
-      DefineElementAccessor(index, getter, setter, attributes) :
-      DefinePropertyAccessor(name, getter, setter, attributes);
+  MaybeObject* result = name->AsArrayIndex(&index)
+      ? DefineElementAccessor(index, getter, setter, attributes)
+      : DefinePropertyAccessor(name, getter, setter, attributes);
+
+  Handle<Object> hresult;
+  if (!result->ToHandle(&hresult)) return result;
+
+  if (FLAG_harmony_observation && map()->is_observed()) {
+    const char* type = preexists ? "reconfigured" : "new";
+    this->EnqueueChangeRecord(type, handle(name), old_value);
+  }
+
+  return *hresult;
 }
 
 
@@ -7517,6 +7622,7 @@ bool Map::EquivalentToForNormalization(Map* other,
     instance_type() == other->instance_type() &&
     bit_field() == other->bit_field() &&
     bit_field2() == other->bit_field2() &&
+    is_observed() == other->is_observed() &&
     function_with_prototype() == other->function_with_prototype();
 }
 
index 3e70fa9..87ae24a 100644 (file)
@@ -769,6 +769,13 @@ class MaybeObject BASE_EMBEDDED {
     return true;
   }
 
+  template<typename T>
+  inline bool ToHandle(Handle<T>* obj) {
+    if (IsFailure()) return false;
+    *obj = handle(T::cast(reinterpret_cast<Object*>(this)));
+    return true;
+  }
+
 #ifdef OBJECT_PRINT
   // Prints this object with details.
   inline void Print() {
@@ -1687,6 +1694,7 @@ class JSObject: public JSReceiver {
                              Handle<Object> getter,
                              Handle<Object> setter,
                              PropertyAttributes attributes);
+  // Can cause GC.
   MUST_USE_RESULT MaybeObject* DefineAccessor(String* name,
                                               Object* getter,
                                               Object* setter,
@@ -1762,6 +1770,7 @@ class JSObject: public JSReceiver {
 
   static Handle<Object> DeleteProperty(Handle<JSObject> obj,
                                        Handle<String> name);
+  // Can cause GC.
   MUST_USE_RESULT MaybeObject* DeleteProperty(String* name, DeleteMode mode);
 
   static Handle<Object> DeleteElement(Handle<JSObject> obj, uint32_t index);
@@ -2009,7 +2018,7 @@ class JSObject: public JSReceiver {
                                                Object* value,
                                                PropertyAttributes attributes);
 
-  // Add a property to an object.
+  // Add a property to an object. May cause GC.
   MUST_USE_RESULT MaybeObject* AddProperty(
       String* name,
       Object* value,
@@ -2277,6 +2286,11 @@ class JSObject: public JSReceiver {
   MUST_USE_RESULT MaybeObject* SetHiddenPropertiesHashTable(
       Object* value);
 
+  // Enqueue change record for Object.observe. May cause GC.
+  void EnqueueChangeRecord(const char* type,
+                           Handle<String> name,
+                           Handle<Object> old_value);
+
   DISALLOW_IMPLICIT_CONSTRUCTORS(JSObject);
 };
 
index a8fc88f..563333f 100644 (file)
@@ -13237,7 +13237,7 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_SetIsObserved) {
   if (obj->map()->is_observed() != is_observed) {
     MaybeObject* maybe = obj->map()->Copy();
     Map* map;
-    if (!maybe->To<Map>(&map)) return maybe;
+    if (!maybe->To(&map)) return maybe;
     map->set_is_observed(is_observed);
     obj->set_map(map);
   }
index e2e6429..2de76e9 100644 (file)
@@ -60,7 +60,7 @@ function InstallFunctions(object, attributes, functions) {
   %ToFastProperties(object);
 }
 
-// Prevents changes to the prototype of a built-infunction.
+// Prevents changes to the prototype of a built-in function.
 // The "prototype" property of the function object is made non-configurable,
 // and the prototype object is made non-extensible. The latter prevents
 // changing the __proto__ property.
index 07656d3..276f1fe 100644 (file)
@@ -25,7 +25,7 @@
 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-// Flags: --harmony-object-observe
+// Flags: --harmony-observation
 
 var allObservers = [];
 function reset() {
@@ -88,6 +88,7 @@ var recordCreated = false;
 Object.defineProperty(changeRecordWithAccessor, 'name', {
   get: function() {
     recordCreated = true;
+    return "bar";
   },
   enumerable: true
 })
@@ -103,6 +104,7 @@ assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
 // Object.notify
 assertThrows(function() { Object.notify(obj, {}); }, TypeError);
 assertThrows(function() { Object.notify(obj, { type: 4 }); }, TypeError);
+assertFalse(recordCreated);
 Object.notify(obj, changeRecordWithAccessor);
 assertFalse(recordCreated);
 
@@ -217,7 +219,7 @@ observer.assertCallbackRecords([
   { object: obj, type: 'foo', val: 5 }
 ]);
 
-// Observing multiple objects; records appear in order;.
+// Observing multiple objects; records appear in order.
 reset();
 var obj2 = {};
 var obj3 = {}
@@ -238,4 +240,36 @@ observer.assertCallbackRecords([
   { object: obj, type: 'foo' },
   { object: obj2, type: 'foo' },
   { object: obj3, type: 'foo' }
-]);
\ No newline at end of file
+]);
+
+// Observing named properties.
+reset();
+var obj = {a: 1}
+Object.observe(obj, observer.callback);
+obj.a = 2;
+obj["a"] = 3;
+delete obj.a;
+obj.a = 4;
+obj.a = 5;
+Object.defineProperty(obj, "a", {value: 6});
+Object.defineProperty(obj, "a", {writable: false});
+obj.a = 7;  // ignored
+Object.defineProperty(obj, "a", {value: 8});
+Object.defineProperty(obj, "a", {get: function() {}});
+delete obj.a;
+Object.defineProperty(obj, "a", {get: function() {}});
+Object.deliverChangeRecords(observer.callback);
+// TODO(observe): oldValue not included yet.
+observer.assertCallbackRecords([
+  { object: obj, name: "a", type: "updated" },
+  { object: obj, name: "a", type: "updated" },
+  { object: obj, name: "a", type: "deleted" },
+  { object: obj, name: "a", type: "new" },
+  { object: obj, name: "a", type: "updated" },
+  { object: obj, name: "a", type: "updated" },
+  { object: obj, name: "a", type: "reconfigured" },
+  { object: obj, name: "a", type: "updated" },
+  { object: obj, name: "a", type: "reconfigured" },
+  { object: obj, name: "a", type: "deleted" },
+  { object: obj, name: "a", type: "new" },
+]);