727 lines
22 KiB
C++
727 lines
22 KiB
C++
// Copyright (C) 2019 The Qt Company Ltd.
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
|
|
|
|
#include "qstdweb_p.h"
|
|
|
|
#include <QtCore/qcoreapplication.h>
|
|
#include <QtCore/qfile.h>
|
|
#include <emscripten/bind.h>
|
|
#include <emscripten/emscripten.h>
|
|
#include <emscripten/html5.h>
|
|
#include <cstdint>
|
|
#include <iostream>
|
|
|
|
#include <unordered_map>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
namespace qstdweb {
|
|
|
|
static void usePotentialyUnusedSymbols()
|
|
{
|
|
// Using this adds a reference on JSEvents and specialHTMLTargets which are always exported.
|
|
// This hack is needed as it is currently impossible to specify a dollar sign in
|
|
// target_link_options. The following is impossible:
|
|
// DEFAULT_LIBRARY_FUNCS_TO_INCLUDE=$JSEvents
|
|
// TODO(mikolajboc): QTBUG-108444, review this when cmake gets fixed.
|
|
// Volatile is to make this unoptimizable, so that the function is referenced, but is not
|
|
// called at runtime.
|
|
volatile bool doIt = false;
|
|
if (doIt)
|
|
emscripten_set_wheel_callback(NULL, 0, 0, NULL);
|
|
}
|
|
|
|
Q_CONSTRUCTOR_FUNCTION(usePotentialyUnusedSymbols)
|
|
typedef double uint53_t; // see Number.MAX_SAFE_INTEGER
|
|
namespace {
|
|
enum class CallbackType {
|
|
Then,
|
|
Catch,
|
|
Finally,
|
|
};
|
|
|
|
void validateCallbacks(const PromiseCallbacks& callbacks) {
|
|
Q_ASSERT(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc);
|
|
}
|
|
|
|
using ThunkId = int;
|
|
|
|
#define THUNK_NAME(type, i) callbackThunk##type##i
|
|
|
|
// A resource pool for exported promise thunk functions. ThunkPool::poolSize sets of
|
|
// 3 promise thunks (then, catch, finally) are exported and can be used by promises
|
|
// in C++. To allocate a thunk, call allocateThunk. When a thunk is ready for use,
|
|
// a callback with allocation RAII object ThunkAllocation will be returned. Deleting
|
|
// the object frees the thunk and automatically makes any pending allocateThunk call
|
|
// run its callback with a free thunk slot.
|
|
class ThunkPool {
|
|
public:
|
|
static constexpr size_t poolSize = 4;
|
|
|
|
// An allocation for a thunk function set. Following the RAII pattern, destruction of
|
|
// this objects frees a corresponding thunk pool entry.
|
|
// To actually make the thunks react to a js promise's callbacks, call bindToPromise.
|
|
class ThunkAllocation {
|
|
public:
|
|
ThunkAllocation(int thunkId, ThunkPool* pool) : m_thunkId(thunkId), m_pool(pool) {}
|
|
~ThunkAllocation() {
|
|
m_pool->free(m_thunkId);
|
|
}
|
|
|
|
// The id of the underlaying thunk set
|
|
int id() const { return m_thunkId; }
|
|
|
|
// Binds the corresponding thunk set to the js promise 'target'.
|
|
void bindToPromise(emscripten::val target, const PromiseCallbacks& callbacks) {
|
|
using namespace emscripten;
|
|
|
|
if (Q_LIKELY(callbacks.thenFunc)) {
|
|
target = target.call<val>(
|
|
"then",
|
|
emscripten::val::module_property(thunkName(CallbackType::Then, id()).data()));
|
|
}
|
|
if (callbacks.catchFunc) {
|
|
target = target.call<val>(
|
|
"catch",
|
|
emscripten::val::module_property(thunkName(CallbackType::Catch, id()).data()));
|
|
}
|
|
if (callbacks.finallyFunc) {
|
|
target = target.call<val>(
|
|
"finally",
|
|
emscripten::val::module_property(thunkName(CallbackType::Finally, id()).data()));
|
|
}
|
|
}
|
|
|
|
private:
|
|
int m_thunkId;
|
|
ThunkPool* m_pool;
|
|
};
|
|
|
|
ThunkPool() {
|
|
std::iota(m_free.begin(), m_free.end(), 0);
|
|
}
|
|
|
|
void setThunkCallback(std::function<void(int, CallbackType, emscripten::val)> callback) {
|
|
m_callback = std::move(callback);
|
|
}
|
|
|
|
void allocateThunk(std::function<void(std::unique_ptr<ThunkAllocation>)> onAllocated) {
|
|
if (m_free.empty()) {
|
|
m_pendingAllocations.push_back(std::move(onAllocated));
|
|
return;
|
|
}
|
|
|
|
const int thunkId = m_free.back();
|
|
m_free.pop_back();
|
|
onAllocated(std::make_unique<ThunkAllocation>(thunkId, this));
|
|
}
|
|
|
|
static QByteArray thunkName(CallbackType type, size_t i) {
|
|
return QStringLiteral("promiseCallback%1%2").arg([type]() -> QString {
|
|
switch (type) {
|
|
case CallbackType::Then:
|
|
return QStringLiteral("Then");
|
|
case CallbackType::Catch:
|
|
return QStringLiteral("Catch");
|
|
case CallbackType::Finally:
|
|
return QStringLiteral("Finally");
|
|
}
|
|
}()).arg(i).toLatin1();
|
|
}
|
|
|
|
static ThunkPool* get();
|
|
|
|
#define THUNK(i) \
|
|
static void THUNK_NAME(Then, i)(emscripten::val result) \
|
|
{ \
|
|
get()->onThunkCalled(i, CallbackType::Then, std::move(result)); \
|
|
} \
|
|
static void THUNK_NAME(Catch, i)(emscripten::val result) \
|
|
{ \
|
|
get()->onThunkCalled(i, CallbackType::Catch, std::move(result)); \
|
|
} \
|
|
static void THUNK_NAME(Finally, i)() \
|
|
{ \
|
|
get()->onThunkCalled(i, CallbackType::Finally, emscripten::val::undefined()); \
|
|
}
|
|
|
|
THUNK(0);
|
|
THUNK(1);
|
|
THUNK(2);
|
|
THUNK(3);
|
|
|
|
#undef THUNK
|
|
|
|
private:
|
|
void onThunkCalled(int index, CallbackType type, emscripten::val result) {
|
|
m_callback(index, type, std::move(result));
|
|
}
|
|
|
|
void free(int thunkId) {
|
|
if (m_pendingAllocations.empty()) {
|
|
// Return the thunk to the free pool
|
|
m_free.push_back(thunkId);
|
|
return;
|
|
}
|
|
|
|
// Take the next enqueued allocation and reuse the thunk
|
|
auto allocation = m_pendingAllocations.back();
|
|
m_pendingAllocations.pop_back();
|
|
allocation(std::make_unique<ThunkAllocation>(thunkId, this));
|
|
}
|
|
|
|
std::function<void(int, CallbackType, emscripten::val)> m_callback;
|
|
|
|
std::vector<int> m_free = std::vector<int>(poolSize);
|
|
std::vector<std::function<void(std::unique_ptr<ThunkAllocation>)>> m_pendingAllocations;
|
|
};
|
|
|
|
Q_GLOBAL_STATIC(ThunkPool, g_thunkPool)
|
|
|
|
ThunkPool* ThunkPool::get()
|
|
{
|
|
return g_thunkPool;
|
|
}
|
|
|
|
#define CALLBACK_BINDING(i) \
|
|
emscripten::function(ThunkPool::thunkName(CallbackType::Then, i).data(), \
|
|
&ThunkPool::THUNK_NAME(Then, i)); \
|
|
emscripten::function(ThunkPool::thunkName(CallbackType::Catch, i).data(), \
|
|
&ThunkPool::THUNK_NAME(Catch, i)); \
|
|
emscripten::function(ThunkPool::thunkName(CallbackType::Finally, i).data(), \
|
|
&ThunkPool::THUNK_NAME(Finally, i));
|
|
|
|
EMSCRIPTEN_BINDINGS(qtThunkPool) {
|
|
CALLBACK_BINDING(0)
|
|
CALLBACK_BINDING(1)
|
|
CALLBACK_BINDING(2)
|
|
CALLBACK_BINDING(3)
|
|
}
|
|
|
|
#undef CALLBACK_BINDING
|
|
#undef THUNK_NAME
|
|
|
|
class WebPromiseManager
|
|
{
|
|
public:
|
|
WebPromiseManager();
|
|
~WebPromiseManager();
|
|
|
|
WebPromiseManager(const WebPromiseManager& other) = delete;
|
|
WebPromiseManager(WebPromiseManager&& other) = delete;
|
|
WebPromiseManager& operator=(const WebPromiseManager& other) = delete;
|
|
WebPromiseManager& operator=(WebPromiseManager&& other) = delete;
|
|
|
|
void adoptPromise(emscripten::val target, PromiseCallbacks callbacks);
|
|
|
|
static WebPromiseManager* get();
|
|
|
|
private:
|
|
struct RegistryEntry {
|
|
PromiseCallbacks callbacks;
|
|
std::unique_ptr<ThunkPool::ThunkAllocation> allocation;
|
|
};
|
|
|
|
static std::optional<CallbackType> parseCallbackType(emscripten::val callbackType);
|
|
|
|
void subscribeToJsPromiseCallbacks(int i, const PromiseCallbacks& callbacks, emscripten::val jsContextfulPromise);
|
|
void promiseThunkCallback(int i, CallbackType type, emscripten::val result);
|
|
|
|
void registerPromise(std::unique_ptr<ThunkPool::ThunkAllocation> allocation, PromiseCallbacks promise);
|
|
void unregisterPromise(ThunkId context);
|
|
|
|
std::array<RegistryEntry, ThunkPool::poolSize> m_promiseRegistry;
|
|
};
|
|
|
|
Q_GLOBAL_STATIC(WebPromiseManager, webPromiseManager)
|
|
|
|
WebPromiseManager::WebPromiseManager()
|
|
{
|
|
ThunkPool::get()->setThunkCallback(std::bind(
|
|
&WebPromiseManager::promiseThunkCallback, this,
|
|
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
|
|
}
|
|
|
|
std::optional<CallbackType>
|
|
WebPromiseManager::parseCallbackType(emscripten::val callbackType)
|
|
{
|
|
if (!callbackType.isString())
|
|
return std::nullopt;
|
|
|
|
const std::string data = callbackType.as<std::string>();
|
|
if (data == "then")
|
|
return CallbackType::Then;
|
|
if (data == "catch")
|
|
return CallbackType::Catch;
|
|
if (data == "finally")
|
|
return CallbackType::Finally;
|
|
return std::nullopt;
|
|
}
|
|
|
|
WebPromiseManager::~WebPromiseManager() = default;
|
|
|
|
WebPromiseManager *WebPromiseManager::get()
|
|
{
|
|
return webPromiseManager();
|
|
}
|
|
|
|
void WebPromiseManager::promiseThunkCallback(int context, CallbackType type, emscripten::val result)
|
|
{
|
|
auto* promiseState = &m_promiseRegistry[context];
|
|
|
|
auto* callbacks = &promiseState->callbacks;
|
|
bool expectingOtherCallbacks;
|
|
switch (type) {
|
|
case CallbackType::Then:
|
|
callbacks->thenFunc(result);
|
|
// At this point, if there is no finally function, we are sure that the Catch callback won't be issued.
|
|
expectingOtherCallbacks = !!callbacks->finallyFunc;
|
|
break;
|
|
case CallbackType::Catch:
|
|
callbacks->catchFunc(result);
|
|
expectingOtherCallbacks = !!callbacks->finallyFunc;
|
|
break;
|
|
case CallbackType::Finally:
|
|
callbacks->finallyFunc();
|
|
expectingOtherCallbacks = false;
|
|
break;
|
|
}
|
|
|
|
if (!expectingOtherCallbacks)
|
|
unregisterPromise(context);
|
|
}
|
|
|
|
void WebPromiseManager::registerPromise(
|
|
std::unique_ptr<ThunkPool::ThunkAllocation> allocation,
|
|
PromiseCallbacks callbacks)
|
|
{
|
|
const ThunkId id = allocation->id();
|
|
m_promiseRegistry[id] =
|
|
RegistryEntry {std::move(callbacks), std::move(allocation)};
|
|
}
|
|
|
|
void WebPromiseManager::unregisterPromise(ThunkId context)
|
|
{
|
|
m_promiseRegistry[context] = {};
|
|
}
|
|
|
|
void WebPromiseManager::adoptPromise(emscripten::val target, PromiseCallbacks callbacks) {
|
|
ThunkPool::get()->allocateThunk([=](std::unique_ptr<ThunkPool::ThunkAllocation> allocation) {
|
|
allocation->bindToPromise(std::move(target), callbacks);
|
|
registerPromise(std::move(allocation), std::move(callbacks));
|
|
});
|
|
}
|
|
#if defined(QT_STATIC)
|
|
|
|
EM_JS(bool, jsHaveAsyncify, (), { return typeof Asyncify !== "undefined"; });
|
|
|
|
#else
|
|
|
|
bool jsHaveAsyncify() { return false; }
|
|
|
|
#endif
|
|
|
|
} // namespace
|
|
|
|
ArrayBuffer::ArrayBuffer(uint32_t size)
|
|
{
|
|
m_arrayBuffer = emscripten::val::global("ArrayBuffer").new_(size);
|
|
}
|
|
|
|
ArrayBuffer::ArrayBuffer(const emscripten::val &arrayBuffer)
|
|
:m_arrayBuffer(arrayBuffer)
|
|
{
|
|
|
|
}
|
|
|
|
uint32_t ArrayBuffer::byteLength() const
|
|
{
|
|
if (m_arrayBuffer.isUndefined() || m_arrayBuffer.isNull())
|
|
return 0;
|
|
|
|
return m_arrayBuffer["byteLength"].as<uint32_t>();
|
|
}
|
|
|
|
emscripten::val ArrayBuffer::val()
|
|
{
|
|
return m_arrayBuffer;
|
|
}
|
|
|
|
Blob::Blob(const emscripten::val &blob)
|
|
:m_blob(blob)
|
|
{
|
|
|
|
}
|
|
|
|
uint32_t Blob::size() const
|
|
{
|
|
return m_blob["size"].as<uint32_t>();
|
|
}
|
|
|
|
// Copies content from the given buffer into a Blob object
|
|
Blob Blob::copyFrom(const char *buffer, uint32_t size)
|
|
{
|
|
Uint8Array contentCopy = Uint8Array::copyFrom(buffer, size);
|
|
|
|
emscripten::val contentArray = emscripten::val::array();
|
|
contentArray.call<void>("push", contentCopy.val());
|
|
emscripten::val type = emscripten::val::object();
|
|
type.set("type","application/octet-stream");
|
|
return Blob(emscripten::val::global("Blob").new_(contentArray, type));
|
|
}
|
|
|
|
emscripten::val Blob::val()
|
|
{
|
|
return m_blob;
|
|
}
|
|
|
|
File::File(const emscripten::val &file)
|
|
:m_file(file)
|
|
{
|
|
|
|
}
|
|
|
|
Blob File::slice(uint64_t begin, uint64_t end) const
|
|
{
|
|
return Blob(m_file.call<emscripten::val>("slice", uint53_t(begin), uint53_t(end)));
|
|
}
|
|
|
|
std::string File::name() const
|
|
{
|
|
return m_file["name"].as<std::string>();
|
|
}
|
|
|
|
uint64_t File::size() const
|
|
{
|
|
return uint64_t(m_file["size"].as<uint53_t>());
|
|
}
|
|
|
|
std::string Blob::type() const
|
|
{
|
|
return m_blob["type"].as<std::string>();
|
|
}
|
|
|
|
// Streams partial file content into the given buffer asynchronously. The completed
|
|
// callback is called on completion.
|
|
void File::stream(uint32_t offset, uint32_t length, char *buffer, const std::function<void ()> &completed) const
|
|
{
|
|
// Read file in chunks in order to avoid holding two copies in memory at the same time
|
|
const uint32_t chunkSize = 256 * 1024;
|
|
const uint32_t end = offset + length;
|
|
// assert end < file.size
|
|
auto fileReader = std::make_shared<qstdweb::FileReader>();
|
|
|
|
// "this" is valid now, but may not be by the time the chunkCompleted callback
|
|
// below is made. Make a copy of the file handle.
|
|
const File fileHandle = *this;
|
|
auto chunkCompleted = std::make_shared<std::function<void (uint32_t, char *buffer)>>();
|
|
*chunkCompleted = [=](uint32_t chunkBegin, char *chunkBuffer) mutable {
|
|
|
|
// Copy current chunk from JS memory to Wasm memory
|
|
qstdweb::ArrayBuffer result = fileReader->result();
|
|
qstdweb::Uint8Array(result).copyTo(chunkBuffer);
|
|
|
|
// Read next chunk if not at buffer end
|
|
uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end);
|
|
uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end);
|
|
if (nextChunkBegin == end) {
|
|
completed();
|
|
chunkCompleted.reset();
|
|
return;
|
|
}
|
|
char *nextChunkBuffer = chunkBuffer + result.byteLength();
|
|
fileReader->onLoad([=](emscripten::val) { (*chunkCompleted)(nextChunkBegin, nextChunkBuffer); });
|
|
qstdweb::Blob blob = fileHandle.slice(nextChunkBegin, nextChunkEnd);
|
|
fileReader->readAsArrayBuffer(blob);
|
|
};
|
|
|
|
// Read first chunk. First iteration is a dummy iteration with no available data.
|
|
(*chunkCompleted)(offset, buffer);
|
|
}
|
|
|
|
// Streams file content into the given buffer asynchronously. The completed
|
|
// callback is called on completion.
|
|
void File::stream(char *buffer, const std::function<void ()> &completed) const
|
|
{
|
|
stream(0, size(), buffer, completed);
|
|
}
|
|
|
|
std::string File::type() const
|
|
{
|
|
return m_file["type"].as<std::string>();
|
|
}
|
|
|
|
emscripten::val File::val()
|
|
{
|
|
return m_file;
|
|
}
|
|
|
|
FileList::FileList(const emscripten::val &fileList)
|
|
:m_fileList(fileList)
|
|
{
|
|
|
|
}
|
|
|
|
int FileList::length() const
|
|
{
|
|
return m_fileList["length"].as<int>();
|
|
}
|
|
|
|
File FileList::item(int index) const
|
|
{
|
|
return File(m_fileList[index]);
|
|
}
|
|
|
|
File FileList::operator[](int index) const
|
|
{
|
|
return item(index);
|
|
}
|
|
|
|
emscripten::val FileList::val() const
|
|
{
|
|
return m_fileList;
|
|
}
|
|
|
|
ArrayBuffer FileReader::result() const
|
|
{
|
|
return ArrayBuffer(m_fileReader["result"]);
|
|
}
|
|
|
|
void FileReader::readAsArrayBuffer(const Blob &blob) const
|
|
{
|
|
m_fileReader.call<void>("readAsArrayBuffer", blob.m_blob);
|
|
}
|
|
|
|
void FileReader::onLoad(const std::function<void(emscripten::val)> &onLoad)
|
|
{
|
|
m_onLoad.reset(new EventCallback(m_fileReader, "load", onLoad));
|
|
}
|
|
|
|
void FileReader::onError(const std::function<void(emscripten::val)> &onError)
|
|
{
|
|
m_onError.reset(new EventCallback(m_fileReader, "error", onError));
|
|
}
|
|
|
|
void FileReader::onAbort(const std::function<void(emscripten::val)> &onAbort)
|
|
{
|
|
m_onAbort.reset(new EventCallback(m_fileReader, "abort", onAbort));
|
|
}
|
|
|
|
emscripten::val FileReader::val()
|
|
{
|
|
return m_fileReader;
|
|
}
|
|
|
|
Uint8Array Uint8Array::heap()
|
|
{
|
|
return Uint8Array(heap_());
|
|
}
|
|
|
|
// Constructs a Uint8Array which references the given emscripten::val, which must contain a JS Unit8Array
|
|
Uint8Array::Uint8Array(const emscripten::val &uint8Array)
|
|
: m_uint8Array(uint8Array)
|
|
{
|
|
|
|
}
|
|
|
|
// Constructs a Uint8Array which references an ArrayBuffer
|
|
Uint8Array::Uint8Array(const ArrayBuffer &buffer)
|
|
: m_uint8Array(Uint8Array::constructor_().new_(buffer.m_arrayBuffer))
|
|
{
|
|
|
|
}
|
|
|
|
// Constructs a Uint8Array which references a view into an ArrayBuffer
|
|
Uint8Array::Uint8Array(const ArrayBuffer &buffer, uint32_t offset, uint32_t length)
|
|
: m_uint8Array(Uint8Array::constructor_().new_(buffer.m_arrayBuffer, offset, length))
|
|
{
|
|
|
|
}
|
|
|
|
// Constructs a Uint8Array which references an area on the heap.
|
|
Uint8Array::Uint8Array(const char *buffer, uint32_t size)
|
|
:m_uint8Array(Uint8Array::constructor_().new_(Uint8Array::heap().buffer().m_arrayBuffer, uintptr_t(buffer), size))
|
|
{
|
|
|
|
}
|
|
|
|
// Constructs a Uint8Array which allocates and references a new ArrayBuffer with the given size.
|
|
Uint8Array::Uint8Array(uint32_t size)
|
|
: m_uint8Array(Uint8Array::constructor_().new_(size))
|
|
{
|
|
|
|
}
|
|
|
|
ArrayBuffer Uint8Array::buffer() const
|
|
{
|
|
return ArrayBuffer(m_uint8Array["buffer"]);
|
|
}
|
|
|
|
uint32_t Uint8Array::length() const
|
|
{
|
|
return m_uint8Array["length"].as<uint32_t>();
|
|
}
|
|
|
|
void Uint8Array::set(const Uint8Array &source)
|
|
{
|
|
m_uint8Array.call<void>("set", source.m_uint8Array); // copies source content
|
|
}
|
|
|
|
// Copies the Uint8Array content to a destination on the heap
|
|
void Uint8Array::copyTo(char *destination) const
|
|
{
|
|
Uint8Array(destination, length()).set(*this);
|
|
}
|
|
|
|
// Copies the Uint8Array content to a destination QByteArray
|
|
QByteArray Uint8Array::copyToQByteArray() const
|
|
{
|
|
if (length() > std::numeric_limits<qsizetype>::max())
|
|
return QByteArray();
|
|
|
|
QByteArray destinationArray;
|
|
destinationArray.resize(length());
|
|
copyTo(destinationArray.data());
|
|
return destinationArray;
|
|
}
|
|
|
|
// Copies the Uint8Array content to a destination on the heap
|
|
void Uint8Array::copy(char *destination, const Uint8Array &source)
|
|
{
|
|
Uint8Array(destination, source.length()).set(source);
|
|
}
|
|
|
|
// Copies content from a source on the heap to a new Uint8Array object
|
|
Uint8Array Uint8Array::copyFrom(const char *buffer, uint32_t size)
|
|
{
|
|
Uint8Array contentCopy(size);
|
|
contentCopy.set(Uint8Array(buffer, size));
|
|
return contentCopy;
|
|
}
|
|
|
|
// Copies content from a QByteArray to a new Uint8Array object
|
|
Uint8Array Uint8Array::copyFrom(const QByteArray &buffer)
|
|
{
|
|
return copyFrom(buffer.constData(), buffer.size());
|
|
}
|
|
|
|
emscripten::val Uint8Array::val()
|
|
{
|
|
return m_uint8Array;
|
|
}
|
|
|
|
emscripten::val Uint8Array::heap_()
|
|
{
|
|
return emscripten::val::module_property("HEAPU8");
|
|
}
|
|
|
|
emscripten::val Uint8Array::constructor_()
|
|
{
|
|
return emscripten::val::global("Uint8Array");
|
|
}
|
|
|
|
// Registers a callback function for a named event on the given element. The event
|
|
// name must be the name as returned by the Event.type property: e.g. "load", "error".
|
|
EventCallback::~EventCallback()
|
|
{
|
|
// Clean up if this instance's callback is still installed on the element
|
|
if (m_element[contextPropertyName(m_eventName).c_str()].as<intptr_t>() == intptr_t(this)) {
|
|
m_element.set(contextPropertyName(m_eventName).c_str(), emscripten::val::undefined());
|
|
m_element.set((std::string("on") + m_eventName).c_str(), emscripten::val::undefined());
|
|
}
|
|
}
|
|
|
|
EventCallback::EventCallback(emscripten::val element, const std::string &name, const std::function<void(emscripten::val)> &fn)
|
|
:m_element(element)
|
|
,m_eventName(name)
|
|
,m_fn(fn)
|
|
{
|
|
m_element.set(contextPropertyName(m_eventName).c_str(), emscripten::val(intptr_t(this)));
|
|
m_element.set((std::string("on") + m_eventName).c_str(), emscripten::val::module_property("qtStdWebEventCallbackActivate"));
|
|
}
|
|
|
|
void EventCallback::activate(emscripten::val event)
|
|
{
|
|
emscripten::val target = event["target"];
|
|
std::string eventName = event["type"].as<std::string>();
|
|
emscripten::val property = target[contextPropertyName(eventName)];
|
|
// This might happen when the event bubbles
|
|
if (property.isUndefined())
|
|
return;
|
|
EventCallback *that = reinterpret_cast<EventCallback *>(property.as<intptr_t>());
|
|
that->m_fn(event);
|
|
}
|
|
|
|
std::string EventCallback::contextPropertyName(const std::string &eventName)
|
|
{
|
|
return std::string("data-qtEventCallbackContext") + eventName;
|
|
}
|
|
|
|
EMSCRIPTEN_BINDINGS(qtStdwebCalback) {
|
|
emscripten::function("qtStdWebEventCallbackActivate", &EventCallback::activate);
|
|
}
|
|
|
|
namespace Promise {
|
|
void adoptPromise(emscripten::val promiseObject, PromiseCallbacks callbacks) {
|
|
validateCallbacks(callbacks);
|
|
|
|
WebPromiseManager::get()->adoptPromise(
|
|
std::move(promiseObject), std::move(callbacks));
|
|
}
|
|
|
|
void all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks) {
|
|
struct State {
|
|
std::map<int, emscripten::val> results;
|
|
int remainingThenCallbacks;
|
|
int remainingFinallyCallbacks;
|
|
};
|
|
|
|
validateCallbacks(callbacks);
|
|
|
|
auto state = std::make_shared<State>();
|
|
state->remainingThenCallbacks = state->remainingFinallyCallbacks = promises.size();
|
|
|
|
for (size_t i = 0; i < promises.size(); ++i) {
|
|
PromiseCallbacks individualPromiseCallback;
|
|
if (callbacks.thenFunc) {
|
|
individualPromiseCallback.thenFunc = [i, state, callbacks](emscripten::val partialResult) mutable {
|
|
state->results.emplace(i, std::move(partialResult));
|
|
if (!--(state->remainingThenCallbacks)) {
|
|
std::vector<emscripten::val> transformed;
|
|
for (auto& data : state->results) {
|
|
transformed.push_back(std::move(data.second));
|
|
}
|
|
callbacks.thenFunc(emscripten::val::array(std::move(transformed)));
|
|
}
|
|
};
|
|
}
|
|
if (callbacks.catchFunc) {
|
|
individualPromiseCallback.catchFunc = [state, callbacks](emscripten::val error) mutable {
|
|
callbacks.catchFunc(error);
|
|
};
|
|
}
|
|
individualPromiseCallback.finallyFunc = [state, callbacks]() mutable {
|
|
if (!--(state->remainingFinallyCallbacks)) {
|
|
if (callbacks.finallyFunc)
|
|
callbacks.finallyFunc();
|
|
// Explicitly reset here for verbosity, this would have been done automatically with the
|
|
// destruction of the adopted promise in WebPromiseManager.
|
|
state.reset();
|
|
}
|
|
};
|
|
|
|
adoptPromise(std::move(promises.at(i)), std::move(individualPromiseCallback));
|
|
}
|
|
}
|
|
}
|
|
|
|
bool haveAsyncify()
|
|
{
|
|
static bool HaveAsyncify = jsHaveAsyncify();
|
|
return HaveAsyncify;
|
|
}
|
|
|
|
} // namespace qstdweb
|
|
|
|
QT_END_NAMESPACE
|