Sur cette page

Qt pour WebAssembly

Qt pour WebAssembly vous permet d'exécuter des applications Qt sur le Web.

WebAssembly (abrégé Wasm) est un format d'instructions binaires destiné à être exécuté dans une machine virtuelle, par exemple dans un navigateur web.

Avec Qt for WebAssembly, vous pouvez distribuer votre application sous forme d'application web s'exécutant dans le bac à sable d'un navigateur. Cette approche convient aux applications web distribuées qui ne nécessitent pas un accès complet aux capacités de l'appareil hôte.

Remarque : Qt for WebAssembly est une plateforme prise en charge, mais certains modules ne le sont pas encore ou sont en phase de préversion technique (Tech Preview). Voir Modules Qt pris en charge.

Premiers pas avec Qt for WebAssembly

La compilation d'applications Qt pour WebAssembly est similaire à celle de Qt pour d'autres plateformes. Vous devez installer un SDK (Emscripten), installer Qt (ou compiler Qt à partir du code source), puis compiler l'application. Il existe toutefois quelques différences : par exemple, Qt pour WebAssembly prend en charge moins de modules et moins de fonctionnalités que les autres versions de Qt.

Installation d'Emscripten

Emscripten est une chaîne d'outils permettant de compiler vers WebAssembly. Elle vous permet d'exécuter Qt sur le Web à une vitesse proche de celle d'une application native, sans avoir recours à des plug-ins de navigateur.

Chaque version mineure de Qt cible une version spécifique d'Emscripten, qui reste inchangée dans les versions correctives. Les paquets binaires de Qt sont compilés à l'aide de la version cible d'Emscripten. Les applications doivent utiliser la même version, car Emscripten ne garantit pas la compatibilité ABI entre les versions.

La version d'Emscripten prise en charge pour la version 6.12.0 est Emscripten 5.0.5.

Reportez-vous à la documentation d'Emscripten pour plus d'informations sur l'installation du SDK Emscripten.

Utilisez la commande « emsdk » pour installer des versions spécifiques d'Emscripten. Par exemple, pour l'installer pour la version 6.12.0, saisissez :

  • ./emsdk install 5.0.5
  • ./emsdk activate 5.0.5

Une fois l'installation terminée, le compilateur Emscripten devrait figurer dans votre chemin d'accès. Vérifiez-le à l'aide de la commande suivante :

em++ --version

Sous Windows, Emscripten est ajouté à votre chemin d'accès après l'installation. Sous macOS ou Linux, vous devez l'ajouter à votre chemin d'accès, comme suit :

source /path/to/emsdk/emsdk_env.sh

Vérifiez cela à l'aide de la commande suivante :

em++ --version

Vous pouvez compiler Qt à partir du code source si vous avez besoin de plus de flexibilité pour choisir la version d’Emscripten. Dans ce cas, les versions mentionnées ci-dessus constituent les versions minimales requises. Les versions plus récentes devraient fonctionner, mais elles peuvent entraîner des changements de comportement qui nécessiteront d’apporter des modifications à Qt.

Installation de Qt

Téléchargez Qt depuis la section « Téléchargements » de votre compte Qt. Nous proposons des versions pour Linux, macOS et Windows en tant que plateformes de développement.

Les versions binaires sont conçues pour fonctionner sur le plus grand nombre possible de navigateurs et sont disponibles en versions mono-thread et multi-thread. Les fonctionnalités non standard telles que Wasm SIMD et Wasm exceptions ne sont pas prises en charge par les versions binaires.

Compilation de Qt à partir du code source

La compilation à partir du code source vous permet de définir des options de configuration de Qt telles que la prise en charge des threads, le niveau OpenGL ES ou la prise en charge SIMD. Téléchargez les sources de Qt depuis la section « Téléchargements » de votre compte Qt.

Configurez Qt en tant que compilation croisée pour la plateforme wasm-emscripten. Cela définit les options de configuration « -static », « -no-feature-thread » et « -no-make examples ». Vous pouvez activer la prise en charge des threads à l'aide de l'option de configuration « -feature-thread ». La compilation de bibliothèques partagées n'est pas prise en charge.

Vous avez besoin d’une version hôte de Qt de la même version. Vous devez également définir le chemin d’accès à la version hôte dans la variable CMake ` QT_HOST_PATH ` ou à l’aide de l’argument de configuration ` -qt-host-path `.

./configure -qt-host-path /path/to/Qt -platform wasm-emscripten -prefix $PWD/qtbase

Remarque : `configure` utilise toujours le générateur et l’outil de compilation Ninja si un exécutable ` ninja ` est disponible. Ninja est multiplateforme, riche en fonctionnalités, performant et recommandé sur toutes les plateformes. L’utilisation d’autres générateurs peut fonctionner, mais n’est pas officiellement prise en charge.

Sous Windows, assurez-vous que Mingw-w64 figure dans votre variable d’environnement ` PATH ` et exécutez `configure` avec les options suivantes :

configure -qt-host-path C:\Path\to\Qt -no-warnings-are-errors -platform wasm-emscripten -prefix %CD%\qtbase

Compilez ensuite les modules requis :

cmake --build . -t qtbase -t qtdeclarative [-t another_module]

Compilation d’applications depuis la ligne de commande

Qt pour WebAssembly prend en charge la compilation d'applications à l'aide de CMake avec ninja ou make.

$ /path/to/qt-wasm/qtbase/bin/qt-cmake .
$ cmake --build .

Remarque : lorsque vous utilisez la version standard de CMake (par opposition à qt-cmake sous Linux ou qt-cmake.bat sous Windows), n’oubliez pas de spécifier un fichier de chaîne d’outils avec l’option « -DCMAKE_TOOLCHAIN_FILE », comme pour toute autre compilation multiplateforme. Pour plus de détails, consultez la page « Premiers pas avec CMake ».

La compilation de l’application génère plusieurs fichiers de sortie, notamment un fichier .wasm contenant le code de l’application et de Qt (lié statiquement), ainsi qu’un fichier .html pouvant être ouvert dans un navigateur pour exécuter l’application.

Remarque : Emscripten produit des fichiers .wasm relativement volumineux au niveau de débogage « -g ». Envisagez d'utiliser l'option « -g2 » pour les builds de débogage.

Exécution des applications

