Skip to content

Fix Problem Switcher Not Update#930

Closed
def-WA2025 wants to merge 1 commit intomasterfrom
fix-problem-switcher-not-update
Closed

Fix Problem Switcher Not Update#930
def-WA2025 wants to merge 1 commit intomasterfrom
fix-problem-switcher-not-update

Conversation

@def-WA2025
Copy link
Member

@def-WA2025 def-WA2025 commented Mar 12, 2026

What does this PR aim to accomplish?

Fix *EX problems not display.

How does this PR accomplish the above?

Update list when click the refresh button. (The refresh button is located at the top of the problem switcher)


By submitting this pull request, I confirm the following:

  1. I have read and understood the contributor's guide, as well as this entire template. I understand which branch to base my commits and Pull Requests against.
  2. I have commented on my proposed changes within the code and I have tested my changes.
  3. I am willing to help maintain this change if there are issues with it later.
  4. It is compatible with the GNU General Public License v3.0
  5. I have squashed any insignificant commits. (git rebase)
  6. I have checked that another pull request for this purpose does not exist.
  7. I have considered and confirmed that this submission will be valuable to others.
  8. I accept that this submission may not be used, and the pull request can be closed at the will of the maintainer.
  9. I give this submission freely and claim no ownership to its content.

  • I have read the above and my PR is ready for review. Check this box to confirm

Summary by Sourcery

Bug Fixes:

  • Fix contest problem list not updating by reloading data from the contest page when needed.

Summary by cubic

Fixes the Problem Switcher not updating so new and EX problems show up after a manual refresh. Adds a refresh control that fetches the latest contest problem list and reloads the view.

  • Bug Fixes
    • Introduced GetContestProblemList to parse the contest page, cache results in localStorage, and optionally reload.
    • Added a "刷新" link in the Problem Switcher that updates the list and reloads the page.
    • Replaced inline fetch logic with the shared helper to prevent stale data.

Written for commit cb969d2. Summary will update on new commits.

@sourcery-ai
Copy link

sourcery-ai bot commented Mar 12, 2026

Reviewer's Guide

Refactors contest problem list retrieval into a reusable function and wires it into the problem switcher so that the list is persisted in localStorage and can be refreshed via a new UI control, fixing stale problem listings.

Sequence diagram for refreshed contest problem list via ProblemSwitcher

sequenceDiagram
    actor User
    participant Browser
    participant ProblemSwitcherUI
    participant GetContestProblemList
    participant XMOJServer
    participant LocalStorage

    User->>ProblemSwitcherUI: Click refresh link
    ProblemSwitcherUI->>GetContestProblemList: GetContestProblemList(true)
    GetContestProblemList->>XMOJServer: HTTP GET contest.php?cid
    XMOJServer-->>GetContestProblemList: HTML contest page
    GetContestProblemList->>GetContestProblemList: Parse HTML and build problemList
    GetContestProblemList->>LocalStorage: setItem(UserScript_Contest_cid_ProblemList, problemList)
    alt RefreshList is true
        GetContestProblemList->>Browser: location.reload()
        Browser->>Browser: Reload page
        Browser->>LocalStorage: getItem(UserScript_Contest_cid_ProblemList)
        Browser-->>Browser: ContestProblemList JSON
        Browser->>ProblemSwitcherUI: Build buttons from ContestProblemList
    end
Loading

Flow diagram for GetContestProblemList function

