fix: Detect Empty PPTX Preview Slides

This commit is contained in:
Danny Avila 2026-05-04 21:04:33 -04:00
parent 414c5bf46f
commit 06c3bfa3c0
2 changed files with 27 additions and 17 deletions

View file

@ -510,6 +510,8 @@ describe('Office HTML producers', () => {
expect(html).toContain("addEventListener('error'");
// The 8-second timeout safety net for silent renderer failures.
expect(html).toContain('renderer-timeout');
expect(html).toContain('renderer-empty-slide-list');
expect(html).toContain("querySelectorAll('.pptx-preview-slide-wrapper')");
/* Diagnostic surfacing: the failure reason must be exposed
* three ways so a debugging user can find it without reading
* source — manual e2e on PR #12934 ("'Preview unavailable'
@ -546,7 +548,8 @@ describe('Office HTML producers', () => {
expect(html).toContain('SENTINEL_FALLBACK');
/* Empty-render detection helper exists in the bootstrap. */
expect(html).toContain('hasRenderedContent');
expect(html).toContain('renderer-produced-empty-wrappers');
expect(html).toContain('renderer-empty-slide-list');
expect(html).toContain("querySelectorAll('.pptx-preview-slide-wrapper')");
/* The slide-list fallback CSS is inlined so the fallback
* renders with the same look as the standalone slide-list
* path (CSP locks `style-src` to inline only). */

View file

@ -1208,6 +1208,9 @@ ${PPTX_SLIDE_LIST_CSS}
}
}
function markSuccess() { settled = true; }
function renderedSlideCount() {
return container ? container.querySelectorAll('.pptx-preview-slide-wrapper').length : 0;
}
/* pptx-preview bundled deps (echarts, etc.) raise rejections deep
* inside their async pipelines that the outer Promise from
@ -1322,20 +1325,16 @@ ${PPTX_SLIDE_LIST_CSS}
}
}
/* Detect the silent-failure case: pptx-preview created the slide
* wrappers but didnt populate them with content. Common with
* pptxgenjs-generated PPTXs — pptx-previews parser handles
* Microsoft PowerPoint XML but chokes on subtle convention
* differences and produces empty 960x540 wrappers. Check if every
* wrapped slide has zero children; if so, the render failed and
* the slide-list fallback should take over. Manual e2e on PR
* #12934. */
/* Detect the silent-failure case: pptx-preview resolves without
* rendering any real slide nodes. Common with pptxgenjs-generated
* PPTXs — the parser creates an empty host wrapper, but never
* populates pptx-preview slide nodes with slide content.
* Manual e2e on PR #12934. */
function hasRenderedContent() {
var wraps = container.querySelectorAll('.lc-slide-wrap');
if (wraps.length === 0) { return false; }
for (var i = 0; i < wraps.length; i++) {
var inner = wraps[i].firstElementChild;
if (inner && inner.children.length > 0) {
var slides = container.querySelectorAll('.pptx-preview-slide-wrapper');
if (slides.length === 0) { return false; }
for (var i = 0; i < slides.length; i++) {
if (slides[i].children.length > 0 || (slides[i].textContent || '').trim().length > 0) {
return true;
}
}
@ -1345,7 +1344,7 @@ ${PPTX_SLIDE_LIST_CSS}
function finalize() {
wrapSlides();
if (!hasRenderedContent()) {
showFallback('renderer-produced-empty-wrappers (pptx-preview compatibility issue with this PPTX format — falling back to slide-list view)');
showFallback('renderer-empty-slide-list');
return;
}
container.style.visibility = 'visible';
@ -1361,14 +1360,22 @@ ${PPTX_SLIDE_LIST_CSS}
var result = previewer.preview(bytes.buffer);
if (result && typeof result.then === 'function') {
result.then(finalize).catch(function (err) { showFallback(err && err.message); });
result
.then(function (pptx) {
if (!pptx || !pptx.slides || pptx.slides.length === 0) {
showFallback('renderer-empty-slide-list');
return;
}
finalize();
})
.catch(function (err) { showFallback(err && err.message); });
}
/* Safety net: if 8 seconds in the preview promise hasnt resolved,
* accept whatever pptx-preview managed to render and apply the
* wrap+scale to it. Empty container → fallback. */
setTimeout(function () {
if (settled) { return; }
if (container && container.children.length > 0) {
if (renderedSlideCount() > 0) {
finalize();
return;
}