L'exécution de l'application nécessite un serveur web. Les fichiers générés lors de la compilation sont tous du contenu statique ; n'importe quel serveur web fera donc l'affaire. Certains cas d'utilisation peuvent nécessiter une configuration particulière du serveur, comme la mise à disposition de certificats HTTPS ou la définition d'en-têtes HTTP requis pour activer la prise en charge du multithreading.

Emrun

Emscripten fournit l'utilitaire emrun pour tester le fonctionnement des applications. Emrun démarre un serveur web, lance un navigateur, et capture et redirige également les sorties stdout/stderr (qui sont normalement envoyées vers la console JavaScript).

/path/to/emscripten/emrun --browser=firefox appname.html

http.server en Python

Une autre option consiste à démarrer un serveur web de développement, puis à lancer le navigateur web séparément. L'une des options les plus simples est http.server de Python :

python -m http.server

Notez qu’il s’agit uniquement d’un serveur web simple qui ne prend pas en charge le `SharedArrayBuffer` requis pour le multithreading, car les en-têtes COOP (Cross-Origin-Opener-Policy) et COEP (Cross-Origin-Embedder-Policy) mentionnés ci-dessous ne sont pas envoyés.

qtwasmserver

Qt fournit un serveur web de développement qui utilise mkcert pour générer des certificats HTTPS. Cela permet de tester les fonctionnalités web nécessitant un contexte sécurisé. Notez que la diffusion via http://localhost est également considérée comme sécurisée, sans nécessiter de certificat.

Le serveur web définit également les en-têtes COOP et COEP sur des valeurs qui activent la prise en charge de SharedArrayBuffer et du multithreading.

Le script qtwasmserver démarre un serveur qui se connecte par défaut à localhost. Vous pouvez ajouter des adresses supplémentaires à l’aide de l’argument de ligne de commande -a, ou utiliser --all pour vous connecter à toutes les adresses disponibles.

Le script qtwasmserver se trouve dans les sources à l'emplacement

qtbase/util/wasm/qtwasmserver/qtwasmserver.py

ou peut être installé à l'aide de pip :

pip install qtwasmserver

Pour plus d’informations sur qtwasmserver, consultez la page qtwasmserver

Utilisation de qtwasmserver :

python /path/to/qtbase/util/wasm/qtwasmserver/qtwasmserver.py --all

Compilation d'applications à l'aide de Qt Creator

Qt Creator: Créer des applications pour le Web.

Déploiement d'applications sur le Web

La création d'une application génère plusieurs fichiers (remplacez « app » par le nom de l'application dans le tableau ci-dessous).

Fichier généréBrève description
app.htmlConteneur HTML
qtloader.jsAPI JavaScript pour le chargement d'applications Qt
app.jsEnvironnement d'exécution JavaScript généré par Emscripten
app.wasmFichier binaire de l'application

Vous pouvez déployer app.html tel quel ou le remplacer par un fichier HTML personnalisé. Il est également possible d'effectuer de légères modifications, comme remplacer l'image de l'écran de démarrage (le logo Qt) par le logo de l'application. Dans les deux cas, qtloader.js fournit une API JavaScript permettant de charger l'application.

Compressez le fichier Wasm à l'aide de gzip ou brotli avant le déploiement, car ces outils offrent un meilleur taux de compression que les autres. Consultez la section « Réduire la taille des binaires » pour plus d'informations.

L’activation de certaines fonctionnalités, telles que le multithreading et le SIMD, génère des binaires .wasm incompatibles avec les navigateurs qui ne prennent pas en charge la fonctionnalité activée. Il est possible de contourner cette limitation en créant plusieurs fichiers .wasm, puis en utilisant la détection des fonctionnalités JavaScript pour sélectionner le bon fichier, mais notez que Qt ne fournit aucune fonctionnalité permettant de le faire.

Utilisation de qtloader

Qt fournit une API JavaScript permettant de télécharger, de compiler et d’instancier des applications Qt pour WebAssembly. Cette API de chargement encapsule les fonctionnalités de chargement fournies par Emscripten et offre des fonctionnalités supplémentaires utiles pour les applications basées sur Qt. Elle est implémentée dans le fichier qtloader.js. Une copie de ce fichier est enregistrée dans le répertoire de compilation au moment de la compilation.

Voici un exemple d'utilisation typique :

const app_container_element = ...;
const instance = await qtLoad({
    qt: {
        containerElements: [ app_container_element ],
        onLoaded: () => { /* handle application load completed */  },
        onExit: () => {  /* handle application exit */ },
    }
});

Le code appelle la fonction de chargement `qtLoad()` avec un objet de configuration. Cet objet de configuration peut contenir n'importe quelle option de configuration emscripten, ainsi qu'un objet de configuration spécial « qt ». L'objet de configuration « qt » prend en charge les propriétés suivantes :

PropriétéBrève description
containerElementsTableau d'éléments conteneurs HTML. L'application les considère comme des QScreens.
onLoadedRappel déclenché lorsque le chargement de l'application est terminé.
onExitFonction de rappel déclenchée lorsque l'application se ferme.

Le tableau containerElements constitue l'interface principale entre Qt et la page Web ; les éléments HTML de ce tableau (généralement des éléments <DIV>) spécifient l'emplacement du contenu de l'application sur la page Web.

L’application considère chaque élément conteneur comme une instance de ` QScreen ` et peut placer des fenêtres d’application sur ces instances d’écran comme d’habitude. Les fenêtres dont l’état « Qt::WindowFullScreen » est activé occupent toute la surface de l’écran, tandis que les fenêtres non « plein écran » affichent des bordures.

La fonction `qtLoad()` renvoie une promesse qui, une fois évaluée, fournit une instance `Emscripten`. Cette instance permet d’accéder aux fonctions exportées par `Embind`. Qt exporte plusieurs de ces fonctions, qui constituent l’API de l’instance.

Utilisation de l’API d’instance Qt

Qt fournit plusieurs fonctions d'instance. Actuellement, celles-ci permettent d'ajouter et de supprimer des éléments de conteneur lors de l'exécution.

PropriétéBrève description
qtAddContainerElementAjoute un élément au conteneur. L'ajout d'un élément crée un nouvel objet ` QScreen`.
qtRemoveContainerElementSupprime un élément de conteneur et l'écran correspondant.
qtSetContainerElementsDéfinit tous les éléments de conteneur
qtResizeContainerElementFaire en sorte que Qt prenne en compte les modifications apportées à la taille de l'élément conteneur.

