From e78081ca1ced52ebc46d7a00288088bf6ee97b01 Mon Sep 17 00:00:00 2001 From: "rafaelw@chromium.org" Date: Tue, 5 Nov 2013 12:25:32 +0000 Subject: [PATCH] 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 --- src/object-observe.js | 20 ++++-- src/objects.cc | 13 +++- src/v8natives.js | 2 +- test/mjsunit/harmony/object-observe.js | 95 ++++++++++++++++++++++++++ 4 files changed, 124 insertions(+), 6 deletions(-) diff --git a/src/object-observe.js b/src/object-observe.js index 9fd611ae8f..718920feb0 100644 --- a/src/object-observe.js +++ b/src/object-observe.js @@ -132,7 +132,8 @@ var defaultAcceptTypes = TypeMapCreateFromList([ 'updated', 'deleted', 'prototype', - 'reconfigured' + 'reconfigured', + 'preventExtensions' ]); // 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)) return; - var changeRecord = (arguments.length < 4) ? - { type: type, object: object, name: name } : - { type: type, object: object, name: name, oldValue: oldValue }; + var changeRecord; + if (arguments.length == 2) { + 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); ObjectInfoEnqueueInternalChangeRecord(objectInfo, changeRecord); } diff --git a/src/objects.cc b/src/objects.cc index 6f371a893e..cc73995ae7 100644 --- a/src/objects.cc +++ b/src/objects.cc @@ -2175,11 +2175,13 @@ void JSObject::EnqueueChangeRecord(Handle object, object = handle(JSGlobalObject::cast(*object)->global_receiver(), isolate); } Handle args[] = { type, object, name, old_value }; + int argc = name.is_null() ? 2 : old_value->IsTheHole() ? 3 : 4; bool threw; + Execution::Call(isolate, Handle(isolate->observers_notify_change()), isolate->factory()->undefined_value(), - old_value->IsTheHole() ? 3 : 4, args, + argc, args, &threw); ASSERT(!threw); } @@ -5442,6 +5444,9 @@ bool JSObject::ReferencesObject(Object* obj) { Handle JSObject::PreventExtensions(Handle object) { Isolate* isolate = object->GetIsolate(); + + if (!object->map()->is_extensible()) return object; + if (object->IsAccessCheckNeeded() && !isolate->MayNamedAccess(*object, isolate->heap()->undefined_value(), @@ -5484,6 +5489,11 @@ Handle JSObject::PreventExtensions(Handle object) { new_map->set_is_extensible(false); object->set_map(*new_map); ASSERT(!object->map()->is_extensible()); + + if (FLAG_harmony_observation && object->map()->is_observed()) { + EnqueueChangeRecord(object, "preventExtensions", Handle(), + isolate->factory()->the_hole_value()); + } return object; } @@ -5512,6 +5522,7 @@ static void FreezeDictionary(Dictionary* dictionary) { Handle JSObject::Freeze(Handle object) { // Freezing non-strict arguments should be handled elsewhere. ASSERT(!object->HasNonStrictArgumentsElements()); + ASSERT(!object->map()->is_observed()); if (object->map()->is_frozen()) return object; diff --git a/src/v8natives.js b/src/v8natives.js index c42d5c4d35..995e7d83ed 100644 --- a/src/v8natives.js +++ b/src/v8natives.js @@ -1249,7 +1249,7 @@ function ObjectFreeze(obj) { throw MakeTypeError("called_on_non_object", ["Object.freeze"]); } var isProxy = %IsJSProxy(obj); - if (isProxy || %HasNonStrictArgumentsElements(obj)) { + if (isProxy || %HasNonStrictArgumentsElements(obj) || %IsObserved(obj)) { if (isProxy) { ProxyFix(obj); } diff --git a/test/mjsunit/harmony/object-observe.js b/test/mjsunit/harmony/object-observe.js index 5615d9e3fd..fa123e3f8d 100644 --- a/test/mjsunit/harmony/object-observe.js +++ b/test/mjsunit/harmony/object-observe.js @@ -300,8 +300,103 @@ observer.assertCallbackRecords([ { 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. reset(); +var obj = {}; Object.observe(obj, observer.callback); Object.getNotifier(obj).notify({ type: 'updated',