Skip to content

Commit b320233

Browse files
Erik Schillingspahnke
Erik Schilling
authored andcommitted
Add support for registered constructors
This allows to register a function (delegate in .NET) as constructor function in JavaScript. In order to support this a new method `SetConstructor` was introduced which allows to register a constructor function for a specific type under a name. This fuction is then available to the executed JavaScript code and invoking `new` with this function results in the objects being registered as `instanceof` the registered function. Additionally, objects which are created in .NET code are also converted to V8 while preserving the `instanceof` relation if the type is registered. Part of JavascriptNet#60
1 parent d2853f6 commit b320233

File tree

6 files changed

+129
-32
lines changed

6 files changed

+129
-32
lines changed

Source/Noesis.Javascript/JavascriptContext.cpp

+41-10
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,15 @@ namespace Noesis { namespace Javascript {
116116
}
117117
#pragma managed(pop)
118118

119+
v8::Local<v8::String> ToV8String(Isolate* isolate, System::String^ value) {
120+
if (value == nullptr)
121+
throw gcnew System::ArgumentNullException("value");
122+
pin_ptr<const wchar_t> namePtr = PtrToStringChars(value);
123+
wchar_t* name = (wchar_t*)namePtr;
124+
125+
return String::NewFromTwoByte(isolate, (uint16_t*)name, v8::NewStringType::kNormal).ToLocalChecked();
126+
}
127+
119128
static JavascriptContext::JavascriptContext()
120129
{
121130
System::Threading::Mutex mutex(true, "FA12B681-E968-4D3A-833D-43B25865BEF1");
@@ -173,6 +182,7 @@ JavascriptContext::JavascriptContext()
173182
isolate->SetFatalErrorHandler(FatalErrorCallback);
174183

175184
mExternals = gcnew System::Collections::Generic::Dictionary<System::Object ^, WrappedJavascriptExternal>();
185+
mTypeToConstructorMapping = gcnew System::Collections::Generic::Dictionary<System::Type ^, System::IntPtr>();
176186
mFunctions = gcnew System::Collections::Generic::List<System::WeakReference ^>();
177187
HandleScope scope(isolate);
178188
mContext = new Persistent<Context>(isolate, Context::New(isolate));
@@ -194,8 +204,12 @@ JavascriptContext::~JavascriptContext()
194204
delete function;
195205
}
196206
mFunctions->Clear();
207+
for each (System::IntPtr p in mTypeToConstructorMapping->Values) {
208+
delete (void *)p;
209+
}
197210
delete mContext;
198211
delete mExternals;
212+
delete mTypeToConstructorMapping;
199213
delete mFunctions;
200214
}
201215
if (isolate != NULL)
@@ -255,10 +269,6 @@ JavascriptContext::SetParameter(System::String^ iName, System::Object^ iObject)
255269
void
256270
JavascriptContext::SetParameter(System::String^ iName, System::Object^ iObject, SetParameterOptions options)
257271
{
258-
if (iName == nullptr)
259-
throw gcnew System::ArgumentNullException("iName");
260-
pin_ptr<const wchar_t> namePtr = PtrToStringChars(iName);
261-
wchar_t* name = (wchar_t*) namePtr;
262272
JavascriptScope scope(this);
263273
v8::Isolate *isolate = JavascriptContext::GetCurrentIsolate();
264274
HandleScope handleScope(isolate);
@@ -276,12 +286,27 @@ JavascriptContext::SetParameter(System::String^ iName, System::Object^ iObject,
276286
}
277287
}
278288

279-
v8::Local<v8::String> key = String::NewFromTwoByte(isolate, (uint16_t*)name, v8::NewStringType::kNormal).ToLocalChecked();
289+
v8::Local<v8::String> key = ToV8String(isolate, iName);
280290
Local<Context>::New(isolate, *mContext)->Global()->Set(isolate->GetCurrentContext(), key, value).ToChecked();
281291
}
282292

283293
////////////////////////////////////////////////////////////////////////////////////////////////////
284294

