aboutsummaryrefslogtreecommitdiff
path: root/sources/plugins/clipboard/plugin.js
blob: 5c387b3a74944db5e1383fef9f2480637b6f7d35 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
/**
 * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
 * For licensing, see LICENSE.md or http://ckeditor.com/license
 */

/**
 * @ignore
 * File overview: Clipboard support.
 */

//
// COPY & PASTE EXECUTION FLOWS:
// -- CTRL+C
//		* if ( isCustomCopyCutSupported )
//			* dataTransfer.setData( 'text/html', getSelectedHtml )
//		* else
//			* browser's default behavior
// -- CTRL+X
//		* listen onKey (onkeydown)
//		* fire 'saveSnapshot' on editor
//		* if ( isCustomCopyCutSupported )
//			* dataTransfer.setData( 'text/html', getSelectedHtml )
//			* extractSelectedHtml // remove selected contents
//		* else
//			* browser's default behavior
//		* deferred second 'saveSnapshot' event
// -- CTRL+V
//		* listen onKey (onkeydown)
//		* simulate 'beforepaste' for non-IEs on editable
//		* listen 'onpaste' on editable ('onbeforepaste' for IE)
//		* fire 'beforePaste' on editor
//		* if ( !canceled && ( htmlInDataTransfer || !external paste) && dataTransfer is not empty ) getClipboardDataByPastebin
//		* fire 'paste' on editor
//		* !canceled && fire 'afterPaste' on editor
// -- Copy command
//		* tryToCutCopy
//			* execCommand
//		* !success && notification
// -- Cut command
//		* fixCut
//		* tryToCutCopy
//			* execCommand
//		* !success && notification
// -- Paste command
//		* fire 'paste' on editable ('beforepaste' for IE)
//		* !canceled && execCommand 'paste'
//		* !success && fire 'pasteDialog' on editor
// -- Paste from native context menu & menubar
//		(Fx & Webkits are handled in 'paste' default listener.
//		Opera cannot be handled at all because it doesn't fire any events
//		Special treatment is needed for IE, for which is this part of doc)
//		* listen 'onpaste'
//		* cancel native event
//		* fire 'beforePaste' on editor
//		* if ( !canceled && ( htmlInDataTransfer || !external paste) && dataTransfer is not empty ) getClipboardDataByPastebin
//		* execIECommand( 'paste' ) -> this fires another 'paste' event, so cancel it
//		* fire 'paste' on editor
//		* !canceled && fire 'afterPaste' on editor
//
//
// PASTE EVENT - PREPROCESSING:
// -- Possible dataValue types: auto, text, html.
// -- Possible dataValue contents:
//		* text (possible \n\r)
//		* htmlified text (text + br,div,p - no presentational markup & attrs - depends on browser)
//		* html
// -- Possible flags:
//		* htmlified - if true then content is a HTML even if no markup inside. This flag is set
//			for content from editable pastebins, because they 'htmlify' pasted content.
//
// -- Type: auto:
//		* content: htmlified text ->	filter, unify text markup (brs, ps, divs), set type: text
//		* content: html ->				filter, set type: html
// -- Type: text:
//		* content: htmlified text ->	filter, unify text markup
//		* content: html ->				filter, strip presentational markup, unify text markup
// -- Type: html:
//		* content: htmlified text ->	filter, unify text markup
//		* content: html ->				filter
//
// -- Phases:
//		* if dataValue is empty copy data from dataTransfer to dataValue (priority 1)
//		* filtering (priorities 3-5) - e.g. pastefromword filters
//		* content type sniffing (priority 6)
//		* markup transformations for text (priority 6)
//
// DRAG & DROP EXECUTION FLOWS:
// -- Drag
//		* save to the global object:
//			* drag timestamp (with 'cke-' prefix),
//			* selected html,
//			* drag range,
//			* editor instance.
//		* put drag timestamp into event.dataTransfer.text
// -- Drop
//		* if events text == saved timestamp && editor == saved editor
//			internal drag & drop occurred
//			* getRangeAtDropPosition
//			* create bookmarks for drag and drop ranges starting from the end of the document
//			* dragRange.deleteContents()
//			* fire 'paste' with saved html and drop range
//		* if events text == saved timestamp && editor != saved editor
//			cross editor drag & drop occurred
//			* getRangeAtDropPosition
//			* fire 'paste' with saved html
//			* dragRange.deleteContents()
//			* FF: refreshCursor on afterPaste
//		* if events text != saved timestamp
//			drop form external source occurred
//			* getRangeAtDropPosition
//			* if event contains html data then fire 'paste' with html
//			* else if event contains text data then fire 'paste' with encoded text
//			* FF: refreshCursor on afterPaste

'use strict';

