blob: 22cb245c0f36bceae607d17c296734d0ca07013e [file] [log] [blame]
/*
* 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 "Position.h"
#include "Range.h"
#include "RenderLayer.h"
#include "RenderMultiColumnSpannerPlaceholder.h"
#include "RenderObject.h"
#include "RenderView.h"
#include "VisibleSelection.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
{
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));
auto* block = start->containingBlock();
while (block && !is<RenderView>(*block)) {
std::unique_ptr<RenderSelectionInfo>& blockInfo = renderers.add(block, nullptr).iterator->value;
if (blockInfo)
break;
blockInfo = makeUnique<RenderSelectionInfo>(*block, clipToVisibleContent == ClipToVisibleContent::Yes);
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 (auto* repaintContainer = info->repaintContainer()) {
FloatQuad absQuad = repaintContainer->localToAbsoluteQuad(FloatRect(currentRect));
currentRect = absQuad.enclosingBoundingBox();
}
selectionRect.unite(currentRect);
}
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