diff options
Diffstat (limited to 'ui/base')
-rw-r--r-- | ui/base/clipboard/clipboard.cc | 183 | ||||
-rw-r--r-- | ui/base/clipboard/clipboard.h | 276 | ||||
-rw-r--r-- | ui/base/clipboard/clipboard_linux.cc | 421 | ||||
-rw-r--r-- | ui/base/clipboard/clipboard_mac.mm | 333 | ||||
-rw-r--r-- | ui/base/clipboard/clipboard_unittest.cc | 432 | ||||
-rw-r--r-- | ui/base/clipboard/clipboard_util_win.cc | 544 | ||||
-rw-r--r-- | ui/base/clipboard/clipboard_util_win.h | 70 | ||||
-rw-r--r-- | ui/base/clipboard/clipboard_win.cc | 606 | ||||
-rw-r--r-- | ui/base/clipboard/scoped_clipboard_writer.cc | 140 | ||||
-rw-r--r-- | ui/base/clipboard/scoped_clipboard_writer.h | 85 | ||||
-rw-r--r-- | ui/base/ui_base.gypi | 10 |
11 files changed, 3100 insertions, 0 deletions
diff --git a/ui/base/clipboard/clipboard.cc b/ui/base/clipboard/clipboard.cc new file mode 100644 index 0000000..4b9bf603 --- /dev/null +++ b/ui/base/clipboard/clipboard.cc @@ -0,0 +1,183 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/base/clipboard/clipboard.h" + +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "gfx/size.h" + +namespace ui { + +namespace { + +// A compromised renderer could send us bad data, so validate it. +// This function only checks that the size parameter makes sense, the caller +// is responsible for further validating the bitmap buffer against +// |bitmap_bytes|. +// +// |params| - Clipboard bitmap contents to validate. +// |bitmap_bytes| - On return contains the number of bytes needed to store +// the bitmap data or -1 if the data is invalid. +// returns: true if the bitmap size is valid, false otherwise. +bool IsBitmapSafe(const Clipboard::ObjectMapParams& params, + uint32* bitmap_bytes) { + *bitmap_bytes = -1; + if (params[1].size() != sizeof(gfx::Size)) + return false; + const gfx::Size* size = + reinterpret_cast<const gfx::Size*>(&(params[1].front())); + uint32 total_size = size->width(); + // Using INT_MAX not SIZE_T_MAX to put a reasonable bound on things. + if (INT_MAX / size->width() <= size->height()) + return false; + total_size *= size->height(); + if (INT_MAX / total_size <= 4) + return false; + total_size *= 4; + *bitmap_bytes = total_size; + return true; +} + +// Validates a plain bitmap on the clipboard. +// Returns true if the clipboard data makes sense and it's safe to access the +// bitmap. +bool ValidatePlainBitmap(const Clipboard::ObjectMapParams& params) { + uint32 bitmap_bytes = -1; + if (!IsBitmapSafe(params, &bitmap_bytes)) + return false; + if (bitmap_bytes != params[0].size()) + return false; + return true; +} + +// Valides a shared bitmap on the clipboard. +// Returns true if the clipboard data makes sense and it's safe to access the +// bitmap. +bool ValidateAndMapSharedBitmap(const Clipboard::ObjectMapParams& params, + base::SharedMemory* bitmap_data) { + using base::SharedMemory; + uint32 bitmap_bytes = -1; + if (!IsBitmapSafe(params, &bitmap_bytes)) + return false; + + if (!bitmap_data || !SharedMemory::IsHandleValid(bitmap_data->handle())) + return false; + + if (!bitmap_data->Map(bitmap_bytes)) { + PLOG(ERROR) << "Failed to map bitmap memory"; + return false; + } + return true; +} + +} // namespace + +void Clipboard::DispatchObject(ObjectType type, const ObjectMapParams& params) { + // All types apart from CBF_WEBKIT need at least 1 non-empty param. + if (type != CBF_WEBKIT && (params.empty() || params[0].empty())) + return; + // Some other types need a non-empty 2nd param. + if ((type == CBF_BOOKMARK || type == CBF_BITMAP || + type == CBF_SMBITMAP || type == CBF_DATA) && + (params.size() != 2 || params[1].empty())) + return; + switch (type) { + case CBF_TEXT: + WriteText(&(params[0].front()), params[0].size()); + break; + + case CBF_HTML: + if (params.size() == 2) { + if (params[1].empty()) + return; + WriteHTML(&(params[0].front()), params[0].size(), + &(params[1].front()), params[1].size()); + } else if (params.size() == 1) { + WriteHTML(&(params[0].front()), params[0].size(), NULL, 0); + } + break; + + case CBF_BOOKMARK: + WriteBookmark(&(params[0].front()), params[0].size(), + &(params[1].front()), params[1].size()); + break; + + case CBF_WEBKIT: + WriteWebSmartPaste(); + break; + + case CBF_BITMAP: + if (!ValidatePlainBitmap(params)) + return; + + WriteBitmap(&(params[0].front()), &(params[1].front())); + break; + + case CBF_SMBITMAP: { + using base::SharedMemory; + using base::SharedMemoryHandle; + + if (params[0].size() != sizeof(SharedMemory*)) + return; + + // It's OK to cast away constness here since we map the handle as + // read-only. + const char* raw_bitmap_data_const = + reinterpret_cast<const char*>(&(params[0].front())); + char* raw_bitmap_data = const_cast<char*>(raw_bitmap_data_const); + scoped_ptr<SharedMemory> bitmap_data( + *reinterpret_cast<SharedMemory**>(raw_bitmap_data)); + + if (!ValidateAndMapSharedBitmap(params, bitmap_data.get())) + return; + WriteBitmap(static_cast<const char*>(bitmap_data->memory()), + &(params[1].front())); + break; + } + +#if !defined(OS_MACOSX) + case CBF_DATA: + WriteData(&(params[0].front()), params[0].size(), + &(params[1].front()), params[1].size()); + break; +#endif // !defined(OS_MACOSX) + + default: + NOTREACHED(); + } +} + +// static +void Clipboard::ReplaceSharedMemHandle(ObjectMap* objects, + base::SharedMemoryHandle bitmap_handle, + base::ProcessHandle process) { + using base::SharedMemory; + bool has_shared_bitmap = false; + + for (ObjectMap::iterator iter = objects->begin(); iter != objects->end(); + ++iter) { + if (iter->first == CBF_SMBITMAP) { + // The code currently only accepts sending a single bitmap over this way. + // Fail hard if we ever encounter more than one shared bitmap structure to + // fill. + CHECK(!has_shared_bitmap); + +#if defined(OS_WIN) + SharedMemory* bitmap = new SharedMemory(bitmap_handle, true, process); +#else + SharedMemory* bitmap = new SharedMemory(bitmap_handle, true); +#endif + + // We store the shared memory object pointer so it can be retrieved by the + // UI thread (see DispatchObject()). + iter->second[0].clear(); + for (size_t i = 0; i < sizeof(SharedMemory*); ++i) + iter->second[0].push_back(reinterpret_cast<char*>(&bitmap)[i]); + has_shared_bitmap = true; + } + } +} + +} // namespace ui diff --git a/ui/base/clipboard/clipboard.h b/ui/base/clipboard/clipboard.h new file mode 100644 index 0000000..715e6ea --- /dev/null +++ b/ui/base/clipboard/clipboard.h @@ -0,0 +1,276 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef UI_BASE_CLIPBOARD_CLIPBOARD_H_ +#define UI_BASE_CLIPBOARD_CLIPBOARD_H_ +#pragma once + +#include <map> +#include <string> +#include <vector> + +#include "base/gtest_prod_util.h" +#include "base/process.h" +#include "base/shared_memory.h" +#include "base/string16.h" + +namespace gfx { +class Size; +} + +class FilePath; + +#if defined(TOOLKIT_USES_GTK) +typedef struct _GtkClipboard GtkClipboard; +#endif + +namespace ui { + +class Clipboard { + public: + typedef std::string FormatType; + + // ObjectType designates the type of data to be stored in the clipboard. This + // designation is shared across all OSes. The system-specific designation + // is defined by FormatType. A single ObjectType might be represented by + // several system-specific FormatTypes. For example, on Linux the CBF_TEXT + // ObjectType maps to "text/plain", "STRING", and several other formats. On + // windows it maps to CF_UNICODETEXT. + enum ObjectType { + CBF_TEXT, + CBF_HTML, + CBF_BOOKMARK, + CBF_FILES, + CBF_WEBKIT, + CBF_BITMAP, + CBF_SMBITMAP, // Bitmap from shared memory. + CBF_DATA, // Arbitrary block of bytes. + }; + + // ObjectMap is a map from ObjectType to associated data. + // The data is organized differently for each ObjectType. The following + // table summarizes what kind of data is stored for each key. + // * indicates an optional argument. + // + // Key Arguments Type + // ------------------------------------- + // CBF_TEXT text char array + // CBF_HTML html char array + // url* char array + // CBF_BOOKMARK html char array + // url char array + // CBF_LINK html char array + // url char array + // CBF_FILES files char array representing multiple files. + // Filenames are separated by null characters and + // the final filename is double null terminated. + // The filenames are encoded in platform-specific + // encoding. + // CBF_WEBKIT none empty vector + // CBF_BITMAP pixels byte array + // size gfx::Size struct + // CBF_SMBITMAP shared_mem A pointer to an unmapped base::SharedMemory + // object containing the bitmap data. + // size gfx::Size struct + // CBF_DATA format char array + // data byte array + typedef std::vector<char> ObjectMapParam; + typedef std::vector<ObjectMapParam> ObjectMapParams; + typedef std::map<int /* ObjectType */, ObjectMapParams> ObjectMap; + + // Buffer designates which clipboard the action should be applied to. + // Only platforms that use the X Window System support the selection + // buffer. Furthermore we currently only use a buffer other than the + // standard buffer when reading from the clipboard so only those + // functions accept a buffer parameter. + enum Buffer { + BUFFER_STANDARD, + BUFFER_SELECTION, + BUFFER_DRAG, + }; + + static bool IsValidBuffer(int32 buffer) { + switch (buffer) { + case BUFFER_STANDARD: + return true; +#if defined(USE_X11) + case BUFFER_SELECTION: + return true; +#endif + case BUFFER_DRAG: + return true; + } + return false; + } + + static Buffer FromInt(int32 buffer) { + return static_cast<Buffer>(buffer); + } + + Clipboard(); + ~Clipboard(); + + // Write a bunch of objects to the system clipboard. Copies are made of the + // contents of |objects|. On Windows they are copied to the system clipboard. + // On linux they are copied into a structure owned by the Clipboard object and + // kept until the system clipboard is set again. + void WriteObjects(const ObjectMap& objects); + + // Behaves as above. If there is some shared memory handle passed as one of + // the objects, it came from the process designated by |process|. This will + // assist in turning it into a shared memory region that the current process + // can use. + void WriteObjects(const ObjectMap& objects, base::ProcessHandle process); + + // On Linux/BSD, we need to know when the clipboard is set to a URL. Most + // platforms don't care. +#if defined(OS_WIN) || defined(OS_MACOSX) + void DidWriteURL(const std::string& utf8_text) {} +#else // !defined(OS_WIN) && !defined(OS_MACOSX) + void DidWriteURL(const std::string& utf8_text); +#endif + + // Tests whether the clipboard contains a certain format + bool IsFormatAvailable(const FormatType& format, Buffer buffer) const; + + // As above, but instead of interpreting |format| by some platform-specific + // definition, interpret it as a literal MIME type. + bool IsFormatAvailableByString(const std::string& format, + Buffer buffer) const; + + // Reads UNICODE text from the clipboard, if available. + void ReadText(Buffer buffer, string16* result) const; + + // Reads ASCII text from the clipboard, if available. + void ReadAsciiText(Buffer buffer, std::string* result) const; + + // Reads HTML from the clipboard, if available. + void ReadHTML(Buffer buffer, string16* markup, std::string* src_url) const; + + // Reads a bookmark from the clipboard, if available. + void ReadBookmark(string16* title, std::string* url) const; + + // Reads a file or group of files from the clipboard, if available, into the + // out parameter. + void ReadFile(FilePath* file) const; + void ReadFiles(std::vector<FilePath>* files) const; + + // Reads raw data from the clipboard with the given format type. Stores result + // as a byte vector. + // TODO(dcheng): Due to platform limitations on Windows, we should make sure + // format is never controlled by the user. + void ReadData(const std::string& format, std::string* result); + + // Get format Identifiers for various types. + static FormatType GetUrlFormatType(); + static FormatType GetUrlWFormatType(); + static FormatType GetMozUrlFormatType(); + static FormatType GetPlainTextFormatType(); + static FormatType GetPlainTextWFormatType(); + static FormatType GetFilenameFormatType(); + static FormatType GetFilenameWFormatType(); + static FormatType GetWebKitSmartPasteFormatType(); + // Win: MS HTML Format, Other: Generic HTML format + static FormatType GetHtmlFormatType(); + static FormatType GetBitmapFormatType(); + + // Embeds a pointer to a SharedMemory object pointed to by |bitmap_handle| + // belonging to |process| into a shared bitmap [CBF_SMBITMAP] slot in + // |objects|. The pointer is deleted by DispatchObjects(). + // + // On non-Windows platforms, |process| is ignored. + static void ReplaceSharedMemHandle(ObjectMap* objects, + base::SharedMemoryHandle bitmap_handle, + base::ProcessHandle process); +#if defined(OS_WIN) + // Firefox text/html + static FormatType GetTextHtmlFormatType(); + static FormatType GetCFHDropFormatType(); + static FormatType GetFileDescriptorFormatType(); + static FormatType GetFileContentFormatZeroType(); +#endif + + private: + FRIEND_TEST_ALL_PREFIXES(ClipboardTest, SharedBitmapTest); + FRIEND_TEST_ALL_PREFIXES(ClipboardTest, EmptyHTMLTest); + + void DispatchObject(ObjectType type, const ObjectMapParams& params); + + void WriteText(const char* text_data, size_t text_len); + + void WriteHTML(const char* markup_data, + size_t markup_len, + const char* url_data, + size_t url_len); + + void WriteBookmark(const char* title_data, + size_t title_len, + const char* url_data, + size_t url_len); + + void WriteWebSmartPaste(); + + void WriteBitmap(const char* pixel_data, const char* size_data); + +#if !defined(OS_MACOSX) + // |format_name| is an ASCII string and should be NULL-terminated. + // TODO(estade): port to mac. + void WriteData(const char* format_name, size_t format_len, + const char* data_data, size_t data_len); +#endif +#if defined(OS_WIN) + void WriteBitmapFromHandle(HBITMAP source_hbitmap, + const gfx::Size& size); + + // Safely write to system clipboard. Free |handle| on failure. + void WriteToClipboard(unsigned int format, HANDLE handle); + + static void ParseBookmarkClipboardFormat(const string16& bookmark, + string16* title, + std::string* url); + + // Free a handle depending on its type (as intuited from format) + static void FreeData(unsigned int format, HANDLE data); + + // Return the window that should be the clipboard owner, creating it + // if neccessary. Marked const for lazily initialization by const methods. + HWND GetClipboardWindow() const; + + // Mark this as mutable so const methods can still do lazy initialization. + mutable HWND clipboard_owner_; + + // True if we can create a window. + bool create_window_; +#elif !defined(OS_MACOSX) + // The public API is via WriteObjects() which dispatches to multiple + // Write*() calls, but on GTK we must write all the clipboard types + // in a single GTK call. To support this we store the current set + // of data we intend to put on the clipboard on clipboard_data_ as + // WriteObjects is running, and then at the end call SetGtkClipboard + // which replaces whatever is on the system clipboard with the + // contents of clipboard_data_. + + public: + typedef std::map<FormatType, std::pair<char*, size_t> > TargetMap; + + private: + // Write changes to gtk clipboard. + void SetGtkClipboard(); + // Insert a mapping into clipboard_data_. + void InsertMapping(const char* key, char* data, size_t data_len); + + // Find the gtk clipboard for the passed buffer enum. + GtkClipboard* LookupBackingClipboard(Buffer clipboard) const; + + TargetMap* clipboard_data_; + GtkClipboard* clipboard_; + GtkClipboard* primary_selection_; +#endif + + DISALLOW_COPY_AND_ASSIGN(Clipboard); +}; + +} // namespace ui + +#endif // UI_BASE_CLIPBOARD_CLIPBOARD_H_ diff --git a/ui/base/clipboard/clipboard_linux.cc b/ui/base/clipboard/clipboard_linux.cc new file mode 100644 index 0000000..da24c4f --- /dev/null +++ b/ui/base/clipboard/clipboard_linux.cc @@ -0,0 +1,421 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/base/clipboard/clipboard.h" + +#include <gtk/gtk.h> +#include <map> +#include <set> +#include <string> +#include <utility> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#include "gfx/gtk_util.h" +#include "gfx/size.h" + +namespace ui { + +namespace { + +const char kMimeBmp[] = "image/bmp"; +const char kMimeHtml[] = "text/html"; +const char kMimeText[] = "text/plain"; +const char kMimeMozillaUrl[] = "text/x-moz-url"; +const char kMimeWebkitSmartPaste[] = "chromium/x-webkit-paste"; + +std::string GdkAtomToString(const GdkAtom& atom) { + gchar* name = gdk_atom_name(atom); + std::string rv(name); + g_free(name); + return rv; +} + +GdkAtom StringToGdkAtom(const std::string& str) { + return gdk_atom_intern(str.c_str(), FALSE); +} + +// GtkClipboardGetFunc callback. +// GTK will call this when an application wants data we copied to the clipboard. +void GetData(GtkClipboard* clipboard, + GtkSelectionData* selection_data, + guint info, + gpointer user_data) { + Clipboard::TargetMap* data_map = + reinterpret_cast<Clipboard::TargetMap*>(user_data); + + std::string target_string = GdkAtomToString(selection_data->target); + Clipboard::TargetMap::iterator iter = data_map->find(target_string); + + if (iter == data_map->end()) + return; + + if (target_string == kMimeBmp) { + gtk_selection_data_set_pixbuf(selection_data, + reinterpret_cast<GdkPixbuf*>(iter->second.first)); + } else { + gtk_selection_data_set(selection_data, selection_data->target, 8, + reinterpret_cast<guchar*>(iter->second.first), + iter->second.second); + } +} + +// GtkClipboardClearFunc callback. +// We are guaranteed this will be called exactly once for each call to +// gtk_clipboard_set_with_data. +void ClearData(GtkClipboard* clipboard, + gpointer user_data) { + Clipboard::TargetMap* map = + reinterpret_cast<Clipboard::TargetMap*>(user_data); + // The same data may be inserted under multiple keys, so use a set to + // uniq them. + std::set<char*> ptrs; + + for (Clipboard::TargetMap::iterator iter = map->begin(); + iter != map->end(); ++iter) { + if (iter->first == kMimeBmp) + g_object_unref(reinterpret_cast<GdkPixbuf*>(iter->second.first)); + else + ptrs.insert(iter->second.first); + } + + for (std::set<char*>::iterator iter = ptrs.begin(); + iter != ptrs.end(); ++iter) { + delete[] *iter; + } + + delete map; +} + +// Called on GdkPixbuf destruction; see WriteBitmap(). +void GdkPixbufFree(guchar* pixels, gpointer data) { + free(pixels); +} + +} // namespace + +Clipboard::Clipboard() : clipboard_data_(NULL) { + clipboard_ = gtk_clipboard_get(GDK_SELECTION_CLIPBOARD); + primary_selection_ = gtk_clipboard_get(GDK_SELECTION_PRIMARY); +} + +Clipboard::~Clipboard() { + gtk_clipboard_store(clipboard_); +} + +void Clipboard::WriteObjects(const ObjectMap& objects) { + clipboard_data_ = new TargetMap(); + + for (ObjectMap::const_iterator iter = objects.begin(); + iter != objects.end(); ++iter) { + DispatchObject(static_cast<ObjectType>(iter->first), iter->second); + } + + SetGtkClipboard(); +} + +// When a URL is copied from a render view context menu (via "copy link +// location", for example), we additionally stick it in the X clipboard. This +// matches other linux browsers. +void Clipboard::DidWriteURL(const std::string& utf8_text) { + gtk_clipboard_set_text(primary_selection_, utf8_text.c_str(), + utf8_text.length()); +} + +// Take ownership of the GTK clipboard and inform it of the targets we support. +void Clipboard::SetGtkClipboard() { + scoped_array<GtkTargetEntry> targets( + new GtkTargetEntry[clipboard_data_->size()]); + + int i = 0; + for (Clipboard::TargetMap::iterator iter = clipboard_data_->begin(); + iter != clipboard_data_->end(); ++iter, ++i) { + targets[i].target = const_cast<char*>(iter->first.c_str()); + targets[i].flags = 0; + targets[i].info = 0; + } + + if (gtk_clipboard_set_with_data(clipboard_, targets.get(), + clipboard_data_->size(), + GetData, ClearData, + clipboard_data_)) { + gtk_clipboard_set_can_store(clipboard_, + targets.get(), + clipboard_data_->size()); + } + + // clipboard_data_ now owned by the GtkClipboard. + clipboard_data_ = NULL; +} + +void Clipboard::WriteText(const char* text_data, size_t text_len) { + char* data = new char[text_len]; + memcpy(data, text_data, text_len); + + InsertMapping(kMimeText, data, text_len); + InsertMapping("TEXT", data, text_len); + InsertMapping("STRING", data, text_len); + InsertMapping("UTF8_STRING", data, text_len); + InsertMapping("COMPOUND_TEXT", data, text_len); +} + +void Clipboard::WriteHTML(const char* markup_data, + size_t markup_len, + const char* url_data, + size_t url_len) { + // TODO(estade): We need to expand relative links with |url_data|. + static const char* html_prefix = "<meta http-equiv=\"content-type\" " + "content=\"text/html; charset=utf-8\">"; + size_t html_prefix_len = strlen(html_prefix); + size_t total_len = html_prefix_len + markup_len + 1; + + char* data = new char[total_len]; + snprintf(data, total_len, "%s", html_prefix); + memcpy(data + html_prefix_len, markup_data, markup_len); + // Some programs expect NULL-terminated data. See http://crbug.com/42624 + data[total_len - 1] = '\0'; + + InsertMapping(kMimeHtml, data, total_len); +} + +// Write an extra flavor that signifies WebKit was the last to modify the +// pasteboard. This flavor has no data. +void Clipboard::WriteWebSmartPaste() { + InsertMapping(kMimeWebkitSmartPaste, NULL, 0); +} + +void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) { + const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data); + + guchar* data = + gfx::BGRAToRGBA(reinterpret_cast<const uint8_t*>(pixel_data), + size->width(), size->height(), 0); + + GdkPixbuf* pixbuf = + gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, TRUE, + 8, size->width(), size->height(), + size->width() * 4, GdkPixbufFree, NULL); + // We store the GdkPixbuf*, and the size_t half of the pair is meaningless. + // Note that this contrasts with the vast majority of entries in our target + // map, which directly store the data and its length. + InsertMapping(kMimeBmp, reinterpret_cast<char*>(pixbuf), 0); +} + +void Clipboard::WriteBookmark(const char* title_data, size_t title_len, + const char* url_data, size_t url_len) { + // Write as a mozilla url (UTF16: URL, newline, title). + string16 url = UTF8ToUTF16(std::string(url_data, url_len) + "\n"); + string16 title = UTF8ToUTF16(std::string(title_data, title_len)); + int data_len = 2 * (title.length() + url.length()); + + char* data = new char[data_len]; + memcpy(data, url.data(), 2 * url.length()); + memcpy(data + 2 * url.length(), title.data(), 2 * title.length()); + InsertMapping(kMimeMozillaUrl, data, data_len); +} + +void Clipboard::WriteData(const char* format_name, size_t format_len, + const char* data_data, size_t data_len) { + std::string format(format_name, format_len); + // We assume that certain mapping types are only written by trusted code. + // Therefore we must upkeep their integrity. + if (format == kMimeBmp) + return; + char* data = new char[data_len]; + memcpy(data, data_data, data_len); + InsertMapping(format.c_str(), data, data_len); +} + +// We do not use gtk_clipboard_wait_is_target_available because of +// a bug with the gtk clipboard. It caches the available targets +// and does not always refresh the cache when it is appropriate. +bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format, + Clipboard::Buffer buffer) const { + GtkClipboard* clipboard = LookupBackingClipboard(buffer); + if (clipboard == NULL) + return false; + + bool format_is_plain_text = GetPlainTextFormatType() == format; + if (format_is_plain_text) { + // This tries a number of common text targets. + if (gtk_clipboard_wait_is_text_available(clipboard)) + return true; + } + + bool retval = false; + GdkAtom* targets = NULL; + GtkSelectionData* data = + gtk_clipboard_wait_for_contents(clipboard, + gdk_atom_intern("TARGETS", false)); + + if (!data) + return false; + + int num = 0; + gtk_selection_data_get_targets(data, &targets, &num); + + // Some programs post data to the clipboard without any targets. If this is + // the case we attempt to make sense of the contents as text. This is pretty + // unfortunate since it means we have to actually copy the data to see if it + // is available, but at least this path shouldn't be hit for conforming + // programs. + if (num <= 0) { + if (format_is_plain_text) { + gchar* text = gtk_clipboard_wait_for_text(clipboard); + if (text) { + g_free(text); + retval = true; + } + } + } + + GdkAtom format_atom = StringToGdkAtom(format); + + for (int i = 0; i < num; i++) { + if (targets[i] == format_atom) { + retval = true; + break; + } + } + + g_free(targets); + gtk_selection_data_free(data); + + return retval; +} + +bool Clipboard::IsFormatAvailableByString(const std::string& format, + Clipboard::Buffer buffer) const { + return IsFormatAvailable(format, buffer); +} + +void Clipboard::ReadText(Clipboard::Buffer buffer, string16* result) const { + GtkClipboard* clipboard = LookupBackingClipboard(buffer); + if (clipboard == NULL) + return; + + result->clear(); + gchar* text = gtk_clipboard_wait_for_text(clipboard); + + if (text == NULL) + return; + + // TODO(estade): do we want to handle the possible error here? + UTF8ToUTF16(text, strlen(text), result); + g_free(text); +} + +void Clipboard::ReadAsciiText(Clipboard::Buffer buffer, + std::string* result) const { + GtkClipboard* clipboard = LookupBackingClipboard(buffer); + if (clipboard == NULL) + return; + + result->clear(); + gchar* text = gtk_clipboard_wait_for_text(clipboard); + + if (text == NULL) + return; + + result->assign(text); + g_free(text); +} + +void Clipboard::ReadFile(FilePath* file) const { + *file = FilePath(); +} + +// TODO(estade): handle different charsets. +// TODO(port): set *src_url. +void Clipboard::ReadHTML(Clipboard::Buffer buffer, string16* markup, + std::string* src_url) const { + GtkClipboard* clipboard = LookupBackingClipboard(buffer); + if (clipboard == NULL) + return; + markup->clear(); + + GtkSelectionData* data = gtk_clipboard_wait_for_contents(clipboard, + StringToGdkAtom(GetHtmlFormatType())); + + if (!data) + return; + + // If the data starts with 0xFEFF, i.e., Byte Order Mark, assume it is + // UTF-16, otherwise assume UTF-8. + if (data->length >= 2 && + reinterpret_cast<uint16_t*>(data->data)[0] == 0xFEFF) { + markup->assign(reinterpret_cast<uint16_t*>(data->data) + 1, + (data->length / 2) - 1); + } else { + UTF8ToUTF16(reinterpret_cast<char*>(data->data), data->length, markup); + } + + // If there is a terminating NULL, drop it. + if (!markup->empty() && markup->at(markup->length() - 1) == '\0') + markup->resize(markup->length() - 1); + + gtk_selection_data_free(data); +} + +void Clipboard::ReadBookmark(string16* title, std::string* url) const { + // TODO(estade): implement this. + NOTIMPLEMENTED(); +} + +void Clipboard::ReadData(const std::string& format, std::string* result) { + GtkSelectionData* data = + gtk_clipboard_wait_for_contents(clipboard_, StringToGdkAtom(format)); + if (!data) + return; + result->assign(reinterpret_cast<char*>(data->data), data->length); + gtk_selection_data_free(data); +} + +// static +Clipboard::FormatType Clipboard::GetPlainTextFormatType() { + return GdkAtomToString(GDK_TARGET_STRING); +} + +// static +Clipboard::FormatType Clipboard::GetPlainTextWFormatType() { + return GetPlainTextFormatType(); +} + +// static +Clipboard::FormatType Clipboard::GetHtmlFormatType() { + return std::string(kMimeHtml); +} + +// static +Clipboard::FormatType Clipboard::GetBitmapFormatType() { + return std::string(kMimeBmp); +} + +// static +Clipboard::FormatType Clipboard::GetWebKitSmartPasteFormatType() { + return std::string(kMimeWebkitSmartPaste); +} + +void Clipboard::InsertMapping(const char* key, + char* data, + size_t data_len) { + DCHECK(clipboard_data_->find(key) == clipboard_data_->end()); + (*clipboard_data_)[key] = std::make_pair(data, data_len); +} + +GtkClipboard* Clipboard::LookupBackingClipboard(Buffer clipboard) const { + switch (clipboard) { + case BUFFER_STANDARD: + return clipboard_; + case BUFFER_SELECTION: + return primary_selection_; + default: + NOTREACHED(); + return NULL; + } +} + +} // namespace ui diff --git a/ui/base/clipboard/clipboard_mac.mm b/ui/base/clipboard/clipboard_mac.mm new file mode 100644 index 0000000..fb62346 --- /dev/null +++ b/ui/base/clipboard/clipboard_mac.mm @@ -0,0 +1,333 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/base/clipboard/clipboard.h" + +#import <Cocoa/Cocoa.h> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_cftyperef.h" +#include "base/scoped_nsobject.h" +#include "base/sys_string_conversions.h" +#include "base/utf_string_conversions.h" +#include "gfx/size.h" +#import "third_party/mozilla/NSPasteboard+Utils.h" + +namespace ui { + +namespace { + +// Would be nice if this were in UTCoreTypes.h, but it isn't +NSString* const kUTTypeURLName = @"public.url-name"; + +// Tells us if WebKit was the last to write to the pasteboard. There's no +// actual data associated with this type. +NSString* const kWebSmartPastePboardType = @"NeXT smart paste pasteboard type"; + +NSPasteboard* GetPasteboard() { + // The pasteboard should not be nil in a UI session, but this handy DCHECK + // can help track down problems if someone tries using clipboard code outside + // of a UI session. + NSPasteboard* pasteboard = [NSPasteboard generalPasteboard]; + DCHECK(pasteboard); + return pasteboard; +} + +} // namespace + +Clipboard::Clipboard() { +} + +Clipboard::~Clipboard() { +} + +void Clipboard::WriteObjects(const ObjectMap& objects) { + NSPasteboard* pb = GetPasteboard(); + [pb declareTypes:[NSArray array] owner:nil]; + + for (ObjectMap::const_iterator iter = objects.begin(); + iter != objects.end(); ++iter) { + DispatchObject(static_cast<ObjectType>(iter->first), iter->second); + } + +} + +void Clipboard::WriteText(const char* text_data, size_t text_len) { + std::string text_str(text_data, text_len); + NSString *text = base::SysUTF8ToNSString(text_str); + NSPasteboard* pb = GetPasteboard(); + [pb addTypes:[NSArray arrayWithObject:NSStringPboardType] owner:nil]; + [pb setString:text forType:NSStringPboardType]; +} + +void Clipboard::WriteHTML(const char* markup_data, + size_t markup_len, + const char* url_data, + size_t url_len) { + // We need to mark it as utf-8. (see crbug.com/11957) + std::string html_fragment_str("<meta charset='utf-8'>"); + html_fragment_str.append(markup_data, markup_len); + NSString *html_fragment = base::SysUTF8ToNSString(html_fragment_str); + + // TODO(avi): url_data? + NSPasteboard* pb = GetPasteboard(); + [pb addTypes:[NSArray arrayWithObject:NSHTMLPboardType] owner:nil]; + [pb setString:html_fragment forType:NSHTMLPboardType]; +} + +void Clipboard::WriteBookmark(const char* title_data, + size_t title_len, + const char* url_data, + size_t url_len) { + std::string title_str(title_data, title_len); + NSString *title = base::SysUTF8ToNSString(title_str); + std::string url_str(url_data, url_len); + NSString *url = base::SysUTF8ToNSString(url_str); + + // TODO(playmobil): In the Windows version of this function, an HTML + // representation of the bookmark is also added to the clipboard, to support + // drag and drop of web shortcuts. I don't think we need to do this on the + // Mac, but we should double check later on. + NSURL* nsurl = [NSURL URLWithString:url]; + + NSPasteboard* pb = GetPasteboard(); + // passing UTIs into the pasteboard methods is valid >= 10.5 + [pb addTypes:[NSArray arrayWithObjects:NSURLPboardType, + kUTTypeURLName, + nil] + owner:nil]; + [nsurl writeToPasteboard:pb]; + [pb setString:title forType:kUTTypeURLName]; +} + +void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) { + const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data); + + // Safe because the image goes away before the call returns. + base::mac::ScopedCFTypeRef<CFDataRef> data( + CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, + reinterpret_cast<const UInt8*>(pixel_data), + size->width()*size->height()*4, + kCFAllocatorNull)); + + base::mac::ScopedCFTypeRef<CGDataProviderRef> data_provider( + CGDataProviderCreateWithCFData(data)); + + base::mac::ScopedCFTypeRef<CGImageRef> cgimage( + CGImageCreate(size->width(), + size->height(), + 8, + 32, + size->width()*4, + base::mac::GetSRGBColorSpace(), // TODO(avi): do better + kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, + data_provider, + NULL, + false, + kCGRenderingIntentDefault)); + // Aggressively free storage since image buffers can potentially be very + // large. + data_provider.reset(); + data.reset(); + + scoped_nsobject<NSBitmapImageRep> bitmap( + [[NSBitmapImageRep alloc] initWithCGImage:cgimage]); + cgimage.reset(); + + scoped_nsobject<NSImage> image([[NSImage alloc] init]); + [image addRepresentation:bitmap]; + + // An API to ask the NSImage to write itself to the clipboard comes in 10.6 :( + // For now, spit out the image as a TIFF. + NSPasteboard* pb = GetPasteboard(); + [pb addTypes:[NSArray arrayWithObject:NSTIFFPboardType] owner:nil]; + NSData *tiff_data = [image TIFFRepresentation]; + LOG_IF(ERROR, tiff_data == NULL) << "Failed to allocate image for clipboard"; + if (tiff_data) { + [pb setData:tiff_data forType:NSTIFFPboardType]; + } +} + +// Write an extra flavor that signifies WebKit was the last to modify the +// pasteboard. This flavor has no data. +void Clipboard::WriteWebSmartPaste() { + NSPasteboard* pb = GetPasteboard(); + NSString* format = base::SysUTF8ToNSString(GetWebKitSmartPasteFormatType()); + [pb addTypes:[NSArray arrayWithObject:format] owner:nil]; + [pb setData:nil forType:format]; +} + +bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format, + Clipboard::Buffer buffer) const { + DCHECK_EQ(buffer, BUFFER_STANDARD); + NSString* format_ns = base::SysUTF8ToNSString(format); + + NSPasteboard* pb = GetPasteboard(); + NSArray* types = [pb types]; + + // Safari only places RTF on the pasteboard, never HTML. We can convert RTF + // to HTML, so the presence of either indicates success when looking for HTML. + if ([format_ns isEqualToString:NSHTMLPboardType]) { + return [types containsObject:NSHTMLPboardType] || + [types containsObject:NSRTFPboardType]; + } + return [types containsObject:format_ns]; +} + +void Clipboard::ReadText(Clipboard::Buffer buffer, string16* result) const { + DCHECK_EQ(buffer, BUFFER_STANDARD); + NSPasteboard* pb = GetPasteboard(); + NSString* contents = [pb stringForType:NSStringPboardType]; + + UTF8ToUTF16([contents UTF8String], + [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding], + result); +} + +void Clipboard::ReadAsciiText(Clipboard::Buffer buffer, + std::string* result) const { + DCHECK_EQ(buffer, BUFFER_STANDARD); + NSPasteboard* pb = GetPasteboard(); + NSString* contents = [pb stringForType:NSStringPboardType]; + + if (!contents) + result->clear(); + else + result->assign([contents UTF8String]); +} + +void Clipboard::ReadHTML(Clipboard::Buffer buffer, string16* markup, + std::string* src_url) const { + DCHECK_EQ(buffer, BUFFER_STANDARD); + if (markup) { + NSPasteboard* pb = GetPasteboard(); + NSArray* supportedTypes = [NSArray arrayWithObjects:NSHTMLPboardType, + NSRTFPboardType, + NSStringPboardType, + nil]; + NSString* bestType = [pb availableTypeFromArray:supportedTypes]; + NSString* contents = [pb stringForType:bestType]; + if ([bestType isEqualToString:NSRTFPboardType]) + contents = [pb htmlFromRtf]; + UTF8ToUTF16([contents UTF8String], + [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding], + markup); + } + + // TODO(avi): src_url? + if (src_url) + src_url->clear(); +} + +void Clipboard::ReadBookmark(string16* title, std::string* url) const { + NSPasteboard* pb = GetPasteboard(); + + if (title) { + NSString* contents = [pb stringForType:kUTTypeURLName]; + UTF8ToUTF16([contents UTF8String], + [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding], + title); + } + + if (url) { + NSString* url_string = [[NSURL URLFromPasteboard:pb] absoluteString]; + if (!url_string) + url->clear(); + else + url->assign([url_string UTF8String]); + } +} + +void Clipboard::ReadFile(FilePath* file) const { + if (!file) { + NOTREACHED(); + return; + } + + *file = FilePath(); + std::vector<FilePath> files; + ReadFiles(&files); + + // Take the first file, if available. + if (!files.empty()) + *file = files[0]; +} + +void Clipboard::ReadFiles(std::vector<FilePath>* files) const { + if (!files) { + NOTREACHED(); + return; + } + + files->clear(); + + NSPasteboard* pb = GetPasteboard(); + NSArray* fileList = [pb propertyListForType:NSFilenamesPboardType]; + + for (unsigned int i = 0; i < [fileList count]; ++i) { + std::string file = [[fileList objectAtIndex:i] UTF8String]; + files->push_back(FilePath(file)); + } +} + +// static +Clipboard::FormatType Clipboard::GetUrlFormatType() { + static const std::string type = base::SysNSStringToUTF8(NSURLPboardType); + return type; +} + +// static +Clipboard::FormatType Clipboard::GetUrlWFormatType() { + static const std::string type = base::SysNSStringToUTF8(NSURLPboardType); + return type; +} + +// static +Clipboard::FormatType Clipboard::GetPlainTextFormatType() { + static const std::string type = base::SysNSStringToUTF8(NSStringPboardType); + return type; +} + +// static +Clipboard::FormatType Clipboard::GetPlainTextWFormatType() { + static const std::string type = base::SysNSStringToUTF8(NSStringPboardType); + return type; +} + +// static +Clipboard::FormatType Clipboard::GetFilenameFormatType() { + static const std::string type = + base::SysNSStringToUTF8(NSFilenamesPboardType); + return type; +} + +// static +Clipboard::FormatType Clipboard::GetFilenameWFormatType() { + static const std::string type = + base::SysNSStringToUTF8(NSFilenamesPboardType); + return type; +} + +// static +Clipboard::FormatType Clipboard::GetHtmlFormatType() { + static const std::string type = base::SysNSStringToUTF8(NSHTMLPboardType); + return type; +} + +// static +Clipboard::FormatType Clipboard::GetBitmapFormatType() { + static const std::string type = base::SysNSStringToUTF8(NSTIFFPboardType); + return type; +} + +// static +Clipboard::FormatType Clipboard::GetWebKitSmartPasteFormatType() { + static const std::string type = + base::SysNSStringToUTF8(kWebSmartPastePboardType); + return type; +} + +} // namespace ui diff --git a/ui/base/clipboard/clipboard_unittest.cc b/ui/base/clipboard/clipboard_unittest.cc new file mode 100644 index 0000000..866db6c --- /dev/null +++ b/ui/base/clipboard/clipboard_unittest.cc @@ -0,0 +1,432 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "build/build_config.h" + +#include <string> + +#include "base/basictypes.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "gfx/size.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" +#include "ui/base/clipboard/clipboard.h" +#include "ui/base/clipboard/scoped_clipboard_writer.h" + +#if defined(OS_WIN) +#include "base/message_loop.h" +#include "ui/base/clipboard/clipboard_util_win.h" +#endif + +#if defined(OS_WIN) || (defined(OS_POSIX) && !defined(OS_MACOSX)) +#include "base/pickle.h" +#endif + +namespace ui { + +#if defined(OS_WIN) +class ClipboardTest : public PlatformTest { + protected: + virtual void SetUp() { + message_loop_.reset(new MessageLoopForUI()); + } + virtual void TearDown() { + } + + private: + scoped_ptr<MessageLoop> message_loop_; +}; +#elif defined(OS_POSIX) +typedef PlatformTest ClipboardTest; +#endif // defined(OS_WIN) + +namespace { + +bool ClipboardContentsIsExpected(const string16& copied_markup, + const string16& pasted_markup) { +#if defined(OS_POSIX) + return pasted_markup.find(copied_markup) != string16::npos; +#else + return copied_markup == pasted_markup; +#endif +} + +} // namespace + +TEST_F(ClipboardTest, ClearTest) { + Clipboard clipboard; + + { + ScopedClipboardWriter clipboard_writer(&clipboard); + clipboard_writer.WriteText(ASCIIToUTF16("clear me")); + } + + { + ScopedClipboardWriter clipboard_writer(&clipboard); + clipboard_writer.WriteHTML(ASCIIToUTF16("<b>broom</b>"), ""); + } + + EXPECT_FALSE(clipboard.IsFormatAvailable( + Clipboard::GetPlainTextWFormatType(), Clipboard::BUFFER_STANDARD)); + EXPECT_FALSE(clipboard.IsFormatAvailable( + Clipboard::GetPlainTextFormatType(), Clipboard::BUFFER_STANDARD)); +} + +TEST_F(ClipboardTest, TextTest) { + Clipboard clipboard; + + string16 text(ASCIIToUTF16("This is a string16!#$")), text_result; + std::string ascii_text; + + { + ScopedClipboardWriter clipboard_writer(&clipboard); + clipboard_writer.WriteText(text); + } + + EXPECT_TRUE(clipboard.IsFormatAvailable( + Clipboard::GetPlainTextWFormatType(), Clipboard::BUFFER_STANDARD)); + EXPECT_TRUE(clipboard.IsFormatAvailable(Clipboard::GetPlainTextFormatType(), + Clipboard::BUFFER_STANDARD)); + clipboard.ReadText(Clipboard::BUFFER_STANDARD, &text_result); + + EXPECT_EQ(text, text_result); + clipboard.ReadAsciiText(Clipboard::BUFFER_STANDARD, &ascii_text); + EXPECT_EQ(UTF16ToUTF8(text), ascii_text); +} + +TEST_F(ClipboardTest, HTMLTest) { + Clipboard clipboard; + + string16 markup(ASCIIToUTF16("<string>Hi!</string>")), markup_result; + std::string url("http://www.example.com/"), url_result; + + { + ScopedClipboardWriter clipboard_writer(&clipboard); + clipboard_writer.WriteHTML(markup, url); + } + + EXPECT_TRUE(clipboard.IsFormatAvailable(Clipboard::GetHtmlFormatType(), + Clipboard::BUFFER_STANDARD)); + clipboard.ReadHTML(Clipboard::BUFFER_STANDARD, &markup_result, &url_result); + EXPECT_TRUE(ClipboardContentsIsExpected(markup, markup_result)); +#if defined(OS_WIN) + // TODO(playmobil): It's not clear that non windows clipboards need to support + // this. + EXPECT_EQ(url, url_result); +#endif // defined(OS_WIN) +} + +TEST_F(ClipboardTest, TrickyHTMLTest) { + Clipboard clipboard; + + string16 markup(ASCIIToUTF16("<em>Bye!<!--EndFragment --></em>")), + markup_result; + std::string url, url_result; + + { + ScopedClipboardWriter clipboard_writer(&clipboard); + clipboard_writer.WriteHTML(markup, url); + } + + EXPECT_TRUE(clipboard.IsFormatAvailable(Clipboard::GetHtmlFormatType(), + Clipboard::BUFFER_STANDARD)); + clipboard.ReadHTML(Clipboard::BUFFER_STANDARD, &markup_result, &url_result); + EXPECT_TRUE(ClipboardContentsIsExpected(markup, markup_result)); +#if defined(OS_WIN) + // TODO(playmobil): It's not clear that non windows clipboards need to support + // this. + EXPECT_EQ(url, url_result); +#endif // defined(OS_WIN) +} + +#if defined(OS_LINUX) +// Regression test for crbug.com/56298 (pasting empty HTML crashes Linux). +TEST_F(ClipboardTest, EmptyHTMLTest) { + Clipboard clipboard; + // ScopedClipboardWriter doesn't let us write empty data to the clipboard. + clipboard.clipboard_data_ = new Clipboard::TargetMap(); + // The 1 is so the compiler doesn't warn about allocating an empty array. + char* empty = new char[1]; + clipboard.InsertMapping("text/html", empty, 0U); + clipboard.SetGtkClipboard(); + + EXPECT_TRUE(clipboard.IsFormatAvailable(Clipboard::GetHtmlFormatType(), + Clipboard::BUFFER_STANDARD)); + string16 markup_result; + std::string url_result; + clipboard.ReadHTML(Clipboard::BUFFER_STANDARD, &markup_result, &url_result); + EXPECT_TRUE(ClipboardContentsIsExpected(string16(), markup_result)); +} +#endif + +// TODO(estade): Port the following test (decide what target we use for urls) +#if !defined(OS_POSIX) || defined(OS_MACOSX) +TEST_F(ClipboardTest, BookmarkTest) { + Clipboard clipboard; + + string16 title(ASCIIToUTF16("The Example Company")), title_result; + std::string url("http://www.example.com/"), url_result; + + { + ScopedClipboardWriter clipboard_writer(&clipboard); + clipboard_writer.WriteBookmark(title, url); + } + + EXPECT_TRUE(clipboard.IsFormatAvailable(Clipboard::GetUrlWFormatType(), + Clipboard::BUFFER_STANDARD)); + clipboard.ReadBookmark(&title_result, &url_result); + EXPECT_EQ(title, title_result); + EXPECT_EQ(url, url_result); +} +#endif // defined(OS_WIN) + +TEST_F(ClipboardTest, MultiFormatTest) { + Clipboard clipboard; + + string16 text(ASCIIToUTF16("Hi!")), text_result; + string16 markup(ASCIIToUTF16("<strong>Hi!</string>")), markup_result; + std::string url("http://www.example.com/"), url_result; + std::string ascii_text; + + { + ScopedClipboardWriter clipboard_writer(&clipboard); + clipboard_writer.WriteHTML(markup, url); + clipboard_writer.WriteText(text); + } + + EXPECT_TRUE(clipboard.IsFormatAvailable(Clipboard::GetHtmlFormatType(), + Clipboard::BUFFER_STANDARD)); + EXPECT_TRUE(clipboard.IsFormatAvailable( + Clipboard::GetPlainTextWFormatType(), Clipboard::BUFFER_STANDARD)); + EXPECT_TRUE(clipboard.IsFormatAvailable( + Clipboard::GetPlainTextFormatType(), Clipboard::BUFFER_STANDARD)); + clipboard.ReadHTML(Clipboard::BUFFER_STANDARD, &markup_result, &url_result); + EXPECT_TRUE(ClipboardContentsIsExpected(markup, markup_result)); +#if defined(OS_WIN) + // TODO(playmobil): It's not clear that non windows clipboards need to support + // this. + EXPECT_EQ(url, url_result); +#endif // defined(OS_WIN) + clipboard.ReadText(Clipboard::BUFFER_STANDARD, &text_result); + EXPECT_EQ(text, text_result); + clipboard.ReadAsciiText(Clipboard::BUFFER_STANDARD, &ascii_text); + EXPECT_EQ(UTF16ToUTF8(text), ascii_text); +} + +TEST_F(ClipboardTest, URLTest) { + Clipboard clipboard; + + string16 url(ASCIIToUTF16("http://www.google.com/")); + + { + ScopedClipboardWriter clipboard_writer(&clipboard); + clipboard_writer.WriteURL(url); + } + + EXPECT_TRUE(clipboard.IsFormatAvailable( + Clipboard::GetPlainTextWFormatType(), Clipboard::BUFFER_STANDARD)); + EXPECT_TRUE(clipboard.IsFormatAvailable(Clipboard::GetPlainTextFormatType(), + Clipboard::BUFFER_STANDARD)); + string16 text_result; + clipboard.ReadText(Clipboard::BUFFER_STANDARD, &text_result); + + EXPECT_EQ(text_result, url); + + std::string ascii_text; + clipboard.ReadAsciiText(Clipboard::BUFFER_STANDARD, &ascii_text); + EXPECT_EQ(UTF16ToUTF8(url), ascii_text); + +#if defined(OS_LINUX) + ascii_text.clear(); + clipboard.ReadAsciiText(Clipboard::BUFFER_SELECTION, &ascii_text); + EXPECT_EQ(UTF16ToUTF8(url), ascii_text); +#endif // defined(OS_LINUX) +} + +TEST_F(ClipboardTest, SharedBitmapTest) { + unsigned int fake_bitmap[] = { + 0x46155189, 0xF6A55C8D, 0x79845674, 0xFA57BD89, + 0x78FD46AE, 0x87C64F5A, 0x36EDC5AF, 0x4378F568, + 0x91E9F63A, 0xC31EA14F, 0x69AB32DF, 0x643A3FD1, + }; + gfx::Size fake_bitmap_size(3, 4); + uint32 bytes = sizeof(fake_bitmap); + + // Create shared memory region. + base::SharedMemory shared_buf; + ASSERT_TRUE(shared_buf.CreateAndMapAnonymous(bytes)); + memcpy(shared_buf.memory(), fake_bitmap, bytes); + base::SharedMemoryHandle handle_to_share; + base::ProcessHandle current_process = base::kNullProcessHandle; +#if defined(OS_WIN) + current_process = GetCurrentProcess(); +#endif + shared_buf.ShareToProcess(current_process, &handle_to_share); + ASSERT_TRUE(shared_buf.Unmap()); + + // Setup data for clipboard. + Clipboard::ObjectMapParam placeholder_param; + Clipboard::ObjectMapParam size_param; + const char* size_data = reinterpret_cast<const char*>(&fake_bitmap_size); + for (size_t i = 0; i < sizeof(fake_bitmap_size); ++i) + size_param.push_back(size_data[i]); + + Clipboard::ObjectMapParams params; + params.push_back(placeholder_param); + params.push_back(size_param); + + Clipboard::ObjectMap objects; + objects[Clipboard::CBF_SMBITMAP] = params; + Clipboard::ReplaceSharedMemHandle(&objects, handle_to_share, current_process); + + Clipboard clipboard; + clipboard.WriteObjects(objects); + + EXPECT_TRUE(clipboard.IsFormatAvailable(Clipboard::GetBitmapFormatType(), + Clipboard::BUFFER_STANDARD)); +} + +#if defined(OS_WIN) || (defined(OS_POSIX) && !defined(OS_MACOSX)) +TEST_F(ClipboardTest, DataTest) { + Clipboard clipboard; + const char* kFormat = "chromium/x-test-format"; + std::string payload("test string"); + Pickle write_pickle; + write_pickle.WriteString(payload); + + { + ScopedClipboardWriter clipboard_writer(&clipboard); + clipboard_writer.WritePickledData(write_pickle, kFormat); + } + + ASSERT_TRUE(clipboard.IsFormatAvailableByString( + kFormat, Clipboard::BUFFER_STANDARD)); + std::string output; + clipboard.ReadData(kFormat, &output); + ASSERT_FALSE(output.empty()); + + Pickle read_pickle(output.data(), output.size()); + void* iter = NULL; + std::string unpickled_string; + ASSERT_TRUE(read_pickle.ReadString(&iter, &unpickled_string)); + EXPECT_EQ(payload, unpickled_string); +} +#endif + +#if defined(OS_WIN) // Windows only tests. +TEST_F(ClipboardTest, HyperlinkTest) { + Clipboard clipboard; + + const std::string kTitle("The Example Company"); + const std::string kUrl("http://www.example.com/"); + const std::string kExpectedHtml("<a href=\"http://www.example.com/\">" + "The Example Company</a>"); + std::string url_result; + string16 html_result; + + { + ScopedClipboardWriter clipboard_writer(&clipboard); + clipboard_writer.WriteHyperlink(ASCIIToUTF16(kTitle), kUrl); + } + + EXPECT_TRUE(clipboard.IsFormatAvailable(Clipboard::GetHtmlFormatType(), + Clipboard::BUFFER_STANDARD)); + clipboard.ReadHTML(Clipboard::BUFFER_STANDARD, &html_result, &url_result); + EXPECT_EQ(ASCIIToUTF16(kExpectedHtml), html_result); +} + +TEST_F(ClipboardTest, WebSmartPasteTest) { + Clipboard clipboard; + + { + ScopedClipboardWriter clipboard_writer(&clipboard); + clipboard_writer.WriteWebSmartPaste(); + } + + EXPECT_TRUE(clipboard.IsFormatAvailable( + Clipboard::GetWebKitSmartPasteFormatType(), Clipboard::BUFFER_STANDARD)); +} + +TEST_F(ClipboardTest, BitmapTest) { + unsigned int fake_bitmap[] = { + 0x46155189, 0xF6A55C8D, 0x79845674, 0xFA57BD89, + 0x78FD46AE, 0x87C64F5A, 0x36EDC5AF, 0x4378F568, + 0x91E9F63A, 0xC31EA14F, 0x69AB32DF, 0x643A3FD1, + }; + + Clipboard clipboard; + + { + ScopedClipboardWriter clipboard_writer(&clipboard); + clipboard_writer.WriteBitmapFromPixels(fake_bitmap, gfx::Size(3, 4)); + } + + EXPECT_TRUE(clipboard.IsFormatAvailable(Clipboard::GetBitmapFormatType(), + Clipboard::BUFFER_STANDARD)); +} + +void HtmlTestHelper(const std::string& cf_html, + const std::string& expected_html) { + std::string html; + ClipboardUtil::CFHtmlToHtml(cf_html, &html, NULL); + EXPECT_EQ(html, expected_html); +} + +TEST_F(ClipboardTest, HtmlTest) { + // Test converting from CF_HTML format data with <!--StartFragment--> and + // <!--EndFragment--> comments, like from MS Word. + HtmlTestHelper("Version:1.0\r\n" + "StartHTML:0000000105\r\n" + "EndHTML:0000000199\r\n" + "StartFragment:0000000123\r\n" + "EndFragment:0000000161\r\n" + "\r\n" + "<html>\r\n" + "<body>\r\n" + "<!--StartFragment-->\r\n" + "\r\n" + "<p>Foo</p>\r\n" + "\r\n" + "<!--EndFragment-->\r\n" + "</body>\r\n" + "</html>\r\n\r\n", + "<p>Foo</p>"); + + // Test converting from CF_HTML format data without <!--StartFragment--> and + // <!--EndFragment--> comments, like from OpenOffice Writer. + HtmlTestHelper("Version:1.0\r\n" + "StartHTML:0000000105\r\n" + "EndHTML:0000000151\r\n" + "StartFragment:0000000121\r\n" + "EndFragment:0000000131\r\n" + "<html>\r\n" + "<body>\r\n" + "<p>Foo</p>\r\n" + "</body>\r\n" + "</html>\r\n\r\n", + "<p>Foo</p>"); +} +#endif // defined(OS_WIN) + +// Test writing all formats we have simultaneously. +TEST_F(ClipboardTest, WriteEverything) { + Clipboard clipboard; + + { + ScopedClipboardWriter writer(&clipboard); + writer.WriteText(UTF8ToUTF16("foo")); + writer.WriteURL(UTF8ToUTF16("foo")); + writer.WriteHTML(UTF8ToUTF16("foo"), "bar"); + writer.WriteBookmark(UTF8ToUTF16("foo"), "bar"); + writer.WriteHyperlink(ASCIIToUTF16("foo"), "bar"); + writer.WriteWebSmartPaste(); + // Left out: WriteFile, WriteFiles, WriteBitmapFromPixels, WritePickledData. + } + + // Passes if we don't crash. +} + +} // namespace ui diff --git a/ui/base/clipboard/clipboard_util_win.cc b/ui/base/clipboard/clipboard_util_win.cc new file mode 100644 index 0000000..c6dd6a3 --- /dev/null +++ b/ui/base/clipboard/clipboard_util_win.cc @@ -0,0 +1,544 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "ui/base/clipboard/clipboard_util_win.h" + +#include <shellapi.h> +#include <shlwapi.h> +#include <wininet.h> // For INTERNET_MAX_URL_LENGTH. + +#include "base/basictypes.h" +#include "base/logging.h" +#include "base/scoped_handle.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "base/win/scoped_hglobal.h" + +namespace ui { + +namespace { + +bool GetUrlFromHDrop(IDataObject* data_object, std::wstring* url, + std::wstring* title) { + DCHECK(data_object && url && title); + + STGMEDIUM medium; + if (FAILED(data_object->GetData(ClipboardUtil::GetCFHDropFormat(), &medium))) + return false; + + HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal)); + + if (!hdrop) + return false; + + bool success = false; + wchar_t filename[MAX_PATH]; + if (DragQueryFileW(hdrop, 0, filename, arraysize(filename))) { + wchar_t url_buffer[INTERNET_MAX_URL_LENGTH]; + if (0 == _wcsicmp(PathFindExtensionW(filename), L".url") && + GetPrivateProfileStringW(L"InternetShortcut", L"url", 0, url_buffer, + arraysize(url_buffer), filename)) { + url->assign(url_buffer); + PathRemoveExtension(filename); + title->assign(PathFindFileName(filename)); + success = true; + } + } + + DragFinish(hdrop); + GlobalUnlock(medium.hGlobal); + // We don't need to call ReleaseStgMedium here because as far as I can tell, + // DragFinish frees the hGlobal for us. + return success; +} + +void SplitUrlAndTitle(const std::wstring& str, + std::wstring* url, + std::wstring* title) { + DCHECK(url && title); + size_t newline_pos = str.find('\n'); + if (newline_pos != std::wstring::npos) { + url->assign(str, 0, newline_pos); + title->assign(str, newline_pos + 1, std::wstring::npos); + } else { + url->assign(str); + title->assign(str); + } +} + +bool GetFileUrl(IDataObject* data_object, std::wstring* url, + std::wstring* title) { + STGMEDIUM store; + if (SUCCEEDED(data_object->GetData(ClipboardUtil::GetFilenameWFormat(), + &store))) { + bool success = false; + { + // filename using unicode + base::win::ScopedHGlobal<wchar_t> data(store.hGlobal); + if (data.get() && data.get()[0] && + (PathFileExists(data.get()) || PathIsUNC(data.get()))) { + wchar_t file_url[INTERNET_MAX_URL_LENGTH]; + DWORD file_url_len = arraysize(file_url); + if (SUCCEEDED(::UrlCreateFromPathW(data.get(), file_url, &file_url_len, + 0))) { + url->assign(file_url); + title->assign(file_url); + success = true; + } + } + } + ReleaseStgMedium(&store); + if (success) + return true; + } + + if (SUCCEEDED(data_object->GetData(ClipboardUtil::GetFilenameFormat(), + &store))) { + bool success = false; + { + // filename using ascii + base::win::ScopedHGlobal<char> data(store.hGlobal); + if (data.get() && data.get()[0] && (PathFileExistsA(data.get()) || + PathIsUNCA(data.get()))) { + char file_url[INTERNET_MAX_URL_LENGTH]; + DWORD file_url_len = arraysize(file_url); + if (SUCCEEDED(::UrlCreateFromPathA(data.get(), file_url, &file_url_len, + 0))) { + url->assign(UTF8ToWide(file_url)); + title->assign(*url); + success = true; + } + } + } + ReleaseStgMedium(&store); + if (success) + return true; + } + return false; +} + +} // namespace + + +FORMATETC* ClipboardUtil::GetUrlFormat() { + static UINT cf = RegisterClipboardFormat(CFSTR_INETURLA); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetUrlWFormat() { + static UINT cf = RegisterClipboardFormat(CFSTR_INETURLW); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetMozUrlFormat() { + // The format is "URL\nTitle" + static UINT cf = RegisterClipboardFormat(L"text/x-moz-url"); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetPlainTextFormat() { + // We don't need to register this format since it's a built in format. + static FORMATETC format = {CF_TEXT, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetPlainTextWFormat() { + // We don't need to register this format since it's a built in format. + static FORMATETC format = {CF_UNICODETEXT, 0, DVASPECT_CONTENT, -1, + TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetFilenameWFormat() { + static UINT cf = RegisterClipboardFormat(CFSTR_FILENAMEW); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetFilenameFormat() { + static UINT cf = RegisterClipboardFormat(CFSTR_FILENAMEA); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetHtmlFormat() { + static UINT cf = RegisterClipboardFormat(L"HTML Format"); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetTextHtmlFormat() { + static UINT cf = RegisterClipboardFormat(L"text/html"); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetCFHDropFormat() { + // We don't need to register this format since it's a built in format. + static FORMATETC format = {CF_HDROP, 0, DVASPECT_CONTENT, -1, + TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetFileDescriptorFormat() { + static UINT cf = RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetFileContentFormatZero() { + static UINT cf = RegisterClipboardFormat(CFSTR_FILECONTENTS); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, 0, TYMED_HGLOBAL}; + return &format; +} + +FORMATETC* ClipboardUtil::GetWebKitSmartPasteFormat() { + static UINT cf = RegisterClipboardFormat(L"WebKit Smart Paste Format"); + static FORMATETC format = {cf, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL}; + return &format; +} + + +bool ClipboardUtil::HasUrl(IDataObject* data_object) { + DCHECK(data_object); + return SUCCEEDED(data_object->QueryGetData(GetMozUrlFormat())) || + SUCCEEDED(data_object->QueryGetData(GetUrlWFormat())) || + SUCCEEDED(data_object->QueryGetData(GetUrlFormat())) || + SUCCEEDED(data_object->QueryGetData(GetFilenameWFormat())) || + SUCCEEDED(data_object->QueryGetData(GetFilenameFormat())); +} + +bool ClipboardUtil::HasFilenames(IDataObject* data_object) { + DCHECK(data_object); + return SUCCEEDED(data_object->QueryGetData(GetCFHDropFormat())); +} + +bool ClipboardUtil::HasFileContents(IDataObject* data_object) { + DCHECK(data_object); + return SUCCEEDED(data_object->QueryGetData(GetFileContentFormatZero())); +} + +bool ClipboardUtil::HasHtml(IDataObject* data_object) { + DCHECK(data_object); + return SUCCEEDED(data_object->QueryGetData(GetHtmlFormat())) || + SUCCEEDED(data_object->QueryGetData(GetTextHtmlFormat())); +} + +bool ClipboardUtil::HasPlainText(IDataObject* data_object) { + DCHECK(data_object); + return SUCCEEDED(data_object->QueryGetData(GetPlainTextWFormat())) || + SUCCEEDED(data_object->QueryGetData(GetPlainTextFormat())); +} + + +bool ClipboardUtil::GetUrl(IDataObject* data_object, + std::wstring* url, std::wstring* title, bool convert_filenames) { + DCHECK(data_object && url && title); + if (!HasUrl(data_object)) + return false; + + // Try to extract a URL from |data_object| in a variety of formats. + STGMEDIUM store; + if (GetUrlFromHDrop(data_object, url, title)) + return true; + + if (SUCCEEDED(data_object->GetData(GetMozUrlFormat(), &store)) || + SUCCEEDED(data_object->GetData(GetUrlWFormat(), &store))) { + { + // Mozilla URL format or unicode URL + base::win::ScopedHGlobal<wchar_t> data(store.hGlobal); + SplitUrlAndTitle(data.get(), url, title); + } + ReleaseStgMedium(&store); + return true; + } + + if (SUCCEEDED(data_object->GetData(GetUrlFormat(), &store))) { + { + // URL using ascii + base::win::ScopedHGlobal<char> data(store.hGlobal); + SplitUrlAndTitle(UTF8ToWide(data.get()), url, title); + } + ReleaseStgMedium(&store); + return true; + } + + if (convert_filenames) { + return GetFileUrl(data_object, url, title); + } else { + return false; + } +} + +bool ClipboardUtil::GetFilenames(IDataObject* data_object, + std::vector<std::wstring>* filenames) { + DCHECK(data_object && filenames); + if (!HasFilenames(data_object)) + return false; + + STGMEDIUM medium; + if (FAILED(data_object->GetData(GetCFHDropFormat(), &medium))) + return false; + + HDROP hdrop = static_cast<HDROP>(GlobalLock(medium.hGlobal)); + if (!hdrop) + return false; + + const int kMaxFilenameLen = 4096; + const unsigned num_files = DragQueryFileW(hdrop, 0xffffffff, 0, 0); + for (unsigned int i = 0; i < num_files; ++i) { + wchar_t filename[kMaxFilenameLen]; + if (!DragQueryFileW(hdrop, i, filename, kMaxFilenameLen)) + continue; + filenames->push_back(filename); + } + + DragFinish(hdrop); + GlobalUnlock(medium.hGlobal); + // We don't need to call ReleaseStgMedium here because as far as I can tell, + // DragFinish frees the hGlobal for us. + return true; +} + +bool ClipboardUtil::GetPlainText(IDataObject* data_object, + std::wstring* plain_text) { + DCHECK(data_object && plain_text); + if (!HasPlainText(data_object)) + return false; + + STGMEDIUM store; + if (SUCCEEDED(data_object->GetData(GetPlainTextWFormat(), &store))) { + { + // Unicode text + base::win::ScopedHGlobal<wchar_t> data(store.hGlobal); + plain_text->assign(data.get()); + } + ReleaseStgMedium(&store); + return true; + } + + if (SUCCEEDED(data_object->GetData(GetPlainTextFormat(), &store))) { + { + // ascii text + base::win::ScopedHGlobal<char> data(store.hGlobal); + plain_text->assign(UTF8ToWide(data.get())); + } + ReleaseStgMedium(&store); + return true; + } + + // If a file is dropped on the window, it does not provide either of the + // plain text formats, so here we try to forcibly get a url. + std::wstring title; + return GetUrl(data_object, plain_text, &title, false); +} + +bool ClipboardUtil::GetHtml(IDataObject* data_object, + std::wstring* html, std::string* base_url) { + DCHECK(data_object && html && base_url); + + STGMEDIUM store; + if (SUCCEEDED(data_object->QueryGetData(GetHtmlFormat())) && + SUCCEEDED(data_object->GetData(GetHtmlFormat(), &store))) { + { + // MS CF html + base::win::ScopedHGlobal<char> data(store.hGlobal); + + std::string html_utf8; + CFHtmlToHtml(std::string(data.get(), data.Size()), &html_utf8, base_url); + html->assign(UTF8ToWide(html_utf8)); + } + ReleaseStgMedium(&store); + return true; + } + + if (FAILED(data_object->QueryGetData(GetTextHtmlFormat()))) + return false; + + if (FAILED(data_object->GetData(GetTextHtmlFormat(), &store))) + return false; + + { + // text/html + base::win::ScopedHGlobal<wchar_t> data(store.hGlobal); + html->assign(data.get()); + } + ReleaseStgMedium(&store); + return true; +} + +bool ClipboardUtil::GetFileContents(IDataObject* data_object, + std::wstring* filename, std::string* file_contents) { + DCHECK(data_object && filename && file_contents); + if (!SUCCEEDED(data_object->QueryGetData(GetFileContentFormatZero())) && + !SUCCEEDED(data_object->QueryGetData(GetFileDescriptorFormat()))) + return false; + + STGMEDIUM content; + // The call to GetData can be very slow depending on what is in + // |data_object|. + if (SUCCEEDED(data_object->GetData(GetFileContentFormatZero(), &content))) { + if (TYMED_HGLOBAL == content.tymed) { + base::win::ScopedHGlobal<char> data(content.hGlobal); + file_contents->assign(data.get(), data.Size()); + } + ReleaseStgMedium(&content); + } + + STGMEDIUM description; + if (SUCCEEDED(data_object->GetData(GetFileDescriptorFormat(), + &description))) { + { + base::win::ScopedHGlobal<FILEGROUPDESCRIPTOR> fgd(description.hGlobal); + // We expect there to be at least one file in here. + DCHECK_GE(fgd->cItems, 1u); + filename->assign(fgd->fgd[0].cFileName); + } + ReleaseStgMedium(&description); + } + return true; +} + +// HtmlToCFHtml and CFHtmlToHtml are based on similar methods in +// WebCore/platform/win/ClipboardUtilitiesWin.cpp. +/* + * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +// Helper method for converting from text/html to MS CF_HTML. +// Documentation for the CF_HTML format is available at +// http://msdn.microsoft.com/en-us/library/aa767917(VS.85).aspx +std::string ClipboardUtil::HtmlToCFHtml(const std::string& html, + const std::string& base_url) { + if (html.empty()) + return std::string(); + + #define MAX_DIGITS 10 + #define MAKE_NUMBER_FORMAT_1(digits) MAKE_NUMBER_FORMAT_2(digits) + #define MAKE_NUMBER_FORMAT_2(digits) "%0" #digits "u" + #define NUMBER_FORMAT MAKE_NUMBER_FORMAT_1(MAX_DIGITS) + + static const char* header = "Version:0.9\r\n" + "StartHTML:" NUMBER_FORMAT "\r\n" + "EndHTML:" NUMBER_FORMAT "\r\n" + "StartFragment:" NUMBER_FORMAT "\r\n" + "EndFragment:" NUMBER_FORMAT "\r\n"; + static const char* source_url_prefix = "SourceURL:"; + + static const char* start_markup = + "<html>\r\n<body>\r\n<!--StartFragment-->\r\n"; + static const char* end_markup = + "\r\n<!--EndFragment-->\r\n</body>\r\n</html>"; + + // Calculate offsets + size_t start_html_offset = strlen(header) - strlen(NUMBER_FORMAT) * 4 + + MAX_DIGITS * 4; + if (!base_url.empty()) { + start_html_offset += strlen(source_url_prefix) + + base_url.length() + 2; // Add 2 for \r\n. + } + size_t start_fragment_offset = start_html_offset + strlen(start_markup); + size_t end_fragment_offset = start_fragment_offset + html.length(); + size_t end_html_offset = end_fragment_offset + strlen(end_markup); + + std::string result = StringPrintf(header, start_html_offset, + end_html_offset, start_fragment_offset, end_fragment_offset); + if (!base_url.empty()) { + result.append(source_url_prefix); + result.append(base_url); + result.append("\r\n"); + } + result.append(start_markup); + result.append(html); + result.append(end_markup); + + #undef MAX_DIGITS + #undef MAKE_NUMBER_FORMAT_1 + #undef MAKE_NUMBER_FORMAT_2 + #undef NUMBER_FORMAT + + return result; +} + +// Helper method for converting from MS CF_HTML to text/html. +void ClipboardUtil::CFHtmlToHtml(const std::string& cf_html, + std::string* html, + std::string* base_url) { + // Obtain base_url if present. + if (base_url) { + static std::string src_url_str("SourceURL:"); + size_t line_start = cf_html.find(src_url_str); + if (line_start != std::string::npos) { + size_t src_end = cf_html.find("\n", line_start); + size_t src_start = line_start + src_url_str.length(); + if (src_end != std::string::npos && src_start != std::string::npos) { + *base_url = cf_html.substr(src_start, src_end - src_start); + TrimWhitespace(*base_url, TRIM_ALL, base_url); + } + } + } + + // Find the markup between "<!--StartFragment-->" and "<!--EndFragment-->". + // If the comments cannot be found, like copying from OpenOffice Writer, + // we simply fall back to using StartFragment/EndFragment bytecount values + // to get the markup. + if (html) { + size_t fragment_start = std::string::npos; + size_t fragment_end = std::string::npos; + + std::string cf_html_lower = StringToLowerASCII(cf_html); + size_t markup_start = cf_html_lower.find("<html", 0); + size_t tag_start = cf_html.find("<!--StartFragment", markup_start); + if (tag_start == std::string::npos) { + static std::string start_fragment_str("StartFragment:"); + size_t start_fragment_start = cf_html.find(start_fragment_str); + if (start_fragment_start != std::string::npos) { + fragment_start = static_cast<size_t>(atoi(cf_html.c_str() + + start_fragment_start + start_fragment_str.length())); + } + + static std::string end_fragment_str("EndFragment:"); + size_t end_fragment_start = cf_html.find(end_fragment_str); + if (end_fragment_start != std::string::npos) { + fragment_end = static_cast<size_t>(atoi(cf_html.c_str() + + end_fragment_start + end_fragment_str.length())); + } + } else { + fragment_start = cf_html.find('>', tag_start) + 1; + size_t tag_end = cf_html.rfind("<!--EndFragment", std::string::npos); + fragment_end = cf_html.rfind('<', tag_end); + } + if (fragment_start != std::string::npos && + fragment_end != std::string::npos) { + *html = cf_html.substr(fragment_start, fragment_end - fragment_start); + TrimWhitespace(*html, TRIM_ALL, html); + } + } +} + +} // namespace ui diff --git a/ui/base/clipboard/clipboard_util_win.h b/ui/base/clipboard/clipboard_util_win.h new file mode 100644 index 0000000..6c322ee --- /dev/null +++ b/ui/base/clipboard/clipboard_util_win.h @@ -0,0 +1,70 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +// +// Some helper functions for working with the clipboard and IDataObjects. + +#ifndef UI_BASE_CLIPBOARD_CLIPBOARD_UTIL_WIN_H_ +#define UI_BASE_CLIPBOARD_CLIPBOARD_UTIL_WIN_H_ +#pragma once + +#include <shlobj.h> +#include <string> +#include <vector> + +namespace ui { + +class ClipboardUtil { + public: + ///////////////////////////////////////////////////////////////////////////// + // Clipboard formats. + static FORMATETC* GetUrlFormat(); + static FORMATETC* GetUrlWFormat(); + static FORMATETC* GetMozUrlFormat(); + static FORMATETC* GetPlainTextFormat(); + static FORMATETC* GetPlainTextWFormat(); + static FORMATETC* GetFilenameFormat(); + static FORMATETC* GetFilenameWFormat(); + // MS HTML Format + static FORMATETC* GetHtmlFormat(); + // Firefox text/html + static FORMATETC* GetTextHtmlFormat(); + static FORMATETC* GetCFHDropFormat(); + static FORMATETC* GetFileDescriptorFormat(); + static FORMATETC* GetFileContentFormatZero(); + static FORMATETC* GetWebKitSmartPasteFormat(); + + ///////////////////////////////////////////////////////////////////////////// + // These methods check to see if |data_object| has the requested type. + // Returns true if it does. + static bool HasUrl(IDataObject* data_object); + static bool HasFilenames(IDataObject* data_object); + static bool HasPlainText(IDataObject* data_object); + static bool HasFileContents(IDataObject* data_object); + static bool HasHtml(IDataObject* data_object); + + ///////////////////////////////////////////////////////////////////////////// + // Helper methods to extract information from an IDataObject. These methods + // return true if the requested data type is found in |data_object|. + static bool GetUrl(IDataObject* data_object, + std::wstring* url, std::wstring* title, bool convert_filenames); + static bool GetFilenames(IDataObject* data_object, + std::vector<std::wstring>* filenames); + static bool GetPlainText(IDataObject* data_object, std::wstring* plain_text); + static bool GetHtml(IDataObject* data_object, std::wstring* text_html, + std::string* base_url); + static bool GetFileContents(IDataObject* data_object, + std::wstring* filename, + std::string* file_contents); + + // A helper method for converting between MS CF_HTML format and plain + // text/html. + static std::string HtmlToCFHtml(const std::string& html, + const std::string& base_url); + static void CFHtmlToHtml(const std::string& cf_html, std::string* html, + std::string* base_url); +}; + +} + +#endif // UI_BASE_CLIPBOARD_CLIPBOARD_UTIL_WIN_H_ diff --git a/ui/base/clipboard/clipboard_win.cc b/ui/base/clipboard/clipboard_win.cc new file mode 100644 index 0000000..7c21dcf --- /dev/null +++ b/ui/base/clipboard/clipboard_win.cc @@ -0,0 +1,606 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Many of these functions are based on those found in +// webkit/port/platform/PasteboardWin.cpp + +#include "ui/base/clipboard/clipboard.h" + +#include <shlobj.h> +#include <shellapi.h> + +#include "base/file_path.h" +#include "base/logging.h" +#include "base/message_loop.h" +#include "base/shared_memory.h" +#include "base/string_util.h" +#include "base/string_number_conversions.h" +#include "base/utf_string_conversions.h" +#include "gfx/size.h" +#include "ui/base/clipboard/clipboard_util_win.h" + +namespace ui { + +namespace { + +// A scoper to manage acquiring and automatically releasing the clipboard. +class ScopedClipboard { + public: + ScopedClipboard() : opened_(false) { } + + ~ScopedClipboard() { + if (opened_) + Release(); + } + + bool Acquire(HWND owner) { + const int kMaxAttemptsToOpenClipboard = 5; + + if (opened_) { + NOTREACHED(); + return false; + } + + // Attempt to open the clipboard, which will acquire the Windows clipboard + // lock. This may fail if another process currently holds this lock. + // We're willing to try a few times in the hopes of acquiring it. + // + // This turns out to be an issue when using remote desktop because the + // rdpclip.exe process likes to read what we've written to the clipboard and + // send it to the RDP client. If we open and close the clipboard in quick + // succession, we might be trying to open it while rdpclip.exe has it open, + // See Bug 815425. + // + // In fact, we believe we'll only spin this loop over remote desktop. In + // normal situations, the user is initiating clipboard operations and there + // shouldn't be contention. + + for (int attempts = 0; attempts < kMaxAttemptsToOpenClipboard; ++attempts) { + // If we didn't manage to open the clipboard, sleep a bit and be hopeful. + if (attempts != 0) + ::Sleep(5); + + if (::OpenClipboard(owner)) { + opened_ = true; + return true; + } + } + + // We failed to acquire the clipboard. + return false; + } + + void Release() { + if (opened_) { + ::CloseClipboard(); + opened_ = false; + } else { + NOTREACHED(); + } + } + + private: + bool opened_; +}; + +LRESULT CALLBACK ClipboardOwnerWndProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + LRESULT lresult = 0; + + switch (message) { + case WM_RENDERFORMAT: + // This message comes when SetClipboardData was sent a null data handle + // and now it's come time to put the data on the clipboard. + // We always set data, so there isn't a need to actually do anything here. + break; + case WM_RENDERALLFORMATS: + // This message comes when SetClipboardData was sent a null data handle + // and now this application is about to quit, so it must put data on + // the clipboard before it exits. + // We always set data, so there isn't a need to actually do anything here. + break; + case WM_DRAWCLIPBOARD: + break; + case WM_DESTROY: + break; + case WM_CHANGECBCHAIN: + break; + default: + lresult = DefWindowProc(hwnd, message, wparam, lparam); + break; + } + return lresult; +} + +template <typename charT> +HGLOBAL CreateGlobalData(const std::basic_string<charT>& str) { + HGLOBAL data = + ::GlobalAlloc(GMEM_MOVEABLE, ((str.size() + 1) * sizeof(charT))); + if (data) { + charT* raw_data = static_cast<charT*>(::GlobalLock(data)); + memcpy(raw_data, str.data(), str.size() * sizeof(charT)); + raw_data[str.size()] = '\0'; + ::GlobalUnlock(data); + } + return data; +}; + +} // namespace + +Clipboard::Clipboard() : create_window_(false) { + if (MessageLoop::current()->type() == MessageLoop::TYPE_UI) { + // Make a dummy HWND to be the clipboard's owner. + WNDCLASSEX wcex = {0}; + wcex.cbSize = sizeof(WNDCLASSEX); + wcex.lpfnWndProc = ClipboardOwnerWndProc; + wcex.hInstance = GetModuleHandle(NULL); + wcex.lpszClassName = L"ClipboardOwnerWindowClass"; + ::RegisterClassEx(&wcex); + create_window_ = true; + } + + clipboard_owner_ = NULL; +} + +Clipboard::~Clipboard() { + if (clipboard_owner_) + ::DestroyWindow(clipboard_owner_); + clipboard_owner_ = NULL; +} + +void Clipboard::WriteObjects(const ObjectMap& objects) { + WriteObjects(objects, NULL); +} + +void Clipboard::WriteObjects(const ObjectMap& objects, + base::ProcessHandle process) { + ScopedClipboard clipboard; + if (!clipboard.Acquire(GetClipboardWindow())) + return; + + ::EmptyClipboard(); + + for (ObjectMap::const_iterator iter = objects.begin(); + iter != objects.end(); ++iter) { + DispatchObject(static_cast<ObjectType>(iter->first), iter->second); + } +} + +void Clipboard::WriteText(const char* text_data, size_t text_len) { + string16 text; + UTF8ToUTF16(text_data, text_len, &text); + HGLOBAL glob = CreateGlobalData(text); + + WriteToClipboard(CF_UNICODETEXT, glob); +} + +void Clipboard::WriteHTML(const char* markup_data, + size_t markup_len, + const char* url_data, + size_t url_len) { + std::string markup(markup_data, markup_len); + std::string url; + + if (url_len > 0) + url.assign(url_data, url_len); + + std::string html_fragment = ClipboardUtil::HtmlToCFHtml(markup, url); + HGLOBAL glob = CreateGlobalData(html_fragment); + + WriteToClipboard(ClipboardUtil::GetHtmlFormat()->cfFormat, glob); +} + +void Clipboard::WriteBookmark(const char* title_data, + size_t title_len, + const char* url_data, + size_t url_len) { + std::string bookmark(title_data, title_len); + bookmark.append(1, L'\n'); + bookmark.append(url_data, url_len); + + string16 wide_bookmark = UTF8ToWide(bookmark); + HGLOBAL glob = CreateGlobalData(wide_bookmark); + + WriteToClipboard(ClipboardUtil::GetUrlWFormat()->cfFormat, glob); +} + +void Clipboard::WriteWebSmartPaste() { + DCHECK(clipboard_owner_); + ::SetClipboardData(ClipboardUtil::GetWebKitSmartPasteFormat()->cfFormat, + NULL); +} + +void Clipboard::WriteBitmap(const char* pixel_data, const char* size_data) { + const gfx::Size* size = reinterpret_cast<const gfx::Size*>(size_data); + HDC dc = ::GetDC(NULL); + + // This doesn't actually cost us a memcpy when the bitmap comes from the + // renderer as we load it into the bitmap using setPixels which just sets a + // pointer. Someone has to memcpy it into GDI, it might as well be us here. + + // TODO(darin): share data in gfx/bitmap_header.cc somehow + BITMAPINFO bm_info = {0}; + bm_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bm_info.bmiHeader.biWidth = size->width(); + bm_info.bmiHeader.biHeight = -size->height(); // sets vertical orientation + bm_info.bmiHeader.biPlanes = 1; + bm_info.bmiHeader.biBitCount = 32; + bm_info.bmiHeader.biCompression = BI_RGB; + + // ::CreateDIBSection allocates memory for us to copy our bitmap into. + // Unfortunately, we can't write the created bitmap to the clipboard, + // (see http://msdn2.microsoft.com/en-us/library/ms532292.aspx) + void *bits; + HBITMAP source_hbitmap = + ::CreateDIBSection(dc, &bm_info, DIB_RGB_COLORS, &bits, NULL, 0); + + if (bits && source_hbitmap) { + // Copy the bitmap out of shared memory and into GDI + memcpy(bits, pixel_data, 4 * size->width() * size->height()); + + // Now we have an HBITMAP, we can write it to the clipboard + WriteBitmapFromHandle(source_hbitmap, *size); + } + + ::DeleteObject(source_hbitmap); + ::ReleaseDC(NULL, dc); +} + +void Clipboard::WriteBitmapFromHandle(HBITMAP source_hbitmap, + const gfx::Size& size) { + // We would like to just call ::SetClipboardData on the source_hbitmap, + // but that bitmap might not be of a sort we can write to the clipboard. + // For this reason, we create a new bitmap, copy the bits over, and then + // write that to the clipboard. + + HDC dc = ::GetDC(NULL); + HDC compatible_dc = ::CreateCompatibleDC(NULL); + HDC source_dc = ::CreateCompatibleDC(NULL); + + // This is the HBITMAP we will eventually write to the clipboard + HBITMAP hbitmap = ::CreateCompatibleBitmap(dc, size.width(), size.height()); + if (!hbitmap) { + // Failed to create the bitmap + ::DeleteDC(compatible_dc); + ::DeleteDC(source_dc); + ::ReleaseDC(NULL, dc); + return; + } + + HBITMAP old_hbitmap = (HBITMAP)SelectObject(compatible_dc, hbitmap); + HBITMAP old_source = (HBITMAP)SelectObject(source_dc, source_hbitmap); + + // Now we need to blend it into an HBITMAP we can place on the clipboard + BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; + ::GdiAlphaBlend(compatible_dc, 0, 0, size.width(), size.height(), + source_dc, 0, 0, size.width(), size.height(), bf); + + // Clean up all the handles we just opened + ::SelectObject(compatible_dc, old_hbitmap); + ::SelectObject(source_dc, old_source); + ::DeleteObject(old_hbitmap); + ::DeleteObject(old_source); + ::DeleteDC(compatible_dc); + ::DeleteDC(source_dc); + ::ReleaseDC(NULL, dc); + + WriteToClipboard(CF_BITMAP, hbitmap); +} + +void Clipboard::WriteData(const char* format_name, size_t format_len, + const char* data_data, size_t data_len) { + std::string format(format_name, format_len); + CLIPFORMAT clip_format = + ::RegisterClipboardFormat(ASCIIToWide(format).c_str()); + + HGLOBAL hdata = ::GlobalAlloc(GMEM_MOVEABLE, data_len); + if (!hdata) + return; + + char* data = static_cast<char*>(::GlobalLock(hdata)); + memcpy(data, data_data, data_len); + ::GlobalUnlock(data); + WriteToClipboard(clip_format, hdata); +} + +void Clipboard::WriteToClipboard(unsigned int format, HANDLE handle) { + DCHECK(clipboard_owner_); + if (handle && !::SetClipboardData(format, handle)) { + DCHECK(ERROR_CLIPBOARD_NOT_OPEN != GetLastError()); + FreeData(format, handle); + } +} + +bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format, + Clipboard::Buffer buffer) const { + DCHECK_EQ(buffer, BUFFER_STANDARD); + int f; + if (!base::StringToInt(format, &f)) + return false; + return ::IsClipboardFormatAvailable(f) != FALSE; +} + +bool Clipboard::IsFormatAvailableByString( + const std::string& ascii_format, Clipboard::Buffer buffer) const { + DCHECK_EQ(buffer, BUFFER_STANDARD); + std::wstring wide_format = ASCIIToWide(ascii_format); + CLIPFORMAT format = ::RegisterClipboardFormat(wide_format.c_str()); + return ::IsClipboardFormatAvailable(format) != FALSE; +} + +void Clipboard::ReadText(Clipboard::Buffer buffer, string16* result) const { + DCHECK_EQ(buffer, BUFFER_STANDARD); + if (!result) { + NOTREACHED(); + return; + } + + result->clear(); + + // Acquire the clipboard. + ScopedClipboard clipboard; + if (!clipboard.Acquire(GetClipboardWindow())) + return; + + HANDLE data = ::GetClipboardData(CF_UNICODETEXT); + if (!data) + return; + + result->assign(static_cast<const char16*>(::GlobalLock(data))); + ::GlobalUnlock(data); +} + +void Clipboard::ReadAsciiText(Clipboard::Buffer buffer, + std::string* result) const { + DCHECK_EQ(buffer, BUFFER_STANDARD); + if (!result) { + NOTREACHED(); + return; + } + + result->clear(); + + // Acquire the clipboard. + ScopedClipboard clipboard; + if (!clipboard.Acquire(GetClipboardWindow())) + return; + + HANDLE data = ::GetClipboardData(CF_TEXT); + if (!data) + return; + + result->assign(static_cast<const char*>(::GlobalLock(data))); + ::GlobalUnlock(data); +} + +void Clipboard::ReadHTML(Clipboard::Buffer buffer, string16* markup, + std::string* src_url) const { + DCHECK_EQ(buffer, BUFFER_STANDARD); + if (markup) + markup->clear(); + + if (src_url) + src_url->clear(); + + // Acquire the clipboard. + ScopedClipboard clipboard; + if (!clipboard.Acquire(GetClipboardWindow())) + return; + + HANDLE data = ::GetClipboardData(ClipboardUtil::GetHtmlFormat()->cfFormat); + if (!data) + return; + + std::string html_fragment(static_cast<const char*>(::GlobalLock(data))); + ::GlobalUnlock(data); + + std::string markup_utf8; + ClipboardUtil::CFHtmlToHtml(html_fragment, markup ? &markup_utf8 : NULL, + src_url); + if (markup) + markup->assign(UTF8ToWide(markup_utf8)); +} + +void Clipboard::ReadBookmark(string16* title, std::string* url) const { + if (title) + title->clear(); + + if (url) + url->clear(); + + // Acquire the clipboard. + ScopedClipboard clipboard; + if (!clipboard.Acquire(GetClipboardWindow())) + return; + + HANDLE data = ::GetClipboardData(ClipboardUtil::GetUrlWFormat()->cfFormat); + if (!data) + return; + + string16 bookmark(static_cast<const char16*>(::GlobalLock(data))); + ::GlobalUnlock(data); + + ParseBookmarkClipboardFormat(bookmark, title, url); +} + +// Read a file in HDROP format from the clipboard. +void Clipboard::ReadFile(FilePath* file) const { + if (!file) { + NOTREACHED(); + return; + } + + *file = FilePath(); + std::vector<FilePath> files; + ReadFiles(&files); + + // Take the first file, if available. + if (!files.empty()) + *file = files[0]; +} + +// Read a set of files in HDROP format from the clipboard. +void Clipboard::ReadFiles(std::vector<FilePath>* files) const { + if (!files) { + NOTREACHED(); + return; + } + + files->clear(); + + ScopedClipboard clipboard; + if (!clipboard.Acquire(GetClipboardWindow())) + return; + + HDROP drop = static_cast<HDROP>(::GetClipboardData(CF_HDROP)); + if (!drop) + return; + + // Count of files in the HDROP. + int count = ::DragQueryFile(drop, 0xffffffff, NULL, 0); + + if (count) { + for (int i = 0; i < count; ++i) { + int size = ::DragQueryFile(drop, i, NULL, 0) + 1; + std::wstring file; + ::DragQueryFile(drop, i, WriteInto(&file, size), size); + files->push_back(FilePath(file)); + } + } +} + +void Clipboard::ReadData(const std::string& format, std::string* result) { + if (!result) { + NOTREACHED(); + return; + } + + CLIPFORMAT clip_format = + ::RegisterClipboardFormat(ASCIIToWide(format).c_str()); + + ScopedClipboard clipboard; + if (!clipboard.Acquire(GetClipboardWindow())) + return; + + HANDLE data = ::GetClipboardData(clip_format); + if (!data) + return; + + result->assign(static_cast<const char*>(::GlobalLock(data)), + ::GlobalSize(data)); + ::GlobalUnlock(data); +} + +// static +void Clipboard::ParseBookmarkClipboardFormat(const string16& bookmark, + string16* title, + std::string* url) { + const string16 kDelim = ASCIIToUTF16("\r\n"); + + const size_t title_end = bookmark.find_first_of(kDelim); + if (title) + title->assign(bookmark.substr(0, title_end)); + + if (url) { + const size_t url_start = bookmark.find_first_not_of(kDelim, title_end); + if (url_start != string16::npos) + *url = UTF16ToUTF8(bookmark.substr(url_start, string16::npos)); + } +} + +// static +Clipboard::FormatType Clipboard::GetUrlFormatType() { + return base::IntToString(ClipboardUtil::GetUrlFormat()->cfFormat); +} + +// static +Clipboard::FormatType Clipboard::GetUrlWFormatType() { + return base::IntToString(ClipboardUtil::GetUrlWFormat()->cfFormat); +} + +// static +Clipboard::FormatType Clipboard::GetMozUrlFormatType() { + return base::IntToString(ClipboardUtil::GetMozUrlFormat()->cfFormat); +} + +// static +Clipboard::FormatType Clipboard::GetPlainTextFormatType() { + return base::IntToString(ClipboardUtil::GetPlainTextFormat()->cfFormat); +} + +// static +Clipboard::FormatType Clipboard::GetPlainTextWFormatType() { + return base::IntToString(ClipboardUtil::GetPlainTextWFormat()->cfFormat); +} + +// static +Clipboard::FormatType Clipboard::GetFilenameFormatType() { + return base::IntToString(ClipboardUtil::GetFilenameFormat()->cfFormat); +} + +// static +Clipboard::FormatType Clipboard::GetFilenameWFormatType() { + return base::IntToString(ClipboardUtil::GetFilenameWFormat()->cfFormat); +} + +// MS HTML Format +// static +Clipboard::FormatType Clipboard::GetHtmlFormatType() { + return base::IntToString(ClipboardUtil::GetHtmlFormat()->cfFormat); +} + +// static +Clipboard::FormatType Clipboard::GetBitmapFormatType() { + return base::IntToString(CF_BITMAP); +} + +// Firefox text/html +// static +Clipboard::FormatType Clipboard::GetTextHtmlFormatType() { + return base::IntToString(ClipboardUtil::GetTextHtmlFormat()->cfFormat); +} + +// static +Clipboard::FormatType Clipboard::GetCFHDropFormatType() { + return base::IntToString(ClipboardUtil::GetCFHDropFormat()->cfFormat); +} + +// static +Clipboard::FormatType Clipboard::GetFileDescriptorFormatType() { + return base::IntToString(ClipboardUtil::GetFileDescriptorFormat()->cfFormat); +} + +// static +Clipboard::FormatType Clipboard::GetFileContentFormatZeroType() { + return base::IntToString(ClipboardUtil::GetFileContentFormatZero()->cfFormat); +} + +// static +Clipboard::FormatType Clipboard::GetWebKitSmartPasteFormatType() { + return base::IntToString( + ClipboardUtil::GetWebKitSmartPasteFormat()->cfFormat); +} + +// static +void Clipboard::FreeData(unsigned int format, HANDLE data) { + if (format == CF_BITMAP) + ::DeleteObject(static_cast<HBITMAP>(data)); + else + ::GlobalFree(data); +} + +HWND Clipboard::GetClipboardWindow() const { + if (!clipboard_owner_ && create_window_) { + clipboard_owner_ = ::CreateWindow(L"ClipboardOwnerWindowClass", + L"ClipboardOwnerWindow", + 0, 0, 0, 0, 0, + HWND_MESSAGE, + 0, 0, 0); + } + return clipboard_owner_; +} + +} // namespace ui diff --git a/ui/base/clipboard/scoped_clipboard_writer.cc b/ui/base/clipboard/scoped_clipboard_writer.cc new file mode 100644 index 0000000..43ffaad --- /dev/null +++ b/ui/base/clipboard/scoped_clipboard_writer.cc @@ -0,0 +1,140 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file implements the ScopedClipboardWriter class. Documentation on its +// purpose can be found in our header. Documentation on the format of the +// parameters for each clipboard target can be found in clipboard.h. + +#include "ui/base/clipboard/scoped_clipboard_writer.h" + +#include "base/pickle.h" +#include "base/utf_string_conversions.h" +#include "gfx/size.h" + +namespace ui { + +ScopedClipboardWriter::ScopedClipboardWriter(Clipboard* clipboard) + : clipboard_(clipboard) { +} + +ScopedClipboardWriter::~ScopedClipboardWriter() { + if (!objects_.empty() && clipboard_) { + clipboard_->WriteObjects(objects_); + if (url_text_.length()) + clipboard_->DidWriteURL(url_text_); + } +} + +void ScopedClipboardWriter::WriteText(const string16& text) { + WriteTextOrURL(text, false); +} + +void ScopedClipboardWriter::WriteURL(const string16& text) { + WriteTextOrURL(text, true); +} + +void ScopedClipboardWriter::WriteHTML(const string16& markup, + const std::string& source_url) { + if (markup.empty()) + return; + + std::string utf8_markup = UTF16ToUTF8(markup); + + Clipboard::ObjectMapParams parameters; + parameters.push_back( + Clipboard::ObjectMapParam(utf8_markup.begin(), + utf8_markup.end())); + if (!source_url.empty()) { + parameters.push_back(Clipboard::ObjectMapParam(source_url.begin(), + source_url.end())); + } + + objects_[Clipboard::CBF_HTML] = parameters; +} + +void ScopedClipboardWriter::WriteBookmark(const string16& bookmark_title, + const std::string& url) { + if (bookmark_title.empty() || url.empty()) + return; + + std::string utf8_markup = UTF16ToUTF8(bookmark_title); + + Clipboard::ObjectMapParams parameters; + parameters.push_back(Clipboard::ObjectMapParam(utf8_markup.begin(), + utf8_markup.end())); + parameters.push_back(Clipboard::ObjectMapParam(url.begin(), url.end())); + objects_[Clipboard::CBF_BOOKMARK] = parameters; +} + +void ScopedClipboardWriter::WriteHyperlink(const string16& anchor_text, + const std::string& url) { + if (anchor_text.empty() || url.empty()) + return; + + // Construct the hyperlink. + std::string html("<a href=\""); + html.append(url); + html.append("\">"); + html.append(UTF16ToUTF8(anchor_text)); + html.append("</a>"); + WriteHTML(UTF8ToUTF16(html), std::string()); +} + +void ScopedClipboardWriter::WriteWebSmartPaste() { + objects_[Clipboard::CBF_WEBKIT] = Clipboard::ObjectMapParams(); +} + +void ScopedClipboardWriter::WriteBitmapFromPixels(const void* pixels, + const gfx::Size& size) { + Clipboard::ObjectMapParam pixels_parameter, size_parameter; + const char* pixels_data = reinterpret_cast<const char*>(pixels); + size_t pixels_length = 4 * size.width() * size.height(); + for (size_t i = 0; i < pixels_length; i++) + pixels_parameter.push_back(pixels_data[i]); + + const char* size_data = reinterpret_cast<const char*>(&size); + size_t size_length = sizeof(gfx::Size); + for (size_t i = 0; i < size_length; i++) + size_parameter.push_back(size_data[i]); + + Clipboard::ObjectMapParams parameters; + parameters.push_back(pixels_parameter); + parameters.push_back(size_parameter); + objects_[Clipboard::CBF_BITMAP] = parameters; +} + +void ScopedClipboardWriter::WritePickledData(const Pickle& pickle, + Clipboard::FormatType format) { + Clipboard::ObjectMapParam format_parameter(format.begin(), format.end()); + Clipboard::ObjectMapParam data_parameter; + + data_parameter.resize(pickle.size()); + memcpy(const_cast<char*>(&data_parameter.front()), + pickle.data(), pickle.size()); + + Clipboard::ObjectMapParams parameters; + parameters.push_back(format_parameter); + parameters.push_back(data_parameter); + objects_[Clipboard::CBF_DATA] = parameters; +} + +void ScopedClipboardWriter::WriteTextOrURL(const string16& text, bool is_url) { + if (text.empty()) + return; + + std::string utf8_text = UTF16ToUTF8(text); + + Clipboard::ObjectMapParams parameters; + parameters.push_back(Clipboard::ObjectMapParam(utf8_text.begin(), + utf8_text.end())); + objects_[Clipboard::CBF_TEXT] = parameters; + + if (is_url) { + url_text_ = utf8_text; + } else { + url_text_.clear(); + } +} + +} // namespace ui diff --git a/ui/base/clipboard/scoped_clipboard_writer.h b/ui/base/clipboard/scoped_clipboard_writer.h new file mode 100644 index 0000000..4bd2da7 --- /dev/null +++ b/ui/base/clipboard/scoped_clipboard_writer.h @@ -0,0 +1,85 @@ +// Copyright (c) 2011 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file declares the ScopedClipboardWriter class, a wrapper around +// the Clipboard class which simplifies writing data to the system clipboard. +// Upon deletion the class atomically writes all data to |clipboard_|, +// avoiding any potential race condition with other processes that are also +// writing to the system clipboard. + +#ifndef UI_BASE_CLIPBOARD_SCOPED_CLIPBOARD_WRITER_H_ +#define UI_BASE_CLIPBOARD_SCOPED_CLIPBOARD_WRITER_H_ +#pragma once + +#include <string> + +#include "base/string16.h" +#include "ui/base/clipboard/clipboard.h" + +class Pickle; + +namespace ui { + +// This class is a wrapper for |Clipboard| that handles packing data +// into a Clipboard::ObjectMap. +// NB: You should probably NOT be using this class if you include +// webkit_glue.h. Use ScopedClipboardWriterGlue instead. +class ScopedClipboardWriter { + public: + // Create an instance that is a simple wrapper around clipboard. + explicit ScopedClipboardWriter(Clipboard* clipboard); + + ~ScopedClipboardWriter(); + + // Converts |text| to UTF-8 and adds it to the clipboard. + void WriteText(const string16& text); + + // Converts the text of the URL to UTF-8 and adds it to the clipboard, then + // notifies the Clipboard that we just wrote a URL. + void WriteURL(const string16& text); + + // Adds HTML to the clipboard. The url parameter is optional, but especially + // useful if the HTML fragment contains relative links. + void WriteHTML(const string16& markup, const std::string& source_url); + + // Adds a bookmark to the clipboard. + void WriteBookmark(const string16& bookmark_title, + const std::string& url); + + // Adds an html hyperlink (<a href>) to the clipboard. |anchor_text| should + // be escaped prior to being passed in. + void WriteHyperlink(const string16& anchor_text, const std::string& url); + + // Used by WebKit to determine whether WebKit wrote the clipboard last + void WriteWebSmartPaste(); + + // Adds a bitmap to the clipboard + // Pixel format is assumed to be 32-bit BI_RGB. + void WriteBitmapFromPixels(const void* pixels, const gfx::Size& size); + + // Adds arbitrary data to clipboard. + void WritePickledData(const Pickle& pickle, Clipboard::FormatType format); + + protected: + // Converts |text| to UTF-8 and adds it to the clipboard. If it's a URL, we + // also notify the clipboard of that fact. + void WriteTextOrURL(const string16& text, bool is_url); + + // We accumulate the data passed to the various targets in the |objects_| + // vector, and pass it to Clipboard::WriteObjects() during object destruction. + Clipboard::ObjectMap objects_; + Clipboard* clipboard_; + + // We keep around the UTF-8 text of the URL in order to pass it to + // Clipboard::DidWriteURL(). + std::string url_text_; + + private: + DISALLOW_COPY_AND_ASSIGN(ScopedClipboardWriter); +}; + +} // namespace ui + +#endif // UI_BASE_CLIPBOARD_SCOPED_CLIPBOARD_WRITER_H_ + diff --git a/ui/base/ui_base.gypi b/ui/base/ui_base.gypi index f441c6f..4844945 100644 --- a/ui/base/ui_base.gypi +++ b/ui/base/ui_base.gypi @@ -69,6 +69,15 @@ 'animation/throb_animation.h', 'animation/tween.cc', 'animation/tween.h', + 'clipboard/clipboard.cc', + 'clipboard/clipboard.h', + 'clipboard/clipboard_linux.cc', + 'clipboard/clipboard_mac.mm', + 'clipboard/clipboard_util_win.cc', + 'clipboard/clipboard_util_win.h', + 'clipboard/clipboard_win.cc', + 'clipboard/scoped_clipboard_writer.cc', + 'clipboard/scoped_clipboard_writer.h', ], }, { @@ -86,6 +95,7 @@ 'animation/animation_unittest.cc', 'animation/multi_animation_unittest.cc', 'animation/slide_animation_unittest.cc', + 'clipboard/clipboard_unittest.cc', 'run_all_unittests.cc', 'test_suite.h', ], |