Skip to content

Commit

Permalink
Slightly delay rendering during scrolling in the viewer (bug 1854145)
Browse files Browse the repository at this point in the history
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.*
  • Loading branch information
Snuffleupagus committed Dec 10, 2023
1 parent 056e639 commit 6304d15
Showing 1 changed file with 64 additions and 3 deletions.
67 changes: 64 additions & 3 deletions web/pdf_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,15 @@ const PagesCountLimit = {
PAUSE_EAGER_PAGE_INIT: 250,
};

// These constants need to be chosen such that the maximum delay, before
// rendering is triggered, cannot exceed the `DELAYED_CLEANUP_TIMEOUT` value
// defined in the `src/display/api.js` file.
const RenderHighestPriorityDelay = {
CALLS: 15, // num
TIMEOUT: 20, // ms
TIME_DELTA: 20, // ms
};

function isValidAnnotationEditorMode(mode) {
return (
Object.values(AnnotationEditorType).includes(mode) &&
Expand Down Expand Up @@ -228,6 +237,8 @@ class PDFViewer {

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

#rhp = null;

#scrollModePageState = null;

#onVisibilityChange = null;
Expand Down Expand Up @@ -1073,6 +1084,15 @@ class PDFViewer {
this._previousScrollMode = ScrollMode.UNKNOWN;
this._spreadMode = SpreadMode.NONE;

if (this.#rhp?.timeout) {
clearTimeout(this.#rhp.timeout);
}
this.#rhp = {
calls: 0,
timeout: null,
timeStamp: performance.now(),
};

this.#scrollModePageState = {
previousPageNumber: 1,
scrollDown: true,
Expand Down Expand Up @@ -1177,7 +1197,7 @@ class PDFViewer {
if (this.pagesCount === 0) {
return;
}
this.update();
this.update(/* scrolled = */ true);
}

#scrollIntoView(pageView, pageSpot = null) {
Expand Down Expand Up @@ -1594,7 +1614,7 @@ class PDFViewer {
};
}

update() {
update(scrolled = false) {
const visible = this._getVisiblePages();
const visiblePages = visible.views,
numVisiblePages = visiblePages.length;
Expand All @@ -1605,7 +1625,48 @@ class PDFViewer {
const newCacheSize = Math.max(DEFAULT_CACHE_SIZE, 2 * numVisiblePages + 1);
this.#buffer.resize(newCacheSize, visible.ids);

this.renderingQueue.renderHighestPriority(visible);
const rhp = this.#rhp;
rhp.calls++;
if (rhp.timeout) {
clearTimeout(rhp.timeout);
rhp.timeout = null;
}
const timeStamp = performance.now(),
timeDelta = timeStamp - rhp.timeStamp;
rhp.timeStamp = timeStamp;

// To improve overall performance, when scrolling very quickly through long
// and complex PDF documents, we try to *slightly* delay rendering here.
// For PDF documents that contain e.g. large images this should also help
// reduce overall memory usage during parsing.
//
// Note that while main-thread rendering will be cancelled quickly, any
// ongoing worker-thread parsing will be aborted with a small delay.
// Hence it's possible to "overburden" the worker-thread with pages that
// are almost immediately evicted from the `PDFPageViewBuffer` cache,
// if rendering is triggered on every single scroll-event.
//
// To avoid the viewer feeling more "sluggish" than before, for short and
// simple PDF documents, we need to ensure that rendering won't be delayed
// "indefinitely" hence the following heuristics are used to trigger
// rendering immediately:
// - If the `update`-method was *not* called from a scroll-event.
// - For every nth time that the `update`-method is called.
// - At least once for every nth milliseconds.
if (
!scrolled ||
rhp.calls > RenderHighestPriorityDelay.CALLS ||
timeDelta > RenderHighestPriorityDelay.TIME_DELTA
) {
rhp.calls = 0;
this.renderingQueue.renderHighestPriority(visible);
} else {
rhp.timeout = setTimeout(() => {
rhp.calls = 0;
rhp.timeout = null;
this.renderingQueue.renderHighestPriority(visible);
}, RenderHighestPriorityDelay.TIMEOUT);
}

const isSimpleLayout =
this._spreadMode === SpreadMode.NONE &&
Expand Down

0 comments on commit 6304d15

Please sign in to comment.