Skip to content

Commit 6304d15

Browse files
committed
Slightly delay rendering during scrolling in the viewer (bug 1854145)
This patch tries to improve a use-case that's always performed somewhat poorly in the default viewer, i.e. scrolling quickly through long and complex PDF documents. For an initial implementation I've purposely tried to make these new delays as small as possible, while still improving cases such as e.g. bug 1854145, to hopefully avoid regressing perceived performance for all PDF documents in the default viewer. *Please refer to the inline code-comments for additional details.*
1 parent 056e639 commit 6304d15

File tree

1 file changed

+64
-3
lines changed

1 file changed

+64
-3
lines changed

web/pdf_viewer.js

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,15 @@ const PagesCountLimit = {
7676
PAUSE_EAGER_PAGE_INIT: 250,
7777
};
7878

79+
// These constants need to be chosen such that the maximum delay, before
80+
// rendering is triggered, cannot exceed the `DELAYED_CLEANUP_TIMEOUT` value
81+
// defined in the `src/display/api.js` file.
82+
const RenderHighestPriorityDelay = {
83+
CALLS: 15, // num
84+
TIMEOUT: 20, // ms
85+
TIME_DELTA: 20, // ms
86+
};
87+
7988
function isValidAnnotationEditorMode(mode) {
8089
return (
8190
Object.values(AnnotationEditorType).includes(mode) &&
@@ -228,6 +237,8 @@ class PDFViewer {
228237

229238
#resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this));
230239

240+
#rhp = null;
241+
231242
#scrollModePageState = null;
232243

233244
#onVisibilityChange = null;
@@ -1073,6 +1084,15 @@ class PDFViewer {
10731084
this._previousScrollMode = ScrollMode.UNKNOWN;
10741085
this._spreadMode = SpreadMode.NONE;
10751086

1087+
if (this.#rhp?.timeout) {
1088+
clearTimeout(this.#rhp.timeout);
1089+
}
1090+
this.#rhp = {
1091+
calls: 0,
1092+
timeout: null,
1093+
timeStamp: performance.now(),
1094+
};
1095+
10761096
this.#scrollModePageState = {
10771097
previousPageNumber: 1,
10781098
scrollDown: true,
@@ -1177,7 +1197,7 @@ class PDFViewer {
11771197
if (this.pagesCount === 0) {
11781198
return;
11791199
}
1180-
this.update();
1200+
this.update(/* scrolled = */ true);
11811201
}
11821202

11831203
#scrollIntoView(pageView, pageSpot = null) {
@@ -1594,7 +1614,7 @@ class PDFViewer {
15941614
};
15951615
}
15961616

1597-
update() {
1617+
update(scrolled = false) {
15981618
const visible = this._getVisiblePages();
15991619
const visiblePages = visible.views,
16001620
numVisiblePages = visiblePages.length;
@@ -1605,7 +1625,48 @@ class PDFViewer {
16051625
const newCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1);
16061626
this.#buffer.resize(newCacheSize, visible.ids);
16071627

1608-
this.renderingQueue.renderHighestPriority(visible);
1628+
const rhp = this.#rhp;
1629+
rhp.calls++;
1630+
if (rhp.timeout) {
1631+
clearTimeout(rhp.timeout);
1632+
rhp.timeout = null;
1633+
}
1634+
const timeStamp = performance.now(),
1635+
timeDelta = timeStamp - rhp.timeStamp;
1636+
rhp.timeStamp = timeStamp;
1637+
1638+
// To improve overall performance, when scrolling very quickly through long
1639+
// and complex PDF documents, we try to *slightly* delay rendering here.
1640+
// For PDF documents that contain e.g. large images this should also help
1641+
// reduce overall memory usage during parsing.
1642+
//
1643+
// Note that while main-thread rendering will be cancelled quickly, any
1644+
// ongoing worker-thread parsing will be aborted with a small delay.
1645+
// Hence it's possible to "overburden" the worker-thread with pages that
1646+
// are almost immediately evicted from the `PDFPageViewBuffer` cache,
1647+
// if rendering is triggered on every single scroll-event.
1648+
//
1649+
// To avoid the viewer feeling more "sluggish" than before, for short and
1650+
// simple PDF documents, we need to ensure that rendering won't be delayed
1651+
// "indefinitely" hence the following heuristics are used to trigger
1652+
// rendering immediately:
1653+
// - If the `update`-method was *not* called from a scroll-event.
1654+
// - For every nth time that the `update`-method is called.
1655+
// - At least once for every nth milliseconds.
1656+
if (
1657+
!scrolled ||
1658+
rhp.calls > RenderHighestPriorityDelay.CALLS ||
1659+
timeDelta > RenderHighestPriorityDelay.TIME_DELTA
1660+
) {
1661+
rhp.calls = 0;
1662+
this.renderingQueue.renderHighestPriority(visible);
1663+
} else {
1664+
rhp.timeout = setTimeout(() => {
1665+
rhp.calls = 0;
1666+
rhp.timeout = null;
1667+
this.renderingQueue.renderHighestPriority(visible);
1668+
}, RenderHighestPriorityDelay.TIMEOUT);
1669+
}
16091670

16101671
const isSimpleLayout =
16111672
this._spreadMode === SpreadMode.NONE &&

0 commit comments

Comments
 (0)