feat: add PDF.js internal overlay guards and tests

This commit is contained in:
Research Assistant 2026-05-20 18:14:32 +08:00
parent 262541598f
commit 1b79c369ca
2 changed files with 87 additions and 0 deletions

View file

@ -206,6 +206,43 @@ function canUseAnnotationOverlay(app, isMobile) {
return true;
}
function hasPdfJsLayerAlignment(windowObj) {
try {
return !!(windowObj && windowObj.pdfjsLib && typeof windowObj.pdfjsLib.setLayerDimensions === 'function');
} catch { return false; }
}
function tryResolvePdfInternalViewer(view) {
if (!view) return null;
try {
// Try common internal property paths used by Obsidian's PDF viewer
var viewerComponent = view.viewer || view.pdfViewer || view.pdfViewerComponent;
if (!viewerComponent && view.constructor && view.constructor.prototype) {
var proto = view.constructor.prototype;
var desc = Object.getOwnPropertyDescriptor(proto, 'viewer')
|| Object.getOwnPropertyDescriptor(proto, 'pdfViewer');
if (desc) {
viewerComponent = typeof desc.get === 'function' ? desc.get.call(view) : (desc.value || (view[desc]));
}
}
if (!viewerComponent && view.pdfViewer) viewerComponent = view.pdfViewer;
if (!viewerComponent) return null;
// Check if pdfViewer has getPageView
var pdfViewer = viewerComponent.pdfViewer || viewerComponent;
if (pdfViewer && typeof pdfViewer.getPageView === 'function') return pdfViewer;
return null;
} catch { return null; }
}
function canRenderPdfInternalOverlayHandle(handle) {
if (!handle) return false;
try {
return typeof handle.getPageView === 'function'
&& typeof handle.currentScale === 'number'
&& typeof handle.pagesCount === 'number';
} catch { return false; }
}
// ── Bridge helpers ──
function runAnnotationSubprocess(vaultPath, pythonInfo, args, timeout, _spawn) {
@ -461,6 +498,9 @@ module.exports = {
toggleDisclosureState,
detectConflictingPlugins,
canUseAnnotationOverlay,
hasPdfJsLayerAlignment,
tryResolvePdfInternalViewer,
canRenderPdfInternalOverlayHandle,
runAnnotationSubprocess,
ANNOTATION_COLORS,
ANNOTATION_DEFAULT_COLOR,

View file

@ -9,6 +9,9 @@ const {
parseRuntimeStatus,
detectConflictingPlugins,
canUseAnnotationOverlay,
hasPdfJsLayerAlignment,
tryResolvePdfInternalViewer,
canRenderPdfInternalOverlayHandle,
} = await import('../src/testable.js');
describe('classifyError', () => {
@ -157,3 +160,47 @@ describe('canUseAnnotationOverlay', () => {
expect(canUseAnnotationOverlay(app, false)).toBe(false);
});
});
describe('hasPdfJsLayerAlignment', () => {
it('returns true when setLayerDimensions exists', () => {
expect(hasPdfJsLayerAlignment({ pdfjsLib: { setLayerDimensions: function () {} } })).toBe(true);
});
it('returns false when pdfjsLib is missing', () => {
expect(hasPdfJsLayerAlignment({})).toBe(false);
});
it('returns false for null', () => {
expect(hasPdfJsLayerAlignment(null)).toBe(false);
});
it('returns false when setLayerDimensions is not a function', () => {
expect(hasPdfJsLayerAlignment({ pdfjsLib: { setLayerDimensions: 42 } })).toBe(false);
});
});
describe('tryResolvePdfInternalViewer', () => {
it('returns null for null view', () => {
expect(tryResolvePdfInternalViewer(null)).toBeNull();
});
it('returns null when view has no accessible pdf viewer', () => {
expect(tryResolvePdfInternalViewer({})).toBeNull();
});
it('resolves getPageView from deep pdfViewer path', () => {
var view = {
viewer: { pdfViewer: { getPageView: function () {}, currentScale: 1, pagesCount: 10 } },
};
var handle = tryResolvePdfInternalViewer(view);
expect(handle).toBeTruthy();
expect(typeof handle.getPageView).toBe('function');
});
});
describe('canRenderPdfInternalOverlayHandle', () => {
it('returns true for valid handle', () => {
expect(canRenderPdfInternalOverlayHandle({ getPageView: function () {}, currentScale: 1, pagesCount: 10 })).toBe(true);
});
it('returns false for null', () => {
expect(canRenderPdfInternalOverlayHandle(null)).toBe(false);
});
it('returns false when getPageView is missing', () => {
expect(canRenderPdfInternalOverlayHandle({ currentScale: 1, pagesCount: 10 })).toBe(false);
});
});