Revision: 12867
Author:   [email protected]
Date:     Tue Nov  6 04:32:36 2012
Log:      Object.observe: generate change records for named properties.

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.

[email protected]
BUG=

Review URL: https://codereview.chromium.org/11347037
http://code.google.com/p/v8/source/detail?r=12867

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

=======================================
--- /branches/bleeding_edge/src/bootstrapper.cc Mon Nov  5 02:25:32 2012
+++ /branches/bleeding_edge/src/bootstrapper.cc Tue Nov  6 04:32:36 2012
@@ -1416,6 +1416,9 @@
     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 @@
                "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;
=======================================
--- /branches/bleeding_edge/src/contexts.h      Mon Nov  5 02:25:32 2012
+++ /branches/bleeding_edge/src/contexts.h      Tue Nov  6 04:32:36 2012
@@ -161,7 +161,8 @@
   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 @@
     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.
=======================================
--- /branches/bleeding_edge/src/flag-definitions.h      Tue Nov  6 03:54:05 2012
+++ /branches/bleeding_edge/src/flag-definitions.h      Tue Nov  6 04:32:36 2012
@@ -144,16 +144,16 @@
 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")
=======================================
--- /branches/bleeding_edge/src/handles.h       Wed Sep 12 09:43:57 2012
+++ /branches/bleeding_edge/src/handles.h       Tue Nov  6 04:32:36 2012
@@ -93,6 +93,13 @@
  private:
   T** location_;
 };
+
+
+// Convenience wrapper.
+template<class T>
+inline Handle<T> handle(T* t) {
+  return Handle<T>(t);
+}


 class DeferredHandles;
=======================================
--- /branches/bleeding_edge/src/ic.cc   Thu Oct 18 05:21:42 2012
+++ /branches/bleeding_edge/src/ic.cc   Tue Nov  6 04:32:36 2012
@@ -1376,6 +1376,11 @@
     RETURN_IF_EMPTY_HANDLE(isolate(), result);
     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
@@ -1902,7 +1907,8 @@
     }

     // 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);
=======================================
--- /branches/bleeding_edge/src/object-observe.js       Thu Oct 25 07:56:44 2012
+++ /branches/bleeding_edge/src/object-observe.js       Tue Nov  6 04:32:36 2012
@@ -68,6 +68,7 @@
       changeObservers: new InternalArray(callback)
     };
     objectInfoMap.set(object, objectInfo);
+    %SetIsObserved(object, true);
     return;
   }

@@ -108,6 +109,15 @@
     }
   }
 }
+
+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]]
@@ -119,7 +129,7 @@
     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')
=======================================
--- /branches/bleeding_edge/src/objects.cc      Mon Nov  5 07:37:04 2012
+++ /branches/bleeding_edge/src/objects.cc      Tue Nov  6 04:32:36 2012
@@ -1677,6 +1677,7 @@
   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 @@
                                  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);
   }
-  return 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 *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 @@
 }


-MaybeObject* JSObject::SetPropertyForResult(LookupResult* result,
+MaybeObject* JSObject::SetPropertyForResult(LookupResult* lookup,
                                             String* name_raw,
                                             Object* value_raw,
                                             PropertyAttributes attributes,
@@ -2829,7 +2857,7 @@
   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 @@
     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 @@
   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(
@@ -2869,35 +2898,45 @@
       return *value;
     }
   }
+
+  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 @@
       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);
-      }
+        result = ConvertDescriptorToField(*name, *value, attributes);
+      } else {
+        ASSERT(details.type() == CONSTANT_FUNCTION);

-      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;
+        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;
+  }
+
+  Handle<Object> hresult;
+  if (!result->ToHandle(&hresult)) return result;
+
+  if (FLAG_harmony_observation && map()->is_observed()) {
+    this->EnqueueChangeRecord("updated", name, old_value);
   }
-  UNREACHABLE();  // keep the compiler happy
-  return *value;
+
+  return *hresult;
 }


@@ -2969,13 +3017,13 @@
   // 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 @@
   }

   // 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 @@
       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 @@
   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();
+  }
+
+  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)));
     }
-    // Check for interceptor.
-    if (result.IsInterceptor()) {
-      // Skip interceptor if forcing a deletion.
-      if (mode == FORCE_DELETION) {
-        return DeletePropertyPostInterceptor(name, mode);
-      }
-      return DeletePropertyWithInterceptor(name);
+    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;
 }


@@ -4611,11 +4697,30 @@
   name->TryFlatten();

   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 @@
     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();
 }

=======================================
--- /branches/bleeding_edge/src/objects.h       Tue Nov  6 04:30:22 2012
+++ /branches/bleeding_edge/src/objects.h       Tue Nov  6 04:32:36 2012
@@ -768,6 +768,13 @@
     *obj = T::cast(reinterpret_cast<Object*>(this));
     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.
@@ -1687,6 +1694,7 @@
                              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 @@

   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 @@
                                                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 @@
   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);
 };

=======================================
--- /branches/bleeding_edge/src/runtime.cc      Tue Nov  6 04:30:22 2012
+++ /branches/bleeding_edge/src/runtime.cc      Tue Nov  6 04:32:36 2012
@@ -13237,7 +13237,7 @@
   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);
   }
=======================================
--- /branches/bleeding_edge/src/v8natives.js    Wed Sep  5 04:45:58 2012
+++ /branches/bleeding_edge/src/v8natives.js    Tue Nov  6 04:32:36 2012
@@ -60,7 +60,7 @@
   %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.
=======================================
--- /branches/bleeding_edge/test/mjsunit/harmony/object-observe.js Thu Oct 25 07:56:44 2012 +++ /branches/bleeding_edge/test/mjsunit/harmony/object-observe.js Tue Nov 6 04:32:36 2012
@@ -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 @@
 Object.defineProperty(changeRecordWithAccessor, 'name', {
   get: function() {
     recordCreated = true;
+    return "bar";
   },
   enumerable: true
 })
@@ -103,6 +104,7 @@
 // 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 @@
   { 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 = {}
@@ -239,3 +241,35 @@
   { object: obj2, type: 'foo' },
   { object: obj3, type: 'foo' }
 ]);
+
+// 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" },
+]);

--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev

Reply via email to