( function() {
	// Register the plugin.
	CKEDITOR.plugins.add( 'clipboard', {
		requires: 'dialog',
		// jscs:disable maximumLineLength
		lang: 'af,ar,az,bg,bn,bs,ca,cs,cy,da,de,de-ch,el,en,en-au,en-ca,en-gb,eo,es,et,eu,fa,fi,fo,fr,fr-ca,gl,gu,he,hi,hr,hu,id,is,it,ja,ka,km,ko,ku,lt,lv,mk,mn,ms,nb,nl,no,oc,pl,pt,pt-br,ro,ru,si,sk,sl,sq,sr,sr-latn,sv,th,tr,tt,ug,uk,vi,zh,zh-cn', // %REMOVE_LINE_CORE%
		// jscs:enable maximumLineLength
		icons: 'copy,copy-rtl,cut,cut-rtl,paste,paste-rtl', // %REMOVE_LINE_CORE%
		hidpi: true, // %REMOVE_LINE_CORE%
		init: function( editor ) {
			var filterType,
				filtersFactory = filtersFactoryFactory();

			if ( editor.config.forcePasteAsPlainText ) {
				filterType = 'plain-text';
			} else if ( editor.config.pasteFilter ) {
				filterType = editor.config.pasteFilter;
			}
			// On Webkit the pasteFilter defaults 'semantic-content' because pasted data is so terrible
			// that it must be always filtered.
			else if ( CKEDITOR.env.webkit && !( 'pasteFilter' in editor.config ) ) {
				filterType = 'semantic-content';
			}

			editor.pasteFilter = filtersFactory.get( filterType );

			initPasteClipboard( editor );
			initDragDrop( editor );

			CKEDITOR.dialog.add( 'paste', CKEDITOR.getUrl( this.path + 'dialogs/paste.js' ) );

			// Convert image file (if present) to base64 string for Firefox. Do it as the first
			// step as the conversion is asynchronous and should hold all further paste processing.
			if ( CKEDITOR.env.gecko ) {
				var supportedImageTypes = [ 'image/png', 'image/jpeg', 'image/gif' ],
					latestId;

				editor.on( 'paste', function( evt ) {
					var dataObj = evt.data,
						data = dataObj.dataValue,
						dataTransfer = dataObj.dataTransfer;

					// If data empty check for image content inside data transfer. #16705
					if ( !data && dataObj.method == 'paste' && dataTransfer && dataTransfer.getFilesCount() == 1 && latestId != dataTransfer.id ) {
						var file = dataTransfer.getFile( 0 );

						if ( CKEDITOR.tools.indexOf( supportedImageTypes, file.type ) != -1 ) {
							var fileReader = new FileReader();

							// Convert image file to img tag with base64 image.
							fileReader.addEventListener( 'load', function() {
								evt.data.dataValue = '<img src="' + fileReader.result + '" />';
								editor.fire( 'paste', evt.data );
							}, false );

							// Proceed with normal flow if reading file was aborted.
							fileReader.addEventListener( 'abort', function() {
								editor.fire( 'paste', evt.data );
							}, false );

							// Proceed with normal flow if reading file failed.
							fileReader.addEventListener( 'error', function() {
								editor.fire( 'paste', evt.data );
							}, false );

							fileReader.readAsDataURL( file );

							latestId = dataObj.dataTransfer.id;

							evt.stop();
						}
					}
				}, null, null, 1 );
			}

			editor.on( 'paste', function( evt ) {
				// Init `dataTransfer` if `paste` event was fired without it, so it will be always available.
				if ( !evt.data.dataTransfer ) {
					evt.data.dataTransfer = new CKEDITOR.plugins.clipboard.dataTransfer();
				}

				// If dataValue is already set (manually or by paste bin), so do not override it.
				if ( evt.data.dataValue ) {
					return;
				}

				var dataTransfer = evt.data.dataTransfer,
					// IE support only text data and throws exception if we try to get html data.
					// This html data object may also be empty if we drag content of the textarea.
					value = dataTransfer.getData( 'text/html' );

				if ( value ) {
					evt.data.dataValue = value;
					evt.data.type = 'html';
				} else {
					// Try to get text data otherwise.
					value = dataTransfer.getData( 'text/plain' );

					if ( value ) {
						evt.data.dataValue = editor.editable().transformPlainTextToHtml( value );
						evt.data.type = 'text';
					}
				}
			}, null, null, 1 );

			editor.on( 'paste', function( evt ) {
				var data = evt.data.dataValue,
					blockElements = CKEDITOR.dtd.$block;

				// Filter webkit garbage.
				if ( data.indexOf( 'Apple-' ) > -1 ) {
					// Replace special webkit's &nbsp; with simple space, because webkit
					// produces them even for normal spaces.
					data = data.replace( /<span class="Apple-converted-space">&nbsp;<\/span>/gi, ' ' );

					// Strip <span> around white-spaces when not in forced 'html' content type.
					// This spans are created only when pasting plain text into Webkit,
					// but for safety reasons remove them always.
					if ( evt.data.type != 'html' ) {
						data = data.replace( /<span class="Apple-tab-span"[^>]*>([^<]*)<\/span>/gi, function( all, spaces ) {
							// Replace tabs with 4 spaces like Fx does.
							return spaces.replace( /\t/g, '&nbsp;&nbsp; &nbsp;' );
						} );
					}

					// This br is produced only when copying & pasting HTML content.
					if ( data.indexOf( '<br class="Apple-interchange-newline">' ) > -1 ) {
						evt.data.startsWithEOL = 1;
						evt.data.preSniffing = 'html'; // Mark as not text.
						data = data.replace( /<br class="Apple-interchange-newline">/, '' );
					}

					// Remove all other classes.
					data = data.replace( /(<[^>]+) class="Apple-[^"]*"/gi, '$1' );
				}

				// Strip editable that was copied from inside. (#9534)
				if ( data.match( /^<[^<]+cke_(editable|contents)/i ) ) {
					var tmp,
						editable_wrapper,
						wrapper = new CKEDITOR.dom.element( 'div' );

					wrapper.setHtml( data );
					// Verify for sure and check for nested editor UI parts. (#9675)
					while ( wrapper.getChildCount() == 1 &&
							( tmp = wrapper.getFirst() ) &&
							tmp.type == CKEDITOR.NODE_ELEMENT &&	// Make sure first-child is element.
							( tmp.hasClass( 'cke_editable' ) || tmp.hasClass( 'cke_contents' ) ) ) {
						wrapper = editable_wrapper = tmp;
					}

					// If editable wrapper was found strip it and bogus <br> (added on FF).
					if ( editable_wrapper )
						data = editable_wrapper.getHtml().replace( /<br>$/i, '' );
				}

				if ( CKEDITOR.env.ie ) {
					// &nbsp; <p> -> <p> (br.cke-pasted-remove will be removed later)
					data = data.replace( /^&nbsp;(?: |\r\n)?<(\w+)/g, function( match, elementName ) {
						if ( elementName.toLowerCase() in blockElements ) {
							evt.data.preSniffing = 'html'; // Mark as not a text.
							return '<' + elementName;
						}
						return match;
					} );
				} else if ( CKEDITOR.env.webkit ) {
					// </p><div><br></div> -> </p><br>
					// We don't mark br, because this situation can happen for htmlified text too.
					data = data.replace( /<\/(\w+)><div><br><\/div>$/, function( match, elementName ) {
						if ( elementName in blockElements ) {
							evt.data.endsWithEOL = 1;
							return '</' + elementName + '>';
						}
						return match;
					} );
				} else if ( CKEDITOR.env.gecko ) {
					// Firefox adds bogus <br> when user pasted text followed by space(s).
					data = data.replace( /(\s)<br>$/, '$1' );
				}

				evt.data.dataValue = data;
			}, null, null, 3 );

			editor.on( 'paste', function( evt ) {
				var dataObj = evt.data,
					type = dataObj.type,
					data = dataObj.dataValue,
					trueType,
					// Default is 'html'.
					defaultType = editor.config.clipboard_defaultContentType || 'html',
					transferType = dataObj.dataTransfer.getTransferType( editor );

				// If forced type is 'html' we don't need to know true data type.
				if ( type == 'html' || dataObj.preSniffing == 'html' ) {
					trueType = 'html';
				} else {
					trueType = recogniseContentType( data );
				}

				// Unify text markup.
				if ( trueType == 'htmlifiedtext' ) {
					data = htmlifiedTextHtmlification( editor.config, data );
				}

				// Strip presentational markup & unify text markup.
				// Forced plain text (dialog or forcePAPT).
				// Note: we do not check dontFilter option in this case, because forcePAPT was implemented
				// before pasteFilter and pasteFilter is automatically used on Webkit&Blink since 4.5, so
				// forcePAPT should have priority as it had before 4.5.
				if ( type == 'text' && trueType == 'html' ) {
					data = filterContent( editor, data, filtersFactory.get( 'plain-text' ) );
				}
				// External paste and pasteFilter exists and filtering isn't disabled.
				else if ( transferType == CKEDITOR.DATA_TRANSFER_EXTERNAL && editor.pasteFilter && !dataObj.dontFilter ) {
					data = filterContent( editor, data, editor.pasteFilter );
				}

				if ( dataObj.startsWithEOL ) {
					data = '<br data-cke-eol="1">' + data;
				}
				if ( dataObj.endsWithEOL ) {
					data += '<br data-cke-eol="1">';
				}

				if ( type == 'auto' ) {
					type = ( trueType == 'html' || defaultType == 'html' ) ? 'html' : 'text';
				}

				dataObj.type = type;
				dataObj.dataValue = data;
				delete dataObj.preSniffing;
				delete dataObj.startsWithEOL;
				delete dataObj.endsWithEOL;
			}, null, null, 6 );

			// Inserts processed data into the editor at the end of the
			// events chain.
			editor.on( 'paste', function( evt ) {
				var data = evt.data;

				if ( data.dataValue ) {
					editor.insertHtml( data.dataValue, data.type, data.range );

					// Defer 'afterPaste' so all other listeners for 'paste' will be fired first.
					// Fire afterPaste only if paste inserted some HTML.
					setTimeout( function() {
						editor.fire( 'afterPaste' );
					}, 0 );
				}
			}, null, null, 1000 );

			editor.on( 'pasteDialog', function( evt ) {
				// TODO it's possible that this setTimeout is not needed any more,
				// because of changes introduced in the same commit as this comment.
				// Editor.getClipboardData adds listener to the dialog's events which are
				// fired after a while (not like 'showDialog').
				setTimeout( function() {
					// Open default paste dialog.
					editor.openDialog( 'paste', evt.data );
				}, 0 );
			} );
		}
	} );

	function firePasteEvents( editor, data, withBeforePaste ) {
		if ( !data.type ) {
			data.type = 'auto';
		}

		if ( withBeforePaste ) {
			// Fire 'beforePaste' event so clipboard flavor get customized
			// by other plugins.
			if ( editor.fire( 'beforePaste', data ) === false )
				return false; // Event canceled
		}

		// Do not fire paste if there is no data (dataValue and dataTranfser are empty).
		// This check should be done after firing 'beforePaste' because for native paste
		// 'beforePaste' is by default fired even for empty clipboard.
		if ( !data.dataValue && data.dataTransfer.isEmpty() ) {
			return false;
		}

		if ( !data.dataValue ) {
			data.dataValue = '';
		}

		// Because of FF bug we need to use this hack, otherwise cursor is hidden
		// or it is not possible to move it (#12420).
		// Also, check that editor.toolbox exists, because the toolbar plugin might not be loaded (#13305).
		if ( CKEDITOR.env.gecko && data.method == 'drop' && editor.toolbox ) {
			editor.once( 'afterPaste', function() {
				editor.toolbox.focus();
			} );
		}

		return editor.fire( 'paste', data );
	}

	function initPasteClipboard( editor ) {
		var clipboard = CKEDITOR.plugins.clipboard,
			preventBeforePasteEvent = 0,
			preventPasteEvent = 0,
			inReadOnly = 0;

		addListeners();
		addButtonsCommands();

		/**
		 * Gets clipboard data by directly accessing the clipboard (IE only) or opening the paste dialog window.
		 *
		 *		editor.getClipboardData( { title: 'Get my data' }, function( data ) {
		 *			if ( data )
		 *				alert( data.type + ' ' + data.dataValue );
		 *		} );
		 *
		 * @member CKEDITOR.editor
		 * @param {Object} options
		 * @param {String} [options.title] The title of the paste dialog window.
		 * @param {Function} callback A function that will be executed with `data.type` and `data.dataValue`
		 * or `null` if none of the capturing methods succeeded.
		 */
		editor.getClipboardData = function( options, callback ) {
			var beforePasteNotCanceled = false,
				dataType = 'auto',
				dialogCommited = false;

			// Options are optional - args shift.
			if ( !callback ) {
				callback = options;
				options = null;
			}

			// Listen with maximum priority to handle content before everyone else.
			// This callback will handle paste event that will be fired if direct
			// access to the clipboard succeed in IE.
			editor.on( 'paste', onPaste, null, null, 0 );

			// Listen at the end of listeners chain to see if event wasn't canceled
			// and to retrieve modified data.type.
			editor.on( 'beforePaste', onBeforePaste, null, null, 1000 );

			// getClipboardDataDirectly() will fire 'beforePaste' synchronously, so we can
			// check if it was canceled and if any listener modified data.type.

			// If command didn't succeed (only IE allows to access clipboard and only if
			// user agrees) open and handle paste dialog.
			if ( getClipboardDataDirectly() === false ) {
				// Direct access to the clipboard wasn't successful so remove listener.
				editor.removeListener( 'paste', onPaste );

				// If beforePaste was canceled do not open dialog.
				// Add listeners only if dialog really opened. 'pasteDialog' can be canceled.
				if ( beforePasteNotCanceled && editor.fire( 'pasteDialog', onDialogOpen ) ) {
					editor.on( 'pasteDialogCommit', onDialogCommit );

					// 'dialogHide' will be fired after 'pasteDialogCommit'.
					editor.on( 'dialogHide', function( evt ) {
						evt.removeListener();
						evt.data.removeListener( 'pasteDialogCommit', onDialogCommit );

						// Because Opera has to wait a while in pasteDialog we have to wait here.
						setTimeout( function() {
							// Notify even if user canceled dialog (clicked 'cancel', ESC, etc).
							if ( !dialogCommited )
								callback( null );
						}, 10 );
					} );
				} else {
					callback( null );
				}
			}

			function onPaste( evt ) {
				evt.removeListener();
				evt.cancel();
				callback( evt.data );
			}

			function onBeforePaste( evt ) {
				evt.removeListener();
				beforePasteNotCanceled = true;
				dataType = evt.data.type;
			}

			function onDialogCommit( evt ) {
				evt.removeListener();
				// Cancel pasteDialogCommit so paste dialog won't automatically fire
				// 'paste' evt by itself.
				evt.cancel();
				dialogCommited = true;
				callback( {
					type: dataType,
					dataValue: evt.data.dataValue,
					dataTransfer: evt.data.dataTransfer,
					method: 'paste'
				} );
			}

			function onDialogOpen() {
				this.customTitle = ( options && options.title );
			}
		};

		function addButtonsCommands() {
			addButtonCommand( 'Cut', 'cut', createCutCopyCmd( 'cut' ), 10, 1 );
			addButtonCommand( 'Copy', 'copy', createCutCopyCmd( 'copy' ), 20, 4 );
			addButtonCommand( 'Paste', 'paste', createPasteCmd(), 30, 8 );

			function addButtonCommand( buttonName, commandName, command, toolbarOrder, ctxMenuOrder ) {
				var lang = editor.lang.clipboard[ commandName ];

				editor.addCommand( commandName, command );
				editor.ui.addButton && editor.ui.addButton( buttonName, {
					label: lang,
					command: commandName,
					toolbar: 'clipboard,' + toolbarOrder
				} );

				// If the "menu" plugin is loaded, register the menu item.
				if ( editor.addMenuItems ) {
					editor.addMenuItem( commandName, {
						label: lang,
						command: commandName,
						group: 'clipboard',
						order: ctxMenuOrder
					} );
				}
			}
		}

		function addListeners() {
			editor.on( 'key', onKey );
			editor.on( 'contentDom', addPasteListenersToEditable );

			// For improved performance, we're checking the readOnly state on selectionChange instead of hooking a key event for that.
			editor.on( 'selectionChange', function( evt ) {
				inReadOnly = evt.data.selection.getRanges()[ 0 ].checkReadOnly();
				setToolbarStates();
			} );

			// If the "contextmenu" plugin is loaded, register the listeners.
			if ( editor.contextMenu ) {
				editor.contextMenu.addListener( function( element, selection ) {
					inReadOnly = selection.getRanges()[ 0 ].checkReadOnly();
					return {
						cut: stateFromNamedCommand( 'cut' ),
						copy: stateFromNamedCommand( 'copy' ),
						paste: stateFromNamedCommand( 'paste' )
					};
				} );
			}
		}

		// Add events listeners to editable.
		function addPasteListenersToEditable() {
			var editable = editor.editable();

			if ( CKEDITOR.plugins.clipboard.isCustomCopyCutSupported ) {
				var initOnCopyCut = function( evt ) {
					// If user tries to cut in read-only editor, we must prevent default action. (#13872)
					if ( !editor.readOnly || evt.name != 'cut' ) {
						clipboard.initPasteDataTransfer( evt, editor );
					}
					evt.data.preventDefault();
				};

				editable.on( 'copy', initOnCopyCut );
				editable.on( 'cut', initOnCopyCut );

				// Delete content with the low priority so one can overwrite cut data.
				editable.on( 'cut', function() {
					// If user tries to cut in read-only editor, we must prevent default action. (#13872)
					if ( !editor.readOnly ) {
						editor.extractSelectedHtml();
					}
				}, null, null, 999 );
			}

			// We'll be catching all pasted content in one line, regardless of whether
			// it's introduced by a document command execution (e.g. toolbar buttons) or
			// user paste behaviors (e.g. CTRL+V).
			editable.on( clipboard.mainPasteEvent, function( evt ) {
				if ( clipboard.mainPasteEvent == 'beforepaste' && preventBeforePasteEvent ) {
					return;
				}

				// If you've just asked yourself why preventPasteEventNow() is not here, but
				// in listener for CTRL+V and exec method of 'paste' command
				// you've asked the same question we did.
				//
				// THE ANSWER:
				//
				// First thing to notice - this answer makes sense only for IE,
				// because other browsers don't listen for 'paste' event.
				//
				// What would happen if we move preventPasteEventNow() here?
				// For:
				// * CTRL+V - IE fires 'beforepaste', so we prevent 'paste' and pasteDataFromClipboard(). OK.
				// * editor.execCommand( 'paste' ) - we fire 'beforepaste', so we prevent
				//		'paste' and pasteDataFromClipboard() and doc.execCommand( 'Paste' ). OK.
				// * native context menu - IE fires 'beforepaste', so we prevent 'paste', but unfortunately
				//		on IE we fail with pasteDataFromClipboard() here, because of... we don't know why, but
				//		we just fail, so... we paste nothing. FAIL.
				// * native menu bar - the same as for native context menu.
				//
				// But don't you know any way to distinguish first two cases from last two?
				// Only one - special flag set in CTRL+V handler and exec method of 'paste'
				// command. And that's what we did using preventPasteEventNow().

				pasteDataFromClipboard( evt );
			} );

			// It's not possible to clearly handle all four paste methods (ctrl+v, native menu bar
			// native context menu, editor's command) in one 'paste/beforepaste' event in IE.
			//
			// For ctrl+v & editor's command it's easy to handle pasting in 'beforepaste' listener,
			// so we do this. For another two methods it's better to use 'paste' event.
			//
			// 'paste' is always being fired after 'beforepaste' (except of weird one on opening native
			// context menu), so for two methods handled in 'beforepaste' we're canceling 'paste'
			// using preventPasteEvent state.
			//
			// 'paste' event in IE is being fired before getClipboardDataByPastebin executes its callback.
			//
			// QUESTION: Why didn't you handle all 4 paste methods in handler for 'paste'?
			//		Wouldn't this just be simpler?
			// ANSWER: Then we would have to evt.data.preventDefault() only for native
			//		context menu and menu bar pastes. The same with execIECommand().
			//		That would force us to mark CTRL+V and editor's paste command with
			//		special flag, other than preventPasteEvent. But we still would have to
			//		have preventPasteEvent for the second event fired by execIECommand.
			//		Code would be longer and not cleaner.
			if ( clipboard.mainPasteEvent == 'beforepaste' ) {
				editable.on( 'paste', function( evt ) {
					if ( preventPasteEvent ) {
						return;
					}

					// Cancel next 'paste' event fired by execIECommand( 'paste' )
					// at the end of this callback.
					preventPasteEventNow();

					// Prevent native paste.
					evt.data.preventDefault();

					pasteDataFromClipboard( evt );

					// Force IE to paste content into pastebin so pasteDataFromClipboard will work.
					if ( !execIECommand( 'paste' ) ) {
						editor.openDialog( 'paste' );
					}
				} );

				// If mainPasteEvent is 'beforePaste' (IE before Edge),
				// dismiss the (wrong) 'beforepaste' event fired on context/toolbar menu open. (#7953)
				editable.on( 'contextmenu', preventBeforePasteEventNow, null, null, 0 );

				editable.on( 'beforepaste', function( evt ) {
					// Do not prevent event on CTRL+V and SHIFT+INS because it blocks paste (#11970).
					if ( evt.data && !evt.data.$.ctrlKey && !evt.data.$.shiftKey )
						preventBeforePasteEventNow();
				}, null, null, 0 );
			}

			editable.on( 'beforecut', function() {
				!preventBeforePasteEvent && fixCut( editor );
			} );

			var mouseupTimeout;

			// Use editor.document instead of editable in non-IEs for observing mouseup
			// since editable won't fire the event if selection process started within
			// iframe and ended out of the editor (#9851).
			editable.attachListener( CKEDITOR.env.ie ? editable : editor.document.getDocumentElement(), 'mouseup', function() {
				mouseupTimeout = setTimeout( function() {
					setToolbarStates();
				}, 0 );
			} );

			// Make sure that deferred mouseup callback isn't executed after editor instance
			// had been destroyed. This may happen when editor.destroy() is called in parallel
			// with mouseup event (i.e. a button with onclick callback) (#10219).
			editor.on( 'destroy', function() {
				clearTimeout( mouseupTimeout );
			} );

			editable.on( 'keyup', setToolbarStates );
		}

		// Create object representing Cut or Copy commands.
		function createCutCopyCmd( type ) {
			return {
				type: type,
				canUndo: type == 'cut', // We can't undo copy to clipboard.
				startDisabled: true,
				fakeKeystroke: type == 'cut' ? CKEDITOR.CTRL + 88 /*X*/ :  CKEDITOR.CTRL + 67 /*C*/,
				exec: function() {
					// Attempts to execute the Cut and Copy operations.
					function tryToCutCopy( type ) {
						if ( CKEDITOR.env.ie )
							return execIECommand( type );

						// non-IEs part
						try {
							// Other browsers throw an error if the command is disabled.
							return editor.document.$.execCommand( type, false, null );
						} catch ( e ) {
							return false;
						}
					}

					this.type == 'cut' && fixCut();

					var success = tryToCutCopy( this.type );

					if ( !success ) {
						// Show cutError or copyError.
						editor.showNotification( editor.lang.clipboard[ this.type + 'Error' ] ); // jshint ignore:line
					}

					return success;
				}
			};
		}

		function createPasteCmd() {
			return {
				// Snapshots are done manually by editable.insertXXX methods.
				canUndo: false,
				async: true,
				fakeKeystroke: CKEDITOR.CTRL + 86 /*V*/,
				exec: function( editor, data ) {
					var cmd = this,
						fire = function( data, withBeforePaste ) {
							data &&	firePasteEvents( editor, data, !!withBeforePaste );

							editor.fire( 'afterCommandExec', {
								name: 'paste',
								command: cmd,
								returnValue: !!data
							} );
						};

					// Check data precisely - don't open dialog on empty string.
					if ( typeof data == 'string' )
						fire( {
								dataValue: data,
								method: 'paste',
								dataTransfer: clipboard.initPasteDataTransfer()
							}, 1 );
					else
						editor.getClipboardData( fire );
				}
			};
		}

		function preventPasteEventNow() {
			preventPasteEvent = 1;
			// For safety reason we should wait longer than 0/1ms.
			// We don't know how long execution of quite complex getClipboardData will take
			// and in for example 'paste' listener execCommand() (which fires 'paste') is called
			// after getClipboardData finishes.
			// Luckily, it's impossible to immediately fire another 'paste' event we want to handle,
			// because we only handle there native context menu and menu bar.
			setTimeout( function() {
				preventPasteEvent = 0;
			}, 100 );
		}

		function preventBeforePasteEventNow() {
			preventBeforePasteEvent = 1;
			setTimeout( function() {
				preventBeforePasteEvent = 0;
			}, 10 );
		}

		// Tries to execute any of the paste, cut or copy commands in IE. Returns a
		// boolean indicating that the operation succeeded.
		// @param {String} command *LOWER CASED* name of command ('paste', 'cut', 'copy').
		function execIECommand( command ) {
			var doc = editor.document,
				body = doc.getBody(),
				enabled = false,
				onExec = function() {
					enabled = true;
				};

			// The following seems to be the only reliable way to detect that
			// clipboard commands are enabled in IE. It will fire the
			// onpaste/oncut/oncopy events only if the security settings allowed
			// the command to execute.
			body.on( command, onExec );

			// IE7: document.execCommand has problem to paste into positioned element.
			if ( CKEDITOR.env.version > 7 ) {
				doc.$.execCommand( command );
			} else {
				doc.$.selection.createRange().execCommand( command );
			}

			body.removeListener( command, onExec );

			return enabled;
		}

		// Cutting off control type element in IE standards breaks the selection entirely. (#4881)
		function fixCut() {
			if ( !CKEDITOR.env.ie || CKEDITOR.env.quirks )
				return;

			var sel = editor.getSelection(),
				control, range, dummy;

			if ( ( sel.getType() == CKEDITOR.SELECTION_ELEMENT ) && ( control = sel.getSelectedElement() ) ) {
				range = sel.getRanges()[ 0 ];
				dummy = editor.document.createText( '' );
				dummy.insertBefore( control );
				range.setStartBefore( dummy );
				range.setEndAfter( control );
				sel.selectRanges( [ range ] );

				// Clear up the fix if the paste wasn't succeeded.
				setTimeout( function() {
					// Element still online?
					if ( control.getParent() ) {
						dummy.remove();
						sel.selectElement( control );
					}
				}, 0 );
			}
		}

		// Allow to peek clipboard content by redirecting the
		// pasting content into a temporary bin and grab the content of it.
		function getClipboardDataByPastebin( evt, callback ) {
			var doc = editor.document,
				editable = editor.editable(),
				cancel = function( evt ) {
					evt.cancel();
				},
				blurListener;

			// Avoid recursions on 'paste' event or consequent paste too fast. (#5730)
			if ( doc.getById( 'cke_pastebin' ) )
				return;

			var sel = editor.getSelection();
			var bms = sel.createBookmarks();

			// #11384. On IE9+ we use native selectionchange (i.e. editor#selectionCheck) to cache the most
			// recent selection which we then lock on editable blur. See selection.js for more info.
			// selectionchange fired before getClipboardDataByPastebin() cached selection
			// before creating bookmark (cached selection will be invalid, because bookmarks modified the DOM),
			// so we need to fire selectionchange one more time, to store current seleciton.
			// Selection will be locked when we focus pastebin.
			if ( CKEDITOR.env.ie )
				sel.root.fire( 'selectionchange' );

			// Create container to paste into.
			// For rich content we prefer to use "body" since it holds
			// the least possibility to be splitted by pasted content, while this may
			// breaks the text selection on a frame-less editable, "div" would be
			// the best one in that case.
			// In another case on old IEs moving the selection into a "body" paste bin causes error panic.
			// Body can't be also used for Opera which fills it with <br>
			// what is indistinguishable from pasted <br> (copying <br> in Opera isn't possible,
			// but it can be copied from other browser).
			var pastebin = new CKEDITOR.dom.element(
				( CKEDITOR.env.webkit || editable.is( 'body' ) ) && !CKEDITOR.env.ie ? 'body' : 'div', doc );

			pastebin.setAttributes( {
				id: 'cke_pastebin',
				'data-cke-temp': '1'
			} );

			var containerOffset = 0,
				offsetParent,
				win = doc.getWindow();

			if ( CKEDITOR.env.webkit ) {
				// It's better to paste close to the real paste destination, so inherited styles
				// (which Webkits will try to compensate by styling span) differs less from the destination's one.
				editable.append( pastebin );
				// Style pastebin like .cke_editable, to minimize differences between origin and destination. (#9754)
				pastebin.addClass( 'cke_editable' );

				// Compensate position of offsetParent.
				if ( !editable.is( 'body' ) ) {
					// We're not able to get offsetParent from pastebin (body element), so check whether
					// its parent (editable) is positioned.
					if ( editable.getComputedStyle( 'position' ) != 'static' )
						offsetParent = editable;
					// And if not - safely get offsetParent from editable.
					else
						offsetParent = CKEDITOR.dom.element.get( editable.$.offsetParent );

					containerOffset = offsetParent.getDocumentPosition().y;
				}
			} else {
				// Opera and IE doesn't allow to append to html element.
				editable.getAscendant( CKEDITOR.env.ie ? 'body' : 'html', 1 ).append( pastebin );
			}

			pastebin.setStyles( {
				position: 'absolute',
				// Position the bin at the top (+10 for safety) of viewport to avoid any subsequent document scroll.
				top: ( win.getScrollPosition().y - containerOffset + 10 ) + 'px',
				width: '1px',
				// Caret has to fit in that height, otherwise browsers like Chrome & Opera will scroll window to show it.
				// Set height equal to viewport's height - 20px (safety gaps), minimum 1px.
				height: Math.max( 1, win.getViewPaneSize().height - 20 ) + 'px',
				overflow: 'hidden',
				// Reset styles that can mess up pastebin position.
				margin: 0,
				padding: 0
			} );

			// Paste fails in Safari when the body tag has 'user-select: none'. (#12506)
			if ( CKEDITOR.env.safari )
				pastebin.setStyles( CKEDITOR.tools.cssVendorPrefix( 'user-select', 'text' ) );

			// Check if the paste bin now establishes new editing host.
			var isEditingHost = pastebin.getParent().isReadOnly();

			if ( isEditingHost ) {
				// Hide the paste bin.
				pastebin.setOpacity( 0 );
				// And make it editable.
				pastebin.setAttribute( 'contenteditable', true );
			}
			// Transparency is not enough since positioned non-editing host always shows
			// resize handler, pull it off the screen instead.
			else {
				pastebin.setStyle( editor.config.contentsLangDirection == 'ltr' ? 'left' : 'right', '-10000px' );
			}

			editor.on( 'selectionChange', cancel, null, null, 0 );

			// Webkit fill fire blur on editable when moving selection to
			// pastebin (if body is used). Cancel it because it causes incorrect
			// selection lock in case of inline editor (#10644).
			// The same seems to apply to Firefox (#10787).
			if ( CKEDITOR.env.webkit || CKEDITOR.env.gecko )
				blurListener = editable.once( 'blur', cancel, null, null, -100 );

			// Temporarily move selection to the pastebin.
			isEditingHost && pastebin.focus();
			var range = new CKEDITOR.dom.range( pastebin );
			range.selectNodeContents( pastebin );
			var selPastebin = range.select();

			// If non-native paste is executed, IE will open security alert and blur editable.
			// Editable will then lock selection inside itself and after accepting security alert
			// this selection will be restored. We overwrite stored selection, so it's restored
			// in pastebin. (#9552)
			if ( CKEDITOR.env.ie ) {
				blurListener = editable.once( 'blur', function() {
					editor.lockSelection( selPastebin );
				} );
			}

			var scrollTop = CKEDITOR.document.getWindow().getScrollPosition().y;

			// Wait a while and grab the pasted contents.
			setTimeout( function() {
				// Restore main window's scroll position which could have been changed
				// by browser in cases described in #9771.
				if ( CKEDITOR.env.webkit )
					CKEDITOR.document.getBody().$.scrollTop = scrollTop;

				// Blur will be fired only on non-native paste. In other case manually remove listener.
				blurListener && blurListener.removeListener();

				// Restore properly the document focus. (#8849)
				if ( CKEDITOR.env.ie )
					editable.focus();

				// IE7: selection must go before removing pastebin. (#8691)
				sel.selectBookmarks( bms );
				pastebin.remove();

				// Grab the HTML contents.
				// We need to look for a apple style wrapper on webkit it also adds
				// a div wrapper if you copy/paste the body of the editor.
				// Remove hidden div and restore selection.
				var bogusSpan;
				if ( CKEDITOR.env.webkit && ( bogusSpan = pastebin.getFirst() ) && ( bogusSpan.is && bogusSpan.hasClass( 'Apple-style-span' ) ) )
					pastebin = bogusSpan;

				editor.removeListener( 'selectionChange', cancel );
				callback( pastebin.getHtml() );
			}, 0 );
		}

		// Try to get content directly on IE from clipboard, without native event
		// being fired before. In other words - synthetically get clipboard data, if it's possible.
		// mainPasteEvent will be fired, so if forced native paste:
		// * worked, getClipboardDataByPastebin will grab it,
		// * didn't work, dataValue and dataTransfer will be empty and editor#paste won't be fired.
		// Clipboard data can be accessed directly only on IEs older than Edge.
		// On other browsers we should fire beforePaste event and return false.
		function getClipboardDataDirectly() {
			if ( clipboard.mainPasteEvent == 'paste' ) {
				// beforePaste should be fired when dialog open so it can be canceled.
				editor.fire( 'beforePaste', { type: 'auto', method: 'paste' } );
				return false;
			}

			// Prevent IE from pasting at the begining of the document.
			editor.focus();

			// Command will be handled by 'beforepaste', but as
			// execIECommand( 'paste' ) will fire also 'paste' event
			// we're canceling it.
			preventPasteEventNow();

			// #9247: Lock focus to prevent IE from hiding toolbar for inline editor.
			var focusManager = editor.focusManager;
			focusManager.lock();

			if ( editor.editable().fire( clipboard.mainPasteEvent ) && !execIECommand( 'paste' ) ) {
				focusManager.unlock();
				return false;
			}
			focusManager.unlock();

			return true;
		}

		// Listens for some clipboard related keystrokes, so they get customized.
		// Needs to be bind to keydown event.
		function onKey( event ) {
			if ( editor.mode != 'wysiwyg' )
				return;

			switch ( event.data.keyCode ) {
				// Paste
				case CKEDITOR.CTRL + 86: // CTRL+V
				case CKEDITOR.SHIFT + 45: // SHIFT+INS
					var editable = editor.editable();

					// Cancel 'paste' event because ctrl+v is for IE handled
					// by 'beforepaste'.
					preventPasteEventNow();

					// Simulate 'beforepaste' event for all browsers using 'paste' as main event.
					if ( clipboard.mainPasteEvent == 'paste' ) {
						editable.fire( 'beforepaste' );
					}

					return;

					// Cut
				case CKEDITOR.CTRL + 88: // CTRL+X
				case CKEDITOR.SHIFT + 46: // SHIFT+DEL
					// Save Undo snapshot.
					editor.fire( 'saveSnapshot' ); // Save before cut
					setTimeout( function() {
						editor.fire( 'saveSnapshot' ); // Save after cut
					}, 50 ); // OSX is slow (#11416).
			}
		}

		function pasteDataFromClipboard( evt ) {
			// Default type is 'auto', but can be changed by beforePaste listeners.
			var eventData = {
					type: 'auto',
					method: 'paste',
					dataTransfer: clipboard.initPasteDataTransfer( evt )
				};

			eventData.dataTransfer.cacheData();

			// Fire 'beforePaste' event so clipboard flavor get customized by other plugins.
			// If 'beforePaste' is canceled continue executing getClipboardDataByPastebin and then do nothing
			// (do not fire 'paste', 'afterPaste' events). This way we can grab all - synthetically
			// and natively pasted content and prevent its insertion into editor
			// after canceling 'beforePaste' event.
			var beforePasteNotCanceled = editor.fire( 'beforePaste', eventData ) !== false;

			// Do not use paste bin if the browser let us get HTML or files from dataTranfer.
			if ( beforePasteNotCanceled && clipboard.canClipboardApiBeTrusted( eventData.dataTransfer, editor ) ) {
				evt.data.preventDefault();
				setTimeout( function() {
					firePasteEvents( editor, eventData );
				}, 0 );
			} else {
				getClipboardDataByPastebin( evt, function( data ) {
					// Clean up.
					eventData.dataValue = data.replace( /<span[^>]+data-cke-bookmark[^<]*?<\/span>/ig, '' );

					// Fire remaining events (without beforePaste)
					beforePasteNotCanceled && firePasteEvents( editor, eventData );
				} );
			}
		}

		function setToolbarStates() {
			if ( editor.mode != 'wysiwyg' )
				return;

			var pasteState = stateFromNamedCommand( 'paste' );

			editor.getCommand( 'cut' ).setState( stateFromNamedCommand( 'cut' ) );
			editor.getCommand( 'copy' ).setState( stateFromNamedCommand( 'copy' ) );
			editor.getCommand( 'paste' ).setState( pasteState );
			editor.fire( 'pasteState', pasteState );
		}

		function stateFromNamedCommand( command ) {
			if ( inReadOnly && command in { paste: 1, cut: 1 } )
				return CKEDITOR.TRISTATE_DISABLED;

			if ( command == 'paste' )
				return CKEDITOR.TRISTATE_OFF;

			// Cut, copy - check if the selection is not empty.
			var sel = editor.getSelection(),
				ranges = sel.getRanges(),
				selectionIsEmpty = sel.getType() == CKEDITOR.SELECTION_NONE || ( ranges.length == 1 && ranges[ 0 ].collapsed );

			return selectionIsEmpty ? CKEDITOR.TRISTATE_DISABLED : CKEDITOR.TRISTATE_OFF;
		}
	}

	// Returns:
	// * 'htmlifiedtext' if content looks like transformed by browser from plain text.
	//		See clipboard/paste.html TCs for more info.
	// * 'html' if it is not 'htmlifiedtext'.
	function recogniseContentType( data ) {
		if ( CKEDITOR.env.webkit ) {
			// Plain text or ( <div><br></div> and text inside <div> ).
			if ( !data.match( /^[^<]*$/g ) && !data.match( /^(<div><br( ?\/)?><\/div>|<div>[^<]*<\/div>)*$/gi ) )
				return 'html';
		} else if ( CKEDITOR.env.ie ) {
			// Text and <br> or ( text and <br> in <p> - paragraphs can be separated by new \r\n ).
			if ( !data.match( /^([^<]|<br( ?\/)?>)*$/gi ) && !data.match( /^(<p>([^<]|<br( ?\/)?>)*<\/p>|(\r\n))*$/gi ) )
				return 'html';
		} else if ( CKEDITOR.env.gecko ) {
			// Text or <br>.
			if ( !data.match( /^([^<]|<br( ?\/)?>)*$/gi ) )
				return 'html';
		} else {
			return 'html';
		}

		return 'htmlifiedtext';
	}

	// This function transforms what browsers produce when
	// pasting plain text into editable element (see clipboard/paste.html TCs
	// for more info) into correct HTML (similar to that produced by text2Html).
	function htmlifiedTextHtmlification( config, data ) {
		function repeatParagraphs( repeats ) {
			// Repeat blocks floor((n+1)/2) times.
			// Even number of repeats - add <br> at the beginning of last <p>.
			return CKEDITOR.tools.repeat( '</p><p>', ~~( repeats / 2 ) ) + ( repeats % 2 == 1 ? '<br>' : '' );
		}

			// Replace adjacent white-spaces (EOLs too - Fx sometimes keeps them) with one space.
		data = data.replace( /\s+/g, ' ' )
			// Remove spaces from between tags.
			.replace( /> +</g, '><' )
			// Normalize XHTML syntax and upper cased <br> tags.
			.replace( /<br ?\/>/gi, '<br>' );

		// IE - lower cased tags.
		data = data.replace( /<\/?[A-Z]+>/g, function( match ) {
			return match.toLowerCase();
		} );

		// Don't touch single lines (no <br|p|div>) - nothing to do here.
		if ( data.match( /^[^<]$/ ) )
			return data;

		// Webkit.
		if ( CKEDITOR.env.webkit && data.indexOf( '<div>' ) > -1 ) {
				// One line break at the beginning - insert <br>
			data = data.replace( /^(<div>(<br>|)<\/div>)(?!$|(<div>(<br>|)<\/div>))/g, '<br>' )
				// Two or more - reduce number of new lines by one.
				.replace( /^(<div>(<br>|)<\/div>){2}(?!$)/g, '<div></div>' );

			// Two line breaks create one paragraph in Webkit.
			if ( data.match( /<div>(<br>|)<\/div>/ ) ) {
				data = '<p>' + data.replace( /(<div>(<br>|)<\/div>)+/g, function( match ) {
					return repeatParagraphs( match.split( '</div><div>' ).length + 1 );
				} ) + '</p>';
			}

			// One line break create br.
			data = data.replace( /<\/div><div>/g, '<br>' );

			// Remove remaining divs.
			data = data.replace( /<\/?div>/g, '' );
		}

		// Opera and Firefox and enterMode != BR.
		if ( CKEDITOR.env.gecko && config.enterMode != CKEDITOR.ENTER_BR ) {
			// Remove bogus <br> - Fx generates two <brs> for one line break.
			// For two line breaks it still produces two <brs>, but it's better to ignore this case than the first one.
			if ( CKEDITOR.env.gecko )
				data = data.replace( /^<br><br>$/, '<br>' );

			// This line satisfy edge case when for Opera we have two line breaks
			//data = data.replace( /)

			if ( data.indexOf( '<br><br>' ) > -1 ) {
				// Two line breaks create one paragraph, three - 2, four - 3, etc.
				data = '<p>' + data.replace( /(<br>){2,}/g, function( match ) {
					return repeatParagraphs( match.length / 4 );
				} ) + '</p>';
			}
		}

		return switchEnterMode( config, data );
	}

	function filtersFactoryFactory() {
		var filters = {};

		function setUpTags() {
			var tags = {};

			for ( var tag in CKEDITOR.dtd ) {
				if ( tag.charAt( 0 ) != '$' && tag != 'div' && tag != 'span' ) {
					tags[ tag ] = 1;
				}
			}

			return tags;
		}

		function createSemanticContentFilter() {
			var filter = new CKEDITOR.filter();

			filter.allow( {
				$1: {
					elements: setUpTags(),
					attributes: true,
					styles: false,
					classes: false
				}
			} );

			return filter;
		}

		return {
			get: function( type ) {
				if ( type == 'plain-text' ) {
					// Does this look confusing to you? Did we forget about enter mode?
					// It is a trick that let's us creating one filter for edidtor, regardless of its
					// activeEnterMode (which as the name indicates can change during runtime).
					//
					// How does it work?
					// The active enter mode is passed to the filter.applyTo method.
					// The filter first marks all elements except <br> as disallowed and then tries to remove
					// them. However, it cannot remove e.g. a <p> element completely, because it's a basic structural element,
					// so it tries to replace it with an element created based on the active enter mode, eventually doing nothing.
					//
					// Now you can sleep well.
					return filters.plainText || ( filters.plainText = new CKEDITOR.filter( 'br' ) );
				} else if ( type == 'semantic-content' ) {
					return filters.semanticContent || ( filters.semanticContent = createSemanticContentFilter() );
				} else if ( type ) {
					// Create filter based on rules (string or object).
					return new CKEDITOR.filter( type );
				}

				return null;
			}
		};
	}

	function filterContent( editor, data, filter ) {
		var fragment = CKEDITOR.htmlParser.fragment.fromHtml( data ),
			writer = new CKEDITOR.htmlParser.basicWriter();

		filter.applyTo( fragment, true, false, editor.activeEnterMode );
		fragment.writeHtml( writer );

		return writer.getHtml();
	}

	function switchEnterMode( config, data ) {
		if ( config.enterMode == CKEDITOR.ENTER_BR ) {
			data = data.replace( /(<\/p><p>)+/g, function( match ) {
				return CKEDITOR.tools.repeat( '<br>', match.length / 7 * 2 );
			} ).replace( /<\/?p>/g, '' );
		} else if ( config.enterMode == CKEDITOR.ENTER_DIV ) {
			data = data.replace( /<(\/)?p>/g, '<$1div>' );
		}

		return data;
	}

	function preventDefaultSetDropEffectToNone( evt ) {
		evt.data.preventDefault();
		evt.data.$.dataTransfer.dropEffect = 'none';
	}

	function initDragDrop( editor ) {
		var clipboard = CKEDITOR.plugins.clipboard;

		editor.on( 'contentDom', function() {
			var editable = editor.editable(),
				dropTarget = CKEDITOR.plugins.clipboard.getDropTarget( editor ),
				top = editor.ui.space( 'top' ),
				bottom = editor.ui.space( 'bottom' );

			// -------------- DRAGOVER TOP & BOTTOM --------------

			// Not allowing dragging on toolbar and bottom (#12613).
			clipboard.preventDefaultDropOnElement( top );
			clipboard.preventDefaultDropOnElement( bottom );

			// -------------- DRAGSTART --------------
			// Listed on dragstart to mark internal and cross-editor drag & drop
			// and save range and selected HTML.

			editable.attachListener( dropTarget, 'dragstart', fireDragEvent );

			// Make sure to reset data transfer (in case dragend was not called or was canceled).
			editable.attachListener( editor, 'dragstart', clipboard.resetDragDataTransfer, clipboard, null, 1 );

			// Create a dataTransfer object and save it globally.
			editable.attachListener( editor, 'dragstart', function( evt ) {
				clipboard.initDragDataTransfer( evt, editor );
			}, null, null, 2 );

			editable.attachListener( editor, 'dragstart', function() {
				// Save drag range globally for cross editor D&D.
				var dragRange = clipboard.dragRange = editor.getSelection().getRanges()[ 0 ];

				// Store number of children, so we can later tell if any text node was split on drop. (#13011, #13447)
				if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) {
					clipboard.dragStartContainerChildCount = dragRange ? getContainerChildCount( dragRange.startContainer ) : null;
					clipboard.dragEndContainerChildCount = dragRange ? getContainerChildCount( dragRange.endContainer ) : null;
				}
			}, null, null, 100 );

			// -------------- DRAGEND --------------
			// Clean up on dragend.

			editable.attachListener( dropTarget, 'dragend', fireDragEvent );

			// Init data transfer if someone wants to use it in dragend.
			editable.attachListener( editor, 'dragend', clipboard.initDragDataTransfer, clipboard, null, 1 );

			// When drag & drop is done we need to reset dataTransfer so the future
			// external drop will be not recognize as internal.
			editable.attachListener( editor, 'dragend', clipboard.resetDragDataTransfer, clipboard, null, 100 );

			// -------------- DRAGOVER --------------
			// We need to call preventDefault on dragover because otherwise if
			// we drop image it will overwrite document.

			editable.attachListener( dropTarget, 'dragover', function( evt ) {
				var target = evt.data.getTarget();

				// Prevent reloading page when dragging image on empty document (#12619).
				if ( target && target.is && target.is( 'html' ) ) {
					evt.data.preventDefault();
					return;
				}

				// If we do not prevent default dragover on IE the file path
				// will be loaded and we will lose content. On the other hand
				// if we prevent it the cursor will not we shown, so we prevent
				// dragover only on IE, on versions which support file API and only
				// if the event contains files.
				if ( CKEDITOR.env.ie &&
					CKEDITOR.plugins.clipboard.isFileApiSupported &&
					evt.data.$.dataTransfer.types.contains( 'Files' ) ) {
					evt.data.preventDefault();
				}
			} );

			// -------------- DROP --------------

			editable.attachListener( dropTarget, 'drop', function( evt ) {
				// Do nothing if event was already prevented. (#13879)
				if ( evt.data.$.defaultPrevented ) {
					return;
				}

				// Cancel native drop.
				evt.data.preventDefault();

				var target = evt.data.getTarget(),
					readOnly = target.isReadOnly();

				// Do nothing if drop on non editable element (#13015).
				// The <html> tag isn't editable (body is), but we want to allow drop on it
				// (so it is possible to drop below editor contents).
				if ( readOnly && !( target.type == CKEDITOR.NODE_ELEMENT && target.is( 'html' ) ) ) {
					return;
				}

				// Getting drop position is one of the most complex parts.
				var dropRange = clipboard.getRangeAtDropPosition( evt, editor ),
					dragRange = clipboard.dragRange;

				// Do nothing if it was not possible to get drop range.
				if ( !dropRange ) {
					return;
				}

				// Fire drop.
				fireDragEvent( evt, dragRange, dropRange  );
			}, null, null, 9999 );

			// Create dataTransfer or get it, if it was created before.
			editable.attachListener( editor, 'drop', clipboard.initDragDataTransfer, clipboard, null, 1 );

			// Execute drop action, fire paste.
			editable.attachListener( editor, 'drop', function( evt ) {
				var data = evt.data;

				if ( !data ) {
					return;
				}

				// Let user modify drag and drop range.
				var dropRange = data.dropRange,
					dragRange = data.dragRange,
					dataTransfer = data.dataTransfer;

				if ( dataTransfer.getTransferType( editor ) == CKEDITOR.DATA_TRANSFER_INTERNAL ) {
					// Execute drop with a timeout because otherwise selection, after drop,
					// on IE is in the drag position, instead of drop position.
					setTimeout( function() {
						clipboard.internalDrop( dragRange, dropRange, dataTransfer, editor );
					}, 0 );
				} else if ( dataTransfer.getTransferType( editor ) == CKEDITOR.DATA_TRANSFER_CROSS_EDITORS ) {
					crossEditorDrop( dragRange, dropRange, dataTransfer );
				} else {
					externalDrop( dropRange, dataTransfer );
				}
			}, null, null, 9999 );

			// Cross editor drag and drop (drag in one Editor and drop in the other).
			function crossEditorDrop( dragRange, dropRange, dataTransfer ) {
				// Paste event should be fired before delete contents because otherwise
				// Chrome have a problem with drop range (Chrome split the drop
				// range container so the offset is bigger then container length).
				dropRange.select();
				firePasteEvents( editor, { dataTransfer: dataTransfer, method: 'drop' }, 1 );

				// Remove dragged content and make a snapshot.
				dataTransfer.sourceEditor.fire( 'saveSnapshot' );

				dataTransfer.sourceEditor.editable().extractHtmlFromRange( dragRange );

				// Make some selection before saving snapshot, otherwise error will be thrown, because
				// there will be no valid selection after content is removed.
				dataTransfer.sourceEditor.getSelection().selectRanges( [ dragRange ] );
				dataTransfer.sourceEditor.fire( 'saveSnapshot' );
			}

			// Drop from external source.
			function externalDrop( dropRange, dataTransfer ) {
				// Paste content into the drop position.
				dropRange.select();

				firePasteEvents( editor, { dataTransfer: dataTransfer, method: 'drop' }, 1 );

				// Usually we reset DataTranfer on dragend,
				// but dragend is called on the same element as dragstart
				// so it will not be called on on external drop.
				clipboard.resetDragDataTransfer();
			}

			// Fire drag/drop events (dragstart, dragend, drop).
			function fireDragEvent( evt, dragRange, dropRange ) {
				var eventData = {
						$: evt.data.$,
						target: evt.data.getTarget()
					};

				if ( dragRange ) {
					eventData.dragRange = dragRange;
				}
				if ( dropRange ) {
					eventData.dropRange = dropRange;
				}

				if ( editor.fire( evt.name, eventData ) === false ) {
					evt.data.preventDefault();
				}
			}

			function getContainerChildCount( container ) {
				if ( container.type != CKEDITOR.NODE_ELEMENT ) {
					container = container.getParent();
				}

				return container.getChildCount();
			}
		} );
	}

	/**
	 * @singleton
	 * @class CKEDITOR.plugins.clipboard
	 */
	CKEDITOR.plugins.clipboard = {
		/**
		 * True if the environment allows to set data on copy or cut manually. This value is false in IE, because this browser
		 * shows the security dialog window when the script tries to set clipboard data and on iOS, because custom data is
		 * not saved to clipboard there.
		 *
		 * @since 4.5
		 * @readonly
		 * @property {Boolean}
		 */
		isCustomCopyCutSupported: !CKEDITOR.env.ie && !CKEDITOR.env.iOS,

		/**
		 * True if the environment supports MIME types and custom data types in dataTransfer/cliboardData getData/setData methods.
		 *
		 * @since 4.5
		 * @readonly
		 * @property {Boolean}
		 */
		isCustomDataTypesSupported: !CKEDITOR.env.ie,

		/**
		 * True if the environment supports File API.
		 *
		 * @since 4.5
		 * @readonly
		 * @property {Boolean}
		 */
		isFileApiSupported: !CKEDITOR.env.ie || CKEDITOR.env.version > 9,

		/**
		 * Main native paste event editable should listen to.
		 *
		 * **Note:** Safari does not like the {@link CKEDITOR.editor#beforePaste} event &mdash; it sometimes does not
		 * handle <kbd>Ctrl+C</kbd> properly. This is probably caused by some race condition between events.
		 * Chrome, Firefox and Edge work well with both events, so it is better to use {@link CKEDITOR.editor#paste}
		 * which will handle pasting from e.g. browsers' menu bars.
		 * IE7/8 does not like the {@link CKEDITOR.editor#paste} event for which it is throwing random errors.
		 *
		 * @since 4.5
		 * @readonly
		 * @property {String}
		 */
		mainPasteEvent: ( CKEDITOR.env.ie && !CKEDITOR.env.edge ) ? 'beforepaste' : 'paste',

		/**
		 * Returns `true` if it is expected that a browser provides HTML data through the Clipboard API.
		 * If not, this method returns `false` and as a result CKEditor will use the paste bin. Read more in
		 * the [Clipboard Integration](http://docs.ckeditor.com/#!/guide/dev_clipboard-section-clipboard-api) guide.
		 *
		 * @since 4.5.2
		 * @returns {Boolean}
		 */
		canClipboardApiBeTrusted: function( dataTransfer, editor ) {
			// If it's an internal or cross-editor data transfer, then it means that custom cut/copy/paste support works
			// and that the data were put manually on the data transfer so we can be sure that it's available.
			if ( dataTransfer.getTransferType( editor ) != CKEDITOR.DATA_TRANSFER_EXTERNAL ) {
				return true;
			}

			// In Chrome we can trust Clipboard API, with the exception of Chrome on Android (in both - mobile and desktop modes), where
			// clipboard API is not available so we need to check it (#13187).
			if ( CKEDITOR.env.chrome && !dataTransfer.isEmpty() ) {
				return true;
			}

			// Because of a Firefox bug HTML data are not available in some cases (e.g. paste from Word), in such cases we
			// need to use the pastebin (#13528, https://bugzilla.mozilla.org/show_bug.cgi?id=1183686).
			if ( CKEDITOR.env.gecko && ( dataTransfer.getData( 'text/html' ) || dataTransfer.getFilesCount() ) ) {
				return true;
			}

			// In Safari and IE HTML data is not available though the Clipboard API.
			// In Edge things are a bit messy at the moment -
			// https://connect.microsoft.com/IE/feedback/details/1572456/edge-clipboard-api-text-html-content-messed-up-in-event-clipboarddata
			// It is safer to use the paste bin in unknown cases.
			return false;
		},

		/**
		 * Returns the element that should be used as the target for the drop event.
		 *
		 * @since 4.5
		 * @param {CKEDITOR.editor} editor The editor instance.
		 * @returns {CKEDITOR.dom.domObject} the element that should be used as the target for the drop event.
		 */
		getDropTarget: function( editor ) {
			var editable = editor.editable();

			// #11123 Firefox needs to listen on document, because otherwise event won't be fired.
			// #11086 IE8 cannot listen on document.
			if ( ( CKEDITOR.env.ie && CKEDITOR.env.version < 9 ) || editable.isInline() ) {
				return editable;
			} else {
				return editor.document;
			}
		},

		/**
		 * IE 8 & 9 split text node on drop so the first node contains the
		 * text before the drop position and the second contains the rest. If you
		 * drag the content from the same node you will be not be able to get
		 * it (the range becomes invalid), so you need to join them back.
		 *
		 * Note that the first node in IE 8 & 9 is the original node object
		 * but with shortened content.
		 *
		 *		Before:
		 *		  --- Text Node A ----------------------------------
		 *		                                             /\
		 *		                                        Drag position
		 *
		 *		After (IE 8 & 9):
		 *		  --- Text Node A -----  --- Text Node B -----------
		 *		                       /\                    /\
		 *		                  Drop position        Drag position
		 *		                                         (invalid)
		 *
		 *		After (other browsers):
		 *		  --- Text Node A ----------------------------------
		 *		                       /\                    /\
		 *		                  Drop position        Drag position
		 *
		 * **Note:** This function is in the public scope for tests usage only.
		 *
		 * @since 4.5
		 * @private
		 * @param {CKEDITOR.dom.range} dragRange The drag range.
		 * @param {CKEDITOR.dom.range} dropRange The drop range.
		 * @param {Number} preDragStartContainerChildCount The number of children of the drag range start container before the drop.
		 * @param {Number} preDragEndContainerChildCount The number of children of the drag range end container before the drop.
		 */
		fixSplitNodesAfterDrop: function( dragRange, dropRange, preDragStartContainerChildCount, preDragEndContainerChildCount ) {
			var dropContainer = dropRange.startContainer;

			if (
				typeof preDragEndContainerChildCount != 'number' ||
				typeof preDragStartContainerChildCount != 'number'
			) {
				return;
			}

			// We are only concerned about ranges anchored in elements.
			if ( dropContainer.type != CKEDITOR.NODE_ELEMENT ) {
				return;
			}

			if ( handleContainer( dragRange.startContainer, dropContainer, preDragStartContainerChildCount ) ) {
				return;
			}

			if ( handleContainer( dragRange.endContainer, dropContainer, preDragEndContainerChildCount ) ) {
				return;
			}

			function handleContainer( dragContainer, dropContainer, preChildCount ) {
				var dragElement = dragContainer;
				if ( dragElement.type == CKEDITOR.NODE_TEXT ) {
					dragElement = dragContainer.getParent();
				}

				if ( dragElement.equals( dropContainer ) && preChildCount != dropContainer.getChildCount() ) {
					applyFix( dropRange );
					return true;
				}
			}

			function applyFix( dropRange ) {
				var nodeBefore = dropRange.startContainer.getChild( dropRange.startOffset - 1 ),
					nodeAfter = dropRange.startContainer.getChild( dropRange.startOffset );

				if (
					nodeBefore && nodeBefore.type == CKEDITOR.NODE_TEXT &&
					nodeAfter && nodeAfter.type == CKEDITOR.NODE_TEXT
				) {
					var offset = nodeBefore.getLength();

					nodeBefore.setText( nodeBefore.getText() + nodeAfter.getText() );
					nodeAfter.remove();

					dropRange.setStart( nodeBefore, offset );
					dropRange.collapse( true );
				}
			}
		},

		/**
		 * Checks whether turning the drag range into bookmarks will invalidate the drop range.
		 * This usually happens when the drop range shares the container with the drag range and is
		 * located after the drag range, but there are countless edge cases.
		 *
		 * This function is stricly related to {@link #internalDrop} which toggles
		 * order in which it creates bookmarks for both ranges based on a value returned
		 * by this method. In some cases this method returns a value which is not necessarily
		 * true in terms of what it was meant to check, but it is convenient, because
		 * we know how it is interpreted in {@link #internalDrop}, so the correct
		 * behavior of the entire algorithm is assured.
		 *
		 * **Note:** This function is in the public scope for tests usage only.
		 *
		 * @since 4.5
		 * @private
		 * @param {CKEDITOR.dom.range} dragRange The first range to compare.
		 * @param {CKEDITOR.dom.range} dropRange The second range to compare.
		 * @returns {Boolean} `true` if the first range is before the second range.
		 */
		isDropRangeAffectedByDragRange: function( dragRange, dropRange ) {
			var dropContainer = dropRange.startContainer,
				dropOffset = dropRange.endOffset;

			// Both containers are the same and drop offset is at the same position or later.
			// " A L] A " " M A "
			//       ^ ^
			if ( dragRange.endContainer.equals( dropContainer ) && dragRange.endOffset <= dropOffset ) {
				return true;
			}

			// Bookmark for drag start container will mess up with offsets.
			// " O [L A " " M A "
			//           ^       ^
			if (
				dragRange.startContainer.getParent().equals( dropContainer ) &&
				dragRange.startContainer.getIndex() < dropOffset
			) {
				return true;
			}

			// Bookmark for drag end container will mess up with offsets.
			// " O] L A " " M A "
			//           ^       ^
			if (
				dragRange.endContainer.getParent().equals( dropContainer ) &&
				dragRange.endContainer.getIndex() < dropOffset
			) {
				return true;
			}

			return false;
		},

		/**
		 * Internal drag and drop (drag and drop in the same editor instance).
		 *
		 * **Note:** This function is in the public scope for tests usage only.
		 *
		 * @since 4.5
		 * @private
		 * @param {CKEDITOR.dom.range} dragRange The first range to compare.
		 * @param {CKEDITOR.dom.range} dropRange The second range to compare.
		 * @param {CKEDITOR.plugins.clipboard.dataTransfer} dataTransfer
		 * @param {CKEDITOR.editor} editor
		 */
		internalDrop: function( dragRange, dropRange, dataTransfer, editor ) {
			var clipboard = CKEDITOR.plugins.clipboard,
				editable = editor.editable(),
				dragBookmark, dropBookmark, isDropRangeAffected;

			// Save and lock snapshot so there will be only
			// one snapshot for both remove and insert content.
			editor.fire( 'saveSnapshot' );
			editor.fire( 'lockSnapshot', { dontUpdate: 1 } );

			if ( CKEDITOR.env.ie && CKEDITOR.env.version < 10 ) {
				this.fixSplitNodesAfterDrop(
					dragRange,
					dropRange,
					clipboard.dragStartContainerChildCount,
					clipboard.dragEndContainerChildCount
				);
			}

			// Because we manipulate multiple ranges we need to do it carefully,
			// changing one range (event creating a bookmark) may make other invalid.
			// We need to change ranges into bookmarks so we can manipulate them easily in the future.
			// We can change the range which is later in the text before we change the preceding range.
			// We call isDropRangeAffectedByDragRange to test the order of ranges.
			isDropRangeAffected = this.isDropRangeAffectedByDragRange( dragRange, dropRange );
			if ( !isDropRangeAffected ) {
				dragBookmark = dragRange.createBookmark( false );
			}
			dropBookmark = dropRange.clone().createBookmark( false );
			if ( isDropRangeAffected ) {
				dragBookmark = dragRange.createBookmark( false );
			}

			// Check if drop range is inside range.
			// This is an edge case when we drop something on editable's margin/padding.
			// That space is not treated as a part of the range we drag, so it is possible to drop there.
			// When we drop, browser tries to find closest drop position and it finds it inside drag range. (#13453)
			var startNode = dragBookmark.startNode,
				endNode = dragBookmark.endNode,
				dropNode = dropBookmark.startNode,
				dropInsideDragRange =
					// Must check endNode because dragRange could be collapsed in some edge cases (simulated DnD).
					endNode &&
					( startNode.getPosition( dropNode ) & CKEDITOR.POSITION_PRECEDING ) &&
					( endNode.getPosition( dropNode ) & CKEDITOR.POSITION_FOLLOWING );

			// If the drop range happens to be inside drag range change it's position to the beginning of the drag range.
			if ( dropInsideDragRange ) {
				// We only change position of bookmark span that is connected with dropBookmark.
				// dropRange will be overwritten and set to the dropBookmark later.
				dropNode.insertBefore( startNode );
			}

			// No we can safely delete content for the drag range...
			dragRange = editor.createRange();
			dragRange.moveToBookmark( dragBookmark );
			editable.extractHtmlFromRange( dragRange, 1 );

			// ...and paste content into the drop position.
			dropRange = editor.createRange();
			dropRange.moveToBookmark( dropBookmark );

			// We do not select drop range, because of may be in the place we can not set the selection
			// (e.g. between blocks, in case of block widget D&D). We put range to the paste event instead.
			firePasteEvents( editor, { dataTransfer: dataTransfer, method: 'drop', range: dropRange }, 1 );

			editor.fire( 'unlockSnapshot' );
		},

		/**
		 * Gets the range from the `drop` event.
		 *
		 * @since 4.5
		 * @param {Object} domEvent A native DOM drop event object.
		 * @param {CKEDITOR.editor} editor The source editor instance.
		 * @returns {CKEDITOR.dom.range} range at drop position.
		 */
		getRangeAtDropPosition: function( dropEvt, editor ) {
			var $evt = dropEvt.data.$,
				x = $evt.clientX,
				y = $evt.clientY,
				$range,
				defaultRange = editor.getSelection( true ).getRanges()[ 0 ],
				range = editor.createRange();

			// Make testing possible.
			if ( dropEvt.data.testRange )
				return dropEvt.data.testRange;

			// Webkits.
			if ( document.caretRangeFromPoint ) {
				$range = editor.document.$.caretRangeFromPoint( x, y );
				range.setStart( CKEDITOR.dom.node( $range.startContainer ), $range.startOffset );
				range.collapse( true );
			}
			// FF.
			else if ( $evt.rangeParent ) {
				range.setStart( CKEDITOR.dom.node( $evt.rangeParent ), $evt.rangeOffset );
				range.collapse( true );
			}
			// IEs 9+.
			// We check if editable is focused to make sure that it's an internal DnD. External DnD must use the second
			// mechanism because of http://dev.ckeditor.com/ticket/13472#comment:6.
			else if ( CKEDITOR.env.ie && CKEDITOR.env.version > 8 && defaultRange && editor.editable().hasFocus ) {
				// On IE 9+ range by default is where we expected it.
				// defaultRange may be undefined if dragover was canceled (file drop).
				return defaultRange;
			}
			// IE 8 and all IEs if !defaultRange or external DnD.
			else if ( document.body.createTextRange ) {
				// To use this method we need a focus (which may be somewhere else in case of external drop).
				editor.focus();

				$range = editor.document.getBody().$.createTextRange();
				try {
					var sucess = false;

					// If user drop between text line IEs moveToPoint throws exception:
					//
					//		Lorem ipsum pulvinar purus et euismod
					//
					//		dolor sit amet,| consectetur adipiscing
					//		               *
					//		vestibulum tincidunt augue eget tempus.
					//
					// * - drop position
					// | - expected cursor position
					//
					// So we try to call moveToPoint with +-1px up to +-20px above or
					// below original drop position to find nearest good drop position.
					for ( var i = 0; i < 20 && !sucess; i++ ) {
						if ( !sucess ) {
							try {
								$range.moveToPoint( x, y - i );
								sucess = true;
							} catch ( err ) {
							}
						}
						if ( !sucess ) {
							try {
								$range.moveToPoint( x, y + i );
								sucess = true;
							} catch ( err ) {
							}
						}
					}

					if ( sucess ) {
						var id = 'cke-temp-' + ( new Date() ).getTime();
						$range.pasteHTML( '<span id="' + id + '">\u200b</span>' );

						var span = editor.document.getById( id );
						range.moveToPosition( span, CKEDITOR.POSITION_BEFORE_START );
						span.remove();
					} else {
						// If the fist method does not succeed we might be next to
						// the short element (like header):
						//
						//		Lorem ipsum pulvinar purus et euismod.
						//
						//
						//		SOME HEADER|        *
						//
						//
						//		vestibulum tincidunt augue eget tempus.
						//
						// * - drop position
						// | - expected cursor position
						//
						// In such situation elementFromPoint returns proper element. Using getClientRect
						// it is possible to check if the cursor should be at the beginning or at the end
						// of paragraph.
						var $element = editor.document.$.elementFromPoint( x, y ),
							element = new CKEDITOR.dom.element( $element ),
							rect;

						if ( !element.equals( editor.editable() ) && element.getName() != 'html' ) {
							rect = element.getClientRect();

							if ( x < rect.left ) {
								range.setStartAt( element, CKEDITOR.POSITION_AFTER_START );
								range.collapse( true );
							} else {
								range.setStartAt( element, CKEDITOR.POSITION_BEFORE_END );
								range.collapse( true );
							}
						}
						// If drop happens on no element elementFromPoint returns html or body.
						//
						//		*      |Lorem ipsum pulvinar purus et euismod.
						//
						//		       vestibulum tincidunt augue eget tempus.
						//
						// * - drop position
						// | - expected cursor position
						//
						// In such case we can try to use default selection. If startContainer is not
						// 'editable' element it is probably proper selection.
						else if ( defaultRange && defaultRange.startContainer &&
							!defaultRange.startContainer.equals( editor.editable() ) ) {
							return defaultRange;

						// Otherwise we can not find any drop position and we have to return null
						// and cancel drop event.
						} else {
							return null;
						}

					}
				} catch ( err ) {
					return null;
				}
			} else {
				return null;
			}

			return range;
		},

		/**
		 * This function tries to link the `evt.data.dataTransfer` property of the {@link CKEDITOR.editor#dragstart},
		 * {@link CKEDITOR.editor#dragend} and {@link CKEDITOR.editor#drop} events to a single
		 * {@link CKEDITOR.plugins.clipboard.dataTransfer} object.
		 *
		 * This method is automatically used by the core of the drag and drop functionality and
		 * usually does not have to be called manually when using the drag and drop events.
		 *
		 * This method behaves differently depending on whether the drag and drop events were fired
		 * artificially (to represent a non-native drag and drop) or whether they were caused by the native drag and drop.
		 *
		 * If the native event is not available, then it will create a new {@link CKEDITOR.plugins.clipboard.dataTransfer}
		 * instance (if it does not exist already) and will link it to this and all following event objects until
		 * the {@link #resetDragDataTransfer} method is called. It means that all three drag and drop events must be fired
		 * in order to ensure that the data transfer is bound correctly.
		 *
		 * If the native event is available, then the {@link CKEDITOR.plugins.clipboard.dataTransfer} is identified
		 * by its ID and a new instance is assigned to the `evt.data.dataTransfer` only if the ID changed or
		 * the {@link #resetDragDataTransfer} method was called.
		 *
		 * @since 4.5
		 * @param {CKEDITOR.dom.event} [evt] A drop event object.
		 * @param {CKEDITOR.editor} [sourceEditor] The source editor instance.
		 */
		initDragDataTransfer: function( evt, sourceEditor ) {
			// Create a new dataTransfer object based on the drop event.
			// If this event was used on dragstart to create dataTransfer
			// both dataTransfer objects will have the same id.
			var nativeDataTransfer = evt.data.$ ? evt.data.$.dataTransfer : null,
				dataTransfer = new this.dataTransfer( nativeDataTransfer, sourceEditor );

			if ( !nativeDataTransfer ) {
				// No native event.
				if ( this.dragData ) {
					dataTransfer = this.dragData;
				} else {
					this.dragData = dataTransfer;
				}
			} else {
				// Native event. If there is the same id we will replace dataTransfer with the one
				// created on drag, because it contains drag editor, drag content and so on.
				// Otherwise (in case of drag from external source) we save new object to
				// the global clipboard.dragData.
				if ( this.dragData && dataTransfer.id == this.dragData.id ) {
					dataTransfer = this.dragData;
				} else {
					this.dragData = dataTransfer;
				}
			}

			evt.data.dataTransfer = dataTransfer;
		},

		/**
		 * Removes the global {@link #dragData} so the next call to {@link #initDragDataTransfer}
		 * always creates a new instance of {@link CKEDITOR.plugins.clipboard.dataTransfer}.
		 *
		 * @since 4.5
		 */
		resetDragDataTransfer: function() {
			this.dragData = null;
		},

		/**
		 * Global object storing the data transfer of the current drag and drop operation.
		 * Do not use it directly, use {@link #initDragDataTransfer} and {@link #resetDragDataTransfer}.
		 *
		 * Note: This object is global (meaning that it is not related to a single editor instance)
		 * in order to handle drag and drop from one editor into another.
		 *
		 * @since 4.5
		 * @private
		 * @property {CKEDITOR.plugins.clipboard.dataTransfer} dragData
		 */

		/**
		 * Range object to save the drag range and remove its content after the drop.
		 *
		 * @since 4.5
		 * @private
		 * @property {CKEDITOR.dom.range} dragRange
		 */

		/**
		 * Initializes and links data transfer objects based on the paste event. If the data
		 * transfer object was already initialized on this event, the function will
		 * return that object. In IE it is not possible to link copy/cut and paste events
		 * so the method always returns a new object. The same happens if there is no paste event
		 * passed to the method.
		 *
		 * @since 4.5
		 * @param {CKEDITOR.dom.event} [evt] A paste event object.
		 * @param {CKEDITOR.editor} [sourceEditor] The source editor instance.
		 * @returns {CKEDITOR.plugins.clipboard.dataTransfer} The data transfer object.
		 */
		initPasteDataTransfer: function( evt, sourceEditor ) {
			if ( !this.isCustomCopyCutSupported ) {
				// Edge does not support custom copy/cut, but it have some useful data in the clipboardData (#13755).
				return new this.dataTransfer( ( CKEDITOR.env.edge && evt && evt.data.$ && evt.data.$.clipboardData ) || null, sourceEditor );
			} else if ( evt && evt.data && evt.data.$ ) {
				var dataTransfer = new this.dataTransfer( evt.data.$.clipboardData, sourceEditor );

				if ( this.copyCutData && dataTransfer.id == this.copyCutData.id ) {
					dataTransfer = this.copyCutData;
					dataTransfer.$ = evt.data.$.clipboardData;
				} else {
					this.copyCutData = dataTransfer;
				}

				return dataTransfer;
			} else {
				return new this.dataTransfer( null, sourceEditor );
			}
		},

		/**
		 * Prevents dropping on the specified element.
		 *
		 * @since 4.5
		 * @param {CKEDITOR.dom.element} element The element on which dropping should be disabled.
		 */
		preventDefaultDropOnElement: function( element ) {
			element && element.on( 'dragover', preventDefaultSetDropEffectToNone );
		}
	};

	// Data type used to link drag and drop events.
	//
	// In IE URL data type is buggie and there is no way to mark drag & drop  without
	// modifying text data (which would be displayed if user drop content to the textarea)
	// so we just read dragged text.
	//
	// In Chrome and Firefox we can use custom data types.
	var clipboardIdDataType = CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ? 'cke/id' : 'Text';
	/**
	 * Facade for the native `dataTransfer`/`clipboadData` object to hide all differences
	 * between browsers.
	 *
	 * @since 4.5
	 * @class CKEDITOR.plugins.clipboard.dataTransfer
	 * @constructor Creates a class instance.
	 * @param {Object} [nativeDataTransfer] A native data transfer object.
	 * @param {CKEDITOR.editor} [editor] The source editor instance. If the editor is defined, dataValue will
	 * be created based on the editor content and the type will be 'html'.
	 */
	CKEDITOR.plugins.clipboard.dataTransfer = function( nativeDataTransfer, editor ) {
		if ( nativeDataTransfer ) {
			this.$ = nativeDataTransfer;
		}

		this._ = {
			metaRegExp: /^<meta.*?>/i,
			bodyRegExp: /<body(?:[\s\S]*?)>([\s\S]*)<\/body>/i,
			fragmentRegExp: /<!--(?:Start|End)Fragment-->/g,

			data: {},
			files: [],

			normalizeType: function( type ) {
				type = type.toLowerCase();

				if ( type == 'text' || type == 'text/plain' ) {
					return 'Text'; // IE support only Text and URL;
				} else if ( type == 'url' ) {
					return 'URL'; // IE support only Text and URL;
				} else {
					return type;
				}
			}
		};

		// Check if ID is already created.
		this.id = this.getData( clipboardIdDataType );

		// If there is no ID we need to create it. Different browsers needs different ID.
		if ( !this.id ) {
			if ( clipboardIdDataType == 'Text' ) {
				// For IE10+ only Text data type is supported and we have to compare dragged
				// and dropped text. If the ID is not set it means that empty string was dragged
				// (ex. image with no alt). We change null to empty string.
				this.id = '';
			} else {
				// String for custom data type.
				this.id = 'cke-' + CKEDITOR.tools.getUniqueId();
			}
		}

		// In IE10+ we can not use any data type besides text, so we do not call setData.
		if ( clipboardIdDataType != 'Text' ) {
			// Try to set ID so it will be passed from the drag to the drop event.
			// On some browsers with some event it is not possible to setData so we
			// need to catch exceptions.
			try {
				this.$.setData( clipboardIdDataType, this.id );
			} catch ( err ) {}
		}

		if ( editor ) {
			this.sourceEditor = editor;

			this.setData( 'text/html', editor.getSelectedHtml( 1 ) );

			// Without setData( 'text', ... ) on dragstart there is no drop event in Safari.
			// Also 'text' data is empty as drop to the textarea does not work if we do not put there text.
			if ( clipboardIdDataType != 'Text' && !this.getData( 'text/plain' ) ) {
				this.setData( 'text/plain', editor.getSelection().getSelectedText() );
			}
		}

		/**
		 * Data transfer ID used to bind all dataTransfer
		 * objects based on the same event (e.g. in drag and drop events).
		 *
		 * @readonly
		 * @property {String} id
		 */

		/**
		 * A native DOM event object.
		 *
		 * @readonly
		 * @property {Object} $
		 */

		/**
		 * Source editor &mdash; the editor where the drag starts.
		 * Might be undefined if the drag starts outside the editor (e.g. when dropping files to the editor).
		 *
		 * @readonly
		 * @property {CKEDITOR.editor} sourceEditor
		 */

		/**
		 * Private properties and methods.
		 *
		 * @private
		 * @property {Object} _
		 */
	};

	/**
	 * Data transfer operation (drag and drop or copy and paste) started and ended in the same
	 * editor instance.
	 *
	 * @since 4.5
	 * @readonly
	 * @property {Number} [=1]
	 * @member CKEDITOR
	 */
	CKEDITOR.DATA_TRANSFER_INTERNAL = 1;

	/**
	 * Data transfer operation (drag and drop or copy and paste) started in one editor
	 * instance and ended in another.
	 *
	 * @since 4.5
	 * @readonly
	 * @property {Number} [=2]
	 * @member CKEDITOR
	 */
	CKEDITOR.DATA_TRANSFER_CROSS_EDITORS = 2;

	/**
	 * Data transfer operation (drag and drop or copy and paste) started outside of the editor.
	 * The source of the data may be a textarea, HTML, another application, etc.
	 *
	 * @since 4.5
	 * @readonly
	 * @property {Number} [=3]
	 * @member CKEDITOR
	 */
	CKEDITOR.DATA_TRANSFER_EXTERNAL = 3;

	CKEDITOR.plugins.clipboard.dataTransfer.prototype = {
		/**
		 * Facade for the native `getData` method.
		 *
		 * @param {String} type The type of data to retrieve.
		 * @returns {String} type Stored data for the given type or an empty string if the data for that type does not exist.
		 */
		getData: function( type ) {
			function isEmpty( data ) {
				return data === undefined || data === null || data === '';
			}

			type = this._.normalizeType( type );

			var data = this._.data[ type ],
				result;

			if ( isEmpty( data ) ) {
				try {
					data = this.$.getData( type );
				} catch ( e ) {}
			}

			if ( isEmpty( data ) ) {
				data = '';
			}

			// Some browsers add <meta http-equiv="content-type" content="text/html; charset=utf-8"> at the begging of the HTML data
			// or surround it with <html><head>...</head><body>(some content)<!--StartFragment--> and <!--EndFragment-->(some content)</body></html>
			// This code removes meta tags and returns only the contents of the <body> element if found. Note that
			// some significant content may be placed outside Start/EndFragment comments so it's kept.
			//
			// See #13583 for more details.
			if ( type == 'text/html' ) {
				data = data.replace( this._.metaRegExp, '' );

				// Keep only contents of the <body> element
				result = this._.bodyRegExp.exec( data );
				if ( result && result.length ) {
					data = result[ 1 ];

					// Remove also comments.
					data = data.replace( this._.fragmentRegExp, '' );
				}
			}
			// Firefox on Linux put files paths as a text/plain data if there are files
			// in the dataTransfer object. We need to hide it, because files should be
			// handled on paste only if dataValue is empty.
			else if ( type == 'Text' && CKEDITOR.env.gecko && this.getFilesCount() &&
				data.substring( 0, 7 ) == 'file://' ) {
				data = '';
			}

			return data;
		},

		/**
		 * Facade for the native `setData` method.
		 *
		 * @param {String} type The type of data to retrieve.
		 * @param {String} value The data to add.
		 */
		setData: function( type, value ) {
			type = this._.normalizeType( type );

			this._.data[ type ] = value;

			// There is "Unexpected call to method or property access." error if you try
			// to set data of unsupported type on IE.
			if ( !CKEDITOR.plugins.clipboard.isCustomDataTypesSupported && type != 'URL' && type != 'Text' ) {
				return;
			}

			// If we use the text type to bind the ID, then if someone tries to set the text, we must also
			// update ID accordingly. #13468.
			if ( clipboardIdDataType == 'Text' && type == 'Text' ) {
				this.id = value;
			}

			try {
				this.$.setData( type, value );
			} catch ( e ) {}
		},

		/**
		 * Gets the data transfer type.
		 *
		 * @param {CKEDITOR.editor} targetEditor The drop/paste target editor instance.
		 * @returns {Number} Possible values: {@link CKEDITOR#DATA_TRANSFER_INTERNAL},
		 * {@link CKEDITOR#DATA_TRANSFER_CROSS_EDITORS}, {@link CKEDITOR#DATA_TRANSFER_EXTERNAL}.
		 */
		getTransferType: function( targetEditor ) {
			if ( !this.sourceEditor ) {
				return CKEDITOR.DATA_TRANSFER_EXTERNAL;
			} else if ( this.sourceEditor == targetEditor ) {
				return CKEDITOR.DATA_TRANSFER_INTERNAL;
			} else {
				return CKEDITOR.DATA_TRANSFER_CROSS_EDITORS;
			}
		},

		/**
		 * Copies the data from the native data transfer to a private cache.
		 * This function is needed because the data from the native data transfer
		 * is available only synchronously to the event listener. It is not possible
		 * to get the data asynchronously, after a timeout, and the {@link CKEDITOR.editor#paste}
		 * event is fired asynchronously &mdash; hence the need for caching the data.
		 */
		cacheData: function() {
			if ( !this.$ ) {
				return;
			}

			var that = this,
				i, file;

			function getAndSetData( type ) {
				type = that._.normalizeType( type );

				var data = that.getData( type );
				if ( data ) {
					that._.data[ type ] = data;
				}
			}

			// Copy data.
			if ( CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ) {
				if ( this.$.types ) {
					for ( i = 0; i < this.$.types.length; i++ ) {
						getAndSetData( this.$.types[ i ] );
					}
				}
			} else {
				getAndSetData( 'Text' );
				getAndSetData( 'URL' );
			}

			// Copy files references.
			file = this._getImageFromClipboard();
			if ( ( this.$ && this.$.files ) || file ) {
				this._.files = [];

				// Edge have empty files property with no length (#13755).
				if ( this.$.files && this.$.files.length ) {
					for ( i = 0; i < this.$.files.length; i++ ) {
						this._.files.push( this.$.files[ i ] );
					}
				}

				// Don't include $.items if both $.files and $.items contains files, because,
				// according to spec and browsers behavior, they contain the same files.
				if ( this._.files.length === 0 && file ) {
					this._.files.push( file );
				}
			}
		},

		/**
		 * Gets the number of files in the dataTransfer object.
		 *
		 * @returns {Number} The number of files.
		 */
		getFilesCount: function() {
			if ( this._.files.length ) {
				return this._.files.length;
			}

			if ( this.$ && this.$.files && this.$.files.length ) {
				return this.$.files.length;
			}

			return this._getImageFromClipboard() ? 1 : 0;
		},

		/**
		 * Gets the file at the index given.
		 *
		 * @param {Number} i Index.
		 * @returns {File} File instance.
		 */
		getFile: function( i ) {
			if ( this._.files.length ) {
				return this._.files[ i ];
			}

			if ( this.$ && this.$.files && this.$.files.length ) {
				return this.$.files[ i ];
			}

			// File or null if the file was not found.
			return i === 0 ? this._getImageFromClipboard() : undefined;
		},

		/**
		 * Checks if the data transfer contains any data.
		 *
		 * @returns {Boolean} `true` if the object contains no data.
		 */
		isEmpty: function() {
			var typesToCheck = {},
				type;

			// If dataTransfer contains files it is not empty.
			if ( this.getFilesCount() ) {
				return false;
			}

			// Add custom types.
			for ( type in this._.data ) {
				typesToCheck[ type ] = 1;
			}

			// Add native types.
			if ( this.$ ) {
				if ( CKEDITOR.plugins.clipboard.isCustomDataTypesSupported ) {
					if ( this.$.types ) {
						for ( var i = 0; i < this.$.types.length; i++ ) {
							typesToCheck[ this.$.types[ i ] ] = 1;
						}
					}
				} else {
					typesToCheck.Text = 1;
					typesToCheck.URL = 1;
				}
			}

			// Remove ID.
			if ( clipboardIdDataType != 'Text' ) {
				typesToCheck[ clipboardIdDataType ] = 0;
			}

			for ( type in typesToCheck ) {
				if ( typesToCheck[ type ] && this.getData( type ) !== '' ) {
					return false;
				}
			}

			return true;
		},

		/**
		 * When the content of the clipboard is pasted in Chrome, the clipboard data object has an empty `files` property,
		 * but it is possible to get the file as `items[0].getAsFile();` (#12961).
		 *
		 * @private
		 * @returns {File} File instance or `null` if not found.
		 */
		_getImageFromClipboard: function() {
			var file;

			if ( this.$ && this.$.items && this.$.items[ 0 ] ) {
				try {
					file = this.$.items[ 0 ].getAsFile();
					// Duck typing
					if ( file && file.type ) {
						return file;
					}
				} catch ( err ) {
					// noop
				}
			}

			return undefined;
		}
	};
} )();

