fix(webview): rescroll target post once content swap settles (#631)#810
fix(webview): rescroll target post once content swap settles (#631)#810jim-daf wants to merge 1 commit intoAwful:developfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a WebView helper intended to restore “scroll-to-target post” behavior after thread content is rendered via innerHTML swaps (Issue #631).
Changes:
- Introduces
AwfulWebView.scrollToTargetAfterContentSettles(...)which injects JS to observe DOM mutations and scroll to a target anchor after a quiet period. - Adds a 2s safety timeout to force a scroll attempt even if the DOM never becomes quiet.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Fallback: if no mutations fire within 2s, jump anyway. | ||
| "setTimeout(function(){ observer.disconnect(); jump(); }, 2000);" + |
There was a problem hiding this comment.
The 2s fallback setTimeout will still fire even if the observer already detected "quiet" and called jump(), causing a second jump later (potentially yanking the user back after they started scrolling). Track the fallback timeout ID and cancel it when jump() runs, or gate jump() with a didJump flag.
| public void scrollToTargetAfterContentSettles(@Nullable String anchorId) { | ||
| String target = anchorId == null ? "null" : "'" + anchorId.replace("'", "") + "'"; | ||
| runJavascript( |
There was a problem hiding this comment.
This helper is not referenced anywhere in the app yet (no call sites found), so the behavior described in the PR title/"Closes #631" won’t actually take effect. Either wire it up from the intended caller (e.g., ThreadDisplayFragment after setBodyHtml) or adjust the PR title/description and avoid closing the issue until it’s integrated.
| String target = anchorId == null ? "null" : "'" + anchorId.replace("'", "") + "'"; | ||
| runJavascript( | ||
| "(function(){" + | ||
| "var anchorId = " + target + ";" + |
There was a problem hiding this comment.
anchorId is interpolated into a JS string literal by stripping only single quotes. This can still break the injected JS (e.g., newlines/backslashes) and also changes legitimate IDs containing '. Prefer proper JS string quoting/escaping (e.g., via JSONObject.quote(anchorId) or a dedicated escaping helper) instead of removing characters.
| "var id = anchorId || (location.hash || '').replace('#','');" + | ||
| "if (!id) { return; }" + | ||
| "var el = document.getElementById(id);" + | ||
| "if (el) { el.scrollIntoView({block: 'start'}); }" + |
There was a problem hiding this comment.
el.scrollIntoView({block: 'start'}) is not supported on older Android WebView/Chromium versions (the project already mentions API 21 / Chromium 37 compatibility in thread.js). This call can throw and prevent the rescroll entirely. Use the legacy boolean form (scrollIntoView(true)/scrollIntoView()) or feature-detect the options-object overload and fall back accordingly.
| "if (el) { el.scrollIntoView({block: 'start'}); }" + | |
| "if (el) {" + | |
| "try { el.scrollIntoView({block: 'start'}); }" + | |
| "catch (e) { el.scrollIntoView(true); }" + | |
| "}" + |
Closes #631.
The post page is rendered by swapping
innerHTMLof the container div, which meansWebViewClient.onPageFinishedonly fires on the initial template load. The previous "rescroll after page settles" heuristic relied on that callback and stopped working once the swap-based loader landed.This helper installs a
MutationObserveron the post container, treats 150ms of DOM quiet as "render is done", and then scrolls the target anchor (or the URL fragment) back into view. A 2s safety timeout guarantees we always jump even if the page never quiets down.ThreadDisplayFragmentshould call this right aftersetBodyHtml, passing the post id when it has one. I did not change the fragment in this PR so the helper can be reviewed on its own.