Revision: 13177
Author: [email protected]
Date: Mon Dec 10 02:53:57 2012
Log: Object.observe support for Function 'prototype' property
BUG=v8:2409
Review URL: https://codereview.chromium.org/11416353
Patch from Adam Klein <[email protected]>.
http://code.google.com/p/v8/source/detail?r=13177
Modified:
/branches/bleeding_edge/src/accessors.cc
/branches/bleeding_edge/src/objects.cc
/branches/bleeding_edge/test/mjsunit/harmony/object-observe.js
=======================================
--- /branches/bleeding_edge/src/accessors.cc Wed Dec 5 03:47:45 2012
+++ /branches/bleeding_edge/src/accessors.cc Mon Dec 10 02:53:57 2012
@@ -465,24 +465,46 @@
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);
}
- ASSERT(function->prototype() == value);
- return 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);
+ }
+
+ return *function;
}
=======================================
--- /branches/bleeding_edge/src/objects.cc Fri Dec 7 04:58:09 2012
+++ /branches/bleeding_edge/src/objects.cc Mon Dec 10 02:53:57 2012
@@ -27,6 +27,7 @@
#include "v8.h"
+#include "accessors.h"
#include "api.h"
#include "arguments.h"
#include "bootstrapper.h"
@@ -3107,8 +3108,17 @@
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 @@
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 {
=======================================
--- /branches/bleeding_edge/test/mjsunit/harmony/object-observe.js Wed Dec
5 04:03:57 2012
+++ /branches/bleeding_edge/test/mjsunit/harmony/object-observe.js Mon Dec
10 02:53:57 2012
@@ -455,7 +455,7 @@
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 @@
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 @@
(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 @@
if (!desc || desc.configurable)
TestObserveConfigurable(obj, prop);
else if (desc.writable)
- TestObserveNonConfigurable(obj, prop);
+ TestObserveNonConfigurable(obj, prop, desc);
}
@@ -817,6 +815,7 @@
{ object: array, name: 'length', type: 'updated', oldValue: 1},
]);
+
// __proto__
reset();
var obj = {};
@@ -836,3 +835,39 @@
{ 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();
--
v8-dev mailing list
[email protected]
http://groups.google.com/group/v8-dev