/**
 * The default content type that is used when pasted data cannot be clearly recognized as HTML or text.
 *
 * For example: `'foo'` may come from a plain text editor or a website. It is not possible to recognize the content
 * type in this case, so the default type will be used. At the same time it is clear that `'<b>example</b> text'` is
 * HTML and its origin is a web page, email or another rich text editor.
 *
 * **Note:** If content type is text, then styles of the paste context are preserved.
 *
 *		CKEDITOR.config.clipboard_defaultContentType = 'text';
 *
 * See also the {@link CKEDITOR.editor#paste} event and read more about the integration with clipboard
 * in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
 *
 * @since 4.0
 * @cfg {'html'/'text'} [clipboard_defaultContentType='html']
 * @member CKEDITOR.config
 */

/**
 * Fired after the user initiated a paste action, but before the data is inserted into the editor.
 * The listeners to this event are able to process the content before its insertion into the document.
 *
 * Read more about the integration with clipboard in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
 *
 * See also:
 *
 * * the {@link CKEDITOR.config#pasteFilter} option,
 * * the {@link CKEDITOR.editor#drop} event,
 * * the {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
 *
 * @since 3.1
 * @event paste
 * @member CKEDITOR.editor
 * @param {CKEDITOR.editor} editor This editor instance.
 * @param data
 * @param {String} data.type The type of data in `data.dataValue`. Usually `'html'` or `'text'`, but for listeners
 * with a priority smaller than `6` it may also be `'auto'` which means that the content type has not been recognised yet
 * (this will be done by the content type sniffer that listens with priority `6`).
 * @param {String} data.dataValue HTML to be pasted.
 * @param {String} data.method Indicates the data transfer method. It could be drag and drop or copy and paste.
 * Possible values: `'drop'`, `'paste'`. Introduced in CKEditor 4.5.
 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer Facade for the native dataTransfer object
 * which provides access to various data types and files, and passes some data between linked events
 * (like drag and drop). Introduced in CKEditor 4.5.
 * @param {Boolean} [data.dontFilter=false] Whether the {@link CKEDITOR.editor#pasteFilter paste filter} should not
 * be applied to data. This option has no effect when `data.type` equals `'text'` which means that for instance
 * {@link CKEDITOR.config#forcePasteAsPlainText} has a higher priority. Introduced in CKEditor 4.5.
 */

