blob: ad404d7f2928a7ae5ac8b6c5022acd971adaffe6 [file] [log] [blame]
/*
* Copyright (C) 2019-2022 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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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.
*/
#include "config.h"
#include "ClipboardItemBindingsDataSource.h"
#include "BitmapImage.h"
#include "Blob.h"
#include "Clipboard.h"
#include "ClipboardItem.h"
#include "Document.h"
#include "ExceptionCode.h"
#include "FileReaderLoader.h"
#include "Frame.h"
#include "GraphicsContext.h"
#include "ImageBuffer.h"
#include "JSBlob.h"
#include "JSDOMPromise.h"
#include "JSDOMPromiseDeferred.h"
#include "PasteboardCustomData.h"
#include "SharedBuffer.h"
#include "markup.h"
#include <wtf/Function.h>
namespace WebCore {
static RefPtr<Document> documentFromClipboard(const Clipboard* clipboard)
{
if (!clipboard)
return nullptr;
auto* frame = clipboard->frame();
if (!frame)
return nullptr;
return frame->document();
}
static FileReaderLoader::ReadType readTypeForMIMEType(const String& type)
{
if (type == "text/uri-list"_s || type == "text/plain"_s || type == "text/html"_s)
return FileReaderLoader::ReadAsText;
return FileReaderLoader::ReadAsArrayBuffer;
}
ClipboardItemBindingsDataSource::ClipboardItemBindingsDataSource(ClipboardItem& item, Vector<KeyValuePair<String, RefPtr<DOMPromise>>>&& itemPromises)
: ClipboardItemDataSource(item)
, m_itemPromises(WTFMove(itemPromises))
{
}
ClipboardItemBindingsDataSource::~ClipboardItemBindingsDataSource() = default;
Vector<String> ClipboardItemBindingsDataSource::types() const
{
return m_itemPromises.map([&] (auto& typeAndItem) {
return typeAndItem.key;
});
}
void ClipboardItemBindingsDataSource::getType(const String& type, Ref<DeferredPromise>&& promise)
{
auto matchIndex = m_itemPromises.findIf([&] (auto& item) {
return type == item.key;
});
if (matchIndex == notFound) {
promise->reject(NotFoundError);
return;
}
auto itemPromise = m_itemPromises[matchIndex].value;
itemPromise->whenSettled([itemPromise, promise = WTFMove(promise), type] () mutable {
if (itemPromise->status() != DOMPromise::Status::Fulfilled) {
promise->reject(AbortError);
return;
}
auto result = itemPromise->result();
if (!result) {
promise->reject(TypeError);
return;
}
String string;
result.getString(itemPromise->globalObject(), string);
if (!string.isNull()) {
promise->resolve<IDLInterface<Blob>>(ClipboardItem::blobFromString(promise->scriptExecutionContext(), string, type));
return;
}
if (!result.isObject()) {
promise->reject(TypeError);
return;
}
if (auto blob = JSBlob::toWrapped(result.getObject()->vm(), result.getObject()))
promise->resolve<IDLInterface<Blob>>(*blob);
else
promise->reject(TypeError);
});
}
void ClipboardItemBindingsDataSource::collectDataForWriting(Clipboard& destination, CompletionHandler<void(std::optional<PasteboardCustomData>)>&& completion)
{
m_itemTypeLoaders.clear();
ASSERT(!m_completionHandler);
m_completionHandler = WTFMove(completion);
m_writingDestination = destination;
m_numberOfPendingClipboardTypes = m_itemPromises.size();
m_itemTypeLoaders = m_itemPromises.map([&] (auto& typeAndItem) {
auto type = typeAndItem.key;
auto itemTypeLoader = ClipboardItemTypeLoader::create(type, [this, protectedItem = Ref { m_item }] {
ASSERT(m_numberOfPendingClipboardTypes);
if (!--m_numberOfPendingClipboardTypes)
invokeCompletionHandler();
});
auto promise = typeAndItem.value;
/* hack: gcc 8.4 will segfault if the WeakPtr is instantiated within the lambda captures */
auto wl = WeakPtr { itemTypeLoader };
promise->whenSettled([this, protectedItem = Ref { m_item }, destination = m_writingDestination, promise, type, weakItemTypeLoader = WTFMove(wl)] () mutable {
if (!weakItemTypeLoader)
return;
Ref itemTypeLoader { *weakItemTypeLoader };
#if !COMPILER(MSVC)
ASSERT_UNUSED(this, notFound != m_itemTypeLoaders.findIf([&] (auto& loader) { return loader.ptr() == itemTypeLoader.ptr(); }));
#endif
auto result = promise->result();
if (!result) {
itemTypeLoader->didFailToResolve();
return;
}
RefPtr clipboard = destination.get();
if (!clipboard) {
itemTypeLoader->didFailToResolve();
return;
}
if (!clipboard->scriptExecutionContext()) {
itemTypeLoader->didFailToResolve();
return;
}
String text;
result.getString(promise->globalObject(), text);
if (!text.isNull()) {
itemTypeLoader->didResolveToString(text);
return;
}
if (!result.isObject()) {
itemTypeLoader->didFailToResolve();
return;
}
if (RefPtr blob = JSBlob::toWrapped(result.getObject()->vm(), result.getObject()))
itemTypeLoader->didResolveToBlob(*clipboard->scriptExecutionContext(), blob.releaseNonNull());
else
itemTypeLoader->didFailToResolve();
});
return itemTypeLoader;
});
if (!m_numberOfPendingClipboardTypes)
invokeCompletionHandler();
}
void ClipboardItemBindingsDataSource::invokeCompletionHandler()
{
if (!m_completionHandler) {
ASSERT_NOT_REACHED();
return;
}
auto completionHandler = std::exchange(m_completionHandler, { });
auto itemTypeLoaders = std::exchange(m_itemTypeLoaders, { });
RefPtr clipboard = m_writingDestination.get();
m_writingDestination = nullptr;
auto document = documentFromClipboard(clipboard.get());
if (!document) {
completionHandler(std::nullopt);
return;
}
PasteboardCustomData customData;
for (auto& itemTypeLoader : itemTypeLoaders) {
auto type = itemTypeLoader->type();
auto& data = itemTypeLoader->data();
if (std::holds_alternative<String>(data) && !!std::get<String>(data))
customData.writeString(type, std::get<String>(data));
else if (std::holds_alternative<Ref<SharedBuffer>>(data))
customData.writeData(type, std::get<Ref<SharedBuffer>>(data).copyRef());
else {
completionHandler(std::nullopt);
return;
}
}
customData.setOrigin(document->originIdentifierForPasteboard());
completionHandler(WTFMove(customData));
}
ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::ClipboardItemTypeLoader(const String& type, CompletionHandler<void()>&& completionHandler)
: m_type(type)
, m_completionHandler(WTFMove(completionHandler))
{
}
ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::~ClipboardItemTypeLoader()
{
if (m_blobLoader)
m_blobLoader->cancel();
invokeCompletionHandler();
}
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::didFinishLoading()
{
ASSERT(m_blobLoader);
auto stringResult = readTypeForMIMEType(m_type) == FileReaderLoader::ReadAsText ? m_blobLoader->stringResult() : nullString();
if (!stringResult.isNull())
m_data = { stringResult };
else if (auto arrayBuffer = m_blobLoader->arrayBufferResult())
m_data = { SharedBuffer::create(static_cast<const char*>(arrayBuffer->data()), arrayBuffer->byteLength()) };
m_blobLoader = nullptr;
invokeCompletionHandler();
}
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::didFail(ExceptionCode)
{
ASSERT(m_blobLoader);
m_blobLoader = nullptr;
invokeCompletionHandler();
}
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::sanitizeDataIfNeeded()
{
if (m_type == "text/html"_s) {
String markupToSanitize;
if (std::holds_alternative<Ref<SharedBuffer>>(m_data)) {
auto& buffer = std::get<Ref<SharedBuffer>>(m_data);
markupToSanitize = String::fromUTF8(buffer->data(), buffer->size());
} else if (std::holds_alternative<String>(m_data))
markupToSanitize = std::get<String>(m_data);
if (markupToSanitize.isEmpty())
return;
m_data = { sanitizeMarkup(markupToSanitize) };
}
if (m_type == "image/png"_s) {
RefPtr<SharedBuffer> bufferToSanitize;
if (std::holds_alternative<Ref<SharedBuffer>>(m_data))
bufferToSanitize = std::get<Ref<SharedBuffer>>(m_data).ptr();
else if (std::holds_alternative<String>(m_data))
bufferToSanitize = utf8Buffer(std::get<String>(m_data));
if (!bufferToSanitize || bufferToSanitize->isEmpty())
return;
auto bitmapImage = BitmapImage::create();
bitmapImage->setData(WTFMove(bufferToSanitize), true);
auto imageBuffer = ImageBuffer::create(bitmapImage->size(), RenderingPurpose::Unspecified, 1, DestinationColorSpace::SRGB(), PixelFormat::BGRA8);
if (!imageBuffer) {
m_data = { nullString() };
return;
}
imageBuffer->context().drawImage(bitmapImage.get(), FloatPoint::zero());
m_data = { SharedBuffer::create(imageBuffer->toData("image/png"_s)) };
}
}
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::invokeCompletionHandler()
{
if (auto completion = WTFMove(m_completionHandler)) {
sanitizeDataIfNeeded();
completion();
}
}
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::didResolveToBlob(ScriptExecutionContext& context, Ref<Blob>&& blob)
{
ASSERT(!m_blobLoader);
m_blobLoader = makeUnique<FileReaderLoader>(readTypeForMIMEType(m_type), this);
m_blobLoader->start(&context, WTFMove(blob));
}
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::didFailToResolve()
{
ASSERT(!m_blobLoader);
invokeCompletionHandler();
}
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::didResolveToString(const String& text)
{
ASSERT(!m_blobLoader);
m_data = { text };
invokeCompletionHandler();
}
} // namespace WebCore