Export Web/SCORM : inline les icônes en data: URI (CORS Firefox/file://)
Build and deploy / test-python (push) Successful in 6m16s
Build and deploy / test-js (push) Successful in 54s
Build and deploy / build-and-push (push) Skipped
Build and deploy / deploy (push) Skipped

Le chemin relatif ("icons/x.svg") était enfin correct, mais Firefox
bloque encore par CORS TOUT chemin de fichier pour mask-image sous
file:// — chaque ressource file:// y est une origine opaque distincte,
indépendamment du chemin (troisième rapport du même utilisateur). Seule
une data: URI (aucune requête réseau séparée) contourne le problème.
_build_icon_data_uris (build_scorm_package.py) encode chaque icône
RÉELLEMENT posée dans le jeu (élément direct ou niché dans un élément de
jeu réutilisable) en base64, ajouté au payload sous
gameData.icon_data_uris ; forgeRenderIcone (JS) le consulte pour toute
régénération après une action, avec repli sur l'ancien chemin relatif
si jamais une icône n'a pas été pré-encodée.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
william
2026-09-02 10:27:04 +02:00
co-authored by Claude Sonnet 5
parent b60fe63a54
commit 82d00b3800
4 changed files with 125 additions and 41 deletions
+23 -22
View File
@@ -50,25 +50,26 @@ def test_export_scorm_index_html_is_offline_and_self_contained(client, game):
assert "let gameData = " in html
def test_export_scorm_icon_widget_uses_a_relative_url(client, game):
"""Régression signalée par un utilisateur : render_icone.py construit
son URL en dur avec un préfixe "/static/..." (correct EN LIGNE, où il
part de la racine du serveur Flask) — jamais réécrit par le refactor
asset_url de templates/play.html (celui-ci ne touche que les <script>/
<link> du template, pas le rendered_html déjà bake dans le payload).
Cette URL, posée en style INLINE (--icon-url), est CONSOMMÉE par
`mask-image: var(--icon-url)` dans static/style.css (voir .icon-svg) —
une URL relative dans une propriété personnalisée CSS se résout par
rapport à LA FEUILLE DE STYLE où le var() est utilisé, pas où elle est
définie (piège CSS connu) : un premier correctif ("static/icons/x.svg")
redevenait donc "static/static/icons/x.svg" une fois résolu depuis
static/style.css (CORS bloqué sur ce chemin doublé — deuxième rapport
du même utilisateur) — seul "icons/x.svg" (sans "static/") est correct
une fois résolu depuis static/style.css. Voir aussi le miroir JS
(forgeRenderIcone, static/js/play/offline/render-special-widgets.js),
qui régénère ce même widget après une action, et devait être corrigé
séparément."""
def test_export_scorm_icon_widget_is_inlined_as_a_data_uri(client, game):
"""Régression signalée par un utilisateur (trois fois de suite) :
render_icone.py construit son URL en dur avec un préfixe "/static/..."
(correct EN LIGNE, où il part de la racine du serveur Flask). Cette
URL, posée en style INLINE (--icon-url), est CONSOMMÉE par `mask-image:
var(--icon-url)` dans static/style.css (voir .icon-svg) :
- un premier correctif ("static/icons/x.svg") ignorait qu'une URL
relative dans une propriété personnalisée CSS se résout par rapport
à LA FEUILLE DE STYLE où le var() est utilisé (static/style.css),
pas où elle est définie (piège CSS connu) — devenait donc
"static/static/icons/x.svg" ;
- un second correctif ("icons/x.svg", sans "static/") réglait bien CE
chemin, mais Firefox bloque encore par CORS TOUT chemin de fichier
pour mask-image sous file:// (chaque ressource file:// y est une
origine opaque distincte, indépendamment du chemin).
Seule une data: URI (aucune requête réseau séparée) contourne
entièrement le problème — voir _build_icon_data_uris
(publish/build_scorm_package.py) et son miroir JS (forgeRenderIcone,
static/js/play/offline/render-special-widgets.js, qui régénère ce
même widget après une action et devait être corrigé séparément)."""
import re
resp = client.post(f"/game/{game}/screens/new", data={"name": "Accueil"}, follow_redirects=False)
screen_id = int(re.search(r"/screens/(\d+)/edit", resp.headers["Location"]).group(1))
@@ -79,9 +80,9 @@ def test_export_scorm_icon_widget_uses_a_relative_url(client, game):
import io
with zipfile.ZipFile(io.BytesIO(resp.data)) as zf:
html = zf.read("index.html").decode("utf-8")
assert "icons/star.svg" in html
assert "static/icons/star.svg" not in html
assert "/static/icons/star.svg" not in html
assert "--icon-url:url(&#x27;data:image/svg+xml;base64," in html
assert "icons/star.svg" not in html
assert '"icon_data_uris": {"star":' in html
def test_export_scorm_databound_text_resolves_a_relation_field(client, game):