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