/**
 * Fired before the {@link #paste} event. Allows to preset data type.
 *
 * **Note:** This event is deprecated. Add a `0` priority listener for the
 * {@link #paste} event instead.
 *
 * @deprecated
 * @event beforePaste
 * @member CKEDITOR.editor
 */

 /**
 * Fired after the {@link #paste} event if content was modified. Note that if the paste
 * event does not insert any data, the `afterPaste` event will not be fired.
 *
 * @event afterPaste
 * @member CKEDITOR.editor
 */

/**
 * Internal event to open the Paste dialog window.
 *
 * @private
 * @event pasteDialog
 * @member CKEDITOR.editor
 * @param {CKEDITOR.editor} editor This editor instance.
 * @param {Function} [data] Callback that will be passed to {@link CKEDITOR.editor#openDialog}.
 */

/**
 * Facade for the native `drop` event. Fired when the native `drop` event occurs.
 *
 * **Note:** To manipulate dropped data, use the {@link CKEDITOR.editor#paste} event.
 * Use the `drop` event only to control drag and drop operations (e.g. to prevent the ability to drop some content).
 *
 * Read more about integration with drag and drop in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
 *
 * See also:
 *
 * * The {@link CKEDITOR.editor#paste} event,
 * * The {@link CKEDITOR.editor#dragstart} and {@link CKEDITOR.editor#dragend} events,
 * * The {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
 *
 * @since 4.5
 * @event drop
 * @member CKEDITOR.editor
 * @param {CKEDITOR.editor} editor This editor instance.
 * @param data
 * @param {Object} data.$ Native drop event.
 * @param {CKEDITOR.dom.node} data.target Drop target.
 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer DataTransfer facade.
 * @param {CKEDITOR.dom.range} data.dragRange Drag range, lets you manipulate the drag range.
 * Note that dragged HTML is saved as `text/html` data on `dragstart` so if you change the drag range
 * on drop, dropped HTML will not change. You need to change it manually using
 * {@link CKEDITOR.plugins.clipboard.dataTransfer#setData dataTransfer.setData}.
 * @param {CKEDITOR.dom.range} data.dropRange Drop range, lets you manipulate the drop range.
 */

