Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Source/Noesis.Javascript/JavaScript.Net.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@
<ClInclude Include="JavascriptContext.h" />
<ClInclude Include="JavascriptException.h" />
<ClInclude Include="JavascriptExternal.h" />
<ClInclude Include="JavascriptFunction.h" />
<ClInclude Include="JavascriptInterop.h" />
<ClInclude Include="SystemInterop.h" />
</ItemGroup>
Expand All @@ -168,6 +169,7 @@
<ClCompile Include="JavascriptContext.cpp" />
<ClCompile Include="JavascriptException.cpp" />
<ClCompile Include="JavascriptExternal.cpp" />
<ClCompile Include="JavascriptFunction.cpp" />
<ClCompile Include="JavascriptInterop.cpp" />
<ClCompile Include="SystemInterop.cpp" />
</ItemGroup>
Expand Down
6 changes: 6 additions & 0 deletions Source/Noesis.Javascript/JavaScript.Net.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
<ClInclude Include="SystemInterop.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="JavascriptFunction.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="AssemblyInfo.cpp">
Expand All @@ -50,6 +53,9 @@
<ClCompile Include="SystemInterop.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="JavascriptFunction.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
Expand Down
16 changes: 16 additions & 0 deletions Source/Noesis.Javascript/JavascriptContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ JavascriptContext::JavascriptContext()
mExternals = gcnew System::Collections::Generic::Dictionary<System::Object ^, WrappedJavascriptExternal>();
HandleScope scope(isolate);
mContext = new Persistent<Context>(isolate, Context::New(isolate));
mIsDisposed = false;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
Expand All @@ -173,6 +174,7 @@ JavascriptContext::~JavascriptContext()
}
if (isolate != NULL)
isolate->Dispose();
mIsDisposed = true;
}

////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -357,6 +359,20 @@ JavascriptContext::GetCurrentIsolate()

////////////////////////////////////////////////////////////////////////////////////////////////////

bool JavascriptContext::IsDisposed()
{
return mIsDisposed;
}

////////////////////////////////////////////////////////////////////////////////////////////////////

Handle<v8::Object> JavascriptContext::GetGlobal()
{
return mContext->Get(this->GetCurrentIsolate())->Global();
}

////////////////////////////////////////////////////////////////////////////////////////////////////

v8::Locker *
JavascriptContext::Enter([System::Runtime::InteropServices::Out] JavascriptContext^% old_context)
{
Expand Down
9 changes: 8 additions & 1 deletion Source/Noesis.Javascript/JavascriptContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,19 +172,24 @@ public ref class JavascriptContext: public System::IDisposable

static v8::Isolate *GetCurrentIsolate();

Handle<v8::Object> GetGlobal();

v8::Locker *Enter([System::Runtime::InteropServices::Out] JavascriptContext^% old_context);

void Exit(v8::Locker *locker, JavascriptContext^ old_context);

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

Handle<ObjectTemplate> GetObjectWrapperTemplate();


bool IsDisposed();

static void FatalErrorCallbackMember(const char* location, const char* message);

////////////////////////////////////////////////////////////
// Data members
////////////////////////////////////////////////////////////

protected:
// By entering an isolate before using a context, we can have multiple
// contexts used simultaneously in different threads.
Expand All @@ -202,6 +207,8 @@ public ref class JavascriptContext: public System::IDisposable
// the context is destroyed.
System::Collections::Generic::Dictionary<System::Object ^, WrappedJavascriptExternal> ^mExternals;

bool mIsDisposed;

// Keeping track of recursion.
[System::ThreadStaticAttribute] static JavascriptContext ^sCurrentContext;

Expand Down
98 changes: 98 additions & 0 deletions Source/Noesis.Javascript/JavascriptFunction.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#include "JavascriptFunction.h"
#include "JavascriptInterop.h"
#include "JavascriptContext.h"

////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Noesis { namespace Javascript {

////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////

JavascriptFunction::JavascriptFunction(v8::Handle<v8::Object> iFunction, JavascriptContext^ context)
{
if (!iFunction->IsFunction())
throw gcnew System::ArgumentException("Trying to use non-function as function");

if(!context)
throw gcnew System::ArgumentException("Must provide a JavascriptContext");

mFuncHandle = new Persistent<Function>(context->GetCurrentIsolate(), Handle<Function>::Cast(iFunction));
mContext = context;
}

JavascriptFunction::~JavascriptFunction()
{
if(mFuncHandle)
{
if (mContext && !mContext->IsDisposed())
{
JavascriptScope scope(mContext);
mFuncHandle->Reset();
}
delete mFuncHandle;
mFuncHandle = nullptr;
}
System::GC::SuppressFinalize(this);
}

JavascriptFunction::!JavascriptFunction()
{
if(mFuncHandle)
{
if (mContext && !mContext->IsDisposed())
{
JavascriptScope scope(mContext);
mFuncHandle->Reset();
}
delete mFuncHandle;
mFuncHandle = nullptr;
}
}

System::Object^ JavascriptFunction::Call(... cli::array<System::Object^>^ args)
{
JavascriptScope scope(mContext);
HandleScope handleScope(mContext->GetCurrentIsolate());

Handle<v8::Object> global = mContext->GetGlobal();

int argc = args->Length;
Handle<v8::Value> *argv = new Handle<v8::Value>[argc];
for (int i = 0; i < argc; i++)
{
argv[i] = JavascriptInterop::ConvertToV8(args[i]);
}

Local<Value> retVal = mFuncHandle->Get(mContext->GetCurrentIsolate())->Call(global, argc, argv);

delete [] argv;
return JavascriptInterop::ConvertFromV8(retVal);
}

bool JavascriptFunction::operator==(JavascriptFunction^ func1, JavascriptFunction^ func2)
{
if(ReferenceEquals(func2, nullptr)) {
return false;
}
Handle<Function> jsFuncPtr1 = func1->mFuncHandle->Get(func1->mContext->GetCurrentIsolate());
Handle<Function> jsFuncPtr2 = func2->mFuncHandle->Get(func2->mContext->GetCurrentIsolate());

return jsFuncPtr1->Equals(jsFuncPtr2);
}

bool JavascriptFunction::Equals(JavascriptFunction^ other)
{
return this == other;
}

bool JavascriptFunction::Equals(Object^ other)
{
JavascriptFunction^ otherFunc = dynamic_cast<JavascriptFunction^>(other);
return (otherFunc && this->Equals(otherFunc));
}

} } // namespace Noesis::Javascript

