summaryrefslogtreecommitdiffstats
path: root/src/corelib
diff options
context:
space:
mode:
Diffstat (limited to 'src/corelib')
-rw-r--r--src/corelib/CMakeLists.txt1
-rw-r--r--src/corelib/Qt6AndroidMacros.cmake21
-rw-r--r--src/corelib/doc/qtcore.qdocconf7
-rw-r--r--src/corelib/doc/src/cmake/qt_add_android_dynamic_feature_java_source_dirs.qdoc30
-rw-r--r--src/corelib/doc/src/qtcore.qdoc9
-rw-r--r--src/corelib/io/qfile.cpp13
-rw-r--r--src/corelib/io/qfilesystemengine.cpp8
-rw-r--r--src/corelib/io/qfilesystemengine_p.h2
-rw-r--r--src/corelib/io/qfsfileengine_p.h2
-rw-r--r--src/corelib/itemmodels/qrangemodel_impl.h41
-rw-r--r--src/corelib/kernel/qmetacontainer.cpp8
-rw-r--r--src/corelib/kernel/qobject.cpp35
-rw-r--r--src/corelib/platform/darwin/qdarwinsecurityscopedfileengine.mm552
-rw-r--r--src/corelib/platform/darwin/qdarwinsecurityscopedfileengine_p.h29
-rw-r--r--src/corelib/platform/wasm/qstdweb.cpp51
-rw-r--r--src/corelib/platform/wasm/qstdweb_p.h21
-rw-r--r--src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp54
-rw-r--r--src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h5
18 files changed, 799 insertions, 90 deletions
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt
index f31968f8199..32b70a1f288 100644
--- a/src/corelib/CMakeLists.txt
+++ b/src/corelib/CMakeLists.txt
@@ -701,6 +701,7 @@ qt_internal_extend_target(Core CONDITION APPLE
kernel/qcore_mac.mm kernel/qcore_mac_p.h
kernel/qcoreapplication_mac.cpp
kernel/qeventdispatcher_cf.mm kernel/qeventdispatcher_cf_p.h
+ platform/darwin/qdarwinsecurityscopedfileengine.mm platform/darwin/qdarwinsecurityscopedfileengine_p.h
LIBRARIES
${FWCoreFoundation}
${FWFoundation}
diff --git a/src/corelib/Qt6AndroidMacros.cmake b/src/corelib/Qt6AndroidMacros.cmake
index 6a83e947146..be362ba1925 100644
--- a/src/corelib/Qt6AndroidMacros.cmake
+++ b/src/corelib/Qt6AndroidMacros.cmake
@@ -106,6 +106,27 @@ function(qt6_add_android_dynamic_features target)
endif()
endfunction()
+
+function(qt_add_android_dynamic_feature_java_source_dirs)
+ qt6_add_android_dynamic_feature_java_source_dirs(${ARGV})
+endfunction()
+
+# Add java source directories for dynamic feature. Intermediate solution until java library
+# support exists.
+function(qt6_add_android_dynamic_feature_java_source_dirs target)
+
+ set(opt_args "")
+ set(single_args "")
+ set(multi_args
+ SOURCE_DIRS
+ )
+ cmake_parse_arguments(PARSE_ARGV 1 arg "${opt_args}" "${single_args}" "${multi_args}")
+ if(arg_SOURCE_DIRS)
+ set_property(TARGET ${target} APPEND PROPERTY
+ _qt_android_gradle_java_source_dirs ${arg_SOURCE_DIRS})
+ endif()
+endfunction()
+
# Generate the deployment settings json file for a cmake target.
function(qt6_android_generate_deployment_settings target)
# Information extracted from mkspecs/features/android/android_deployment_settings.prf
diff --git a/src/corelib/doc/qtcore.qdocconf b/src/corelib/doc/qtcore.qdocconf
index d2b386373a0..b3e4e9d30a9 100644
--- a/src/corelib/doc/qtcore.qdocconf
+++ b/src/corelib/doc/qtcore.qdocconf
@@ -21,7 +21,7 @@ qhp.QtCore.virtualFolder = qtcore
qhp.QtCore.indexTitle = Qt Core
qhp.QtCore.indexRoot =
-qhp.QtCore.subprojects = manual classes
+qhp.QtCore.subprojects = manual examples classes
qhp.QtCore.subprojects.manual.title = Qt Core
qhp.QtCore.subprojects.manual.indexTitle = Qt Core module topics
qhp.QtCore.subprojects.manual.type = manual
@@ -31,6 +31,11 @@ qhp.QtCore.subprojects.classes.indexTitle = Qt Core C++ Classes
qhp.QtCore.subprojects.classes.selectors = class fake:headerfile
qhp.QtCore.subprojects.classes.sortPages = true
+qhp.QtCore.subprojects.examples.title = Examples
+qhp.QtCore.subprojects.examples.indexTitle = Qt Core Examples
+qhp.QtCore.subprojects.examples.selectors = example
+qhp.QtCore.subprojects.examples.sortPages = true
+
tagfile = ../../../doc/qtcore/qtcore.tags
# Make QtCore depend on all doc modules; this ensures complete inheritance
diff --git a/src/corelib/doc/src/cmake/qt_add_android_dynamic_feature_java_source_dirs.qdoc b/src/corelib/doc/src/cmake/qt_add_android_dynamic_feature_java_source_dirs.qdoc
new file mode 100644
index 00000000000..cf670110cab
--- /dev/null
+++ b/src/corelib/doc/src/cmake/qt_add_android_dynamic_feature_java_source_dirs.qdoc
@@ -0,0 +1,30 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+\page qt_add_android_dynamic_feature_java_source_dirs.html
+\ingroup cmake-commands-qtcore
+
+\title qt_add_android_dynamic_feature_java_source_dirs
+\keyword qt6_add_android_dynamic_feature_java_source_dirs
+
+\summary {Adds Java source directories to a dynamic feature build.}
+
+\include cmake-find-package-core.qdocinc
+
+\cmakecommandsince 6.11
+
+\section1 Synopsis
+
+\badcode
+qt_add_android_dynamic_feature_java_source_dirs(target [SOURCE_DIRS <directory1> <directory2> ...])
+\endcode
+
+\versionlessCMakeCommandsNote qt6_add_android_dynamic_feature_java_source_dirs()
+
+\section1 Description
+
+The command adds extra Java/Kotlin source directories to the \c {target}
+executable when building the executable app with dynamic feature functionality.
+To be used in conjunction with qt6_add_android_dynamic_features().
+*/
diff --git a/src/corelib/doc/src/qtcore.qdoc b/src/corelib/doc/src/qtcore.qdoc
index ec5fa564639..fbcd02aeea5 100644
--- a/src/corelib/doc/src/qtcore.qdoc
+++ b/src/corelib/doc/src/qtcore.qdoc
@@ -31,3 +31,12 @@
target_link_libraries(mytarget PRIVATE Qt6::CorePrivate)
\endcode
*/
+
+/*!
+ \group corelib_examples
+ \title Qt Core Examples
+
+ \brief Examples for the Qt Core.
+
+ To learn how to use features of the Qt Core module, see examples:
+*/
diff --git a/src/corelib/io/qfile.cpp b/src/corelib/io/qfile.cpp
index e1fc043a0ff..0184fd838aa 100644
--- a/src/corelib/io/qfile.cpp
+++ b/src/corelib/io/qfile.cpp
@@ -592,6 +592,10 @@ QFile::rename(const QString &newName)
return false;
}
+ // Keep engine for target alive during the operation
+ // FIXME: Involve the target engine in the operation
+ auto targetEngine = QFileSystemEngine::createLegacyEngine(newName);
+
// If the file exists and it is a case-changing rename ("foo" -> "Foo"),
// compare Ids to make sure it really is a different file.
// Note: this does not take file engines into account.
@@ -738,6 +742,11 @@ QFile::link(const QString &linkName)
qWarning("QFile::link: Empty or null file name");
return false;
}
+
+ // Keep engine for target alive during the operation
+ // FIXME: Involve the target engine in the operation
+ auto targetEngine = QFileSystemEngine::createLegacyEngine(linkName);
+
QFileInfo fi(linkName);
if (d->engine()->link(fi.absoluteFilePath())) {
unsetError();
@@ -771,6 +780,10 @@ bool QFilePrivate::copy(const QString &newName)
Q_ASSERT(error == QFile::NoError);
Q_ASSERT(!q->isOpen());
+ // Keep engine for target alive during the operation
+ // FIXME: Involve the target engine in the operation
+ auto targetEngine = QFileSystemEngine::createLegacyEngine(newName);
+
// Some file engines can perform this copy more efficiently (e.g., Windows
// calling CopyFile).
if (engine()->copy(newName))
diff --git a/src/corelib/io/qfilesystemengine.cpp b/src/corelib/io/qfilesystemengine.cpp
index 03da2331e05..46d4cb709e2 100644
--- a/src/corelib/io/qfilesystemengine.cpp
+++ b/src/corelib/io/qfilesystemengine.cpp
@@ -190,6 +190,14 @@ QFileSystemEngine::createLegacyEngine(QFileSystemEntry &entry, QFileSystemMetaDa
return engine;
}
+std::unique_ptr<QAbstractFileEngine>
+QFileSystemEngine::createLegacyEngine(const QString &fileName)
+{
+ QFileSystemEntry entry(fileName);
+ QFileSystemMetaData metaData;
+ return createLegacyEngine(entry, metaData);
+}
+
//static
QString QFileSystemEngine::resolveUserName(const QFileSystemEntry &entry, QFileSystemMetaData &metaData)
{
diff --git a/src/corelib/io/qfilesystemengine_p.h b/src/corelib/io/qfilesystemengine_p.h
index ee70ccc1e1b..46eeeda569e 100644
--- a/src/corelib/io/qfilesystemengine_p.h
+++ b/src/corelib/io/qfilesystemengine_p.h
@@ -161,6 +161,8 @@ public:
static std::unique_ptr<QAbstractFileEngine>
createLegacyEngine(QFileSystemEntry &entry, QFileSystemMetaData &data);
+ static std::unique_ptr<QAbstractFileEngine>
+ createLegacyEngine(const QString &fileName);
private:
static QString slowCanonicalized(const QString &path);
diff --git a/src/corelib/io/qfsfileengine_p.h b/src/corelib/io/qfsfileengine_p.h
index 2de6cb0cb73..8ad673bf0bf 100644
--- a/src/corelib/io/qfsfileengine_p.h
+++ b/src/corelib/io/qfsfileengine_p.h
@@ -82,7 +82,7 @@ public:
bool setFileTime(const QDateTime &newDate, QFile::FileTime time) override;
QDateTime fileTime(QFile::FileTime time) const override;
void setFileName(const QString &file) override;
- void setFileEntry(QFileSystemEntry &&entry);
+ virtual void setFileEntry(QFileSystemEntry &&entry);
int handle() const override;
#ifndef QT_NO_FILESYSTEMITERATOR
diff --git a/src/corelib/itemmodels/qrangemodel_impl.h b/src/corelib/itemmodels/qrangemodel_impl.h
index 70552cbfe05..88bd6cf444e 100644
--- a/src/corelib/itemmodels/qrangemodel_impl.h
+++ b/src/corelib/itemmodels/qrangemodel_impl.h
@@ -28,7 +28,7 @@
#include <functional>
#include <iterator>
#include <type_traits>
-#include <QtCore/q20type_traits.h>
+#include <QtCore/qxptype_traits.h>
#include <tuple>
#include <QtCore/q23utility.h>
@@ -562,35 +562,30 @@ namespace QRangeModelDetails
}
};
- template <typename P, typename R, typename = void>
- struct protocol_parentRow : std::false_type {};
template <typename P, typename R>
- struct protocol_parentRow<P, R,
- std::void_t<decltype(std::declval<P&>().parentRow(std::declval<wrapped_t<R>&>()))>>
- : std::true_type {};
+ using protocol_parentRow_test = decltype(std::declval<P&>()
+ .parentRow(std::declval<QRangeModelDetails::wrapped_t<R>&>()));
+ template <typename P, typename R>
+ using protocol_parentRow = qxp::is_detected<protocol_parentRow_test, P, R>;
- template <typename P, typename R, typename = void>
- struct protocol_childRows : std::false_type {};
template <typename P, typename R>
- struct protocol_childRows<P, R,
- std::void_t<decltype(std::declval<P&>().childRows(std::declval<wrapped_t<R>&>()))>>
- : std::true_type {};
+ using protocol_childRows_test = decltype(std::declval<P&>()
+ .childRows(std::declval<QRangeModelDetails::wrapped_t<R>&>()));
+ template <typename P, typename R>
+ using protocol_childRows = qxp::is_detected<protocol_childRows_test, P, R>;
- template <typename P, typename R, typename = void>
- struct protocol_setParentRow : std::false_type {};
template <typename P, typename R>
- struct protocol_setParentRow<P, R,
- std::void_t<decltype(std::declval<P&>().setParentRow(std::declval<wrapped_t<R>&>(),
- std::declval<wrapped_t<R>*>()))>>
- : std::true_type {};
+ using protocol_setParentRow_test = decltype(std::declval<P&>()
+ .setParentRow(std::declval<QRangeModelDetails::wrapped_t<R>&>(),
+ std::declval<QRangeModelDetails::wrapped_t<R>*>()));
+ template <typename P, typename R>
+ using protocol_setParentRow = qxp::is_detected<protocol_setParentRow_test, P, R>;
- template <typename P, typename R, typename = void>
- struct protocol_mutable_childRows : std::false_type {};
template <typename P, typename R>
- struct protocol_mutable_childRows<P, R,
- std::void_t<decltype(refTo(std::declval<P&>().childRows(std::declval<wrapped_t<R>&>()))
- = {}) >>
- : std::true_type {};
+ using protocol_mutable_childRows_test = decltype(refTo(std::declval<P&>()
+ .childRows(std::declval<wrapped_t<R>&>())) = {});
+ template <typename P, typename R>
+ using protocol_mutable_childRows = qxp::is_detected<protocol_mutable_childRows_test, P, R>;
template <typename P, typename = void>
struct protocol_newRow : std::false_type {};
diff --git a/src/corelib/kernel/qmetacontainer.cpp b/src/corelib/kernel/qmetacontainer.cpp
index 4b4ea06d8b9..6173198a972 100644
--- a/src/corelib/kernel/qmetacontainer.cpp
+++ b/src/corelib/kernel/qmetacontainer.cpp
@@ -210,7 +210,7 @@ void QMetaContainer::destroyIterator(const void *iterator) const
*/
bool QMetaContainer::compareIterator(const void *i, const void *j) const
{
- return hasIterator() ? d_ptr->compareIteratorFn(i, j) : false;
+ return i == j || (hasIterator() && d_ptr->compareIteratorFn(i, j));
}
/*!
@@ -249,7 +249,7 @@ void QMetaContainer::advanceIterator(void *iterator, qsizetype step) const
*/
qsizetype QMetaContainer::diffIterator(const void *i, const void *j) const
{
- return hasIterator() ? d_ptr->diffIteratorFn(i, j) : 0;
+ return (i != j && hasIterator()) ? d_ptr->diffIteratorFn(i, j) : 0;
}
/*!
@@ -327,7 +327,7 @@ void QMetaContainer::destroyConstIterator(const void *iterator) const
*/
bool QMetaContainer::compareConstIterator(const void *i, const void *j) const
{
- return hasConstIterator() ? d_ptr->compareConstIteratorFn(i, j) : false;
+ return i == j || (hasConstIterator() && d_ptr->compareConstIteratorFn(i, j));
}
/*!
@@ -366,7 +366,7 @@ void QMetaContainer::advanceConstIterator(void *iterator, qsizetype step) const
*/
qsizetype QMetaContainer::diffConstIterator(const void *i, const void *j) const
{
- return hasConstIterator() ? d_ptr->diffConstIteratorFn(i, j) : 0;
+ return (i != j && hasConstIterator()) ? d_ptr->diffConstIteratorFn(i, j) : 0;
}
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qobject.cpp b/src/corelib/kernel/qobject.cpp
index 02c9f00f301..607dc23f56c 100644
--- a/src/corelib/kernel/qobject.cpp
+++ b/src/corelib/kernel/qobject.cpp
@@ -2696,23 +2696,38 @@ static void err_method_notfound(const QObject *object,
case QSIGNAL_CODE: type = "signal"; break;
}
const char *loc = extract_location(method);
+ const char *err;
if (strchr(method, ')') == nullptr) // common typing mistake
- qCWarning(lcConnect, "QObject::%s: Parentheses expected, %s %s::%s%s%s", func, type,
- object->metaObject()->className(), method + 1, loc ? " in " : "", loc ? loc : "");
+ err = "Parentheses expected,";
else
- qCWarning(lcConnect, "QObject::%s: No such %s %s::%s%s%s", func, type,
- object->metaObject()->className(), method + 1, loc ? " in " : "", loc ? loc : "");
+ err = "No such";
+ qCWarning(lcConnect, "QObject::%s: %s %s %s::%s%s%s", func, err, type,
+ object->metaObject()->className(), method + 1, loc ? " in " : "", loc ? loc : "");
+}
+
+enum class ConnectionEnd : bool { Sender, Receiver };
+Q_DECL_COLD_FUNCTION
+static void err_info_about_object(const char *func, const QObject *o, ConnectionEnd end)
+{
+ if (!o)
+ return;
+ const QString name = o->objectName();
+ if (name.isEmpty())
+ return;
+ const bool sender = end == ConnectionEnd::Sender;
+ qCWarning(lcConnect, "QObject::%s: (%s name:%*s'%ls')",
+ func,
+ sender ? "sender" : "receiver",
+ sender ? 3 : 1, // ← length of generated whitespace
+ "",
+ qUtf16Printable(name));
}
Q_DECL_COLD_FUNCTION
static void err_info_about_objects(const char *func, const QObject *sender, const QObject *receiver)
{
- QString a = sender ? sender->objectName() : QString();
- QString b = receiver ? receiver->objectName() : QString();
- if (!a.isEmpty())
- qCWarning(lcConnect, "QObject::%s: (sender name: '%s')", func, a.toLocal8Bit().data());
- if (!b.isEmpty())
- qCWarning(lcConnect, "QObject::%s: (receiver name: '%s')", func, b.toLocal8Bit().data());
+ err_info_about_object(func, sender, ConnectionEnd::Sender);
+ err_info_about_object(func, receiver, ConnectionEnd::Receiver);
}
Q_DECL_COLD_FUNCTION
diff --git a/src/corelib/platform/darwin/qdarwinsecurityscopedfileengine.mm b/src/corelib/platform/darwin/qdarwinsecurityscopedfileengine.mm
new file mode 100644
index 00000000000..cb38445f4fe
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinsecurityscopedfileengine.mm
@@ -0,0 +1,552 @@
+// Copyright (C) 2025 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
+// Qt-Security score:significant reason:default
+
+#include "qdarwinsecurityscopedfileengine_p.h"
+
+#include <QtCore/qloggingcategory.h>
+#include <QtCore/qstandardpaths.h>
+#include <QtCore/qreadwritelock.h>
+#include <QtCore/qscopedvaluerollback.h>
+
+#include <QtCore/private/qcore_mac_p.h>
+#include <QtCore/private/qfsfileengine_p.h>
+#include <QtCore/private/qfilesystemengine_p.h>
+
+#include <thread>
+#include <mutex>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <Foundation/NSURL.h>
+
+QT_BEGIN_NAMESPACE
+
+using namespace Qt::StringLiterals;
+
+Q_STATIC_LOGGING_CATEGORY(lcSecEngine, "qt.core.io.security-scoped-fileengine", QtCriticalMsg)
+
+template<typename T> class BackgroundLoader;
+
+/*
+ File engine handler for security scoped file paths.
+
+ Installs itself as soon as QtCore is loaded if the application
+ is sandboxed (optionally on macOS, and always on iOS and friends).
+*/
+class SecurityScopedFileEngineHandler : public QAbstractFileEngineHandler
+{
+public:
+ SecurityScopedFileEngineHandler();
+ ~SecurityScopedFileEngineHandler();
+
+ void registerPossiblySecurityScopedURL(NSURL *url);
+
+ std::unique_ptr<QAbstractFileEngine> create(const QString &fileName) const override;
+
+ static BackgroundLoader<SecurityScopedFileEngineHandler>& get();
+
+private:
+ Q_DISABLE_COPY_MOVE(SecurityScopedFileEngineHandler)
+
+ void saveBookmark(NSURL *url);
+ void saveBookmarks();
+
+ NSURL *bookmarksFile() const;
+
+ static NSString *cacheKeyForUrl(NSURL *url);
+ static NSString *cacheKeyForPath(const QString &url);
+
+ NSMutableDictionary *m_bookmarks = nullptr;
+ mutable QReadWriteLock m_bookmarkLock;
+
+ friend class SecurityScopedFileEngine;
+};
+
+/*
+ Helper class for asynchronous instantiation of types.
+*/
+template<typename T>
+class BackgroundLoader
+{
+public:
+ explicit BackgroundLoader(bool shouldLoad) {
+ if (shouldLoad) {
+ m_thread = std::thread([this]() {
+ m_instance = std::make_unique<T>();
+ });
+ }
+ }
+
+ ~BackgroundLoader()
+ {
+ std::scoped_lock lock(m_mutex);
+ if (m_thread.joinable())
+ m_thread.join();
+ }
+
+ T* operator->() const
+ {
+ std::scoped_lock lock(m_mutex);
+ if (m_thread.joinable())
+ m_thread.join();
+ return m_instance.get();
+ }
+
+ explicit operator bool() const
+ {
+ std::scoped_lock lock(m_mutex);
+ return m_thread.joinable() || m_instance;
+ }
+
+private:
+ mutable std::mutex m_mutex;
+ mutable std::thread m_thread;
+ std::unique_ptr<T> m_instance;
+};
+
+/*
+ Thread-safe background-loading of optional security scoped handler,
+ with the ability to kick off instantiation early during program load.
+*/
+BackgroundLoader<SecurityScopedFileEngineHandler>& SecurityScopedFileEngineHandler::get()
+{
+ using Handler = BackgroundLoader<SecurityScopedFileEngineHandler>;
+ static Handler handler = []() -> Handler {
+ if (!qt_apple_isSandboxed())
+ return Handler{false};
+
+ qCInfo(lcSecEngine) << "Application sandbox is active. Registering security-scoped file engine.";
+ return Handler{true};
+ }();
+ return handler;
+}
+
+static void initializeSecurityScopedFileEngineHandler()
+{
+ // Kick off loading of bookmarks early in the background
+ std::ignore = SecurityScopedFileEngineHandler::get();
+}
+Q_CONSTRUCTOR_FUNCTION(initializeSecurityScopedFileEngineHandler);
+
+/*
+ Registration function for possibly security scoped URLs.
+
+ Entry points that might provide security scoped URLs such as file
+ dialogs or drag-and-drop should use this function to ensure that
+ the security scoped file engine handler knows about the URL.
+*/
+QUrl qt_apple_urlFromPossiblySecurityScopedURL(NSURL *url)
+{
+ if (auto &handler = SecurityScopedFileEngineHandler::get())
+ handler->registerPossiblySecurityScopedURL(url);
+
+ // Note: The URL itself doesn't encode any of the bookmark data,
+ // neither in the scheme or as fragments or query parameters,
+ // as it's all handled by the bookmark cache in the file engine.
+ return QUrl(QString::fromNSString(url.absoluteString)
+ .normalized(QString::NormalizationForm_C));
+}
+
+static bool checkIfResourceIsReachable(NSURL *url)
+{
+ NSError *error = nullptr;
+ if ([url checkResourceIsReachableAndReturnError:&error])
+ return true;
+
+ // Our goal is to check whether the file exists or not, and if
+ // not, defer creating a bookmark for it. If we get any other
+ // error we want to know.
+ if (![error.domain isEqualToString:NSCocoaErrorDomain] || error.code != NSFileReadNoSuchFileError) {
+ qCWarning(lcSecEngine) << "Unexpected" << error
+ << "when resolving reachability for" << url;
+ }
+
+ return false;
+}
+
+/*
+ File engine for maintaining access lifetime of security-scoped
+ resources on sandboxed Apple platforms.
+
+ Note that there isn't necessarily a 1:1 relationship between
+ the file being operated on by the QFSFileEngine and the security
+ scoped resource that allows access to it, for example in the
+ case of a folder giving access to all files (and sub-folders)
+ within it.
+*/
+class SecurityScopedFileEngine : public QFSFileEngine
+{
+ Q_DECLARE_PRIVATE(QFSFileEngine)
+public:
+ SecurityScopedFileEngine(const QString &fileName, NSURL *securityScopedUrl)
+ : QFSFileEngine(fileName)
+ , m_securityScopedUrl([securityScopedUrl retain])
+ {
+ startAccessingSecurityScopedResource();
+ }
+
+ ~SecurityScopedFileEngine()
+ {
+ stopAccessingSecurityScopedResource();
+ [m_securityScopedUrl release];
+ }
+
+ void setFileName(const QString &fileName) override
+ {
+ QFileSystemEntry entry(fileName);
+ setFileEntry(std::move(entry));
+ }
+
+ void setFileEntry(QFileSystemEntry &&entry) override
+ {
+ // We can't rely on the new entry being accessible under the same
+ // security scope as the original path, or even that the new path
+ // is a security scoped resource, so stop access here, and start
+ // access for the new resource below if needed.
+ stopAccessingSecurityScopedResource();
+ [m_securityScopedUrl release];
+ m_securityScopedUrl = nil;
+
+ const QString fileName = entry.filePath();
+ QFSFileEngine::setFileEntry(std::move(entry));
+
+ // The new path may not be a security scoped resource, but if it is
+ // we need to establish access to it. The only way to do that is to
+ // actually create an engine for it, including resolving bookmarks.
+ auto newEngine = SecurityScopedFileEngineHandler::get()->create(fileName);
+ if (auto *engine = dynamic_cast<SecurityScopedFileEngine*>(newEngine.get())) {
+ m_securityScopedUrl = [engine->m_securityScopedUrl retain];
+ startAccessingSecurityScopedResource();
+ }
+ }
+
+private:
+ void startAccessingSecurityScopedResource()
+ {
+ if ([m_securityScopedUrl startAccessingSecurityScopedResource]) {
+ qCDebug(lcSecEngine) << "Started accessing" << m_securityScopedUrl.path
+ << "on behalf of" << fileName(DefaultName);
+
+ m_securityScopeWasReachable = securityScopeIsReachable();
+ } else {
+ qCWarning(lcSecEngine) << "Unexpectedly using security scoped"
+ << "file engine for" << m_securityScopedUrl.path
+ << "on behalf of" << fileName(DefaultName)
+ << "without needing scoped access";
+ }
+ }
+
+ void stopAccessingSecurityScopedResource()
+ {
+ if (!m_securityScopeWasReachable && securityScopeIsReachable()) {
+ // The security scoped URL didn't exist when we first started
+ // accessing it, but it does now, so persist a bookmark for it.
+ qCDebug(lcSecEngine) << "Security scoped resource has been created. Saving bookmark.";
+ SecurityScopedFileEngineHandler::get()->saveBookmark(m_securityScopedUrl);
+ }
+
+ // Note: Stopping access is a no-op if we didn't have access
+ [m_securityScopedUrl stopAccessingSecurityScopedResource];
+ qCDebug(lcSecEngine) << "Stopped accessing" << m_securityScopedUrl.path
+ << "on behalf of" << fileName(DefaultName);
+ }
+
+ bool securityScopeIsReachable() const
+ {
+ return checkIfResourceIsReachable(m_securityScopedUrl);
+ }
+
+ // See note above about relationship to fileName
+ NSURL *m_securityScopedUrl = nullptr;
+ bool m_securityScopeWasReachable = false;
+};
+
+// ----------------------------------------------------------------------
+
+SecurityScopedFileEngineHandler::SecurityScopedFileEngineHandler()
+{
+ QMacAutoReleasePool pool;
+
+ NSURL *savedBookmarks = bookmarksFile();
+ if ([NSFileManager.defaultManager fileExistsAtPath:savedBookmarks.path]) {
+ NSError *error = nullptr;
+ m_bookmarks = [[NSDictionary dictionaryWithContentsOfURL:savedBookmarks
+ error:&error] mutableCopy];
+
+ if (error) {
+ qCWarning(lcSecEngine) << "Failed to load bookmarks from"
+ << savedBookmarks << ":" << error;
+ } else {
+ qCInfo(lcSecEngine) << "Loaded existing bookmarks for" << m_bookmarks.allKeys;
+ }
+ }
+
+ if (!m_bookmarks)
+ m_bookmarks = [NSMutableDictionary new];
+}
+
+SecurityScopedFileEngineHandler::~SecurityScopedFileEngineHandler()
+{
+ [m_bookmarks release];
+}
+
+void SecurityScopedFileEngineHandler::registerPossiblySecurityScopedURL(NSURL *url)
+{
+ QMacAutoReleasePool pool;
+
+ // Start accessing the resource, to check if it's security scoped,
+ // and allow us to create a bookmark for it on both macOS and iOS.
+ if (![url startAccessingSecurityScopedResource])
+ return; // All good, not security scoped
+
+ if (checkIfResourceIsReachable(url)) {
+ // We can access the resource, which means it exists, so we can
+ // create a persistent bookmark for it right away. We want to do
+ // this as soon as possible, so that if the app is terminated the
+ // user can continue working on the file without the app needing
+ // to ask for access again via a file dialog.
+ saveBookmark(url);
+ } else {
+ // The file isn't accessible, likely because it doesn't exist.
+ // As we can only create security scoped bookmarks for files
+ // that exist we store the URL itself for now, and save it to
+ // a bookmark later when we detect that the file has been created.
+ qCInfo(lcSecEngine) << "Resource is not reachable."
+ << "Registering URL" << url << "instead";
+ QWriteLocker locker(&m_bookmarkLock);
+ m_bookmarks[cacheKeyForUrl(url)] = url;
+ }
+
+ // Balance access from above
+ [url stopAccessingSecurityScopedResource];
+
+#if defined(Q_OS_MACOS)
+ // On macOS, unlike iOS, URLs from file dialogs, etc, come with implicit
+ // access already, and we are expected to balance this access with an
+ // explicit stopAccessingSecurityScopedResource. We release the last
+ // access here to unify the behavior between macOS and iOS, and then
+ // leave it up to the SecurityScopedFileEngine to regain access, where
+ // we know the lifetime of resource use, and when to release access.
+ [url stopAccessingSecurityScopedResource];
+#endif
+}
+
+std::unique_ptr<QAbstractFileEngine> SecurityScopedFileEngineHandler::create(const QString &fileName) const
+{
+ QMacAutoReleasePool pool;
+
+ static thread_local bool recursionGuard = false;
+ if (recursionGuard)
+ return nullptr;
+
+ if (fileName.isEmpty())
+ return nullptr;
+
+ QFileSystemEntry fileSystemEntry(fileName);
+ QFileSystemMetaData metaData;
+
+ {
+ // Check if there's another engine that claims to handle the given file name.
+ // This covers non-QFSFileEngines like QTemporaryFileEngine, and QResourceFileEngine.
+ // If there isn't one, we'll get nullptr back, and know that we can access the
+ // file via our special QFSFileEngine.
+ QScopedValueRollback<bool> rollback(recursionGuard, true);
+ if (auto engine = QFileSystemEngine::createLegacyEngine(fileSystemEntry, metaData)) {
+ // Shortcut the logic of the createLegacyEngine call we're in by
+ // just returning this engine now.
+ qCDebug(lcSecEngine) << "Preferring non-QFSFileEngine engine"
+ << engine.get() << "for" << fileName;
+ return engine;
+ }
+ }
+
+ // We're mapping the file name to existing bookmarks below, so make sure
+ // we use as close as we can get to the canonical path. For files that
+ // do not exist we fall back to the cleaned absolute path.
+ auto canonicalEntry = QFileSystemEngine::canonicalName(fileSystemEntry, metaData);
+ if (canonicalEntry.isEmpty())
+ canonicalEntry = QFileSystemEngine::absoluteName(fileSystemEntry);
+
+ if (canonicalEntry.isRelative()) {
+ // We try to map relative paths to absolute above, but doing so requires
+ // knowledge of the current working directory, which we only have if the
+ // working directory has already started access through other means. We
+ // can't explicitly start access of the working directory here, as doing
+ // so requires its name, which we can't get from getcwd() without access.
+ // Fortunately all of the entry points of security scoped URLs such as
+ // file dialogs or drag-and-drop give us absolute paths, and APIs like
+ // QDir::filePath() will construct absolute URLs without needing the
+ // current working directory.
+ qCWarning(lcSecEngine) << "Could not resolve" << fileSystemEntry.filePath()
+ << "against current working working directory";
+ return nullptr;
+ }
+
+ // Clean the path as well, to remove any trailing slashes for directories
+ QString filePath = QDir::cleanPath(canonicalEntry.filePath());
+
+ // Files inside the sandbox container can always be accessed directly
+ static const QString sandboxRoot = QString::fromNSString(NSHomeDirectory());
+ if (filePath.startsWith(sandboxRoot))
+ return nullptr;
+
+ // The same applies to files inside the application's own bundle
+ static const QString bundleRoot = QString::fromNSString(NSBundle.mainBundle.bundlePath);
+ if (filePath.startsWith(bundleRoot))
+ return nullptr;
+
+ qCDebug(lcSecEngine) << "Looking up bookmark for" << filePath << "based on incoming fileName" << fileName;
+
+ // Check if we have a persisted bookmark for this fileName, or
+ // any of its containing directories (which will give us access
+ // to the file).
+ QReadLocker locker(&m_bookmarkLock);
+ auto *cacheKey = cacheKeyForPath(filePath);
+ NSObject *bookmarkData = nullptr;
+ while (cacheKey.length > 1) {
+ bookmarkData = m_bookmarks[cacheKey];
+ if (bookmarkData)
+ break;
+ cacheKey = [cacheKey stringByDeletingLastPathComponent];
+ }
+
+ // We didn't find a bookmark, so there's no point in trying to manage
+ // this file via a SecurityScopedFileEngine.
+ if (!bookmarkData) {
+ qCDebug(lcSecEngine) << "No bookmark found. Falling back to QFSFileEngine.";
+ return nullptr;
+ }
+
+ NSURL *securityScopedUrl = nullptr;
+ if ([bookmarkData isKindOfClass:NSURL.class]) {
+ securityScopedUrl = static_cast<NSURL*>(bookmarkData);
+ } else {
+ NSError *error = nullptr;
+ BOOL bookmarkDataIsStale = NO;
+ securityScopedUrl = [NSURL URLByResolvingBookmarkData:static_cast<NSData*>(bookmarkData)
+ options:
+ #if defined(Q_OS_MACOS)
+ NSURLBookmarkResolutionWithSecurityScope
+ #else
+ // iOS bookmarks are always security scoped, and we
+ // don't need or want any of the other options.
+ NSURLBookmarkResolutionOptions(0)
+ #endif
+ relativeToURL:nil /* app-scoped bookmark */
+ bookmarkDataIsStale:&bookmarkDataIsStale
+ error:&error];
+
+ if (!securityScopedUrl || error) {
+ qCWarning(lcSecEngine) << "Failed to resolve bookmark data for"
+ << fileName << ":" << error;
+ return nullptr;
+ }
+
+ if (bookmarkDataIsStale) {
+ // This occurs when for example the file has been renamed, moved,
+ // or deleted. Normally this would be the place to update the
+ // bookmark to point to the new location, but Qt clients may not
+ // be prepared for QFiles changing their file-names under their
+ // feet so we treat it as a missing file.
+ qCDebug(lcSecEngine) << "Bookmark for" << cacheKey << "was stale";
+ locker.unlock();
+ QWriteLocker writeLocker(&m_bookmarkLock);
+ [m_bookmarks removeObjectForKey:cacheKey];
+ auto *mutableThis = const_cast<SecurityScopedFileEngineHandler*>(this);
+ mutableThis->saveBookmarks();
+ return nullptr;
+ }
+ }
+
+ qCInfo(lcSecEngine) << "Resolved security scope" << securityScopedUrl
+ << "for path" << filePath;
+ return std::make_unique<SecurityScopedFileEngine>(fileName, securityScopedUrl);
+}
+
+/*
+ Create an app-scoped bookmark, and store it in our persistent cache.
+
+ We do this so that the user can continue accessing the file even after
+ application restarts.
+
+ Storing the bookmarks to disk (inside the sandbox) is safe, as only the
+ app that created the app-scoped bookmarks can obtain access to the file
+ system resource that the URL points to. Specifically, a bookmark created
+ with security scope fails to resolve if the caller does not have the same
+ code signing identity as the caller that created the bookmark.
+*/
+void SecurityScopedFileEngineHandler::saveBookmark(NSURL *url)
+{
+ NSError *error = nullptr;
+ NSData *bookmarkData = [url bookmarkDataWithOptions:
+ #if defined(Q_OS_MACOS)
+ NSURLBookmarkCreationWithSecurityScope
+ #else
+ // iOS bookmarks are always security scoped, and we
+ // don't need or want any of the other options.
+ NSURLBookmarkCreationOptions(0)
+ #endif
+ includingResourceValuesForKeys:nil
+ relativeToURL:nil /* app-scoped bookmark */
+ error:&error];
+
+ if (bookmarkData) {
+ QWriteLocker locker(&m_bookmarkLock);
+ NSString *cacheKey = cacheKeyForUrl(url);
+ qCInfo(lcSecEngine)
+ << (m_bookmarks[cacheKey] ? "Updating" : "Registering")
+ << "bookmark for" << cacheKey;
+ m_bookmarks[cacheKey] = bookmarkData;
+ saveBookmarks();
+ } else {
+ qCWarning(lcSecEngine) << "Failed to create bookmark data for" << url << error;
+ }
+}
+
+/*
+ Saves the bookmarks cache to disk.
+
+ We do this preemptively whenever we create a bookmark, to ensure
+ the file can be accessed later on even if the app crashes.
+*/
+void SecurityScopedFileEngineHandler::saveBookmarks()
+{
+ QMacAutoReleasePool pool;
+
+ NSError *error = nullptr;
+ NSURL *bookmarksFilePath = bookmarksFile();
+ [NSFileManager.defaultManager
+ createDirectoryAtURL:[bookmarksFilePath URLByDeletingLastPathComponent]
+ withIntermediateDirectories:YES attributes:nil error:&error];
+ if (error) {
+ qCWarning(lcSecEngine) << "Failed to create bookmarks path:" << error;
+ return;
+ }
+ [m_bookmarks writeToURL:bookmarksFile() error:&error];
+ if (error) {
+ qCWarning(lcSecEngine) << "Failed to save bookmarks to"
+ << bookmarksFile() << ":" << error;
+ }
+}
+
+NSURL *SecurityScopedFileEngineHandler::bookmarksFile() const
+{
+ NSURL *appSupportDir = [[NSFileManager.defaultManager URLsForDirectory:
+ NSApplicationSupportDirectory inDomains:NSUserDomainMask] firstObject];
+ return [appSupportDir URLByAppendingPathComponent:@"SecurityScopedBookmarks.plist"];
+}
+
+NSString *SecurityScopedFileEngineHandler::cacheKeyForUrl(NSURL *url)
+{
+ return cacheKeyForPath(QString::fromNSString(url.path));
+}
+
+NSString *SecurityScopedFileEngineHandler::cacheKeyForPath(const QString &path)
+{
+ auto normalized = path.normalized(QString::NormalizationForm_D);
+ // We assume the file paths we get via file dialogs and similar
+ // are already canonical, but clean it just in case.
+ return QDir::cleanPath(normalized).toNSString();
+}
+
+QT_END_NAMESPACE
diff --git a/src/corelib/platform/darwin/qdarwinsecurityscopedfileengine_p.h b/src/corelib/platform/darwin/qdarwinsecurityscopedfileengine_p.h
new file mode 100644
index 00000000000..f6098fa977d
--- /dev/null
+++ b/src/corelib/platform/darwin/qdarwinsecurityscopedfileengine_p.h
@@ -0,0 +1,29 @@
+// Copyright (C) 2025 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
+// Qt-Security score:significant reason:default
+
+#ifndef QDARWINSECURITYSCOPEDFILEENGINE_H
+#define QDARWINSECURITYSCOPEDFILEENGINE_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is part of the QPA API and is not meant to be used
+// in applications. Usage of this API may make your code
+// source and binary incompatible with future versions of Qt.
+//
+// We mean it.
+//
+
+#include <QtCore/qurl.h>
+
+Q_FORWARD_DECLARE_OBJC_CLASS(NSURL);
+
+QT_BEGIN_NAMESPACE
+
+Q_CORE_EXPORT QUrl qt_apple_urlFromPossiblySecurityScopedURL(NSURL *url);
+
+QT_END_NAMESPACE
+
+#endif // QDARWINSECURITYSCOPEDFILEENGINE_H
diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp
index 287138bb915..4f3ecc4c6d9 100644
--- a/src/corelib/platform/wasm/qstdweb.cpp
+++ b/src/corelib/platform/wasm/qstdweb.cpp
@@ -178,12 +178,17 @@ Blob Blob::slice(uint32_t begin, uint32_t end) const
ArrayBuffer Blob::arrayBuffer_sync() const
{
emscripten::val buffer;
- uint32_t handlerIndex = qstdweb::Promise::make(m_blob, QStringLiteral("arrayBuffer"), {
- .thenFunc = [&buffer](emscripten::val arrayBuffer) {
- buffer = arrayBuffer;
+ QList<uint32_t> handlers;
+ qstdweb::Promise::make(
+ handlers,
+ m_blob,
+ QStringLiteral("arrayBuffer"),
+ {
+ .thenFunc = [&buffer](emscripten::val arrayBuffer) {
+ buffer = arrayBuffer;
}
});
- Promise::suspendExclusive(handlerIndex);
+ Promise::suspendExclusive(handlers);
return ArrayBuffer(buffer);
}
@@ -441,7 +446,7 @@ EventCallback::EventCallback(emscripten::val element, const std::string &name,
}
-uint32_t Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks)
+uint32_t Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbacks, QList<uint32_t> *handlers)
{
Q_ASSERT_X(!!callbacks.catchFunc || !!callbacks.finallyFunc || !!callbacks.thenFunc,
"Promise::adoptPromise", "must provide at least one callback function");
@@ -498,14 +503,21 @@ uint32_t Promise::adoptPromise(emscripten::val promise, PromiseCallbacks callbac
promise = promise.call<emscripten::val>("finally",
suspendResume->jsEventHandlerAt(*finallyIndex));
+ if (handlers) {
+ if (thenIndex)
+ handlers->push_back(*thenIndex);
+ if (catchIndex)
+ handlers->push_back(*catchIndex);
+ handlers->push_back(*finallyIndex);
+ }
return *finallyIndex;
}
-void Promise::suspendExclusive(uint32_t handlerIndex)
+void Promise::suspendExclusive(QList<uint32_t> handlerIndices)
{
QWasmSuspendResumeControl *suspendResume = QWasmSuspendResumeControl::get();
Q_ASSERT(suspendResume);
- suspendResume->suspendExclusive(handlerIndex);
+ suspendResume->suspendExclusive(handlerIndices);
suspendResume->sendPendingEvents();
}
@@ -657,11 +669,12 @@ void FileSystemWritableFileStreamIODevice::close()
return;
}
- uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("close"), {
+ QList<uint32_t> handlers;
+ Promise::make(handlers, m_stream.val(), QStringLiteral("close"), {
.thenFunc = [](emscripten::val) {
}
});
- Promise::suspendExclusive(handlerIndex);
+ Promise::suspendExclusive(handlers);
QIODevice::close();
}
@@ -683,14 +696,15 @@ bool FileSystemWritableFileStreamIODevice::seek(qint64 pos)
emscripten::val seekParams = emscripten::val::object();
seekParams.set("type", std::string("seek"));
seekParams.set("position", static_cast<double>(pos));
- uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("write"), {
+ QList<uint32_t> handlers;
+ Promise::make(handlers, m_stream.val(), QStringLiteral("write"), {
.thenFunc = [&success](emscripten::val) {
success = true;
},
.catchFunc = [](emscripten::val) {
}
}, seekParams);
- Promise::suspendExclusive(handlerIndex);
+ Promise::suspendExclusive(handlers);
if (!success)
return false;
@@ -708,14 +722,15 @@ qint64 FileSystemWritableFileStreamIODevice::writeData(const char *data, qint64
bool success = false;
Uint8Array array = Uint8Array::copyFrom(data, size);
- uint32_t handlerIndex = Promise::make(m_stream.val(), QStringLiteral("write"), {
+ QList<uint32_t> handlers;
+ Promise::make(handlers, m_stream.val(), QStringLiteral("write"), {
.thenFunc = [&success](emscripten::val) {
success = true;
},
.catchFunc = [](emscripten::val) {
}
}, array.val());
- Promise::suspendExclusive(handlerIndex);
+ Promise::suspendExclusive(handlers);
if (success) {
qint64 newPos = pos() + size;
@@ -770,7 +785,8 @@ bool FileSystemFileIODevice::open(QIODevice::OpenMode mode)
File file;
bool success = false;
- uint32_t handlerIndex = Promise::make(m_fileHandle.val(), QStringLiteral("getFile"), {
+ QList<uint32_t> handlers;
+ Promise::make(handlers, m_fileHandle.val(), QStringLiteral("getFile"), {
.thenFunc = [&file, &success](emscripten::val fileVal) {
file = File(fileVal);
success = true;
@@ -778,7 +794,7 @@ bool FileSystemFileIODevice::open(QIODevice::OpenMode mode)
.catchFunc = [](emscripten::val) {
}
});
- Promise::suspendExclusive(handlerIndex);
+ Promise::suspendExclusive(handlers);
if (success) {
m_blobDevice = std::make_unique<BlobIODevice>(file.slice(0, file.size()));
@@ -796,7 +812,8 @@ bool FileSystemFileIODevice::open(QIODevice::OpenMode mode)
FileSystemWritableFileStream writableStream;
bool success = false;
- uint32_t handlerIndex = Promise::make(m_fileHandle.val(), QStringLiteral("createWritable"), {
+ QList<uint32_t> handlers;
+ Promise::make(handlers, m_fileHandle.val(), QStringLiteral("createWritable"), {
.thenFunc = [&writableStream, &success](emscripten::val writable) {
writableStream = FileSystemWritableFileStream(writable);
success = true;
@@ -804,7 +821,7 @@ bool FileSystemFileIODevice::open(QIODevice::OpenMode mode)
.catchFunc = [](emscripten::val) {
}
});
- Promise::suspendExclusive(handlerIndex);
+ Promise::suspendExclusive(handlers);
if (success) {
m_writableDevice = std::make_unique<FileSystemWritableFileStreamIODevice>(writableStream);
diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h
index 9a97370448e..b14d9e4012f 100644
--- a/src/corelib/platform/wasm/qstdweb_p.h
+++ b/src/corelib/platform/wasm/qstdweb_p.h
@@ -238,7 +238,7 @@ namespace qstdweb {
};
namespace Promise {
- uint32_t Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks);
+ uint32_t Q_CORE_EXPORT adoptPromise(emscripten::val promise, PromiseCallbacks callbacks, QList<uint32_t> *handlers = nullptr);
template<typename... Args>
uint32_t make(emscripten::val target,
@@ -255,7 +255,24 @@ namespace qstdweb {
return adoptPromise(std::move(promiseObject), std::move(callbacks));
}
- void Q_CORE_EXPORT suspendExclusive(uint32_t handlerIndex);
+ template<typename... Args>
+ void make(
+ QList<uint32_t> &handlers,
+ emscripten::val target,
+ QString methodName,
+ PromiseCallbacks callbacks,
+ Args... args)
+ {
+ emscripten::val promiseObject = target.call<emscripten::val>(
+ methodName.toStdString().c_str(), std::forward<Args>(args)...);
+ if (promiseObject.isUndefined() || promiseObject["constructor"]["name"].as<std::string>() != "Promise") {
+ qFatal("This function did not return a promise");
+ }
+
+ adoptPromise(std::move(promiseObject), std::move(callbacks), &handlers);
+ }
+
+ void Q_CORE_EXPORT suspendExclusive(QList<uint32_t> handlerIndices);
void Q_CORE_EXPORT all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks);
};
diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
index 5fe92926240..a4bc7843380 100644
--- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
+++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol.cpp
@@ -196,9 +196,13 @@ void QWasmSuspendResumeControl::suspend()
qtSuspendJs();
}
-void QWasmSuspendResumeControl::suspendExclusive(uint32_t eventHandlerIndex)
+void QWasmSuspendResumeControl::suspendExclusive(QList<uint32_t> eventHandlerIndices)
{
- suspendResumeControlJs().set("exclusiveEventHandler", eventHandlerIndex);
+ m_eventFilter = [eventHandlerIndices](int handler) {
+ return eventHandlerIndices.contains(handler);
+ };
+
+ suspendResumeControlJs().set("exclusiveEventHandler", eventHandlerIndices.back());
qtSuspendJs();
}
@@ -211,37 +215,27 @@ int QWasmSuspendResumeControl::sendPendingEvents()
emscripten::val control = suspendResumeControlJs();
emscripten::val pendingEvents = control["pendingEvents"];
- if (control["exclusiveEventHandler"].as<int>() > 0)
- return sendPendingExclusiveEvent();
-
- if (pendingEvents["length"].as<int>() == 0)
- return 0;
-
int count = 0;
- while (pendingEvents["length"].as<int>() > 0) { // Make sure it is reentrant
- // Grab one event (handler and arg), and call it
- emscripten::val event = pendingEvents.call<val>("shift");
- auto it = m_eventHandlers.find(event["index"].as<int>());
- if (it != m_eventHandlers.end())
- it->second(event["arg"]);
- ++count;
+ for (int i = 0; i < pendingEvents["length"].as<int>();) {
+ if (!m_eventFilter(pendingEvents[i]["index"].as<int>())) {
+ ++i;
+ } else {
+ // Grab one event (handler and arg), and call it
+ emscripten::val event = pendingEvents[i];
+ pendingEvents.call<void>("splice", i, 1);
+
+ auto it = m_eventHandlers.find(event["index"].as<int>());
+ if (it != m_eventHandlers.end())
+ it->second(event["arg"]);
+ ++count;
+ }
}
- return count;
-}
-// Sends the pending exclusive event, and resets the "exclusive" state
-int QWasmSuspendResumeControl::sendPendingExclusiveEvent()
-{
- emscripten::val control = suspendResumeControlJs();
- int exclusiveHandlerIndex = control["exclusiveEventHandler"].as<int>();
- control.set("exclusiveEventHandler", 0);
- emscripten::val event = control["pendingEvents"].call<val>("pop");
- int eventHandlerIndex = event["index"].as<int>();
- Q_ASSERT(exclusiveHandlerIndex == eventHandlerIndex);
- auto it = m_eventHandlers.find(eventHandlerIndex);
- Q_ASSERT(it != m_eventHandlers.end());
- it->second(event["arg"]);
- return 1;
+ if (control["exclusiveEventHandler"].as<int>() > 0) {
+ control.set("exclusiveEventHandler", 0);
+ m_eventFilter = [](int) { return true;};
+ }
+ return count;
}
void qtSendPendingEvents()
diff --git a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
index b750d80314c..ff97ff3d7ea 100644
--- a/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
+++ b/src/corelib/platform/wasm/qwasmsuspendresumecontrol_p.h
@@ -38,15 +38,16 @@ public:
static emscripten::val suspendResumeControlJs();
void suspend();
- void suspendExclusive(uint32_t eventHandlerIndex);
+ // Accept events for all handlers, start to process events on last handler in list
+ void suspendExclusive(QList<uint32_t> eventHandlerIndices);
int sendPendingEvents();
- int sendPendingExclusiveEvent();
private:
friend void qtSendPendingEvents();
static QWasmSuspendResumeControl *s_suspendResumeControl;
std::map<int, std::function<void(emscripten::val)>> m_eventHandlers;
+ std::function<bool(int)> m_eventFilter = [](int) { return true; };
};
class Q_CORE_EXPORT QWasmEventHandler