diff options
Diffstat (limited to 'src/corelib/platform')
-rw-r--r-- | src/corelib/platform/android/qandroidextras.cpp | 113 | ||||
-rw-r--r-- | src/corelib/platform/android/qandroidnativeinterface.cpp | 62 | ||||
-rw-r--r-- | src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm | 6 | ||||
-rw-r--r-- | src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm | 22 | ||||
-rw-r--r-- | src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm | 65 | ||||
-rw-r--r-- | src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h | 4 | ||||
-rw-r--r-- | src/corelib/platform/ios/PrivacyInfo.xcprivacy | 31 | ||||
-rw-r--r-- | src/corelib/platform/wasm/qstdweb.cpp | 393 | ||||
-rw-r--r-- | src/corelib/platform/wasm/qstdweb_p.h | 158 | ||||
-rw-r--r-- | src/corelib/platform/windows/qcomobject_p.h | 127 | ||||
-rw-r--r-- | src/corelib/platform/windows/qfactorycacheregistration_p.h | 2 | ||||
-rw-r--r-- | src/corelib/platform/windows/qt_winrtbase_p.h | 34 |
12 files changed, 831 insertions, 186 deletions
diff --git a/src/corelib/platform/android/qandroidextras.cpp b/src/corelib/platform/android/qandroidextras.cpp index 414374d1e0..aa0c3fd093 100644 --- a/src/corelib/platform/android/qandroidextras.cpp +++ b/src/corelib/platform/android/qandroidextras.cpp @@ -126,6 +126,8 @@ QAndroidBinder QAndroidParcelPrivate::readBinder() const \l {https://developer.android.com/reference/android/os/Parcel.html}{Android Parcel} methods. + \include qtcore.qdoc qtcoreprivate-usage + \since 6.2 */ @@ -243,6 +245,8 @@ QJniObject QAndroidParcel::handle() const \l {https://developer.android.com/reference/android/os/Binder.html}{Android Binder} methods. + \include qtcore.qdoc qtcoreprivate-usage + \since 6.2 */ @@ -383,6 +387,8 @@ QJniObject QAndroidBinder::handle() const It is useful when you perform a QtAndroidPrivate::bindService operation. + \include qtcore.qdoc qtcoreprivate-usage + \since 6.2 */ @@ -507,6 +513,8 @@ public: Create a subclass of this class to be notified of the results when using the \c QtAndroidPrivate::startActivity() and \c QtAndroidPrivate::startIntentSender() APIs. + + \include qtcore.qdoc qtcoreprivate-usage */ /*! @@ -604,6 +612,8 @@ public: \l {https://developer.android.com/reference/android/app/Service.html}{Android Service} methods. + \include qtcore.qdoc qtcoreprivate-usage + \since 6.2 */ @@ -662,6 +672,45 @@ QAndroidBinder* QAndroidService::onBind(const QAndroidIntent &/*intent*/) return nullptr; } +static jboolean onTransact(JNIEnv */*env*/, jclass /*cls*/, jlong id, jint code, jobject data, + jobject reply, jint flags) +{ + if (!id) + return false; + + return reinterpret_cast<QAndroidBinder*>(id)->onTransact( + code, QAndroidParcel(data), QAndroidParcel(reply), QAndroidBinder::CallType(flags)); +} + +static void onServiceConnected(JNIEnv */*env*/, jclass /*cls*/, jlong id, jstring name, + jobject service) +{ + if (!id) + return; + + return reinterpret_cast<QAndroidServiceConnection *>(id)->onServiceConnected( + QJniObject(name).toString(), QAndroidBinder(service)); +} + +static void onServiceDisconnected(JNIEnv */*env*/, jclass /*cls*/, jlong id, jstring name) +{ + if (!id) + return; + + return reinterpret_cast<QAndroidServiceConnection *>(id)->onServiceDisconnected( + QJniObject(name).toString()); +} + +bool QtAndroidPrivate::registerExtrasNatives(QJniEnvironment &env) +{ + static const JNINativeMethod methods[] = { + {"onTransact", "(JILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void *)onTransact}, + {"onServiceConnected", "(JLjava/lang/String;Landroid/os/IBinder;)V", (void *)onServiceConnected}, + {"onServiceDisconnected", "(JLjava/lang/String;)V", (void *)onServiceDisconnected} + }; + + return env.registerNativeMethods("org/qtproject/qt/android/extras/QtNative", methods, 3); +} /*! \class QAndroidIntent @@ -674,6 +723,8 @@ QAndroidBinder* QAndroidService::onBind(const QAndroidIntent &/*intent*/) \l {https://developer.android.com/reference/android/content/Intent.html}{Android Intent} methods. + \include qtcore.qdoc qtcoreprivate-usage + \since 6.2 */ @@ -799,6 +850,8 @@ QJniObject QAndroidIntent::handle() const \brief The QtAndroidPrivate namespace provides miscellaneous functions to aid Android development. \inheaderfile QtCore/private/qandroidextras_p.h + + \include qtcore.qdoc qtcoreprivate-usage */ /*! @@ -1069,29 +1122,29 @@ static void sendRequestPermissionsResult(JNIEnv *env, jobject *obj, jint request QFuture<QtAndroidPrivate::PermissionResult> requestPermissionsInternal(const QStringList &permissions) { - QSharedPointer<QPromise<QtAndroidPrivate::PermissionResult>> promise; - promise.reset(new QPromise<QtAndroidPrivate::PermissionResult>()); - QFuture<QtAndroidPrivate::PermissionResult> future = promise->future(); - promise->start(); - // No mechanism to request permission for SDK version below 23, because // permissions defined in the manifest are granted at install time. if (QtAndroidPrivate::androidSdkVersion() < 23) { - for (int i = 0; i < permissions.size(); ++i) - promise->addResult(QtAndroidPrivate::checkPermission(permissions.at(i)).result(), i); - promise->finish(); - return future; + QList<QtAndroidPrivate::PermissionResult> result; + result.reserve(permissions.size()); + // ### can we kick off all checkPermission()s, and whenAll() collect results? + for (const QString &permission : permissions) + result.push_back(QtAndroidPrivate::checkPermission(permission).result()); + return QtFuture::makeReadyRangeFuture(result); } - if (!QtAndroidPrivate::acquireAndroidDeadlockProtector()) { - promise->addResult(QtAndroidPrivate::Denied); - promise->finish(); - return future; - } + if (!QtAndroidPrivate::acquireAndroidDeadlockProtector()) + return QtFuture::makeReadyValueFuture(QtAndroidPrivate::Denied); + + QSharedPointer<QPromise<QtAndroidPrivate::PermissionResult>> promise; + promise.reset(new QPromise<QtAndroidPrivate::PermissionResult>()); + QFuture<QtAndroidPrivate::PermissionResult> future = promise->future(); + promise->start(); const int requestCode = nextRequestCode(); QMutexLocker locker(&g_pendingPermissionRequestsMutex); g_pendingPermissionRequests->insert(requestCode, promise); + locker.unlock(); QNativeInterface::QAndroidApplication::runOnAndroidMainThread([permissions, requestCode] { QJniEnvironment env; @@ -1130,15 +1183,9 @@ QFuture<QtAndroidPrivate::PermissionResult> QtAndroidPrivate::requestPermissions(const QStringList &permissions) { // avoid the uneccessary call and response to an empty permission string - if (permissions.size() > 0) - return requestPermissionsInternal(permissions); - - QPromise<QtAndroidPrivate::PermissionResult> promise; - QFuture<QtAndroidPrivate::PermissionResult> future = promise.future(); - promise.start(); - promise.addResult(QtAndroidPrivate::Denied); - promise.finish(); - return future; + if (permissions.isEmpty()) + return QtFuture::makeReadyValueFuture(QtAndroidPrivate::Denied); + return requestPermissionsInternal(permissions); } /*! @@ -1152,25 +1199,18 @@ QtAndroidPrivate::requestPermissions(const QStringList &permissions) QFuture<QtAndroidPrivate::PermissionResult> QtAndroidPrivate::checkPermission(const QString &permission) { - QPromise<QtAndroidPrivate::PermissionResult> promise; - QFuture<QtAndroidPrivate::PermissionResult> future = promise.future(); - promise.start(); - - if (permission.size() > 0) { + QtAndroidPrivate::PermissionResult result = Denied; + if (!permission.isEmpty()) { auto res = QJniObject::callStaticMethod<jint>(qtNativeClassName, "checkSelfPermission", "(Ljava/lang/String;)I", QJniObject::fromString(permission).object()); - promise.addResult(resultFromAndroid(res)); - } else { - promise.addResult(QtAndroidPrivate::Denied); + result = resultFromAndroid(res); } - - promise.finish(); - return future; + return QtFuture::makeReadyValueFuture(result); } -bool QtAndroidPrivate::registerPermissionNatives() +bool QtAndroidPrivate::registerPermissionNatives(QJniEnvironment &env) { if (QtAndroidPrivate::androidSdkVersion() < 23) return true; @@ -1180,8 +1220,9 @@ bool QtAndroidPrivate::registerPermissionNatives() reinterpret_cast<void *>(sendRequestPermissionsResult) }}; - QJniEnvironment env; return env.registerNativeMethods(qtNativeClassName, methods, 1); } QT_END_NAMESPACE + +#include "moc_qandroidextras_p.cpp" diff --git a/src/corelib/platform/android/qandroidnativeinterface.cpp b/src/corelib/platform/android/qandroidnativeinterface.cpp index 91b54a38e0..351893eb81 100644 --- a/src/corelib/platform/android/qandroidnativeinterface.cpp +++ b/src/corelib/platform/android/qandroidnativeinterface.cpp @@ -7,9 +7,13 @@ #include <QtCore/private/qjnihelpers_p.h> #include <QtCore/qjniobject.h> #if QT_CONFIG(future) && !defined(QT_NO_QOBJECT) -#include <QtConcurrent/QtConcurrent> +#include <QtCore/qfuture.h> +#include <QtCore/qfuturewatcher.h> #include <QtCore/qpromise.h> +#include <QtCore/qtimer.h> +#include <QtCore/qthreadpool.h> #include <deque> +#include <memory> #endif QT_BEGIN_NAMESPACE @@ -17,8 +21,12 @@ QT_BEGIN_NAMESPACE #if QT_CONFIG(future) && !defined(QT_NO_QOBJECT) static const char qtNativeClassName[] = "org/qtproject/qt/android/QtNative"; -typedef std::pair<std::function<QVariant()>, QSharedPointer<QPromise<QVariant>>> RunnablePair; -typedef std::deque<RunnablePair> PendingRunnables; +struct PendingRunnable { + std::function<QVariant()> function; + std::shared_ptr<QPromise<QVariant>> promise; +}; + +using PendingRunnables = std::deque<PendingRunnable>; Q_GLOBAL_STATIC(PendingRunnables, g_pendingRunnables); Q_CONSTINIT static QBasicMutex g_pendingRunnablesMutex; #endif @@ -38,10 +46,10 @@ Q_CONSTINIT static QBasicMutex g_pendingRunnablesMutex; QT_DEFINE_NATIVE_INTERFACE(QAndroidApplication); /*! - \fn jobject QNativeInterface::QAndroidApplication::context() + \fn QJniObject QNativeInterface::QAndroidApplication::context() - Returns the Android context as a \c jobject. The context is an \c Activity - if the main activity object is valid. Otherwise, the context is a \c Service. + Returns the Android context as a \c QJniObject. The context is an \c Activity + if the most recently started activity object is valid. Otherwise, the context is a \c Service. \since 6.2 */ @@ -60,7 +68,7 @@ QtJniTypes::Context QNativeInterface::QAndroidApplication::context() */ bool QNativeInterface::QAndroidApplication::isActivityContext() { - return QtAndroidPrivate::activity(); + return QtAndroidPrivate::activity().isValid(); } /*! @@ -86,8 +94,7 @@ int QNativeInterface::QAndroidApplication::sdkVersion() */ void QNativeInterface::QAndroidApplication::hideSplashScreen(int duration) { - QJniObject::callStaticMethod<void>("org/qtproject/qt/android/QtNative", - "hideSplashScreen", "(I)V", duration); + QtAndroidPrivate::activity().callMethod<void>("hideSplashScreen", duration); } /*! @@ -155,12 +162,12 @@ QFuture<QVariant> QNativeInterface::QAndroidApplication::runOnAndroidMainThread( const std::function<QVariant()> &runnable, const QDeadlineTimer timeout) { - QSharedPointer<QPromise<QVariant>> promise(new QPromise<QVariant>()); + auto promise = std::make_shared<QPromise<QVariant>>(); QFuture<QVariant> future = promise->future(); promise->start(); - (void) QtConcurrent::run([=, &future]() { - if (!timeout.isForever()) { + if (!timeout.isForever()) { + QThreadPool::globalInstance()->start([=]() mutable { QEventLoop loop; QTimer::singleShot(timeout.remainingTime(), &loop, [&]() { future.cancel(); @@ -176,12 +183,24 @@ QFuture<QVariant> QNativeInterface::QAndroidApplication::runOnAndroidMainThread( loop.quit(); }); watcher.setFuture(future); + + // we're going to sleep, make sure we don't block + // QThreadPool::globalInstance(): + + QThreadPool::globalInstance()->releaseThread(); + const auto sg = qScopeGuard([] { + QThreadPool::globalInstance()->reserveThread(); + }); loop.exec(); - } - }); + }); + } QMutexLocker locker(&g_pendingRunnablesMutex); - g_pendingRunnables->push_back(std::pair(runnable, promise)); +#ifdef __cpp_aggregate_paren_init + g_pendingRunnables->emplace_back(runnable, std::move(promise)); +#else + g_pendingRunnables->push_back({runnable, std::move(promise)}); +#endif locker.unlock(); QJniObject::callStaticMethod<void>(qtNativeClassName, @@ -199,24 +218,23 @@ static void runPendingCppRunnables(JNIEnv */*env*/, jobject /*obj*/) if (g_pendingRunnables->empty()) break; - std::pair pair = std::move(g_pendingRunnables->front()); + PendingRunnable r = std::move(g_pendingRunnables->front()); g_pendingRunnables->pop_front(); locker.unlock(); // run the runnable outside the sync block! - auto promise = pair.second; - if (!promise->isCanceled()) - promise->addResult(pair.first()); - promise->finish(); + if (!r.promise->isCanceled()) + r.promise->addResult(r.function()); + r.promise->finish(); } } #endif -bool QtAndroidPrivate::registerNativeInterfaceNatives() +bool QtAndroidPrivate::registerNativeInterfaceNatives(QJniEnvironment &env) { #if QT_CONFIG(future) && !defined(QT_NO_QOBJECT) const JNINativeMethod methods = {"runPendingCppRunnables", "()V", (void *)runPendingCppRunnables}; - return QJniEnvironment().registerNativeMethods(qtNativeClassName, &methods, 1); + return env.registerNativeMethods(qtNativeClassName, &methods, 1); #else return true; #endif diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm index 01fb638283..0cd375561f 100644 --- a/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_bluetooth.mm @@ -31,7 +31,8 @@ - (Qt::PermissionStatus)currentStatus { - switch (CBCentralManager.authorization) { + auto status = CBCentralManager.authorization; + switch (status) { case CBManagerAuthorizationNotDetermined: return Qt::PermissionStatus::Undetermined; case CBManagerAuthorizationRestricted: @@ -41,7 +42,8 @@ return Qt::PermissionStatus::Granted; } - Q_UNREACHABLE(); + qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self; + return Qt::PermissionStatus::Denied; } - (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm index 79a85ef3d2..a3eddd6d8f 100644 --- a/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_calendar.mm @@ -5,8 +5,6 @@ #include <EventKit/EventKit.h> -QT_DEFINE_PERMISSION_STATUS_CONVERTER(EKAuthorizationStatus); - @interface QDarwinCalendarPermissionHandler () @property (nonatomic, retain) EKEventStore *eventStore; @end @@ -20,8 +18,24 @@ QT_DEFINE_PERMISSION_STATUS_CONVERTER(EKAuthorizationStatus); - (Qt::PermissionStatus)currentStatus { - const auto status = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent]; - return nativeStatusToQtStatus(status); + auto status = [EKEventStore authorizationStatusForEntityType:EKEntityTypeEvent]; + switch (status) { + case EKAuthorizationStatusNotDetermined: + return Qt::PermissionStatus::Undetermined; + case EKAuthorizationStatusRestricted: + case EKAuthorizationStatusDenied: + return Qt::PermissionStatus::Denied; + case EKAuthorizationStatusAuthorized: + return Qt::PermissionStatus::Granted; +#if QT_MACOS_IOS_PLATFORM_SDK_EQUAL_OR_ABOVE(140000, 170000) + case EKAuthorizationStatusWriteOnly: + // FIXME: Add WriteOnly AccessMode + return Qt::PermissionStatus::Denied; +#endif + } + + qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self; + return Qt::PermissionStatus::Denied; } - (QStringList)usageDescriptionsFor:(QPermission)permission diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm b/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm index 5414e97cbf..1d32c0fcac 100644 --- a/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_location.mm @@ -55,7 +55,7 @@ struct PermissionRequest - (Qt::PermissionStatus)checkPermission:(QPermission)permission { - const auto locationPermission = permission.data<QLocationPermission>(); + const auto locationPermission = *permission.value<QLocationPermission>(); auto status = [self authorizationStatus:locationPermission]; if (status != Qt::PermissionStatus::Granted) @@ -66,24 +66,39 @@ struct PermissionRequest - (Qt::PermissionStatus)authorizationStatus:(QLocationPermission)permission { - switch ([self authorizationStatus]) { + NSString *bundleIdentifier = NSBundle.mainBundle.bundleIdentifier; + if (!bundleIdentifier || !bundleIdentifier.length) { + qCWarning(lcLocationPermission) << "Missing bundle identifier" + << "in Info.plist. Can not use location permissions."; + return Qt::PermissionStatus::Denied; + } + +#if defined(Q_OS_VISIONOS) + if (permission.availability() == QLocationPermission::Always) + return Qt::PermissionStatus::Denied; +#endif + + auto status = [self authorizationStatus]; + switch (status) { case kCLAuthorizationStatusRestricted: case kCLAuthorizationStatusDenied: return Qt::PermissionStatus::Denied; case kCLAuthorizationStatusNotDetermined: return Qt::PermissionStatus::Undetermined; +#if !defined(Q_OS_VISIONOS) case kCLAuthorizationStatusAuthorizedAlways: return Qt::PermissionStatus::Granted; -#ifdef Q_OS_IOS +#endif +#if defined(Q_OS_IOS) || defined(Q_OS_VISIONOS) case kCLAuthorizationStatusAuthorizedWhenInUse: - if (permission.availability() == QLocationPermission::WhenInUse) - return Qt::PermissionStatus::Granted; - else - return Qt::PermissionStatus::Denied; // FIXME: Verify + if (permission.availability() == QLocationPermission::Always) + return Qt::PermissionStatus::Denied; + return Qt::PermissionStatus::Granted; #endif } - Q_UNREACHABLE(); + qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" << self; + return Qt::PermissionStatus::Denied; } - (CLAuthorizationStatus)authorizationStatus @@ -93,7 +108,7 @@ struct PermissionRequest return self.manager.authorizationStatus; } - return CLLocationManager.authorizationStatus; + return QT_IGNORE_DEPRECATIONS(CLLocationManager.authorizationStatus); } - (Qt::PermissionStatus)accuracyAuthorization:(QLocationPermission)permission @@ -109,19 +124,24 @@ struct PermissionRequest if (permission.accuracy() == QLocationPermission::Approximate) return Qt::PermissionStatus::Granted; else - return Qt::PermissionStatus::Denied; // FIXME: Verify + return Qt::PermissionStatus::Denied; } - Q_UNREACHABLE(); + qCWarning(lcPermissions) << "Unknown accuracy status" << status << "detected in" << self; + return Qt::PermissionStatus::Denied; } - (QStringList)usageDescriptionsFor:(QPermission)permission { +#if defined(Q_OS_MACOS) + return { "NSLocationUsageDescription" }; +#else // iOS 11 and above QStringList usageDescriptions = { "NSLocationWhenInUseUsageDescription" }; - const auto locationPermission = permission.data<QLocationPermission>(); + const auto locationPermission = *permission.value<QLocationPermission>(); if (locationPermission.availability() == QLocationPermission::Always) - usageDescriptions << "NSLocationAlwaysUsageDescription"; + usageDescriptions << "NSLocationAlwaysAndWhenInUseUsageDescription"; return usageDescriptions; +#endif } - (void)requestPermission:(QPermission)permission withCallback:(PermissionCallback)callback @@ -150,7 +170,7 @@ struct PermissionRequest self.manager.delegate = self; } - const auto locationPermission = permission.data<QLocationPermission>(); + const auto locationPermission = *permission.value<QLocationPermission>(); switch (locationPermission.availability()) { case QLocationPermission::WhenInUse: // The documentation specifies that requestWhenInUseAuthorization can @@ -164,19 +184,32 @@ struct PermissionRequest } break; case QLocationPermission::Always: +#if defined(Q_OS_VISIONOS) + [self deliverResult]; // Not supported +#else // The documentation specifies that requestAlwaysAuthorization can only // be called when the current authorization status is either undetermined, // or authorized when in use. switch ([self authorizationStatus]) { case kCLAuthorizationStatusNotDetermined: + [self.manager requestAlwaysAuthorization]; + break; #ifdef Q_OS_IOS case kCLAuthorizationStatusAuthorizedWhenInUse: + // Unfortunately when asking for AlwaysAuthorization when in + // the WhenInUse state, to "upgrade" the permission, the iOS + // location system will not give us a callback if the user + // denies the request, leaving us hanging without a way to + // respond to the permission request. + qCWarning(lcLocationPermission) << "QLocationPermission::WhenInUse" + << "can not be upgraded to QLocationPermission::Always on iOS." + << "Please request QLocationPermission::Always directly."; + Q_FALLTHROUGH(); #endif - [self.manager requestAlwaysAuthorization]; - break; default: [self deliverResult]; } +#endif break; } } diff --git a/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h b/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h index 9e4bbe92de..649af06507 100644 --- a/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h +++ b/src/corelib/platform/darwin/qdarwinpermissionplugin_p_p.h @@ -83,7 +83,9 @@ Qt::PermissionStatus nativeStatusToQtStatus(NativeStatus status) case Converter::Undetermined: return Qt::PermissionStatus::Undetermined; } - Q_UNREACHABLE(); + qCWarning(lcPermissions) << "Unknown permission status" << status << "detected in" + << QT_STRINGIFY(QT_DARWIN_PERMISSION_PLUGIN); + return Qt::PermissionStatus::Denied; } } // namespace diff --git a/src/corelib/platform/ios/PrivacyInfo.xcprivacy b/src/corelib/platform/ios/PrivacyInfo.xcprivacy new file mode 100644 index 0000000000..5f84a229a5 --- /dev/null +++ b/src/corelib/platform/ios/PrivacyInfo.xcprivacy @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>NSPrivacyTracking</key> + <false/> + <key>NSPrivacyCollectedDataTypes</key> + <array/> + <key>NSPrivacyTrackingDomains</key> + <array/> + <key>NSPrivacyAccessedAPITypes</key> + <array> + <dict> + <key>NSPrivacyAccessedAPIType</key> + <string>NSPrivacyAccessedAPICategoryFileTimestamp</string> + <key>NSPrivacyAccessedAPITypeReasons</key> + <array> + <string>0A2A.1</string> <!-- QFileInfo --> + </array> + </dict> + <dict> + <key>NSPrivacyAccessedAPIType</key> + <string>NSPrivacyAccessedAPICategoryDiskSpace</string> + <key>NSPrivacyAccessedAPITypeReasons</key> + <array> + <string>85F4.1</string> <!-- QStorageInfo --> + </array> + </dict> + </array> +</dict> +</plist> diff --git a/src/corelib/platform/wasm/qstdweb.cpp b/src/corelib/platform/wasm/qstdweb.cpp index 69ed8fe34f..75e76a6806 100644 --- a/src/corelib/platform/wasm/qstdweb.cpp +++ b/src/corelib/platform/wasm/qstdweb.cpp @@ -5,9 +5,13 @@ #include <QtCore/qcoreapplication.h> #include <QtCore/qfile.h> +#include <QtCore/qmimedata.h> + #include <emscripten/bind.h> #include <emscripten/emscripten.h> #include <emscripten/html5.h> +#include <emscripten/threading.h> + #include <cstdint> #include <iostream> @@ -15,6 +19,8 @@ QT_BEGIN_NAMESPACE +using namespace Qt::Literals::StringLiterals; + namespace qstdweb { static void usePotentialyUnusedSymbols() @@ -28,12 +34,59 @@ static void usePotentialyUnusedSymbols() // called at runtime. volatile bool doIt = false; if (doIt) - emscripten_set_wheel_callback(NULL, 0, 0, NULL); + emscripten_set_wheel_callback(EMSCRIPTEN_EVENT_TARGET_WINDOW, 0, 0, NULL); } Q_CONSTRUCTOR_FUNCTION(usePotentialyUnusedSymbols) typedef double uint53_t; // see Number.MAX_SAFE_INTEGER namespace { +// Reads file in chunks in order to avoid holding two copies in memory at the same time +struct ChunkedFileReader +{ +public: + static void read(File file, char *buffer, uint32_t offset, uint32_t end, + std::function<void()> onCompleted) + { + (new ChunkedFileReader(end, std::move(onCompleted), std::move(file))) + ->readNextChunk(offset, buffer); + } + +private: + ChunkedFileReader(uint32_t end, std::function<void()> onCompleted, File file) + : end(end), onCompleted(std::move(onCompleted)), file(std::move(file)) + { + } + + void readNextChunk(uint32_t chunkBegin, char *chunkBuffer) + { + // Copy current chunk from JS memory to Wasm memory + qstdweb::ArrayBuffer result = fileReader.result(); + qstdweb::Uint8Array(result).copyTo(chunkBuffer); + + // Read next chunk if not at buffer end + const uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end); + if (nextChunkBegin == end) { + onCompleted(); + delete this; + return; + } + char *nextChunkBuffer = chunkBuffer + result.byteLength(); + fileReader.onLoad([this, nextChunkBegin, nextChunkBuffer](emscripten::val) { + readNextChunk(nextChunkBegin, nextChunkBuffer); + }); + const uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end); + qstdweb::Blob blob = file.slice(nextChunkBegin, nextChunkEnd); + fileReader.readAsArrayBuffer(blob); + } + + static constexpr uint32_t chunkSize = 256 * 1024; + + qstdweb::FileReader fileReader; + uint32_t end; + std::function<void()> onCompleted; + File file; +}; + enum class CallbackType { Then, Catch, @@ -85,11 +138,12 @@ public: "catch", emscripten::val::module_property(thunkName(CallbackType::Catch, id()).data())); } - if (callbacks.finallyFunc) { - target = target.call<val>( - "finally", - emscripten::val::module_property(thunkName(CallbackType::Finally, id()).data())); - } + // Guarantee the invocation of at least one callback by always + // registering 'finally'. This is required by WebPromiseManager + // design + target = target.call<val>( + "finally", emscripten::val::module_property( + thunkName(CallbackType::Finally, id()).data())); } private: @@ -270,25 +324,21 @@ void WebPromiseManager::promiseThunkCallback(int context, CallbackType type, ems auto* promiseState = &m_promiseRegistry[context]; auto* callbacks = &promiseState->callbacks; - bool expectingOtherCallbacks; switch (type) { case CallbackType::Then: callbacks->thenFunc(result); - // At this point, if there is no finally function, we are sure that the Catch callback won't be issued. - expectingOtherCallbacks = !!callbacks->finallyFunc; break; case CallbackType::Catch: callbacks->catchFunc(result); - expectingOtherCallbacks = !!callbacks->finallyFunc; break; case CallbackType::Finally: - callbacks->finallyFunc(); - expectingOtherCallbacks = false; + // Final callback may be empty, used solely for promise unregistration + if (callbacks->finallyFunc) { + callbacks->finallyFunc(); + } + unregisterPromise(context); break; - } - - if (!expectingOtherCallbacks) - unregisterPromise(context); + } } void WebPromiseManager::registerPromise( @@ -314,13 +364,15 @@ void WebPromiseManager::adoptPromise(emscripten::val target, PromiseCallbacks ca #if defined(QT_STATIC) EM_JS(bool, jsHaveAsyncify, (), { return typeof Asyncify !== "undefined"; }); +EM_JS(bool, jsHaveJspi, (), + { return typeof Asyncify !== "undefined" && !!Asyncify.makeAsyncFunction && !!WebAssembly.Function; }); #else bool jsHaveAsyncify() { return false; } +bool jsHaveJspi() { return false; } #endif - } // namespace ArrayBuffer::ArrayBuffer(uint32_t size) @@ -342,7 +394,12 @@ uint32_t ArrayBuffer::byteLength() const return m_arrayBuffer["byteLength"].as<uint32_t>(); } -emscripten::val ArrayBuffer::val() +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 { return m_arrayBuffer; } @@ -353,24 +410,55 @@ 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 { return m_blob["size"].as<uint32_t>(); } -// Copies content from the given buffer into a Blob object -Blob Blob::copyFrom(const char *buffer, uint32_t size) +Blob Blob::copyFrom(const char *buffer, uint32_t size, std::string mimeType) { Uint8Array contentCopy = Uint8Array::copyFrom(buffer, size); emscripten::val contentArray = emscripten::val::array(); contentArray.call<void>("push", contentCopy.val()); emscripten::val type = emscripten::val::object(); - type.set("type","application/octet-stream"); + type.set("type", std::move(mimeType)); return Blob(emscripten::val::global("Blob").new_(contentArray, type)); } -emscripten::val Blob::val() +// Copies content from the given buffer into a Blob object +Blob Blob::copyFrom(const char *buffer, uint32_t size) +{ + 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, QStringLiteral("arrayBuffer"), { + .thenFunc = [&loop, &buffer](emscripten::val arrayBuffer) { + buffer = arrayBuffer; + loop.quit(); + } + }); + loop.exec(); + return ArrayBuffer(buffer); +} + +emscripten::val Blob::val() const { return m_blob; } @@ -381,6 +469,17 @@ File::File(const emscripten::val &file) } +File::~File() = default; + +File::File(const File &other) = default; + +File::File(File &&other) = default; + +File &File::operator=(const File &other) = default; + +File &File::operator=(File &&other) = default; + + Blob File::slice(uint64_t begin, uint64_t end) const { return Blob(m_file.call<emscripten::val>("slice", uint53_t(begin), uint53_t(end))); @@ -403,47 +502,17 @@ std::string Blob::type() const // Streams partial file content into the given buffer asynchronously. The completed // callback is called on completion. -void File::stream(uint32_t offset, uint32_t length, char *buffer, const std::function<void ()> &completed) const +void File::stream(uint32_t offset, uint32_t length, char *buffer, + std::function<void()> completed) const { - // Read file in chunks in order to avoid holding two copies in memory at the same time - const uint32_t chunkSize = 256 * 1024; - const uint32_t end = offset + length; - // assert end < file.size - auto fileReader = std::make_shared<qstdweb::FileReader>(); - - // "this" is valid now, but may not be by the time the chunkCompleted callback - // below is made. Make a copy of the file handle. - const File fileHandle = *this; - auto chunkCompleted = std::make_shared<std::function<void (uint32_t, char *buffer)>>(); - *chunkCompleted = [=](uint32_t chunkBegin, char *chunkBuffer) mutable { - - // Copy current chunk from JS memory to Wasm memory - qstdweb::ArrayBuffer result = fileReader->result(); - qstdweb::Uint8Array(result).copyTo(chunkBuffer); - - // Read next chunk if not at buffer end - uint32_t nextChunkBegin = std::min(chunkBegin + result.byteLength(), end); - uint32_t nextChunkEnd = std::min(nextChunkBegin + chunkSize, end); - if (nextChunkBegin == end) { - completed(); - chunkCompleted.reset(); - return; - } - char *nextChunkBuffer = chunkBuffer + result.byteLength(); - fileReader->onLoad([=](emscripten::val) { (*chunkCompleted)(nextChunkBegin, nextChunkBuffer); }); - qstdweb::Blob blob = fileHandle.slice(nextChunkBegin, nextChunkEnd); - fileReader->readAsArrayBuffer(blob); - }; - - // Read first chunk. First iteration is a dummy iteration with no available data. - (*chunkCompleted)(offset, buffer); + ChunkedFileReader::read(*this, buffer, offset, offset + length, std::move(completed)); } // Streams file content into the given buffer asynchronously. The completed // callback is called on completion. -void File::stream(char *buffer, const std::function<void ()> &completed) const +void File::stream(char *buffer, std::function<void()> completed) const { - stream(0, size(), buffer, completed); + stream(0, size(), buffer, std::move(completed)); } std::string File::type() const @@ -451,11 +520,27 @@ std::string File::type() const return m_file["type"].as<std::string>(); } -emscripten::val File::val() +emscripten::val File::val() const { return m_file; } +FileUrlRegistration::FileUrlRegistration(File file) +{ + m_path = QString::fromStdString(emscripten::val::global("window")["URL"].call<std::string>( + "createObjectURL", file.file())); +} + +FileUrlRegistration::~FileUrlRegistration() +{ + emscripten::val::global("window")["URL"].call<void>("revokeObjectURL", + emscripten::val(m_path.toStdString())); +} + +FileUrlRegistration::FileUrlRegistration(FileUrlRegistration &&other) = default; + +FileUrlRegistration &FileUrlRegistration::operator=(FileUrlRegistration &&other) = default; + FileList::FileList(const emscripten::val &fileList) :m_fileList(fileList) { @@ -494,20 +579,23 @@ void FileReader::readAsArrayBuffer(const Blob &blob) const void FileReader::onLoad(const std::function<void(emscripten::val)> &onLoad) { - m_onLoad.reset(new EventCallback(m_fileReader, "load", onLoad)); + m_onLoad.reset(); + m_onLoad = std::make_unique<EventCallback>(m_fileReader, "load", onLoad); } void FileReader::onError(const std::function<void(emscripten::val)> &onError) { - m_onError.reset(new EventCallback(m_fileReader, "error", onError)); + m_onError.reset(); + m_onError = std::make_unique<EventCallback>(m_fileReader, "error", onError); } void FileReader::onAbort(const std::function<void(emscripten::val)> &onAbort) { - m_onAbort.reset(new EventCallback(m_fileReader, "abort", onAbort)); + m_onAbort.reset(); + m_onAbort = std::make_unique<EventCallback>(m_fileReader, "abort", onAbort); } -emscripten::val FileReader::val() +emscripten::val FileReader::val() const { return m_fileReader; } @@ -567,6 +655,13 @@ void Uint8Array::set(const Uint8Array &source) 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 void Uint8Array::copyTo(char *destination) const { @@ -605,7 +700,7 @@ Uint8Array Uint8Array::copyFrom(const QByteArray &buffer) return copyFrom(buffer.constData(), buffer.size()); } -emscripten::val Uint8Array::val() +emscripten::val Uint8Array::val() const { return m_uint8Array; } @@ -620,45 +715,45 @@ emscripten::val Uint8Array::constructor_() return emscripten::val::global("Uint8Array"); } +class EventListener { +public: + EventListener(uintptr_t handler) + :m_handler(handler) + { + + } + + // Special function - addEventListender() allows adding an object with a + // handleEvent() function which eceives the event. + void handleEvent(emscripten::val event) { + auto handlerPtr = reinterpret_cast<std::function<void(emscripten::val)> *>(m_handler); + (*handlerPtr)(event); + } + + uintptr_t m_handler; +}; + // Registers a callback function for a named event on the given element. The event // name must be the name as returned by the Event.type property: e.g. "load", "error". EventCallback::~EventCallback() { - // Clean up if this instance's callback is still installed on the element - if (m_element[contextPropertyName(m_eventName).c_str()].as<intptr_t>() == intptr_t(this)) { - m_element.set(contextPropertyName(m_eventName).c_str(), emscripten::val::undefined()); - m_element.set((std::string("on") + m_eventName).c_str(), emscripten::val::undefined()); - } + m_element.call<void>("removeEventListener", m_eventName, m_eventListener); } -EventCallback::EventCallback(emscripten::val element, const std::string &name, const std::function<void(emscripten::val)> &fn) +EventCallback::EventCallback(emscripten::val element, const std::string &name, const std::function<void(emscripten::val)> &handler) :m_element(element) ,m_eventName(name) - ,m_fn(fn) -{ - m_element.set(contextPropertyName(m_eventName).c_str(), emscripten::val(intptr_t(this))); - m_element.set((std::string("on") + m_eventName).c_str(), emscripten::val::module_property("qtStdWebEventCallbackActivate")); -} - -void EventCallback::activate(emscripten::val event) + ,m_handler(std::make_unique<std::function<void(emscripten::val)>>(handler)) { - emscripten::val target = event["target"]; - std::string eventName = event["type"].as<std::string>(); - emscripten::val property = target[contextPropertyName(eventName)]; - // This might happen when the event bubbles - if (property.isUndefined()) - return; - EventCallback *that = reinterpret_cast<EventCallback *>(property.as<intptr_t>()); - that->m_fn(event); -} - -std::string EventCallback::contextPropertyName(const std::string &eventName) -{ - return std::string("data-qtEventCallbackContext") + eventName; + uintptr_t handlerUint = reinterpret_cast<uintptr_t>(m_handler.get()); // FIXME: pass pointer directly instead + m_eventListener = emscripten::val::module_property("QtEventListener").new_(handlerUint); + m_element.call<void>("addEventListener", m_eventName, m_eventListener); } EMSCRIPTEN_BINDINGS(qtStdwebCalback) { - emscripten::function("qtStdWebEventCallbackActivate", &EventCallback::activate); + emscripten::class_<EventListener>("QtEventListener") + .constructor<uintptr_t>() + .function("handleEvent", &EventListener::handleEvent); } namespace Promise { @@ -715,12 +810,128 @@ namespace Promise { } } +// Asyncify and thread blocking: Normally, it's not possible to block the main +// thread, except if asyncify is enabled. Secondary threads can always block. +// +// haveAsyncify(): returns true if the main thread can block on QEventLoop::exec(), +// if either asyncify 1 or 2 (JSPI) is available. +// +// haveJspi(): returns true if asyncify 2 (JSPI) is available. +// +// canBlockCallingThread(): returns true if the calling thread can block on +// QEventLoop::exec(), using either asyncify or as a seconarday thread. +bool haveJspi() +{ + static bool HaveJspi = jsHaveJspi(); + return HaveJspi; +} + bool haveAsyncify() { - static bool HaveAsyncify = jsHaveAsyncify(); + static bool HaveAsyncify = jsHaveAsyncify() || haveJspi(); return HaveAsyncify; } +bool canBlockCallingThread() +{ + return haveAsyncify() || !emscripten_is_main_runtime_thread(); +} + +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 QT_END_NAMESPACE diff --git a/src/corelib/platform/wasm/qstdweb_p.h b/src/corelib/platform/wasm/qstdweb_p.h index b3fc8a95f7..a3b5bd5b6b 100644 --- a/src/corelib/platform/wasm/qstdweb_p.h +++ b/src/corelib/platform/wasm/qstdweb_p.h @@ -16,15 +16,28 @@ // #include <private/qglobal_p.h> +#include <QtCore/qglobal.h> +#include "QtCore/qhash.h" +#include "QtCore/qiodevice.h" + #include <emscripten/val.h> + #include <cstdint> #include <functional> -#include "initializer_list" -#include <QtCore/qglobal.h> -#include "QtCore/qhash.h" +#include <initializer_list> +#include <memory> +#include <string> +#include <utility> + +#if QT_CONFIG(thread) +#include <emscripten/proxying.h> +#include <emscripten/threading.h> +#endif // #if QT_CONFIG(thread) QT_BEGIN_NAMESPACE +class QMimeData; + namespace qstdweb { extern const char makeContextfulPromiseFunctionName[]; @@ -46,7 +59,8 @@ namespace qstdweb { explicit ArrayBuffer(uint32_t size); explicit ArrayBuffer(const emscripten::val &arrayBuffer); uint32_t byteLength() const; - emscripten::val val(); + ArrayBuffer slice(uint32_t begin, uint32_t end) const; + emscripten::val val() const; private: friend class Uint8Array; @@ -56,9 +70,13 @@ namespace qstdweb { class Q_CORE_EXPORT Blob { public: explicit Blob(const emscripten::val &blob); + static Blob fromArrayBuffer(const ArrayBuffer &arrayBuffer); 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); - emscripten::val val(); + Blob slice(uint32_t begin, uint32_t end) const; + ArrayBuffer arrayBuffer_sync() const; + emscripten::val val() const; std::string type() const; private: @@ -70,19 +88,49 @@ namespace qstdweb { public: File() = default; explicit File(const emscripten::val &file); + ~File(); + + File(const File &other); + File(File &&other); + File &operator=(const File &other); + File &operator=(File &&other); Blob slice(uint64_t begin, uint64_t end) const; std::string name() const; uint64_t size() const; std::string type() const; - void stream(uint32_t offset, uint32_t length, char *buffer, const std::function<void ()> &completed) const; - void stream(char *buffer, const std::function<void ()> &completed) const; - emscripten::val val(); + void stream(uint32_t offset, uint32_t length, char *buffer, + std::function<void()> completed) const; + void stream(char *buffer, std::function<void()> completed) const; + emscripten::val val() const; + void fileUrlRegistration() const; + const QString &fileUrlPath() const { return m_urlPath; } + emscripten::val file() const { return m_file; } private: emscripten::val m_file = emscripten::val::undefined(); + QString m_urlPath; + }; + + class Q_CORE_EXPORT FileUrlRegistration + { + public: + explicit FileUrlRegistration(File file); + ~FileUrlRegistration(); + + FileUrlRegistration(const FileUrlRegistration &other) = delete; + FileUrlRegistration(FileUrlRegistration &&other); + FileUrlRegistration &operator=(const FileUrlRegistration &other) = delete; + FileUrlRegistration &operator=(FileUrlRegistration &&other); + + const QString &path() const { return m_path; } + + private: + QString m_path; }; + using FileUrlRegistrations = std::vector<std::unique_ptr<FileUrlRegistration>>; + class Q_CORE_EXPORT FileList { public: FileList() = default; @@ -105,7 +153,7 @@ namespace qstdweb { void onLoad(const std::function<void(emscripten::val)> &onLoad); void onError(const std::function<void(emscripten::val)> &onError); void onAbort(const std::function<void(emscripten::val)> &onAbort); - emscripten::val val(); + emscripten::val val() const; private: emscripten::val m_fileReader = emscripten::val::global("FileReader").new_(); @@ -126,6 +174,7 @@ namespace qstdweb { ArrayBuffer buffer() const; uint32_t length() const; void set(const Uint8Array &source); + Uint8Array subarray(uint32_t begin, uint32_t end); void copyTo(char *destination) const; QByteArray copyToQByteArray() const; @@ -133,7 +182,7 @@ namespace qstdweb { static void copy(char *destination, const Uint8Array &source); static Uint8Array copyFrom(const char *buffer, uint32_t size); static Uint8Array copyFrom(const QByteArray &buffer); - emscripten::val val(); + emscripten::val val() const; private: static emscripten::val heap_(); @@ -150,13 +199,12 @@ namespace qstdweb { EventCallback& operator=(EventCallback const&) = delete; EventCallback(emscripten::val element, const std::string &name, const std::function<void(emscripten::val)> &fn); - static void activate(emscripten::val event); private: - static std::string contextPropertyName(const std::string &eventName); emscripten::val m_element = emscripten::val::undefined(); std::string m_eventName; - std::function<void(emscripten::val)> m_fn; + std::unique_ptr<std::function<void(emscripten::val)>> m_handler; + emscripten::val m_eventListener = emscripten::val::undefined(); }; struct PromiseCallbacks @@ -187,6 +235,46 @@ namespace qstdweb { void Q_CORE_EXPORT all(std::vector<emscripten::val> promises, PromiseCallbacks callbacks); }; + template<class F> + decltype(auto) bindForever(F 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() { static emscripten::val savedWindow = emscripten::val::global("window"); @@ -194,6 +282,50 @@ namespace qstdweb { } bool haveAsyncify(); + bool Q_CORE_EXPORT haveJspi(); + bool canBlockCallingThread(); + + struct CancellationFlag + { + }; + +#if QT_CONFIG(thread) + template<class T> + T proxyCall(std::function<T()> task, emscripten::ProxyingQueue *queue) + { + T result; + queue->proxySync(emscripten_main_runtime_thread_id(), + [task, result = &result]() { *result = task(); }); + return result; + } + + template<> + inline void proxyCall<void>(std::function<void()> task, emscripten::ProxyingQueue *queue) + { + queue->proxySync(emscripten_main_runtime_thread_id(), task); + } + + template<class T> + T runTaskOnMainThread(std::function<T()> task, emscripten::ProxyingQueue *queue) + { + return emscripten_is_main_runtime_thread() ? task() : proxyCall<T>(std::move(task), queue); + } + + template<class T> + T runTaskOnMainThread(std::function<T()> task) + { + emscripten::ProxyingQueue singleUseQueue; + return runTaskOnMainThread<T>(task, &singleUseQueue); + } + +#else + template<class T> + T runTaskOnMainThread(std::function<T()> task) + { + return task(); + } +#endif // QT_CONFIG(thread) + } QT_END_NAMESPACE diff --git a/src/corelib/platform/windows/qcomobject_p.h b/src/corelib/platform/windows/qcomobject_p.h new file mode 100644 index 0000000000..8f27a18ff6 --- /dev/null +++ b/src/corelib/platform/windows/qcomobject_p.h @@ -0,0 +1,127 @@ +// Copyright (C) 2023 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 + +#ifndef QCOMOBJECT_P_H +#define QCOMOBJECT_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/private/qglobal_p.h> + +#if defined(Q_OS_WIN) || defined(Q_QDOC) + +# include <QtCore/qt_windows.h> + +QT_BEGIN_NAMESPACE + +namespace QtPrivate { + +template <typename... TInterfaces> +struct QComObjectTraits +{ + static constexpr bool isGuidOf(REFIID riid) noexcept + { + return ((riid == __uuidof(TInterfaces)) || ...); + } +}; + +} // namespace QtPrivate + +// NOTE: In order to be able to query the intermediate interface, i.e. the one you do not specify in +// QComObject interface list (TFirstInterface, TInterfaces) but that is a base for any of them +// (except IUnknown) you need to provide an explicit specialization of function +// QComObjectTraits<...>::isGuidOf for that type. For example, if you want to inherit interface +// IMFSampleGrabberSinkCallback which inherits IMFClockStateSink and you want to be able to query +// the latter one you need to provide this explicit specialization: +// +// class SinkCallback : public QComObject<IMFSampleGrabberSinkCallback> +// { +// ... +// }; +// +// namespace QtPrivate { +// +// template <> +// struct QComObjectTraits<IMFSampleGrabberSinkCallback> +// { +// static constexpr bool isGuidOf(REFIID riid) noexcept +// { +// return QComObjectTraits<IMFSampleGrabberSinkCallback, IMFClockStateSink>::isGuidOf(riid); +// } +// }; +// +// } + +template <typename TFirstInterface, typename... TAdditionalInterfaces> +class QComObject : public TFirstInterface, public TAdditionalInterfaces... +{ +public: + STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject) override + { + if (!ppvObject) + return E_POINTER; + + if (riid == __uuidof(IUnknown)) { + *ppvObject = static_cast<IUnknown *>(static_cast<TFirstInterface *>(this)); + AddRef(); + + return S_OK; + } + + return tryQueryInterface<TFirstInterface, TAdditionalInterfaces...>(riid, ppvObject); + } + + STDMETHODIMP_(ULONG) AddRef() override { return ++m_referenceCount; } + + STDMETHODIMP_(ULONG) Release() override + { + const LONG referenceCount = --m_referenceCount; + if (referenceCount == 0) + delete this; + + return referenceCount; + } + +protected: + QComObject() = default; + + // Destructor is not public. Caller should call Release. + // Derived class should make its destructor private to force this behavior. + virtual ~QComObject() = default; + +private: + template <typename TInterface, typename... TRest> + HRESULT tryQueryInterface(REFIID riid, void **ppvObject) + { + if (QtPrivate::QComObjectTraits<TInterface>::isGuidOf(riid)) { + *ppvObject = static_cast<TInterface *>(this); + AddRef(); + + return S_OK; + } + + if constexpr (sizeof...(TRest) > 0) + return tryQueryInterface<TRest...>(riid, ppvObject); + + *ppvObject = nullptr; + + return E_NOINTERFACE; + } + + std::atomic<LONG> m_referenceCount = 1; +}; + +QT_END_NAMESPACE + +#endif // Q_OS_WIN + +#endif // QCOMOBJECT_P_H diff --git a/src/corelib/platform/windows/qfactorycacheregistration_p.h b/src/corelib/platform/windows/qfactorycacheregistration_p.h index 6a80ce63fa..d0b19b995b 100644 --- a/src/corelib/platform/windows/qfactorycacheregistration_p.h +++ b/src/corelib/platform/windows/qfactorycacheregistration_p.h @@ -23,7 +23,7 @@ #ifdef QT_USE_FACTORY_CACHE_REGISTRATION -#include <winrt/base.h> +#include "qt_winrtbase_p.h" QT_BEGIN_NAMESPACE diff --git a/src/corelib/platform/windows/qt_winrtbase_p.h b/src/corelib/platform/windows/qt_winrtbase_p.h new file mode 100644 index 0000000000..fb7366f93d --- /dev/null +++ b/src/corelib/platform/windows/qt_winrtbase_p.h @@ -0,0 +1,34 @@ +// 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 + +#ifndef QT_WINRTBASE_P_H +#define QT_WINRTBASE_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qglobal.h> + +#if QT_CONFIG(cpp_winrt) +# include <winrt/base.h> +# include <QtCore/private/qfactorycacheregistration_p.h> +// Workaround for Windows SDK bug. +// See https://github.com/microsoft/Windows.UI.Composition-Win32-Samples/issues/47 +namespace winrt::impl +{ + template <typename Async> + auto wait_for(Async const& async, Windows::Foundation::TimeSpan const& timeout); +} +// See https://learn.microsoft.com/en-us/windows/uwp/cpp-and-winrt-apis/faq#how-do-i-resolve-ambiguities-with-getcurrenttime-and-or-try- +// for more workarounds. +#endif // QT_CONFIG(cpp/winrt) + +#endif // QT_WINRTBASE_P_H |