////////////////////////////////////////////////////////////////////////////////////////////////////
45 changes: 45 additions & 0 deletions Source/Noesis.Javascript/JavascriptFunction.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#pragma once

//////////////////////////////////////////////////////////////////////////

#include <v8.h>

#include "JavascriptContext.h"

using namespace v8;

//////////////////////////////////////////////////////////////////////////

namespace Noesis { namespace Javascript {

//////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////
// JavascriptFunction
//
// Wraps around JS function object and allow calling it in later time
//////////////////////////////////////////////////////////////////////////
public ref class JavascriptFunction
{
public:
JavascriptFunction(v8::Handle<v8::Object> iFunction, JavascriptContext^ context);
~JavascriptFunction();
!JavascriptFunction();

System::Object^ Call(... cli::array<System::Object^>^ args);

static bool operator== (JavascriptFunction^ func1, JavascriptFunction^ func2);
bool Equals(JavascriptFunction^ other);

virtual bool Equals(Object^ other) override;

private:
v8::Persistent<v8::Function>* mFuncHandle;
JavascriptContext^ mContext;
};

//////////////////////////////////////////////////////////////////////////

} } // namespace Noesis::Javascript

//////////////////////////////////////////////////////////////////////////
3 changes: 3 additions & 0 deletions Source/Noesis.Javascript/JavascriptInterop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
#include "SystemInterop.h"
#include "JavascriptException.h"
#include "JavascriptExternal.h"
#include "JavascriptFunction.h"

#include <string>

Expand Down Expand Up @@ -129,6 +130,8 @@ JavascriptInterop::ConvertFromV8(Handle<Value> iValue, ConvertedObjects &already
return ConvertArrayFromV8(iValue, already_converted);
if (iValue->IsDate())
return ConvertDateFromV8(iValue);
if (iValue->IsFunction())
return gcnew JavascriptFunction(iValue->ToObject(), JavascriptContext::GetCurrent());
if (iValue->IsObject())
{
Handle<Object> object = iValue->ToObject();
Expand Down
75 changes: 75 additions & 0 deletions Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Noesis.Javascript.Tests
{
[TestClass]
public class JavascriptFunctionTests
{
private JavascriptContext _context;

[TestInitialize]
public void SetUp()
{
_context = new JavascriptContext();
}

[TestCleanup]
public void TearDown()
{
_context.Dispose();
}

[TestMethod]
public void GetFunctionExpressionFromJsContext()
{
_context.Run("a = function(a, b) { return a + b; }");

JavascriptFunction funcObj = _context.GetParameter("a") as JavascriptFunction;
funcObj.Should().NotBeNull();
funcObj.Call(1, 2).Should().BeOfType<int>().Which.Should().Be(3);
}

[TestMethod]
public void GetNamedFunctionFromJsContext()
{
_context.Run("function test(a, b) { return a + b; }");

JavascriptFunction funcObj = _context.GetParameter("test") as JavascriptFunction;
funcObj.Should().NotBeNull();
funcObj.Call(1, 2).Should().BeOfType<int>().Which.Should().Be(3);
}

[TestMethod]
public void GetArrowFunctionExpressionFromJsContext()
{
_context.Run("a = (a, b) => a + b");

JavascriptFunction funcObj = _context.GetParameter("a") as JavascriptFunction;
funcObj.Should().NotBeNull();
funcObj.Call(1, 2).Should().BeOfType<int>().Which.Should().Be(3);
}

[TestMethod]
public void PassFunctionToMethodInManagedObjectAndUseItToFilterAList()
{
_context.SetParameter("collection", new CollectionWrapper());

var result = _context.Run("collection.Filter(x => x % 2 === 0)") as IEnumerable<int>;
result.Should().NotBeNull();
result.Should().BeEquivalentTo(2, 4);
}
}

class CollectionWrapper
{
private IEnumerable<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

public IEnumerable<int> Filter(JavascriptFunction predicate)
{
return numbers.Where(x => (bool) predicate.Call(x));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
<Compile Include="FatalErrorHandlerTests.cs" />
<Compile Include="InternationalizationTests.cs" />
<Compile Include="IsolationTests.cs" />
<Compile Include="JavascriptFunctionTests.cs" />
<Compile Include="MemoryLeakTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="VersionStringTests.cs" />
Expand Down