2
* Copyright (C) 2019 Apple Inc. All rights reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions
7
* 1. Redistributions of source code must retain the above copyright
8
* notice, this list of conditions and the following disclaimer.
9
* 2. Redistributions in binary form must reproduce the above copyright
10
* notice, this list of conditions and the following disclaimer in the
11
* documentation and/or other materials provided with the distribution.
13
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23
* THE POSSIBILITY OF SUCH DAMAGE.
27
#include "ClipboardItemBindingsDataSource.h"
29
#include "BitmapImage.h"
31
#include "Clipboard.h"
32
#include "ClipboardItem.h"
34
#include "FileReaderLoader.h"
36
#include "GraphicsContext.h"
37
#include "ImageBuffer.h"
39
#include "JSDOMPromise.h"
40
#include "JSDOMPromiseDeferred.h"
41
#include "PasteboardCustomData.h"
42
#include "SharedBuffer.h"
44
#include <wtf/Function.h>
48
static RefPtr<Document> documentFromClipboard(const Clipboard* clipboard)
53
auto* frame = clipboard->frame();
57
return frame->document();
60
static FileReaderLoader::ReadType readTypeForMIMEType(const String& type)
62
if (type == "text/uri-list"_s || type == "text/plain"_s || type == "text/html"_s)
63
return FileReaderLoader::ReadAsText;
64
return FileReaderLoader::ReadAsArrayBuffer;
67
ClipboardItemBindingsDataSource::ClipboardItemBindingsDataSource(ClipboardItem& item, Vector<KeyValuePair<String, RefPtr<DOMPromise>>>&& itemPromises)
68
: ClipboardItemDataSource(item)
69
, m_itemPromises(WTFMove(itemPromises))
73
ClipboardItemBindingsDataSource::~ClipboardItemBindingsDataSource() = default;
75
Vector<String> ClipboardItemBindingsDataSource::types() const
77
return m_itemPromises.map([&] (auto& typeAndItem) {
78
return typeAndItem.key;
82
void ClipboardItemBindingsDataSource::getType(const String& type, Ref<DeferredPromise>&& promise)
84
auto matchIndex = m_itemPromises.findMatching([&] (auto& item) {
85
return type == item.key;
88
if (matchIndex == notFound) {
89
promise->reject(NotFoundError);
93
auto itemPromise = m_itemPromises[matchIndex].value;
94
itemPromise->whenSettled([itemPromise, promise = makeRefPtr(promise.get()), type] () mutable {
95
if (itemPromise->status() != DOMPromise::Status::Fulfilled) {
96
promise->reject(AbortError);
100
auto result = itemPromise->result();
102
promise->reject(TypeError);
107
result.getString(itemPromise->globalObject(), string);
108
if (!string.isNull()) {
109
promise->resolve<IDLInterface<Blob>>(ClipboardItem::blobFromString(string, type));
113
if (!result.isObject()) {
114
promise->reject(TypeError);
118
if (auto blob = JSBlob::toWrapped(result.getObject()->vm(), result.getObject()))
119
promise->resolve<IDLInterface<Blob>>(*blob);
121
promise->reject(TypeError);
125
void ClipboardItemBindingsDataSource::collectDataForWriting(Clipboard& destination, CompletionHandler<void(Optional<PasteboardCustomData>)>&& completion)
127
m_itemTypeLoaders.clear();
128
ASSERT(!m_completionHandler);
129
m_completionHandler = WTFMove(completion);
130
m_writingDestination = makeWeakPtr(destination);
131
m_numberOfPendingClipboardTypes = m_itemPromises.size();
132
m_itemTypeLoaders = m_itemPromises.map([&] (auto& typeAndItem) {
133
auto type = typeAndItem.key;
134
auto itemTypeLoader = ClipboardItemTypeLoader::create(type, [this, protectedItem = makeRef(m_item)] {
135
ASSERT(m_numberOfPendingClipboardTypes);
136
if (!--m_numberOfPendingClipboardTypes)
137
invokeCompletionHandler();
140
auto promise = typeAndItem.value;
141
promise->whenSettled([this, protectedItem = makeRefPtr(m_item), destination = m_writingDestination, promise, type, weakItemTypeLoader = makeWeakPtr(itemTypeLoader.ptr())] () mutable {
142
if (!weakItemTypeLoader)
145
auto itemTypeLoader = makeRef(*weakItemTypeLoader);
146
ASSERT_UNUSED(this, notFound != m_itemTypeLoaders.findMatching([&] (auto& loader) { return loader.ptr() == itemTypeLoader.ptr(); }));
148
auto result = promise->result();
150
itemTypeLoader->didFailToResolve();
154
auto clipboard = makeRefPtr(destination.get());
156
itemTypeLoader->didFailToResolve();
160
if (!clipboard->scriptExecutionContext()) {
161
itemTypeLoader->didFailToResolve();
166
result.getString(promise->globalObject(), text);
167
if (!text.isNull()) {
168
itemTypeLoader->didResolveToString(text);
172
if (!result.isObject()) {
173
itemTypeLoader->didFailToResolve();
177
if (auto blob = makeRefPtr(JSBlob::toWrapped(result.getObject()->vm(), result.getObject())))
178
itemTypeLoader->didResolveToBlob(*clipboard->scriptExecutionContext(), blob.releaseNonNull());
180
itemTypeLoader->didFailToResolve();
183
return itemTypeLoader;
186
if (!m_numberOfPendingClipboardTypes)
187
invokeCompletionHandler();
190
void ClipboardItemBindingsDataSource::invokeCompletionHandler()
192
if (!m_completionHandler) {
193
ASSERT_NOT_REACHED();
197
auto completionHandler = std::exchange(m_completionHandler, { });
198
auto itemTypeLoaders = std::exchange(m_itemTypeLoaders, { });
199
auto clipboard = makeRefPtr(m_writingDestination.get());
200
m_writingDestination = nullptr;
202
auto document = documentFromClipboard(clipboard.get());
204
completionHandler(WTF::nullopt);
208
PasteboardCustomData customData;
209
for (auto& itemTypeLoader : itemTypeLoaders) {
210
auto type = itemTypeLoader->type();
211
auto& data = itemTypeLoader->data();
212
if (WTF::holds_alternative<String>(data) && !!WTF::get<String>(data))
213
customData.writeString(type, WTF::get<String>(data));
214
else if (WTF::holds_alternative<Ref<SharedBuffer>>(data))
215
customData.writeData(type, WTF::get<Ref<SharedBuffer>>(data).copyRef());
217
completionHandler(WTF::nullopt);
222
customData.setOrigin(document->originIdentifierForPasteboard());
223
completionHandler(WTFMove(customData));
226
ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::ClipboardItemTypeLoader(const String& type, CompletionHandler<void()>&& completionHandler)
228
, m_completionHandler(WTFMove(completionHandler))
232
ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::~ClipboardItemTypeLoader()
235
m_blobLoader->cancel();
237
invokeCompletionHandler();
240
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::didFinishLoading()
242
ASSERT(m_blobLoader);
243
auto stringResult = readTypeForMIMEType(m_type) == FileReaderLoader::ReadAsText ? m_blobLoader->stringResult() : nullString();
244
if (!stringResult.isNull())
245
m_data = { stringResult };
246
else if (auto arrayBuffer = m_blobLoader->arrayBufferResult())
247
m_data = { SharedBuffer::create(static_cast<const char*>(arrayBuffer->data()), arrayBuffer->byteLength()) };
248
m_blobLoader = nullptr;
249
invokeCompletionHandler();
252
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::didFail(int)
254
ASSERT(m_blobLoader);
255
m_blobLoader = nullptr;
256
invokeCompletionHandler();
259
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::sanitizeDataIfNeeded()
261
if (m_type == "text/html"_s) {
262
String markupToSanitize;
263
if (WTF::holds_alternative<Ref<SharedBuffer>>(m_data)) {
264
auto& buffer = WTF::get<Ref<SharedBuffer>>(m_data);
265
markupToSanitize = String::fromUTF8(buffer->data(), buffer->size());
266
} else if (WTF::holds_alternative<String>(m_data))
267
markupToSanitize = WTF::get<String>(m_data);
269
if (markupToSanitize.isEmpty())
272
m_data = { sanitizeMarkup(markupToSanitize) };
275
if (m_type == "image/png"_s) {
276
RefPtr<SharedBuffer> bufferToSanitize;
277
if (WTF::holds_alternative<Ref<SharedBuffer>>(m_data))
278
bufferToSanitize = WTF::get<Ref<SharedBuffer>>(m_data).ptr();
279
else if (WTF::holds_alternative<String>(m_data))
280
bufferToSanitize = utf8Buffer(WTF::get<String>(m_data));
282
if (!bufferToSanitize || bufferToSanitize->isEmpty())
285
auto bitmapImage = BitmapImage::create();
286
bitmapImage->setData(WTFMove(bufferToSanitize), true);
287
auto imageBuffer = ImageBuffer::create(bitmapImage->size(), RenderingMode::Unaccelerated);
289
m_data = { nullString() };
293
imageBuffer->context().drawImage(bitmapImage.get(), FloatPoint::zero());
294
m_data = { SharedBuffer::create(imageBuffer->toData("image/png"_s)) };
298
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::invokeCompletionHandler()
300
if (auto completion = WTFMove(m_completionHandler)) {
301
sanitizeDataIfNeeded();
306
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::didResolveToBlob(ScriptExecutionContext& context, Ref<Blob>&& blob)
308
ASSERT(!m_blobLoader);
309
m_blobLoader = makeUnique<FileReaderLoader>(readTypeForMIMEType(m_type), this);
310
m_blobLoader->start(&context, WTFMove(blob));
313
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::didFailToResolve()
315
ASSERT(!m_blobLoader);
316
invokeCompletionHandler();
319
void ClipboardItemBindingsDataSource::ClipboardItemTypeLoader::didResolveToString(const String& text)
321
ASSERT(!m_blobLoader);
323
invokeCompletionHandler();
326
} // namespace WebCore