|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 #include <QGuiApplication> |
|
6 #include <QMimeData> |
|
7 #include <QString> |
|
8 #include <QStringList> |
|
9 #include <QByteArray> |
|
10 #include <QImage> |
|
11 #include <QImageWriter> |
|
12 #include <QBuffer> |
|
13 |
|
14 #include "gfxPlatform.h" |
|
15 #include "mozilla/ArrayUtils.h" |
|
16 #include "mozilla/gfx/2D.h" |
|
17 |
|
18 #include "nsClipboard.h" |
|
19 #include "nsISupportsPrimitives.h" |
|
20 #include "nsXPIDLString.h" |
|
21 #include "nsPrimitiveHelpers.h" |
|
22 #include "nsIInputStream.h" |
|
23 #include "nsReadableUtils.h" |
|
24 #include "nsStringStream.h" |
|
25 #include "nsComponentManagerUtils.h" |
|
26 |
|
27 #include "imgIContainer.h" |
|
28 #include "gfxImageSurface.h" |
|
29 |
|
30 using namespace mozilla; |
|
31 using namespace mozilla::gfx; |
|
32 |
|
33 NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard) |
|
34 |
|
35 //------------------------------------------------------------------------- |
|
36 // |
|
37 // nsClipboard constructor |
|
38 // |
|
39 //------------------------------------------------------------------------- |
|
40 nsClipboard::nsClipboard() : nsIClipboard(), |
|
41 mSelectionOwner(nullptr), |
|
42 mGlobalOwner(nullptr), |
|
43 mSelectionTransferable(nullptr), |
|
44 mGlobalTransferable(nullptr) |
|
45 { |
|
46 // No implementation needed |
|
47 } |
|
48 |
|
49 //------------------------------------------------------------------------- |
|
50 // |
|
51 // nsClipboard destructor |
|
52 // |
|
53 //------------------------------------------------------------------------- |
|
54 nsClipboard::~nsClipboard() |
|
55 { |
|
56 } |
|
57 |
|
58 static inline QImage::Format |
|
59 _moz2dformat_to_qformat(SurfaceFormat aFormat) |
|
60 { |
|
61 switch (aFormat) { |
|
62 case SurfaceFormat::B8G8R8A8: |
|
63 return QImage::Format_ARGB32_Premultiplied; |
|
64 case SurfaceFormat::B8G8R8X8: |
|
65 return QImage::Format_ARGB32; |
|
66 case SurfaceFormat::R5G6B5: |
|
67 return QImage::Format_RGB16; |
|
68 default: |
|
69 return QImage::Format_Invalid; |
|
70 } |
|
71 } |
|
72 |
|
73 // nsClipboard::SetNativeClipboardData ie. Copy |
|
74 |
|
75 NS_IMETHODIMP |
|
76 nsClipboard::SetNativeClipboardData( nsITransferable *aTransferable, |
|
77 QClipboard::Mode clipboardMode ) |
|
78 { |
|
79 if (nullptr == aTransferable) |
|
80 { |
|
81 NS_WARNING("nsClipboard::SetNativeClipboardData(): no transferable!"); |
|
82 return NS_ERROR_FAILURE; |
|
83 } |
|
84 |
|
85 // get flavor list that includes all flavors that can be written (including |
|
86 // ones obtained through conversion) |
|
87 nsCOMPtr<nsISupportsArray> flavorList; |
|
88 nsresult rv = aTransferable->FlavorsTransferableCanExport( getter_AddRefs(flavorList) ); |
|
89 |
|
90 if (NS_FAILED(rv)) |
|
91 { |
|
92 NS_WARNING("nsClipboard::SetNativeClipboardData(): no FlavorsTransferable !"); |
|
93 return NS_ERROR_FAILURE; |
|
94 } |
|
95 |
|
96 QClipboard *cb = QGuiApplication::clipboard(); |
|
97 QMimeData *mimeData = new QMimeData; |
|
98 |
|
99 uint32_t flavorCount = 0; |
|
100 flavorList->Count(&flavorCount); |
|
101 bool imageAdded = false; |
|
102 |
|
103 for (uint32_t i = 0; i < flavorCount; ++i) |
|
104 { |
|
105 nsCOMPtr<nsISupports> genericFlavor; |
|
106 flavorList->GetElementAt(i,getter_AddRefs(genericFlavor)); |
|
107 nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface(genericFlavor)); |
|
108 |
|
109 if (currentFlavor) |
|
110 { |
|
111 // flavorStr is the mime type |
|
112 nsXPIDLCString flavorStr; |
|
113 currentFlavor->ToString(getter_Copies(flavorStr)); |
|
114 |
|
115 // Clip is the data which will be sent to the clipboard |
|
116 nsCOMPtr<nsISupports> clip; |
|
117 // len is the length of the data |
|
118 uint32_t len; |
|
119 |
|
120 // Unicode text? |
|
121 if (!strcmp(flavorStr.get(), kUnicodeMime)) |
|
122 { |
|
123 rv = aTransferable->GetTransferData(flavorStr,getter_AddRefs(clip),&len); |
|
124 nsCOMPtr<nsISupportsString> wideString; |
|
125 wideString = do_QueryInterface(clip); |
|
126 if (!wideString || NS_FAILED(rv)) |
|
127 continue; |
|
128 |
|
129 nsAutoString utf16string; |
|
130 wideString->GetData(utf16string); |
|
131 QString str = QString::fromUtf16((const ushort*)utf16string.get()); |
|
132 |
|
133 // Add text to the mimeData |
|
134 mimeData->setText(str); |
|
135 } |
|
136 |
|
137 // html? |
|
138 else if (!strcmp(flavorStr.get(), kHTMLMime)) |
|
139 { |
|
140 rv = aTransferable->GetTransferData(flavorStr,getter_AddRefs(clip),&len); |
|
141 nsCOMPtr<nsISupportsString> wideString; |
|
142 wideString = do_QueryInterface(clip); |
|
143 if (!wideString || NS_FAILED(rv)) |
|
144 continue; |
|
145 |
|
146 nsAutoString utf16string; |
|
147 wideString->GetData(utf16string); |
|
148 QString str = QString::fromUtf16((const ushort*)utf16string.get()); |
|
149 |
|
150 // Add html to the mimeData |
|
151 mimeData->setHtml(str); |
|
152 } |
|
153 |
|
154 // image? |
|
155 else if (!imageAdded // image is added only once to the clipboard |
|
156 && (!strcmp(flavorStr.get(), kNativeImageMime) |
|
157 || !strcmp(flavorStr.get(), kPNGImageMime) |
|
158 || !strcmp(flavorStr.get(), kJPEGImageMime) |
|
159 || !strcmp(flavorStr.get(), kJPGImageMime) |
|
160 || !strcmp(flavorStr.get(), kGIFImageMime)) |
|
161 ) |
|
162 { |
|
163 // Look through our transfer data for the image |
|
164 static const char* const imageMimeTypes[] = { |
|
165 kNativeImageMime, kPNGImageMime, kJPEGImageMime, kJPGImageMime, kGIFImageMime }; |
|
166 nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive; |
|
167 for (uint32_t i = 0; !ptrPrimitive && i < ArrayLength(imageMimeTypes); i++) |
|
168 { |
|
169 aTransferable->GetTransferData(imageMimeTypes[i], getter_AddRefs(clip), &len); |
|
170 ptrPrimitive = do_QueryInterface(clip); |
|
171 } |
|
172 |
|
173 if (!ptrPrimitive) |
|
174 continue; |
|
175 |
|
176 nsCOMPtr<nsISupports> primitiveData; |
|
177 ptrPrimitive->GetData(getter_AddRefs(primitiveData)); |
|
178 nsCOMPtr<imgIContainer> image(do_QueryInterface(primitiveData)); |
|
179 if (!image) // Not getting an image for an image mime type!? |
|
180 continue; |
|
181 |
|
182 RefPtr<SourceSurface> surface = |
|
183 image->GetFrame(imgIContainer::FRAME_CURRENT, |
|
184 imgIContainer::FLAG_SYNC_DECODE); |
|
185 if (!surface) |
|
186 continue; |
|
187 |
|
188 RefPtr<DataSourceSurface> dataSurface = |
|
189 surface->GetDataSurface(); |
|
190 if (!dataSurface) |
|
191 continue; |
|
192 |
|
193 DataSourceSurface::MappedSurface map; |
|
194 if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) |
|
195 continue; |
|
196 |
|
197 QImage qImage(map.mData, |
|
198 dataSurface->GetSize().width, |
|
199 dataSurface->GetSize().height, |
|
200 map.mStride, |
|
201 _moz2dformat_to_qformat(dataSurface->GetFormat())); |
|
202 |
|
203 dataSurface->Unmap(); |
|
204 |
|
205 // Add image to the mimeData |
|
206 mimeData->setImageData(qImage); |
|
207 imageAdded = true; |
|
208 } |
|
209 |
|
210 // Other flavors, adding data to clipboard "as is" |
|
211 else |
|
212 { |
|
213 rv = aTransferable->GetTransferData(flavorStr.get(), getter_AddRefs(clip), &len); |
|
214 // nothing found? |
|
215 if (!clip || NS_FAILED(rv)) |
|
216 continue; |
|
217 |
|
218 void *primitive_data = nullptr; |
|
219 nsPrimitiveHelpers::CreateDataFromPrimitive(flavorStr.get(), clip, |
|
220 &primitive_data, len); |
|
221 |
|
222 if (primitive_data) |
|
223 { |
|
224 QByteArray data ((const char *)primitive_data, len); |
|
225 // Add data to the mimeData |
|
226 mimeData->setData(flavorStr.get(), data); |
|
227 nsMemory::Free(primitive_data); |
|
228 } |
|
229 } |
|
230 } |
|
231 } |
|
232 |
|
233 // If we have some mime data, add it to the clipboard |
|
234 if(!mimeData->formats().isEmpty()) |
|
235 cb->setMimeData(mimeData, clipboardMode); |
|
236 else |
|
237 delete mimeData; |
|
238 |
|
239 return NS_OK; |
|
240 } |
|
241 |
|
242 // nsClipboard::GetNativeClipboardData ie. Paste |
|
243 // |
|
244 NS_IMETHODIMP |
|
245 nsClipboard::GetNativeClipboardData(nsITransferable *aTransferable, |
|
246 QClipboard::Mode clipboardMode) |
|
247 { |
|
248 if (nullptr == aTransferable) |
|
249 { |
|
250 NS_WARNING("GetNativeClipboardData: Transferable is null!"); |
|
251 return NS_ERROR_FAILURE; |
|
252 } |
|
253 |
|
254 // get flavor list that includes all acceptable flavors (including |
|
255 // ones obtained through conversion) |
|
256 nsCOMPtr<nsISupportsArray> flavorList; |
|
257 nsresult errCode = aTransferable->FlavorsTransferableCanImport( |
|
258 getter_AddRefs(flavorList)); |
|
259 |
|
260 if (NS_FAILED(errCode)) |
|
261 { |
|
262 NS_WARNING("nsClipboard::GetNativeClipboardData(): no FlavorsTransferable!"); |
|
263 return NS_ERROR_FAILURE; |
|
264 } |
|
265 |
|
266 QClipboard *cb = QGuiApplication::clipboard(); |
|
267 const QMimeData *mimeData = cb->mimeData(clipboardMode); |
|
268 |
|
269 // Walk through flavors and see which flavor matches the one being pasted |
|
270 uint32_t flavorCount; |
|
271 flavorList->Count(&flavorCount); |
|
272 nsAutoCString foundFlavor; |
|
273 |
|
274 for (uint32_t i = 0; i < flavorCount; ++i) |
|
275 { |
|
276 nsCOMPtr<nsISupports> genericFlavor; |
|
277 flavorList->GetElementAt(i,getter_AddRefs(genericFlavor)); |
|
278 nsCOMPtr<nsISupportsCString> currentFlavor(do_QueryInterface( genericFlavor) ); |
|
279 |
|
280 if (currentFlavor) |
|
281 { |
|
282 nsXPIDLCString flavorStr; |
|
283 currentFlavor->ToString(getter_Copies(flavorStr)); |
|
284 |
|
285 // Ok, so which flavor the data being pasted could be? |
|
286 // Text? |
|
287 if (!strcmp(flavorStr.get(), kUnicodeMime) && mimeData->hasText()) |
|
288 { |
|
289 // Clipboard has text and flavor accepts text, so lets |
|
290 // handle the data as text |
|
291 foundFlavor = nsAutoCString(flavorStr); |
|
292 |
|
293 // Get the text data from clipboard |
|
294 QString text = mimeData->text(); |
|
295 const QChar *unicode = text.unicode(); |
|
296 // Is there a more correct way to get the size in UTF16? |
|
297 uint32_t len = (uint32_t) 2*text.size(); |
|
298 |
|
299 // And then to genericDataWrapper |
|
300 nsCOMPtr<nsISupports> genericDataWrapper; |
|
301 nsPrimitiveHelpers::CreatePrimitiveForData( |
|
302 foundFlavor.get(), |
|
303 (void*)unicode, |
|
304 len, |
|
305 getter_AddRefs(genericDataWrapper)); |
|
306 // Data is good, set it to the transferable |
|
307 aTransferable->SetTransferData(foundFlavor.get(), |
|
308 genericDataWrapper,len); |
|
309 // And thats all |
|
310 break; |
|
311 } |
|
312 |
|
313 // html? |
|
314 if (!strcmp(flavorStr.get(), kHTMLMime) && mimeData->hasHtml()) |
|
315 { |
|
316 // Clipboard has text/html and flavor accepts text/html, so lets |
|
317 // handle the data as text/html |
|
318 foundFlavor = nsAutoCString(flavorStr); |
|
319 |
|
320 // Get the text data from clipboard |
|
321 QString html = mimeData->html(); |
|
322 const QChar *unicode = html.unicode(); |
|
323 // Is there a more correct way to get the size in UTF16? |
|
324 uint32_t len = (uint32_t) 2*html.size(); |
|
325 |
|
326 // And then to genericDataWrapper |
|
327 nsCOMPtr<nsISupports> genericDataWrapper; |
|
328 nsPrimitiveHelpers::CreatePrimitiveForData( |
|
329 foundFlavor.get(), |
|
330 (void*)unicode, |
|
331 len, |
|
332 getter_AddRefs(genericDataWrapper)); |
|
333 // Data is good, set it to the transferable |
|
334 aTransferable->SetTransferData(foundFlavor.get(), |
|
335 genericDataWrapper,len); |
|
336 // And thats all |
|
337 break; |
|
338 } |
|
339 |
|
340 // Image? |
|
341 if (( !strcmp(flavorStr.get(), kJPEGImageMime) |
|
342 || !strcmp(flavorStr.get(), kJPGImageMime) |
|
343 || !strcmp(flavorStr.get(), kPNGImageMime) |
|
344 || !strcmp(flavorStr.get(), kGIFImageMime)) |
|
345 && mimeData->hasImage()) |
|
346 { |
|
347 // Try to retrieve an image from clipboard |
|
348 QImage image = cb->image(); |
|
349 if(image.isNull()) |
|
350 continue; |
|
351 |
|
352 // Lets set the image format |
|
353 QByteArray imageFormat; |
|
354 if (!strcmp(flavorStr.get(), kJPEGImageMime) || !strcmp(flavorStr.get(), kJPGImageMime)) |
|
355 imageFormat = "jpeg"; |
|
356 else if (!strcmp(flavorStr.get(), kPNGImageMime)) |
|
357 imageFormat = "png"; |
|
358 else if (!strcmp(flavorStr.get(), kGIFImageMime)) |
|
359 imageFormat = "gif"; |
|
360 else |
|
361 continue; |
|
362 |
|
363 // Write image from clippboard to a QByteArrayBuffer |
|
364 QByteArray imageData; |
|
365 QBuffer imageBuffer(&imageData); |
|
366 QImageWriter imageWriter(&imageBuffer, imageFormat); |
|
367 if(!imageWriter.write(image)) |
|
368 continue; |
|
369 |
|
370 // Add the data to inputstream |
|
371 nsCOMPtr<nsIInputStream> byteStream; |
|
372 NS_NewByteInputStream(getter_AddRefs(byteStream), imageData.constData(), |
|
373 imageData.size(), NS_ASSIGNMENT_COPY); |
|
374 // Data is good, set it to the transferable |
|
375 aTransferable->SetTransferData(flavorStr, byteStream, sizeof(nsIInputStream*)); |
|
376 |
|
377 imageBuffer.close(); |
|
378 |
|
379 // And thats all |
|
380 break; |
|
381 } |
|
382 |
|
383 // Other mimetype? |
|
384 // Trying to forward the data "as is" |
|
385 if(mimeData->hasFormat(flavorStr.get())) |
|
386 { |
|
387 // get the data from the clipboard |
|
388 QByteArray clipboardData = mimeData->data(flavorStr.get()); |
|
389 // And add it to genericDataWrapper |
|
390 nsCOMPtr<nsISupports> genericDataWrapper; |
|
391 nsPrimitiveHelpers::CreatePrimitiveForData( |
|
392 foundFlavor.get(), |
|
393 (void*) clipboardData.data(), |
|
394 clipboardData.size(), |
|
395 getter_AddRefs(genericDataWrapper)); |
|
396 |
|
397 // Data is good, set it to the transferable |
|
398 aTransferable->SetTransferData(foundFlavor.get(), |
|
399 genericDataWrapper,clipboardData.size()); |
|
400 // And thats all |
|
401 break; |
|
402 } |
|
403 } |
|
404 } |
|
405 |
|
406 return NS_OK; |
|
407 } |
|
408 |
|
409 NS_IMETHODIMP |
|
410 nsClipboard::HasDataMatchingFlavors(const char** aFlavorList, uint32_t aLength, |
|
411 int32_t aWhichClipboard, bool *_retval) |
|
412 { |
|
413 *_retval = false; |
|
414 if (aWhichClipboard != kGlobalClipboard) |
|
415 return NS_OK; |
|
416 |
|
417 // Which kind of data in the clipboard |
|
418 QClipboard *cb = QGuiApplication::clipboard(); |
|
419 const QMimeData *mimeData = cb->mimeData(); |
|
420 const char *flavor=nullptr; |
|
421 QStringList formats = mimeData->formats(); |
|
422 for (uint32_t i = 0; i < aLength; ++i) |
|
423 { |
|
424 flavor = aFlavorList[i]; |
|
425 if (flavor) |
|
426 { |
|
427 QString qflavor(flavor); |
|
428 |
|
429 if (strcmp(flavor,kTextMime) == 0) |
|
430 { |
|
431 NS_WARNING("DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode INSTEAD"); |
|
432 } |
|
433 |
|
434 // QClipboard says it has text/plain, mozilla wants to |
|
435 // know if the data is text/unicode -> interpret text/plain to text/unicode |
|
436 if (formats.contains(qflavor) || |
|
437 strcmp(flavor, kUnicodeMime) == 0) |
|
438 { |
|
439 // A match has been found, return' |
|
440 *_retval = true; |
|
441 break; |
|
442 } |
|
443 } |
|
444 } |
|
445 return NS_OK; |
|
446 } |
|
447 |
|
448 /** |
|
449 * Sets the transferable object |
|
450 */ |
|
451 NS_IMETHODIMP |
|
452 nsClipboard::SetData(nsITransferable *aTransferable, |
|
453 nsIClipboardOwner *aOwner, |
|
454 int32_t aWhichClipboard) |
|
455 { |
|
456 // See if we can short cut |
|
457 if ( |
|
458 (aWhichClipboard == kGlobalClipboard |
|
459 && aTransferable == mGlobalTransferable.get() |
|
460 && aOwner == mGlobalOwner.get() |
|
461 ) |
|
462 || |
|
463 (aWhichClipboard == kSelectionClipboard |
|
464 && aTransferable == mSelectionTransferable.get() |
|
465 && aOwner == mSelectionOwner.get() |
|
466 ) |
|
467 ) |
|
468 { |
|
469 return NS_OK; |
|
470 } |
|
471 |
|
472 nsresult rv; |
|
473 if (!mPrivacyHandler) { |
|
474 rv = NS_NewClipboardPrivacyHandler(getter_AddRefs(mPrivacyHandler)); |
|
475 NS_ENSURE_SUCCESS(rv, rv); |
|
476 } |
|
477 rv = mPrivacyHandler->PrepareDataForClipboard(aTransferable); |
|
478 NS_ENSURE_SUCCESS(rv, rv); |
|
479 |
|
480 EmptyClipboard(aWhichClipboard); |
|
481 |
|
482 QClipboard::Mode mode; |
|
483 |
|
484 if (kGlobalClipboard == aWhichClipboard) |
|
485 { |
|
486 mGlobalOwner = aOwner; |
|
487 mGlobalTransferable = aTransferable; |
|
488 |
|
489 mode = QClipboard::Clipboard; |
|
490 } |
|
491 else |
|
492 { |
|
493 mSelectionOwner = aOwner; |
|
494 mSelectionTransferable = aTransferable; |
|
495 |
|
496 mode = QClipboard::Selection; |
|
497 } |
|
498 return SetNativeClipboardData( aTransferable, mode ); |
|
499 } |
|
500 |
|
501 /** |
|
502 * Gets the transferable object |
|
503 */ |
|
504 NS_IMETHODIMP |
|
505 nsClipboard::GetData(nsITransferable *aTransferable, int32_t aWhichClipboard) |
|
506 { |
|
507 if (nullptr != aTransferable) |
|
508 { |
|
509 QClipboard::Mode mode; |
|
510 if (kGlobalClipboard == aWhichClipboard) |
|
511 { |
|
512 mode = QClipboard::Clipboard; |
|
513 } |
|
514 else |
|
515 { |
|
516 mode = QClipboard::Selection; |
|
517 } |
|
518 return GetNativeClipboardData(aTransferable, mode); |
|
519 } |
|
520 else |
|
521 { |
|
522 NS_WARNING("nsClipboard::GetData(), aTransferable is NULL."); |
|
523 } |
|
524 return NS_ERROR_FAILURE; |
|
525 } |
|
526 |
|
527 NS_IMETHODIMP |
|
528 nsClipboard::EmptyClipboard(int32_t aWhichClipboard) |
|
529 { |
|
530 if (aWhichClipboard == kSelectionClipboard) |
|
531 { |
|
532 if (mSelectionOwner) |
|
533 { |
|
534 mSelectionOwner->LosingOwnership(mSelectionTransferable); |
|
535 mSelectionOwner = nullptr; |
|
536 } |
|
537 mSelectionTransferable = nullptr; |
|
538 } |
|
539 else |
|
540 { |
|
541 if (mGlobalOwner) |
|
542 { |
|
543 mGlobalOwner->LosingOwnership(mGlobalTransferable); |
|
544 mGlobalOwner = nullptr; |
|
545 } |
|
546 mGlobalTransferable = nullptr; |
|
547 } |
|
548 |
|
549 return NS_OK; |
|
550 } |
|
551 |
|
552 NS_IMETHODIMP |
|
553 nsClipboard::SupportsSelectionClipboard(bool *_retval) |
|
554 { |
|
555 NS_ENSURE_ARG_POINTER(_retval); |
|
556 |
|
557 QClipboard *cb = QGuiApplication::clipboard(); |
|
558 if (cb->supportsSelection()) |
|
559 { |
|
560 *_retval = true; // we support the selection clipboard |
|
561 } |
|
562 else |
|
563 { |
|
564 *_retval = false; |
|
565 } |
|
566 |
|
567 return NS_OK; |
|
568 } |
|
569 |
|
570 NS_IMETHODIMP |
|
571 nsClipboard::SupportsFindClipboard(bool* _retval) |
|
572 { |
|
573 NS_ENSURE_ARG_POINTER(_retval); |
|
574 |
|
575 *_retval = false; |
|
576 return NS_OK; |
|
577 } |