diff --git a/test-app/app/src/main/assets/app/mainpage.js b/test-app/app/src/main/assets/app/mainpage.js index dfa8d5806..dc338ca8a 100644 --- a/test-app/app/src/main/assets/app/mainpage.js +++ b/test-app/app/src/main/assets/app/mainpage.js @@ -20,6 +20,7 @@ shared.runRequireTests(); shared.runWeakRefTests(); shared.runRuntimeTests(); shared.runWorkerTests(); +require("./tests/testWebAssembly"); require("./tests/testInterfaceDefaultMethods"); require("./tests/testInterfaceStaticMethods"); require("./tests/testMetadata"); diff --git a/test-app/app/src/main/assets/app/tests/testWebAssembly.js b/test-app/app/src/main/assets/app/tests/testWebAssembly.js new file mode 100644 index 000000000..63c4e1117 --- /dev/null +++ b/test-app/app/src/main/assets/app/tests/testWebAssembly.js @@ -0,0 +1,73 @@ +describe("Test WebAssembly ", () => { + // https://wasdk.github.io/WasmFiddle/?15acre + // + // #include + // #include + // + // #define WASM_EXPORT __attribute__((visibility("default"))) + // + // extern double logarithm(double value); + // + // WASM_EXPORT int log(double value) { + // return logarithm(value); + // } + let wasmCode = new Uint8Array([ + 0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x8b, 0x80, 0x80, 0x80, 0x00, 0x02, + 0x60, 0x01, 0x7c, 0x01, 0x7c, 0x60, 0x01, 0x7c, 0x01, 0x7f, 0x02, 0x91, 0x80, 0x80, 0x80, + 0x00, 0x01, 0x03, 0x65, 0x6e, 0x76, 0x09, 0x6c, 0x6f, 0x67, 0x61, 0x72, 0x69, 0x74, 0x68, + 0x6d, 0x00, 0x00, 0x03, 0x82, 0x80, 0x80, 0x80, 0x00, 0x01, 0x01, 0x04, 0x84, 0x80, 0x80, + 0x80, 0x00, 0x01, 0x70, 0x00, 0x00, 0x05, 0x83, 0x80, 0x80, 0x80, 0x00, 0x01, 0x00, 0x01, + 0x06, 0x81, 0x80, 0x80, 0x80, 0x00, 0x00, 0x07, 0x90, 0x80, 0x80, 0x80, 0x00, 0x02, 0x06, + 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x03, 0x6c, 0x6f, 0x67, 0x00, 0x01, 0x0a, + 0x8d, 0x80, 0x80, 0x80, 0x00, 0x01, 0x87, 0x80, 0x80, 0x80, 0x00, 0x00, 0x20, 0x00, 0x10, + 0x00, 0xaa, 0x0b + ]); + + it("Handle compilation failures", done => { + WebAssembly.compile(new Uint8Array([ 1, 2, 3, 4 ])).then(moduleInstance => { + expect(true).toBe(false, "The success callback of the compilation promise was called"); + done(); + }).catch(e => { + expect(e.name).toEqual("CompileError"); + expect(e.message).toEqual("WebAssembly.compile(): expected magic word 00 61 73 6d, found 01 02 03 04 @+0"); + done(); + }); + }); + + it("Compile and instantiate a WebAssembly module asynchronously", done => { + let importsObj = { + env: { + logarithm: Math.log + } + }; + + WebAssembly.compile(wasmCode).then(wasmModule => { + WebAssembly.instantiate(wasmModule, importsObj).then(moduleInstance => { + expect(moduleInstance).toBeDefined(); + expect(moduleInstance.exports).toBeDefined(); + expect(moduleInstance.exports.log).toEqual(jasmine.any(Function)); + let actual = moduleInstance.exports.log(Math.E); + expect(actual).toEqual(1); + done(); + }).catch(e => { + expect(true).toBe(false, "An unexpected error occurred while instantiating the WebAssembly module: " + e.toString()); + done(); + }); + }).catch(e => { + expect(true).toBe(false, "An unexpected error occurred while compiling the WebAssembly module: " + e.toString()); + done(); + }); + }); + + it("Compile and instantiate a WebAssembly module inside a worker", done => { + let worker = new Worker("./testWebAssemblyWorker"); + + worker.onmessage = msg => { + expect(msg.data).toEqual(1); + worker.terminate(); + done(); + }; + + worker.postMessage(Array.from(wasmCode)); + }); +}); diff --git a/test-app/app/src/main/assets/app/tests/testWebAssemblyWorker.js b/test-app/app/src/main/assets/app/tests/testWebAssemblyWorker.js new file mode 100644 index 000000000..7cff696de --- /dev/null +++ b/test-app/app/src/main/assets/app/tests/testWebAssemblyWorker.js @@ -0,0 +1,16 @@ +self.onmessage = function(msg) { + let wasmCode = new Uint8Array(msg.data); + + let importsObj = { + env: { + logarithm: Math.log + } + }; + + WebAssembly.compile(wasmCode).then(wasmModule => { + WebAssembly.instantiate(wasmModule, importsObj).then(moduleInstance => { + let result = moduleInstance.exports.log(Math.E); + self.postMessage(result); + }); + }); +} diff --git a/test-app/runtime/CMakeLists.txt b/test-app/runtime/CMakeLists.txt index 1f8a95269..abedf7949 100644 --- a/test-app/runtime/CMakeLists.txt +++ b/test-app/runtime/CMakeLists.txt @@ -152,6 +152,7 @@ add_library( src/main/cpp/JSONObjectHelper.cpp src/main/cpp/Logger.cpp src/main/cpp/ManualInstrumentation.cpp + src/main/cpp/MessageLoopTimer.cpp src/main/cpp/MetadataMethodInfo.cpp src/main/cpp/MetadataNode.cpp src/main/cpp/MetadataReader.cpp @@ -222,7 +223,8 @@ endif() # completing its build. find_library(system-log log) find_library(system-z z) +find_library(system-android android) # Command info: https://cmake.org/cmake/help/v3.4/command/target_link_libraries.html # Specifies libraries CMake should link to your target library. -target_link_libraries(NativeScript ${system-log} ${system-z}) +target_link_libraries(NativeScript ${system-log} ${system-z} ${system-android}) diff --git a/test-app/runtime/src/main/cpp/MessageLoopTimer.cpp b/test-app/runtime/src/main/cpp/MessageLoopTimer.cpp new file mode 100644 index 000000000..2b2612458 --- /dev/null +++ b/test-app/runtime/src/main/cpp/MessageLoopTimer.cpp @@ -0,0 +1,146 @@ +#include "MessageLoopTimer.h" +#include +#include +#include +#include +#include "include/libplatform/libplatform.h" +#include "NativeScriptAssert.h" +#include "ArgConverter.h" +#include "Runtime.h" + +using namespace tns; +using namespace v8; + +static const int SLEEP_INTERVAL_MS = 100; + +void MessageLoopTimer::Init(Local context) { + this->RegisterStartStopFunctions(context); + + std::string proxyScript = R"( + (function () { + // We proxy the WebAssembly's compile, compileStreaming, instantiate and + // instantiateStreaming methods so that they can start and stop a + // MessageLoopTimer inside the promise callbacks. This timer will call + // the v8::platform::PumpMessageLoop method at regular intervals. + // https://github.com/NativeScript/android-runtime/issues/1558 + + global.WebAssembly = new Proxy(WebAssembly, { + get: (target, name) => { + let origMethod = target[name]; + let proxyMethods = [ + "compile", + "compileStreaming", + "instantiate", + "instantiateStreaming" + ]; + + if (proxyMethods.indexOf(name) < 0) { + return origMethod; + } + + return function (...args) { + __messageLoopTimerStart(); + let result = origMethod.apply(this, args); + return result.then(x => { + __messageLoopTimerStop(); + return x; + }).catch(e => { + __messageLoopTimerStop(); + throw e; + }); + }; + } + }); + })(); + )"; + + Isolate* isolate = context->GetIsolate(); + + auto source = ArgConverter::ConvertToV8String(isolate, proxyScript); + Local