wasm: add QIODevices for accessing JS data
BlobIoDevice: Supports reading data from a JS Blob, which can be a File (on disk) or some other object which can provide data. The native access functions are async and using this class requires that asyncify is available. Uint8ArrayIODevice: Supports reading and writing to a Uint8Array / ArrayBuffer. Similar to the existing QByteArray::fromEcmaUint8Array() API, except that it supports incremental accesss. Change-Id: Ic5de3534ff75eb6c745287b73b15ccd92d74ac2c Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
This commit is contained in:
parent
cd3a409589
commit
fde6bdfc5a
@ -503,6 +503,11 @@ uint32_t ArrayBuffer::byteLength() const
|
|||||||
return m_arrayBuffer["byteLength"].as<uint32_t>();
|
return m_arrayBuffer["byteLength"].as<uint32_t>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ArrayBuffer ArrayBuffer::slice(uint32_t begin, uint32_t end) const
|
||||||
|
{
|
||||||
|
return ArrayBuffer(m_arrayBuffer.call<emscripten::val>("slice", begin, end));
|
||||||
|
}
|
||||||
|
|
||||||
emscripten::val ArrayBuffer::val() const
|
emscripten::val ArrayBuffer::val() const
|
||||||
{
|
{
|
||||||
return m_arrayBuffer;
|
return m_arrayBuffer;
|
||||||
@ -514,6 +519,13 @@ Blob::Blob(const emscripten::val &blob)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Blob Blob::fromArrayBuffer(const ArrayBuffer &arrayBuffer)
|
||||||
|
{
|
||||||
|
auto array = emscripten::val::array();
|
||||||
|
array.call<void>("push", arrayBuffer.val());
|
||||||
|
return Blob(emscripten::val::global("Blob").new_(array));
|
||||||
|
}
|
||||||
|
|
||||||
uint32_t Blob::size() const
|
uint32_t Blob::size() const
|
||||||
{
|
{
|
||||||
return m_blob["size"].as<uint32_t>();
|
return m_blob["size"].as<uint32_t>();
|
||||||
@ -536,6 +548,25 @@ Blob Blob::copyFrom(const char *buffer, uint32_t size)
|
|||||||
return copyFrom(buffer, size, "application/octet-stream");
|
return copyFrom(buffer, size, "application/octet-stream");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Blob Blob::slice(uint32_t begin, uint32_t end) const
|
||||||
|
{
|
||||||
|
return Blob(m_blob.call<emscripten::val>("slice", begin, end));
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayBuffer Blob::arrayBuffer_sync() const
|
||||||
|
{
|
||||||
|
QEventLoop loop;
|
||||||
|
emscripten::val buffer;
|
||||||
|
qstdweb::Promise::make(m_blob, "arrayBuffer", {
|
||||||
|
.thenFunc = [&loop, &buffer](emscripten::val arrayBuffer) {
|
||||||
|
buffer = arrayBuffer;
|
||||||
|
loop.quit();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
loop.exec();
|
||||||
|
return ArrayBuffer(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
emscripten::val Blob::val() const
|
emscripten::val Blob::val() const
|
||||||
{
|
{
|
||||||
return m_blob;
|
return m_blob;
|
||||||
@ -706,6 +737,13 @@ void Uint8Array::set(const Uint8Array &source)
|
|||||||
m_uint8Array.call<void>("set", source.m_uint8Array); // copies source content
|
m_uint8Array.call<void>("set", source.m_uint8Array); // copies source content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Uint8Array Uint8Array::subarray(uint32_t begin, uint32_t end)
|
||||||
|
{
|
||||||
|
// Note: using uint64_t here errors with "Cannot convert a BigInt value to a number"
|
||||||
|
// (see JS BigInt and Number types). Use uint32_t for now.
|
||||||
|
return Uint8Array(m_uint8Array.call<emscripten::val>("subarray", begin, end));
|
||||||
|
}
|
||||||
|
|
||||||
// Copies the Uint8Array content to a destination on the heap
|
// Copies the Uint8Array content to a destination on the heap
|
||||||
void Uint8Array::copyTo(char *destination) const
|
void Uint8Array::copyTo(char *destination) const
|
||||||
{
|
{
|
||||||
@ -890,6 +928,101 @@ readDataTransfer(emscripten::val webDataTransfer, std::function<QVariant(QByteAr
|
|||||||
return DataTransferReader::read(webDataTransfer, std::move(imageReader), std::move(onDone));
|
return DataTransferReader::read(webDataTransfer, std::move(imageReader), std::move(onDone));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BlobIODevice::BlobIODevice(Blob blob)
|
||||||
|
: m_blob(blob)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BlobIODevice::open(QIODevice::OpenMode mode)
|
||||||
|
{
|
||||||
|
if (mode.testFlag(QIODevice::WriteOnly))
|
||||||
|
return false;
|
||||||
|
return QIODevice::open(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BlobIODevice::isSequential() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 BlobIODevice::size() const
|
||||||
|
{
|
||||||
|
return m_blob.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BlobIODevice::seek(qint64 pos)
|
||||||
|
{
|
||||||
|
if (pos >= size())
|
||||||
|
return false;
|
||||||
|
return QIODevice::seek(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 BlobIODevice::readData(char *data, qint64 maxSize)
|
||||||
|
{
|
||||||
|
uint64_t begin = QIODevice::pos();
|
||||||
|
uint64_t end = std::min<uint64_t>(begin + maxSize, size());
|
||||||
|
uint64_t size = end - begin;
|
||||||
|
if (size > 0) {
|
||||||
|
qstdweb::ArrayBuffer buffer = m_blob.slice(begin, end).arrayBuffer_sync();
|
||||||
|
qstdweb::Uint8Array(buffer).copyTo(data);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 BlobIODevice::writeData(const char *, qint64)
|
||||||
|
{
|
||||||
|
Q_UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8ArrayIODevice::Uint8ArrayIODevice(Uint8Array array)
|
||||||
|
: m_array(array)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Uint8ArrayIODevice::open(QIODevice::OpenMode mode)
|
||||||
|
{
|
||||||
|
return QIODevice::open(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Uint8ArrayIODevice::isSequential() const
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 Uint8ArrayIODevice::size() const
|
||||||
|
{
|
||||||
|
return m_array.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Uint8ArrayIODevice::seek(qint64 pos)
|
||||||
|
{
|
||||||
|
if (pos >= size())
|
||||||
|
return false;
|
||||||
|
return QIODevice::seek(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 Uint8ArrayIODevice::readData(char *data, qint64 maxSize)
|
||||||
|
{
|
||||||
|
uint64_t begin = QIODevice::pos();
|
||||||
|
uint64_t end = std::min<uint64_t>(begin + maxSize, size());
|
||||||
|
uint64_t size = end - begin;
|
||||||
|
if (size > 0)
|
||||||
|
m_array.subarray(begin, end).copyTo(data);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 Uint8ArrayIODevice::writeData(const char *data, qint64 maxSize)
|
||||||
|
{
|
||||||
|
uint64_t begin = QIODevice::pos();
|
||||||
|
uint64_t end = std::min<uint64_t>(begin + maxSize, size());
|
||||||
|
uint64_t size = end - begin;
|
||||||
|
if (size > 0)
|
||||||
|
m_array.subarray(begin, end).set(Uint8Array(data, size));
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace qstdweb
|
} // namespace qstdweb
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -58,6 +58,7 @@ namespace qstdweb {
|
|||||||
explicit ArrayBuffer(uint32_t size);
|
explicit ArrayBuffer(uint32_t size);
|
||||||
explicit ArrayBuffer(const emscripten::val &arrayBuffer);
|
explicit ArrayBuffer(const emscripten::val &arrayBuffer);
|
||||||
uint32_t byteLength() const;
|
uint32_t byteLength() const;
|
||||||
|
ArrayBuffer slice(uint32_t begin, uint32_t end) const;
|
||||||
emscripten::val val() const;
|
emscripten::val val() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -68,9 +69,12 @@ namespace qstdweb {
|
|||||||
class Q_CORE_EXPORT Blob {
|
class Q_CORE_EXPORT Blob {
|
||||||
public:
|
public:
|
||||||
explicit Blob(const emscripten::val &blob);
|
explicit Blob(const emscripten::val &blob);
|
||||||
|
static Blob fromArrayBuffer(const ArrayBuffer &arrayBuffer);
|
||||||
uint32_t size() const;
|
uint32_t size() const;
|
||||||
static Blob copyFrom(const char *buffer, uint32_t size, std::string mimeType);
|
static Blob copyFrom(const char *buffer, uint32_t size, std::string mimeType);
|
||||||
static Blob copyFrom(const char *buffer, uint32_t size);
|
static Blob copyFrom(const char *buffer, uint32_t size);
|
||||||
|
Blob slice(uint32_t begin, uint32_t end) const;
|
||||||
|
ArrayBuffer arrayBuffer_sync() const;
|
||||||
emscripten::val val() const;
|
emscripten::val val() const;
|
||||||
std::string type() const;
|
std::string type() const;
|
||||||
|
|
||||||
@ -140,6 +144,7 @@ namespace qstdweb {
|
|||||||
ArrayBuffer buffer() const;
|
ArrayBuffer buffer() const;
|
||||||
uint32_t length() const;
|
uint32_t length() const;
|
||||||
void set(const Uint8Array &source);
|
void set(const Uint8Array &source);
|
||||||
|
Uint8Array subarray(uint32_t begin, uint32_t end);
|
||||||
|
|
||||||
void copyTo(char *destination) const;
|
void copyTo(char *destination) const;
|
||||||
QByteArray copyToQByteArray() const;
|
QByteArray copyToQByteArray() const;
|
||||||
@ -207,6 +212,40 @@ namespace qstdweb {
|
|||||||
return wrappedCallback;
|
return wrappedCallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Q_CORE_EXPORT BlobIODevice: public QIODevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BlobIODevice(Blob blob);
|
||||||
|
bool open(QIODeviceBase::OpenMode mode) override;
|
||||||
|
bool isSequential() const override;
|
||||||
|
qint64 size() const override;
|
||||||
|
bool seek(qint64 pos) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
qint64 readData(char *data, qint64 maxSize) override;
|
||||||
|
qint64 writeData(const char *, qint64) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Blob m_blob;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Uint8ArrayIODevice: public QIODevice
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Uint8ArrayIODevice(Uint8Array array);
|
||||||
|
bool open(QIODevice::OpenMode mode) override;
|
||||||
|
bool isSequential() const override;
|
||||||
|
qint64 size() const override;
|
||||||
|
bool seek(qint64 pos) override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
qint64 readData(char *data, qint64 maxSize) override;
|
||||||
|
qint64 writeData(const char *data, qint64 size) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Uint8Array m_array;
|
||||||
|
};
|
||||||
|
|
||||||
inline emscripten::val window()
|
inline emscripten::val window()
|
||||||
{
|
{
|
||||||
static emscripten::val savedWindow = emscripten::val::global("window");
|
static emscripten::val savedWindow = emscripten::val::global("window");
|
||||||
@ -225,6 +264,7 @@ namespace qstdweb {
|
|||||||
readDataTransfer(emscripten::val webObject, std::function<QVariant(QByteArray)> imageReader,
|
readDataTransfer(emscripten::val webObject, std::function<QVariant(QByteArray)> imageReader,
|
||||||
std::function<void(std::unique_ptr<QMimeData>)> onDone);
|
std::function<void(std::unique_ptr<QMimeData>)> onDone);
|
||||||
|
|
||||||
|
|
||||||
#if QT_CONFIG(thread)
|
#if QT_CONFIG(thread)
|
||||||
template<class T>
|
template<class T>
|
||||||
T proxyCall(std::function<T()> task, emscripten::ProxyingQueue *queue)
|
T proxyCall(std::function<T()> task, emscripten::ProxyingQueue *queue)
|
||||||
@ -261,6 +301,7 @@ namespace qstdweb {
|
|||||||
return task();
|
return task();
|
||||||
}
|
}
|
||||||
#endif // QT_CONFIG(thread)
|
#endif // QT_CONFIG(thread)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
@ -70,3 +70,28 @@ add_custom_command(
|
|||||||
${CMAKE_CURRENT_BINARY_DIR}/qtwasmtestlib.js)
|
${CMAKE_CURRENT_BINARY_DIR}/qtwasmtestlib.js)
|
||||||
|
|
||||||
target_link_options(qwasmcompositor_auto PRIVATE -sASYNCIFY -Os)
|
target_link_options(qwasmcompositor_auto PRIVATE -sASYNCIFY -Os)
|
||||||
|
|
||||||
|
qt_internal_add_manual_test(iodevices_auto
|
||||||
|
SOURCES
|
||||||
|
iodevices_main.cpp
|
||||||
|
../qtwasmtestlib/qtwasmtestlib.cpp
|
||||||
|
LIBRARIES
|
||||||
|
Qt::Core
|
||||||
|
Qt::CorePrivate
|
||||||
|
Qt::GuiPrivate
|
||||||
|
)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
TARGET iodevices_auto POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/iodevices_auto.html
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/iodevices_auto.html)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
TARGET iodevices_auto POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/../qtwasmtestlib/qtwasmtestlib.js
|
||||||
|
${CMAKE_CURRENT_BINARY_DIR}/qtwasmtestlib.js)
|
||||||
|
|
||||||
|
target_link_options(iodevices_auto PRIVATE -sASYNCIFY -Os)
|
||||||
|
|
||||||
|
10
tests/manual/wasm/qstdweb/iodevices_auto.html
Normal file
10
tests/manual/wasm/qstdweb/iodevices_auto.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<script type="text/javascript" src="qtwasmtestlib.js"></script>
|
||||||
|
<script type="text/javascript" src="iodevices_auto.js"></script>
|
||||||
|
<script>
|
||||||
|
window.onload = () => {
|
||||||
|
runTestCase(iodevices_auto_entry, document.getElementById("log"));
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<p>Running qstdweb iodevices auto test.</p>
|
||||||
|
<div id="log"></div>
|
103
tests/manual/wasm/qstdweb/iodevices_main.cpp
Normal file
103
tests/manual/wasm/qstdweb/iodevices_main.cpp
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// Copyright (C) 2022 The Qt Company Ltd.
|
||||||
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
|
||||||
|
|
||||||
|
#include <QtCore/QtCore>
|
||||||
|
#include <QtCore/private/qstdweb_p.h>
|
||||||
|
|
||||||
|
#include <qtwasmtestlib.h>
|
||||||
|
|
||||||
|
#include "emscripten.h"
|
||||||
|
|
||||||
|
using qstdweb::ArrayBuffer;
|
||||||
|
using qstdweb::Uint8Array;
|
||||||
|
using qstdweb::Blob;
|
||||||
|
using qstdweb::BlobIODevice;
|
||||||
|
using qstdweb::Uint8ArrayIODevice;
|
||||||
|
|
||||||
|
class WasmIoDevicesTest: public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void blobIODevice();
|
||||||
|
void uint8ArrayIODevice();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Creates a test arraybuffer with byte values [0..size] % 256 * 2
|
||||||
|
char testByteValue(int i) { return (i % 256) * 2; }
|
||||||
|
ArrayBuffer createTestArrayBuffer(int size)
|
||||||
|
{
|
||||||
|
ArrayBuffer buffer(size);
|
||||||
|
Uint8Array array(buffer);
|
||||||
|
for (int i = 0; i < size; ++i)
|
||||||
|
array.val().set(i, testByteValue(i));
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WasmIoDevicesTest::blobIODevice()
|
||||||
|
{
|
||||||
|
if (!qstdweb::canBlockCallingThread()) {
|
||||||
|
QtWasmTest::completeTestFunction(QtWasmTest::TestResult::Skip, "requires asyncify");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create test buffer and BlobIODevice
|
||||||
|
const int bufferSize = 16;
|
||||||
|
BlobIODevice blobDevice(Blob::fromArrayBuffer(createTestArrayBuffer(bufferSize)));
|
||||||
|
|
||||||
|
// Read back byte for byte from the device
|
||||||
|
QWASMVERIFY(blobDevice.open(QIODevice::ReadOnly));
|
||||||
|
for (int i = 0; i < bufferSize; ++i) {
|
||||||
|
char byte;
|
||||||
|
blobDevice.seek(i);
|
||||||
|
blobDevice.read(&byte, 1);
|
||||||
|
QWASMCOMPARE(byte, testByteValue(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
blobDevice.close();
|
||||||
|
QWASMVERIFY(!blobDevice.open(QIODevice::WriteOnly));
|
||||||
|
QWASMSUCCESS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WasmIoDevicesTest::uint8ArrayIODevice()
|
||||||
|
{
|
||||||
|
// Create test buffer and Uint8ArrayIODevice
|
||||||
|
const int bufferSize = 1024;
|
||||||
|
Uint8Array array(createTestArrayBuffer(bufferSize));
|
||||||
|
Uint8ArrayIODevice arrayDevice(array);
|
||||||
|
|
||||||
|
// Read back byte for byte from the device
|
||||||
|
QWASMVERIFY(arrayDevice.open(QIODevice::ReadWrite));
|
||||||
|
for (int i = 0; i < bufferSize; ++i) {
|
||||||
|
char byte;
|
||||||
|
arrayDevice.seek(i);
|
||||||
|
arrayDevice.read(&byte, 1);
|
||||||
|
QWASMCOMPARE(byte, testByteValue(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a different set of bytes
|
||||||
|
QWASMCOMPARE(arrayDevice.seek(0), true);
|
||||||
|
for (int i = 0; i < bufferSize; ++i) {
|
||||||
|
char byte = testByteValue(i + 1);
|
||||||
|
arrayDevice.seek(i);
|
||||||
|
QWASMCOMPARE(arrayDevice.write(&byte, 1), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that the original array was updated
|
||||||
|
QByteArray copy = QByteArray::fromEcmaUint8Array(array.val());
|
||||||
|
for (int i = 0; i < bufferSize; ++i)
|
||||||
|
QWASMCOMPARE(copy.at(i), testByteValue(i + 1));
|
||||||
|
|
||||||
|
arrayDevice.close();
|
||||||
|
QWASMSUCCESS();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
auto testObject = std::make_shared<WasmIoDevicesTest>();
|
||||||
|
QtWasmTest::initTestCase<QCoreApplication>(argc, argv, testObject);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "iodevices_main.moc"
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user