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     //Todo : setURL, setFiles, setData, setHtml (needs URL)
 210     data->setPlainText(jGetPlainText());
 211     return adoptPtr(new Pasteboard(data, true));
 212 }
 213 
 214 #if ENABLE(DRAG_SUPPORT)
 215 PassOwnPtr<Pasteboard> Pasteboard::createForDragAndDrop()
 216 {
 217     return create(DataObjectJava::create());
 218 }
 219 
 220 PassOwnPtr<Pasteboard> Pasteboard::createForDragAndDrop(const DragData& dragData)
 221 {
 222     return create(dragData.platformData());
 223 }
 224 
 225 void Pasteboard::setDragImage(DragImageRef image, const IntPoint& hotSpot)
 226 {
 227 }
 228 #endif
 229 
 230 void Pasteboard::writeSelection(
 231     Range& selectedRange,
 232     bool canSmartCopyOrDelete,
 233     Frame& frame,
 234     ShouldSerializeSelectedTextForClipboard shouldSerializeSelectedTextForClipboard)
 235 {
 236     String markup = createMarkup(selectedRange, 0, AnnotateForInterchange, false, ResolveNonLocalURLs);
 237     String plainText = shouldSerializeSelectedTextForClipboard == IncludeImageAltTextForClipboard
 238         ? frame.editor().selectedTextForClipboard()
 239         : frame.editor().selectedText();
 240 
 241 #if OS(WINDOWS)
 242     replaceNewlinesWithWindowsStyleNewlines(plainText);
 243 #endif
 244     replaceNBSPWithSpace(plainText);
 245 
 246     m_dataObject->clear();
 247     m_dataObject->setPlainText(plainText);
 248     m_dataObject->setHTML(markup, frame.document()->url());
 249 
 250     if (m_copyPasteMode) {
 251         jWriteSelection(canSmartCopyOrDelete, plainText, markup);
 252     }
 253 }
 254 
 255 void Pasteboard::writePlainText(const String& text, SmartReplaceOption)
 256 {
 257     String plainText(text);
 258 #if OS(WINDOWS)
 259     replaceNewlinesWithWindowsStyleNewlines(plainText);
 260 #endif
 261 
 262     if (m_dataObject) {
 263         m_dataObject->clear();
 264         m_dataObject->setPlainText(plainText);
 265     }
 266     if (m_copyPasteMode) {
 267         jWritePlainText(plainText);
 268     }
 269 }
 270 
 271 void Pasteboard::write(const PasteboardURL& pasteboardURL)
 272 {
 273     ASSERT(!pasteboardURL.url.isEmpty());
 274 
 275     String title(pasteboardURL.title);
 276     if (title.isEmpty()) {
 277         title = pasteboardURL.url.lastPathComponent();
 278         if (title.isEmpty()) {
 279             title = pasteboardURL.url.host();
 280         }
 281     }
 282     String markup(urlToMarkup(pasteboardURL.url, title));
 283 
 284     m_dataObject->clear();
 285     m_dataObject->setURL(pasteboardURL.url, title);
 286     m_dataObject->setPlainText(pasteboardURL.url.string());
 287     m_dataObject->setHTML(markup, pasteboardURL.url);
 288 
 289     if (m_copyPasteMode) {
 290         jWriteURL(pasteboardURL.url.string(), markup);
 291     }
 292 }
 293 
 294 void Pasteboard::writeImage(Element& node, const URL& url, const String& title)
 295 {
 296     m_dataObject->setURL(url, title);
 297 
 298     // Write the bytes of the image to the file format
 299     writeImageToDataObject(m_dataObject,    node, url);
 300 
 301     AtomicString imageURL = node.getAttribute(HTMLNames::srcAttr);
 302     if (!imageURL.isEmpty()) {
 303         String fullURL = node.document().completeURL(stripLeadingAndTrailingHTMLSpaces(imageURL));
 304         if (!fullURL.isEmpty()) {
 305             m_dataObject->setHTML(
 306                 imageToMarkup(fullURL, node),
 307                 node.document().url());
 308         }
 309     }
 310     if (m_copyPasteMode) {
 311         Image* image = getCachedImage(node)->image();
 312         jWriteImage(*image);
 313     }
 314 }
 315 
 316 bool Pasteboard::writeString(const String& type, const String& data)
 317 {
 318     // DnD only mode
 319     if (m_dataObject) {
 320         return m_dataObject->setData(type, data);
 321     }
 322     return false;
 323 }
 324 
 325 String Pasteboard::readString(const String& type)
 326 {
 327     // DnD only mode
 328     if (m_dataObject) {
 329         return m_dataObject->getData(type);
 330     }
 331     return String();
 332 }
 333 
 334 void Pasteboard::clear(const String& type)
 335 {
 336     if (m_dataObject) {
 337         m_dataObject->clearData(type);
 338     }
 339     if (m_copyPasteMode) {
 340         String canonicalMimeType = DataObjectJava::normalizeMIMEType(type);
 341         if (canonicalMimeType == DataObjectJava::mimeURIList())
 342             jWriteURL(DataObjectJava::emptyString(), DataObjectJava::emptyString());
 343         else if (canonicalMimeType == DataObjectJava::mimeHTML())
 344             jWriteSelection(false, DataObjectJava::emptyString(), DataObjectJava::emptyString());
 345         else if (canonicalMimeType == DataObjectJava::mimePlainText())
 346             jWritePlainText(DataObjectJava::emptyString());
 347     }
 348 }
 349     
 350 void Pasteboard::clear()
 351 {
 352     if (m_dataObject) {
 353         m_dataObject->clear();
 354     }
 355     if (m_copyPasteMode) {
 356         jWriteURL(DataObjectJava::emptyString(), DataObjectJava::emptyString());
 357         jWriteSelection(false, DataObjectJava::emptyString(), DataObjectJava::emptyString());
 358         jWritePlainText(DataObjectJava::emptyString());
 359     }
 360 }
 361     
 362 Vector<String> Pasteboard::types()
 363 {
 364     if (m_dataObject) {
 365         return m_dataObject->types();
 366     }
 367     return Vector<String>();
 368 }
 369 
 370 bool Pasteboard::hasData()
 371 {
 372     return m_dataObject && m_dataObject->hasData();
 373 }
 374 
 375 Vector<String> Pasteboard::readFilenames()
 376 {
 377     if (m_dataObject) {
 378         Vector<String> fn;
 379         m_dataObject->asFilenames(fn);
 380         return fn;
 381     }
 382     return Vector<String>();
 383 }
 384 
 385 void Pasteboard::read(PasteboardPlainText& text)
 386 {
 387     if (m_copyPasteMode) {
 388         text.text = jGetPlainText();
 389         if (m_dataObject) {
 390             m_dataObject->setPlainText(text.text);
 391         }
 392         return;
 393     }
 394     if (m_dataObject) {
 395         text.text = m_dataObject->asPlainText();
 396     }
 397 }
 398 
 399 bool Pasteboard::canSmartReplace()
 400 {
 401     // we do not provide smart replace for now
 402     return false;
 403 }
 404 
 405 PassRefPtr<DocumentFragment> Pasteboard::documentFragment(
 406     Frame& frame,
 407     Range& range,
 408     bool allowPlainText,
 409     bool &chosePlainText)
 410 {
 411     chosePlainText = false;
 412 
 413     String htmlString = m_copyPasteMode ?
 414         jGetHtml() :
 415         m_dataObject ? m_dataObject->asHTML() : String();
 416 
 417     if (!htmlString.isNull()) {
 418         if (PassRefPtr<DocumentFragment> fragment = createFragmentFromMarkup(
 419                 *frame.document(),
 420                 htmlString,
 421                 String(),
 422                 DisallowScriptingContent))
 423         {
 424             return fragment;
 425         }
 426     }
 427 
 428     if (!allowPlainText) {
 429         return 0;
 430     }
 431 
 432     String plainTextString = m_copyPasteMode ?
 433         jGetPlainText() :
 434         m_dataObject ? m_dataObject->asPlainText() : String();
 435 
 436     if (!plainTextString.isNull()) {
 437         chosePlainText = true;
 438         if (PassRefPtr<DocumentFragment> fragment = createFragmentFromText(
 439                 range,
 440                 plainTextString))
 441         {
 442             return fragment;
 443         }
 444     }
 445     return 0;
 446 }
 447 
 448 void Pasteboard::writePasteboard(const Pasteboard& sourcePasteboard)
 449 {
 450     if (m_dataObject) {
 451         m_dataObject = sourcePasteboard.dataObject()->copy();
 452     }
 453     if (m_copyPasteMode) {
 454         RefPtr<DataObjectJava> data = sourcePasteboard.dataObject();
 455         if (data->containsURL()) jWriteURL(data->asURL(), data->asHTML());
 456         if (data->containsHTML()) jWriteSelection(false, data->asPlainText(), data->asHTML());
 457         if (data->containsPlainText()) jWritePlainText(data->asPlainText());
 458     }
 459 }
 460 
 461 } // namespace WebCore