1 /*
   2  * Copyright (c) 2011, 2015, Oracle and/or its affiliates. All rights reserved.
   3  */
   4 #include "config.h"
   5 
   6 #include "PasteboardUtilitiesJava.h"
   7 #include "CachedImage.h"
   8 #include "DocumentFragment.h"
   9 #include "Image.h"
  10 #include "Editor.h"
  11 #include "Frame.h"
  12 #include "FrameView.h"
  13 #include "markup.h"
  14 #include "Pasteboard.h"
  15 #include "RenderImage.h"
  16 #include "Range.h"
  17 #include "NotImplemented.h"
  18 #include "DataObjectJava.h"
  19 #include "DragData.h"
  20 #include "JavaEnv.h"
  21 #include "JavaRef.h"
  22 #include "WTFString.h"
  23 #include "StringBuilder.h"
  24 #include "NamedNodeMap.h"
  25 #include "Attr.h"
  26 #include "HTMLNames.h"
  27 #include "HTMLParserIdioms.h"
  28 #include "com_sun_webkit_WCPasteboard.h"
  29 
  30 namespace WebCore {
  31 
  32 ///////////////////
  33 // WCPasteboard JNI
  34 ///////////////////
  35 
  36 namespace {
  37 
  38 #define PB_CLASS jPBClass()
  39 
  40 #define DEFINE_PB_CLASS(_name) \
  41     JNIEnv* env = WebCore_GetJavaEnv(); \
  42     static JGClass cls(env->FindClass(_name)); \
  43     ASSERT(cls);
  44 
  45 #define DEFINE_PB_STATIC_METHOD(_name, _params) \
  46     JNIEnv* env = WebCore_GetJavaEnv(); \
  47     static jmethodID mid = env->GetStaticMethodID(PB_CLASS, _name, _params); \
  48     ASSERT(mid);
  49 
  50 #define CALL_PB_STATIC_VOID_METHOD(...) \
  51     env->CallStaticVoidMethod(PB_CLASS, mid, __VA_ARGS__); \
  52     CheckAndClearException(env);
  53 
  54 #define CALL_PB_STATIC_JSTROBJ_METHOD(_jstrobj) \
  55     JLString _jstrobj(static_cast<jstring>(env->CallStaticObjectMethod(PB_CLASS, mid))); \
  56     CheckAndClearException(env);
  57 
  58 jclass jPBClass()
  59 {
  60     DEFINE_PB_CLASS("com/sun/webkit/WCPasteboard");
  61     return cls;
  62 }
  63 
  64 String jGetPlainText()
  65 {
  66     DEFINE_PB_STATIC_METHOD("getPlainText", "()Ljava/lang/String;");
  67     CALL_PB_STATIC_JSTROBJ_METHOD(jstr);
  68 
  69     return jstr ? String(env, jstr) : String();
  70 }
  71 
  72 void jWritePlainText(const String& plainText)
  73 {
  74     DEFINE_PB_STATIC_METHOD("writePlainText", "(Ljava/lang/String;)V");
  75     CALL_PB_STATIC_VOID_METHOD((jstring)plainText.toJavaString(env));
  76 }
  77 
  78 void jWriteSelection(bool canSmartCopyOrDelete, const String& plainText, const String& markup)
  79 {
  80     DEFINE_PB_STATIC_METHOD("writeSelection", "(ZLjava/lang/String;Ljava/lang/String;)V");
  81     CALL_PB_STATIC_VOID_METHOD(
  82         bool_to_jbool(canSmartCopyOrDelete),
  83         (jstring)plainText.toJavaString(env),
  84         (jstring)markup.toJavaString(env));
  85 }
  86 
  87 void jWriteImage(const Image& image)
  88 {
  89     DEFINE_PB_STATIC_METHOD("writeImage", "(Lcom/sun/webkit/graphics/WCImageFrame;)V");
  90     CALL_PB_STATIC_VOID_METHOD(jobject(*const_cast<Image&>(image).javaImage()));
  91 }
  92 
  93 void jWriteURL(const String& url, const String& markup)
  94 {
  95     DEFINE_PB_STATIC_METHOD("writeUrl", "(Ljava/lang/String;Ljava/lang/String;)V");
  96     CALL_PB_STATIC_VOID_METHOD(
  97         (jstring)url.toJavaString(env),
  98         (jstring)markup.toJavaString(env));
  99 }
 100 
 101 String jGetHtml()
 102 {
 103     DEFINE_PB_STATIC_METHOD("getHtml", "()Ljava/lang/String;");
 104     CALL_PB_STATIC_JSTROBJ_METHOD(jstr);
 105 
 106     return jstr ? String(env, jstr) : String();
 107 }
 108 
 109 ///////////////////
 110 // Helper functions
 111 ///////////////////
 112 
 113 CachedImage* getCachedImage(const Element& element)
 114 {
 115     // Attempt to pull CachedImage from element
 116     RenderObject* renderer = element.renderer();
 117     if (!renderer || !renderer->isImage()) {
 118         return 0;
 119     }
 120     RenderImage* image = static_cast<RenderImage*>(renderer);
 121     if (image->cachedImage() && !image->cachedImage()->errorOccurred()) {
 122         return image->cachedImage();
 123     }
 124     return 0;
 125 }
 126 
 127 void writeImageToDataObject(PassRefPtr<DataObjectJava> dataObject, const Element& element, const URL& url)
 128 {
 129     if (!dataObject) {
 130         return;
 131     }
 132     // Shove image data into a DataObject for use as a file
 133     CachedImage* cachedImage = getCachedImage(element);
 134     if (!cachedImage || !cachedImage->image() || !cachedImage->isLoaded()) {
 135         return;
 136     }
 137     SharedBuffer* imageBuffer = cachedImage->image()->data();
 138     if (!imageBuffer || !imageBuffer->size()) {
 139         return;
 140     }
 141     dataObject->m_fileContent = imageBuffer;
 142 
 143     // Determine the filename for the file contents of the image.  We try to
 144     // use the alt tag if one exists, otherwise we fall back on the suggested
 145     // filename in the http header, and finally we resort to using the filename
 146     // in the URL.
 147     //String title = element->getAttribute(altAttr);
 148     //if (title.isEmpty())
 149     //  title = cachedImage->response().suggestedFilename();
 150 
 151     //TODO: do we need it?
 152     dataObject->m_fileContentFilename = cachedImage->response().suggestedFilename();
 153 }
 154 
 155 String imageToMarkup(const String& url, const Element& element)
 156 {
 157     StringBuilder markup;
 158     markup.append("<img src=\"");
 159     markup.append(url);
 160     markup.append("\"");
 161     // Copy over attributes.  If we are dragging an image, we expect things like
 162     // the id to be copied as well.
 163     NamedNodeMap* attrs = element.attributes();
 164     unsigned length = attrs->length();
 165     for (unsigned i = 0; i < length; ++i) {
 166         RefPtr<Attr> attr(static_cast<Attr*>(attrs->item(i).get()));
 167         if (attr->name() == "src")
 168             continue;
 169         markup.append(" ");
 170         markup.append(attr->name());
 171         markup.append("=\"");
 172         String escapedAttr = attr->value();
 173         escapedAttr.replace("\"", "&quot;");
 174         markup.append(escapedAttr);
 175         markup.append("\"");
 176     }
 177 
 178     markup.append("/>");
 179     return markup.toString();
 180 }
 181 
 182 } // anonymouse namespace
 183 
 184 ///////////////////////////
 185 // WebCore::Pasteboard impl
 186 ///////////////////////////
 187 
 188 Pasteboard::Pasteboard(PassRefPtr<DataObjectJava> dataObject, bool copyPasteMode = false) :
 189     m_dataObject(dataObject),
 190     m_copyPasteMode(copyPasteMode)
 191 {
 192     ASSERT(m_dataObject);
 193 }
 194 
 195 PassOwnPtr<Pasteboard> Pasteboard::create(PassRefPtr<DataObjectJava> dataObject)
 196 {
 197     return adoptPtr(new Pasteboard(dataObject));
 198 }
 199 
 200 PassOwnPtr<Pasteboard> Pasteboard::createPrivate()
 201 {
 202     return adoptPtr(new Pasteboard(DataObjectJava::create()));
 203 }
 204     
 205 PassOwnPtr<Pasteboard> Pasteboard::createForCopyAndPaste()
 206 {
 207     // Use single shared data instance for all copy'n'paste pasteboards.
 208     static RefPtr<DataObjectJava> data = DataObjectJava::create();
 209 
 210     return adoptPtr(new Pasteboard(data, true));
 211 }
 212 
 213 #if ENABLE(DRAG_SUPPORT)
 214 PassOwnPtr<Pasteboard> Pasteboard::createForDragAndDrop()
 215 {
 216     return create(DataObjectJava::create());
 217 }
 218 
 219 PassOwnPtr<Pasteboard> Pasteboard::createForDragAndDrop(const DragData& dragData)
 220 {
 221     return create(dragData.platformData());
 222 }
 223 
 224 void Pasteboard::setDragImage(DragImageRef image, const IntPoint& hotSpot)
 225 {
 226 }
 227 #endif
 228 
 229 void Pasteboard::writeSelection(
 230     Range& selectedRange,
 231     bool canSmartCopyOrDelete,
 232     Frame& frame,
 233     ShouldSerializeSelectedTextForClipboard shouldSerializeSelectedTextForClipboard)
 234 {
 235     String markup = createMarkup(selectedRange, 0, AnnotateForInterchange, false, ResolveNonLocalURLs);
 236     String plainText = shouldSerializeSelectedTextForClipboard == IncludeImageAltTextForClipboard
 237         ? frame.editor().selectedTextForClipboard()
 238         : frame.editor().selectedText();
 239 
 240 #if OS(WINDOWS)
 241     replaceNewlinesWithWindowsStyleNewlines(plainText);
 242 #endif
 243     replaceNBSPWithSpace(plainText);
 244 
 245     m_dataObject->clear();
 246     m_dataObject->setPlainText(plainText);
 247     m_dataObject->setHTML(markup, frame.document()->url());
 248 
 249     if (m_copyPasteMode) {
 250         jWriteSelection(canSmartCopyOrDelete, plainText, markup);
 251     }
 252 }
 253 
 254 void Pasteboard::writePlainText(const String& text, SmartReplaceOption)
 255 {
 256     String plainText(text);
 257 #if OS(WINDOWS)
 258     replaceNewlinesWithWindowsStyleNewlines(plainText);
 259 #endif
 260 
 261     if (m_dataObject) {
 262         m_dataObject->clear();
 263         m_dataObject->setPlainText(plainText);
 264     }
 265     if (m_copyPasteMode) {
 266         jWritePlainText(plainText);
 267     }
 268 }
 269 
 270 void Pasteboard::write(const PasteboardURL& pasteboardURL)
 271 {
 272     ASSERT(!pasteboardURL.url.isEmpty());
 273 
 274     String title(pasteboardURL.title);
 275     if (title.isEmpty()) {
 276         title = pasteboardURL.url.lastPathComponent();
 277         if (title.isEmpty()) {
 278             title = pasteboardURL.url.host();
 279         }
 280     }
 281     String markup(urlToMarkup(pasteboardURL.url, title));
 282 
 283     m_dataObject->clear();
 284     m_dataObject->setURL(pasteboardURL.url, title);
 285     m_dataObject->setPlainText(pasteboardURL.url.string());
 286     m_dataObject->setHTML(markup, pasteboardURL.url);
 287 
 288     if (m_copyPasteMode) {
 289         jWriteURL(pasteboardURL.url.string(), markup);
 290     }
 291 }
 292 
 293 void Pasteboard::writeImage(Element& node, const URL& url, const String& title)
 294 {
 295     m_dataObject->setURL(url, title);
 296 
 297     // Write the bytes of the image to the file format
 298     writeImageToDataObject(m_dataObject,    node, url);
 299 
 300     AtomicString imageURL = node.getAttribute(HTMLNames::srcAttr);
 301     if (!imageURL.isEmpty()) {
 302         String fullURL = node.document().completeURL(stripLeadingAndTrailingHTMLSpaces(imageURL));
 303         if (!fullURL.isEmpty()) {
 304             m_dataObject->setHTML(
 305                 imageToMarkup(fullURL, node),
 306                 node.document().url());
 307         }
 308     }
 309     if (m_copyPasteMode) {
 310         Image* image = getCachedImage(node)->image();
 311         jWriteImage(*image);
 312     }
 313 }
 314 
 315 bool Pasteboard::writeString(const String& type, const String& data)
 316 {
 317     // DnD only mode
 318     if (m_dataObject) {
 319         return m_dataObject->setData(type, data);
 320     }
 321     return false;
 322 }
 323 
 324 String Pasteboard::readString(const String& type)
 325 {
 326     // DnD only mode
 327     if (m_dataObject) {
 328         return m_dataObject->getData(type);
 329     }
 330     return String();
 331 }
 332 
 333 void Pasteboard::clear(const String& type)
 334 {
 335     if (m_dataObject) {
 336         m_dataObject->clearData(type);
 337     }
 338     if (m_copyPasteMode) {
 339         String canonicalMimeType = DataObjectJava::normalizeMIMEType(type);
 340         if (canonicalMimeType == DataObjectJava::mimeURIList())
 341             jWriteURL(DataObjectJava::emptyString(), DataObjectJava::emptyString());
 342         else if (canonicalMimeType == DataObjectJava::mimeHTML())
 343             jWriteSelection(false, DataObjectJava::emptyString(), DataObjectJava::emptyString());
 344         else if (canonicalMimeType == DataObjectJava::mimePlainText())
 345             jWritePlainText(DataObjectJava::emptyString());
 346     }
 347 }
 348     
 349 void Pasteboard::clear()
 350 {
 351     if (m_dataObject) {
 352         m_dataObject->clear();
 353     }
 354     if (m_copyPasteMode) {
 355         jWriteURL(DataObjectJava::emptyString(), DataObjectJava::emptyString());
 356         jWriteSelection(false, DataObjectJava::emptyString(), DataObjectJava::emptyString());
 357         jWritePlainText(DataObjectJava::emptyString());
 358     }
 359 }
 360     
 361 Vector<String> Pasteboard::types()
 362 {
 363     if (m_dataObject) {
 364         return m_dataObject->types();
 365     }
 366     return Vector<String>();
 367 }
 368 
 369 bool Pasteboard::hasData()
 370 {
 371     return m_dataObject && m_dataObject->hasData();
 372 }
 373 
 374 Vector<String> Pasteboard::readFilenames()
 375 {
 376     if (m_dataObject) {
 377         Vector<String> fn;
 378         m_dataObject->asFilenames(fn);
 379         return fn;
 380     }
 381     return Vector<String>();
 382 }
 383 
 384 void Pasteboard::read(PasteboardPlainText& text)
 385 {
 386     if (m_copyPasteMode) {
 387         text.text = jGetPlainText();
 388         if (m_dataObject) {
 389             m_dataObject->setPlainText(text.text);
 390         }
 391         return;
 392     }
 393     if (m_dataObject) {
 394         text.text = m_dataObject->asPlainText();
 395     }
 396 }
 397 
 398 bool Pasteboard::canSmartReplace()
 399 {
 400     // we do not provide smart replace for now
 401     return false;
 402 }
 403 
 404 PassRefPtr<DocumentFragment> Pasteboard::documentFragment(
 405     Frame& frame,
 406     Range& range,
 407     bool allowPlainText,
 408     bool &chosePlainText)
 409 {
 410     chosePlainText = false;
 411 
 412     String htmlString = m_copyPasteMode ?
 413         jGetHtml() :
 414         m_dataObject ? m_dataObject->asHTML() : String();
 415 
 416     if (!htmlString.isNull()) {
 417         if (PassRefPtr<DocumentFragment> fragment = createFragmentFromMarkup(
 418                 *frame.document(),
 419                 htmlString,
 420                 String(),
 421                 DisallowScriptingContent))
 422         {
 423             return fragment;
 424         }
 425     }
 426 
 427     if (!allowPlainText) {
 428         return 0;
 429     }
 430 
 431     String plainTextString = m_copyPasteMode ?
 432         jGetPlainText() :
 433         m_dataObject ? m_dataObject->asPlainText() : String();
 434 
 435     if (!plainTextString.isNull()) {
 436         chosePlainText = true;
 437         if (PassRefPtr<DocumentFragment> fragment = createFragmentFromText(
 438                 range,
 439                 plainTextString))
 440         {
 441             return fragment;
 442         }
 443     }
 444     return 0;
 445 }
 446 
 447 void Pasteboard::writePasteboard(const Pasteboard& sourcePasteboard)
 448 {
 449     if (m_dataObject) {
 450         m_dataObject = sourcePasteboard.dataObject()->copy();
 451     }
 452     if (m_copyPasteMode) {
 453         RefPtr<DataObjectJava> data = sourcePasteboard.dataObject();
 454         if (data->containsURL()) jWriteURL(data->asURL(), data->asHTML());
 455         if (data->containsHTML()) jWriteSelection(false, data->asPlainText(), data->asHTML());
 456         if (data->containsPlainText()) jWritePlainText(data->asPlainText());
 457     }
 458 }
 459 
 460 } // namespace WebCore