Skip to content

Commit db6d087

Browse files
committed
Skip useless tasks in the worker to improve fast scrolling with scanned 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.
1 parent 5537298 commit db6d087

File tree

2 files changed

+126
-65
lines changed

2 files changed

+126
-65
lines changed

src/display/api.js

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1274,14 +1274,19 @@ class PDFDocumentProxy {
12741274
class PDFPageProxy {
12751275
#delayedCleanupTimeout = null;
12761276

1277+
#pageId;
1278+
12771279
#pendingCleanup = false;
12781280

1281+
static #_pageId = 1;
1282+
12791283
constructor(pageIndex, pageInfo, transport, pdfBug = false) {
12801284
this._pageIndex = pageIndex;
12811285
this._pageInfo = pageInfo;
12821286
this._transport = transport;
12831287
this._stats = pdfBug ? new StatTimer() : null;
12841288
this._pdfBug = pdfBug;
1289+
this.#pageId = PDFPageProxy.#_pageId++;
12851290
/** @type {PDFObjects} */
12861291
this.commonObjs = transport.commonObjs;
12871292
this.objs = new PDFObjects();
@@ -1358,6 +1363,7 @@ class PDFPageProxy {
13581363
const intentArgs = this._transport.getRenderingIntent(intent);
13591364

13601365
return this._transport.getAnnotations(
1366+
this.#pageId,
13611367
this._pageIndex,
13621368
intentArgs.renderingIntent
13631369
);
@@ -1368,7 +1374,7 @@ class PDFPageProxy {
13681374
* {Object} with JS actions.
13691375
*/
13701376
getJSActions() {
1371-
return this._transport.getPageJSActions(this._pageIndex);
1377+
return this._transport.getPageJSActions(this.#pageId, this._pageIndex);
13721378
}
13731379

13741380
/**
@@ -1428,7 +1434,9 @@ class PDFPageProxy {
14281434
this.#abortDelayedCleanup();
14291435

14301436
if (!optionalContentConfigPromise) {
1431-
optionalContentConfigPromise = this._transport.getOptionalContentConfig();
1437+
optionalContentConfigPromise = this._transport.getOptionalContentConfig(
1438+
this.#pageId
1439+
);
14321440
}
14331441

14341442
let intentState = this._intentStates.get(intentArgs.cacheKey);
@@ -1602,6 +1610,7 @@ class PDFPageProxy {
16021610
return this._transport.messageHandler.sendWithStream(
16031611
"GetTextContent",
16041612
{
1613+
pageId: this.#pageId,
16051614
pageIndex: this._pageIndex,
16061615
includeMarkedContent: includeMarkedContent === true,
16071616
disableNormalization: disableNormalization === true,
@@ -1661,7 +1670,7 @@ class PDFPageProxy {
16611670
* or `null` when no structure tree is present for the current page.
16621671
*/
16631672
getStructTree() {
1664-
return this._transport.getStructTree(this._pageIndex);
1673+
return this._transport.getStructTree(this.#pageId, this._pageIndex);
16651674
}
16661675

16671676
/**
@@ -1807,6 +1816,7 @@ class PDFPageProxy {
18071816
const readableStream = this._transport.messageHandler.sendWithStream(
18081817
"GetOperatorList",
18091818
{
1819+
pageId: this.#pageId,
18101820
pageIndex: this._pageIndex,
18111821
intent: renderingIntent,
18121822
cacheKey,
@@ -2923,8 +2933,9 @@ class WorkerTransport {
29232933
});
29242934
}
29252935

2926-
getAnnotations(pageIndex, intent) {
2936+
getAnnotations(pageId, pageIndex, intent) {
29272937
return this.messageHandler.sendWithPromise("GetAnnotations", {
2938+
pageId,
29282939
pageIndex,
29292940
intent,
29302941
});
@@ -2983,14 +2994,16 @@ class WorkerTransport {
29832994
return this.#cacheSimpleMethod("GetDocJSActions");
29842995
}
29852996

2986-
getPageJSActions(pageIndex) {
2997+
getPageJSActions(pageId, pageIndex) {
29872998
return this.messageHandler.sendWithPromise("GetPageJSActions", {
2999+
pageId,
29883000
pageIndex,
29893001
});
29903002
}
29913003

2992-
getStructTree(pageIndex) {
3004+
getStructTree(pageId, pageIndex) {
29933005
return this.messageHandler.sendWithPromise("GetStructTree", {
3006+
pageId,
29943007
pageIndex,
29953008
});
29963009
}
@@ -2999,9 +3012,9 @@ class WorkerTransport {
29993012
return this.messageHandler.sendWithPromise("GetOutline", null);
30003013
}
30013014

3002-
getOptionalContentConfig() {
3015+
getOptionalContentConfig(pageId) {
30033016
return this.messageHandler
3004-
.sendWithPromise("GetOptionalContentConfig", null)
3017+
.sendWithPromise("GetOptionalContentConfig", { pageId })
30053018
.then(results => {
30063019
return new OptionalContentConfig(results);
30073020
});

src/shared/message_handler.js

Lines changed: 105 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,12 @@ function wrapReason(reason) {
7070
}
7171

7272
class MessageHandler {
73+
#cancelledPageIds = new Set();
74+
75+
#executorRunning = false;
76+
77+
#queue = [];
78+
7379
constructor(sourceName, targetName, comObj) {
7480
this.sourceName = sourceName;
7581
this.targetName = targetName;
@@ -81,71 +87,111 @@ class MessageHandler {
8187
this.callbackCapabilities = Object.create(null);
8288
this.actionHandler = Object.create(null);
8389

84-
this._onComObjOnMessage = event => {
85-
const data = event.data;
86-
if (data.targetName !== this.sourceName) {
87-
return;
88-
}
89-
if (data.stream) {
90-
this.#processStreamMessage(data);
91-
return;
92-
}
93-
if (data.callback) {
94-
const callbackId = data.callbackId;
95-
const capability = this.callbackCapabilities[callbackId];
96-
if (!capability) {
97-
throw new Error(`Cannot resolve callback ${callbackId}`);
90+
this._onComObjOnMessage = ({ data }) => {
91+
if (data.targetName === this.sourceName) {
92+
// The meesages in the worker queue are processed with a
93+
// higher priority than the tasks in the event queue.
94+
// So, postponing the task execution, will ensure that the message
95+
// queue is drained.
96+
// If at some point we've a cancelled task (e.g. GetOperatorList),
97+
// we're able to skip the task execution with the same pageId.
98+
this.#queue.push(data);
99+
if (data.pageId && data.stream === StreamKind.CANCEL) {
100+
this.#cancelledPageIds.add(data.pageId);
98101
}
99-
delete this.callbackCapabilities[callbackId];
100-
101-
if (data.callback === CallbackKind.DATA) {
102-
capability.resolve(data.data);
103-
} else if (data.callback === CallbackKind.ERROR) {
104-
capability.reject(wrapReason(data.reason));
105-
} else {
106-
throw new Error("Unexpected callback case");
102+
if (!this.#executorRunning) {
103+
this.#executorRunning = true;
104+
this.#postponeExecution();
107105
}
108-
return;
109106
}
110-
const action = this.actionHandler[data.action];
111-
if (!action) {
112-
throw new Error(`Unknown action from worker: ${data.action}`);
107+
};
108+
comObj.addEventListener("message", this._onComObjOnMessage);
109+
}
110+
111+
#postponeExecution() {
112+
setTimeout(this.#executor.bind(this), 0);
113+
}
114+
115+
#executor() {
116+
if (this.#queue.length === 0) {
117+
this.#cancelledPageIds.clear();
118+
this.#executorRunning = false;
119+
return;
120+
}
121+
122+
const data = this.#queue.shift();
123+
124+
if (data.stream) {
125+
if (
126+
data.stream === StreamKind.CANCEL ||
127+
!this.#cancelledPageIds.has(data.pageId)
128+
) {
129+
this.#processStreamMessage(data);
113130
}
114-
if (data.callbackId) {
115-
const cbSourceName = this.sourceName;
116-
const cbTargetName = data.sourceName;
131+
this.#postponeExecution();
132+
return;
133+
}
117134

118-
new Promise(function (resolve) {
119-
resolve(action(data.data));
120-
}).then(
121-
function (result) {
122-
comObj.postMessage({
123-
sourceName: cbSourceName,
124-
targetName: cbTargetName,
125-
callback: CallbackKind.DATA,
126-
callbackId: data.callbackId,
127-
data: result,
128-
});
129-
},
130-
function (reason) {
131-
comObj.postMessage({
132-
sourceName: cbSourceName,
133-
targetName: cbTargetName,
134-
callback: CallbackKind.ERROR,
135-
callbackId: data.callbackId,
136-
reason: wrapReason(reason),
137-
});
138-
}
139-
);
140-
return;
135+
const pageId = data.data?.pageId;
136+
if (pageId && this.#cancelledPageIds.has(pageId)) {
137+
this.#postponeExecution();
138+
return;
139+
}
140+
141+
if (data.callback) {
142+
const callbackId = data.callbackId;
143+
const capability = this.callbackCapabilities[callbackId];
144+
if (!capability) {
145+
throw new Error(`Cannot resolve callback ${callbackId}`);
141146
}
142-
if (data.streamId) {
143-
this.#createStreamSink(data);
144-
return;
147+
delete this.callbackCapabilities[callbackId];
148+
149+
if (data.callback === CallbackKind.DATA) {
150+
capability.resolve(data.data);
151+
} else if (data.callback === CallbackKind.ERROR) {
152+
capability.reject(wrapReason(data.reason));
153+
} else {
154+
throw new Error("Unexpected callback case");
145155
}
156+
this.#postponeExecution();
157+
return;
158+
}
159+
const action = this.actionHandler[data.action];
160+
if (!action) {
161+
throw new Error(`Unknown action from worker: ${data.action}`);
162+
}
163+
if (data.callbackId) {
164+
const cbSourceName = this.sourceName;
165+
const cbTargetName = data.sourceName;
166+
167+
new Promise(function (resolve) {
168+
resolve(action(data.data));
169+
}).then(
170+
result => {
171+
this.comObj.postMessage({
172+
sourceName: cbSourceName,
173+
targetName: cbTargetName,
174+
callback: CallbackKind.DATA,
175+
callbackId: data.callbackId,
176+
data: result,
177+
});
178+
},
179+
reason => {
180+
this.comObj.postMessage({
181+
sourceName: cbSourceName,
182+
targetName: cbTargetName,
183+
callback: CallbackKind.ERROR,
184+
callbackId: data.callbackId,
185+
reason: wrapReason(reason),
186+
});
187+
}
188+
);
189+
} else if (data.streamId) {
190+
this.#createStreamSink(data);
191+
} else {
146192
action(data.data);
147-
};
148-
comObj.addEventListener("message", this._onComObjOnMessage);
193+
}
194+
this.#postponeExecution();
149195
}
150196

151197
on(actionName, handler) {
@@ -224,6 +270,7 @@ class MessageHandler {
224270
sourceName = this.sourceName,
225271
targetName = this.targetName,
226272
comObj = this.comObj;
273+
const pageId = data?.pageId;
227274

228275
return new ReadableStream(
229276
{
@@ -276,6 +323,7 @@ class MessageHandler {
276323
targetName,
277324
stream: StreamKind.CANCEL,
278325
streamId,
326+
pageId,
279327
reason: wrapReason(reason),
280328
});
281329
// Return Promise to signal success or failure.

0 commit comments

Comments
 (0)