Avoid calling destroy on the PDF.js loading task (#21277)

GitOrigin-RevId: b315e78ff739d301583f2139109f3244abceade8
This commit is contained in:
Alf Eaton 2024-10-24 09:39:42 +01:00 committed by Copybot
parent aa0fe5aeb3
commit 5ec52e2e5b
3 changed files with 48 additions and 78 deletions

View file

@ -1,7 +1,6 @@
import { FC, useCallback } from 'react' import { FC, useCallback } from 'react'
import useIsMounted from '@/shared/hooks/use-is-mounted' import useIsMounted from '@/shared/hooks/use-is-mounted'
import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path' import { useFileTreePathContext } from '@/features/file-tree/contexts/file-tree-path'
import { debugConsole } from '@/utils/debugging'
const FileViewPdf: FC<{ const FileViewPdf: FC<{
fileId: string fileId: string
@ -21,7 +20,6 @@ const FileViewPdf: FC<{
// bail out if loading PDF.js took too long // bail out if loading PDF.js took too long
if (!mountedRef.current) { if (!mountedRef.current) {
onError()
return return
} }
@ -37,7 +35,6 @@ const FileViewPdf: FC<{
// bail out if loading the PDF took too long // bail out if loading the PDF took too long
if (!mountedRef.current) { if (!mountedRef.current) {
onError()
return return
} }
@ -47,6 +44,12 @@ const FileViewPdf: FC<{
for (let i = 1; i <= pdf.numPages; i++) { for (let i = 1; i <= pdf.numPages; i++) {
const page = await pdf.getPage(i) const page = await pdf.getPage(i)
// bail out if the component has been unmounted
if (!mountedRef.current) {
return
}
const viewport = page.getViewport({ scale }) const viewport = page.getViewport({ scale })
const canvas = document.createElement('canvas') const canvas = document.createElement('canvas')
@ -64,11 +67,6 @@ const FileViewPdf: FC<{
} }
onLoad() onLoad()
return () => {
pdf.cleanup().catch(debugConsole.error)
pdf.destroy()
}
} }
}, },
[mountedRef, pathInFolder, fileId, previewByPath, onLoad, onError] [mountedRef, pathInFolder, fileId, previewByPath, onLoad, onError]

View file

@ -64,24 +64,23 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
const handleContainer = useCallback( const handleContainer = useCallback(
parent => { parent => {
if (parent) { if (parent) {
let wrapper: PDFJSWrapper | undefined
try { try {
wrapper = new PDFJSWrapper(parent.firstChild) setPdfJsWrapper(new PDFJSWrapper(parent.firstChild))
setPdfJsWrapper(wrapper)
} catch (error: any) { } catch (error: any) {
setLoadingError(true) setLoadingError(true)
captureException(error) captureException(error)
} }
return () => {
setPdfJsWrapper(null)
wrapper?.destroy().catch(debugConsole.error)
}
} }
}, },
[setLoadingError] [setLoadingError]
) )
useEffect(() => {
return () => {
setPdfJsWrapper(null)
}
}, [])
const [startFetch, setStartFetch] = useState(0) const [startFetch, setStartFetch] = useState(0)
// listen for events and trigger rendering. // listen for events and trigger rendering.
@ -181,7 +180,9 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
pdfJsWrapper pdfJsWrapper
.loadDocument({ url, pdfFile, abortController, handleFetchError }) .loadDocument({ url, pdfFile, abortController, handleFetchError })
.then(doc => { .then(doc => {
if (doc) {
setTotalPages(doc.numPages) setTotalPages(doc.numPages)
}
}) })
.catch(error => { .catch(error => {
if (abortController.signal.aborted) return if (abortController.signal.aborted) return
@ -190,7 +191,6 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
}) })
return () => { return () => {
abortController.abort() abortController.abort()
pdfJsWrapper.abortDocumentLoading()
} }
} }
}, [pdfJsWrapper, url, pdfFile, setError, setStartFetch]) }, [pdfJsWrapper, url, pdfFile, setError, setStartFetch])

View file

