b1d131ba42007e8e38f6aae17af566bf43192dec
[WebKit-https.git] / Source / WebCore / page / TextIndicator.cpp
1 /*
2  * Copyright (C) 2010 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 "Frame.h"
31 #include "FrameSelection.h"
32 #include "FrameSnapshotting.h"
33 #include "FrameView.h"
34 #include "GeometryUtilities.h"
35 #include "GraphicsContext.h"
36 #include "ImageBuffer.h"
37 #include "IntRect.h"
38 #include "Page.h"
39
40 using namespace WebCore;
41
42 // These should match the values in TextIndicatorWindow.
43 // FIXME: Ideally these would only be in one place.
44 #if ENABLE(LEGACY_TEXT_INDICATOR_STYLE)
45 const float horizontalBorder = 3;
46 const float verticalBorder = 1;
47 const float dropShadowBlurRadius = 1.5;
48 #else
49 const float horizontalBorder = 2;
50 const float verticalBorder = 1;
51 const float dropShadowBlurRadius = 12;
52 #endif
53
54 namespace WebCore {
55
56 static FloatRect outsetIndicatorRectIncludingShadow(const FloatRect rect)
57 {
58     FloatRect outsetRect = rect;
59     outsetRect.inflateX(dropShadowBlurRadius + horizontalBorder);
60     outsetRect.inflateY(dropShadowBlurRadius + verticalBorder);
61     return outsetRect;
62 }
63
64 static bool textIndicatorsForTextRectsOverlap(const Vector<FloatRect>& textRects)
65 {
66     size_t count = textRects.size();
67     if (count <= 1)
68         return false;
69
70     Vector<FloatRect> indicatorRects;
71     indicatorRects.reserveInitialCapacity(count);
72
73     for (size_t i = 0; i < count; ++i) {
74         FloatRect indicatorRect = outsetIndicatorRectIncludingShadow(textRects[i]);
75
76         for (size_t j = indicatorRects.size(); j; ) {
77             --j;
78             if (indicatorRect.intersects(indicatorRects[j]))
79                 return true;
80         }
81
82         indicatorRects.uncheckedAppend(indicatorRect);
83     }
84
85     return false;
86 }
87
88 PassRefPtr<TextIndicator> TextIndicator::create(const TextIndicatorData& data)
89 {
90     return adoptRef(new TextIndicator(data));
91 }
92
93 PassRefPtr<TextIndicator> TextIndicator::createWithRange(const Range& range, TextIndicatorPresentationTransition presentationTransition)
94 {
95     Frame* frame = range.startContainer()->document().frame();
96
97     if (!frame)
98         return nullptr;
99
100     VisibleSelection oldSelection = frame->selection().selection();
101     frame->selection().setSelection(&range);
102
103     RefPtr<TextIndicator> indicator = TextIndicator::createWithSelectionInFrame(*frame, presentationTransition);
104
105     frame->selection().setSelection(oldSelection);
106     
107     return indicator.release();
108 }
109
110 // FIXME (138889): Ideally the FrameSnapshotting functions would be more flexible
111 // and we wouldn't have to implement this here.
112 static PassRefPtr<Image> snapshotSelectionWithHighlight(Frame& frame)
113 {
114     auto& selection = frame.selection();
115
116     if (!selection.isRange())
117         return nullptr;
118
119     FloatRect selectionBounds = selection.selectionBounds();
120
121     // It is possible for the selection bounds to be empty; see https://bugs.webkit.org/show_bug.cgi?id=56645.
122     if (selectionBounds.isEmpty())
123         return nullptr;
124
125     std::unique_ptr<ImageBuffer> snapshot = snapshotFrameRect(frame, enclosingIntRect(selectionBounds), 0);
126
127     if (!snapshot)
128         return nullptr;
129
130     return snapshot->copyImage(CopyBackingStore, Unscaled);
131 }
132
133 PassRefPtr<TextIndicator> TextIndicator::createWithSelectionInFrame(Frame& frame, TextIndicatorPresentationTransition presentationTransition)
134 {
135     IntRect selectionRect = enclosingIntRect(frame.selection().selectionBounds());
136     std::unique_ptr<ImageBuffer> indicatorBuffer = snapshotSelection(frame, SnapshotOptionsForceBlackText);
137     if (!indicatorBuffer)
138         return nullptr;
139     RefPtr<Image> indicatorBitmap = indicatorBuffer->copyImage(CopyBackingStore, Unscaled);
140     if (!indicatorBitmap)
141         return nullptr;
142
143     RefPtr<Image> indicatorBitmapWithHighlight;
144     if (presentationTransition == TextIndicatorPresentationTransition::BounceAndCrossfade || presentationTransition == TextIndicatorPresentationTransition::Crossfade)
145         indicatorBitmapWithHighlight = snapshotSelectionWithHighlight(frame);
146
147     // Store the selection rect in window coordinates, to be used subsequently
148     // to determine if the indicator and selection still precisely overlap.
149     IntRect selectionRectInWindowCoordinates = frame.view()->contentsToWindow(selectionRect);
150
151     Vector<FloatRect> textRects;
152     frame.selection().getClippedVisibleTextRectangles(textRects);
153
154     // The bounding rect of all the text rects can be different than the selection
155     // rect when the selection spans multiple lines; the indicator doesn't actually
156     // care where the selection highlight goes, just where the text actually is.
157     FloatRect textBoundingRectInWindowCoordinates;
158     Vector<FloatRect> textRectsInWindowCoordinates;
159     for (const FloatRect& textRect : textRects) {
160         FloatRect textRectInWindowCoordinates = frame.view()->contentsToWindow(enclosingIntRect(textRect));
161         textRectsInWindowCoordinates.append(textRectInWindowCoordinates);
162         textBoundingRectInWindowCoordinates.unite(textRectInWindowCoordinates);
163     }
164
165     Vector<FloatRect> textRectsInBoundingRectCoordinates;
166     for (auto rect : textRectsInWindowCoordinates) {
167         rect.moveBy(-textBoundingRectInWindowCoordinates.location());
168         textRectsInBoundingRectCoordinates.append(rect);
169     }
170
171     TextIndicatorData data;
172     data.selectionRectInWindowCoordinates = selectionRectInWindowCoordinates;
173     data.textBoundingRectInWindowCoordinates = textBoundingRectInWindowCoordinates;
174     data.textRectsInBoundingRectCoordinates = textRectsInBoundingRectCoordinates;
175     data.contentImageScaleFactor = frame.page()->deviceScaleFactor();
176     data.contentImage = indicatorBitmap;
177     data.contentImageWithHighlight = indicatorBitmapWithHighlight;
178     data.presentationTransition = presentationTransition;
179
180     return TextIndicator::create(data);
181 }
182
183 TextIndicator::TextIndicator(const TextIndicatorData& data)
184     : m_data(data)
185 {
186     ASSERT(m_data.contentImageScaleFactor != 1 || m_data.contentImage->size() == enclosingIntRect(m_data.selectionRectInWindowCoordinates).size());
187
188     if (textIndicatorsForTextRectsOverlap(m_data.textRectsInBoundingRectCoordinates)) {
189         m_data.textRectsInBoundingRectCoordinates[0] = unionRect(m_data.textRectsInBoundingRectCoordinates);
190         m_data.textRectsInBoundingRectCoordinates.shrink(1);
191     }
192 }
193
194 TextIndicator::~TextIndicator()
195 {
196 }
197     
198 bool TextIndicator::wantsBounce() const
199 {
200     switch (m_data.presentationTransition) {
201     case TextIndicatorPresentationTransition::BounceAndCrossfade:
202     case TextIndicatorPresentationTransition::Bounce:
203         return true;
204         
205     case TextIndicatorPresentationTransition::FadeIn:
206     case TextIndicatorPresentationTransition::Crossfade:
207     case TextIndicatorPresentationTransition::None:
208         return false;
209     }
210
211     ASSERT_NOT_REACHED();
212     return false;
213 }
214
215 bool TextIndicator::wantsContentCrossfade() const
216 {
217     if (!m_data.contentImageWithHighlight)
218         return false;
219     
220     switch (m_data.presentationTransition) {
221     case TextIndicatorPresentationTransition::BounceAndCrossfade:
222     case TextIndicatorPresentationTransition::Crossfade:
223         return true;
224         
225     case TextIndicatorPresentationTransition::Bounce:
226     case TextIndicatorPresentationTransition::FadeIn:
227     case TextIndicatorPresentationTransition::None:
228         return false;
229     }
230
231     ASSERT_NOT_REACHED();
232     return false;
233 }
234
235 bool TextIndicator::wantsFadeIn() const
236 {
237     switch (m_data.presentationTransition) {
238     case TextIndicatorPresentationTransition::FadeIn:
239         return true;
240         
241     case TextIndicatorPresentationTransition::Bounce:
242     case TextIndicatorPresentationTransition::BounceAndCrossfade:
243     case TextIndicatorPresentationTransition::Crossfade:
244     case TextIndicatorPresentationTransition::None:
245         return false;
246     }
247
248     ASSERT_NOT_REACHED();
249     return false;
250 }
251
252 bool TextIndicator::wantsManualAnimation() const
253 {
254     switch (m_data.presentationTransition) {
255     case TextIndicatorPresentationTransition::FadeIn:
256     case TextIndicatorPresentationTransition::Crossfade:
257         return true;
258
259     case TextIndicatorPresentationTransition::Bounce:
260     case TextIndicatorPresentationTransition::BounceAndCrossfade:
261     case TextIndicatorPresentationTransition::None:
262         return false;
263     }
264
265     ASSERT_NOT_REACHED();
266     return false;
267 }
268
269 } // namespace WebCore