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:
parent
5267d7b884
commit
e78081ca1c
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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',
|
||||||
|
Loading…
Reference in New Issue
Block a user