Commit b80cbd79 authored by rossberg@chromium.org's avatar rossberg@chromium.org

Object.observe: generate change records for named properties.

In more detail:
- Set observation bit for observed objects (and make NormalizedMapCache respect it).
- Mutation of observed objects is always delegated from ICs to runtime.
- Introduce JS runtime function for notifying generated changes.
- Invoke this function in the appropriate places (including some local refactoring).
- Inclusion of oldValue field is not yet implemented, nor element properties.

Also, shortened flag to --harmony-observation.

R=verwaest@chromium.org
BUG=

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@12867 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent beaccd74
......@@ -1416,6 +1416,9 @@ void Genesis::InstallExperimentalNativeFunctions() {
INSTALL_NATIVE(JSFunction, "DerivedSetTrap", derived_set_trap);
INSTALL_NATIVE(JSFunction, "ProxyEnumerate", proxy_enumerate);
}
if (FLAG_harmony_observation) {
INSTALL_NATIVE(JSFunction, "NotifyChange", observers_notify_change);
}
}
#undef INSTALL_NATIVE
......@@ -1829,7 +1832,7 @@ bool Genesis::InstallExperimentalNatives() {
"native collection.js") == 0) {
if (!CompileExperimentalBuiltin(isolate(), i)) return false;
}
if (FLAG_harmony_object_observe &&
if (FLAG_harmony_observation &&
strcmp(ExperimentalNatives::GetScriptName(i).start(),
"native object-observe.js") == 0) {
if (!CompileExperimentalBuiltin(isolate(), i)) return false;
......
......@@ -161,7 +161,8 @@ enum BindingFlags {
V(DERIVED_HAS_TRAP_INDEX, JSFunction, derived_has_trap) \
V(DERIVED_GET_TRAP_INDEX, JSFunction, derived_get_trap) \
V(DERIVED_SET_TRAP_INDEX, JSFunction, derived_set_trap) \
V(PROXY_ENUMERATE, JSFunction, proxy_enumerate) \
V(PROXY_ENUMERATE_INDEX, JSFunction, proxy_enumerate) \
V(OBSERVERS_NOTIFY_CHANGE_INDEX, JSFunction, observers_notify_change) \
V(RANDOM_SEED_INDEX, ByteArray, random_seed)
// JSFunctions are pairs (context, function code), sometimes also called
......@@ -288,7 +289,8 @@ class Context: public FixedArray {
DERIVED_HAS_TRAP_INDEX,
DERIVED_GET_TRAP_INDEX,
DERIVED_SET_TRAP_INDEX,
PROXY_ENUMERATE,
PROXY_ENUMERATE_INDEX,
OBSERVERS_NOTIFY_CHANGE_INDEX,
RANDOM_SEED_INDEX,
// Properties from here are treated as weak references by the full GC.
......
......@@ -144,16 +144,16 @@ DEFINE_bool(harmony_modules, false,
DEFINE_bool(harmony_proxies, false, "enable harmony proxies")
DEFINE_bool(harmony_collections, false,
"enable harmony collections (sets, maps, and weak maps)")
DEFINE_bool(harmony_object_observe, false,
DEFINE_bool(harmony_observation, false,
"enable harmony object observation (implies harmony collections")
DEFINE_bool(harmony, false, "enable all harmony features (except typeof)")
DEFINE_implication(harmony, harmony_scoping)
DEFINE_implication(harmony, harmony_modules)
DEFINE_implication(harmony, harmony_proxies)
DEFINE_implication(harmony, harmony_collections)
DEFINE_implication(harmony, harmony_object_observe)
DEFINE_implication(harmony, harmony_observation)
DEFINE_implication(harmony_modules, harmony_scoping)
DEFINE_implication(harmony_object_observe, harmony_collections)
DEFINE_implication(harmony_observation, harmony_collections)
// Flags for experimental implementation features.
DEFINE_bool(packed_arrays, true, "optimizes arrays that have no holes")
......
......@@ -95,6 +95,13 @@ class Handle {
};
// Convenience wrapper.
template<class T>
inline Handle<T> handle(T* t) {
return Handle<T>(t);
}
class DeferredHandles;
class HandleScopeImplementer;
......
......@@ -1377,6 +1377,11 @@ MaybeObject* StoreIC::Store(State state,
return *value;
}
// Observed objects are always modified through the runtime.
if (FLAG_harmony_observation && receiver->map()->is_observed()) {
return receiver->SetProperty(*name, *value, NONE, strict_mode);
}
// Use specialized code for setting the length of arrays with fast
// properties. Slow properties might indicate redefinition of the
// length property.
......@@ -1902,7 +1907,8 @@ MaybeObject* KeyedStoreIC::Store(State state,
}
// Update inline cache and stub cache.
if (FLAG_use_ic && !receiver->IsJSGlobalProxy()) {
if (FLAG_use_ic && !receiver->IsJSGlobalProxy() &&
!(FLAG_harmony_observation && receiver->map()->is_observed())) {
LookupResult lookup(isolate());
if (LookupForWrite(receiver, name, &lookup)) {
UpdateCaches(&lookup, state, strict_mode, receiver, name, value);
......
......@@ -68,6 +68,7 @@ function ObjectObserve(object, callback) {
changeObservers: new InternalArray(callback)
};
objectInfoMap.set(object, objectInfo);
%SetIsObserved(object, true);
return;
}
......@@ -109,6 +110,15 @@ function EnqueueChangeRecord(changeRecord, observers) {
}
}
function NotifyChange(type, object, name, oldValue) {
var objectInfo = objectInfoMap.get(object);
var changeRecord = (arguments.length < 4) ?
{ type: type, object: object, name: name } :
{ type: type, object: object, name: name, oldValue: oldValue };
InternalObjectFreeze(changeRecord);
EnqueueChangeRecord(changeRecord, objectInfo.changeObservers);
}
function ObjectNotify(object, changeRecord) {
// TODO: notifier needs to be [[THIS]]
if (!IS_STRING(changeRecord.type))
......@@ -119,7 +129,7 @@ function ObjectNotify(object, changeRecord) {
return;
var newRecord = {
object: object // TODO: Needs to be 'object' retreived from notifier
object: object // TODO: Needs to be 'object' retrieved from notifier
};
for (var prop in changeRecord) {
if (prop === 'object')
......@@ -161,4 +171,4 @@ function SetupObjectObserve() {
));
}
SetupObjectObserve();
\ No newline at end of file
SetupObjectObserve();
This diff is collapsed.
......@@ -769,6 +769,13 @@ class MaybeObject BASE_EMBEDDED {
return true;
}
template<typename T>
inline bool ToHandle(Handle<T>* obj) {
if (IsFailure()) return false;
*obj = handle(T::cast(reinterpret_cast<Object*>(this)));
return true;
}
#ifdef OBJECT_PRINT
// Prints this object with details.
inline void Print() {
......@@ -1687,6 +1694,7 @@ class JSObject: public JSReceiver {
Handle<Object> getter,
Handle<Object> setter,
PropertyAttributes attributes);
// Can cause GC.
MUST_USE_RESULT MaybeObject* DefineAccessor(String* name,
Object* getter,
Object* setter,
......@@ -1762,6 +1770,7 @@ class JSObject: public JSReceiver {
static Handle<Object> DeleteProperty(Handle<JSObject> obj,
Handle<String> name);
// Can cause GC.
MUST_USE_RESULT MaybeObject* DeleteProperty(String* name, DeleteMode mode);
static Handle<Object> DeleteElement(Handle<JSObject> obj, uint32_t index);
......@@ -2009,7 +2018,7 @@ class JSObject: public JSReceiver {
Object* value,
PropertyAttributes attributes);
// Add a property to an object.
// Add a property to an object. May cause GC.
MUST_USE_RESULT MaybeObject* AddProperty(
String* name,
Object* value,
......@@ -2277,6 +2286,11 @@ class JSObject: public JSReceiver {
MUST_USE_RESULT MaybeObject* SetHiddenPropertiesHashTable(
Object* value);
// Enqueue change record for Object.observe. May cause GC.
void EnqueueChangeRecord(const char* type,
Handle<String> name,
Handle<Object> old_value);
DISALLOW_IMPLICIT_CONSTRUCTORS(JSObject);
};
......
......@@ -13237,7 +13237,7 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_SetIsObserved) {
if (obj->map()->is_observed() != is_observed) {
MaybeObject* maybe = obj->map()->Copy();
Map* map;
if (!maybe->To<Map>(&map)) return maybe;
if (!maybe->To(&map)) return maybe;
map->set_is_observed(is_observed);
obj->set_map(map);
}
......
......@@ -60,7 +60,7 @@ function InstallFunctions(object, attributes, functions) {
%ToFastProperties(object);
}
// Prevents changes to the prototype of a built-infunction.
// Prevents changes to the prototype of a built-in function.
// The "prototype" property of the function object is made non-configurable,
// and the prototype object is made non-extensible. The latter prevents
// changing the __proto__ property.
......
......@@ -25,7 +25,7 @@
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// Flags: --harmony-object-observe
// Flags: --harmony-observation
var allObservers = [];
function reset() {
......@@ -88,6 +88,7 @@ var recordCreated = false;
Object.defineProperty(changeRecordWithAccessor, 'name', {
get: function() {
recordCreated = true;
return "bar";
},
enumerable: true
})
......@@ -103,6 +104,7 @@ assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
// Object.notify
assertThrows(function() { Object.notify(obj, {}); }, TypeError);
assertThrows(function() { Object.notify(obj, { type: 4 }); }, TypeError);
assertFalse(recordCreated);
Object.notify(obj, changeRecordWithAccessor);
assertFalse(recordCreated);
......@@ -217,7 +219,7 @@ observer.assertCallbackRecords([
{ object: obj, type: 'foo', val: 5 }
]);
// Observing multiple objects; records appear in order;.
// Observing multiple objects; records appear in order.
reset();
var obj2 = {};
var obj3 = {}
......@@ -238,4 +240,36 @@ observer.assertCallbackRecords([
{ object: obj, type: 'foo' },
{ object: obj2, type: 'foo' },
{ object: obj3, type: 'foo' }
]);
\ No newline at end of file
]);
// Observing named properties.
reset();
var obj = {a: 1}
Object.observe(obj, observer.callback);
obj.a = 2;
obj["a"] = 3;
delete obj.a;
obj.a = 4;
obj.a = 5;
Object.defineProperty(obj, "a", {value: 6});
Object.defineProperty(obj, "a", {writable: false});
obj.a = 7; // ignored
Object.defineProperty(obj, "a", {value: 8});
Object.defineProperty(obj, "a", {get: function() {}});
delete obj.a;
Object.defineProperty(obj, "a", {get: function() {}});
Object.deliverChangeRecords(observer.callback);
// TODO(observe): oldValue not included yet.
observer.assertCallbackRecords([
{ object: obj, name: "a", type: "updated" },
{ object: obj, name: "a", type: "updated" },
{ object: obj, name: "a", type: "deleted" },
{ object: obj, name: "a", type: "new" },
{ object: obj, name: "a", type: "updated" },
{ object: obj, name: "a", type: "updated" },
{ object: obj, name: "a", type: "reconfigured" },
{ object: obj, name: "a", type: "updated" },
{ object: obj, name: "a", type: "reconfigured" },
{ object: obj, name: "a", type: "deleted" },
{ object: obj, name: "a", type: "new" },
]);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment