From 6d454f561818d0db58aa67bae354bdff0175085e Mon Sep 17 00:00:00 2001 From: Tom Rathbone Date: Tue, 7 May 2013 16:50:35 +0100 Subject: [PATCH 1/4] Implement handling of JavascriptFunction --- .../Noesis.Javascript/JavaScript.Net.vcxproj | 2 + .../JavaScript.Net.vcxproj.filters | 6 ++ Source/Noesis.Javascript/JavascriptContext.h | 1 + .../Noesis.Javascript/JavascriptFunction.cpp | 93 +++++++++++++++++++ Source/Noesis.Javascript/JavascriptFunction.h | 45 +++++++++ .../Noesis.Javascript/JavascriptInterop.cpp | 3 + .../JavascriptFunctionTests.cs | 33 +++++++ .../Noesis.Javascript.Tests.csproj | 1 + 8 files changed, 184 insertions(+) create mode 100644 Source/Noesis.Javascript/JavascriptFunction.cpp create mode 100644 Source/Noesis.Javascript/JavascriptFunction.h create mode 100644 Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs diff --git a/Source/Noesis.Javascript/JavaScript.Net.vcxproj b/Source/Noesis.Javascript/JavaScript.Net.vcxproj index aea6b78..4d60057 100644 --- a/Source/Noesis.Javascript/JavaScript.Net.vcxproj +++ b/Source/Noesis.Javascript/JavaScript.Net.vcxproj @@ -160,6 +160,7 @@ + @@ -168,6 +169,7 @@ + diff --git a/Source/Noesis.Javascript/JavaScript.Net.vcxproj.filters b/Source/Noesis.Javascript/JavaScript.Net.vcxproj.filters index 153155f..d3666f9 100644 --- a/Source/Noesis.Javascript/JavaScript.Net.vcxproj.filters +++ b/Source/Noesis.Javascript/JavaScript.Net.vcxproj.filters @@ -30,6 +30,9 @@ Header Files + + Header Files + @@ -50,6 +53,9 @@ Source Files + + Source Files + diff --git a/Source/Noesis.Javascript/JavascriptContext.h b/Source/Noesis.Javascript/JavascriptContext.h index 850007d..6dbbe16 100644 --- a/Source/Noesis.Javascript/JavascriptContext.h +++ b/Source/Noesis.Javascript/JavascriptContext.h @@ -185,6 +185,7 @@ public ref class JavascriptContext: public System::IDisposable //////////////////////////////////////////////////////////// // Data members //////////////////////////////////////////////////////////// + protected: // By entering an isolate before using a context, we can have multiple // contexts used simultaneously in different threads. diff --git a/Source/Noesis.Javascript/JavascriptFunction.cpp b/Source/Noesis.Javascript/JavascriptFunction.cpp new file mode 100644 index 0000000..94ac8dc --- /dev/null +++ b/Source/Noesis.Javascript/JavascriptFunction.cpp @@ -0,0 +1,93 @@ +#include "JavascriptFunction.h" +#include "JavascriptInterop.h" +#include "JavascriptContext.h" + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +namespace Noesis { namespace Javascript { + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +//////////////////////////////////////////////////////////////////////////////////////////////////// + +JavascriptFunction::JavascriptFunction( v8::Handle 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(); + *mFuncHandle = Persistent::New(Handle::Cast(iFunction)); + mContext = context; +} + +JavascriptFunction::~JavascriptFunction() +{ + if(mFuncHandle) + { + JavascriptScope scope(mContext); + mFuncHandle->Dispose(); + delete mFuncHandle; + mFuncHandle = nullptr; + } + System::GC::SuppressFinalize(this); +} + +JavascriptFunction::!JavascriptFunction() +{ + if(mFuncHandle) + { + JavascriptScope scope(mContext); + mFuncHandle->Dispose(); + delete mFuncHandle; + mFuncHandle = nullptr; + } +} + +System::Object^ JavascriptFunction::Call(... cli::array^ args) +{ + JavascriptScope scope(mContext); + HandleScope handleScope; + + Handle global = (*mFuncHandle)->CreationContext()->Global(); + + int argc = args->Length; + Handle *argv = new Handle[argc]; + for (int i = 0; i < argc; i++) + { + argv[i] = JavascriptInterop::ConvertToV8(args[i]); + } + + Local retVal = (*mFuncHandle)->Call(global, argc, argv); + + delete [] argv; + return JavascriptInterop::ConvertFromV8(retVal); +} + +bool JavascriptFunction::operator==( JavascriptFunction^ func1, JavascriptFunction^ func2 ) +{ + if(ReferenceEquals(func2, nullptr)) { + return false; + } + Handle jsFuncPtr1 = *(func1->mFuncHandle); + Handle jsFuncPtr2 = *(func2->mFuncHandle); + + return jsFuncPtr1->Equals(jsFuncPtr2); +} + +bool JavascriptFunction::Equals( JavascriptFunction^ other ) +{ + return this == other; +} + +bool JavascriptFunction::Equals(Object^ other ) +{ + JavascriptFunction^ otherFunc = dynamic_cast(other); + return (otherFunc && this->Equals(otherFunc)); +} + +} } // namespace Noesis::Javascript + +//////////////////////////////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/Source/Noesis.Javascript/JavascriptFunction.h b/Source/Noesis.Javascript/JavascriptFunction.h new file mode 100644 index 0000000..387f03c --- /dev/null +++ b/Source/Noesis.Javascript/JavascriptFunction.h @@ -0,0 +1,45 @@ +#pragma once + +////////////////////////////////////////////////////////////////////////// + +#include + +#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 iFunction, JavascriptContext^ context); + ~JavascriptFunction(); + !JavascriptFunction(); + + System::Object^ Call(... cli::array^ args); + + static bool operator== (JavascriptFunction^ func1, JavascriptFunction^ func2); + bool Equals(JavascriptFunction^ other); + + virtual bool Equals(Object^ other) override; + +private: + v8::Persistent* mFuncHandle; + JavascriptContext^ mContext; +}; + +////////////////////////////////////////////////////////////////////////// + +} } // namespace Noesis::Javascript + +////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/Source/Noesis.Javascript/JavascriptInterop.cpp b/Source/Noesis.Javascript/JavascriptInterop.cpp index d6f4de7..03f94cb 100644 --- a/Source/Noesis.Javascript/JavascriptInterop.cpp +++ b/Source/Noesis.Javascript/JavascriptInterop.cpp @@ -33,6 +33,7 @@ #include "SystemInterop.h" #include "JavascriptException.h" #include "JavascriptExternal.h" +#include "JavascriptFunction.h" #include @@ -129,6 +130,8 @@ JavascriptInterop::ConvertFromV8(Handle 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 = iValue->ToObject(); diff --git a/Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs b/Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs new file mode 100644 index 0000000..6456710 --- /dev/null +++ b/Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs @@ -0,0 +1,33 @@ +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 GetFunctionFromJsContext() + { + _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().Which.Should().Be(3); + } + } +} diff --git a/Tests/Noesis.Javascript.Tests/Noesis.Javascript.Tests.csproj b/Tests/Noesis.Javascript.Tests/Noesis.Javascript.Tests.csproj index a5f0bb9..f647371 100644 --- a/Tests/Noesis.Javascript.Tests/Noesis.Javascript.Tests.csproj +++ b/Tests/Noesis.Javascript.Tests/Noesis.Javascript.Tests.csproj @@ -85,6 +85,7 @@ + From 070d1cf910f65dd1d3ea0aeef139968c823c880b Mon Sep 17 00:00:00 2001 From: Sebastian Pahnke Date: Sat, 5 May 2018 19:02:58 +0200 Subject: [PATCH 2/4] Replace deprecated API calls --- .../Noesis.Javascript/JavascriptContext.cpp | 7 +++++ Source/Noesis.Javascript/JavascriptContext.h | 2 ++ .../Noesis.Javascript/JavascriptFunction.cpp | 27 +++++++++---------- Source/Noesis.Javascript/JavascriptFunction.h | 2 +- .../JavascriptFunctionTests.cs | 2 +- 5 files changed, 24 insertions(+), 16 deletions(-) diff --git a/Source/Noesis.Javascript/JavascriptContext.cpp b/Source/Noesis.Javascript/JavascriptContext.cpp index c2b39a6..303e988 100644 --- a/Source/Noesis.Javascript/JavascriptContext.cpp +++ b/Source/Noesis.Javascript/JavascriptContext.cpp @@ -357,6 +357,13 @@ JavascriptContext::GetCurrentIsolate() //////////////////////////////////////////////////////////////////////////////////////////////////// +Handle JavascriptContext::GetGlobal() +{ + return mContext->Get(this->GetCurrentIsolate())->Global(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + v8::Locker * JavascriptContext::Enter([System::Runtime::InteropServices::Out] JavascriptContext^% old_context) { diff --git a/Source/Noesis.Javascript/JavascriptContext.h b/Source/Noesis.Javascript/JavascriptContext.h index 6dbbe16..aed20ef 100644 --- a/Source/Noesis.Javascript/JavascriptContext.h +++ b/Source/Noesis.Javascript/JavascriptContext.h @@ -172,6 +172,8 @@ public ref class JavascriptContext: public System::IDisposable static v8::Isolate *GetCurrentIsolate(); + Handle GetGlobal(); + v8::Locker *Enter([System::Runtime::InteropServices::Out] JavascriptContext^% old_context); void Exit(v8::Locker *locker, JavascriptContext^ old_context); diff --git a/Source/Noesis.Javascript/JavascriptFunction.cpp b/Source/Noesis.Javascript/JavascriptFunction.cpp index 94ac8dc..a6ff73f 100644 --- a/Source/Noesis.Javascript/JavascriptFunction.cpp +++ b/Source/Noesis.Javascript/JavascriptFunction.cpp @@ -10,7 +10,7 @@ namespace Noesis { namespace Javascript { //////////////////////////////////////////////////////////////////////////////////////////////////// -JavascriptFunction::JavascriptFunction( v8::Handle iFunction, JavascriptContext^ context) +JavascriptFunction::JavascriptFunction(v8::Handle iFunction, JavascriptContext^ context) { if (!iFunction->IsFunction()) throw gcnew System::ArgumentException("Trying to use non-function as function"); @@ -18,8 +18,7 @@ JavascriptFunction::JavascriptFunction( v8::Handle iFunction, Javasc if(!context) throw gcnew System::ArgumentException("Must provide a JavascriptContext"); - mFuncHandle = new Persistent(); - *mFuncHandle = Persistent::New(Handle::Cast(iFunction)); + mFuncHandle = new Persistent(context->GetCurrentIsolate(), Handle::Cast(iFunction)); mContext = context; } @@ -28,7 +27,7 @@ JavascriptFunction::~JavascriptFunction() if(mFuncHandle) { JavascriptScope scope(mContext); - mFuncHandle->Dispose(); + mFuncHandle->Reset(); delete mFuncHandle; mFuncHandle = nullptr; } @@ -40,18 +39,18 @@ JavascriptFunction::!JavascriptFunction() if(mFuncHandle) { JavascriptScope scope(mContext); - mFuncHandle->Dispose(); + mFuncHandle->Reset(); delete mFuncHandle; mFuncHandle = nullptr; } } System::Object^ JavascriptFunction::Call(... cli::array^ args) -{ +{ JavascriptScope scope(mContext); - HandleScope handleScope; + HandleScope handleScope(mContext->GetCurrentIsolate()); - Handle global = (*mFuncHandle)->CreationContext()->Global(); + Handle global = mContext->GetGlobal(); int argc = args->Length; Handle *argv = new Handle[argc]; @@ -60,29 +59,29 @@ System::Object^ JavascriptFunction::Call(... cli::array^ args) argv[i] = JavascriptInterop::ConvertToV8(args[i]); } - Local retVal = (*mFuncHandle)->Call(global, argc, argv); + Local retVal = mFuncHandle->Get(mContext->GetCurrentIsolate())->Call(global, argc, argv); delete [] argv; return JavascriptInterop::ConvertFromV8(retVal); } -bool JavascriptFunction::operator==( JavascriptFunction^ func1, JavascriptFunction^ func2 ) +bool JavascriptFunction::operator==(JavascriptFunction^ func1, JavascriptFunction^ func2) { if(ReferenceEquals(func2, nullptr)) { return false; } - Handle jsFuncPtr1 = *(func1->mFuncHandle); - Handle jsFuncPtr2 = *(func2->mFuncHandle); + Handle jsFuncPtr1 = func1->mFuncHandle->Get(func1->mContext->GetCurrentIsolate()); + Handle jsFuncPtr2 = func2->mFuncHandle->Get(func2->mContext->GetCurrentIsolate()); return jsFuncPtr1->Equals(jsFuncPtr2); } -bool JavascriptFunction::Equals( JavascriptFunction^ other ) +bool JavascriptFunction::Equals(JavascriptFunction^ other) { return this == other; } -bool JavascriptFunction::Equals(Object^ other ) +bool JavascriptFunction::Equals(Object^ other) { JavascriptFunction^ otherFunc = dynamic_cast(other); return (otherFunc && this->Equals(otherFunc)); diff --git a/Source/Noesis.Javascript/JavascriptFunction.h b/Source/Noesis.Javascript/JavascriptFunction.h index 387f03c..199fe29 100644 --- a/Source/Noesis.Javascript/JavascriptFunction.h +++ b/Source/Noesis.Javascript/JavascriptFunction.h @@ -35,7 +35,7 @@ public ref class JavascriptFunction private: v8::Persistent* mFuncHandle; - JavascriptContext^ mContext; + JavascriptContext^ mContext; }; ////////////////////////////////////////////////////////////////////////// diff --git a/Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs b/Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs index 6456710..055f7ad 100644 --- a/Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs +++ b/Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs @@ -20,7 +20,7 @@ public void TearDown() _context.Dispose(); } - [TestMethod] + [TestMethod] public void GetFunctionFromJsContext() { _context.Run("a = function(a,b) {return a+b;}"); From 0ed8cb947f1e2a70e82273eb22c4898dbaac61c1 Mon Sep 17 00:00:00 2001 From: Sebastian Pahnke Date: Tue, 8 May 2018 10:08:22 +0200 Subject: [PATCH 3/4] Add more tests for function passing and managed object interop --- .../JavascriptFunctionTests.cs | 48 +++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs b/Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs index 055f7ad..8a40d01 100644 --- a/Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs +++ b/Tests/Noesis.Javascript.Tests/JavascriptFunctionTests.cs @@ -1,4 +1,6 @@ -using FluentAssertions; +using System.Collections.Generic; +using System.Linq; +using FluentAssertions; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Noesis.Javascript.Tests @@ -21,13 +23,53 @@ public void TearDown() } [TestMethod] - public void GetFunctionFromJsContext() + public void GetFunctionExpressionFromJsContext() { - _context.Run("a = function(a,b) {return a+b;}"); + _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().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().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().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; + result.Should().NotBeNull(); + result.Should().BeEquivalentTo(2, 4); + } + } + + class CollectionWrapper + { + private IEnumerable numbers = new List { 1, 2, 3, 4, 5 }; + + public IEnumerable Filter(JavascriptFunction predicate) + { + return numbers.Where(x => (bool) predicate.Call(x)); + } } } From 2e7e9124d558db209211dfee3ccdb7abcff9cb5d Mon Sep 17 00:00:00 2001 From: Sebastian Pahnke Date: Thu, 10 May 2018 13:23:19 +0200 Subject: [PATCH 4/4] Only reset the V8 function handle if the context has not been disposed --- Source/Noesis.Javascript/JavascriptContext.cpp | 9 +++++++++ Source/Noesis.Javascript/JavascriptContext.h | 6 +++++- Source/Noesis.Javascript/JavascriptFunction.cpp | 14 ++++++++++---- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/Source/Noesis.Javascript/JavascriptContext.cpp b/Source/Noesis.Javascript/JavascriptContext.cpp index 303e988..3af39a4 100644 --- a/Source/Noesis.Javascript/JavascriptContext.cpp +++ b/Source/Noesis.Javascript/JavascriptContext.cpp @@ -157,6 +157,7 @@ JavascriptContext::JavascriptContext() mExternals = gcnew System::Collections::Generic::Dictionary(); HandleScope scope(isolate); mContext = new Persistent(isolate, Context::New(isolate)); + mIsDisposed = false; } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -173,6 +174,7 @@ JavascriptContext::~JavascriptContext() } if (isolate != NULL) isolate->Dispose(); + mIsDisposed = true; } //////////////////////////////////////////////////////////////////////////////////////////////////// @@ -357,6 +359,13 @@ JavascriptContext::GetCurrentIsolate() //////////////////////////////////////////////////////////////////////////////////////////////////// +bool JavascriptContext::IsDisposed() +{ + return mIsDisposed; +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// + Handle JavascriptContext::GetGlobal() { return mContext->Get(this->GetCurrentIsolate())->Global(); diff --git a/Source/Noesis.Javascript/JavascriptContext.h b/Source/Noesis.Javascript/JavascriptContext.h index aed20ef..3174553 100644 --- a/Source/Noesis.Javascript/JavascriptContext.h +++ b/Source/Noesis.Javascript/JavascriptContext.h @@ -181,7 +181,9 @@ public ref class JavascriptContext: public System::IDisposable JavascriptExternal* WrapObject(System::Object^ iObject); Handle GetObjectWrapperTemplate(); - + + bool IsDisposed(); + static void FatalErrorCallbackMember(const char* location, const char* message); //////////////////////////////////////////////////////////// @@ -205,6 +207,8 @@ public ref class JavascriptContext: public System::IDisposable // the context is destroyed. System::Collections::Generic::Dictionary ^mExternals; + bool mIsDisposed; + // Keeping track of recursion. [System::ThreadStaticAttribute] static JavascriptContext ^sCurrentContext; diff --git a/Source/Noesis.Javascript/JavascriptFunction.cpp b/Source/Noesis.Javascript/JavascriptFunction.cpp index a6ff73f..c890a9c 100644 --- a/Source/Noesis.Javascript/JavascriptFunction.cpp +++ b/Source/Noesis.Javascript/JavascriptFunction.cpp @@ -26,8 +26,11 @@ JavascriptFunction::~JavascriptFunction() { if(mFuncHandle) { - JavascriptScope scope(mContext); - mFuncHandle->Reset(); + if (mContext && !mContext->IsDisposed()) + { + JavascriptScope scope(mContext); + mFuncHandle->Reset(); + } delete mFuncHandle; mFuncHandle = nullptr; } @@ -38,8 +41,11 @@ JavascriptFunction::!JavascriptFunction() { if(mFuncHandle) { - JavascriptScope scope(mContext); - mFuncHandle->Reset(); + if (mContext && !mContext->IsDisposed()) + { + JavascriptScope scope(mContext); + mFuncHandle->Reset(); + } delete mFuncHandle; mFuncHandle = nullptr; }