From fbc6e0d883062d05b3f5213b9c6db26788634c2f Mon Sep 17 00:00:00 2001 From: "rossberg@chromium.org" Date: Thu, 8 Nov 2012 12:58:08 +0000 Subject: [PATCH] Object.observe: generate change records for indexed properties. Details: - Extend ElementAccessors with GetAttributes method. - Add HasLocalElement, Get[Local]ElementAttribute methods to JSReceiver/JSObject. - Otherwise, mirror implementation for named properties. Cannot correctly handle the cases yet where an accessor is redefined or deleted. Also fixed handling of object info table. (Based on CL https://codereview.chromium.org/11362115/) R=verwaest@chromium.org,mstarzinger@chromium.org BUG= Review URL: https://codereview.chromium.org/11365111 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@12900 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- src/elements.cc | 61 +++++++ src/elements.h | 11 ++ src/object-observe.js | 2 +- src/objects-inl.h | 34 +++- src/objects.cc | 315 +++++++++++++++++++-------------- src/objects.h | 26 ++- src/runtime.cc | 7 +- test/mjsunit/harmony/object-observe.js | 58 +++++- 8 files changed, 367 insertions(+), 147 deletions(-) diff --git a/src/elements.cc b/src/elements.cc index 3d18829..89d3a7b 100644 --- a/src/elements.cc +++ b/src/elements.cc @@ -564,6 +564,29 @@ class ElementsAccessorBase : public ElementsAccessor { : backing_store->GetHeap()->the_hole_value(); } + MUST_USE_RESULT virtual PropertyAttributes GetAttributes( + Object* receiver, + JSObject* holder, + uint32_t key, + FixedArrayBase* backing_store) { + if (backing_store == NULL) { + backing_store = holder->elements(); + } + return ElementsAccessorSubclass::GetAttributesImpl( + receiver, holder, key, BackingStore::cast(backing_store)); + } + + MUST_USE_RESULT static PropertyAttributes GetAttributesImpl( + Object* receiver, + JSObject* obj, + uint32_t key, + BackingStore* backing_store) { + if (key >= ElementsAccessorSubclass::GetCapacityImpl(backing_store)) { + return ABSENT; + } + return backing_store->is_the_hole(key) ? ABSENT : NONE; + } + MUST_USE_RESULT virtual MaybeObject* SetLength(JSArray* array, Object* length) { return ElementsAccessorSubclass::SetLengthImpl( @@ -1143,6 +1166,16 @@ class ExternalElementsAccessor : backing_store->GetHeap()->undefined_value(); } + MUST_USE_RESULT static PropertyAttributes GetAttributesImpl( + Object* receiver, + JSObject* obj, + uint32_t key, + BackingStore* backing_store) { + return + key < ExternalElementsAccessorSubclass::GetCapacityImpl(backing_store) + ? NONE : ABSENT; + } + MUST_USE_RESULT static MaybeObject* SetLengthImpl( JSObject* obj, Object* length, @@ -1431,6 +1464,18 @@ class DictionaryElementsAccessor return obj->GetHeap()->the_hole_value(); } + MUST_USE_RESULT static PropertyAttributes GetAttributesImpl( + Object* receiver, + JSObject* obj, + uint32_t key, + SeededNumberDictionary* backing_store) { + int entry = backing_store->FindEntry(key); + if (entry != SeededNumberDictionary::kNotFound) { + return backing_store->DetailsAt(entry).attributes(); + } + return ABSENT; + } + static bool HasElementImpl(Object* receiver, JSObject* holder, uint32_t key, @@ -1490,6 +1535,22 @@ class NonStrictArgumentsElementsAccessor : public ElementsAccessorBase< } } + MUST_USE_RESULT static PropertyAttributes GetAttributesImpl( + Object* receiver, + JSObject* obj, + uint32_t key, + FixedArray* parameter_map) { + Object* probe = GetParameterMapArg(obj, parameter_map, key); + if (!probe->IsTheHole()) { + return NONE; + } else { + // If not aliased, check the arguments. + FixedArray* arguments = FixedArray::cast(parameter_map->get(1)); + return ElementsAccessor::ForArray(arguments)->GetAttributes( + receiver, obj, key, arguments); + } + } + MUST_USE_RESULT static MaybeObject* SetLengthImpl( JSObject* obj, Object* length, diff --git a/src/elements.h b/src/elements.h index 822fca5..8a83f0f 100644 --- a/src/elements.h +++ b/src/elements.h @@ -71,6 +71,17 @@ class ElementsAccessor { uint32_t key, FixedArrayBase* backing_store = NULL) = 0; + // Returns an element's attributes, or ABSENT if there is no such + // element. This method doesn't iterate up the prototype chain. The caller + // can optionally pass in the backing store to use for the check, which must + // be compatible with the ElementsKind of the ElementsAccessor. If + // backing_store is NULL, the holder->elements() is used as the backing store. + MUST_USE_RESULT virtual PropertyAttributes GetAttributes( + Object* receiver, + JSObject* holder, + uint32_t key, + FixedArrayBase* backing_store = NULL) = 0; + // Modifies the length data property as specified for JSArrays and resizes the // underlying backing store accordingly. The method honors the semantics of // changing array sizes as defined in EcmaScript 5.1 15.4.5.2, i.e. array that diff --git a/src/object-observe.js b/src/object-observe.js index bd621d8..5830de8 100644 --- a/src/object-observe.js +++ b/src/object-observe.js @@ -45,7 +45,7 @@ InternalObjectHashTable.prototype = { return %ObjectHashTableGet(this.table, key); }, set: function(key, value) { - return %ObjectHashTableSet(this.table, key, value); + this.table = %ObjectHashTableSet(this.table, key, value); }, has: function(key) { return %ObjectHashTableHas(this.table, key); diff --git a/src/objects-inl.h b/src/objects-inl.h index d6c5cc2..a88b83a 100644 --- a/src/objects-inl.h +++ b/src/objects-inl.h @@ -5053,6 +5053,11 @@ PropertyAttributes JSReceiver::GetPropertyAttribute(String* key) { } +PropertyAttributes JSReceiver::GetElementAttribute(uint32_t index) { + return GetElementAttributeWithReceiver(this, index, true); +} + + // TODO(504): this may be useful in other places too where JSGlobalProxy // is used. Object* JSObject::BypassGlobalProxy() { @@ -5077,7 +5082,34 @@ bool JSReceiver::HasElement(uint32_t index) { if (IsJSProxy()) { return JSProxy::cast(this)->HasElementWithHandler(index); } - return JSObject::cast(this)->HasElementWithReceiver(this, index); + return JSObject::cast(this)->GetElementAttribute(index) != ABSENT; +} + + +bool JSReceiver::HasLocalElement(uint32_t index) { + if (IsJSProxy()) { + return JSProxy::cast(this)->HasElementWithHandler(index); + } + return JSObject::cast(this)->GetLocalElementAttribute(index) != ABSENT; +} + + +PropertyAttributes JSReceiver::GetElementAttributeWithReceiver( + JSReceiver* receiver, uint32_t index, bool continue_search) { + if (IsJSProxy()) { + return JSProxy::cast(this)->GetElementAttributeWithHandler(receiver, index); + } + return JSObject::cast(this)->GetElementAttributeWithReceiver( + receiver, index, continue_search); +} + + +PropertyAttributes JSReceiver::GetLocalElementAttribute(uint32_t index) { + if (IsJSProxy()) { + return JSProxy::cast(this)->GetElementAttributeWithHandler(this, index); + } + return JSObject::cast(this)->GetElementAttributeWithReceiver( + this, index, false); } diff --git a/src/objects.cc b/src/objects.cc index 1518933..a310685 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -3227,8 +3227,8 @@ PropertyAttributes JSReceiver::GetPropertyAttributeWithReceiver( String* key) { uint32_t index = 0; if (IsJSObject() && key->AsArrayIndex(&index)) { - return JSObject::cast(this)->HasElementWithReceiver(receiver, index) - ? NONE : ABSENT; + return JSObject::cast(this)->GetElementAttributeWithReceiver( + receiver, index, true); } // Named property. LookupResult lookup(GetIsolate()); @@ -3278,8 +3278,7 @@ PropertyAttributes JSReceiver::GetLocalPropertyAttribute(String* name) { // Check whether the name is an array index. uint32_t index = 0; if (IsJSObject() && name->AsArrayIndex(&index)) { - if (JSObject::cast(this)->HasLocalElement(index)) return NONE; - return ABSENT; + return GetLocalElementAttribute(index); } // Named property. LookupResult lookup(GetIsolate()); @@ -3288,6 +3287,107 @@ PropertyAttributes JSReceiver::GetLocalPropertyAttribute(String* name) { } +PropertyAttributes JSObject::GetElementAttributeWithReceiver( + JSReceiver* receiver, uint32_t index, bool continue_search) { + Isolate* isolate = GetIsolate(); + + // Check access rights if needed. + if (IsAccessCheckNeeded()) { + if (!isolate->MayIndexedAccess(this, index, v8::ACCESS_HAS)) { + isolate->ReportFailedAccessCheck(this, v8::ACCESS_HAS); + return ABSENT; + } + } + + if (IsJSGlobalProxy()) { + Object* proto = GetPrototype(); + if (proto->IsNull()) return ABSENT; + ASSERT(proto->IsJSGlobalObject()); + return JSReceiver::cast(proto)->GetElementAttributeWithReceiver( + receiver, index, continue_search); + } + + // Check for lookup interceptor except when bootstrapping. + if (HasIndexedInterceptor() && !isolate->bootstrapper()->IsActive()) { + return GetElementAttributeWithInterceptor(receiver, index, continue_search); + } + + return GetElementAttributeWithoutInterceptor( + receiver, index, continue_search); +} + + +PropertyAttributes JSObject::GetElementAttributeWithInterceptor( + JSReceiver* receiver, uint32_t index, bool continue_search) { + Isolate* isolate = GetIsolate(); + // Make sure that the top context does not change when doing + // callbacks or interceptor calls. + AssertNoContextChange ncc; + HandleScope scope(isolate); + Handle interceptor(GetIndexedInterceptor()); + Handle hreceiver(receiver); + Handle holder(this); + CustomArguments args(isolate, interceptor->data(), receiver, this); + v8::AccessorInfo info(args.end()); + if (!interceptor->query()->IsUndefined()) { + v8::IndexedPropertyQuery query = + v8::ToCData(interceptor->query()); + LOG(isolate, + ApiIndexedPropertyAccess("interceptor-indexed-has", this, index)); + v8::Handle result; + { + // Leaving JavaScript. + VMState state(isolate, EXTERNAL); + result = query(index, info); + } + if (!result.IsEmpty()) + return static_cast(result->Int32Value()); + } else if (!interceptor->getter()->IsUndefined()) { + v8::IndexedPropertyGetter getter = + v8::ToCData(interceptor->getter()); + LOG(isolate, + ApiIndexedPropertyAccess("interceptor-indexed-get-has", this, index)); + v8::Handle result; + { + // Leaving JavaScript. + VMState state(isolate, EXTERNAL); + result = getter(index, info); + } + if (!result.IsEmpty()) return DONT_ENUM; + } + + return holder->GetElementAttributeWithoutInterceptor( + *hreceiver, index, continue_search); +} + + +PropertyAttributes JSObject::GetElementAttributeWithoutInterceptor( + JSReceiver* receiver, uint32_t index, bool continue_search) { + Isolate* isolate = GetIsolate(); + HandleScope scope(isolate); + Handle hreceiver(receiver); + Handle holder(this); + PropertyAttributes attr = holder->GetElementsAccessor()->GetAttributes( + *hreceiver, *holder, index); + if (attr != ABSENT) return attr; + + if (holder->IsStringObjectWithCharacterAt(index)) { + return static_cast(READ_ONLY | DONT_DELETE); + } + + if (!continue_search) return ABSENT; + + Object* pt = holder->GetPrototype(); + if (pt->IsJSProxy()) { + // We need to follow the spec and simulate a call to [[GetOwnProperty]]. + return JSProxy::cast(pt)->GetElementAttributeWithHandler(*hreceiver, index); + } + if (pt->IsNull()) return ABSENT; + return JSObject::cast(pt)->GetElementAttributeWithReceiver( + *hreceiver, index, true); +} + + MaybeObject* NormalizedMapCache::Get(JSObject* obj, PropertyNormalizationMode mode) { Isolate* isolate = obj->GetIsolate(); @@ -4009,15 +4109,39 @@ MaybeObject* JSObject::DeleteElement(uint32_t index, DeleteMode mode) { return JSGlobalObject::cast(proto)->DeleteElement(index, mode); } - if (HasIndexedInterceptor()) { - // Skip interceptor if forcing deletion. - if (mode != FORCE_DELETION) { - return DeleteElementWithInterceptor(index); + // From this point on everything needs to be handlified. + HandleScope scope(isolate); + Handle self(this); + + Handle name; + Handle old_value(isolate->heap()->the_hole_value()); + bool preexists = false; + if (FLAG_harmony_observation && map()->is_observed()) { + name = isolate->factory()->Uint32ToString(index); + preexists = self->HasLocalElement(index); + if (preexists) { + // TODO(observe): only read & set old_value if it's not an accessor + old_value = Object::GetElement(self, index); } - mode = JSReceiver::FORCE_DELETION; } - return GetElementsAccessor()->Delete(this, index, mode); + MaybeObject* result; + // Skip interceptor if forcing deletion. + if (self->HasIndexedInterceptor() && mode != FORCE_DELETION) { + result = self->DeleteElementWithInterceptor(index); + } else { + result = self->GetElementsAccessor()->Delete(*self, index, mode); + } + + Handle hresult; + if (!result->ToHandle(&hresult)) return result; + + if (FLAG_harmony_observation && map()->is_observed()) { + if (preexists && !self->HasLocalElement(index)) + self->EnqueueChangeRecord("deleted", name, old_value); + } + + return *hresult; } @@ -9410,64 +9534,7 @@ MaybeObject* JSObject::EnsureCanContainElements(Arguments* args, } -bool JSObject::HasElementWithInterceptor(JSReceiver* receiver, uint32_t index) { - Isolate* isolate = GetIsolate(); - // Make sure that the top context does not change when doing - // callbacks or interceptor calls. - AssertNoContextChange ncc; - HandleScope scope(isolate); - Handle interceptor(GetIndexedInterceptor()); - Handle receiver_handle(receiver); - Handle holder_handle(this); - CustomArguments args(isolate, interceptor->data(), receiver, this); - v8::AccessorInfo info(args.end()); - if (!interceptor->query()->IsUndefined()) { - v8::IndexedPropertyQuery query = - v8::ToCData(interceptor->query()); - LOG(isolate, - ApiIndexedPropertyAccess("interceptor-indexed-has", this, index)); - v8::Handle result; - { - // Leaving JavaScript. - VMState state(isolate, EXTERNAL); - result = query(index, info); - } - if (!result.IsEmpty()) { - ASSERT(result->IsInt32()); - return true; // absence of property is signaled by empty handle. - } - } else if (!interceptor->getter()->IsUndefined()) { - v8::IndexedPropertyGetter getter = - v8::ToCData(interceptor->getter()); - LOG(isolate, - ApiIndexedPropertyAccess("interceptor-indexed-has-get", this, index)); - v8::Handle result; - { - // Leaving JavaScript. - VMState state(isolate, EXTERNAL); - result = getter(index, info); - } - if (!result.IsEmpty()) return true; - } - - if (holder_handle->GetElementsAccessor()->HasElement( - *receiver_handle, *holder_handle, index)) { - return true; - } - - if (holder_handle->IsStringObjectWithCharacterAt(index)) return true; - Object* pt = holder_handle->GetPrototype(); - if (pt->IsJSProxy()) { - // We need to follow the spec and simulate a call to [[GetOwnProperty]]. - return JSProxy::cast(pt)->GetElementAttributeWithHandler( - receiver, index) != ABSENT; - } - if (pt->IsNull()) return false; - return JSObject::cast(pt)->HasElementWithReceiver(*receiver_handle, index); -} - - -JSObject::LocalElementType JSObject::HasLocalElement(uint32_t index) { +JSObject::LocalElementType JSObject::GetLocalElementType(uint32_t index) { // Check access rights if needed. if (IsAccessCheckNeeded()) { Heap* heap = GetHeap(); @@ -9481,13 +9548,13 @@ JSObject::LocalElementType JSObject::HasLocalElement(uint32_t index) { Object* proto = GetPrototype(); if (proto->IsNull()) return UNDEFINED_ELEMENT; ASSERT(proto->IsJSGlobalObject()); - return JSObject::cast(proto)->HasLocalElement(index); + return JSObject::cast(proto)->GetLocalElementType(index); } // Check for lookup interceptor if (HasIndexedInterceptor()) { - return HasElementWithInterceptor(this, index) ? INTERCEPTED_ELEMENT - : UNDEFINED_ELEMENT; + return GetElementAttributeWithInterceptor(this, index, false) != ABSENT + ? INTERCEPTED_ELEMENT : UNDEFINED_ELEMENT; } // Handle [] on String objects. @@ -9576,40 +9643,6 @@ JSObject::LocalElementType JSObject::HasLocalElement(uint32_t index) { } -bool JSObject::HasElementWithReceiver(JSReceiver* receiver, uint32_t index) { - // Check access rights if needed. - if (IsAccessCheckNeeded()) { - Heap* heap = GetHeap(); - if (!heap->isolate()->MayIndexedAccess(this, index, v8::ACCESS_HAS)) { - heap->isolate()->ReportFailedAccessCheck(this, v8::ACCESS_HAS); - return false; - } - } - - // Check for lookup interceptor - if (HasIndexedInterceptor()) { - return HasElementWithInterceptor(receiver, index); - } - - ElementsAccessor* accessor = GetElementsAccessor(); - if (accessor->HasElement(receiver, this, index)) { - return true; - } - - // Handle [] on String objects. - if (this->IsStringObjectWithCharacterAt(index)) return true; - - Object* pt = GetPrototype(); - if (pt->IsNull()) return false; - if (pt->IsJSProxy()) { - // We need to follow the spec and simulate a call to [[GetOwnProperty]]. - return JSProxy::cast(pt)->GetElementAttributeWithHandler( - receiver, index) != ABSENT; - } - return JSObject::cast(pt)->HasElementWithReceiver(receiver, index); -} - - MaybeObject* JSObject::SetElementWithInterceptor(uint32_t index, Object* value, PropertyAttributes attributes, @@ -10207,28 +10240,31 @@ Handle JSObject::SetElement(Handle object, MaybeObject* JSObject::SetElement(uint32_t index, - Object* value, + Object* value_raw, PropertyAttributes attributes, StrictModeFlag strict_mode, bool check_prototype, SetPropertyMode set_mode) { + Isolate* isolate = GetIsolate(); + HandleScope scope(isolate); + Handle self(this); + Handle value(value_raw); + // Check access rights if needed. if (IsAccessCheckNeeded()) { Heap* heap = GetHeap(); - if (!heap->isolate()->MayIndexedAccess(this, index, v8::ACCESS_SET)) { - HandleScope scope(heap->isolate()); - Handle value_handle(value); - heap->isolate()->ReportFailedAccessCheck(this, v8::ACCESS_SET); - return *value_handle; + if (!heap->isolate()->MayIndexedAccess(*self, index, v8::ACCESS_SET)) { + heap->isolate()->ReportFailedAccessCheck(*self, v8::ACCESS_SET); + return *value; } } if (IsJSGlobalProxy()) { Object* proto = GetPrototype(); - if (proto->IsNull()) return value; + if (proto->IsNull()) return *value; ASSERT(proto->IsJSGlobalObject()); return JSObject::cast(proto)->SetElement(index, - value, + *value, attributes, strict_mode, check_prototype, @@ -10237,10 +10273,8 @@ MaybeObject* JSObject::SetElement(uint32_t index, // Don't allow element properties to be redefined for external arrays. if (HasExternalArrayElements() && set_mode == DEFINE_PROPERTY) { - Isolate* isolate = GetHeap()->isolate(); - Handle receiver(this); Handle number = isolate->factory()->NewNumberFromUint(index); - Handle args[] = { receiver, number }; + Handle args[] = { self, number }; Handle error = isolate->factory()->NewTypeError( "redef_external_array_element", HandleVector(args, ARRAY_SIZE(args))); return isolate->Throw(*error); @@ -10255,22 +10289,45 @@ MaybeObject* JSObject::SetElement(uint32_t index, dictionary->set_requires_slow_elements(); } + // From here on, everything has to be handlified. + Handle name; + Handle old_value(isolate->heap()->the_hole_value()); + PropertyAttributes old_attributes; + bool preexists = false; + if (FLAG_harmony_observation && map()->is_observed()) { + name = isolate->factory()->Uint32ToString(index); + preexists = self->HasLocalElement(index); + if (preexists) { + old_attributes = self->GetLocalPropertyAttribute(*name); + // TODO(observe): only read & set old_value if we have a data property + old_value = Object::GetElement(self, index); + } + } + // Check for lookup interceptor - if (HasIndexedInterceptor()) { - return SetElementWithInterceptor(index, - value, - attributes, - strict_mode, - check_prototype, - set_mode); + MaybeObject* result = self->HasIndexedInterceptor() + ? self->SetElementWithInterceptor( + index, *value, attributes, strict_mode, check_prototype, set_mode) + : self->SetElementWithoutInterceptor( + index, *value, attributes, strict_mode, check_prototype, set_mode); + + Handle hresult; + if (!result->ToHandle(&hresult)) return result; + + if (FLAG_harmony_observation && map()->is_observed()) { + PropertyAttributes new_attributes = self->GetLocalPropertyAttribute(*name); + if (!preexists) { + self->EnqueueChangeRecord("new", name, old_value); + } else if (new_attributes != old_attributes || old_value->IsTheHole()) { + self->EnqueueChangeRecord("reconfigured", name, old_value); + } else { + Handle newValue = Object::GetElement(self, index); + if (!newValue->SameValue(*old_value)) + self->EnqueueChangeRecord("updated", name, old_value); + } } - return SetElementWithoutInterceptor(index, - value, - attributes, - strict_mode, - check_prototype, - set_mode); + return *hresult; } diff --git a/src/objects.h b/src/objects.h index 7e47850..f245d97 100644 --- a/src/objects.h +++ b/src/objects.h @@ -1483,10 +1483,18 @@ class JSReceiver: public HeapObject { String* name); PropertyAttributes GetLocalPropertyAttribute(String* name); + inline PropertyAttributes GetElementAttribute(uint32_t index); + inline PropertyAttributes GetElementAttributeWithReceiver( + JSReceiver* receiver, + uint32_t index, + bool continue_search); + inline PropertyAttributes GetLocalElementAttribute(uint32_t index); + // Can cause a GC. inline bool HasProperty(String* name); inline bool HasLocalProperty(String* name); inline bool HasElement(uint32_t index); + inline bool HasLocalElement(uint32_t index); // Return the object's prototype (might be Heap::null_value()). inline Object* GetPrototype(); @@ -1701,6 +1709,9 @@ class JSObject: public JSReceiver { LookupResult* result, String* name, bool continue_search); + PropertyAttributes GetElementAttributeWithReceiver(JSReceiver* receiver, + uint32_t index, + bool continue_search); static void DefineAccessor(Handle object, Handle name, @@ -1822,9 +1833,6 @@ class JSObject: public JSReceiver { // be represented as a double and not a Smi. bool ShouldConvertToFastDoubleElements(bool* has_smi_only_elements); - // Tells whether the index'th element is present. - bool HasElementWithReceiver(JSReceiver* receiver, uint32_t index); - // Computes the new capacity when expanding the elements of a JSObject. static int NewElementsCapacity(int old_capacity) { // (old_capacity + 50%) + 16 @@ -1849,9 +1857,7 @@ class JSObject: public JSReceiver { DICTIONARY_ELEMENT }; - LocalElementType HasLocalElement(uint32_t index); - - bool HasElementWithInterceptor(JSReceiver* receiver, uint32_t index); + LocalElementType GetLocalElementType(uint32_t index); MUST_USE_RESULT MaybeObject* SetFastElement(uint32_t index, Object* value, @@ -2209,6 +2215,14 @@ class JSObject: public JSReceiver { Object* structure, uint32_t index, Object* holder); + MUST_USE_RESULT PropertyAttributes GetElementAttributeWithInterceptor( + JSReceiver* receiver, + uint32_t index, + bool continue_search); + MUST_USE_RESULT PropertyAttributes GetElementAttributeWithoutInterceptor( + JSReceiver* receiver, + uint32_t index, + bool continue_search); MUST_USE_RESULT MaybeObject* SetElementWithCallback( Object* structure, uint32_t index, diff --git a/src/runtime.cc b/src/runtime.cc index c6d1388..7ecd6a0 100644 --- a/src/runtime.cc +++ b/src/runtime.cc @@ -1091,7 +1091,7 @@ static MaybeObject* GetOwnProperty(Isolate* isolate, // This could be an element. uint32_t index; if (name->AsArrayIndex(&index)) { - switch (obj->HasLocalElement(index)) { + switch (obj->GetLocalElementType(index)) { case JSObject::UNDEFINED_ELEMENT: return heap->undefined_value(); @@ -4699,7 +4699,7 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_IsPropertyEnumerable) { uint32_t index; if (key->AsArrayIndex(&index)) { - JSObject::LocalElementType type = object->HasLocalElement(index); + JSObject::LocalElementType type = object->GetLocalElementType(index); switch (type) { case JSObject::UNDEFINED_ELEMENT: case JSObject::STRING_CHARACTER_ELEMENT: @@ -13291,8 +13291,7 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_ObjectHashTableSet) { CONVERT_ARG_HANDLE_CHECKED(ObjectHashTable, table, 0); Handle key = args.at(1); Handle value = args.at(2); - PutIntoObjectHashTable(table, key, value); - return isolate->heap()->undefined_value(); + return *PutIntoObjectHashTable(table, key, value); } diff --git a/test/mjsunit/harmony/object-observe.js b/test/mjsunit/harmony/object-observe.js index 12d154b..27cf2f9 100644 --- a/test/mjsunit/harmony/object-observe.js +++ b/test/mjsunit/harmony/object-observe.js @@ -227,19 +227,20 @@ Object.observe(obj, observer.callback); Object.observe(obj3, observer.callback); Object.observe(obj2, observer.callback); Object.notify(obj, { - type: 'foo', + type: 'foo1', }); Object.notify(obj2, { - type: 'foo', + type: 'foo2', }); Object.notify(obj3, { - type: 'foo', + type: 'foo3', }); +Object.observe(obj3, observer.callback); Object.deliverChangeRecords(observer.callback); observer.assertCallbackRecords([ - { object: obj, type: 'foo' }, - { object: obj2, type: 'foo' }, - { object: obj3, type: 'foo' } + { object: obj, type: 'foo1' }, + { object: obj2, type: 'foo2' }, + { object: obj3, type: 'foo3' } ]); // Observing named properties. @@ -286,3 +287,48 @@ observer.assertCallbackRecords([ { object: obj, name: "a", type: "deleted", oldValue: 10 }, { object: obj, name: "a", type: "new" }, ]); + +// Observing indexed properties. +reset(); +var obj = {'1': 1} +Object.observe(obj, observer.callback); +obj[1] = 2; +obj[1] = 3; +delete obj[1]; +obj[1] = 4; +obj[1] = 4; // ignored +obj[1] = 5; +Object.defineProperty(obj, "1", {value: 6}); +Object.defineProperty(obj, "1", {writable: false}); +obj[1] = 7; // ignored +Object.defineProperty(obj, "1", {value: 8}); +Object.defineProperty(obj, "1", {value: 7, writable: true}); +Object.defineProperty(obj, "1", {get: function() {}}); +delete obj[1]; +delete obj[1]; +Object.defineProperty(obj, "1", {get: function() {}, configurable: true}); +Object.defineProperty(obj, "1", {value: 9, writable: true}); +obj[1] = 10; +delete obj[1]; +Object.defineProperty(obj, "1", {value: 11, configurable: true}); +Object.deliverChangeRecords(observer.callback); +observer.assertCallbackRecords([ + { object: obj, name: "1", type: "updated", oldValue: 1 }, + { object: obj, name: "1", type: "updated", oldValue: 2 }, + { object: obj, name: "1", type: "deleted", oldValue: 3 }, + { object: obj, name: "1", type: "new" }, + { object: obj, name: "1", type: "updated", oldValue: 4 }, + { object: obj, name: "1", type: "updated", oldValue: 5 }, + { object: obj, name: "1", type: "reconfigured", oldValue: 6 }, + { object: obj, name: "1", type: "updated", oldValue: 6 }, + { object: obj, name: "1", type: "reconfigured", oldValue: 8 }, + { object: obj, name: "1", type: "reconfigured", oldValue: 7 }, + // TODO(observe): oldValue should not be present below. + { object: obj, name: "1", type: "deleted", oldValue: undefined }, + { object: obj, name: "1", type: "new" }, + // TODO(observe): oldValue should be absent below, and type = "reconfigured". + { object: obj, name: "1", type: "updated", oldValue: undefined }, + { object: obj, name: "1", type: "updated", oldValue: 9 }, + { object: obj, name: "1", type: "deleted", oldValue: 10 }, + { object: obj, name: "1", type: "new" }, +]); -- 2.7.4