From cf13130cba22d8ad783e6dac89ed7b9bbe6418d6 Mon Sep 17 00:00:00 2001 From: jeetiss Date: Fri, 9 Apr 2021 12:43:43 +0300 Subject: [PATCH] use one pdf worker for all documents and for all rerenders --- src/Document.jsx | 16 ++++++++++++++++ src/Singleton.js | 29 +++++++++++++++++++++++++++++ src/Singleton.spec.js | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+) create mode 100644 src/Singleton.js create mode 100644 src/Singleton.spec.js diff --git a/src/Document.jsx b/src/Document.jsx index c4d72c833..7c7c84bee 100644 --- a/src/Document.jsx +++ b/src/Document.jsx @@ -35,9 +35,15 @@ import { isFile as isFileProp, isRef, } from './shared/propTypes'; +import Singleton from './Singleton'; const { PDFDataRangeTransport } = pdfjs; +const workerProvider = new Singleton( + () => new pdfjs.PDFWorker({ name: 'react-pdf-worker' }), + (pdfjsWorker) => pdfjsWorker.destroy(), +); + export default class Document extends PureComponent { state = { pdf: null, @@ -70,6 +76,11 @@ export default class Document extends PureComponent { linkService = new LinkService(); componentDidMount() { + const { options } = this.props; + if (options && !options.worker) { + this.worker = workerProvider.acquire(); + } + this.loadDocument(); this.setupLinkService(); } @@ -87,6 +98,7 @@ export default class Document extends PureComponent { // If loading is in progress, let's destroy it if (this.loadingTask) this.loadingTask.destroy(); + if (this.worker) workerProvider.release(); } loadDocument = async () => { @@ -112,6 +124,10 @@ export default class Document extends PureComponent { const { options, onLoadProgress, onPassword } = this.props; + if (this.worker) { + options.worker = this.worker; + } + try { // If another rendering is in progress, let's cancel it cancelRunningTask(this.runningTask); diff --git a/src/Singleton.js b/src/Singleton.js new file mode 100644 index 000000000..8d30b3df1 --- /dev/null +++ b/src/Singleton.js @@ -0,0 +1,29 @@ +class Singleton { + constructor(create, destroy) { + this.create = create; + this.destroy = destroy; + + this.usageCount = 0; + } + + acquire() { + if (!this.usageCount) { + this.instance = this.create(); + } + + this.usageCount += 1; + + return this.instance; + } + + release() { + this.usageCount -= 1; + + if (this.usageCount <= 0) { + this.destroy(this.instance); + this.usageCount = 0; + } + } +} + +export default Singleton; diff --git a/src/Singleton.spec.js b/src/Singleton.spec.js new file mode 100644 index 000000000..e54c103cb --- /dev/null +++ b/src/Singleton.spec.js @@ -0,0 +1,35 @@ +import Singleton from './Singleton'; + +describe('Singleton', () => { + it('should create instance only on first acquire call', () => { + const create = jest.fn(); + const singleton = new Singleton(create, () => {}); + + singleton.acquire(); + singleton.acquire(); + + expect(create).toHaveBeenCalledTimes(1); + }); + + it('should destroy instance when nobody uses it', () => { + const destroy = jest.fn(); + const singleton = new Singleton(() => {}, destroy); + + singleton.acquire(); + singleton.acquire(); + singleton.release(); + singleton.release(); + + expect(destroy).toHaveBeenCalledTimes(1); + }); + + it('returns one instance for all acquire calls', () => { + const instance = {}; + const singleton = new Singleton(() => instance, () => {}); + + const first = singleton.acquire(); + const second = singleton.acquire(); + + expect(first).toEqual(second); + }); +});