From 1b79c369ca73b36874eec447da8eb9269cd4a6b1 Mon Sep 17 00:00:00 2001 From: Research Assistant Date: Wed, 20 May 2026 18:14:32 +0800 Subject: [PATCH] feat: add PDF.js internal overlay guards and tests --- paperforge/plugin/src/testable.js | 40 +++++++++++++++++++++ paperforge/plugin/tests/errors.test.mjs | 47 +++++++++++++++++++++++++ 2 files changed, 87 insertions(+) diff --git a/paperforge/plugin/src/testable.js b/paperforge/plugin/src/testable.js index 9266d085..f5902b1c 100644 --- a/paperforge/plugin/src/testable.js +++ b/paperforge/plugin/src/testable.js @@ -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, diff --git a/paperforge/plugin/tests/errors.test.mjs b/paperforge/plugin/tests/errors.test.mjs index c2cf61bd..bd5acaf9 100644 --- a/paperforge/plugin/tests/errors.test.mjs +++ b/paperforge/plugin/tests/errors.test.mjs @@ -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); + }); +});