Update ANGLE
[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     });
231 }
232
233 static Color estimatedBackgroundColorForRange(const Range& range, const Frame& frame)
234 {
235     auto estimatedBackgroundColor = frame.view() ? frame.view()->documentBackgroundColor() : Color::transparent;
236
237     RenderElement* renderer = nullptr;
238     auto commonAncestor = range.commonAncestorContainer();
239     while (commonAncestor) {
240         if (is<RenderElement>(commonAncestor->renderer())) {
241             renderer = downcast<RenderElement>(commonAncestor->renderer());
242             break;
243         }
244         commonAncestor = commonAncestor->parentOrShadowHostElement();
245     }
246
247     auto boundingRectForRange = enclosingIntRect(absoluteBoundingRectForRange(range));
248     Vector<Color> parentRendererBackgroundColors;
249     for (; !!renderer; renderer = renderer->parent()) {
250         auto absoluteBoundingBox = renderer->absoluteBoundingBoxRect();
251         auto& style = renderer->style();
252         if (!absoluteBoundingBox.contains(boundingRectForRange) || !style.hasBackground())
253             continue;
254
255         if (styleContainsComplexBackground(style))
256             return estimatedBackgroundColor;
257
258         auto visitedDependentBackgroundColor = style.visitedDependentColor(CSSPropertyBackgroundColor);
259         if (visitedDependentBackgroundColor != Color::transparent)
260             parentRendererBackgroundColors.append(visitedDependentBackgroundColor);
261     }
262     parentRendererBackgroundColors.reverse();
263     for (const auto& backgroundColor : parentRendererBackgroundColors)
264         estimatedBackgroundColor = estimatedBackgroundColor.blend(backgroundColor);
265
266     return estimatedBackgroundColor;
267 }
268
269 static bool hasAnyIllegibleColors(TextIndicatorData& data, const Color& backgroundColor, HashSet<Color>&& textColors)
270 {
271     if (data.options & TextIndicatorOptionPaintAllContent)
272         return false;
273
274     if (!(data.options & TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges))
275         return false;
276
277     if (!(data.options & TextIndicatorOptionComputeEstimatedBackgroundColor))
278         return false;
279
280     bool hasOnlyLegibleTextColors = true;
281     if (data.options & TextIndicatorOptionRespectTextColor) {
282         for (auto& textColor : textColors) {
283             hasOnlyLegibleTextColors = textColorIsLegibleAgainstBackgroundColor(textColor, backgroundColor);
284             if (!hasOnlyLegibleTextColors)
285                 break;
286         }
287     } else
288         hasOnlyLegibleTextColors = textColorIsLegibleAgainstBackgroundColor(Color::black, backgroundColor);
289
290     return !hasOnlyLegibleTextColors || textColors.isEmpty();
291 }
292
293 static bool initializeIndicator(TextIndicatorData& data, Frame& frame, const Range& range, FloatSize margin, bool indicatesCurrentSelection)
294 {
295     if (auto* document = frame.document())
296         document->updateLayoutIgnorePendingStylesheets();
297
298     bool treatRangeAsComplexDueToIllegibleTextColors = false;
299     if (data.options & TextIndicatorOptionComputeEstimatedBackgroundColor) {
300         data.estimatedBackgroundColor = estimatedBackgroundColorForRange(range, frame);
301         treatRangeAsComplexDueToIllegibleTextColors = hasAnyIllegibleColors(data, data.estimatedBackgroundColor, estimatedTextColorsForRange(range));
302     }
303
304     Vector<FloatRect> textRects;
305
306     // FIXME (138888): Ideally we wouldn't remove the margin in this case, but we need to
307     // ensure that the indicator and indicator-with-highlight overlap precisely, and
308     // we can't add a margin to the indicator-with-highlight.
309     if (indicatesCurrentSelection && !(data.options & TextIndicatorOptionIncludeMarginIfRangeMatchesSelection))
310         margin = FloatSize();
311
312     FrameSelection::TextRectangleHeight textRectHeight = (data.options & TextIndicatorOptionTightlyFitContent) ? FrameSelection::TextRectangleHeight::TextHeight : FrameSelection::TextRectangleHeight::SelectionHeight;
313
314     if ((data.options & TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges) && (hasNonInlineOrReplacedElements(range) || treatRangeAsComplexDueToIllegibleTextColors))
315         data.options |= TextIndicatorOptionPaintAllContent;
316 #if PLATFORM(IOS_FAMILY)
317     else if (data.options & TextIndicatorOptionUseSelectionRectForSizing)
318         getSelectionRectsForRange(textRects, range);
319 #endif
320     else {
321         Vector<IntRect> absoluteTextRects;
322         range.absoluteTextRects(absoluteTextRects, textRectHeight == FrameSelection::TextRectangleHeight::SelectionHeight, nullptr, Range::BoundingRectBehavior::RespectClipping);
323
324         textRects.reserveInitialCapacity(absoluteTextRects.size());
325         for (auto& rect : absoluteTextRects)
326             textRects.uncheckedAppend(rect);
327     }
328
329     if (textRects.isEmpty())
330         textRects.append(absoluteBoundingRectForRange(range));
331
332     auto frameView = frame.view();
333
334     // Use the exposedContentRect/viewExposedRect instead of visibleContentRect to avoid creating a huge indicator for a large view inside a scroll view.
335     IntRect contentsClipRect;
336 #if PLATFORM(IOS_FAMILY)
337     contentsClipRect = enclosingIntRect(frameView->exposedContentRect());
338 #else
339     if (auto viewExposedRect = frameView->viewExposedRect())
340         contentsClipRect = frameView->viewToContents(enclosingIntRect(*viewExposedRect));
341     else
342         contentsClipRect = frameView->visibleContentRect();
343 #endif
344
345     if (data.options & TextIndicatorOptionExpandClipBeyondVisibleRect) {
346         contentsClipRect.inflateX(contentsClipRect.width() / 2);
347         contentsClipRect.inflateY(contentsClipRect.height() / 2);
348     }
349
350     FloatRect textBoundingRectInRootViewCoordinates;
351     FloatRect textBoundingRectInDocumentCoordinates;
352     Vector<FloatRect> clippedTextRectsInDocumentCoordinates;
353     Vector<FloatRect> textRectsInRootViewCoordinates;
354     for (const FloatRect& textRect : textRects) {
355         FloatRect clippedTextRect;
356         if (data.options & TextIndicatorOptionDoNotClipToVisibleRect)
357             clippedTextRect = textRect;
358         else
359             clippedTextRect = intersection(textRect, contentsClipRect);
360         if (clippedTextRect.isEmpty())
361             continue;
362
363         clippedTextRectsInDocumentCoordinates.append(clippedTextRect);
364
365         FloatRect textRectInDocumentCoordinatesIncludingMargin = clippedTextRect;
366         textRectInDocumentCoordinatesIncludingMargin.inflateX(margin.width());
367         textRectInDocumentCoordinatesIncludingMargin.inflateY(margin.height());
368         textBoundingRectInDocumentCoordinates.unite(textRectInDocumentCoordinatesIncludingMargin);
369
370         FloatRect textRectInRootViewCoordinates = frame.view()->contentsToRootView(enclosingIntRect(textRectInDocumentCoordinatesIncludingMargin));
371         textRectsInRootViewCoordinates.append(textRectInRootViewCoordinates);
372         textBoundingRectInRootViewCoordinates.unite(textRectInRootViewCoordinates);
373     }
374
375     Vector<FloatRect> textRectsInBoundingRectCoordinates;
376     for (auto rect : textRectsInRootViewCoordinates) {
377         rect.moveBy(-textBoundingRectInRootViewCoordinates.location());
378         textRectsInBoundingRectCoordinates.append(rect);
379     }
380
381     // Store the selection rect in window coordinates, to be used subsequently
382     // to determine if the indicator and selection still precisely overlap.
383     data.selectionRectInRootViewCoordinates = frame.view()->contentsToRootView(enclosingIntRect(frame.selection().selectionBounds(FrameSelection::ClipToVisibleContent::No)));
384     data.textBoundingRectInRootViewCoordinates = textBoundingRectInRootViewCoordinates;
385     data.textRectsInBoundingRectCoordinates = textRectsInBoundingRectCoordinates;
386
387     return takeSnapshots(data, frame, enclosingIntRect(textBoundingRectInDocumentCoordinates), clippedTextRectsInDocumentCoordinates);
388 }
389
390 } // namespace WebCore