diff options
author | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-12 19:00:48 +0000 |
---|---|---|
committer | ben@chromium.org <ben@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2011-01-12 19:00:48 +0000 |
commit | 41d2e4af3daa1d1ca8f8cbfa6788604a4c9913b9 (patch) | |
tree | fa82bb18c231744e72ffd334217be1e256c9e849 /ui | |
parent | 356a50ef463bb52fdd9d5700ca11b4b83d4809bc (diff) | |
download | chromium_src-41d2e4af3daa1d1ca8f8cbfa6788604a4c9913b9.zip chromium_src-41d2e4af3daa1d1ca8f8cbfa6788604a4c9913b9.tar.gz chromium_src-41d2e4af3daa1d1ca8f8cbfa6788604a4c9913b9.tar.bz2 |
Move OSExchangeData from src/app to src/ui/base/dragdrop
BUG=none
TEST=none
TBR=brettw
Review URL: http://codereview.chromium.org/6200005
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@71205 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui')
-rw-r--r-- | ui/DEPS | 6 | ||||
-rw-r--r-- | ui/base/dragdrop/os_exchange_data.cc | 148 | ||||
-rw-r--r-- | ui/base/dragdrop/os_exchange_data.h | 199 | ||||
-rw-r--r-- | ui/base/dragdrop/os_exchange_data_provider_gtk.cc | 248 | ||||
-rw-r--r-- | ui/base/dragdrop/os_exchange_data_provider_gtk.h | 120 | ||||
-rw-r--r-- | ui/base/dragdrop/os_exchange_data_provider_win.cc | 925 | ||||
-rw-r--r-- | ui/base/dragdrop/os_exchange_data_provider_win.h | 180 | ||||
-rw-r--r-- | ui/base/dragdrop/os_exchange_data_win_unittest.cc | 377 |
8 files changed, 2203 insertions, 0 deletions
@@ -1,4 +1,10 @@ include_rules = [ "+gfx", + "+net", "+third_party/mozilla", + + # Temporary until all of src/app is consumed into src/ui + "+app", + "+grit/app_locale_settings.h", + "+grit/app_strings.h", ] diff --git a/ui/base/dragdrop/os_exchange_data.cc b/ui/base/dragdrop/os_exchange_data.cc new file mode 100644 index 0000000..e5701c3 --- /dev/null +++ b/ui/base/dragdrop/os_exchange_data.cc @@ -0,0 +1,148 @@ +// 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/dragdrop/os_exchange_data.h" + +#include "base/pickle.h" +#include "googleurl/src/gurl.h" + +namespace ui { + +OSExchangeData::DownloadFileInfo::DownloadFileInfo( + const FilePath& filename, + DownloadFileProvider* downloader) + : filename(filename), + downloader(downloader) { +} + +OSExchangeData::DownloadFileInfo::~DownloadFileInfo() {} + +OSExchangeData::OSExchangeData() : provider_(CreateProvider()) { +} + +OSExchangeData::OSExchangeData(Provider* provider) : provider_(provider) { +} + +OSExchangeData::~OSExchangeData() { +} + +void OSExchangeData::SetString(const std::wstring& data) { + provider_->SetString(data); +} + +void OSExchangeData::SetURL(const GURL& url, const std::wstring& title) { + provider_->SetURL(url, title); +} + +void OSExchangeData::SetFilename(const std::wstring& full_path) { + provider_->SetFilename(full_path); +} + +void OSExchangeData::SetPickledData(CustomFormat format, const Pickle& data) { + provider_->SetPickledData(format, data); +} + +bool OSExchangeData::GetString(std::wstring* data) const { + return provider_->GetString(data); +} + +bool OSExchangeData::GetURLAndTitle(GURL* url, std::wstring* title) const { + return provider_->GetURLAndTitle(url, title); +} + +bool OSExchangeData::GetFilename(std::wstring* full_path) const { + return provider_->GetFilename(full_path); +} + +bool OSExchangeData::GetPickledData(CustomFormat format, Pickle* data) const { + return provider_->GetPickledData(format, data); +} + +bool OSExchangeData::HasString() const { + return provider_->HasString(); +} + +bool OSExchangeData::HasURL() const { + return provider_->HasURL(); +} + +bool OSExchangeData::HasFile() const { + return provider_->HasFile(); +} + +bool OSExchangeData::HasCustomFormat(CustomFormat format) const { + return provider_->HasCustomFormat(format); +} + +bool OSExchangeData::HasAllFormats( + int formats, + const std::set<CustomFormat>& custom_formats) const { + if ((formats & STRING) != 0 && !HasString()) + return false; + if ((formats & URL) != 0 && !HasURL()) + return false; +#if defined(OS_WIN) + if ((formats & FILE_CONTENTS) != 0 && !provider_->HasFileContents()) + return false; + if ((formats & HTML) != 0 && !provider_->HasHtml()) + return false; +#endif + if ((formats & FILE_NAME) != 0 && !provider_->HasFile()) + return false; + for (std::set<CustomFormat>::const_iterator i = custom_formats.begin(); + i != custom_formats.end(); ++i) { + if (!HasCustomFormat(*i)) + return false; + } + return true; +} + +bool OSExchangeData::HasAnyFormat( + int formats, + const std::set<CustomFormat>& custom_formats) const { + if ((formats & STRING) != 0 && HasString()) + return true; + if ((formats & URL) != 0 && HasURL()) + return true; +#if defined(OS_WIN) + if ((formats & FILE_CONTENTS) != 0 && provider_->HasFileContents()) + return true; + if ((formats & HTML) != 0 && provider_->HasHtml()) + return true; +#endif + if ((formats & FILE_NAME) != 0 && provider_->HasFile()) + return true; + for (std::set<CustomFormat>::const_iterator i = custom_formats.begin(); + i != custom_formats.end(); ++i) { + if (HasCustomFormat(*i)) + return true; + } + return false; +} + +#if defined(OS_WIN) +void OSExchangeData::SetFileContents(const std::wstring& filename, + const std::string& file_contents) { + provider_->SetFileContents(filename, file_contents); +} + +void OSExchangeData::SetHtml(const std::wstring& html, const GURL& base_url) { + provider_->SetHtml(html, base_url); +} + +bool OSExchangeData::GetFileContents(std::wstring* filename, + std::string* file_contents) const { + return provider_->GetFileContents(filename, file_contents); +} + +bool OSExchangeData::GetHtml(std::wstring* html, GURL* base_url) const { + return provider_->GetHtml(html, base_url); +} + +void OSExchangeData::SetDownloadFileInfo(const DownloadFileInfo& download) { + return provider_->SetDownloadFileInfo(download); +} +#endif + +} // namespace ui diff --git a/ui/base/dragdrop/os_exchange_data.h b/ui/base/dragdrop/os_exchange_data.h new file mode 100644 index 0000000..c04737d --- /dev/null +++ b/ui/base/dragdrop/os_exchange_data.h @@ -0,0 +1,199 @@ +// 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_DRAGDROP_OS_EXCHANGE_DATA_H_ +#define UI_BASE_DRAGDROP_OS_EXCHANGE_DATA_H_ +#pragma once + +#include "build/build_config.h" + +#include <set> +#include <string> + +#if defined(OS_WIN) +#include <objidl.h> +#elif !defined(OS_MACOSX) +#include <gtk/gtk.h> +#endif + +#include "app/download_file_interface.h" +#include "base/basictypes.h" +#include "base/file_path.h" +#include "base/scoped_ptr.h" + +class GURL; +class Pickle; + +namespace ui { + +/////////////////////////////////////////////////////////////////////////////// +// +// OSExchangeData +// An object that holds interchange data to be sent out to OS services like +// clipboard, drag and drop, etc. This object exposes an API that clients can +// use to specify raw data and its high level type. This object takes care of +// translating that into something the OS can understand. +// +/////////////////////////////////////////////////////////////////////////////// + +// NOTE: Support for html and file contents is required by TabContentViewWin. +// TabContentsViewGtk uses a different class to handle drag support that does +// not use OSExchangeData. As such, file contents and html support is only +// compiled on windows. +class OSExchangeData { + public: + // CustomFormats are used for non-standard data types. For example, bookmark + // nodes are written using a CustomFormat. +#if defined(OS_WIN) + typedef CLIPFORMAT CustomFormat; +#elif !defined(OS_MACOSX) + typedef GdkAtom CustomFormat; +#endif + + // Enumeration of the known formats. + enum Format { + STRING = 1 << 0, + URL = 1 << 1, + FILE_NAME = 1 << 2, + PICKLED_DATA = 1 << 3, +#if defined(OS_WIN) + FILE_CONTENTS = 1 << 4, + HTML = 1 << 5, +#endif + }; + + // Encapsulates the info about a file to be downloaded. + struct DownloadFileInfo { + DownloadFileInfo(const FilePath& filename, + DownloadFileProvider* downloader); + ~DownloadFileInfo(); + + FilePath filename; + scoped_refptr<DownloadFileProvider> downloader; + }; + + // Provider defines the platform specific part of OSExchangeData that + // interacts with the native system. + class Provider { + public: + Provider() {} + virtual ~Provider() {} + + virtual void SetString(const std::wstring& data) = 0; + virtual void SetURL(const GURL& url, const std::wstring& title) = 0; + virtual void SetFilename(const std::wstring& full_path) = 0; + virtual void SetPickledData(CustomFormat format, const Pickle& data) = 0; + + virtual bool GetString(std::wstring* data) const = 0; + virtual bool GetURLAndTitle(GURL* url, std::wstring* title) const = 0; + virtual bool GetFilename(std::wstring* full_path) const = 0; + virtual bool GetPickledData(CustomFormat format, Pickle* data) const = 0; + + virtual bool HasString() const = 0; + virtual bool HasURL() const = 0; + virtual bool HasFile() const = 0; + virtual bool HasCustomFormat( + OSExchangeData::CustomFormat format) const = 0; + +#if defined(OS_WIN) + virtual void SetFileContents(const std::wstring& filename, + const std::string& file_contents) = 0; + virtual void SetHtml(const std::wstring& html, const GURL& base_url) = 0; + virtual bool GetFileContents(std::wstring* filename, + std::string* file_contents) const = 0; + virtual bool GetHtml(std::wstring* html, GURL* base_url) const = 0; + virtual bool HasFileContents() const = 0; + virtual bool HasHtml() const = 0; + virtual void SetDownloadFileInfo(const DownloadFileInfo& download) = 0; +#endif + }; + + OSExchangeData(); + // Creates an OSExchangeData with the specified provider. OSExchangeData + // takes ownership of the supplied provider. + explicit OSExchangeData(Provider* provider); + + ~OSExchangeData(); + + // Registers the specific string as a possible format for data. + static CustomFormat RegisterCustomFormat(const std::string& type); + + // Returns the Provider, which actually stores and manages the data. + const Provider& provider() const { return *provider_; } + Provider& provider() { return *provider_; } + + // These functions add data to the OSExchangeData object of various Chrome + // types. The OSExchangeData object takes care of translating the data into + // a format suitable for exchange with the OS. + // NOTE WELL: Typically, a data object like this will contain only one of the + // following types of data. In cases where more data is held, the + // order in which these functions are called is _important_! + // ---> The order types are added to an OSExchangeData object controls + // the order of enumeration in our IEnumFORMATETC implementation! + // This comes into play when selecting the best (most preferable) + // data type for insertion into a DropTarget. + void SetString(const std::wstring& data); + // A URL can have an optional title in some exchange formats. + void SetURL(const GURL& url, const std::wstring& title); + // A full path to a file. + // TODO: convert to Filepath. + void SetFilename(const std::wstring& full_path); + // Adds pickled data of the specified format. + void SetPickledData(CustomFormat format, const Pickle& data); + + // These functions retrieve data of the specified type. If data exists, the + // functions return and the result is in the out parameter. If the data does + // not exist, the out parameter is not touched. The out parameter cannot be + // NULL. + bool GetString(std::wstring* data) const; + bool GetURLAndTitle(GURL* url, std::wstring* title) const; + // Return the path of a file, if available. + bool GetFilename(std::wstring* full_path) const; + bool GetPickledData(CustomFormat format, Pickle* data) const; + + // Test whether or not data of certain types is present, without actually + // returning anything. + bool HasString() const; + bool HasURL() const; + bool HasFile() const; + bool HasCustomFormat(CustomFormat format) const; + + // Returns true if this OSExchangeData has data for ALL the formats in + // |formats| and ALL the custom formats in |custom_formats|. + bool HasAllFormats(int formats, + const std::set<CustomFormat>& custom_formats) const; + + // Returns true if this OSExchangeData has data in any of the formats in + // |formats| or any custom format in |custom_formats|. + bool HasAnyFormat(int formats, + const std::set<CustomFormat>& custom_formats) const; + +#if defined(OS_WIN) + // Adds the bytes of a file (CFSTR_FILECONTENTS and CFSTR_FILEDESCRIPTOR). + void SetFileContents(const std::wstring& filename, + const std::string& file_contents); + // Adds a snippet of HTML. |html| is just raw html but this sets both + // text/html and CF_HTML. + void SetHtml(const std::wstring& html, const GURL& base_url); + bool GetFileContents(std::wstring* filename, + std::string* file_contents) const; + bool GetHtml(std::wstring* html, GURL* base_url) const; + + // Adds a download file with full path (CF_HDROP). + void SetDownloadFileInfo(const DownloadFileInfo& download); +#endif + + private: + // Creates the platform specific Provider. + static Provider* CreateProvider(); + + // Provides the actual data. + scoped_ptr<Provider> provider_; + + DISALLOW_COPY_AND_ASSIGN(OSExchangeData); +}; + +} // namespace ui + +#endif // UI_BASE_DRAGDROP_OS_EXCHANGE_DATA_H_ diff --git a/ui/base/dragdrop/os_exchange_data_provider_gtk.cc b/ui/base/dragdrop/os_exchange_data_provider_gtk.cc new file mode 100644 index 0000000..83bd600 --- /dev/null +++ b/ui/base/dragdrop/os_exchange_data_provider_gtk.cc @@ -0,0 +1,248 @@ +// 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/dragdrop/os_exchange_data_provider_gtk.h" + +#include <algorithm> + +#include "app/gtk_dnd_util.h" +#include "base/file_path.h" +#include "base/utf_string_conversions.h" +#include "net/base/net_util.h" + +namespace ui { + +OSExchangeDataProviderGtk::OSExchangeDataProviderGtk( + int known_formats, + const std::set<GdkAtom>& known_custom_formats) + : known_formats_(known_formats), + known_custom_formats_(known_custom_formats), + formats_(0), + drag_image_(NULL) { +} + +OSExchangeDataProviderGtk::OSExchangeDataProviderGtk() + : known_formats_(0), + formats_(0), + drag_image_(NULL) { +} + +OSExchangeDataProviderGtk::~OSExchangeDataProviderGtk() { + if (drag_image_) + g_object_unref(drag_image_); +} + +bool OSExchangeDataProviderGtk::HasDataForAllFormats( + int formats, + const std::set<GdkAtom>& custom_formats) const { + if ((formats_ & formats) != formats) + return false; + for (std::set<GdkAtom>::iterator i = custom_formats.begin(); + i != custom_formats.end(); ++i) { + if (pickle_data_.find(*i) == pickle_data_.end()) + return false; + } + return true; +} + +GtkTargetList* OSExchangeDataProviderGtk::GetTargetList() const { + GtkTargetList* targets = gtk_target_list_new(NULL, 0); + + if ((formats_ & OSExchangeData::STRING) != 0) + gtk_target_list_add_text_targets(targets, OSExchangeData::STRING); + + if ((formats_ & OSExchangeData::URL) != 0) { + gtk_target_list_add_uri_targets(targets, OSExchangeData::URL); + gtk_target_list_add( + targets, + gtk_dnd_util::GetAtomForTarget(gtk_dnd_util::CHROME_NAMED_URL), + 0, + OSExchangeData::URL); + } + + if ((formats_ & OSExchangeData::FILE_NAME) != 0) + gtk_target_list_add_uri_targets(targets, OSExchangeData::FILE_NAME); + + for (PickleData::const_iterator i = pickle_data_.begin(); + i != pickle_data_.end(); ++i) { + gtk_target_list_add(targets, i->first, 0, OSExchangeData::PICKLED_DATA); + } + + return targets; +} + +void OSExchangeDataProviderGtk::WriteFormatToSelection( + int format, + GtkSelectionData* selection) const { + if ((format & OSExchangeData::STRING) != 0) { + gtk_selection_data_set_text( + selection, + reinterpret_cast<const gchar*>(string_.c_str()), + -1); + } + + if ((format & OSExchangeData::URL) != 0) { + // TODO: this should be pulled out of TabContentsDragSource into a common + // place. + Pickle pickle; + pickle.WriteString(UTF16ToUTF8(title_)); + pickle.WriteString(url_.spec()); + gtk_selection_data_set( + selection, + gtk_dnd_util::GetAtomForTarget(gtk_dnd_util::CHROME_NAMED_URL), + 8, + reinterpret_cast<const guchar*>(pickle.data()), + pickle.size()); + + gchar* uri_array[2]; + uri_array[0] = strdup(url_.spec().c_str()); + uri_array[1] = NULL; + gtk_selection_data_set_uris(selection, uri_array); + free(uri_array[0]); + } + + if ((format & OSExchangeData::FILE_NAME) != 0) { + gchar* uri_array[2]; + uri_array[0] = + strdup(net::FilePathToFileURL(FilePath(filename_)).spec().c_str()); + uri_array[1] = NULL; + gtk_selection_data_set_uris(selection, uri_array); + free(uri_array[0]); + } + + if ((format & OSExchangeData::PICKLED_DATA) != 0) { + for (PickleData::const_iterator i = pickle_data_.begin(); + i != pickle_data_.end(); ++i) { + const Pickle& data = i->second; + gtk_selection_data_set( + selection, + i->first, + 8, + reinterpret_cast<const guchar*>(data.data()), + data.size()); + } + } +} + +void OSExchangeDataProviderGtk::SetString(const std::wstring& data) { + string_ = WideToUTF16Hack(data); + formats_ |= OSExchangeData::STRING; +} + +void OSExchangeDataProviderGtk::SetURL(const GURL& url, + const std::wstring& title) { + url_ = url; + title_ = WideToUTF16Hack(title); + formats_ |= OSExchangeData::URL; +} + +void OSExchangeDataProviderGtk::SetFilename(const std::wstring& full_path) { + filename_ = WideToUTF8(full_path); + formats_ |= OSExchangeData::FILE_NAME; +} + +void OSExchangeDataProviderGtk::SetPickledData(GdkAtom format, + const Pickle& data) { + pickle_data_[format] = data; + formats_ |= OSExchangeData::PICKLED_DATA; +} + +bool OSExchangeDataProviderGtk::GetString(std::wstring* data) const { + if ((formats_ & OSExchangeData::STRING) == 0) + return false; + *data = UTF16ToWideHack(string_); + return true; +} + +bool OSExchangeDataProviderGtk::GetURLAndTitle(GURL* url, + std::wstring* title) const { + if ((formats_ & OSExchangeData::URL) == 0) { + title->clear(); + return GetPlainTextURL(url); + } + + if (!url_.is_valid()) + return false; + + *url = url_; + *title = UTF16ToWideHack(title_); + return true; +} + +bool OSExchangeDataProviderGtk::GetFilename(std::wstring* full_path) const { + if ((formats_ & OSExchangeData::FILE_NAME) == 0) + return false; + *full_path = UTF8ToWide(filename_); + return true; +} + +bool OSExchangeDataProviderGtk::GetPickledData(GdkAtom format, + Pickle* data) const { + PickleData::const_iterator i = pickle_data_.find(format); + if (i == pickle_data_.end()) + return false; + + *data = i->second; + return true; +} + +bool OSExchangeDataProviderGtk::HasString() const { + return (known_formats_ & OSExchangeData::STRING) != 0 || + (formats_ & OSExchangeData::STRING) != 0; +} + +bool OSExchangeDataProviderGtk::HasURL() const { + if ((known_formats_ & OSExchangeData::URL) != 0 || + (formats_ & OSExchangeData::URL) != 0) { + return true; + } + // No URL, see if we have plain text that can be parsed as a URL. + return GetPlainTextURL(NULL); +} + +bool OSExchangeDataProviderGtk::HasFile() const { + return (known_formats_ & OSExchangeData::FILE_NAME) != 0 || + (formats_ & OSExchangeData::FILE_NAME) != 0; + } + +bool OSExchangeDataProviderGtk::HasCustomFormat(GdkAtom format) const { + return known_custom_formats_.find(format) != known_custom_formats_.end() || + pickle_data_.find(format) != pickle_data_.end(); +} + +bool OSExchangeDataProviderGtk::GetPlainTextURL(GURL* url) const { + if ((formats_ & OSExchangeData::STRING) == 0) + return false; + + GURL test_url(string_); + if (!test_url.is_valid()) + return false; + + if (url) + *url = test_url; + return true; +} + +void OSExchangeDataProviderGtk::SetDragImage(GdkPixbuf* drag_image, + const gfx::Point& cursor_offset) { + if (drag_image_) + g_object_unref(drag_image_); + g_object_ref(drag_image); + drag_image_ = drag_image; + cursor_offset_ = cursor_offset; +} + +/////////////////////////////////////////////////////////////////////////////// +// OSExchangeData, public: + +// static +OSExchangeData::Provider* OSExchangeData::CreateProvider() { + return new OSExchangeDataProviderGtk(); +} + +GdkAtom OSExchangeData::RegisterCustomFormat(const std::string& type) { + return gdk_atom_intern(type.c_str(), false); +} + +} // namespace ui diff --git a/ui/base/dragdrop/os_exchange_data_provider_gtk.h b/ui/base/dragdrop/os_exchange_data_provider_gtk.h new file mode 100644 index 0000000..2025b98 --- /dev/null +++ b/ui/base/dragdrop/os_exchange_data_provider_gtk.h @@ -0,0 +1,120 @@ +// 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_DRAGDROP_OS_EXCHANGE_DATA_PROVIDER_GTK_H_ +#define UI_BASE_DRAGDROP_OS_EXCHANGE_DATA_PROVIDER_GTK_H_ +#pragma once + +#include <gtk/gtk.h> +#include <map> +#include <set> +#include <string> + +#include "base/pickle.h" +#include "base/string16.h" +#include "gfx/point.h" +#include "googleurl/src/gurl.h" +#include "ui/base/dragdrop/os_exchange_data.h" + +namespace ui { + +// OSExchangeData::Provider implementation for Gtk. OSExchangeDataProviderGtk +// is created with a set of known data types. In addition specific data +// types can be set on OSExchangeDataProviderGtk by way of the various setters. +// The various has methods return true if the format was supplied to the +// constructor, or explicitly set. +class OSExchangeDataProviderGtk : public OSExchangeData::Provider { + public: + OSExchangeDataProviderGtk(int known_formats, + const std::set<GdkAtom>& known_custom_formats_); + OSExchangeDataProviderGtk(); + + virtual ~OSExchangeDataProviderGtk(); + + int known_formats() const { return known_formats_; } + const std::set<GdkAtom>& known_custom_formats() const { + return known_custom_formats_; + } + + // Returns true if all the formats and custom formats identified by |formats| + // and |custom_formats| have been set in this provider. + // + // NOTE: this is NOT the same as whether a format may be provided (as is + // returned by the various HasXXX methods), but rather if the data for the + // formats has been set on this provider by way of the various Setter + // methods. + bool HasDataForAllFormats(int formats, + const std::set<GdkAtom>& custom_formats) const; + + // Returns the set of formats available as a GtkTargetList. It is up to the + // caller to free (gtk_target_list_unref) the returned list. + GtkTargetList* GetTargetList() const; + + // Writes the data to |selection|. |format| is any combination of + // OSExchangeData::Formats. + void WriteFormatToSelection(int format, + GtkSelectionData* selection) const; + + // Provider methods. + virtual void SetString(const std::wstring& data); + virtual void SetURL(const GURL& url, const std::wstring& title); + virtual void SetFilename(const std::wstring& full_path); + virtual void SetPickledData(OSExchangeData::CustomFormat format, + const Pickle& data); + virtual bool GetString(std::wstring* data) const; + virtual bool GetURLAndTitle(GURL* url, std::wstring* title) const; + virtual bool GetFilename(std::wstring* full_path) const; + virtual bool GetPickledData(OSExchangeData::CustomFormat format, + Pickle* data) const; + virtual bool HasString() const; + virtual bool HasURL() const; + virtual bool HasFile() const; + virtual bool HasCustomFormat(OSExchangeData::CustomFormat format) const; + + // Set the image and cursor offset data for this drag. Will + // increment the ref count of pixbuf. + void SetDragImage(GdkPixbuf* pixbuf, const gfx::Point& cursor_offset); + GdkPixbuf* drag_image() const { return drag_image_; } + gfx::Point cursor_offset() const { return cursor_offset_; } + + private: + typedef std::map<OSExchangeData::CustomFormat, Pickle> PickleData; + + // Returns true if |formats_| contains a string format and the string can be + // parsed as a URL. + bool GetPlainTextURL(GURL* url) const; + + // These are the possible formats the OSExchangeData may contain. Don't + // confuse this with the actual formats that have been set, which are + // |formats_| and |custom_formats_|. + const int known_formats_; + const std::set<GdkAtom> known_custom_formats_; + + // Actual formats that have been set. See comment above |known_formats_| + // for details. + int formats_; + + // String contents. + string16 string_; + + // URL contents. + GURL url_; + string16 title_; + + // File name. + std::string filename_; + + // PICKLED_DATA contents. + PickleData pickle_data_; + + // Drag image and offset data. + GdkPixbuf* drag_image_; + gfx::Point cursor_offset_; + + DISALLOW_COPY_AND_ASSIGN(OSExchangeDataProviderGtk); +}; + +} // namespace ui + +#endif // UI_BASE_DRAGDROP_OS_EXCHANGE_DATA_PROVIDER_GTK_H_ diff --git a/ui/base/dragdrop/os_exchange_data_provider_win.cc b/ui/base/dragdrop/os_exchange_data_provider_win.cc new file mode 100644 index 0000000..f8488a3 --- /dev/null +++ b/ui/base/dragdrop/os_exchange_data_provider_win.cc @@ -0,0 +1,925 @@ +// 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/dragdrop/os_exchange_data_provider_win.h" + +#include "app/l10n_util.h" +#include "base/file_path.h" +#include "base/i18n/file_util_icu.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "base/scoped_handle.h" +#include "base/stl_util-inl.h" +#include "base/utf_string_conversions.h" +#include "base/win/scoped_hglobal.h" +#include "googleurl/src/gurl.h" +#include "grit/app_strings.h" +#include "net/base/net_util.h" +#include "ui/base/clipboard/clipboard_util_win.h" + +namespace ui { + +// Creates a new STGMEDIUM object to hold the specified text. The caller +// owns the resulting object. The "Bytes" version does not NULL terminate, the +// string version does. +static STGMEDIUM* GetStorageForBytes(const char* data, size_t bytes); +static STGMEDIUM* GetStorageForWString(const std::wstring& data); +static STGMEDIUM* GetStorageForString(const std::string& data); +// Creates the contents of an Internet Shortcut file for the given URL. +static void GetInternetShortcutFileContents(const GURL& url, std::string* data); +// Creates a valid file name given a suggested title and URL. +static void CreateValidFileNameFromTitle(const GURL& url, + const std::wstring& title, + std::wstring* validated); +// Creates a new STGMEDIUM object to hold a file. +static STGMEDIUM* GetStorageForFileName(const std::wstring& full_path); +// Creates a File Descriptor for the creation of a file to the given URL and +// returns a handle to it. +static STGMEDIUM* GetStorageForFileDescriptor( + const std::wstring& valid_file_name); + +/////////////////////////////////////////////////////////////////////////////// +// FormatEtcEnumerator + +// +// This object implements an enumeration interface. The existence of an +// implementation of this interface is exposed to clients through +// OSExchangeData's EnumFormatEtc method. Our implementation is nobody's +// business but our own, so it lives in this file. +// +// This Windows API is truly a gem. It wants to be an enumerator but assumes +// some sort of sequential data (why not just use an array?). See comments +// throughout. +// +class FormatEtcEnumerator : public IEnumFORMATETC { + public: + FormatEtcEnumerator(DataObjectImpl::StoredData::const_iterator begin, + DataObjectImpl::StoredData::const_iterator end); + ~FormatEtcEnumerator(); + + // IEnumFORMATETC implementation: + HRESULT __stdcall Next( + ULONG count, FORMATETC* elements_array, ULONG* elements_fetched); + HRESULT __stdcall Skip(ULONG skip_count); + HRESULT __stdcall Reset(); + HRESULT __stdcall Clone(IEnumFORMATETC** clone); + + // IUnknown implementation: + HRESULT __stdcall QueryInterface(const IID& iid, void** object); + ULONG __stdcall AddRef(); + ULONG __stdcall Release(); + + private: + // This can only be called from |CloneFromOther|, since it initializes the + // contents_ from the other enumerator's contents. + FormatEtcEnumerator() : ref_count_(0) { + } + + // Clone a new FormatEtc from another instance of this enumeration. + static FormatEtcEnumerator* CloneFromOther(const FormatEtcEnumerator* other); + + private: + // We are _forced_ to use a vector as our internal data model as Windows' + // retarded IEnumFORMATETC API assumes a deterministic ordering of elements + // through methods like Next and Skip. This exposes the underlying data + // structure to the user. Bah. + std::vector<FORMATETC*> contents_; + + // The cursor of the active enumeration - an index into |contents_|. + size_t cursor_; + + LONG ref_count_; + + DISALLOW_COPY_AND_ASSIGN(FormatEtcEnumerator); +}; + +// Safely makes a copy of all of the relevant bits of a FORMATETC object. +static void CloneFormatEtc(FORMATETC* source, FORMATETC* clone) { + *clone = *source; + if (source->ptd) { + source->ptd = + static_cast<DVTARGETDEVICE*>(CoTaskMemAlloc(sizeof(DVTARGETDEVICE))); + *(clone->ptd) = *(source->ptd); + } +} + +FormatEtcEnumerator::FormatEtcEnumerator( + DataObjectImpl::StoredData::const_iterator start, + DataObjectImpl::StoredData::const_iterator end) + : ref_count_(0), cursor_(0) { + // Copy FORMATETC data from our source into ourselves. + while (start != end) { + FORMATETC* format_etc = new FORMATETC; + CloneFormatEtc(&(*start)->format_etc, format_etc); + contents_.push_back(format_etc); + ++start; + } +} + +FormatEtcEnumerator::~FormatEtcEnumerator() { + STLDeleteContainerPointers(contents_.begin(), contents_.end()); +} + +STDMETHODIMP FormatEtcEnumerator::Next( + ULONG count, FORMATETC* elements_array, ULONG* elements_fetched) { + // MSDN says |elements_fetched| is allowed to be NULL if count is 1. + if (!elements_fetched) + DCHECK(count == 1); + + // This method copies count elements into |elements_array|. + ULONG index = 0; + while (cursor_ < contents_.size() && index < count) { + CloneFormatEtc(contents_[cursor_], &elements_array[index]); + ++cursor_; + ++index; + } + // The out param is for how many we actually copied. + if (elements_fetched) + *elements_fetched = index; + + // If the two don't agree, then we fail. + return index == count ? S_OK : S_FALSE; +} + +STDMETHODIMP FormatEtcEnumerator::Skip(ULONG skip_count) { + cursor_ += skip_count; + // MSDN implies it's OK to leave the enumerator trashed. + // "Whatever you say, boss" + return cursor_ <= contents_.size() ? S_OK : S_FALSE; +} + +STDMETHODIMP FormatEtcEnumerator::Reset() { + cursor_ = 0; + return S_OK; +} + +STDMETHODIMP FormatEtcEnumerator::Clone(IEnumFORMATETC** clone) { + // Clone the current enumerator in its exact state, including cursor. + FormatEtcEnumerator* e = CloneFromOther(this); + e->AddRef(); + *clone = e; + return S_OK; +} + +STDMETHODIMP FormatEtcEnumerator::QueryInterface(const IID& iid, + void** object) { + *object = NULL; + if (IsEqualIID(iid, IID_IUnknown) || IsEqualIID(iid, IID_IEnumFORMATETC)) { + *object = this; + } else { + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +ULONG FormatEtcEnumerator::AddRef() { + return InterlockedIncrement(&ref_count_); +} + +ULONG FormatEtcEnumerator::Release() { + if (InterlockedDecrement(&ref_count_) == 0) { + ULONG copied_refcnt = ref_count_; + delete this; + return copied_refcnt; + } + return ref_count_; +} + +// static +FormatEtcEnumerator* FormatEtcEnumerator::CloneFromOther( + const FormatEtcEnumerator* other) { + FormatEtcEnumerator* e = new FormatEtcEnumerator; + // Copy FORMATETC data from our source into ourselves. + std::vector<FORMATETC*>::const_iterator start = other->contents_.begin(); + while (start != other->contents_.end()) { + FORMATETC* format_etc = new FORMATETC; + CloneFormatEtc(*start, format_etc); + e->contents_.push_back(format_etc); + ++start; + } + // Carry over + e->cursor_ = other->cursor_; + return e; +} + +/////////////////////////////////////////////////////////////////////////////// +// OSExchangeDataProviderWin, public: + +// static +bool OSExchangeDataProviderWin::HasPlainTextURL(IDataObject* source) { + std::wstring plain_text; + return (ClipboardUtil::GetPlainText(source, &plain_text) && + !plain_text.empty() && GURL(plain_text).is_valid()); +} + +// static +bool OSExchangeDataProviderWin::GetPlainTextURL(IDataObject* source, + GURL* url) { + std::wstring plain_text; + if (ClipboardUtil::GetPlainText(source, &plain_text) && + !plain_text.empty()) { + GURL gurl(plain_text); + if (gurl.is_valid()) { + *url = gurl; + return true; + } + } + return false; +} + +// static +DataObjectImpl* OSExchangeDataProviderWin::GetDataObjectImpl( + const OSExchangeData& data) { + return static_cast<const OSExchangeDataProviderWin*>(&data.provider())-> + data_.get(); +} + +// static +IDataObject* OSExchangeDataProviderWin::GetIDataObject( + const OSExchangeData& data) { + return static_cast<const OSExchangeDataProviderWin*>(&data.provider())-> + data_object(); +} + +// static +IAsyncOperation* OSExchangeDataProviderWin::GetIAsyncOperation( + const OSExchangeData& data) { + return static_cast<const OSExchangeDataProviderWin*>(&data.provider())-> + async_operation(); +} + +OSExchangeDataProviderWin::OSExchangeDataProviderWin(IDataObject* source) + : data_(new DataObjectImpl()), + source_object_(source) { +} + +OSExchangeDataProviderWin::OSExchangeDataProviderWin() + : data_(new DataObjectImpl()), + source_object_(data_.get()) { +} + +OSExchangeDataProviderWin::~OSExchangeDataProviderWin() { +} + +void OSExchangeDataProviderWin::SetString(const std::wstring& data) { + STGMEDIUM* storage = GetStorageForWString(data); + data_->contents_.push_back( + new DataObjectImpl::StoredDataInfo(CF_UNICODETEXT, storage)); + + // Also add plain text. + storage = GetStorageForString(WideToUTF8(data)); + data_->contents_.push_back( + new DataObjectImpl::StoredDataInfo(CF_TEXT, storage)); +} + +void OSExchangeDataProviderWin::SetURL(const GURL& url, + const std::wstring& title) { + // NOTE WELL: + // Every time you change the order of the first two CLIPFORMATS that get + // added here, you need to update the EnumerationViaCOM test case in + // the _unittest.cc file to reflect the new arrangement otherwise that test + // will fail! It assumes an insertion order. + + // Add text/x-moz-url for drags from Firefox + std::wstring x_moz_url_str = UTF8ToWide(url.spec()); + x_moz_url_str += '\n'; + x_moz_url_str += title; + STGMEDIUM* storage = GetStorageForWString(x_moz_url_str); + data_->contents_.push_back(new DataObjectImpl::StoredDataInfo( + ClipboardUtil::GetMozUrlFormat()->cfFormat, storage)); + + // Add a .URL shortcut file for dragging to Explorer. + std::wstring valid_file_name; + CreateValidFileNameFromTitle(url, title, &valid_file_name); + std::string shortcut_url_file_contents; + GetInternetShortcutFileContents(url, &shortcut_url_file_contents); + SetFileContents(valid_file_name, shortcut_url_file_contents); + + // Add a UniformResourceLocator link for apps like IE and Word. + storage = GetStorageForWString(UTF8ToWide(url.spec())); + data_->contents_.push_back(new DataObjectImpl::StoredDataInfo( + ClipboardUtil::GetUrlWFormat()->cfFormat, storage)); + storage = GetStorageForString(url.spec()); + data_->contents_.push_back(new DataObjectImpl::StoredDataInfo( + ClipboardUtil::GetUrlFormat()->cfFormat, storage)); + + // TODO(beng): add CF_HTML. + // http://code.google.com/p/chromium/issues/detail?id=6767 + + // Also add text representations (these should be last since they're the + // least preferable). + storage = GetStorageForWString(UTF8ToWide(url.spec())); + data_->contents_.push_back( + new DataObjectImpl::StoredDataInfo(CF_UNICODETEXT, storage)); + storage = GetStorageForString(url.spec()); + data_->contents_.push_back( + new DataObjectImpl::StoredDataInfo(CF_TEXT, storage)); +} + +void OSExchangeDataProviderWin::SetFilename(const std::wstring& full_path) { + STGMEDIUM* storage = GetStorageForFileName(full_path); + DataObjectImpl::StoredDataInfo* info = + new DataObjectImpl::StoredDataInfo(CF_HDROP, storage); + data_->contents_.push_back(info); +} + +void OSExchangeDataProviderWin::SetPickledData(CLIPFORMAT format, + const Pickle& data) { + STGMEDIUM* storage = GetStorageForString( + std::string(static_cast<const char *>(data.data()), + static_cast<size_t>(data.size()))); + data_->contents_.push_back( + new DataObjectImpl::StoredDataInfo(format, storage)); +} + +void OSExchangeDataProviderWin::SetFileContents( + const std::wstring& filename, + const std::string& file_contents) { + // Add CFSTR_FILEDESCRIPTOR + STGMEDIUM* storage = GetStorageForFileDescriptor(filename); + data_->contents_.push_back(new DataObjectImpl::StoredDataInfo( + ClipboardUtil::GetFileDescriptorFormat()->cfFormat, storage)); + + // Add CFSTR_FILECONTENTS + storage = GetStorageForBytes(file_contents.data(), file_contents.length()); + data_->contents_.push_back(new DataObjectImpl::StoredDataInfo( + ClipboardUtil::GetFileContentFormatZero(), storage)); +} + +void OSExchangeDataProviderWin::SetHtml(const std::wstring& html, + const GURL& base_url) { + // Add both MS CF_HTML and text/html format. CF_HTML should be in utf-8. + std::string utf8_html = WideToUTF8(html); + std::string url = base_url.is_valid() ? base_url.spec() : std::string(); + + std::string cf_html = ClipboardUtil::HtmlToCFHtml(utf8_html, url); + STGMEDIUM* storage = GetStorageForBytes(cf_html.c_str(), cf_html.size()); + data_->contents_.push_back(new DataObjectImpl::StoredDataInfo( + ClipboardUtil::GetHtmlFormat()->cfFormat, storage)); + + STGMEDIUM* storage_plain = GetStorageForBytes(utf8_html.c_str(), + utf8_html.size()); + data_->contents_.push_back(new DataObjectImpl::StoredDataInfo( + ClipboardUtil::GetTextHtmlFormat()->cfFormat, storage_plain)); +} + +bool OSExchangeDataProviderWin::GetString(std::wstring* data) const { + return ClipboardUtil::GetPlainText(source_object_, data); +} + +bool OSExchangeDataProviderWin::GetURLAndTitle(GURL* url, + std::wstring* title) const { + std::wstring url_str; + bool success = ClipboardUtil::GetUrl(source_object_, &url_str, title, true); + if (success) { + GURL test_url(url_str); + if (test_url.is_valid()) { + *url = test_url; + return true; + } + } else if (GetPlainTextURL(source_object_, url)) { + title->clear(); + return true; + } + return false; +} + +bool OSExchangeDataProviderWin::GetFilename(std::wstring* full_path) const { + std::vector<std::wstring> filenames; + bool success = ClipboardUtil::GetFilenames(source_object_, &filenames); + if (success) + full_path->assign(filenames[0]); + return success; +} + +bool OSExchangeDataProviderWin::GetPickledData(CLIPFORMAT format, + Pickle* data) const { + DCHECK(data); + FORMATETC format_etc = + { format, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + bool success = false; + STGMEDIUM medium; + if (SUCCEEDED(source_object_->GetData(&format_etc, &medium))) { + if (medium.tymed & TYMED_HGLOBAL) { + base::win::ScopedHGlobal<char> c_data(medium.hGlobal); + DCHECK(c_data.Size() > 0); + // Need to subtract 1 as SetPickledData adds an extra byte to the end. + *data = Pickle(c_data.get(), static_cast<int>(c_data.Size() - 1)); + success = true; + } + ReleaseStgMedium(&medium); + } + return success; +} + +bool OSExchangeDataProviderWin::GetFileContents( + std::wstring* filename, + std::string* file_contents) const { + return ClipboardUtil::GetFileContents(source_object_, filename, + file_contents); +} + +bool OSExchangeDataProviderWin::GetHtml(std::wstring* html, + GURL* base_url) const { + std::string url; + bool success = ClipboardUtil::GetHtml(source_object_, html, &url); + if (success) + *base_url = GURL(url); + return success; +} + +bool OSExchangeDataProviderWin::HasString() const { + return ClipboardUtil::HasPlainText(source_object_); +} + +bool OSExchangeDataProviderWin::HasURL() const { + return (ClipboardUtil::HasUrl(source_object_) || + HasPlainTextURL(source_object_)); +} + +bool OSExchangeDataProviderWin::HasFile() const { + return ClipboardUtil::HasFilenames(source_object_); +} + +bool OSExchangeDataProviderWin::HasFileContents() const { + return ClipboardUtil::HasFileContents(source_object_); +} + +bool OSExchangeDataProviderWin::HasHtml() const { + return ClipboardUtil::HasHtml(source_object_); +} + +bool OSExchangeDataProviderWin::HasCustomFormat(CLIPFORMAT format) const { + FORMATETC format_etc = + { format, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + return (source_object_->QueryGetData(&format_etc) == S_OK); +} + +void OSExchangeDataProviderWin::SetDownloadFileInfo( + const OSExchangeData::DownloadFileInfo& download) { + // If the filename is not provided, set stoarge to NULL to indicate that + // the delay rendering will be used. + STGMEDIUM* storage = NULL; + if (!download.filename.empty()) + storage = GetStorageForFileName(download.filename.value()); + + // Add CF_HDROP. + DataObjectImpl::StoredDataInfo* info = new DataObjectImpl::StoredDataInfo( + ClipboardUtil::GetCFHDropFormat()->cfFormat, storage); + info->downloader = download.downloader; + data_->contents_.push_back(info); +} + +/////////////////////////////////////////////////////////////////////////////// +// DataObjectImpl, IDataObject implementation: + +// The following function, DuplicateMedium, is derived from WCDataObject.cpp +// in the WebKit source code. This is the license information for the file: +/* + * Copyright (C) 2007 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. + */ +static void DuplicateMedium(CLIPFORMAT source_clipformat, + STGMEDIUM* source, + STGMEDIUM* destination) { + switch (source->tymed) { + case TYMED_HGLOBAL: + destination->hGlobal = + static_cast<HGLOBAL>(OleDuplicateData( + source->hGlobal, source_clipformat, 0)); + break; + case TYMED_MFPICT: + destination->hMetaFilePict = + static_cast<HMETAFILEPICT>(OleDuplicateData( + source->hMetaFilePict, source_clipformat, 0)); + break; + case TYMED_GDI: + destination->hBitmap = + static_cast<HBITMAP>(OleDuplicateData( + source->hBitmap, source_clipformat, 0)); + break; + case TYMED_ENHMF: + destination->hEnhMetaFile = + static_cast<HENHMETAFILE>(OleDuplicateData( + source->hEnhMetaFile, source_clipformat, 0)); + break; + case TYMED_FILE: + destination->lpszFileName = + static_cast<LPOLESTR>(OleDuplicateData( + source->lpszFileName, source_clipformat, 0)); + break; + case TYMED_ISTREAM: + destination->pstm = source->pstm; + destination->pstm->AddRef(); + break; + case TYMED_ISTORAGE: + destination->pstg = source->pstg; + destination->pstg->AddRef(); + break; + } + + destination->tymed = source->tymed; + destination->pUnkForRelease = source->pUnkForRelease; + if (destination->pUnkForRelease) + destination->pUnkForRelease->AddRef(); +} + +DataObjectImpl::DataObjectImpl() + : is_aborting_(false), + in_async_mode_(false), + async_operation_started_(false), + observer_(NULL) { +} + +DataObjectImpl::~DataObjectImpl() { + StopDownloads(); + STLDeleteContainerPointers(contents_.begin(), contents_.end()); + if (observer_) + observer_->OnDataObjectDisposed(); +} + +void DataObjectImpl::StopDownloads() { + for (StoredData::iterator iter = contents_.begin(); + iter != contents_.end(); ++iter) { + if ((*iter)->downloader.get()) { + (*iter)->downloader->Stop(); + (*iter)->downloader = 0; + } + } +} + +void DataObjectImpl::OnDownloadCompleted(const FilePath& file_path) { + CLIPFORMAT hdrop_format = ClipboardUtil::GetCFHDropFormat()->cfFormat; + DataObjectImpl::StoredData::iterator iter = contents_.begin(); + for (; iter != contents_.end(); ++iter) { + if ((*iter)->format_etc.cfFormat == hdrop_format) { + // Release the old storage. + if ((*iter)->owns_medium) { + ReleaseStgMedium((*iter)->medium); + delete (*iter)->medium; + } + + // Update the storage. + (*iter)->owns_medium = true; + (*iter)->medium = GetStorageForFileName(file_path.value()); + + break; + } + } + DCHECK(iter != contents_.end()); +} + +void DataObjectImpl::OnDownloadAborted() { +} + +HRESULT DataObjectImpl::GetData(FORMATETC* format_etc, STGMEDIUM* medium) { + if (is_aborting_) + return DV_E_FORMATETC; + + StoredData::iterator iter = contents_.begin(); + while (iter != contents_.end()) { + if ((*iter)->format_etc.cfFormat == format_etc->cfFormat && + (*iter)->format_etc.lindex == format_etc->lindex && + ((*iter)->format_etc.tymed & format_etc->tymed)) { + // If medium is NULL, delay-rendering will be used. + if ((*iter)->medium) { + DuplicateMedium((*iter)->format_etc.cfFormat, (*iter)->medium, medium); + } else { + // Check if the left button is down. + bool is_left_button_down = (GetKeyState(VK_LBUTTON) & 0x8000) != 0; + + bool wait_for_data = false; + if ((*iter)->in_delay_rendering) { + // Make sure the left button is up. Sometimes the drop target, like + // Shell, might be too aggresive in calling GetData when the left + // button is not released. + if (is_left_button_down) + return DV_E_FORMATETC; + + // In async mode, we do not want to start waiting for the data before + // the async operation is started. This is because we want to postpone + // until Shell kicks off a background thread to do the work so that + // we do not block the UI thread. + if (!in_async_mode_ || async_operation_started_) + wait_for_data = true; + } else { + // If the left button is up and the target has not requested the data + // yet, it probably means that the target does not support delay- + // rendering. So instead, we wait for the data. + if (is_left_button_down) { + (*iter)->in_delay_rendering = true; + memset(medium, 0, sizeof(STGMEDIUM)); + } else { + wait_for_data = true; + } + } + + if (!wait_for_data) + return DV_E_FORMATETC; + + // Notify the observer we start waiting for the data. This gives + // an observer a chance to end the drag and drop. + if (observer_) + observer_->OnWaitForData(); + + // Now we can start the download. + if ((*iter)->downloader.get()) { + if (!(*iter)->downloader->Start(this)) { + is_aborting_ = true; + return DV_E_FORMATETC; + } + } + + // The stored data should have been updated with the final version. + // So we just need to call this function again to retrieve it. + return GetData(format_etc, medium); + } + return S_OK; + } + ++iter; + } + + return DV_E_FORMATETC; +} + +HRESULT DataObjectImpl::GetDataHere(FORMATETC* format_etc, + STGMEDIUM* medium) { + return DATA_E_FORMATETC; +} + +HRESULT DataObjectImpl::QueryGetData(FORMATETC* format_etc) { + StoredData::const_iterator iter = contents_.begin(); + while (iter != contents_.end()) { + if ((*iter)->format_etc.cfFormat == format_etc->cfFormat) + return S_OK; + ++iter; + } + return DV_E_FORMATETC; +} + +HRESULT DataObjectImpl::GetCanonicalFormatEtc( + FORMATETC* format_etc, FORMATETC* result) { + format_etc->ptd = NULL; + return E_NOTIMPL; +} + +HRESULT DataObjectImpl::SetData( + FORMATETC* format_etc, STGMEDIUM* medium, BOOL should_release) { + STGMEDIUM* local_medium = new STGMEDIUM; + if (should_release) { + *local_medium = *medium; + } else { + DuplicateMedium(format_etc->cfFormat, medium, local_medium); + } + + DataObjectImpl::StoredDataInfo* info = + new DataObjectImpl::StoredDataInfo(format_etc->cfFormat, local_medium); + info->medium->tymed = format_etc->tymed; + info->owns_medium = !!should_release; + contents_.push_back(info); + + return S_OK; +} + +HRESULT DataObjectImpl::EnumFormatEtc( + DWORD direction, IEnumFORMATETC** enumerator) { + if (direction == DATADIR_GET) { + FormatEtcEnumerator* e = + new FormatEtcEnumerator(contents_.begin(), contents_.end()); + e->AddRef(); + *enumerator = e; + return S_OK; + } + return E_NOTIMPL; +} + +HRESULT DataObjectImpl::DAdvise( + FORMATETC* format_etc, DWORD advf, IAdviseSink* sink, DWORD* connection) { + return OLE_E_ADVISENOTSUPPORTED; +} + +HRESULT DataObjectImpl::DUnadvise(DWORD connection) { + return OLE_E_ADVISENOTSUPPORTED; +} + +HRESULT DataObjectImpl::EnumDAdvise(IEnumSTATDATA** enumerator) { + return OLE_E_ADVISENOTSUPPORTED; +} + +/////////////////////////////////////////////////////////////////////////////// +// DataObjectImpl, IAsyncOperation implementation: + +HRESULT DataObjectImpl::EndOperation( + HRESULT result, IBindCtx* reserved, DWORD effects) { + async_operation_started_ = false; + return S_OK; +} + +HRESULT DataObjectImpl::GetAsyncMode(BOOL* is_op_async) { + *is_op_async = in_async_mode_ ? TRUE : FALSE; + return S_OK; +} + +HRESULT DataObjectImpl::InOperation(BOOL* in_async_op) { + *in_async_op = async_operation_started_ ? TRUE : FALSE; + return S_OK; +} + +HRESULT DataObjectImpl::SetAsyncMode(BOOL do_op_async) { + in_async_mode_ = (do_op_async == TRUE); + return S_OK; +} + +HRESULT DataObjectImpl::StartOperation(IBindCtx* reserved) { + async_operation_started_ = true; + return S_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// DataObjectImpl, IUnknown implementation: + +HRESULT DataObjectImpl::QueryInterface(const IID& iid, void** object) { + if (!object) + return E_POINTER; + if (IsEqualIID(iid, IID_IDataObject) || IsEqualIID(iid, IID_IUnknown)) { + *object = static_cast<IDataObject*>(this); + } else if (in_async_mode_ && IsEqualIID(iid, IID_IAsyncOperation)) { + *object = static_cast<IAsyncOperation*>(this); + } else { + *object = NULL; + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +ULONG DataObjectImpl::AddRef() { + base::RefCountedThreadSafe<DownloadFileObserver>::AddRef(); + return 0; +} + +ULONG DataObjectImpl::Release() { + base::RefCountedThreadSafe<DownloadFileObserver>::Release(); + return 0; +} + +/////////////////////////////////////////////////////////////////////////////// +// DataObjectImpl, private: + +static STGMEDIUM* GetStorageForBytes(const char* data, size_t bytes) { + HANDLE handle = GlobalAlloc(GPTR, static_cast<int>(bytes)); + base::win::ScopedHGlobal<char> scoped(handle); + size_t allocated = static_cast<size_t>(GlobalSize(handle)); + memcpy(scoped.get(), data, allocated); + + STGMEDIUM* storage = new STGMEDIUM; + storage->hGlobal = handle; + storage->tymed = TYMED_HGLOBAL; + storage->pUnkForRelease = NULL; + return storage; +} + +template<class T> +static HGLOBAL CopyStringToGlobalHandle(const T& payload) { + int bytes = static_cast<int>(payload.size() + 1) * sizeof(T::value_type); + HANDLE handle = GlobalAlloc(GPTR, bytes); + void* data = GlobalLock(handle); + size_t allocated = static_cast<size_t>(GlobalSize(handle)); + memcpy(data, payload.c_str(), allocated); + static_cast<T::value_type*>(data)[payload.size()] = '\0'; + GlobalUnlock(handle); + return handle; +} + +static STGMEDIUM* GetStorageForWString(const std::wstring& data) { + STGMEDIUM* storage = new STGMEDIUM; + storage->hGlobal = CopyStringToGlobalHandle<std::wstring>(data); + storage->tymed = TYMED_HGLOBAL; + storage->pUnkForRelease = NULL; + return storage; +} + +static STGMEDIUM* GetStorageForString(const std::string& data) { + STGMEDIUM* storage = new STGMEDIUM; + storage->hGlobal = CopyStringToGlobalHandle<std::string>(data); + storage->tymed = TYMED_HGLOBAL; + storage->pUnkForRelease = NULL; + return storage; +} + +static void GetInternetShortcutFileContents(const GURL& url, + std::string* data) { + DCHECK(data); + static const std::string kInternetShortcutFileStart = + "[InternetShortcut]\r\nURL="; + static const std::string kInternetShortcutFileEnd = + "\r\n"; + *data = kInternetShortcutFileStart + url.spec() + kInternetShortcutFileEnd; +} + +static void CreateValidFileNameFromTitle(const GURL& url, + const std::wstring& title, + std::wstring* validated) { + if (title.empty()) { + if (url.is_valid()) { + *validated = net::GetSuggestedFilename( + url, std::string(), std::string(), FilePath()).ToWStringHack(); + } else { + // Nothing else can be done, just use a default. + *validated = + l10n_util::GetStringUTF16(IDS_APP_UNTITLED_SHORTCUT_FILE_NAME); + } + } else { + *validated = title; + file_util::ReplaceIllegalCharactersInPath(validated, '-'); + } + static const wchar_t extension[] = L".url"; + static const size_t max_length = MAX_PATH - arraysize(extension); + if (validated->size() > max_length) + validated->erase(max_length); + *validated += extension; +} + +static STGMEDIUM* GetStorageForFileName(const std::wstring& full_path) { + const size_t kDropSize = sizeof(DROPFILES); + const size_t kTotalBytes = + kDropSize + (full_path.length() + 2) * sizeof(wchar_t); + HANDLE hdata = GlobalAlloc(GMEM_MOVEABLE, kTotalBytes); + + base::win::ScopedHGlobal<DROPFILES> locked_mem(hdata); + DROPFILES* drop_files = locked_mem.get(); + drop_files->pFiles = sizeof(DROPFILES); + drop_files->fWide = TRUE; + wchar_t* data = reinterpret_cast<wchar_t*>( + reinterpret_cast<BYTE*>(drop_files) + kDropSize); + const size_t copy_size = (full_path.length() + 1) * sizeof(wchar_t); + memcpy(data, full_path.c_str(), copy_size); + data[full_path.length() + 1] = L'\0'; // Double NULL + + STGMEDIUM* storage = new STGMEDIUM; + storage->tymed = TYMED_HGLOBAL; + storage->hGlobal = drop_files; + storage->pUnkForRelease = NULL; + return storage; +} + +static STGMEDIUM* GetStorageForFileDescriptor( + const std::wstring& valid_file_name) { + DCHECK(!valid_file_name.empty() && valid_file_name.size() + 1 <= MAX_PATH); + HANDLE handle = GlobalAlloc(GPTR, sizeof(FILEGROUPDESCRIPTOR)); + FILEGROUPDESCRIPTOR* descriptor = + reinterpret_cast<FILEGROUPDESCRIPTOR*>(GlobalLock(handle)); + + descriptor->cItems = 1; + wcscpy_s(descriptor->fgd[0].cFileName, + valid_file_name.size() + 1, + valid_file_name.c_str()); + descriptor->fgd[0].dwFlags = FD_LINKUI; + + GlobalUnlock(handle); + + STGMEDIUM* storage = new STGMEDIUM; + storage->hGlobal = handle; + storage->tymed = TYMED_HGLOBAL; + storage->pUnkForRelease = NULL; + return storage; +} + + +/////////////////////////////////////////////////////////////////////////////// +// OSExchangeData, public: + +// static +OSExchangeData::Provider* OSExchangeData::CreateProvider() { + return new OSExchangeDataProviderWin(); +} + +// static +OSExchangeData::CustomFormat OSExchangeData::RegisterCustomFormat( + const std::string& type) { + return RegisterClipboardFormat(ASCIIToWide(type).c_str()); +} + +} // namespace ui diff --git a/ui/base/dragdrop/os_exchange_data_provider_win.h b/ui/base/dragdrop/os_exchange_data_provider_win.h new file mode 100644 index 0000000..dc888bd --- /dev/null +++ b/ui/base/dragdrop/os_exchange_data_provider_win.h @@ -0,0 +1,180 @@ +// 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_DRAGDROP_OS_EXCHANGE_DATA_PROVIDER_WIN_H_ +#define UI_BASE_DRAGDROP_OS_EXCHANGE_DATA_PROVIDER_WIN_H_ +#pragma once + +#include <objidl.h> +#include <shlobj.h> +#include <string> + +#include "base/scoped_comptr_win.h" +#include "ui/base/dragdrop/os_exchange_data.h" + +namespace ui { + +class DataObjectImpl : public DownloadFileObserver, + public IDataObject, + public IAsyncOperation { + public: + class Observer { + public: + virtual void OnWaitForData() = 0; + virtual void OnDataObjectDisposed() = 0; + protected: + virtual ~Observer() { } + }; + + DataObjectImpl(); + + // Accessors. + void set_observer(Observer* observer) { observer_ = observer; } + + // DownloadFileObserver implementation: + virtual void OnDownloadCompleted(const FilePath& file_path); + virtual void OnDownloadAborted(); + + // IDataObject implementation: + HRESULT __stdcall GetData(FORMATETC* format_etc, STGMEDIUM* medium); + HRESULT __stdcall GetDataHere(FORMATETC* format_etc, STGMEDIUM* medium); + HRESULT __stdcall QueryGetData(FORMATETC* format_etc); + HRESULT __stdcall GetCanonicalFormatEtc( + FORMATETC* format_etc, FORMATETC* result); + HRESULT __stdcall SetData( + FORMATETC* format_etc, STGMEDIUM* medium, BOOL should_release); + HRESULT __stdcall EnumFormatEtc( + DWORD direction, IEnumFORMATETC** enumerator); + HRESULT __stdcall DAdvise(FORMATETC* format_etc, DWORD advf, + IAdviseSink* sink, DWORD* connection); + HRESULT __stdcall DUnadvise(DWORD connection); + HRESULT __stdcall EnumDAdvise(IEnumSTATDATA** enumerator); + + // IAsyncOperation implementation: + HRESULT __stdcall EndOperation( + HRESULT result, IBindCtx* reserved, DWORD effects); + HRESULT __stdcall GetAsyncMode(BOOL* is_op_async); + HRESULT __stdcall InOperation(BOOL* in_async_op); + HRESULT __stdcall SetAsyncMode(BOOL do_op_async); + HRESULT __stdcall StartOperation(IBindCtx* reserved); + + // IUnknown implementation: + HRESULT __stdcall QueryInterface(const IID& iid, void** object); + ULONG __stdcall AddRef(); + ULONG __stdcall Release(); + + private: + // FormatEtcEnumerator only likes us for our StoredDataMap typedef. + friend class FormatEtcEnumerator; + friend class OSExchangeDataProviderWin; + + virtual ~DataObjectImpl(); + + void StopDownloads(); + + // Our internal representation of stored data & type info. + struct StoredDataInfo { + FORMATETC format_etc; + STGMEDIUM* medium; + bool owns_medium; + bool in_delay_rendering; + scoped_refptr<DownloadFileProvider> downloader; + + StoredDataInfo(CLIPFORMAT cf, STGMEDIUM* medium) + : medium(medium), + owns_medium(true), + in_delay_rendering(false) { + format_etc.cfFormat = cf; + format_etc.dwAspect = DVASPECT_CONTENT; + format_etc.lindex = -1; + format_etc.ptd = NULL; + format_etc.tymed = medium ? medium->tymed : TYMED_HGLOBAL; + } + + StoredDataInfo(FORMATETC* format_etc, STGMEDIUM* medium) + : format_etc(*format_etc), + medium(medium), + owns_medium(true), + in_delay_rendering(false) { + } + + ~StoredDataInfo() { + if (owns_medium) { + ReleaseStgMedium(medium); + delete medium; + } + if (downloader.get()) + downloader->Stop(); + } + }; + + typedef std::vector<StoredDataInfo*> StoredData; + StoredData contents_; + + ScopedComPtr<IDataObject> source_object_; + + bool is_aborting_; + bool in_async_mode_; + bool async_operation_started_; + Observer* observer_; +}; + +class OSExchangeDataProviderWin : public OSExchangeData::Provider { + public: + // Returns true if source has plain text that is a valid url. + static bool HasPlainTextURL(IDataObject* source); + + // Returns true if source has plain text that is a valid URL and sets url to + // that url. + static bool GetPlainTextURL(IDataObject* source, GURL* url); + + static DataObjectImpl* GetDataObjectImpl(const OSExchangeData& data); + static IDataObject* GetIDataObject(const OSExchangeData& data); + static IAsyncOperation* GetIAsyncOperation(const OSExchangeData& data); + + explicit OSExchangeDataProviderWin(IDataObject* source); + OSExchangeDataProviderWin(); + + virtual ~OSExchangeDataProviderWin(); + + IDataObject* data_object() const { return data_.get(); } + IAsyncOperation* async_operation() const { return data_.get(); } + + // OSExchangeData::Provider methods. + virtual void SetString(const std::wstring& data); + virtual void SetURL(const GURL& url, const std::wstring& title); + virtual void SetFilename(const std::wstring& full_path); + virtual void SetPickledData(OSExchangeData::CustomFormat format, + const Pickle& data); + virtual void SetFileContents(const std::wstring& filename, + const std::string& file_contents); + virtual void SetHtml(const std::wstring& html, const GURL& base_url); + + virtual bool GetString(std::wstring* data) const; + virtual bool GetURLAndTitle(GURL* url, std::wstring* title) const; + virtual bool GetFilename(std::wstring* full_path) const; + virtual bool GetPickledData(OSExchangeData::CustomFormat format, + Pickle* data) const; + virtual bool GetFileContents(std::wstring* filename, + std::string* file_contents) const; + virtual bool GetHtml(std::wstring* html, GURL* base_url) const; + virtual bool HasString() const; + virtual bool HasURL() const; + virtual bool HasFile() const; + virtual bool HasFileContents() const; + virtual bool HasHtml() const; + virtual bool HasCustomFormat(OSExchangeData::CustomFormat format) const; + virtual void SetDownloadFileInfo( + const OSExchangeData::DownloadFileInfo& download_info); + + private: + scoped_refptr<DataObjectImpl> data_; + ScopedComPtr<IDataObject> source_object_; + + DISALLOW_COPY_AND_ASSIGN(OSExchangeDataProviderWin); +}; + +} // namespace ui + +#endif // UI_BASE_DRAGDROP_OS_EXCHANGE_DATA_PROVIDER_WIN_H_ diff --git a/ui/base/dragdrop/os_exchange_data_win_unittest.cc b/ui/base/dragdrop/os_exchange_data_win_unittest.cc new file mode 100644 index 0000000..d9509b9 --- /dev/null +++ b/ui/base/dragdrop/os_exchange_data_win_unittest.cc @@ -0,0 +1,377 @@ +// 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/dragdrop/os_exchange_data.h" +#include "ui/base/dragdrop/os_exchange_data_provider_win.h" +#include "base/pickle.h" +#include "base/ref_counted.h" +#include "base/scoped_handle.h" +#include "base/scoped_ptr.h" +#include "base/utf_string_conversions.h" +#include "base/win/scoped_hglobal.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "ui/base/clipboard/clipboard_util_win.h" + +namespace ui { + +typedef testing::Test OSExchangeDataTest; + +namespace { + +OSExchangeData::Provider* CloneProvider(const OSExchangeData& data) { + return new OSExchangeDataProviderWin( + OSExchangeDataProviderWin::GetIDataObject(data)); +} + +} // namespace + +// Test setting/getting using the OSExchangeData API +TEST(OSExchangeDataTest, StringDataGetAndSet) { + OSExchangeData data; + std::wstring input = L"I can has cheezburger?"; + data.SetString(input); + + OSExchangeData data2(CloneProvider(data)); + std::wstring output; + EXPECT_TRUE(data2.GetString(&output)); + EXPECT_EQ(input, output); + std::string url_spec = "http://www.goats.com/"; + GURL url(url_spec); + std::wstring title; + EXPECT_FALSE(data2.GetURLAndTitle(&url, &title)); + // No URLs in |data|, so url should be untouched. + EXPECT_EQ(url_spec, url.spec()); +} + +// Test getting using the IDataObject COM API +TEST(OSExchangeDataTest, StringDataAccessViaCOM) { + OSExchangeData data; + std::wstring input = L"O hai googlz."; + data.SetString(input); + ScopedComPtr<IDataObject> com_data( + OSExchangeDataProviderWin::GetIDataObject(data)); + + FORMATETC format_etc = + { CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + EXPECT_EQ(S_OK, com_data->QueryGetData(&format_etc)); + + STGMEDIUM medium; + EXPECT_EQ(S_OK, com_data->GetData(&format_etc, &medium)); + std::wstring output = + base::win::ScopedHGlobal<wchar_t>(medium.hGlobal).get(); + EXPECT_EQ(input, output); + ReleaseStgMedium(&medium); +} + +// Test setting using the IDataObject COM API +TEST(OSExchangeDataTest, StringDataWritingViaCOM) { + OSExchangeData data; + std::wstring input = L"http://www.google.com/"; + + ScopedComPtr<IDataObject> com_data( + OSExchangeDataProviderWin::GetIDataObject(data)); + + // Store data in the object using the COM SetData API. + CLIPFORMAT cfstr_ineturl = RegisterClipboardFormat(CFSTR_INETURL); + FORMATETC format_etc = + { cfstr_ineturl, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + STGMEDIUM medium; + medium.tymed = TYMED_HGLOBAL; + HGLOBAL glob = GlobalAlloc(GPTR, sizeof(wchar_t) * (input.size() + 1)); + size_t stringsz = input.size(); + SIZE_T sz = GlobalSize(glob); + base::win::ScopedHGlobal<wchar_t> global_lock(glob); + wchar_t* buffer_handle = global_lock.get(); + wcscpy_s(buffer_handle, input.size() + 1, input.c_str()); + medium.hGlobal = glob; + medium.pUnkForRelease = NULL; + EXPECT_EQ(S_OK, com_data->SetData(&format_etc, &medium, TRUE)); + + // Construct a new object with the old object so that we can use our access + // APIs. + OSExchangeData data2(CloneProvider(data)); + EXPECT_TRUE(data2.HasURL()); + GURL url_from_data; + std::wstring title; + EXPECT_TRUE(data2.GetURLAndTitle(&url_from_data, &title)); + GURL reference_url(input); + EXPECT_EQ(reference_url.spec(), url_from_data.spec()); +} + +TEST(OSExchangeDataTest, URLDataAccessViaCOM) { + OSExchangeData data; + GURL url("http://www.google.com/"); + data.SetURL(url, L""); + ScopedComPtr<IDataObject> com_data( + OSExchangeDataProviderWin::GetIDataObject(data)); + + CLIPFORMAT cfstr_ineturl = RegisterClipboardFormat(CFSTR_INETURL); + FORMATETC format_etc = + { cfstr_ineturl, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + EXPECT_EQ(S_OK, com_data->QueryGetData(&format_etc)); + + STGMEDIUM medium; + EXPECT_EQ(S_OK, com_data->GetData(&format_etc, &medium)); + std::wstring output = + base::win::ScopedHGlobal<wchar_t>(medium.hGlobal).get(); + EXPECT_EQ(url.spec(), WideToUTF8(output)); + ReleaseStgMedium(&medium); +} + +TEST(OSExchangeDataTest, MultipleFormatsViaCOM) { + OSExchangeData data; + std::string url_spec = "http://www.google.com/"; + GURL url(url_spec); + std::wstring text = L"O hai googlz."; + data.SetURL(url, L"Google"); + data.SetString(text); + + ScopedComPtr<IDataObject> com_data( + OSExchangeDataProviderWin::GetIDataObject(data)); + + CLIPFORMAT cfstr_ineturl = RegisterClipboardFormat(CFSTR_INETURL); + FORMATETC url_format_etc = + { cfstr_ineturl, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + EXPECT_EQ(S_OK, com_data->QueryGetData(&url_format_etc)); + FORMATETC text_format_etc = + { CF_UNICODETEXT, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + EXPECT_EQ(S_OK, com_data->QueryGetData(&text_format_etc)); + + STGMEDIUM medium; + EXPECT_EQ(S_OK, com_data->GetData(&url_format_etc, &medium)); + std::wstring output_url = + base::win::ScopedHGlobal<wchar_t>(medium.hGlobal).get(); + EXPECT_EQ(url.spec(), WideToUTF8(output_url)); + ReleaseStgMedium(&medium); + + // The text is supposed to be the raw text of the URL, _NOT_ the value of + // |text|! This is because the URL is added first and thus takes precedence! + EXPECT_EQ(S_OK, com_data->GetData(&text_format_etc, &medium)); + std::wstring output_text = + base::win::ScopedHGlobal<wchar_t>(medium.hGlobal).get(); + EXPECT_EQ(url_spec, WideToUTF8(output_text)); + ReleaseStgMedium(&medium); +} + +TEST(OSExchangeDataTest, EnumerationViaCOM) { + OSExchangeData data; + data.SetURL(GURL("http://www.google.com/"), L""); + data.SetString(L"O hai googlz."); + + CLIPFORMAT cfstr_file_group_descriptor = + RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR); + CLIPFORMAT text_x_moz_url = RegisterClipboardFormat(L"text/x-moz-url"); + + ScopedComPtr<IDataObject> com_data( + OSExchangeDataProviderWin::GetIDataObject(data)); + ScopedComPtr<IEnumFORMATETC> enumerator; + EXPECT_EQ(S_OK, com_data.get()->EnumFormatEtc(DATADIR_GET, + enumerator.Receive())); + + // Test that we can get one item. + { + // Explictly don't reset the first time, to verify the creation state is + // OK. + ULONG retrieved = 0; + FORMATETC elements_array[1]; + EXPECT_EQ(S_OK, enumerator->Next(1, + reinterpret_cast<FORMATETC*>(&elements_array), &retrieved)); + EXPECT_EQ(1, retrieved); + EXPECT_EQ(text_x_moz_url, elements_array[0].cfFormat); + } + + // Test that we can get one item with a NULL retrieved value. + { + EXPECT_EQ(S_OK, enumerator->Reset()); + FORMATETC elements_array[1]; + EXPECT_EQ(S_OK, enumerator->Next(1, + reinterpret_cast<FORMATETC*>(&elements_array), NULL)); + EXPECT_EQ(text_x_moz_url, elements_array[0].cfFormat); + } + + // Test that we can get two items. + { + EXPECT_EQ(S_OK, enumerator->Reset()); + ULONG retrieved = 0; + FORMATETC elements_array[2]; + EXPECT_EQ(S_OK, enumerator->Next(2, + reinterpret_cast<FORMATETC*>(&elements_array), &retrieved)); + EXPECT_EQ(2, retrieved); + EXPECT_EQ(text_x_moz_url, elements_array[0].cfFormat); + EXPECT_EQ(cfstr_file_group_descriptor, elements_array[1].cfFormat); + } + + // Test that we can skip the first item. + { + EXPECT_EQ(S_OK, enumerator->Reset()); + EXPECT_EQ(S_OK, enumerator->Skip(1)); + ULONG retrieved = 0; + FORMATETC elements_array[1]; + EXPECT_EQ(S_OK, enumerator->Next(1, + reinterpret_cast<FORMATETC*>(&elements_array), &retrieved)); + EXPECT_EQ(1, retrieved); + EXPECT_EQ(cfstr_file_group_descriptor, elements_array[0].cfFormat); + } + + // Test that we can skip the first item, and create a clone that matches in + // this state, and modify the original without affecting the clone. + { + EXPECT_EQ(S_OK, enumerator->Reset()); + EXPECT_EQ(S_OK, enumerator->Skip(1)); + ScopedComPtr<IEnumFORMATETC> cloned_enumerator; + EXPECT_EQ(S_OK, enumerator.get()->Clone(cloned_enumerator.Receive())); + EXPECT_EQ(S_OK, enumerator.get()->Reset()); + + { + ULONG retrieved = 0; + FORMATETC elements_array[1]; + EXPECT_EQ(S_OK, cloned_enumerator->Next(1, + reinterpret_cast<FORMATETC*>(&elements_array), &retrieved)); + EXPECT_EQ(1, retrieved); + EXPECT_EQ(cfstr_file_group_descriptor, elements_array[0].cfFormat); + } + + { + ULONG retrieved = 0; + FORMATETC elements_array[1]; + EXPECT_EQ(S_OK, enumerator->Next(1, + reinterpret_cast<FORMATETC*>(&elements_array), &retrieved)); + EXPECT_EQ(1, retrieved); + EXPECT_EQ(text_x_moz_url, elements_array[0].cfFormat); + } + } +} + +TEST(OSExchangeDataTest, TestURLExchangeFormats) { + OSExchangeData data; + std::string url_spec = "http://www.google.com/"; + GURL url(url_spec); + std::wstring url_title = L"Google"; + data.SetURL(url, url_title); + std::wstring output; + + OSExchangeData data2(CloneProvider(data)); + + // URL spec and title should match + GURL output_url; + std::wstring output_title; + EXPECT_TRUE(data2.GetURLAndTitle(&output_url, &output_title)); + EXPECT_EQ(url_spec, output_url.spec()); + EXPECT_EQ(url_title, output_title); + std::wstring output_string; + + // URL should be the raw text response + EXPECT_TRUE(data2.GetString(&output_string)); + EXPECT_EQ(url_spec, WideToUTF8(output_string)); + + // File contents access via COM + ScopedComPtr<IDataObject> com_data( + OSExchangeDataProviderWin::GetIDataObject(data)); + { + CLIPFORMAT cfstr_file_contents = + RegisterClipboardFormat(CFSTR_FILECONTENTS); + FORMATETC format_etc = + { cfstr_file_contents, NULL, DVASPECT_CONTENT, 0, TYMED_HGLOBAL }; + EXPECT_EQ(S_OK, com_data->QueryGetData(&format_etc)); + + STGMEDIUM medium; + EXPECT_EQ(S_OK, com_data->GetData(&format_etc, &medium)); + base::win::ScopedHGlobal<char> glob(medium.hGlobal); + std::string output(glob.get(), glob.Size()); + std::string file_contents = "[InternetShortcut]\r\nURL="; + file_contents += url_spec; + file_contents += "\r\n"; + EXPECT_EQ(file_contents, output); + ReleaseStgMedium(&medium); + } +} + +TEST(OSExchangeDataTest, TestPickledData) { + CLIPFORMAT test_cf = RegisterClipboardFormat(L"chrome/test"); + + Pickle saved_pickle; + saved_pickle.WriteInt(1); + saved_pickle.WriteInt(2); + OSExchangeData data; + data.SetPickledData(test_cf, saved_pickle); + + OSExchangeData copy(CloneProvider(data)); + EXPECT_TRUE(copy.HasCustomFormat(test_cf)); + + Pickle restored_pickle; + EXPECT_TRUE(copy.GetPickledData(test_cf, &restored_pickle)); + void* p_iterator = NULL; + int value; + EXPECT_TRUE(restored_pickle.ReadInt(&p_iterator, &value)); + EXPECT_EQ(1, value); + EXPECT_TRUE(restored_pickle.ReadInt(&p_iterator, &value)); + EXPECT_EQ(2, value); +} + +TEST(OSExchangeDataTest, FileContents) { + OSExchangeData data; + std::string file_contents("data\0with\0nulls", 15); + data.SetFileContents(L"filename.txt", file_contents); + + OSExchangeData copy(CloneProvider(data)); + std::wstring filename; + std::string read_contents; + EXPECT_TRUE(copy.GetFileContents(&filename, &read_contents)); + EXPECT_EQ(L"filename.txt", filename); + EXPECT_EQ(file_contents, read_contents); +} + +TEST(OSExchangeDataTest, Html) { + OSExchangeData data; + GURL url("http://www.google.com/"); + std::wstring html( + L"<HTML>\n<BODY>\n" + L"<b>bold.</b> <i><b>This is bold italic.</b></i>\n" + L"</BODY>\n</HTML>"); + data.SetHtml(html, url); + + OSExchangeData copy(CloneProvider(data)); + std::wstring read_html; + EXPECT_TRUE(copy.GetHtml(&read_html, &url)); + EXPECT_EQ(html, read_html); + + // Check the CF_HTML too. + std::string expected_cf_html( + "Version:0.9\r\nStartHTML:0000000139\r\nEndHTML:0000000292\r\n" + "StartFragment:0000000177\r\nEndFragment:0000000254\r\n" + "SourceURL:http://www.google.com/\r\n<html>\r\n<body>\r\n" + "<!--StartFragment-->\r\n"); + expected_cf_html += WideToUTF8(html); + expected_cf_html.append("\r\n<!--EndFragment-->\r\n</body>\r\n</html>"); + + STGMEDIUM medium; + IDataObject* data_object = OSExchangeDataProviderWin::GetIDataObject(data); + EXPECT_EQ(S_OK, + data_object->GetData(ui::ClipboardUtil::GetHtmlFormat(), &medium)); + base::win::ScopedHGlobal<char> glob(medium.hGlobal); + std::string output(glob.get(), glob.Size()); + EXPECT_EQ(expected_cf_html, output); + ReleaseStgMedium(&medium); +} + +TEST(OSExchangeDataTest, SetURLWithMaxPath) { + OSExchangeData data; + std::wstring long_title(L'a', MAX_PATH + 1); + data.SetURL(GURL("http://google.com"), long_title); +} + +TEST(OSExchangeDataTest, ProvideURLForPlainTextURL) { + OSExchangeData data; + data.SetString(L"http://google.com"); + + OSExchangeData data2(CloneProvider(data)); + ASSERT_TRUE(data2.HasURL()); + GURL read_url; + std::wstring title; + EXPECT_TRUE(data2.GetURLAndTitle(&read_url, &title)); + EXPECT_EQ(GURL("http://google.com"), read_url); +} + +} // namespace ui |