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
@@ -58,6 +58,21 @@ test('icône — repli sur "star" si _icon_slug absent', () => {
assert.ok(html.includes("--icon-url:url(&#x27;icons/star.svg&#x27;)"));
});
// Régression signalée par un utilisateur (deux fois) : un chemin de
// fichier pour --icon-url, même corrigé pour être relatif à
// static/style.css (là où mask-image:var(--icon-url) le consomme), reste
// bloqué par CORS sous Firefox+file:// (chaque ressource file:// y est une
// origine opaque distincte). gameData.icon_data_uris (pré-encodé à
// l'export, voir build_scorm_package.py::_build_icon_data_uris) contourne
// entièrement le problème : aucune requête réseau séparée pour une data: URI.
test('icône — utilise gameData.icon_data_uris quand pré-encodée (contourne le blocage CORS de Firefox sous file://)', () => {
const el = { id: 6, widget: 'icone', attributes: { _icon_slug: 'envelope' }, style: {} };
const gd = baseGameData();
gd.icon_data_uris = { envelope: 'data:image/svg+xml;base64,QUJD' };
const html = forgeRenderElementHtml(el, gd, {}, {});
assert.ok(html.includes("--icon-url:url(&#x27;data:image/svg+xml;base64,QUJD&#x27;)"));
});
test('onglets — un panneau actif, les autres masqués', () => {
const parent = { id: 7, widget: 'onglets', attributes: {}, style: {} };
const tab1 = { id: 8, name: 'Infos', widget: 'conteneur', content: '', attributes: {}, style: {}, parent_id: 7 };
@@ -73,17 +73,18 @@ function forgeRenderIcone(el, meta, gameData, childrenMap, ctx, parentFlexDirect
var existingClass = attrs.class || "";
delete attrs.class;
attrs.class = (existingClass + " icon-svg").trim();
// Chemin RELATIF, et surtout PAS préfixé "static/" : cette URL est
// posée dans --icon-url (style inline) mais 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é (static/style.css), pas
// où elle est définie (piège CSS connu) : "static/icons/x.svg" y
// redeviendrait "static/static/icons/x.svg" (404/CORS bloqué — bug
// signalé). "icons/x.svg" est correct une fois résolu depuis
// static/style.css (voir aussi _relativize_absolute_urls dans
// publish/build_scorm_package.py, même correctif côté rendu initial).
var iconUrl = "icons/" + iconSlug + ".svg";
// data: URI pré-calculée à l'export (voir gameData.icon_data_uris,
// publish/build_scorm_package.py::_build_icon_data_uris) — jamais un
// chemin de fichier, même relatif : Firefox traite chaque ressource
// file:// comme une origine opaque distincte, et mask-image y déclenche
// un fetch en mode CORS quel que soit le chemin ("Blocage d'une requête
// multiorigine... la requête CORS n'utilise pas HTTP", signalé par un
// utilisateur MALGRÉ un chemin relatif par ailleurs correct). Repli sur
// un chemin relatif si jamais l'icône n'a pas été pré-encodée (ne
// devrait pas arriver : _collect_icon_slugs scanne tous les widgets
// "icone", posés directement ou nichés dans un élément de jeu
// réutilisable) — au moins un lien exploitable plutôt qu'un vide.
var iconUrl = (gameData.icon_data_uris && gameData.icon_data_uris[iconSlug]) || ("icons/" + iconSlug + ".svg");
var style = forgeStyleString(el, parentFlexDirection, el.widget) + " --icon-url:url('" + iconUrl + "');";
return "<i " + forgeAttrString(attrs, style) + "></i>";
}