Skip to content

fix(selection): collapse idle overlay so page touch drag gestures are not stolen - #2103

Open
guangzan wants to merge 1 commit into
mengxi-ream:mainfrom
guangzan:fix/selection-overlay-touch-gesture
Open

fix(selection): collapse idle overlay so page touch drag gestures are not stolen#2103
guangzan wants to merge 1 commit into
mengxi-ream:mainfrom
guangzan:fix/selection-overlay-touch-gesture

Conversation

@guangzan

Copy link
Copy Markdown

Description

On mobile-emulated/touch devices, page-level touch drag gestures (e.g. carousels built with Embla/swiper) became impossible to drag when the extension was installed. This PR fixes the selection toolbar's overlay root so it no longer steals horizontal touch pans from the page.

Problem

Reproduction (Chrome DevTools mobile emulation, touch enabled):

  1. Open a page with a drag-based carousel, e.g. https://ui.shadcn.com/docs/components/base/carousel
  2. Set body { touch-action: manipulation } via DevTools
  3. The extension injects <read-frog-selection> (selection content script)

Result: the carousel cannot be dragged at all — the browser fires pointercancel shortly after the first few pointermove events, aborting the JS-driven drag.

Either of these makes the carousel work again:

  • Remove touch-action: manipulation from body
  • Remove the <style> inside <read-frog-selection>

Root Cause

The selection toolbar renders a persistent full-viewport overlay root:

<div className="pointer-events-none fixed inset-0 z-2147483647" data-rf-selection-overlay-root>

This layer is mounted unconditionally, even while idle (no selection, no toolbar visible). It is pointer-events: none, so it does not intercept clicks — but pointer-events does not remove an element from the browser's touch gesture arbitration:

  • touch-action: manipulation on body (= pan-x pan-y + no double-tap zoom) makes the compositor decide gesture ownership immediately, without the double-tap delay window.
  • With a full-viewport fixed layer (its own touch-action: auto) sitting above the carousel, Chrome's gesture arbitration re-computes the allowed touch behaviors for the touch point. The carousel container's touch-action: pan-y contract (reserve horizontal pans for the script) is no longer honored — Chrome claims the horizontal pan as page scrolling and fires pointercancel at the carousel, terminating its pointer-based drag.

Removing the host's <style> "fixes" it only because that <style> carries the entry CSS (WXT :host reset + Tailwind); without it the overlay's fixed inset-0 / z-index classes never apply, the layer collapses to a small static box and stops covering the viewport.

Fix

Collapse the overlay root to 0x0 (fixed h-0 w-0) while the toolbar is idle, and restore the full-viewport fixed inset-0 layer only while the toolbar is visible (isSelectionToolbarVisible):

  • Idle (the 99% case): no full-viewport layer participates in touch gesture arbitration → page drag gestures work.
  • Visible: the overlay expands as before; positioning is unaffected because getViewportRect() reads window.visualViewport and viewportPointToHostPoint() only runs while the toolbar is visible (at which point the container has already been restored to full viewport).

Evidence

Verified with the built extension in real Chrome (mobile emulation + CDP touch swipe on the shadcn carousel, body { touch-action: manipulation } set), counting pointercancel events per swipe:

Scenario pointercancel Carousel draggable
Before fix (extension + manipulation) fired (~7 moves in) no
After fix (same setup) 0 yes
After fix, toolbar visible after selection n/a (overlay intentionally full-viewport) toolbar shows & positions correctly

Also verified post-fix that selecting text still expands the overlay root back to inset-0 and the toolbar appears at the correct position with no React errors.

Surgical isolation performed during debugging (for context, all with extension + manipulation):

  • Hiding the overlay root → no pointercancel (culprit confirmed)
  • Collapsing it to 0x0 → no pointercancel (chosen fix)
  • Setting touch-action: none/pan-y on the overlay → still canceled (so a CSS touch-action override does not work; geometry is what matters)
  • Hiding the toast viewport → still canceled (toast layer not involved)

