1/*
2 * Copyright (C) 2007 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. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "DragImage.h"
28
29#include "Frame.h"
30#include "FrameSnapshotting.h"
31#include "FrameView.h"
32#include "ImageBuffer.h"
33#include "NotImplemented.h"
34#include "Range.h"
35#include "RenderElement.h"
36#include "RenderObject.h"
37#include "RenderView.h"
38#include "TextIndicator.h"
39
40namespace WebCore {
41
42#if PLATFORM(COCOA)
43const float ColorSwatchCornerRadius = 4;
44const float ColorSwatchStrokeSize = 4;
45const float ColorSwatchWidth = 24;
46#endif
47
48DragImageRef fitDragImageToMaxSize(DragImageRef image, const IntSize& layoutSize, const IntSize& maxSize)
49{
50 float heightResizeRatio = 0.0f;
51 float widthResizeRatio = 0.0f;
52 float resizeRatio = -1.0f;
53 IntSize originalSize = dragImageSize(image);
54
55 if (layoutSize.width() > maxSize.width()) {
56 widthResizeRatio = maxSize.width() / (float)layoutSize.width();
57 resizeRatio = widthResizeRatio;
58 }
59
60 if (layoutSize.height() > maxSize.height()) {
61 heightResizeRatio = maxSize.height() / (float)layoutSize.height();
62 if ((resizeRatio < 0.0f) || (resizeRatio > heightResizeRatio))
63 resizeRatio = heightResizeRatio;
64 }
65
66 if (layoutSize == originalSize)
67 return resizeRatio > 0.0f ? scaleDragImage(image, FloatSize(resizeRatio, resizeRatio)) : image;
68
69 // The image was scaled in the webpage so at minimum we must account for that scaling.
70 float scaleX = layoutSize.width() / (float)originalSize.width();
71 float scaleY = layoutSize.height() / (float)originalSize.height();
72 if (resizeRatio > 0.0f) {
73 scaleX *= resizeRatio;
74 scaleY *= resizeRatio;
75 }
76
77 return scaleDragImage(image, FloatSize(scaleX, scaleY));
78}
79
80struct ScopedNodeDragEnabler {
81 ScopedNodeDragEnabler(Frame& frame, Node& node)
82 : frame(frame)
83 , node(node)
84 {
85 if (node.renderer())
86 node.renderer()->updateDragState(true);
87 frame.document()->updateLayout();
88 }
89
90 ~ScopedNodeDragEnabler()
91 {
92 if (node.renderer())
93 node.renderer()->updateDragState(false);
94 }
95
96 const Frame& frame;
97 const Node& node;
98};
99
100static DragImageRef createDragImageFromSnapshot(std::unique_ptr<ImageBuffer> snapshot, Node* node)
101{
102 if (!snapshot)
103 return nullptr;
104
105 ImageOrientationDescription orientation;
106#if ENABLE(CSS_IMAGE_ORIENTATION)
107 if (node) {
108 RenderObject* renderer = node->renderer();
109 if (!renderer || !is<RenderElement>(renderer))
110 return nullptr;
111
112 auto& renderElement = downcast<RenderElement>(*renderer);
113 orientation.setRespectImageOrientation(renderElement.shouldRespectImageOrientation());
114 orientation.setImageOrientationEnum(renderElement.style().imageOrientation());
115 }
116#else
117 UNUSED_PARAM(node);
118#endif
119 RefPtr<Image> image = ImageBuffer::sinkIntoImage(WTFMove(snapshot), PreserveResolution::Yes);
120 if (!image)
121 return nullptr;
122 return createDragImageFromImage(image.get(), orientation);
123}
124
125DragImageRef createDragImageForNode(Frame& frame, Node& node)
126{
127 ScopedNodeDragEnabler enableDrag(frame, node);
128 return createDragImageFromSnapshot(snapshotNode(frame, node), &node);
129}
130
131#if !ENABLE(DATA_INTERACTION)
132
133DragImageRef createDragImageForSelection(Frame& frame, TextIndicatorData&, bool forceBlackText)
134{
135 SnapshotOptions options = forceBlackText ? SnapshotOptionsForceBlackText : SnapshotOptionsNone;
136 return createDragImageFromSnapshot(snapshotSelection(frame, options), nullptr);
137}
138
139#endif
140
141struct ScopedFrameSelectionState {
142 ScopedFrameSelectionState(Frame& frame)
143 : frame(frame)
144 {
145 if (auto* renderView = frame.contentRenderer())
146 selection = renderView->selection().get();
147 }
148
149 ~ScopedFrameSelectionState()
150 {
151 if (auto* renderView = frame.contentRenderer()) {
152 ASSERT(selection);
153 renderView->selection().set(selection.value(), SelectionRangeData::RepaintMode::Nothing);
154 }
155 }
156
157 const Frame& frame;
158 Optional<SelectionRangeData::Context> selection;
159};
160
161#if !PLATFORM(IOS_FAMILY)
162
163DragImageRef createDragImageForRange(Frame& frame, Range& range, bool forceBlackText)
164{
165 frame.document()->updateLayout();
166 RenderView* view = frame.contentRenderer();
167 if (!view)
168 return nullptr;
169
170 // To snapshot the range, temporarily select it and take selection snapshot.
171 Position start = range.startPosition();
172 Position candidate = start.downstream();
173 if (candidate.deprecatedNode() && candidate.deprecatedNode()->renderer())
174 start = candidate;
175
176 Position end = range.endPosition();
177 candidate = end.upstream();
178 if (candidate.deprecatedNode() && candidate.deprecatedNode()->renderer())
179 end = candidate;
180
181 if (start.isNull() || end.isNull() || start == end)
182 return nullptr;
183
184 const ScopedFrameSelectionState selectionState(frame);
185
186 RenderObject* startRenderer = start.deprecatedNode()->renderer();
187 RenderObject* endRenderer = end.deprecatedNode()->renderer();
188 if (!startRenderer || !endRenderer)
189 return nullptr;
190
191 SnapshotOptions options = SnapshotOptionsPaintSelectionOnly | (forceBlackText ? SnapshotOptionsForceBlackText : SnapshotOptionsNone);
192 int startOffset = start.deprecatedEditingOffset();
193 int endOffset = end.deprecatedEditingOffset();
194 ASSERT(startOffset >= 0 && endOffset >= 0);
195 view->selection().set({ startRenderer, endRenderer, static_cast<unsigned>(startOffset), static_cast<unsigned>(endOffset) }, SelectionRangeData::RepaintMode::Nothing);
196 // We capture using snapshotFrameRect() because we fake up the selection using
197 // FrameView but snapshotSelection() uses the selection from the Frame itself.
198 return createDragImageFromSnapshot(snapshotFrameRect(frame, view->selection().boundsClippedToVisibleContent(), options), nullptr);
199}
200
201#endif
202
203DragImageRef createDragImageForImage(Frame& frame, Node& node, IntRect& imageRect, IntRect& elementRect)
204{
205 ScopedNodeDragEnabler enableDrag(frame, node);
206
207 RenderObject* renderer = node.renderer();
208 if (!renderer)
209 return nullptr;
210
211 // Calculate image and element metrics for the client, then create drag image.
212 LayoutRect topLevelRect;
213 IntRect paintingRect = snappedIntRect(renderer->paintingRootRect(topLevelRect));
214
215 if (paintingRect.isEmpty())
216 return nullptr;
217
218 elementRect = snappedIntRect(topLevelRect);
219 imageRect = paintingRect;
220
221 return createDragImageFromSnapshot(snapshotNode(frame, node), &node);
222}
223
224#if !ENABLE(DATA_INTERACTION)
225DragImageRef platformAdjustDragImageForDeviceScaleFactor(DragImageRef image, float deviceScaleFactor)
226{
227 // Later code expects the drag image to be scaled by device's scale factor.
228 return scaleDragImage(image, { deviceScaleFactor, deviceScaleFactor });
229}
230#endif
231
232#if !PLATFORM(MAC)
233const int linkDragBorderInset = 2;
234
235IntPoint dragOffsetForLinkDragImage(DragImageRef dragImage)
236{
237 IntSize size = dragImageSize(dragImage);
238 return { -size.width() / 2, -linkDragBorderInset };
239}
240
241FloatPoint anchorPointForLinkDragImage(DragImageRef dragImage)
242{
243 IntSize size = dragImageSize(dragImage);
244 return { 0.5, static_cast<float>((size.height() - linkDragBorderInset) / size.height()) };
245}
246#endif
247
248DragImage::DragImage()
249 : m_dragImageRef { nullptr }
250{
251}
252
253DragImage::DragImage(DragImageRef dragImageRef)
254 : m_dragImageRef { dragImageRef }
255{
256}
257
258DragImage::DragImage(DragImage&& other)
259 : m_dragImageRef { std::exchange(other.m_dragImageRef, nullptr) }
260{
261 m_indicatorData = other.m_indicatorData;
262 m_visiblePath = other.m_visiblePath;
263}
264
265DragImage& DragImage::operator=(DragImage&& other)
266{
267 if (m_dragImageRef)
268 deleteDragImage(m_dragImageRef);
269
270 m_dragImageRef = std::exchange(other.m_dragImageRef, nullptr);
271 m_indicatorData = other.m_indicatorData;
272 m_visiblePath = other.m_visiblePath;
273
274 return *this;
275}
276
277DragImage::~DragImage()
278{
279 if (m_dragImageRef)
280 deleteDragImage(m_dragImageRef);
281}
282
283#if !PLATFORM(COCOA) && !PLATFORM(GTK) && !PLATFORM(WIN)
284
285IntSize dragImageSize(DragImageRef)
286{
287 notImplemented();
288 return { 0, 0 };
289}
290
291void deleteDragImage(DragImageRef)
292{
293 notImplemented();
294}
295
296DragImageRef scaleDragImage(DragImageRef, FloatSize)
297{
298 notImplemented();
299 return nullptr;
300}
301
302DragImageRef dissolveDragImageToFraction(DragImageRef, float)
303{
304 notImplemented();
305 return nullptr;
306}
307
308DragImageRef createDragImageFromImage(Image*, ImageOrientationDescription)
309{
310 notImplemented();
311 return nullptr;
312}
313
314DragImageRef createDragImageIconForCachedImageFilename(const String&)
315{
316 notImplemented();
317 return nullptr;
318}
319
320DragImageRef createDragImageForLink(Element&, URL&, const String&, TextIndicatorData&, FontRenderingMode, float)
321{
322 notImplemented();
323 return nullptr;
324}
325
326#endif
327
328} // namespace WebCore
329
330