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*) {
MaybeObject* Accessors::FunctionSetPrototype(JSObject* object,
Object* value,
Object* value_raw,
void*) {
Heap* heap = object->GetHeap();
JSFunction* function = FindInstanceOf<JSFunction>(object);
if (function == NULL) return heap->undefined_value();
if (!function->should_have_prototype()) {
Isolate* isolate = object->GetIsolate();
Heap* heap = isolate->heap();
JSFunction* function_raw = FindInstanceOf<JSFunction>(object);
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.
return object->SetLocalPropertyIgnoreAttributes(heap->prototype_symbol(),
value,
value_raw,
NONE);
}
Object* prototype;
{ MaybeObject* maybe_prototype = function->SetPrototype(value);
if (!maybe_prototype->ToObject(&prototype)) return maybe_prototype;
HandleScope scope(isolate);
Handle<JSFunction> function(function_raw, isolate);
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 @@
#include "v8.h"
#include "accessors.h"
#include "api.h"
#include "arguments.h"
#include "bootstrapper.h"
......@@ -3107,8 +3108,17 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
Handle<Object> old_value(isolate->heap()->the_hole_value(), isolate);
PropertyAttributes old_attributes = ABSENT;
if (FLAG_harmony_observation && map()->is_observed()) {
old_value = handle(lookup.GetLazyValue(), isolate);
bool is_observed = FLAG_harmony_observation && self->map()->is_observed();
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();
}
......@@ -3172,7 +3182,7 @@ MaybeObject* JSObject::SetLocalPropertyIgnoreAttributes(
Handle<Object> hresult;
if (!result->ToHandle(&hresult, isolate)) return result;
if (FLAG_harmony_observation && map()->is_observed()) {
if (is_observed) {
if (lookup.IsTransition()) {
EnqueueChangeRecord(self, "new", name, old_value);
} else {
......
......@@ -455,7 +455,7 @@ function TestObserveConfigurable(obj, prop) {
delete obj[prop];
}
function TestObserveNonConfigurable(obj, prop) {
function TestObserveNonConfigurable(obj, prop, desc) {
reset();
obj[prop] = 1;
Object.observe(obj, observer.callback);
......@@ -465,10 +465,10 @@ function TestObserveNonConfigurable(obj, prop) {
Object.defineProperty(obj, prop, {value: 6});
Object.defineProperty(obj, prop, {value: 6}); // ignored
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});
obj[prop] = 7; // ignored
Object.defineProperty(obj, prop, {get: function() {}}); // ignored
Object.deliverChangeRecords(observer.callback);
observer.assertCallbackRecords([
{ object: obj, name: prop, type: "updated", oldValue: 1 },
......@@ -544,9 +544,7 @@ function blacklisted(obj, prop) {
(obj instanceof Int32Array && prop === "length") ||
(obj instanceof ArrayBuffer && prop == 1) ||
// TODO(observe): oldValue when reconfiguring array length
(obj instanceof Array && prop === "length") ||
// TODO(observe): prototype property on functions
(obj instanceof Function && prop === "prototype")
(obj instanceof Array && prop === "length")
}
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)
TestObserveConfigurable(obj, prop);
else if (desc.writable)
TestObserveNonConfigurable(obj, prop);
TestObserveNonConfigurable(obj, prop, desc);
}
......@@ -817,6 +815,7 @@ observer.assertCallbackRecords([
{ object: array, name: 'length', type: 'updated', oldValue: 1},
]);
// __proto__
reset();
var obj = {};
......@@ -836,3 +835,39 @@ observer.assertCallbackRecords([
{ object: obj, name: '__proto__', type: 'prototype', oldValue: p },
{ 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