flowchart TD
    A[Start GetContestProblemList RefreshList] --> B[Build contest URL with cid from SearchParams]
    B --> C[fetch contest.php?cid]
    C --> D{Request succeeded and status 200?}
    D -->|No| J[End]
    D -->|Yes| E[Read response text]
    E --> F{Response contains forbidden message?}
    F -->|Yes| J
    F -->|No| G[Parse HTML with DOMParser]
    G --> H[Select #problemset tbody rows]
    H --> I[Iterate rows and build problemList with title and url]
    I --> K[localStorage.setItem UserScript_Contest_cid_ProblemList]
    K --> L{RefreshList is true?}
    L -->|No| J
    L -->|Yes| M[location.reload]
    M --> J[End]
Loading

File-Level Changes

Change Details Files
Extract contest problem list fetching/parsing into a reusable API on unsafeWindow and persist results to localStorage, optionally forcing a page reload after refresh.
  • Introduce async GetContestProblemList(RefreshList) on unsafeWindow that fetches the contest page, parses the problem table, builds a problem list, and stores it in localStorage under a contest-specific key.
  • Guard fetching/parsing with status and content checks to avoid processing private or not-started contests.
  • On successful refresh with RefreshList=true, trigger a full page reload so UI components are rebuilt from the updated list.
  • Wrap network and DOM parsing logic in a try/catch and log errors to console.
XMOJ.user.js
Wire the new contest problem list function into the existing problem switcher so it initializes from localStorage and exposes a manual refresh control.
  • Replace the inline fetch-and-parse logic used when ContestProblemList is null with a call to unsafeWindow.GetContestProblemList(false), then re-read from localStorage.
  • Add a centered "刷新" (Refresh) link at the top of the problem switcher that calls GetContestProblemList(true) to update the stored problem list and reload the page.
  • Continue to render problem switcher entries from the JSON-parsed ContestProblemList stored in localStorage.
XMOJ.user.js

Possibly linked issues

  • #: PR adds contest problem list refresh logic so the problem switcher updates correctly, resolving the reported bug

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@github-actions
Copy link
Contributor

请向dev分支提交pull request, 本pull request将被自动关闭

@hendragon-bot hendragon-bot bot added the user-script This issue or pull request is related to the main user script label Mar 12, 2026
@github-actions github-actions bot closed this Mar 12, 2026
@github-actions
Copy link
Contributor

请向dev分支提交pull request, 本pull request将被自动关闭

1 similar comment
@github-actions
Copy link
Contributor

请向dev分支提交pull request, 本pull request将被自动关闭

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 2 security issues, 2 other issues, and left some high level feedback:

Security issues:

  • User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities (link)
  • User controlled data in a problemSwitcher.innerHTML is an anti-pattern that can lead to XSS vulnerabilities (link)

General comments:

  • In main(), unsafeWindow.GetContestProblemList(false) is async but not awaited before immediately reading from localStorage, which can lead to a race where ContestProblemList is still null; consider awaiting the call or refactoring to use the returned data directly.
  • The inline onclick="GetContestProblemList(true)" handler couples HTML to a global function; it would be more robust to attach the click listener via JavaScript to avoid relying on a global name and to make the async handling explicit.
  • GetContestProblemList silently fails when the contest is private or not started and may leave ContestProblemList null, which will cause JSON.parse(ContestProblemList) to throw; consider guarding the parse or handling the no-data case more gracefully.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `main()`, `unsafeWindow.GetContestProblemList(false)` is async but not awaited before immediately reading from `localStorage`, which can lead to a race where `ContestProblemList` is still null; consider awaiting the call or refactoring to use the returned data directly.
- The inline `onclick="GetContestProblemList(true)"` handler couples HTML to a global function; it would be more robust to attach the click listener via JavaScript to avoid relying on a global name and to make the async handling explicit.
- `GetContestProblemList` silently fails when the contest is private or not started and may leave `ContestProblemList` null, which will cause `JSON.parse(ContestProblemList)` to throw; consider guarding the parse or handling the no-data case more gracefully.

## Individual Comments

### Comment 1
<location path="XMOJ.user.js" line_range="2296" />
<code_context>
-                                localStorage.setItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList", JSON.stringify(problemList));
-                                ContestProblemList = JSON.stringify(problemList);
-                            }
+                            unsafeWindow.GetContestProblemList(false);
+                            ContestProblemList = localStorage.getItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList");
                         }
</code_context>
<issue_to_address>
**issue (bug_risk):** The async `GetContestProblemList` call is not awaited before reading from `localStorage`, which can lead to a race condition.

Previously the fetch and `localStorage.setItem` completed inline before `ContestProblemList` was used. Now `GetContestProblemList` is `async`, but `unsafeWindow.GetContestProblemList(false);` is called without `await` and `localStorage` is read immediately afterward, so `ContestProblemList` may still be `null`, causing `JSON.parse(ContestProblemList)` to throw. Please either `await unsafeWindow.GetContestProblemList(false);` (marking the caller `async` if needed) before reading from `localStorage`, or refactor `GetContestProblemList` to return the list directly instead of going through `localStorage`.
</issue_to_address>

### Comment 2
<location path="XMOJ.user.js" line_range="2294-2300" />
<code_context>
                         }
                         let ContestProblemList = localStorage.getItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList");
                         if (ContestProblemList == null) {
-                            const contestReq = await fetch("https://www.xmoj.tech/contest.php?cid=" + SearchParams.get("cid"));
</code_context>
<issue_to_address>
**issue (bug_risk):** `JSON.parse` is called even when `ContestProblemList` may still be `null`, which can throw at runtime.

On failure of `GetContestProblemList` (network error, non-200, contest not visible), the `localStorage` key isn’t set and `ContestProblemList` stays `null`. Since `JSON.parse(null)` throws `SyntaxError`, this refactor makes that failure path more likely. Please guard before parsing (e.g., handle the `null` case explicitly or default to an empty array) instead of parsing `null`.
</issue_to_address>

### Comment 3
<location path="XMOJ.user.js" line_range="2320" />
<code_context>
                        problemSwitcher.innerHTML += `<a href="javascript:void(0)" onclick="GetContestProblemList(true)" title="刷新列表" class="mb-2" style="text-align: center;" active>刷新</a>`;
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-document-method):** User controlled data in methods like `innerHTML`, `outerHTML` or `document.write` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

