How Chromium Handles Hit Testing for DOM Elements

This article explains how the Chromium browser performs hit testing, the process of determining which Document Object Model (DOM) element lies beneath a user's mouse cursor or touch point. It covers the journey of an input event from the operating system to the browser process, the fast-path compositor thread check, the detailed layout tree traversal in the Blink rendering engine, and how CSS rules and touch adjustments finalize the target element.

The Journey of an Input Event

When a user clicks a mouse or touches a screen, the operating system captures the raw hardware coordinates and sends them to Chromium’s Browser Process. Because Chromium uses a multi-process architecture, the Browser Process does not know the exact layout of the web page. To route the event to the correct Renderer Process (especially on pages containing out-of-process iframes), Chromium uses its display compositor, known as Viz. Viz analyzes surface layers to determine which Renderer Process owns the pixel at the event's coordinates and forwards the input event accordingly.

Compositor Thread Hit Testing (The Fast Path)

Inside the Renderer Process, the input event first arrives at the Compositor Thread. To keep the browser responsive and avoid waiting on heavy JavaScript execution, the compositor attempts to handle the hit test itself using a simplified representation of the page divided into layers.

The compositor checks the event coordinates against cached data called Hit Test Regions. If the interaction is a scroll event on a standard layer, the compositor handles it immediately. However, the compositor lacks full details about the DOM. If the target area contains complex shapes, non-rectangular borders, or overlapping elements that the compositor cannot confidently resolve, it marks the area as a "non-fast scrollable region" or complex region, and forwards the event to the Blink Main Thread.

When the event reaches the Main Thread, Chromium's Blink rendering engine performs a deep, precise hit test. Blink does not use the DOM tree directly for hit testing because the DOM only represents document structure, not visual geometry.

Instead, Blink uses the Layout Tree (composed of LayoutObject nodes) and the PaintLayer Tree. These trees are generated after CSS is applied and contain the exact pixel positions, dimensions, and styling information of every visible element on the page.

Traversing Paint Layers and Stacking Contexts

A main-thread hit test traverses the PaintLayer tree from front to back. It starts with the visually topmost layer and works its way down to the background. This reverse-painting order ensures that the browser respects CSS stacking contexts, testing elements with a higher z-index or 3D transformations first.

Within each PaintLayer, Blink checks if the input coordinates fall within the bounding box of the associated LayoutObject nodes. It recursively checks child elements before parent elements, as children are typically rendered on top of their parents.

Accounting for CSS Rules

Falling within an element's rectangular bounding box does not guarantee a match. As Blink tests each LayoutObject, it evaluates specific CSS properties that alter hit testing behavior:

  • Pointer Events: If an element has pointer-events: none, Blink ignores it and continues testing the elements layered beneath it.
  • Visibility: Elements with visibility: hidden or display: none are skipped entirely.
  • Rounded Corners: If an element has a border-radius, Blink calculates the exact mathematical curve. If the click falls inside the element's rectangular box, but outside the rounded corner curve, the hit test passes through to the element behind it.
  • Clipping and Masking: Properties like overflow: hidden and clip-path restrict the hit-testable area of an element and its children. Clicks outside the clipped region are ignored for those elements.

Handling Touch Events (Touch Adjustment)

While a mouse cursor targets a single precise pixel, human fingers are much less accurate. For touch events, Chromium alters the hit test from a single point to a rectangular bounding box that represents the physical contact area of the finger.

When this bounding box overlaps multiple LayoutObject nodes, Blink triggers a phase called Touch Adjustment (or "fuzzing"). This heuristic algorithm analyzes all elements within the touch area, prioritizing interactive elements like links, buttons, and form inputs over static text or layout containers. It calculates the best candidate based on the overlap area and distance from the center of the touch point.

Resolving the DOM Node

Once a LayoutObject passes all geometric, CSS, and touch adjustment checks, the hit test stops immediately. Blink takes the winning LayoutObject and maps it back to its corresponding DOM node. With the target DOM element identified, Chromium initiates the standard DOM event dispatch cycle, sending the mouse or touch event down the DOM tree during the capture phase and back up during the bubbling phase.