Portage vers le qtloader de Qt 6.6

Qt 6.6 inclut un nouveau qtloader dont l’implémentation a été simplifiée et dont le champ d’application a été réduit. Cela implique des modifications de l’API qui peuvent nécessiter le portage du code JavaScript de l’application. Qt fournit une API de compatibilité pour faciliter la transition. Selon le cas d’utilisation, plusieurs solutions sont possibles :

  • Si vous utilisez directement le fichier ` app.html ` généré, celui-ci sera également mis à jour lors de la compilation. Aucune action n'est requise.
  • Si vous utilisez les fonctionnalités de base de qtloader, vous pouvez utiliser l’API de compatibilité incluse dans Qt 6.6 à titre de mesure temporaire. Cette API sera supprimée dans une prochaine version ; vous devriez prévoir de mettre à jour votre application pour utiliser le nouveau qtloader. L’étape 1 de portage ci-dessous est nécessaire.
  • Si vous utilisez des fonctionnalités avancées (telles que l’ajout d’éléments conteneurs lors de l’exécution), le portage vers le nouveau chargeur ou l’API d’instance est nécessaire. Les étapes de portage 1 et 2 ci-dessous sont requises.

Étapes de portage

  1. Intégrez le fichier « app.js » (environnement d’exécution JavaScript généré par Emscripten) à partir du fichier HTML de chargement.
    <script src="app.js"></script>

    Avant Qt 6.6, qtloader chargeait et évaluait ce fichier JavaScript. Ce n'est plus le cas, et le fichier doit désormais être inclus à l'aide d'une balise <script>.

  2. Effectuez la migration vers la nouvelle API JavaScript et d'instance.

Voir les sections de la documentation ci-dessus.

Navigateurs pris en charge

Ordinateurs de bureau

Qt pour WebAssembly est développé et testé sur les navigateurs suivants :

  • Chrome
  • Firefox
  • Safari
  • Edge

Qt devrait fonctionner si le navigateur prend en charge WebAssembly. Les navigateurs qui prennent en charge WebAssembly prennent souvent en charge WebGL, bien que certains navigateurs mettent sur liste noire les GPU plus anciens ou non pris en charge.Le fichier s/qtloader.js fournit des API permettant de vérifier si WebGL est disponible.

Qt n'utilise pas directement les fonctionnalités du système d'exploitation et cela ne fait aucune différence que, par exemple, Firefox s'exécute sous Windows ou sous macOS. Qt utilise toutefois certaines adaptations au système d'exploitation, par exemple pour la gestion des touches Ctrl/Cmd sous macOS.

Mobile

Les applications Qt pour WebAssembly fonctionnent sur les navigateurs mobiles tels que Safari mobile et Chrome pour Android.

Modules Qt pris en charge

Qt pour WebAssembly prend en charge un sous-ensemble des modules et fonctionnalités Qt. Les modules testés sont répertoriés ci-dessous ; les autres modules peuvent fonctionner ou non.

Les modules suivants sont en préversion technologique. Leurs fonctionnalités peuvent être limitées ou faire l'objet de modifications importantes dans les prochaines versions.

Dans tous les cas, la prise en charge des modules peut ne pas être complète et il peut exister des limitations supplémentaires, dues soit au bac à sable du navigateur, soit au caractère incomplet du portage de la plateforme Qt. Consultez la section « Développer avec Qt pour WebAssembly » pour plus d'informations.

Développement avec Qt pour WebAssembly

Compilation avec CMake

Si une configuration spécifique à Emscripten est nécessaire dans CMake, le code suivant peut être utilisé :

if(EMSCRIPTEN)
    # WebAssembly specific code
else()
    # other platforms
endif()

Ce code permet de prendre en charge les configurations spécifiques à Emscripten tout en garantissant la compatibilité avec d'autres plateformes.

OpenGL et WebGL

Qt pour WebAssembly prend en charge le rendu accéléré par le matériel à l’aide d’ https://developer.mozilla.org/en-US/docs/Web/API/WebGL_APIu WebGL.

WebGL est étroitement conforme à OpenGL ES, avec la correspondance de versions suivante :

OpenGLWebGL
OpenGL ES 2.0WebGL 1
OpenGL ES 3.0WebGL 2

Qt utilise la version la plus récente de WebGL disponible. Il s'agit généralement de WebGL 2 sur les navigateurs actuels, mais cela peut être WebGL 1 si le matériel impose des limitations. Nous recommandons de cibler les appareils prenant en charge WebGL 2 avec Qt pour WebAssembly.

Les différences entre OpenGL pour le Web et OpenGL pour les ordinateurs de bureau sont documentées dans la section Différences entre WebGL et OpenGL. Il existe d'autres différences entre WebGL 1.0 et WebGL 2.0, documentées dans la spécification WebGL 2.0.

Un sous-ensemble d'ES2 (et d'ES3) compatible avec WebGL est utilisé par défaut. Si vous avez besoin d'utiliser ` glDrawArrays ` et ` glDrawElements ` sans tampons liés, vous pouvez activer la prise en charge complète d'ES2 en ajoutant

target_link_options(<your target> PRIVATE -s FULL_ES2=1)

et/ou l’émulation ES3 complète en ajoutant

target_link_options(<your target> PRIVATE -s FULL_ES3=1)

à la ligne « CMakeLists.txt » de votre projet.

Pour plus d’informations sur la prise en charge d’OpenGL par Emscripten, rendez-vous sur https://emscripten.org/docs/porting/multimedia_and_graphics/OpenGL-support.html

Limitations du contexte OpenGL

WebGL ne prend pas en charge plusieurs contextes par surface. Cela a des implications pour les applications qui utilisent ` QOpenGLContext `, soit directement, soit indirectement via d'autres classes telles que ` QOpenGLWidget`.

Chaque instance de ` QOpenGLContext ` ne doit être utilisée qu'avec une seule surface. En pratique, le contexte est associé à la surface lors du premier appel à `makeCurrent()`. L'appel ultérieur de `makeCurrent()` sur une autre surface, ou à partir d'un autre ` QOpenGLContext ` avec la même surface, n'est pas pris en charge.

Le partage de contexte OpenGL n’est pas pris en charge. L’appel à ` QOpenGLContext::setShareContext()` n’a aucun effet, et ` QOpenGLContext::shareContext()` renvoie toujours `nullptr`.