/**
 * Facade for the native `dragstart` event. Fired when the native `dragstart` event occurs.
 *
 * This event can be canceled in order to block the drag start operation. It can also be fired to mimic the start of the drag and drop
 * operation. For instance, the `widget` plugin uses this option to integrate its custom block widget drag and drop with
 * the entire system.
 *
 * Read more about integration with drag and drop in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
 *
 * See also:
 *
 * * The {@link CKEDITOR.editor#paste} event,
 * * The {@link CKEDITOR.editor#drop} and {@link CKEDITOR.editor#dragend} events,
 * * The {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
 *
 * @since 4.5
 * @event dragstart
 * @member CKEDITOR.editor
 * @param {CKEDITOR.editor} editor This editor instance.
 * @param data
 * @param {Object} data.$ Native dragstart event.
 * @param {CKEDITOR.dom.node} data.target Drag target.
 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer DataTransfer facade.
 */

/**
 * Facade for the native `dragend` event. Fired when the native `dragend` event occurs.
 *
 * Read more about integration with drag and drop in the [Clipboard Deep Dive guide](#!/guide/dev_clipboard).
 *
 * See also:
 *
 * * The {@link CKEDITOR.editor#paste} event,
 * * The {@link CKEDITOR.editor#drop} and {@link CKEDITOR.editor#dragend} events,
 * * The {@link CKEDITOR.plugins.clipboard.dataTransfer} class.
 *
 * @since 4.5
 * @event dragend
 * @member CKEDITOR.editor
 * @param {CKEDITOR.editor} editor This editor instance.
 * @param data
 * @param {Object} data.$ Native dragend event.
 * @param {CKEDITOR.dom.node} data.target Drag target.
 * @param {CKEDITOR.plugins.clipboard.dataTransfer} data.dataTransfer DataTransfer facade.
 */

