| // 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. |
| |
| #include "content/browser/renderer_host/clipboard_message_filter.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/pickle.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "base/task_scheduler/post_task.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "content/browser/blob_storage/chrome_blob_storage_context.h" |
| #include "content/common/clipboard_messages.h" |
| #include "content/public/browser/blob_handle.h" |
| #include "content/public/browser/browser_context.h" |
| #include "ipc/ipc_message_macros.h" |
| #include "third_party/skia/include/core/SkBitmap.h" |
| #include "ui/base/clipboard/clipboard.h" |
| #include "ui/base/clipboard/custom_data_helper.h" |
| #include "ui/base/clipboard/scoped_clipboard_writer.h" |
| #include "ui/gfx/codec/png_codec.h" |
| #include "ui/gfx/geometry/size.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| void ReleaseSharedMemoryPixels(void* addr, void* context) { |
| delete reinterpret_cast<base::SharedMemory*>(context); |
| } |
| |
| // No-op helper for delayed cleanup of BlobHandles generated by reading |
| // clipboard images. |
| void CleanupReadImageBlob(std::unique_ptr<content::BlobHandle>) {} |
| |
| } // namespace |
| |
| ClipboardMessageFilter::ClipboardMessageFilter( |
| scoped_refptr<ChromeBlobStorageContext> blob_storage_context) |
| : BrowserMessageFilter(ClipboardMsgStart), |
| blob_storage_context_(std::move(blob_storage_context)), |
| clipboard_writer_( |
| new ui::ScopedClipboardWriter(ui::CLIPBOARD_TYPE_COPY_PASTE)) {} |
| |
| void ClipboardMessageFilter::OverrideThreadForMessage( |
| const IPC::Message& message, BrowserThread::ID* thread) { |
| // Clipboard writes should always occur on the UI thread due the restrictions |
| // of various platform APIs. In general, the clipboard is not thread-safe, so |
| // all clipboard calls should be serviced from the UI thread. |
| // |
| // Windows needs clipboard reads to be serviced from the IO thread because |
| // these are sync IPCs which can result in deadlocks with NPAPI plugins if |
| // serviced from the UI thread. Note that Windows clipboard calls ARE |
| // thread-safe so it is ok for reads and writes to be serviced from different |
| // threads. |
| #if !defined(OS_WIN) |
| if (IPC_MESSAGE_CLASS(message) == ClipboardMsgStart) |
| *thread = BrowserThread::UI; |
| #endif |
| } |
| |
| bool ClipboardMessageFilter::OnMessageReceived(const IPC::Message& message) { |
| bool handled = true; |
| IPC_BEGIN_MESSAGE_MAP(ClipboardMessageFilter, message) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_GetSequenceNumber, OnGetSequenceNumber) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_IsFormatAvailable, OnIsFormatAvailable) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_Clear, OnClear) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_ReadAvailableTypes, |
| OnReadAvailableTypes) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_ReadText, OnReadText) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_ReadHTML, OnReadHTML) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_ReadRTF, OnReadRTF) |
| IPC_MESSAGE_HANDLER_DELAY_REPLY(ClipboardHostMsg_ReadImage, OnReadImage) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_ReadCustomData, OnReadCustomData) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_WriteText, OnWriteText) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_WriteHTML, OnWriteHTML) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_WriteSmartPasteMarker, |
| OnWriteSmartPasteMarker) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_WriteCustomData, OnWriteCustomData) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_WriteBookmark, OnWriteBookmark) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_WriteImage, OnWriteImage) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_CommitWrite, OnCommitWrite); |
| #if defined(OS_MACOSX) |
| IPC_MESSAGE_HANDLER(ClipboardHostMsg_FindPboardWriteStringAsync, |
| OnFindPboardWriteString) |
| #endif |
| IPC_MESSAGE_UNHANDLED(handled = false) |
| IPC_END_MESSAGE_MAP() |
| return handled; |
| } |
| |
| ClipboardMessageFilter::~ClipboardMessageFilter() { |
| clipboard_writer_->Reset(); |
| } |
| |
| void ClipboardMessageFilter::OnGetSequenceNumber(ui::ClipboardType type, |
| uint64_t* sequence_number) { |
| *sequence_number = GetClipboard()->GetSequenceNumber(type); |
| } |
| |
| void ClipboardMessageFilter::OnReadAvailableTypes( |
| ui::ClipboardType type, |
| std::vector<base::string16>* types, |
| bool* contains_filenames) { |
| GetClipboard()->ReadAvailableTypes(type, types, contains_filenames); |
| } |
| |
| void ClipboardMessageFilter::OnIsFormatAvailable(ClipboardFormat format, |
| ui::ClipboardType type, |
| bool* result) { |
| switch (format) { |
| case CLIPBOARD_FORMAT_PLAINTEXT: |
| *result = GetClipboard()->IsFormatAvailable( |
| ui::Clipboard::GetPlainTextWFormatType(), type) || |
| GetClipboard()->IsFormatAvailable( |
| ui::Clipboard::GetPlainTextFormatType(), type); |
| break; |
| case CLIPBOARD_FORMAT_HTML: |
| *result = GetClipboard()->IsFormatAvailable( |
| ui::Clipboard::GetHtmlFormatType(), type); |
| break; |
| case CLIPBOARD_FORMAT_SMART_PASTE: |
| *result = GetClipboard()->IsFormatAvailable( |
| ui::Clipboard::GetWebKitSmartPasteFormatType(), type); |
| break; |
| case CLIPBOARD_FORMAT_BOOKMARK: |
| #if defined(OS_WIN) || defined(OS_MACOSX) |
| *result = GetClipboard()->IsFormatAvailable( |
| ui::Clipboard::GetUrlWFormatType(), type); |
| #else |
| *result = false; |
| #endif |
| break; |
| } |
| } |
| |
| void ClipboardMessageFilter::OnClear(ui::ClipboardType type) { |
| GetClipboard()->Clear(type); |
| } |
| |
| void ClipboardMessageFilter::OnReadText(ui::ClipboardType type, |
| base::string16* result) { |
| if (GetClipboard()->IsFormatAvailable( |
| ui::Clipboard::GetPlainTextWFormatType(), type)) { |
| GetClipboard()->ReadText(type, result); |
| } else if (GetClipboard()->IsFormatAvailable( |
| ui::Clipboard::GetPlainTextFormatType(), type)) { |
| std::string ascii; |
| GetClipboard()->ReadAsciiText(type, &ascii); |
| *result = base::ASCIIToUTF16(ascii); |
| } else { |
| result->clear(); |
| } |
| } |
| |
| void ClipboardMessageFilter::OnReadHTML(ui::ClipboardType type, |
| base::string16* markup, |
| GURL* url, |
| uint32_t* fragment_start, |
| uint32_t* fragment_end) { |
| std::string src_url_str; |
| GetClipboard()->ReadHTML(type, markup, &src_url_str, fragment_start, |
| fragment_end); |
| *url = GURL(src_url_str); |
| } |
| |
| void ClipboardMessageFilter::OnReadRTF(ui::ClipboardType type, |
| std::string* result) { |
| GetClipboard()->ReadRTF(type, result); |
| } |
| |
| void ClipboardMessageFilter::OnReadImage(ui::ClipboardType type, |
| IPC::Message* reply_msg) { |
| SkBitmap bitmap = GetClipboard()->ReadImage(type); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskPriority::BACKGROUND, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, |
| base::BindOnce(&ClipboardMessageFilter::ReadAndEncodeImage, this, bitmap, |
| reply_msg)); |
| } |
| |
| void ClipboardMessageFilter::ReadAndEncodeImage(const SkBitmap& bitmap, |
| IPC::Message* reply_msg) { |
| if (!bitmap.isNull()) { |
| std::unique_ptr<std::vector<uint8_t>> png_data(new std::vector<uint8_t>); |
| if (gfx::PNGCodec::FastEncodeBGRASkBitmap(bitmap, false, png_data.get())) { |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::Bind(&ClipboardMessageFilter::OnReadAndEncodeImageFinished, |
| this, base::Passed(&png_data), reply_msg)); |
| return; |
| } |
| } |
| ClipboardHostMsg_ReadImage::WriteReplyParams(reply_msg, std::string(), |
| std::string(), -1); |
| Send(reply_msg); |
| } |
| |
| void ClipboardMessageFilter::OnReadAndEncodeImageFinished( |
| std::unique_ptr<std::vector<uint8_t>> png_data, |
| IPC::Message* reply_msg) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (png_data->size() < std::numeric_limits<uint32_t>::max()) { |
| std::unique_ptr<content::BlobHandle> blob_handle = |
| blob_storage_context_->CreateMemoryBackedBlob( |
| reinterpret_cast<char*>(png_data->data()), png_data->size()); |
| if (blob_handle) { |
| ClipboardHostMsg_ReadImage::WriteReplyParams( |
| reply_msg, blob_handle->GetUUID(), ui::Clipboard::kMimeTypePNG, |
| static_cast<int64_t>(png_data->size())); |
| Send(reply_msg); |
| // Give the renderer a minute to pick up a reference to the blob before |
| // giving up. |
| // TODO(dmurph): There should be a better way of transferring ownership of |
| // a blob from the browser to the renderer, rather than relying on this |
| // timeout to clean up eventually. See https://crbug.com/604800. |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&CleanupReadImageBlob, base::Passed(&blob_handle)), |
| base::TimeDelta::FromMinutes(1)); |
| return; |
| } |
| } |
| ClipboardHostMsg_ReadImage::WriteReplyParams(reply_msg, std::string(), |
| std::string(), -1); |
| Send(reply_msg); |
| } |
| |
| void ClipboardMessageFilter::OnReadCustomData(ui::ClipboardType clipboard_type, |
| const base::string16& type, |
| base::string16* result) { |
| GetClipboard()->ReadCustomData(clipboard_type, type, result); |
| } |
| |
| void ClipboardMessageFilter::OnWriteText(ui::ClipboardType clipboard_type, |
| const base::string16& text) { |
| clipboard_writer_->WriteText(text); |
| } |
| |
| void ClipboardMessageFilter::OnWriteHTML(ui::ClipboardType clipboard_type, |
| const base::string16& markup, |
| const GURL& url) { |
| clipboard_writer_->WriteHTML(markup, url.spec()); |
| } |
| |
| void ClipboardMessageFilter::OnWriteSmartPasteMarker( |
| ui::ClipboardType clipboard_type) { |
| clipboard_writer_->WriteWebSmartPaste(); |
| } |
| |
| void ClipboardMessageFilter::OnWriteCustomData( |
| ui::ClipboardType clipboard_type, |
| const std::map<base::string16, base::string16>& data) { |
| base::Pickle pickle; |
| ui::WriteCustomDataToPickle(data, &pickle); |
| clipboard_writer_->WritePickledData( |
| pickle, ui::Clipboard::GetWebCustomDataFormatType()); |
| } |
| |
| void ClipboardMessageFilter::OnWriteBookmark(ui::ClipboardType clipboard_type, |
| const std::string& url, |
| const base::string16& title) { |
| clipboard_writer_->WriteBookmark(title, url); |
| } |
| |
| void ClipboardMessageFilter::OnWriteImage(ui::ClipboardType clipboard_type, |
| const gfx::Size& size, |
| base::SharedMemoryHandle handle) { |
| if (!base::SharedMemory::IsHandleValid(handle)) { |
| return; |
| } |
| |
| std::unique_ptr<base::SharedMemory> bitmap_buffer( |
| new base::SharedMemory(handle, true)); |
| |
| SkBitmap bitmap; |
| // Let Skia do some sanity checking for (no negative widths/heights, no |
| // overflows while calculating bytes per row, etc). |
| if (!bitmap.setInfo( |
| SkImageInfo::MakeN32Premul(size.width(), size.height()))) { |
| return; |
| } |
| |
| // Make sure the size is representable as a signed 32-bit int, so |
| // SkBitmap::getSize() won't be truncated. |
| if (!sk_64_isS32(bitmap.computeSize64())) |
| return; |
| |
| if (!bitmap_buffer->Map(bitmap.getSize())) |
| return; |
| |
| if (!bitmap.installPixels(bitmap.info(), bitmap_buffer->memory(), |
| bitmap.rowBytes(), NULL, &ReleaseSharedMemoryPixels, |
| bitmap_buffer.get())) |
| return; |
| |
| // On success, SkBitmap now owns the SharedMemory. |
| ignore_result(bitmap_buffer.release()); |
| clipboard_writer_->WriteImage(bitmap); |
| } |
| |
| void ClipboardMessageFilter::OnCommitWrite(ui::ClipboardType clipboard_type) { |
| #if defined(OS_WIN) |
| // On non-Windows platforms, all clipboard IPCs are handled on the UI thread. |
| // However, Windows handles the clipboard IPCs on the IO thread to prevent |
| // deadlocks. Clipboard writes must still occur on the UI thread because the |
| // clipboard object from the IO thread cannot create windows so it cannot be |
| // the "owner" of the clipboard's contents. See http://crbug.com/5823. |
| BrowserThread::DeleteSoon(BrowserThread::UI, FROM_HERE, |
| clipboard_writer_.release()); |
| #endif |
| clipboard_writer_.reset( |
| new ui::ScopedClipboardWriter(ui::CLIPBOARD_TYPE_COPY_PASTE)); |
| } |
| |
| // static |
| ui::Clipboard* ClipboardMessageFilter::GetClipboard() { |
| // We have a static instance of the clipboard service for use by all message |
| // filters. This instance lives for the life of the browser processes. |
| static ui::Clipboard* clipboard = ui::Clipboard::GetForCurrentThread(); |
| return clipboard; |
| } |
| |
| } // namespace content |