Commit 0e7306cc authored by rossberg@chromium.org's avatar rossberg@chromium.org

Implement Object.getNotifier() and remove Object.notify()

Updated all tests to use getNotifier or actual object mutation instead of notify, and added tests for new behavior of getNotifier.

Review URL: https://codereview.chromium.org/11369154
Patch from Adam Klein <adamk@chromium.org>.

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@12923 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 237684fa
...@@ -96,7 +96,8 @@ var kMessages = { ...@@ -96,7 +96,8 @@ var kMessages = {
observe_non_object: ["Object.", "%0", " cannot ", "%0", " non-object"], observe_non_object: ["Object.", "%0", " cannot ", "%0", " non-object"],
observe_non_function: ["Object.", "%0", " cannot deliver to non-function"], observe_non_function: ["Object.", "%0", " cannot deliver to non-function"],
observe_callback_frozen: ["Object.observe cannot deliver to a frozen function object"], observe_callback_frozen: ["Object.observe cannot deliver to a frozen function object"],
observe_type_non_string: ["Object.notify provided changeRecord with non-string 'type' property"], observe_type_non_string: ["Invalid changeRecord with non-string 'type' property"],
observe_notify_non_notifier: ["notify called on non-notifier object"],
// RangeError // RangeError
invalid_array_length: ["Invalid array length"], invalid_array_length: ["Invalid array length"],
stack_overflow: ["Maximum call stack size exceeded"], stack_overflow: ["Maximum call stack size exceeded"],
......
...@@ -34,6 +34,7 @@ var observationState = %GetObservationState(); ...@@ -34,6 +34,7 @@ var observationState = %GetObservationState();
if (IS_UNDEFINED(observationState.observerInfoMap)) { if (IS_UNDEFINED(observationState.observerInfoMap)) {
observationState.observerInfoMap = %CreateObjectHashTable(); observationState.observerInfoMap = %CreateObjectHashTable();
observationState.objectInfoMap = %CreateObjectHashTable(); observationState.objectInfoMap = %CreateObjectHashTable();
observationState.notifierTargetMap = %CreateObjectHashTable();
observationState.activeObservers = new InternalArray; observationState.activeObservers = new InternalArray;
observationState.observerPriority = 0; observationState.observerPriority = 0;
} }
...@@ -57,6 +58,16 @@ InternalObjectHashTable.prototype = { ...@@ -57,6 +58,16 @@ InternalObjectHashTable.prototype = {
var observerInfoMap = new InternalObjectHashTable('observerInfoMap'); var observerInfoMap = new InternalObjectHashTable('observerInfoMap');
var objectInfoMap = new InternalObjectHashTable('objectInfoMap'); var objectInfoMap = new InternalObjectHashTable('objectInfoMap');
var notifierTargetMap = new InternalObjectHashTable('notifierTargetMap');
function CreateObjectInfo(object) {
var info = {
changeObservers: new InternalArray,
notifier: null,
};
objectInfoMap.set(object, info);
return info;
}
function ObjectObserve(object, callback) { function ObjectObserve(object, callback) {
if (!IS_SPEC_OBJECT(object)) if (!IS_SPEC_OBJECT(object))
...@@ -75,13 +86,8 @@ function ObjectObserve(object, callback) { ...@@ -75,13 +86,8 @@ function ObjectObserve(object, callback) {
var objectInfo = objectInfoMap.get(object); var objectInfo = objectInfoMap.get(object);
if (IS_UNDEFINED(objectInfo)) { if (IS_UNDEFINED(objectInfo)) {
// TODO: setup objectInfo.notifier objectInfo = CreateObjectInfo(object);
objectInfo = {
changeObservers: new InternalArray(callback)
};
objectInfoMap.set(object, objectInfo);
%SetIsObserved(object, true); %SetIsObserved(object, true);
return;
} }
var changeObservers = objectInfo.changeObservers; var changeObservers = objectInfo.changeObservers;
...@@ -130,17 +136,28 @@ function NotifyChange(type, object, name, oldValue) { ...@@ -130,17 +136,28 @@ function NotifyChange(type, object, name, oldValue) {
EnqueueChangeRecord(changeRecord, objectInfo.changeObservers); EnqueueChangeRecord(changeRecord, objectInfo.changeObservers);
} }
function ObjectNotify(object, changeRecord) { var notifierPrototype = {};
// TODO: notifier needs to be [[THIS]]
function ObjectNotifierNotify(changeRecord) {
if (!IS_SPEC_OBJECT(this))
throw MakeTypeError("called_on_non_object", ["notify"]);
var target = notifierTargetMap.get(this);
if (IS_UNDEFINED(target))
throw MakeTypeError("observe_notify_non_notifier");
if (!IS_STRING(changeRecord.type)) if (!IS_STRING(changeRecord.type))
throw MakeTypeError("observe_type_non_string"); throw MakeTypeError("observe_type_non_string");
var objectInfo = objectInfoMap.get(object); var objectInfo = objectInfoMap.get(target);
if (IS_UNDEFINED(objectInfo)) if (IS_UNDEFINED(objectInfo))
return; return;
if (!objectInfo.changeObservers.length)
return;
var newRecord = { var newRecord = {
object: object // TODO: Needs to be 'object' retrieved from notifier object: target
}; };
for (var prop in changeRecord) { for (var prop in changeRecord) {
if (prop === 'object') if (prop === 'object')
...@@ -152,6 +169,27 @@ function ObjectNotify(object, changeRecord) { ...@@ -152,6 +169,27 @@ function ObjectNotify(object, changeRecord) {
EnqueueChangeRecord(newRecord, objectInfo.changeObservers); EnqueueChangeRecord(newRecord, objectInfo.changeObservers);
} }
function ObjectGetNotifier(object) {
if (!IS_SPEC_OBJECT(object))
throw MakeTypeError("observe_non_object", ["getNotifier"]);
if (InternalObjectIsFrozen(object))
return null;
var objectInfo = objectInfoMap.get(object);
if (IS_UNDEFINED(objectInfo))
objectInfo = CreateObjectInfo(object);
if (IS_NULL(objectInfo.notifier)) {
objectInfo.notifier = {
__proto__: notifierPrototype
};
notifierTargetMap.set(objectInfo.notifier, object);
}
return objectInfo.notifier;
}
function DeliverChangeRecordsForObserver(observer) { function DeliverChangeRecordsForObserver(observer) {
var observerInfo = observerInfoMap.get(observer); var observerInfo = observerInfoMap.get(observer);
if (IS_UNDEFINED(observerInfo)) if (IS_UNDEFINED(observerInfo))
...@@ -190,10 +228,13 @@ function SetupObjectObserve() { ...@@ -190,10 +228,13 @@ function SetupObjectObserve() {
%CheckIsBootstrapping(); %CheckIsBootstrapping();
InstallFunctions($Object, DONT_ENUM, $Array( InstallFunctions($Object, DONT_ENUM, $Array(
"deliverChangeRecords", ObjectDeliverChangeRecords, "deliverChangeRecords", ObjectDeliverChangeRecords,
"notify", ObjectNotify, // TODO: Remove when getNotifier is implemented. "getNotifier", ObjectGetNotifier,
"observe", ObjectObserve, "observe", ObjectObserve,
"unobserve", ObjectUnobserve "unobserve", ObjectUnobserve
)); ));
InstallFunctions(notifierPrototype, DONT_ENUM, $Array(
"notify", ObjectNotifierNotify
));
} }
SetupObjectObserve(); SetupObjectObserve();
...@@ -64,20 +64,20 @@ TEST(PerIsolateState) { ...@@ -64,20 +64,20 @@ TEST(PerIsolateState) {
Handle<Value> observer = CompileRun("observer"); Handle<Value> observer = CompileRun("observer");
Handle<Value> obj = CompileRun("obj"); Handle<Value> obj = CompileRun("obj");
Handle<Value> notify_fun1 = CompileRun( Handle<Value> notify_fun1 = CompileRun(
"(function() { Object.notify(obj, {type: 'a'}); })"); "(function() { obj.foo = 'bar'; })");
Handle<Value> notify_fun2; Handle<Value> notify_fun2;
{ {
LocalContext context2; LocalContext context2;
context2->Global()->Set(String::New("obj"), obj); context2->Global()->Set(String::New("obj"), obj);
notify_fun2 = CompileRun( notify_fun2 = CompileRun(
"(function() { Object.notify(obj, {type: 'b'}); })"); "(function() { obj.foo = 'baz'; })");
} }
Handle<Value> notify_fun3; Handle<Value> notify_fun3;
{ {
LocalContext context3; LocalContext context3;
context3->Global()->Set(String::New("obj"), obj); context3->Global()->Set(String::New("obj"), obj);
notify_fun3 = CompileRun( notify_fun3 = CompileRun(
"(function() { Object.notify(obj, {type: 'c'}); })"); "(function() { obj.foo = 'bat'; })");
} }
{ {
LocalContext context4; LocalContext context4;
...@@ -100,7 +100,7 @@ TEST(EndOfMicrotaskDelivery) { ...@@ -100,7 +100,7 @@ TEST(EndOfMicrotaskDelivery) {
"var count = 0;" "var count = 0;"
"var observer = function(records) { count = records.length };" "var observer = function(records) { count = records.length };"
"Object.observe(obj, observer);" "Object.observe(obj, observer);"
"Object.notify(obj, {type: 'a'});"); "obj.foo = 'bar';");
CHECK_EQ(1, CompileRun("count")->Int32Value()); CHECK_EQ(1, CompileRun("count")->Int32Value());
} }
...@@ -118,7 +118,7 @@ TEST(DeliveryOrdering) { ...@@ -118,7 +118,7 @@ TEST(DeliveryOrdering) {
"Object.observe(obj1, observer1);" "Object.observe(obj1, observer1);"
"Object.observe(obj1, observer2);" "Object.observe(obj1, observer2);"
"Object.observe(obj1, observer3);" "Object.observe(obj1, observer3);"
"Object.notify(obj1, {type: 'a'});"); "obj1.foo = 'bar';");
CHECK_EQ(3, CompileRun("ordering.length")->Int32Value()); CHECK_EQ(3, CompileRun("ordering.length")->Int32Value());
CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value()); CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value());
CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value()); CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
...@@ -128,7 +128,7 @@ TEST(DeliveryOrdering) { ...@@ -128,7 +128,7 @@ TEST(DeliveryOrdering) {
"Object.observe(obj2, observer3);" "Object.observe(obj2, observer3);"
"Object.observe(obj2, observer2);" "Object.observe(obj2, observer2);"
"Object.observe(obj2, observer1);" "Object.observe(obj2, observer1);"
"Object.notify(obj2, {type: 'b'});"); "obj2.foo = 'baz'");
CHECK_EQ(3, CompileRun("ordering.length")->Int32Value()); CHECK_EQ(3, CompileRun("ordering.length")->Int32Value());
CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value()); CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value());
CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value()); CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
...@@ -146,7 +146,7 @@ TEST(DeliveryOrderingReentrant) { ...@@ -146,7 +146,7 @@ TEST(DeliveryOrderingReentrant) {
"function observer1() { ordering.push(1); };" "function observer1() { ordering.push(1); };"
"function observer2() {" "function observer2() {"
" if (!reentered) {" " if (!reentered) {"
" Object.notify(obj, {type: 'b'});" " obj.foo = 'baz';"
" reentered = true;" " reentered = true;"
" }" " }"
" ordering.push(2);" " ordering.push(2);"
...@@ -155,7 +155,7 @@ TEST(DeliveryOrderingReentrant) { ...@@ -155,7 +155,7 @@ TEST(DeliveryOrderingReentrant) {
"Object.observe(obj, observer1);" "Object.observe(obj, observer1);"
"Object.observe(obj, observer2);" "Object.observe(obj, observer2);"
"Object.observe(obj, observer3);" "Object.observe(obj, observer3);"
"Object.notify(obj, {type: 'a'});"); "obj.foo = 'bar';");
CHECK_EQ(5, CompileRun("ordering.length")->Int32Value()); CHECK_EQ(5, CompileRun("ordering.length")->Int32Value());
CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value()); CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value());
CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value()); CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
......
...@@ -101,27 +101,42 @@ assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError); ...@@ -101,27 +101,42 @@ assertThrows(function() { Object.observe(obj, frozenFunction); }, TypeError);
// Object.unobserve // Object.unobserve
assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError); assertThrows(function() { Object.unobserve(4, observer.callback); }, TypeError);
// Object.notify // Object.getNotifier
assertThrows(function() { Object.notify(obj, {}); }, TypeError); var notifier = Object.getNotifier(obj);
assertThrows(function() { Object.notify(obj, { type: 4 }); }, TypeError); assertSame(notifier, Object.getNotifier(obj));
assertFalse(recordCreated); assertEquals(null, Object.getNotifier(Object.freeze({})));
Object.notify(obj, changeRecordWithAccessor); assertFalse(notifier.hasOwnProperty('notify'));
assertEquals([], Object.keys(notifier));
var notifyDesc = Object.getOwnPropertyDescriptor(notifier.__proto__, 'notify');
assertTrue(notifyDesc.configurable);
assertTrue(notifyDesc.writable);
assertFalse(notifyDesc.enumerable);
assertThrows(function() { notifier.notify({}); }, TypeError);
assertThrows(function() { notifier.notify({ type: 4 }); }, TypeError);
var notify = notifier.notify;
assertThrows(function() { notify.call(undefined, { type: 'a' }); }, TypeError);
assertThrows(function() { notify.call(null, { type: 'a' }); }, TypeError);
assertThrows(function() { notify.call(5, { type: 'a' }); }, TypeError);
assertThrows(function() { notify.call('hello', { type: 'a' }); }, TypeError);
assertThrows(function() { notify.call(false, { type: 'a' }); }, TypeError);
assertThrows(function() { notify.call({}, { type: 'a' }); }, TypeError);
assertFalse(recordCreated); assertFalse(recordCreated);
notifier.notify(changeRecordWithAccessor);
assertFalse(recordCreated); // not observed yet
// Object.deliverChangeRecords // Object.deliverChangeRecords
assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError); assertThrows(function() { Object.deliverChangeRecords(nonFunction); }, TypeError);
// Multiple records are delivered. // Multiple records are delivered.
Object.observe(obj, observer.callback); Object.observe(obj, observer.callback);
Object.notify(obj, { notifier.notify({
object: obj,
type: 'updated', type: 'updated',
name: 'foo', name: 'foo',
expando: 1 expando: 1
}); });
Object.notify(obj, { notifier.notify({
object: obj, object: notifier, // object property is ignored
type: 'deleted', type: 'deleted',
name: 'bar', name: 'bar',
expando2: 'str' expando2: 'str'
...@@ -141,7 +156,7 @@ observer.assertNotCalled(); ...@@ -141,7 +156,7 @@ observer.assertNotCalled();
reset(); reset();
Object.observe(obj, observer.callback); Object.observe(obj, observer.callback);
Object.observe(obj, observer.callback); Object.observe(obj, observer.callback);
Object.notify(obj, { Object.getNotifier(obj).notify({
type: 'foo', type: 'foo',
}); });
Object.deliverChangeRecords(observer.callback); Object.deliverChangeRecords(observer.callback);
...@@ -150,7 +165,7 @@ observer.assertCalled(); ...@@ -150,7 +165,7 @@ observer.assertCalled();
// Observation can be stopped. // Observation can be stopped.
reset(); reset();
Object.unobserve(obj, observer.callback); Object.unobserve(obj, observer.callback);
Object.notify(obj, { Object.getNotifier(obj).notify({
type: 'foo', type: 'foo',
}); });
Object.deliverChangeRecords(observer.callback); Object.deliverChangeRecords(observer.callback);
...@@ -160,7 +175,7 @@ observer.assertNotCalled(); ...@@ -160,7 +175,7 @@ observer.assertNotCalled();
reset(); reset();
Object.unobserve(obj, observer.callback); Object.unobserve(obj, observer.callback);
Object.unobserve(obj, observer.callback); Object.unobserve(obj, observer.callback);
Object.notify(obj, { Object.getNotifier(obj).notify({
type: 'foo', type: 'foo',
}); });
Object.deliverChangeRecords(observer.callback); Object.deliverChangeRecords(observer.callback);
...@@ -168,11 +183,11 @@ observer.assertNotCalled(); ...@@ -168,11 +183,11 @@ observer.assertNotCalled();
// Re-observation works and only includes changeRecords after of call. // Re-observation works and only includes changeRecords after of call.
reset(); reset();
Object.notify(obj, { Object.getNotifier(obj).notify({
type: 'foo', type: 'foo',
}); });
Object.observe(obj, observer.callback); Object.observe(obj, observer.callback);
Object.notify(obj, { Object.getNotifier(obj).notify({
type: 'foo', type: 'foo',
}); });
records = undefined; records = undefined;
...@@ -182,31 +197,31 @@ observer.assertRecordCount(1); ...@@ -182,31 +197,31 @@ observer.assertRecordCount(1);
// Observing a continuous stream of changes, while itermittantly unobserving. // Observing a continuous stream of changes, while itermittantly unobserving.
reset(); reset();
Object.observe(obj, observer.callback); Object.observe(obj, observer.callback);
Object.notify(obj, { Object.getNotifier(obj).notify({
type: 'foo', type: 'foo',
val: 1 val: 1
}); });
Object.unobserve(obj, observer.callback); Object.unobserve(obj, observer.callback);
Object.notify(obj, { Object.getNotifier(obj).notify({
type: 'foo', type: 'foo',
val: 2 val: 2
}); });
Object.observe(obj, observer.callback); Object.observe(obj, observer.callback);
Object.notify(obj, { Object.getNotifier(obj).notify({
type: 'foo', type: 'foo',
val: 3 val: 3
}); });
Object.unobserve(obj, observer.callback); Object.unobserve(obj, observer.callback);
Object.notify(obj, { Object.getNotifier(obj).notify({
type: 'foo', type: 'foo',
val: 4 val: 4
}); });
Object.observe(obj, observer.callback); Object.observe(obj, observer.callback);
Object.notify(obj, { Object.getNotifier(obj).notify({
type: 'foo', type: 'foo',
val: 5 val: 5
}); });
...@@ -226,13 +241,13 @@ var obj3 = {} ...@@ -226,13 +241,13 @@ var obj3 = {}
Object.observe(obj, observer.callback); Object.observe(obj, observer.callback);
Object.observe(obj3, observer.callback); Object.observe(obj3, observer.callback);
Object.observe(obj2, observer.callback); Object.observe(obj2, observer.callback);
Object.notify(obj, { Object.getNotifier(obj).notify({
type: 'foo1', type: 'foo1',
}); });
Object.notify(obj2, { Object.getNotifier(obj2).notify({
type: 'foo2', type: 'foo2',
}); });
Object.notify(obj3, { Object.getNotifier(obj3).notify({
type: 'foo3', type: 'foo3',
}); });
Object.observe(obj3, observer.callback); Object.observe(obj3, observer.callback);
......
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