La destruction d’une surface (par exemple, une ` QWindow`) entraîne la perte du contexte associé. L’application doit gérer cette situation en recréant le contexte.

QOpenGLWidget et les autres classes qui utilisent le partage de contexte en interne ne sont pas prises en charge.

Multithreading

Qt pour WebAssembly prend en charge le multithreading grâce à la prise en charge des Pthreads par Emscripten, chaque thread étant pris en charge par un web worker. Pour activer le multithreading, installez le composant « WebAssembly (multi-threaded) » disponible sur Qt Maintenance Tool, ou compilez Qt à partir du code source en passant l'option « -feature-thread » à la commande configure.

Le code de multithreading existant peut généralement être réutilisé, mais peut nécessiter des modifications pour contourner certaines spécificités de l'implémentation de pthread. Certaines fonctionnalités d'Emscripten et de Qt ne sont pas prises en charge, notamment la fonctionnalité de proxy de thread et la boucle de rendu multithread de Qt Quick.

Sachez qu’il est particulièrement important de ne pas bloquer le thread principal dans Qt pour WebAssembly, car celui-ci peut être amené à traiter des requêtes provenant de threads secondaires. Par exemple, toutes les minuteries de Qt sont planifiées sur le thread principal et ne se déclencheront pas si celui-ci est bloqué. Autre exemple : la création d’un nouveau Web Worker (pour un thread) ne peut s’effectuer qu’à partir du thread principal.

Emscripten propose quelques solutions pour pallier ce problème. Les attentes de courte durée, telles que l’acquisition d’un verrou mutex, sont gérées par une attente active (busy-waiting) et le traitement d’événements pendant l’attente du verrou. Les attentes plus longues sur le thread principal doivent être évitées. En particulier, la pratique courante consistant à appeler ` QThread::wait()` ou `pthread_join()` pour attendre un thread secondaire ne fonctionnera pas, à moins que l’application ne puisse garantir que le thread (et le web worker) a déjà été lancé et qu’il sera en mesure de s’achever sans l’aide du thread principal au moment où l’appel à `wait()` ou `join()` est effectué.

La fonctionnalité multithread nécessite la prise en charge par le navigateur de l’API SharedArrayBuffer. (Normalement, Emscripten stocke le tas dans un objet ArrayBuffer. Pour le multithreading, le tas doit être partagé avec les Web Workers et un SharedArrayBuffer est nécessaire.) Cette API est généralement disponible dans tous les navigateurs modernes, mais peut être désactivée si certaines exigences de sécurité ne sont pas respectées. Les binaires WebAssembly pour lesquels la prise en charge des threads est activée ne pourront alors pas s’exécuter, même si le binaire ne lance pas réellement de thread.