295+
296+
void JavascriptContext::SetConstructor(System::String^ name, System::Type^ associatedType, System::Delegate^ constructor)
297+
{
298+
JavascriptScope scope(this);
299+
v8::Isolate *isolate = JavascriptContext::GetCurrentIsolate();
300+
HandleScope handleScope(isolate);
301+
302+
Handle<FunctionTemplate> functionTemplate = JavascriptInterop::GetFunctionTemplateFromSystemDelegate(constructor);
303+
JavascriptInterop::InitObjectWrapperTemplate(functionTemplate->InstanceTemplate());
304+
mTypeToConstructorMapping[associatedType] = System::IntPtr(new Persistent<FunctionTemplate>(isolate, functionTemplate));
305+
Local<Context>::New(isolate, *mContext)->Global()->Set(isolate->GetCurrentContext(), ToV8String(isolate, name), functionTemplate->GetFunction());
306+
}
307+
308+
////////////////////////////////////////////////////////////////////////////////////////////////////
309+
285310
System::Object^
286311
JavascriptContext::GetParameter(System::String^ iName)
287312
{
@@ -507,12 +532,18 @@ JavascriptContext::WrapObject(System::Object^ iObject)
507532

508533
////////////////////////////////////////////////////////////////////////////////////////////////////
509534

510-
Handle<ObjectTemplate>
511-
JavascriptContext::GetObjectWrapperTemplate()
535+
Handle<FunctionTemplate>
536+
JavascriptContext::GetObjectWrapperConstructorTemplate(System::Type ^type)
512537
{
513-
if (objectWrapperTemplate == NULL)
514-
objectWrapperTemplate = new Persistent<ObjectTemplate>(isolate, JavascriptInterop::NewObjectWrapperTemplate());
515-
return Local<ObjectTemplate>::New(isolate, *objectWrapperTemplate);
538+
System::IntPtr ptrToConstructor;
539+
if (!mTypeToConstructorMapping->TryGetValue(type, ptrToConstructor)) {
540+
Local<FunctionTemplate> constructor = FunctionTemplate::New(GetCurrentIsolate());
541+
JavascriptInterop::InitObjectWrapperTemplate(constructor->InstanceTemplate());
542+
mTypeToConstructorMapping[type] = System::IntPtr(new Persistent<FunctionTemplate>(isolate, constructor));
543+
return constructor;
544+
}
545+
Persistent<FunctionTemplate> *constructor = (Persistent<FunctionTemplate> *)(void *)ptrToConstructor;
546+
return constructor->Get(isolate);
516547
}
517548

518549
////////////////////////////////////////////////////////////////////////////////////////////////////

Source/Noesis.Javascript/JavascriptContext.h

+5-4
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,8 @@ public ref class JavascriptContext: public System::IDisposable
141141

142142
void SetParameter(System::String^ iName, System::Object^ iObject, SetParameterOptions options);
143143

144+
void SetConstructor(System::String^ name, System::Type^ associatedType, System::Delegate^ constructor);
145+
144146
System::Object^ GetParameter(System::String^ iName);
145147

146148
virtual System::Object^ Run(System::String^ iSourceCode);
@@ -193,7 +195,7 @@ public ref class JavascriptContext: public System::IDisposable
193195

194196
JavascriptExternal* WrapObject(System::Object^ iObject);
195197

196-
Handle<ObjectTemplate> GetObjectWrapperTemplate();
198+
Handle<FunctionTemplate> GetObjectWrapperConstructorTemplate(System::Type ^type);
197199

198200
void RegisterFunction(System::Object^ f);
199201

@@ -214,15 +216,14 @@ public ref class JavascriptContext: public System::IDisposable
214216
// v8 context required to be active for all v8 operations.
215217
Persistent<Context>* mContext;
216218

217-
// Avoids us recreating these too often.
218-
Persistent<ObjectTemplate> *objectWrapperTemplate;
219-
220219
// Stores every JavascriptExternal we create. This saves time if the same
221220
// objects are recreated frequently, and stops us building up a huge
222221
// collection of JavascriptExternal objects that won't be freed until
223222
// the context is destroyed.
224223
System::Collections::Generic::Dictionary<System::Object ^, WrappedJavascriptExternal> ^mExternals;
225224

225+
System::Collections::Generic::Dictionary<System::Type ^, System::IntPtr> ^mTypeToConstructorMapping;
226+
226227
// Stores every JavascriptFunction we create. Ensures we dispose of them
227228
// all.
228229
System::Collections::Generic::List<System::WeakReference ^> ^mFunctions;

Source/Noesis.Javascript/JavascriptInterop.cpp

+20-16
Original file line numberDiff line numberDiff line change
@@ -49,19 +49,15 @@ using namespace System::Collections::Generic;
4949

5050
////////////////////////////////////////////////////////////////////////////////////////////////////
5151

52-
Handle<ObjectTemplate>
53-
JavascriptInterop::NewObjectWrapperTemplate()
52+
void JavascriptInterop::InitObjectWrapperTemplate(Handle<ObjectTemplate> &object)
5453
{
55-
Handle<ObjectTemplate> result = ObjectTemplate::New(JavascriptContext::GetCurrentIsolate());
56-
result->SetInternalFieldCount(1);
54+
object->SetInternalFieldCount(1);
5755

5856
NamedPropertyHandlerConfiguration namedPropertyConfig((GenericNamedPropertyGetterCallback) Getter, (GenericNamedPropertySetterCallback) Setter, nullptr, nullptr, nullptr, Local<Value>(), PropertyHandlerFlags::kOnlyInterceptStrings);
59-
result->SetHandler(namedPropertyConfig);
57+
object->SetHandler(namedPropertyConfig);
6058

6159
IndexedPropertyHandlerConfiguration indexedPropertyConfig((IndexedPropertyGetterCallback) IndexGetter, (IndexedPropertySetterCallback) IndexSetter);
62-
result->SetHandler(indexedPropertyConfig);
63-
64-
return result;
60+
object->SetHandler(indexedPropertyConfig);
6561
}
6662

6763
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -271,9 +267,10 @@ JavascriptInterop::WrapObject(System::Object^ iObject)
271267

272268
if (context != nullptr)
273269
{
274-
Handle<ObjectTemplate> templ = context->GetObjectWrapperTemplate();
270+
Handle<FunctionTemplate> templ = context->GetObjectWrapperConstructorTemplate(iObject->GetType());
275271
v8::Isolate *isolate = JavascriptContext::GetCurrentIsolate();
276-
Handle<Object> object = templ->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
272+
Handle<ObjectTemplate> instanceTemplate = templ->InstanceTemplate();
273+
Handle<Object> object = instanceTemplate->NewInstance(isolate->GetCurrentContext()).ToLocalChecked();
277274
object->SetInternalField(0, External::New(isolate, context->WrapObject(iObject)));
278275

279276
return object;
@@ -475,15 +472,22 @@ JavascriptInterop::ConvertFromSystemList(System::Object^ iObject)
475472

476473
////////////////////////////////////////////////////////////////////////////////////////////////////
477474

475+
v8::Handle<v8::FunctionTemplate>
476+
JavascriptInterop::GetFunctionTemplateFromSystemDelegate(System::Delegate^ iDelegate)
477+
{
478+
JavascriptContext^ context = JavascriptContext::GetCurrent();
479+
v8::Isolate *isolate = JavascriptContext::GetCurrentIsolate();
480+
v8::Handle<v8::External> external = v8::External::New(isolate, context->WrapObject(iDelegate));
481+
482+
return v8::FunctionTemplate::New(isolate, DelegateInvoker, external);
483+
}
484+
485+
////////////////////////////////////////////////////////////////////////////////////////////////////
486+
478487
v8::Handle<v8::Value>
479488
JavascriptInterop::ConvertFromSystemDelegate(System::Delegate^ iDelegate)
480489
{
481-
JavascriptContext^ context = JavascriptContext::GetCurrent();
482-
v8::Isolate *isolate = JavascriptContext::GetCurrentIsolate();
483-
v8::Handle<v8::External> external = v8::External::New(isolate, context->WrapObject(iDelegate));
484-
485-
v8::Handle<v8::FunctionTemplate> method = v8::FunctionTemplate::New(isolate, DelegateInvoker, external);
486-
return method->GetFunction();
490+
return GetFunctionTemplateFromSystemDelegate(iDelegate)->GetFunction();
487491
}
488492

489493
////////////////////////////////////////////////////////////////////////////////////////////////////

Source/Noesis.Javascript/JavascriptInterop.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ class JavascriptInterop
7171
////////////////////////////////////////////////////////////
7272
public:
7373

74-
static Handle<ObjectTemplate> NewObjectWrapperTemplate();
74+
static void InitObjectWrapperTemplate(Handle<ObjectTemplate> &object);
7575

7676
static System::Object^ ConvertFromV8(Handle<Value> iValue);
7777

@@ -83,6 +83,8 @@ class JavascriptInterop
8383

8484
static Handle<Value> HandleTargetInvocationException(System::Reflection::TargetInvocationException^ exception);
8585

86+
static v8::Handle<v8::FunctionTemplate> GetFunctionTemplateFromSystemDelegate(System::Delegate^ iDelegate);
87+
8688
private:
8789
static System::Object^ ConvertFromV8(Handle<Value> iValue, ConvertedObjects &already_converted);
8890

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+

2+
using FluentAssertions;
3+
using Microsoft.VisualStudio.TestTools.UnitTesting;
4+
using System;
5+
6+
namespace Noesis.Javascript.Tests
7+
{
8+
[TestClass]
9+
public class InstanceOfTest
10+
{
11+
private JavascriptContext _context;
12+
13+
private class TestClass
14+
{
15+
public int foo { get; set; }
16+
}
17+
18+
[TestInitialize]
19+
public void SetUp()
20+
{
21+
_context = new JavascriptContext();
22+
}
23+
24+
[TestMethod]
25+
public void RegisteredConstructorInstanceOfTest()
26+
{
27+
_context.SetConstructor("Test", typeof(TestClass), new Func<TestClass>(() => new TestClass()));
28+
_context.Run("(new Test()) instanceof Test").Should().Be(true);
29+
}
30+
31+
[TestMethod]
32+
public void RegisteredConstructorInstanceOfWorksWithCSharpObjectTest()
33+
{
34+
_context.SetConstructor("Test", typeof(TestClass), new Func<TestClass>(() => new TestClass()));
35+
_context.SetParameter("test", new TestClass());
36+
_context.Run("test instanceof Test").Should().Be(true);
37+
}
38+
39+
[TestMethod]
40+
public void ManipulatingPropertyWorks()
41+
{
42+
_context.SetConstructor("Test", typeof(TestClass), new Func<TestClass>(() => new TestClass()));
43+
var testObject = new TestClass { foo = 42 };
44+
_context.SetParameter("test", testObject);
45+
_context.Run("test.foo").Should().Be(42);
46+
_context.Run("test.foo = 1");
47+
testObject.foo.Should().Be(1);
48+
}
49+
50+
[TestMethod]
51+
public void UnregisteredObjectInstanceOfTest()
52+
{
53+
_context.SetParameter("test", new TestClass());
54+
_context.Invoking(x => x.Run("test instanceof Test"))
55+
.ShouldThrow<JavascriptException>().WithMessage("ReferenceError: Test is not defined");
56+
}
57+
}
58+
}

Tests/Noesis.Javascript.Tests/Noesis.Javascript.Tests.csproj

+2-1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
<Compile Include="ExceptionTests.cs" />
9191
<Compile Include="FatalErrorHandlerTests.cs" />
9292
<Compile Include="FlagsTest.cs" />
93+
<Compile Include="InstanceOfTest.cs" />
9394
<Compile Include="InternationalizationTests.cs" />
9495
<Compile Include="IsolationTests.cs" />
9596
<Compile Include="JavascriptFunctionTests.cs" />
@@ -126,4 +127,4 @@ copy $(ProjectDir)..\..\$(V8Platform)\$(Configuration)\icu*.* $(ProjectDir)$(Out
126127
copy $(ProjectDir)..\..\$(V8Platform)\$(Configuration)\*.bin $(ProjectDir)$(OutDir)
127128
</PostBuildEvent>
128129
</PropertyGroup>
129-
</Project>
130+
</Project>

0 commit comments

Comments
 (0)