1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "third_party/blink/renderer/core/clipboard/data_transfer.h"
6
7 #include "testing/gtest/include/gtest/gtest.h"
8 #include "third_party/blink/renderer/core/dom/element.h"
9 #include "third_party/blink/renderer/core/frame/local_frame.h"
10 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
11 #include "third_party/blink/renderer/core/frame/settings.h"
12 #include "third_party/blink/renderer/core/layout/layout_object.h"
13 #include "third_party/blink/renderer/core/page/drag_image.h"
14 #include "third_party/blink/renderer/core/paint/paint_layer_scrollable_area.h"
15 #include "third_party/blink/renderer/core/testing/core_unit_test_helper.h"
16
17 namespace blink {
18
19 class DataTransferTest : public RenderingTest {
20 protected:
GetPage() const21 Page& GetPage() const { return *GetDocument().GetPage(); }
GetFrame() const22 LocalFrame& GetFrame() const { return *GetDocument().GetFrame(); }
23 };
24
TEST_F(DataTransferTest,NodeImage)25 TEST_F(DataTransferTest, NodeImage) {
26 SetBodyInnerHTML(R"HTML(
27 <style>
28 #sample { width: 100px; height: 100px; }
29 </style>
30 <div id=sample></div>
31 )HTML");
32 Element* sample = GetDocument().getElementById("sample");
33 const std::unique_ptr<DragImage> image =
34 DataTransfer::NodeImage(GetFrame(), *sample);
35 EXPECT_EQ(IntSize(100, 100), image->Size());
36 }
37
TEST_F(DataTransferTest,NodeImageWithNestedElement)38 TEST_F(DataTransferTest, NodeImageWithNestedElement) {
39 SetBodyInnerHTML(R"HTML(
40 <style>
41 div { -webkit-user-drag: element }
42 span:-webkit-drag { color: #0F0 }
43 </style>
44 <div id=sample><span>Green when dragged</span></div>
45 )HTML");
46 Element* sample = GetDocument().getElementById("sample");
47 const std::unique_ptr<DragImage> image =
48 DataTransfer::NodeImage(GetFrame(), *sample);
49 EXPECT_EQ(Color(0, 255, 0),
50 sample->firstChild()->GetLayoutObject()->ResolveColor(
51 GetCSSPropertyColor()))
52 << "Descendants node should have :-webkit-drag.";
53 }
54
TEST_F(DataTransferTest,NodeImageWithPsuedoClassWebKitDrag)55 TEST_F(DataTransferTest, NodeImageWithPsuedoClassWebKitDrag) {
56 SetBodyInnerHTML(R"HTML(
57 <style>
58 #sample { width: 100px; height: 100px; }
59 #sample:-webkit-drag { width: 200px; height: 200px; }
60 </style>
61 <div id=sample></div>
62 )HTML");
63 Element* sample = GetDocument().getElementById("sample");
64 const std::unique_ptr<DragImage> image =
65 DataTransfer::NodeImage(GetFrame(), *sample);
66 EXPECT_EQ(IntSize(200, 200), image->Size())
67 << ":-webkit-drag should affect dragged image.";
68 }
69
TEST_F(DataTransferTest,NodeImageWithoutDraggedLayoutObject)70 TEST_F(DataTransferTest, NodeImageWithoutDraggedLayoutObject) {
71 SetBodyInnerHTML(R"HTML(
72 <style>
73 #sample { width: 100px; height: 100px; }
74 #sample:-webkit-drag { display:none }
75 </style>
76 <div id=sample></div>
77 )HTML");
78 Element* sample = GetDocument().getElementById("sample");
79 const std::unique_ptr<DragImage> image =
80 DataTransfer::NodeImage(GetFrame(), *sample);
81 EXPECT_EQ(nullptr, image.get()) << ":-webkit-drag blows away layout object";
82 }
83
TEST_F(DataTransferTest,NodeImageWithChangingLayoutObject)84 TEST_F(DataTransferTest, NodeImageWithChangingLayoutObject) {
85 SetBodyInnerHTML(R"HTML(
86 <style>
87 #sample { color: blue; }
88 #sample:-webkit-drag { display: inline-block; color: red; }
89 </style>
90 <span id=sample>foo</span>
91 )HTML");
92 Element* sample = GetDocument().getElementById("sample");
93 UpdateAllLifecyclePhasesForTest();
94 LayoutObject* before_layout_object = sample->GetLayoutObject();
95 const std::unique_ptr<DragImage> image =
96 DataTransfer::NodeImage(GetFrame(), *sample);
97
98 EXPECT_TRUE(sample->GetLayoutObject() != before_layout_object)
99 << ":-webkit-drag causes sample to have different layout object.";
100 EXPECT_EQ(Color(255, 0, 0),
101 sample->GetLayoutObject()->ResolveColor(GetCSSPropertyColor()))
102 << "#sample has :-webkit-drag.";
103
104 // Layout w/o :-webkit-drag
105 UpdateAllLifecyclePhasesForTest();
106
107 EXPECT_EQ(Color(0, 0, 255),
108 sample->GetLayoutObject()->ResolveColor(GetCSSPropertyColor()))
109 << "#sample doesn't have :-webkit-drag.";
110 }
111
TEST_F(DataTransferTest,NodeImageExceedsViewportBounds)112 TEST_F(DataTransferTest, NodeImageExceedsViewportBounds) {
113 SetBodyInnerHTML(R"HTML(
114 <style>
115 * { margin: 0; }
116 #node { width: 2000px; height: 2000px; }
117 </style>
118 <div id='node'></div>
119 )HTML");
120 Element& node = *GetDocument().getElementById("node");
121 const auto image = DataTransfer::NodeImage(GetFrame(), node);
122 EXPECT_EQ(IntSize(800, 600), image->Size());
123 }
124
TEST_F(DataTransferTest,NodeImageUnderScrollOffset)125 TEST_F(DataTransferTest, NodeImageUnderScrollOffset) {
126 SetBodyInnerHTML(R"HTML(
127 <style>
128 * { margin: 0; }
129 #first { width: 500px; height: 500px; }
130 #second { width: 800px; height: 900px; }
131 </style>
132 <div id='first'></div>
133 <div id='second'></div>
134 )HTML");
135
136 const int scroll_amount = 10;
137 LocalFrameView* frame_view = GetDocument().View();
138 frame_view->LayoutViewport()->SetScrollOffset(
139 ScrollOffset(0, scroll_amount), mojom::blink::ScrollType::kProgrammatic);
140
141 // The first div should be offset by the scroll offset.
142 Element& first = *GetDocument().getElementById("first");
143 const auto first_image = DataTransfer::NodeImage(GetFrame(), first);
144 const int first_height = 500;
145 EXPECT_EQ(IntSize(500, first_height), first_image->Size());
146
147 // The second div should also be offset by the scroll offset. In addition,
148 // the second div should be clipped by the viewport.
149 Element& second = *GetDocument().getElementById("second");
150 const auto second_image = DataTransfer::NodeImage(GetFrame(), second);
151 const int viewport_height = 600;
152 EXPECT_EQ(IntSize(800, viewport_height - (first_height - scroll_amount)),
153 second_image->Size());
154 }
155
TEST_F(DataTransferTest,NodeImageSizeWithPageScaleFactor)156 TEST_F(DataTransferTest, NodeImageSizeWithPageScaleFactor) {
157 SetBodyInnerHTML(R"HTML(
158 <style>
159 * { margin: 0; }
160 html, body { height: 2000px; }
161 #node { width: 200px; height: 141px; }
162 </style>
163 <div id='node'></div>
164 )HTML");
165 const int page_scale_factor = 2;
166 GetPage().SetPageScaleFactor(page_scale_factor);
167 Element& node = *GetDocument().getElementById("node");
168 const auto image = DataTransfer::NodeImage(GetFrame(), node);
169 const int node_width = 200;
170 const int node_height = 141;
171 EXPECT_EQ(
172 IntSize(node_width * page_scale_factor, node_height * page_scale_factor),
173 image->Size());
174
175 // Check that a scroll offset is scaled to device coordinates which includes
176 // page scale factor.
177 const int scroll_amount = 10;
178 LocalFrameView* frame_view = GetDocument().View();
179 frame_view->LayoutViewport()->SetScrollOffset(
180 ScrollOffset(0, scroll_amount), mojom::blink::ScrollType::kProgrammatic);
181 const auto image_with_offset = DataTransfer::NodeImage(GetFrame(), node);
182 EXPECT_EQ(
183 IntSize(node_width * page_scale_factor, node_height * page_scale_factor),
184 image_with_offset->Size());
185 }
186
TEST_F(DataTransferTest,NodeImageSizeWithPageScaleFactorTooLarge)187 TEST_F(DataTransferTest, NodeImageSizeWithPageScaleFactorTooLarge) {
188 SetBodyInnerHTML(R"HTML(
189 <style>
190 * { margin: 0; }
191 html, body { height: 2000px; }
192 #node { width: 800px; height: 601px; }
193 </style>
194 <div id='node'></div>
195 )HTML");
196 const int page_scale_factor = 2;
197 GetPage().SetPageScaleFactor(page_scale_factor);
198 Element& node = *GetDocument().getElementById("node");
199 const auto image = DataTransfer::NodeImage(GetFrame(), node);
200 const int node_width = 800;
201 const int node_height = 601;
202 EXPECT_EQ(IntSize(node_width * page_scale_factor,
203 (node_height - 1) * page_scale_factor),
204 image->Size());
205
206 // Check that a scroll offset is scaled to device coordinates which includes
207 // page scale factor.
208 const int scroll_amount = 10;
209 LocalFrameView* frame_view = GetDocument().View();
210 frame_view->LayoutViewport()->SetScrollOffset(
211 ScrollOffset(0, scroll_amount), mojom::blink::ScrollType::kProgrammatic);
212 const auto image_with_offset = DataTransfer::NodeImage(GetFrame(), node);
213 EXPECT_EQ(IntSize(node_width * page_scale_factor,
214 (node_height - scroll_amount) * page_scale_factor),
215 image_with_offset->Size());
216 }
217
TEST_F(DataTransferTest,NodeImageWithPageScaleFactor)218 TEST_F(DataTransferTest, NodeImageWithPageScaleFactor) {
219 // #bluegreen is a 2x1 rectangle where the left pixel is blue and the right
220 // pixel is green. The element is offset by a margin of 1px.
221 SetBodyInnerHTML(R"HTML(
222 <style>
223 * { margin: 0; }
224 #bluegreen {
225 width: 1px;
226 height: 1px;
227 background: #0f0;
228 border-left: 1px solid #00f;
229 margin: 1px;
230 }
231 </style>
232 <div id='bluegreen'></div>
233 )HTML");
234 const int page_scale_factor = 2;
235 GetPage().SetPageScaleFactor(page_scale_factor);
236 Element& blue_green = *GetDocument().getElementById("bluegreen");
237 const auto image = DataTransfer::NodeImage(GetFrame(), blue_green);
238 const int blue_green_width = 2;
239 const int blue_green_height = 1;
240 EXPECT_EQ(IntSize(blue_green_width * page_scale_factor,
241 blue_green_height * page_scale_factor),
242 image->Size());
243
244 // Even though #bluegreen is offset by a margin of 1px (which is 2px in device
245 // coordinates), we expect it to be painted at 0x0 and completely fill the 4x2
246 // bitmap.
247 SkBitmap expected_bitmap;
248 expected_bitmap.allocN32Pixels(4, 2);
249 expected_bitmap.eraseArea(SkIRect::MakeXYWH(0, 0, 2, 2), 0xFF0000FF);
250 expected_bitmap.eraseArea(SkIRect::MakeXYWH(2, 0, 2, 2), 0xFF00FF00);
251 const SkBitmap& bitmap = image->Bitmap();
252 for (int x = 0; x < bitmap.width(); ++x)
253 for (int y = 0; y < bitmap.height(); ++y)
254 EXPECT_EQ(expected_bitmap.getColor(x, y), bitmap.getColor(x, y));
255 }
256
TEST_F(DataTransferTest,NodeImageFullyOffscreen)257 TEST_F(DataTransferTest, NodeImageFullyOffscreen) {
258 SetBodyInnerHTML(R"HTML(
259 <style>
260 #target {
261 position: absolute;
262 top: 800px;
263 left: 0;
264 height: 100px;
265 width: 200px;
266 background: lightblue;
267 isolation: isolate;
268 }
269 </style>
270 <div id="target" draggable="true" ondragstart="drag(event)"></div>
271 )HTML");
272
273 const int scroll_amount = 800;
274 LocalFrameView* frame_view = GetDocument().View();
275 frame_view->LayoutViewport()->SetScrollOffset(
276 ScrollOffset(0, scroll_amount), mojom::blink::ScrollType::kProgrammatic);
277
278 Element& target = *GetDocument().getElementById("target");
279 const auto image = DataTransfer::NodeImage(GetFrame(), target);
280
281 EXPECT_EQ(IntSize(200, 100), image->Size());
282 }
283
TEST_F(DataTransferTest,NodeImageWithScrolling)284 TEST_F(DataTransferTest, NodeImageWithScrolling) {
285 SetBodyInnerHTML(R"HTML(
286 <style>
287 #target {
288 position: absolute;
289 top: 800px;
290 left: 0;
291 height: 100px;
292 width: 200px;
293 background: lightblue;
294 isolation: isolate;
295 }
296 </style>
297 <div id="target" draggable="true" ondragstart="drag(event)"></div>
298 )HTML");
299
300 Element& target = *GetDocument().getElementById("target");
301 const auto image = DataTransfer::NodeImage(GetFrame(), target);
302
303 EXPECT_EQ(IntSize(200, 100), image->Size());
304 }
305
TEST_F(DataTransferTest,NodeImageInOffsetStackingContext)306 TEST_F(DataTransferTest, NodeImageInOffsetStackingContext) {
307 SetBodyInnerHTML(R"HTML(
308 <style>
309 * { margin: 0; }
310 #container {
311 position: absolute;
312 top: 4px;
313 z-index: 10;
314 }
315 #drag {
316 width: 5px;
317 height: 5px;
318 background: #0F0;
319 }
320 </style>
321 <div id="container">
322 <div id="drag" draggable="true"></div>
323 </div>
324 )HTML");
325 Element& drag = *GetDocument().getElementById("drag");
326 const auto image = DataTransfer::NodeImage(GetFrame(), drag);
327 constexpr int drag_width = 5;
328 constexpr int drag_height = 5;
329 EXPECT_EQ(IntSize(drag_width, drag_height), image->Size());
330
331 // The dragged image should be (drag_width x drag_height) and fully green.
332 Color green = 0xFF00FF00;
333 const SkBitmap& bitmap = image->Bitmap();
334 for (int x = 0; x < drag_width; ++x) {
335 for (int y = 0; y < drag_height; ++y)
336 EXPECT_EQ(green, bitmap.getColor(x, y));
337 }
338 }
339
TEST_F(DataTransferTest,NodeImageWithLargerPositionedDescendant)340 TEST_F(DataTransferTest, NodeImageWithLargerPositionedDescendant) {
341 SetBodyInnerHTML(R"HTML(
342 <style>
343 * { margin: 0; }
344 #drag {
345 position: absolute;
346 top: 100px;
347 left: 0;
348 height: 1px;
349 width: 1px;
350 background: #00f;
351 }
352 #child {
353 position: absolute;
354 top: -1px;
355 left: 0;
356 height: 3px;
357 width: 1px;
358 background: #0f0;
359 }
360 </style>
361 <div id="drag" draggable="true">
362 <div id="child"></div>
363 </div>
364 )HTML");
365 Element& drag = *GetDocument().getElementById("drag");
366 const auto image = DataTransfer::NodeImage(GetFrame(), drag);
367
368 // The positioned #child should expand the dragged image's size.
369 constexpr int drag_width = 1;
370 constexpr int drag_height = 3;
371 EXPECT_EQ(IntSize(drag_width, drag_height), image->Size());
372
373 // The dragged image should be (drag_width x drag_height) and fully green
374 // which is the color of the #child which fully covers the dragged element.
375 Color green = 0xFF00FF00;
376 const SkBitmap& bitmap = image->Bitmap();
377 for (int x = 0; x < drag_width; ++x) {
378 for (int y = 0; y < drag_height; ++y)
379 EXPECT_EQ(green, bitmap.getColor(x, y));
380 }
381 }
382
383 } // namespace blink
384