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