Skip to content

Commit

Permalink
Skip useless tasks in the worker to improve fast scrolling with scann…
Browse files Browse the repository at this point in the history
…ed books (bug 1866296)

When a page rendering is cancelled, a task is sent to the worker but before it's executed
the rendering task is: the cancel task is more or less useless in this case.
So in using the fact that draining the message queue has a higher priority
than draining the event one, it's possible to get all the current tasks, hence
it's possible to cancel some tasks which are before a cancel task.
  • Loading branch information
calixteman committed Dec 10, 2023
1 parent 5537298 commit db6d087
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 65 deletions.
29 changes: 21 additions & 8 deletions src/display/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -1274,14 +1274,19 @@ class PDFDocumentProxy {
class PDFPageProxy {
#delayedCleanupTimeout = null;

#pageId;

#pendingCleanup = false;

static #_pageId = 1;

constructor(pageIndex, pageInfo, transport, pdfBug = false) {
this._pageIndex = pageIndex;
this._pageInfo = pageInfo;
this._transport = transport;
this._stats = pdfBug ? new StatTimer() : null;
this._pdfBug = pdfBug;
this.#pageId = PDFPageProxy.#_pageId++;
/** @type {PDFObjects} */
this.commonObjs = transport.commonObjs;
this.objs = new PDFObjects();
Expand Down Expand Up @@ -1358,6 +1363,7 @@ class PDFPageProxy {
const intentArgs = this._transport.getRenderingIntent(intent);

return this._transport.getAnnotations(
this.#pageId,
this._pageIndex,
intentArgs.renderingIntent
);
Expand All @@ -1368,7 +1374,7 @@ class PDFPageProxy {
* {Object} with JS actions.
*/
getJSActions() {
return this._transport.getPageJSActions(this._pageIndex);
return this._transport.getPageJSActions(this.#pageId, this._pageIndex);
}

/**
Expand Down Expand Up @@ -1428,7 +1434,9 @@ class PDFPageProxy {
this.#abortDelayedCleanup();

if (!optionalContentConfigPromise) {
optionalContentConfigPromise = this._transport.getOptionalContentConfig();
optionalContentConfigPromise = this._transport.getOptionalContentConfig(
this.#pageId
);
}

let intentState = this._intentStates.get(intentArgs.cacheKey);
Expand Down Expand Up @@ -1602,6 +1610,7 @@ class PDFPageProxy {
return this._transport.messageHandler.sendWithStream(
"GetTextContent",
{
pageId: this.#pageId,
pageIndex: this._pageIndex,
includeMarkedContent: includeMarkedContent === true,
disableNormalization: disableNormalization === true,
Expand Down Expand Up @@ -1661,7 +1670,7 @@ class PDFPageProxy {
* or `null` when no structure tree is present for the current page.
*/
getStructTree() {
return this._transport.getStructTree(this._pageIndex);
return this._transport.getStructTree(this.#pageId, this._pageIndex);
}

/**
Expand Down Expand Up @@ -1807,6 +1816,7 @@ class PDFPageProxy {
const readableStream = this._transport.messageHandler.sendWithStream(
"GetOperatorList",
{
pageId: this.#pageId,
pageIndex: this._pageIndex,
intent: renderingIntent,
cacheKey,
Expand Down Expand Up @@ -2923,8 +2933,9 @@ class WorkerTransport {
});
}

getAnnotations(pageIndex, intent) {
getAnnotations(pageId, pageIndex, intent) {
return this.messageHandler.sendWithPromise("GetAnnotations", {
pageId,
pageIndex,
intent,
});
Expand Down Expand Up @@ -2983,14 +2994,16 @@ class WorkerTransport {
return this.#cacheSimpleMethod("GetDocJSActions");
}

getPageJSActions(pageIndex) {
getPageJSActions(pageId, pageIndex) {
return this.messageHandler.sendWithPromise("GetPageJSActions", {
pageId,
pageIndex,
});
}

getStructTree(pageIndex) {
getStructTree(pageId, pageIndex) {
return this.messageHandler.sendWithPromise("GetStructTree", {
pageId,
pageIndex,
});
}
Expand All @@ -2999,9 +3012,9 @@ class WorkerTransport {
return this.messageHandler.sendWithPromise("GetOutline", null);
}

getOptionalContentConfig() {
getOptionalContentConfig(pageId) {
return this.messageHandler
.sendWithPromise("GetOptionalContentConfig", null)
.sendWithPromise("GetOptionalContentConfig", { pageId })
.then(results => {
return new OptionalContentConfig(results);
});
Expand Down
162 changes: 105 additions & 57 deletions src/shared/message_handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ function wrapReason(reason) {
}

class MessageHandler {
#cancelledPageIds = new Set();

#executorRunning = false;

#queue = [];

constructor(sourceName, targetName, comObj) {
this.sourceName = sourceName;
this.targetName = targetName;
Expand All @@ -81,71 +87,111 @@ class MessageHandler {
this.callbackCapabilities = Object.create(null);
this.actionHandler = Object.create(null);

this._onComObjOnMessage = event => {
const data = event.data;
if (data.targetName !== this.sourceName) {
return;
}
if (data.stream) {
this.#processStreamMessage(data);
return;
}
if (data.callback) {
const callbackId = data.callbackId;
const capability = this.callbackCapabilities[callbackId];
if (!capability) {
throw new Error(`Cannot resolve callback ${callbackId}`);
this._onComObjOnMessage = ({ data }) => {
if (data.targetName === this.sourceName) {
// The meesages in the worker queue are processed with a
// higher priority than the tasks in the event queue.
// So, postponing the task execution, will ensure that the message
// queue is drained.
// If at some point we've a cancelled task (e.g. GetOperatorList),
// we're able to skip the task execution with the same pageId.
this.#queue.push(data);
if (data.pageId && data.stream === StreamKind.CANCEL) {
this.#cancelledPageIds.add(data.pageId);
}
delete this.callbackCapabilities[callbackId];

if (data.callback === CallbackKind.DATA) {
capability.resolve(data.data);
} else if (data.callback === CallbackKind.ERROR) {
capability.reject(wrapReason(data.reason));
} else {
throw new Error("Unexpected callback case");
if (!this.#executorRunning) {
this.#executorRunning = true;
this.#postponeExecution();
}
return;
}
const action = this.actionHandler[data.action];
if (!action) {
throw new Error(`Unknown action from worker: ${data.action}`);
};
comObj.addEventListener("message", this._onComObjOnMessage);
}

#postponeExecution() {
setTimeout(this.#executor.bind(this), 0);
}

#executor() {
if (this.#queue.length === 0) {
this.#cancelledPageIds.clear();
this.#executorRunning = false;
return;
}

const data = this.#queue.shift();

if (data.stream) {
if (
data.stream === StreamKind.CANCEL ||
!this.#cancelledPageIds.has(data.pageId)
) {
this.#processStreamMessage(data);
}
if (data.callbackId) {
const cbSourceName = this.sourceName;
const cbTargetName = data.sourceName;
this.#postponeExecution();
return;
}

new Promise(function (resolve) {
resolve(action(data.data));
}).then(
function (result) {
comObj.postMessage({
sourceName: cbSourceName,
targetName: cbTargetName,
callback: CallbackKind.DATA,
callbackId: data.callbackId,
data: result,
});
},
function (reason) {
comObj.postMessage({
sourceName: cbSourceName,
targetName: cbTargetName,
callback: CallbackKind.ERROR,
callbackId: data.callbackId,
reason: wrapReason(reason),
});
}
);
return;
const pageId = data.data?.pageId;
if (pageId && this.#cancelledPageIds.has(pageId)) {
this.#postponeExecution();
return;
}

if (data.callback) {
const callbackId = data.callbackId;
const capability = this.callbackCapabilities[callbackId];
if (!capability) {
throw new Error(`Cannot resolve callback ${callbackId}`);
}
if (data.streamId) {
this.#createStreamSink(data);
return;
delete this.callbackCapabilities[callbackId];

if (data.callback === CallbackKind.DATA) {
capability.resolve(data.data);
} else if (data.callback === CallbackKind.ERROR) {
capability.reject(wrapReason(data.reason));
} else {
throw new Error("Unexpected callback case");
}
this.#postponeExecution();
return;
}
const action = this.actionHandler[data.action];
if (!action) {
throw new Error(`Unknown action from worker: ${data.action}`);
}
if (data.callbackId) {
const cbSourceName = this.sourceName;
const cbTargetName = data.sourceName;

new Promise(function (resolve) {
resolve(action(data.data));
}).then(
result => {
this.comObj.postMessage({
sourceName: cbSourceName,
targetName: cbTargetName,
callback: CallbackKind.DATA,
callbackId: data.callbackId,
data: result,
});
},
reason => {
this.comObj.postMessage({
sourceName: cbSourceName,
targetName: cbTargetName,
callback: CallbackKind.ERROR,
callbackId: data.callbackId,
reason: wrapReason(reason),
});
}
);
} else if (data.streamId) {
this.#createStreamSink(data);
} else {
action(data.data);
};
comObj.addEventListener("message", this._onComObjOnMessage);
}
this.#postponeExecution();
}

on(actionName, handler) {
Expand Down Expand Up @@ -224,6 +270,7 @@ class MessageHandler {
sourceName = this.sourceName,
targetName = this.targetName,
comObj = this.comObj;
const pageId = data?.pageId;

return new ReadableStream(
{
Expand Down Expand Up @@ -276,6 +323,7 @@ class MessageHandler {
targetName,
stream: StreamKind.CANCEL,
streamId,
pageId,
reason: wrapReason(reason),
});
// Return Promise to signal success or failure.
Expand Down

0 comments on commit db6d087

Please sign in to comment.