L’activation de SharedArrayBuffer nécessite un contexte de navigation sécurisé (où la page est servie via https:// ou http://localhost) et que la page soit en mode isolé inter-origines. Ce dernier point peut être obtenu en définissant les en-têtes dits COOP et COEP sur le serveur web :

  • Cross-Origin-Opener-Policy: same-origin
  • Cross-Origin-Embedder-Policy: require-corp

SIMD

Emscripten prend en charge le SIMD WebAssembly, qui fournit des types et des opérations SIMD 128 bits pour WebAssembly.

Compilez Qt à partir du code source et configurez-le avec l’option -feature-wasm-simd128 pour l’activer ; cela transmettra l’option -msimd128 lors de la compilation et de la liaison. Notez que Qt ne contient pas encore de chemins de code optimisés pour wasm-simd à ce stade ; toutefois, l'activation de wasm-simd permettra la vectorisation automatique par le compilateur, qui pourra alors utiliser les instructions SIMD.

Vous pouvez cibler directement le SIMD de WebAssembly en utilisant soit les extensions vectorielles SIMD de GCC/Clang, soit les fonctions intrinsèques WASM SIMD128. Pour plus d'informations, consultez la documentation SIMD d'Emscripten .

De plus, Emscripten prend en charge l’émulation/la traduction des instructions SSE x86 en instructions SIMD Wasm. Qt n’utilise pas cette émulation, car l’utilisation d’instructions SIMD SSE qui n’ont pas d’équivalent SIMD Wasm natif peut entraîner une baisse des performances.

Notez que les binaires activant le SIMD sont incompatibles avec les navigateurs qui ne prennent pas en charge le SIMD WebAssembly, même si les chemins de code SIMD ne sont pas appelés lors de l'exécution. La prise en charge du SIMD peut devoir être activée dans les paramètres avancés des navigateurs, tels que « about:config » ou « chrome:flags ».

Réseau

Qt offre une prise en charge limitée des fonctionnalités réseau. En général, les protocoles réseau déjà utilisés sur le Web peuvent également être utilisés depuis Qt, tandis que d'autres ne sont pas directement disponibles en raison du bac à sable Web.

Les protocoles suivants sont pris en charge :

  • QNetworkAccessManager les requêtes HTTP vers le serveur d'origine de la page Web ou vers un serveur prenant en charge le CORS. Cela inclut XMLHttpRequest depuis QML.
  • QWebSocket Connexions vers n'importe quel hôte. Notez que les pages Web servies via le protocole sécurisé https n'autorisent les connexions WebSockets que via le protocole sécurisé wss.
  • Sockets TCP POSIX émulés via WebSockets, à l’aide des fonctionnalités fournies par Emscripten. Notez que cela nécessite l’exécution d’un serveur de transfert chargé de la traduction des sockets.

Tous les autres protocoles réseau ne sont pas pris en charge.

Remarque : la fonction « QWebSocketServer » n’est pas prise en charge en raison des limitations des navigateurs. Les navigateurs restreignent les fonctionnalités des sockets côté serveur afin de garantir la sécurité au sein du bac à sable Web. Par conséquent, toute fonctionnalité reposant sur la fonction « QWebSocketServer » pour accepter les connexions réseau entrantes ne peut pas être utilisée dans l’environnement Web.

Remarque : Qt MQTT les modules ` QtRemoteObjects ` peuvent être utilisés avec ` QtWebSockets ` comme protocoles de transport. Ils ne sont pas officiellement pris en charge et peuvent fonctionner ou non, ou présenter des fonctionnalités manquantes. Consultez QMqttClient::connectToHostWebSocket et l'exemple d'application WebSockets de QtRemoteObjects.

Partage de ressources entre origines (CORS) et politique (CORP)

Les applications WebAssembly qui utilisent le réseau peuvent nécessiter que le serveur définisse les en-têtes de réponse « Cross-Origin Resource Sharing » (CORS) et « Cross-Origin Resource Policy » (CORP). Ceux-ci restreignent les requêtes HTTP provenant de domaines différents, ce qui présente un risque de sécurité.

À l'aide de QHttpServer, voici un exemple de configuration de ces en-têtes :

auto headers = response.headers();
headers.append("Access-Control-Allow-Origin", "*");
headers.append("Access-Control-Allow-Origin", "localhost");
headers.append("Access-Control-Allow-Methods", "POST, GET, OPTIONS");

headers.append("Cross-Origin-Opener-Policy", "same-origin");
headers.append("Cross-Origin-Embedder-Policy", "require-corp");
headers.append("Cross-Origin-Resource-Policy", "cross-origin");

D'autres serveurs connus disposent de leurs propres méthodes pour configurer l'envoi de ces en-têtes.

L'utilisation du caractère générique « * » pour l'en-tête `Access-Control-Allow-Origin` entraînera une erreur si elle est associée à l'en-tête `Access-Control-Allow-Credentials`.

Le script utilitaire fourni, qtwasmserver.py, définira ces en-têtes si l’option ` --cross-origin-isolation ` est utilisée.

Accès aux fichiers locaux

L’accès au système de fichiers est isolé dans un bac à sable sur le Web, ce qui a des implications sur la manière dont l’application gère les fichiers. La plateforme Web fournit des API permettant d'accéder au système de fichiers local sous le contrôle de l'utilisateur, ainsi que des API permettant d'accéder au stockage persistant. Emscripten et Qt encapsulent ces fonctionnalités et fournissent des API plus faciles à utiliser depuis des applications C++ et basées sur Qt.

La plateforme Web offre des fonctionnalités permettant d'accéder aux fichiers locaux et au stockage persistant :

  • <input type="file"> pour afficher une boîte de dialogue native d’ouverture de fichier dans laquelle l’utilisateur peut sélectionner un fichier.
  • IndexedDB fournit un stockage local persistant (non accessible en dehors du navigateur)

Emscripten propose plusieurs systèmes de fichiers dotés d'une API de type POSIX. Parmi ceux-ci, on trouve :

  • le système de fichiers éphémère MEMFS, qui stocke les fichiers en mémoire
  • le système de fichiers persistant IDBFS, qui stocke les fichiers à l'aide d'IndexedDB

Emscripten monte un système de fichiers MEMFS temporaire sur « / » au démarrage de l’application. Cela signifie que la fonction ` QFile ` peut être utilisée et que, par défaut, les fichiers seront lus et écrits en mémoire. Ce système de fichiers n’est pas conservé lors du rafraîchissement du navigateur.

Comme les pages Web n’ont pas d’accès direct au système de fichiers local, Qt fournit une API « QFileDialog » destinée à être utilisée avec Qt for WebAssembly.

Accès au presse-papiers

Qt prend en charge la copie et le collage de texte, d'URL, de types de fichiers connus et d'images via le presse-papiers du système, avec quelques différences dues au bac à sable Web. Il ne prend pas en charge les données binaires arbitraires de type application/octet-stream. En général, l'accès au presse-papiers nécessite l'autorisation de l'utilisateur, qui peut être obtenue en gérant un événement d'entrée (par exemple CTRL+c) ou en utilisant l'API du presse-papiers.

Polices

Le module Qt WASM contient 3 polices intégrées : « Bitstream Vera Sans » (police de secours), « DejaVu Sans » et « DejaVu Sans Mono ».

Ces polices offrent un jeu de caractères limité. Qt propose plusieurs options pour ajouter des polices supplémentaires :

L'une d'entre elles consiste à utiliser ` FontLoader ` dans Qml, qui permet de récupérer une police soit via une URL, soit à l'aide du système de ressources Qt (de la même manière que fonctionnent les applications de bureau classiques).

L'autre façon d'utiliser une police consiste à l'ajouter via QFontDatabase::addApplicationFontFromData.

Accessibilité et lecteurs d’écran

Qt for WebAssembly offre une prise en charge de base des lecteurs d'écran. Les éléments d'interface utilisateur simples, tels que les boutons et les cases à cocher, fonctionnent, tandis que les éléments plus complexes, comme les tableaux ou les arborescences, peuvent ne pas être pris en charge. Les formats Qt Widgets et Qt Quick sont tous deux pris en charge.

Les configurations de lecteurs d'écran et de navigateurs suivantes ont été testées et sont reconnues comme fonctionnelles. D'autres navigateurs et lecteurs d'écran peuvent également fonctionner.

  • VoiceOver avec Safari sous macOS
  • VoiceOver avec Chrome sur macOS

La mise en œuvre de l'accessibilité fonctionne en créant des éléments HTML « fantômes » qui fournissent les informations d'accessibilité pour les éléments de l'interface utilisateur Qt. Cette fonctionnalité est désactivée par défaut. L'utilisateur final peut l'activer en sélectionnant un bouton « Activer le lecteur d'écran » à l'aide de son lecteur d'écran. Une fois activée, cette fonctionnalité ajoutera des éléments d'accessibilité à la page web.

Glisser-déposer

  • Le dépôt est pris en charge (fichiers et types MIME pris en charge par le navigateur).
  • Le glisser-déposer interne à l'application est pris en charge.
  • Le glisser-déposer hors de l'application n'est pas pris en charge.

Démarrage de l'application et boucle d'événements

Qt pour WebAssembly prend en charge l'approche standard de démarrage de Qt, dans laquelle l'application crée un objet ` QApplication ` et appelle la fonction `exec` :

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    QWindow appWindow;

    return app.exec();
}

L'appel à `exec()` ci-dessus bloque normalement et traite les événements jusqu'à l'arrêt de l'application. Malheureusement, cela n'est pas possible sur la plateforme Web, où le blocage du thread principal n'est pas autorisé. À la place, le contrôle doit être restitué à la boucle d'événements du navigateur après le traitement de chaque événement.

