| /* |
| * 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 |