// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Many of these functions are based on those found in // webkit/port/platform/PasteboardWin.cpp #include "ui/base/clipboard/clipboard.h" #include #include #include "base/basictypes.h" #include "base/bind.h" #include "base/files/file_path.h" #include "base/logging.h" #include "base/memory/shared_memory.h" #include "base/message_loop/message_loop.h" #include "base/safe_numerics.h" #include "base/stl_util.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/utf_offset_string_conversions.h" #include "base/strings/utf_string_conversions.h" #include "base/win/message_window.h" #include "base/win/scoped_gdi_object.h" #include "base/win/scoped_hdc.h" #include "third_party/skia/include/core/SkBitmap.h" #include "ui/base/clipboard/clipboard_util_win.h" #include "ui/base/clipboard/custom_data_helper.h" #include "ui/gfx/canvas.h" #include "ui/gfx/size.h" namespace ui { namespace { // A scoper to manage acquiring and automatically releasing the clipboard. class ScopedClipboard { public: ScopedClipboard() : opened_(false) { } ~ScopedClipboard() { if (opened_) Release(); } bool Acquire(HWND owner) { const int kMaxAttemptsToOpenClipboard = 5; if (opened_) { NOTREACHED(); return false; } // Attempt to open the clipboard, which will acquire the Windows clipboard // lock. This may fail if another process currently holds this lock. // We're willing to try a few times in the hopes of acquiring it. // // This turns out to be an issue when using remote desktop because the // rdpclip.exe process likes to read what we've written to the clipboard and // send it to the RDP client. If we open and close the clipboard in quick // succession, we might be trying to open it while rdpclip.exe has it open, // See Bug 815425. // // In fact, we believe we'll only spin this loop over remote desktop. In // normal situations, the user is initiating clipboard operations and there // shouldn't be contention. for (int attempts = 0; attempts < kMaxAttemptsToOpenClipboard; ++attempts) { // If we didn't manage to open the clipboard, sleep a bit and be hopeful. if (attempts != 0) ::Sleep(5); if (::OpenClipboard(owner)) { opened_ = true; return true; } } // We failed to acquire the clipboard. return false; } void Release() { if (opened_) { ::CloseClipboard(); opened_ = false; } else { NOTREACHED(); } } private: bool opened_; }; bool ClipboardOwnerWndProc(UINT message, WPARAM wparam, LPARAM lparam, LRESULT* result) { switch (message) { case WM_RENDERFORMAT: // This message comes when SetClipboardData was sent a null data handle // and now it's come time to put the data on the clipboard. // We always set data, so there isn't a need to actually do anything here. break; case WM_RENDERALLFORMATS: // This message comes when SetClipboardData was sent a null data handle // and now this application is about to quit, so it must put data on // the clipboard before it exits. // We always set data, so there isn't a need to actually do anything here. break; case WM_DRAWCLIPBOARD: break; case WM_DESTROY: break; case WM_CHANGECBCHAIN: break; default: return false; } *result = 0; return true; } template HGLOBAL CreateGlobalData(const std::basic_string& str) { HGLOBAL data = ::GlobalAlloc(GMEM_MOVEABLE, ((str.size() + 1) * sizeof(charT))); if (data) { charT* raw_data = static_cast(::GlobalLock(data)); memcpy(raw_data, str.data(), str.size() * sizeof(charT)); raw_data[str.size()] = '\0'; ::GlobalUnlock(data); } return data; }; bool BitmapHasInvalidPremultipliedColors(const SkBitmap& bitmap) { for (int x = 0; x < bitmap.width(); ++x) { for (int y = 0; y < bitmap.height(); ++y) { uint32_t pixel = *bitmap.getAddr32(x, y); if (SkColorGetR(pixel) > SkColorGetA(pixel) || SkColorGetG(pixel) > SkColorGetA(pixel) || SkColorGetB(pixel) > SkColorGetA(pixel)) return true; } } return false; } void MakeBitmapOpaque(const SkBitmap& bitmap) { for (int x = 0; x < bitmap.width(); ++x) { for (int y = 0; y < bitmap.height(); ++y) { *bitmap.getAddr32(x, y) = SkColorSetA(*bitmap.getAddr32(x, y), 0xFF); } } } } // namespace Clipboard::FormatType::FormatType() : data_() {} Clipboard::FormatType::FormatType(UINT native_format) : data_() { // There's no good way to actually initialize this in the constructor in // C++03. data_.cfFormat = native_format; data_.dwAspect = DVASPECT_CONTENT; data_.lindex = -1; data_.tymed = TYMED_HGLOBAL; } Clipboard::FormatType::FormatType(UINT native_format, LONG index) : data_() { // There's no good way to actually initialize this in the constructor in // C++03. data_.cfFormat = native_format; data_.dwAspect = DVASPECT_CONTENT; data_.lindex = index; data_.tymed = TYMED_HGLOBAL; } Clipboard::FormatType::~FormatType() { } std::string Clipboard::FormatType::Serialize() const { return base::IntToString(data_.cfFormat); } // static Clipboard::FormatType Clipboard::FormatType::Deserialize( const std::string& serialization) { int clipboard_format = -1; if (!base::StringToInt(serialization, &clipboard_format)) { NOTREACHED(); return FormatType(); } return FormatType(clipboard_format); } bool Clipboard::FormatType::operator<(const FormatType& other) const { return ToUINT() < other.ToUINT(); } Clipboard::Clipboard() { if (base::MessageLoop::current()->type() == base::MessageLoop::TYPE_UI) clipboard_owner_.reset(new base::win::MessageWindow()); } Clipboard::~Clipboard() { } void Clipboard::WriteObjects(ClipboardType type, const ObjectMap& objects) { DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); ScopedClipboard clipboard; if (!clipboard.Acquire(GetClipboardWindow())) return; ::EmptyClipboard(); for (ObjectMap::const_iterator iter = objects.begin(); iter != objects.end(); ++iter) { DispatchObject(static_cast(iter->first), iter->second); } } void Clipboard::WriteText(const char* text_data, size_t text_len) { string16 text; UTF8ToUTF16(text_data, text_len, &text); HGLOBAL glob = CreateGlobalData(text); WriteToClipboard(CF_UNICODETEXT, glob); } void Clipboard::WriteHTML(const char* markup_data, size_t markup_len, const char* url_data, size_t url_len) { std::string markup(markup_data, markup_len); std::string url; if (url_len > 0) url.assign(url_data, url_len); std::string html_fragment = ClipboardUtil::HtmlToCFHtml(markup, url); HGLOBAL glob = CreateGlobalData(html_fragment); WriteToClipboard(Clipboard::GetHtmlFormatType().ToUINT(), glob); } void Clipboard::WriteRTF(const char* rtf_data, size_t data_len) { WriteData(GetRtfFormatType(), rtf_data, data_len); } void Clipboard::WriteBookmark(const char* title_data, size_t title_len, const char* url_data, size_t url_len) { std::string bookmark(title_data, title_len); bookmark.append(1, L'\n'); bookmark.append(url_data, url_len); string16 wide_bookmark = UTF8ToWide(bookmark); HGLOBAL glob = CreateGlobalData(wide_bookmark); WriteToClipboard(GetUrlWFormatType().ToUINT(), glob); } void Clipboard::WriteWebSmartPaste() { DCHECK(clipboard_owner_->hwnd() != NULL); ::SetClipboardData(GetWebKitSmartPasteFormatType().ToUINT(), NULL); } void Clipboard::WriteBitmap(const SkBitmap& bitmap) { HDC dc = ::GetDC(NULL); // This doesn't actually cost us a memcpy when the bitmap comes from the // renderer as we load it into the bitmap using setPixels which just sets a // pointer. Someone has to memcpy it into GDI, it might as well be us here. // TODO(darin): share data in gfx/bitmap_header.cc somehow BITMAPINFO bm_info = {0}; bm_info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bm_info.bmiHeader.biWidth = bitmap.width(); bm_info.bmiHeader.biHeight = -bitmap.height(); // sets vertical orientation bm_info.bmiHeader.biPlanes = 1; bm_info.bmiHeader.biBitCount = 32; bm_info.bmiHeader.biCompression = BI_RGB; // ::CreateDIBSection allocates memory for us to copy our bitmap into. // Unfortunately, we can't write the created bitmap to the clipboard, // (see http://msdn2.microsoft.com/en-us/library/ms532292.aspx) void *bits; HBITMAP source_hbitmap = ::CreateDIBSection(dc, &bm_info, DIB_RGB_COLORS, &bits, NULL, 0); if (bits && source_hbitmap) { { SkAutoLockPixels bitmap_lock(bitmap); // Copy the bitmap out of shared memory and into GDI memcpy(bits, bitmap.getPixels(), bitmap.getSize()); } // Now we have an HBITMAP, we can write it to the clipboard WriteBitmapFromHandle(source_hbitmap, gfx::Size(bitmap.width(), bitmap.height())); } ::DeleteObject(source_hbitmap); ::ReleaseDC(NULL, dc); } void Clipboard::WriteBitmapFromHandle(HBITMAP source_hbitmap, const gfx::Size& size) { // We would like to just call ::SetClipboardData on the source_hbitmap, // but that bitmap might not be of a sort we can write to the clipboard. // For this reason, we create a new bitmap, copy the bits over, and then // write that to the clipboard. HDC dc = ::GetDC(NULL); HDC compatible_dc = ::CreateCompatibleDC(NULL); HDC source_dc = ::CreateCompatibleDC(NULL); // This is the HBITMAP we will eventually write to the clipboard HBITMAP hbitmap = ::CreateCompatibleBitmap(dc, size.width(), size.height()); if (!hbitmap) { // Failed to create the bitmap ::DeleteDC(compatible_dc); ::DeleteDC(source_dc); ::ReleaseDC(NULL, dc); return; } HBITMAP old_hbitmap = (HBITMAP)SelectObject(compatible_dc, hbitmap); HBITMAP old_source = (HBITMAP)SelectObject(source_dc, source_hbitmap); // Now we need to blend it into an HBITMAP we can place on the clipboard BLENDFUNCTION bf = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA}; ::GdiAlphaBlend(compatible_dc, 0, 0, size.width(), size.height(), source_dc, 0, 0, size.width(), size.height(), bf); // Clean up all the handles we just opened ::SelectObject(compatible_dc, old_hbitmap); ::SelectObject(source_dc, old_source); ::DeleteObject(old_hbitmap); ::DeleteObject(old_source); ::DeleteDC(compatible_dc); ::DeleteDC(source_dc); ::ReleaseDC(NULL, dc); WriteToClipboard(CF_BITMAP, hbitmap); } void Clipboard::WriteData(const FormatType& format, const char* data_data, size_t data_len) { HGLOBAL hdata = ::GlobalAlloc(GMEM_MOVEABLE, data_len); if (!hdata) return; char* data = static_cast(::GlobalLock(hdata)); memcpy(data, data_data, data_len); ::GlobalUnlock(data); WriteToClipboard(format.ToUINT(), hdata); } void Clipboard::WriteToClipboard(unsigned int format, HANDLE handle) { DCHECK(clipboard_owner_->hwnd() != NULL); if (handle && !::SetClipboardData(format, handle)) { DCHECK(ERROR_CLIPBOARD_NOT_OPEN != GetLastError()); FreeData(format, handle); } } uint64 Clipboard::GetSequenceNumber(ClipboardType type) { DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); return ::GetClipboardSequenceNumber(); } bool Clipboard::IsFormatAvailable(const Clipboard::FormatType& format, ClipboardType type) const { DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); return ::IsClipboardFormatAvailable(format.ToUINT()) != FALSE; } void Clipboard::Clear(ClipboardType type) { DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); ScopedClipboard clipboard; if (!clipboard.Acquire(GetClipboardWindow())) return; ::EmptyClipboard(); } void Clipboard::ReadAvailableTypes(ClipboardType type, std::vector* types, bool* contains_filenames) const { if (!types || !contains_filenames) { NOTREACHED(); return; } types->clear(); if (::IsClipboardFormatAvailable(GetPlainTextFormatType().ToUINT())) types->push_back(UTF8ToUTF16(kMimeTypeText)); if (::IsClipboardFormatAvailable(GetHtmlFormatType().ToUINT())) types->push_back(UTF8ToUTF16(kMimeTypeHTML)); if (::IsClipboardFormatAvailable(GetRtfFormatType().ToUINT())) types->push_back(UTF8ToUTF16(kMimeTypeRTF)); if (::IsClipboardFormatAvailable(CF_DIB)) types->push_back(UTF8ToUTF16(kMimeTypePNG)); *contains_filenames = false; // Acquire the clipboard. ScopedClipboard clipboard; if (!clipboard.Acquire(GetClipboardWindow())) return; HANDLE hdata = ::GetClipboardData(GetWebCustomDataFormatType().ToUINT()); if (!hdata) return; ReadCustomDataTypes(::GlobalLock(hdata), ::GlobalSize(hdata), types); ::GlobalUnlock(hdata); } void Clipboard::ReadText(ClipboardType type, string16* result) const { DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); if (!result) { NOTREACHED(); return; } result->clear(); // Acquire the clipboard. ScopedClipboard clipboard; if (!clipboard.Acquire(GetClipboardWindow())) return; HANDLE data = ::GetClipboardData(CF_UNICODETEXT); if (!data) return; result->assign(static_cast(::GlobalLock(data))); ::GlobalUnlock(data); } void Clipboard::ReadAsciiText(ClipboardType type, std::string* result) const { DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); if (!result) { NOTREACHED(); return; } result->clear(); // Acquire the clipboard. ScopedClipboard clipboard; if (!clipboard.Acquire(GetClipboardWindow())) return; HANDLE data = ::GetClipboardData(CF_TEXT); if (!data) return; result->assign(static_cast(::GlobalLock(data))); ::GlobalUnlock(data); } void Clipboard::ReadHTML(ClipboardType type, string16* markup, std::string* src_url, uint32* fragment_start, uint32* fragment_end) const { DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); markup->clear(); // TODO(dcheng): Remove these checks, I don't think they should be optional. DCHECK(src_url); if (src_url) src_url->clear(); *fragment_start = 0; *fragment_end = 0; // Acquire the clipboard. ScopedClipboard clipboard; if (!clipboard.Acquire(GetClipboardWindow())) return; HANDLE data = ::GetClipboardData(GetHtmlFormatType().ToUINT()); if (!data) return; std::string cf_html(static_cast(::GlobalLock(data))); ::GlobalUnlock(data); size_t html_start = std::string::npos; size_t start_index = std::string::npos; size_t end_index = std::string::npos; ClipboardUtil::CFHtmlExtractMetadata(cf_html, src_url, &html_start, &start_index, &end_index); // This might happen if the contents of the clipboard changed and CF_HTML is // no longer available. if (start_index == std::string::npos || end_index == std::string::npos || html_start == std::string::npos) return; if (start_index < html_start || end_index < start_index) return; std::vector offsets; offsets.push_back(start_index - html_start); offsets.push_back(end_index - html_start); markup->assign(base::UTF8ToUTF16AndAdjustOffsets(cf_html.data() + html_start, &offsets)); *fragment_start = base::checked_numeric_cast(offsets[0]); *fragment_end = base::checked_numeric_cast(offsets[1]); } void Clipboard::ReadRTF(ClipboardType type, std::string* result) const { DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); ReadData(GetRtfFormatType(), result); } SkBitmap Clipboard::ReadImage(ClipboardType type) const { DCHECK_EQ(type, CLIPBOARD_TYPE_COPY_PASTE); // Acquire the clipboard. ScopedClipboard clipboard; if (!clipboard.Acquire(GetClipboardWindow())) return SkBitmap(); // We use a DIB rather than a DDB here since ::GetObject() with the // HBITMAP returned from ::GetClipboardData(CF_BITMAP) always reports a color // depth of 32bpp. BITMAPINFO* bitmap = static_cast(::GetClipboardData(CF_DIB)); if (!bitmap) return SkBitmap(); int color_table_length = 0; switch (bitmap->bmiHeader.biBitCount) { case 1: case 4: case 8: color_table_length = bitmap->bmiHeader.biClrUsed ? bitmap->bmiHeader.biClrUsed : 1 << bitmap->bmiHeader.biBitCount; break; case 16: case 32: if (bitmap->bmiHeader.biCompression == BI_BITFIELDS) color_table_length = 3; break; case 24: break; default: NOTREACHED(); } const void* bitmap_bits = reinterpret_cast(bitmap) + bitmap->bmiHeader.biSize + color_table_length * sizeof(RGBQUAD); gfx::Canvas canvas(gfx::Size(bitmap->bmiHeader.biWidth, bitmap->bmiHeader.biHeight), 1.0f, false); { skia::ScopedPlatformPaint scoped_platform_paint(canvas.sk_canvas()); HDC dc = scoped_platform_paint.GetPlatformSurface(); ::SetDIBitsToDevice(dc, 0, 0, bitmap->bmiHeader.biWidth, bitmap->bmiHeader.biHeight, 0, 0, 0, bitmap->bmiHeader.biHeight, bitmap_bits, bitmap, DIB_RGB_COLORS); } // Windows doesn't really handle alpha channels well in many situations. When // the source image is < 32 bpp, we force the bitmap to be opaque. When the // source image is 32 bpp, the alpha channel might still contain garbage data. // Since Windows uses premultiplied alpha, we scan for instances where // (R, G, B) > A. If there are any invalid premultiplied colors in the image, // we assume the alpha channel contains garbage and force the bitmap to be // opaque as well. Note that this heuristic will fail on a transparent bitmap // containing only black pixels... const SkBitmap& device_bitmap = canvas.sk_canvas()->getDevice()->accessBitmap(true); { SkAutoLockPixels lock(device_bitmap); bool has_invalid_alpha_channel = bitmap->bmiHeader.biBitCount < 32 || BitmapHasInvalidPremultipliedColors(device_bitmap); if (has_invalid_alpha_channel) { MakeBitmapOpaque(device_bitmap); } } return canvas.ExtractImageRep().sk_bitmap(); } void Clipboard::ReadCustomData(ClipboardType clipboard_type, const string16& type, string16* result) const { DCHECK_EQ(clipboard_type, CLIPBOARD_TYPE_COPY_PASTE); // Acquire the clipboard. ScopedClipboard clipboard; if (!clipboard.Acquire(GetClipboardWindow())) return; HANDLE hdata = ::GetClipboardData(GetWebCustomDataFormatType().ToUINT()); if (!hdata) return; ReadCustomDataForType(::GlobalLock(hdata), ::GlobalSize(hdata), type, result); ::GlobalUnlock(hdata); } void Clipboard::ReadBookmark(string16* title, std::string* url) const { if (title) title->clear(); if (url) url->clear(); // Acquire the clipboard. ScopedClipboard clipboard; if (!clipboard.Acquire(GetClipboardWindow())) return; HANDLE data = ::GetClipboardData(GetUrlWFormatType().ToUINT()); if (!data) return; string16 bookmark(static_cast(::GlobalLock(data))); ::GlobalUnlock(data); ParseBookmarkClipboardFormat(bookmark, title, url); } void Clipboard::ReadData(const FormatType& format, std::string* result) const { if (!result) { NOTREACHED(); return; } ScopedClipboard clipboard; if (!clipboard.Acquire(GetClipboardWindow())) return; HANDLE data = ::GetClipboardData(format.ToUINT()); if (!data) return; result->assign(static_cast(::GlobalLock(data)), ::GlobalSize(data)); ::GlobalUnlock(data); } // static void Clipboard::ParseBookmarkClipboardFormat(const string16& bookmark, string16* title, std::string* url) { const string16 kDelim = ASCIIToUTF16("\r\n"); const size_t title_end = bookmark.find_first_of(kDelim); if (title) title->assign(bookmark.substr(0, title_end)); if (url) { const size_t url_start = bookmark.find_first_not_of(kDelim, title_end); if (url_start != string16::npos) *url = UTF16ToUTF8(bookmark.substr(url_start, string16::npos)); } } // static Clipboard::FormatType Clipboard::GetFormatType( const std::string& format_string) { return FormatType( ::RegisterClipboardFormat(ASCIIToWide(format_string).c_str())); } // static const Clipboard::FormatType& Clipboard::GetUrlFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(CFSTR_INETURLA))); return type; } // static const Clipboard::FormatType& Clipboard::GetUrlWFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(CFSTR_INETURLW))); return type; } // static const Clipboard::FormatType& Clipboard::GetMozUrlFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(L"text/x-moz-url"))); return type; } // static const Clipboard::FormatType& Clipboard::GetPlainTextFormatType() { CR_DEFINE_STATIC_LOCAL(FormatType, type, (CF_TEXT)); return type; } // static const Clipboard::FormatType& Clipboard::GetPlainTextWFormatType() { CR_DEFINE_STATIC_LOCAL(FormatType, type, (CF_UNICODETEXT)); return type; } // static const Clipboard::FormatType& Clipboard::GetFilenameFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(CFSTR_FILENAMEA))); return type; } // static const Clipboard::FormatType& Clipboard::GetFilenameWFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(CFSTR_FILENAMEW))); return type; } // MS HTML Format // static const Clipboard::FormatType& Clipboard::GetHtmlFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(L"HTML Format"))); return type; } // MS RTF Format // static const Clipboard::FormatType& Clipboard::GetRtfFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(L"Rich Text Format"))); return type; } // static const Clipboard::FormatType& Clipboard::GetBitmapFormatType() { CR_DEFINE_STATIC_LOCAL(FormatType, type, (CF_BITMAP)); return type; } // Firefox text/html // static const Clipboard::FormatType& Clipboard::GetTextHtmlFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(L"text/html"))); return type; } // static const Clipboard::FormatType& Clipboard::GetCFHDropFormatType() { CR_DEFINE_STATIC_LOCAL(FormatType, type, (CF_HDROP)); return type; } // static const Clipboard::FormatType& Clipboard::GetFileDescriptorFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(CFSTR_FILEDESCRIPTOR))); return type; } // static const Clipboard::FormatType& Clipboard::GetFileContentZeroFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(CFSTR_FILECONTENTS), 0)); return type; } // static const Clipboard::FormatType& Clipboard::GetIDListFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(CFSTR_SHELLIDLIST))); return type; } // static const Clipboard::FormatType& Clipboard::GetWebKitSmartPasteFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(L"WebKit Smart Paste Format"))); return type; } // static const Clipboard::FormatType& Clipboard::GetWebCustomDataFormatType() { // TODO(dcheng): This name is temporary. See http://crbug.com/106449. CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(L"Chromium Web Custom MIME Data Format"))); return type; } // static const Clipboard::FormatType& Clipboard::GetPepperCustomDataFormatType() { CR_DEFINE_STATIC_LOCAL( FormatType, type, (::RegisterClipboardFormat(L"Chromium Pepper MIME Data Format"))); return type; } // static void Clipboard::FreeData(unsigned int format, HANDLE data) { if (format == CF_BITMAP) ::DeleteObject(static_cast(data)); else ::GlobalFree(data); } HWND Clipboard::GetClipboardWindow() const { if (!clipboard_owner_) return NULL; if (clipboard_owner_->hwnd() == NULL) clipboard_owner_->Create(base::Bind(&ClipboardOwnerWndProc)); return clipboard_owner_->hwnd(); } } // namespace ui