Qt contourne cette limitation en faisant en sorte que la fonction `exec()` renvoie le contrôle du thread principal au navigateur, tout en conservant la pile. Du point de vue du code de l’application, la fonction `exec()` est appelée et le traitement des événements se déroule comme d’habitude. Cependant, l’appel à `exec()` ne renvoie jamais de résultat, pas même à la fermeture de l’application.

Ce comportement est généralement acceptable, car le navigateur libère la mémoire de l’application au moment de sa fermeture. Cela signifie toutefois que le code de fermeture ne s’exécute pas, car l’objet application fait l’objet d’une fuite de mémoire et son destructeur ne s’exécute pas.

Vous pouvez éviter cela en réécrivant la fonction `main()` pour qu’elle soit asynchrone, ce qui est possible puisque Emscripten ne quitte pas le runtime lorsque `main()` renvoie une valeur. Le code de l’application omet alors d’effectuer l’appel à `exec()` et peut arrêter Qt proprement en supprimant la fenêtre de niveau supérieur et les objets d’application.

QApplication *g_app = nullptr;
AppWindow *g_appWindow = nullptr;

int main(int argc, char **argv)
{
    g_app = new QApplication(argc, argv);
    g_appWindow = new AppWindow();
    return 0;
}

Remarque : cela s'applique lorsque Asyncify ou JSPI n'est pas utilisé. L'appel à exec() dans main() est obligatoire lorsque vous utilisez Asyncify ou JSPI.

Asyncify et JSPI

La version par défaut de Qt pour WebAssembly ne prend pas en charge la réintégration dans la boucle d'événements, par exemple via l'appel de ` QEventLoop::exec()` ou ` QDialog::exec()`, en raison des restrictions de la plateforme Web qui empêchent le code C++ synchrone d'appeler des API JavaScript asynchrones.

Les fonctionnalités nécessitant l’utilisation d’asyncify/JSPI comprennent :

  • les QDialogs, les QMessageBoxes avec valeurs de retour.
  • Le glisser-déposer (en particulier le glissement).
  • Boucles d'événements imbriquées/secondaires exec().

Emscripten permet de contourner ces limitations grâce à la fonctionnalité Asyncify. Cette fonctionnalité est disponible en deux versions :

  • Asyncify, implémentée via une étape de post-traitement du code WebAssembly.
  • JSPI, implémentée à l'aide de la fonctionnalité d'intégration JS Promise de WebAssembly.

Consultez les sections ci-dessous pour plus de détails sur la manière d’activer chaque option et sur les compromis impliqués. En résumé, Asyncify est disponible dès aujourd’hui mais entraîne une surcharge sous forme de temps de compilation plus longs, de fichiers binaires plus volumineux et de pertes de performances en exécution. JSPI n’entraîne qu’une surcharge minime, voire nulle, mais n’est pas encore pris en charge par tous les navigateurs.

Remarque : l'appel de la fonction `exec()` de l'application dans `main()` était facultatif dans les versions antérieures de Qt pour WebAssembly, mais il est désormais obligatoire lors de l'utilisation d'Asyncify ou de JSPI.

Asyncify

Activez Asyncify en ajoutant les indicateurs « -sASYNCIFY -Os » aux options de l'éditeur de liens :

CMake :

target_link_options(<your target> PUBLIC -sASYNCIFY -Os)

qmake :

QMAKE_LFLAGS += -sASYNCIFY -Os

L'activation d'asyncify entraîne une surcharge se traduisant par une augmentation de la taille des binaires et de l'utilisation du processeur. Effectuez la compilation avec les optimisations activées afin de minimiser cette surcharge.

JSPI (intégration des promesses JS)

