The custom gesture system and the selection clamp/repair chain
replaced browser behavior with worse approximations. Keep only the
non-touch gate on selection auto-paging (de70103), which fixes
drag-selecting paging away mid-gesture; everything else returns to
upstream behavior.
Chrome's native touch selection misbehaves in the paginated column
layout: quick taps select words, and dragging from a line-start word
re-anchors at a rendered line break, dropping the word and freezing
the gesture. preventDefault on touchstart disables it entirely; a
long-press timer selects the word under the finger instead and drag
moves extend word by word, clamped to the visible page and identical
for every word. Clean taps clear the selection or follow links
(click events being suppressed by the preventDefault).
A selection write at the moment Chrome re-anchors the drag detaches
its touch selection controller; the same writes on a settled
selection are fine. Record the word synchronously, repair only when
the selection has been unchanged for 150ms.
Rewriting both selection boundaries mid-gesture detaches Chrome's
touch selection controller and the selection stops following the
finger. extend() moves only the focus, which the controller
tolerates.
Dragging just past a line-start word makes Chrome flip the touch
selection and re-anchor it at a rendered line break, silently
dropping the long-pressed word from the selection. Record the word
at the initial long-press and restore its start boundary whenever
the selection no longer covers it.
The helper created its probe range in the module's own document
(the host, in the bundled web reader), so boundary points in the
content iframe collapsed the range and every selection read as
backward.
The last visible range can extend past the visible column on text
nodes that span multiple columns (its end-offset bisect uses union
rects), so comparing DOM boundary points against it misses off-view
selection endpoints. Test the endpoint caret rect against the
visible column bounds instead.
Chrome emits collapsed selectionchange events while the long-press
word selection is being set up; clearing the flag on them disarmed
the clamp before the selection ever became a Range.
Chrome word-snapping during a touch selection drag can map the
endpoint past the visible column into the next (off-screen) page,
selecting the entire visible page at once. Re-map the endpoint via
caretRangeFromPoint at the same line height inside the visible
column whenever it lands outside the last visible range.
In Safari, clicking on links triggers `selectionchange`. Since we do not
move the caret when flipping through pages, when you have flipped to the
next or previous pages and click on any link, it would take you back to
the caret instead of activating the link.
Note that the bug occurs in Safari 17 but not in WebKitGTK 2.46.
Not sure if there's a way to fix this and still retain the "set anchor
to caret on click" behavior. But I guess this behavior isn't really that
essential, anyway. So removing it for now.
Fixes#44
The view follows caret when using caret browsing (F7). Also allows
selecting across pages. And if you click on or select some text and
resize the window, the caret/selection always stays visible.
Some quirks regarding caret browsing:
- In Chromium, in paginated mode, it scrolls when changing selection
(https://issues.chromium.org/issues/40889650)
- In WebKitGTK, in scrolled mode, the view doesn't follow the caret
(which would be a WebKit bug)
What's `margin` doing here?! It's like this since the first commit, but
it makes no sense... I think I added it incorrectly, being misled by
the getBoundingClientRect bug in Firefox...
The effect of this is more obvious if you enable caret browsing (F7 in
all browsers). Previously, if you go to a fragment, the caret isn't
moved. Now it works as expected.
This is a purely internal change, but it might make things easier in the
future if one wants to place stuff on the margins (e.g. to fade the edge
of the iframe when animating, or place prev/next buttons on the sides)
Anchoring triggers the `scroll` event, which in scrolled mode causes
`#afterScroll()` to be called again with the reason `scroll`, which
shouldn't happen as it's just anchoring, not scrolling.
Massively simplify margin/padding and header/footer layout and eliminate
unnecessary resizes and resize loops.
Also remove layout and style API from View, as these are only applicable
to the paginator.