@SuppressWarnings("deprecation")
    @Override
    protected void onBrowserEvent2(Event event) {
        // Get the event target.
        EventTarget eventTarget = event.getEventTarget();
        if (!Element.is(eventTarget)) {
            return;
        }
        final Element target = event.getEventTarget().cast();

        // Find the cell where the event occurred.
        TableSectionElement tbody = getTableBodyElement();
        TableSectionElement tfoot = getTableFootElement();
        TableSectionElement thead = getTableHeadElement();
        TableSectionElement targetTableSection = null;
        TableCellElement targetTableCell = null;
        Element cellParent = null;
        Element headerParent = null; // Header in the headerBuilder.
        Element headerColumnParent = null; // Column in the headerBuilder.
        Element footerParent = null; // Header in the footerBuilder.
        Element footerColumnParent = null; // Column in the footerBuilder.
        {
            Element maybeTableCell = null;
            Element cur = target;

            /*
             * If an event happens in the TD element but outside the cell's div, we want
             * to handle it as if it happened within the table cell.
             */
            if (TableCellElement.TAG_TD.equalsIgnoreCase(cur.getTagName()) &&
                tableBuilder.isColumn(cur.getFirstChildElement())) {
                cur = cur.getFirstChildElement();
            }

            while (cur != null && targetTableSection == null) {
                /*
                 * Found the table section. Return the most recent cell element that we
                 * discovered.
                 */
                if (cur == tbody || cur == tfoot || cur == thead) {
                    targetTableSection = cur.cast(); // We found the table section.
                    if (maybeTableCell != null) {
                        targetTableCell = maybeTableCell.cast();
                        break;
                    }
                }

                // Look for a table cell.
                String tagName = cur.getTagName();
                if (TableCellElement.TAG_TD.equalsIgnoreCase(tagName)
                    || TableCellElement.TAG_TH.equalsIgnoreCase(tagName)) {
                    /*
                     * Found a table cell, but we can't return yet because it may be part
                     * of a sub table within the a CellTable cell.
                     */
                    maybeTableCell = cur;
                }

                // Look for the most immediate cell parent if not already found.
                if (cellParent == null && tableBuilder.isColumn(cur)) {
                    cellParent = cur;
                }

                /*
                 * Look for the most immediate header parent if not already found. Its
                 * possible that the footer or header will mistakenly identify a header
                 * from the other section, so we remember both. When we eventually reach
                 * the target table section element, we'll know for sure if its a header
                 * of footer.
                 */
                if (headerParent == null && headerBuilder.isHeader(cur)) {
                    headerParent = cur;
                }
                if (footerParent == null && footerBuilder.isHeader(cur)) {
                    footerParent = cur;
                }

                // Look for the most immediate column parent if not already found.
                if (headerColumnParent == null && headerBuilder.isColumn(cur)) {
                    headerColumnParent = cur;
                }
                if (footerColumnParent == null && footerBuilder.isColumn(cur)) {
                    footerColumnParent = cur;
                }

                // Iterate.
                cur = cur.getParentElement();
            }
        }
        if (targetTableCell == null) {
            return;
        }

        // Support the legacy mode where the div inside of the TD is the cell
        // parent.
        if (legacyRenderRowValues) {
            cellParent = targetTableCell.getFirstChildElement();
        }

        /*
         * Forward the event to the associated header, footer, or column.
         */
        TableRowElement targetTableRow = targetTableCell.getParentElement().cast();
        String eventType = event.getType();
        boolean isSelect = BrowserEvents.CLICK.equals(eventType)
                           || (BrowserEvents.KEYDOWN.equals(eventType) && event.getKeyCode() == KeyCodes.KEY_ENTER);

        int col = targetTableCell.getCellIndex();
        if (targetTableSection == thead || targetTableSection == tfoot) {
            boolean isHeader = (targetTableSection == thead);
            headerParent = isHeader
                    ? headerParent
                    : footerParent;
            Element columnParent = isHeader
                    ? headerColumnParent
                    : footerColumnParent;

            boolean shouldSortColumn = true;
            // Fire the event to the header.
            if (headerParent != null) {
                Header<?> header =
                        isHeader
                                ? headerBuilder.getHeader(headerParent)
                                : footerBuilder
                                        .getHeader(footerParent);

                if (header != null) {
                    int headerIndex = isHeader
                            ? headerBuilder.getRowIndex(targetTableRow)
                            :
                                    footerBuilder.getRowIndex(targetTableRow);
                    Context context = new Context(headerIndex, col, header.getKey());

                    if (cellConsumesEventType(header.getCell(), eventType)) {
                        header.onBrowserEvent(context, headerParent, event);
                    }

                    if (isSelect) {
                        // Preview the event, and possibily disable the column sort event. The event preview is
                        // forced even if the header cell does not consume click event
                        shouldSortColumn = header.onPreviewColumnSortEvent(context, headerParent, event);
                    }
                }
            }

            // Sort the header.
            if (isSelect && shouldSortColumn && columnParent != null) {
                Column<T, ?> column =
                        isHeader
                                ? headerBuilder.getColumn(columnParent)
                                : footerBuilder
                                        .getColumn(columnParent);
                if (column != null && column.isSortable()) {

                    updatingSortList = true;

                    // Get current col sort state.
                    final List<ColumnSortInfo> list = new ArrayList<>(sortList.size());
                    int index = -1;
                    ColumnSortInfo currentColumnSortInfo = null;
                    for (int i = 0; i < sortList.size(); i++) {
                        final ColumnSortInfo columnSortInfo = sortList.get(i);
                        list.add(columnSortInfo);
                        if (columnSortInfo.getColumn().equals(column)) {
                            index = i;
                            currentColumnSortInfo = columnSortInfo;
                        }
                    }

                    // Change the list.
                    if (index != -1) {
                        if (!event.getCtrlKey()) {
                            list.clear();
                        } else {
                            list.remove(index);
                        }

                        // If we are already non default then remove.
                        if (column.isDefaultSortAscending() == currentColumnSortInfo.isAscending()) {
                            final ColumnSortInfo newColumnSortInfo = new ColumnSortInfo(
                                    column,
                                    !currentColumnSortInfo.isAscending());
                            list.add(newColumnSortInfo);
                        }
                    } else {
                        if (!event.getCtrlKey()) {
                            list.clear();
                        }

                        final ColumnSortInfo newColumnSortInfo = new ColumnSortInfo(
                                column,
                                column.isDefaultSortAscending());
                        list.add(newColumnSortInfo);
                    }

                    // Push the list items backwards.
                    sortList.clear();
                    for (int i = list.size() - 1; i >= 0; i--) {
                        sortList.push(list.get(i));
                    }

                    /*
                     * Force the headers to refresh the next time data is pushed so we
                     * update the sort icon in the header.
                     */
                    headersDirty = true;
//                    sortList.push(column);
                    updatingSortList = false;

                    ColumnSortEvent.fire(this, sortList);
                }
            }
        } else if (targetTableSection == tbody) {
            /*
             * Get the row index of the data value. This may not correspond to the DOM
             * row index if the user specifies multiple table rows per row object.
             */
            int absRow = tableBuilder.getRowValueIndex(targetTableRow);
            int relRow = absRow - getPageStart();
            int subrow = tableBuilder.getSubrowValueIndex(targetTableRow);

            if (!skipRowHoverCheck) {
                boolean isRowChange = hoveringRow != targetTableRow;
                if (BrowserEvents.MOUSEOVER.equals(eventType)) {
                    // Unstyle the old row if it is still part of the table.
                    if (hoveringRow != null && getTableBodyElement().isOrHasChild(hoveringRow)) {
                        setRowHover(hoveringRow, event, false, isRowChange);
                    }
                    hoveringRow = targetTableRow;
                    setRowHover(hoveringRow, event, true, isRowChange);
                } else if (BrowserEvents.MOUSEOUT.equals(eventType) && hoveringRow != null) {
                    boolean unhover = true;
                    if (!skipRowHoverFloatElementCheck) {
                        // Ignore events happening directly over the hovering row. If there are floating element
                        // on top of the row, mouseout event should not be triggered. This is to avoid the flickring
                        // effect if the floating element is shown/hide based on hover event.
                        int clientX = event.getClientX() + Window.getScrollLeft();
                        int clientY = event.getClientY() + Window.getScrollTop();
                        int rowLeft = hoveringRow.getAbsoluteLeft();
                        int rowTop = hoveringRow.getAbsoluteTop();
                        int rowWidth = hoveringRow.getOffsetWidth();
                        int rowHeight = hoveringRow.getOffsetHeight();
                        int rowBottom = rowTop + rowHeight;
                        int rowRight = rowLeft + rowWidth;
                        unhover = clientX < rowLeft || clientX > rowRight || clientY < rowTop || clientY > rowBottom;
                    }
                    if (unhover) {
                        setRowHover(hoveringRow, event, false, isRowChange);
                        hoveringRow = null;
                    }
                }
            }

            // If the event causes us to page, then the physical index will be out
            // of bounds of the underlying data.
            if (!isRowWithinBounds(relRow)) {
                return;
            }

            /*
             * Fire a preview event. The preview event is fired even if the TD does
             * not contain a cell so the selection handler and keyboard handler have a
             * chance to act.
             */
            boolean isSelectionHandled =
                    handlesSelection
                    || KeyboardSelectionPolicy.BOUND_TO_SELECTION == getKeyboardSelectionPolicy();
            T value = getVisibleItem(relRow);

            /*
             * Create a new context based on the dom column index instead of using the
             * user provided one from TableBuilder. We trigger cell preview events for
             * table cells even if there is no associated Cell instance. If we used
             * the user provided context, we could get inconsistent states where the
             * Context is sometimes user provided and sometimes generated based on the
             * DOM column index.
             */
            Context context = new Context(absRow, col, getValueKey(value), subrow);
            CellPreviewEvent<T> previewEvent =
                    CellPreviewEvent.fire(this, event, this, context, value, cellIsEditing,
                            isSelectionHandled);

            // Pass the event to the cell.
            if (cellParent != null && !previewEvent.isCanceled()) {
                HasCell<T, ?> column;
                if (legacyRenderRowValues) {
                    column = columns.get(col);
                } else {
                    column = tableBuilder.getColumn(context, value, cellParent);
                }
                if (column != null) {
                    fireEventToCell(event, eventType, cellParent, value, context, column);
                }
            }
        }
    }
Get the thead element that contains the headers.
stroom-core-client-widget/src/main/java/com/google/gwt/user/cellview/client/AbstractCellTable.java