Commit 9a0623f2 authored by rossberg@chromium.org's avatar rossberg@chromium.org

Object.observe support for Function 'prototype' property

BUG=v8:2409

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

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@13177 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 1aa2891c
...@@ -465,24 +465,46 @@ MaybeObject* Accessors::FunctionGetPrototype(Object* object, void*) { ...@@ -465,24 +465,46 @@ MaybeObject* Accessors::FunctionGetPrototype(Object* object, void*) {
MaybeObject* Accessors::FunctionSetPrototype(JSObject* object, MaybeObject* Accessors::FunctionSetPrototype(JSObject* object,
Object* value, Object* value_raw,
void*) { void*) {
Heap* heap = object->GetHeap(); Isolate* isolate = object->GetIsolate();
JSFunction* function = FindInstanceOf<JSFunction>(object); Heap* heap = isolate->heap();
if (function == NULL) return heap->undefined_value(); JSFunction* function_raw = FindInstanceOf<JSFunction>(object);
if (!function->should_have_prototype()) { if (function_raw == NULL) return heap->undefined_value();
if (!function_raw->should_have_prototype()) {
// Since we hit this accessor, object will have no prototype property. // Since we hit this accessor, object will have no prototype property.
return object->SetLocalPropertyIgnoreAttributes(heap->prototype_symbol(), return object->SetLocalPropertyIgnoreAttributes(heap->prototype_symbol(),
value, value_raw,
NONE); NONE);
} }
Object* prototype; HandleScope scope(isolate);
{ MaybeObject* maybe_prototype = function->SetPrototype(value); Handle<JSFunction> function(function_raw, isolate);
if (!maybe_prototype->ToObject(&prototype)) return maybe_prototype; Handle<Object> value(value_raw, isolate);
Handle<Object> old_value;
bool is_observed =
FLAG_harmony_observation &&
*function == object &&
function->map()->is_observed();
if (is_observed) {
if (function->has_prototype())
old_value = handle(function->prototype(), isolate);
else
old_value = isolate->factory()->NewFunctionPrototype(function);
}
Handle<Object> result;
MaybeObject* maybe_result = function->SetPrototype(*value);
if (!maybe_result->ToHandle(&result, isolate)) return maybe_result;
ASSERT(function->prototype() == *value);
if (is_observed && !old_value->SameValue(*value)) {
JSObject::EnqueueChangeRecord(
function, "updated", isolate->factory()->prototype_symbol(), old_value);
} }
ASSERT(function->prototype() == value);
return function; return *function;
} }
......
...@@ -27,6 +27,7 @@ ...@@ -27,6 +27,7 @@
#include "v8.h" #include "v8.h"
#include "accessors.h"
#include "api.h" #include "api.h"
#include "arguments.h" #include "arguments.h"
#include "bootstrapper.h" #include "bootstrapper.h"
...@@ -3107,8 +3108,17 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes( ...@@ -3107,8 +3108,17 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
Handle<Object> old_value(isolate->heap()->the_hole_value(), isolate); Handle<Object> old_value(isolate->heap()->the_hole_value(), isolate);
PropertyAttributes old_attributes = ABSENT; PropertyAttributes old_attributes = ABSENT;
if (FLAG_harmony_observation && map()->is_observed()) { bool is_observed = FLAG_harmony_observation && self->map()->is_observed();
old_value = handle(lookup.GetLazyValue(), isolate); if (is_observed) {
// Function prototypes are stored specially
if (self->IsJSFunction() &&
JSFunction::cast(*self)->should_have_prototype() &&
name->Equals(isolate->heap()->prototype_symbol())) {
MaybeObject* maybe = Accessors::FunctionGetPrototype(*self, NULL);
if (!maybe->ToHandle(&old_value, isolate)) return maybe;
} else {
old_value = handle(lookup.GetLazyValue(), isolate);
}
old_attributes = lookup.GetAttributes(); old_attributes = lookup.GetAttributes();
} }
...@@ -3172,7 +3182,7 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes( ...@@ -3172,7 +3182,7 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
Handle<Object> hresult; Handle<Object> hresult;
if (!result->ToHandle(&hresult, isolate)) return result; if (!result->ToHandle(&hresult, isolate)) return result;
if (FLAG_harmony_observation && map()->is_observed()) { if (is_observed) {
if (lookup.IsTransition()) { if (lookup.IsTransition()) {
EnqueueChangeRecord(self, "new", name, old_value); EnqueueChangeRecord(self, "new", name, old_value);
} else { } else {
......
...@@ -455,7 +455,7 @@ function TestObserveConfigurable(obj, prop) { ...@@ -455,7 +455,7 @@ function TestObserveConfigurable(obj, prop) {
delete obj[prop]; delete obj[prop];
} }
function TestObserveNonConfigurable(obj, prop) { function TestObserveNonConfigurable(obj, prop, desc) {
reset(); reset();
obj[prop] = 1; obj[prop] = 1;
Object.observe(obj, observer.callback); Object.observe(obj, observer.callback);
...@@ -465,10 +465,10 @@ function TestObserveNonConfigurable(obj, prop) { ...@@ -465,10 +465,10 @@ function TestObserveNonConfigurable(obj, prop) {
Object.defineProperty(obj, prop, {value: 6}); Object.defineProperty(obj, prop, {value: 6});
Object.defineProperty(obj, prop, {value: 6}); // ignored Object.defineProperty(obj, prop, {value: 6}); // ignored
Object.defineProperty(obj, prop, {value: 7}); Object.defineProperty(obj, prop, {value: 7});
Object.defineProperty(obj, prop, {enumerable: true}); // ignored Object.defineProperty(obj, prop,
{enumerable: desc.enumerable}); // ignored
Object.defineProperty(obj, prop, {writable: false}); Object.defineProperty(obj, prop, {writable: false});
obj[prop] = 7; // ignored obj[prop] = 7; // ignored
Object.defineProperty(obj, prop, {get: function() {}}); // ignored
Object.deliverChangeRecords(observer.callback); Object.deliverChangeRecords(observer.callback);
observer.assertCallbackRecords([ observer.assertCallbackRecords([
{ object: obj, name: prop, type: "updated", oldValue: 1 }, { object: obj, name: prop, type: "updated", oldValue: 1 },
...@@ -544,9 +544,7 @@ function blacklisted(obj, prop) { ...@@ -544,9 +544,7 @@ function blacklisted(obj, prop) {
(obj instanceof Int32Array && prop === "length") || (obj instanceof Int32Array && prop === "length") ||
(obj instanceof ArrayBuffer && prop == 1) || (obj instanceof ArrayBuffer && prop == 1) ||
// TODO(observe): oldValue when reconfiguring array length // TODO(observe): oldValue when reconfiguring array length
(obj instanceof Array && prop === "length") || (obj instanceof Array && prop === "length")
// TODO(observe): prototype property on functions
(obj instanceof Function && prop === "prototype")
} }
for (var i in objects) for (var j in properties) { for (var i in objects) for (var j in properties) {
...@@ -558,7 +556,7 @@ for (var i in objects) for (var j in properties) { ...@@ -558,7 +556,7 @@ for (var i in objects) for (var j in properties) {
if (!desc || desc.configurable) if (!desc || desc.configurable)
TestObserveConfigurable(obj, prop); TestObserveConfigurable(obj, prop);
else if (desc.writable) else if (desc.writable)
TestObserveNonConfigurable(obj, prop); TestObserveNonConfigurable(obj, prop, desc);
} }
...@@ -817,6 +815,7 @@ observer.assertCallbackRecords([ ...@@ -817,6 +815,7 @@ observer.assertCallbackRecords([
{ object: array, name: 'length', type: 'updated', oldValue: 1}, { object: array, name: 'length', type: 'updated', oldValue: 1},
]); ]);
// __proto__ // __proto__
reset(); reset();
var obj = {}; var obj = {};
...@@ -836,3 +835,39 @@ observer.assertCallbackRecords([ ...@@ -836,3 +835,39 @@ observer.assertCallbackRecords([
{ object: obj, name: '__proto__', type: 'prototype', oldValue: p }, { object: obj, name: '__proto__', type: 'prototype', oldValue: p },
{ object: obj, name: '__proto__', type: 'prototype', oldValue: null }, { object: obj, name: '__proto__', type: 'prototype', oldValue: null },
]); ]);
// Function.prototype
reset();
var fun = function(){};
Object.observe(fun, observer.callback);
var myproto = {foo: 'bar'};
fun.prototype = myproto;
fun.prototype = 7;
fun.prototype = 7; // ignored
Object.defineProperty(fun, 'prototype', {value: 8});
Object.deliverChangeRecords(observer.callback);
observer.assertRecordCount(3);
// Manually examine the first record in order to test
// lazy creation of oldValue
assertSame(fun, observer.records[0].object);
assertEquals('prototype', observer.records[0].name);
assertEquals('updated', observer.records[0].type);
// The only existing reference to the oldValue object is in this
// record, so to test that lazy creation happened correctly
// we compare its constructor to our function (one of the invariants
// ensured when creating an object via AllocateFunctionPrototype).
assertSame(fun, observer.records[0].oldValue.constructor);
observer.records.splice(0, 1);
observer.assertCallbackRecords([
{ object: fun, name: 'prototype', type: 'updated', oldValue: myproto },
{ object: fun, name: 'prototype', type: 'updated', oldValue: 7 },
]);
// Function.prototype should not be observable except on the object itself
reset();
var fun = function(){};
var obj = { __proto__: fun };
Object.observe(obj, observer.callback);
obj.prototype = 7;
Object.deliverChangeRecords(observer.callback);
observer.assertNotCalled();
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