Make Object.freeze/seal/preventExtensions observable

Note: spec has been updated here: http://wiki.ecmascript.org/doku.php?id=harmony:observe_spec_changes.

R=rossberg@chromium.org, rossberg
BUG=v8:2975,v8:2941

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@17481 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
rafaelw@chromium.org 2013-11-05 12:25:32 +00:00
parent 5267d7b884
commit e78081ca1c
4 changed files with 124 additions and 6 deletions

View File

@ -132,7 +132,8 @@ var defaultAcceptTypes = TypeMapCreateFromList([
'updated', 'updated',
'deleted', 'deleted',
'prototype', 'prototype',
'reconfigured' 'reconfigured',
'preventExtensions'
]); ]);
// An Observer is a registration to observe an object by a callback with // An Observer is a registration to observe an object by a callback with
@ -463,9 +464,20 @@ function NotifyChange(type, object, name, oldValue) {
if (!ObjectInfoHasActiveObservers(objectInfo)) if (!ObjectInfoHasActiveObservers(objectInfo))
return; return;
var changeRecord = (arguments.length < 4) ? var changeRecord;
{ type: type, object: object, name: name } : if (arguments.length == 2) {
{ type: type, object: object, name: name, oldValue: oldValue }; changeRecord = { type: type, object: object };
} else if (arguments.length == 3) {
changeRecord = { type: type, object: object, name: name };
} else {
changeRecord = {
type: type,
object: object,
name: name,
oldValue: oldValue
};
}
ObjectFreeze(changeRecord); ObjectFreeze(changeRecord);
ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord); ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord);
} }

View File

