blob: ea90bf74b9ca76efadb3ae443877ac6209bf2edb [file] [log] [blame]
/*
* Copyright (C) 2010, 2015-2016 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 APPLE INC. AND ITS CONTRIBUTORS ``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 APPLE INC. OR ITS CONTRIBUTORS
* 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 "TextIndicator.h"
#include "Document.h"
#include "Editor.h"
#include "Element.h"
#include "Frame.h"
#include "FrameSelection.h"
#include "FrameSnapshotting.h"
#include "FrameView.h"
#include "GeometryUtilities.h"
#include "GraphicsContext.h"
#include "ImageBuffer.h"
#include "IntRect.h"
#include "NodeTraversal.h"
#include "Range.h"
#include "RenderObject.h"
using namespace WebCore;
namespace WebCore {
static bool initializeIndicator(TextIndicatorData&, Frame&, const Range&, FloatSize margin, bool indicatesCurrentSelection);
TextIndicator::TextIndicator(const TextIndicatorData& data)
: m_data(data)
{
}
TextIndicator::~TextIndicator()
{
}
Ref<TextIndicator> TextIndicator::create(const TextIndicatorData& data)
{
return adoptRef(*new TextIndicator(data));
}
RefPtr<TextIndicator> TextIndicator::createWithRange(const Range& range, TextIndicatorOptions options, TextIndicatorPresentationTransition presentationTransition, FloatSize margin)
{
Frame* frame = range.startContainer().document().frame();
if (!frame)
return nullptr;
Ref<Frame> protector(*frame);
#if PLATFORM(IOS)
frame->editor().setIgnoreCompositionSelectionChange(true);
frame->selection().setUpdateAppearanceEnabled(true);
#endif
VisibleSelection oldSelection = frame->selection().selection();
frame->selection().setSelection(range);
TextIndicatorData data;
data.presentationTransition = presentationTransition;
data.options = options;
bool indicatesCurrentSelection = areRangesEqual(&range, oldSelection.toNormalizedRange().get());
if (!initializeIndicator(data, *frame, range, margin, indicatesCurrentSelection))
return nullptr;
RefPtr<TextIndicator> indicator = TextIndicator::create(data);
frame->selection().setSelection(oldSelection);
#if PLATFORM(IOS)
frame->editor().setIgnoreCompositionSelectionChange(false, Editor::RevealSelection::No);
frame->selection().setUpdateAppearanceEnabled(false);
#endif
return indicator;
}
RefPtr<TextIndicator> TextIndicator::createWithSelectionInFrame(Frame& frame, TextIndicatorOptions options, TextIndicatorPresentationTransition presentationTransition, FloatSize margin)
{
RefPtr<Range> range = frame.selection().toNormalizedRange();
if (!range)
return nullptr;
TextIndicatorData data;
data.presentationTransition = presentationTransition;
data.options = options;
if (!initializeIndicator(data, frame, *range, margin, true))
return nullptr;
return TextIndicator::create(data);
}
static bool hasNonInlineOrReplacedElements(const Range& range)
{
Node* stopNode = range.pastLastNode();
for (Node* node = range.firstNode(); node != stopNode; node = NodeTraversal::next(*node)) {
if (!node)
continue;
RenderObject* renderer = node->renderer();
if (!renderer)
continue;
if ((!renderer->isInline() || renderer->isReplaced()) && range.intersectsNode(*node).releaseReturnValue())
return true;
}
return false;
}
static SnapshotOptions snapshotOptionsForTextIndicatorOptions(TextIndicatorOptions options)
{
SnapshotOptions snapshotOptions = SnapshotOptionsNone;
if (!(options & TextIndicatorOptionRespectTextColor))
snapshotOptions |= SnapshotOptionsForceBlackText;
if (!(options & TextIndicatorOptionPaintAllContent)) {
if (options & TextIndicatorOptionPaintBackgrounds)
snapshotOptions |= SnapshotOptionsPaintSelectionAndBackgroundsOnly;
else
snapshotOptions |= SnapshotOptionsPaintSelectionOnly;
} else
snapshotOptions |= SnapshotOptionsExcludeSelectionHighlighting;
return snapshotOptions;
}
static RefPtr<Image> takeSnapshot(Frame& frame, IntRect rect, SnapshotOptions options, float& scaleFactor, Vector<FloatRect>& clipRectsInDocumentCoordinates)
{
std::unique_ptr<ImageBuffer> buffer = snapshotFrameRectWithClip(frame, rect, clipRectsInDocumentCoordinates, options);
if (!buffer)
return nullptr;
scaleFactor = buffer->resolutionScale();
return ImageBuffer::sinkIntoImage(WTFMove(buffer), Unscaled);
}
static bool takeSnapshots(TextIndicatorData& data, Frame& frame, IntRect snapshotRect, Vector<FloatRect>& clipRectsInDocumentCoordinates)
{
SnapshotOptions snapshotOptions = snapshotOptionsForTextIndicatorOptions(data.options);
data.contentImage = takeSnapshot(frame, snapshotRect, snapshotOptions, data.contentImageScaleFactor, clipRectsInDocumentCoordinates);
if (!data.contentImage)
return false;
if (data.options & TextIndicatorOptionIncludeSnapshotWithSelectionHighlight) {
float snapshotScaleFactor;
data.contentImageWithHighlight = takeSnapshot(frame, snapshotRect, SnapshotOptionsNone, snapshotScaleFactor, clipRectsInDocumentCoordinates);
ASSERT(!data.contentImageWithHighlight || data.contentImageScaleFactor == snapshotScaleFactor);
}
return true;
}
static bool initializeIndicator(TextIndicatorData& data, Frame& frame, const Range& range, FloatSize margin, bool indicatesCurrentSelection)
{
Vector<FloatRect> textRects;
// FIXME (138888): Ideally we wouldn't remove the margin in this case, but we need to
// ensure that the indicator and indicator-with-highlight overlap precisely, and
// we can't add a margin to the indicator-with-highlight.
if (indicatesCurrentSelection && !(data.options & TextIndicatorOptionIncludeMarginIfRangeMatchesSelection))
margin = FloatSize();
FrameSelection::TextRectangleHeight textRectHeight = (data.options & TextIndicatorOptionTightlyFitContent) ? FrameSelection::TextRectangleHeight::TextHeight : FrameSelection::TextRectangleHeight::SelectionHeight;
if ((data.options & TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges) && hasNonInlineOrReplacedElements(range))
data.options |= TextIndicatorOptionPaintAllContent;
else {
if (data.options & TextIndicatorOptionDoNotClipToVisibleRect)
frame.selection().getTextRectangles(textRects, textRectHeight);
else
frame.selection().getClippedVisibleTextRectangles(textRects, textRectHeight);
}
if (textRects.isEmpty()) {
RenderView* renderView = frame.contentRenderer();
if (!renderView)
return false;
FloatRect boundingRect = range.absoluteBoundingRect();
if (data.options & TextIndicatorOptionDoNotClipToVisibleRect)
textRects.append(boundingRect);
else {
// Clip to the visible rect, just like getClippedVisibleTextRectangles does.
// FIXME: We really want to clip to the unobscured rect in both cases, I think.
// (this seems to work on Mac, but maybe not iOS?)
FloatRect visibleContentRect = frame.view()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
textRects.append(intersection(visibleContentRect, boundingRect));
}
}
FloatRect textBoundingRectInRootViewCoordinates;
FloatRect textBoundingRectInDocumentCoordinates;
Vector<FloatRect> textRectsInRootViewCoordinates;
for (const FloatRect& textRect : textRects) {
FloatRect textRectInDocumentCoordinatesIncludingMargin = textRect;
textRectInDocumentCoordinatesIncludingMargin.inflateX(margin.width());
textRectInDocumentCoordinatesIncludingMargin.inflateY(margin.height());
textBoundingRectInDocumentCoordinates.unite(textRectInDocumentCoordinatesIncludingMargin);
FloatRect textRectInRootViewCoordinates = frame.view()->contentsToRootView(enclosingIntRect(textRectInDocumentCoordinatesIncludingMargin));
textRectsInRootViewCoordinates.append(textRectInRootViewCoordinates);
textBoundingRectInRootViewCoordinates.unite(textRectInRootViewCoordinates);
}
Vector<FloatRect> textRectsInBoundingRectCoordinates;
for (auto rect : textRectsInRootViewCoordinates) {
rect.moveBy(-textBoundingRectInRootViewCoordinates.location());
textRectsInBoundingRectCoordinates.append(rect);
}
// Store the selection rect in window coordinates, to be used subsequently
// to determine if the indicator and selection still precisely overlap.
data.selectionRectInRootViewCoordinates = frame.view()->contentsToRootView(enclosingIntRect(frame.selection().selectionBounds()));
data.textBoundingRectInRootViewCoordinates = textBoundingRectInRootViewCoordinates;
data.textRectsInBoundingRectCoordinates = textRectsInBoundingRectCoordinates;
return takeSnapshots(data, frame, enclosingIntRect(textBoundingRectInDocumentCoordinates), textRects);
}
} // namespace WebCore