/**
 * Defines a filter which is applied to external data pasted or dropped into the editor. Possible values are:
 *
 * * `'plain-text'` &ndash; Content will be pasted as a plain text.
 * * `'semantic-content'` &ndash; Known tags (except `div`, `span`) with all attributes (except
 * `style` and `class`) will be kept.
 * * `'h1 h2 p div'` &ndash; Custom rules compatible with {@link CKEDITOR.filter}.
 * * `null` &ndash; Content will not be filtered by the paste filter (but it still may be filtered
 * by [Advanced Content Filter](#!/guide/dev_advanced_content_filter)). This value can be used to
 * disable the paste filter in Chrome and Safari, where this option defaults to `'semantic-content'`.
 *
 * Example:
 *
 *		config.pasteFilter = 'plain-text';
 *
 * Custom setting:
 *
 *		config.pasteFilter = 'h1 h2 p ul ol li; img[!src, alt]; a[!href]';
 *
 * Based on this configuration option, a proper {@link CKEDITOR.filter} instance will be defined and assigned to the editor
 * as a {@link CKEDITOR.editor#pasteFilter}. You can tweak the paste filter settings on the fly on this object
 * as well as delete or replace it.
 *
 *		var editor = CKEDITOR.replace( 'editor', {
 *			pasteFilter: 'semantic-content'
 *		} );
 *
 *		editor.on( 'instanceReady', function() {
 *			// The result of this will be that all semantic content will be preserved
 *			// except tables.
 *			editor.pasteFilter.disallow( 'table' );
 *		} );
 *
 * Note that the paste filter is applied only to **external** data. There are three data sources:
 *
 * * copied and pasted in the same editor (internal),
 * * copied from one editor and pasted into another (cross-editor),
 * * coming from all other sources like websites, MS Word, etc. (external).
 *
 * If {@link CKEDITOR.config#allowedContent Advanced Content Filter} is not disabled, then
 * it will also be applied to pasted and dropped data. The paste filter job is to "normalize"
 * external data which often needs to be handled differently than content produced by the editor.
 *
 * This setting defaults to `'semantic-content'` in Chrome, Opera and Safari (all Blink and Webkit based browsers)
 * due to messy HTML which these browsers keep in the clipboard. In other browsers it defaults to `null`.
 *
 * @since 4.5
 * @cfg {String} [pasteFilter='semantic-content' in Chrome and Safari and `null` in other browsers]
 * @member CKEDITOR.config
 */

