blob: 625d8874a873f2208225202816342a7e4abf0d3a [file] [log] [blame]
/*
* Copyright (C) 2006, 2007, 2008 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 COMPUTER, INC. ``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 COMPUTER, INC. OR
* 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 "core/clipboard/DataTransfer.h"
#include <memory>
#include "build/build_config.h"
#include "core/clipboard/DataObject.h"
#include "core/clipboard/DataTransferItem.h"
#include "core/clipboard/DataTransferItemList.h"
#include "core/editing/EphemeralRange.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/VisibleSelection.h"
#include "core/editing/serializers/Serialization.h"
#include "core/fileapi/FileList.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/VisualViewport.h"
#include "core/html/HTMLImageElement.h"
#include "core/html/forms/TextControlElement.h"
#include "core/layout/LayoutImage.h"
#include "core/layout/LayoutObject.h"
#include "core/loader/resource/ImageResourceContent.h"
#include "core/page/ChromeClient.h"
#include "core/page/Page.h"
#include "core/paint/PaintInfo.h"
#include "core/paint/PaintLayer.h"
#include "core/paint/PaintLayerPainter.h"
#include "platform/DragImage.h"
#include "platform/clipboard/ClipboardMimeTypes.h"
#include "platform/clipboard/ClipboardUtilities.h"
#include "platform/graphics/StaticBitmapImage.h"
#include "platform/graphics/paint/PaintRecordBuilder.h"
#include "platform/network/mime/MIMETypeRegistry.h"
#include "public/platform/WebScreenInfo.h"
#include "third_party/skia/include/core/SkSurface.h"
namespace blink {
namespace {
class DraggedNodeImageBuilder {
STACK_ALLOCATED();
public:
DraggedNodeImageBuilder(const LocalFrame& local_frame, Node& node)
: local_frame_(&local_frame),
node_(&node)
#if DCHECK_IS_ON()
,
dom_tree_version_(node.GetDocument().DomTreeVersion())
#endif
{
for (Node& descendant : NodeTraversal::InclusiveDescendantsOf(*node_))
descendant.SetDragged(true);
}
~DraggedNodeImageBuilder() {
#if DCHECK_IS_ON()
DCHECK_EQ(dom_tree_version_, node_->GetDocument().DomTreeVersion());
#endif
for (Node& descendant : NodeTraversal::InclusiveDescendantsOf(*node_))
descendant.SetDragged(false);
}
std::unique_ptr<DragImage> CreateImage() {
#if DCHECK_IS_ON()
DCHECK_EQ(dom_tree_version_, node_->GetDocument().DomTreeVersion());
#endif
// Construct layout object for |m_node| with pseudo class "-webkit-drag"
local_frame_->View()->UpdateAllLifecyclePhasesExceptPaint();
LayoutObject* const dragged_layout_object = node_->GetLayoutObject();
if (!dragged_layout_object)
return nullptr;
// Paint starting at the nearest stacking context, clipped to the object
// itself. This will also paint the contents behind the object if the
// object contains transparency and there are other elements in the same
// stacking context which stacked below.
PaintLayer* layer = dragged_layout_object->EnclosingLayer();
if (!layer->StackingNode()->IsStackingContext())
layer = layer->StackingNode()->AncestorStackingContextNode()->Layer();
IntRect absolute_bounding_box =
dragged_layout_object->AbsoluteBoundingBoxRectIncludingDescendants();
// TODO(chrishtr): consider using the root frame's visible rect instead
// of the local frame, to avoid over-clipping.
FloatRect visible_rect =
layer->GetLayoutObject().GetFrameView()->VisibleContentRect();
// If the absolute bounding box is large enough to be possibly a memory
// or IPC payload issue, clip it to the visible content rect.
if (absolute_bounding_box.Size().Area() > visible_rect.Size().Area()) {
absolute_bounding_box.Intersect(IntRect(visible_rect));
}
FloatRect bounding_box =
layer->GetLayoutObject()
.AbsoluteToLocalQuad(FloatQuad(absolute_bounding_box),
kUseTransforms)
.BoundingBox();
PaintLayerPaintingInfo painting_info(layer, LayoutRect(bounding_box),
kGlobalPaintFlattenCompositingLayers,
LayoutSize());
PaintLayerFlags flags = kPaintLayerHaveTransparency |
kPaintLayerAppliedTransform |
kPaintLayerUncachedClipRects;
PaintRecordBuilder builder;
dragged_layout_object->GetDocument().Lifecycle().AdvanceTo(
DocumentLifecycle::kInPaint);
PaintLayerPainter(*layer).Paint(builder.Context(), painting_info, flags);
dragged_layout_object->GetDocument().Lifecycle().AdvanceTo(
DocumentLifecycle::kPaintClean);
PropertyTreeState border_box_properties = PropertyTreeState::Root();
if (RuntimeEnabledFeatures::SlimmingPaintV2Enabled()) {
border_box_properties =
*layer->GetLayoutObject().FirstFragment().LocalBorderBoxProperties();
}
FloatPoint paint_offset = dragged_layout_object->LocalToAncestorPoint(
FloatPoint(), &layer->GetLayoutObject(), kUseTransforms);
return DataTransfer::CreateDragImageForFrame(
*local_frame_, 1.0f,
LayoutObject::ShouldRespectImageOrientation(dragged_layout_object),
bounding_box.Size(), paint_offset, builder, border_box_properties);
}
private:
const Member<const LocalFrame> local_frame_;
const Member<Node> node_;
#if DCHECK_IS_ON()
const uint64_t dom_tree_version_;
#endif
};
} // namespace
static DragOperation ConvertEffectAllowedToDragOperation(const String& op) {
// Values specified in
// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#dom-datatransfer-effectallowed
if (op == "uninitialized")
return kDragOperationEvery;
if (op == "none")
return kDragOperationNone;
if (op == "copy")
return kDragOperationCopy;
if (op == "link")
return kDragOperationLink;
if (op == "move")
return (DragOperation)(kDragOperationGeneric | kDragOperationMove);
if (op == "copyLink")
return (DragOperation)(kDragOperationCopy | kDragOperationLink);
if (op == "copyMove")
return (DragOperation)(kDragOperationCopy | kDragOperationGeneric |
kDragOperationMove);
if (op == "linkMove")
return (DragOperation)(kDragOperationLink | kDragOperationGeneric |
kDragOperationMove);
if (op == "all")
return kDragOperationEvery;
return kDragOperationPrivate; // really a marker for "no conversion"
}
static String ConvertDragOperationToEffectAllowed(DragOperation op) {
bool move_set = !!((kDragOperationGeneric | kDragOperationMove) & op);
if ((move_set && (op & kDragOperationCopy) && (op & kDragOperationLink)) ||
(op == kDragOperationEvery))
return "all";
if (move_set && (op & kDragOperationCopy))
return "copyMove";
if (move_set && (op & kDragOperationLink))
return "linkMove";
if ((op & kDragOperationCopy) && (op & kDragOperationLink))
return "copyLink";
if (move_set)
return "move";
if (op & kDragOperationCopy)
return "copy";
if (op & kDragOperationLink)
return "link";
return "none";
}
// We provide the IE clipboard types (URL and Text), and the clipboard types
// specified in the WHATWG Web Applications 1.0 draft see
// http://www.whatwg.org/specs/web-apps/current-work/ Section 6.3.5.3
static String NormalizeType(const String& type,
bool* convert_to_url = nullptr) {
String clean_type = type.StripWhiteSpace().DeprecatedLower();
if (clean_type == kMimeTypeText ||
clean_type.StartsWith(kMimeTypeTextPlainEtc))
return kMimeTypeTextPlain;
if (clean_type == kMimeTypeURL) {
if (convert_to_url)
*convert_to_url = true;
return kMimeTypeTextURIList;
}
return clean_type;
}
DataTransfer* DataTransfer::Create() {
DataTransfer* data =
Create(kCopyAndPaste, kDataTransferWritable, DataObject::Create());
data->drop_effect_ = "none";
data->effect_allowed_ = "none";
return data;
}
DataTransfer* DataTransfer::Create(DataTransferType type,
DataTransferAccessPolicy policy,
DataObject* data_object) {
return new DataTransfer(type, policy, data_object);
}
DataTransfer::~DataTransfer() = default;
void DataTransfer::setDropEffect(const String& effect) {
if (!IsForDragAndDrop())
return;
// The attribute must ignore any attempts to set it to a value other than
// none, copy, link, and move.
if (effect != "none" && effect != "copy" && effect != "link" &&
effect != "move")
return;
// The specification states that dropEffect can be changed at all times, even
// if the DataTransfer instance is protected or neutered.
//
// Allowing these changes seems inconsequential, but findDropZone() in
// EventHandler.cpp relies on being able to call setDropEffect during
// dragenter, when the DataTransfer policy is DataTransferTypesReadable.
drop_effect_ = effect;
}
void DataTransfer::setEffectAllowed(const String& effect) {
if (!IsForDragAndDrop())
return;
if (ConvertEffectAllowedToDragOperation(effect) == kDragOperationPrivate) {
// This means that there was no conversion, and the effectAllowed that
// we are passed isn't a valid effectAllowed, so we should ignore it,
// and not set m_effectAllowed.
// The attribute must ignore any attempts to set it to a value other than
// none, copy, copyLink, copyMove, link, linkMove, move, all, and
// uninitialized.
return;
}
if (CanWriteData())
effect_allowed_ = effect;
}
void DataTransfer::clearData(const String& type) {
if (!CanWriteData())
return;
if (type.IsNull())
data_object_->ClearAll();
else
data_object_->ClearData(NormalizeType(type));
}
String DataTransfer::getData(const String& type) const {
if (!CanReadData())
return String();
bool convert_to_url = false;
String data = data_object_->GetData(NormalizeType(type, &convert_to_url));
if (!convert_to_url)
return data;
return ConvertURIListToURL(data);
}
void DataTransfer::setData(const String& type, const String& data) {
if (!CanWriteData())
return;
data_object_->SetData(NormalizeType(type), data);
}
bool DataTransfer::hasDataStoreItemListChanged() const {
return data_store_item_list_changed_ || !CanReadTypes();
}
void DataTransfer::OnItemListChanged() {
data_store_item_list_changed_ = true;
}
Vector<String> DataTransfer::types() {
if (!CanReadTypes())
return Vector<String>();
data_store_item_list_changed_ = false;
return data_object_->Types();
}
FileList* DataTransfer::files() const {
FileList* files = FileList::Create();
if (!CanReadData())
return files;
for (size_t i = 0; i < data_object_->length(); ++i) {
if (data_object_->Item(i)->Kind() == DataObjectItem::kFileKind) {
Blob* blob = data_object_->Item(i)->GetAsFile();
if (blob && blob->IsFile())
files->Append(ToFile(blob));
}
}
return files;
}
void DataTransfer::setDragImage(Element* image, int x, int y) {
DCHECK(image);
if (!IsForDragAndDrop())
return;
IntPoint location(x, y);
if (IsHTMLImageElement(*image) && !image->isConnected())
SetDragImageResource(ToHTMLImageElement(*image).CachedImage(), location);
else
SetDragImageElement(image, location);
}
void DataTransfer::ClearDragImage() {
if (!CanSetDragImage())
return;
drag_image_ = nullptr;
drag_loc_ = IntPoint();
drag_image_element_ = nullptr;
}
void DataTransfer::SetDragImageResource(ImageResourceContent* img,
const IntPoint& loc) {
setDragImage(img, nullptr, loc);
}
void DataTransfer::SetDragImageElement(Node* node, const IntPoint& loc) {
setDragImage(nullptr, node, loc);
}
FloatRect DataTransfer::ClipByVisualViewport(const FloatRect& rect_in_document,
const LocalFrame& frame) {
IntRect viewport_in_root_frame =
IntRect(frame.GetPage()->GetVisualViewport().VisibleRect());
FloatRect viewport_in_document =
frame.View()->RootFrameToDocument(viewport_in_root_frame);
return Intersection(viewport_in_document, rect_in_document);
}
// static
// Converts from size in CSS space to device space based on the given frame.
FloatSize DataTransfer::DeviceSpaceSize(const FloatSize& css_size,
const LocalFrame& frame) {
float device_scale_factor = frame.GetPage()->DeviceScaleFactorDeprecated();
float page_scale_factor = frame.GetPage()->GetVisualViewport().Scale();
FloatSize device_size(css_size);
device_size.Scale(device_scale_factor * page_scale_factor);
return device_size;
}
// static
// Returns a DragImage whose bitmap contains |contents|, positioned and scaled
// in device space.
std::unique_ptr<DragImage> DataTransfer::CreateDragImageForFrame(
const LocalFrame& frame,
float opacity,
RespectImageOrientationEnum image_orientation,
const FloatSize& css_size,
const FloatPoint& paint_offset,
PaintRecordBuilder& builder,
const PropertyTreeState& property_tree_state) {
float device_scale_factor = frame.GetPage()->DeviceScaleFactorDeprecated();
float page_scale_factor = frame.GetPage()->GetVisualViewport().Scale();
FloatSize device_size = DeviceSpaceSize(css_size, frame);
AffineTransform transform;
FloatSize paint_offset_size =
DeviceSpaceSize(FloatSize(paint_offset.X(), paint_offset.Y()), frame);
transform.Translate(-paint_offset_size.Width(), -paint_offset_size.Height());
transform.Scale(device_scale_factor * page_scale_factor);
// Rasterize upfront, since DragImage::create() is going to do it anyway
// (SkImage::asLegacyBitmap).
SkSurfaceProps surface_props(0, kUnknown_SkPixelGeometry);
sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(
device_size.Width(), device_size.Height(), &surface_props);
if (!surface)
return nullptr;
SkiaPaintCanvas skia_paint_canvas(surface->getCanvas());
skia_paint_canvas.concat(AffineTransformToSkMatrix(transform));
builder.EndRecording(skia_paint_canvas, property_tree_state);
scoped_refptr<Image> image =
StaticBitmapImage::Create(surface->makeImageSnapshot());
float screen_device_scale_factor =
frame.GetPage()->GetChromeClient().GetScreenInfo().device_scale_factor;
return DragImage::Create(image.get(), image_orientation,
screen_device_scale_factor, kInterpolationDefault,
opacity);
}
// static
std::unique_ptr<DragImage> DataTransfer::NodeImage(const LocalFrame& frame,
Node& node) {
DraggedNodeImageBuilder image_node(frame, node);
return image_node.CreateImage();
}
std::unique_ptr<DragImage> DataTransfer::CreateDragImage(
IntPoint& loc,
LocalFrame* frame) const {
if (drag_image_element_) {
loc = drag_loc_;
return NodeImage(*frame, *drag_image_element_);
}
if (drag_image_) {
loc = drag_loc_;
return DragImage::Create(drag_image_->GetImage());
}
return nullptr;
}
static ImageResourceContent* GetImageResourceContent(Element* element) {
// Attempt to pull ImageResourceContent from element
DCHECK(element);
LayoutObject* layout_object = element->GetLayoutObject();
if (!layout_object || !layout_object->IsImage())
return nullptr;
LayoutImage* image = ToLayoutImage(layout_object);
if (image->CachedImage() && !image->CachedImage()->ErrorOccurred())
return image->CachedImage();
return nullptr;
}
static void WriteImageToDataObject(DataObject* data_object,
Element* element,
const KURL& image_url) {
// Shove image data into a DataObject for use as a file
ImageResourceContent* cached_image = GetImageResourceContent(element);
if (!cached_image || !cached_image->GetImage() || !cached_image->IsLoaded())
return;
Image* image = cached_image->GetImage();
scoped_refptr<SharedBuffer> image_buffer = image->Data();
if (!image_buffer || !image_buffer->size())
return;
data_object->AddSharedBuffer(
image_buffer, image_url, image->FilenameExtension(),
cached_image->GetResponse().HttpHeaderFields().Get(
HTTPNames::Content_Disposition));
}
void DataTransfer::DeclareAndWriteDragImage(Element* element,
const KURL& link_url,
const KURL& image_url,
const String& title) {
if (!data_object_)
return;
data_object_->SetURLAndTitle(link_url.IsValid() ? link_url : image_url,
title);
// Write the bytes in the image to the file format.
WriteImageToDataObject(data_object_.Get(), element, image_url);
// Put img tag on the clipboard referencing the image
data_object_->SetData(kMimeTypeTextHTML,
CreateMarkup(element, kIncludeNode, kResolveAllURLs));
}
void DataTransfer::WriteURL(Node* node, const KURL& url, const String& title) {
if (!data_object_)
return;
DCHECK(!url.IsEmpty());
data_object_->SetURLAndTitle(url, title);
// The URL can also be used as plain text.
data_object_->SetData(kMimeTypeTextPlain, url.GetString());
// The URL can also be used as an HTML fragment.
data_object_->SetHTMLAndBaseURL(
CreateMarkup(node, kIncludeNode, kResolveAllURLs), url);
}
void DataTransfer::WriteSelection(const FrameSelection& selection) {
if (!data_object_)
return;
if (!EnclosingTextControl(
selection.ComputeVisibleSelectionInDOMTreeDeprecated().Start())) {
data_object_->SetHTMLAndBaseURL(selection.SelectedHTMLForClipboard(),
selection.GetFrame()->GetDocument()->Url());
}
String str = selection.SelectedTextForClipboard();
#if defined(OS_WIN)
ReplaceNewlinesWithWindowsStyleNewlines(str);
#endif
ReplaceNBSPWithSpace(str);
data_object_->SetData(kMimeTypeTextPlain, str);
}
void DataTransfer::SetAccessPolicy(DataTransferAccessPolicy policy) {
// once you go numb, can never go back
DCHECK(policy_ != kDataTransferNumb || policy == kDataTransferNumb);
policy_ = policy;
}
bool DataTransfer::CanReadTypes() const {
return policy_ == kDataTransferReadable ||
policy_ == kDataTransferTypesReadable ||
policy_ == kDataTransferWritable;
}
bool DataTransfer::CanReadData() const {
return policy_ == kDataTransferReadable || policy_ == kDataTransferWritable;
}
bool DataTransfer::CanWriteData() const {
return policy_ == kDataTransferWritable;
}
bool DataTransfer::CanSetDragImage() const {
return policy_ == kDataTransferImageWritable ||
policy_ == kDataTransferWritable;
}
DragOperation DataTransfer::SourceOperation() const {
DragOperation op = ConvertEffectAllowedToDragOperation(effect_allowed_);
DCHECK_NE(op, kDragOperationPrivate);
return op;
}
DragOperation DataTransfer::DestinationOperation() const {
DragOperation op = ConvertEffectAllowedToDragOperation(drop_effect_);
DCHECK(op == kDragOperationCopy || op == kDragOperationNone ||
op == kDragOperationLink ||
op == (DragOperation)(kDragOperationGeneric | kDragOperationMove) ||
op == kDragOperationEvery);
return op;
}
void DataTransfer::SetSourceOperation(DragOperation op) {
DCHECK_NE(op, kDragOperationPrivate);
effect_allowed_ = ConvertDragOperationToEffectAllowed(op);
}
void DataTransfer::SetDestinationOperation(DragOperation op) {
DCHECK(op == kDragOperationCopy || op == kDragOperationNone ||
op == kDragOperationLink || op == kDragOperationGeneric ||
op == kDragOperationMove ||
op == static_cast<DragOperation>(kDragOperationGeneric |
kDragOperationMove));
drop_effect_ = ConvertDragOperationToEffectAllowed(op);
}
bool DataTransfer::HasDropZoneType(const String& keyword) {
if (keyword.StartsWith("file:"))
return HasFileOfType(keyword.Substring(5));
if (keyword.StartsWith("string:"))
return HasStringOfType(keyword.Substring(7));
return false;
}
DataTransferItemList* DataTransfer::items() {
// FIXME: According to the spec, we are supposed to return the same collection
// of items each time. We now return a wrapper that always wraps the *same*
// set of items, so JS shouldn't be able to tell, but we probably still want
// to fix this.
return DataTransferItemList::Create(this, data_object_);
}
DataObject* DataTransfer::GetDataObject() const {
return data_object_;
}
DataTransfer::DataTransfer(DataTransferType type,
DataTransferAccessPolicy policy,
DataObject* data_object)
: policy_(policy),
drop_effect_("uninitialized"),
effect_allowed_("uninitialized"),
transfer_type_(type),
data_object_(data_object),
data_store_item_list_changed_(true) {
data_object_->AddObserver(this);
}
void DataTransfer::setDragImage(ImageResourceContent* image,
Node* node,
const IntPoint& loc) {
if (!CanSetDragImage())
return;
drag_image_ = image;
drag_loc_ = loc;
drag_image_element_ = node;
}
bool DataTransfer::HasFileOfType(const String& type) const {
if (!CanReadTypes())
return false;
for (size_t i = 0; i < data_object_->length(); ++i) {
if (data_object_->Item(i)->Kind() == DataObjectItem::kFileKind) {
Blob* blob = data_object_->Item(i)->GetAsFile();
if (blob && blob->IsFile() &&
DeprecatedEqualIgnoringCase(blob->type(), type))
return true;
}
}
return false;
}
bool DataTransfer::HasStringOfType(const String& type) const {
if (!CanReadTypes())
return false;
return data_object_->Types().Contains(type);
}
DragOperation ConvertDropZoneOperationToDragOperation(
const String& drag_operation) {
if (drag_operation == "copy")
return kDragOperationCopy;
if (drag_operation == "move")
return kDragOperationMove;
if (drag_operation == "link")
return kDragOperationLink;
return kDragOperationNone;
}
String ConvertDragOperationToDropZoneOperation(DragOperation operation) {
switch (operation) {
case kDragOperationCopy:
return String("copy");
case kDragOperationMove:
return String("move");
case kDragOperationLink:
return String("link");
default:
return String("copy");
}
}
void DataTransfer::Trace(blink::Visitor* visitor) {
visitor->Trace(data_object_);
visitor->Trace(drag_image_);
visitor->Trace(drag_image_element_);
ScriptWrappable::Trace(visitor);
}
} // namespace blink