### Comment 4
<location path="XMOJ.user.js" line_range="2320" />
<code_context>
                        problemSwitcher.innerHTML += `<a href="javascript:void(0)" onclick="GetContestProblemList(true)" title="刷新列表" class="mb-2" style="text-align: center;" active>刷新</a>`;
</code_context>
<issue_to_address>
**security (javascript.browser.security.insecure-innerhtml):** User controlled data in a `problemSwitcher.innerHTML` is an anti-pattern that can lead to XSS vulnerabilities

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

localStorage.setItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList", JSON.stringify(problemList));
ContestProblemList = JSON.stringify(problemList);
}
unsafeWindow.GetContestProblemList(false);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): The async GetContestProblemList call is not awaited before reading from localStorage, which can lead to a race condition.

Previously the fetch and localStorage.setItem completed inline before ContestProblemList was used. Now GetContestProblemList is async, but unsafeWindow.GetContestProblemList(false); is called without await and localStorage is read immediately afterward, so ContestProblemList may still be null, causing JSON.parse(ContestProblemList) to throw. Please either await unsafeWindow.GetContestProblemList(false); (marking the caller async if needed) before reading from localStorage, or refactor GetContestProblemList to return the list directly instead of going through localStorage.

Comment on lines 2294 to 2300
let ContestProblemList = localStorage.getItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList");
if (ContestProblemList == null) {
const contestReq = await fetch("https://www.xmoj.tech/contest.php?cid=" + SearchParams.get("cid"));
const res = await contestReq.text();
if (contestReq.status === 200 && res.indexOf("比赛尚未开始或私有,不能查看题目。") === -1) {
const parser = new DOMParser();
const dom = parser.parseFromString(res, "text/html");
const rows = (dom.querySelector("#problemset > tbody")).rows;
let problemList = [];
for (let i = 0; i < rows.length; i++) {
problemList.push({
"title": rows[i].children[2].innerText,
"url": rows[i].children[2].children[0].href
});
}
localStorage.setItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList", JSON.stringify(problemList));
ContestProblemList = JSON.stringify(problemList);
}
unsafeWindow.GetContestProblemList(false);
ContestProblemList = localStorage.getItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList");
}

let problemSwitcher = document.createElement("div");
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): JSON.parse is called even when ContestProblemList may still be null, which can throw at runtime.

On failure of GetContestProblemList (network error, non-200, contest not visible), the localStorage key isn’t set and ContestProblemList stays null. Since JSON.parse(null) throws SyntaxError, this refactor makes that failure path more likely. Please guard before parsing (e.g., handle the null case explicitly or default to an empty array) instead of parsing null.

problemSwitcher.style.flexDirection = "column";

let problemList = JSON.parse(ContestProblemList);
problemSwitcher.innerHTML += `<a href="javascript:void(0)" onclick="GetContestProblemList(true)" title="刷新列表" class="mb-2" style="text-align: center;" active>刷新</a>`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (javascript.browser.security.insecure-document-method): User controlled data in methods like innerHTML, outerHTML or document.write is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

problemSwitcher.style.flexDirection = "column";

let problemList = JSON.parse(ContestProblemList);
problemSwitcher.innerHTML += `<a href="javascript:void(0)" onclick="GetContestProblemList(true)" title="刷新列表" class="mb-2" style="text-align: center;" active>刷新</a>`;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security (javascript.browser.security.insecure-innerhtml): User controlled data in a problemSwitcher.innerHTML is an anti-pattern that can lead to XSS vulnerabilities

Source: opengrep

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file

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" location="XMOJ.user.js:2296">
P1: Await the async cache refresh before reading `ContestProblemList`, otherwise first-load problem switcher rendering still races the fetch and can crash on `problemList.length`.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

localStorage.setItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList", JSON.stringify(problemList));
ContestProblemList = JSON.stringify(problemList);
}
unsafeWindow.GetContestProblemList(false);
Copy link

@cubic-dev-ai cubic-dev-ai bot Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Await the async cache refresh before reading ContestProblemList, otherwise first-load problem switcher rendering still races the fetch and can crash on problemList.length.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At XMOJ.user.js, line 2296:

<comment>Await the async cache refresh before reading `ContestProblemList`, otherwise first-load problem switcher rendering still races the fetch and can crash on `problemList.length`.</comment>

<file context>
@@ -2270,22 +2293,8 @@ async function main() {
-                                localStorage.setItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList", JSON.stringify(problemList));
-                                ContestProblemList = JSON.stringify(problemList);
-                            }
+                            unsafeWindow.GetContestProblemList(false);
+                            ContestProblemList = localStorage.getItem("UserScript-Contest-" + SearchParams.get("cid") + "-ProblemList");
                         }
</file context>
Suggested change
unsafeWindow.GetContestProblemList(false);
await unsafeWindow.GetContestProblemList(false);
Fix with Cubic

@def-WA2025 def-WA2025 deleted the fix-problem-switcher-not-update branch March 12, 2026 15:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/M user-script This issue or pull request is related to the main user script

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant