@papyrus-sdk/ui-react-native 0.2.24 → 0.2.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/dist/{chunk-B36LWZCX.mjs → chunk-K2ZLYLHR.mjs} +84 -1
- package/dist/chunk-K2ZLYLHR.mjs.map +1 -0
- package/dist/{chunk-B25KK4VL.mjs → chunk-KXNP73MZ.mjs} +1 -7
- package/dist/chunk-KXNP73MZ.mjs.map +1 -0
- package/dist/gesture/pinchZoom.d.mts +35 -1
- package/dist/gesture/pinchZoom.d.ts +35 -1
- package/dist/gesture/pinchZoom.js +89 -2
- package/dist/gesture/pinchZoom.js.map +1 -1
- package/dist/gesture/pinchZoom.mjs +11 -3
- package/dist/gesture/selectionInteraction.d.mts +1 -6
- package/dist/gesture/selectionInteraction.d.ts +1 -6
- package/dist/gesture/selectionInteraction.js +0 -7
- package/dist/gesture/selectionInteraction.js.map +1 -1
- package/dist/gesture/selectionInteraction.mjs +1 -3
- package/dist/index.d.mts +38 -27
- package/dist/index.d.ts +38 -27
- package/dist/index.js +3939 -2799
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +3723 -2586
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -2
- package/runtime/comicRuntime.test.ts +151 -0
- package/runtime/documentLoadRuntime.test.ts +172 -0
- package/runtime/epubScrollDiagnostics.test.ts +107 -0
- package/runtime/epubScrollDiagnostics.ts +127 -0
- package/runtime/index.html +1529 -956
- package/runtime/runtime.js +590 -24
- package/runtime/syncComicRuntime.mjs +22 -6
- package/dist/chunk-B25KK4VL.mjs.map +0 -1
- package/dist/chunk-B36LWZCX.mjs.map +0 -1
package/runtime/runtime.js
CHANGED
|
@@ -139,8 +139,18 @@
|
|
|
139
139
|
const COMIC_PREVIEW_CACHE_LIMIT = 8;
|
|
140
140
|
const comicHelpers = window.PapyrusComicRuntime || {};
|
|
141
141
|
const runtimeConfig = window.__PAPYRUS_RUNTIME_CONFIG__ || {};
|
|
142
|
+
const epubDiagnosticsEnabled =
|
|
143
|
+
runtimeConfig.epubDiagnostics === true ||
|
|
144
|
+
runtimeConfig.epubDiagnostics === 'true';
|
|
145
|
+
const EPUB_STAGE_TIMEOUT_MS = 10000;
|
|
142
146
|
let comicDispose = null;
|
|
143
147
|
let comicGeneration = 0;
|
|
148
|
+
let epubInteractionCleanup = null;
|
|
149
|
+
const epubSelectionCleanups = new Set();
|
|
150
|
+
let epubScrollDiagnostics = null;
|
|
151
|
+
// Shared generation for all document formats. The historical variable name
|
|
152
|
+
// is kept because the shipped runtime tests and diagnostics refer to it.
|
|
153
|
+
let epubLoadGeneration = 0;
|
|
144
154
|
|
|
145
155
|
const sendMessage = (payload) => {
|
|
146
156
|
if (window.ReactNativeWebView && window.ReactNativeWebView.postMessage) {
|
|
@@ -246,6 +256,223 @@
|
|
|
246
256
|
sendMessage({ type: 'event', name, payload });
|
|
247
257
|
};
|
|
248
258
|
|
|
259
|
+
const sendEpubDiagnostic = (name, payload) => {
|
|
260
|
+
if (!epubDiagnosticsEnabled) return;
|
|
261
|
+
sendEvent(name, {
|
|
262
|
+
loadId: payload.loadId,
|
|
263
|
+
timestamp: Date.now(),
|
|
264
|
+
sourceKind: payload.sourceKind,
|
|
265
|
+
byteLength: payload.byteLength,
|
|
266
|
+
currentType: currentType,
|
|
267
|
+
durationMs: payload.durationMs,
|
|
268
|
+
uri: payload.uri,
|
|
269
|
+
status: payload.status,
|
|
270
|
+
contentLength: payload.contentLength,
|
|
271
|
+
contentType: payload.contentType,
|
|
272
|
+
transferEncoding: payload.transferEncoding,
|
|
273
|
+
error: payload.error,
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const withEpubStageTimeout = (promise, stage, payload) =>
|
|
278
|
+
new Promise((resolve, reject) => {
|
|
279
|
+
let settled = false;
|
|
280
|
+
const startedAt = performance.now();
|
|
281
|
+
const timeout = setTimeout(() => {
|
|
282
|
+
if (settled) return;
|
|
283
|
+
settled = true;
|
|
284
|
+
const error = new Error(`EPUB ${stage} excedeu ${EPUB_STAGE_TIMEOUT_MS}ms`);
|
|
285
|
+
sendEpubDiagnostic('epub.load.timeout', {
|
|
286
|
+
...payload,
|
|
287
|
+
error: { message: error.message, stage },
|
|
288
|
+
durationMs: performance.now() - startedAt,
|
|
289
|
+
});
|
|
290
|
+
reject(error);
|
|
291
|
+
}, EPUB_STAGE_TIMEOUT_MS);
|
|
292
|
+
|
|
293
|
+
Promise.resolve(promise).then(
|
|
294
|
+
(value) => {
|
|
295
|
+
if (settled) return;
|
|
296
|
+
settled = true;
|
|
297
|
+
clearTimeout(timeout);
|
|
298
|
+
resolve(value);
|
|
299
|
+
},
|
|
300
|
+
(error) => {
|
|
301
|
+
if (settled) return;
|
|
302
|
+
settled = true;
|
|
303
|
+
clearTimeout(timeout);
|
|
304
|
+
reject(error);
|
|
305
|
+
},
|
|
306
|
+
);
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
const isEpubDiagnosticsEnabled = () => {
|
|
310
|
+
const configured = runtimeConfig.epubScrollDiagnostics;
|
|
311
|
+
return configured === true || configured === '1' || configured === 'true' ||
|
|
312
|
+
Boolean(window.__PAPYRUS_EPUB_SCROLL_DIAGNOSTICS__);
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const createEpubScrollDiagnostics = (manager) => {
|
|
316
|
+
if (!isEpubDiagnosticsEnabled()) return null;
|
|
317
|
+
|
|
318
|
+
const state = {
|
|
319
|
+
lastScrollTop: null,
|
|
320
|
+
lastTouchY: null,
|
|
321
|
+
lastTouchX: null,
|
|
322
|
+
lastInputDeltaY: 0,
|
|
323
|
+
touchActive: false,
|
|
324
|
+
selectionActive: false,
|
|
325
|
+
gestureLock: false,
|
|
326
|
+
consecutiveStallSamples: 0,
|
|
327
|
+
firstStallTimestamp: null,
|
|
328
|
+
stallReported: false,
|
|
329
|
+
};
|
|
330
|
+
const container = manager && manager.container;
|
|
331
|
+
|
|
332
|
+
const getSnapshot = () => {
|
|
333
|
+
const scrollTop = Number(container && container.scrollTop) || 0;
|
|
334
|
+
const scrollHeight = Number(container && container.scrollHeight) || 0;
|
|
335
|
+
const clientHeight = Number(container && container.clientHeight) || 0;
|
|
336
|
+
let scrollEnabled = true;
|
|
337
|
+
try {
|
|
338
|
+
const overflowY = window.getComputedStyle(container).overflowY;
|
|
339
|
+
scrollEnabled = overflowY !== 'hidden' && overflowY !== 'clip';
|
|
340
|
+
} catch (_) {}
|
|
341
|
+
return { scrollTop, scrollHeight, clientHeight, scrollEnabled };
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const emit = (name, payload) => {
|
|
345
|
+
sendEvent(name, {
|
|
346
|
+
timestamp: Date.now(),
|
|
347
|
+
documentType: currentType,
|
|
348
|
+
currentPage,
|
|
349
|
+
spineIndex: Math.max(0, currentPage - 1),
|
|
350
|
+
progress: pageCount > 0 ? currentPage / pageCount : null,
|
|
351
|
+
...(payload || {}),
|
|
352
|
+
});
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const resetStall = () => {
|
|
356
|
+
state.consecutiveStallSamples = 0;
|
|
357
|
+
state.firstStallTimestamp = null;
|
|
358
|
+
state.stallReported = false;
|
|
359
|
+
};
|
|
360
|
+
|
|
361
|
+
const recordScroll = (event) => {
|
|
362
|
+
const snapshot = getSnapshot();
|
|
363
|
+
const previousScrollTop = state.lastScrollTop === null
|
|
364
|
+
? snapshot.scrollTop
|
|
365
|
+
: state.lastScrollTop;
|
|
366
|
+
const deltaY = state.lastInputDeltaY;
|
|
367
|
+
const inputDirection = deltaY > 4 ? 'up' : deltaY < -4 ? 'down' : 'none';
|
|
368
|
+
const offsetUnchanged = Math.abs(snapshot.scrollTop - previousScrollTop) <= 1;
|
|
369
|
+
const validStallSample = inputDirection === 'up' &&
|
|
370
|
+
snapshot.scrollTop > 2 &&
|
|
371
|
+
snapshot.scrollEnabled &&
|
|
372
|
+
!state.selectionActive &&
|
|
373
|
+
!state.gestureLock &&
|
|
374
|
+
offsetUnchanged;
|
|
375
|
+
|
|
376
|
+
if (validStallSample) {
|
|
377
|
+
state.consecutiveStallSamples += 1;
|
|
378
|
+
if (state.firstStallTimestamp === null) {
|
|
379
|
+
state.firstStallTimestamp = Date.now();
|
|
380
|
+
}
|
|
381
|
+
const durationMs = Date.now() - state.firstStallTimestamp;
|
|
382
|
+
if (!state.stallReported && state.consecutiveStallSamples >= 3 && durationMs >= 150) {
|
|
383
|
+
state.stallReported = true;
|
|
384
|
+
emit('epub.scroll.stall', {
|
|
385
|
+
...snapshot,
|
|
386
|
+
previousScrollTop,
|
|
387
|
+
deltaY,
|
|
388
|
+
direction: 'up',
|
|
389
|
+
consecutiveSamples: state.consecutiveStallSamples,
|
|
390
|
+
durationMs,
|
|
391
|
+
selectionActive: state.selectionActive,
|
|
392
|
+
gestureLock: state.gestureLock,
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
} else if (!offsetUnchanged || inputDirection !== 'up') {
|
|
396
|
+
resetStall();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
state.lastScrollTop = snapshot.scrollTop;
|
|
400
|
+
emit('epub.scroll', {
|
|
401
|
+
...snapshot,
|
|
402
|
+
previousScrollTop,
|
|
403
|
+
deltaY,
|
|
404
|
+
direction: snapshot.scrollTop < previousScrollTop ? 'up' :
|
|
405
|
+
snapshot.scrollTop > previousScrollTop ? 'down' : 'none',
|
|
406
|
+
touchActive: state.touchActive,
|
|
407
|
+
selectionActive: state.selectionActive,
|
|
408
|
+
gestureLock: state.gestureLock,
|
|
409
|
+
});
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
setSelectionActive(active) {
|
|
414
|
+
state.selectionActive = Boolean(active);
|
|
415
|
+
emit('epub.selection', { selectionActive: state.selectionActive });
|
|
416
|
+
},
|
|
417
|
+
setGestureLock(active) {
|
|
418
|
+
state.gestureLock = Boolean(active);
|
|
419
|
+
emit('epub.gesture-lock', { gestureLock: state.gestureLock });
|
|
420
|
+
},
|
|
421
|
+
touchStart(event) {
|
|
422
|
+
const touch = event && event.touches && event.touches[0];
|
|
423
|
+
state.touchActive = true;
|
|
424
|
+
state.lastTouchY = touch ? Number(touch.clientY) : null;
|
|
425
|
+
state.lastTouchX = touch ? Number(touch.clientX) : null;
|
|
426
|
+
state.lastInputDeltaY = 0;
|
|
427
|
+
resetStall();
|
|
428
|
+
emit('epub.touch.start', { x: state.lastTouchX, y: state.lastTouchY });
|
|
429
|
+
},
|
|
430
|
+
touchMove(event) {
|
|
431
|
+
const touch = event && event.touches && event.touches[0];
|
|
432
|
+
if (!touch) return;
|
|
433
|
+
const previousY = state.lastTouchY;
|
|
434
|
+
const previousX = state.lastTouchX;
|
|
435
|
+
state.lastTouchY = Number(touch.clientY);
|
|
436
|
+
state.lastTouchX = Number(touch.clientX);
|
|
437
|
+
state.lastInputDeltaY = previousY === null ? 0 : previousY - state.lastTouchY;
|
|
438
|
+
emit('epub.touch.move', {
|
|
439
|
+
x: state.lastTouchX,
|
|
440
|
+
y: state.lastTouchY,
|
|
441
|
+
deltaY: previousY === null ? 0 : previousY - state.lastTouchY,
|
|
442
|
+
deltaX: previousX === null ? 0 : previousX - state.lastTouchX,
|
|
443
|
+
});
|
|
444
|
+
recordScroll();
|
|
445
|
+
},
|
|
446
|
+
touchEnd(event) {
|
|
447
|
+
const touch = event && event.changedTouches && event.changedTouches[0];
|
|
448
|
+
state.touchActive = false;
|
|
449
|
+
emit('epub.touch.end', {
|
|
450
|
+
x: touch ? Number(touch.clientX) : state.lastTouchX,
|
|
451
|
+
y: touch ? Number(touch.clientY) : state.lastTouchY,
|
|
452
|
+
});
|
|
453
|
+
state.lastTouchY = null;
|
|
454
|
+
state.lastTouchX = null;
|
|
455
|
+
state.lastInputDeltaY = 0;
|
|
456
|
+
resetStall();
|
|
457
|
+
},
|
|
458
|
+
scroll: recordScroll,
|
|
459
|
+
relocated(location) {
|
|
460
|
+
emit('epub.relocated', {
|
|
461
|
+
href: location && location.start ? location.start.href || null : null,
|
|
462
|
+
cfi: location && location.start ? location.start.cfi || null : null,
|
|
463
|
+
});
|
|
464
|
+
},
|
|
465
|
+
settled(event) {
|
|
466
|
+
emit('epub.momentum.settled', {
|
|
467
|
+
scrollTop: Number(event && event.top) || 0,
|
|
468
|
+
});
|
|
469
|
+
},
|
|
470
|
+
command(name, payload) {
|
|
471
|
+
emit('epub.programmatic-scroll', { command: name, ...(payload || {}) });
|
|
472
|
+
},
|
|
473
|
+
};
|
|
474
|
+
};
|
|
475
|
+
|
|
249
476
|
const reportError = (error, context) => {
|
|
250
477
|
const message = error && error.message ? error.message : String(error);
|
|
251
478
|
const stack = error && error.stack ? error.stack : null;
|
|
@@ -296,42 +523,98 @@
|
|
|
296
523
|
};
|
|
297
524
|
|
|
298
525
|
const clearViewer = () => {
|
|
526
|
+
if (epubInteractionCleanup) {
|
|
527
|
+
epubInteractionCleanup();
|
|
528
|
+
epubInteractionCleanup = null;
|
|
529
|
+
}
|
|
530
|
+
epubSelectionCleanups.forEach((cleanup) => cleanup());
|
|
531
|
+
epubSelectionCleanups.clear();
|
|
299
532
|
clearComicState();
|
|
300
533
|
while (viewer.firstChild) {
|
|
301
534
|
viewer.removeChild(viewer.firstChild);
|
|
302
535
|
}
|
|
303
536
|
};
|
|
304
537
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
538
|
+
const decodeBase64 = (value) => {
|
|
539
|
+
const clean = value.replace(/\s/g, '');
|
|
540
|
+
const binary = atob(clean);
|
|
541
|
+
const len = binary.length;
|
|
542
|
+
const bytes = new Uint8Array(len);
|
|
310
543
|
for (let i = 0; i < len; i += 1) {
|
|
311
544
|
bytes[i] = binary.charCodeAt(i);
|
|
312
545
|
}
|
|
313
|
-
|
|
314
|
-
|
|
546
|
+
return bytes;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
const toExactArrayBuffer = (bytes) => {
|
|
550
|
+
if (
|
|
551
|
+
bytes.byteOffset === 0 &&
|
|
552
|
+
bytes.byteLength === bytes.buffer.byteLength
|
|
553
|
+
) {
|
|
554
|
+
return bytes.buffer;
|
|
555
|
+
}
|
|
556
|
+
return bytes.buffer.slice(
|
|
557
|
+
bytes.byteOffset,
|
|
558
|
+
bytes.byteOffset + bytes.byteLength
|
|
559
|
+
);
|
|
560
|
+
};
|
|
315
561
|
|
|
316
562
|
const decodeBase64ToText = (value) => {
|
|
317
563
|
const bytes = decodeBase64(value);
|
|
318
564
|
return new TextDecoder('utf-8').decode(bytes);
|
|
319
565
|
};
|
|
320
566
|
|
|
567
|
+
const isMetroAssetUri = (value) => {
|
|
568
|
+
try {
|
|
569
|
+
const url = new URL(value);
|
|
570
|
+
return (
|
|
571
|
+
(url.protocol === 'http:' || url.protocol === 'https:') &&
|
|
572
|
+
url.pathname.startsWith('/assets/') &&
|
|
573
|
+
url.searchParams.has('platform') &&
|
|
574
|
+
url.searchParams.has('hash')
|
|
575
|
+
);
|
|
576
|
+
} catch {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
|
|
321
581
|
const sourceToArrayBuffer = async (source) => {
|
|
322
582
|
if (source.kind === 'uri') {
|
|
323
|
-
if (/^(?:file|content):\/\//i.test(source.uri)) {
|
|
583
|
+
if (/^(?:file|content|android\.resource):\/\//i.test(source.uri)) {
|
|
324
584
|
return readLocalFile(source.uri);
|
|
325
585
|
}
|
|
586
|
+
const isEpubHttpSource = isMetroAssetUri(source.uri);
|
|
587
|
+
if (isEpubHttpSource) {
|
|
588
|
+
sendEpubDiagnostic('epub.source.fetch.start', {uri: source.uri});
|
|
589
|
+
}
|
|
326
590
|
const response = await fetch(source.uri);
|
|
591
|
+
if (isEpubHttpSource) {
|
|
592
|
+
sendEpubDiagnostic('epub.source.fetch.response', {
|
|
593
|
+
uri: source.uri,
|
|
594
|
+
status: response.status,
|
|
595
|
+
contentLength: response.headers.get('content-length'),
|
|
596
|
+
contentType: response.headers.get('content-type'),
|
|
597
|
+
transferEncoding: response.headers.get('transfer-encoding'),
|
|
598
|
+
});
|
|
599
|
+
}
|
|
327
600
|
if (!response.ok && response.status !== 0) {
|
|
328
601
|
throw new Error(`Falha ao carregar quadrinho (${response.status})`);
|
|
329
602
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
603
|
+
if (isEpubHttpSource) {
|
|
604
|
+
sendEpubDiagnostic('epub.source.body.start', {uri: source.uri});
|
|
605
|
+
}
|
|
606
|
+
const body = await response.arrayBuffer();
|
|
607
|
+
if (isEpubHttpSource) {
|
|
608
|
+
sendEpubDiagnostic('epub.source.body.end', {
|
|
609
|
+
uri: source.uri,
|
|
610
|
+
byteLength: body.byteLength,
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
return body;
|
|
614
|
+
}
|
|
615
|
+
if (source.kind === 'base64') {
|
|
616
|
+
return toExactArrayBuffer(decodeBase64(source.data));
|
|
617
|
+
}
|
|
335
618
|
throw new Error('Fonte de quadrinho inválida.');
|
|
336
619
|
};
|
|
337
620
|
|
|
@@ -809,7 +1092,7 @@
|
|
|
809
1092
|
return pages.length > 0 ? pages : [''];
|
|
810
1093
|
};
|
|
811
1094
|
|
|
812
|
-
const loadText = async (source) => {
|
|
1095
|
+
const loadText = async (source, isCurrentLoad = () => true) => {
|
|
813
1096
|
let text = '';
|
|
814
1097
|
if (source.kind === 'uri') {
|
|
815
1098
|
const res = await fetch(source.uri);
|
|
@@ -820,6 +1103,9 @@
|
|
|
820
1103
|
text = source.text || '';
|
|
821
1104
|
}
|
|
822
1105
|
|
|
1106
|
+
if (!isCurrentLoad()) {
|
|
1107
|
+
throw new Error('Document load superseded by a newer load');
|
|
1108
|
+
}
|
|
823
1109
|
clearViewer();
|
|
824
1110
|
textPages = paginateText(text);
|
|
825
1111
|
pageCount = textPages.length;
|
|
@@ -829,7 +1115,18 @@
|
|
|
829
1115
|
return { pageCount };
|
|
830
1116
|
};
|
|
831
1117
|
|
|
832
|
-
const loadEpub = async (source) => {
|
|
1118
|
+
const loadEpub = async (source, loadId, generation) => {
|
|
1119
|
+
const diagnosticPayload = {
|
|
1120
|
+
loadId,
|
|
1121
|
+
sourceKind: source.kind,
|
|
1122
|
+
};
|
|
1123
|
+
const isCurrentLoad = () => generation === epubLoadGeneration;
|
|
1124
|
+
sendEpubDiagnostic('epub.load.start', diagnosticPayload);
|
|
1125
|
+
|
|
1126
|
+
if (epubInteractionCleanup) {
|
|
1127
|
+
epubInteractionCleanup();
|
|
1128
|
+
epubInteractionCleanup = null;
|
|
1129
|
+
}
|
|
833
1130
|
if (rendition && typeof rendition.destroy === 'function') {
|
|
834
1131
|
rendition.destroy();
|
|
835
1132
|
}
|
|
@@ -839,28 +1136,56 @@
|
|
|
839
1136
|
|
|
840
1137
|
let data = null;
|
|
841
1138
|
if (source.kind === 'uri') {
|
|
842
|
-
data =
|
|
1139
|
+
data = await withEpubStageTimeout(
|
|
1140
|
+
sourceToArrayBuffer(source),
|
|
1141
|
+
'source',
|
|
1142
|
+
diagnosticPayload,
|
|
1143
|
+
);
|
|
843
1144
|
} else if (source.kind === 'base64') {
|
|
844
|
-
|
|
1145
|
+
// epub.js expects an ArrayBuffer for binary books. Passing the decoded
|
|
1146
|
+
// Uint8Array directly can leave `book.ready` pending in Android WebView.
|
|
1147
|
+
data = toExactArrayBuffer(decodeBase64(source.data));
|
|
845
1148
|
} else if (source.kind === 'text') {
|
|
846
1149
|
const encoder = new TextEncoder();
|
|
847
1150
|
data = encoder.encode(source.text || '').buffer;
|
|
848
1151
|
}
|
|
849
1152
|
|
|
1153
|
+
if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');
|
|
1154
|
+
sendEpubDiagnostic('epub.source.received', {
|
|
1155
|
+
...diagnosticPayload,
|
|
1156
|
+
byteLength: data?.byteLength,
|
|
1157
|
+
});
|
|
1158
|
+
sendEpubDiagnostic('epub.source.decoded', {
|
|
1159
|
+
...diagnosticPayload,
|
|
1160
|
+
byteLength: data?.byteLength,
|
|
1161
|
+
});
|
|
850
1162
|
book = ePub(data);
|
|
851
|
-
|
|
1163
|
+
sendEpubDiagnostic('epub.book.created', {
|
|
1164
|
+
...diagnosticPayload,
|
|
1165
|
+
byteLength: data?.byteLength,
|
|
1166
|
+
});
|
|
1167
|
+
sendEpubDiagnostic('epub.book.ready.start', diagnosticPayload);
|
|
1168
|
+
await withEpubStageTimeout(book.ready, 'book.ready', diagnosticPayload);
|
|
1169
|
+
if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');
|
|
1170
|
+
sendEpubDiagnostic('epub.book.ready.end', diagnosticPayload);
|
|
852
1171
|
|
|
853
1172
|
spineItems = book.spine && book.spine.items ? book.spine.items : [];
|
|
854
1173
|
pageCount = spineItems.length;
|
|
855
1174
|
currentPage = 1;
|
|
1175
|
+
sendEpubDiagnostic('epub.spine.ready', {
|
|
1176
|
+
...diagnosticPayload,
|
|
1177
|
+
pageCount,
|
|
1178
|
+
});
|
|
856
1179
|
|
|
857
1180
|
clearViewer();
|
|
858
1181
|
rendition = book.renderTo(viewer, {
|
|
1182
|
+
manager: 'continuous',
|
|
859
1183
|
width: '100%',
|
|
860
1184
|
height: '100%',
|
|
861
|
-
flow: '
|
|
1185
|
+
flow: 'scrolled-continuous',
|
|
862
1186
|
spread: 'none',
|
|
863
1187
|
});
|
|
1188
|
+
sendEpubDiagnostic('epub.rendition.created', diagnosticPayload);
|
|
864
1189
|
|
|
865
1190
|
if (rendition && rendition.hooks && rendition.hooks.content) {
|
|
866
1191
|
rendition.hooks.content.register((contents) => {
|
|
@@ -868,6 +1193,41 @@
|
|
|
868
1193
|
if (frame) {
|
|
869
1194
|
frame.setAttribute('sandbox', 'allow-scripts allow-same-origin');
|
|
870
1195
|
}
|
|
1196
|
+
const selectionDocument = contents && contents.document;
|
|
1197
|
+
const selectionWindow = contents && contents.window;
|
|
1198
|
+
if (
|
|
1199
|
+
selectionDocument &&
|
|
1200
|
+
typeof selectionDocument.addEventListener === 'function' &&
|
|
1201
|
+
selectionWindow &&
|
|
1202
|
+
typeof selectionWindow.getSelection === 'function'
|
|
1203
|
+
) {
|
|
1204
|
+
const handleSelectionChange = () => {
|
|
1205
|
+
const selection = selectionWindow.getSelection();
|
|
1206
|
+
const text = selection ? selection.toString().trim() : '';
|
|
1207
|
+
epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(Boolean(text));
|
|
1208
|
+
};
|
|
1209
|
+
selectionDocument.addEventListener('selectionchange', handleSelectionChange);
|
|
1210
|
+
const selectionCleanup = () => {
|
|
1211
|
+
selectionDocument.removeEventListener('selectionchange', handleSelectionChange);
|
|
1212
|
+
epubSelectionCleanups.delete(selectionCleanup);
|
|
1213
|
+
};
|
|
1214
|
+
epubSelectionCleanups.add(selectionCleanup);
|
|
1215
|
+
if (contents && typeof contents === 'object') {
|
|
1216
|
+
contents.__papyrusSelectionCleanup = selectionCleanup;
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
});
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
if (
|
|
1223
|
+
rendition &&
|
|
1224
|
+
rendition.hooks &&
|
|
1225
|
+
rendition.hooks.unloaded &&
|
|
1226
|
+
typeof rendition.hooks.unloaded.register === 'function'
|
|
1227
|
+
) {
|
|
1228
|
+
rendition.hooks.unloaded.register((view) => {
|
|
1229
|
+
const cleanup = view && view.contents && view.contents.__papyrusSelectionCleanup;
|
|
1230
|
+
if (typeof cleanup === 'function') cleanup();
|
|
871
1231
|
});
|
|
872
1232
|
}
|
|
873
1233
|
|
|
@@ -875,6 +1235,7 @@
|
|
|
875
1235
|
rendition.on('selected', (cfiRange, contents) => {
|
|
876
1236
|
const selection = contents && contents.window ? contents.window.getSelection() : null;
|
|
877
1237
|
const text = selection ? selection.toString().trim() : '';
|
|
1238
|
+
epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(Boolean(text));
|
|
878
1239
|
if (text) {
|
|
879
1240
|
sendEvent('TEXT_SELECTED', { text, pageIndex: Math.max(0, currentPage - 1) });
|
|
880
1241
|
}
|
|
@@ -882,12 +1243,177 @@
|
|
|
882
1243
|
rendition.annotations.remove(cfiRange, 'highlight');
|
|
883
1244
|
}
|
|
884
1245
|
});
|
|
1246
|
+
rendition.on('relocated', (location) => {
|
|
1247
|
+
const start = location && location.start;
|
|
1248
|
+
let index = start && Number.isInteger(start.index) ? start.index : -1;
|
|
1249
|
+
if (index < 0 && start && start.href) index = getSpineIndexByHref(start.href);
|
|
1250
|
+
if (index < 0 || index >= spineItems.length) return;
|
|
1251
|
+
epubScrollDiagnostics && epubScrollDiagnostics.relocated(location);
|
|
1252
|
+
const nextPage = index + 1;
|
|
1253
|
+
if (nextPage === currentPage) return;
|
|
1254
|
+
currentPage = nextPage;
|
|
1255
|
+
sendState();
|
|
1256
|
+
});
|
|
885
1257
|
}
|
|
886
1258
|
|
|
887
|
-
|
|
1259
|
+
sendEpubDiagnostic('epub.display.start', diagnosticPayload);
|
|
1260
|
+
await withEpubStageTimeout(
|
|
1261
|
+
displayEpubPage(0),
|
|
1262
|
+
'rendition.display',
|
|
1263
|
+
diagnosticPayload,
|
|
1264
|
+
);
|
|
1265
|
+
if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');
|
|
1266
|
+
sendEpubDiagnostic('epub.display.end', diagnosticPayload);
|
|
1267
|
+
if (
|
|
1268
|
+
rendition &&
|
|
1269
|
+
rendition.manager &&
|
|
1270
|
+
typeof rendition.manager.on === 'function' &&
|
|
1271
|
+
typeof rendition.on === 'function'
|
|
1272
|
+
) {
|
|
1273
|
+
let touchStart = null;
|
|
1274
|
+
let lastScrollOffset = -1;
|
|
1275
|
+
const manager = rendition.manager;
|
|
1276
|
+
epubScrollDiagnostics = createEpubScrollDiagnostics(manager);
|
|
1277
|
+
|
|
1278
|
+
// ContinuousManager schedules checks through its serial queue. Observe
|
|
1279
|
+
// the queue itself so diagnostics can distinguish queued work from the
|
|
1280
|
+
// check currently being executed without changing epub.js semantics.
|
|
1281
|
+
const originalCheck = typeof manager.check === 'function' ? manager.check : null;
|
|
1282
|
+
const queue = manager.q;
|
|
1283
|
+
const originalEnqueue = queue && typeof queue.enqueue === 'function'
|
|
1284
|
+
? queue.enqueue
|
|
1285
|
+
: null;
|
|
1286
|
+
let checkRequestId = 0;
|
|
1287
|
+
if (originalCheck) {
|
|
1288
|
+
manager.check = function (...args) {
|
|
1289
|
+
const requestId = ++checkRequestId;
|
|
1290
|
+
const checkContext = this;
|
|
1291
|
+
if (epubScrollDiagnostics) {
|
|
1292
|
+
epubScrollDiagnostics.command('manager.check.request', {
|
|
1293
|
+
requestId,
|
|
1294
|
+
queueLength: manager.q && typeof manager.q.length === 'function'
|
|
1295
|
+
? manager.q.length()
|
|
1296
|
+
: null,
|
|
1297
|
+
});
|
|
1298
|
+
}
|
|
1299
|
+
epubScrollDiagnostics && epubScrollDiagnostics.command('manager.check.start', {
|
|
1300
|
+
requestId,
|
|
1301
|
+
queueLength: queue && typeof queue.length === 'function'
|
|
1302
|
+
? queue.length()
|
|
1303
|
+
: null,
|
|
1304
|
+
});
|
|
1305
|
+
let result;
|
|
1306
|
+
try {
|
|
1307
|
+
result = originalCheck.apply(checkContext, args);
|
|
1308
|
+
} catch (error) {
|
|
1309
|
+
epubScrollDiagnostics && epubScrollDiagnostics.command('manager.check.end', {
|
|
1310
|
+
requestId,
|
|
1311
|
+
status: 'error',
|
|
1312
|
+
message: error && error.message ? error.message : String(error),
|
|
1313
|
+
});
|
|
1314
|
+
throw error;
|
|
1315
|
+
}
|
|
1316
|
+
return Promise.resolve(result)
|
|
1317
|
+
.then(
|
|
1318
|
+
(result) => {
|
|
1319
|
+
epubScrollDiagnostics && epubScrollDiagnostics.command('manager.check.end', {
|
|
1320
|
+
requestId,
|
|
1321
|
+
status: 'ready',
|
|
1322
|
+
});
|
|
1323
|
+
return result;
|
|
1324
|
+
},
|
|
1325
|
+
(error) => {
|
|
1326
|
+
epubScrollDiagnostics && epubScrollDiagnostics.command('manager.check.end', {
|
|
1327
|
+
requestId,
|
|
1328
|
+
status: 'error',
|
|
1329
|
+
message: error && error.message ? error.message : String(error),
|
|
1330
|
+
});
|
|
1331
|
+
throw error;
|
|
1332
|
+
},
|
|
1333
|
+
);
|
|
1334
|
+
};
|
|
1335
|
+
}
|
|
1336
|
+
if (originalEnqueue) {
|
|
1337
|
+
queue.enqueue = function (...args) {
|
|
1338
|
+
const queueLengthBefore = typeof queue.length === 'function' ? queue.length() : null;
|
|
1339
|
+
const result = originalEnqueue.apply(this, args);
|
|
1340
|
+
epubScrollDiagnostics && epubScrollDiagnostics.command('manager.queue.enqueue', {
|
|
1341
|
+
queueLengthBefore,
|
|
1342
|
+
queueLengthAfter: typeof queue.length === 'function' ? queue.length() : null,
|
|
1343
|
+
running: Boolean(queue.running),
|
|
1344
|
+
});
|
|
1345
|
+
return result;
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
const getTouchPoint = (event) => {
|
|
1349
|
+
const touch =
|
|
1350
|
+
(event && event.changedTouches && event.changedTouches[0]) ||
|
|
1351
|
+
(event && event.touches && event.touches[0]);
|
|
1352
|
+
if (!touch) return null;
|
|
1353
|
+
const x = Number(touch.screenX ?? touch.clientX);
|
|
1354
|
+
const y = Number(touch.screenY ?? touch.clientY);
|
|
1355
|
+
return Number.isFinite(x) && Number.isFinite(y) ? { x, y } : null;
|
|
1356
|
+
};
|
|
1357
|
+
const handleScroll = (event) => {
|
|
1358
|
+
const offsetY = Math.max(0, Number(event && event.top) || 0);
|
|
1359
|
+
epubScrollDiagnostics && epubScrollDiagnostics.scroll(event);
|
|
1360
|
+
if (Math.abs(offsetY - lastScrollOffset) < 1) return;
|
|
1361
|
+
lastScrollOffset = offsetY;
|
|
1362
|
+
sendEvent('VIEWER_SCROLL', { offsetY });
|
|
1363
|
+
};
|
|
1364
|
+
const handleTouchStart = (event) => {
|
|
1365
|
+
touchStart = getTouchPoint(event);
|
|
1366
|
+
epubScrollDiagnostics && epubScrollDiagnostics.touchStart(event);
|
|
1367
|
+
};
|
|
1368
|
+
const handleTouchMove = (event) => {
|
|
1369
|
+
epubScrollDiagnostics && epubScrollDiagnostics.touchMove(event);
|
|
1370
|
+
};
|
|
1371
|
+
const handleTouchEnd = (event) => {
|
|
1372
|
+
const end = getTouchPoint(event);
|
|
1373
|
+
const start = touchStart;
|
|
1374
|
+
touchStart = null;
|
|
1375
|
+
if (start && end && Math.hypot(end.x - start.x, end.y - start.y) <= 12) {
|
|
1376
|
+
sendEvent('VIEWER_TAP', {});
|
|
1377
|
+
}
|
|
1378
|
+
epubScrollDiagnostics && epubScrollDiagnostics.touchEnd(event);
|
|
1379
|
+
};
|
|
1380
|
+
|
|
1381
|
+
manager.on('scroll', handleScroll);
|
|
1382
|
+
const handleScrolled = (event) => {
|
|
1383
|
+
epubScrollDiagnostics && epubScrollDiagnostics.settled(event);
|
|
1384
|
+
};
|
|
1385
|
+
manager.on('scrolled', handleScrolled);
|
|
1386
|
+
rendition.on('touchstart', handleTouchStart);
|
|
1387
|
+
rendition.on('touchmove', handleTouchMove);
|
|
1388
|
+
rendition.on('touchend', handleTouchEnd);
|
|
1389
|
+
epubInteractionCleanup = () => {
|
|
1390
|
+
epubSelectionCleanups.forEach((cleanup) => cleanup());
|
|
1391
|
+
epubSelectionCleanups.clear();
|
|
1392
|
+
epubScrollDiagnostics && epubScrollDiagnostics.setSelectionActive(false);
|
|
1393
|
+
if (originalCheck) manager.check = originalCheck;
|
|
1394
|
+
if (originalEnqueue) queue.enqueue = originalEnqueue;
|
|
1395
|
+
if (typeof manager.off === 'function') manager.off('scroll', handleScroll);
|
|
1396
|
+
if (typeof manager.off === 'function') manager.off('scrolled', handleScrolled);
|
|
1397
|
+
if (typeof rendition.off === 'function') {
|
|
1398
|
+
rendition.off('touchstart', handleTouchStart);
|
|
1399
|
+
rendition.off('touchmove', handleTouchMove);
|
|
1400
|
+
rendition.off('touchend', handleTouchEnd);
|
|
1401
|
+
}
|
|
1402
|
+
epubScrollDiagnostics = null;
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
888
1405
|
applyEpubZoom();
|
|
889
1406
|
|
|
890
|
-
const outline = await
|
|
1407
|
+
const outline = await withEpubStageTimeout(
|
|
1408
|
+
buildOutline(),
|
|
1409
|
+
'outline',
|
|
1410
|
+
diagnosticPayload,
|
|
1411
|
+
);
|
|
1412
|
+
if (!isCurrentLoad()) throw new Error('EPUB load superseded by a newer load');
|
|
1413
|
+
sendEpubDiagnostic('epub.load.ready', {
|
|
1414
|
+
...diagnosticPayload,
|
|
1415
|
+
pageCount,
|
|
1416
|
+
});
|
|
891
1417
|
return { pageCount, outline };
|
|
892
1418
|
};
|
|
893
1419
|
|
|
@@ -896,6 +1422,10 @@
|
|
|
896
1422
|
const item = spineItems[pageIndex];
|
|
897
1423
|
if (!item) return;
|
|
898
1424
|
const target = item.href || item.idref || item.cfiBase || pageIndex;
|
|
1425
|
+
epubScrollDiagnostics && epubScrollDiagnostics.command('display', {
|
|
1426
|
+
target,
|
|
1427
|
+
pageIndex,
|
|
1428
|
+
});
|
|
899
1429
|
await rendition.display(target);
|
|
900
1430
|
currentPage = pageIndex + 1;
|
|
901
1431
|
sendState();
|
|
@@ -1004,18 +1534,33 @@
|
|
|
1004
1534
|
|
|
1005
1535
|
try {
|
|
1006
1536
|
if (kind === 'load') {
|
|
1537
|
+
const generation = (epubLoadGeneration += 1);
|
|
1007
1538
|
currentType = payload.type;
|
|
1008
1539
|
zoom = 1.0;
|
|
1009
1540
|
|
|
1010
1541
|
if (currentType === 'text') {
|
|
1011
|
-
const result = await loadText(
|
|
1542
|
+
const result = await loadText(
|
|
1543
|
+
payload.source,
|
|
1544
|
+
() => generation === epubLoadGeneration,
|
|
1545
|
+
);
|
|
1546
|
+
if (generation !== epubLoadGeneration) {
|
|
1547
|
+
throw new Error('Document load superseded by a newer load');
|
|
1548
|
+
}
|
|
1549
|
+
sendEvent('document.ready', {
|
|
1550
|
+
loadId: id,
|
|
1551
|
+
pageCount: result.pageCount,
|
|
1552
|
+
});
|
|
1012
1553
|
sendState();
|
|
1013
1554
|
sendResponse(id, true, result);
|
|
1014
1555
|
return;
|
|
1015
1556
|
}
|
|
1016
1557
|
|
|
1017
1558
|
if (currentType === 'epub') {
|
|
1018
|
-
const result = await loadEpub(payload.source);
|
|
1559
|
+
const result = await loadEpub(payload.source, id, generation);
|
|
1560
|
+
sendEvent('document.ready', {
|
|
1561
|
+
loadId: id,
|
|
1562
|
+
pageCount: result.pageCount,
|
|
1563
|
+
});
|
|
1019
1564
|
sendState({ outline: result.outline || [] });
|
|
1020
1565
|
sendResponse(id, true, result);
|
|
1021
1566
|
return;
|
|
@@ -1132,7 +1677,28 @@
|
|
|
1132
1677
|
|
|
1133
1678
|
sendResponse(id, false, null, 'Unknown command');
|
|
1134
1679
|
} catch (err) {
|
|
1135
|
-
|
|
1680
|
+
const errorMessage = err && err.message ? err.message : String(err);
|
|
1681
|
+
const isSuperseded = /superseded by a newer load/i.test(errorMessage);
|
|
1682
|
+
if (kind === 'load' && isSuperseded) {
|
|
1683
|
+
sendEvent('document.stale', { loadId: id, error: errorMessage });
|
|
1684
|
+
} else if (kind === 'load') {
|
|
1685
|
+
sendEvent('document.error', {
|
|
1686
|
+
loadId: id,
|
|
1687
|
+
error: { message: errorMessage, stack: err?.stack || null },
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
if (kind === 'load' && payload?.type === 'epub') {
|
|
1691
|
+
sendEpubDiagnostic('epub.load.error', {
|
|
1692
|
+
loadId: id,
|
|
1693
|
+
sourceKind: payload.source?.kind,
|
|
1694
|
+
error: { message: errorMessage, stack: err?.stack || null },
|
|
1695
|
+
});
|
|
1696
|
+
sendEvent('document.error', {
|
|
1697
|
+
loadId: id,
|
|
1698
|
+
error: { message: errorMessage, stack: err?.stack || null },
|
|
1699
|
+
});
|
|
1700
|
+
}
|
|
1701
|
+
sendResponse(id, false, null, errorMessage);
|
|
1136
1702
|
}
|
|
1137
1703
|
};
|
|
1138
1704
|
|