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
This commit is contained in:
parent
0f0da437e2
commit
fbc6e0d883
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
317
src/objects.cc
317
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<InterceptorInfo> interceptor(GetIndexedInterceptor());
|
||||
Handle<JSReceiver> hreceiver(receiver);
|
||||
Handle<JSObject> holder(this);
|
||||
CustomArguments args(isolate, interceptor->data(), receiver, this);
|
||||
v8::AccessorInfo info(args.end());
|
||||
if (!interceptor->query()->IsUndefined()) {
|
||||
v8::IndexedPropertyQuery query =
|
||||
v8::ToCData<v8::IndexedPropertyQuery>(interceptor->query());
|
||||
LOG(isolate,
|
||||
ApiIndexedPropertyAccess("interceptor-indexed-has", this, index));
|
||||
v8::Handle<v8::Integer> result;
|
||||
{
|
||||
// Leaving JavaScript.
|
||||
VMState state(isolate, EXTERNAL);
|
||||
result = query(index, info);
|
||||
}
|
||||
if (!result.IsEmpty())
|
||||
return static_cast<PropertyAttributes>(result->Int32Value());
|
||||
} else if (!interceptor->getter()->IsUndefined()) {
|
||||
v8::IndexedPropertyGetter getter =
|
||||
v8::ToCData<v8::IndexedPropertyGetter>(interceptor->getter());
|
||||
LOG(isolate,
|
||||
ApiIndexedPropertyAccess("interceptor-indexed-get-has", this, index));
|
||||
v8::Handle<v8::Value> 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<JSReceiver> hreceiver(receiver);
|
||||
Handle<JSObject> holder(this);
|
||||
PropertyAttributes attr = holder->GetElementsAccessor()->GetAttributes(
|
||||
*hreceiver, *holder, index);
|
||||
if (attr != ABSENT) return attr;
|
||||
|
||||
if (holder->IsStringObjectWithCharacterAt(index)) {
|
||||
return static_cast<PropertyAttributes>(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<JSObject> self(this);
|
||||
|
||||
Handle<String> name;
|
||||
Handle<Object> 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<Object> 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<InterceptorInfo> interceptor(GetIndexedInterceptor());
|
||||
Handle<JSReceiver> receiver_handle(receiver);
|
||||
Handle<JSObject> holder_handle(this);
|
||||
CustomArguments args(isolate, interceptor->data(), receiver, this);
|
||||
v8::AccessorInfo info(args.end());
|
||||
if (!interceptor->query()->IsUndefined()) {
|
||||
v8::IndexedPropertyQuery query =
|
||||
v8::ToCData<v8::IndexedPropertyQuery>(interceptor->query());
|
||||
LOG(isolate,
|
||||
ApiIndexedPropertyAccess("interceptor-indexed-has", this, index));
|
||||
v8::Handle<v8::Integer> 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<v8::IndexedPropertyGetter>(interceptor->getter());
|
||||
LOG(isolate,
|
||||
ApiIndexedPropertyAccess("interceptor-indexed-has-get", this, index));
|
||||
v8::Handle<v8::Value> 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<Object> JSObject::SetElement(Handle<JSObject> 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<JSObject> self(this);
|
||||
Handle<Object> 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<Object> 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<Object> receiver(this);
|
||||
Handle<Object> number = isolate->factory()->NewNumberFromUint(index);
|
||||
Handle<Object> args[] = { receiver, number };
|
||||
Handle<Object> args[] = { self, number };
|
||||
Handle<Object> 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();
|
||||
}
|
||||
|
||||
// Check for lookup interceptor
|
||||
if (HasIndexedInterceptor()) {
|
||||
return SetElementWithInterceptor(index,
|
||||
value,
|
||||
attributes,
|
||||
strict_mode,
|
||||
check_prototype,
|
||||
set_mode);
|
||||
// From here on, everything has to be handlified.
|
||||
Handle<String> name;
|
||||
Handle<Object> 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);
|
||||
}
|
||||
}
|
||||
|
||||
return SetElementWithoutInterceptor(index,
|
||||
value,
|
||||
attributes,
|
||||
strict_mode,
|
||||
check_prototype,
|
||||
set_mode);
|
||||
// Check for lookup interceptor
|
||||
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<Object> 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<Object> newValue = Object::GetElement(self, index);
|
||||
if (!newValue->SameValue(*old_value))
|
||||
self->EnqueueChangeRecord("updated", name, old_value);
|
||||
}
|
||||
}
|
||||
|
||||
return *hresult;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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<JSObject> object,
|
||||
Handle<String> 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,
|
||||
|
@ -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<Object> key = args.at<Object>(1);
|
||||
Handle<Object> value = args.at<Object>(2);
|
||||
PutIntoObjectHashTable(table, key, value);
|
||||
return isolate->heap()->undefined_value();
|
||||
return *PutIntoObjectHashTable(table, key, value);
|
||||
}
|
||||
|
||||
|
||||
|
@ -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" },
|
||||
]);
|
||||
|
Loading…
Reference in New Issue
Block a user