Repository navigation
Documents: instant typing and faster opening in long documents - #139
Merged
Merged
Conversation
…r or footer Every keystroke laid out each page's header and footer again from the measured-story cache, whose key held the document's whole list definitions. A document with 176 lists serialized half a megabyte of them into the key of every page's header and footer, then hashed each key to look it up: about 45 ms of each keystroke in a 28-page document. A layout now serializes the list definitions once, like the styles, and the keys name them by the cache's short id. A header or footer without page fields no longer takes the page number into its key either, so it is measured once rather than once a page, at opening and after.
…d them Opening a document read its watermark and page background for the Design tab at once, parsing the whole body XML twice (and every header): about 140 ms of opening a 670 KB document. Each piece of the package's design is now read the first time something asks for it.
…he document After each keystroke the controls listed the document's tracked changes seven times, each a pass over every run, parsed the main document's relationships (a document with 300 hyperlinks has 60 KB of them) to find the theme and the bibliography, and parsed the theme. The changes are now listed once per document revision, and the relationships and theme colors once per version of their parts: 8.7 ms -> 3.6 ms per keystroke.
…nged paragraphs' keys The body and its headers and footers each serialized the styles and list definitions for their cache keys on every keystroke; one layout now serializes each once for both. An unchanged paragraph also keeps the key strings of the last layout, so looking every paragraph up no longer hashes the whole document's text again: about 1 ms of each keystroke in a 1,500-paragraph document.
An autosave a moment after typing stopped held the page for about 380 ms in a 1,500-paragraph document, so keys typed then waited. The save parsed the whole body XML again just to keep its root and final section properties, parsed each unchanged paragraph's source XML with a parser of its own, and deflated the package on the page's thread. The save's starting skeleton is now taken from the parse made when the document opened, each paragraph's source XML is parsed once (ahead of the first save, a little at a time while the page is idle), and the autosave zips the parts in a worker, falling back to the page's thread where workers are unavailable: 380 ms -> 96 ms of the page's time. Saved files are byte for byte the same as before for all 2,257 fixture and corpus documents, edited and saved twice each.
Opening a document lays every paragraph out once, measuring the text of each line prefix by prefix. A 28-page document of 1,500 paragraphs spent about 255 ms there; profiling showed the canvas measureText calls were only about 50 ms of it. The rest was repeated bookkeeping around them: - Every line was built twice: once to find where it breaks and again to build the result, repeating each measurement. A paragraph's last line is exactly what the first pass built when it ran out of characters, so it is now used as is. Most paragraphs are a single line. - Each character copied its run's whole format (40 properties), and each line's text groups were new style objects, so the measurer serialized thousands of them to key its memory. A run's characters now share one style per font slot, and a group measures as the style it was copied from (it differs only in layout's own output fields). - Characters were built with conditional object spreads, the shaping key string was rebuilt for every character, and color, underline and background decisions were recomputed per character; they are now built plainly, computed once per style object, and skipped when the style object has not changed. - The geometry layer copied each remembered measurement before copying it again, built the bundled decoration defaults it never used for canvas measurements, and segmented ASCII text into graphemes. Layout output is unchanged: every line, fragment, caret stop and metric of the 2,256 test and public fixtures and a large real document is identical before and after, including after typing into its longest paragraph with the same measurer. Non-minified production build, opening the 28-page document in Chrome: validateCanvasLayout 293 -> 195 ms, paragraph measuring 254 -> 151 ms. A generated document of 120 paragraphs of 1,200 characters: first layout 464 -> 298 ms, relayout after a keystroke 14.8 -> 12.7 ms.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Typing in a long document with many lists, a header and footer, and hundreds of hyperlinks lagged on every keystroke and froze shortly after each pause; opening it took nearly two seconds. This fixes the underlying causes. Each one was found by profiling a real 28-page, 1,500-paragraph document (176 lists, 299 hyperlinks, 16 pictures). The document is not included here.
Results (Chrome, production build, same machine)
Causes and fixes
DOMParser(1,500 parsers), and deflated the package on the page's thread. Now:DocxPackage.prepareSave).package-zip.js), falling back to the page's thread where workers are unavailable.export()stays synchronous; autosave usesexportAsync()).measureText. Now:Verification
npm run buildandnpm run check:licensespass.paragraph-end-typingfails the same way on main.page-story-cache-keys.test.js: stories without fields are measured once; a list definition changed in place still relays out.revision-list-cache.test.js: the change list follows edits and undo; rewritten relationships and theme parts are read again.save-preparation.test.js: repeated saves after edits equal a freshly opened package's;exportAsyncproduces the same parts.line-measure-reuse.test.js.Not in this PR
🤖 Generated with Claude Code