summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikolaj Boc <mikolaj.boc@qt.io>2023-08-04 15:11:42 +0200
committerMikolaj Boc <mikolaj.boc@qt.io>2023-08-28 17:53:29 +0200
commit5c5c9dd830ecaa5dc1be80ef8199d7077096b61e (patch)
tree129553de73b2477c131d6eb8dd771c2f1c5cf282
parent39a5ed4bdd348d3cd18a08d322b48d85e8d116e8 (diff)
Main thread-proxy localStorage oprations in native WASM QSettings
localStorage is unavailable on workers. Operations will now get proxied to the main thread instead. This - among other benefits - makes tst_QSettings::testThreadSafety pass. Task-number: QTBUG-115509 Change-Id: Iebbe5e9f9069948f8728e0a82628cc082b30de12 Reviewed-by: Morten Johan Sørvig <morten.sorvig@qt.io>
-rw-r--r--src/corelib/io/qsettings_wasm.cpp153
-rw-r--r--tests/auto/corelib/io/qsettings/tst_qsettings.cpp21
2 files changed, 108 insertions, 66 deletions
diff --git a/src/corelib/io/qsettings_wasm.cpp b/src/corelib/io/qsettings_wasm.cpp
index 9b206e26d5..da51fe6836 100644
--- a/src/corelib/io/qsettings_wasm.cpp
+++ b/src/corelib/io/qsettings_wasm.cpp
@@ -18,7 +18,9 @@
#include <QSet>
#include <emscripten.h>
-#include <emscripten/val.h>
+# include <emscripten/proxying.h>
+# include <emscripten/threading.h>
+# include <emscripten/val.h>
QT_BEGIN_NAMESPACE
@@ -58,7 +60,6 @@ public:
QString fileName() const final;
private:
- val m_localStorage = val::global("window")["localStorage"];
QStringList m_keyPrefixes;
};
@@ -108,89 +109,106 @@ void QWasmLocalStorageSettingsPrivate::remove(const QString &key)
{
const std::string removed = QString(m_keyPrefixes.first() + key).toStdString();
- std::vector<std::string> children = { removed };
- const int length = m_localStorage["length"].as<int>();
- for (int i = 0; i < length; ++i) {
- const QString storedKeyWithPrefix =
- QString::fromStdString(m_localStorage.call<val>("key", i).as<std::string>());
+ qstdweb::runTaskOnMainThread<void>([this, &removed, &key]() {
+ std::vector<std::string> children = { removed };
+ const int length = val::global("window")["localStorage"]["length"].as<int>();
+ for (int i = 0; i < length; ++i) {
+ const QString storedKeyWithPrefix = QString::fromStdString(
+ val::global("window")["localStorage"].call<val>("key", i).as<std::string>());
- const QStringView storedKey = keyNameFromPrefixedStorageName(
- m_keyPrefixes.first(), QStringView(storedKeyWithPrefix));
- if (storedKey.isEmpty() || !storedKey.startsWith(key))
- continue;
+ const QStringView storedKey = keyNameFromPrefixedStorageName(
+ m_keyPrefixes.first(), QStringView(storedKeyWithPrefix));
+ if (storedKey.isEmpty() || !storedKey.startsWith(key))
+ continue;
- children.push_back(storedKeyWithPrefix.toStdString());
- }
+ children.push_back(storedKeyWithPrefix.toStdString());
+ }
- for (const auto &child : children)
- m_localStorage.call<val>("removeItem", child);
+ for (const auto &child : children)
+ val::global("window")["localStorage"].call<val>("removeItem", child);
+ });
}
void QWasmLocalStorageSettingsPrivate::set(const QString &key, const QVariant &value)
{
- const std::string keyString = QString(m_keyPrefixes.first() + key).toStdString();
- const std::string valueString = QSettingsPrivate::variantToString(value).toStdString();
- m_localStorage.call<void>("setItem", keyString, valueString);
+ qstdweb::runTaskOnMainThread<void>([this, &key, &value]() {
+ const std::string keyString = QString(m_keyPrefixes.first() + key).toStdString();
+ const std::string valueString = QSettingsPrivate::variantToString(value).toStdString();
+ val::global("window")["localStorage"].call<void>("setItem", keyString, valueString);
+ });
}
std::optional<QVariant> QWasmLocalStorageSettingsPrivate::get(const QString &key) const
{
- for (const auto &prefix : m_keyPrefixes) {
- const std::string keyString = QString(prefix + key).toStdString();
- const emscripten::val value = m_localStorage.call<val>("getItem", keyString);
- if (!value.isNull())
- return QSettingsPrivate::stringToVariant(
- QString::fromStdString(value.as<std::string>()));
- if (!fallbacks)
- return std::nullopt;
- }
- return std::nullopt;
+ return qstdweb::runTaskOnMainThread<std::optional<QVariant>>(
+ [this, &key]() -> std::optional<QVariant> {
+ for (const auto &prefix : m_keyPrefixes) {
+ const std::string keyString = QString(prefix + key).toStdString();
+ const emscripten::val value =
+ val::global("window")["localStorage"].call<val>("getItem", keyString);
+ if (!value.isNull()) {
+ return QSettingsPrivate::stringToVariant(
+ QString::fromStdString(value.as<std::string>()));
+ }
+ if (!fallbacks) {
+ return std::nullopt;
+ }
+ }
+ return std::nullopt;
+ });
}
QStringList QWasmLocalStorageSettingsPrivate::children(const QString &prefix, ChildSpec spec) const
{
- QSet<QString> nodes;
- // Loop through all keys on window.localStorage, return Qt keys belonging to
- // this application, with the correct prefix, and according to ChildSpec.
- QStringList children;
- const int length = m_localStorage["length"].as<int>();
- for (int i = 0; i < length; ++i) {
- for (const auto &storagePrefix : m_keyPrefixes) {
- const QString keyString =
- QString::fromStdString(m_localStorage.call<val>("key", i).as<std::string>());
-
- const QStringView key =
- keyNameFromPrefixedStorageName(storagePrefix, QStringView(keyString));
- if (!key.isEmpty() && key.startsWith(prefix)) {
- QStringList children;
- QSettingsPrivate::processChild(key.sliced(prefix.length()), spec, children);
- if (!children.isEmpty())
- nodes.insert(children.first());
+ return qstdweb::runTaskOnMainThread<QStringList>([this, &prefix, &spec]() -> QStringList {
+ QSet<QString> nodes;
+ // Loop through all keys on window.localStorage, return Qt keys belonging to
+ // this application, with the correct prefix, and according to ChildSpec.
+ QStringList children;
+ const int length = val::global("window")["localStorage"]["length"].as<int>();
+ for (int i = 0; i < length; ++i) {
+ for (const auto &storagePrefix : m_keyPrefixes) {
+ const QString keyString =
+ QString::fromStdString(val::global("window")["localStorage"]
+ .call<val>("key", i)
+ .as<std::string>());
+
+ const QStringView key =
+ keyNameFromPrefixedStorageName(storagePrefix, QStringView(keyString));
+ if (!key.isEmpty() && key.startsWith(prefix)) {
+ QStringList children;
+ QSettingsPrivate::processChild(key.sliced(prefix.length()), spec, children);
+ if (!children.isEmpty())
+ nodes.insert(children.first());
+ }
+ if (!fallbacks)
+ break;
}
- if (!fallbacks)
- break;
}
- }
- return QStringList(nodes.begin(), nodes.end());
+ return QStringList(nodes.begin(), nodes.end());
+ });
}
void QWasmLocalStorageSettingsPrivate::clear()
{
- // Get all Qt keys from window.localStorage
- const int length = m_localStorage["length"].as<int>();
- QStringList keys;
- keys.reserve(length);
- for (int i = 0; i < length; ++i)
- keys.append(QString::fromStdString((m_localStorage.call<val>("key", i).as<std::string>())));
-
- // Remove all Qt keys. Note that localStorage does not guarantee a stable
- // iteration order when the storage is mutated, which is why removal is done
- // in a second step after getting all keys.
- for (const QString &key : keys) {
- if (!keyNameFromPrefixedStorageName(m_keyPrefixes.first(), key).isEmpty())
- m_localStorage.call<val>("removeItem", key.toStdString());
- }
+ qstdweb::runTaskOnMainThread<void>([this]() {
+ // Get all Qt keys from window.localStorage
+ const int length = val::global("window")["localStorage"]["length"].as<int>();
+ QStringList keys;
+ keys.reserve(length);
+ for (int i = 0; i < length; ++i)
+ keys.append(QString::fromStdString(
+ (val::global("window")["localStorage"].call<val>("key", i).as<std::string>())));
+
+ // Remove all Qt keys. Note that localStorage does not guarantee a stable
+ // iteration order when the storage is mutated, which is why removal is done
+ // in a second step after getting all keys.
+ for (const QString &key : keys) {
+ if (!keyNameFromPrefixedStorageName(m_keyPrefixes.first(), key).isEmpty())
+ val::global("window")["localStorage"].call<val>("removeItem", key.toStdString());
+ }
+ });
}
void QWasmLocalStorageSettingsPrivate::sync() { }
@@ -361,9 +379,12 @@ QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::
format = QSettings::WebLocalStorageFormat;
// Check if cookies are enabled (required for using persistent storage)
- const bool cookiesEnabled = val::global("navigator")["cookieEnabled"].as<bool>();
- constexpr QLatin1StringView cookiesWarningMessage
- ("QSettings::%1 requires cookies, falling back to IniFormat with temporary file");
+
+ const bool cookiesEnabled = qstdweb::runTaskOnMainThread<bool>(
+ []() { return val::global("navigator")["cookieEnabled"].as<bool>(); });
+
+ constexpr QLatin1StringView cookiesWarningMessage(
+ "QSettings::%1 requires cookies, falling back to IniFormat with temporary file");
if (!cookiesEnabled) {
if (format == QSettings::WebLocalStorageFormat) {
qWarning() << cookiesWarningMessage.arg("WebLocalStorageFormat");
diff --git a/tests/auto/corelib/io/qsettings/tst_qsettings.cpp b/tests/auto/corelib/io/qsettings/tst_qsettings.cpp
index 0661ac17ca..74784fb3c2 100644
--- a/tests/auto/corelib/io/qsettings/tst_qsettings.cpp
+++ b/tests/auto/corelib/io/qsettings/tst_qsettings.cpp
@@ -10,6 +10,7 @@
#include <QtCore/QCoreApplication>
#include <QtCore/QDir>
+#include <QtCore/QEventLoop>
#include <QtCore/QtGlobal>
#include <QtCore/QThread>
#include <QtCore/QSysInfo>
@@ -44,6 +45,7 @@
#if defined(Q_OS_WASM)
#include <QtCore/private/qstdweb_p.h>
+#include "emscripten/threading.h"
#include "emscripten/val.h"
#endif
@@ -2136,6 +2138,12 @@ void tst_QSettings::testThreadSafety()
#if !QT_CONFIG(thread)
QSKIP("This test requires threads to be enabled.");
#endif // !QT_CONFIG(thread)
+#if defined(Q_OS_WASM)
+ if (!qstdweb::haveJspi())
+ QSKIP("Test needs jspi on WASM. Calls are proxied to the main thread from SettingsThreads, "
+ "which necessitates the use of an event loop to yield to the main loop. Event loops "
+ "require jspi.");
+#endif
SettingsThread threads[NumThreads];
int i, j;
@@ -2144,6 +2152,19 @@ void tst_QSettings::testThreadSafety()
for (i = 0; i < NumThreads; ++i)
threads[i].start(i + 1);
+
+#if defined(Q_OS_WASM) && QT_CONFIG(thread)
+ QEventLoop loop;
+ int remaining = NumThreads;
+ for (int i = 0; i < NumThreads; ++i) {
+ QObject::connect(&threads[i], &QThread::finished, this, [&remaining, &loop]() {
+ if (!--remaining)
+ loop.quit();
+ });
+ }
+ loop.exec();
+#endif // defined(Q_OS_WASM) && QT_CONFIG(thread)
+
for (i = 0; i < NumThreads; ++i)
threads[i].wait();