Replace WTF::move with WTFMove
[WebKit-https.git] / Source / WebCore / page / TextIndicator.cpp
1 /*
2  * Copyright (C) 2010, 2015 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "TextIndicator.h"
28
29 #include "Document.h"
30 #include "Editor.h"
31 #include "Element.h"
32 #include "Frame.h"
33 #include "FrameSelection.h"
34 #include "FrameSnapshotting.h"
35 #include "FrameView.h"
36 #include "GeometryUtilities.h"
37 #include "GraphicsContext.h"
38 #include "ImageBuffer.h"
39 #include "IntRect.h"
40 #include "NodeTraversal.h"
41 #include "Page.h"
42 #include "Range.h"
43 #include "RenderObject.h"
44
45 using namespace WebCore;
46
47 namespace WebCore {
48
49 static bool initializeIndicator(TextIndicatorData&, Frame&, const Range&, FloatSize margin, bool indicatesCurrentSelection);
50
51 TextIndicator::TextIndicator(const TextIndicatorData& data)
52     : m_data(data)
53 {
54 }
55
56 TextIndicator::~TextIndicator()
57 {
58 }
59
60 Ref<TextIndicator> TextIndicator::create(const TextIndicatorData& data)
61 {
62     return adoptRef(*new TextIndicator(data));
63 }
64
65 RefPtr<TextIndicator> TextIndicator::createWithRange(const Range& range, TextIndicatorOptions options, TextIndicatorPresentationTransition presentationTransition, FloatSize margin)
66 {
67     Frame* frame = range.startContainer().document().frame();
68
69     if (!frame)
70         return nullptr;
71
72 #if PLATFORM(IOS)
73     frame->editor().setIgnoreCompositionSelectionChange(true);
74     frame->selection().setUpdateAppearanceEnabled(true);
75 #endif
76
77     VisibleSelection oldSelection = frame->selection().selection();
78     frame->selection().setSelection(range);
79
80     TextIndicatorData data;
81
82     data.presentationTransition = presentationTransition;
83     data.options = options;
84
85     bool indicatesCurrentSelection = areRangesEqual(&range, oldSelection.toNormalizedRange().get());
86
87     if (!initializeIndicator(data, *frame, range, margin, indicatesCurrentSelection))
88         return nullptr;
89
90     RefPtr<TextIndicator> indicator = TextIndicator::create(data);
91
92     frame->selection().setSelection(oldSelection);
93
94 #if PLATFORM(IOS)
95     frame->editor().setIgnoreCompositionSelectionChange(false, Editor::RevealSelection::No);
96     frame->selection().setUpdateAppearanceEnabled(false);
97 #endif
98
99     return indicator;
100 }
101
102 RefPtr<TextIndicator> TextIndicator::createWithSelectionInFrame(Frame& frame, TextIndicatorOptions options, TextIndicatorPresentationTransition presentationTransition, FloatSize margin)
103 {
104     RefPtr<Range> range = frame.selection().toNormalizedRange();
105     if (!range)
106         return nullptr;
107
108     TextIndicatorData data;
109
110     data.presentationTransition = presentationTransition;
111     data.options = options;
112
113     if (!initializeIndicator(data, frame, *range, margin, true))
114         return nullptr;
115
116     return TextIndicator::create(data);
117 }
118
119 static bool hasNonInlineOrReplacedElements(const Range& range)
120 {
121     Node* stopNode = range.pastLastNode();
122     for (Node* node = range.firstNode(); node != stopNode; node = NodeTraversal::next(*node)) {
123         if (!node)
124             continue;
125         RenderObject* renderer = node->renderer();
126         if (!renderer)
127             continue;
128         if ((!renderer->isInline() || renderer->isReplaced()) && range.intersectsNode(node, ASSERT_NO_EXCEPTION))
129             return true;
130     }
131
132     return false;
133 }
134
135 static SnapshotOptions snapshotOptionsForTextIndicatorOptions(TextIndicatorOptions options)
136 {
137     SnapshotOptions snapshotOptions = SnapshotOptionsNone;
138     if (!(options & TextIndicatorOptionRespectTextColor))
139         snapshotOptions |= SnapshotOptionsForceBlackText;
140
141     if (!(options & TextIndicatorOptionPaintAllContent)) {
142         if (options & TextIndicatorOptionPaintBackgrounds)
143             snapshotOptions |= SnapshotOptionsPaintSelectionAndBackgroundsOnly;
144         else
145             snapshotOptions |= SnapshotOptionsPaintSelectionOnly;
146     } else
147         snapshotOptions |= SnapshotOptionsExcludeSelectionHighlighting;
148
149     return snapshotOptions;
150 }
151
152 static RefPtr<Image> takeSnapshot(Frame& frame, IntRect rect, SnapshotOptions options, float& scaleFactor)
153 {
154     std::unique_ptr<ImageBuffer> buffer = snapshotFrameRect(frame, rect, options);
155     if (!buffer)
156         return nullptr;
157     scaleFactor = buffer->resolutionScale();
158     return ImageBuffer::sinkIntoImage(WTFMove(buffer), Unscaled);
159 }
160
161 static bool takeSnapshots(TextIndicatorData& data, Frame& frame, IntRect snapshotRect)
162 {
163     SnapshotOptions snapshotOptions = snapshotOptionsForTextIndicatorOptions(data.options);
164
165     data.contentImage = takeSnapshot(frame, snapshotRect, snapshotOptions, data.contentImageScaleFactor);
166     if (!data.contentImage)
167         return false;
168
169     if (data.options & TextIndicatorOptionIncludeSnapshotWithSelectionHighlight) {
170         float snapshotScaleFactor;
171         data.contentImageWithHighlight = takeSnapshot(frame, snapshotRect, SnapshotOptionsNone, snapshotScaleFactor);
172         ASSERT(!data.contentImageWithHighlight || data.contentImageScaleFactor == snapshotScaleFactor);
173     }
174     
175     return true;
176 }
177
178 static bool initializeIndicator(TextIndicatorData& data, Frame& frame, const Range& range, FloatSize margin, bool indicatesCurrentSelection)
179 {
180     Vector<FloatRect> textRects;
181
182     // FIXME (138888): Ideally we wouldn't remove the margin in this case, but we need to
183     // ensure that the indicator and indicator-with-highlight overlap precisely, and
184     // we can't add a margin to the indicator-with-highlight.
185     if (indicatesCurrentSelection && !(data.options & TextIndicatorOptionIncludeMarginIfRangeMatchesSelection))
186         margin = FloatSize();
187
188     FrameSelection::TextRectangleHeight textRectHeight = (data.options & TextIndicatorOptionTightlyFitContent) ? FrameSelection::TextRectangleHeight::TextHeight : FrameSelection::TextRectangleHeight::SelectionHeight;
189
190     if ((data.options & TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges) && hasNonInlineOrReplacedElements(range))
191         data.options |= TextIndicatorOptionPaintAllContent;
192     else {
193         if (data.options & TextIndicatorOptionDoNotClipToVisibleRect)
194             frame.selection().getTextRectangles(textRects, textRectHeight);
195         else
196             frame.selection().getClippedVisibleTextRectangles(textRects, textRectHeight);
197     }
198
199     if (textRects.isEmpty()) {
200         RenderView* renderView = frame.contentRenderer();
201         if (!renderView)
202             return false;
203         FloatRect boundingRect = range.absoluteBoundingRect();
204         if (data.options & TextIndicatorOptionDoNotClipToVisibleRect)
205             textRects.append(boundingRect);
206         else {
207             // Clip to the visible rect, just like getClippedVisibleTextRectangles does.
208             // FIXME: We really want to clip to the unobscured rect in both cases, I think.
209             // (this seems to work on Mac, but maybe not iOS?)
210             FloatRect visibleContentRect = frame.view()->visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect);
211             textRects.append(intersection(visibleContentRect, boundingRect));
212         }
213     }
214
215     FloatRect textBoundingRectInRootViewCoordinates;
216     FloatRect textBoundingRectInDocumentCoordinates;
217     Vector<FloatRect> textRectsInRootViewCoordinates;
218     for (const FloatRect& textRect : textRects) {
219         FloatRect textRectInDocumentCoordinatesIncludingMargin = textRect;
220         textRectInDocumentCoordinatesIncludingMargin.inflateX(margin.width());
221         textRectInDocumentCoordinatesIncludingMargin.inflateY(margin.height());
222         textBoundingRectInDocumentCoordinates.unite(textRectInDocumentCoordinatesIncludingMargin);
223
224         FloatRect textRectInRootViewCoordinates = frame.view()->contentsToRootView(enclosingIntRect(textRectInDocumentCoordinatesIncludingMargin));
225         textRectsInRootViewCoordinates.append(textRectInRootViewCoordinates);
226         textBoundingRectInRootViewCoordinates.unite(textRectInRootViewCoordinates);
227     }
228
229     Vector<FloatRect> textRectsInBoundingRectCoordinates;
230     for (auto rect : textRectsInRootViewCoordinates) {
231         rect.moveBy(-textBoundingRectInRootViewCoordinates.location());
232         textRectsInBoundingRectCoordinates.append(rect);
233     }
234
235     // Store the selection rect in window coordinates, to be used subsequently
236     // to determine if the indicator and selection still precisely overlap.
237     data.selectionRectInRootViewCoordinates = frame.view()->contentsToRootView(enclosingIntRect(frame.selection().selectionBounds()));
238     data.textBoundingRectInRootViewCoordinates = textBoundingRectInRootViewCoordinates;
239     data.textRectsInBoundingRectCoordinates = textRectsInBoundingRectCoordinates;
240
241     return takeSnapshots(data, frame, enclosingIntRect(textBoundingRectInDocumentCoordinates));
242 }
243
244 } // namespace WebCore