For Developers‎ > ‎Design Documents‎ > ‎

Compositor (Touch) Hit Testing

@leviw, @yusufo, @rbyers

Implementation status:

Implemented: crbug.com/135818

Undergoing substantial changes (not yet reflected here): crbug.com/248522, crbug.com/261307 

Background & Problem Statement

User events such as a mouse click are received by the browser process, then marshaled to WebKit where the click event is hit tested through a page's DOM, checking for event handlers along the way. On touch-capable devices, a finger drag can be used to scroll the page, but a Touch event handler on the page may also optionally override this default behavior by calling preventDefault. Because there is no way to determine programmatically if an event handler will prevent this default scrolling behavior, if a Touch event occurs where there's an event handler, we have to first marshal the event to WebKit to run through its event handler. The WebKit thread is often slow to respond, particularly during page load, which can result in very long delays between a Touch intended to scroll the page, and the scroll actually occurring.

The original solution for this problem was for WebKit to inform the embedding application of when a page had at least one Touch event handler registered. If there were no Touch event handlers registered, we wouldn't send the events to WebKit, and would scroll immediately. This document describes a more flexible solution where regions of the page where Touch event handlers are active are used by the compositor to avoid waiting for WebKit hit testing for as much of a page as is possible.

Project goal

Remove latency for touch scrolling wherever possible without changing any behavior for pages that use touch event handlers.

Tracking hit test rects

In WebKit, we hook into the creation of Touch event handlers, and add DOM Nodes with any type of Touch event handler to a counted map in WebCore::Document. After a layout occurs, or when a Touch event handler is added or removed, code in WebCore::ScrollingCoordinator iterates across all DOM Nodes to generate a vector of rectangles where Touch events need to be marshaled to WebKit. Due to out-of-flow children, this involves walking through all the child renderers associated with each Node being tracked. If a Touch event handler is registered on the DOMWindow or Document node, we avoid the potentially expensive process of walking the renderers, and simply use the view's bounds, as they're guaranteed to be inclusive. Note: registering a Touch event handler on the DOMWindow or Document node will defeat any benefit provided by this project!

Currently, these rects are generated in the outermost Document's coordinate system. This is to enable us to share code with the iOS implementation of the same functionality (see this bug for background). Placing these rects in the coordinate system of their enclosing cc::Layer would allow us to compute tighter bounds when there are transforms, as well as avoid needing to recompute these rects when rects in a sub-frame are scrolled, since the compositor is already aware of the scroll.

Hit testing in the compositor

The hit testing is currently done just for the touchStart events since the point at which these event hit determines where the next train of events will be sent until we receive another touchStart(due to a different gesture starting or due to another finger being pressed on screen). On the compositor, we first check whether the touch event falls on any layer and then we walk the layer hierarchy checking each the touchEventHandlerRegion on every layer. Currently because of the reasons mentioned above about using the outermost coordinate system, this search ends on the main scrolling layer, and if there is a hit, the compositor forwards this touch event to the renderer and then it is sent to WebKit to be processed as usual. If there is no touchEventHandlerRegion that was hit, the compositor sends an ACK with NO_CONSUMER_EXISTS.

Browser side processing

As far as the browser side is concerned, only the ACKs it receives for the outgoing touch events matter in determining the current state. Currently there are four states that the ACK can be at. INPUT_EVENT_STATE_ACK_UNKNOWN is the initial default state that the touch_event_queue is at and might not be used on different platforms(ex: Android). When a touchStart event comes the touch event queue on the browser side always sends this touch event through IPC to the compositor. Then the touch event queue waits for the ACK for that touchStart to make a decision about the rest of the touch events in queue.

If it receives NO_CONSUMER_EXISTS, it stops sending touch events to the compositor until the next touchStart arrives and sends them directly to the platform specific gesture detector. This is mostly the case for regular browsing helps the gesture detector take over after a single touch event gets ACKed back from the compositor making it possible for the gesture to be generated fast enough to not cause any visible lag.

If it receives either NOT_CONSUMED or CONSUMED, this means there was a hit in the touchEventHandlerRegion and we should continue sending the touchMoves and touchEnd following this event to the compositor( which will send them to the renderer without doing any hit testing). If the ACK was CONSUMED, then the touchEventHandler had called preventDefault and neither this particular touch event nor the rest of the touch events until the next touchStart should be sent to the gesture detector. If the ACK was NOT_CONSUMED, this might mean either the touchEventHandlerRegion was too conservative and when the touchStart was hit tested in WebKit it didn't hit any touchEventHandlers or the touchEventHandler didn't preventDefault or process that particular touch event. In this case the touch_event_queue still forwards this event to the gesture_detector.

Comments