Commit 6b16d0bc authored by rossberg@chromium.org's avatar rossberg@chromium.org

Make Object.observe on the global object functional

The approach in this change is to handle the unwrapping/wrapping of the global object transparently with respect to the JS implementation of Object.observe. An alternate approach would be to add a runtime method like %IsJSGlobalProxy and %UnwrapJSGlobalProxy, but it seems ugly to give JS (even implementation JS) access to the unwrapped global.

BUG=v8:2409

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@13142 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 23850c16
......@@ -49,7 +49,7 @@ InternalObjectHashTable.prototype = {
%ObjectHashTableSet(observationState[this.tableName], key, value);
},
has: function(key) {
return %ObjectHashTableHas(observationState[this.tableName], key);
return !IS_UNDEFINED(this.get(key));
}
};
......
......@@ -1753,6 +1753,9 @@ void JSObject::EnqueueChangeRecord(Handle<JSObject> object,
Isolate* isolate = object->GetIsolate();
HandleScope scope;
Handle<String> type = isolate->factory()->LookupAsciiSymbol(type_str);
if (object->IsJSGlobalObject()) {
object = handle(JSGlobalObject::cast(*object)->global_receiver(), isolate);
}
Handle<Object> args[] = { type, object, name, old_value };
bool threw;
Execution::Call(Handle<JSFunction>(isolate->observers_notify_change()),
......
......@@ -13415,6 +13415,12 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_HaveSameMap) {
RUNTIME_FUNCTION(MaybeObject*, Runtime_IsObserved) {
ASSERT(args.length() == 1);
CONVERT_ARG_CHECKED(JSReceiver, obj, 0);
if (obj->IsJSGlobalProxy()) {
Object* proto = obj->GetPrototype();
if (obj->IsNull()) return isolate->heap()->false_value();
ASSERT(proto->IsJSGlobalObject());
obj = JSReceiver::cast(proto);
}
return isolate->heap()->ToBoolean(obj->map()->is_observed());
}
......@@ -13423,6 +13429,12 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_SetIsObserved) {
ASSERT(args.length() == 2);
CONVERT_ARG_CHECKED(JSReceiver, obj, 0);
CONVERT_BOOLEAN_ARG_CHECKED(is_observed, 1);
if (obj->IsJSGlobalProxy()) {
Object* proto = obj->GetPrototype();
if (obj->IsNull()) return isolate->heap()->undefined_value();
ASSERT(proto->IsJSGlobalObject());
obj = JSReceiver::cast(proto);
}
if (obj->map()->is_observed() != is_observed) {
MaybeObject* maybe = obj->map()->Copy();
Map* map;
......@@ -13458,6 +13470,10 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_ObjectHashTableGet) {
ASSERT(args.length() == 2);
CONVERT_ARG_CHECKED(ObjectHashTable, table, 0);
Object* key = args[1];
if (key->IsJSGlobalProxy()) {
key = key->GetPrototype();
if (key->IsNull()) return isolate->heap()->undefined_value();
}
Object* lookup = table->Lookup(key);
return lookup->IsTheHole() ? isolate->heap()->undefined_value() : lookup;
}
......@@ -13468,21 +13484,15 @@ RUNTIME_FUNCTION(MaybeObject*, Runtime_ObjectHashTableSet) {
ASSERT(args.length() == 3);
CONVERT_ARG_HANDLE_CHECKED(ObjectHashTable, table, 0);
Handle<Object> key = args.at<Object>(1);
if (key->IsJSGlobalProxy()) {
key = handle(key->GetPrototype(), isolate);
if (key->IsNull()) return *table;
}
Handle<Object> value = args.at<Object>(2);
return *PutIntoObjectHashTable(table, key, value);
}
RUNTIME_FUNCTION(MaybeObject*, Runtime_ObjectHashTableHas) {
NoHandleAllocation ha;
ASSERT(args.length() == 2);
CONVERT_ARG_CHECKED(ObjectHashTable, table, 0);
Object* key = args[1];
Object* lookup = table->Lookup(key);
return isolate->heap()->ToBoolean(!lookup->IsTheHole());
}
// ----------------------------------------------------------------------------
// Implementation of Runtime
......
......@@ -332,7 +332,6 @@ namespace internal {
F(CreateObjectHashTable, 0, 1) \
F(ObjectHashTableGet, 2, 1) \
F(ObjectHashTableSet, 3, 1) \
F(ObjectHashTableHas, 2, 1) \
\
/* Statements */ \
F(NewClosure, 3, 1) \
......
......@@ -218,3 +218,63 @@ TEST(ObjectHashTableGrowth) {
CompileRun("obj.foo = 'bar'");
CHECK(CompileRun("ran")->BooleanValue());
}
TEST(GlobalObjectObservation) {
HarmonyIsolate isolate;
HandleScope scope;
LocalContext context;
Handle<Object> global_proxy = context->Global();
Handle<Object> inner_global = global_proxy->GetPrototype().As<Object>();
CompileRun(
"var records = [];"
"var global = this;"
"Object.observe(global, function(r) { [].push.apply(records, r) });"
"global.foo = 'hello';");
CHECK_EQ(1, CompileRun("records.length")->Int32Value());
CHECK(global_proxy->StrictEquals(CompileRun("records[0].object")));
// Detached, mutating the proxy has no effect.
context->DetachGlobal();
CompileRun("global.bar = 'goodbye';");
CHECK_EQ(1, CompileRun("records.length")->Int32Value());
// Mutating the global object directly still has an effect...
CompileRun("this.bar = 'goodbye';");
CHECK_EQ(2, CompileRun("records.length")->Int32Value());
CHECK(inner_global->StrictEquals(CompileRun("records[1].object")));
// Reattached, back to global proxy.
context->ReattachGlobal(global_proxy);
CompileRun("global.baz = 'again';");
CHECK_EQ(3, CompileRun("records.length")->Int32Value());
CHECK(global_proxy->StrictEquals(CompileRun("records[2].object")));
// Attached to a different context, should not leak mutations
// to the old context.
context->DetachGlobal();
{
LocalContext context2;
context2->DetachGlobal();
context2->ReattachGlobal(global_proxy);
CompileRun(
"var records2 = [];"
"Object.observe(this, function(r) { [].push.apply(records2, r) });"
"this.bat = 'context2';");
CHECK_EQ(1, CompileRun("records2.length")->Int32Value());
CHECK(global_proxy->StrictEquals(CompileRun("records2[0].object")));
}
CHECK_EQ(3, CompileRun("records.length")->Int32Value());
// Attaching by passing to Context::New
{
// Delegates to Context::New
LocalContext context3(NULL, Handle<ObjectTemplate>(), global_proxy);
CompileRun(
"var records3 = [];"
"Object.observe(this, function(r) { [].push.apply(records3, r) });"
"this.qux = 'context3';");
CHECK_EQ(1, CompileRun("records3.length")->Int32Value());
CHECK(global_proxy->StrictEquals(CompileRun("records3[0].object")));
}
CHECK_EQ(3, CompileRun("records.length")->Int32Value());
}
......@@ -32,6 +32,14 @@ function reset() {
allObservers.forEach(function(observer) { observer.reset(); });
}
function stringifyNoThrow(arg) {
try {
return JSON.stringify(arg);
} catch (e) {
return '{<circular reference>}';
}
}
function createObserver() {
"use strict"; // So that |this| in callback can be undefined.
......@@ -58,7 +66,7 @@ function createObserver() {
for (var i = 0; i < recs.length; i++) {
if ('name' in recs[i])
recs[i].name = String(recs[i].name);
print(i, JSON.stringify(this.records[i]), JSON.stringify(recs[i]));
print(i, stringifyNoThrow(this.records[i]), stringifyNoThrow(recs[i]));
assertSame(this.records[i].object, recs[i].object);
assertEquals('string', typeof recs[i].type);
assertPropertiesEqual(this.records[i], recs[i]);
......@@ -499,7 +507,7 @@ function createProxy(create, x) {
},
target: {isProxy: true},
callback: function(changeRecords) {
print("callback", JSON.stringify(handler.proxy), JSON.stringify(got));
print("callback", stringifyNoThrow(handler.proxy), stringifyNoThrow(got));
for (var i in changeRecords) {
var got = changeRecords[i];
var change = {object: handler.proxy, name: got.name, type: got.type};
......@@ -538,9 +546,7 @@ function blacklisted(obj, prop) {
// TODO(observe): oldValue when reconfiguring array length
(obj instanceof Array && prop === "length") ||
// TODO(observe): prototype property on functions
(obj instanceof Function && prop === "prototype") ||
// TODO(observe): global object
obj === this;
(obj instanceof Function && prop === "prototype")
}
for (var i in objects) for (var j in properties) {
......@@ -548,7 +554,7 @@ for (var i in objects) for (var j in properties) {
var prop = properties[j];
if (blacklisted(obj, prop)) continue;
var desc = Object.getOwnPropertyDescriptor(obj, prop);
print("***", typeof obj, JSON.stringify(obj), prop);
print("***", typeof obj, stringifyNoThrow(obj), prop);
if (!desc || desc.configurable)
TestObserveConfigurable(obj, prop);
else if (desc.writable)
......
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