blob: 3b0b20a1f4360eae913b228e4e010d3065b6a264 [file] [log] [blame]
/*
* Copyright (C) 2007, 2009, 2010 Apple Inc. All rights reserved.
* Copyright (C) 2008 Google Inc.
*
* 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 "third_party/blink/renderer/core/page/drag_controller.h"
#include <memory>
#include "base/memory/scoped_refptr.h"
#include "build/build_config.h"
#include "third_party/blink/public/platform/web_common.h"
#include "third_party/blink/public/platform/web_drag_data.h"
#include "third_party/blink/public/platform/web_drag_operation.h"
#include "third_party/blink/public/platform/web_image.h"
#include "third_party/blink/public/platform/web_point.h"
#include "third_party/blink/public/platform/web_screen_info.h"
#include "third_party/blink/renderer/core/clipboard/data_object.h"
#include "third_party/blink/renderer/core/clipboard/data_transfer.h"
#include "third_party/blink/renderer/core/clipboard/data_transfer_access_policy.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/document_fragment.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
#include "third_party/blink/renderer/core/dom/shadow_root.h"
#include "third_party/blink/renderer/core/dom/text.h"
#include "third_party/blink/renderer/core/dom/user_gesture_indicator.h"
#include "third_party/blink/renderer/core/editing/commands/drag_and_drop_command.h"
#include "third_party/blink/renderer/core/editing/drag_caret.h"
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/editing/editor.h"
#include "third_party/blink/renderer/core/editing/ephemeral_range.h"
#include "third_party/blink/renderer/core/editing/frame_selection.h"
#include "third_party/blink/renderer/core/editing/selection_template.h"
#include "third_party/blink/renderer/core/editing/serializers/serialization.h"
#include "third_party/blink/renderer/core/events/text_event.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
#include "third_party/blink/renderer/core/html/forms/html_form_element.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/html_anchor_element.h"
#include "third_party/blink/renderer/core/html/html_plugin_element.h"
#include "third_party/blink/renderer/core/input/event_handler.h"
#include "third_party/blink/renderer/core/input_type_names.h"
#include "third_party/blink/renderer/core/layout/hit_test_request.h"
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
#include "third_party/blink/renderer/core/layout/layout_image.h"
#include "third_party/blink/renderer/core/layout/layout_theme.h"
#include "third_party/blink/renderer/core/layout/layout_view.h"
#include "third_party/blink/renderer/core/loader/frame_load_request.h"
#include "third_party/blink/renderer/core/loader/frame_loader.h"
#include "third_party/blink/renderer/core/loader/resource/image_resource_content.h"
#include "third_party/blink/renderer/core/page/chrome_client.h"
#include "third_party/blink/renderer/core/page/drag_data.h"
#include "third_party/blink/renderer/core/page/drag_state.h"
#include "third_party/blink/renderer/core/page/page.h"
#include "third_party/blink/renderer/core/svg/graphics/svg_image_for_container.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/drag_image.h"
#include "third_party/blink/renderer/platform/geometry/int_rect.h"
#include "third_party/blink/renderer/platform/geometry/int_size.h"
#include "third_party/blink/renderer/platform/graphics/bitmap_image.h"
#include "third_party/blink/renderer/platform/graphics/image.h"
#include "third_party/blink/renderer/platform/graphics/image_orientation.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_record_builder.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_fetcher.h"
#include "third_party/blink/renderer/platform/loader/fetch/resource_request.h"
#include "third_party/blink/renderer/platform/shared_buffer.h"
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/time.h"
#if defined(OS_WIN)
#include <windows.h>
#endif
namespace blink {
static const int kMaxOriginalImageArea = 1500 * 1500;
static const int kLinkDragBorderInset = 2;
static const float kDragImageAlpha = 0.75f;
#if DCHECK_IS_ON()
static bool DragTypeIsValid(DragSourceAction action) {
switch (action) {
case kDragSourceActionDHTML:
case kDragSourceActionImage:
case kDragSourceActionLink:
case kDragSourceActionSelection:
return true;
case kDragSourceActionNone:
return false;
}
// Make sure MSVC doesn't complain that not all control paths return a value.
NOTREACHED();
return false;
}
#endif // DCHECK_IS_ON()
static WebMouseEvent CreateMouseEvent(DragData* drag_data) {
WebMouseEvent result(
WebInputEvent::kMouseMove, drag_data->ClientPosition(),
drag_data->GlobalPosition(), WebPointerProperties::Button::kLeft, 0,
static_cast<WebInputEvent::Modifiers>(drag_data->GetModifiers()),
CurrentTimeTicks());
// TODO(dtapuska): Really we should chnage DragData to store the viewport
// coordinates and scale.
result.SetFrameScale(1);
return result;
}
static DataTransfer* CreateDraggingDataTransfer(DataTransferAccessPolicy policy,
DragData* drag_data) {
return DataTransfer::Create(DataTransfer::kDragAndDrop, policy,
drag_data->PlatformData());
}
DragController::DragController(Page* page)
: page_(page),
document_under_mouse_(nullptr),
drag_initiator_(nullptr),
file_input_element_under_mouse_(nullptr),
document_is_handling_drag_(false),
drag_destination_action_(kDragDestinationActionNone),
did_initiate_drag_(false) {}
DragController* DragController::Create(Page* page) {
return MakeGarbageCollected<DragController>(page);
}
static DocumentFragment* DocumentFragmentFromDragData(
DragData* drag_data,
LocalFrame* frame,
Range* context,
bool allow_plain_text,
DragSourceType& drag_source_type) {
DCHECK(drag_data);
drag_source_type = DragSourceType::kHTMLSource;
Document& document = context->OwnerDocument();
if (drag_data->ContainsCompatibleContent()) {
if (DocumentFragment* fragment = drag_data->AsFragment(frame))
return fragment;
if (drag_data->ContainsURL(DragData::kDoNotConvertFilenames)) {
String title;
String url = drag_data->AsURL(DragData::kDoNotConvertFilenames, &title);
if (!url.IsEmpty()) {
HTMLAnchorElement* anchor = HTMLAnchorElement::Create(document);
anchor->SetHref(AtomicString(url));
if (title.IsEmpty()) {
// Try the plain text first because the url might be normalized or
// escaped.
if (drag_data->ContainsPlainText())
title = drag_data->AsPlainText();
if (title.IsEmpty())
title = url;
}
Node* anchor_text = document.createTextNode(title);
anchor->AppendChild(anchor_text);
DocumentFragment* fragment = document.createDocumentFragment();
fragment->AppendChild(anchor);
return fragment;
}
}
}
if (allow_plain_text && drag_data->ContainsPlainText()) {
drag_source_type = DragSourceType::kPlainTextSource;
return CreateFragmentFromText(EphemeralRange(context),
drag_data->AsPlainText());
}
return nullptr;
}
bool DragController::DragIsMove(FrameSelection& selection,
DragData* drag_data) {
return document_under_mouse_ == drag_initiator_ &&
selection.SelectionHasFocus() &&
selection.ComputeVisibleSelectionInDOMTreeDeprecated()
.IsContentEditable() &&
selection.ComputeVisibleSelectionInDOMTreeDeprecated().IsRange() &&
!IsCopyKeyDown(drag_data);
}
void DragController::ClearDragCaret() {
page_->GetDragCaret().Clear();
}
void DragController::DragEnded() {
drag_initiator_ = nullptr;
did_initiate_drag_ = false;
page_->GetDragCaret().Clear();
}
void DragController::DragExited(DragData* drag_data, LocalFrame& local_root) {
DCHECK(drag_data);
LocalFrameView* frame_view(local_root.View());
if (frame_view) {
DataTransferAccessPolicy policy = DataTransferAccessPolicy::kTypesReadable;
DataTransfer* data_transfer = CreateDraggingDataTransfer(policy, drag_data);
data_transfer->SetSourceOperation(drag_data->DraggingSourceOperationMask());
local_root.GetEventHandler().CancelDragAndDrop(CreateMouseEvent(drag_data),
data_transfer);
data_transfer->SetAccessPolicy(
DataTransferAccessPolicy::kNumb); // invalidate clipboard here for
// security
}
MouseMovedIntoDocument(nullptr);
if (file_input_element_under_mouse_)
file_input_element_under_mouse_->SetCanReceiveDroppedFiles(false);
file_input_element_under_mouse_ = nullptr;
}
void DragController::PerformDrag(DragData* drag_data, LocalFrame& local_root) {
DCHECK(drag_data);
document_under_mouse_ =
local_root.DocumentAtPoint(LayoutPoint(drag_data->ClientPosition()));
std::unique_ptr<UserGestureIndicator> gesture =
LocalFrame::NotifyUserActivation(
document_under_mouse_ ? document_under_mouse_->GetFrame() : nullptr,
UserGestureToken::kNewGesture);
if ((drag_destination_action_ & kDragDestinationActionDHTML) &&
document_is_handling_drag_) {
bool prevented_default = false;
if (local_root.View()) {
// Sending an event can result in the destruction of the view and part.
DataTransfer* data_transfer = CreateDraggingDataTransfer(
DataTransferAccessPolicy::kReadable, drag_data);
data_transfer->SetSourceOperation(
drag_data->DraggingSourceOperationMask());
EventHandler& event_handler = local_root.GetEventHandler();
prevented_default = event_handler.PerformDragAndDrop(
CreateMouseEvent(drag_data), data_transfer) !=
WebInputEventResult::kNotHandled;
if (!prevented_default && document_under_mouse_) {
// When drop target is plugin element and it can process drag, we
// should prevent default behavior.
const HitTestLocation location(local_root.View()->ConvertFromRootFrame(
LayoutPoint(drag_data->ClientPosition())));
const HitTestResult result =
event_handler.HitTestResultAtLocation(location);
prevented_default |=
IsHTMLPlugInElement(*result.InnerNode()) &&
ToHTMLPlugInElement(result.InnerNode())->CanProcessDrag();
}
// Invalidate clipboard here for security.
data_transfer->SetAccessPolicy(DataTransferAccessPolicy::kNumb);
}
if (prevented_default) {
document_under_mouse_ = nullptr;
ClearDragCaret();
return;
}
}
if ((drag_destination_action_ & kDragDestinationActionEdit) &&
ConcludeEditDrag(drag_data)) {
document_under_mouse_ = nullptr;
return;
}
document_under_mouse_ = nullptr;
if (OperationForLoad(drag_data, local_root) != kDragOperationNone) {
if (page_->GetSettings().GetNavigateOnDragDrop()) {
ResourceRequest resource_request(drag_data->AsURL());
// TODO(mkwst): Perhaps this should use a unique origin as the requestor
// origin rather than the origin of the dragged data URL?
resource_request.SetRequestorOrigin(
SecurityOrigin::Create(KURL(drag_data->AsURL())));
resource_request.SetHasUserGesture(LocalFrame::HasTransientUserActivation(
document_under_mouse_ ? document_under_mouse_->GetFrame() : nullptr));
page_->MainFrame()->Navigate(FrameLoadRequest(nullptr, resource_request),
WebFrameLoadType::kStandard);
}
// TODO(bokan): This case happens when we end a URL drag inside a guest
// process which doesn't navigate. We assume that since we'll navigate the
// page in the general case we don't end up sending `dragleave` and
// `dragend` events but for plugins we wont navigate so it seems we should
// be sending these events. crbug.com/748243.
local_root.GetEventHandler().ClearDragState();
}
}
void DragController::MouseMovedIntoDocument(Document* new_document) {
if (document_under_mouse_ == new_document)
return;
// If we were over another document clear the selection
if (document_under_mouse_)
ClearDragCaret();
document_under_mouse_ = new_document;
}
DragOperation DragController::DragEnteredOrUpdated(DragData* drag_data,
LocalFrame& local_root) {
DCHECK(drag_data);
MouseMovedIntoDocument(
local_root.DocumentAtPoint(LayoutPoint(drag_data->ClientPosition())));
// TODO(esprehn): Replace acceptsLoadDrops with a Setting used in core.
drag_destination_action_ =
page_->GetChromeClient().AcceptsLoadDrops()
? kDragDestinationActionAny
: static_cast<DragDestinationAction>(kDragDestinationActionDHTML |
kDragDestinationActionEdit);
DragOperation drag_operation = kDragOperationNone;
document_is_handling_drag_ = TryDocumentDrag(
drag_data, drag_destination_action_, drag_operation, local_root);
if (!document_is_handling_drag_ &&
(drag_destination_action_ & kDragDestinationActionLoad))
drag_operation = OperationForLoad(drag_data, local_root);
return drag_operation;
}
static HTMLInputElement* AsFileInput(Node* node) {
DCHECK(node);
for (; node; node = node->OwnerShadowHost()) {
if (IsHTMLInputElement(*node) &&
ToHTMLInputElement(node)->type() == input_type_names::kFile)
return ToHTMLInputElement(node);
}
return nullptr;
}
// This can return null if an empty document is loaded.
static Element* ElementUnderMouse(Document* document_under_mouse,
const LayoutPoint& point) {
HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive);
HitTestLocation location(point);
HitTestResult result(request, location);
document_under_mouse->GetLayoutView()->HitTest(location, result);
Node* n = result.InnerNode();
while (n && !n->IsElementNode())
n = n->ParentOrShadowHostNode();
if (n && n->IsInShadowTree())
n = n->OwnerShadowHost();
return ToElement(n);
}
bool DragController::TryDocumentDrag(DragData* drag_data,
DragDestinationAction action_mask,
DragOperation& drag_operation,
LocalFrame& local_root) {
DCHECK(drag_data);
if (!document_under_mouse_)
return false;
if (drag_initiator_ && !document_under_mouse_->GetSecurityOrigin()->CanAccess(
drag_initiator_->GetSecurityOrigin()))
return false;
bool is_handling_drag = false;
if (action_mask & kDragDestinationActionDHTML) {
is_handling_drag = TryDHTMLDrag(drag_data, drag_operation, local_root);
// Do not continue if m_documentUnderMouse has been reset by tryDHTMLDrag.
// tryDHTMLDrag fires dragenter event. The event listener that listens
// to this event may create a nested run loop (open a modal dialog),
// which could process dragleave event and reset m_documentUnderMouse in
// dragExited.
if (!document_under_mouse_)
return false;
}
// It's unclear why this check is after tryDHTMLDrag.
// We send drag events in tryDHTMLDrag and that may be the reason.
LocalFrameView* frame_view = document_under_mouse_->View();
if (!frame_view)
return false;
if (is_handling_drag) {
page_->GetDragCaret().Clear();
return true;
}
if ((action_mask & kDragDestinationActionEdit) &&
CanProcessDrag(drag_data, local_root)) {
LayoutPoint point = frame_view->ConvertFromRootFrame(
LayoutPoint(drag_data->ClientPosition()));
Element* element = ElementUnderMouse(document_under_mouse_.Get(), point);
if (!element)
return false;
HTMLInputElement* element_as_file_input = AsFileInput(element);
if (file_input_element_under_mouse_ != element_as_file_input) {
if (file_input_element_under_mouse_)
file_input_element_under_mouse_->SetCanReceiveDroppedFiles(false);
file_input_element_under_mouse_ = element_as_file_input;
}
if (!file_input_element_under_mouse_) {
page_->GetDragCaret().SetCaretPosition(
document_under_mouse_->GetFrame()->PositionForPoint(point));
}
LocalFrame* inner_frame = element->GetDocument().GetFrame();
drag_operation = DragIsMove(inner_frame->Selection(), drag_data)
? kDragOperationMove
: kDragOperationCopy;
if (file_input_element_under_mouse_) {
bool can_receive_dropped_files = false;
if (!file_input_element_under_mouse_->IsDisabledFormControl()) {
can_receive_dropped_files = file_input_element_under_mouse_->Multiple()
? drag_data->NumberOfFiles() > 0
: drag_data->NumberOfFiles() == 1;
}
if (!can_receive_dropped_files)
drag_operation = kDragOperationNone;
file_input_element_under_mouse_->SetCanReceiveDroppedFiles(
can_receive_dropped_files);
}
return true;
}
// We are not over an editable region. Make sure we're clearing any prior drag
// cursor.
page_->GetDragCaret().Clear();
if (file_input_element_under_mouse_)
file_input_element_under_mouse_->SetCanReceiveDroppedFiles(false);
file_input_element_under_mouse_ = nullptr;
return false;
}
DragOperation DragController::OperationForLoad(DragData* drag_data,
LocalFrame& local_root) {
DCHECK(drag_data);
Document* doc =
local_root.DocumentAtPoint(LayoutPoint(drag_data->ClientPosition()));
if (doc &&
(did_initiate_drag_ || doc->IsPluginDocument() || HasEditableStyle(*doc)))
return kDragOperationNone;
return GetDragOperation(drag_data);
}
// Returns true if node at |point| is editable with populating |dragCaret| and
// |range|, otherwise returns false.
// TODO(yosin): We should return |VisibleSelection| rather than three values.
static bool SetSelectionToDragCaret(LocalFrame* frame,
const SelectionInDOMTree& drag_caret,
Range*& range,
const LayoutPoint& point) {
frame->Selection().SetSelectionAndEndTyping(drag_caret);
// TODO(editing-dev): The use of
// UpdateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. See http://crbug.com/590369 for more details.
frame->GetDocument()->UpdateStyleAndLayoutIgnorePendingStylesheets();
if (!frame->Selection().ComputeVisibleSelectionInDOMTree().IsNone()) {
return frame->Selection()
.ComputeVisibleSelectionInDOMTree()
.IsContentEditable();
}
const PositionWithAffinity& position = frame->PositionForPoint(point);
if (!position.IsConnected())
return false;
frame->Selection().SetSelectionAndEndTyping(
SelectionInDOMTree::Builder().Collapse(position).Build());
// TODO(editing-dev): The use of
// UpdateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. See http://crbug.com/590369 for more details.
frame->GetDocument()->UpdateStyleAndLayoutIgnorePendingStylesheets();
const VisibleSelection& visible_selection =
frame->Selection().ComputeVisibleSelectionInDOMTree();
range = CreateRange(visible_selection.ToNormalizedEphemeralRange());
return !visible_selection.IsNone() && visible_selection.IsContentEditable();
}
DispatchEventResult DragController::DispatchTextInputEventFor(
LocalFrame* inner_frame,
DragData* drag_data) {
// Layout should be clean due to a hit test performed in |elementUnderMouse|.
DCHECK(!inner_frame->GetDocument()->NeedsLayoutTreeUpdate());
DCHECK(page_->GetDragCaret().HasCaret());
String text = page_->GetDragCaret().IsContentRichlyEditable()
? ""
: drag_data->AsPlainText();
const PositionWithAffinity& caret_position =
page_->GetDragCaret().CaretPosition();
DCHECK(caret_position.IsConnected()) << caret_position;
Element* target = FindEventTargetFrom(
*inner_frame,
CreateVisibleSelection(
SelectionInDOMTree::Builder().Collapse(caret_position).Build()));
return target->DispatchEvent(
*TextEvent::CreateForDrop(inner_frame->DomWindow(), text));
}
bool DragController::ConcludeEditDrag(DragData* drag_data) {
DCHECK(drag_data);
HTMLInputElement* file_input = file_input_element_under_mouse_;
if (file_input_element_under_mouse_) {
file_input_element_under_mouse_->SetCanReceiveDroppedFiles(false);
file_input_element_under_mouse_ = nullptr;
}
if (!document_under_mouse_)
return false;
LayoutPoint point = document_under_mouse_->View()->ConvertFromRootFrame(
LayoutPoint(drag_data->ClientPosition()));
Element* element = ElementUnderMouse(document_under_mouse_.Get(), point);
if (!element)
return false;
LocalFrame* inner_frame = element->ownerDocument()->GetFrame();
DCHECK(inner_frame);
if (page_->GetDragCaret().HasCaret() &&
DispatchTextInputEventFor(inner_frame, drag_data) !=
DispatchEventResult::kNotCanceled)
return true;
if (drag_data->ContainsFiles() && file_input) {
// fileInput should be the element we hit tested for, unless it was made
// display:none in a drop event handler.
if (file_input->GetLayoutObject())
DCHECK_EQ(file_input, element);
if (file_input->IsDisabledFormControl())
return false;
return file_input->ReceiveDroppedFiles(drag_data);
}
// TODO(paulmeyer): Isn't |m_page->dragController()| the same as |this|?
if (!page_->GetDragController().CanProcessDrag(
drag_data, inner_frame->LocalFrameRoot())) {
page_->GetDragCaret().Clear();
return false;
}
if (page_->GetDragCaret().HasCaret()) {
// TODO(editing-dev): Use of updateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. See http://crbug.com/590369 for more details.
page_->GetDragCaret()
.CaretPosition()
.GetPosition()
.GetDocument()
->UpdateStyleAndLayoutIgnorePendingStylesheets();
}
const PositionWithAffinity& caret_position =
page_->GetDragCaret().CaretPosition();
if (!caret_position.IsConnected()) {
// "editing/pasteboard/drop-text-events-sideeffect-crash.html" and
// "editing/pasteboard/drop-text-events-sideeffect.html" reach here.
page_->GetDragCaret().Clear();
return false;
}
VisibleSelection drag_caret = CreateVisibleSelection(
SelectionInDOMTree::Builder().Collapse(caret_position).Build());
page_->GetDragCaret().Clear();
// |innerFrame| can be removed by event handler called by
// |dispatchTextInputEventFor()|.
if (!inner_frame->Selection().IsAvailable()) {
// "editing/pasteboard/drop-text-events-sideeffect-crash.html" reaches
// here.
return false;
}
Range* range = CreateRange(drag_caret.ToNormalizedEphemeralRange());
Element* root_editable_element =
inner_frame->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.RootEditableElement();
// For range to be null a WebKit client must have done something bad while
// manually controlling drag behaviour
if (!range)
return false;
ResourceFetcher* fetcher = range->OwnerDocument().Fetcher();
ResourceCacheValidationSuppressor validation_suppressor(fetcher);
// Start new Drag&Drop command group, invalidate previous command group.
// Assume no other places is firing |DeleteByDrag| and |InsertFromDrop|.
inner_frame->GetEditor().RegisterCommandGroup(
DragAndDropCommand::Create(*inner_frame->GetDocument()));
if (DragIsMove(inner_frame->Selection(), drag_data) ||
IsRichlyEditablePosition(drag_caret.Base())) {
DragSourceType drag_source_type = DragSourceType::kHTMLSource;
DocumentFragment* fragment = DocumentFragmentFromDragData(
drag_data, inner_frame, range, true, drag_source_type);
if (!fragment)
return false;
if (DragIsMove(inner_frame->Selection(), drag_data)) {
// NSTextView behavior is to always smart delete on moving a selection,
// but only to smart insert if the selection granularity is word
// granularity.
const DeleteMode delete_mode =
inner_frame->GetEditor().SmartInsertDeleteEnabled()
? DeleteMode::kSmart
: DeleteMode::kSimple;
const InsertMode insert_mode =
(delete_mode == DeleteMode::kSmart &&
inner_frame->Selection().Granularity() == TextGranularity::kWord &&
drag_data->CanSmartReplace())
? InsertMode::kSmart
: InsertMode::kSimple;
if (!inner_frame->GetEditor().DeleteSelectionAfterDraggingWithEvents(
FindEventTargetFrom(
*inner_frame,
inner_frame->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()),
delete_mode, drag_caret.Base()))
return false;
inner_frame->Selection().SetSelectionAndEndTyping(
SelectionInDOMTree::Builder()
.SetBaseAndExtent(EphemeralRange(range))
.Build());
if (inner_frame->Selection().IsAvailable()) {
DCHECK(document_under_mouse_);
if (!inner_frame->GetEditor().ReplaceSelectionAfterDraggingWithEvents(
element, drag_data, fragment, range, insert_mode,
drag_source_type))
return false;
}
} else {
if (SetSelectionToDragCaret(inner_frame, drag_caret.AsSelection(), range,
point)) {
DCHECK(document_under_mouse_);
if (!inner_frame->GetEditor().ReplaceSelectionAfterDraggingWithEvents(
element, drag_data, fragment, range,
drag_data->CanSmartReplace() ? InsertMode::kSmart
: InsertMode::kSimple,
drag_source_type))
return false;
}
}
} else {
String text = drag_data->AsPlainText();
if (text.IsEmpty())
return false;
if (SetSelectionToDragCaret(inner_frame, drag_caret.AsSelection(), range,
point)) {
DCHECK(document_under_mouse_);
if (!inner_frame->GetEditor().ReplaceSelectionAfterDraggingWithEvents(
element, drag_data,
CreateFragmentFromText(EphemeralRange(range), text), range,
InsertMode::kSimple, DragSourceType::kPlainTextSource))
return false;
}
}
if (root_editable_element) {
if (LocalFrame* frame = root_editable_element->GetDocument().GetFrame()) {
frame->GetEventHandler().UpdateDragStateAfterEditDragIfNeeded(
root_editable_element);
}
}
return true;
}
bool DragController::CanProcessDrag(DragData* drag_data,
LocalFrame& local_root) {
DCHECK(drag_data);
if (!drag_data->ContainsCompatibleContent())
return false;
if (!local_root.ContentLayoutObject())
return false;
LayoutPoint point = local_root.View()->ConvertFromRootFrame(
LayoutPoint(drag_data->ClientPosition()));
HitTestLocation location(point);
HitTestResult result =
local_root.GetEventHandler().HitTestResultAtLocation(location);
if (!result.InnerNode())
return false;
if (drag_data->ContainsFiles() && AsFileInput(result.InnerNode()))
return true;
if (IsHTMLPlugInElement(*result.InnerNode())) {
HTMLPlugInElement* plugin = ToHTMLPlugInElement(result.InnerNode());
if (!plugin->CanProcessDrag() && !HasEditableStyle(*result.InnerNode()))
return false;
} else if (!HasEditableStyle(*result.InnerNode())) {
return false;
}
if (did_initiate_drag_ && document_under_mouse_ == drag_initiator_ &&
result.IsSelected(location))
return false;
return true;
}
static DragOperation DefaultOperationForDrag(DragOperation src_op_mask) {
// This is designed to match IE's operation fallback for the case where
// the page calls preventDefault() in a drag event but doesn't set dropEffect.
if (src_op_mask == kDragOperationEvery)
return kDragOperationCopy;
if (src_op_mask == kDragOperationNone)
return kDragOperationNone;
if (src_op_mask & kDragOperationMove)
return kDragOperationMove;
if (src_op_mask & kDragOperationCopy)
return kDragOperationCopy;
if (src_op_mask & kDragOperationLink)
return kDragOperationLink;
// FIXME: Does IE really return "generic" even if no operations were allowed
// by the source?
return kDragOperationGeneric;
}
bool DragController::TryDHTMLDrag(DragData* drag_data,
DragOperation& operation,
LocalFrame& local_root) {
DCHECK(drag_data);
DCHECK(document_under_mouse_);
if (!local_root.View())
return false;
DataTransferAccessPolicy policy = DataTransferAccessPolicy::kTypesReadable;
DataTransfer* data_transfer = CreateDraggingDataTransfer(policy, drag_data);
DragOperation src_op_mask = drag_data->DraggingSourceOperationMask();
data_transfer->SetSourceOperation(src_op_mask);
WebMouseEvent event = CreateMouseEvent(drag_data);
if (local_root.GetEventHandler().UpdateDragAndDrop(event, data_transfer) ==
WebInputEventResult::kNotHandled) {
data_transfer->SetAccessPolicy(
DataTransferAccessPolicy::kNumb); // invalidate clipboard here for
// security
return false;
}
operation = data_transfer->DestinationOperation();
if (data_transfer->DropEffectIsUninitialized()) {
operation = DefaultOperationForDrag(src_op_mask);
} else if (!(src_op_mask & operation)) {
// The element picked an operation which is not supported by the source
operation = kDragOperationNone;
}
data_transfer->SetAccessPolicy(
DataTransferAccessPolicy::kNumb); // invalidate clipboard here for
// security
return true;
}
bool SelectTextInsteadOfDrag(const Node& node) {
if (!node.IsTextNode())
return false;
// Editable elements loose their draggability,
// see https://github.com/whatwg/html/issues/3114.
if (HasEditableStyle(node))
return true;
for (Node& node : NodeTraversal::InclusiveAncestorsOf(node)) {
if (node.IsHTMLElement() && ToHTMLElement(&node)->draggable())
return false;
}
return node.CanStartSelection();
}
Node* DragController::DraggableNode(const LocalFrame* src,
Node* start_node,
const IntPoint& drag_origin,
SelectionDragPolicy selection_drag_policy,
DragSourceAction& drag_type) const {
if (src->Selection().Contains(drag_origin)) {
drag_type = kDragSourceActionSelection;
if (selection_drag_policy == kImmediateSelectionDragResolution)
return start_node;
} else {
drag_type = kDragSourceActionNone;
}
Node* node = nullptr;
DragSourceAction candidate_drag_type = kDragSourceActionNone;
for (const LayoutObject* layout_object = start_node->GetLayoutObject();
layout_object; layout_object = layout_object->Parent()) {
node = layout_object->NonPseudoNode();
if (!node) {
// Anonymous layout blocks don't correspond to actual DOM nodes, so we
// skip over them for the purposes of finding a draggable node.
continue;
}
if (drag_type != kDragSourceActionSelection &&
SelectTextInsteadOfDrag(*node)) {
// We have a click in an unselected, selectable text that is not
// draggable... so we want to start the selection process instead
// of looking for a parent to try to drag.
return nullptr;
}
if (node->IsElementNode()) {
EUserDrag drag_mode = layout_object->Style()->UserDrag();
if (drag_mode == EUserDrag::kNone)
continue;
// Even if the image is part of a selection, we always only drag the image
// in this case.
if (layout_object->IsImage() && src->GetSettings() &&
src->GetSettings()->GetLoadsImagesAutomatically()) {
drag_type = kDragSourceActionImage;
return node;
}
// Other draggable elements are considered unselectable.
if (drag_mode == EUserDrag::kElement) {
candidate_drag_type = kDragSourceActionDHTML;
break;
}
if (IsHTMLAnchorElement(*node) &&
ToHTMLAnchorElement(node)->IsLiveLink()) {
candidate_drag_type = kDragSourceActionLink;
break;
}
}
}
if (candidate_drag_type == kDragSourceActionNone) {
// Either:
// 1) Nothing under the cursor is considered draggable, so we bail out.
// 2) There was a selection under the cursor but selectionDragPolicy is set
// to DelayedSelectionDragResolution and no other draggable element could
// be found, so bail out and allow text selection to start at the cursor
// instead.
return nullptr;
}
DCHECK(node);
if (drag_type == kDragSourceActionSelection) {
// Dragging unselectable elements in a selection has special behavior if
// selectionDragPolicy is DelayedSelectionDragResolution and this drag was
// flagged as a potential selection drag. In that case, don't allow
// selection and just drag the entire selection instead.
DCHECK_EQ(selection_drag_policy, kDelayedSelectionDragResolution);
node = start_node;
} else {
// If the cursor isn't over a selection, then just drag the node we found
// earlier.
DCHECK_EQ(drag_type, kDragSourceActionNone);
drag_type = candidate_drag_type;
}
return node;
}
static ImageResourceContent* GetImageResource(Element* element) {
DCHECK(element);
LayoutObject* layout_object = element->GetLayoutObject();
if (!layout_object || !layout_object->IsImage())
return nullptr;
LayoutImage* image = ToLayoutImage(layout_object);
return image->CachedImage();
}
static Image* GetImage(Element* element) {
DCHECK(element);
ImageResourceContent* cached_image = GetImageResource(element);
return (cached_image && !cached_image->ErrorOccurred())
? cached_image->GetImage()
: nullptr;
}
static void PrepareDataTransferForImageDrag(LocalFrame* source,
DataTransfer* data_transfer,
Element* node,
const KURL& link_url,
const KURL& image_url,
const String& label) {
node->GetDocument().UpdateStyleAndLayoutTree();
if (HasRichlyEditableStyle(*node)) {
// TODO(editing-dev): We should use |EphemeralRange| instead of |Range|.
Range* range = source->GetDocument()->createRange();
range->selectNode(node, ASSERT_NO_EXCEPTION);
source->Selection().SetSelectionAndEndTyping(
SelectionInDOMTree::Builder()
.SetBaseAndExtent(EphemeralRange(range))
.Build());
}
data_transfer->DeclareAndWriteDragImage(node, link_url, image_url, label);
}
bool DragController::PopulateDragDataTransfer(LocalFrame* src,
const DragState& state,
const IntPoint& drag_origin) {
#if DCHECK_IS_ON()
DCHECK(DragTypeIsValid(state.drag_type_));
#endif
DCHECK(src);
if (!src->View() || !src->ContentLayoutObject())
return false;
HitTestLocation location(drag_origin);
HitTestResult hit_test_result =
src->GetEventHandler().HitTestResultAtLocation(location);
// FIXME: Can this even happen? I guess it's possible, but should verify
// with a web test.
if (!state.drag_src_->IsShadowIncludingInclusiveAncestorOf(
hit_test_result.InnerNode())) {
// The original node being dragged isn't under the drag origin anymore...
// maybe it was hidden or moved out from under the cursor. Regardless, we
// don't want to start a drag on something that's not actually under the
// drag origin.
return false;
}
const KURL& link_url = hit_test_result.AbsoluteLinkURL();
const KURL& image_url = hit_test_result.AbsoluteImageURL();
DataTransfer* data_transfer = state.drag_data_transfer_.Get();
Node* node = state.drag_src_.Get();
if (IsHTMLAnchorElement(*node) && ToHTMLAnchorElement(node)->IsLiveLink() &&
!link_url.IsEmpty()) {
// Simplify whitespace so the title put on the clipboard resembles what
// the user sees on the web page. This includes replacing newlines with
// spaces.
data_transfer->WriteURL(node, link_url,
hit_test_result.TextContent().SimplifyWhiteSpace());
}
if (state.drag_type_ == kDragSourceActionSelection) {
data_transfer->WriteSelection(src->Selection());
} else if (state.drag_type_ == kDragSourceActionImage) {
if (image_url.IsEmpty() || !node || !node->IsElementNode())
return false;
Element* element = ToElement(node);
PrepareDataTransferForImageDrag(src, data_transfer, element, link_url,
image_url,
hit_test_result.AltDisplayString());
} else if (state.drag_type_ == kDragSourceActionLink) {
if (link_url.IsEmpty())
return false;
} else if (state.drag_type_ == kDragSourceActionDHTML) {
LayoutObject* layout_object = node->GetLayoutObject();
if (!layout_object) {
// The layoutObject has disappeared, this can happen if the onStartDrag
// handler has hidden the element in some way. In this case we just kill
// the drag.
return false;
}
IntRect bounding_including_descendants =
layout_object->AbsoluteBoundingBoxRectIncludingDescendants();
IntSize delta = drag_origin - bounding_including_descendants.Location();
data_transfer->SetDragImageElement(node, IntPoint(delta));
// FIXME: For DHTML/draggable element drags, write element markup to
// clipboard.
}
return true;
}
static IntPoint DragLocationForDHTMLDrag(const IntPoint& mouse_dragged_point,
const IntPoint& drag_origin,
const IntPoint& drag_image_offset,
bool is_link_image) {
// dragImageOffset is the cursor position relative to the lower-left corner of
// the image.
const int y_offset = -drag_image_offset.Y();
if (is_link_image) {
return IntPoint(mouse_dragged_point.X() - drag_image_offset.X(),
mouse_dragged_point.Y() + y_offset);
}
return IntPoint(drag_origin.X() - drag_image_offset.X(),
drag_origin.Y() + y_offset);
}
FloatRect DragController::ClippedSelection(const LocalFrame& frame) {
DCHECK(frame.View());
return DataTransfer::ClipByVisualViewport(
FloatRect(frame.Selection().AbsoluteUnclippedBounds()), frame);
}
static IntPoint DragLocationForSelectionDrag(const LocalFrame& frame) {
IntRect dragging_rect =
EnclosingIntRect(DragController::ClippedSelection(frame));
int xpos = dragging_rect.MaxX();
xpos = dragging_rect.X() < xpos ? dragging_rect.X() : xpos;
int ypos = dragging_rect.MaxY();
ypos = dragging_rect.Y() < ypos ? dragging_rect.Y() : ypos;
return IntPoint(xpos, ypos);
}
static const IntSize MaxDragImageSize(float device_scale_factor) {
#if defined(OS_MACOSX)
// Match Safari's drag image size.
static const IntSize kMaxDragImageSize(400, 400);
#else
static const IntSize kMaxDragImageSize(200, 200);
#endif
IntSize max_size_in_pixels = kMaxDragImageSize;
max_size_in_pixels.Scale(device_scale_factor);
return max_size_in_pixels;
}
static std::unique_ptr<DragImage> DragImageForImage(
Element* element,
Image* image,
float device_scale_factor,
const IntPoint& drag_origin,
const IntPoint& image_element_location,
const IntSize& image_element_size_in_pixels,
IntPoint& drag_location) {
std::unique_ptr<DragImage> drag_image;
IntPoint origin;
// Substitute an appropriately-sized SVGImageForContainer, to ensure dragged
// SVG images scale seamlessly.
scoped_refptr<SVGImageForContainer> svg_image;
if (image->IsSVGImage()) {
KURL url = element->GetDocument().CompleteURL(element->ImageSourceURL());
svg_image = SVGImageForContainer::Create(
ToSVGImage(image), FloatSize(image_element_size_in_pixels), 1, url);
image = svg_image.get();
}
InterpolationQuality interpolation_quality = kInterpolationDefault;
if (const ComputedStyle* style = element->GetComputedStyle()) {
if (style->ImageRendering() == EImageRendering::kPixelated)
interpolation_quality = kInterpolationNone;
}
RespectImageOrientationEnum should_respect_image_orientation =
LayoutObject::ShouldRespectImageOrientation(element->GetLayoutObject());
ImageOrientation orientation;
if (should_respect_image_orientation == kRespectImageOrientation &&
image->IsBitmapImage())
orientation = ToBitmapImage(image)->CurrentFrameOrientation();
IntSize image_size = orientation.UsesWidthAsHeight()
? image->Size().TransposedSize()
: image->Size();
FloatSize image_scale =
DragImage::ClampedImageScale(image_size, image_element_size_in_pixels,
MaxDragImageSize(device_scale_factor));
if (image_size.Area() <= kMaxOriginalImageArea &&
(drag_image = DragImage::Create(
image, should_respect_image_orientation, device_scale_factor,
interpolation_quality, kDragImageAlpha, image_scale))) {
IntSize original_size = image_element_size_in_pixels;
origin = image_element_location;
IntSize new_size = drag_image->Size();
// Properly orient the drag image and orient it differently if it's smaller
// than the original
float scale = new_size.Width() / (float)original_size.Width();
float dx = origin.X() - drag_origin.X();
dx *= scale;
origin.SetX((int)(dx + 0.5));
float dy = origin.Y() - drag_origin.Y();
dy *= scale;
origin.SetY((int)(dy + 0.5));
}
drag_location = drag_origin + origin;
return drag_image;
}
static std::unique_ptr<DragImage> DragImageForLink(const KURL& link_url,
const String& link_text,
float device_scale_factor) {
FontDescription font_description;
LayoutTheme::GetTheme().SystemFont(blink::CSSValueNone, font_description);
return DragImage::Create(link_url, link_text, font_description,
device_scale_factor);
}
static IntPoint DragLocationForLink(const DragImage* link_image,
const IntPoint& origin,
float device_scale_factor,
float page_scale_factor) {
if (!link_image)
return origin;
// Offset the image so that the cursor is horizontally centered.
FloatPoint image_offset(-link_image->Size().Width() / 2.f,
-kLinkDragBorderInset);
// |origin| is in the coordinate space of the frame's contents whereas the
// size of |link_image| is in physical pixels. Adjust the image offset to be
// scaled in the frame's contents.
// TODO(pdr): Unify this calculation with the DragImageForImage scaling code.
float scale = 1.f / (device_scale_factor * page_scale_factor);
image_offset.Scale(scale, scale);
image_offset.MoveBy(origin);
return RoundedIntPoint(image_offset);
}
// static
std::unique_ptr<DragImage> DragController::DragImageForSelection(
const LocalFrame& frame,
float opacity) {
if (!frame.Selection().ComputeVisibleSelectionInDOMTreeDeprecated().IsRange())
return nullptr;
frame.View()->UpdateAllLifecyclePhasesExceptPaint();
DCHECK(frame.GetDocument()->IsActive());
FloatRect painting_rect = ClippedSelection(frame);
GlobalPaintFlags paint_flags =
kGlobalPaintSelectionOnly | kGlobalPaintFlattenCompositingLayers;
PaintRecordBuilder builder;
frame.View()->PaintContentsOutsideOfLifecycle(
builder.Context(), paint_flags,
CullRect(EnclosingIntRect(painting_rect)));
PropertyTreeState property_tree_state =
frame.View()->GetLayoutView()->FirstFragment().LocalBorderBoxProperties();
return DataTransfer::CreateDragImageForFrame(
frame, opacity, kDoNotRespectImageOrientation, painting_rect.Size(),
painting_rect.Location(), builder, property_tree_state);
}
bool DragController::StartDrag(LocalFrame* src,
const DragState& state,
const WebMouseEvent& drag_event,
const IntPoint& drag_origin) {
#if DCHECK_IS_ON()
DCHECK(DragTypeIsValid(state.drag_type_));
#endif
DCHECK(src);
if (!src->View() || !src->ContentLayoutObject())
return false;
HitTestLocation location(drag_origin);
HitTestResult hit_test_result =
src->GetEventHandler().HitTestResultAtLocation(location);
if (!state.drag_src_->IsShadowIncludingInclusiveAncestorOf(
hit_test_result.InnerNode())) {
// The original node being dragged isn't under the drag origin anymore...
// maybe it was hidden or moved out from under the cursor. Regardless, we
// don't want to start a drag on something that's not actually under the
// drag origin.
return false;
}
const KURL& link_url = hit_test_result.AbsoluteLinkURL();
const KURL& image_url = hit_test_result.AbsoluteImageURL();
// TODO(pdr): This code shouldn't be necessary because drag_origin is already
// in the coordinate space of the view's contents.
IntPoint mouse_dragged_point = src->View()->ConvertFromRootFrame(
FlooredIntPoint(drag_event.PositionInRootFrame()));
IntPoint drag_location;
IntPoint drag_offset;
DataTransfer* data_transfer = state.drag_data_transfer_.Get();
// We allow DHTML/JS to set the drag image, even if its a link, image or text
// we're dragging. This is in the spirit of the IE API, which allows
// overriding of pasteboard data and DragOp.
std::unique_ptr<DragImage> drag_image =
data_transfer->CreateDragImage(drag_offset, src);
if (drag_image) {
drag_location = DragLocationForDHTMLDrag(mouse_dragged_point, drag_origin,
drag_offset, !link_url.IsEmpty());
}
Node* node = state.drag_src_.Get();
if (state.drag_type_ == kDragSourceActionSelection) {
if (!drag_image) {
drag_image = DragImageForSelection(*src, kDragImageAlpha);
drag_location = DragLocationForSelectionDrag(*src);
}
DoSystemDrag(drag_image.get(), drag_location, drag_origin, data_transfer,
src, false);
} else if (state.drag_type_ == kDragSourceActionImage) {
if (image_url.IsEmpty() || !node || !node->IsElementNode())
return false;
Element* element = ToElement(node);
Image* image = GetImage(element);
if (!image || image->IsNull() || !image->Data() || !image->Data()->size())
return false;
// We shouldn't be starting a drag for an image that can't provide an
// extension.
// This is an early detection for problems encountered later upon drop.
DCHECK(!image->FilenameExtension().IsEmpty());
if (!drag_image) {
const IntRect& image_rect = hit_test_result.ImageRect();
IntSize image_size_in_pixels = image_rect.Size();
// TODO(oshima): Remove this scaling and simply pass imageRect to
// dragImageForImage once all platforms are migrated to use zoom for dsf.
image_size_in_pixels.Scale(src->GetPage()->DeviceScaleFactorDeprecated() *
src->GetPage()->GetVisualViewport().Scale());
float screen_device_scale_factor =
src->GetPage()->GetChromeClient().GetScreenInfo().device_scale_factor;
// Pass the selected image size in DIP becasue dragImageForImage clips the
// image in DIP. The coordinates of the locations are in Viewport
// coordinates, and they're converted in the Blink client.
// TODO(oshima): Currently, the dragged image on high DPI is scaled and
// can be blurry because of this. Consider to clip in the screen
// coordinates to use high resolution image on high DPI screens.
drag_image = DragImageForImage(element, image, screen_device_scale_factor,
drag_origin, image_rect.Location(),
image_size_in_pixels, drag_location);
}
DoSystemDrag(drag_image.get(), drag_location, drag_origin, data_transfer,
src, false);
} else if (state.drag_type_ == kDragSourceActionLink) {
if (link_url.IsEmpty())
return false;
if (src->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.IsCaret() &&
src->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.IsContentEditable()) {
// a user can initiate a drag on a link without having any text
// selected. In this case, we should expand the selection to
// the enclosing anchor element
if (Node* node = EnclosingAnchorElement(
src->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated()
.Base())) {
src->Selection().SetSelectionAndEndTyping(
SelectionInDOMTree::Builder().SelectAllChildren(*node).Build());
}
}
if (!drag_image) {
DCHECK(src->GetPage());
float screen_device_scale_factor =
src->GetPage()->GetChromeClient().GetScreenInfo().device_scale_factor;
drag_image = DragImageForLink(link_url, hit_test_result.TextContent(),
screen_device_scale_factor);
drag_location = DragLocationForLink(drag_image.get(), mouse_dragged_point,
screen_device_scale_factor,
src->GetPage()->PageScaleFactor());
}
DoSystemDrag(drag_image.get(), drag_location, mouse_dragged_point,
data_transfer, src, true);
} else if (state.drag_type_ == kDragSourceActionDHTML) {
DoSystemDrag(drag_image.get(), drag_location, drag_origin, data_transfer,
src, false);
} else {
NOTREACHED();
return false;
}
return true;
}
// TODO(esprehn): forLink is dead code, what was it for?
void DragController::DoSystemDrag(DragImage* image,
const IntPoint& drag_location,
const IntPoint& event_pos,
DataTransfer* data_transfer,
LocalFrame* frame,
bool for_link) {
did_initiate_drag_ = true;
drag_initiator_ = frame->GetDocument();
// TODO(pdr): |drag_location| and |event_pos| should be passed in as
// FloatPoints and we should calculate these adjusted values in floating
// point to avoid unnecessary rounding.
IntPoint adjusted_drag_location =
frame->View()->FrameToViewport(drag_location);
IntPoint adjusted_event_pos = frame->View()->FrameToViewport(event_pos);
IntSize offset_size(adjusted_event_pos - adjusted_drag_location);
gfx::Point offset_point(offset_size.Width(), offset_size.Height());
WebDragData drag_data = data_transfer->GetDataObject()->ToWebDragData();
WebDragOperationsMask drag_operation_mask =
static_cast<WebDragOperationsMask>(data_transfer->SourceOperation());
SkBitmap drag_image;
if (image) {
float resolution_scale = image->ResolutionScale();
float device_scale_factor =
page_->GetChromeClient().GetScreenInfo().device_scale_factor;
if (device_scale_factor != resolution_scale) {
DCHECK_GT(resolution_scale, 0);
float scale = device_scale_factor / resolution_scale;
image->Scale(scale, scale);
}
drag_image = image->Bitmap();
}
page_->GetChromeClient().StartDragging(frame, drag_data, drag_operation_mask,
std::move(drag_image), offset_point);
}
DragOperation DragController::GetDragOperation(DragData* drag_data) {
// FIXME: To match the MacOS behaviour we should return DragOperationNone
// if we are a modal window, we are the drag source, or the window is an
// attached sheet If this can be determined from within WebCore
// operationForDrag can be pulled into WebCore itself
DCHECK(drag_data);
return drag_data->ContainsURL() && !did_initiate_drag_ ? kDragOperationCopy
: kDragOperationNone;
}
bool DragController::IsCopyKeyDown(DragData* drag_data) {
int modifiers = drag_data->GetModifiers();
#if defined(OS_MACOSX)
return modifiers & WebInputEvent::kAltKey;
#else
return modifiers & WebInputEvent::kControlKey;
#endif
}
DragState& DragController::GetDragState() {
if (!drag_state_)
drag_state_ = MakeGarbageCollected<DragState>();
return *drag_state_;
}
void DragController::Trace(blink::Visitor* visitor) {
visitor->Trace(page_);
visitor->Trace(document_under_mouse_);
visitor->Trace(drag_initiator_);
visitor->Trace(drag_state_);
visitor->Trace(file_input_element_under_mouse_);
}
} // namespace blink