Contrairement à Asyncify, l'utilisation de JSPI nécessite de compiler Qt à partir du code source. Passez les options -feature-wasm-jspi et -feature-wasm-exceptions au script de configuration de Qt pour l'activer. (JSPI n'est pas compatible avec les exceptions émulées par défaut par Emscripten.)

Ensuite, activez JSPI pour votre application en ajoutant l'option -sJSPI aux options de l'éditeur de liens :

CMake :

target_link_options(<your target> PUBLIC -sJSPI)

qmake :

QMAKE_LFLAGS += -sJSPI

Débogage et profilage

Le débogage Wasm s'effectue dans la console JavaScript du navigateur. Il n'est pas possible de déboguer des applications Wasm directement dans l'Qt Creator.

Vous pouvez augmenter le niveau de verbosité pour faciliter le débogage à l’aide des arguments de l’éditeur de liens Emscripten :

  • -s LIBRARY_DEBUG=1 (affiche les appels aux bibliothèques)
  • -s SYSCALL_DEBUG=1 (affiche les appels système)
  • -s FS_LOG=1 (affiche les opérations sur le système de fichiers)
  • -s SOCKET_DEBUG (affiche les sockets et les transferts de données réseau)

CMake :

target_link_options(<your target> PRIVATE -s LIBRARY_DEBUG=1)

qmake :

QMAKE_LFLAGS_DEBUG += -s LIBRARY_DEBUG=1

Optimisation

L'optimisation de Qt pour WebAssembly utilise la chaîne d'outils Emscripten pour générer des binaires, et de nombreux paramètres peuvent avoir un impact sur les performances et la taille des binaires. Pour plus d'informations, consultez la page Emscripten : Optimisation du code.

Vous pouvez passer des options de l'éditeur de liens et du compilateur exactement comme pour des applications C++ classiques :

target_compile_options(<your target> PRIVATE -oz -flto)
target_link_options(<your target> PRIVATE -flto)
QMAKE_CXXFLAGS += -oz -flto
QMAKE_LFLAGS += -flto

Réduire la taille des fichiers binaires

Afin d’offrir une expérience utilisateur fluide, il est important de réduire le temps de téléchargement et de chargement des applications WebAssembly. La réduction de la taille des fichiers binaires des applications est l’un des aspects clés permettant d’accélérer le téléchargement. Utilisez les solutions suivantes pour réduire la taille des fichiers binaires :

  • Veillez à ne distribuer que des versions finales. Les versions de débogage contiennent des symboles de débogage et sont beaucoup plus volumineuses.
  • Activez la compression sur le serveur. Les algorithmes les plus courants, tels qu'gzip et Brotli, fonctionnent bien avec les binaires Wasm et peuvent réduire considérablement leur taille.
  • Essayez d’utiliser des options de compilation et de liaison susceptibles de générer des binaires plus petits (par exemple « -os », « -oz »). Les résultats varient en fonction de l’application concernée.
  • Désactivez les fonctionnalités inutilisées lors de la compilation de Qt pour WebAssembly à partir du code source (voir ci-dessous).
Désactivation des fonctionnalités

Par défaut, une application WebAssembly est liée statiquement aux bibliothèques Qt, ce qui permet au compilateur d’éliminer le code mort. Cependant, en raison de la nature dynamique de Qt, il n’est pas toujours possible pour le compilateur d’effectuer de telles optimisations.

Si vous compilez Qt pour WebAssembly à partir du code source, vous pouvez désactiver certaines fonctionnalités afin de réduire la taille des binaires Qt et, par conséquent, celle des binaires .wasm. Qt désactive par défaut certaines fonctionnalités pour la plateforme WebAssembly, mais vous pouvez également désactiver celles que votre application n'utilise pas. Consultez la section « Fonctionnalités désactivées » pour plus d'informations.

Vous pouvez désactiver les fonctionnalités suivantes pour réduire la taille des binaires (généralement de 10 à 15 %) :

Argument de configurationBrève description
-no-feature-cssparserAnalyseur syntaxique pour les feuilles de style en cascade.
-no-feature-datetimeeditÉdition des dates et des heures (dépend de datetimeparser).
-no-feature-datetimeparserAnalyse des chaînes de date et d'heure.
-no-feature-dockwidgetAncrage des widgets dans une « QMainWindow » ou affichage en tant que fenêtres de premier plan sur le bureau.
-no-feature-gesturesInfrastructure pour les gestes.
-no-feature-mimetypeGestion des types MIME.
-no-feature-qml-networkTransparence réseau.
-no-feature-qml-list-modelListModel Type QML.
-no-feature-qml-table-modelTableModel Type QML.
-no-feature-quick-canvasÉlément Canvas.
-no-feature-quick-pathÉléments de chemin.
-no-feature-quick-pathviewPathView Élément.
-no-feature-quick-treeviewTreeView élément.
-no-feature-style-stylesheetStyle de widget configurable via CSS.
-no-feature-tableviewImplémentation par défaut du modèle/vue d'une vue de tableau.
-no-feature-texthtmlparserAnalyseur pour le HTML.
-no-feature-textmarkdownreaderLecteur Markdown (CommonMark et GitHub).
-no-feature-textodfwriterGénérateur ODF.

Exceptions Wasm

Par défaut, Qt est compilé sans prise en charge des exceptions ; le lancement d'une exception entraîne l'arrêt du programme. Les exceptions WebAssembly peuvent être activées en compilant à partir du code source et en passant l'option -feature-wasm-exceptions à la commande « configure » de Qt. Cela transmettra l'option -fwasm-exceptions au compilateur lors de la compilation et de la liaison. Qt ne prend pas en charge l’activation de la prise en charge par Emscripten de l’ancienne implémentation des exceptions basée sur JavaScript.

Notez que l'appel à ` QApplication::exec()` n'est pas pris en charge lorsque les exceptions sont activées, en raison de détails d'implémentation internes. À la place, écrivez la fonction `main()` de manière à ce qu'elle renvoie une valeur rapidement et n'appelle pas `exec()`, comme décrit dans la section Démarrage de l'application et boucle d'événements.

Bibliothèques partagées et liaison dynamique (aperçu technologique)

Qt pour WebAssembly utilise par défaut la liaison statique, dans laquelle l'application est déployée sous la forme d'un fichier WebAssembly unique contenant les bibliothèques Qt et le code de l'application. La liaison dynamique est un mode de compilation alternatif dans lequel chaque bibliothèque et chaque plugin sont distribués individuellement.

Par exemple, une application utilisant Qt Quick peut recourir aux bibliothèques et plugins suivants :

  • <qtpath>/lib/libQt6Core.so
  • <qtpath>/lib/libQt6Gui.so
  • <qtpath>/lib/libQt6Qml.so
  • <qtpath>/lib/libQt6Quick.so
  • <qtpath>/plugins/imageformats/libqjpeg.so
  • <qtpath>/plugins/imageformats/libqjgif.so
  • <qtpath>/qml/QtQuick/Window/libquickwindowplugin.so

La prise en charge des liens dynamiques dans la version 6.12 est disponible en avant-première technologique. Cette implémentation convient au prototypage et à l'évaluation, mais n'est pas adaptée à une utilisation en production. Les limitations et restrictions actuelles sont les suivantes :

  • Le multithreading n'est pas pris en charge.
  • La fonctionnalité « Asyncify » n’est pas prise en charge.

Les applications Qt compilées avec la liaison dynamique nécessitent la présence de deux fichiers supplémentaires à côté des binaires : qt_plugins.json et qt_qml_imports.json. Ces fichiers spécifient une liste de bibliothèques partagées qui seront chargées au démarrage de l'application. Un outil d'aide est disponible pour les générer : wasmdeployqt. Pour découvrir comment utiliser cet outil, vous pouvez l'exécuter avec l'option « --help » afin d'obtenir un aperçu des options nécessaires à son fonctionnement ainsi qu'un exemple d'utilisation.

Le serveur web hébergeant l’application doit disposer des bibliothèques partagées Qt. Pour ce faire, vous pouvez copier le contenu du dossier d’installation de Qt sur le serveur web ou créer un lien symbolique dans le système de fichiers.

Démarrage rapide

La procédure de compilation et de déploiement diffère légèrement de celle des versions WASM statiques et des versions de bureau partagées. Pensez à commencer par un petit exemple avant de passer à la compilation d'une application complète.

  1. Compilez Qt à partir du code source, en passant l'option ` -shared ` au script de configuration de Qt. Utilisez l'option `-prefix` pour définir le répertoire d'installation.
  2. Compilez votre application à l'aide de Qt, comme indiqué à l'étape 1.
  3. Créez des listes de préchargement des plugins en exécutant l'outil de déploiement depuis le répertoire de compilation de votre application.
    • <qtpath>/qtbase/bin/wasmdeployqt --qt-wasm-dir=<Path to the Qt for WebAssembly directory> --qml-root-path=<Root directory for QML files> --qt-host-dir=<Path to the Qt host directory>

Déploiement des bibliothèques partagées : en détail

La version des bibliothèques partagées de Qt est déployée en deux étapes : la première rend la version de Qt et de l’application disponible au téléchargement depuis le serveur web, tandis que la seconde télécharge les plugins Qt requis et les importations Qt Quick au démarrage de l’application.

Dans un premier temps, rendez l'installation de Qt disponible au téléchargement depuis le serveur web. Selon les spécificités de la configuration du serveur web, il peut exister différentes façons d'y parvenir. En général, le chargeur Qt s'attend à trouver les bibliothèques et les plugins Qt dans un répertoire nommé « qt », relatif au fichier HTML qui charge l'application.

Si vous copiez déjà l’application sur le serveur web dans le cadre du déploiement, vous pouvez également y copier Qt. Si vous servez l’application directement depuis son répertoire de compilation — ce qui est souvent le cas pendant les phases de développement —, la création d’un lien symbolique vers Qt peut s’avérer une bonne solution.

Préparez la deuxième étape en créant des listes de préchargement pour les composants Qt tels que les plugins et les importations Qt Quick. Le préchargement garantit que tous les composants Qt requis sont disponibles au démarrage de l’application. Le chargement différé, où les composants sont téléchargés à la demande, est également possible mais n’est pas abordé ici.

Le préchargement est mis en œuvre par le chargeur JavaScript de Qt, qui télécharge les fichiers depuis le serveur web vers le système de fichiers en mémoire fourni par Emscripten. Les fichiers à télécharger sont spécifiés à l’aide de listes de téléchargement au format JSON. Qt fournit un outil permettant de générer des listes de préchargement ; voir la section « Démarrage rapide » ci-dessus.

Problèmes connus

  • Les boucles d'événements imbriquées ne sont prises en charge que lors de l'utilisation de la fonctionnalité expérimentale Asyncify ou JSPI.
  • L'impression n'est pas prise en charge.
  • QDnsLookup Les requêtes DNS et l'QSsl e ne fonctionnent pas et ne sont pas pris en charge en raison du bac à sable Web. Le navigateur gère les requêtes DNS et les certificats SSL. Le DNS sur HTTP peut être utilisé par les applications nécessitant des requêtes DNS.
  • Les QTcpSockets peuvent être utilisées, mais toutes les fonctions des sockets POSIX ne sont pas prises en charge par le proxy. Un proxy de serveur WebSockets tel que Websockify doit être utilisé.
  • Toutes les classes Q*Server ne sont pas prises en charge par la plateforme.
  • QWebSocket Les connexions ne sont prises en charge par Emscripten que sur le thread principal.
  • QWebSockets pour WebAssembly ne prend pas en charge l’envoi de trames « ping » ou « pong », car l’API disponible pour les pages web et les navigateurs n’expose pas cette fonctionnalité.
  • Pour utiliser QtWebSockets, il peut être nécessaire de définir le sous-protocole sur « mqtt » pour utiliser QtMqtt. Utilisez QWebSocketHandshakeOptions lorsque vous ouvrez QWebSocket.
  • Polices : le bac à sable Wasm n’autorise pas l’accès aux polices système. Les fichiers de polices doivent être distribués avec l’application, par exemple dans les ressources Qt ou via un téléchargement. Qt for WebAssembly intègre lui-même une de ces polices.
  • Des artefacts liés à la mémoire graphique non initialisée peuvent apparaître sur certains composants d’ Qt Quick Controls 2, tels que les cases à cocher. Ce phénomène est parfois observable sur les écrans HighDPi.
  • Les styles natifs pour Windows et macOS ne sont pas pris en charge, car la plateforme Wasm ne fournit pas cette fonctionnalité.
  • Une erreur de liaison telle que « wasm-ld : error : initial memory too small » nécessite un ajustement de la taille de la mémoire initiale. Utilisez QT_WASM_INITIAL_MEMORY pour définir la taille initiale en ko, qui doit être un multiple de 64 Ko (65 536). La valeur par défaut est de 50 Mo. Dans CMakeLists.txt : set_target_properties(<target> PROPERTIES QT_WASM_INITIAL_MEMORY "150MB")
  • La commande add_executable dans CMakeLists.txt ne génère pas le fichier <target>.html et ne copie pas qtloader.js. Utilisez plutôt qt_add_executable.

Autres sujets

Référence des options de configuration de Qt

Les options de configuration suivantes s'appliquent lors de la compilation de Qt pour WebAssembly à partir du code source.

Argument de configurationBrève description
-feature-threadWasm multithread.
-feature-wasm-simd128Activer la prise en charge SIMD de WebAssembly.
-feature-wasm-exceptionsActiver la prise en charge des exceptions WebAssembly.
-device-option QT_EMSCRIPTEN_ASYNCIFY=1Activer la prise en charge d'asyncify.
-device-option QT_EMSCRIPTEN_ASYNCIFY=2Activer la prise en charge d'asyncify (JSPI).

Qt désactive par défaut certaines fonctionnalités pour la plateforme WebAssembly, afin de réduire la taille du binaire. Vous pouvez activer explicitement une fonctionnalité lors de la configuration de Qt pour WebAssembly :

Argument de configurationBrève description
-feature-topleveldomainPermet de vérifier si un domaine est un domaine de premier niveau.

Tailles de téléchargement types

Taille prévue (taille du téléchargement) : les modules Wasm générés par le compilateur peuvent être volumineux, mais se compressent bien :

Exemplegzipbrotli
helloglwindow (QtCore + QtGui)2,8 Mo2,1 Mo
wiggly widget (QtCore + QtGui + QtWidgets)4,3 M3,2 M
SensorTag (QtCore + QtGui + QtWidgets + QtQuick + QtCharts)8,6 M6,3 M

La compression est généralement gérée côté serveur web, à l'aide de fonctionnalités de compression standard : le serveur compresse automatiquement les fichiers ou récupère leurs versions pré-compressées. Il n'est généralement pas nécessaire de prévoir un traitement particulier pour les fichiers Wasm.

Pour plus d’informations, consultez la section Réduire la taille des binaires.

Exemples

Exemples et démos d'applications Qt s'exécutant dans un navigateur Web : Qt Demos

Ressources externes

Licence

Qt pour WebAssembly est disponible sous licence commerciale auprès de The Qt Company. Il est également disponible sous licence GNU General Public License, version 3. Pour plus d'informations, consultez la page Licences Qt.

Voir également https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS et https://developer.mozilla.org/en-US/docs/Web/HTTP/Cross-Origin_Resource_Policy.

© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.