Changes

  • src/entrypoints/selection.content/selection-toolbar/index.tsx: collapse/expand the overlay root based on isSelectionToolbarVisible
  • src/entrypoints/selection.content/selection-toolbar/__tests__/selection-toolbar.test.tsx: regression test asserting h-0 w-0 while idle and inset-0 while visible
  • .changeset/selection-overlay-touch-gesture.md: patch changeset for @read-frog/extension

Test Plan

  • pnpm vitest run src/entrypoints/selection.content — 218/218 passed
  • Full pre-push suite (with SKIP_FREE_API=true per repo convention)
  • Manual E2E with built extension on the shadcn carousel page (table above)

… not stolen

A persistent full-viewport fixed inset-0 overlay layer makes Chrome claim
horizontal touch pans on pages using `touch-action: manipulation`, firing
pointercancel and breaking touch drag gestures (e.g. carousels) outside the
toolbar. Collapse the overlay root to 0x0 while the toolbar is idle and
restore the full-viewport layer only while the toolbar is visible.
@changeset-bot

changeset-bot Bot commented Aug 18, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 1b9f4fc

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@read-frog/extension Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions Bot added fix contrib-trust:new PR author trust score is 0-29. needs-maintainer-review Contributor trust automation recommends maintainer review. labels Aug 18, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Contributor trust score

23/100 — New contributor

This score estimates contributor familiarity with mengxi-ream/read-frog using public GitHub signals. It is advisory only and does not block merges automatically.

Outcome

Score breakdown

Dimension Score Signals
Repo familiarity 0/35 commits in repo, merged PRs, reviews
Community standing 10/25 account age, followers, repo role
OSS influence 8/20 stars on owned non-fork repositories
PR track record 5/20 merge rate across resolved PRs in this repo

Signals used

  • Repo commits: 0 (author commits reachable from the repository default branch)
  • Repo PR history: merged 0, open 1, closed-unmerged 0
  • Repo reviews: 0
  • PR counted changed lines: 47 (+46 / -1)
  • Repo permission: read
  • Followers: 80
  • Account age: 94 months
  • Owned non-fork repos considered: max 254, total 349 (guangzan/tona (254), guangzan/deepseek-lane (23), guangzan/tinytab (21), guangzan/live2d-models (16), guangzan/epidemic (9), guangzan/emoji-popover (6), guangzan/rselectron (4), guangzan/monorail (4), guangzan/vite-plugin-ignore-public (3), guangzan/dynamic-theme (3), guangzan/nahida-template (2), guangzan/festival-avatar (2), guangzan/implement-design-skill (1), guangzan/guangzan.github.io (1), guangzan/termbio (0))

Policy

  • Low-score review threshold: < 30
  • Auto-close: score < 20 and counted changed lines > 1000
  • Migration-related files are excluded from the auto-close line count
  • Policy version: v1.2

Updated automatically when the PR changes or when a maintainer reruns the workflow.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b9f4fc530

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +585 to +587
isSelectionToolbarVisible
? `pointer-events-none fixed inset-0 ${SELECTION_CONTENT_OVERLAY_LAYERS.selectionOverlay}`
: "pointer-events-none fixed h-0 w-0",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Gate the expanded overlay on rendered toolbar state

If the toolbar is visible and then the selection toolbar is disabled, the site becomes disabled, or all toolbar features are turned off via synced config, isSelectionToolbarVisible can remain true while the child toolbar is no longer rendered by the condition below. In that state this branch still keeps a full-viewport fixed overlay with no visible toolbar to interact with, so the touch-drag stealing this patch is meant to avoid can persist until a later mouse-driven clear; collapse the root whenever the toolbar content cannot render, or clear the visible atom when those booleans become false.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contrib-trust:new PR author trust score is 0-29. fix needs-maintainer-review Contributor trust automation recommends maintainer review.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant