Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qsettings_wasm.cpp
Go to the documentation of this file.
1// Copyright (C) 2022 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qsettings.h"
5#ifndef QT_NO_SETTINGS
6
7#include "qsettings_p.h"
8#ifndef QT_NO_QOBJECT
9#include "qcoreapplication.h"
10#include <QFile>
11#endif // QT_NO_QOBJECT
12#include <QDebug>
13#include <QtCore/private/qstdweb_p.h>
14
15#include <QFileInfo>
16#include <QDir>
17#include <QList>
18#include <QSet>
19
20#include <emscripten.h>
21# include <emscripten/proxying.h>
22# include <emscripten/threading.h>
23# include <emscripten/val.h>
24
26
27using emscripten::val;
28using namespace Qt::StringLiterals;
29
30namespace {
31QStringView keyNameFromPrefixedStorageName(QStringView prefix, QStringView prefixedStorageName)
32{
33 // Return the key slice after m_keyPrefix, or an empty string view if no match
34 if (!prefixedStorageName.startsWith(prefix))
35 return QStringView();
36 return prefixedStorageName.sliced(prefix.length());
37}
38} // namespace
39
40//
41// Native settings implementation for WebAssembly using window.localStorage
42// as the storage backend. localStorage is a key-value store with a synchronous
43// API and a 5MB storage limit.
44//
46{
47public:
49 const QString &application);
51
52 void remove(const QString &key) final;
53 void set(const QString &key, const QVariant &value) final;
54 std::optional<QVariant> get(const QString &key) const final;
55 QStringList children(const QString &prefix, ChildSpec spec) const final;
56 void clear() final;
57 void sync() final;
58 void flush() final;
59 bool isWritable() const final;
60 QString fileName() const final;
61
63 QStringList m_keyPrefixes;
64};
65
67 const QString &organization,
68 const QString &application)
69 : QSettingsPrivate(QSettings::NativeFormat, scope, organization, application)
70{
71 if (organization.isEmpty()) {
73 return;
74 }
75
76 // The key prefix contians "qt" to separate Qt keys from other keys on localStorage, a
77 // version tag to allow for making changes to the key format in the future, the org
78 // and app names.
79 //
80 // User code could could create separate settings object with different org and app names,
81 // and would expect them to have separate settings. Also, different webassembly instances
82 // on the page could write to the same window.localStorage. Add the org and app name
83 // to the key prefix to differentiate, even if that leads to keys with redundant sections
84 // for the common case of a single org and app name.
85 //
86 // Also, the common Qt mechanism for user/system scope and all-application settings are
87 // implemented, using different prefixes.
88 const QString allAppsSetting = QStringLiteral("all-apps");
89 const QString systemSetting = QStringLiteral("sys-tem");
90
91 const QLatin1String separator("-");
92 const QLatin1String doubleSeparator("--");
93 const QString escapedOrganization = QString(organization).replace(separator, doubleSeparator);
94 const QString escapedApplication = QString(application).replace(separator, doubleSeparator);
95 const QString prefix = "qt-v0-" + escapedOrganization + separator;
97 if (!escapedApplication.isEmpty())
98 m_keyPrefixes.push_back(prefix + escapedApplication + separator);
99 m_keyPrefixes.push_back(prefix + allAppsSetting + separator);
100 }
101 if (!escapedApplication.isEmpty()) {
102 m_keyPrefixes.push_back(prefix + escapedApplication + separator + systemSetting
103 + separator);
104 }
105 m_keyPrefixes.push_back(prefix + allAppsSetting + separator + systemSetting + separator);
106}
107
109{
110 const std::string removed = QString(m_keyPrefixes.first() + key).toStdString();
111
112 qstdweb::runTaskOnMainThread<void>([this, &removed, &key]() {
113 std::vector<std::string> children = { removed };
114 const int length = val::global("window")["localStorage"]["length"].as<int>();
115 for (int i = 0; i < length; ++i) {
116 const QString storedKeyWithPrefix = QString::fromStdString(
117 val::global("window")["localStorage"].call<val>("key", i).as<std::string>());
118
119 const QStringView storedKey = keyNameFromPrefixedStorageName(
120 m_keyPrefixes.first(), QStringView(storedKeyWithPrefix));
121 if (storedKey.isEmpty() || !storedKey.startsWith(key))
122 continue;
123
124 children.push_back(storedKeyWithPrefix.toStdString());
125 }
126
127 for (const auto &child : children)
128 val::global("window")["localStorage"].call<val>("removeItem", child);
129 });
130}
131
133{
134 qstdweb::runTaskOnMainThread<void>([this, &key, &value]() {
135 const std::string keyString = QString(m_keyPrefixes.first() + key).toStdString();
136 const std::string valueString = QSettingsPrivate::variantToString(value).toStdString();
137 val::global("window")["localStorage"].call<void>("setItem", keyString, valueString);
138 });
139}
140
141std::optional<QVariant> QWasmLocalStorageSettingsPrivate::get(const QString &key) const
142{
143 return qstdweb::runTaskOnMainThread<std::optional<QVariant>>(
144 [this, &key]() -> std::optional<QVariant> {
145 for (const auto &prefix : m_keyPrefixes) {
146 const std::string keyString = QString(prefix + key).toStdString();
147 const emscripten::val value =
148 val::global("window")["localStorage"].call<val>("getItem", keyString);
149 if (!value.isNull()) {
151 QString::fromStdString(value.as<std::string>()));
152 }
153 if (!fallbacks) {
154 return std::nullopt;
155 }
156 }
157 return std::nullopt;
158 });
159}
160
162{
163 return qstdweb::runTaskOnMainThread<QStringList>([this, &prefix, &spec]() -> QStringList {
164 QSet<QString> nodes;
165 // Loop through all keys on window.localStorage, return Qt keys belonging to
166 // this application, with the correct prefix, and according to ChildSpec.
168 const int length = val::global("window")["localStorage"]["length"].as<int>();
169 for (int i = 0; i < length; ++i) {
170 for (const auto &storagePrefix : m_keyPrefixes) {
171 const QString keyString =
172 QString::fromStdString(val::global("window")["localStorage"]
173 .call<val>("key", i)
174 .as<std::string>());
175
176 const QStringView key =
177 keyNameFromPrefixedStorageName(storagePrefix, QStringView(keyString));
178 if (!key.isEmpty() && key.startsWith(prefix)) {
180 QSettingsPrivate::processChild(key.sliced(prefix.length()), spec, children);
181 if (!children.isEmpty())
182 nodes.insert(children.first());
183 }
184 if (!fallbacks)
185 break;
186 }
187 }
188
189 return QStringList(nodes.begin(), nodes.end());
190 });
191}
192
194{
195 qstdweb::runTaskOnMainThread<void>([this]() {
196 // Get all Qt keys from window.localStorage
197 const int length = val::global("window")["localStorage"]["length"].as<int>();
199 keys.reserve(length);
200 for (int i = 0; i < length; ++i)
202 (val::global("window")["localStorage"].call<val>("key", i).as<std::string>())));
203
204 // Remove all Qt keys. Note that localStorage does not guarantee a stable
205 // iteration order when the storage is mutated, which is why removal is done
206 // in a second step after getting all keys.
207 for (const QString &key : keys) {
208 if (!keyNameFromPrefixedStorageName(m_keyPrefixes.first(), key).isEmpty())
209 val::global("window")["localStorage"].call<val>("removeItem", key.toStdString());
210 }
211 });
212}
213
215
217
219{
220 return true;
221}
222
227
228//
229// Native settings implementation for WebAssembly using the indexed database as
230// the storage backend
231//
233{
234public:
235 QWasmIDBSettingsPrivate(QSettings::Scope scope, const QString &organization,
236 const QString &application);
238
239 void clear() override;
240 void sync() override;
241
242private:
243 bool writeSettingsToTemporaryFile(const QString &fileName, void *dataPtr, int size);
244 void loadIndexedDBFiles();
245
246
247 QString databaseName;
248 QString id;
249};
250
251constexpr char DbName[] = "/home/web_user";
252
254 const QString &organization,
255 const QString &application)
256 : QConfFileSettingsPrivate(QSettings::WebIndexedDBFormat, scope, organization, application)
257{
258 Q_ASSERT_X(qstdweb::haveJspi(), Q_FUNC_INFO, "QWasmIDBSettingsPrivate needs JSPI to work");
259
260 if (organization.isEmpty()) {
262 return;
263 }
264
265 databaseName = organization;
266 id = application;
267
268 loadIndexedDBFiles();
269
271}
272
274
275bool QWasmIDBSettingsPrivate::writeSettingsToTemporaryFile(const QString &fileName, void *dataPtr,
276 int size)
277{
279 QFileInfo fileInfo(fileName);
280 QDir dir(fileInfo.path());
281 if (!dir.exists())
282 dir.mkpath(fileInfo.path());
283
285 return false;
286
287 return size == file.write(reinterpret_cast<char *>(dataPtr), size);
288}
289
298
300{
301 // Reload the files, in case there were any changes in IndexedDB, and flush them to disk.
302 // Thanks to this, QConfFileSettingsPrivate::sync will handle key merging correctly.
303 loadIndexedDBFiles();
304
306
309 QByteArray dataPointer = file.readAll();
310
311 int error = 0;
312 emscripten_idb_store(DbName, fileName().toLocal8Bit(),
313 reinterpret_cast<void *>(dataPointer.data()), dataPointer.length(),
314 &error);
316 }
317}
318
319void QWasmIDBSettingsPrivate::loadIndexedDBFiles()
320{
321 for (const auto *confFile : getConfFiles()) {
322 int exists = 0;
323 int error = 0;
324 emscripten_idb_exists(DbName, confFile->name.toLocal8Bit(), &exists, &error);
325 if (error) {
327 return;
328 }
329 if (exists) {
330 void *contents;
331 int size;
332 emscripten_idb_load(DbName, confFile->name.toLocal8Bit(), &contents, &size, &error);
333 if (error || !writeSettingsToTemporaryFile(confFile->name, contents, size)) {
335 return;
336 }
337 }
338 }
339}
340
342 const QString &organization, const QString &application)
343{
344 // Make WebLocalStorageFormat the default native format
346 format = QSettings::WebLocalStorageFormat;
347
348 // Check if cookies are enabled (required for using persistent storage)
349
350 const bool cookiesEnabled = qstdweb::runTaskOnMainThread<bool>(
351 []() { return val::global("navigator")["cookieEnabled"].as<bool>(); });
352
353 constexpr QLatin1StringView cookiesWarningMessage(
354 "QSettings::%1 requires cookies, falling back to IniFormat with temporary file");
355 if (!cookiesEnabled) {
356 if (format == QSettings::WebLocalStorageFormat) {
357 qWarning() << cookiesWarningMessage.arg("WebLocalStorageFormat");
359 } else if (format == QSettings::WebIndexedDBFormat) {
360 qWarning() << cookiesWarningMessage.arg("WebIndexedDBFormat");
362 }
363 }
364 if (format == QSettings::WebIndexedDBFormat && !qstdweb::haveJspi()) {
365 qWarning() << "QSettings::WebIndexedDBFormat requires JSPI, falling back to IniFormat with "
366 "temporary file";
368 }
369
370 // Create settings backend according to selected format
371 switch (format) {
372 case QSettings::Format::WebLocalStorageFormat:
373 return new QWasmLocalStorageSettingsPrivate(scope, organization, application);
374 case QSettings::Format::WebIndexedDBFormat:
375 return new QWasmIDBSettingsPrivate(scope, organization, application);
393 return new QConfFileSettingsPrivate(format, scope, organization, application);
395 return nullptr;
397 Q_UNREACHABLE();
398 break;
399 }
400}
401
403#endif // QT_NO_SETTINGS
\inmodule QtCore
Definition qbytearray.h:57
virtual void initAccess()
QString fileName() const override
const QList< QConfFile * > & getConfFiles() const
\inmodule QtCore
Definition qdir.h:20
\inmodule QtCore
Definition qfile.h:93
QFILE_MAYBE_NODISCARD bool open(OpenMode flags) override
Opens the file using OpenMode mode, returning true if successful; otherwise false.
Definition qfile.cpp:904
QByteArray readAll()
Reads all remaining data from the device, and returns it as a byte array.
qint64 write(const char *data, qint64 len)
Writes at most maxSize bytes of data from data to the device.
bool isEmpty() const noexcept
Definition qlist.h:401
T & first()
Definition qlist.h:645
void push_back(parameter_type t)
Definition qlist.h:675
QObjectList children
Definition qobject.h:74
static QSettingsPrivate * create(QSettings::Format format, QSettings::Scope scope, const QString &organization, const QString &application)
void setStatus(QSettings::Status status) const
static QVariant stringToVariant(const QString &s)
static QString variantToString(const QVariant &v)
QSettings::Scope scope
static void processChild(QStringView key, ChildSpec spec, QStringList &result)
\inmodule QtCore
Definition qsettings.h:30
Format
This enum type specifies the storage format used by QSettings.
Definition qsettings.h:48
@ CustomFormat2
Definition qsettings.h:64
@ CustomFormat10
Definition qsettings.h:72
@ CustomFormat1
Definition qsettings.h:63
@ CustomFormat15
Definition qsettings.h:77
@ CustomFormat13
Definition qsettings.h:75
@ CustomFormat3
Definition qsettings.h:65
@ CustomFormat8
Definition qsettings.h:70
@ NativeFormat
Definition qsettings.h:49
@ CustomFormat7
Definition qsettings.h:69
@ CustomFormat16
Definition qsettings.h:78
@ CustomFormat5
Definition qsettings.h:67
@ CustomFormat9
Definition qsettings.h:71
@ CustomFormat11
Definition qsettings.h:73
@ CustomFormat12
Definition qsettings.h:74
@ CustomFormat14
Definition qsettings.h:76
@ CustomFormat4
Definition qsettings.h:66
@ InvalidFormat
Definition qsettings.h:62
@ CustomFormat6
Definition qsettings.h:68
Scope
This enum specifies whether settings are user-specific or shared by all users of the same system.
Definition qsettings.h:84
@ AccessError
Definition qsettings.h:41
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:78
constexpr qsizetype length() const noexcept
Same as size().
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QString & replace(qsizetype i, qsizetype len, QChar after)
Definition qstring.cpp:3824
static QString fromStdString(const std::string &s)
Definition qstring.h:1447
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
std::string toStdString() const
Returns a std::string object with the data contained in this QString.
Definition qstring.h:1444
qsizetype length() const noexcept
Returns the number of characters in this string.
Definition qstring.h:191
\inmodule QtCore
Definition qvariant.h:65
QWasmIDBSettingsPrivate(QSettings::Scope scope, const QString &organization, const QString &application)
std::optional< QVariant > get(const QString &key) const final
~QWasmLocalStorageSettingsPrivate() final=default
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
b clear()
Combined button and popup list for selecting options.
bool haveJspi()
Definition qstdweb.cpp:823
#define Q_FUNC_INFO
QList< QString > QStringList
Constructs a string list that contains the given string, str.
DBusConnection const char DBusError * error
EGLOutputLayerEXT EGLint EGLAttrib value
[5]
#define qWarning
Definition qlogging.h:166
GLuint64 key
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLenum GLuint GLenum GLsizei length
GLenum GLuint id
[7]
GLint GLsizei GLsizei GLenum format
static QString keyString(int sym, QChar::Category category)
#define Q_ASSERT_X(cond, x, msg)
Definition qrandom.cpp:48
constexpr char DbName[]
static char * toLocal8Bit(char *out, QStringView in, QStringConverter::State *state)
#define QStringLiteral(str)
QFuture< QSet< QChar > > set
[10]
QFile file
[0]
QStringList keys
QString dir
[11]
QLayoutItem * child
[0]