1 /* 2 * Copyright (C) 2007-2016 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26 #include "config.h" 27 #include "DragController.h" 28 29 #include "HTMLAnchorElement.h" 30 #include "SVGAElement.h" 31 32 #if ENABLE(DRAG_SUPPORT) 33 #include "CachedImage.h" 34 #include "CachedResourceLoader.h" 35 #include "ClientRect.h" 36 #include "DataTransfer.h" 37 #include "Document.h" 38 #include "DocumentFragment.h" 39 #include "DragActions.h" 40 #include "DragClient.h" 41 #include "DragData.h" 42 #include "DragImage.h" 43 #include "DragState.h" 44 #include "Editor.h" 45 #include "EditorClient.h" 46 #include "EventHandler.h" 47 #include "FloatRect.h" 48 #include "FrameLoadRequest.h" 49 #include "FrameLoader.h" 50 #include "FrameSelection.h" 51 #include "FrameView.h" 52 #include "HTMLAttachmentElement.h" 53 #include "HTMLImageElement.h" 54 #include "HTMLInputElement.h" 55 #include "HTMLPlugInElement.h" 56 #include "HitTestRequest.h" 57 #include "HitTestResult.h" 58 #include "Image.h" 59 #include "ImageOrientation.h" 60 #include "MainFrame.h" 61 #include "MoveSelectionCommand.h" 62 #include "Page.h" 63 #include "Pasteboard.h" 64 #include "PlatformKeyboardEvent.h" 65 #include "PluginDocument.h" 66 #include "PluginViewBase.h" 67 #include "RenderFileUploadControl.h" 68 #include "RenderImage.h" 69 #include "RenderView.h" 70 #include "ReplaceSelectionCommand.h" 71 #include "ResourceRequest.h" 72 #include "SecurityOrigin.h" 73 #include "Settings.h" 74 #include "ShadowRoot.h" 75 #include "StyleProperties.h" 76 #include "Text.h" 77 #include "TextEvent.h" 78 #include "htmlediting.h" 79 #include "markup.h" 80 81 #if ENABLE(DATA_INTERACTION) 82 #include "SelectionRect.h" 83 #endif 84 85 #include <wtf/CurrentTime.h> 86 #include <wtf/RefPtr.h> 87 #endif 88 89 namespace WebCore { 90 91 bool isDraggableLink(const Element& element) 92 { 93 if (is<HTMLAnchorElement>(element)) 94 return downcast<HTMLAnchorElement>(element).isLiveLink(); 95 if (is<SVGAElement>(element)) 96 return element.isLink(); 97 return false; 98 } 99 100 #if ENABLE(DRAG_SUPPORT) 101 102 static PlatformMouseEvent createMouseEvent(const DragData& dragData) 103 { 104 bool shiftKey = false; 105 bool ctrlKey = false; 106 bool altKey = false; 107 bool metaKey = false; 108 109 PlatformKeyboardEvent::getCurrentModifierState(shiftKey, ctrlKey, altKey, metaKey); 110 111 return PlatformMouseEvent(dragData.clientPosition(), dragData.globalPosition(), 112 LeftButton, PlatformEvent::MouseMoved, 0, shiftKey, ctrlKey, altKey, 113 metaKey, currentTime(), ForceAtClick, NoTap); 114 } 115 116 DragController::DragController(Page& page, DragClient& client) 117 : m_page(page) 118 , m_client(client) 119 , m_numberOfItemsToBeAccepted(0) 120 , m_documentIsHandlingDrag(false) 121 , m_dragDestinationAction(DragDestinationActionNone) 122 , m_dragSourceAction(DragSourceActionNone) 123 , m_didInitiateDrag(false) 124 , m_sourceDragOperation(DragOperationNone) 125 { 126 } 127 128 DragController::~DragController() 129 { 130 m_client.dragControllerDestroyed(); 131 } 132 133 static RefPtr<DocumentFragment> documentFragmentFromDragData(const DragData& dragData, Frame& frame, Range& context, bool allowPlainText, bool& chosePlainText) 134 { 135 chosePlainText = false; 136 137 Document& document = context.ownerDocument(); 138 if (dragData.containsCompatibleContent()) { 139 if (auto fragment = frame.editor().webContentFromPasteboard(*Pasteboard::createForDragAndDrop(dragData), context, allowPlainText, chosePlainText)) 140 return fragment; 141 142 if (dragData.containsURL(DragData::DoNotConvertFilenames)) { 143 String title; 144 String url = dragData.asURL(DragData::DoNotConvertFilenames, &title); 145 if (!url.isEmpty()) { 146 auto anchor = HTMLAnchorElement::create(document); 147 anchor->setHref(url); 148 if (title.isEmpty()) { 149 // Try the plain text first because the url might be normalized or escaped. 150 if (dragData.containsPlainText()) 151 title = dragData.asPlainText(); 152 if (title.isEmpty()) 153 title = url; 154 } 155 anchor->appendChild(document.createTextNode(title)); 156 auto fragment = document.createDocumentFragment(); 157 fragment->appendChild(anchor); 158 return WTFMove(fragment); 159 } 160 } 161 } 162 if (allowPlainText && dragData.containsPlainText()) { 163 chosePlainText = true; 164 return createFragmentFromText(context, dragData.asPlainText()).ptr(); 165 } 166 167 return nullptr; 168 } 169 170 bool DragController::dragIsMove(FrameSelection& selection, const DragData& dragData) 171 { 172 const VisibleSelection& visibleSelection = selection.selection(); 173 return m_documentUnderMouse == m_dragInitiator && visibleSelection.isContentEditable() && visibleSelection.isRange() && !isCopyKeyDown(dragData); 174 } 175 176 void DragController::clearDragCaret() 177 { 178 m_page.dragCaretController().clear(); 179 } 180 181 void DragController::dragEnded() 182 { 183 m_dragInitiator = nullptr; 184 m_didInitiateDrag = false; 185 clearDragCaret(); 186 187 m_client.dragEnded(); 188 } 189 190 DragOperation DragController::dragEntered(const DragData& dragData) 191 { 192 return dragEnteredOrUpdated(dragData); 193 } 194 195 void DragController::dragExited(const DragData& dragData) 196 { 197 if (RefPtr<FrameView> v = m_page.mainFrame().view()) { 198 #if ENABLE(DASHBOARD_SUPPORT) 199 DataTransferAccessPolicy policy = (m_page.mainFrame().settings().usesDashboardBackwardCompatibilityMode() && (!m_documentUnderMouse || m_documentUnderMouse->securityOrigin().isLocal())) 200 ? DataTransferAccessPolicy::Readable : DataTransferAccessPolicy::TypesReadable; 201 #else 202 DataTransferAccessPolicy policy = DataTransferAccessPolicy::TypesReadable; 203 #endif 204 auto dataTransfer = DataTransfer::createForDrop(policy, dragData); 205 dataTransfer->setSourceOperation(dragData.draggingSourceOperationMask()); 206 m_page.mainFrame().eventHandler().cancelDragAndDrop(createMouseEvent(dragData), dataTransfer); 207 dataTransfer->setAccessPolicy(DataTransferAccessPolicy::Numb); // Invalidate dataTransfer here for security. 208 } 209 mouseMovedIntoDocument(nullptr); 210 if (m_fileInputElementUnderMouse) 211 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false); 212 m_fileInputElementUnderMouse = nullptr; 213 } 214 215 DragOperation DragController::dragUpdated(const DragData& dragData) 216 { 217 return dragEnteredOrUpdated(dragData); 218 } 219 220 bool DragController::performDragOperation(const DragData& dragData) 221 { 222 m_documentUnderMouse = m_page.mainFrame().documentAtPoint(dragData.clientPosition()); 223 224 ShouldOpenExternalURLsPolicy shouldOpenExternalURLsPolicy = ShouldOpenExternalURLsPolicy::ShouldNotAllow; 225 if (m_documentUnderMouse) 226 shouldOpenExternalURLsPolicy = m_documentUnderMouse->shouldOpenExternalURLsPolicyToPropagate(); 227 228 if ((m_dragDestinationAction & DragDestinationActionDHTML) && m_documentIsHandlingDrag) { 229 m_client.willPerformDragDestinationAction(DragDestinationActionDHTML, dragData); 230 Ref<MainFrame> mainFrame(m_page.mainFrame()); 231 bool preventedDefault = false; 232 if (mainFrame->view()) { 233 // Sending an event can result in the destruction of the view and part. 234 auto dataTransfer = DataTransfer::createForDrop(DataTransferAccessPolicy::Readable, dragData); 235 dataTransfer->setSourceOperation(dragData.draggingSourceOperationMask()); 236 preventedDefault = mainFrame->eventHandler().performDragAndDrop(createMouseEvent(dragData), dataTransfer); 237 dataTransfer->setAccessPolicy(DataTransferAccessPolicy::Numb); // Invalidate dataTransfer here for security. 238 } 239 if (preventedDefault) { 240 clearDragCaret(); 241 m_documentUnderMouse = nullptr; 242 return true; 243 } 244 } 245 246 if ((m_dragDestinationAction & DragDestinationActionEdit) && concludeEditDrag(dragData)) { 247 m_documentUnderMouse = nullptr; 248 return true; 249 } 250 251 m_documentUnderMouse = nullptr; 252 253 if (operationForLoad(dragData) == DragOperationNone) 254 return false; 255 256 m_client.willPerformDragDestinationAction(DragDestinationActionLoad, dragData); 257 m_page.mainFrame().loader().load(FrameLoadRequest(&m_page.mainFrame(), ResourceRequest(dragData.asURL()), shouldOpenExternalURLsPolicy)); 258 return true; 259 } 260 261 void DragController::mouseMovedIntoDocument(Document* newDocument) 262 { 263 if (m_documentUnderMouse == newDocument) 264 return; 265 266 // If we were over another document clear the selection 267 if (m_documentUnderMouse) 268 clearDragCaret(); 269 m_documentUnderMouse = newDocument; 270 } 271 272 DragOperation DragController::dragEnteredOrUpdated(const DragData& dragData) 273 { 274 mouseMovedIntoDocument(m_page.mainFrame().documentAtPoint(dragData.clientPosition())); 275 276 m_dragDestinationAction = m_client.actionMaskForDrag(dragData); 277 if (m_dragDestinationAction == DragDestinationActionNone) { 278 clearDragCaret(); // FIXME: Why not call mouseMovedIntoDocument(nullptr)? 279 return DragOperationNone; 280 } 281 282 DragOperation dragOperation = DragOperationNone; 283 m_documentIsHandlingDrag = tryDocumentDrag(dragData, m_dragDestinationAction, dragOperation); 284 if (!m_documentIsHandlingDrag && (m_dragDestinationAction & DragDestinationActionLoad)) 285 dragOperation = operationForLoad(dragData); 286 return dragOperation; 287 } 288 289 static HTMLInputElement* asFileInput(Node& node) 290 { 291 if (!is<HTMLInputElement>(node)) 292 return nullptr; 293 294 auto* inputElement = &downcast<HTMLInputElement>(node); 295 296 // If this is a button inside of the a file input, move up to the file input. 297 if (inputElement->isTextButton() && is<ShadowRoot>(inputElement->treeScope().rootNode())) { 298 auto& host = *downcast<ShadowRoot>(inputElement->treeScope().rootNode()).host(); 299 inputElement = is<HTMLInputElement>(host) ? &downcast<HTMLInputElement>(host) : nullptr; 300 } 301 302 return inputElement && inputElement->isFileUpload() ? inputElement : nullptr; 303 } 304 305 // This can return null if an empty document is loaded. 306 static Element* elementUnderMouse(Document* documentUnderMouse, const IntPoint& p) 307 { 308 Frame* frame = documentUnderMouse->frame(); 309 float zoomFactor = frame ? frame->pageZoomFactor() : 1; 310 LayoutPoint point(p.x() * zoomFactor, p.y() * zoomFactor); 311 312 HitTestResult result(point); 313 documentUnderMouse->renderView()->hitTest(HitTestRequest(), result); 314 315 Node* node = result.innerNode(); 316 while (node && !is<Element>(*node)) 317 node = node->parentNode(); 318 if (node) 319 node = node->deprecatedShadowAncestorNode(); 320 321 return downcast<Element>(node); 322 } 323 324 bool DragController::tryDocumentDrag(const DragData& dragData, DragDestinationAction actionMask, DragOperation& dragOperation) 325 { 326 if (!m_documentUnderMouse) 327 return false; 328 329 if (m_dragInitiator && !m_documentUnderMouse->securityOrigin().canReceiveDragData(m_dragInitiator->securityOrigin())) 330 return false; 331 332 bool isHandlingDrag = false; 333 if (actionMask & DragDestinationActionDHTML) { 334 isHandlingDrag = tryDHTMLDrag(dragData, dragOperation); 335 // Do not continue if m_documentUnderMouse has been reset by tryDHTMLDrag. 336 // tryDHTMLDrag fires dragenter event. The event listener that listens 337 // to this event may create a nested message loop (open a modal dialog), 338 // which could process dragleave event and reset m_documentUnderMouse in 339 // dragExited. 340 if (!m_documentUnderMouse) 341 return false; 342 } 343 344 // It's unclear why this check is after tryDHTMLDrag. 345 // We send drag events in tryDHTMLDrag and that may be the reason. 346 RefPtr<FrameView> frameView = m_documentUnderMouse->view(); 347 if (!frameView) 348 return false; 349 350 if (isHandlingDrag) { 351 clearDragCaret(); 352 return true; 353 } 354 355 if ((actionMask & DragDestinationActionEdit) && canProcessDrag(dragData)) { 356 if (dragData.containsColor()) { 357 dragOperation = DragOperationGeneric; 358 return true; 359 } 360 361 IntPoint point = frameView->windowToContents(dragData.clientPosition()); 362 Element* element = elementUnderMouse(m_documentUnderMouse.get(), point); 363 if (!element) 364 return false; 365 366 HTMLInputElement* elementAsFileInput = asFileInput(*element); 367 if (m_fileInputElementUnderMouse != elementAsFileInput) { 368 if (m_fileInputElementUnderMouse) 369 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false); 370 m_fileInputElementUnderMouse = elementAsFileInput; 371 } 372 373 if (!m_fileInputElementUnderMouse) 374 m_page.dragCaretController().setCaretPosition(m_documentUnderMouse->frame()->visiblePositionForPoint(point)); 375 else 376 clearDragCaret(); 377 378 Frame* innerFrame = element->document().frame(); 379 dragOperation = dragIsMove(innerFrame->selection(), dragData) ? DragOperationMove : DragOperationCopy; 380 m_numberOfItemsToBeAccepted = 0; 381 382 unsigned numberOfFiles = dragData.numberOfFiles(); 383 if (m_fileInputElementUnderMouse) { 384 if (m_fileInputElementUnderMouse->isDisabledFormControl()) 385 m_numberOfItemsToBeAccepted = 0; 386 else if (m_fileInputElementUnderMouse->multiple()) 387 m_numberOfItemsToBeAccepted = numberOfFiles; 388 else if (numberOfFiles > 1) 389 m_numberOfItemsToBeAccepted = 0; 390 else 391 m_numberOfItemsToBeAccepted = 1; 392 393 if (!m_numberOfItemsToBeAccepted) 394 dragOperation = DragOperationNone; 395 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(m_numberOfItemsToBeAccepted); 396 } else { 397 // We are not over a file input element. The dragged item(s) will only 398 // be loaded into the view the number of dragged items is 1. 399 m_numberOfItemsToBeAccepted = numberOfFiles != 1 ? 0 : 1; 400 } 401 402 return true; 403 } 404 405 // We are not over an editable region. Make sure we're clearing any prior drag cursor. 406 clearDragCaret(); 407 if (m_fileInputElementUnderMouse) 408 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false); 409 m_fileInputElementUnderMouse = nullptr; 410 return false; 411 } 412 413 DragSourceAction DragController::delegateDragSourceAction(const IntPoint& rootViewPoint) 414 { 415 m_dragSourceAction = m_client.dragSourceActionMaskForPoint(rootViewPoint); 416 return m_dragSourceAction; 417 } 418 419 DragOperation DragController::operationForLoad(const DragData& dragData) 420 { 421 Document* document = m_page.mainFrame().documentAtPoint(dragData.clientPosition()); 422 423 bool pluginDocumentAcceptsDrags = false; 424 425 if (is<PluginDocument>(document)) { 426 const Widget* widget = downcast<PluginDocument>(*document).pluginWidget(); 427 const PluginViewBase* pluginView = is<PluginViewBase>(widget) ? downcast<PluginViewBase>(widget) : nullptr; 428 429 if (pluginView) 430 pluginDocumentAcceptsDrags = pluginView->shouldAllowNavigationFromDrags(); 431 } 432 433 if (document && (m_didInitiateDrag || (is<PluginDocument>(*document) && !pluginDocumentAcceptsDrags) || document->hasEditableStyle())) 434 return DragOperationNone; 435 return dragOperation(dragData); 436 } 437 438 static bool setSelectionToDragCaret(Frame* frame, VisibleSelection& dragCaret, RefPtr<Range>& range, const IntPoint& point) 439 { 440 Ref<Frame> protector(*frame); 441 frame->selection().setSelection(dragCaret); 442 if (frame->selection().selection().isNone()) { 443 dragCaret = frame->visiblePositionForPoint(point); 444 frame->selection().setSelection(dragCaret); 445 range = dragCaret.toNormalizedRange(); 446 } 447 return !frame->selection().isNone() && frame->selection().selection().isContentEditable(); 448 } 449 450 bool DragController::dispatchTextInputEventFor(Frame* innerFrame, const DragData& dragData) 451 { 452 ASSERT(m_page.dragCaretController().hasCaret()); 453 String text = m_page.dragCaretController().isContentRichlyEditable() ? emptyString() : dragData.asPlainText(); 454 Node* target = innerFrame->editor().findEventTargetFrom(m_page.dragCaretController().caretPosition()); 455 return target->dispatchEvent(TextEvent::createForDrop(innerFrame->document()->domWindow(), text)); 456 } 457 458 bool DragController::concludeEditDrag(const DragData& dragData) 459 { 460 RefPtr<HTMLInputElement> fileInput = m_fileInputElementUnderMouse; 461 if (m_fileInputElementUnderMouse) { 462 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false); 463 m_fileInputElementUnderMouse = nullptr; 464 } 465 466 if (!m_documentUnderMouse) 467 return false; 468 469 IntPoint point = m_documentUnderMouse->view()->windowToContents(dragData.clientPosition()); 470 Element* element = elementUnderMouse(m_documentUnderMouse.get(), point); 471 if (!element) 472 return false; 473 RefPtr<Frame> innerFrame = element->document().frame(); 474 ASSERT(innerFrame); 475 476 if (m_page.dragCaretController().hasCaret() && !dispatchTextInputEventFor(innerFrame.get(), dragData)) 477 return true; 478 479 if (dragData.containsColor()) { 480 Color color = dragData.asColor(); 481 if (!color.isValid()) 482 return false; 483 RefPtr<Range> innerRange = innerFrame->selection().toNormalizedRange(); 484 RefPtr<MutableStyleProperties> style = MutableStyleProperties::create(); 485 style->setProperty(CSSPropertyColor, color.serialized(), false); 486 if (!innerFrame->editor().shouldApplyStyle(style.get(), innerRange.get())) 487 return false; 488 m_client.willPerformDragDestinationAction(DragDestinationActionEdit, dragData); 489 innerFrame->editor().applyStyle(style.get(), EditActionSetColor); 490 return true; 491 } 492 493 if (dragData.containsFiles() && fileInput) { 494 // fileInput should be the element we hit tested for, unless it was made 495 // display:none in a drop event handler. 496 ASSERT(fileInput == element || !fileInput->renderer()); 497 if (fileInput->isDisabledFormControl()) 498 return false; 499 500 return fileInput->receiveDroppedFiles(dragData); 501 } 502 503 if (!m_page.dragController().canProcessDrag(dragData)) { 504 clearDragCaret(); 505 return false; 506 } 507 508 VisibleSelection dragCaret = m_page.dragCaretController().caretPosition(); 509 clearDragCaret(); 510 RefPtr<Range> range = dragCaret.toNormalizedRange(); 511 RefPtr<Element> rootEditableElement = innerFrame->selection().selection().rootEditableElement(); 512 513 // For range to be null a WebKit client must have done something bad while 514 // manually controlling drag behaviour 515 if (!range) 516 return false; 517 518 ResourceCacheValidationSuppressor validationSuppressor(range->ownerDocument().cachedResourceLoader()); 519 if (dragIsMove(innerFrame->selection(), dragData) || dragCaret.isContentRichlyEditable()) { 520 bool chosePlainText = false; 521 RefPtr<DocumentFragment> fragment = documentFragmentFromDragData(dragData, *innerFrame, *range, true, chosePlainText); 522 if (!fragment || !innerFrame->editor().shouldInsertFragment(fragment, range, EditorInsertAction::Dropped)) { 523 return false; 524 } 525 526 m_client.willPerformDragDestinationAction(DragDestinationActionEdit, dragData); 527 if (dragIsMove(innerFrame->selection(), dragData)) { 528 // NSTextView behavior is to always smart delete on moving a selection, 529 // but only to smart insert if the selection granularity is word granularity. 530 bool smartDelete = innerFrame->editor().smartInsertDeleteEnabled(); 531 bool smartInsert = smartDelete && innerFrame->selection().granularity() == WordGranularity && dragData.canSmartReplace(); 532 applyCommand(MoveSelectionCommand::create(fragment, dragCaret.base(), smartInsert, smartDelete)); 533 } else { 534 if (setSelectionToDragCaret(innerFrame.get(), dragCaret, range, point)) { 535 ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::PreventNesting; 536 if (dragData.canSmartReplace()) 537 options |= ReplaceSelectionCommand::SmartReplace; 538 if (chosePlainText) 539 options |= ReplaceSelectionCommand::MatchStyle; 540 applyCommand(ReplaceSelectionCommand::create(*m_documentUnderMouse, WTFMove(fragment), options, EditActionInsertFromDrop)); 541 } 542 } 543 } else { 544 String text = dragData.asPlainText(); 545 if (text.isEmpty() || !innerFrame->editor().shouldInsertText(text, range.get(), EditorInsertAction::Dropped)) { 546 return false; 547 } 548 549 m_client.willPerformDragDestinationAction(DragDestinationActionEdit, dragData); 550 if (setSelectionToDragCaret(innerFrame.get(), dragCaret, range, point)) 551 applyCommand(ReplaceSelectionCommand::create(*m_documentUnderMouse, createFragmentFromText(*range, text), ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::MatchStyle | ReplaceSelectionCommand::PreventNesting, EditActionInsertFromDrop)); 552 } 553 554 if (rootEditableElement) { 555 if (Frame* frame = rootEditableElement->document().frame()) 556 frame->eventHandler().updateDragStateAfterEditDragIfNeeded(*rootEditableElement); 557 } 558 559 return true; 560 } 561 562 bool DragController::canProcessDrag(const DragData& dragData) 563 { 564 if (!dragData.containsCompatibleContent()) 565 return false; 566 567 IntPoint point = m_page.mainFrame().view()->windowToContents(dragData.clientPosition()); 568 HitTestResult result = HitTestResult(point); 569 if (!m_page.mainFrame().contentRenderer()) 570 return false; 571 572 result = m_page.mainFrame().eventHandler().hitTestResultAtPoint(point, HitTestRequest::ReadOnly | HitTestRequest::Active); 573 574 if (!result.innerNonSharedNode()) 575 return false; 576 577 if (dragData.containsFiles() && asFileInput(*result.innerNonSharedNode())) 578 return true; 579 580 if (is<HTMLPlugInElement>(*result.innerNonSharedNode())) { 581 if (!downcast<HTMLPlugInElement>(result.innerNonSharedNode())->canProcessDrag() && !result.innerNonSharedNode()->hasEditableStyle()) 582 return false; 583 } else if (!result.innerNonSharedNode()->hasEditableStyle()) 584 return false; 585 586 if (m_didInitiateDrag && m_documentUnderMouse == m_dragInitiator && result.isSelected()) 587 return false; 588 589 return true; 590 } 591 592 static DragOperation defaultOperationForDrag(DragOperation srcOpMask) 593 { 594 // This is designed to match IE's operation fallback for the case where 595 // the page calls preventDefault() in a drag event but doesn't set dropEffect. 596 if (srcOpMask == DragOperationEvery) 597 return DragOperationCopy; 598 if (srcOpMask == DragOperationNone) 599 return DragOperationNone; 600 if (srcOpMask & DragOperationMove || srcOpMask & DragOperationGeneric) 601 return DragOperationMove; 602 if (srcOpMask & DragOperationCopy) 603 return DragOperationCopy; 604 if (srcOpMask & DragOperationLink) 605 return DragOperationLink; 606 607 // FIXME: Does IE really return "generic" even if no operations were allowed by the source? 608 return DragOperationGeneric; 609 } 610 611 bool DragController::tryDHTMLDrag(const DragData& dragData, DragOperation& operation) 612 { 613 ASSERT(m_documentUnderMouse); 614 Ref<MainFrame> mainFrame(m_page.mainFrame()); 615 RefPtr<FrameView> viewProtector = mainFrame->view(); 616 if (!viewProtector) 617 return false; 618 619 #if ENABLE(DASHBOARD_SUPPORT) 620 DataTransferAccessPolicy policy = (mainFrame->settings().usesDashboardBackwardCompatibilityMode() && m_documentUnderMouse->securityOrigin().isLocal()) ? 621 DataTransferAccessPolicy::Readable : DataTransferAccessPolicy::TypesReadable; 622 #else 623 DataTransferAccessPolicy policy = DataTransferAccessPolicy::TypesReadable; 624 #endif 625 auto dataTransfer = DataTransfer::createForDrop(policy, dragData); 626 DragOperation srcOpMask = dragData.draggingSourceOperationMask(); 627 dataTransfer->setSourceOperation(srcOpMask); 628 629 PlatformMouseEvent event = createMouseEvent(dragData); 630 if (!mainFrame->eventHandler().updateDragAndDrop(event, dataTransfer)) { 631 dataTransfer->setAccessPolicy(DataTransferAccessPolicy::Numb); // Invalidate dataTransfer here for security. 632 return false; 633 } 634 635 operation = dataTransfer->destinationOperation(); 636 if (dataTransfer->dropEffectIsUninitialized()) 637 operation = defaultOperationForDrag(srcOpMask); 638 else if (!(srcOpMask & operation)) { 639 // The element picked an operation which is not supported by the source 640 operation = DragOperationNone; 641 } 642 643 dataTransfer->setAccessPolicy(DataTransferAccessPolicy::Numb); // Invalidate dataTransfer here for security. 644 return true; 645 } 646 647 Element* DragController::draggableElement(const Frame* sourceFrame, Element* startElement, const IntPoint& dragOrigin, DragState& state) const 648 { 649 state.type = (sourceFrame->selection().contains(dragOrigin)) ? DragSourceActionSelection : DragSourceActionNone; 650 if (!startElement) 651 return nullptr; 652 #if ENABLE(ATTACHMENT_ELEMENT) 653 // Unlike image elements, attachment elements are immediately selected upon mouse down, 654 // but for those elements we still want to use the single element drag behavior as long as 655 // the element is the only content of the selection. 656 const VisibleSelection& selection = sourceFrame->selection().selection(); 657 if (selection.isRange() && is<HTMLAttachmentElement>(selection.start().anchorNode()) && selection.start().anchorNode() == selection.end().anchorNode()) 658 state.type = DragSourceActionNone; 659 #endif 660 661 for (auto* element = startElement; element; element = element->parentOrShadowHostElement()) { 662 auto* renderer = element->renderer(); 663 if (!renderer) 664 continue; 665 666 EUserDrag dragMode = renderer->style().userDrag(); 667 if ((m_dragSourceAction & DragSourceActionDHTML) && dragMode == DRAG_ELEMENT) { 668 state.type = static_cast<DragSourceAction>(state.type | DragSourceActionDHTML); 669 return element; 670 } 671 if (dragMode == DRAG_AUTO) { 672 if ((m_dragSourceAction & DragSourceActionImage) 673 && is<HTMLImageElement>(*element) 674 && sourceFrame->settings().loadsImagesAutomatically()) { 675 state.type = static_cast<DragSourceAction>(state.type | DragSourceActionImage); 676 return element; 677 } 678 if ((m_dragSourceAction & DragSourceActionLink) && isDraggableLink(*element)) { 679 state.type = static_cast<DragSourceAction>(state.type | DragSourceActionLink); 680 return element; 681 } 682 #if ENABLE(ATTACHMENT_ELEMENT) 683 if ((m_dragSourceAction & DragSourceActionAttachment) 684 && is<HTMLAttachmentElement>(*element) 685 && downcast<HTMLAttachmentElement>(*element).file()) { 686 state.type = static_cast<DragSourceAction>(state.type | DragSourceActionAttachment); 687 return element; 688 } 689 #endif 690 } 691 } 692 693 // We either have nothing to drag or we have a selection and we're not over a draggable element. 694 return (state.type & DragSourceActionSelection) ? startElement : nullptr; 695 } 696 697 static CachedImage* getCachedImage(Element& element) 698 { 699 RenderObject* renderer = element.renderer(); 700 if (!is<RenderImage>(renderer)) 701 return nullptr; 702 auto& image = downcast<RenderImage>(*renderer); 703 return image.cachedImage(); 704 } 705 706 static Image* getImage(Element& element) 707 { 708 CachedImage* cachedImage = getCachedImage(element); 709 // Don't use cachedImage->imageForRenderer() here as that may return BitmapImages for cached SVG Images. 710 // Users of getImage() want access to the SVGImage, in order to figure out the filename extensions, 711 // which would be empty when asking the cached BitmapImages. 712 return (cachedImage && !cachedImage->errorOccurred()) ? 713 cachedImage->image() : nullptr; 714 } 715 716 static void selectElement(Element& element) 717 { 718 RefPtr<Range> range = element.document().createRange(); 719 range->selectNode(element); 720 element.document().frame()->selection().setSelection(VisibleSelection(*range, DOWNSTREAM)); 721 } 722 723 static IntPoint dragLocForDHTMLDrag(const IntPoint& mouseDraggedPoint, const IntPoint& dragOrigin, const IntPoint& dragImageOffset, bool isLinkImage) 724 { 725 // dragImageOffset is the cursor position relative to the lower-left corner of the image. 726 #if PLATFORM(COCOA) 727 // We add in the Y dimension because we are a flipped view, so adding moves the image down. 728 const int yOffset = dragImageOffset.y(); 729 #else 730 const int yOffset = -dragImageOffset.y(); 731 #endif 732 733 if (isLinkImage) 734 return IntPoint(mouseDraggedPoint.x() - dragImageOffset.x(), mouseDraggedPoint.y() + yOffset); 735 736 return IntPoint(dragOrigin.x() - dragImageOffset.x(), dragOrigin.y() + yOffset); 737 } 738 739 static IntPoint dragLocForSelectionDrag(Frame& src) 740 { 741 IntRect draggingRect = enclosingIntRect(src.selection().selectionBounds()); 742 int xpos = draggingRect.maxX(); 743 xpos = draggingRect.x() < xpos ? draggingRect.x() : xpos; 744 int ypos = draggingRect.maxY(); 745 #if PLATFORM(COCOA) 746 // Deal with flipped coordinates on Mac 747 ypos = draggingRect.y() > ypos ? draggingRect.y() : ypos; 748 #else 749 ypos = draggingRect.y() < ypos ? draggingRect.y() : ypos; 750 #endif 751 return IntPoint(xpos, ypos); 752 } 753 754 bool DragController::startDrag(Frame& src, const DragState& state, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin) 755 { 756 if (!src.view() || !src.contentRenderer() || !state.source) 757 return false; 758 759 Ref<Frame> protector(src); 760 HitTestResult hitTestResult = src.eventHandler().hitTestResultAtPoint(dragOrigin, HitTestRequest::ReadOnly | HitTestRequest::Active); 761 762 // FIXME(136836): Investigate whether all elements should use the containsIncludingShadowDOM() path here. 763 bool includeShadowDOM = false; 764 #if ENABLE(VIDEO) 765 includeShadowDOM = state.source->isMediaElement(); 766 #endif 767 bool sourceContainsHitNode; 768 if (!includeShadowDOM) 769 sourceContainsHitNode = state.source->contains(hitTestResult.innerNode()); 770 else 771 sourceContainsHitNode = state.source->containsIncludingShadowDOM(hitTestResult.innerNode()); 772 773 if (!sourceContainsHitNode) { 774 // The original node being dragged isn't under the drag origin anymore... maybe it was 775 // hidden or moved out from under the cursor. Regardless, we don't want to start a drag on 776 // something that's not actually under the drag origin. 777 return false; 778 } 779 780 URL linkURL = hitTestResult.absoluteLinkURL(); 781 URL imageURL = hitTestResult.absoluteImageURL(); 782 #if ENABLE(ATTACHMENT_ELEMENT) 783 URL attachmentURL = hitTestResult.absoluteAttachmentURL(); 784 m_draggingAttachmentURL = URL(); 785 #endif 786 787 IntPoint mouseDraggedPoint = src.view()->windowToContents(dragEvent.position()); 788 789 m_draggingImageURL = URL(); 790 m_sourceDragOperation = srcOp; 791 792 DragImage dragImage; 793 IntPoint dragLoc(0, 0); 794 IntPoint dragImageOffset(0, 0); 795 796 ASSERT(state.dataTransfer); 797 798 DataTransfer& dataTransfer = *state.dataTransfer; 799 if (state.type == DragSourceActionDHTML) { 800 dragImage = DragImage { dataTransfer.createDragImage(dragImageOffset) }; 801 // We allow DHTML/JS to set the drag image, even if its a link, image or text we're dragging. 802 // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp. 803 if (dragImage) { 804 dragLoc = dragLocForDHTMLDrag(mouseDraggedPoint, dragOrigin, dragImageOffset, !linkURL.isEmpty()); 805 m_dragOffset = dragImageOffset; 806 } 807 } 808 809 if (state.type == DragSourceActionSelection || !imageURL.isEmpty() || !linkURL.isEmpty()) { 810 // Selection, image, and link drags receive a default set of allowed drag operations that 811 // follows from: 812 // http://trac.webkit.org/browser/trunk/WebKit/mac/WebView/WebHTMLView.mm?rev=48526#L3430 813 m_sourceDragOperation = static_cast<DragOperation>(m_sourceDragOperation | DragOperationGeneric | DragOperationCopy); 814 } 815 816 ASSERT(state.source); 817 Element& element = *state.source; 818 819 IntRect dragImageBounds; 820 Image* image = getImage(element); 821 if (state.type == DragSourceActionSelection) { 822 if (!dataTransfer.pasteboard().hasData()) { 823 // FIXME: This entire block is almost identical to the code in Editor::copy, and the code should be shared. 824 RefPtr<Range> selectionRange = src.selection().toNormalizedRange(); 825 ASSERT(selectionRange); 826 827 #if ENABLE(DATA_INTERACTION) 828 Vector<SelectionRect> selectionRects; 829 selectionRange->collectSelectionRects(selectionRects); 830 for (auto selectionRect : selectionRects) 831 dragImageBounds.unite(selectionRect.rect()); 832 dragImageBounds.inflate(SelectionDragImagePadding); 833 #endif 834 835 src.editor().willWriteSelectionToPasteboard(selectionRange.get()); 836 837 if (enclosingTextFormControl(src.selection().selection().start())) 838 dataTransfer.pasteboard().writePlainText(src.editor().selectedTextForDataTransfer(), Pasteboard::CannotSmartReplace); 839 else { 840 #if PLATFORM(COCOA) || PLATFORM(GTK) 841 src.editor().writeSelectionToPasteboard(dataTransfer.pasteboard()); 842 #else 843 // FIXME: Convert all other platforms to match Mac and delete this. 844 dataTransfer.pasteboard().writeSelection(*selectionRange, src.editor().canSmartCopyOrDelete(), src, IncludeImageAltTextForDataTransfer); 845 #endif 846 } 847 848 src.editor().didWriteSelectionToPasteboard(); 849 } 850 m_client.willPerformDragSourceAction(DragSourceActionSelection, dragOrigin, dataTransfer); 851 if (!dragImage) { 852 dragImage = DragImage { dissolveDragImageToFraction(createDragImageForSelection(src), DragImageAlpha) }; 853 dragLoc = dragLocForSelectionDrag(src); 854 m_dragOffset = IntPoint(dragOrigin.x() - dragLoc.x(), dragOrigin.y() - dragLoc.y()); 855 } 856 857 if (!dragImage) 858 return false; 859 860 doSystemDrag(WTFMove(dragImage), dragLoc, dragOrigin, dragImageBounds, dataTransfer, src, DragSourceActionSelection); 861 return true; 862 } 863 864 if (!src.document()->securityOrigin().canDisplay(linkURL)) { 865 src.document()->addConsoleMessage(MessageSource::Security, MessageLevel::Error, "Not allowed to drag local resource: " + linkURL.stringCenterEllipsizedToLength()); 866 return false; 867 } 868 869 if (!imageURL.isEmpty() && image && !image->isNull() && (m_dragSourceAction & DragSourceActionImage)) { 870 // We shouldn't be starting a drag for an image that can't provide an extension. 871 // This is an early detection for problems encountered later upon drop. 872 ASSERT(!image->filenameExtension().isEmpty()); 873 #if PLATFORM(JAVA) 874 m_client.updateFileExtension(imageURL); 875 #endif 876 if (!dataTransfer.pasteboard().hasData()) { 877 m_draggingImageURL = imageURL; 878 if (element.isContentRichlyEditable()) 879 selectElement(element); 880 declareAndWriteDragImage(dataTransfer, element, !linkURL.isEmpty() ? linkURL : imageURL, hitTestResult.altDisplayString()); 881 } 882 883 m_client.willPerformDragSourceAction(DragSourceActionImage, dragOrigin, dataTransfer); 884 885 if (!dragImage) { 886 IntRect imageRect = hitTestResult.imageRect(); 887 imageRect.setLocation(m_page.mainFrame().view()->rootViewToContents(src.view()->contentsToRootView(imageRect.location()))); 888 doImageDrag(element, dragOrigin, hitTestResult.imageRect(), dataTransfer, src, m_dragOffset); 889 } else { 890 // DHTML defined drag image 891 doSystemDrag(WTFMove(dragImage), dragLoc, dragOrigin, { }, dataTransfer, src, DragSourceActionImage); 892 } 893 894 return true; 895 } 896 897 if (!linkURL.isEmpty() && (m_dragSourceAction & DragSourceActionLink)) { 898 if (!dataTransfer.pasteboard().hasData()) { 899 // Simplify whitespace so the title put on the dataTransfer resembles what the user sees 900 // on the web page. This includes replacing newlines with spaces. 901 src.editor().copyURL(linkURL, hitTestResult.textContent().simplifyWhiteSpace(), dataTransfer.pasteboard()); 902 } else { 903 // Make sure the pasteboard also contains trustworthy link data 904 // but don't overwrite more general pasteboard types. 905 PasteboardURL pasteboardURL; 906 pasteboardURL.url = linkURL; 907 pasteboardURL.title = hitTestResult.textContent(); 908 dataTransfer.pasteboard().writeTrustworthyWebURLsPboardType(pasteboardURL); 909 } 910 911 const VisibleSelection& sourceSelection = src.selection().selection(); 912 if (sourceSelection.isCaret() && sourceSelection.isContentEditable()) { 913 // a user can initiate a drag on a link without having any text 914 // selected. In this case, we should expand the selection to 915 // the enclosing anchor element 916 Position pos = sourceSelection.base(); 917 Node* node = enclosingAnchorElement(pos); 918 if (node) 919 src.selection().setSelection(VisibleSelection::selectionFromContentsOfNode(node)); 920 } 921 922 m_client.willPerformDragSourceAction(DragSourceActionLink, dragOrigin, dataTransfer); 923 if (!dragImage) { 924 dragImage = DragImage { createDragImageForLink(linkURL, hitTestResult.textContent(), src.settings().fontRenderingMode()) }; 925 IntSize size = dragImageSize(dragImage.get()); 926 m_dragOffset = IntPoint(-size.width() / 2, -LinkDragBorderInset); 927 dragLoc = IntPoint(mouseDraggedPoint.x() + m_dragOffset.x(), mouseDraggedPoint.y() + m_dragOffset.y()); 928 dragImage = DragImage { platformAdjustDragImageForDeviceScaleFactor(dragImage.get(), m_page.deviceScaleFactor()) }; 929 } 930 doSystemDrag(WTFMove(dragImage), dragLoc, mouseDraggedPoint, { }, dataTransfer, src, DragSourceActionLink); 931 932 return true; 933 } 934 935 #if ENABLE(ATTACHMENT_ELEMENT) 936 if (!attachmentURL.isEmpty() && (m_dragSourceAction & DragSourceActionAttachment)) { 937 if (!dataTransfer.pasteboard().hasData()) { 938 m_draggingAttachmentURL = attachmentURL; 939 selectElement(element); 940 declareAndWriteAttachment(dataTransfer, element, attachmentURL); 941 } 942 943 m_client.willPerformDragSourceAction(DragSourceActionAttachment, dragOrigin, dataTransfer); 944 945 if (!dragImage) { 946 dragImage = DragImage { dissolveDragImageToFraction(createDragImageForSelection(src), DragImageAlpha) }; 947 dragLoc = dragLocForSelectionDrag(src); 948 m_dragOffset = IntPoint(dragOrigin.x() - dragLoc.x(), dragOrigin.y() - dragLoc.y()); 949 } 950 doSystemDrag(WTFMove(dragImage), dragLoc, dragOrigin, { }, dataTransfer, src, DragSourceActionAttachment); 951 return true; 952 } 953 #endif 954 955 if (state.type == DragSourceActionDHTML && dragImage) { 956 ASSERT(m_dragSourceAction & DragSourceActionDHTML); 957 m_client.willPerformDragSourceAction(DragSourceActionDHTML, dragOrigin, dataTransfer); 958 doSystemDrag(WTFMove(dragImage), dragLoc, dragOrigin, { }, dataTransfer, src, DragSourceActionDHTML); 959 return true; 960 } 961 962 return false; 963 } 964 965 void DragController::doImageDrag(Element& element, const IntPoint& dragOrigin, const IntRect& layoutRect, DataTransfer& dataTransfer, Frame& frame, IntPoint& dragImageOffset) 966 { 967 IntPoint mouseDownPoint = dragOrigin; 968 DragImage dragImage; 969 IntPoint scaledOrigin; 970 971 if (!element.renderer()) 972 return; 973 974 ImageOrientationDescription orientationDescription(element.renderer()->shouldRespectImageOrientation(), element.renderer()->style().imageOrientation()); 975 976 Image* image = getImage(element); 977 if (image && image->size().height() * image->size().width() <= MaxOriginalImageArea 978 && (dragImage = DragImage { createDragImageFromImage(image, element.renderer() ? orientationDescription : ImageOrientationDescription()) })) { 979 980 dragImage = DragImage { fitDragImageToMaxSize(dragImage.get(), layoutRect.size(), maxDragImageSize()) }; 981 IntSize fittedSize = dragImageSize(dragImage.get()); 982 983 dragImage = DragImage { platformAdjustDragImageForDeviceScaleFactor(dragImage.get(), m_page.deviceScaleFactor()) }; 984 dragImage = DragImage { dissolveDragImageToFraction(dragImage.get(), DragImageAlpha) }; 985 986 // Properly orient the drag image and orient it differently if it's smaller than the original. 987 float scale = fittedSize.width() / (float)layoutRect.width(); 988 float dx = scale * (layoutRect.x() - mouseDownPoint.x()); 989 float originY = layoutRect.y(); 990 #if PLATFORM(COCOA) 991 // Compensate for accursed flipped coordinates in Cocoa. 992 originY += layoutRect.height(); 993 #endif 994 float dy = scale * (originY - mouseDownPoint.y()); 995 scaledOrigin = IntPoint((int)(dx + 0.5), (int)(dy + 0.5)); 996 } else { 997 if (CachedImage* cachedImage = getCachedImage(element)) { 998 dragImage = DragImage { createDragImageIconForCachedImageFilename(cachedImage->response().suggestedFilename()) }; 999 if (dragImage) 1000 scaledOrigin = IntPoint(DragIconRightInset - dragImageSize(dragImage.get()).width(), DragIconBottomInset); 1001 } 1002 } 1003 1004 if (!dragImage) 1005 return; 1006 1007 dragImageOffset = mouseDownPoint + scaledOrigin; 1008 doSystemDrag(WTFMove(dragImage), dragImageOffset, dragOrigin, element.boundsInRootViewSpace(), dataTransfer, frame, DragSourceActionImage); 1009 } 1010 1011 void DragController::doSystemDrag(DragImage image, const IntPoint& dragLoc, const IntPoint& eventPos, const IntRect& dragImageBounds, DataTransfer& dataTransfer, Frame& frame, DragSourceAction dragSourceAction) 1012 { 1013 FloatPoint dragImageAnchor = { 0.5, 0.5 }; 1014 if (dragSourceAction == DragSourceActionLink) 1015 dragImageAnchor.setY(1); 1016 else if (!dragImageBounds.isEmpty()) { 1017 dragImageAnchor.setX((eventPos.x() - dragImageBounds.x()) / (float)dragImageBounds.width()); 1018 dragImageAnchor.setY((eventPos.y() - dragImageBounds.y()) / (float)dragImageBounds.height()); 1019 } 1020 1021 m_didInitiateDrag = true; 1022 m_dragInitiator = frame.document(); 1023 // Protect this frame and view, as a load may occur mid drag and attempt to unload this frame 1024 Ref<MainFrame> frameProtector(m_page.mainFrame()); 1025 RefPtr<FrameView> viewProtector = frameProtector->view(); 1026 m_client.startDrag(WTFMove(image), viewProtector->rootViewToContents(frame.view()->contentsToRootView(dragLoc)), viewProtector->rootViewToContents(frame.view()->contentsToRootView(eventPos)), dragImageAnchor, dataTransfer, frameProtector.get(), dragSourceAction); 1027 // DragClient::startDrag can cause our Page to dispear, deallocating |this|. 1028 if (!frameProtector->page()) 1029 return; 1030 1031 cleanupAfterSystemDrag(); 1032 } 1033 1034 // Manual drag caret manipulation 1035 void DragController::placeDragCaret(const IntPoint& windowPoint) 1036 { 1037 mouseMovedIntoDocument(m_page.mainFrame().documentAtPoint(windowPoint)); 1038 if (!m_documentUnderMouse) 1039 return; 1040 Frame* frame = m_documentUnderMouse->frame(); 1041 FrameView* frameView = frame->view(); 1042 if (!frameView) 1043 return; 1044 IntPoint framePoint = frameView->windowToContents(windowPoint); 1045 1046 m_page.dragCaretController().setCaretPosition(frame->visiblePositionForPoint(framePoint)); 1047 } 1048 1049 #endif // ENABLE(DRAG_SUPPORT) 1050 1051 } // namespace WebCore