1/* 2 * Copyright (C) 2010 Martin Robinson <mrobinson@webkit.org> 3 * Copyright (C) Igalia S.L. 4 * Copyright (C) 2013 Collabora Ltd. 5 * All rights reserved. 6 * 7 * This library is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU Library General Public 9 * License as published by the Free Software Foundation; either 10 * version 2 of the License, or (at your option) any later version. 11 * 12 * This library is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 * Library General Public License for more details. 16 * 17 * You should have received a copy of the GNU Library General Public License 18 * along with this library; see the file COPYING.LIB. If not, write to 19 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 20 * Boston, MA 02110-1301, USA. 21 * 22 */ 23#include "config.h" 24#include "PasteboardHelper.h" 25 26#include "Chrome.h" 27#include "DataObjectGtk.h" 28#include "Frame.h" 29#include "GRefPtrGtk.h" 30#include "GtkVersioning.h" 31#include "Page.h" 32#include "Pasteboard.h" 33#include "TextResourceDecoder.h" 34#include <gtk/gtk.h> 35#include <wtf/gobject/GUniquePtr.h> 36 37namespace WebCore { 38 39static GdkAtom textPlainAtom; 40static GdkAtom markupAtom; 41static GdkAtom netscapeURLAtom; 42static GdkAtom uriListAtom; 43static GdkAtom smartPasteAtom; 44static GdkAtom unknownAtom; 45 46static String gMarkupPrefix; 47 48static void removeMarkupPrefix(String& markup) 49{ 50 // The markup prefix is not harmful, but we remove it from the string anyway, so that 51 // we can have consistent results with other ports during the layout tests. 52 if (markup.startsWith(gMarkupPrefix)) 53 markup.remove(0, gMarkupPrefix.length()); 54} 55 56static void initGdkAtoms() 57{ 58 static gboolean initialized = FALSE; 59 60 if (initialized) 61 return; 62 63 initialized = TRUE; 64 65 textPlainAtom = gdk_atom_intern("text/plain;charset=utf-8", FALSE); 66 markupAtom = gdk_atom_intern("text/html", FALSE); 67 netscapeURLAtom = gdk_atom_intern("_NETSCAPE_URL", FALSE); 68 uriListAtom = gdk_atom_intern("text/uri-list", FALSE); 69 smartPasteAtom = gdk_atom_intern("application/vnd.webkitgtk.smartpaste", FALSE); 70 unknownAtom = gdk_atom_intern("application/vnd.webkitgtk.unknown", FALSE); 71 gMarkupPrefix = "<meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">"; 72} 73 74PasteboardHelper* PasteboardHelper::defaultPasteboardHelper() 75{ 76 DEPRECATED_DEFINE_STATIC_LOCAL(PasteboardHelper, defaultHelper, ()); 77 return &defaultHelper; 78} 79 80PasteboardHelper::PasteboardHelper() 81 : m_targetList(gtk_target_list_new(0, 0)) 82{ 83 initGdkAtoms(); 84 85 gtk_target_list_add_text_targets(m_targetList, PasteboardHelper::TargetTypeText); 86 gtk_target_list_add(m_targetList, markupAtom, 0, PasteboardHelper::TargetTypeMarkup); 87 gtk_target_list_add_uri_targets(m_targetList, PasteboardHelper::TargetTypeURIList); 88 gtk_target_list_add(m_targetList, netscapeURLAtom, 0, PasteboardHelper::TargetTypeNetscapeURL); 89 gtk_target_list_add_image_targets(m_targetList, PasteboardHelper::TargetTypeImage, TRUE); 90 gtk_target_list_add(m_targetList, unknownAtom, 0, PasteboardHelper::TargetTypeUnknown); 91} 92 93PasteboardHelper::~PasteboardHelper() 94{ 95 gtk_target_list_unref(m_targetList); 96} 97 98static inline GdkDisplay* displayFromFrame(Frame* frame) 99{ 100 ASSERT(frame); 101 Page* page = frame->page(); 102 ASSERT(page); 103 PlatformPageClient client = page->chrome().platformPageClient(); 104 return client ? gtk_widget_get_display(client) : gdk_display_get_default(); 105} 106 107GtkClipboard* PasteboardHelper::getPrimarySelectionClipboard(Frame* frame) const 108{ 109 return gtk_clipboard_get_for_display(displayFromFrame(frame), GDK_SELECTION_PRIMARY); 110} 111 112GtkTargetList* PasteboardHelper::targetList() const 113{ 114 return m_targetList; 115} 116 117static String selectionDataToUTF8String(GtkSelectionData* data) 118{ 119 // g_strndup guards against selection data that is not null-terminated. 120 GUniquePtr<gchar> markupString(g_strndup(reinterpret_cast<const char*>(gtk_selection_data_get_data(data)), gtk_selection_data_get_length(data))); 121 return String::fromUTF8(markupString.get()); 122} 123 124void PasteboardHelper::getClipboardContents(GtkClipboard* clipboard) 125{ 126 DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); 127 ASSERT(dataObject); 128 129 if (gtk_clipboard_wait_is_text_available(clipboard)) { 130 GUniquePtr<gchar> textData(gtk_clipboard_wait_for_text(clipboard)); 131 if (textData) 132 dataObject->setText(String::fromUTF8(textData.get())); 133 } 134 135 if (gtk_clipboard_wait_is_target_available(clipboard, markupAtom)) { 136 if (GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard, markupAtom)) { 137 String markup(selectionDataToUTF8String(data)); 138 removeMarkupPrefix(markup); 139 dataObject->setMarkup(markup); 140 gtk_selection_data_free(data); 141 } 142 } 143 144 if (gtk_clipboard_wait_is_target_available(clipboard, uriListAtom)) { 145 if (GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard, uriListAtom)) { 146 dataObject->setURIList(selectionDataToUTF8String(data)); 147 gtk_selection_data_free(data); 148 } 149 } 150} 151 152void PasteboardHelper::fillSelectionData(GtkSelectionData* selectionData, guint info, DataObjectGtk* dataObject) 153{ 154 if (info == TargetTypeText) 155 gtk_selection_data_set_text(selectionData, dataObject->text().utf8().data(), -1); 156 157 else if (info == TargetTypeMarkup) { 158 // Some Linux applications refuse to accept pasted markup unless it is 159 // prefixed by a content-type meta tag. 160 CString markup = String(gMarkupPrefix + dataObject->markup()).utf8(); 161 gtk_selection_data_set(selectionData, markupAtom, 8, 162 reinterpret_cast<const guchar*>(markup.data()), markup.length()); 163 164 } else if (info == TargetTypeURIList) { 165 CString uriList = dataObject->uriList().utf8(); 166 gtk_selection_data_set(selectionData, uriListAtom, 8, 167 reinterpret_cast<const guchar*>(uriList.data()), uriList.length()); 168 169 } else if (info == TargetTypeNetscapeURL && dataObject->hasURL()) { 170 String url(dataObject->url()); 171 String result(url); 172 result.append("\n"); 173 174 if (dataObject->hasText()) 175 result.append(dataObject->text()); 176 else 177 result.append(url); 178 179 GUniquePtr<gchar> resultData(g_strdup(result.utf8().data())); 180 gtk_selection_data_set(selectionData, netscapeURLAtom, 8, 181 reinterpret_cast<const guchar*>(resultData.get()), strlen(resultData.get())); 182 183 } else if (info == TargetTypeImage) 184 gtk_selection_data_set_pixbuf(selectionData, dataObject->image()); 185 186 else if (info == TargetTypeSmartPaste) 187 gtk_selection_data_set_text(selectionData, "", -1); 188 189 else if (info == TargetTypeUnknown) { 190 GVariantBuilder builder; 191 g_variant_builder_init(&builder, G_VARIANT_TYPE_ARRAY); 192 193 auto types = dataObject->unknownTypes(); 194 auto end = types.end(); 195 for (auto it = types.begin(); it != end; ++it) { 196 GUniquePtr<gchar> dictItem(g_strdup_printf("{'%s', '%s'}", it->key.utf8().data(), it->value.utf8().data())); 197 g_variant_builder_add_parsed(&builder, dictItem.get()); 198 } 199 200 GRefPtr<GVariant> variant = g_variant_builder_end(&builder); 201 GUniquePtr<gchar> serializedVariant(g_variant_print(variant.get(), TRUE)); 202 gtk_selection_data_set(selectionData, unknownAtom, 1, reinterpret_cast<const guchar*>(serializedVariant.get()), strlen(serializedVariant.get())); 203 } 204} 205 206GtkTargetList* PasteboardHelper::targetListForDataObject(DataObjectGtk* dataObject, SmartPasteInclusion shouldInludeSmartPaste) 207{ 208 GtkTargetList* list = gtk_target_list_new(0, 0); 209 210 if (dataObject->hasText()) 211 gtk_target_list_add_text_targets(list, TargetTypeText); 212 213 if (dataObject->hasMarkup()) 214 gtk_target_list_add(list, markupAtom, 0, TargetTypeMarkup); 215 216 if (dataObject->hasURIList()) { 217 gtk_target_list_add_uri_targets(list, TargetTypeURIList); 218 gtk_target_list_add(list, netscapeURLAtom, 0, TargetTypeNetscapeURL); 219 } 220 221 if (dataObject->hasImage()) 222 gtk_target_list_add_image_targets(list, TargetTypeImage, TRUE); 223 224 if (dataObject->hasUnknownTypeData()) 225 gtk_target_list_add(list, unknownAtom, 0, TargetTypeUnknown); 226 227 if (shouldInludeSmartPaste == IncludeSmartPaste) 228 gtk_target_list_add(list, smartPasteAtom, 0, TargetTypeSmartPaste); 229 230 return list; 231} 232 233void PasteboardHelper::fillDataObjectFromDropData(GtkSelectionData* data, guint /* info */, DataObjectGtk* dataObject) 234{ 235 if (!gtk_selection_data_get_data(data)) 236 return; 237 238 GdkAtom target = gtk_selection_data_get_target(data); 239 if (target == textPlainAtom) 240 dataObject->setText(selectionDataToUTF8String(data)); 241 else if (target == markupAtom) { 242 String markup(selectionDataToUTF8String(data)); 243 removeMarkupPrefix(markup); 244 dataObject->setMarkup(markup); 245 } else if (target == uriListAtom) { 246 dataObject->setURIList(selectionDataToUTF8String(data)); 247 } else if (target == netscapeURLAtom) { 248 String urlWithLabel(selectionDataToUTF8String(data)); 249 Vector<String> pieces; 250 urlWithLabel.split("\n", pieces); 251 252 // Give preference to text/uri-list here, as it can hold more 253 // than one URI but still take the label if there is one. 254 if (!dataObject->hasURIList()) 255 dataObject->setURIList(pieces[0]); 256 if (pieces.size() > 1) 257 dataObject->setText(pieces[1]); 258 } else if (target == unknownAtom) { 259 GRefPtr<GVariant> variant = g_variant_new_parsed(reinterpret_cast<const char*>(gtk_selection_data_get_data(data))); 260 261 GUniqueOutPtr<gchar> key; 262 GUniqueOutPtr<gchar> value; 263 GVariantIter iter; 264 265 g_variant_iter_init(&iter, variant.get()); 266 while (g_variant_iter_next(&iter, "{ss}", &key.outPtr(), &value.outPtr())) 267 dataObject->setUnknownTypeData(key.get(), value.get()); 268 } 269} 270 271Vector<GdkAtom> PasteboardHelper::dropAtomsForContext(GtkWidget* widget, GdkDragContext* context) 272{ 273 // Always search for these common atoms. 274 Vector<GdkAtom> dropAtoms; 275 dropAtoms.append(textPlainAtom); 276 dropAtoms.append(markupAtom); 277 dropAtoms.append(uriListAtom); 278 dropAtoms.append(netscapeURLAtom); 279 dropAtoms.append(unknownAtom); 280 281 // For images, try to find the most applicable image type. 282 GRefPtr<GtkTargetList> list = adoptGRef(gtk_target_list_new(0, 0)); 283 gtk_target_list_add_image_targets(list.get(), TargetTypeImage, TRUE); 284 GdkAtom atom = gtk_drag_dest_find_target(widget, context, list.get()); 285 if (atom != GDK_NONE) 286 dropAtoms.append(atom); 287 288 return dropAtoms; 289} 290 291static DataObjectGtk* settingClipboardDataObject = 0; 292 293static void getClipboardContentsCallback(GtkClipboard* clipboard, GtkSelectionData *selectionData, guint info, gpointer) 294{ 295 DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); 296 ASSERT(dataObject); 297 PasteboardHelper::defaultPasteboardHelper()->fillSelectionData(selectionData, info, dataObject); 298} 299 300static void clearClipboardContentsCallback(GtkClipboard* clipboard, gpointer data) 301{ 302 DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); 303 ASSERT(dataObject); 304 305 // Only clear the DataObject for this clipboard if we are not currently setting it. 306 if (dataObject != settingClipboardDataObject) 307 dataObject->clearAll(); 308 309 if (!data) 310 return; 311 312 GClosure* callback = static_cast<GClosure*>(data); 313 GValue firstArgument = {0, {{0}}}; 314 g_value_init(&firstArgument, G_TYPE_POINTER); 315 g_value_set_pointer(&firstArgument, clipboard); 316 g_closure_invoke(callback, 0, 1, &firstArgument, 0); 317 g_closure_unref(callback); 318} 319 320void PasteboardHelper::writeClipboardContents(GtkClipboard* clipboard, SmartPasteInclusion includeSmartPaste, GClosure* callback) 321{ 322 DataObjectGtk* dataObject = DataObjectGtk::forClipboard(clipboard); 323 GtkTargetList* list = targetListForDataObject(dataObject, includeSmartPaste); 324 325 int numberOfTargets; 326 GtkTargetEntry* table = gtk_target_table_new_from_list(list, &numberOfTargets); 327 328 if (numberOfTargets > 0 && table) { 329 settingClipboardDataObject = dataObject; 330 331 gtk_clipboard_set_with_data(clipboard, table, numberOfTargets, 332 getClipboardContentsCallback, clearClipboardContentsCallback, callback); 333 gtk_clipboard_set_can_store(clipboard, 0, 0); 334 335 settingClipboardDataObject = 0; 336 337 } else 338 gtk_clipboard_clear(clipboard); 339 340 if (table) 341 gtk_target_table_free(table, numberOfTargets); 342 gtk_target_list_unref(list); 343} 344 345bool PasteboardHelper::clipboardContentSupportsSmartReplace(GtkClipboard* clipboard) 346{ 347 return gtk_clipboard_wait_is_target_available(clipboard, smartPasteAtom); 348} 349 350} 351 352