From 321792ef1baac764227d55fc944f32cf29e50c6a Mon Sep 17 00:00:00 2001 From: "sky@chromium.org" Date: Wed, 13 May 2009 23:21:45 +0000 Subject: Stubs out OSExchangeData for views on gtk. BUG=none TEST=none Review URL: http://codereview.chromium.org/113321 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@16012 0039d316-1c4b-4281-b951-d872f2087c98 --- app/app.vcproj | 2 +- app/os_exchange_data.cc | 706 --------------------- app/os_exchange_data.h | 23 +- app/os_exchange_data_gtk.cc | 75 +++ app/os_exchange_data_unittest.cc | 365 ----------- app/os_exchange_data_win.cc | 706 +++++++++++++++++++++ app/os_exchange_data_win_unittest.cc | 365 +++++++++++ build/build_config.h | 1 + chrome/browser/bookmarks/bookmark_drag_data.cc | 3 +- .../bookmarks/bookmark_drag_data_unittest.cc | 4 +- chrome/chrome.gyp | 14 +- chrome/common/temp_scaffolding_stubs.h | 2 + chrome/test/unit/unittests.vcproj | 2 +- 13 files changed, 1186 insertions(+), 1082 deletions(-) delete mode 100644 app/os_exchange_data.cc create mode 100644 app/os_exchange_data_gtk.cc delete mode 100644 app/os_exchange_data_unittest.cc create mode 100644 app/os_exchange_data_win.cc create mode 100644 app/os_exchange_data_win_unittest.cc diff --git a/app/app.vcproj b/app/app.vcproj index 64b07d87..cc618af 100644 --- a/app/app.vcproj +++ b/app/app.vcproj @@ -222,7 +222,7 @@ > - -#include "app/os_exchange_data.h" - -#include "app/l10n_util.h" -#include "base/clipboard_util.h" -#include "base/file_util.h" -#include "base/logging.h" -#include "base/pickle.h" -#include "base/scoped_handle.h" -#include "base/stl_util-inl.h" -#include "base/string_util.h" -#include "googleurl/src/gurl.h" -#include "grit/generated_resources.h" -#include "net/base/net_util.h" - -// 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 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(OSExchangeData::StoredData::const_iterator begin, - OSExchangeData::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 contents_; - - // The cursor of the active enumeration - an index into |contents_|. - int cursor_; - - LONG ref_count_; - - DISALLOW_EVIL_CONSTRUCTORS(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(CoTaskMemAlloc(sizeof(DVTARGETDEVICE))); - *(clone->ptd) = *(source->ptd); - } -} - -FormatEtcEnumerator::FormatEtcEnumerator( - OSExchangeData::StoredData::const_iterator start, - OSExchangeData::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|. - int index = 0; - while (cursor_ < static_cast(contents_.size()) && - static_cast(index) < count) { - CloneFormatEtc(contents_.at(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_ <= static_cast(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::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; -} - -/////////////////////////////////////////////////////////////////////////////// -// OSExchangeData, public: - -// static -bool OSExchangeData::HasPlainTextURL(IDataObject* source) { - std::wstring plain_text; - return (ClipboardUtil::GetPlainText(source, &plain_text) && - !plain_text.empty() && GURL(plain_text).is_valid()); -} - -// static -bool OSExchangeData::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; -} - -OSExchangeData::OSExchangeData() - : ref_count_(0) { -} - -OSExchangeData::OSExchangeData(IDataObject* source) - : ref_count_(0) { - source_object_ = source; -} - -OSExchangeData::~OSExchangeData() { - STLDeleteContainerPointers(contents_.begin(), contents_.end()); -} - -void OSExchangeData::SetString(const std::wstring& data) { - STGMEDIUM* storage = GetStorageForWString(data); - contents_.push_back(new StoredDataInfo(CF_UNICODETEXT, storage)); - - // Also add plain text. - storage = GetStorageForString(WideToUTF8(data)); - contents_.push_back(new StoredDataInfo(CF_TEXT, storage)); -} - -void OSExchangeData::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); - contents_.push_back(new 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())); - contents_.push_back(new StoredDataInfo( - ClipboardUtil::GetUrlWFormat()->cfFormat, storage)); - storage = GetStorageForString(url.spec()); - contents_.push_back(new StoredDataInfo( - ClipboardUtil::GetUrlFormat()->cfFormat, storage)); - - // TODO(beng): (http://b/1085501) add CF_HTML... - - // Also add text representations (these should be last since they're the - // least preferable). - storage = GetStorageForWString(UTF8ToWide(url.spec())); - contents_.push_back(new StoredDataInfo(CF_UNICODETEXT, storage)); - storage = GetStorageForString(url.spec()); - contents_.push_back(new StoredDataInfo(CF_TEXT, storage)); -} - -void OSExchangeData::SetFilename(const std::wstring& full_path) { - const size_t drop_size = sizeof(DROPFILES); - const size_t bytes = drop_size + (full_path.length() + 2) * sizeof(wchar_t); - HANDLE hdata = ::GlobalAlloc(GMEM_MOVEABLE, bytes); - if (!hdata) - return; - - ScopedHGlobal locked_mem(hdata); - DROPFILES* drop_files = locked_mem.get(); - drop_files->pFiles = sizeof(DROPFILES); - drop_files->fWide = TRUE; - wchar_t* data = reinterpret_cast((BYTE*)(drop_files) + drop_size); - 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 - - // Set up the STGMEDIUM - STGMEDIUM* storage = new STGMEDIUM; - storage->tymed = TYMED_HGLOBAL; - storage->hGlobal = drop_files; - storage->pUnkForRelease = NULL; - - // Set up the StoredDataInfo - StoredDataInfo* info = new StoredDataInfo(CF_HDROP, storage); - contents_.push_back(info); -} - -void OSExchangeData::SetPickledData(CLIPFORMAT format, const Pickle& data) { - STGMEDIUM* storage = GetStorageForString( - std::string(static_cast(data.data()), - static_cast(data.size()))); - contents_.push_back(new StoredDataInfo(format, storage)); -} - -void OSExchangeData::SetFileContents(const std::wstring& filename, - const std::string& file_contents) { - // Add CFSTR_FILEDESCRIPTOR - STGMEDIUM* storage = GetStorageForFileDescriptor(filename); - contents_.push_back(new StoredDataInfo( - ClipboardUtil::GetFileDescriptorFormat()->cfFormat, storage)); - - // Add CFSTR_FILECONTENTS - storage = GetStorageForBytes(file_contents.data(), file_contents.length()); - contents_.push_back(new StoredDataInfo( - ClipboardUtil::GetFileContentFormatZero()->cfFormat, storage)); -} - -void OSExchangeData::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()); - contents_.push_back(new StoredDataInfo( - ClipboardUtil::GetHtmlFormat()->cfFormat, storage)); - - STGMEDIUM* storage_plain = GetStorageForBytes(utf8_html.c_str(), - utf8_html.size()); - contents_.push_back(new StoredDataInfo( - ClipboardUtil::GetTextHtmlFormat()->cfFormat, storage_plain)); -} - -bool OSExchangeData::GetString(std::wstring* data) const { - return ClipboardUtil::GetPlainText(source_object_, data); -} - -bool OSExchangeData::GetURLAndTitle(GURL* url, std::wstring* title) const { - std::wstring url_str; - bool success = ClipboardUtil::GetUrl(source_object_, &url_str, title); - 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 OSExchangeData::GetFilename(std::wstring* full_path) const { - std::vector filenames; - bool success = ClipboardUtil::GetFilenames(source_object_, &filenames); - if (success) - full_path->assign(filenames[0]); - return success; -} - -bool OSExchangeData::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) { - ScopedHGlobal 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(c_data.Size() - 1)); - success = true; - } - ReleaseStgMedium(&medium); - } - return success; -} - -bool OSExchangeData::GetFileContents(std::wstring* filename, - std::string* file_contents) const { - return ClipboardUtil::GetFileContents(source_object_, filename, - file_contents); -} - -bool OSExchangeData::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 OSExchangeData::HasString() const { - return ClipboardUtil::HasPlainText(source_object_); -} - -bool OSExchangeData::HasURL() const { - return (ClipboardUtil::HasUrl(source_object_) || - HasPlainTextURL(source_object_)); -} - -bool OSExchangeData::HasFile() const { - return ClipboardUtil::HasFilenames(source_object_); -} - -bool OSExchangeData::HasFormat(CLIPFORMAT format) const { - FORMATETC format_etc = - { format, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; - return (source_object_->QueryGetData(&format_etc) == S_OK); -} - -/////////////////////////////////////////////////////////////////////////////// -// OSExchangeData, 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(OleDuplicateData( - source->hGlobal, source_clipformat, 0)); - break; - case TYMED_MFPICT: - destination->hMetaFilePict = - static_cast(OleDuplicateData( - source->hMetaFilePict, source_clipformat, 0)); - break; - case TYMED_GDI: - destination->hBitmap = - static_cast(OleDuplicateData( - source->hBitmap, source_clipformat, 0)); - break; - case TYMED_ENHMF: - destination->hEnhMetaFile = - static_cast(OleDuplicateData( - source->hEnhMetaFile, source_clipformat, 0)); - break; - case TYMED_FILE: - destination->lpszFileName = - static_cast(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(); -} - -HRESULT OSExchangeData::GetData(FORMATETC* format_etc, STGMEDIUM* medium) { - StoredData::const_iterator iter = contents_.begin(); - while (iter != contents_.end()) { - if ((*iter)->format_etc.cfFormat == format_etc->cfFormat) { - DuplicateMedium((*iter)->format_etc.cfFormat, (*iter)->medium, medium); - return S_OK; - } - ++iter; - } - - return DV_E_FORMATETC; -} - -HRESULT OSExchangeData::GetDataHere(FORMATETC* format_etc, STGMEDIUM* medium) { - return DATA_E_FORMATETC; -} - -HRESULT OSExchangeData::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 OSExchangeData::GetCanonicalFormatEtc( - FORMATETC* format_etc, FORMATETC* result) { - format_etc->ptd = NULL; - return E_NOTIMPL; -} - -HRESULT OSExchangeData::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); - } - - StoredDataInfo* info = - new 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 OSExchangeData::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 OSExchangeData::DAdvise( - FORMATETC* format_etc, DWORD advf, IAdviseSink* sink, DWORD* connection) { - return OLE_E_ADVISENOTSUPPORTED; -} - -HRESULT OSExchangeData::DUnadvise(DWORD connection) { - return OLE_E_ADVISENOTSUPPORTED; -} - -HRESULT OSExchangeData::EnumDAdvise(IEnumSTATDATA** enumerator) { - return OLE_E_ADVISENOTSUPPORTED; -} - -/////////////////////////////////////////////////////////////////////////////// -// OSExchangeData, IUnknown implementation: - -HRESULT OSExchangeData::QueryInterface(const IID& iid, void** object) { - *object = NULL; - if (IsEqualIID(iid, IID_IUnknown) || IsEqualIID(iid, IID_IDataObject)) { - *object = this; - } else { - return E_NOINTERFACE; - } - AddRef(); - return S_OK; -} - -ULONG OSExchangeData::AddRef() { - return InterlockedIncrement(&ref_count_); -} - -ULONG OSExchangeData::Release() { - if (InterlockedDecrement(&ref_count_) == 0) { - ULONG copied_refcnt = ref_count_; - delete this; - return copied_refcnt; - } - return ref_count_; -} - -/////////////////////////////////////////////////////////////////////////////// -// OSExchangeData, private: - -static STGMEDIUM* GetStorageForBytes(const char* data, size_t bytes) { - HANDLE handle = GlobalAlloc(GPTR, static_cast(bytes)); - ScopedHGlobal scoped(handle); - size_t allocated = static_cast(GlobalSize(handle)); - memcpy(scoped.get(), data, allocated); - - STGMEDIUM* storage = new STGMEDIUM; - storage->hGlobal = handle; - storage->tymed = TYMED_HGLOBAL; - storage->pUnkForRelease = NULL; - return storage; -} - -template -static HGLOBAL CopyStringToGlobalHandle(const T& payload) { - int bytes = static_cast(payload.size() + 1) * sizeof(T::value_type); - HANDLE handle = GlobalAlloc(GPTR, bytes); - void* data = GlobalLock(handle); - size_t allocated = static_cast(GlobalSize(handle)); - memcpy(data, payload.c_str(), allocated); - static_cast(data)[payload.size()] = '\0'; - GlobalUnlock(handle); - return handle; -} - -static STGMEDIUM* GetStorageForWString(const std::wstring& data) { - STGMEDIUM* storage = new STGMEDIUM; - storage->hGlobal = CopyStringToGlobalHandle(data); - storage->tymed = TYMED_HGLOBAL; - storage->pUnkForRelease = NULL; - return storage; -} - -static STGMEDIUM* GetStorageForString(const std::string& data) { - STGMEDIUM* storage = new STGMEDIUM; - storage->hGlobal = CopyStringToGlobalHandle(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(), std::wstring()); - } else { - // Nothing else can be done, just use a default. - *validated = l10n_util::GetString(IDS_UNTITLED_SHORTCUT_FILE_NAME); - } - } else { - *validated = title; - file_util::ReplaceIllegalCharacters(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* 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(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; -} diff --git a/app/os_exchange_data.h b/app/os_exchange_data.h index 5668008..046c77c 100644 --- a/app/os_exchange_data.h +++ b/app/os_exchange_data.h @@ -5,8 +5,12 @@ #ifndef APP_OS_EXCHANGE_DATA_H_ #define APP_OS_EXCHANGE_DATA_H_ +#include "build/build_config.h" + +#if defined(OS_WIN) #include #include +#endif #include #include "base/basictypes.h" @@ -23,8 +27,13 @@ class Pickle; // translating that into something the OS can understand. // /////////////////////////////////////////////////////////////////////////////// +#if defined(OS_WIN) class OSExchangeData : public IDataObject { +#else +class OSExchangeData { +#endif public: +#if defined(OS_WIN) // Returns true if source has plain text that is a valid url. static bool HasPlainTextURL(IDataObject* source); @@ -32,8 +41,10 @@ class OSExchangeData : public IDataObject { // that url. static bool GetPlainTextURL(IDataObject* source, GURL* url); + explicit OSExchangeData(IDataObject* source); +#endif + OSExchangeData(); - OSExchangeData(IDataObject* source); virtual ~OSExchangeData(); // These functions add data to the OSExchangeData object of various Chrome @@ -51,8 +62,10 @@ class OSExchangeData : public IDataObject { void SetURL(const GURL& url, const std::wstring& title); // A full path to a file void SetFilename(const std::wstring& full_path); +#if defined(OS_WIN) // Adds pickled data of the specified format. void SetPickledData(CLIPFORMAT format, const Pickle& data); +#endif // Adds the bytes of a file (CFSTR_FILECONTENTS and CFSTR_FILEDESCRIPTOR). void SetFileContents(const std::wstring& filename, const std::string& file_contents); @@ -68,7 +81,9 @@ class OSExchangeData : public IDataObject { bool GetURLAndTitle(GURL* url, std::wstring* title) const; // Return the path of a file, if available. bool GetFilename(std::wstring* full_path) const; +#if defined(OS_WIN) bool GetPickledData(CLIPFORMAT format, Pickle* data) const; +#endif bool GetFileContents(std::wstring* filename, std::string* file_contents) const; bool GetHtml(std::wstring* html, GURL* base_url) const; @@ -79,6 +94,7 @@ class OSExchangeData : public IDataObject { bool HasURL() const; bool HasURLTitle() const; bool HasFile() const; +#if defined(OS_WIN) bool HasFormat(CLIPFORMAT format) const; // IDataObject implementation: @@ -100,8 +116,10 @@ class OSExchangeData : public IDataObject { HRESULT __stdcall QueryInterface(const IID& iid, void** object); ULONG __stdcall AddRef(); ULONG __stdcall Release(); +#endif private: +#if defined(OS_WIN) // FormatEtcEnumerator only likes us for our StoredDataMap typedef. friend class FormatEtcEnumerator; @@ -137,8 +155,9 @@ class OSExchangeData : public IDataObject { CComPtr source_object_; LONG ref_count_; +#endif - DISALLOW_EVIL_CONSTRUCTORS(OSExchangeData); + DISALLOW_COPY_AND_ASSIGN(OSExchangeData); }; #endif // #ifndef APP_OS_EXCHANGE_DATA_H_ diff --git a/app/os_exchange_data_gtk.cc b/app/os_exchange_data_gtk.cc new file mode 100644 index 0000000..b599470 --- /dev/null +++ b/app/os_exchange_data_gtk.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2009 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 "app/os_exchange_data.h" + +#include "base/logging.h" + +OSExchangeData::OSExchangeData() { +} + +OSExchangeData::~OSExchangeData() { +} + +void OSExchangeData::SetString(const std::wstring& data) { + NOTIMPLEMENTED(); +} + +void OSExchangeData::SetURL(const GURL& url, const std::wstring& title) { + NOTIMPLEMENTED(); +} + +void OSExchangeData::SetFilename(const std::wstring& full_path) { + NOTIMPLEMENTED(); +} + +void OSExchangeData::SetFileContents(const std::wstring& filename, + const std::string& file_contents) { + NOTIMPLEMENTED(); +} + +void OSExchangeData::SetHtml(const std::wstring& html, const GURL& base_url) { + NOTIMPLEMENTED(); +} + +bool OSExchangeData::GetString(std::wstring* data) const { + NOTIMPLEMENTED(); + return false; +} + +bool OSExchangeData::GetURLAndTitle(GURL* url, std::wstring* title) const { + NOTIMPLEMENTED(); + return false; +} + +bool OSExchangeData::GetFilename(std::wstring* full_path) const { + NOTIMPLEMENTED(); + return false; +} + +bool OSExchangeData::GetFileContents(std::wstring* filename, + std::string* file_contents) const { + NOTIMPLEMENTED(); + return false; +} + +bool OSExchangeData::GetHtml(std::wstring* html, GURL* base_url) const { + NOTIMPLEMENTED(); + return false; +} + +bool OSExchangeData::HasString() const { + NOTIMPLEMENTED(); + return false; +} + +bool OSExchangeData::HasURL() const { + NOTIMPLEMENTED(); + return false; +} + +bool OSExchangeData::HasFile() const { + NOTIMPLEMENTED(); + return false; +} diff --git a/app/os_exchange_data_unittest.cc b/app/os_exchange_data_unittest.cc deleted file mode 100644 index 13a28a9..0000000 --- a/app/os_exchange_data_unittest.cc +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright (c) 2006-2008 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 -#include - -#include "app/os_exchange_data.h" -#include "base/clipboard_util.h" -#include "base/pickle.h" -#include "base/ref_counted.h" -#include "base/scoped_handle.h" -#include "base/scoped_ptr.h" -#include "base/string_util.h" -#include "googleurl/src/gurl.h" -#include "testing/gtest/include/gtest/gtest.h" - -typedef testing::Test OSExchangeDataTest; - -// Test setting/getting using the OSExchangeData API -TEST(OSExchangeDataTest, StringDataGetAndSet) { - OSExchangeData* data = new OSExchangeData; - std::wstring input = L"I can has cheezburger?"; - data->SetString(input); - - OSExchangeData* data2 = new OSExchangeData(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()); - // data gets freed when data2 releases the ref on it - delete data2; -} - -// Test getting using the IDataObject COM API -TEST(OSExchangeDataTest, StringDataAccessViaCOM) { - OSExchangeData* data = new OSExchangeData; - std::wstring input = L"O hai googlz."; - data->SetString(input); - CComPtr com_data(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 = - ScopedHGlobal(medium.hGlobal).get(); - EXPECT_EQ(input, output); - ReleaseStgMedium(&medium); - - // data is freed automatically by CComPtr. -} - -// Test setting using the IDataObject COM API -TEST(OSExchangeDataTest, StringDataWritingViaCOM) { - OSExchangeData* data = new OSExchangeData; - std::wstring input = L"http://www.google.com/"; - - CComPtr com_data(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); - ScopedHGlobal 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 = new OSExchangeData(com_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()); - // deleting data2 will free data because it holds a ref to it. - delete data2; -} - -TEST(OSExchangeDataTest, URLDataAccessViaCOM) { - OSExchangeData* data = new OSExchangeData; - GURL url("http://www.google.com/"); - data->SetURL(url, L""); - CComPtr com_data(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 = - ScopedHGlobal(medium.hGlobal).get(); - EXPECT_EQ(url.spec(), WideToUTF8(output)); - ReleaseStgMedium(&medium); -} - -TEST(OSExchangeDataTest, MultipleFormatsViaCOM) { - OSExchangeData* data = new OSExchangeData; - 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); - - CComPtr com_data(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 = - ScopedHGlobal(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 = - ScopedHGlobal(medium.hGlobal).get(); - EXPECT_EQ(url_spec, WideToUTF8(output_text)); - ReleaseStgMedium(&medium); -} - -TEST(OSExchangeDataTest, EnumerationViaCOM) { - OSExchangeData* data = new OSExchangeData; - 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"); - - CComPtr com_data(data); - CComPtr enumerator; - EXPECT_EQ(S_OK, com_data->EnumFormatEtc(DATADIR_GET, &enumerator)); - - // 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(&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(&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(&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(&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)); - CComPtr cloned_enumerator; - EXPECT_EQ(S_OK, enumerator->Clone(&cloned_enumerator)); - EXPECT_EQ(S_OK, enumerator->Reset()); - - { - ULONG retrieved = 0; - FORMATETC elements_array[1]; - EXPECT_EQ(S_OK, cloned_enumerator->Next(1, - reinterpret_cast(&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(&elements_array), &retrieved)); - EXPECT_EQ(1, retrieved); - EXPECT_EQ(text_x_moz_url, elements_array[0].cfFormat); - } - } -} - -TEST(OSExchangeDataTest, TestURLExchangeFormats) { - OSExchangeData* data = new OSExchangeData; - 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 = new OSExchangeData(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 - CComPtr com_data(data); - { - CLIPFORMAT cfstr_file_contents = - RegisterClipboardFormat(CFSTR_FILECONTENTS); - FORMATETC format_etc = - { cfstr_file_contents, 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)); - ScopedHGlobal 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); - } - - // Need to manually free data2 since we never stuff it into a COMPtr. - delete data2; -} - -TEST(OSExchangeDataTest, TestPickledData) { - CLIPFORMAT test_cf = RegisterClipboardFormat(L"chrome/test"); - - Pickle saved_pickle; - saved_pickle.WriteInt(1); - saved_pickle.WriteInt(2); - scoped_refptr data(new OSExchangeData()); - data->SetPickledData(test_cf, saved_pickle); - - scoped_refptr copy(new OSExchangeData(data.get())); - EXPECT_TRUE(copy->HasFormat(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) { - scoped_refptr data(new OSExchangeData); - std::string file_contents("data\0with\0nulls", 15); - data->SetFileContents(L"filename.txt", file_contents); - - scoped_refptr copy(new OSExchangeData(data.get())); - 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) { - scoped_refptr data(new OSExchangeData); - GURL url("http://www.google.com/"); - std::wstring html( - L"\n\n" - L"bold. This is bold italic.\n" - L"\n"); - data->SetHtml(html, url); - - scoped_refptr copy(new OSExchangeData(data.get())); - 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\r\n\r\n" - "\r\n"); - expected_cf_html += WideToUTF8(html); - expected_cf_html.append("\r\n\r\n\r\n"); - - STGMEDIUM medium; - EXPECT_EQ(S_OK, data->GetData(ClipboardUtil::GetHtmlFormat(), &medium)); - ScopedHGlobal glob(medium.hGlobal); - std::string output(glob.get(), glob.Size()); - EXPECT_EQ(expected_cf_html, output); - ReleaseStgMedium(&medium); -} - -TEST(OSExchangeDataTest, SetURLWithMaxPath) { - scoped_refptr data(new OSExchangeData); - std::wstring long_title(L'a', MAX_PATH + 1); - data->SetURL(GURL("http://google.com"), long_title); -} - -TEST(OSExchangeDataTest, ProvideURLForPlainTextURL) { - scoped_refptr data(new OSExchangeData); - data->SetString(L"http://google.com"); - - scoped_ptr data2(new OSExchangeData(data.get())); - 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); -} diff --git a/app/os_exchange_data_win.cc b/app/os_exchange_data_win.cc new file mode 100644 index 0000000..c9e2311 --- /dev/null +++ b/app/os_exchange_data_win.cc @@ -0,0 +1,706 @@ +// Copyright (c) 2006-2008 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 + +#include "app/os_exchange_data.h" + +#include "app/l10n_util.h" +#include "base/clipboard_util.h" +#include "base/file_util.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "base/scoped_handle.h" +#include "base/stl_util-inl.h" +#include "base/string_util.h" +#include "googleurl/src/gurl.h" +#include "grit/generated_resources.h" +#include "net/base/net_util.h" + +// 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 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(OSExchangeData::StoredData::const_iterator begin, + OSExchangeData::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 contents_; + + // The cursor of the active enumeration - an index into |contents_|. + int cursor_; + + LONG ref_count_; + + DISALLOW_EVIL_CONSTRUCTORS(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(CoTaskMemAlloc(sizeof(DVTARGETDEVICE))); + *(clone->ptd) = *(source->ptd); + } +} + +FormatEtcEnumerator::FormatEtcEnumerator( + OSExchangeData::StoredData::const_iterator start, + OSExchangeData::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|. + int index = 0; + while (cursor_ < static_cast(contents_.size()) && + static_cast(index) < count) { + CloneFormatEtc(contents_.at(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_ <= static_cast(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::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; +} + +/////////////////////////////////////////////////////////////////////////////// +// OSExchangeData, public: + +// static +bool OSExchangeData::HasPlainTextURL(IDataObject* source) { + std::wstring plain_text; + return (ClipboardUtil::GetPlainText(source, &plain_text) && + !plain_text.empty() && GURL(plain_text).is_valid()); +} + +// static +bool OSExchangeData::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; +} + +OSExchangeData::OSExchangeData() + : ref_count_(0) { +} + +OSExchangeData::OSExchangeData(IDataObject* source) + : ref_count_(0) { + source_object_ = source; +} + +OSExchangeData::~OSExchangeData() { + STLDeleteContainerPointers(contents_.begin(), contents_.end()); +} + +void OSExchangeData::SetString(const std::wstring& data) { + STGMEDIUM* storage = GetStorageForWString(data); + contents_.push_back(new StoredDataInfo(CF_UNICODETEXT, storage)); + + // Also add plain text. + storage = GetStorageForString(WideToUTF8(data)); + contents_.push_back(new StoredDataInfo(CF_TEXT, storage)); +} + +void OSExchangeData::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); + contents_.push_back(new 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())); + contents_.push_back(new StoredDataInfo( + ClipboardUtil::GetUrlWFormat()->cfFormat, storage)); + storage = GetStorageForString(url.spec()); + contents_.push_back(new StoredDataInfo( + ClipboardUtil::GetUrlFormat()->cfFormat, storage)); + + // TODO(beng): (http://b/1085501) add CF_HTML... + + // Also add text representations (these should be last since they're the + // least preferable). + storage = GetStorageForWString(UTF8ToWide(url.spec())); + contents_.push_back(new StoredDataInfo(CF_UNICODETEXT, storage)); + storage = GetStorageForString(url.spec()); + contents_.push_back(new StoredDataInfo(CF_TEXT, storage)); +} + +void OSExchangeData::SetFilename(const std::wstring& full_path) { + const size_t drop_size = sizeof(DROPFILES); + const size_t bytes = drop_size + (full_path.length() + 2) * sizeof(wchar_t); + HANDLE hdata = ::GlobalAlloc(GMEM_MOVEABLE, bytes); + if (!hdata) + return; + + ScopedHGlobal locked_mem(hdata); + DROPFILES* drop_files = locked_mem.get(); + drop_files->pFiles = sizeof(DROPFILES); + drop_files->fWide = TRUE; + wchar_t* data = reinterpret_cast((BYTE*)(drop_files) + drop_size); + 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 + + // Set up the STGMEDIUM + STGMEDIUM* storage = new STGMEDIUM; + storage->tymed = TYMED_HGLOBAL; + storage->hGlobal = drop_files; + storage->pUnkForRelease = NULL; + + // Set up the StoredDataInfo + StoredDataInfo* info = new StoredDataInfo(CF_HDROP, storage); + contents_.push_back(info); +} + +void OSExchangeData::SetPickledData(CLIPFORMAT format, const Pickle& data) { + STGMEDIUM* storage = GetStorageForString( + std::string(static_cast(data.data()), + static_cast(data.size()))); + contents_.push_back(new StoredDataInfo(format, storage)); +} + +void OSExchangeData::SetFileContents(const std::wstring& filename, + const std::string& file_contents) { + // Add CFSTR_FILEDESCRIPTOR + STGMEDIUM* storage = GetStorageForFileDescriptor(filename); + contents_.push_back(new StoredDataInfo( + ClipboardUtil::GetFileDescriptorFormat()->cfFormat, storage)); + + // Add CFSTR_FILECONTENTS + storage = GetStorageForBytes(file_contents.data(), file_contents.length()); + contents_.push_back(new StoredDataInfo( + ClipboardUtil::GetFileContentFormatZero()->cfFormat, storage)); +} + +void OSExchangeData::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()); + contents_.push_back(new StoredDataInfo( + ClipboardUtil::GetHtmlFormat()->cfFormat, storage)); + + STGMEDIUM* storage_plain = GetStorageForBytes(utf8_html.c_str(), + utf8_html.size()); + contents_.push_back(new StoredDataInfo( + ClipboardUtil::GetTextHtmlFormat()->cfFormat, storage_plain)); +} + +bool OSExchangeData::GetString(std::wstring* data) const { + return ClipboardUtil::GetPlainText(source_object_, data); +} + +bool OSExchangeData::GetURLAndTitle(GURL* url, std::wstring* title) const { + std::wstring url_str; + bool success = ClipboardUtil::GetUrl(source_object_, &url_str, title); + 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 OSExchangeData::GetFilename(std::wstring* full_path) const { + std::vector filenames; + bool success = ClipboardUtil::GetFilenames(source_object_, &filenames); + if (success) + full_path->assign(filenames[0]); + return success; +} + +bool OSExchangeData::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) { + ScopedHGlobal 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(c_data.Size() - 1)); + success = true; + } + ReleaseStgMedium(&medium); + } + return success; +} + +bool OSExchangeData::GetFileContents(std::wstring* filename, + std::string* file_contents) const { + return ClipboardUtil::GetFileContents(source_object_, filename, + file_contents); +} + +bool OSExchangeData::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 OSExchangeData::HasString() const { + return ClipboardUtil::HasPlainText(source_object_); +} + +bool OSExchangeData::HasURL() const { + return (ClipboardUtil::HasUrl(source_object_) || + HasPlainTextURL(source_object_)); +} + +bool OSExchangeData::HasFile() const { + return ClipboardUtil::HasFilenames(source_object_); +} + +bool OSExchangeData::HasFormat(CLIPFORMAT format) const { + FORMATETC format_etc = + { format, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }; + return (source_object_->QueryGetData(&format_etc) == S_OK); +} + +/////////////////////////////////////////////////////////////////////////////// +// OSExchangeData, 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(OleDuplicateData( + source->hGlobal, source_clipformat, 0)); + break; + case TYMED_MFPICT: + destination->hMetaFilePict = + static_cast(OleDuplicateData( + source->hMetaFilePict, source_clipformat, 0)); + break; + case TYMED_GDI: + destination->hBitmap = + static_cast(OleDuplicateData( + source->hBitmap, source_clipformat, 0)); + break; + case TYMED_ENHMF: + destination->hEnhMetaFile = + static_cast(OleDuplicateData( + source->hEnhMetaFile, source_clipformat, 0)); + break; + case TYMED_FILE: + destination->lpszFileName = + static_cast(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(); +} + +HRESULT OSExchangeData::GetData(FORMATETC* format_etc, STGMEDIUM* medium) { + StoredData::const_iterator iter = contents_.begin(); + while (iter != contents_.end()) { + if ((*iter)->format_etc.cfFormat == format_etc->cfFormat) { + DuplicateMedium((*iter)->format_etc.cfFormat, (*iter)->medium, medium); + return S_OK; + } + ++iter; + } + + return DV_E_FORMATETC; +} + +HRESULT OSExchangeData::GetDataHere(FORMATETC* format_etc, STGMEDIUM* medium) { + return DATA_E_FORMATETC; +} + +HRESULT OSExchangeData::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 OSExchangeData::GetCanonicalFormatEtc( + FORMATETC* format_etc, FORMATETC* result) { + format_etc->ptd = NULL; + return E_NOTIMPL; +} + +HRESULT OSExchangeData::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); + } + + StoredDataInfo* info = + new 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 OSExchangeData::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 OSExchangeData::DAdvise( + FORMATETC* format_etc, DWORD advf, IAdviseSink* sink, DWORD* connection) { + return OLE_E_ADVISENOTSUPPORTED; +} + +HRESULT OSExchangeData::DUnadvise(DWORD connection) { + return OLE_E_ADVISENOTSUPPORTED; +} + +HRESULT OSExchangeData::EnumDAdvise(IEnumSTATDATA** enumerator) { + return OLE_E_ADVISENOTSUPPORTED; +} + +/////////////////////////////////////////////////////////////////////////////// +// OSExchangeData, IUnknown implementation: + +HRESULT OSExchangeData::QueryInterface(const IID& iid, void** object) { + *object = NULL; + if (IsEqualIID(iid, IID_IUnknown) || IsEqualIID(iid, IID_IDataObject)) { + *object = this; + } else { + return E_NOINTERFACE; + } + AddRef(); + return S_OK; +} + +ULONG OSExchangeData::AddRef() { + return InterlockedIncrement(&ref_count_); +} + +ULONG OSExchangeData::Release() { + if (InterlockedDecrement(&ref_count_) == 0) { + ULONG copied_refcnt = ref_count_; + delete this; + return copied_refcnt; + } + return ref_count_; +} + +/////////////////////////////////////////////////////////////////////////////// +// OSExchangeData, private: + +static STGMEDIUM* GetStorageForBytes(const char* data, size_t bytes) { + HANDLE handle = GlobalAlloc(GPTR, static_cast(bytes)); + ScopedHGlobal scoped(handle); + size_t allocated = static_cast(GlobalSize(handle)); + memcpy(scoped.get(), data, allocated); + + STGMEDIUM* storage = new STGMEDIUM; + storage->hGlobal = handle; + storage->tymed = TYMED_HGLOBAL; + storage->pUnkForRelease = NULL; + return storage; +} + +template +static HGLOBAL CopyStringToGlobalHandle(const T& payload) { + int bytes = static_cast(payload.size() + 1) * sizeof(T::value_type); + HANDLE handle = GlobalAlloc(GPTR, bytes); + void* data = GlobalLock(handle); + size_t allocated = static_cast(GlobalSize(handle)); + memcpy(data, payload.c_str(), allocated); + static_cast(data)[payload.size()] = '\0'; + GlobalUnlock(handle); + return handle; +} + +static STGMEDIUM* GetStorageForWString(const std::wstring& data) { + STGMEDIUM* storage = new STGMEDIUM; + storage->hGlobal = CopyStringToGlobalHandle(data); + storage->tymed = TYMED_HGLOBAL; + storage->pUnkForRelease = NULL; + return storage; +} + +static STGMEDIUM* GetStorageForString(const std::string& data) { + STGMEDIUM* storage = new STGMEDIUM; + storage->hGlobal = CopyStringToGlobalHandle(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(), std::wstring()); + } else { + // Nothing else can be done, just use a default. + *validated = l10n_util::GetString(IDS_UNTITLED_SHORTCUT_FILE_NAME); + } + } else { + *validated = title; + file_util::ReplaceIllegalCharacters(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* 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(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; +} diff --git a/app/os_exchange_data_win_unittest.cc b/app/os_exchange_data_win_unittest.cc new file mode 100644 index 0000000..13a28a9 --- /dev/null +++ b/app/os_exchange_data_win_unittest.cc @@ -0,0 +1,365 @@ +// Copyright (c) 2006-2008 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 +#include + +#include "app/os_exchange_data.h" +#include "base/clipboard_util.h" +#include "base/pickle.h" +#include "base/ref_counted.h" +#include "base/scoped_handle.h" +#include "base/scoped_ptr.h" +#include "base/string_util.h" +#include "googleurl/src/gurl.h" +#include "testing/gtest/include/gtest/gtest.h" + +typedef testing::Test OSExchangeDataTest; + +// Test setting/getting using the OSExchangeData API +TEST(OSExchangeDataTest, StringDataGetAndSet) { + OSExchangeData* data = new OSExchangeData; + std::wstring input = L"I can has cheezburger?"; + data->SetString(input); + + OSExchangeData* data2 = new OSExchangeData(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()); + // data gets freed when data2 releases the ref on it + delete data2; +} + +// Test getting using the IDataObject COM API +TEST(OSExchangeDataTest, StringDataAccessViaCOM) { + OSExchangeData* data = new OSExchangeData; + std::wstring input = L"O hai googlz."; + data->SetString(input); + CComPtr com_data(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 = + ScopedHGlobal(medium.hGlobal).get(); + EXPECT_EQ(input, output); + ReleaseStgMedium(&medium); + + // data is freed automatically by CComPtr. +} + +// Test setting using the IDataObject COM API +TEST(OSExchangeDataTest, StringDataWritingViaCOM) { + OSExchangeData* data = new OSExchangeData; + std::wstring input = L"http://www.google.com/"; + + CComPtr com_data(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); + ScopedHGlobal 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 = new OSExchangeData(com_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()); + // deleting data2 will free data because it holds a ref to it. + delete data2; +} + +TEST(OSExchangeDataTest, URLDataAccessViaCOM) { + OSExchangeData* data = new OSExchangeData; + GURL url("http://www.google.com/"); + data->SetURL(url, L""); + CComPtr com_data(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 = + ScopedHGlobal(medium.hGlobal).get(); + EXPECT_EQ(url.spec(), WideToUTF8(output)); + ReleaseStgMedium(&medium); +} + +TEST(OSExchangeDataTest, MultipleFormatsViaCOM) { + OSExchangeData* data = new OSExchangeData; + 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); + + CComPtr com_data(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 = + ScopedHGlobal(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 = + ScopedHGlobal(medium.hGlobal).get(); + EXPECT_EQ(url_spec, WideToUTF8(output_text)); + ReleaseStgMedium(&medium); +} + +TEST(OSExchangeDataTest, EnumerationViaCOM) { + OSExchangeData* data = new OSExchangeData; + 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"); + + CComPtr com_data(data); + CComPtr enumerator; + EXPECT_EQ(S_OK, com_data->EnumFormatEtc(DATADIR_GET, &enumerator)); + + // 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(&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(&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(&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(&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)); + CComPtr cloned_enumerator; + EXPECT_EQ(S_OK, enumerator->Clone(&cloned_enumerator)); + EXPECT_EQ(S_OK, enumerator->Reset()); + + { + ULONG retrieved = 0; + FORMATETC elements_array[1]; + EXPECT_EQ(S_OK, cloned_enumerator->Next(1, + reinterpret_cast(&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(&elements_array), &retrieved)); + EXPECT_EQ(1, retrieved); + EXPECT_EQ(text_x_moz_url, elements_array[0].cfFormat); + } + } +} + +TEST(OSExchangeDataTest, TestURLExchangeFormats) { + OSExchangeData* data = new OSExchangeData; + 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 = new OSExchangeData(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 + CComPtr com_data(data); + { + CLIPFORMAT cfstr_file_contents = + RegisterClipboardFormat(CFSTR_FILECONTENTS); + FORMATETC format_etc = + { cfstr_file_contents, 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)); + ScopedHGlobal 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); + } + + // Need to manually free data2 since we never stuff it into a COMPtr. + delete data2; +} + +TEST(OSExchangeDataTest, TestPickledData) { + CLIPFORMAT test_cf = RegisterClipboardFormat(L"chrome/test"); + + Pickle saved_pickle; + saved_pickle.WriteInt(1); + saved_pickle.WriteInt(2); + scoped_refptr data(new OSExchangeData()); + data->SetPickledData(test_cf, saved_pickle); + + scoped_refptr copy(new OSExchangeData(data.get())); + EXPECT_TRUE(copy->HasFormat(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) { + scoped_refptr data(new OSExchangeData); + std::string file_contents("data\0with\0nulls", 15); + data->SetFileContents(L"filename.txt", file_contents); + + scoped_refptr copy(new OSExchangeData(data.get())); + 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) { + scoped_refptr data(new OSExchangeData); + GURL url("http://www.google.com/"); + std::wstring html( + L"\n\n" + L"bold. This is bold italic.\n" + L"\n"); + data->SetHtml(html, url); + + scoped_refptr copy(new OSExchangeData(data.get())); + 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\r\n\r\n" + "\r\n"); + expected_cf_html += WideToUTF8(html); + expected_cf_html.append("\r\n\r\n\r\n"); + + STGMEDIUM medium; + EXPECT_EQ(S_OK, data->GetData(ClipboardUtil::GetHtmlFormat(), &medium)); + ScopedHGlobal glob(medium.hGlobal); + std::string output(glob.get(), glob.Size()); + EXPECT_EQ(expected_cf_html, output); + ReleaseStgMedium(&medium); +} + +TEST(OSExchangeDataTest, SetURLWithMaxPath) { + scoped_refptr data(new OSExchangeData); + std::wstring long_title(L'a', MAX_PATH + 1); + data->SetURL(GURL("http://google.com"), long_title); +} + +TEST(OSExchangeDataTest, ProvideURLForPlainTextURL) { + scoped_refptr data(new OSExchangeData); + data->SetString(L"http://google.com"); + + scoped_ptr data2(new OSExchangeData(data.get())); + 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); +} diff --git a/build/build_config.h b/build/build_config.h index 43d36f0..a95edc5 100644 --- a/build/build_config.h +++ b/build/build_config.h @@ -25,6 +25,7 @@ #endif #elif defined(_WIN32) #define OS_WIN 1 +#define TOOLKIT_VIEWS 1 #else #error Please add support for your platform in build/build_config.h #endif diff --git a/chrome/browser/bookmarks/bookmark_drag_data.cc b/chrome/browser/bookmarks/bookmark_drag_data.cc index 4b3bdb6..c5a56ed 100644 --- a/chrome/browser/bookmarks/bookmark_drag_data.cc +++ b/chrome/browser/bookmarks/bookmark_drag_data.cc @@ -5,7 +5,7 @@ #include "chrome/browser/bookmarks/bookmark_drag_data.h" // TODO(port): Port this file. -#if defined(OS_WIN) +#if defined(TOOLKIT_VIEWS) #include "app/os_exchange_data.h" #else #include "chrome/common/temp_scaffolding_stubs.h" @@ -17,7 +17,6 @@ #include "chrome/browser/profile.h" #include "chrome/common/url_constants.h" - #if defined(OS_WIN) static CLIPFORMAT clipboard_format = 0; #endif diff --git a/chrome/browser/bookmarks/bookmark_drag_data_unittest.cc b/chrome/browser/bookmarks/bookmark_drag_data_unittest.cc index 1d1e1f2..9507196 100644 --- a/chrome/browser/bookmarks/bookmark_drag_data_unittest.cc +++ b/chrome/browser/bookmarks/bookmark_drag_data_unittest.cc @@ -22,7 +22,7 @@ TEST_F(BookmarkDragDataTest, InitialState) { TEST_F(BookmarkDragDataTest, BogusRead) { scoped_refptr data(new OSExchangeData()); BookmarkDragData drag_data; - EXPECT_FALSE(drag_data.Read(data.get())); + EXPECT_FALSE(drag_data.Read(OSExchangeData(data.get()))); EXPECT_FALSE(drag_data.is_valid()); } @@ -36,7 +36,7 @@ TEST_F(BookmarkDragDataTest, JustURL) { data->SetURL(url, title); BookmarkDragData drag_data; - EXPECT_TRUE(drag_data.Read(data.get())); + EXPECT_TRUE(drag_data.Read(OSExchangeData(data.get()))); EXPECT_TRUE(drag_data.is_valid()); ASSERT_EQ(1, drag_data.elements.size()); EXPECT_TRUE(drag_data.elements[0].is_url); diff --git a/chrome/chrome.gyp b/chrome/chrome.gyp index 4082664..7634269 100644 --- a/chrome/chrome.gyp +++ b/chrome/chrome.gyp @@ -202,7 +202,8 @@ '../app/l10n_util_win.cc', '../app/l10n_util_win.h', '../app/message_box_flags.h', - '../app/os_exchange_data.cc', + '../app/os_exchange_data_win.cc', + '../app/os_exchange_data_gtk.cc', '../app/os_exchange_data.h', '../app/resource_bundle.cc', '../app/resource_bundle.h', @@ -238,7 +239,14 @@ '../app/gfx/icon_util.cc', '../app/gfx/icon_util.h', '../app/os_exchange_data.cc', - '../app/os_exchange_data.h', + ], + 'conditions': [ + ['toolkit_views!="1"', { + 'sources!': [ + '../app/os_exchange_data.h', + '../app/os_exchange_data_gtk.cc', + ], + }], ], }], ], @@ -1588,7 +1596,7 @@ '../third_party/GTM', '../third_party/GTM/AppKit', ], - }], + }], ['OS=="win"', { 'defines': [ '__STD_C', diff --git a/chrome/common/temp_scaffolding_stubs.h b/chrome/common/temp_scaffolding_stubs.h index 35904d2..a5bd9b0 100644 --- a/chrome/common/temp_scaffolding_stubs.h +++ b/chrome/common/temp_scaffolding_stubs.h @@ -461,11 +461,13 @@ class FontsLanguagesWindowView { void SelectLanguagesTab() { NOTIMPLEMENTED(); } }; +#if !defined(TOOLKIT_VIEWS) class OSExchangeData { public: void SetString(const std::wstring& data) { NOTIMPLEMENTED(); } void SetURL(const GURL& url, const std::wstring& title) { NOTIMPLEMENTED(); } }; +#endif class BaseDragSource { }; diff --git a/chrome/test/unit/unittests.vcproj b/chrome/test/unit/unittests.vcproj index 025ce8b..f18b051 100644 --- a/chrome/test/unit/unittests.vcproj +++ b/chrome/test/unit/unittests.vcproj @@ -1120,7 +1120,7 @@ >