duburcqa / duburcqa/diff-desk

A refresh leaves one card behind, whichever engine is reading.

Open
#111 0 comments 0 reactions 0 assignees View on GitHub

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. IntersectionObserver and ResizeObserver hold 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 = "" against replaceChildren() 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 f0 whether 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.