| // 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_host_impl.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/memory/ptr_util.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/public/browser/blob_handle.h" |
| #include "content/public/browser/browser_context.h" |
| #include "ipc/ipc_message_macros.h" |
| #include "mojo/public/cpp/bindings/strong_binding.h" |
| #include "mojo/public/cpp/system/platform_handle.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) { |
| MojoResult result = MojoUnmapBuffer(context); |
| DCHECK_EQ(MOJO_RESULT_OK, result); |
| } |
| |
| // No-op helper for delayed cleanup of BlobHandles generated by reading |
| // clipboard images. |
| void CleanupReadImageBlob(std::unique_ptr<content::BlobHandle>) {} |
| |
| } // namespace |
| |
| ClipboardHostImpl::ClipboardHostImpl( |
| scoped_refptr<ChromeBlobStorageContext> blob_storage_context) |
| : clipboard_(ui::Clipboard::GetForCurrentThread()), |
| blob_storage_context_(std::move(blob_storage_context)), |
| clipboard_writer_( |
| new ui::ScopedClipboardWriter(ui::CLIPBOARD_TYPE_COPY_PASTE)) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| } |
| |
| void ClipboardHostImpl::Create( |
| scoped_refptr<ChromeBlobStorageContext> blob_storage_context, |
| mojom::ClipboardHostRequest request) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| mojo::MakeStrongBinding( |
| base::WrapUnique<ClipboardHostImpl>( |
| new ClipboardHostImpl(std::move(blob_storage_context))), |
| std::move(request)); |
| } |
| |
| ClipboardHostImpl::~ClipboardHostImpl() { |
| clipboard_writer_->Reset(); |
| } |
| |
| void ClipboardHostImpl::GetSequenceNumber(blink::mojom::ClipboardBuffer buffer, |
| GetSequenceNumberCallback callback) { |
| ui::ClipboardType clipboard_type; |
| ConvertBufferType(buffer, &clipboard_type); |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::move(callback).Run(clipboard_->GetSequenceNumber(clipboard_type)); |
| } |
| |
| void ClipboardHostImpl::ReadAvailableTypes( |
| blink::mojom::ClipboardBuffer buffer, |
| ReadAvailableTypesCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ui::ClipboardType clipboard_type; |
| ConvertBufferType(buffer, &clipboard_type); |
| std::vector<base::string16> types; |
| bool contains_filenames; |
| clipboard_->ReadAvailableTypes(clipboard_type, &types, &contains_filenames); |
| std::move(callback).Run(types, contains_filenames); |
| } |
| |
| void ClipboardHostImpl::IsFormatAvailable(blink::mojom::ClipboardFormat format, |
| blink::mojom::ClipboardBuffer buffer, |
| IsFormatAvailableCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ui::ClipboardType clipboard_type; |
| ConvertBufferType(buffer, &clipboard_type); |
| bool result = false; |
| switch (format) { |
| case blink::mojom::ClipboardFormat::kPlaintext: |
| result = clipboard_->IsFormatAvailable( |
| ui::Clipboard::GetPlainTextWFormatType(), clipboard_type) || |
| clipboard_->IsFormatAvailable( |
| ui::Clipboard::GetPlainTextFormatType(), clipboard_type); |
| break; |
| case blink::mojom::ClipboardFormat::kHtml: |
| result = clipboard_->IsFormatAvailable(ui::Clipboard::GetHtmlFormatType(), |
| clipboard_type); |
| break; |
| case blink::mojom::ClipboardFormat::kSmartPaste: |
| result = clipboard_->IsFormatAvailable( |
| ui::Clipboard::GetWebKitSmartPasteFormatType(), clipboard_type); |
| break; |
| case blink::mojom::ClipboardFormat::kBookmark: |
| #if defined(OS_WIN) || defined(OS_MACOSX) |
| result = clipboard_->IsFormatAvailable(ui::Clipboard::GetUrlWFormatType(), |
| clipboard_type); |
| #else |
| result = false; |
| #endif |
| break; |
| } |
| std::move(callback).Run(result); |
| } |
| |
| void ClipboardHostImpl::ReadText(blink::mojom::ClipboardBuffer buffer, |
| ReadTextCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ui::ClipboardType clipboard_type; |
| ConvertBufferType(buffer, &clipboard_type); |
| |
| base::string16 result; |
| if (clipboard_->IsFormatAvailable(ui::Clipboard::GetPlainTextWFormatType(), |
| clipboard_type)) { |
| clipboard_->ReadText(clipboard_type, &result); |
| } else if (clipboard_->IsFormatAvailable( |
| ui::Clipboard::GetPlainTextFormatType(), clipboard_type)) { |
| std::string ascii; |
| clipboard_->ReadAsciiText(clipboard_type, &ascii); |
| result = base::ASCIIToUTF16(ascii); |
| } |
| std::move(callback).Run(result); |
| } |
| |
| void ClipboardHostImpl::ReadHtml(blink::mojom::ClipboardBuffer buffer, |
| ReadHtmlCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ui::ClipboardType clipboard_type; |
| ConvertBufferType(buffer, &clipboard_type); |
| |
| base::string16 markup; |
| std::string src_url_str; |
| uint32_t fragment_start = 0; |
| uint32_t fragment_end = 0; |
| clipboard_->ReadHTML(clipboard_type, &markup, &src_url_str, &fragment_start, |
| &fragment_end); |
| std::move(callback).Run(std::move(markup), GURL(src_url_str), fragment_start, |
| fragment_end); |
| } |
| |
| void ClipboardHostImpl::ReadRtf(blink::mojom::ClipboardBuffer buffer, |
| ReadRtfCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| ui::ClipboardType clipboard_type; |
| ConvertBufferType(buffer, &clipboard_type); |
| std::string result; |
| clipboard_->ReadRTF(clipboard_type, &result); |
| std::move(callback).Run(result); |
| } |
| |
| void ClipboardHostImpl::ReadImage(blink::mojom::ClipboardBuffer buffer, |
| ReadImageCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ui::ClipboardType clipboard_type; |
| ConvertBufferType(buffer, &clipboard_type); |
| |
| SkBitmap bitmap = clipboard_->ReadImage(clipboard_type); |
| |
| base::PostTaskWithTraits( |
| FROM_HERE, |
| {base::MayBlock(), base::TaskPriority::BACKGROUND, |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, |
| base::BindOnce(&ClipboardHostImpl::ReadAndEncodeImage, |
| base::Unretained(this), bitmap, std::move(callback))); |
| } |
| |
| void ClipboardHostImpl::ReadAndEncodeImage(const SkBitmap& bitmap, |
| ReadImageCallback callback) { |
| if (!bitmap.isNull()) { |
| std::vector<uint8_t> png_data; |
| if (gfx::PNGCodec::FastEncodeBGRASkBitmap(bitmap, false, &png_data)) { |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::BindOnce(&ClipboardHostImpl::OnReadAndEncodeImageFinished, |
| base::Unretained(this), std::move(png_data), |
| std::move(callback))); |
| return; |
| } |
| } |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| base::BindOnce( |
| [](ReadImageCallback callback) { |
| std::move(callback).Run(std::string(), |
| std::string(), -1); |
| }, |
| std::move(callback))); |
| } |
| |
| void ClipboardHostImpl::OnReadAndEncodeImageFinished( |
| std::vector<uint8_t> png_data, |
| ReadImageCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| 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) { |
| std::move(callback).Run(blob_handle->GetUUID(), |
| ui::Clipboard::kMimeTypePNG, |
| static_cast<int64_t>(png_data.size())); |
| // 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::BindOnce(&CleanupReadImageBlob, base::Passed(&blob_handle)), |
| base::TimeDelta::FromMinutes(1)); |
| return; |
| } |
| } |
| std::move(callback).Run(std::string(), std::string(), -1); |
| } |
| |
| void ClipboardHostImpl::ReadCustomData(blink::mojom::ClipboardBuffer buffer, |
| const base::string16& type, |
| ReadCustomDataCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| ui::ClipboardType clipboard_type; |
| ConvertBufferType(buffer, &clipboard_type); |
| |
| base::string16 result; |
| clipboard_->ReadCustomData(clipboard_type, type, &result); |
| std::move(callback).Run(result); |
| } |
| |
| void ClipboardHostImpl::WriteText(blink::mojom::ClipboardBuffer, |
| const base::string16& text) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| clipboard_writer_->WriteText(text); |
| } |
| |
| void ClipboardHostImpl::WriteHtml(blink::mojom::ClipboardBuffer, |
| const base::string16& markup, |
| const GURL& url) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| clipboard_writer_->WriteHTML(markup, url.spec()); |
| } |
| |
| void ClipboardHostImpl::WriteSmartPasteMarker( |
| blink::mojom::ClipboardBuffer buffer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| clipboard_writer_->WriteWebSmartPaste(); |
| } |
| |
| void ClipboardHostImpl::WriteCustomData( |
| blink::mojom::ClipboardBuffer, |
| const std::unordered_map<base::string16, base::string16>& data) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| base::Pickle pickle; |
| ui::WriteCustomDataToPickle(data, &pickle); |
| clipboard_writer_->WritePickledData( |
| pickle, ui::Clipboard::GetWebCustomDataFormatType()); |
| } |
| |
| void ClipboardHostImpl::WriteBookmark(blink::mojom::ClipboardBuffer, |
| const std::string& url, |
| const base::string16& title) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| clipboard_writer_->WriteBookmark(title, url); |
| } |
| |
| void ClipboardHostImpl::WriteImage( |
| blink::mojom::ClipboardBuffer, |
| const gfx::Size& size, |
| mojo::ScopedSharedBufferHandle shared_buffer_handle) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| 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; |
| } |
| |
| auto mapped = shared_buffer_handle->Map(bitmap.computeByteSize()); |
| if (!mapped) { |
| return; |
| } |
| |
| if (!bitmap.installPixels(bitmap.info(), mapped.get(), bitmap.rowBytes(), |
| &ReleaseSharedMemoryPixels, mapped.get())) { |
| return; |
| } |
| |
| // On success, SkBitmap now owns the SharedMemory. |
| mapped.release(); |
| clipboard_writer_->WriteImage(bitmap); |
| } |
| |
| void ClipboardHostImpl::CommitWrite(blink::mojom::ClipboardBuffer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| clipboard_writer_.reset( |
| new ui::ScopedClipboardWriter(ui::CLIPBOARD_TYPE_COPY_PASTE)); |
| } |
| |
| #if !defined(OS_MACOSX) |
| void ClipboardHostImpl::WriteStringToFindPboard(const base::string16& text) { |
| mojo::ReportBadMessage("Unexpected call to WriteStringToFindPboard."); |
| } |
| #endif |
| |
| void ClipboardHostImpl::ConvertBufferType(blink::mojom::ClipboardBuffer buffer, |
| ui::ClipboardType* result) { |
| *result = ui::CLIPBOARD_TYPE_COPY_PASTE; |
| switch (buffer) { |
| case blink::mojom::ClipboardBuffer::kStandard: |
| break; |
| case blink::mojom::ClipboardBuffer::kSelection: |
| #if defined(USE_X11) |
| *result = ui::CLIPBOARD_TYPE_SELECTION; |
| break; |
| #else |
| // Chrome OS and non-X11 unix builds do not support |
| // the X selection clipboad. |
| // TODO: remove the need for this case, see http://crbug.com/361753 |
| mojo::ReportBadMessage("Cannot use kSelection on non X11 platforms."); |
| #endif |
| } |
| } |
| } // namespace content |