Use "= default" to denote default constructor or destructor
[WebKit-https.git] / Source / WebCore / page / TextIndicator.cpp
1 /*
2  * Copyright (C) 2010, 2015-2016 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 "ColorHash.h"
30 #include "Document.h"
31 #include "Editor.h"
32 #include "Element.h"
33 #include "Frame.h"
34 #include "FrameSelection.h"
35 #include "FrameSnapshotting.h"
36 #include "FrameView.h"
37 #include "GeometryUtilities.h"
38 #include "GraphicsContext.h"
39 #include "ImageBuffer.h"
40 #include "IntRect.h"
41 #include "NodeTraversal.h"
42 #include "Range.h"
43 #include "RenderElement.h"
44 #include "RenderObject.h"
45 #include "RenderText.h"
46 #include "TextIterator.h"
47 #include "TextPaintStyle.h"
48
49 #if PLATFORM(IOS)
50 #include "SelectionRect.h"
51 #endif
52
53 using namespace WebCore;
54
55 namespace WebCore {
56
57 static bool initializeIndicator(TextIndicatorData&, Frame&, const Range&, FloatSize margin, bool indicatesCurrentSelection);
58
59 TextIndicator::TextIndicator(const TextIndicatorData& data)
60     : m_data(data)
61 {
62 }
63
64 TextIndicator::~TextIndicator() = default;
65
66 Ref<TextIndicator> TextIndicator::create(const TextIndicatorData& data)
67 {
68     return adoptRef(*new TextIndicator(data));
69 }
70
71 RefPtr<TextIndicator> TextIndicator::createWithRange(const Range& range, TextIndicatorOptions options, TextIndicatorPresentationTransition presentationTransition, FloatSize margin)
72 {
73     Frame* frame = range.startContainer().document().frame();
74
75     if (!frame)
76         return nullptr;
77
78     Ref<Frame> protector(*frame);
79
80     VisibleSelection oldSelection = frame->selection().selection();
81     TemporarySelectionOptions temporarySelectionOptions = TemporarySelectionOptionDefault;
82 #if PLATFORM(IOS)
83     temporarySelectionOptions |= TemporarySelectionOptionIgnoreSelectionChanges;
84     temporarySelectionOptions |= TemporarySelectionOptionEnableAppearanceUpdates;
85 #endif
86     TemporarySelectionChange selectionChange(*frame, { range }, temporarySelectionOptions);
87
88     TextIndicatorData data;
89
90     data.presentationTransition = presentationTransition;
91     data.options = options;
92
93     bool indicatesCurrentSelection = areRangesEqual(&range, oldSelection.toNormalizedRange().get());
94
95     if (!initializeIndicator(data, *frame, range, margin, indicatesCurrentSelection))
96         return nullptr;
97
98     return TextIndicator::create(data);
99 }
100
101 RefPtr<TextIndicator> TextIndicator::createWithSelectionInFrame(Frame& frame, TextIndicatorOptions options, TextIndicatorPresentationTransition presentationTransition, FloatSize margin)
102 {
103     RefPtr<Range> range = frame.selection().toNormalizedRange();
104     if (!range)
105         return nullptr;
106
107     TextIndicatorData data;
108
109     data.presentationTransition = presentationTransition;
110     data.options = options;
111
112     if (!initializeIndicator(data, frame, *range, margin, true))
113         return nullptr;
114
115     return TextIndicator::create(data);
116 }
117
118 static bool hasNonInlineOrReplacedElements(const Range& range)
119 {
120     Node* stopNode = range.pastLastNode();
121     for (Node* node = range.firstNode(); node != stopNode; node = NodeTraversal::next(*node)) {
122         if (!node)
123             continue;
124         RenderObject* renderer = node->renderer();
125         if (!renderer)
126             continue;
127         if ((!renderer->isInline() || renderer->isReplaced()) && range.intersectsNode(*node).releaseReturnValue())
128             return true;
129     }
130
131     return false;
132 }
133
134 static SnapshotOptions snapshotOptionsForTextIndicatorOptions(TextIndicatorOptions options)
135 {
136     SnapshotOptions snapshotOptions = SnapshotOptionsNone;
137     if (!(options & TextIndicatorOptionRespectTextColor))
138         snapshotOptions |= SnapshotOptionsForceBlackText;
139
140     if (!(options & TextIndicatorOptionPaintAllContent)) {
141         if (options & TextIndicatorOptionPaintBackgrounds)
142             snapshotOptions |= SnapshotOptionsPaintSelectionAndBackgroundsOnly;
143         else
144             snapshotOptions |= SnapshotOptionsPaintSelectionOnly;
145     } else
146         snapshotOptions |= SnapshotOptionsExcludeSelectionHighlighting;
147
148     return snapshotOptions;
149 }
150
151 static RefPtr<Image> takeSnapshot(Frame& frame, IntRect rect, SnapshotOptions options, float& scaleFactor, const Vector<FloatRect>& clipRectsInDocumentCoordinates)
152 {
153     std::unique_ptr<ImageBuffer> buffer = snapshotFrameRectWithClip(frame, rect, clipRectsInDocumentCoordinates, options);
154     if (!buffer)
155         return nullptr;
156     scaleFactor = buffer->resolutionScale();
157     return ImageBuffer::sinkIntoImage(WTFMove(buffer), Unscaled);
158 }
159
160 static bool takeSnapshots(TextIndicatorData& data, Frame& frame, IntRect snapshotRect, const Vector<FloatRect>& clipRectsInDocumentCoordinates)
161 {
162     SnapshotOptions snapshotOptions = snapshotOptionsForTextIndicatorOptions(data.options);
163
164     data.contentImage = takeSnapshot(frame, snapshotRect, snapshotOptions, data.contentImageScaleFactor, clipRectsInDocumentCoordinates);
165     if (!data.contentImage)
166         return false;
167
168     if (data.options & TextIndicatorOptionIncludeSnapshotWithSelectionHighlight) {
169         float snapshotScaleFactor;
170         data.contentImageWithHighlight = takeSnapshot(frame, snapshotRect, SnapshotOptionsNone, snapshotScaleFactor, clipRectsInDocumentCoordinates);
171         ASSERT(!data.contentImageWithHighlight || data.contentImageScaleFactor == snapshotScaleFactor);
172     }
173
174     if (data.options & TextIndicatorOptionIncludeSnapshotOfAllVisibleContentWithoutSelection) {
175         float snapshotScaleFactor;
176         auto snapshotRect = frame.view()->visibleContentRect();
177         data.contentImageWithoutSelection = takeSnapshot(frame, snapshotRect, SnapshotOptionsPaintEverythingExcludingSelection, snapshotScaleFactor, { });
178         data.contentImageWithoutSelectionRectInRootViewCoordinates = frame.view()->contentsToRootView(snapshotRect);
179     }
180     
181     return true;
182 }
183
184 #if PLATFORM(IOS)
185
186 static void getSelectionRectsForRange(Vector<FloatRect>& resultingRects, const Range& range)
187 {
188     Vector<SelectionRect> selectionRectsForRange;
189     Vector<FloatRect> selectionRectsForRangeInBoundingRectCoordinates;
190     range.collectSelectionRects(selectionRectsForRange);
191     for (auto selectionRect : selectionRectsForRange)
192         resultingRects.append(selectionRect.rect());
193 }
194
195 #endif
196
197 static bool styleContainsComplexBackground(const RenderStyle& style)
198 {
199     if (style.hasBlendMode())
200         return true;
201
202     if (style.hasBackgroundImage())
203         return true;
204
205     if (style.hasBackdropFilter())
206         return true;
207
208     return false;
209 }
210
211 static HashSet<Color> estimatedTextColorsForRange(const Range& range)
212 {
213     HashSet<Color> colors;
214     for (TextIterator iterator(&range); !iterator.atEnd(); iterator.advance()) {
215         auto* node = iterator.node();
216         if (!is<Text>(node) || !is<RenderText>(node->renderer()))
217             continue;
218
219         colors.add(node->renderer()->style().color());
220     }
221     return colors;
222 }
223
224 static Color estimatedBackgroundColorForRange(const Range& range, const Frame& frame)
225 {
226     auto estimatedBackgroundColor = frame.view() ? frame.view()->documentBackgroundColor() : Color::transparent;
227
228     RenderElement* renderer = nullptr;
229     auto commonAncestor = range.commonAncestorContainer();
230     while (commonAncestor) {
231         if (is<RenderElement>(commonAncestor->renderer())) {
232             renderer = downcast<RenderElement>(commonAncestor->renderer());
233             break;
234         }
235         commonAncestor = commonAncestor->parentOrShadowHostElement();
236     }
237
238     auto boundingRectForRange = enclosingIntRect(range.absoluteBoundingRect(Range::RespectClippingForTextRects::Yes));
239     Vector<Color> parentRendererBackgroundColors;
240     for (; !!renderer; renderer = renderer->parent()) {
241         auto absoluteBoundingBox = renderer->absoluteBoundingBoxRect();
242         auto& style = renderer->style();
243         if (!absoluteBoundingBox.contains(boundingRectForRange) || !style.hasBackground())
244             continue;
245
246         if (styleContainsComplexBackground(style))
247             return estimatedBackgroundColor;
248
249         auto visitedDependentBackgroundColor = style.visitedDependentColor(CSSPropertyBackgroundColor);
250         if (visitedDependentBackgroundColor != Color::transparent)
251             parentRendererBackgroundColors.append(visitedDependentBackgroundColor);
252     }
253     parentRendererBackgroundColors.reverse();
254     for (auto backgroundColor : parentRendererBackgroundColors)
255         estimatedBackgroundColor = estimatedBackgroundColor.blend(backgroundColor);
256
257     return estimatedBackgroundColor;
258 }
259
260 static bool hasAnyIllegibleColors(TextIndicatorData& data, const Color& backgroundColor, HashSet<Color>&& textColors)
261 {
262     if (data.options & TextIndicatorOptionPaintAllContent)
263         return false;
264
265     if (!(data.options & TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges))
266         return false;
267
268     if (!(data.options & TextIndicatorOptionComputeEstimatedBackgroundColor))
269         return false;
270
271     bool hasOnlyLegibleTextColors = true;
272     if (data.options & TextIndicatorOptionRespectTextColor) {
273         for (auto& textColor : textColors) {
274             hasOnlyLegibleTextColors = textColorIsLegibleAgainstBackgroundColor(textColor, backgroundColor);
275             if (!hasOnlyLegibleTextColors)
276                 break;
277         }
278     } else
279         hasOnlyLegibleTextColors = textColorIsLegibleAgainstBackgroundColor(Color::black, backgroundColor);
280
281     return !hasOnlyLegibleTextColors || textColors.isEmpty();
282 }
283
284 static bool initializeIndicator(TextIndicatorData& data, Frame& frame, const Range& range, FloatSize margin, bool indicatesCurrentSelection)
285 {
286     if (auto* document = frame.document())
287         document->updateLayoutIgnorePendingStylesheets();
288
289     bool treatRangeAsComplexDueToIllegibleTextColors = false;
290     if (data.options & TextIndicatorOptionComputeEstimatedBackgroundColor) {
291         data.estimatedBackgroundColor = estimatedBackgroundColorForRange(range, frame);
292         treatRangeAsComplexDueToIllegibleTextColors = hasAnyIllegibleColors(data, data.estimatedBackgroundColor, estimatedTextColorsForRange(range));
293     }
294
295     Vector<FloatRect> textRects;
296
297     // FIXME (138888): Ideally we wouldn't remove the margin in this case, but we need to
298     // ensure that the indicator and indicator-with-highlight overlap precisely, and
299     // we can't add a margin to the indicator-with-highlight.
300     if (indicatesCurrentSelection && !(data.options & TextIndicatorOptionIncludeMarginIfRangeMatchesSelection))
301         margin = FloatSize();
302
303     FrameSelection::TextRectangleHeight textRectHeight = (data.options & TextIndicatorOptionTightlyFitContent) ? FrameSelection::TextRectangleHeight::TextHeight : FrameSelection::TextRectangleHeight::SelectionHeight;
304
305     if ((data.options & TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges) && (hasNonInlineOrReplacedElements(range) || treatRangeAsComplexDueToIllegibleTextColors))
306         data.options |= TextIndicatorOptionPaintAllContent;
307 #if PLATFORM(IOS)
308     else if (data.options & TextIndicatorOptionUseSelectionRectForSizing)
309         getSelectionRectsForRange(textRects, range);
310 #endif
311     else {
312         Vector<IntRect> absoluteTextRects;
313         range.absoluteTextRects(absoluteTextRects, textRectHeight == FrameSelection::TextRectangleHeight::SelectionHeight, nullptr, Range::RespectClippingForTextRects::Yes);
314
315         textRects.reserveInitialCapacity(absoluteTextRects.size());
316         for (auto& rect : absoluteTextRects)
317             textRects.uncheckedAppend(rect);
318     }
319
320     if (textRects.isEmpty())
321         textRects.append(range.absoluteBoundingRect(Range::RespectClippingForTextRects::Yes));
322
323     auto frameView = frame.view();
324
325     // Use the exposedContentRect/viewExposedRect instead of visibleContentRect to avoid creating a huge indicator for a large view inside a scroll view.
326     IntRect contentsClipRect;
327 #if PLATFORM(IOS)
328     contentsClipRect = enclosingIntRect(frameView->exposedContentRect());
329 #else
330     if (auto viewExposedRect = frameView->viewExposedRect())
331         contentsClipRect = frameView->viewToContents(enclosingIntRect(*viewExposedRect));
332     else
333         contentsClipRect = frameView->visibleContentRect();
334 #endif
335
336     if (data.options & TextIndicatorOptionExpandClipBeyondVisibleRect) {
337         contentsClipRect.inflateX(contentsClipRect.width() / 2);
338         contentsClipRect.inflateY(contentsClipRect.height() / 2);
339     }
340
341     FloatRect textBoundingRectInRootViewCoordinates;
342     FloatRect textBoundingRectInDocumentCoordinates;
343     Vector<FloatRect> clippedTextRectsInDocumentCoordinates;
344     Vector<FloatRect> textRectsInRootViewCoordinates;
345     for (const FloatRect& textRect : textRects) {
346         FloatRect clippedTextRect;
347         if (data.options & TextIndicatorOptionDoNotClipToVisibleRect)
348             clippedTextRect = textRect;
349         else
350             clippedTextRect = intersection(textRect, contentsClipRect);
351         if (clippedTextRect.isEmpty())
352             continue;
353
354         clippedTextRectsInDocumentCoordinates.append(clippedTextRect);
355
356         FloatRect textRectInDocumentCoordinatesIncludingMargin = clippedTextRect;
357         textRectInDocumentCoordinatesIncludingMargin.inflateX(margin.width());
358         textRectInDocumentCoordinatesIncludingMargin.inflateY(margin.height());
359         textBoundingRectInDocumentCoordinates.unite(textRectInDocumentCoordinatesIncludingMargin);
360
361         FloatRect textRectInRootViewCoordinates = frame.view()->contentsToRootView(enclosingIntRect(textRectInDocumentCoordinatesIncludingMargin));
362         textRectsInRootViewCoordinates.append(textRectInRootViewCoordinates);
363         textBoundingRectInRootViewCoordinates.unite(textRectInRootViewCoordinates);
364     }
365
366     Vector<FloatRect> textRectsInBoundingRectCoordinates;
367     for (auto rect : textRectsInRootViewCoordinates) {
368         rect.moveBy(-textBoundingRectInRootViewCoordinates.location());
369         textRectsInBoundingRectCoordinates.append(rect);
370     }
371
372     // Store the selection rect in window coordinates, to be used subsequently
373     // to determine if the indicator and selection still precisely overlap.
374     data.selectionRectInRootViewCoordinates = frame.view()->contentsToRootView(enclosingIntRect(frame.selection().selectionBounds()));
375     data.textBoundingRectInRootViewCoordinates = textBoundingRectInRootViewCoordinates;
376     data.textRectsInBoundingRectCoordinates = textRectsInBoundingRectCoordinates;
377
378     return takeSnapshots(data, frame, enclosingIntRect(textBoundingRectInDocumentCoordinates), clippedTextRectsInDocumentCoordinates);
379 }
380
381 } // namespace WebCore