summaryrefslogtreecommitdiff
path: root/library/src/main/cpp/akono-jni.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'library/src/main/cpp/akono-jni.cpp')
-rw-r--r--library/src/main/cpp/akono-jni.cpp400
1 files changed, 365 insertions, 35 deletions
diff --git a/library/src/main/cpp/akono-jni.cpp b/library/src/main/cpp/akono-jni.cpp
index f355362b..b9e71012 100644
--- a/library/src/main/cpp/akono-jni.cpp
+++ b/library/src/main/cpp/akono-jni.cpp
@@ -2,12 +2,35 @@
#include <jni.h>
#include <libplatform/libplatform.h>
#include <v8.h>
+
#define NODE_WANT_INTERNALS 1
+
#include <node.h>
+
#include <uv.h>
#include <unistd.h>
#include <android/log.h>
+#include <node_main_instance.h>
+#include <node_native_module_env.h>
+
+
+
+// Provide stubs so that libnode.so links properly
+namespace node {
+ namespace native_module {
+ const bool has_code_cache = false;
+
+ void NativeModuleEnv::InitializeCodeCache() {}
+ }
+ v8::StartupData *NodeMainInstance::GetEmbeddedSnapshotBlob() {
+ return nullptr;
+ }
+
+ const std::vector<size_t> *NodeMainInstance::GetIsolateDataIndexes() {
+ return nullptr;
+ }
+}
static int pfd[2];
@@ -15,25 +38,22 @@ static pthread_t thr;
static const char *tag = "myapp";
-static void *thread_func(void*)
-{
+static void *thread_func(void *) {
ssize_t rdsz;
char buf[128];
- while((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) {
- if(buf[rdsz - 1] == '\n') --rdsz;
+ while ((rdsz = read(pfd[0], buf, sizeof buf - 1)) > 0) {
+ if (buf[rdsz - 1] == '\n') --rdsz;
buf[rdsz] = 0; /* add null-terminator */
__android_log_write(ANDROID_LOG_DEBUG, tag, buf);
}
return 0;
}
-static void mylog(const char *msg)
-{
+static void mylog(const char *msg) {
__android_log_write(ANDROID_LOG_DEBUG, tag, msg);
}
-int start_logger(const char *app_name)
-{
+int start_logger(const char *app_name) {
tag = app_name;
/* make stdout line-buffered and stderr unbuffered */
@@ -46,14 +66,13 @@ int start_logger(const char *app_name)
dup2(pfd[1], 2);
/* spawn the logging thread */
- if(pthread_create(&thr, 0, thread_func, 0) == -1)
+ if (pthread_create(&thr, 0, thread_func, 0) == -1)
return -1;
pthread_detach(thr);
return 0;
}
-
/**
* Helper class to manage conversion from a JNI string to a C string.
*/
@@ -61,14 +80,16 @@ class JStringValue {
private:
jstring m_jstr;
const char *m_cstr;
- JNIEnv* m_env;
+ JNIEnv *m_env;
public:
- JStringValue(JNIEnv* env, jstring s) : m_env(env), m_jstr(s) {
+ JStringValue(JNIEnv *env, jstring s) : m_env(env), m_jstr(s) {
m_cstr = env->GetStringUTFChars(s, NULL);
}
+
~JStringValue() {
m_env->ReleaseStringUTFChars(m_jstr, m_cstr);
}
+
const char *operator*() {
return m_cstr;
}
@@ -86,6 +107,58 @@ static void InitNode(std::vector<const char *> argv) {
node::Init(&ret_argc, &argv[0], &ret_exec_argc, &ret_exec_argv);
}
+// Forward declarations
+void notifyCb(uv_async_t *async);
+
+static void sendMessageCallback(const v8::FunctionCallbackInfo<v8::Value> &args);
+static void loadModuleCallback(const v8::FunctionCallbackInfo<v8::Value> &args);
+static void getDataCallback(const v8::FunctionCallbackInfo<v8::Value> &args);
+
+static const char *main_code = "global.__akono_run = (x) => {"
+ " console.log('running code', x);"
+ " global.eval(x);"
+ "};"
+ ""
+ "global.__akono_onMessage = (x) => {"
+ " console.log('got __akono_onMessage', x);"
+ "};"
+ ""
+ "mod = require('module');"
+ "mod._saved_findPath = mod._findPath;"
+ "mod._akonoMods = {};"
+ "mod._findPath = (request, paths, isMain) => {"
+ " console.log('in _findPath');"
+ " const res = mod._saved_findPath(request, paths, isMain);"
+ " if (res !== false) return res;"
+ " const args = JSON.stringify({ request, paths});"
+ " const loadResult = JSON.parse(global.__akono_loadModule(args));"
+ " console.log('got loadModule result', loadResult);"
+ " if (!loadResult) return false;"
+ " mod._akonoMods[loadResult.path] = loadResult;"
+ " console.log('returning path', loadResult.path);"
+ " return loadResult.path;"
+ "};"
+ ""
+ "function stripBOM(content) {"
+ " if (content.charCodeAt(0) === 0xFEFF) {"
+ " content = content.slice(1);"
+ " }"
+ " return content;"
+ "}"
+ ""
+ "mod._saved_js_extension = mod._extensions[\".js\"];"
+ "mod._extensions[\".js\"] = (module, filename) => {"
+ " console.log('handling js extension', [module, filename]);"
+ " if (mod._akonoMods.hasOwnProperty(filename)) {"
+ " const akmod = mod._akonoMods[filename];"
+ " console.log('found mod', akmod);"
+ " const content = akmod.content;"
+ " return module._compile(stripBOM(content), filename);"
+ " }"
+ " console.log('falling back');"
+ " return mod._saved_js_extension(module, filename);"
+ "};";
+
class NativeAkonoInstance {
private:
@@ -97,8 +170,16 @@ public:
v8::Isolate *isolate;
node::Environment *environment;
v8::Persistent<v8::Context> globalContext;
+ uv_async_t async_notify;
+ uv_loop_t *loop;
+ bool breakRequested = false;
+ JNIEnv *currentJniEnv = nullptr;
+ jobject currentJniThiz = nullptr;
NativeAkonoInstance() : globalContext() {
+ loop = uv_default_loop();
+ uv_async_init(loop, &async_notify, notifyCb);
+ async_notify.data = this;
if (!logInitialized) {
start_logger("myapp");
@@ -112,14 +193,13 @@ public:
// Here, only the arguments used to initialize the global node/v8 platform
// are relevant, the others are skipped.
- InitNode(std::vector<const char *>{"node", "--trace-events-enabled"});
+ InitNode(std::vector<const char *>{"node", "-e", main_code});
platform = node::InitializeV8Platform(10);
v8::V8::Initialize();
v8Initialized = true;
}
-
node::ArrayBufferAllocator *allocator = node::CreateArrayBufferAllocator();
this->isolate = node::NewIsolate(allocator, uv_default_loop());
@@ -128,18 +208,20 @@ public:
v8::HandleScope handle_scope(isolate);
node::IsolateData *isolateData = node::CreateIsolateData(
- this->isolate,
- uv_default_loop(),
- platform,
- allocator);
+ this->isolate,
+ uv_default_loop(),
+ platform,
+ allocator);
- globalContext.Reset(isolate, v8::Context::New(isolate));
+ globalContext.Reset(isolate, node::NewContext(isolate));
+
+ v8::Local<v8::Context> context = globalContext.Get(isolate);
// Arguments for node itself
- std::vector<const char*> nodeArgv{"node", "-e", "console.log('hello world');"};
+ std::vector<const char *> nodeArgv{"node", "-e", "console.log('hello world');"};
// Arguments for the script run by node
- std::vector<const char*> nodeExecArgv{};
+ std::vector<const char *> nodeExecArgv{};
mylog("entering global scopt");
@@ -155,25 +237,86 @@ public:
nodeExecArgv.size(),
&nodeExecArgv[0]);
+
mylog("loading environment");
node::LoadEnvironment(environment);
- mylog("finished environment");
+ mylog("finished loading environment");
+
+ v8::Local<v8::ObjectTemplate> dataTemplate = v8::ObjectTemplate::New(isolate);
+ dataTemplate->SetInternalFieldCount(1);
+ v8::Local<v8::Object> dataObject = dataTemplate->NewInstance(context).ToLocalChecked();
+ dataObject->SetAlignedPointerInInternalField(0, this);
+
+ v8::Local<v8::Function> sendMessageFunction = v8::Function::New(context,
+ sendMessageCallback,
+ dataObject).ToLocalChecked();
+
+ v8::Local<v8::Function> loadModuleFunction = v8::Function::New(context,
+ loadModuleCallback,
+ dataObject).ToLocalChecked();
+
+ v8::Local<v8::Function> getDataFunction = v8::Function::New(context,
+ getDataCallback,
+ dataObject).ToLocalChecked();
+
+ v8::Local<v8::Object> global = context->Global();
+
+ global->Set(v8::String::NewFromUtf8(isolate, "__akono_sendMessage",
+ v8::NewStringType::kNormal).ToLocalChecked(),
+ sendMessageFunction);
+
+ global->Set(v8::String::NewFromUtf8(isolate, "__akono_loadModule",
+ v8::NewStringType::kNormal).ToLocalChecked(),
+ loadModuleFunction);
+
+ // Get data synchronously (!) from the embedder
+ global->Set(v8::String::NewFromUtf8(isolate, "__akono_getData",
+ v8::NewStringType::kNormal).ToLocalChecked(),
+ getDataFunction);
- //v8::Isolate::CreateParams create_params;
- //create_params.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
- //this->isolate = v8::Isolate::New(create_params);
-//
-// node::IsolateData *isolateData = node::CreateIsolateData()
-// node::Environment *environment = node::CreateEnvironment(isolateData);
+ }
+
+ /**
+ * Process the node message loop until a break has been requested.
+ *
+ * @param env JNI env of the thread we're running in.
+ */
+ void runNode() {
+ this->breakRequested = false;
+ while (1) {
+ uv_run(uv_default_loop(), UV_RUN_ONCE);
+ if (this->breakRequested)
+ break;
+ }
+ }
+
+ /**
+ * Inject code into the running node instance.
+ *
+ * Must not be called from a different thread.
+ */
+ void makeCallback(const char *code) {
+ mylog("in makeCallback");
+ v8::Isolate::Scope isolate_scope(isolate);
+ v8::HandleScope handle_scope(isolate);
+ v8::Local<v8::Context> context = globalContext.Get(isolate);
+ v8::Context::Scope context_scope(context);
+ v8::Local<v8::Object> global = context->Global();
+ v8::Local<v8::Value> argv[] = {
+ v8::String::NewFromUtf8(isolate, code,
+ v8::NewStringType::kNormal).ToLocalChecked()
+ };
+ mylog("calling node::MakeCallback");
+ node::MakeCallback(isolate, global, "__akono_run", 1, argv, {0, 0});
}
~NativeAkonoInstance() {
//this->isolate->Dispose();
}
- jstring evalJs(JNIEnv* env, jstring sourceString) {
+ jstring evalJs(JNIEnv *env, jstring sourceString) {
mylog("begin evalJs");
JStringValue jsv(env, sourceString);
@@ -234,25 +377,212 @@ bool NativeAkonoInstance::logInitialized = false;
node::MultiIsolatePlatform *NativeAkonoInstance::platform = nullptr;
+void notifyCb(uv_async_t *async) {
+ NativeAkonoInstance *akono = (NativeAkonoInstance *) async->data;
+ mylog("async notifyCb called!");
+ akono->breakRequested = true;
+}
+
+static void sendMessageCallback(const v8::FunctionCallbackInfo<v8::Value> &args) {
+ if (args.Length() < 1) return;
+ v8::Isolate *isolate = args.GetIsolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<v8::Value> arg = args[0];
+ v8::String::Utf8Value value(isolate, arg);
+ mylog("sendMessageCallback called, yay!");
+
+ v8::Local<v8::Object> data = v8::Local<v8::Object>::Cast(args.Data());
+
+ mylog("getting instance");
+ NativeAkonoInstance *myInstance = (NativeAkonoInstance *) data->GetAlignedPointerFromInternalField(0);
+
+ JNIEnv *env = myInstance->currentJniEnv;
+
+ if (env == nullptr) {
+ mylog("FATAL: JNI env is nullptr");
+ return;
+ }
+
+ mylog("finding class");
+ jclass clazz = env->FindClass("akono/AkonoJni");
+
+ if (clazz == nullptr) {
+ mylog("FATAL: class not found");
+ return;
+ }
+
+ mylog("creating strings");
+ jstring jstr1 = env->NewStringUTF("message");
+ jstring jstr2 = env->NewStringUTF(*value);
+
+ mylog("getting method");
+
+ jmethodID meth = env->GetMethodID(clazz, "internalOnNotify", "(Ljava/lang/String;Ljava/lang/String;)V");
+
+ if (meth == nullptr) {
+ mylog("FATAL: method not found");
+ return;
+ }
+
+ mylog("calling method");
+
+ env->CallVoidMethod(myInstance->currentJniThiz, meth, jstr1, jstr2);
+}
+
+
+static void loadModuleCallback(const v8::FunctionCallbackInfo<v8::Value> &args) {
+ if (args.Length() < 1) return;
+ v8::Isolate *isolate = args.GetIsolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<v8::Value> arg = args[0];
+ v8::String::Utf8Value value(isolate, arg);
+ mylog("sendMessageCallback called, yay!");
+
+ v8::Local<v8::Object> data = v8::Local<v8::Object>::Cast(args.Data());
+
+ mylog("getting instance");
+ NativeAkonoInstance *myInstance = (NativeAkonoInstance *) data->GetAlignedPointerFromInternalField(0);
+
+ JNIEnv *env = myInstance->currentJniEnv;
+
+ if (env == nullptr) {
+ mylog("FATAL: JNI env is nullptr");
+ return;
+ }
+
+ mylog("finding class");
+ jclass clazz = env->FindClass("akono/AkonoJni");
+
+ if (clazz == nullptr) {
+ mylog("FATAL: class not found");
+ return;
+ }
+
+ mylog("creating strings");
+ jstring jstr1 = env->NewStringUTF(*value);
+
+ mylog("getting method");
+
+ jmethodID meth = env->GetMethodID(clazz, "internalOnModuleLoad", "(Ljava/lang/String;)Ljava/lang/String;");
+
+ if (meth == nullptr) {
+ mylog("FATAL: method not found");
+ return;
+ }
+
+ mylog("calling method");
+
+ jstring jresult = (jstring) env->CallObjectMethod(myInstance->currentJniThiz, meth, jstr1);
+
+ JStringValue resultStringValue(env, jresult);
+
+ printf("before creating string, res %s\n", *resultStringValue);
+
+ // Create a string containing the JavaScript source code.
+ v8::Local<v8::String> rs =
+ v8::String::NewFromUtf8(isolate, *resultStringValue,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked();
+
+ args.GetReturnValue().Set(rs);
+}
+
+
+static void getDataCallback(const v8::FunctionCallbackInfo<v8::Value> &args) {
+ if (args.Length() < 1) return;
+ v8::Isolate *isolate = args.GetIsolate();
+ v8::HandleScope scope(isolate);
+ v8::Local<v8::Value> arg = args[0];
+ v8::String::Utf8Value value(isolate, arg);
+ mylog("getDataCallback called");
+
+ v8::Local<v8::Object> data = v8::Local<v8::Object>::Cast(args.Data());
+ mylog("getting instance");
+ NativeAkonoInstance *myInstance = (NativeAkonoInstance *) data->GetAlignedPointerFromInternalField(0);
+
+ JNIEnv *env = myInstance->currentJniEnv;
+ if (env == nullptr) {
+ mylog("FATAL: JNI env is nullptr");
+ return;
+ }
+
+ mylog("finding class");
+ jclass clazz = env->FindClass("akono/AkonoJni");
+
+ if (clazz == nullptr) {
+ mylog("FATAL: class not found");
+ return;
+ }
+
+ mylog("creating strings");
+ jstring jstr1 = env->NewStringUTF(*value);
+
+ mylog("getting method");
+
+ jmethodID meth = env->GetMethodID(clazz, "internalOnGetData", "(Ljava/lang/String;)Ljava/lang/String;");
+
+ if (meth == nullptr) {
+ mylog("FATAL: method not found");
+ return;
+ }
+
+ mylog("calling method");
+
+ jstring jresult = (jstring) env->CallObjectMethod(myInstance->currentJniThiz, meth, jstr1);
+
+ JStringValue resultStringValue(env, jresult);
+
+ printf("before creating string, res %s\n", *resultStringValue);
+
+ // Create a string containing the JavaScript source code.
+ v8::Local<v8::String> rs =
+ v8::String::NewFromUtf8(isolate, *resultStringValue,
+ v8::NewStringType::kNormal)
+ .ToLocalChecked();
+
+ args.GetReturnValue().Set(rs);
+}
+
+
extern "C" JNIEXPORT jobject JNICALL
-Java_akono_AkonoJni_initNative(JNIEnv* env, jobject thiz)
-{
+Java_akono_AkonoJni_initNative(JNIEnv *env, jobject thiz) {
NativeAkonoInstance *myInstance = new NativeAkonoInstance();
return env->NewDirectByteBuffer(myInstance, 0);
}
extern "C" JNIEXPORT void JNICALL
-Java_akono_AkonoJni_destroyNative(JNIEnv* env, jobject thiz, jobject buf)
-{
+Java_akono_AkonoJni_destroyNative(JNIEnv *env, jobject thiz, jobject buf) {
NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf);
delete myInstance;
}
extern "C" JNIEXPORT jstring JNICALL
-Java_akono_AkonoJni_evalJs(JNIEnv* env, jobject thiz, jstring sourceStr, jobject buf)
-{
+Java_akono_AkonoJni_evalJs(JNIEnv *env, jobject thiz, jstring sourceStr, jobject buf) {
NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf);
return myInstance->evalJs(env, sourceStr);
}
+
+extern "C" JNIEXPORT void JNICALL
+Java_akono_AkonoJni_notifyNative(JNIEnv *env, jobject thiz, jobject buf) {
+ NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf);
+ uv_async_send(&myInstance->async_notify);
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_akono_AkonoJni_runNode(JNIEnv *env, jobject thiz, jobject buf) {
+ NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf);
+ myInstance->currentJniEnv = env;
+ myInstance->currentJniThiz = thiz;
+ myInstance->runNode();
+}
+
+extern "C" JNIEXPORT void JNICALL
+Java_akono_AkonoJni_makeCallbackNative(JNIEnv *env, jobject thiz, jstring sourceStr, jobject buf) {
+ JStringValue jsv(env, sourceStr);
+ NativeAkonoInstance *myInstance = (NativeAkonoInstance *) env->GetDirectBufferAddress(buf);
+ myInstance->currentJniEnv = env;
+ myInstance->currentJniThiz = thiz;
+ return myInstance->makeCallback(*jsv);
+}