Revision: 12923
Author: [email protected]
Date: Fri Nov 9 08:14:42 2012
Log: 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 <[email protected]>.
http://code.google.com/p/v8/source/detail?r=12923
Modified:
/branches/bleeding_edge/src/messages.js
/branches/bleeding_edge/src/object-observe.js
/branches/bleeding_edge/test/cctest/test-object-observe.cc
/branches/bleeding_edge/test/mjsunit/harmony/object-observe.js
=======================================
--- /branches/bleeding_edge/src/messages.js Fri Nov 9 00:22:02 2012
+++ /branches/bleeding_edge/src/messages.js Fri Nov 9 08:14:42 2012
@@ -96,7 +96,8 @@
observe_non_object: ["Object.", "%0", " cannot ", "%0", "
non-object"],
observe_non_function: ["Object.", "%0", " cannot deliver to
non-function"],
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
invalid_array_length: ["Invalid array length"],
stack_overflow: ["Maximum call stack size exceeded"],
=======================================
--- /branches/bleeding_edge/src/object-observe.js Fri Nov 9 02:51:35 2012
+++ /branches/bleeding_edge/src/object-observe.js Fri Nov 9 08:14:42 2012
@@ -34,6 +34,7 @@
if (IS_UNDEFINED(observationState.observerInfoMap)) {
observationState.observerInfoMap = %CreateObjectHashTable();
observationState.objectInfoMap = %CreateObjectHashTable();
+ observationState.notifierTargetMap = %CreateObjectHashTable();
observationState.activeObservers = new InternalArray;
observationState.observerPriority = 0;
}
@@ -57,6 +58,16 @@
var observerInfoMap = new InternalObjectHashTable('observerInfoMap');
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) {
if (!IS_SPEC_OBJECT(object))
@@ -75,13 +86,8 @@
var objectInfo = objectInfoMap.get(object);
if (IS_UNDEFINED(objectInfo)) {
- // TODO: setup objectInfo.notifier
- objectInfo = {
- changeObservers: new InternalArray(callback)
- };
- objectInfoMap.set(object, objectInfo);
+ objectInfo = CreateObjectInfo(object);
%SetIsObserved(object, true);
- return;
}
var changeObservers = objectInfo.changeObservers;
@@ -130,17 +136,28 @@
EnqueueChangeRecord(changeRecord, objectInfo.changeObservers);
}
-function ObjectNotify(object, changeRecord) {
- // TODO: notifier needs to be [[THIS]]
+var notifierPrototype = {};
+
+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))
throw MakeTypeError("observe_type_non_string");
- var objectInfo = objectInfoMap.get(object);
+ var objectInfo = objectInfoMap.get(target);
if (IS_UNDEFINED(objectInfo))
return;
+ if (!objectInfo.changeObservers.length)
+ return;
+
var newRecord = {
- object: object // TODO: Needs to be 'object' retrieved from notifier
+ object: target
};
for (var prop in changeRecord) {
if (prop === 'object')
@@ -151,6 +168,27 @@
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) {
var observerInfo = observerInfoMap.get(observer);
@@ -190,10 +228,13 @@
%CheckIsBootstrapping();
InstallFunctions($Object, DONT_ENUM, $Array(
"deliverChangeRecords", ObjectDeliverChangeRecords,
- "notify", ObjectNotify, // TODO: Remove when getNotifier is
implemented.
+ "getNotifier", ObjectGetNotifier,
"observe", ObjectObserve,
"unobserve", ObjectUnobserve
));
+ InstallFunctions(notifierPrototype, DONT_ENUM, $Array(
+ "notify", ObjectNotifierNotify
+ ));
}
SetupObjectObserve();
=======================================
--- /branches/bleeding_edge/test/cctest/test-object-observe.cc Fri Nov 9
02:51:35 2012
+++ /branches/bleeding_edge/test/cctest/test-object-observe.cc Fri Nov 9
08:14:42 2012
@@ -64,20 +64,20 @@
Handle<Value> observer = CompileRun("observer");
Handle<Value> obj = CompileRun("obj");
Handle<Value> notify_fun1 = CompileRun(
- "(function() { Object.notify(obj, {type: 'a'}); })");
+ "(function() { obj.foo = 'bar'; })");
Handle<Value> notify_fun2;
{
LocalContext context2;
context2->Global()->Set(String::New("obj"), obj);
notify_fun2 = CompileRun(
- "(function() { Object.notify(obj, {type: 'b'}); })");
+ "(function() { obj.foo = 'baz'; })");
}
Handle<Value> notify_fun3;
{
LocalContext context3;
context3->Global()->Set(String::New("obj"), obj);
notify_fun3 = CompileRun(
- "(function() { Object.notify(obj, {type: 'c'}); })");
+ "(function() { obj.foo = 'bat'; })");
}
{
LocalContext context4;
@@ -100,7 +100,7 @@
"var count = 0;"
"var observer = function(records) { count = records.length };"
"Object.observe(obj, observer);"
- "Object.notify(obj, {type: 'a'});");
+ "obj.foo = 'bar';");
CHECK_EQ(1, CompileRun("count")->Int32Value());
}
@@ -118,7 +118,7 @@
"Object.observe(obj1, observer1);"
"Object.observe(obj1, observer2);"
"Object.observe(obj1, observer3);"
- "Object.notify(obj1, {type: 'a'});");
+ "obj1.foo = 'bar';");
CHECK_EQ(3, CompileRun("ordering.length")->Int32Value());
CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value());
CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
@@ -128,7 +128,7 @@
"Object.observe(obj2, observer3);"
"Object.observe(obj2, observer2);"
"Object.observe(obj2, observer1);"
- "Object.notify(obj2, {type: 'b'});");
+ "obj2.foo = 'baz'");
CHECK_EQ(3, CompileRun("ordering.length")->Int32Value());
CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value());
CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
@@ -146,7 +146,7 @@
"function observer1() { ordering.push(1); };"
"function observer2() {"
" if (!reentered) {"
- " Object.notify(obj, {type: 'b'});"
+ " obj.foo = 'baz';"
" reentered = true;"
" }"
" ordering.push(2);"
@@ -155,7 +155,7 @@
"Object.observe(obj, observer1);"
"Object.observe(obj, observer2);"
"Object.observe(obj, observer3);"
- "Object.notify(obj, {type: 'a'});");
+ "obj.foo = 'bar';");
CHECK_EQ(5, CompileRun("ordering.length")->Int32Value());
CHECK_EQ(1, CompileRun("ordering[0]")->Int32Value());
CHECK_EQ(2, CompileRun("ordering[1]")->Int32Value());
=======================================
--- /branches/bleeding_edge/test/mjsunit/harmony/object-observe.js Fri Nov
9 04:28:22 2012
+++ /branches/bleeding_edge/test/mjsunit/harmony/object-observe.js Fri Nov
9 08:14:42 2012
@@ -101,27 +101,42 @@
// Object.unobserve
assertThrows(function() { Object.unobserve(4, observer.callback); },
TypeError);
-// Object.notify
-assertThrows(function() { Object.notify(obj, {}); }, TypeError);
-assertThrows(function() { Object.notify(obj, { type: 4 }); }, TypeError);
+// Object.getNotifier
+var notifier = Object.getNotifier(obj);
+assertSame(notifier, Object.getNotifier(obj));
+assertEquals(null, Object.getNotifier(Object.freeze({})));
+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);
-Object.notify(obj, changeRecordWithAccessor);
-assertFalse(recordCreated);
+notifier.notify(changeRecordWithAccessor);
+assertFalse(recordCreated); // not observed yet
// Object.deliverChangeRecords
assertThrows(function() { Object.deliverChangeRecords(nonFunction); },
TypeError);
// Multiple records are delivered.
Object.observe(obj, observer.callback);
-Object.notify(obj, {
- object: obj,
+notifier.notify({
type: 'updated',
name: 'foo',
expando: 1
});
-Object.notify(obj, {
- object: obj,
+notifier.notify({
+ object: notifier, // object property is ignored
type: 'deleted',
name: 'bar',
expando2: 'str'
@@ -141,7 +156,7 @@
reset();
Object.observe(obj, observer.callback);
Object.observe(obj, observer.callback);
-Object.notify(obj, {
+Object.getNotifier(obj).notify({
type: 'foo',
});
Object.deliverChangeRecords(observer.callback);
@@ -150,7 +165,7 @@
// Observation can be stopped.
reset();
Object.unobserve(obj, observer.callback);
-Object.notify(obj, {
+Object.getNotifier(obj).notify({
type: 'foo',
});
Object.deliverChangeRecords(observer.callback);
@@ -160,7 +175,7 @@
reset();
Object.unobserve(obj, observer.callback);
Object.unobserve(obj, observer.callback);
-Object.notify(obj, {
+Object.getNotifier(obj).notify({
type: 'foo',
});
Object.deliverChangeRecords(observer.callback);
@@ -168,11 +183,11 @@
// Re-observation works and only includes changeRecords after of call.
reset();
-Object.notify(obj, {
+Object.getNotifier(obj).notify({
type: 'foo',
});
Object.observe(obj, observer.callback);
-Object.notify(obj, {
+Object.getNotifier(obj).notify({
type: 'foo',
});
records = undefined;
@@ -182,31 +197,31 @@
// Observing a continuous stream of changes, while itermittantly
unobserving.
reset();
Object.observe(obj, observer.callback);
-Object.notify(obj, {
+Object.getNotifier(obj).notify({
type: 'foo',
val: 1
});
Object.unobserve(obj, observer.callback);
-Object.notify(obj, {
+Object.getNotifier(obj).notify({
type: 'foo',
val: 2
});
Object.observe(obj, observer.callback);
-Object.notify(obj, {
+Object.getNotifier(obj).notify({
type: 'foo',
val: 3
});
Object.unobserve(obj, observer.callback);
-Object.notify(obj, {
+Object.getNotifier(obj).notify({
type: 'foo',
val: 4
});
Object.observe(obj, observer.callback);
-Object.notify(obj, {
+Object.getNotifier(obj).notify({
type: 'foo',
val: 5
});
@@ -226,13 +241,13 @@
Object.observe(obj, observer.callback);
Object.observe(obj3, observer.callback);
Object.observe(obj2, observer.callback);
-Object.notify(obj, {
+Object.getNotifier(obj).notify({
type: 'foo1',
});
-Object.notify(obj2, {
+Object.getNotifier(obj2).notify({
type: 'foo2',
});
-Object.notify(obj3, {
+Object.getNotifier(obj3).notify({
type: 'foo3',
});
Object.observe(obj3, observer.callback);
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev