Compositor (Touch) Hit Testing

Implementation status:

Background & Problem Statement

User events such as a mouse click are received by the browser process, then marshaled to blink 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 blink to run through its event handler. The blink 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 blink 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 blink, 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 blink 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. In particular we want to allow scroll to start quickly during page load on pages that have some touch handlers but are not covered by them.

Tracking hit test rects in blink

In blink, 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 blink. 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!

The rects are generated in the co-ordinate system of each RenderLayer, and then in a second step 'projected' onto the co-ordinate system of the GraphicsLayer into which they paint. This way changes like scrolling and accelerated animations do not invalidate the rects.

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, (as of the fix for bug 351723) we do a ray cast at the point of the touch and consult the touchEventHandlerRegion for each layer until we hit a layer we know is opaque to hit testing. If there is a hit, the compositor forwards this touch event to the renderer and then it is sent to blink 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 blink 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.