@ -1,6 +1,5 @@
import { captureException } from '@/infrastructure/error-reporter' import { captureException } from '@/infrastructure/error-reporter'
import { generatePdfCachingTransportFactory } from './pdf-caching-transport' import { generatePdfCachingTransportFactory } from './pdf-caching-transport'
import { debugConsole } from '@/utils/debugging'
import { PDFJS, loadPdfDocumentFromUrl, imageResourcesPath } from './pdf-js' import { PDFJS, loadPdfDocumentFromUrl, imageResourcesPath } from './pdf-js'
import { import {
PDFViewer, PDFViewer,
@ -13,12 +12,11 @@ import 'pdfjs-dist/web/pdf_viewer.css'
const DEFAULT_RANGE_CHUNK_SIZE = 128 * 1024 // 128K chunks const DEFAULT_RANGE_CHUNK_SIZE = 128 * 1024 // 128K chunks
export default class PDFJSWrapper { export default class PDFJSWrapper {
private loadDocumentTask: PDFJS.PDFDocumentLoadingTask | undefined
public readonly viewer: PDFViewer public readonly viewer: PDFViewer
public readonly eventBus: EventBus public readonly eventBus: EventBus
private readonly linkService: PDFLinkService private readonly linkService: PDFLinkService
private readonly pdfCachingTransportFactory: any private readonly pdfCachingTransportFactory: any
private url?: string
// eslint-disable-next-line no-useless-constructor // eslint-disable-next-line no-useless-constructor
constructor(public container: HTMLDivElement) { constructor(public container: HTMLDivElement) {
@ -49,7 +47,7 @@ export default class PDFJSWrapper {
} }
// load a document from a URL // load a document from a URL
loadDocument({ async loadDocument({
url, url,
pdfFile, pdfFile,
abortController, abortController,
@ -60,13 +58,8 @@ export default class PDFJSWrapper {
abortController: AbortController abortController: AbortController
handleFetchError: (error: Error) => void handleFetchError: (error: Error) => void
}) { }) {
// cancel any previous loading task this.url = url
if (this.loadDocumentTask) {
this.loadDocumentTask.destroy().catch(debugConsole.error)
this.loadDocumentTask = undefined
}
return new Promise<PDFJS.PDFDocumentProxy>((resolve, reject) => {
const rangeTransport = this.pdfCachingTransportFactory({ const rangeTransport = this.pdfCachingTransportFactory({
url, url,
pdfFile, pdfFile,
@ -79,42 +72,31 @@ export default class PDFJSWrapper {
// custom range transport. Restore it by bumping the chunk size. // custom range transport. Restore it by bumping the chunk size.
rangeChunkSize = pdfFile.size rangeChunkSize = pdfFile.size
} }
this.loadDocumentTask = loadPdfDocumentFromUrl(url, {
try {
const doc = await loadPdfDocumentFromUrl(url, {
rangeChunkSize, rangeChunkSize,
range: rangeTransport, range: rangeTransport,
}) }).promise
this.loadDocumentTask.promise // check that this is still the current URL
.then(doc => { if (url !== this.url) {
if (!this.loadDocumentTask || !this.viewer) { return
return // ignoring the response since loading task has been aborted
} }
const previousDoc = this.viewer.pdfDocument
this.viewer.setDocument(doc) this.viewer.setDocument(doc)
this.linkService.setDocument(doc) this.linkService.setDocument(doc)
resolve(doc)
if (previousDoc) { return doc
previousDoc.cleanup().catch(debugConsole.error) } catch (error: any) {
previousDoc.destroy().catch(debugConsole.error)
}
})
.catch(error => {
if (this.loadDocumentTask) {
if (!error || error.name !== 'MissingPDFException') { if (!error || error.name !== 'MissingPDFException') {
captureException(error, { captureException(error, {
tags: { handler: 'pdf-preview' }, tags: { handler: 'pdf-preview' },
}) })
} }
reject(error) throw error
} }
})
.finally(() => {
this.loadDocumentTask = undefined
})
})
} }
// update the current scale value if the container size changes // update the current scale value if the container size changes
@ -216,14 +198,4 @@ export default class PDFJSWrapper {
isVisible() { isVisible() {
return this.viewer.container.offsetParent !== null return this.viewer.container.offsetParent !== null
} }
abortDocumentLoading() {
this.loadDocumentTask = undefined
}
async destroy() {
await this.loadDocumentTask?.destroy().catch(debugConsole.error)
this.loadDocumentTask = undefined
await this.viewer.pdfDocument?.destroy().catch(debugConsole.error)
}
} }