HighlightData.cpp   [plain text]


/*
 * Copyright (C) 2014 Igalia S.L.
 * Copyright (C) 2015-2017 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER “AS IS” AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "config.h"
#include "HighlightData.h"

#include "Document.h"
#include "FrameSelection.h"
#include "Logging.h"
#include "Position.h"
#include "Range.h"
#include "RenderLayer.h"
#include "RenderMultiColumnSpannerPlaceholder.h"
#include "RenderObject.h"
#include "RenderView.h"
#include "VisibleSelection.h"
#include <wtf/text/TextStream.h>

namespace WebCore {

namespace { // See bug #177808.

struct SelectionData {
    using RendererMap = HashMap<RenderObject*, std::unique_ptr<RenderSelectionInfo>>;
    using RenderBlockMap = HashMap<const RenderBlock*, std::unique_ptr<RenderBlockSelectionInfo>>;

    Optional<unsigned> startOffset;
    Optional<unsigned> endOffset;
    RendererMap renderers;
    RenderBlockMap blocks;
};

class HighlightIterator {
public:
    HighlightIterator(RenderObject* start)
        : m_current(start)
    {
        checkForSpanner();
    }
    
    RenderObject* current() const
    {
        return m_current;
    }
    
    RenderObject* next()
    {
        RenderObject* currentSpan = m_spannerStack.isEmpty() ? nullptr : m_spannerStack.last()->spanner();
        m_current = m_current->nextInPreOrder(currentSpan);
        checkForSpanner();
        if (!m_current && currentSpan) {
            RenderObject* placeholder = m_spannerStack.last();
            m_spannerStack.removeLast();
            m_current = placeholder->nextInPreOrder();
            checkForSpanner();
        }
        return m_current;
    }

private:
    void checkForSpanner()
    {
        if (!is<RenderMultiColumnSpannerPlaceholder>(m_current))
            return;
        auto& placeholder = downcast<RenderMultiColumnSpannerPlaceholder>(*m_current);
        m_spannerStack.append(&placeholder);
        m_current = placeholder.spanner();
    }

    RenderObject* m_current { nullptr };
    Vector<RenderMultiColumnSpannerPlaceholder*> m_spannerStack;
};

} // anonymous namespace

static RenderObject* rendererAfterOffset(const RenderObject& renderer, unsigned offset)
{
    auto* child = renderer.childAt(offset);
    return child ? child : renderer.nextInPreOrderAfterChildren();
}

static bool isValidRendererForSelection(const RenderObject& renderer, const HighlightData::RenderRange& selection)
{
    return (renderer.canBeSelectionLeaf() || &renderer == selection.start() || &renderer == selection.end())
        && renderer.selectionState() != RenderObject::HighlightState::None
        && renderer.containingBlock();
}

static RenderBlock* containingBlockBelowView(const RenderObject& renderer)
{
    auto* containingBlock = renderer.containingBlock();
    return is<RenderView>(containingBlock) ? nullptr : containingBlock;
}

static SelectionData collectSelectionData(const HighlightData::RenderRange& selection, bool repaintDifference)
{
    SelectionData oldSelectionData { selection.startOffset(), selection.endOffset(), { }, { } };
    // Blocks contain selected objects and fill gaps between them, either on the left, right, or in between lines and blocks.
    // In order to get the repaint rect right, we have to examine left, middle, and right rects individually, since otherwise
    // the union of those rects might remain the same even when changes have occurred.
    auto* start = selection.start();
    RenderObject* stop = nullptr;
    if (selection.end())
        stop = rendererAfterOffset(*selection.end(), selection.endOffset().value());
    HighlightIterator selectionIterator(start);
    while (start && start != stop) {
        if (isValidRendererForSelection(*start, selection)) {
            // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well.
            oldSelectionData.renderers.set(start, makeUnique<RenderSelectionInfo>(*start, true));
            if (repaintDifference) {
                for (auto* block = containingBlockBelowView(*start); block; block = containingBlockBelowView(*block)) {
                    auto& blockInfo = oldSelectionData.blocks.add(block, nullptr).iterator->value;
                    if (blockInfo)
                        break;
                    blockInfo = makeUnique<RenderBlockSelectionInfo>(*block);
                }
            }
        }
        start = selectionIterator.next();
    }
    return oldSelectionData;
}

HighlightData::HighlightData(RenderView& view)
    : m_renderView(view)
#if ENABLE(SERVICE_CONTROLS)
    , m_selectionRectGatherer(view)
#endif
{
}

void HighlightData::setRenderRange(const RenderRange& renderRange)
{
    ASSERT(renderRange.start() && renderRange.end());
    m_renderRange = renderRange;
}

RenderObject::HighlightState HighlightData::highlightStateForRenderer(RenderObject& renderer)
{
    if (&renderer == m_renderRange.start()) {
        if (m_renderRange.start() && m_renderRange.end() && m_renderRange.start() == m_renderRange.end())
            return RenderObject::HighlightState::Both;
        if (m_renderRange.start())
            return RenderObject::HighlightState::Start;
    }
    if (&renderer == m_renderRange.end())
        return RenderObject::HighlightState::End;

    RenderObject* highlightEnd = nullptr;
    auto* highlightDataEnd = m_renderRange.end();
    if (highlightDataEnd)
        highlightEnd = rendererAfterOffset(*highlightDataEnd, m_renderRange.endOffset().value());
    UNUSED_PARAM(highlightEnd);
    HighlightIterator highlightIterator(m_renderRange.start());
    for (auto* currentRenderer = m_renderRange.start(); currentRenderer && currentRenderer != m_renderRange.end(); currentRenderer = highlightIterator.next()) {
        if (currentRenderer == m_renderRange.start() || currentRenderer == m_renderRange.end())
            continue;
        if (!currentRenderer->canBeSelectionLeaf())
            continue;
        if (&renderer == currentRenderer)
            return RenderObject::HighlightState::Inside;
    }
    return RenderObject::HighlightState::None;
}

void HighlightData::setSelection(const RenderRange& selection, RepaintMode blockRepaintMode)
{
    if ((selection.start() && !selection.end()) || (selection.end() && !selection.start()))
        return;
    // Just return if the selection hasn't changed.
    auto isCaret = m_renderView.frame().selection().isCaret();
    if (selection == m_renderRange && m_selectionWasCaret == isCaret)
        return;
#if ENABLE(SERVICE_CONTROLS)
    // Clear the current rects and create a notifier for the new rects we are about to gather.
    // The Notifier updates the Editor when it goes out of scope and is destroyed.
    auto rectNotifier = m_selectionRectGatherer.clearAndCreateNotifier();
#endif
    m_selectionWasCaret = isCaret;
    applySelection(selection, blockRepaintMode);
}

void HighlightData::clearSelection()
{
    m_renderView.layer()->repaintBlockSelectionGaps();
    setSelection({ }, HighlightData::RepaintMode::NewMinusOld);
}

void HighlightData::repaint() const
{
    HashSet<RenderBlock*> processedBlocks;
    RenderObject* end = nullptr;
    if (m_renderRange.end())
        end = rendererAfterOffset(*m_renderRange.end(), m_renderRange.endOffset().value());
    HighlightIterator highlightIterator(m_renderRange.start());
    for (auto* renderer = highlightIterator.current(); renderer && renderer != end; renderer = highlightIterator.next()) {
        if (!renderer->canBeSelectionLeaf() && renderer != m_renderRange.start() && renderer != m_renderRange.end())
            continue;
        if (renderer->selectionState() == RenderObject::HighlightState::None)
            continue;
        RenderSelectionInfo(*renderer, true).repaint();
        // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well.
        for (auto* block = containingBlockBelowView(*renderer); block; block = containingBlockBelowView(*block)) {
            if (!processedBlocks.add(block).isNewEntry)
                break;
            RenderSelectionInfo(*block, true).repaint();
        }
    }
}

IntRect HighlightData::collectBounds(ClipToVisibleContent clipToVisibleContent) const
{
    LOG_WITH_STREAM(Selection, stream << "HighlightData::collectBounds (clip to visible " << (clipToVisibleContent == ClipToVisibleContent::Yes ? "yes" : "no"));

    SelectionData::RendererMap renderers;
    auto* start = m_renderRange.start();
    RenderObject* stop = nullptr;
    if (m_renderRange.end())
        stop = rendererAfterOffset(*m_renderRange.end(), m_renderRange.endOffset().value());

    HighlightIterator highlightIterator(start);
    while (start && start != stop) {
        if ((start->canBeSelectionLeaf() || start == m_renderRange.start() || start == m_renderRange.end())
            && start->selectionState() != RenderObject::HighlightState::None) {
            // Blocks are responsible for painting line gaps and margin gaps. They must be examined as well.
            renderers.set(start, makeUnique<RenderSelectionInfo>(*start, clipToVisibleContent == ClipToVisibleContent::Yes));
            LOG_WITH_STREAM(Selection, stream << " added start " << *start << " with rect " << renderers.get(start)->rect());

            auto* block = start->containingBlock();
            while (block && !is<RenderView>(*block)) {
                LOG_WITH_STREAM(Scrolling, stream << " added block " << *block);
                std::unique_ptr<RenderSelectionInfo>& blockInfo = renderers.add(block, nullptr).iterator->value;
                if (blockInfo)
                    break;
                blockInfo = makeUnique<RenderSelectionInfo>(*block, clipToVisibleContent == ClipToVisibleContent::Yes);
                LOG_WITH_STREAM(Selection, stream << " added containing block " << *block << " with rect " << blockInfo->rect());
                block = block->containingBlock();
            }
        }
        start = highlightIterator.next();
    }

    // Now create a single bounding box rect that encloses the whole selection.
    LayoutRect selectionRect;
    for (auto& info : renderers.values()) {
        // RenderSelectionInfo::rect() is in the coordinates of the repaintContainer, so map to page coordinates.
        LayoutRect currentRect = info->rect();
        if (currentRect.isEmpty())
            continue;

        if (auto* repaintContainer = info->repaintContainer()) {
            FloatRect localRect = currentRect;
            FloatQuad absQuad = repaintContainer->localToAbsoluteQuad(localRect);
            currentRect = absQuad.enclosingBoundingBox();
            LOG_WITH_STREAM(Selection, stream << " rect " << localRect << " mapped to " << currentRect << " in container " << *repaintContainer);
        }
        selectionRect.unite(currentRect);
    }

    LOG_WITH_STREAM(Selection, stream << " final rect " << selectionRect);
    return snappedIntRect(selectionRect);
}

void HighlightData::applySelection(const RenderRange& newSelection, RepaintMode blockRepaintMode)
{
    auto oldSelectionData = collectSelectionData(m_renderRange, blockRepaintMode == RepaintMode::NewXOROld);
    // Remove current selection.
    for (auto* renderer : oldSelectionData.renderers.keys())
        renderer->setSelectionStateIfNeeded(RenderObject::HighlightState::None);
    m_renderRange = newSelection;
    auto* selectionStart = m_renderRange.start();
    // Update the selection status of all objects between selectionStart and selectionEnd
    if (selectionStart && selectionStart == m_renderRange.end())
        selectionStart->setSelectionStateIfNeeded(RenderObject::HighlightState::Both);
    else {
        if (selectionStart)
            selectionStart->setSelectionStateIfNeeded(RenderObject::HighlightState::Start);
        if (auto* end = m_renderRange.end())
            end->setSelectionStateIfNeeded(RenderObject::HighlightState::End);
    }

    RenderObject* selectionEnd = nullptr;
    auto* selectionDataEnd = m_renderRange.end();
    if (selectionDataEnd)
        selectionEnd = rendererAfterOffset(*selectionDataEnd, m_renderRange.endOffset().value());
    HighlightIterator selectionIterator(selectionStart);
    for (auto* currentRenderer = selectionStart; currentRenderer && currentRenderer != selectionEnd; currentRenderer = selectionIterator.next()) {
        if (currentRenderer == selectionStart || currentRenderer == m_renderRange.end())
            continue;
        if (!currentRenderer->canBeSelectionLeaf())
            continue;
        currentRenderer->setSelectionStateIfNeeded(RenderObject::HighlightState::Inside);
    }

    if (blockRepaintMode != RepaintMode::Nothing)
        m_renderView.layer()->clearBlockSelectionGapsBounds();

    // Now that the selection state has been updated for the new objects, walk them again and
    // put them in the new objects list.
    SelectionData::RendererMap newSelectedRenderers;
    SelectionData::RenderBlockMap newSelectedBlocks;
    selectionIterator = HighlightIterator(selectionStart);
    for (auto* currentRenderer = selectionStart; currentRenderer && currentRenderer != selectionEnd; currentRenderer = selectionIterator.next()) {
        if (isValidRendererForSelection(*currentRenderer, m_renderRange)) {
            std::unique_ptr<RenderSelectionInfo> selectionInfo = makeUnique<RenderSelectionInfo>(*currentRenderer, true);
#if ENABLE(SERVICE_CONTROLS)
            for (auto& rect : selectionInfo->collectedSelectionRects())
                m_selectionRectGatherer.addRect(selectionInfo->repaintContainer(), rect);
            if (!currentRenderer->isTextOrLineBreak())
                m_selectionRectGatherer.setTextOnly(false);
#endif
            newSelectedRenderers.set(currentRenderer, WTFMove(selectionInfo));
            auto* containingBlock = currentRenderer->containingBlock();
            while (containingBlock && !is<RenderView>(*containingBlock)) {
                std::unique_ptr<RenderBlockSelectionInfo>& blockInfo = newSelectedBlocks.add(containingBlock, nullptr).iterator->value;
                if (blockInfo)
                    break;
                blockInfo = makeUnique<RenderBlockSelectionInfo>(*containingBlock);
                containingBlock = containingBlock->containingBlock();
#if ENABLE(SERVICE_CONTROLS)
                m_selectionRectGatherer.addGapRects(blockInfo->repaintContainer(), blockInfo->rects());
#endif
            }
        }
    }

    if (blockRepaintMode == RepaintMode::Nothing)
        return;

    // Have any of the old selected objects changed compared to the new selection?
    for (auto& selectedRendererInfo : oldSelectionData.renderers) {
        auto* renderer = selectedRendererInfo.key;
        auto* newInfo = newSelectedRenderers.get(renderer);
        auto* oldInfo = selectedRendererInfo.value.get();
        if (!newInfo || oldInfo->rect() != newInfo->rect() || oldInfo->state() != newInfo->state()
            || (m_renderRange.start() == renderer && oldSelectionData.startOffset != m_renderRange.startOffset())
            || (m_renderRange.end() == renderer && oldSelectionData.endOffset != m_renderRange.endOffset())) {
            oldInfo->repaint();
            if (newInfo) {
                newInfo->repaint();
                newSelectedRenderers.remove(renderer);
            }
        }
    }

    // Any new objects that remain were not found in the old objects dict, and so they need to be updated.
    for (auto& selectedRendererInfo : newSelectedRenderers)
        selectedRendererInfo.value->repaint();

    // Have any of the old blocks changed?
    for (auto& selectedBlockInfo : oldSelectionData.blocks) {
        auto* block = selectedBlockInfo.key;
        auto* newInfo = newSelectedBlocks.get(block);
        auto* oldInfo = selectedBlockInfo.value.get();
        if (!newInfo || oldInfo->rects() != newInfo->rects() || oldInfo->state() != newInfo->state()) {
            oldInfo->repaint();
            if (newInfo) {
                newInfo->repaint();
                newSelectedBlocks.remove(block);
            }
        }
    }

    // Any new blocks that remain were not found in the old blocks dict, and so they need to be updated.
    for (auto& selectedBlockInfo : newSelectedBlocks)
        selectedBlockInfo.value->repaint();
}

} // namespace WebCore