[iOS WK2] Presenting an action sheet on an image map prevents selection UI from updating
[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 "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 "Range.h"
42 #include "RenderElement.h"
43 #include "RenderObject.h"
44 #include "RenderText.h"
45 #include "TextIterator.h"
46 #include "TextPaintStyle.h"
47
48 #if PLATFORM(IOS)
49 #include "SelectionRect.h"
50 #endif
51
52 using namespace WebCore;
53
54 namespace WebCore {
55
56 static bool initializeIndicator(TextIndicatorData&, Frame&, const Range&, FloatSize margin, bool indicatesCurrentSelection);
57
58 TextIndicator::TextIndicator(const TextIndicatorData& data)
59     : m_data(data)
60 {
61 }
62
63 TextIndicator::~TextIndicator()
64 {
65 }
66
67 Ref<TextIndicator> TextIndicator::create(const TextIndicatorData& data)
68 {
69     return adoptRef(*new TextIndicator(data));
70 }
71
72 RefPtr<TextIndicator> TextIndicator::createWithRange(const Range& range, TextIndicatorOptions options, TextIndicatorPresentationTransition presentationTransition, FloatSize margin)
73 {
74     Frame* frame = range.startContainer().document().frame();
75
76     if (!frame)
77         return nullptr;
78
79     Ref<Frame> protector(*frame);
80
81     VisibleSelection oldSelection = frame->selection().selection();
82     TemporarySelectionOptions temporarySelectionOptions = TemporarySelectionOptionDefault;
83 #if PLATFORM(IOS)
84     temporarySelectionOptions |= TemporarySelectionOptionIgnoreSelectionChanges;
85     temporarySelectionOptions |= TemporarySelectionOptionEnableAppearanceUpdates;
86 #endif
87     TemporarySelectionChange selectionChange(*frame, { range }, temporarySelectionOptions);
88
89     TextIndicatorData data;
90
91     data.presentationTransition = presentationTransition;
92     data.options = options;
93
94     bool indicatesCurrentSelection = areRangesEqual(&range, oldSelection.toNormalizedRange().get());
95
96     if (!initializeIndicator(data, *frame, range, margin, indicatesCurrentSelection))
97         return nullptr;
98
99     return TextIndicator::create(data);
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).releaseReturnValue())
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, 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), Unscaled);
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)
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 Vector<Color> estimatedTextColorsForRange(const Range& range)
213 {
214     Vector<Color> colors;
215     HashSet<RGBA32> uniqueRGBValues;
216     for (TextIterator iterator(&range); !iterator.atEnd(); iterator.advance()) {
217         auto* node = iterator.node();
218         if (!is<Text>(node) || !is<RenderText>(node->renderer()))
219             continue;
220
221         auto& color = node->renderer()->style().color();
222         if (uniqueRGBValues.contains(color.rgb()))
223             continue;
224
225         uniqueRGBValues.add(color.rgb());
226         colors.append(color);
227     }
228     return colors;
229 }
230
231 static Color estimatedBackgroundColorForRange(const Range& range, const Frame& frame)
232 {
233     auto estimatedBackgroundColor = frame.view() ? frame.view()->documentBackgroundColor() : Color::transparent;
234
235     RenderElement* renderer = nullptr;
236     auto commonAncestor = range.commonAncestorContainer();
237     while (commonAncestor) {
238         if (is<RenderElement>(commonAncestor->renderer())) {
239             renderer = downcast<RenderElement>(commonAncestor->renderer());
240             break;
241         }
242         commonAncestor = commonAncestor->parentOrShadowHostElement();
243     }
244
245     auto boundingRectForRange = enclosingIntRect(range.absoluteBoundingRect());
246     Vector<Color> parentRendererBackgroundColors;
247     for (; !!renderer; renderer = renderer->parent()) {
248         auto absoluteBoundingBox = renderer->absoluteBoundingBoxRect();
249         auto& style = renderer->style();
250         if (!absoluteBoundingBox.contains(boundingRectForRange) || !style.hasBackground())
251             continue;
252
253         if (styleContainsComplexBackground(style))
254             return estimatedBackgroundColor;
255
256         auto visitedDependentBackgroundColor = style.visitedDependentColor(CSSPropertyBackgroundColor);
257         if (visitedDependentBackgroundColor != Color::transparent)
258             parentRendererBackgroundColors.append(visitedDependentBackgroundColor);
259     }
260     parentRendererBackgroundColors.reverse();
261     for (auto backgroundColor : parentRendererBackgroundColors)
262         estimatedBackgroundColor = estimatedBackgroundColor.blend(backgroundColor);
263
264     return estimatedBackgroundColor;
265 }
266
267 static void adjustTextIndicatorDataOptionsForEstimatedColorsIfNecessary(TextIndicatorData& data, const Color& backgroundColor, Vector<Color>&& textColors)
268 {
269     if (data.options & TextIndicatorOptionPaintAllContent)
270         return;
271
272     if (!(data.options & TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges))
273         return;
274
275     bool hasOnlyLegibleTextColors = true;
276     if (data.options & TextIndicatorOptionRespectTextColor) {
277         for (auto& textColor : textColors) {
278             hasOnlyLegibleTextColors = textColorIsLegibleAgainstBackgroundColor(textColor, backgroundColor);
279             if (!hasOnlyLegibleTextColors)
280                 break;
281         }
282     } else
283         hasOnlyLegibleTextColors = textColorIsLegibleAgainstBackgroundColor(Color::black, backgroundColor);
284
285     if (!hasOnlyLegibleTextColors || !textColors.size()) {
286         // If the text color is not legible against the estimated color, force all content to be painted.
287         data.options &= ~TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges;
288         data.options |= TextIndicatorOptionPaintAllContent;
289     }
290 }
291
292 static bool initializeIndicator(TextIndicatorData& data, Frame& frame, const Range& range, FloatSize margin, bool indicatesCurrentSelection)
293 {
294     if (auto* document = frame.document())
295         document->updateLayoutIgnorePendingStylesheets();
296
297     if (data.options & TextIndicatorOptionComputeEstimatedBackgroundColor) {
298         data.estimatedBackgroundColor = estimatedBackgroundColorForRange(range, frame);
299         adjustTextIndicatorDataOptionsForEstimatedColorsIfNecessary(data, data.estimatedBackgroundColor, estimatedTextColorsForRange(range));
300     }
301
302     Vector<FloatRect> textRects;
303
304     // FIXME (138888): Ideally we wouldn't remove the margin in this case, but we need to
305     // ensure that the indicator and indicator-with-highlight overlap precisely, and
306     // we can't add a margin to the indicator-with-highlight.
307     if (indicatesCurrentSelection && !(data.options & TextIndicatorOptionIncludeMarginIfRangeMatchesSelection))
308         margin = FloatSize();
309
310     FrameSelection::TextRectangleHeight textRectHeight = (data.options & TextIndicatorOptionTightlyFitContent) ? FrameSelection::TextRectangleHeight::TextHeight : FrameSelection::TextRectangleHeight::SelectionHeight;
311
312     if ((data.options & TextIndicatorOptionUseBoundingRectAndPaintAllContentForComplexRanges) && hasNonInlineOrReplacedElements(range))
313         data.options |= TextIndicatorOptionPaintAllContent;
314 #if PLATFORM(IOS)
315     else if (data.options & TextIndicatorOptionUseSelectionRectForSizing)
316         getSelectionRectsForRange(textRects, range);
317 #endif
318     else
319         frame.selection().getTextRectangles(textRects, textRectHeight);
320
321     if (textRects.isEmpty())
322         textRects.append(range.absoluteBoundingRect());
323
324     auto frameView = frame.view();
325
326     // Use the exposedContentRect/viewExposedRect instead of visibleContentRect to avoid creating a huge indicator for a large view inside a scroll view.
327     IntRect contentsClipRect;
328 #if PLATFORM(IOS)
329     contentsClipRect = enclosingIntRect(frameView->exposedContentRect());
330 #else
331     if (auto viewExposedRect = frameView->viewExposedRect())
332         contentsClipRect = frameView->viewToContents(enclosingIntRect(*viewExposedRect));
333     else
334         contentsClipRect = frameView->visibleContentRect();
335 #endif
336
337     if (data.options & TextIndicatorOptionExpandClipBeyondVisibleRect) {
338         contentsClipRect.inflateX(contentsClipRect.width() / 2);
339         contentsClipRect.inflateY(contentsClipRect.height() / 2);
340     }
341
342     FloatRect textBoundingRectInRootViewCoordinates;
343     FloatRect textBoundingRectInDocumentCoordinates;
344     Vector<FloatRect> clippedTextRectsInDocumentCoordinates;
345     Vector<FloatRect> textRectsInRootViewCoordinates;
346     for (const FloatRect& textRect : textRects) {
347         FloatRect clippedTextRect;
348         if (data.options & TextIndicatorOptionDoNotClipToVisibleRect)
349             clippedTextRect = textRect;
350         else
351             clippedTextRect = intersection(textRect, contentsClipRect);
352         if (clippedTextRect.isEmpty())
353             continue;
354
355         clippedTextRectsInDocumentCoordinates.append(clippedTextRect);
356
357         FloatRect textRectInDocumentCoordinatesIncludingMargin = clippedTextRect;
358         textRectInDocumentCoordinatesIncludingMargin.inflateX(margin.width());
359         textRectInDocumentCoordinatesIncludingMargin.inflateY(margin.height());
360         textBoundingRectInDocumentCoordinates.unite(textRectInDocumentCoordinatesIncludingMargin);
361
362         FloatRect textRectInRootViewCoordinates = frame.view()->contentsToRootView(enclosingIntRect(textRectInDocumentCoordinatesIncludingMargin));
363         textRectsInRootViewCoordinates.append(textRectInRootViewCoordinates);
364         textBoundingRectInRootViewCoordinates.unite(textRectInRootViewCoordinates);
365     }
366
367     Vector<FloatRect> textRectsInBoundingRectCoordinates;
368     for (auto rect : textRectsInRootViewCoordinates) {
369         rect.moveBy(-textBoundingRectInRootViewCoordinates.location());
370         textRectsInBoundingRectCoordinates.append(rect);
371     }
372
373     // Store the selection rect in window coordinates, to be used subsequently
374     // to determine if the indicator and selection still precisely overlap.
375     data.selectionRectInRootViewCoordinates = frame.view()->contentsToRootView(enclosingIntRect(frame.selection().selectionBounds()));
376     data.textBoundingRectInRootViewCoordinates = textBoundingRectInRootViewCoordinates;
377     data.textRectsInBoundingRectCoordinates = textRectsInBoundingRectCoordinates;
378
379     return takeSnapshots(data, frame, enclosingIntRect(textBoundingRectInDocumentCoordinates), clippedTextRectsInDocumentCoordinates);
380 }
381
382 } // namespace WebCore