Allow JavaScript accessors on API objects.
authorvogelheim <vogelheim@chromium.org>
Tue, 29 Sep 2015 11:15:59 +0000 (04:15 -0700)
committerCommit bot <commit-bot@chromium.org>
Tue, 29 Sep 2015 11:16:14 +0000 (11:16 +0000)
(This is somewhat experimental; hence protected by #ifdef.)

R=epertoso@chromium.org, jochen@chromium.org
BUG=chromium:508898
LOG=N

Review URL: https://codereview.chromium.org/1367953002

Cr-Commit-Position: refs/heads/master@{#31002}

build/features.gypi
include/v8.h
src/api-natives.cc
src/api-natives.h
src/api.cc
test/cctest/cctest.gyp
test/cctest/test-api-accessors.cc [new file with mode: 0644]

index 86b3511..f7bf57d 100644 (file)
@@ -67,6 +67,9 @@
 
     # Set to 1 to enable building with wasm prototype.
     'v8_wasm%': 0,
+
+    # Enable/disable JavaScript API accessors.
+    'v8_js_accessors%': 0,
   },
   'target_defaults': {
     'conditions': [
       ['v8_wasm!=0', {
         'defines': ['V8_WASM',],
       }],
+      ['v8_js_accessors!=0', {
+        'defines': ['V8_JS_ACCESSORS'],
+      }],
     ],  # conditions
     'configurations': {
       'DebugBaseCommon': {
index 54c6dcd..e19daee 100644 (file)
@@ -4027,6 +4027,13 @@ class V8_EXPORT Template : public Data {
      PropertyAttribute attribute = None,
      AccessControl settings = DEFAULT);
 
+#ifdef V8_JS_ACCESSORS
+  void SetAccessorProperty(Local<Name> name,
+                           Local<Function> getter = Local<Function>(),
+                           Local<Function> setter = Local<Function>(),
+                           PropertyAttribute attribute = None);
+#endif  // V8_JS_ACCESSORS
+
   /**
    * Whenever the property with the given name is accessed on objects
    * created from this Template the getter and setter callbacks
index 0a3bc17..051ea4a 100644 (file)
@@ -37,6 +37,25 @@ MaybeHandle<Object> Instantiate(Isolate* isolate, Handle<Object> data,
 }
 
 
+MaybeHandle<JSFunction> InstantiateFunctionOrMaybeDont(Isolate* isolate,
+                                                       Handle<Object> data) {
+  DCHECK(data->IsFunctionTemplateInfo() || data->IsJSFunction());
+  if (data->IsFunctionTemplateInfo()) {
+    // A function template needs to be instantiated.
+    return InstantiateFunction(isolate,
+                               Handle<FunctionTemplateInfo>::cast(data));
+#ifdef V8_JS_ACCESSORS
+  } else if (data->IsJSFunction()) {
+    // If we already have a proper function, we do not need additional work.
+    // (This should only happen for JavaScript API accessors.)
+    return Handle<JSFunction>::cast(data);
+#endif  // V8_JS_ACCESSORS
+  } else {
+    UNREACHABLE();
+    return MaybeHandle<JSFunction>();
+  }
+}
+
 MaybeHandle<Object> DefineAccessorProperty(Isolate* isolate,
                                            Handle<JSObject> object,
                                            Handle<Name> name,
@@ -44,18 +63,14 @@ MaybeHandle<Object> DefineAccessorProperty(Isolate* isolate,
                                            Handle<Object> setter,
                                            PropertyAttributes attributes) {
   if (!getter->IsUndefined()) {
-    ASSIGN_RETURN_ON_EXCEPTION(
-        isolate, getter,
-        InstantiateFunction(isolate,
-                            Handle<FunctionTemplateInfo>::cast(getter)),
-        Object);
+    ASSIGN_RETURN_ON_EXCEPTION(isolate, getter,
+                               InstantiateFunctionOrMaybeDont(isolate, getter),
+                               Object);
   }
   if (!setter->IsUndefined()) {
-    ASSIGN_RETURN_ON_EXCEPTION(
-        isolate, setter,
-        InstantiateFunction(isolate,
-                            Handle<FunctionTemplateInfo>::cast(setter)),
-        Object);
+    ASSIGN_RETURN_ON_EXCEPTION(isolate, setter,
+                               InstantiateFunctionOrMaybeDont(isolate, setter),
+                               Object);
   }
   RETURN_ON_EXCEPTION(isolate, JSObject::DefineAccessor(object, name, getter,
                                                         setter, attributes),
@@ -364,10 +379,19 @@ void ApiNatives::AddDataProperty(Isolate* isolate, Handle<TemplateInfo> info,
 
 void ApiNatives::AddAccessorProperty(Isolate* isolate,
                                      Handle<TemplateInfo> info,
-                                     Handle<Name> name,
-                                     Handle<FunctionTemplateInfo> getter,
-                                     Handle<FunctionTemplateInfo> setter,
+                                     Handle<Name> name, Handle<Object> getter,
+                                     Handle<Object> setter,
                                      PropertyAttributes attributes) {
+#ifdef V8_JS_ACCESSORS
+  DCHECK(getter.is_null() || getter->IsFunctionTemplateInfo() ||
+         getter->IsJSFunction());
+  DCHECK(setter.is_null() || setter->IsFunctionTemplateInfo() ||
+         setter->IsJSFunction());
+#else
+  DCHECK(getter.is_null() || getter->IsFunctionTemplateInfo());
+  DCHECK(setter.is_null() || setter->IsFunctionTemplateInfo());
+#endif  // V8_JS_ACCESSORS
+
   const int kSize = 4;
   PropertyDetails details(attributes, ACCESSOR, 0, PropertyCellType::kNoCell);
   auto details_handle = handle(details.AsSmi(), isolate);
index c5e3982..0639677 100644 (file)
@@ -45,9 +45,8 @@ class ApiNatives {
                               PropertyAttributes attributes);
 
   static void AddAccessorProperty(Isolate* isolate, Handle<TemplateInfo> info,
-                                  Handle<Name> name,
-                                  Handle<FunctionTemplateInfo> getter,
-                                  Handle<FunctionTemplateInfo> setter,
+                                  Handle<Name> name, Handle<Object> getter,
+                                  Handle<Object> setter,
                                   PropertyAttributes attributes);
 
   static void AddNativeDataProperty(Isolate* isolate, Handle<TemplateInfo> info,
index d6727fa..0b99743 100644 (file)
@@ -955,6 +955,25 @@ void Template::SetAccessorProperty(
 }
 
 
+#ifdef V8_JS_ACCESSORS
+void Template::SetAccessorProperty(v8::Local<v8::Name> name,
+                                   v8::Local<Function> getter,
+                                   v8::Local<Function> setter,
+                                   v8::PropertyAttribute attribute) {
+  auto templ = Utils::OpenHandle(this);
+  auto isolate = templ->GetIsolate();
+  ENTER_V8(isolate);
+  DCHECK(!name.IsEmpty());
+  DCHECK(!getter.IsEmpty() || !setter.IsEmpty());
+  i::HandleScope scope(isolate);
+  i::ApiNatives::AddAccessorProperty(
+      isolate, templ, Utils::OpenHandle(*name),
+      Utils::OpenHandle(*getter, true), Utils::OpenHandle(*setter, true),
+      static_cast<PropertyAttributes>(attribute));
+}
+#endif  // V8_JS_ACCESSORS
+
+
 // --- F u n c t i o n   T e m p l a t e ---
 static void InitializeFunctionTemplate(
     i::Handle<i::FunctionTemplateInfo> info) {
index 6f942f8..a7092a8 100644 (file)
@@ -99,6 +99,7 @@
         'test-alloc.cc',
         'test-api.cc',
         'test-api.h',
+        'test-api-accessors.cc',
         'test-api-interceptors.cc',
         'test-array-list.cc',
         'test-ast.cc',
diff --git a/test/cctest/test-api-accessors.cc b/test/cctest/test-api-accessors.cc
new file mode 100644 (file)
index 0000000..f6d1ef0
--- /dev/null
@@ -0,0 +1,93 @@
+// Copyright 2015 the V8 project authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// TODO(jochen): Remove this after the setting is turned on globally.
+#define V8_IMMINENT_DEPRECATION_WARNINGS
+
+#include "test/cctest/cctest.h"
+
+#include "include/v8.h"
+
+
+#ifdef V8_JS_ACCESSORS
+static void CppAccessor(const v8::FunctionCallbackInfo<v8::Value>& info) {
+  info.GetReturnValue().Set(42);
+}
+
+static const char* JsAccessor =
+    "function firstChildJS(value) { return 41; }; firstChildJS";
+
+TEST(JavascriptAccessors) {
+  v8::Isolate* isolate = CcTest::isolate();
+  v8::HandleScope scope(isolate);
+  LocalContext env;
+
+  // We emulate Embedder-created DOM Node instances. Specifically:
+  // - 'parent': FunctionTemplate ~= DOM Node superclass
+  // - 'child':  FunctionTemplate ~= a specific DOM node type, like a <div />
+  //
+  // We'll install both a C++-based and a JS-based accessor on the parent,
+  // and expect it to be callable on the child.
+
+  // Setup the parent template ( =~ DOM Node w/ accessors).
+  v8::Local<v8::FunctionTemplate> parent = v8::FunctionTemplate::New(isolate);
+  {
+    auto signature = v8::Signature::New(isolate, parent);
+
+    // cpp accessor as "firstChild":
+    parent->PrototypeTemplate()->SetAccessorProperty(
+        v8_str("firstChild"),
+        v8::FunctionTemplate::New(isolate, CppAccessor, v8::Local<v8::Value>(),
+                                  signature));
+
+    // JS accessor as "firstChildJS":
+    auto js_accessor = v8::Local<v8::Function>::Cast(CompileRun(JsAccessor));
+    parent->PrototypeTemplate()->SetAccessorProperty(v8_str("firstChildJS"),
+                                                     js_accessor);
+  }
+
+  // Setup child object ( =~ a specific DOM Node, e.g. a <div> ).
+  // Also, make a creation function on the global object, so we can access it
+  // in a test.
+  v8::Local<v8::FunctionTemplate> child = v8::FunctionTemplate::New(isolate);
+  child->Inherit(parent);
+  CHECK(env->Global()
+            ->Set(env.local(), v8_str("Node"),
+                  child->GetFunction(env.local()).ToLocalChecked())
+            .IsJust());
+
+  // Setup done: Let's test it:
+
+  // The simple case: Run it once.
+  ExpectInt32("var n = new Node(); n.firstChild", 42);
+  ExpectInt32("var n = new Node(); n.firstChildJS", 41);
+
+  // Run them in a loop. This will likely trigger the optimizing compiler:
+  ExpectInt32(
+      "var m = new Node(); "
+      "var sum = 0; "
+      "for (var i = 0; i < 3; ++i) { "
+      "  sum += m.firstChild; "
+      "  sum += m.firstChildJS; "
+      "}; "
+      "sum;",
+      3 * (42 + 41));
+
+  // Obtain the accessor and call it via apply on the Node:
+  ExpectInt32(
+      "var n = new Node(); "
+      "var g = Object.getOwnPropertyDescriptor("
+      "    n.__proto__.__proto__, 'firstChild')['get']; "
+      "g.apply(n);",
+      42);
+  ExpectInt32(
+      "var n = new Node(); "
+      "var g = Object.getOwnPropertyDescriptor("
+      "    n.__proto__.__proto__, 'firstChildJS')['get']; "
+      "g.apply(n);",
+      41);
+
+  // TODO(vogelheim): Verify compatible receiver check works.
+}
+#endif  // V8_JS_ACCESSORS