From a6fdf60dab5c2beab5d70aace8824ac4e7c7ee2a Mon Sep 17 00:00:00 2001 From: Matthew Podwysocki Date: Mon, 22 Aug 2016 11:38:33 -0400 Subject: [PATCH] Implement ChakraBridge in C++ (#584) * feat(ChakraBridge) - Initial commit * feat(ChakraBridge) - Additional features * feat(ChakraBridge) - Add all major functions * feat(ChakraBridge) - removing db file * feat(ChakraBridge) - Add public facing class * feat(ChakraBridge) - Add get/set global variables * feat(ChakraBridge) - Removing JsonValue^ * feat(ChakraBridge) - Adding LoadFromScript * feat(ChakraBridge) - Updating gitignore * feat(ChakraBridge) - Adding extra methods * feat(ChakraBridge) - Removing old code * feat(ChakraBridge) - Better error handling * feat(ChakraBridge) - Turn into WinRT component * feat(ChakraBridge) - Fix files * feat(ChakraBridge) - Fix files * feat(ChakraBridge) - rename class to JsrtJavaScriptExecturo * feat(ChakraBridge) - change as per comments on #584 * feat(ChakraBridge) - Adding load from file * feat(ChakraBridge) - Removing require method stuff * feat(ChakraBridge) - Fixing debug writeln * feat(ChakraBridge) - Add serialize script * feat(ChakraBridge) - Add pseudocode for loading from file * feat(ChakraBridge) - better cleanup for methods * feat(ChakraBridge) - Add context for callbacks * feat(ChakraBridge) - Fixed callbacks * feat(ChakraBridge) - Add byte* overload for loading serialized script * feat(ChakraBridge) - renamed to ChakraBridge * feat(ChakraBridge) - fix release builds * feat(ChakraBridge) - add serialization methods to bridge * feat(ChakraBridge) - remove unneeded headers * feat(ChakraBridge) - add XML docs --- .gitignore | 19 + .../ChakraBridge/ChakraBridge.vcxproj | 230 ++++++++++ .../ChakraBridge/ChakraBridge.vcxproj.filters | 21 + ReactWindows/ChakraBridge/ChakraHost.cpp | 402 ++++++++++++++++++ ReactWindows/ChakraBridge/ChakraHost.h | 145 +++++++ .../ChakraBridge/ChakraStringResult.h | 18 + .../ChakraBridge/NativeJavaScriptExecutor.cpp | 217 ++++++++++ .../ChakraBridge/NativeJavaScriptExecutor.h | 146 +++++++ .../ChakraBridge/SerializedSourceContext.h | 20 + ReactWindows/ChakraBridge/pch.cpp | 1 + ReactWindows/ChakraBridge/pch.h | 53 +++ ReactWindows/ReactNative.sln | 34 +- ReactWindows/ReactNative/ReactNative.csproj | 7 +- 13 files changed, 1311 insertions(+), 2 deletions(-) create mode 100644 ReactWindows/ChakraBridge/ChakraBridge.vcxproj create mode 100644 ReactWindows/ChakraBridge/ChakraBridge.vcxproj.filters create mode 100644 ReactWindows/ChakraBridge/ChakraHost.cpp create mode 100644 ReactWindows/ChakraBridge/ChakraHost.h create mode 100644 ReactWindows/ChakraBridge/ChakraStringResult.h create mode 100644 ReactWindows/ChakraBridge/NativeJavaScriptExecutor.cpp create mode 100644 ReactWindows/ChakraBridge/NativeJavaScriptExecutor.h create mode 100644 ReactWindows/ChakraBridge/SerializedSourceContext.h create mode 100644 ReactWindows/ChakraBridge/pch.cpp create mode 100644 ReactWindows/ChakraBridge/pch.h diff --git a/.gitignore b/.gitignore index 9f1293ecab..5a704fd67b 100644 --- a/.gitignore +++ b/.gitignore @@ -116,3 +116,22 @@ packages/ #JavaScript files *.jsbundle + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Build results +[Dd]ebugPublic/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Ll]og/ diff --git a/ReactWindows/ChakraBridge/ChakraBridge.vcxproj b/ReactWindows/ChakraBridge/ChakraBridge.vcxproj new file mode 100644 index 0000000000..c77cb2c5b6 --- /dev/null +++ b/ReactWindows/ChakraBridge/ChakraBridge.vcxproj @@ -0,0 +1,230 @@ + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + {4b72c796-16d5-4e3a-81c0-3e36f531e578} + WindowsRuntimeComponent + ChakraBridge + en-US + 14.0 + true + Windows Store + 10.0.10586.0 + 10.0.10240.0 + 10.0 + + + + DynamicLibrary + true + v140 + + + DynamicLibrary + true + v140 + + + DynamicLibrary + true + v140 + + + DynamicLibrary + false + true + v140 + + + DynamicLibrary + false + true + v140 + + + DynamicLibrary + false + true + v140 + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + + + false + + + false + + + false + + + false + + + false + + + + Use + _WINRT_DLL;USE_EDGEMODE_JSRT;%(PreprocessorDefinitions) + pch.h + $(IntDir)pch.pch + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + /bigobj %(AdditionalOptions) + 28204 + + + Console + false + + + + + Use + _WINRT_DLL;USE_EDGEMODE_JSRT;NDEBUG;%(PreprocessorDefinitions) + pch.h + $(IntDir)pch.pch + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + /bigobj %(AdditionalOptions) + 28204 + + + Console + false + + + + + Use + _WINRT_DLL;%(PreprocessorDefinitions) + pch.h + $(IntDir)pch.pch + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + /bigobj %(AdditionalOptions) + 28204 + + + Console + false + + + + + Use + _WINRT_DLL;NDEBUG;%(PreprocessorDefinitions) + pch.h + $(IntDir)pch.pch + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + /bigobj %(AdditionalOptions) + 28204 + + + Console + false + + + + + Use + _WINRT_DLL;%(PreprocessorDefinitions) + pch.h + $(IntDir)pch.pch + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + /bigobj %(AdditionalOptions) + 28204 + + + Console + false + + + + + Use + _WINRT_DLL;NDEBUG;%(PreprocessorDefinitions) + pch.h + $(IntDir)pch.pch + $(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories) + /bigobj %(AdditionalOptions) + 28204 + + + Console + false + + + + + + + + + + + + + + Create + Create + Create + Create + Create + Create + + + + + + \ No newline at end of file diff --git a/ReactWindows/ChakraBridge/ChakraBridge.vcxproj.filters b/ReactWindows/ChakraBridge/ChakraBridge.vcxproj.filters new file mode 100644 index 0000000000..9e4395a596 --- /dev/null +++ b/ReactWindows/ChakraBridge/ChakraBridge.vcxproj.filters @@ -0,0 +1,21 @@ + + + + + a4e4ad2b-1c8c-48ad-b1a7-45cfc1ce191a + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ReactWindows/ChakraBridge/ChakraHost.cpp b/ReactWindows/ChakraBridge/ChakraHost.cpp new file mode 100644 index 0000000000..8827219f21 --- /dev/null +++ b/ReactWindows/ChakraBridge/ChakraHost.cpp @@ -0,0 +1,402 @@ +#include "pch.h" +#include "ChakraHost.h" +#include "SerializedSourceContext.h" + +void ThrowException(const wchar_t* szException) +{ + // We ignore error since we're already in an error state. + JsValueRef errorValue; + JsValueRef errorObject; + JsPointerToString(szException, wcslen(szException), &errorValue); + JsCreateError(errorValue, &errorObject); + JsSetException(errorObject); +}; + +JsErrorCode DefineHostCallback(JsValueRef globalObject, const wchar_t *callbackName, JsNativeFunction callback, void *callbackState) +{ + JsPropertyIdRef propertyId; + IfFailRet(JsGetPropertyIdFromName(callbackName, &propertyId)); + + JsValueRef function; + IfFailRet(JsCreateFunction(callback, callbackState, &function)); + IfFailRet(JsSetProperty(globalObject, propertyId, function, true)); + + return JsNoError; +}; + +JsValueRef InvokeConsole(const wchar_t* kind, JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState) +{ +#ifdef _DEBUG + + ChakraHost* self = (ChakraHost*)callbackState; + wchar_t buff[56]; + swprintf(buff, 56, L"[JS {%s}] ", kind); + OutputDebugStringW(buff); + + // First argument is this-context, ignore... + for (USHORT i = 1; i < argumentCount; i++) + { + JsValueRef resultJSString; + IfFailThrow(self->JsonStringify(arguments[i], &resultJSString), L"JSON.stringify failed."); + + const wchar_t* szBuf; + size_t szBufLen; + IfFailThrow(JsStringToPointer(resultJSString, &szBuf, &szBufLen), L"Failed to get string from pointer."); + + OutputDebugStringW(szBuf); + OutputDebugStringW(L" "); + } + + OutputDebugStringW(L"\n"); + +#endif + + return JS_INVALID_REFERENCE; +}; + +JsValueRef CALLBACK ConsoleLog(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState) +{ + return InvokeConsole(L"log", callee, isConstructCall, arguments, argumentCount, callbackState); +} + +JsValueRef CALLBACK ConsoleWarn(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState) +{ + return InvokeConsole(L"warn", callee, isConstructCall, arguments, argumentCount, callbackState); +} + +JsValueRef CALLBACK ConsoleInfo(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState) +{ + return InvokeConsole(L"info", callee, isConstructCall, arguments, argumentCount, callbackState); +} + +JsValueRef CALLBACK ConsoleError(JsValueRef callee, bool isConstructCall, JsValueRef *arguments, unsigned short argumentCount, void *callbackState) +{ + return InvokeConsole(L"error", callee, isConstructCall, arguments, argumentCount, callbackState); +} + +bool CALLBACK LoadSourceCallback(JsSourceContext sourceContext, const wchar_t** scriptBuffer) +{ + SerializedSourceContext* context = (SerializedSourceContext*)sourceContext; + FILE* file; + + _wfopen_s(&file, context->sourcePath, L"rb"); + + unsigned int current = ftell(file); + fseek(file, 0, SEEK_END); + unsigned int end = ftell(file); + fseek(file, current, SEEK_SET); + unsigned int lengthBytes = end - current; + char* rawBytes = (char *)calloc(lengthBytes + 1, sizeof(char)); + + if (rawBytes == nullptr) + { + return false; + } + + fread((void *)rawBytes, sizeof(char), lengthBytes, file); + fclose(file); + + wchar_t* contents = (wchar_t *)calloc(lengthBytes + 1, sizeof(wchar_t)); + if (contents == nullptr) + { + free(rawBytes); + return false; + } + + if (MultiByteToWideChar(CP_UTF8, 0, rawBytes, lengthBytes + 1, contents, lengthBytes + 1) == 0) + { + free(rawBytes); + free(contents); + return false; + } + + free(rawBytes); + *scriptBuffer = contents; + + return true; +} + +void CALLBACK UnloadSourceCallback(JsSourceContext sourceContext) +{ + SerializedSourceContext* context = (SerializedSourceContext*)sourceContext; + delete context; +} + +JsErrorCode ChakraHost::RunSerailizedScript(BYTE* buffer, const wchar_t* szPath, const wchar_t* szSourceUri, JsValueRef* result) +{ + SerializedSourceContext* context = new SerializedSourceContext(); + context->byteCode = buffer; + context->sourcePath = szPath; + context->scriptBuffer = nullptr; + + IfFailRet(JsRunSerializedScriptWithCallback( + &LoadSourceCallback, + &UnloadSourceCallback, + buffer, + (JsSourceContext)context, + szSourceUri, + result)); + + return JsNoError; +} + +JsErrorCode ChakraHost::RunSerializedScriptFromFile(const wchar_t* szSerializedPath, const wchar_t* szPath, const wchar_t* szSourceUri, JsValueRef* result) +{ + FILE* file = NULL; + BYTE* buffer = NULL; + if (_wfopen_s(&file, szSerializedPath, L"rb")) + { + return JsErrorInvalidArgument; + } + + unsigned int current = ftell(file); + fseek(file, 0, SEEK_END); + unsigned int end = ftell(file); + fseek(file, current, SEEK_SET); + unsigned int lengthBytes = end - current; + buffer = (BYTE*)calloc(lengthBytes + 1, sizeof(BYTE)); + fread((void *)buffer, sizeof(BYTE), lengthBytes, file); + fclose(file); + + IfFailRet(RunSerailizedScript(buffer, szPath, szSourceUri, result)); + + return JsNoError; +} + +JsErrorCode ChakraHost::SerializeScriptFromFile(const wchar_t* szPath, const wchar_t* szDestination) +{ + JsErrorCode status = JsNoError; + FILE *file; + char *rawBytes = nullptr; + wchar_t *contents = nullptr; + + if (_wfopen_s(&file, szPath, L"rb")) + { + status = JsErrorInvalidArgument; + goto cleanup; + } + + unsigned int current = ftell(file); + fseek(file, 0, SEEK_END); + unsigned int end = ftell(file); + fseek(file, current, SEEK_SET); + unsigned int lengthBytes = end - current; + rawBytes = (char *)calloc(lengthBytes + 1, sizeof(char)); + + if (rawBytes == nullptr) + { + status = JsErrorFatal; + goto cleanup; + } + + fread((void *)rawBytes, sizeof(char), lengthBytes, file); + fclose(file); + + contents = (wchar_t *)calloc(lengthBytes + 1, sizeof(wchar_t)); + if (contents == nullptr) + { + status = JsErrorFatal; + goto cleanup; + } + + if (MultiByteToWideChar(CP_UTF8, 0, rawBytes, lengthBytes + 1, contents, lengthBytes + 1) == 0) + { + status = JsErrorFatal; + goto cleanup; + } + + IfFailCleanup(SerializeScript(contents, szDestination)); + +cleanup: + if (rawBytes) + { + free(rawBytes); + } + if (contents) + { + free(contents); + } + + return status; +} + +JsErrorCode ChakraHost::SerializeScript(const wchar_t* szScript, const wchar_t* szDestination) +{ + JsErrorCode status; + BYTE* buf = NULL; + ULONG bufSize = 0L; + IfFailCleanup(JsSerializeScript(szScript, buf, &bufSize)); + + buf = new BYTE[bufSize]; + IfFailCleanup(JsSerializeScript(szScript, buf, &bufSize)); + + FILE *file; + if (_wfopen_s(&file, szDestination, L"wb")) + { + status = JsErrorInvalidArgument; + goto cleanup; + } + + if (fwrite(buf, sizeof(BYTE), bufSize, file) != bufSize) + { + status = JsErrorFatal; + goto cleanup; + } + + if (fclose(file)) + { + status = JsErrorFatal; + goto cleanup; + } + +cleanup: + if (buf) + { + delete[] buf; + } + return status; +} + +JsErrorCode ChakraHost::RunScriptFromFile(const wchar_t* szFileName, const wchar_t* szSourceUri, JsValueRef* result) +{ + JsErrorCode status = JsNoError; + FILE *file; + + if (_wfopen_s(&file, szFileName, L"rb")) + { + return JsErrorInvalidArgument; + } + + unsigned int current = ftell(file); + fseek(file, 0, SEEK_END); + unsigned int end = ftell(file); + fseek(file, current, SEEK_SET); + unsigned int lengthBytes = end - current; + char *rawBytes = (char *)calloc(lengthBytes + 1, sizeof(char)); + + if (rawBytes == nullptr) + { + return JsErrorFatal; + } + + fread((void *)rawBytes, sizeof(char), lengthBytes, file); + if (fclose(file)) + { + return JsErrorFatal; + } + + wchar_t *contents = (wchar_t *)calloc(lengthBytes + 1, sizeof(wchar_t)); + if (contents == nullptr) + { + free(rawBytes); + return JsErrorFatal; + } + + if (MultiByteToWideChar(CP_UTF8, 0, rawBytes, lengthBytes + 1, contents, lengthBytes + 1) == 0) + { + free(rawBytes); + free(contents); + return JsErrorFatal; + } + + status = RunScript(contents, szSourceUri, result); + + free(rawBytes); + free(contents); + + return status; +} + +JsErrorCode ChakraHost::RunScript(const wchar_t* szScript, const wchar_t* szSourceUri, JsValueRef* result) +{ + return JsRunScript(szScript, currentSourceContext++, szSourceUri, result); +} + +JsErrorCode ChakraHost::JsonStringify(JsValueRef argument, JsValueRef* result) +{ + JsValueRef args[2] = { globalObject, argument }; + IfFailRet(JsCallFunction(jsonStringifyObject, args, 2, result)); + return JsNoError; +} + +JsErrorCode ChakraHost::JsonParse(JsValueRef argument, JsValueRef* result) +{ + JsValueRef args[2] = { globalObject, argument }; + IfFailRet(JsCallFunction(jsonParseObject, args, 2, result)); + return JsNoError; +} + +JsErrorCode ChakraHost::GetGlobalVariable(const wchar_t* szPropertyName, JsValueRef* result) +{ + JsPropertyIdRef globalVarId; + IfFailRet(JsGetPropertyIdFromName(szPropertyName, &globalVarId)); + IfFailRet(JsGetProperty(globalObject, globalVarId, result)); + return JsNoError; +} + +JsErrorCode ChakraHost::SetGlobalVariable(const wchar_t* szPropertyName, JsValueRef value) +{ + JsPropertyIdRef globalVarId; + IfFailRet(JsGetPropertyIdFromName(szPropertyName, &globalVarId)); + IfFailRet(JsSetProperty(globalObject, globalVarId, value, true)); + return JsNoError; +} + +JsErrorCode ChakraHost::InitJson() +{ + JsPropertyIdRef jsonPropertyId; + IfFailRet(JsGetPropertyIdFromName(L"JSON", &jsonPropertyId)); + JsValueRef jsonObject; + IfFailRet(JsGetProperty(globalObject, jsonPropertyId, &jsonObject)); + + JsPropertyIdRef jsonParseId; + IfFailRet(JsGetPropertyIdFromName(L"parse", &jsonParseId)); + IfFailRet(JsGetProperty(jsonObject, jsonParseId, &jsonParseObject)); + + JsPropertyIdRef jsonStringifyId; + IfFailRet(JsGetPropertyIdFromName(L"stringify", &jsonStringifyId)); + IfFailRet(JsGetProperty(jsonObject, jsonStringifyId, &jsonStringifyObject)); + + return JsNoError; +} + +JsErrorCode ChakraHost::InitConsole() +{ + JsPropertyIdRef consolePropertyId; + IfFailRet(JsGetPropertyIdFromName(L"console", &consolePropertyId)); + + JsValueRef consoleObject; + IfFailRet(JsCreateObject(&consoleObject)); + IfFailRet(JsSetProperty(globalObject, consolePropertyId, consoleObject, true)); + + IfFailRet(DefineHostCallback(consoleObject, L"info", ConsoleInfo, this)); + IfFailRet(DefineHostCallback(consoleObject, L"log", ConsoleLog, this)); + IfFailRet(DefineHostCallback(consoleObject, L"warn", ConsoleWarn, this)); + IfFailRet(DefineHostCallback(consoleObject, L"error", ConsoleError, this)); + + return JsNoError; +} + +JsErrorCode ChakraHost::Init() +{ + currentSourceContext = 0; + + IfFailRet(JsCreateRuntime(JsRuntimeAttributeNone, nullptr, &runtime)); + IfFailRet(JsCreateContext(runtime, &context)); + IfFailRet(JsSetCurrentContext(context)); + + IfFailRet(JsGetGlobalObject(&globalObject)); + + IfFailRet(InitJson()); + IfFailRet(InitConsole()); + + return JsNoError; +} + +JsErrorCode ChakraHost::Destroy() +{ + IfFailRet(JsSetCurrentContext(JS_INVALID_REFERENCE)); + IfFailRet(JsDisposeRuntime(runtime)); + + return JsNoError; +} \ No newline at end of file diff --git a/ReactWindows/ChakraBridge/ChakraHost.h b/ReactWindows/ChakraBridge/ChakraHost.h new file mode 100644 index 0000000000..d7db471afa --- /dev/null +++ b/ReactWindows/ChakraBridge/ChakraHost.h @@ -0,0 +1,145 @@ +#pragma once + +#include "pch.h" +#include + +/// +/// This class wraps the main functionality dealing with the JSRT. +/// +class ChakraHost +{ +public: + /// + /// Initializes a JSRT session with the context, globals, JSON, and console logging. + /// + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + JsErrorCode Init(); + + /// + /// Destorys the current JSRT session. + /// + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + JsErrorCode Destroy(); + + /// + /// Serializes a given script and saves it to the desired location. + /// + /// The script string to serialize + /// The destination to save the serialized script. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + JsErrorCode SerializeScript(const wchar_t* szScript, const wchar_t* szDestination); + + /// + /// Loads a script file, serializes its contents and then saves to the desired location. + /// + /// The file path of the script to serialize. + /// The destination to save the serialized script. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + JsErrorCode SerializeScriptFromFile(const wchar_t* szPath, const wchar_t* szDestination); + + /// + /// Runs a serialized script from the given byte buffer and source URI and returns the result. + /// + /// A byte buffer which contains the serialized byte code. + /// The source URI for the script. + /// [Out] The result from running the serialized script. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + JsErrorCode RunSerailizedScript(BYTE* buffer, const wchar_t* szPath, const wchar_t* szSourceUri, JsValueRef* result); + + /// + /// Loads a serialized script from the path, runs it with the byte buffer and source URI and returns the result. + /// + /// The path containing the serialized script. + /// The source URI for the script. + /// [Out] The result from running the serialized script. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + JsErrorCode RunSerializedScriptFromFile(const wchar_t* szSerializedPath, const wchar_t* szPath, const wchar_t* szSourceUri, JsValueRef* result); + + /// + /// Runs the given script with the source URI and returns the result. + /// + /// The script to run. + /// The source URI of the script. + /// [Out] The result from running the script. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + JsErrorCode RunScript(const wchar_t* szScript, const wchar_t* szSourceUri, JsValueRef* result); + + /// + /// Runs the script from the file location with the source URI and returns the result. + /// + /// The location of the script to run. + /// The source URI of the script. + /// [Out] The result from running the script. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + JsErrorCode RunScriptFromFile(const wchar_t* szPath, const wchar_t* szSourceUri, JsValueRef* result); + + /// + /// Calls JSON stringify on the given and returns the result. + /// + /// The argument to stringify. + /// [Out] The result from running JSON.stringify. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + JsErrorCode JsonStringify(JsValueRef argument, JsValueRef* result); + + /// + /// Calls JSON parse on the given and returns the result. + /// + /// The argument to parse. + /// [Out] The result from running JSON.parse. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + JsErrorCode JsonParse(JsValueRef argument, JsValueRef* result); + + /// + /// Gets a global variable and returns the result. + /// + /// The property name from global to get. + /// [Out] The global property value. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + JsErrorCode GetGlobalVariable(const wchar_t* szPropertyName, JsValueRef* result); + + /// + /// Sets a global variable with the . + /// + /// The property name from global to get. + /// The global property value to set. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + JsErrorCode SetGlobalVariable(const wchar_t* szPropertyName, JsValueRef value); + + /// + /// The JSRT global object for the session. + /// + JsValueRef globalObject; +private: + JsErrorCode InitJson(); + JsErrorCode InitConsole(); + + unsigned currentSourceContext; + JsRuntimeHandle runtime; + JsContextRef context; + JsValueRef jsonParseObject; + JsValueRef jsonStringifyObject; +}; \ No newline at end of file diff --git a/ReactWindows/ChakraBridge/ChakraStringResult.h b/ReactWindows/ChakraBridge/ChakraStringResult.h new file mode 100644 index 0000000000..60a8744051 --- /dev/null +++ b/ReactWindows/ChakraBridge/ChakraStringResult.h @@ -0,0 +1,18 @@ +#pragma once + +using namespace Platform; + +namespace ChakraBridge { + +/// +/// A compound result containing the string result and a for the error. +/// +public value struct ChakraStringResult +{ + /// The for the operation, JsNoError if no error has occurred. + int ErrorCode; + /// The string result for the operation. + String^ Result; +}; + +}; diff --git a/ReactWindows/ChakraBridge/NativeJavaScriptExecutor.cpp b/ReactWindows/ChakraBridge/NativeJavaScriptExecutor.cpp new file mode 100644 index 0000000000..486ca82fd0 --- /dev/null +++ b/ReactWindows/ChakraBridge/NativeJavaScriptExecutor.cpp @@ -0,0 +1,217 @@ +#include "pch.h" +#include "NativeJavaScriptExecutor.h" + +using namespace ChakraBridge; + +int NativeJavaScriptExecutor::InitializeHost() +{ + return this->host.Init(); +} + +int NativeJavaScriptExecutor::DisposeHost() +{ + return this->host.Destroy(); +} + +int NativeJavaScriptExecutor::SetGlobalVariable(String^ variableName, String^ stringifiedText) +{ + JsValueRef valueStringified; + IfFailRet(JsPointerToString(stringifiedText->Data(), stringifiedText->Length(), &valueStringified)); + + JsValueRef valueJson; + IfFailRet(this->host.JsonParse(valueStringified, &valueJson)); + IfFailRet(this->host.SetGlobalVariable(variableName->Data(), valueJson)); + + return JsNoError; +} + +ChakraStringResult NativeJavaScriptExecutor::GetGlobalVariable(String^ variableName) +{ + JsValueRef globalVariable; + IfFailRetNullPtr(this->host.GetGlobalVariable(variableName->Data(), &globalVariable)); + + JsValueRef globalVariableJson; + IfFailRetNullPtr(this->host.JsonStringify(globalVariable, &globalVariableJson)); + + const wchar_t* szBuf; + size_t bufLen; + IfFailRetNullPtr(JsStringToPointer(globalVariableJson, &szBuf, &bufLen)); + + ChakraStringResult finalResult = { JsNoError, ref new String(szBuf, bufLen) }; + return finalResult; +} + +ChakraStringResult NativeJavaScriptExecutor::RunScript(String^ source, String^ sourceUri) +{ + JsValueRef result; + IfFailRetNullPtr(this->host.RunScript(source->Data(), sourceUri->Data(), &result)); + + JsValueRef resultJson; + IfFailRetNullPtr(this->host.JsonStringify(result, &resultJson)); + + const wchar_t* szBuf; + size_t bufLen; + IfFailRetNullPtr(JsStringToPointer(resultJson, &szBuf, &bufLen)); + + ChakraStringResult finalResult = { JsNoError, ref new String(szBuf, bufLen) }; + return finalResult; +} + +ChakraStringResult NativeJavaScriptExecutor::RunScriptFromFile(String^ sourceFilePath, String^ sourceUri) +{ + JsValueRef result; + IfFailRetNullPtr(this->host.RunScriptFromFile(sourceFilePath->Data(), sourceUri->Data(), &result)); + + JsValueRef resultJson; + IfFailRetNullPtr(this->host.JsonStringify(result, &resultJson)); + + const wchar_t* szBuf; + size_t bufLen; + IfFailRetNullPtr(JsStringToPointer(resultJson, &szBuf, &bufLen)); + + ChakraStringResult finalResult = { JsNoError, ref new String(szBuf, bufLen) }; + return finalResult; +} + +int NativeJavaScriptExecutor::SerializeScript(String^ source, String^ destination) { + return this->host.SerializeScript(source->Data(), destination->Data()); +} + +int NativeJavaScriptExecutor::SerializeScriptFromFile(String^ file, String^ destination) +{ + return this->host.SerializeScriptFromFile(file->Data(), destination->Data()); +} + +ChakraStringResult NativeJavaScriptExecutor::RunSerializedScript(const Array^ buffer, String^ sourceFilePath, String^ sourceUri) +{ + JsValueRef result; + IfFailRetNullPtr(this->host.RunSerailizedScript(buffer->Data, sourceFilePath->Data(), sourceUri->Data(), &result)); + + JsValueRef resultJson; + IfFailRetNullPtr(this->host.JsonStringify(result, &resultJson)); + + const wchar_t* szBuf; + size_t bufLen; + IfFailRetNullPtr(JsStringToPointer(resultJson, &szBuf, &bufLen)); + + ChakraStringResult finalResult = { JsNoError, ref new String(szBuf, bufLen) }; + return finalResult; +} + +ChakraStringResult NativeJavaScriptExecutor::RunSerializedScriptFromFile(String^ serializedPath, String^ sourceFilePath, String^ sourceUri) +{ + JsValueRef result; + IfFailRetNullPtr(this->host.RunSerializedScriptFromFile(serializedPath->Data(), sourceFilePath->Data(), sourceUri->Data(), &result)); + + JsValueRef resultJson; + IfFailRetNullPtr(this->host.JsonStringify(result, &resultJson)); + + const wchar_t* szBuf; + size_t bufLen; + IfFailRetNullPtr(JsStringToPointer(resultJson, &szBuf, &bufLen)); + + ChakraStringResult finalResult = { JsNoError, ref new String(szBuf, bufLen) }; + return finalResult; +} + +ChakraStringResult NativeJavaScriptExecutor::CallFunctionAndReturnFlushedQueue(String^ moduleName, String^ methodName, String^ args) +{ + JsPropertyIdRef modulePropertyId; + IfFailRetNullPtr(JsGetPropertyIdFromName(moduleName->Data(), &modulePropertyId)); + + JsValueRef moduleObject; + IfFailRetNullPtr(JsGetProperty(host.globalObject, modulePropertyId, &moduleObject)); + + JsPropertyIdRef methodPropertyId; + IfFailRetNullPtr(JsGetPropertyIdFromName(methodName->Data(), &methodPropertyId)); + + JsValueRef methodObject; + IfFailRetNullPtr(JsGetProperty(moduleObject, methodPropertyId, &methodObject)); + + JsValueRef argObj; + IfFailRetNullPtr(JsPointerToString(args->Data(), args->Length(), &argObj)); + + JsValueRef jsonObj; + IfFailRetNullPtr(host.JsonParse(argObj, &jsonObj)); + + JsValueRef result; + JsValueRef newArgs[2] = { host.globalObject, jsonObj }; + IfFailRetNullPtr(JsCallFunction(methodObject, newArgs, 2, &result)); + + JsValueRef stringifiedResult; + IfFailRetNullPtr(host.JsonStringify(result, &stringifiedResult)); + + const wchar_t* szBuf; + size_t bufLen; + IfFailRetNullPtr(JsStringToPointer(stringifiedResult, &szBuf, &bufLen)); + + ChakraStringResult finalResult = { JsNoError, ref new String(szBuf, bufLen) }; + return finalResult; +} + +ChakraStringResult NativeJavaScriptExecutor::InvokeCallbackAndReturnFlushedQueue(int callbackId, String^ args) +{ + JsPropertyIdRef fbBridgeId; + IfFailRetNullPtr(JsGetPropertyIdFromName(L"__fbBatchedBridge", &fbBridgeId)); + + JsValueRef fbBridgeObj; + IfFailRetNullPtr(JsGetProperty(host.globalObject, fbBridgeId, &fbBridgeObj)); + + JsPropertyIdRef methodId; + IfFailRetNullPtr(JsGetPropertyIdFromName(L"invokeCallbackAndReturnFlushedQueue", &methodId)); + + JsValueRef method; + IfFailRetNullPtr(JsGetProperty(fbBridgeObj, methodId, &method)); + + JsValueRef callbackIdRef; + IfFailRetNullPtr(JsIntToNumber(callbackId, &callbackIdRef)); + + JsValueRef argsObj; + IfFailRetNullPtr(JsPointerToString(args->Data(), args->Length(), &argsObj)); + + JsValueRef argsJson; + IfFailRetNullPtr(host.JsonParse(argsObj, &argsJson)); + + JsValueRef result; + JsValueRef newArgs[3] = { host.globalObject, callbackIdRef, argsJson }; + IfFailRetNullPtr(JsCallFunction(method, newArgs, 3, &result)); + + JsValueRef stringifiedResult; + IfFailRetNullPtr(host.JsonStringify(result, &stringifiedResult)); + + const wchar_t* szBuf; + size_t bufLen; + IfFailRetNullPtr(JsStringToPointer(stringifiedResult, &szBuf, &bufLen)); + + ChakraStringResult finalResult = { JsNoError, ref new String(szBuf, bufLen) }; + return finalResult; +} + +ChakraStringResult NativeJavaScriptExecutor::FlushedQueue() +{ + JsPropertyIdRef fbBridgeId; + IfFailRetNullPtr(JsGetPropertyIdFromName(L"__fbBatchedBridge", &fbBridgeId)); + + JsValueRef fbBridgeObj; + IfFailRetNullPtr(JsGetProperty(host.globalObject, fbBridgeId, &fbBridgeObj)); + + JsPropertyIdRef methodId; + IfFailRetNullPtr(JsGetPropertyIdFromName(L"flushedQueue", &methodId)); + + JsValueRef method; + IfFailRetNullPtr(JsGetProperty(fbBridgeObj, methodId, &method)); + + JsValueRef result; + JsValueRef newArgs[1] = { host.globalObject }; + IfFailRetNullPtr(JsCallFunction(method, newArgs, 1, &result)); + + JsValueRef stringifiedResult; + IfFailRetNullPtr(host.JsonStringify(result, &stringifiedResult)); + + const wchar_t* szBuf; + size_t bufLen; + IfFailRetNullPtr(JsStringToPointer(stringifiedResult, &szBuf, &bufLen)); + + ChakraStringResult finalResult = { JsNoError, ref new String(szBuf, bufLen) }; + return finalResult; +} diff --git a/ReactWindows/ChakraBridge/NativeJavaScriptExecutor.h b/ReactWindows/ChakraBridge/NativeJavaScriptExecutor.h new file mode 100644 index 0000000000..4c55e46999 --- /dev/null +++ b/ReactWindows/ChakraBridge/NativeJavaScriptExecutor.h @@ -0,0 +1,146 @@ +#pragma once + +#include "pch.h" +#include "ChakraHost.h" +#include "ChakraStringResult.h" + +using namespace Platform; + +namespace ChakraBridge { + +/// +/// This class interfaces with the unmanaged JSRT Chakra Host. +/// +public ref class NativeJavaScriptExecutor sealed +{ +public: + /// + /// Initializes the JSRT session. + /// + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + int InitializeHost(); + + /// + /// Disposes the current JSRT session. + /// + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + int DisposeHost(); + + /// + /// Gets the global variable value. + /// + /// The global variable to get. + /// + /// A compount result with the JSON stringified value and an error code if any occurred. + /// + ChakraStringResult GetGlobalVariable(String^ variableName); + + /// + /// Sets the global variable + /// + /// The variable name to set. + /// The value to set on the global variable. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + int SetGlobalVariable(String^ variableName, String^ value); + + /// + /// Runs the given script with the source URI and returns the result. + /// + /// The source of the script to run. + /// The source URI of the script to run. + /// + /// A compount result with the JSON stringified value and an error code if any occurred. + /// + ChakraStringResult RunScript(String^ source, String^ sourceUri); + + /// + /// Runs the script from the file location and source URI and returns the result. + /// + /// The file path which contains the source to run. + /// The source URI of the script to run. + /// + /// A compount result with the JSON stringified value and an error code if any occurred. + /// + ChakraStringResult RunScriptFromFile(String^ sourceFilePath, String^ sourceUri); + + /// + /// Serializes a source script to a file destination. + /// + /// The source script to serialize. + /// The destination to save the serialized script. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + int SerializeScript(String^ source, String^ destination); + + /// + /// Serializes a script from a file location with source URI. + /// + /// The location of the source file to serialize. + /// The destination to save the serialized source. + /// + /// JsNoError is no error, else a JsErrorCode with the appropriate error. + /// + int SerializeScriptFromFile(String^ file, String^ destination); + + /// + /// Runs a serialized script from the byte array, source file path, and source URI. + /// + /// The buffer containing the serialized script. + /// The path of the source file. + /// The source URI. + /// + /// A compount result with the JSON stringified value and an error code if any occurred. + /// + ChakraStringResult RunSerializedScript(const Array^ buffer, String^ sourceFilePath, String^ sourceUri); + + /// + /// Runs a serialzed script from the serialized path, the source file path and source URI. + /// + /// The path containing the serailized script. + /// The path of the source file. + /// The source URI. + /// + /// A compount result with the JSON stringified value and an error code if any occurred. + /// + ChakraStringResult RunSerializedScriptFromFile(String^ serializedPath, String^ sourceFilePath, String^ sourceUri); + + /// + /// Calls the underlying function with the given module and method name and JSON stringified arguments. + /// + /// The module name to call. + /// The method name on the module to call. + /// JSON stringified arguments to call on the module. + /// + /// A compount result with the JSON stringified value and an error code if any occurred. + /// + ChakraStringResult CallFunctionAndReturnFlushedQueue(String^ moduleName, String^ methodName, String^ args); + + /// + /// Calls the underlying function with the callback ID and JSON stringified arguments. + /// + /// The callback ID. + /// JSON stringified arguments to call on the module. + /// + /// A compount result with the JSON stringified value and an error code if any occurred. + /// + ChakraStringResult InvokeCallbackAndReturnFlushedQueue(int callbackId, String^ args); + + /// + /// Calls the flush queue function. + /// + /// + /// A compount result with the JSON stringified value and an error code if any occurred. + /// + ChakraStringResult FlushedQueue(); +private: + ChakraHost host; +}; + +}; diff --git a/ReactWindows/ChakraBridge/SerializedSourceContext.h b/ReactWindows/ChakraBridge/SerializedSourceContext.h new file mode 100644 index 0000000000..8c2775eb87 --- /dev/null +++ b/ReactWindows/ChakraBridge/SerializedSourceContext.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +struct SerializedSourceContext +{ + BYTE* byteCode; + const wchar_t* sourcePath; + wchar_t* scriptBuffer; + + ~SerializedSourceContext() + { + delete[] byteCode; + delete[] sourcePath; + if (scriptBuffer) + { + delete[] scriptBuffer; + } + } +}; \ No newline at end of file diff --git a/ReactWindows/ChakraBridge/pch.cpp b/ReactWindows/ChakraBridge/pch.cpp new file mode 100644 index 0000000000..bcb5590be1 --- /dev/null +++ b/ReactWindows/ChakraBridge/pch.cpp @@ -0,0 +1 @@ +#include "pch.h" diff --git a/ReactWindows/ChakraBridge/pch.h b/ReactWindows/ChakraBridge/pch.h new file mode 100644 index 0000000000..489eede670 --- /dev/null +++ b/ReactWindows/ChakraBridge/pch.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "ChakraStringResult.h" + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include + +#define IfFailRetNullPtr(v) \ + { \ + JsErrorCode status = (v); \ + if (status != JsNoError) \ + { \ + ChakraStringResult stringResult; \ + stringResult.ErrorCode = status; \ + return stringResult; \ + } \ + } + +#define IfFailRet(v) \ + { \ + JsErrorCode error = (v); \ + if (error != JsNoError) \ + { \ + return error; \ + } \ + } + +#define IfFailThrow(v, e) \ + { \ + JsErrorCode error = (v); \ + if (error != JsNoError) \ + { \ + ThrowException((e)); \ + return JS_INVALID_REFERENCE; \ + } \ + } + +#define IfFailCleanup(v) \ + { \ + status = (v); \ + if (status != JsNoError) \ + { \ + goto cleanup; \ + } \ + } \ No newline at end of file diff --git a/ReactWindows/ReactNative.sln b/ReactWindows/ReactNative.sln index 4bf2927bfe..75ae9c95f3 100644 --- a/ReactWindows/ReactNative.sln +++ b/ReactWindows/ReactNative.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.25123.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactNative", "ReactNative\ReactNative.csproj", "{C7673AD5-E3AA-468C-A5FD-FA38154E205C}" EndProject @@ -9,6 +9,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Playground", "Playground\Pl EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactNative.Tests", "ReactNative.Tests\ReactNative.Tests.csproj", "{5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ChakraBridge", "ChakraBridge\ChakraBridge.vcxproj", "{4B72C796-16D5-4E3A-81C0-3E36F531E578}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -145,6 +147,36 @@ Global {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.ReleaseBundle|x86.ActiveCfg = Release|x86 {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.ReleaseBundle|x86.Build.0 = Release|x86 {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.ReleaseBundle|x86.Deploy.0 = Release|x86 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|ARM.ActiveCfg = Debug|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|ARM.Build.0 = Debug|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|x64.ActiveCfg = Debug|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|x64.Build.0 = Debug|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|x86.ActiveCfg = Debug|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Debug|x86.Build.0 = Debug|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|Any CPU.ActiveCfg = Release|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|Any CPU.Build.0 = Release|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|ARM.ActiveCfg = Debug|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|ARM.Build.0 = Debug|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|x64.ActiveCfg = Debug|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|x64.Build.0 = Debug|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|x86.ActiveCfg = Debug|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.DebugBundle|x86.Build.0 = Debug|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|Any CPU.ActiveCfg = Release|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|ARM.ActiveCfg = Release|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|ARM.Build.0 = Release|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|x64.ActiveCfg = Release|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|x64.Build.0 = Release|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|x86.ActiveCfg = Release|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.Release|x86.Build.0 = Release|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|Any CPU.ActiveCfg = Release|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|Any CPU.Build.0 = Release|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|ARM.ActiveCfg = Release|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|ARM.Build.0 = Release|ARM + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|x64.ActiveCfg = Release|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|x64.Build.0 = Release|x64 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|x86.ActiveCfg = Release|Win32 + {4B72C796-16D5-4E3A-81C0-3E36F531E578}.ReleaseBundle|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index e1647f17b6..0817da3084 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -430,7 +430,12 @@ Windows Mobile Extensions for the UWP - + + + {4b72c796-16d5-4e3a-81c0-3e36f531e578} + ChakraBridge + + 14.0