mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-04-18 03:04:07 +02:00
Dont revoke blob URLs while printing but do it after
It fixes #19988. In Firefox, when printing, the document is cloned and use an image cache which isn't available when there's a service worker.
This commit is contained in:
parent
a67b952118
commit
ea15ac31ef
@ -1784,4 +1784,113 @@ describe("PDF viewer", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("PDFPrintService", () => {
|
||||
describe("blob URL revocation (issue #19988)", () => {
|
||||
let pages;
|
||||
|
||||
beforeEach(async () => {
|
||||
pages = await loadAndWait(
|
||||
"basicapi.pdf",
|
||||
".textLayer .endOfContent",
|
||||
null,
|
||||
{
|
||||
earlySetup: () => {
|
||||
// Track blob URLs created during the print phase (between
|
||||
// beforeprint and afterprint).
|
||||
let trackPrintURLs = false;
|
||||
window._printBlobURLs = [];
|
||||
|
||||
const origCreate = URL.createObjectURL.bind(URL);
|
||||
URL.createObjectURL = blob => {
|
||||
const url = origCreate(blob);
|
||||
if (trackPrintURLs) {
|
||||
window._printBlobURLs.push(url);
|
||||
}
|
||||
return url;
|
||||
};
|
||||
|
||||
// beforeprint fires before renderPages(); start tracking here.
|
||||
window.addEventListener("beforeprint", () => {
|
||||
trackPrintURLs = true;
|
||||
});
|
||||
|
||||
// window.print() is called by performPrint() after renderPages()
|
||||
// completes and all images are loaded into #printContainer.
|
||||
window.print = () => {
|
||||
const isFirefox = navigator.userAgent.includes("Firefox");
|
||||
if (isFirefox) {
|
||||
// Firefox re-fetches blob URLs when rendering the print
|
||||
// preview (especially when a service worker is registered).
|
||||
// Verify the URLs are still accessible at this point.
|
||||
window._printImagesAccessible = Promise.all(
|
||||
window._printBlobURLs.map(url =>
|
||||
fetch(url).then(
|
||||
() => true,
|
||||
() => false
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Chrome uses the cached decoded data already in the <img>
|
||||
// elements and does not re-fetch blob URLs for printing.
|
||||
// Just verify the images rendered correctly.
|
||||
const imgs = document.querySelectorAll("#printContainer img");
|
||||
window._printImagesAccessible = Promise.resolve(
|
||||
Array.from(imgs).map(
|
||||
img => img.complete && img.naturalWidth > 0
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
},
|
||||
appSetup: app => {
|
||||
app._testPrintResolver = Promise.withResolvers();
|
||||
},
|
||||
eventBusSetup: eventBus => {
|
||||
eventBus.on(
|
||||
"afterprint",
|
||||
() => {
|
||||
// Wait for the checks initiated in window.print() before
|
||||
// resolving, so the test can assert on them.
|
||||
(window._printImagesAccessible ?? Promise.resolve([])).then(
|
||||
window.PDFViewerApplication._testPrintResolver.resolve
|
||||
);
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await closePages(pages);
|
||||
});
|
||||
|
||||
it("must keep print image blob URLs accessible until destroy() is called", async () => {
|
||||
await Promise.all(
|
||||
pages.map(async ([browserName, page]) => {
|
||||
await waitAndClick(page, "#printButton");
|
||||
|
||||
// Resolves with an array of booleans, one per print page image.
|
||||
const accessible = await awaitPromise(
|
||||
await page.evaluateHandle(() => [
|
||||
window.PDFViewerApplication._testPrintResolver.promise,
|
||||
])
|
||||
);
|
||||
|
||||
expect(accessible.length)
|
||||
.withContext(`In ${browserName}: print pages were rendered`)
|
||||
.toBeGreaterThan(0);
|
||||
expect(accessible.every(v => v))
|
||||
.withContext(
|
||||
`In ${browserName}: all print images accessible at print time`
|
||||
)
|
||||
.toBeTrue();
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -144,6 +144,12 @@ class PDFPrintService {
|
||||
this.pageStyleSheet.remove();
|
||||
this.pageStyleSheet = null;
|
||||
}
|
||||
if (this._blobURLs) {
|
||||
for (const url of this._blobURLs) {
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
this._blobURLs = null;
|
||||
}
|
||||
this.scratchCanvas.width = this.scratchCanvas.height = 0;
|
||||
this.scratchCanvas = null;
|
||||
activeService = null;
|
||||
@ -189,7 +195,13 @@ class PDFPrintService {
|
||||
this.throwIfInactive();
|
||||
const img = document.createElement("img");
|
||||
this.scratchCanvas.toBlob(blob => {
|
||||
img.src = URL.createObjectURL(blob);
|
||||
const blobURL = URL.createObjectURL(blob);
|
||||
img.src = blobURL;
|
||||
// Defer revocation until after printing completes (in destroy()) to avoid
|
||||
// broken print images in Firefox when a service worker is registered,
|
||||
// since Firefox re-fetches blob URLs when rendering the print dialog.
|
||||
// See https://github.com/mozilla/pdf.js/issues/19988
|
||||
(this._blobURLs ??= []).push(blobURL);
|
||||
});
|
||||
|
||||
const wrapper = document.createElement("div");
|
||||
@ -201,13 +213,9 @@ class PDFPrintService {
|
||||
img.onload = resolve;
|
||||
img.onerror = reject;
|
||||
|
||||
promise
|
||||
.catch(() => {
|
||||
// Avoid "Uncaught promise" messages in the console.
|
||||
})
|
||||
.then(() => {
|
||||
URL.revokeObjectURL(img.src);
|
||||
});
|
||||
promise.catch(() => {
|
||||
// Avoid "Uncaught promise" messages in the console.
|
||||
});
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user