From 247373d6501bab94ce1ea0048e390bd910963db9 Mon Sep 17 00:00:00 2001 From: Lorn Potter Date: Mon, 24 Sep 2018 17:25:08 +1000 Subject: wasm: refactor and convert from EM_ASM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This also has the benefit that binary data is no longer accessed via a global. Change-Id: I2db744696fe88ca03f55897016d9d38e302f820b Reviewed-by: Edward Welbourne Reviewed-by: Mårten Nordheim Reviewed-by: Morten Johan Sørvig --- src/websockets/qwebsocket_p.h | 7 ++ src/websockets/qwebsocket_wasm_p.cpp | 224 ++++++++++++++--------------------- 2 files changed, 94 insertions(+), 137 deletions(-) (limited to 'src') diff --git a/src/websockets/qwebsocket_p.h b/src/websockets/qwebsocket_p.h index 73f2745..fd631d4 100644 --- a/src/websockets/qwebsocket_p.h +++ b/src/websockets/qwebsocket_p.h @@ -69,6 +69,10 @@ #include "qwebsocketdataprocessor_p.h" #include "qdefaultmaskgenerator_p.h" +#ifdef Q_OS_WASM +#include +#endif + QT_BEGIN_NAMESPACE class QWebSocketHandshakeRequest; @@ -247,6 +251,9 @@ private: QMap m_headers; friend class QWebSocketServerPrivate; +#ifdef Q_OS_WASM + emscripten::val socketContext = emscripten::val::null(); +#endif }; QT_END_NAMESPACE diff --git a/src/websockets/qwebsocket_wasm_p.cpp b/src/websockets/qwebsocket_wasm_p.cpp index 7b9f271..2ac61ba 100644 --- a/src/websockets/qwebsocket_wasm_p.cpp +++ b/src/websockets/qwebsocket_wasm_p.cpp @@ -42,92 +42,104 @@ #include #include -#include #include using namespace emscripten; -QByteArray g_messageArray; -// easiest way to transliterate binary data to js/wasm - -val getBinaryMessage() +static void q_onErrorCallback(val event) { - return val(typed_memory_view(g_messageArray.size(), - reinterpret_cast(g_messageArray.constData()))); -} + val target = event["target"]; -EMSCRIPTEN_BINDINGS(wasm_module) { - function("getBinaryMessage", &getBinaryMessage); + QWebSocketPrivate *wsp = reinterpret_cast(target["data-context"].as()); + Q_ASSERT (wsp); + + emit wsp->q_func()->error(wsp->error()); } -static void onOpenCallback(void *data) +static void q_onCloseCallback(val event) { - auto handler = reinterpret_cast(data); - Q_ASSERT (handler); - emit handler->q_func()->connected(); + val target = event["target"]; + + QWebSocketPrivate *wsp = reinterpret_cast(target["data-context"].as()); + Q_ASSERT (wsp); + + emit wsp->q_func()->disconnected(); } -static void onCloseCallback(void *data, int message) +static void q_onOpenCallback(val event) { - Q_UNUSED(message); - auto handler = reinterpret_cast(data); - Q_ASSERT (handler); - emit handler->q_func()->disconnected(); + val target = event["target"]; + + QWebSocketPrivate *wsp = reinterpret_cast(target["data-context"].as()); + Q_ASSERT (wsp); + + emit wsp->q_func()->connected(); } -static void onErrorCallback(void *data, int message) +static void q_onIncomingMessageCallback(val event) { - Q_UNUSED(message); - auto handler = reinterpret_cast(data); - Q_ASSERT (handler); - emit handler->q_func()->error(handler->error()); + val target = event["target"]; + + if (event["data"].typeOf().as() == "string") { + QWebSocketPrivate *wsp = reinterpret_cast(target["data-context"].as()); + Q_ASSERT (wsp); + + const QString message = QString::fromStdString(event["data"].as()); + if (!message.isEmpty()) + wsp->q_func()->textMessageReceived(message); + } else { + val reader = val::global("FileReader").new_(); + reader.set("onload", val::module_property("QWebSocketPrivate_readBlob")); + reader.set("data-context", target["data-context"]); + reader.call("readAsArrayBuffer", event["data"]); + } } -static void onIncomingMessageCallback(void *data, int message, int length, int dataType) +static void q_readBlob(val event) { - QWebSocketPrivate *handler = reinterpret_cast(data); - Q_ASSERT (handler); - - QWebSocket *webSocket = handler->q_func(); - const char *text = reinterpret_cast(message); - - switch (dataType) { - case 0: //string - webSocket->textMessageReceived(QLatin1String(text)); - break; - case 1: //blob - case 2: //arraybuffer - webSocket->binaryMessageReceived(QByteArray::fromRawData(text, length)); - break; - }; + val fileReader = event["target"]; + + QWebSocketPrivate *wsp = reinterpret_cast(fileReader["data-context"].as()); + Q_ASSERT (wsp); + + // Set up source typed array + val result = fileReader["result"]; // ArrayBuffer + val Uint8Array = val::global("Uint8Array"); + val sourceTypedArray = Uint8Array.new_(result); + + // Allocate and set up destination typed array + const size_t size = result["byteLength"].as(); + QByteArray buffer(size, Qt::Uninitialized); + + val destinationTypedArray = Uint8Array.new_(val::module_property("HEAPU8")["buffer"], + reinterpret_cast(buffer.data()), size); + destinationTypedArray.call("set", sourceTypedArray); + + wsp->q_func()->binaryMessageReceived(buffer); +} + + +EMSCRIPTEN_BINDINGS(wasm_module) { + function("QWebSocketPrivate_onErrorCallback", q_onErrorCallback); + function("QWebSocketPrivate_onCloseCallback", q_onCloseCallback); + function("QWebSocketPrivate_onOpenCallback", q_onOpenCallback); + function("QWebSocketPrivate_onIncomingMessageCallback", q_onIncomingMessageCallback); + function("QWebSocketPrivate_readBlob", q_readBlob); } qint64 QWebSocketPrivate::sendTextMessage(const QString &message) { - EM_ASM_ARGS({ - if (window.qWebSocket === undefined) - console.log("cannot find websocket object"); - else - window.qWebSocket.send(Pointer_stringify($0)); - }, message.toLocal8Bit().constData()); - + socketContext.call("send", message.toStdString()); return message.length(); } qint64 QWebSocketPrivate::sendBinaryMessage(const QByteArray &data) { - g_messageArray = data; - EM_ASM({ - if (window.qWebSocket === undefined) { - console.log("cannot find websocket object"); - } else { - var array = Module.getBinaryMessage(); - window.qWebSocket.binaryType = 'arraybuffer'; - window.qWebSocket.send(array); - } - }); - - g_messageArray.clear(); + socketContext.call("send", + val(typed_memory_view(data.size(), + reinterpret_cast + (data.constData())))); + return data.length(); } @@ -136,19 +148,10 @@ void QWebSocketPrivate::close(QWebSocketProtocol::CloseCode closeCode, QString r Q_Q(QWebSocket); m_closeCode = closeCode; m_closeReason = reason; - const quint16 closeReason = (quint16)closeCode; Q_EMIT q->aboutToClose(); - QCoreApplication::processEvents(); - - EM_ASM_ARGS({ - if (window.qWebSocket === undefined) { - console.log("cannot find websocket object"); - } else { - var reasonMessage = Pointer_stringify($0); - window.qWebSocket.close($1, reasonMessage); - window.qWebSocket = undefined; - } - }, reason.toLatin1().data(), closeReason); + + socketContext.call("close", static_cast(closeCode), + reason.toLatin1().toStdString()); } void QWebSocketPrivate::open(const QNetworkRequest &request, bool mask) @@ -162,74 +165,21 @@ void QWebSocketPrivate::open(const QNetworkRequest &request, bool mask) return; } - QByteArray urlbytes = url.toString().toUtf8(); + const std::string urlbytes = url.toString().toStdString(); // HTML WebSockets do not support arbitrary request headers, but // do support the WebSocket protocol header. This header is // required for some use cases like MQTT. - QByteArray protocolHeaderValue = request.rawHeader("Sec-WebSocket-Protocol"); - - EM_ASM_ARGS({ - if (window.qWebSocket != undefined) - return; - - var wsUri = Pointer_stringify($0); - var wsProtocol = Pointer_stringify($1); - var handler = $2; - var onOpenCb = $3; - var onCloseCb = $4; - var onErrorCb = $5; - var onIncomingMessageCb = $6; - - window.qWebSocket = wsProtocol.length > 0 - ? new WebSocket(wsUri, wsProtocol) - : new WebSocket(wsUri); - - window.qWebSocket.onopen = function(event) { - Runtime.dynCall('vi', onOpenCb, [handler]); - }; - - window.qWebSocket.onclose = function(event) { - window.qWebSocket = undefined; - Runtime.dynCall('vii', onCloseCb, [handler, event.code]); - }; - - window.qWebSocket.onerror = function(event) { - Runtime.dynCall('vii', onErrorCb, [handler, event.error]); - }; - - window.qWebSocket.onmessage = function(event) { - var outgoingMessage; - var bufferLength = 0; - var dataType; - - if (window.qWebSocket.binaryType == 'arraybuffer' && typeof event.data == 'object') { - - var byteArray = new Uint8Array(event.data); - bufferLength = byteArray.length; - - outgoingMessage = _malloc(byteArray.length); - HEAPU8.set(byteArray, outgoingMessage); - - dataType = 2; - } else if (typeof event.data == 'string') { - dataType = 0; - outgoingMessage = allocate(intArrayFromString(event.data), 'i8', ALLOC_NORMAL); - } else if (window.qWebSocket.binaryType == 'blob') { - var byteArray = new Int8Array($0); - outgoingMessage = new Blob(byteArray.buffer); - dataType = 1; - } - - Runtime.dynCall('viiii', onIncomingMessageCb, [handler, outgoingMessage, bufferLength, dataType]); - _free(outgoingMessage); - }; - - }, urlbytes.constData(), - protocolHeaderValue.data(), - this, - reinterpret_cast(onOpenCallback), - reinterpret_cast(onCloseCallback), - reinterpret_cast(onErrorCallback), - reinterpret_cast(onIncomingMessageCallback)); + const std::string protocolHeaderValue = request.rawHeader("Sec-WebSocket-Protocol").toStdString(); + val webSocket = val::global("WebSocket"); + + socketContext = !protocolHeaderValue.empty() + ? webSocket.new_(urlbytes, protocolHeaderValue) + : webSocket.new_(urlbytes); + + socketContext.set("onerror", val::module_property("QWebSocketPrivate_onErrorCallback")); + socketContext.set("onclose", val::module_property("QWebSocketPrivate_onCloseCallback")); + socketContext.set("onopen", val::module_property("QWebSocketPrivate_onOpenCallback")); + socketContext.set("onmessage", val::module_property("QWebSocketPrivate_onIncomingMessageCallback")); + socketContext.set("data-context", val(quintptr(reinterpret_cast(this)))); } -- cgit v1.2.1