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