/**
 * {@link CKEDITOR.filter Content filter} which is used when external data is pasted or dropped into the editor
 * or a forced paste as plain text occurs.
 *
 * This object might be used on the fly to define rules for pasted external content.
 * This object is available and used if the {@link CKEDITOR.plugins.clipboard clipboard} plugin is enabled and
 * {@link CKEDITOR.config#pasteFilter} or {@link CKEDITOR.config#forcePasteAsPlainText} was defined.
 *
 * To enable the filter:
 *
 *		var editor = CKEDITOR.replace( 'editor', {
 *			pasteFilter: 'plain-text'
 *		} );
 *
 * You can also modify the filter on the fly later on:
 *
 *		editor.pasteFilter = new CKEDITOR.filter( 'p h1 h2; a[!href]' );
 *
 * Note that the paste filter is only applied to **external** data. There are three data sources:
 *
 * * copied and pasted in the same editor (internal),
 * * copied from one editor and pasted into another (cross-editor),
 * * coming from all other sources like websites, MS Word, etc. (external).
 *
 * If {@link CKEDITOR.config#allowedContent Advanced Content Filter} is not disabled, then
 * it will also be applied to pasted and dropped data. The paste filter job is to "normalize"
 * external data which often needs to be handled differently than content produced by the editor.
 *
 * @since 4.5
 * @readonly
 * @property {CKEDITOR.filter} [pasteFilter]
 * @member CKEDITOR.editor
 */