fix(viewer): suppress stale PDF render publication - #323
Conversation
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughPDF 인라인 렌더링이 ChangesPDF 렌더링 취소 처리
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Polling
participant Viewer
participant PDFjs
participant PreviewDOM
Polling->>Viewer: PDF 렌더링 요청 및 AbortSignal 전달
Viewer->>PDFjs: PDF 로드, 페이지 조회, 캔버스 렌더링
Polling->>Viewer: 렌더링 후 취소 상태 확인
Viewer-->>Polling: false 반환
Polling-->>PreviewDOM: 링크와 완료 상태를 게시하지 않음
Possibly related issues
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@opencode-agent @cwl-noema-review Please independently review exact current head |
|
@coderabbitai review |
|
@coderabbitai review Please review exact current head |
|
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
head_sha="$(git rev-parse HEAD)"
base_sha="83ec6f7fe2b04bdcd28bf98ec350e41e55730a18"
expected_head="79689f928d8552c65dbdecf85ed45e2e9a6f1109"
printf 'HEAD=%s\nEXPECTED_HEAD=%s\nBASE=%s\n' "$head_sha" "$expected_head" "$base_sha"
git diff --check "${base_sha}..${expected_head}"
git diff --stat "${base_sha}..${expected_head}"
git diff --name-status "${base_sha}..${expected_head}"
printf '\n== Locate candidate files ==\n'
fd -HI '^(viewer\.js|test_viewer_render_cancellation\.py)$' . \
-E .git -E node_modules -E dist -E build || true
printf '\n== JavaScript structure ==\n'
viewer_file="$(fd -HI '^viewer\.js$' . -E .git -E node_modules -E dist -E build | head -n 1)"
if [ -n "$viewer_file" ]; then
ast-grep outline "$viewer_file" --items all --type function --match 'renderPdfInline|poll' --view expanded || true
printf '\n== Relevant source occurrences ==\n'
rg -n -C 16 -P '\b(renderPdfInline|poll)\s*\(|\.render\s*\(|appendChild\s*\(|textContent\s*=|innerHTML\s*=|ready|preview|abortSignal|aborted)\b' "$viewer_file"
fi
printf '\n== Exact source diff ==\n'
git diff --find-renames --find-copies --unified=80 "${base_sha}..${expected_head}" -- \
':(glob)**/viewer.js' \
':(glob)**/test_viewer_render_cancellation.py'Length of output: 2440 🏁 Script executed: #!/bin/bash
set -euo pipefail
base_sha="83ec6f7fe2b04bdcd28bf98ec350e41e55730a18"
head_sha="79689f928d8552c65dbdecf85ed45e2e9a6f1109"
viewer="src/main/resources/static/assets/viewer/viewer.js"
test_file="scripts/test_viewer_render_cancellation.py"
printf '%s\n' '== viewer.js: imports, state, and renderPdfInline =='
sed -n '1,235p' "$viewer" | cat -n
printf '%s\n' '== viewer.js: poll and subsequent state publication =='
sed -n '236,430p' "$viewer" | cat -n
printf '%s\n' '== Regression test =='
cat -n "$test_file"
printf '%s\n' '== Exact diff =='
git diff --check "${base_sha}..${head_sha}"
git diff --unified=60 "${base_sha}..${head_sha}" -- "$viewer" "$test_file"
printf '%s\n' '== All DOM/state publication calls in viewer.js =='
rg -n -C 5 -e 'appendChild\s*\(' -e 'replaceChildren\s*\(' -e '\.textContent\s*=' \
-e '\.innerHTML\s*=' -e 'setAttribute\s*\(' -e 'updateStatus\s*\(' \
-e 'renderPdfInline\s*\(' -e 'await\s+' "$viewer"Length of output: 42342
No findings.
The Node regression holds the PDF.js render promise, aborts the operation, completes the old render, and verifies zero preview children. This test covers stale canvas and metadata publication. The change does not modify the separate signed external-artifact path or
|
d7a3342 to
4ea2cce
Compare
|
@opencode-agent @cwl-noema-review Please independently review exact current head |
Pull request was converted to draft
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
scripts/test_viewer_render_cancellation.py (1)
151-156: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win마이크로태스크 개수에 의존하면 검증 대상 경로가 달라집니다.
renderPdfInline은 abort 전에getPdfJsModule(),loadingTask.promise,pdfDocument.getPage(1)을 순차로 await합니다.getPage가async함수이므로 필요한 마이크로태스크 수가 두 번을 초과할 수 있습니다. 이 경우 abort는 Line 160의getPage이후 확인에서 감지됩니다. assert는 통과하지만 렌더 완료 후 게시 억제 경로(viewer.js Line 183-186)는 검증되지 않습니다.
render()호출 시점을 관측 가능한 신호로 만들고 그 신호를 await한 후 abort하십시오. 그러면 검증 대상 경로가 결정됩니다.♻️ 제안 수정
+ let renderStarted; + const renderStartedPromise = new Promise(resolve => { + renderStarted = resolve; + }); let resolveRender; const renderPromise = new Promise(resolve => { resolveRender = resolve; }); const pdfDocument = { numPages: 1, async getPage() { return { getViewport({ scale }) { return { width: 100 * scale, height: 200 * scale }; }, render() { + renderStarted(); return { promise: renderPromise }; }, }; }, async destroy() {}, };- // Let getDocument/getPage reach the deliberately unresolved render promise. - await Promise.resolve(); - await Promise.resolve(); + // Wait until page.render() actually starts, then supersede the operation. + await renderStartedPromise; controller.abort(); resolveRender(); - await rendering; + assert.equal(await rendering, false, "an aborted render must report failure");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@scripts/test_viewer_render_cancellation.py` around lines 151 - 156, Update the cancellation test around renderPdfInline so it observes a signal when render() is invoked, awaits that signal, and only then calls controller.abort() and resolveRender(). Remove the fixed double Promise.resolve() scheduling, ensuring the test deterministically exercises the post-render publication-suppression path rather than relying on getDocument/getPage microtask timing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@scripts/test_viewer_render_cancellation.py`:
- Around line 171-186: Convert the module-level
test_superseded_pdf_render_does_not_publish function into a method on a
unittest.TestCase subclass, preserving its existing Node.js harness execution
and assertions. Ensure the module imports unittest and the resulting test class
and method are discoverable by python3 -m unittest discover -s scripts.
---
Nitpick comments:
In `@scripts/test_viewer_render_cancellation.py`:
- Around line 151-156: Update the cancellation test around renderPdfInline so it
observes a signal when render() is invoked, awaits that signal, and only then
calls controller.abort() and resolveRender(). Remove the fixed double
Promise.resolve() scheduling, ensuring the test deterministically exercises the
post-render publication-suppression path rather than relying on
getDocument/getPage microtask timing.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: c609e833-b01a-4e9c-a495-1ef3b4e6b369
📒 Files selected for processing (2)
scripts/test_viewer_render_cancellation.pysrc/main/resources/static/assets/viewer/viewer.js
|
Exact-current-head read-only review request for @opencode-agent review |
Rate Limit Exceeded
|
|
@opencode-agent review Review exact head Review read-only; preserve independent reviewer identity. Do not mutate the branch, synthesize author approval, weaken protection, or merge. |
🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
base_sha="eb0a32e87e7470469c8fa0f8c67d9583654bef57"
head_sha="27689534a1c22ca088b4261225afb75db0009635"
printf 'checked_out_head=%s\nrequested_head=%s\nrequested_base=%s\n' \
"$(git rev-parse HEAD)" "$head_sha" "$base_sha"
git cat-file -e "${base_sha}^{commit}"
git cat-file -e "${head_sha}^{commit}"
printf '\n== Range integrity ==\n'
git merge-base --is-ancestor "$base_sha" "$head_sha"
git diff --check "${base_sha}..${head_sha}"
git diff --stat "${base_sha}..${head_sha}"
git diff --name-status "${base_sha}..${head_sha}"
printf '\n== Changed paths ==\n'
git diff --name-only "${base_sha}..${head_sha}"
printf '\n== Viewer structure ==\n'
viewer="$(fd -HI '^viewer\.js$' . -E .git -E node_modules -E dist -E build | head -n 1)"
printf 'viewer=%s\n' "$viewer"
ast-grep outline "$viewer" --items all --type function --match 'renderPdfInline|poll|start|init' --view expanded || true
printf '\n== Viewer publication and cancellation contexts ==\n'
rg -n -C 18 -P '\b(renderPdfInline|poll)\s*\(|await\s+|abortSignal|\.aborted|\.render\s*\(|appendChild\s*\(|replaceChildren\s*\(|textContent\s*=|innerHTML\s*=|updateStatus\s*\(' "$viewer"
printf '\n== Regression test candidates and test configuration ==\n'
fd -HI '^test_viewer_render_cancellation\.py$|^(pyproject\.toml|pytest\.ini|setup\.cfg|tox\.ini)$' . \
-E .git -E node_modules -E dist -E build \
-x sh -c 'echo "== $1 =="; cat -n "$1"' sh {}
printf '\n== Exact diff ==\n'
git diff --find-renames --find-copies --unified=100 "${base_sha}..${head_sha}" -- \
':(glob)**/viewer.js' \
':(glob)**/test_viewer_render_cancellation.py' \
':(glob)**/pyproject.toml' \
':(glob)**/pytest.ini' \
':(glob)**/setup.cfg' \
':(glob)**/tox.ini'Length of output: 36206 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '== Tracked automation and project files that can configure or invoke Python tests =='
fd -HI -t f . \
-E .git -E node_modules -E dist -E build \
| rg '(^|/)(\.github/workflows/[^/]+|Makefile|README\.md|pyproject\.toml|pytest\.ini|setup\.cfg|tox\.ini|.*\.(ya?ml|json|sh))$' \
| while IFS= read -r file; do
if rg -n -i -C 3 \
-e 'python[0-9.]*\s+-m\s+unittest' \
-e 'unittest\s+discover' \
-e 'test_viewer_render_cancellation' \
-e 'scripts/test_.*\.py' \
-e 'pytest' \
"$file"; then
printf '\n== %s ==\n' "$file"
rg -n -i -C 3 \
-e 'python[0-9.]*\s+-m\s+unittest' \
-e 'unittest\s+discover' \
-e 'test_viewer_render_cancellation' \
-e 'scripts/test_.*\.py' \
-e 'pytest' \
"$file"
fi
done
printf '%s\n' '== All tracked test modules under scripts =='
fd -HI -t f '^test_.*\.py$' scripts 2>/dev/null | sort
printf '%s\n' '== Repository-wide static references to the new test module =='
rg -n -C 3 -P '\btest_viewer_render_cancellation\b|python[0-9.]*\s+-m\s+unittest|unittest\s+discover' \
-g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**' .Length of output: 7383
No findings.
The Node regression waits until This range does not add active PDF.js render-task cancellation or loading-task destruction. That work remains outside this bounded slice and separate from child issue
|
|
Exact-current-head independent review request for CI, Security Scan, SAST Semgrep, and fuzz are terminal-success; every inline finding is resolved or outdated. The deterministic unittest/Node regression now observes actual render start and proves stale publication suppression. @opencode-agent review Review this exact head read-only. Do not mutate the branch, synthesize author approval, weaken protection, or merge. |
Rate Limit Exceeded
|
|
Exact-current-head independent review request for Freshly rechecked: CI, Security Scan, SAST Semgrep, and fuzz are terminal-success; mergeable is true; every inline review thread is resolved or outdated. Review this exact head only. Do not mutate the branch, synthesize author approval, or bypass protection. @coderabbitai review |
Rate Limit Exceeded
|
Objective
Prevent a superseded PDF.js render from publishing stale canvas, metadata, preview links, or terminal
Ready.state after a newer viewer operation starts.Exact current-head evidence
eb0a32e87e7470469c8fa0f8c67d9583654bef57;27689534a1c22ca088b4261225afb75db0009635;viewer.jsand the executable Node/unittest regression only;Rendering contract
The runtime checks cancellation around PDF.js module loading, document loading, page acquisition, render completion, and every subsequent DOM/publication boundary. The regression is discoverable through
python3 -m unittest discover -s scripts, waits for an observablerender()start rather than a guessed microtask count, supersedes the operation, and proves the stale canvas, metadata, links, and terminal status cannot publish.Scope boundary
This slice suppresses stale publication. It does not itself actively invoke
RenderTask.cancel()or destroy an unresolved loading task; child #445 owns that resource-cancellation delta and must be rebuilt on protected main after this parent integrates. Issue #322 remains open for rapid multi-generation/error-state coverage, signed-token lifecycle parity, terminal focus ownership, and unhandled-rejection proof.Merge gate
Keep this exact head unchanged. Auto-merge may act only after all live required checks remain terminal-success, zero valid unresolved findings remain, and a qualifying independent non-author approval is attached to this exact head. Automated evidence is not approval and predecessor-head evidence does not transfer.