fix: 修复异常页面的 TypeError,并兼容新版 /web/contest 比赛页面 - #1028
Conversation
在每个对 querySelector 结果取属性/调用方法的地方先判断是否为 null, 避免在 403/404 等页面结构不同时抛出 TypeError。 - 语句:前面加 if (xxx.querySelector(...) != null) - 条件:在条件中加 xxx.querySelector(...) != null && - 变量声明/赋值:为 null 时使用空值([] / "" / 0) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
Deploying xmoj-script-dev-channel with
|
| Latest commit: |
9840c3b
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://94145503.xmoj-script-dev-channel.pages.dev |
| Branch Preview URL: | https://claude-issue-1009-u2n0g5.xmoj-script-dev-channel.pages.dev |
Reviewer's GuideThis PR systematically hardens XMOJ.user.js against missing DOM nodes by guarding querySelector dereferences and supplying safe fallbacks across page-specific features, including parsed response documents. It addresses null-related TypeErrors but does not fix errors caused by indexed collections or deeper child chains, and browser validation remains necessary for both UI variants. Sequence diagram for guarded DOM updatessequenceDiagram
participant Script
participant DOM
participant Element
Script->>DOM: querySelector(selector)
DOM-->>Script: Element or null
alt element exists
Script->>Element: Set property or call method
else element missing
Script->>Script: Skip update or use fallback value
end
Flow diagram for null-safe DOM manipulationflowchart TD
A[Page or parsed response has varying DOM] --> B{querySelector returns an element?}
B -->|Yes| C[Read property or modify element]
B -->|No| D[Skip operation or use safe fallback]
D --> E[Continue page script without null TypeError]
C --> E
File-Level Changes
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
@zsTree0830 测试一下吧 |
There was a problem hiding this comment.
Hey - I've found 2 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="XMOJ.user.js" line_range="4397" />
<code_context>
const RequestID = ++CaptchaRequestID;
const CaptchaInput = document.querySelector("#vcode");
- document.querySelector("#CaptchaElement").style.display = "block";
+ if (document.querySelector("#CaptchaElement") != null) document.querySelector("#CaptchaElement").style.display = "block";
CaptchaInput.value = "";
SetCaptchaStatus(StatusMessage || "");
</code_context>
<issue_to_address>
**issue (broader_impact):** The new guard protects only `#CaptchaElement`; `RefreshCaptcha` still immediately executes `CaptchaInput.value = ""` even when `document.querySelector("#vcode")` returned `null`, so captcha refresh still throws the same null-property TypeError.
**Triggers:** When the submit page has no `#vcode` input but the captcha refresh path runs.
**Suggested fix:** Check `CaptchaInput` for null before accessing it, or return from `RefreshCaptcha` when the input is absent.
</issue_to_address>
### Comment 2
<location path="XMOJ.user.js" line_range="3914" />
<code_context>
return null;
}
const ParsedDocument = new DOMParser().parseFromString(Response, "text/html");
- let Rating = (parseInt(ParsedDocument.querySelector("#statics > tbody > tr:nth-child(4) > td:nth-child(2)").innerText.trim()) / parseInt(ParsedDocument.querySelector("#statics > tbody > tr:nth-child(3) > td:nth-child(2)").innerText.trim())).toFixed(3) * 1000;
</code_context>
<issue_to_address>
**issue (bug_risk):** When the outer container is absent, the new guard skips rebuilding the required DOM, but execution continues into code that assumes the rebuilt elements exist: rank handling indexes `HeaderCells[0]`, and the open-contest path passes a missing textarea from `querySelector` to `CodeMirror.fromTextArea`. The malformed page therefore still crashes instead of being safely ignored.
**Triggers:** When the page lacks the container matched by the guarded selector.
**Suggested fix:** Return from the page-specific branch when its required container is missing, or guard every downstream operation that depends on the generated DOM.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 2 findings to address first, and most changes only turn selector failures into no-ops, but the profile-edit path now substitutes empty strings for missing form fields and can send those values to the account-modification endpoint, potentially persisting incorrect account data. Reverting prevents further occurrences, but values already written would need to be repaired separately.
Blocking findings: XMOJ.user.js:4397, XMOJ.user.js:3914
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0303ba78d8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- 各页面在所需容器缺失时(如403/404页面)直接跳过处理, 避免后续代码使用未生成的元素(排名表头、CodeMirror 文本框等) - RefreshCaptcha: #vcode 缺失时直接返回 - 修改个人信息: 表单元素缺失时不提交,避免用空值覆盖用户信息 - 自动提交/刷AC率/获取数据: 找不到提交记录时跳过,避免提交错误代码或无限轮询 - 恢复发帖板块 ID 的原写法(前面已有 null 检查) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
- GetUserInfo: #statics 表格缺失时返回默认值且不写入缓存,避免索引空数组 - ImproveACRate: 没有解析到已通过的题目时保持按钮禁用,避免用无效题号提交 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
…ript-dev/XMOJ-Script into claude/issue-1009-u2n0g5
Sourcery withdrew this approval because the latest commits introduced blocking findings.
- 新版比赛应用(/web/*)不再运行旧版 DOM 改写和 Bootstrap 替换, 改为在页面异步渲染及站内切换后应用主题、倒计时、复制、讨论入口、 批量打开题目、代码阅读器等增强功能 - 主题在 Vue、接口数据和字体加载前同步应用,保留站点 Bootstrap 3 样式 - 比赛题号、题目列表和文件输入输出信息改为通过新版 API 获取, 题目切换器和比赛结束后回退提交不再依赖旧版比赛 HTML - 图片放大功能提取为 InitializeImageEnlarger,供新旧页面共用 - 新增 tests/contest-web.test.cjs 覆盖路由解析、题号映射和主题应用 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
Sourcery withdrew this approval because it has stopped reviewing this pull request.
|
Sourcery has withdrawn its approval of this pull request. It auto-reviews a pull request 5 times, and this push is past that limit, so the approval no longer reflects code Sourcery has read. Comment |
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
- /web 页面与旧版页面一样移除站点的 Bootstrap 3 样式,改用 Bootstrap 5 和脚本皮肤; 新版应用渲染的 Bootstrap 3 标记在每次渲染后映射为 Bootstrap 5 类名 (导航栏、下拉菜单、按钮、分页、徽章等),修复出现原始“Toggle navigation”按钮的问题 - 脚本添加的按钮使用旧版页面的 btn-outline-secondary / copy-btn 样式 - 新版顶部导航栏复用 NavbarStyler,NavbarStyler 移到脚本顶层以便共用 - ResetType 用户菜单提取为 CreateUserMenuItems / InitializeUserMenu,新旧页面共用 - Translate 与 RemoveUseless 在新版页面上与旧版行为一致 - 比赛列表创建者和标程列与旧版一样使用普通链接 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
…ript-dev/XMOJ-Script into claude/issue-1009-u2n0g5
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="XMOJ.user.js">
<violation number="1">
P1: This line renders the `GetNotice` response directly into `innerHTML` after removing `PurifyHTML`. Marked preserves raw HTML, so a malicious or compromised notice can inject event-handler or unsafe-link markup; restore sanitization before the mention replacement.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="XMOJ.user.js">
<violation number="1">
P1: This removes the sanitizer from notice rendering and allows HTML/event-handler XSS from `GetNotice` content. Keep `PurifyHTML` around `marked.parse` before applying the mention replacement.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
@sourcery-ai review |
|
Sorry @boomzero, your pull request is larger than the review limit of 150,000 diff characters |
The load-time hide used visibility: hidden on the body, and innerText skips invisible text, so the score captured before the reveal was empty. Hide with opacity instead and read the score with textContent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
…ript-dev/XMOJ-Script into claude/issue-1009-u2n0g5
zsTree0830
left a comment
There was a problem hiding this comment.
例如按钮等的适配没那么好欸,比例很奇怪
还有比赛题目页下面多出来老 UI 脚本没有的按钮 & 样例输入输出炸了
还有刷新时怎么 topbar 一直在缩小
…king - Problem page buttons are full size and outlined, as on the legacy page. - With RemoveUseless, hide the repeated buttons below the statement, as the legacy page does. - Sample input/output uses the legacy layout: side by side cards with the copy button on the right; file names are no longer oversized headings. - The early block now also removes old stylesheet links that were parsed before it ran. They set the root font size to 10px/14px until DOMContentLoaded, so the navbar shrank and grew back on reload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc
…ript-dev/XMOJ-Script into claude/issue-1009-u2n0g5
|
@zsTree0830 感谢测试!三个问题都已在 3a74844 中修复:
已在真实站点上对比旧版 Generated by Claude Code |
zsTree0830
left a comment
There was a problem hiding this comment.
我建议是和原来页面diff一下
包括页面:
- 比赛题目页
/web/contest/****** - 订正排名
/web/contest/******/rank-correct - 题目详情页
/web/contest/******/***
还有比赛列表的搜索
|
这两个渲染方式都不同,不可能完全一样啊 |
|
经测试,我其实觉得问题不大 |
|
只是觉得对手机端兼容性不太好 |
|
还有 |
|
原来 /web/contest 变成 SPA 了吗 |
What does this PR aim to accomplish?:
Closes #1009
Closes #1029
TypeErrors, which DebugMode shows as an "internal error" popup./web/contest/(a Vue app mounted on#app) break the script's navigation and layout. The site no longer links to the old contest pages.How does this PR accomplish the above?:
Error fixes (#1009)
querySelectorresult is checked fornull. Page branches that rebuild their own container skip the page when it is missing.GetUserInfo.reinfo.php) goes through every result group and skips elements without a test point's structure. This fixes the "Cannot read properties of undefined" error reported in the issue./web, which handles them itself.New contest pages (#1029)
/webgets the same Bootstrap 5 and skin setup as the legacy pages; the site's Bootstrap 3 CSS is removed there too.NavbarStyler), theResetTypeuser menu,TranslateandRemoveUseless.InitializeContestWebAppre-applies the script's enhancements after async renders and in-app route changes. Vue's own elements are never replaced./api/contest/...JSON API. The submit page's fallback to the original problem after a contest ends no longer reads legacy contest HTML.Old contest pages redirect to
/web, and their handlers are removedThe site no longer links to these pages. The script redirects them at document-start:
contest.php,contest.php?cid=X/web/contest,/web/contest/Xproblem.php?cid=X&pid=N/web/contest/X/<letter>problem_std.php,problem_solution.php…/std,…/solutioncontestrank-correct.php?cid=X/web/contest/X/rank-correctThe handlers for those pages are removed (about 450 lines).
problem.php?id=…,contestrank-oi.php,submitpage.phpandstatus.phpare still in use and unchanged. On ordinary problem pages the problem ID now comes fromid. It was previously read from the contest cache, which gavenull, so the I/O file name and the discussion count used the wrong problem.Loading: fewer flashes and layout shifts
color-scheme.main()starts, 4 s after DOMContentLoaded at the latest.devhad 2–4 jumps per load..phppages can still paint before a userscript manager injects the script.Other fixes
Tests
npm testrunstests/contest-web.test.cjs(9 tests), and a newTestworkflow runs it on pull requests and on pushes todevandmaster. The tests cover:tests/userscript-harness.cjsruns the script on the live site in Chromium the way a userscript manager does. It checks each/webpage, the redirects, legacy pages and both user menus, and saves screenshots. Credentials come only from environment variables. The script's backend and all submit/modify/login/logout POSTs are blocked. See the header of the file for usage.Verification
/webcontest pages, all six redirects, and the legacy problem and problem-set pages, in both the default and monochrome skins.By submitting this pull request, I confirm the following:
git rebase)🤖 Generated with Claude Code
https://claude.ai/code/session_0135Ggqqp6xktQZytLRJQ9nc