@ -2175,11 +2175,13 @@ void JSObject::EnqueueChangeRecord(Handle<JSObject> object,
object = handle(JSGlobalObject::cast(*object)->global_receiver(), isolate); object = handle(JSGlobalObject::cast(*object)->global_receiver(), isolate);
} }
Handle<Object> args[] = { type, object, name, old_value }; Handle<Object> args[] = { type, object, name, old_value };
int argc = name.is_null() ? 2 : old_value->IsTheHole() ? 3 : 4;
bool threw; bool threw;
Execution::Call(isolate, Execution::Call(isolate,
Handle<JSFunction>(isolate->observers_notify_change()), Handle<JSFunction>(isolate->observers_notify_change()),
isolate->factory()->undefined_value(), isolate->factory()->undefined_value(),
old_value->IsTheHole() ? 3 : 4, args, argc, args,
&threw); &threw);
ASSERT(!threw); ASSERT(!threw);
} }
@ -5442,6 +5444,9 @@ bool JSObject::ReferencesObject(Object* obj) {
Handle<Object> JSObject::PreventExtensions(Handle<JSObject> object) { Handle<Object> JSObject::PreventExtensions(Handle<JSObject> object) {
Isolate* isolate = object->GetIsolate(); Isolate* isolate = object->GetIsolate();
if (!object->map()->is_extensible()) return object;
if (object->IsAccessCheckNeeded() && if (object->IsAccessCheckNeeded() &&
!isolate->MayNamedAccess(*object, !isolate->MayNamedAccess(*object,
isolate->heap()->undefined_value(), isolate->heap()->undefined_value(),
@ -5484,6 +5489,11 @@ Handle<Object> JSObject::PreventExtensions(Handle<JSObject> object) {
new_map->set_is_extensible(false); new_map->set_is_extensible(false);
object->set_map(*new_map); object->set_map(*new_map);
ASSERT(!object->map()->is_extensible()); ASSERT(!object->map()->is_extensible());
if (FLAG_harmony_observation && object->map()->is_observed()) {
EnqueueChangeRecord(object, "preventExtensions", Handle<Name>(),
isolate->factory()->the_hole_value());
}
return object; return object;
} }
@ -5512,6 +5522,7 @@ static void FreezeDictionary(Dictionary* dictionary) {
Handle<Object> JSObject::Freeze(Handle<JSObject> object) { Handle<Object> JSObject::Freeze(Handle<JSObject> object) {
// Freezing non-strict arguments should be handled elsewhere. // Freezing non-strict arguments should be handled elsewhere.
ASSERT(!object->HasNonStrictArgumentsElements()); ASSERT(!object->HasNonStrictArgumentsElements());
ASSERT(!object->map()->is_observed());
if (object->map()->is_frozen()) return object; if (object->map()->is_frozen()) return object;

View File

@ -1249,7 +1249,7 @@ function ObjectFreeze(obj) {
throw MakeTypeError("called_on_non_object", ["Object.freeze"]); throw MakeTypeError("called_on_non_object", ["Object.freeze"]);
} }
var isProxy = %IsJSProxy(obj); var isProxy = %IsJSProxy(obj);
if (isProxy || %HasNonStrictArgumentsElements(obj)) { if (isProxy || %HasNonStrictArgumentsElements(obj) || %IsObserved(obj)) {
if (isProxy) { if (isProxy) {
ProxyFix(obj); ProxyFix(obj);
} }

View File

@ -300,8 +300,103 @@ observer.assertCallbackRecords([
{ object: obj, type: 'deleted', name: '', oldValue: ' ' }, { object: obj, type: 'deleted', name: '', oldValue: ' ' },
]); ]);
// Object.preventExtensions
reset();
var obj = { foo: 'bar'};
Object.observe(obj, observer.callback);
obj.baz = 'bat';
Object.preventExtensions(obj);
Object.deliverChangeRecords(observer.callback);
observer.assertCallbackRecords([
{ object: obj, type: 'new', name: 'baz' },
{ object: obj, type: 'preventExtensions' },
]);
reset();
var obj = { foo: 'bar'};
Object.preventExtensions(obj);
Object.observe(obj, observer.callback);
Object.preventExtensions(obj);
Object.deliverChangeRecords(observer.callback);
observer.assertNotCalled();
// Object.freeze
reset();
var obj = { a: 'a' };
Object.defineProperty(obj, 'b', {
writable: false,
configurable: true,
value: 'b'
});
Object.defineProperty(obj, 'c', {
writable: true,
configurable: false,
value: 'c'
});
Object.defineProperty(obj, 'd', {
writable: false,
configurable: false,
value: 'd'
});
Object.observe(obj, observer.callback);
Object.freeze(obj);
Object.deliverChangeRecords(observer.callback);
observer.assertCallbackRecords([
{ object: obj, type: 'reconfigured', name: 'a' },
{ object: obj, type: 'reconfigured', name: 'b' },
{ object: obj, type: 'reconfigured', name: 'c' },
{ object: obj, type: 'preventExtensions' },
]);
reset();
var obj = { foo: 'bar'};
Object.freeze(obj);
Object.observe(obj, observer.callback);
Object.freeze(obj);
Object.deliverChangeRecords(observer.callback);
observer.assertNotCalled();
// Object.seal
reset();
var obj = { a: 'a' };
Object.defineProperty(obj, 'b', {
writable: false,
configurable: true,
value: 'b'
});
Object.defineProperty(obj, 'c', {
writable: true,
configurable: false,
value: 'c'
});
Object.defineProperty(obj, 'd', {
writable: false,
configurable: false,
value: 'd'
});
Object.observe(obj, observer.callback);
Object.seal(obj);
Object.deliverChangeRecords(observer.callback);
observer.assertCallbackRecords([
{ object: obj, type: 'reconfigured', name: 'a' },
{ object: obj, type: 'reconfigured', name: 'b' },
{ object: obj, type: 'preventExtensions' },
]);
reset();
var obj = { foo: 'bar'};
Object.seal(obj);
Object.observe(obj, observer.callback);
Object.seal(obj);
Object.deliverChangeRecords(observer.callback);
observer.assertNotCalled();
// Observing a continuous stream of changes, while itermittantly unobserving. // Observing a continuous stream of changes, while itermittantly unobserving.
reset(); reset();
var obj = {};
Object.observe(obj, observer.callback); Object.observe(obj, observer.callback);
Object.getNotifier(obj).notify({ Object.getNotifier(obj).notify({
type: 'updated', type: 'updated',