SelectionRangeData.cpp [plain text]
#include "config.h"
#include "SelectionRangeData.h"
#include "Document.h"
#include "FrameSelection.h"
#include "Position.h"
#include "Range.h"
#include "RenderLayer.h"
#include "RenderMultiColumnSpannerPlaceholder.h"
#include "RenderObject.h"
#include "RenderView.h"
#include "VisibleSelection.h"
namespace WebCore {
namespace {
struct SelectionData {
using RendererMap = HashMap<RenderObject*, std::unique_ptr<RenderSelectionInfo>>;
using RenderBlockMap = HashMap<const RenderBlock*, std::unique_ptr<RenderBlockSelectionInfo>>;
std::optional<unsigned> startPosition;
std::optional<unsigned> endPosition;
RendererMap renderers;
RenderBlockMap blocks;
};
class SelectionIterator {
public:
SelectionIterator(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;
};
}
static RenderObject* rendererAfterPosition(const RenderObject& renderer, unsigned offset)
{
auto* child = renderer.childAt(offset);
return child ? child : renderer.nextInPreOrderAfterChildren();
}
static bool isValidRendererForSelection(const RenderObject& renderer, const SelectionRangeData::Context& selection)
{
return (renderer.canBeSelectionLeaf() || &renderer == selection.start() || &renderer == selection.end())
&& renderer.selectionState() != RenderObject::SelectionNone
&& renderer.containingBlock();
}
static RenderBlock* containingBlockBelowView(const RenderObject& renderer)
{
auto* containingBlock = renderer.containingBlock();
return is<RenderView>(containingBlock) ? nullptr : containingBlock;
}
static SelectionData collect(const SelectionRangeData::Context& selection, bool repaintDifference)
{
SelectionData oldSelectionData { selection.startPosition(), selection.endPosition(), { }, { } };
auto* start = selection.start();
RenderObject* stop = nullptr;
if (selection.end())
stop = rendererAfterPosition(*selection.end(), selection.endPosition().value());
SelectionIterator selectionIterator(start);
while (start && start != stop) {
if (isValidRendererForSelection(*start, selection)) {
oldSelectionData.renderers.set(start, std::make_unique<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 = std::make_unique<RenderBlockSelectionInfo>(*block);
}
}
}
start = selectionIterator.next();
}
return oldSelectionData;
}
SelectionRangeData::SelectionRangeData(RenderView& view)
: m_renderView(view)
#if ENABLE(SERVICE_CONTROLS)
, m_selectionRectGatherer(view)
#endif
{
}
void SelectionRangeData::set(const Context& selection, RepaintMode blockRepaintMode)
{
if ((selection.start() && !selection.end()) || (selection.end() && !selection.start()))
return;
auto isCaret = m_renderView.frame().selection().isCaret();
if (selection == m_selectionContext && m_selectionWasCaret == isCaret)
return;
#if ENABLE(SERVICE_CONTROLS)
auto rectNotifier = m_selectionRectGatherer.clearAndCreateNotifier();
#endif
m_selectionWasCaret = isCaret;
apply(selection, blockRepaintMode);
}
void SelectionRangeData::clear()
{
m_renderView.layer()->repaintBlockSelectionGaps();
set({ }, SelectionRangeData::RepaintMode::NewMinusOld);
}
void SelectionRangeData::repaint() const
{
HashSet<RenderBlock*> processedBlocks;
RenderObject* end = nullptr;
if (m_selectionContext.end())
end = rendererAfterPosition(*m_selectionContext.end(), m_selectionContext.endPosition().value());
SelectionIterator selectionIterator(m_selectionContext.start());
for (auto* renderer = selectionIterator.current(); renderer && renderer != end; renderer = selectionIterator.next()) {
if (!renderer->canBeSelectionLeaf() && renderer != m_selectionContext.start() && renderer != m_selectionContext.end())
continue;
if (renderer->selectionState() == RenderObject::SelectionNone)
continue;
RenderSelectionInfo(*renderer, true).repaint();
for (auto* block = containingBlockBelowView(*renderer); block; block = containingBlockBelowView(*block)) {
if (!processedBlocks.add(block).isNewEntry)
break;
RenderSelectionInfo(*block, true).repaint();
}
}
}
IntRect SelectionRangeData::collectBounds(ClipToVisibleContent clipToVisibleContent) const
{
SelectionData::RendererMap renderers;
auto* start = m_selectionContext.start();
RenderObject* stop = nullptr;
if (m_selectionContext.end())
stop = rendererAfterPosition(*m_selectionContext.end(), m_selectionContext.endPosition().value());
SelectionIterator selectionIterator(start);
while (start && start != stop) {
if ((start->canBeSelectionLeaf() || start == m_selectionContext.start() || start == m_selectionContext.end())
&& start->selectionState() != RenderObject::SelectionNone) {
renderers.set(start, std::make_unique<RenderSelectionInfo>(*start, clipToVisibleContent == ClipToVisibleContent::Yes));
auto* block = start->containingBlock();
while (block && !is<RenderView>(*block)) {
std::unique_ptr<RenderSelectionInfo>& blockInfo = renderers.add(block, nullptr).iterator->value;
if (blockInfo)
break;
blockInfo = std::make_unique<RenderSelectionInfo>(*block, clipToVisibleContent == ClipToVisibleContent::Yes);
block = block->containingBlock();
}
}
start = selectionIterator.next();
}
LayoutRect selectionRect;
for (auto& info : renderers.values()) {
LayoutRect currentRect = info->rect();
if (auto* repaintContainer = info->repaintContainer()) {
FloatQuad absQuad = repaintContainer->localToAbsoluteQuad(FloatRect(currentRect));
currentRect = absQuad.enclosingBoundingBox();
}
selectionRect.unite(currentRect);
}
return snappedIntRect(selectionRect);
}
void SelectionRangeData::apply(const Context& newSelection, RepaintMode blockRepaintMode)
{
auto oldSelectionData = collect(m_selectionContext, blockRepaintMode == RepaintMode::NewXOROld);
for (auto* renderer : oldSelectionData.renderers.keys())
renderer->setSelectionStateIfNeeded(RenderObject::SelectionNone);
m_selectionContext = newSelection;
auto* selectionStart = m_selectionContext.start();
if (selectionStart && selectionStart == m_selectionContext.end())
selectionStart->setSelectionStateIfNeeded(RenderObject::SelectionBoth);
else {
if (selectionStart)
selectionStart->setSelectionStateIfNeeded(RenderObject::SelectionStart);
if (auto* end = m_selectionContext.end())
end->setSelectionStateIfNeeded(RenderObject::SelectionEnd);
}
RenderObject* selectionEnd = nullptr;
auto* selectionDataEnd = m_selectionContext.end();
if (selectionDataEnd)
selectionEnd = rendererAfterPosition(*selectionDataEnd, m_selectionContext.endPosition().value());
SelectionIterator selectionIterator(selectionStart);
for (auto* currentRenderer = selectionStart; currentRenderer && currentRenderer != selectionEnd; currentRenderer = selectionIterator.next()) {
if (currentRenderer == selectionStart || currentRenderer == m_selectionContext.end())
continue;
if (!currentRenderer->canBeSelectionLeaf())
continue;
currentRenderer->setSelectionStateIfNeeded(RenderObject::SelectionInside);
}
if (blockRepaintMode != RepaintMode::Nothing)
m_renderView.layer()->clearBlockSelectionGapsBounds();
SelectionData::RendererMap newSelectedRenderers;
SelectionData::RenderBlockMap newSelectedBlocks;
selectionIterator = SelectionIterator(selectionStart);
for (auto* currentRenderer = selectionStart; currentRenderer && currentRenderer != selectionEnd; currentRenderer = selectionIterator.next()) {
if (isValidRendererForSelection(*currentRenderer, m_selectionContext)) {
std::unique_ptr<RenderSelectionInfo> selectionInfo = std::make_unique<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 = std::make_unique<RenderBlockSelectionInfo>(*containingBlock);
containingBlock = containingBlock->containingBlock();
#if ENABLE(SERVICE_CONTROLS)
m_selectionRectGatherer.addGapRects(blockInfo->repaintContainer(), blockInfo->rects());
#endif
}
}
}
if (blockRepaintMode == RepaintMode::Nothing)
return;
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_selectionContext.start() == renderer && oldSelectionData.startPosition != m_selectionContext.startPosition())
|| (m_selectionContext.end() == renderer && oldSelectionData.endPosition != m_selectionContext.endPosition())) {
oldInfo->repaint();
if (newInfo) {
newInfo->repaint();
newSelectedRenderers.remove(renderer);
}
}
}
for (auto& selectedRendererInfo : newSelectedRenderers)
selectedRendererInfo.value->repaint();
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);
}
}
}
for (auto& selectedBlockInfo : newSelectedBlocks)
selectedBlockInfo.value->repaint();
}
}