summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/io/qsettings.h6
-rw-r--r--src/corelib/io/qsettings_p.h6
-rw-r--r--src/corelib/io/qsettings_wasm.cpp286
3 files changed, 227 insertions, 71 deletions
diff --git a/src/corelib/io/qsettings.h b/src/corelib/io/qsettings.h
index b8fba349ca..5d2c330728 100644
--- a/src/corelib/io/qsettings.h
+++ b/src/corelib/io/qsettings.h
@@ -54,6 +54,12 @@ public:
Registry64Format,
#endif
+#if defined(Q_OS_WASM)
+ // FIXME: add public API in next minor release.
+ // WebLocalStorageFormat (IniFormat + 1)
+ // WebIDBSFormat (IniFormat + 2)
+#endif
+
InvalidFormat = 16,
CustomFormat1,
CustomFormat2,
diff --git a/src/corelib/io/qsettings_p.h b/src/corelib/io/qsettings_p.h
index d1ea37ea0c..2429820242 100644
--- a/src/corelib/io/qsettings_p.h
+++ b/src/corelib/io/qsettings_p.h
@@ -216,10 +216,6 @@ protected:
mutable QSettings::Status status;
};
-#ifdef Q_OS_WASM
-class QWasmSettingsPrivate;
-#endif
-
class QConfFileSettingsPrivate : public QSettingsPrivate
{
public:
@@ -266,7 +262,7 @@ private:
Qt::CaseSensitivity caseSensitivity;
qsizetype nextPosition;
#ifdef Q_OS_WASM
- friend class QWasmSettingsPrivate;
+ friend class QWasmIDBSettingsPrivate;
#endif
};
diff --git a/src/corelib/io/qsettings_wasm.cpp b/src/corelib/io/qsettings_wasm.cpp
index 5c15e6d89b..8404a526b6 100644
--- a/src/corelib/io/qsettings_wasm.cpp
+++ b/src/corelib/io/qsettings_wasm.cpp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Qt Company Ltd.
+// Copyright (C) 2022 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 "qsettings.h"
@@ -16,20 +16,160 @@
#include <QList>
#include <emscripten.h>
+#include <emscripten/val.h>
QT_BEGIN_NAMESPACE
+using emscripten::val;
using namespace Qt::StringLiterals;
-static bool isReadReady = false;
+//
+// Native settings implementation for WebAssembly using window.localStorage
+// as the storage backend. localStorage is a key-value store with a synchronous
+// API and a 5MB storage limit.
+//
+class QWasmLocalStorageSettingsPrivate final : public QSettingsPrivate
+{
+public:
+ QWasmLocalStorageSettingsPrivate(QSettings::Scope scope, const QString &organization,
+ const QString &application);
+
+ void remove(const QString &key) final;
+ void set(const QString &key, const QVariant &value) final;
+ std::optional<QVariant> get(const QString &key) const final;
+ QStringList children(const QString &prefix, ChildSpec spec) const final;
+ void clear() final;
+ void sync() final;
+ void flush() final;
+ bool isWritable() const final;
+ QString fileName() const final;
+
+private:
+ QString prependStoragePrefix(const QString &key) const;
+ QStringView removeStoragePrefix(QStringView key) const;
+ val m_localStorage = val::global("window")["localStorage"];
+ QString m_keyPrefix;
+};
+
+QWasmLocalStorageSettingsPrivate::QWasmLocalStorageSettingsPrivate(QSettings::Scope scope,
+ const QString &organization,
+ const QString &application)
+ : QSettingsPrivate(QSettings::NativeFormat, scope, organization, application)
+{
+ // The key prefix contians "qt" to separate Qt keys from other keys on localStorage, a
+ // version tag to allow for making changes to the key format in the future, the org
+ // and app names.
+ //
+ // User code could could create separate settings object with different org and app names,
+ // and would expect them to have separate settings. Also, different webassembly instanaces
+ // on the page could write to the same window.localStorage. Add the org and app name
+ // to the key prefix to differentiate, even if that leads to keys with redundant sectons
+ // for the common case of a single org and app name.
+ const QLatin1String separator("-");
+ const QLatin1String doubleSeparator("--");
+ const QString escapedOrganization = QString(organization).replace(separator, doubleSeparator);
+ const QString escapedApplication = QString(application).replace(separator, doubleSeparator);
+ const QLatin1String prefix("qt-v0-");
+ m_keyPrefix.reserve(prefix.length() + escapedOrganization.length() +
+ escapedApplication.length() + separator.length() * 2);
+ m_keyPrefix = prefix + escapedOrganization + separator + escapedApplication + separator;
+}
+
+void QWasmLocalStorageSettingsPrivate::remove(const QString &key)
+{
+ const std::string keyString = prependStoragePrefix(key).toStdString();
+ m_localStorage.call<val>("removeItem", keyString);
+}
+
+void QWasmLocalStorageSettingsPrivate::set(const QString &key, const QVariant &value)
+{
+ const std::string keyString = prependStoragePrefix(key).toStdString();
+ const std::string valueString = QSettingsPrivate::variantToString(value).toStdString();
+ m_localStorage.call<void>("setItem", keyString, valueString);
+}
+
+std::optional<QVariant> QWasmLocalStorageSettingsPrivate::get(const QString &key) const
+{
+ const std::string keyString = prependStoragePrefix(key).toStdString();
+ const emscripten::val value = m_localStorage.call<val>("getItem", keyString);
+ if (value.isNull())
+ return std::nullopt;
+ const QString valueString = QString::fromStdString(value.as<std::string>());
+ return QSettingsPrivate::stringToVariant(valueString);
+}
+
+QStringList QWasmLocalStorageSettingsPrivate::children(const QString &prefix, ChildSpec spec) const
+{
+ // 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) {
+ const QString keyString =
+ QString::fromStdString(m_localStorage.call<val>("key", i).as<std::string>());
+
+ const QStringView key = removeStoragePrefix(QStringView(keyString));
+ if (key.isEmpty())
+ continue;
+ if (!key.startsWith(prefix))
+ continue;
+
+ QSettingsPrivate::processChild(key.sliced(prefix.length()), spec, children);
+ }
+
+ return children;
+}
+
+void QWasmLocalStorageSettingsPrivate::clear()
+{
+ // Remove all Qt keys from window.localStorage
+ const int length = m_localStorage["length"].as<int>();
+ for (int i = 0; i < length; ++i) {
+ std::string fullKey = (m_localStorage.call<val>("key", i).as<std::string>());
+ QString key = QString::fromStdString(fullKey);
+ if (removeStoragePrefix(QStringView(key)).isEmpty() == false)
+ m_localStorage.call<val>("removeItem", fullKey);
+ }
+}
+
+void QWasmLocalStorageSettingsPrivate::sync() { }
+
+void QWasmLocalStorageSettingsPrivate::flush() { }
-class QWasmSettingsPrivate : public QConfFileSettingsPrivate
+bool QWasmLocalStorageSettingsPrivate::isWritable() const
+{
+ return true;
+}
+
+QString QWasmLocalStorageSettingsPrivate::fileName() const
+{
+ return QString();
+}
+
+QString QWasmLocalStorageSettingsPrivate::prependStoragePrefix(const QString &key) const
+{
+ return m_keyPrefix + key;
+}
+
+QStringView QWasmLocalStorageSettingsPrivate::removeStoragePrefix(QStringView key) const
+{
+ // Return the key slice after m_keyPrefix, or an empty string view if no match
+ if (!key.startsWith(m_keyPrefix))
+ return QStringView();
+ return key.sliced(m_keyPrefix.length());
+}
+
+//
+// Native settings implementation for WebAssembly using the indexed database as
+// the storage backend
+//
+class QWasmIDBSettingsPrivate : public QConfFileSettingsPrivate
{
public:
- QWasmSettingsPrivate(QSettings::Scope scope, const QString &organization,
- const QString &application);
- ~QWasmSettingsPrivate();
- static QWasmSettingsPrivate *get(void *userData);
+ QWasmIDBSettingsPrivate(QSettings::Scope scope, const QString &organization,
+ const QString &application);
+ ~QWasmIDBSettingsPrivate();
+ static QWasmIDBSettingsPrivate *get(void *userData);
std::optional<QVariant> get(const QString &key) const override;
QStringList children(const QString &prefix, ChildSpec spec) const override;
@@ -46,14 +186,15 @@ public:
private:
QString databaseName;
QString id;
- static QList<QWasmSettingsPrivate *> liveSettings;
+ static QList<QWasmIDBSettingsPrivate *> liveSettings;
};
-QList<QWasmSettingsPrivate *> QWasmSettingsPrivate::liveSettings;
+QList<QWasmIDBSettingsPrivate *> QWasmIDBSettingsPrivate::liveSettings;
+static bool isReadReady = false;
-static void QWasmSettingsPrivate_onLoad(void *userData, void *dataPtr, int size)
+static void QWasmIDBSettingsPrivate_onLoad(void *userData, void *dataPtr, int size)
{
- QWasmSettingsPrivate *settings = QWasmSettingsPrivate::get(userData);
+ QWasmIDBSettingsPrivate *settings = QWasmIDBSettingsPrivate::get(userData);
if (!settings)
return;
@@ -70,21 +211,21 @@ static void QWasmSettingsPrivate_onLoad(void *userData, void *dataPtr, int size)
}
}
-static void QWasmSettingsPrivate_onError(void *userData)
+static void QWasmIDBSettingsPrivate_onError(void *userData)
{
- if (QWasmSettingsPrivate *settings = QWasmSettingsPrivate::get(userData))
+ if (QWasmIDBSettingsPrivate *settings = QWasmIDBSettingsPrivate::get(userData))
settings->setStatus(QSettings::AccessError);
}
-static void QWasmSettingsPrivate_onStore(void *userData)
+static void QWasmIDBSettingsPrivate_onStore(void *userData)
{
- if (QWasmSettingsPrivate *settings = QWasmSettingsPrivate::get(userData))
+ if (QWasmIDBSettingsPrivate *settings = QWasmIDBSettingsPrivate::get(userData))
settings->setStatus(QSettings::NoError);
}
-static void QWasmSettingsPrivate_onCheck(void *userData, int exists)
+static void QWasmIDBSettingsPrivate_onCheck(void *userData, int exists)
{
- if (QWasmSettingsPrivate *settings = QWasmSettingsPrivate::get(userData)) {
+ if (QWasmIDBSettingsPrivate *settings = QWasmIDBSettingsPrivate::get(userData)) {
if (exists)
settings->loadLocal(settings->fileName().toLocal8Bit());
else
@@ -92,31 +233,9 @@ static void QWasmSettingsPrivate_onCheck(void *userData, int exists)
}
}
-QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format,
- QSettings::Scope scope,
- const QString &organization,
- const QString &application)
-{
- Q_UNUSED(format);
- if (organization == "Qt"_L1)
- {
- QString organizationDomain = QCoreApplication::organizationDomain();
- QString applicationName = QCoreApplication::applicationName();
-
- QSettingsPrivate *newSettings;
- newSettings = new QWasmSettingsPrivate(scope, organizationDomain, applicationName);
-
- newSettings->beginGroupOrArray(QSettingsGroup(normalizedKey(organization)));
- if (!application.isEmpty())
- newSettings->beginGroupOrArray(QSettingsGroup(normalizedKey(application)));
-
- return newSettings;
- }
- return new QWasmSettingsPrivate(scope, organization, application);
-}
-
-QWasmSettingsPrivate::QWasmSettingsPrivate(QSettings::Scope scope, const QString &organization,
- const QString &application)
+QWasmIDBSettingsPrivate::QWasmIDBSettingsPrivate(QSettings::Scope scope,
+ const QString &organization,
+ const QString &application)
: QConfFileSettingsPrivate(QSettings::NativeFormat, scope, organization, application)
{
liveSettings.push_back(this);
@@ -128,29 +247,29 @@ QWasmSettingsPrivate::QWasmSettingsPrivate(QSettings::Scope scope, const QString
emscripten_idb_async_exists("/home/web_user",
fileName().toLocal8Bit(),
reinterpret_cast<void*>(this),
- QWasmSettingsPrivate_onCheck,
- QWasmSettingsPrivate_onError);
+ QWasmIDBSettingsPrivate_onCheck,
+ QWasmIDBSettingsPrivate_onError);
}
-QWasmSettingsPrivate::~QWasmSettingsPrivate()
+QWasmIDBSettingsPrivate::~QWasmIDBSettingsPrivate()
{
liveSettings.removeAll(this);
}
-QWasmSettingsPrivate *QWasmSettingsPrivate::get(void *userData)
+QWasmIDBSettingsPrivate *QWasmIDBSettingsPrivate::get(void *userData)
{
- if (QWasmSettingsPrivate::liveSettings.contains(userData))
- return reinterpret_cast<QWasmSettingsPrivate *>(userData);
+ if (QWasmIDBSettingsPrivate::liveSettings.contains(userData))
+ return reinterpret_cast<QWasmIDBSettingsPrivate *>(userData);
return nullptr;
}
- void QWasmSettingsPrivate::initAccess()
+void QWasmIDBSettingsPrivate::initAccess()
{
if (isReadReady)
QConfFileSettingsPrivate::initAccess();
}
-std::optional<QVariant> QWasmSettingsPrivate::get(const QString &key) const
+std::optional<QVariant> QWasmIDBSettingsPrivate::get(const QString &key) const
{
if (isReadReady)
return QConfFileSettingsPrivate::get(key);
@@ -158,22 +277,22 @@ std::optional<QVariant> QWasmSettingsPrivate::get(const QString &key) const
return std::nullopt;
}
-QStringList QWasmSettingsPrivate::children(const QString &prefix, ChildSpec spec) const
+QStringList QWasmIDBSettingsPrivate::children(const QString &prefix, ChildSpec spec) const
{
return QConfFileSettingsPrivate::children(prefix, spec);
}
-void QWasmSettingsPrivate::clear()
+void QWasmIDBSettingsPrivate::clear()
{
QConfFileSettingsPrivate::clear();
emscripten_idb_async_delete("/home/web_user",
fileName().toLocal8Bit(),
reinterpret_cast<void*>(this),
- QWasmSettingsPrivate_onStore,
- QWasmSettingsPrivate_onError);
+ QWasmIDBSettingsPrivate_onStore,
+ QWasmIDBSettingsPrivate_onError);
}
-void QWasmSettingsPrivate::sync()
+void QWasmIDBSettingsPrivate::sync()
{
QConfFileSettingsPrivate::sync();
@@ -186,22 +305,22 @@ void QWasmSettingsPrivate::sync()
reinterpret_cast<void *>(dataPointer.data()),
dataPointer.length(),
reinterpret_cast<void*>(this),
- QWasmSettingsPrivate_onStore,
- QWasmSettingsPrivate_onError);
+ QWasmIDBSettingsPrivate_onStore,
+ QWasmIDBSettingsPrivate_onError);
}
}
-void QWasmSettingsPrivate::flush()
+void QWasmIDBSettingsPrivate::flush()
{
sync();
}
-bool QWasmSettingsPrivate::isWritable() const
+bool QWasmIDBSettingsPrivate::isWritable() const
{
return isReadReady && QConfFileSettingsPrivate::isWritable();
}
-void QWasmSettingsPrivate::syncToLocal(const char *data, int size)
+void QWasmIDBSettingsPrivate::syncToLocal(const char *data, int size)
{
QFile file(fileName());
@@ -214,27 +333,62 @@ void QWasmSettingsPrivate::syncToLocal(const char *data, int size)
reinterpret_cast<void *>(data.data()),
data.length(),
reinterpret_cast<void*>(this),
- QWasmSettingsPrivate_onStore,
- QWasmSettingsPrivate_onError);
+ QWasmIDBSettingsPrivate_onStore,
+ QWasmIDBSettingsPrivate_onError);
setReady();
}
}
-void QWasmSettingsPrivate::loadLocal(const QByteArray &filename)
+void QWasmIDBSettingsPrivate::loadLocal(const QByteArray &filename)
{
emscripten_idb_async_load("/home/web_user",
filename.data(),
reinterpret_cast<void*>(this),
- QWasmSettingsPrivate_onLoad,
- QWasmSettingsPrivate_onError);
+ QWasmIDBSettingsPrivate_onLoad,
+ QWasmIDBSettingsPrivate_onError);
}
-void QWasmSettingsPrivate::setReady()
+void QWasmIDBSettingsPrivate::setReady()
{
isReadReady = true;
setStatus(QSettings::NoError);
QConfFileSettingsPrivate::initAccess();
}
+QSettingsPrivate *QSettingsPrivate::create(QSettings::Format format, QSettings::Scope scope,
+ const QString &organization, const QString &application)
+{
+ const auto WebLocalStorageFormat = QSettings::IniFormat + 1;
+ const auto WebIdbFormat = QSettings::IniFormat + 2;
+
+ // Make WebLocalStorageFormat the default native format
+ if (format == QSettings::NativeFormat)
+ format = QSettings::Format(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");
+ if (format == WebLocalStorageFormat && !cookiesEnabled) {
+ qWarning() << cookiesWarningMessage.arg("WebLocalStorageFormat");
+ format = QSettings::IniFormat;
+ } else if (format == WebIdbFormat && !cookiesEnabled) {
+ qWarning() << cookiesWarningMessage.arg("WebIdbFormat");
+ format = QSettings::IniFormat;
+ }
+
+ // Create settings backend according to selected format
+ if (format == WebLocalStorageFormat) {
+ return new QWasmLocalStorageSettingsPrivate(scope, organization, application);
+ } else if (format == WebIdbFormat) {
+ return new QWasmIDBSettingsPrivate(scope, organization, application);
+ } else if (format == QSettings::IniFormat) {
+ return new QConfFileSettingsPrivate(format, scope, organization, application);
+ }
+
+ qWarning() << "Unsupported settings format" << format;
+ return nullptr;
+}
+
QT_END_NAMESPACE
#endif // QT_NO_SETTINGS