A refresh leaves one card behind, whichever engine is reading.
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 4
- Forks
- 1
- Avg merge
- 37m
- Merged PRs (30d)
- 92
Description
Every rescan - a page load, a Refresh press, a source change - leaves exactly one section.file behind, detached and alive. It is always f0, the first card, whatever the reader is standing on and whichever engine is reading. Node count grows by 216 per refresh with no plateau: 8,256 at load and 14,737 after thirty, on a 126-file review, measured after forcing a collection each time.
What it costs is now small. Before #110 the card held the set of every line in the view that its rows were filled from, which was a megabyte a refresh; a card now holds nothing but itself, so what is left behind is on the order of a hundred kilobytes and the heap is flat. Process memory still climbs - about 5 MB a refresh in Chromium and 2.6 MB in WebKit - which is larger than the nodes account for, so part of it is the engine's own bookkeeping rather than the page's.
Measured, and ruled out as the thing holding it:
- The observers.
IntersectionObserverandResizeObserverhold their targets, and the page rebuilds without unobserving; disconnecting all three where the cards are replaced changes the numbers not at all - +216 nodes and +80 listeners either way, one harness, both arms. innerHTML = ""againstreplaceChildren()for dropping the cards: identical to the byte, 23.9 MB and 10,417 nodes after ten refreshes.- The scroll anchor: the card left behind is
f0whether the reader is at 0, 25,000 or 60,000 px, so it is not the card being read. - The selection, the focused element,
reaching, and the remembered heights and folds: cleared after every refresh, no change. - Each step of a redraw in turn -
sayWhereToStep,leftToReview,markCurrent,paintSel,fitNotes,settleNear,renderTabs,showSource,roomPerRow: stubbing any one of them still leaves 4 to 8 cards behind, which is the run-to-run band, and removing more work leaves more rather than fewer.
A heap snapshot names the holder as a closure context reached from Blink's traced handles, which is where the engine keeps a function it holds on the page's behalf - an event listener, an observer callback, a platform callback. Which one it is, this has not pinned down.
Worth taking further only as far as the cost justifies: a hundred kilobytes a refresh is a slow leak, and a reader who reloads the page starts from nothing. The measurement scripts are worth keeping in mind for whoever picks it up - a heap snapshot read for retainers, DOM.getDetachedDomNodes for the census, and stubbing one step of a redraw at a time.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the heap snapshot retainers and DOM.getDetachedDomNodes census, then use the existing measurement scripts while stubbing one redraw step at a time. Trace the Blink closure context to the event listener, observer callback, or platform callback retaining f0. Done means refreshes no longer leave detached cards behind and the node, listener, and memory measurements stop growing across repeated rescans.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- frontend, performance, web-dev
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100