1 /* 2 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. 3 * Copyright (C) 2008, 2009 Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include "config.h" 28 #include "core/platform/chromium/ClipboardChromium.h" 29 30 #include "HTMLNames.h" 31 #include "bindings/v8/ExceptionState.h" 32 #include "core/dom/DataTransferItemList.h" 33 #include "core/dom/Document.h" 34 #include "core/dom/Element.h" 35 #include "core/dom/ExceptionCode.h" 36 #include "core/dom/StringCallback.h" 37 #include "core/editing/markup.h" 38 #include "core/fileapi/File.h" 39 #include "core/fileapi/FileList.h" 40 #include "core/loader/cache/ImageResource.h" 41 #include "core/page/Frame.h" 42 #include "core/platform/DragData.h" 43 #include "core/platform/MIMETypeRegistry.h" 44 #include "core/platform/chromium/ChromiumDataObject.h" 45 #include "core/platform/chromium/ChromiumDataObjectItem.h" 46 #include "core/platform/chromium/ClipboardMimeTypes.h" 47 #include "core/platform/chromium/ClipboardUtilitiesChromium.h" 48 #include "core/platform/graphics/Image.h" 49 #include "core/rendering/RenderImage.h" 50 51 #include "wtf/text/WTFString.h" 52 53 namespace WebCore { 54 55 namespace { 56 57 // A wrapper class that invalidates a DataTransferItemList when the associated Clipboard object goes out of scope. 58 class DataTransferItemListPolicyWrapper : public DataTransferItemList { 59 public: 60 static PassRefPtr<DataTransferItemListPolicyWrapper> create(PassRefPtr<ClipboardChromium>, PassRefPtr<ChromiumDataObject>); 61 virtual ~DataTransferItemListPolicyWrapper(); 62 63 virtual size_t length() const; 64 virtual PassRefPtr<DataTransferItem> item(unsigned long index) OVERRIDE; 65 virtual void deleteItem(unsigned long index, ExceptionState&) OVERRIDE; 66 virtual void clear() OVERRIDE; 67 virtual void add(const String& data, const String& type, ExceptionState&) OVERRIDE; 68 virtual void add(PassRefPtr<File>) OVERRIDE; 69 70 private: 71 DataTransferItemListPolicyWrapper(PassRefPtr<ClipboardChromium>, PassRefPtr<ChromiumDataObject>); 72 73 RefPtr<ClipboardChromium> m_clipboard; 74 RefPtr<ChromiumDataObject> m_dataObject; 75 }; 76 77 78 PassRefPtr<DataTransferItemListPolicyWrapper> DataTransferItemListPolicyWrapper::create( 79 PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<ChromiumDataObject> list) 80 { 81 return adoptRef(new DataTransferItemListPolicyWrapper(clipboard, list)); 82 } 83 84 DataTransferItemListPolicyWrapper::~DataTransferItemListPolicyWrapper() 85 { 86 } 87 88 size_t DataTransferItemListPolicyWrapper::length() const 89 { 90 if (!m_clipboard->canReadTypes()) 91 return 0; 92 return m_dataObject->length(); 93 } 94 95 PassRefPtr<DataTransferItem> DataTransferItemListPolicyWrapper::item(unsigned long index) 96 { 97 if (!m_clipboard->canReadTypes()) 98 return 0; 99 RefPtr<ChromiumDataObjectItem> item = m_dataObject->item(index); 100 if (!item) 101 return 0; 102 103 return DataTransferItemPolicyWrapper::create(m_clipboard, item); 104 } 105 106 void DataTransferItemListPolicyWrapper::deleteItem(unsigned long index, ExceptionState& es) 107 { 108 if (!m_clipboard->canWriteData()) { 109 es.throwDOMException(InvalidStateError); 110 return; 111 } 112 m_dataObject->deleteItem(index); 113 } 114 115 void DataTransferItemListPolicyWrapper::clear() 116 { 117 if (!m_clipboard->canWriteData()) 118 return; 119 m_dataObject->clearAll(); 120 } 121 122 void DataTransferItemListPolicyWrapper::add(const String& data, const String& type, ExceptionState& es) 123 { 124 if (!m_clipboard->canWriteData()) 125 return; 126 m_dataObject->add(data, type, es); 127 } 128 129 void DataTransferItemListPolicyWrapper::add(PassRefPtr<File> file) 130 { 131 if (!m_clipboard->canWriteData()) 132 return; 133 m_dataObject->add(file, m_clipboard->frame()->document()->scriptExecutionContext()); 134 } 135 136 DataTransferItemListPolicyWrapper::DataTransferItemListPolicyWrapper( 137 PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<ChromiumDataObject> dataObject) 138 : m_clipboard(clipboard) 139 , m_dataObject(dataObject) 140 { 141 } 142 143 } // namespace 144 145 PassRefPtr<DataTransferItemPolicyWrapper> DataTransferItemPolicyWrapper::create( 146 PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<ChromiumDataObjectItem> item) 147 { 148 return adoptRef(new DataTransferItemPolicyWrapper(clipboard, item)); 149 } 150 151 DataTransferItemPolicyWrapper::~DataTransferItemPolicyWrapper() 152 { 153 } 154 155 String DataTransferItemPolicyWrapper::kind() const 156 { 157 if (!m_clipboard->canReadTypes()) 158 return String(); 159 return m_item->kind(); 160 } 161 162 String DataTransferItemPolicyWrapper::type() const 163 { 164 if (!m_clipboard->canReadTypes()) 165 return String(); 166 return m_item->type(); 167 } 168 169 void DataTransferItemPolicyWrapper::getAsString(PassRefPtr<StringCallback> callback) const 170 { 171 if (!m_clipboard->canReadData()) 172 return; 173 174 m_item->getAsString(callback, m_clipboard->frame()->document()->scriptExecutionContext()); 175 } 176 177 PassRefPtr<Blob> DataTransferItemPolicyWrapper::getAsFile() const 178 { 179 if (!m_clipboard->canReadData()) 180 return 0; 181 182 return m_item->getAsFile(); 183 } 184 185 DataTransferItemPolicyWrapper::DataTransferItemPolicyWrapper( 186 PassRefPtr<ClipboardChromium> clipboard, PassRefPtr<ChromiumDataObjectItem> item) 187 : m_clipboard(clipboard) 188 , m_item(item) 189 { 190 } 191 192 using namespace HTMLNames; 193 194 // We provide the IE clipboard types (URL and Text), and the clipboard types specified in the WHATWG Web Applications 1.0 draft 195 // see http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3 196 197 static String normalizeType(const String& type, bool* convertToURL = 0) 198 { 199 String cleanType = type.stripWhiteSpace().lower(); 200 if (cleanType == mimeTypeText || cleanType.startsWith(mimeTypeTextPlainEtc)) 201 return mimeTypeTextPlain; 202 if (cleanType == mimeTypeURL) { 203 if (convertToURL) 204 *convertToURL = true; 205 return mimeTypeTextURIList; 206 } 207 return cleanType; 208 } 209 210 PassRefPtr<Clipboard> Clipboard::create(ClipboardAccessPolicy policy, DragData* dragData, Frame* frame) 211 { 212 return ClipboardChromium::create(DragAndDrop, dragData->platformData(), policy, frame); 213 } 214 215 ClipboardChromium::ClipboardChromium(ClipboardType clipboardType, 216 PassRefPtr<ChromiumDataObject> dataObject, 217 ClipboardAccessPolicy policy, 218 Frame* frame) 219 : Clipboard(policy, clipboardType) 220 , m_dataObject(dataObject) 221 , m_frame(frame) 222 { 223 } 224 225 ClipboardChromium::~ClipboardChromium() 226 { 227 if (m_dragImage) 228 m_dragImage->removeClient(this); 229 } 230 231 PassRefPtr<ClipboardChromium> ClipboardChromium::create(ClipboardType clipboardType, 232 PassRefPtr<ChromiumDataObject> dataObject, ClipboardAccessPolicy policy, Frame* frame) 233 { 234 return adoptRef(new ClipboardChromium(clipboardType, dataObject, policy, frame)); 235 } 236 237 void ClipboardChromium::clearData(const String& type) 238 { 239 if (!canWriteData()) 240 return; 241 242 m_dataObject->clearData(normalizeType(type)); 243 244 ASSERT_NOT_REACHED(); 245 } 246 247 void ClipboardChromium::clearAllData() 248 { 249 if (!canWriteData()) 250 return; 251 252 m_dataObject->clearAll(); 253 } 254 255 String ClipboardChromium::getData(const String& type) const 256 { 257 if (!canReadData()) 258 return String(); 259 260 bool convertToURL = false; 261 String data = m_dataObject->getData(normalizeType(type, &convertToURL)); 262 if (!convertToURL) 263 return data; 264 return convertURIListToURL(data); 265 } 266 267 bool ClipboardChromium::setData(const String& type, const String& data) 268 { 269 if (!canWriteData()) 270 return false; 271 272 return m_dataObject->setData(normalizeType(type), data); 273 } 274 275 // extensions beyond IE's API 276 ListHashSet<String> ClipboardChromium::types() const 277 { 278 if (!canReadTypes()) 279 return ListHashSet<String>(); 280 281 return m_dataObject->types(); 282 } 283 284 PassRefPtr<FileList> ClipboardChromium::files() const 285 { 286 RefPtr<FileList> files = FileList::create(); 287 if (!canReadData()) 288 return files.release(); 289 290 for (size_t i = 0; i < m_dataObject->length(); ++i) { 291 if (m_dataObject->item(i)->kind() == DataTransferItem::kindFile) { 292 RefPtr<Blob> blob = m_dataObject->item(i)->getAsFile(); 293 if (blob && blob->isFile()) 294 files->append(toFile(blob.get())); 295 } 296 } 297 298 return files.release(); 299 } 300 301 void ClipboardChromium::setDragImage(ImageResource* image, Node* node, const IntPoint& loc) 302 { 303 if (!canSetDragImage()) 304 return; 305 306 if (m_dragImage) 307 m_dragImage->removeClient(this); 308 m_dragImage = image; 309 if (m_dragImage) 310 m_dragImage->addClient(this); 311 312 m_dragLoc = loc; 313 m_dragImageElement = node; 314 } 315 316 void ClipboardChromium::setDragImage(ImageResource* img, const IntPoint& loc) 317 { 318 setDragImage(img, 0, loc); 319 } 320 321 void ClipboardChromium::setDragImageElement(Node* node, const IntPoint& loc) 322 { 323 setDragImage(0, node, loc); 324 } 325 326 PassOwnPtr<DragImage> ClipboardChromium::createDragImage(IntPoint& loc) const 327 { 328 if (m_dragImageElement) { 329 if (m_frame) { 330 loc = m_dragLoc; 331 return m_frame->nodeImage(m_dragImageElement.get()); 332 } 333 } else if (m_dragImage) { 334 loc = m_dragLoc; 335 return DragImage::create(m_dragImage->image()); 336 } 337 return nullptr; 338 } 339 340 static ImageResource* getImageResource(Element* element) 341 { 342 // Attempt to pull ImageResource from element 343 ASSERT(element); 344 RenderObject* renderer = element->renderer(); 345 if (!renderer || !renderer->isImage()) 346 return 0; 347 348 RenderImage* image = toRenderImage(renderer); 349 if (image->cachedImage() && !image->cachedImage()->errorOccurred()) 350 return image->cachedImage(); 351 352 return 0; 353 } 354 355 static void writeImageToDataObject(ChromiumDataObject* dataObject, Element* element, 356 const KURL& url) 357 { 358 // Shove image data into a DataObject for use as a file 359 ImageResource* cachedImage = getImageResource(element); 360 if (!cachedImage || !cachedImage->imageForRenderer(element->renderer()) || !cachedImage->isLoaded()) 361 return; 362 363 SharedBuffer* imageBuffer = cachedImage->imageForRenderer(element->renderer())->data(); 364 if (!imageBuffer || !imageBuffer->size()) 365 return; 366 367 String imageExtension = cachedImage->image()->filenameExtension(); 368 ASSERT(!imageExtension.isEmpty()); 369 370 // Determine the filename for the file contents of the image. 371 String filename = cachedImage->response().suggestedFilename(); 372 if (filename.isEmpty()) 373 filename = url.lastPathComponent(); 374 375 String fileExtension; 376 if (filename.isEmpty()) { 377 filename = element->getAttribute(altAttr); 378 } else { 379 // Strip any existing extension. Assume that alt text is usually not a filename. 380 int extensionIndex = filename.reverseFind('.'); 381 if (extensionIndex != -1) { 382 fileExtension = filename.substring(extensionIndex + 1); 383 filename.truncate(extensionIndex); 384 } 385 } 386 387 if (!fileExtension.isEmpty() && fileExtension != imageExtension) { 388 String imageMimeType = MIMETypeRegistry::getMIMETypeForExtension(imageExtension); 389 ASSERT(imageMimeType.startsWith("image/")); 390 // Use the file extension only if it has imageMimeType: it's untrustworthy otherwise. 391 if (imageMimeType == MIMETypeRegistry::getMIMETypeForExtension(fileExtension)) 392 imageExtension = fileExtension; 393 } 394 395 imageExtension = "." + imageExtension; 396 ClipboardChromium::validateFilename(filename, imageExtension); 397 398 dataObject->addSharedBuffer(filename + imageExtension, imageBuffer); 399 } 400 401 void ClipboardChromium::declareAndWriteDragImage(Element* element, const KURL& url, const String& title, Frame* frame) 402 { 403 if (!m_dataObject) 404 return; 405 406 m_dataObject->setURLAndTitle(url, title); 407 408 // Write the bytes in the image to the file format. 409 writeImageToDataObject(m_dataObject.get(), element, url); 410 411 // Put img tag on the clipboard referencing the image 412 m_dataObject->setData(mimeTypeTextHTML, createMarkup(element, IncludeNode, 0, ResolveAllURLs)); 413 } 414 415 void ClipboardChromium::writeURL(const KURL& url, const String& title, Frame*) 416 { 417 if (!m_dataObject) 418 return; 419 ASSERT(!url.isEmpty()); 420 421 m_dataObject->setURLAndTitle(url, title); 422 423 // The URL can also be used as plain text. 424 m_dataObject->setData(mimeTypeTextPlain, url.string()); 425 426 // The URL can also be used as an HTML fragment. 427 m_dataObject->setHTMLAndBaseURL(urlToMarkup(url, title), url); 428 } 429 430 void ClipboardChromium::writeRange(Range* selectedRange, Frame* frame) 431 { 432 ASSERT(selectedRange); 433 if (!m_dataObject) 434 return; 435 436 m_dataObject->setHTMLAndBaseURL(createMarkup(selectedRange, 0, AnnotateForInterchange, false, ResolveNonLocalURLs), frame->document()->url()); 437 438 String str = frame->selectedTextForClipboard(); 439 #if OS(WINDOWS) 440 replaceNewlinesWithWindowsStyleNewlines(str); 441 #endif 442 replaceNBSPWithSpace(str); 443 m_dataObject->setData(mimeTypeTextPlain, str); 444 } 445 446 void ClipboardChromium::writePlainText(const String& text) 447 { 448 if (!m_dataObject) 449 return; 450 451 String str = text; 452 #if OS(WINDOWS) 453 replaceNewlinesWithWindowsStyleNewlines(str); 454 #endif 455 replaceNBSPWithSpace(str); 456 457 m_dataObject->setData(mimeTypeTextPlain, str); 458 } 459 460 bool ClipboardChromium::hasData() 461 { 462 ASSERT(isForDragAndDrop()); 463 464 return m_dataObject->length() > 0; 465 } 466 467 PassRefPtr<DataTransferItemList> ClipboardChromium::items() 468 { 469 // FIXME: According to the spec, we are supposed to return the same collection of items each 470 // time. We now return a wrapper that always wraps the *same* set of items, so JS shouldn't be 471 // able to tell, but we probably still want to fix this. 472 return DataTransferItemListPolicyWrapper::create(this, m_dataObject); 473 } 474 475 } // namespace WebCore 476