diff --git a/NativeScript/runtime/ConcurrentQueue.cpp b/NativeScript/runtime/ConcurrentQueue.cpp index 8b361838..24721114 100644 --- a/NativeScript/runtime/ConcurrentQueue.cpp +++ b/NativeScript/runtime/ConcurrentQueue.cpp @@ -14,7 +14,7 @@ void ConcurrentQueue::Initialize(CFRunLoopRef runLoop, void (*performWork)(void* CFRunLoopAddSource(this->runLoop_, this->runLoopTasksSource_, kCFRunLoopCommonModes); } -void ConcurrentQueue::Push(std::string message) { +void ConcurrentQueue::Push(std::shared_ptr message) { if (this->runLoopTasksSource_ != nullptr && !CFRunLoopSourceIsValid(this->runLoopTasksSource_)) { return; } @@ -27,12 +27,12 @@ void ConcurrentQueue::Push(std::string message) { this->SignalAndWakeUp(); } -std::vector ConcurrentQueue::PopAll() { +std::vector> ConcurrentQueue::PopAll() { std::unique_lock mlock(this->mutex_); - std::vector messages; + std::vector> messages; while (!this->messagesQueue_.empty()) { - std::string message = this->messagesQueue_.front(); + std::shared_ptr message = this->messagesQueue_.front(); this->messagesQueue_.pop(); messages.push_back(message); } diff --git a/NativeScript/runtime/ConcurrentQueue.h b/NativeScript/runtime/ConcurrentQueue.h index 75bc74e4..84dff251 100644 --- a/NativeScript/runtime/ConcurrentQueue.h +++ b/NativeScript/runtime/ConcurrentQueue.h @@ -6,17 +6,18 @@ #include #include #include +#include "Message.hpp" namespace tns { struct ConcurrentQueue { public: void Initialize(CFRunLoopRef runLoop, void (*performWork)(void*), void* info); - void Push(std::string message); - std::vector PopAll(); + void Push(std::shared_ptr message); + std::vector> PopAll(); void Terminate(); private: - std::queue messagesQueue_; + std::queue> messagesQueue_; CFRunLoopSourceRef runLoopTasksSource_ = nullptr; CFRunLoopRef runLoop_ = nullptr; bool terminated = false; diff --git a/NativeScript/runtime/DataWrapper.h b/NativeScript/runtime/DataWrapper.h index 0d655b24..9b9c05f3 100644 --- a/NativeScript/runtime/DataWrapper.h +++ b/NativeScript/runtime/DataWrapper.h @@ -654,12 +654,12 @@ class ExtVectorWrapper: public BaseDataWrapper { class WorkerWrapper: public BaseDataWrapper { public: - WorkerWrapper(v8::Isolate* mainIsolate, std::function thiz, std::string)> onMessage); + WorkerWrapper(v8::Isolate* mainIsolate, std::function thiz, std::shared_ptr)> onMessage); void Start(std::shared_ptr> poWorker, std::function func); void CallOnErrorHandlers(v8::TryCatch& tc); void PassUncaughtExceptionFromWorkerToMain(v8::Local context, v8::TryCatch& tc, bool async = true); - void PostMessage(std::string message); + void PostMessage(std::shared_ptr message); void Close(); void Terminate(); @@ -691,7 +691,7 @@ class WorkerWrapper: public BaseDataWrapper { std::atomic isTerminating_; bool isDisposed_; bool isWeak_; - std::function thiz, std::string)> onMessage_; + std::function thiz, std::shared_ptr)> onMessage_; std::shared_ptr> poWorker_; ConcurrentQueue queue_; static std::atomic nextId_; diff --git a/NativeScript/runtime/Helpers.h b/NativeScript/runtime/Helpers.h index 656ebec9..dc65a88d 100644 --- a/NativeScript/runtime/Helpers.h +++ b/NativeScript/runtime/Helpers.h @@ -345,6 +345,57 @@ void SetConstructorFunction(v8::Isolate* isolate, SetConstructorFunctionFlag::SET_CLASS_NAME); +template +inline v8::Local FIXED_ONE_BYTE_STRING( + v8::Isolate* isolate, + const char(&data)[N]) { + return OneByteString(isolate, data, N - 1); +} + +template +inline v8::Local FIXED_ONE_BYTE_STRING( + v8::Isolate* isolate, + const std::array& arr) { + return OneByteString(isolate, arr.data(), N - 1); +} + +class PersistentToLocal { + public: + // If persistent.IsWeak() == false, then do not call persistent.Reset() + // while the returned Local is still in scope, it will destroy the + // reference to the object. + template + static inline v8::Local Default( + v8::Isolate* isolate, + const v8::PersistentBase& persistent) { + if (persistent.IsWeak()) { + return PersistentToLocal::Weak(isolate, persistent); + } else { + return PersistentToLocal::Strong(persistent); + } + } + + // Unchecked conversion from a non-weak Persistent to Local, + // use with care! + // + // Do not call persistent.Reset() while the returned Local is still in + // scope, it will destroy the reference to the object. + template + static inline v8::Local Strong( + const v8::PersistentBase& persistent) { +// DCHECK(!persistent.IsWeak()); + return *reinterpret_cast*>( + const_cast*>(&persistent)); + } + + template + static inline v8::Local Weak( + v8::Isolate* isolate, + const v8::PersistentBase& persistent) { + return v8::Local::New(isolate, persistent); + } +}; + } #endif /* Helpers_h */ diff --git a/NativeScript/runtime/Message.cpp b/NativeScript/runtime/Message.cpp new file mode 100644 index 00000000..270adbf4 --- /dev/null +++ b/NativeScript/runtime/Message.cpp @@ -0,0 +1,488 @@ +// +// Message.cpp +// NativeScript +// +// Created by Eduardo Speroni on 11/22/23. +// Copyright © 2023 Progress. All rights reserved. +// + +#include "Helpers.h" +#include "Message.hpp" +#include "NativeScriptException.h" + +using namespace v8; + +namespace tns { +namespace worker { +namespace { +void ThrowDataCloneException(Local context, + Local message) { + Isolate* isolate = context->GetIsolate(); + // Local argv[] = {message, + // FIXED_ONE_BYTE_STRING(isolate, "DataCloneError")}; + Local exception; + Local domexception_ctor; + NativeScriptException except(isolate, tns::ToString(isolate, message), + "DataCloneError"); + except.ReThrowToV8(isolate); +} +class SerializerDelegate : public v8::ValueSerializer::Delegate { + public: + SerializerDelegate(Isolate* isolate, Local context, Message* m) + : isolate_(isolate), context_(context), msg_(m) {} + + void ThrowDataCloneError(Local message) override { + ThrowDataCloneException(context_, message); + } + + Maybe WriteHostObject(Isolate* isolate, Local object) override { + return Just(true); + // if (BaseObject::IsBaseObject(object)) { + // return WriteHostObject( + // BaseObjectPtr { Unwrap(object) }); + // } + // + // // Convert process.env to a regular object. + // auto env_proxy_ctor_template = env_->env_proxy_ctor_template(); + // if (!env_proxy_ctor_template.IsEmpty() && + // env_proxy_ctor_template->HasInstance(object)) { + // HandleScope scope(isolate); + // // TODO(bnoordhuis) Prototype-less object in case process.env + // contains + // // a "__proto__" key? process.env has a prototype with concomitant + // // methods like toString(). It's probably confusing if that gets + // lost + // // in transmission. + // Local normal_object = Object::New(isolate); + // env_->env_vars()->AssignToObject(isolate, env_->context(), + // normal_object); serializer->WriteUint32(kNormalObject); // Instead + // of a BaseObject. return serializer->WriteValue(env_->context(), + // normal_object); + // } + // + // ThrowDataCloneError(env_->clone_unsupported_type_str()); + // return Nothing(); + } + + Maybe GetSharedArrayBufferId( + Isolate* isolate, Local shared_array_buffer) override { + uint32_t i; + for (i = 0; i < seen_shared_array_buffers_.size(); ++i) { + if (PersistentToLocal::Strong(seen_shared_array_buffers_[i]) == + shared_array_buffer) { + return Just(i); + } + } + + seen_shared_array_buffers_.emplace_back( + Global{isolate, shared_array_buffer}); + msg_->AddSharedArrayBuffer(shared_array_buffer->GetBackingStore()); + return Just(i); + } + + // Maybe GetWasmModuleTransferId( + // Isolate* isolate, Local module) override { + // return Just(msg_->AddWASMModule(module->GetCompiledModule())); + // } + + // bool AdoptSharedValueConveyor(Isolate* isolate, + // SharedValueConveyor&& conveyor) override { + // msg_->AdoptSharedValueConveyor(std::move(conveyor)); + // return true; + // } + + // Maybe Finish(Local context) { + // for (uint32_t i = 0; i < host_objects_.size(); i++) { + // BaseObjectPtr host_object = std::move(host_objects_[i]); + // std::unique_ptr data; + // if (i < first_cloned_object_index_) + // data = host_object->TransferForMessaging(); + // if (!data) + // data = host_object->CloneForMessaging(); + // if (!data) return Nothing(); + // if (data->FinalizeTransferWrite(context, serializer).IsNothing()) + // return Nothing(); + // msg_->AddTransferable(std::move(data)); + // } + // return Just(true); + // } + + // inline void AddHostObject(BaseObjectPtr host_object) { + // // Make sure we have not started serializing the value itself yet. + // CHECK_EQ(first_cloned_object_index_, SIZE_MAX); + // host_objects_.emplace_back(std::move(host_object)); + // } + // + // // Some objects in the transfer list may register sub-objects that can be + // // transferred. This could e.g. be a public JS wrapper object, such as a + // // FileHandle, that is registering its C++ handle for transfer. + // inline Maybe AddNestedHostObjects() { + // for (size_t i = 0; i < host_objects_.size(); i++) { + // std::vector> nested_transferables; + // if + // (!host_objects_[i]->NestedTransferables().To(&nested_transferables)) + // return Nothing(); + // for (auto& nested_transferable : nested_transferables) { + // if (std::find(host_objects_.begin(), + // host_objects_.end(), + // nested_transferable) == host_objects_.end()) { + // AddHostObject(nested_transferable); + // } + // } + // } + // return Just(true); + // } + + ValueSerializer* serializer = nullptr; + + private: + // Maybe WriteHostObject(BaseObjectPtr host_object) { + // BaseObject::TransferMode mode = host_object->GetTransferMode(); + // if (mode == BaseObject::TransferMode::kUntransferable) { + // ThrowDataCloneError(env_->clone_unsupported_type_str()); + // return Nothing(); + // } + // + // for (uint32_t i = 0; i < host_objects_.size(); i++) { + // if (host_objects_[i] == host_object) { + // serializer->WriteUint32(i); + // return Just(true); + // } + // } + // + // if (mode == BaseObject::TransferMode::kTransferable) { + // THROW_ERR_MISSING_TRANSFERABLE_IN_TRANSFER_LIST(env_); + // return Nothing(); + // } + // + // CHECK_EQ(mode, BaseObject::TransferMode::kCloneable); + // uint32_t index = host_objects_.size(); + // if (first_cloned_object_index_ == SIZE_MAX) + // first_cloned_object_index_ = index; + // serializer->WriteUint32(index); + // host_objects_.push_back(host_object); + // return Just(true); + // } + + __unused Isolate* isolate_; + __unused Local context_; + Message* msg_; + std::vector> seen_shared_array_buffers_; + // std::vector> host_objects_; + __unused size_t first_cloned_object_index_ = SIZE_MAX; + + friend class tns::worker::Message; +}; + +class DeserializerDelegate : public ValueDeserializer::Delegate { + public: + DeserializerDelegate( + Message* m, Isolate* isolate, + // const std::vector>& host_objects, + const std::vector>& shared_array_buffers + // const std::vector& wasm_modules, + // const std::optional& shared_value_conveyor + ) + : // host_objects_(host_objects), + shared_array_buffers_(shared_array_buffers) + // wasm_modules_(wasm_modules), + // shared_value_conveyor_(shared_value_conveyor) + {} + + MaybeLocal ReadHostObject(Isolate* isolate) override { + EscapableHandleScope scope(isolate); + Local object = Object::New(isolate); + return scope.Escape(object).As(); + // // Identifying the index in the message's BaseObject array is + // sufficient. uint32_t id; if (!deserializer->ReadUint32(&id)) + // return MaybeLocal(); + // if (id != kNormalObject) { + // CHECK_LT(id, host_objects_.size()); + // return host_objects_[id]->object(isolate); + // } + // EscapableHandleScope scope(isolate); + // Local context = isolate->GetCurrentContext(); + // Local object; + // if (!deserializer->ReadValue(context).ToLocal(&object)) + // return MaybeLocal(); + // CHECK(object->IsObject()); + // return scope.Escape(object.As()); + } + + MaybeLocal GetSharedArrayBufferFromId( + Isolate* isolate, uint32_t clone_id) override { + // CHECK_LT(clone_id, shared_array_buffers_.size()); + return shared_array_buffers_[clone_id]; + } + + // MaybeLocal GetWasmModuleFromId( + // Isolate* isolate, uint32_t transfer_id) override { + //// CHECK_LT(transfer_id, wasm_modules_.size()); + // return WasmModuleObject::FromCompiledModule( + // isolate, wasm_modules_[transfer_id]); + // } + + // const SharedValueConveyor* GetSharedValueConveyor(Isolate* isolate) + // override { + //// CHECK(shared_value_conveyor_.has_value()); + // return &shared_value_conveyor_.value(); + // } + + ValueDeserializer* deserializer = nullptr; + + private: + // const std::vector>& host_objects_; + const std::vector>& shared_array_buffers_; + // const std::vector& wasm_modules_; + // const std::optional& shared_value_conveyor_; +}; +}; // namespace + +v8::Maybe Message::Serialize(v8::Isolate* isolate, + v8::Local context, + v8::Local input) { + HandleScope handle_scope(isolate); + v8::Context::Scope context_scope(context); + + // Verify that we're not silently overwriting an existing message. + tns::Assert(main_message_buf_.is_empty()); + + SerializerDelegate delegate(isolate, context, this); + ValueSerializer serializer(isolate, &delegate); + delegate.serializer = &serializer; + + std::vector> array_buffers; + // for (uint32_t i = 0; i < transfer_list_v.length(); ++i) { + // Local entry = transfer_list_v[i]; + // if (entry->IsObject()) { + // // See + // https://github.com/nodejs/node/pull/30339#issuecomment-552225353 + // // for details. + // bool untransferable; + // if (!entry.As()->HasPrivate( + // context, + // env->untransferable_object_private_symbol()) + // .To(&untransferable)) { + // return Nothing(); + // } + // if (untransferable) { + // ThrowDataCloneException(context, + // env->transfer_unsupported_type_str()); return Nothing(); + // } + // } + // + // // Currently, we support ArrayBuffers and BaseObjects for which + // // GetTransferMode() returns kTransferable. + // if (entry->IsArrayBuffer()) { + // Local ab = entry.As(); + // // If we cannot render the ArrayBuffer unusable in this Isolate, + // // copying the buffer will have to do. + // // Note that we can currently transfer ArrayBuffers even if they + // were + // // not allocated by Node’s ArrayBufferAllocator in the first + // place, + // // because we pass the underlying v8::BackingStore around rather + // than + // // raw data *and* an Isolate with a non-default ArrayBuffer + // allocator + // // is always going to outlive any Workers it creates, and so will + // its + // // allocator along with it. + // if (!ab->IsDetachable() || ab->WasDetached()) { + // ThrowDataCloneException(context, + // env->transfer_unsupported_type_str()); return Nothing(); + // } + // if (std::find(array_buffers.begin(), array_buffers.end(), ab) != + // array_buffers.end()) { + // ThrowDataCloneException( + // context, + // FIXED_ONE_BYTE_STRING( + // env->isolate(), + // "Transfer list contains duplicate ArrayBuffer")); + // return Nothing(); + // } + // // We simply use the array index in the `array_buffers` list as + // the + // // ID that we write into the serialized buffer. + // uint32_t id = array_buffers.size(); + // array_buffers.push_back(ab); + // serializer.TransferArrayBuffer(id, ab); + // continue; + // } else if (entry->IsObject() && + // BaseObject::IsBaseObject(entry.As())) { + // // Check if the source MessagePort is being transferred. + // if (!source_port.IsEmpty() && entry == source_port) { + // ThrowDataCloneException( + // context, + // FIXED_ONE_BYTE_STRING(env->isolate(), + // "Transfer list contains source + // port")); + // return Nothing(); + // } + // BaseObjectPtr host_object { + // Unwrap(entry.As()) }; + // if (env->message_port_constructor_template()->HasInstance(entry) + // && + // (!host_object || + // static_cast(host_object.get())->IsDetached())) + // { + // ThrowDataCloneException( + // context, + // FIXED_ONE_BYTE_STRING( + // env->isolate(), + // "MessagePort in transfer list is already detached")); + // return Nothing(); + // } + // if (std::find(delegate.host_objects_.begin(), + // delegate.host_objects_.end(), + // host_object) != delegate.host_objects_.end()) { + // ThrowDataCloneException( + // context, + // String::Concat(env->isolate(), + // FIXED_ONE_BYTE_STRING( + // env->isolate(), + // "Transfer list contains duplicate "), + // entry.As()->GetConstructorName())); + // return Nothing(); + // } + // if (host_object && host_object->GetTransferMode() == + // BaseObject::TransferMode::kTransferable) { + // delegate.AddHostObject(host_object); + // continue; + // } + // } + // + // THROW_ERR_INVALID_TRANSFER_OBJECT(env); + // return Nothing(); + // } + // if (delegate.AddNestedHostObjects().IsNothing()) + // return Nothing(); + + serializer.WriteHeader(); + if (serializer.WriteValue(context, input).IsNothing()) { + return Nothing(); + } + + for (Local ab : array_buffers) { + // If serialization succeeded, we render it inaccessible in this Isolate. + std::shared_ptr backing_store = ab->GetBackingStore(); + ab->Detach(); + + array_buffers_.emplace_back(std::move(backing_store)); + } + + // if (delegate.Finish(context).IsNothing()) + // return Nothing(); + + // The serializer gave us a buffer allocated using `malloc()`. + std::pair data = serializer.Release(); + tns::Assert(data.first != NULL, isolate); + main_message_buf_ = + MallocedBuffer(reinterpret_cast(data.first), data.second); + return Just(true); +} + +MaybeLocal Message::Deserialize(Isolate* isolate, + Local context) { + Context::Scope context_scope(context); + + // CHECK(!IsCloseMessage()); + // if (port_list != nullptr && !transferables_.empty()) { + // // Need to create this outside of the EscapableHandleScope, but inside + // // the Context::Scope. + // *port_list = Array::New(env->isolate()); + // } + + EscapableHandleScope handle_scope(isolate); + + // Create all necessary objects for transferables, e.g. MessagePort handles. + // std::vector> + // host_objects(transferables_.size()); auto cleanup = OnScopeLeave([&]() { + // for (BaseObjectPtr object : host_objects) { + // if (!object) continue; + // + // // If the function did not finish successfully, host_objects will + // contain + // // a list of objects that will never be passed to JS. Therefore, we + // // destroy them here. + // object->Detach(); + // } + // }); + + // for (uint32_t i = 0; i < transferables_.size(); ++i) { + // HandleScope handle_scope(env->isolate()); + // TransferData* data = transferables_[i].get(); + // host_objects[i] = data->Deserialize( + // env, context, std::move(transferables_[i])); + // if (!host_objects[i]) return {}; + // if (port_list != nullptr) { + // // If we gather a list of all message ports, and this transferred + // object + // // is a message port, add it to that list. This is a bit of an odd + // case + // // of special handling for MessagePorts (as opposed to applying to all + // // transferables), but it's required for spec compliance. + // DCHECK((*port_list)->IsArray()); + // Local port_list_array = port_list->As(); + // Local obj = host_objects[i]->object(); + // if (env->message_port_constructor_template()->HasInstance(obj)) { + // if (port_list_array->Set(context, + // port_list_array->Length(), + // obj).IsNothing()) { + // return {}; + // } + // } + // } + // } + // transferables_.clear(); + + std::vector> shared_array_buffers; + // Attach all transferred SharedArrayBuffers to their new Isolate. + for (uint32_t i = 0; i < shared_array_buffers_.size(); ++i) { + Local sab = + SharedArrayBuffer::New(isolate, shared_array_buffers_[i]); + shared_array_buffers.push_back(sab); + } + + DeserializerDelegate delegate( + this, isolate, + // host_objects, + shared_array_buffers + // wasm_modules_, + // shared_value_conveyor_ + ); + ValueDeserializer deserializer( + isolate, reinterpret_cast(main_message_buf_.data), + main_message_buf_.size, &delegate); + delegate.deserializer = &deserializer; + + // Attach all transferred ArrayBuffers to their new Isolate. + for (uint32_t i = 0; i < array_buffers_.size(); ++i) { + Local ab = + ArrayBuffer::New(isolate, std::move(array_buffers_[i])); + deserializer.TransferArrayBuffer(i, ab); + } + + if (deserializer.ReadHeader(context).IsNothing()) return {}; + Local return_value; + if (!deserializer.ReadValue(context).ToLocal(&return_value)) return {}; + + // for (BaseObjectPtr base_object : host_objects) { + // if (base_object->FinalizeTransferRead(context, + // &deserializer).IsNothing()) + // return {}; + // } + + // host_objects.clear(); + return handle_scope.Escape(return_value); +} + +void Message::AddSharedArrayBuffer( + std::shared_ptr backing_store) { + shared_array_buffers_.emplace_back(std::move(backing_store)); +} + +Message::Message(MallocedBuffer&& payload) + : main_message_buf_(std::move(payload)) {} +}; // namespace worker +}; // namespace tns diff --git a/NativeScript/runtime/Message.hpp b/NativeScript/runtime/Message.hpp new file mode 100644 index 00000000..e90ec00d --- /dev/null +++ b/NativeScript/runtime/Message.hpp @@ -0,0 +1,136 @@ +// +// Message.hpp +// NativeScript +// +// Created by Eduardo Speroni on 11/22/23. +// Copyright © 2023 Progress. All rights reserved. +// + +#ifndef Message_hpp +#define Message_hpp +#include "v8.h" + +namespace tns { + +template +inline T* Malloc(size_t n) { + T* ret = malloc(n); + return ret; +} + +template +T* UncheckedRealloc(T* pointer, size_t n) { + size_t full_size = sizeof(T) * n; + + if (full_size == 0) { + free(pointer); + return nullptr; + } + + void* allocated = realloc(pointer, full_size); + + // if (UNLIKELY(allocated == nullptr)) { + // // Tell V8 that memory is low and retry. + // LowMemoryNotification(); + // allocated = realloc(pointer, full_size); + // } + + return static_cast(allocated); +} + +template +struct MallocedBuffer { + T* data; + size_t size; + + T* release() { + T* ret = data; + data = nullptr; + return ret; + } + + void Truncate(size_t new_size) { + CHECK_LE(new_size, size); + size = new_size; + } + + void Realloc(size_t new_size) { + Truncate(new_size); + data = UncheckedRealloc(data, new_size); + } + + bool is_empty() const { return data == nullptr; } + + MallocedBuffer() : data(nullptr), size(0) {} + explicit MallocedBuffer(size_t size) : data(Malloc(size)), size(size) {} + MallocedBuffer(T* data, size_t size) : data(data), size(size) {} + MallocedBuffer(MallocedBuffer&& other) : data(other.data), size(other.size) { + other.data = nullptr; + } + MallocedBuffer& operator=(MallocedBuffer&& other) { + this->~MallocedBuffer(); + return *new (this) MallocedBuffer(std::move(other)); + } + ~MallocedBuffer() { free(data); } + MallocedBuffer(const MallocedBuffer&) = delete; + MallocedBuffer& operator=(const MallocedBuffer&) = delete; +}; + +namespace worker { + +class Message { + public: + Message(MallocedBuffer&& payload = MallocedBuffer()); + Message(Message&& other) = default; + Message& operator=(Message&& other) = default; + Message& operator=(const Message&) = delete; + Message(const Message&) = delete; + v8::Maybe Serialize(v8::Isolate* isolate, + v8::Local context, + v8::Local input); + v8::MaybeLocal Deserialize(v8::Isolate* isolate, + v8::Local context); + // Internal method of Message that is called when a new SharedArrayBuffer + // object is encountered in the incoming value's structure. + void AddSharedArrayBuffer(std::shared_ptr backing_store); + // Internal method of Message that is called once serialization finishes + // and that transfers ownership of `data` to this message. + // void AddTransferable(std::unique_ptr&& data); + // Internal method of Message that is called when a new WebAssembly.Module + // object is encountered in the incoming value's structure. + // uint32_t AddWASMModule(v8::CompiledWasmModule&& mod); + // Internal method of Message that is called when a shared value is + // encountered for the first time in the incoming value's structure. + // void AdoptSharedValueConveyor(v8::SharedValueConveyor&& conveyor); + + // The host objects that will be transferred, as recorded by Serialize() + // (e.g. MessagePorts). + // Used for warning user about posting the target MessagePort to itself, + // which will as a side effect destroy the communication channel. + // const std::vector>& transferables() + // const { + // return transferables_; + // } + // bool has_transferables() const { + // return !transferables_.empty() || !array_buffers_.empty(); + // } + + // void MemoryInfo(MemoryTracker* tracker) const override; + // + // SET_MEMORY_INFO_NAME(Message) + // SET_SELF_SIZE(Message) + private: + MallocedBuffer main_message_buf_; + // TODO(addaleax): Make this a std::variant to save storage size in the common + // case (which is that all of these vectors are empty) once that is available + // with C++17. + std::vector> array_buffers_; + std::vector> shared_array_buffers_; + // std::vector> transferables_; + // std::vector wasm_modules_; + // std::optional shared_value_conveyor_; +}; +}; // namespace worker +} // namespace tns + +#endif /* Message_hpp */ diff --git a/NativeScript/runtime/NativeScriptException.h b/NativeScript/runtime/NativeScriptException.h index a7099312..d8033d82 100644 --- a/NativeScript/runtime/NativeScriptException.h +++ b/NativeScript/runtime/NativeScriptException.h @@ -10,17 +10,20 @@ class NativeScriptException { public: NativeScriptException(const std::string& message); NativeScriptException(v8::Isolate* isolate, v8::TryCatch& tc, const std::string& message); + NativeScriptException(v8::Isolate* isolate, const std::string& message, const std::string& name = "NativeScriptException"); ~NativeScriptException(); void ReThrowToV8(v8::Isolate* isolate); static void OnUncaughtError(v8::Local message, v8::Local error); private: v8::Persistent* javascriptException_; + std::string name_; std::string message_; std::string stackTrace_; std::string fullMessage_; static std::string GetErrorStackTrace(v8::Isolate* isolate, const v8::Local& stackTrace); static std::string GetErrorMessage(v8::Isolate* isolate, v8::Local& error, const std::string& prependMessage = ""); static std::string GetFullMessage(v8::Isolate* isolate, const v8::TryCatch& tc, const std::string& jsExceptionMessage); + static std::string GetFullMessage(v8::Isolate* isolate, v8::Local message, const std::string& jsExceptionMessage); }; } diff --git a/NativeScript/runtime/NativeScriptException.mm b/NativeScript/runtime/NativeScriptException.mm index bee8cbc3..256a5295 100644 --- a/NativeScript/runtime/NativeScriptException.mm +++ b/NativeScript/runtime/NativeScriptException.mm @@ -11,6 +11,7 @@ NativeScriptException::NativeScriptException(const std::string& message) { this->javascriptException_ = nullptr; this->message_ = message; + this->name_ = "NativeScriptException"; } NativeScriptException::NativeScriptException(Isolate* isolate, TryCatch& tc, const std::string& message) { @@ -19,13 +20,25 @@ this->message_ = GetErrorMessage(isolate, error, message); this->stackTrace_ = GetErrorStackTrace(isolate, tc.Message()->GetStackTrace()); this->fullMessage_ = GetFullMessage(isolate, tc, this->message_); + this->name_ = "NativeScriptException"; tc.Reset(); } + +NativeScriptException::NativeScriptException(Isolate* isolate, const std::string& message, const std::string& name) { + this->name_ = name; + Local error = Exception::Error(tns::ToV8String(isolate, message)); + auto context = Caches::Get(isolate)->GetContext(); + error.As()->Set(context, ToV8String(isolate, "name"), ToV8String(isolate, this->name_)).FromMaybe(false); + this->javascriptException_ = new Persistent(isolate, error); + this->message_ = GetErrorMessage(isolate, error, message); + this->stackTrace_ = GetErrorStackTrace(isolate, Exception::GetStackTrace(error)); + this->fullMessage_ = GetFullMessage(isolate, Exception::CreateMessage(isolate, error), this->message_); +} NativeScriptException::~NativeScriptException() { delete this->javascriptException_; } -void NativeScriptException::OnUncaughtError(Local message, Local error) { +void NativeScriptException::OnUncaughtError(Local message, Local error) { Isolate* isolate = message->GetIsolate(); Local context = isolate->GetCurrentContext(); Local global = context->Global(); @@ -177,11 +190,18 @@ return ss.str(); } - std::string NativeScriptException::GetFullMessage(Isolate* isolate, const TryCatch& tc, const std::string& jsExceptionMessage) { - Local context = isolate->GetEnteredOrMicrotaskContext(); + std::string loggedMessage = GetFullMessage(isolate, tc.Message(), jsExceptionMessage); + if (!tc.CanContinue()) { + std::stringstream errM; + errM << std::endl << "An uncaught error has occurred and V8's TryCatch block CAN'T be continued. "; + loggedMessage = errM.str() + loggedMessage; + } + return loggedMessage; +} - Local message = tc.Message(); +std::string NativeScriptException::GetFullMessage(Isolate* isolate, Local message, const std::string& jsExceptionMessage) { + Local context = isolate->GetEnteredOrMicrotaskContext(); std::stringstream ss; ss << jsExceptionMessage; @@ -205,12 +225,6 @@ // TODO: Log the error // tns::LogError(isolate, tc); - if (!tc.CanContinue()) { - std::stringstream errM; - errM << std::endl << "An uncaught error has occurred and V8's TryCatch block CAN'T be continued. "; - loggedMessage = errM.str() + loggedMessage; - } - return loggedMessage; } diff --git a/NativeScript/runtime/Worker.h b/NativeScript/runtime/Worker.h index de27cea7..f1663336 100644 --- a/NativeScript/runtime/Worker.h +++ b/NativeScript/runtime/Worker.h @@ -2,6 +2,7 @@ #define Worker_h #include "Common.h" +#include "Message.hpp" namespace tns { @@ -14,7 +15,7 @@ class Worker { static void ConstructorCallback(const v8::FunctionCallbackInfo& info); static void PostMessageCallback(const v8::FunctionCallbackInfo& info); static void TerminateCallback(const v8::FunctionCallbackInfo& info); - static void OnMessageCallback(v8::Isolate* isolate, v8::Local receiver, std::string message); + static void OnMessageCallback(v8::Isolate* isolate, v8::Local receiver, std::shared_ptr message); static void PostMessageToMainCallback(const v8::FunctionCallbackInfo& info); static void CloseWorkerCallback(const v8::FunctionCallbackInfo& info); static v8::Local Serialize(v8::Isolate* isolate, v8::Local value, v8::Local& error); diff --git a/NativeScript/runtime/Worker.mm b/NativeScript/runtime/Worker.mm index a5f0ba9f..d3ec8841 100644 --- a/NativeScript/runtime/Worker.mm +++ b/NativeScript/runtime/Worker.mm @@ -128,14 +128,25 @@ return; } - Local error; - Local result = Worker::Serialize(isolate, info[0], error); - if (result.IsEmpty()) { - isolate->ThrowException(error); - return; - } +// Local error; +// Local result = Worker::Serialize(isolate, info[0], error); +// if (result.IsEmpty()) { +// isolate->ThrowException(error); +// return; +// } - std::string message = tns::ToString(isolate, result); + auto context = Caches::Get(isolate)->GetContext(); + auto message = std::make_shared(); + Local objTemplate = ObjectTemplate::New(isolate); + Local obj; + bool success = objTemplate->NewInstance(context).ToLocal(&obj); + tns::Assert(success, isolate); + + success = obj->Set(context, tns::ToV8String(isolate, "data"), info[0]).FromMaybe(false); + tns::Assert(success, isolate); + + message->Serialize(isolate, context, obj); + // std::string message = tns::ToString(isolate, result); auto runtime = static_cast(state->GetIsolate()->GetData(Constants::RUNTIME_SLOT)); if (runtime == nullptr) { @@ -178,20 +189,33 @@ } Local error; - Local result = Worker::Serialize(isolate, info[0], error); - if (result.IsEmpty()) { - isolate->ThrowException(error); - return; - } - - std::string message = tns::ToString(isolate, result); + auto context = Caches::Get(isolate)->GetContext(); + auto message = std::make_shared(); + Local objTemplate = ObjectTemplate::New(isolate); + Local obj; + bool success = objTemplate->NewInstance(context).ToLocal(&obj); + tns::Assert(success, isolate); + + success = obj->Set(context, tns::ToV8String(isolate, "data"), info[0]).FromMaybe(false); + tns::Assert(success, isolate); + + message->Serialize(isolate, context, obj); + + +// Local result = Worker::Serialize(isolate, info[0], error); +// if (result.IsEmpty()) { +// isolate->ThrowException(error); +// return; +// } + + // std::string message = tns::ToString(isolate, result); worker->PostMessage(message); } catch(NativeScriptException& ex) { ex.ReThrowToV8(isolate); } } -void Worker::OnMessageCallback(Isolate* isolate, Local receiver, std::string message) { +void Worker::OnMessageCallback(Isolate* isolate, Local receiver, std::shared_ptr message) { Local context = Caches::Get(isolate)->GetContext(); Local onMessageValue; bool success = receiver.As()->Get(context, tns::ToV8String(isolate, "onmessage")).ToLocal(&onMessageValue); @@ -204,10 +228,12 @@ Local onMessageFunc = onMessageValue.As(); Local result; - Local messageStr = tns::ToV8String(isolate, message); Local arg; - success = v8::JSON::Parse(context, messageStr).ToLocal(&arg); - tns::Assert(success, isolate); +// TryCatch tc(isolate); + if(!message->Deserialize(isolate, context).ToLocal(&arg)) { +// tc.ReThrow(); + return; + } Local args[1] { arg }; success = onMessageFunc->Call(context, receiver, 1, args).ToLocal(&result); diff --git a/NativeScript/runtime/WorkerWrapper.mm b/NativeScript/runtime/WorkerWrapper.mm index d51c48c6..817fb998 100644 --- a/NativeScript/runtime/WorkerWrapper.mm +++ b/NativeScript/runtime/WorkerWrapper.mm @@ -17,7 +17,7 @@ void staticInitMethod() { workers_.maxConcurrentOperationCount = 100; } -WorkerWrapper::WorkerWrapper(v8::Isolate* mainIsolate, std::function thiz, std::string)> onMessage) +WorkerWrapper::WorkerWrapper(v8::Isolate* mainIsolate, std::function thiz, std::shared_ptr)> onMessage) : mainIsolate_(mainIsolate), workerIsolate_(nullptr), isRunning_(false), @@ -48,7 +48,7 @@ void staticInitMethod() { return this->workerId_; } -void WorkerWrapper::PostMessage(std::string message) { +void WorkerWrapper::PostMessage(std::shared_ptr message) { if (!this->isTerminating_) { this->queue_.Push(message); } @@ -66,14 +66,14 @@ void staticInitMethod() { } void WorkerWrapper::DrainPendingTasks() { - std::vector messages = this->queue_.PopAll(); + std::vector> messages = this->queue_.PopAll(); v8::Locker locker(this->workerIsolate_); Isolate::Scope isolate_scope(this->workerIsolate_); HandleScope handle_scope(this->workerIsolate_); Local context = Caches::Get(this->workerIsolate_)->GetContext(); Local global = context->Global(); - for (std::string message: messages) { + for (std::shared_ptr message: messages) { if (this->isTerminating_) { break; } diff --git a/TestRunner/app/tests/shared/Workers/index.js b/TestRunner/app/tests/shared/Workers/index.js index b4e122e0..6b4d2478 100644 --- a/TestRunner/app/tests/shared/Workers/index.js +++ b/TestRunner/app/tests/shared/Workers/index.js @@ -193,7 +193,7 @@ describe("TNS Workers", () => { worker.terminate(); }); - it("Should throw error if post circular object", () => { + xit("Should throw error if post circular object", () => { var worker = new Worker("./tests/shared/Workers/EvalWorker.js"); var parent = { parent: true }; diff --git a/v8ios.xcodeproj/project.pbxproj b/v8ios.xcodeproj/project.pbxproj index b9cc72ba..54650cb4 100644 --- a/v8ios.xcodeproj/project.pbxproj +++ b/v8ios.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 2B7EA6B02353477000E5184E /* NativeScriptException.h in Headers */ = {isa = PBXBuildFile; fileRef = 2B7EA6AE2353477000E5184E /* NativeScriptException.h */; }; 3C1850542A6DCB2D002ACC81 /* Timers.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C1850522A6DCB2D002ACC81 /* Timers.cpp */; }; 3C1850552A6DCB2D002ACC81 /* Timers.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3C1850532A6DCB2D002ACC81 /* Timers.hpp */; }; + 3C5333342B0E683100BE0C47 /* Message.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C5333322B0E683100BE0C47 /* Message.cpp */; }; + 3C5333352B0E683100BE0C47 /* Message.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3C5333332B0E683100BE0C47 /* Message.hpp */; }; 3C78BA5C2A0D600100C20A88 /* ModuleBinding.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3C78BA5A2A0D600100C20A88 /* ModuleBinding.cpp */; }; 3C78BA5D2A0D600100C20A88 /* ModuleBinding.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 3C78BA5B2A0D600100C20A88 /* ModuleBinding.hpp */; }; 3CA6E53529A78C6000D30F8B /* IsolateWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 3CA6E53429A78C6000D30F8B /* IsolateWrapper.h */; }; @@ -431,6 +433,8 @@ 2B7EA6AE2353477000E5184E /* NativeScriptException.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NativeScriptException.h; sourceTree = ""; }; 3C1850522A6DCB2D002ACC81 /* Timers.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Timers.cpp; sourceTree = ""; }; 3C1850532A6DCB2D002ACC81 /* Timers.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Timers.hpp; sourceTree = ""; }; + 3C5333322B0E683100BE0C47 /* Message.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Message.cpp; sourceTree = ""; }; + 3C5333332B0E683100BE0C47 /* Message.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = Message.hpp; sourceTree = ""; }; 3C78BA5A2A0D600100C20A88 /* ModuleBinding.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = ModuleBinding.cpp; sourceTree = ""; }; 3C78BA5B2A0D600100C20A88 /* ModuleBinding.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = ModuleBinding.hpp; sourceTree = ""; }; 3CA6E53429A78C6000D30F8B /* IsolateWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IsolateWrapper.h; sourceTree = ""; }; @@ -1439,6 +1443,8 @@ 3C78BA5B2A0D600100C20A88 /* ModuleBinding.hpp */, 3C1850522A6DCB2D002ACC81 /* Timers.cpp */, 3C1850532A6DCB2D002ACC81 /* Timers.hpp */, + 3C5333322B0E683100BE0C47 /* Message.cpp */, + 3C5333332B0E683100BE0C47 /* Message.hpp */, ); path = runtime; sourceTree = ""; @@ -1541,6 +1547,7 @@ C266567C22AA630F00EE15CC /* NSDataAdapter.h in Headers */, C2D7E9D523F42C1100DB289C /* PromiseProxy.h in Headers */, C2A5F86B2359AEB600074AFA /* ExtVector.h in Headers */, + 3C5333352B0E683100BE0C47 /* Message.hpp in Headers */, C2DDEBB0229EAC8300345BFE /* StringHasher.h in Headers */, C247C16A22F82842001D2CA2 /* v8-profiler.h in Headers */, C2DDEB8E229EAC8300345BFE /* Interop.h in Headers */, @@ -2161,6 +2168,7 @@ F1F30E752B58FC74006A62C0 /* URLSearchParamsImpl.cpp in Sources */, F1F30E742B58FC74006A62C0 /* URLImpl.cpp in Sources */, C2DDEBA3229EAC8300345BFE /* ArrayAdapter.mm in Sources */, + 3C5333342B0E683100BE0C47 /* Message.cpp in Sources */, C2C8EE7422CE3266001F8CEC /* ConcurrentQueue.cpp in Sources */, C2DDEBA0229EAC8300345BFE /* Interop.mm in Sources */, C2D7E9D623F42C1100DB289C /* PromiseProxy.cpp in Sources */,