Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove event listeners with signal in web/pdf_viewer.js #18055

Merged
merged 1 commit into from
May 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 55 additions & 69 deletions web/pdf_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -211,12 +211,12 @@ class PDFViewer {

#containerTopLeft = null;

#copyCallbackBound = null;

#enableHighlightFloatingButton = false;

#enablePermissions = false;

#eventAbortController = null;

#mlManager = null;

#getAllTextInProgress = false;
Expand All @@ -231,8 +231,6 @@ class PDFViewer {

#scrollModePageState = null;

#onVisibilityChange = null;

#scaleTimeoutId = null;

#textLayerMode = TextLayerMode.ENABLE;
Expand Down Expand Up @@ -313,7 +311,6 @@ class PDFViewer {

this.scroll = watchScroll(this.container, this._scrollUpdate.bind(this));
this.presentationModeState = PresentationModeState.UNKNOWN;
this._onBeforeDraw = this._onAfterDraw = null;
this._resetView();

if (
Expand Down Expand Up @@ -625,7 +622,7 @@ class PDFViewer {
return params;
}

async #onePageRenderedOrForceFetch() {
async #onePageRenderedOrForceFetch(signal) {
// Unless the viewer *and* its pages are visible, rendering won't start and
// `this._onePageRenderedCapability` thus won't be resolved.
// To ensure that automatic printing, on document load, still works even in
Expand All @@ -646,23 +643,22 @@ class PDFViewer {

// Handle the window/tab becoming inactive *after* rendering has started;
// fixes (another part of) bug 1746213.
const visibilityChangePromise = new Promise(resolve => {
this.#onVisibilityChange = () => {
if (document.visibilityState !== "hidden") {
return;
}
resolve();
};
document.addEventListener("visibilitychange", this.#onVisibilityChange);
const hiddenCapability = Promise.withResolvers();
function onVisibilityChange() {
if (document.visibilityState === "hidden") {
hiddenCapability.resolve();
}
}
document.addEventListener("visibilitychange", onVisibilityChange, {
signal,
});

await Promise.race([
this._onePageRenderedCapability.promise,
visibilityChangePromise,
hiddenCapability.promise,
]);
// Ensure that the "visibilitychange" listener is always removed.
document.removeEventListener("visibilitychange", this.#onVisibilityChange);
this.#onVisibilityChange = null;
// Ensure that the "visibilitychange" listener is removed immediately.
document.removeEventListener("visibilitychange", onVisibilityChange);
}

async getAllText() {
Expand Down Expand Up @@ -788,26 +784,31 @@ class PDFViewer {
? pdfDocument.getPermissions()
: Promise.resolve();

const { eventBus, pageColors, viewer } = this;

this.#eventAbortController = new AbortController();
const { signal } = this.#eventAbortController;

// Given that browsers don't handle huge amounts of DOM-elements very well,
// enforce usage of PAGE-scrolling when loading *very* long/large documents.
if (pagesCount > PagesCountLimit.FORCE_SCROLL_MODE_PAGE) {
console.warn(
"Forcing PAGE-scrolling for performance reasons, given the length of the document."
);
const mode = (this._scrollMode = ScrollMode.PAGE);
this.eventBus.dispatch("scrollmodechanged", { source: this, mode });
eventBus.dispatch("scrollmodechanged", { source: this, mode });
}

this._pagesCapability.promise.then(
() => {
this.eventBus.dispatch("pagesloaded", { source: this, pagesCount });
eventBus.dispatch("pagesloaded", { source: this, pagesCount });
},
() => {
/* Prevent "Uncaught (in promise)"-messages in the console. */
}
);

this._onBeforeDraw = evt => {
const onBeforeDraw = evt => {
const pageView = this._pages[evt.pageNumber - 1];
if (!pageView) {
return;
Expand All @@ -816,18 +817,17 @@ class PDFViewer {
// evicted from the buffer and destroyed even if we pause its rendering.
this.#buffer.push(pageView);
};
this.eventBus._on("pagerender", this._onBeforeDraw);
eventBus._on("pagerender", onBeforeDraw, { signal });

this._onAfterDraw = evt => {
const onAfterDraw = evt => {
if (evt.cssTransform) {
return;
}
this._onePageRenderedCapability.resolve({ timestamp: evt.timestamp });

this.eventBus._off("pagerendered", this._onAfterDraw);
this._onAfterDraw = null;
eventBus._off("pagerendered", onAfterDraw); // Remove immediately.
};
this.eventBus._on("pagerendered", this._onAfterDraw);
eventBus._on("pagerendered", onAfterDraw, { signal });

// Fetch a single page so we can get a viewport that will be the default
// viewport for all pages
Expand All @@ -846,7 +846,7 @@ class PDFViewer {
const element = (this.#hiddenCopyElement =
document.createElement("div"));
element.id = "hiddenCopyElement";
this.viewer.before(element);
viewer.before(element);
}

if (annotationEditorMode !== AnnotationEditorType.DISABLE) {
Expand All @@ -857,16 +857,16 @@ class PDFViewer {
} else if (isValidAnnotationEditorMode(mode)) {
this.#annotationEditorUIManager = new AnnotationEditorUIManager(
this.container,
this.viewer,
viewer,
this.#altTextManager,
this.eventBus,
eventBus,
pdfDocument,
this.pageColors,
pageColors,
this.#annotationEditorHighlightColors,
this.#enableHighlightFloatingButton,
this.#mlManager
);
this.eventBus.dispatch("annotationeditoruimanager", {
eventBus.dispatch("annotationeditoruimanager", {
source: this,
uiManager: this.#annotationEditorUIManager,
});
Expand All @@ -879,19 +879,19 @@ class PDFViewer {
}

const viewerElement =
this._scrollMode === ScrollMode.PAGE ? null : this.viewer;
this._scrollMode === ScrollMode.PAGE ? null : viewer;
const scale = this.currentScale;
const viewport = firstPdfPage.getViewport({
scale: scale * PixelsPerInch.PDF_TO_CSS_UNITS,
});
// Ensure that the various layers always get the correct initial size,
// see issue 15795.
this.viewer.style.setProperty("--scale-factor", viewport.scale);
viewer.style.setProperty("--scale-factor", viewport.scale);
if (
this.pageColors?.foreground === "CanvasText" ||
this.pageColors?.background === "Canvas"
pageColors?.foreground === "CanvasText" ||
pageColors?.background === "Canvas"
) {
this.viewer.style.setProperty(
viewer.style.setProperty(
"--hcm-highlight-filter",
pdfDocument.filterFactory.addHighlightHCMFilter(
"highlight",
Expand All @@ -901,7 +901,7 @@ class PDFViewer {
"Highlight"
)
);
this.viewer.style.setProperty(
viewer.style.setProperty(
"--hcm-highlight-selected-filter",
pdfDocument.filterFactory.addHighlightHCMFilter(
"highlight_selected",
Expand All @@ -916,7 +916,7 @@ class PDFViewer {
for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) {
const pageView = new PDFPageView({
container: viewerElement,
eventBus: this.eventBus,
eventBus,
id: pageNum,
scale,
defaultViewport: viewport.clone(),
Expand All @@ -926,7 +926,7 @@ class PDFViewer {
annotationMode,
imageResourcesPath: this.imageResourcesPath,
maxCanvasPixels: this.maxCanvasPixels,
pageColors: this.pageColors,
pageColors,
l10n: this.l10n,
layerProperties: this._layerProperties,
});
Expand All @@ -947,21 +947,24 @@ class PDFViewer {
// Fetch all the pages since the viewport is needed before printing
// starts to create the correct size canvas. Wait until one page is
// rendered so we don't tie up too many resources early on.
this.#onePageRenderedOrForceFetch().then(async () => {
this.#onePageRenderedOrForceFetch(signal).then(async () => {
if (pdfDocument !== this.pdfDocument) {
return; // The document was closed while the first page rendered.
}
this.findController?.setDocument(pdfDocument); // Enable searching.
this._scriptingManager?.setDocument(pdfDocument); // Enable scripting.

if (this.#hiddenCopyElement) {
this.#copyCallbackBound = this.#copyCallback.bind(
this,
textLayerMode
document.addEventListener(
"copy",
this.#copyCallback.bind(this, textLayerMode),
{ signal }
);
document.addEventListener("copy", this.#copyCallbackBound);
}

if (this.#annotationEditorUIManager) {
// Ensure that the Editor buttons, in the toolbar, are updated.
this.eventBus.dispatch("annotationeditormodechanged", {
eventBus.dispatch("annotationeditormodechanged", {
source: this,
mode: this.#annotationEditorMode,
});
Expand Down Expand Up @@ -1011,14 +1014,14 @@ class PDFViewer {
}
});

this.eventBus.dispatch("pagesinit", { source: this });
eventBus.dispatch("pagesinit", { source: this });

pdfDocument.getMetadata().then(({ info }) => {
if (pdfDocument !== this.pdfDocument) {
return; // The document was closed while the metadata resolved.
}
if (info.Language) {
this.viewer.lang = info.Language;
viewer.lang = info.Language;
}
});

Expand Down Expand Up @@ -1079,35 +1082,18 @@ class PDFViewer {
pages: [],
};

if (this._onBeforeDraw) {
this.eventBus._off("pagerender", this._onBeforeDraw);
this._onBeforeDraw = null;
}
if (this._onAfterDraw) {
this.eventBus._off("pagerendered", this._onAfterDraw);
this._onAfterDraw = null;
}
if (this.#onVisibilityChange) {
document.removeEventListener(
"visibilitychange",
this.#onVisibilityChange
);
this.#onVisibilityChange = null;
}
this.#eventAbortController?.abort();
this.#eventAbortController = null;

// Remove the pages from the DOM...
this.viewer.textContent = "";
// ... and reset the Scroll mode CSS class(es) afterwards.
this._updateScrollMode();

this.viewer.removeAttribute("lang");

if (this.#hiddenCopyElement) {
document.removeEventListener("copy", this.#copyCallbackBound);
this.#copyCallbackBound = null;

this.#hiddenCopyElement.remove();
this.#hiddenCopyElement = null;
}
this.#hiddenCopyElement?.remove();
this.#hiddenCopyElement = null;
}

#ensurePageViewVisible() {
Expand Down