1/* 2 * Copyright (C) 2007, 2009, 2010, 2013 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 COMPUTER, 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 COMPUTER, 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#if ENABLE(DRAG_SUPPORT) 30 31#include "CachedImage.h" 32#include "Clipboard.h" 33#include "ClipboardAccessPolicy.h" 34#include "CachedResourceLoader.h" 35#include "Document.h" 36#include "DocumentFragment.h" 37#include "DragActions.h" 38#include "DragClient.h" 39#include "DragData.h" 40#include "DragSession.h" 41#include "DragState.h" 42#include "Editor.h" 43#include "EditorClient.h" 44#include "Element.h" 45#include "EventHandler.h" 46#include "ExceptionCodePlaceholder.h" 47#include "FloatRect.h" 48#include "Frame.h" 49#include "FrameLoadRequest.h" 50#include "FrameLoader.h" 51#include "FrameSelection.h" 52#include "FrameView.h" 53#include "HTMLAnchorElement.h" 54#include "HTMLInputElement.h" 55#include "HTMLNames.h" 56#include "HTMLPlugInElement.h" 57#include "HitTestRequest.h" 58#include "HitTestResult.h" 59#include "Image.h" 60#include "ImageOrientation.h" 61#include "MoveSelectionCommand.h" 62#include "Page.h" 63#include "PlatformKeyboardEvent.h" 64#include "PluginDocument.h" 65#include "PluginViewBase.h" 66#include "RenderFileUploadControl.h" 67#include "RenderImage.h" 68#include "RenderView.h" 69#include "ReplaceSelectionCommand.h" 70#include "ResourceRequest.h" 71#include "SecurityOrigin.h" 72#include "Settings.h" 73#include "ShadowRoot.h" 74#include "StylePropertySet.h" 75#include "Text.h" 76#include "TextEvent.h" 77#include "htmlediting.h" 78#include "markup.h" 79#include <wtf/CurrentTime.h> 80#include <wtf/RefPtr.h> 81 82namespace WebCore { 83 84static PlatformMouseEvent createMouseEvent(DragData* dragData) 85{ 86 bool shiftKey, ctrlKey, altKey, metaKey; 87 shiftKey = ctrlKey = altKey = metaKey = false; 88 int keyState = dragData->modifierKeyState(); 89 shiftKey = static_cast<bool>(keyState & PlatformEvent::ShiftKey); 90 ctrlKey = static_cast<bool>(keyState & PlatformEvent::CtrlKey); 91 altKey = static_cast<bool>(keyState & PlatformEvent::AltKey); 92 metaKey = static_cast<bool>(keyState & PlatformEvent::MetaKey); 93 94 return PlatformMouseEvent(dragData->clientPosition(), dragData->globalPosition(), 95 LeftButton, PlatformEvent::MouseMoved, 0, shiftKey, ctrlKey, altKey, 96 metaKey, currentTime()); 97} 98 99DragController::DragController(Page* page, DragClient* client) 100 : m_page(page) 101 , m_client(client) 102 , m_documentUnderMouse(0) 103 , m_dragInitiator(0) 104 , m_fileInputElementUnderMouse(0) 105 , m_documentIsHandlingDrag(false) 106 , m_dragDestinationAction(DragDestinationActionNone) 107 , m_dragSourceAction(DragSourceActionNone) 108 , m_didInitiateDrag(false) 109 , m_sourceDragOperation(DragOperationNone) 110{ 111 ASSERT(m_client); 112} 113 114DragController::~DragController() 115{ 116 m_client->dragControllerDestroyed(); 117} 118 119PassOwnPtr<DragController> DragController::create(Page* page, DragClient* client) 120{ 121 return adoptPtr(new DragController(page, client)); 122} 123 124static PassRefPtr<DocumentFragment> documentFragmentFromDragData(DragData* dragData, Frame* frame, RefPtr<Range> context, 125 bool allowPlainText, bool& chosePlainText) 126{ 127 ASSERT(dragData); 128 chosePlainText = false; 129 130 Document* document = context->ownerDocument(); 131 ASSERT(document); 132 if (document && dragData->containsCompatibleContent()) { 133 if (PassRefPtr<DocumentFragment> fragment = dragData->asFragment(frame, context, allowPlainText, chosePlainText)) 134 return fragment; 135 136 if (dragData->containsURL(frame, DragData::DoNotConvertFilenames)) { 137 String title; 138 String url = dragData->asURL(frame, DragData::DoNotConvertFilenames, &title); 139 if (!url.isEmpty()) { 140 RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::create(document); 141 anchor->setHref(url); 142 if (title.isEmpty()) { 143 // Try the plain text first because the url might be normalized or escaped. 144 if (dragData->containsPlainText()) 145 title = dragData->asPlainText(frame); 146 if (title.isEmpty()) 147 title = url; 148 } 149 RefPtr<Node> anchorText = document->createTextNode(title); 150 anchor->appendChild(anchorText, IGNORE_EXCEPTION); 151 RefPtr<DocumentFragment> fragment = document->createDocumentFragment(); 152 fragment->appendChild(anchor, IGNORE_EXCEPTION); 153 return fragment.get(); 154 } 155 } 156 } 157 if (allowPlainText && dragData->containsPlainText()) { 158 chosePlainText = true; 159 return createFragmentFromText(context.get(), dragData->asPlainText(frame)).get(); 160 } 161 162 return 0; 163} 164 165bool DragController::dragIsMove(FrameSelection* selection, DragData* dragData) 166{ 167 return m_documentUnderMouse == m_dragInitiator && selection->isContentEditable() && selection->isRange() && !isCopyKeyDown(dragData); 168} 169 170// FIXME: This method is poorly named. We're just clearing the selection from the document this drag is exiting. 171void DragController::cancelDrag() 172{ 173 m_page->dragCaretController()->clear(); 174} 175 176void DragController::dragEnded() 177{ 178 m_dragInitiator = 0; 179 m_didInitiateDrag = false; 180 m_page->dragCaretController()->clear(); 181 182 m_client->dragEnded(); 183} 184 185DragSession DragController::dragEntered(DragData* dragData) 186{ 187 return dragEnteredOrUpdated(dragData); 188} 189 190void DragController::dragExited(DragData* dragData) 191{ 192 ASSERT(dragData); 193 Frame* mainFrame = m_page->mainFrame(); 194 195 if (RefPtr<FrameView> v = mainFrame->view()) { 196 ClipboardAccessPolicy policy = (!m_documentUnderMouse || m_documentUnderMouse->securityOrigin()->isLocal()) ? ClipboardReadable : ClipboardTypesReadable; 197 RefPtr<Clipboard> clipboard = Clipboard::create(policy, dragData, mainFrame); 198 clipboard->setSourceOperation(dragData->draggingSourceOperationMask()); 199 mainFrame->eventHandler()->cancelDragAndDrop(createMouseEvent(dragData), clipboard.get()); 200 clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security 201 } 202 mouseMovedIntoDocument(0); 203 if (m_fileInputElementUnderMouse) 204 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false); 205 m_fileInputElementUnderMouse = 0; 206} 207 208DragSession DragController::dragUpdated(DragData* dragData) 209{ 210 return dragEnteredOrUpdated(dragData); 211} 212 213bool DragController::performDrag(DragData* dragData) 214{ 215 ASSERT(dragData); 216 m_documentUnderMouse = m_page->mainFrame()->documentAtPoint(dragData->clientPosition()); 217 if ((m_dragDestinationAction & DragDestinationActionDHTML) && m_documentIsHandlingDrag) { 218 m_client->willPerformDragDestinationAction(DragDestinationActionDHTML, dragData); 219 RefPtr<Frame> mainFrame = m_page->mainFrame(); 220 bool preventedDefault = false; 221 if (mainFrame->view()) { 222 // Sending an event can result in the destruction of the view and part. 223 RefPtr<Clipboard> clipboard = Clipboard::create(ClipboardReadable, dragData, mainFrame.get()); 224 clipboard->setSourceOperation(dragData->draggingSourceOperationMask()); 225 preventedDefault = mainFrame->eventHandler()->performDragAndDrop(createMouseEvent(dragData), clipboard.get()); 226 clipboard->setAccessPolicy(ClipboardNumb); // Invalidate clipboard here for security 227 } 228 if (preventedDefault) { 229 m_documentUnderMouse = 0; 230 return true; 231 } 232 } 233 234 if ((m_dragDestinationAction & DragDestinationActionEdit) && concludeEditDrag(dragData)) { 235 m_documentUnderMouse = 0; 236 return true; 237 } 238 239 m_documentUnderMouse = 0; 240 241 if (operationForLoad(dragData) == DragOperationNone) 242 return false; 243 244 m_client->willPerformDragDestinationAction(DragDestinationActionLoad, dragData); 245 m_page->mainFrame()->loader()->load(FrameLoadRequest(m_page->mainFrame(), ResourceRequest(dragData->asURL(m_page->mainFrame())))); 246 return true; 247} 248 249void DragController::mouseMovedIntoDocument(Document* newDocument) 250{ 251 if (m_documentUnderMouse == newDocument) 252 return; 253 254 // If we were over another document clear the selection 255 if (m_documentUnderMouse) 256 cancelDrag(); 257 m_documentUnderMouse = newDocument; 258} 259 260DragSession DragController::dragEnteredOrUpdated(DragData* dragData) 261{ 262 ASSERT(dragData); 263 ASSERT(m_page->mainFrame()); 264 mouseMovedIntoDocument(m_page->mainFrame()->documentAtPoint(dragData->clientPosition())); 265 266 m_dragDestinationAction = m_client->actionMaskForDrag(dragData); 267 if (m_dragDestinationAction == DragDestinationActionNone) { 268 cancelDrag(); // FIXME: Why not call mouseMovedIntoDocument(0)? 269 return DragSession(); 270 } 271 272 DragSession dragSession; 273 m_documentIsHandlingDrag = tryDocumentDrag(dragData, m_dragDestinationAction, dragSession); 274 if (!m_documentIsHandlingDrag && (m_dragDestinationAction & DragDestinationActionLoad)) 275 dragSession.operation = operationForLoad(dragData); 276 return dragSession; 277} 278 279static HTMLInputElement* asFileInput(Node* node) 280{ 281 ASSERT(node); 282 283 HTMLInputElement* inputElement = node->toInputElement(); 284 285 // If this is a button inside of the a file input, move up to the file input. 286 if (inputElement && inputElement->isTextButton() && inputElement->treeScope()->rootNode()->isShadowRoot()) 287 inputElement = toShadowRoot(inputElement->treeScope()->rootNode())->host()->toInputElement(); 288 289 return inputElement && inputElement->isFileUpload() ? inputElement : 0; 290} 291 292// This can return null if an empty document is loaded. 293static Element* elementUnderMouse(Document* documentUnderMouse, const IntPoint& p) 294{ 295 Frame* frame = documentUnderMouse->frame(); 296 float zoomFactor = frame ? frame->pageZoomFactor() : 1; 297 LayoutPoint point = roundedLayoutPoint(FloatPoint(p.x() * zoomFactor, p.y() * zoomFactor)); 298 299 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::DisallowShadowContent); 300 HitTestResult result(point); 301 documentUnderMouse->renderView()->hitTest(request, result); 302 303 Node* n = result.innerNode(); 304 while (n && !n->isElementNode()) 305 n = n->parentNode(); 306 if (n) 307 n = n->deprecatedShadowAncestorNode(); 308 309 return toElement(n); 310} 311 312bool DragController::tryDocumentDrag(DragData* dragData, DragDestinationAction actionMask, DragSession& dragSession) 313{ 314 ASSERT(dragData); 315 316 if (!m_documentUnderMouse) 317 return false; 318 319 if (m_dragInitiator && !m_documentUnderMouse->securityOrigin()->canReceiveDragData(m_dragInitiator->securityOrigin())) 320 return false; 321 322 bool isHandlingDrag = false; 323 if (actionMask & DragDestinationActionDHTML) { 324 isHandlingDrag = tryDHTMLDrag(dragData, dragSession.operation); 325 // Do not continue if m_documentUnderMouse has been reset by tryDHTMLDrag. 326 // tryDHTMLDrag fires dragenter event. The event listener that listens 327 // to this event may create a nested message loop (open a modal dialog), 328 // which could process dragleave event and reset m_documentUnderMouse in 329 // dragExited. 330 if (!m_documentUnderMouse) 331 return false; 332 } 333 334 // It's unclear why this check is after tryDHTMLDrag. 335 // We send drag events in tryDHTMLDrag and that may be the reason. 336 RefPtr<FrameView> frameView = m_documentUnderMouse->view(); 337 if (!frameView) 338 return false; 339 340 if (isHandlingDrag) { 341 m_page->dragCaretController()->clear(); 342 return true; 343 } 344 345 if ((actionMask & DragDestinationActionEdit) && canProcessDrag(dragData)) { 346 if (dragData->containsColor()) { 347 dragSession.operation = DragOperationGeneric; 348 return true; 349 } 350 351 IntPoint point = frameView->windowToContents(dragData->clientPosition()); 352 Element* element = elementUnderMouse(m_documentUnderMouse.get(), point); 353 if (!element) 354 return false; 355 356 HTMLInputElement* elementAsFileInput = asFileInput(element); 357 if (m_fileInputElementUnderMouse != elementAsFileInput) { 358 if (m_fileInputElementUnderMouse) 359 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false); 360 m_fileInputElementUnderMouse = elementAsFileInput; 361 } 362 363 if (!m_fileInputElementUnderMouse) 364 m_page->dragCaretController()->setCaretPosition(m_documentUnderMouse->frame()->visiblePositionForPoint(point)); 365 366 Frame* innerFrame = element->document()->frame(); 367 dragSession.operation = dragIsMove(innerFrame->selection(), dragData) ? DragOperationMove : DragOperationCopy; 368 dragSession.mouseIsOverFileInput = m_fileInputElementUnderMouse; 369 dragSession.numberOfItemsToBeAccepted = 0; 370 371 unsigned numberOfFiles = dragData->numberOfFiles(); 372 if (m_fileInputElementUnderMouse) { 373 if (m_fileInputElementUnderMouse->isDisabledFormControl()) 374 dragSession.numberOfItemsToBeAccepted = 0; 375 else if (m_fileInputElementUnderMouse->multiple()) 376 dragSession.numberOfItemsToBeAccepted = numberOfFiles; 377 else if (numberOfFiles > 1) 378 dragSession.numberOfItemsToBeAccepted = 0; 379 else 380 dragSession.numberOfItemsToBeAccepted = 1; 381 382 if (!dragSession.numberOfItemsToBeAccepted) 383 dragSession.operation = DragOperationNone; 384 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(dragSession.numberOfItemsToBeAccepted); 385 } else { 386 // We are not over a file input element. The dragged item(s) will only 387 // be loaded into the view the number of dragged items is 1. 388 dragSession.numberOfItemsToBeAccepted = numberOfFiles != 1 ? 0 : 1; 389 } 390 391 return true; 392 } 393 394 // We are not over an editable region. Make sure we're clearing any prior drag cursor. 395 m_page->dragCaretController()->clear(); 396 if (m_fileInputElementUnderMouse) 397 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false); 398 m_fileInputElementUnderMouse = 0; 399 return false; 400} 401 402DragSourceAction DragController::delegateDragSourceAction(const IntPoint& rootViewPoint) 403{ 404 m_dragSourceAction = m_client->dragSourceActionMaskForPoint(rootViewPoint); 405 return m_dragSourceAction; 406} 407 408DragOperation DragController::operationForLoad(DragData* dragData) 409{ 410 ASSERT(dragData); 411 Document* doc = m_page->mainFrame()->documentAtPoint(dragData->clientPosition()); 412 413 bool pluginDocumentAcceptsDrags = false; 414 415 if (doc && doc->isPluginDocument()) { 416 const Widget* widget = toPluginDocument(doc)->pluginWidget(); 417 const PluginViewBase* pluginView = (widget && widget->isPluginViewBase()) ? static_cast<const PluginViewBase*>(widget) : 0; 418 419 if (pluginView) 420 pluginDocumentAcceptsDrags = pluginView->shouldAllowNavigationFromDrags(); 421 } 422 423 if (doc && (m_didInitiateDrag || (doc->isPluginDocument() && !pluginDocumentAcceptsDrags) || doc->rendererIsEditable())) 424 return DragOperationNone; 425 return dragOperation(dragData); 426} 427 428static bool setSelectionToDragCaret(Frame* frame, VisibleSelection& dragCaret, RefPtr<Range>& range, const IntPoint& point) 429{ 430 frame->selection()->setSelection(dragCaret); 431 if (frame->selection()->isNone()) { 432 dragCaret = frame->visiblePositionForPoint(point); 433 frame->selection()->setSelection(dragCaret); 434 range = dragCaret.toNormalizedRange(); 435 } 436 return !frame->selection()->isNone() && frame->selection()->isContentEditable(); 437} 438 439bool DragController::dispatchTextInputEventFor(Frame* innerFrame, DragData* dragData) 440{ 441 ASSERT(m_page->dragCaretController()->hasCaret()); 442 String text = m_page->dragCaretController()->isContentRichlyEditable() ? "" : dragData->asPlainText(innerFrame); 443 Node* target = innerFrame->editor().findEventTargetFrom(m_page->dragCaretController()->caretPosition()); 444 return target->dispatchEvent(TextEvent::createForDrop(innerFrame->document()->domWindow(), text), IGNORE_EXCEPTION); 445} 446 447bool DragController::concludeEditDrag(DragData* dragData) 448{ 449 ASSERT(dragData); 450 451 RefPtr<HTMLInputElement> fileInput = m_fileInputElementUnderMouse; 452 if (m_fileInputElementUnderMouse) { 453 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false); 454 m_fileInputElementUnderMouse = 0; 455 } 456 457 if (!m_documentUnderMouse) 458 return false; 459 460 IntPoint point = m_documentUnderMouse->view()->windowToContents(dragData->clientPosition()); 461 Element* element = elementUnderMouse(m_documentUnderMouse.get(), point); 462 if (!element) 463 return false; 464 RefPtr<Frame> innerFrame = element->ownerDocument()->frame(); 465 ASSERT(innerFrame); 466 467 if (m_page->dragCaretController()->hasCaret() && !dispatchTextInputEventFor(innerFrame.get(), dragData)) 468 return true; 469 470 if (dragData->containsColor()) { 471 Color color = dragData->asColor(); 472 if (!color.isValid()) 473 return false; 474 RefPtr<Range> innerRange = innerFrame->selection()->toNormalizedRange(); 475 RefPtr<MutableStylePropertySet> style = MutableStylePropertySet::create(); 476 style->setProperty(CSSPropertyColor, color.serialized(), false); 477 if (!innerFrame->editor().shouldApplyStyle(style.get(), innerRange.get())) 478 return false; 479 m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData); 480 innerFrame->editor().applyStyle(style.get(), EditActionSetColor); 481 return true; 482 } 483 484 if (dragData->containsFiles() && fileInput) { 485 // fileInput should be the element we hit tested for, unless it was made 486 // display:none in a drop event handler. 487 ASSERT(fileInput == element || !fileInput->renderer()); 488 if (fileInput->isDisabledFormControl()) 489 return false; 490 491 return fileInput->receiveDroppedFiles(dragData); 492 } 493 494 if (!m_page->dragController()->canProcessDrag(dragData)) { 495 m_page->dragCaretController()->clear(); 496 return false; 497 } 498 499 VisibleSelection dragCaret = m_page->dragCaretController()->caretPosition(); 500 m_page->dragCaretController()->clear(); 501 RefPtr<Range> range = dragCaret.toNormalizedRange(); 502 RefPtr<Element> rootEditableElement = innerFrame->selection()->rootEditableElement(); 503 504 // For range to be null a WebKit client must have done something bad while 505 // manually controlling drag behaviour 506 if (!range) 507 return false; 508 CachedResourceLoader* cachedResourceLoader = range->ownerDocument()->cachedResourceLoader(); 509 ResourceCacheValidationSuppressor validationSuppressor(cachedResourceLoader); 510 if (dragIsMove(innerFrame->selection(), dragData) || dragCaret.isContentRichlyEditable()) { 511 bool chosePlainText = false; 512 RefPtr<DocumentFragment> fragment = documentFragmentFromDragData(dragData, innerFrame.get(), range, true, chosePlainText); 513 if (!fragment || !innerFrame->editor().shouldInsertFragment(fragment, range, EditorInsertActionDropped)) { 514 return false; 515 } 516 517 m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData); 518 if (dragIsMove(innerFrame->selection(), dragData)) { 519 // NSTextView behavior is to always smart delete on moving a selection, 520 // but only to smart insert if the selection granularity is word granularity. 521 bool smartDelete = innerFrame->editor().smartInsertDeleteEnabled(); 522 bool smartInsert = smartDelete && innerFrame->selection()->granularity() == WordGranularity && dragData->canSmartReplace(); 523 applyCommand(MoveSelectionCommand::create(fragment, dragCaret.base(), smartInsert, smartDelete)); 524 } else { 525 if (setSelectionToDragCaret(innerFrame.get(), dragCaret, range, point)) { 526 ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::PreventNesting; 527 if (dragData->canSmartReplace()) 528 options |= ReplaceSelectionCommand::SmartReplace; 529 if (chosePlainText) 530 options |= ReplaceSelectionCommand::MatchStyle; 531 applyCommand(ReplaceSelectionCommand::create(m_documentUnderMouse.get(), fragment, options)); 532 } 533 } 534 } else { 535 String text = dragData->asPlainText(innerFrame.get()); 536 if (text.isEmpty() || !innerFrame->editor().shouldInsertText(text, range.get(), EditorInsertActionDropped)) { 537 return false; 538 } 539 540 m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData); 541 if (setSelectionToDragCaret(innerFrame.get(), dragCaret, range, point)) 542 applyCommand(ReplaceSelectionCommand::create(m_documentUnderMouse.get(), createFragmentFromText(range.get(), text), ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::MatchStyle | ReplaceSelectionCommand::PreventNesting)); 543 } 544 545 if (rootEditableElement) { 546 if (Frame* frame = rootEditableElement->document()->frame()) 547 frame->eventHandler()->updateDragStateAfterEditDragIfNeeded(rootEditableElement.get()); 548 } 549 550 return true; 551} 552 553bool DragController::canProcessDrag(DragData* dragData) 554{ 555 ASSERT(dragData); 556 557 if (!dragData->containsCompatibleContent()) 558 return false; 559 560 IntPoint point = m_page->mainFrame()->view()->windowToContents(dragData->clientPosition()); 561 HitTestResult result = HitTestResult(point); 562 if (!m_page->mainFrame()->contentRenderer()) 563 return false; 564 565 result = m_page->mainFrame()->eventHandler()->hitTestResultAtPoint(point, HitTestRequest::ReadOnly | HitTestRequest::Active); 566 567 if (!result.innerNonSharedNode()) 568 return false; 569 570 if (dragData->containsFiles() && asFileInput(result.innerNonSharedNode())) 571 return true; 572 573 if (result.innerNonSharedNode()->isPluginElement()) { 574 HTMLPlugInElement* plugin = static_cast<HTMLPlugInElement*>(result.innerNonSharedNode()); 575 if (!plugin->canProcessDrag() && !result.innerNonSharedNode()->rendererIsEditable()) 576 return false; 577 } else if (!result.innerNonSharedNode()->rendererIsEditable()) 578 return false; 579 580 if (m_didInitiateDrag && m_documentUnderMouse == m_dragInitiator && result.isSelected()) 581 return false; 582 583 return true; 584} 585 586static DragOperation defaultOperationForDrag(DragOperation srcOpMask) 587{ 588 // This is designed to match IE's operation fallback for the case where 589 // the page calls preventDefault() in a drag event but doesn't set dropEffect. 590 if (srcOpMask == DragOperationEvery) 591 return DragOperationCopy; 592 if (srcOpMask == DragOperationNone) 593 return DragOperationNone; 594 if (srcOpMask & DragOperationMove || srcOpMask & DragOperationGeneric) 595 return DragOperationMove; 596 if (srcOpMask & DragOperationCopy) 597 return DragOperationCopy; 598 if (srcOpMask & DragOperationLink) 599 return DragOperationLink; 600 601 // FIXME: Does IE really return "generic" even if no operations were allowed by the source? 602 return DragOperationGeneric; 603} 604 605bool DragController::tryDHTMLDrag(DragData* dragData, DragOperation& operation) 606{ 607 ASSERT(dragData); 608 ASSERT(m_documentUnderMouse); 609 RefPtr<Frame> mainFrame = m_page->mainFrame(); 610 RefPtr<FrameView> viewProtector = mainFrame->view(); 611 if (!viewProtector) 612 return false; 613 614 ClipboardAccessPolicy policy = m_documentUnderMouse->securityOrigin()->isLocal() ? ClipboardReadable : ClipboardTypesReadable; 615 RefPtr<Clipboard> clipboard = Clipboard::create(policy, dragData, mainFrame.get()); 616 DragOperation srcOpMask = dragData->draggingSourceOperationMask(); 617 clipboard->setSourceOperation(srcOpMask); 618 619 PlatformMouseEvent event = createMouseEvent(dragData); 620 if (!mainFrame->eventHandler()->updateDragAndDrop(event, clipboard.get())) { 621 clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security 622 return false; 623 } 624 625 operation = clipboard->destinationOperation(); 626 if (clipboard->dropEffectIsUninitialized()) 627 operation = defaultOperationForDrag(srcOpMask); 628 else if (!(srcOpMask & operation)) { 629 // The element picked an operation which is not supported by the source 630 operation = DragOperationNone; 631 } 632 633 clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security 634 return true; 635} 636 637Element* DragController::draggableElement(const Frame* sourceFrame, Element* startElement, const IntPoint& dragOrigin, DragState& state) const 638{ 639 state.type = (sourceFrame->selection()->contains(dragOrigin)) ? DragSourceActionSelection : DragSourceActionNone; 640 if (!startElement) 641 return 0; 642 643 for (const RenderObject* renderer = startElement->renderer(); renderer; renderer = renderer->parent()) { 644 Node* node = renderer->nonPseudoNode(); 645 if (!node) 646 // Anonymous render blocks don't correspond to actual DOM nodes, so we skip over them 647 // for the purposes of finding a draggable node. 648 continue; 649 if (!(state.type & DragSourceActionSelection) && node->isTextNode() && node->canStartSelection()) 650 // In this case we have a click in the unselected portion of text. If this text is 651 // selectable, we want to start the selection process instead of looking for a parent 652 // to try to drag. 653 return 0; 654 if (node->isElementNode()) { 655 EUserDrag dragMode = renderer->style()->userDrag(); 656 if ((m_dragSourceAction & DragSourceActionDHTML) && dragMode == DRAG_ELEMENT) { 657 state.type = static_cast<DragSourceAction>(state.type | DragSourceActionDHTML); 658 return toElement(node); 659 } 660 if (dragMode == DRAG_AUTO) { 661 if ((m_dragSourceAction & DragSourceActionImage) 662 && node->hasTagName(HTMLNames::imgTag) 663 && sourceFrame->settings() 664 && sourceFrame->settings()->loadsImagesAutomatically()) { 665 state.type = static_cast<DragSourceAction>(state.type | DragSourceActionImage); 666 return toElement(node); 667 } 668 if ((m_dragSourceAction & DragSourceActionLink) 669 && node->hasTagName(HTMLNames::aTag) 670 && static_cast<HTMLAnchorElement*>(node)->isLiveLink()) { 671 state.type = static_cast<DragSourceAction>(state.type | DragSourceActionLink); 672 return toElement(node); 673 } 674 } 675 } 676 } 677 678 // We either have nothing to drag or we have a selection and we're not over a draggable element. 679 return (state.type & DragSourceActionSelection) ? startElement : 0; 680} 681 682static CachedImage* getCachedImage(Element* element) 683{ 684 ASSERT(element); 685 RenderObject* renderer = element->renderer(); 686 if (!renderer || !renderer->isRenderImage()) 687 return 0; 688 RenderImage* image = toRenderImage(renderer); 689 return image->cachedImage(); 690} 691 692static Image* getImage(Element* element) 693{ 694 ASSERT(element); 695 CachedImage* cachedImage = getCachedImage(element); 696 // Don't use cachedImage->imageForRenderer() here as that may return BitmapImages for cached SVG Images. 697 // Users of getImage() want access to the SVGImage, in order to figure out the filename extensions, 698 // which would be empty when asking the cached BitmapImages. 699 return (cachedImage && !cachedImage->errorOccurred()) ? 700 cachedImage->image() : 0; 701} 702 703static void prepareClipboardForImageDrag(Frame* source, Clipboard* clipboard, Element* node, const KURL& linkURL, const KURL& imageURL, const String& label) 704{ 705 if (node->isContentRichlyEditable()) { 706 RefPtr<Range> range = source->document()->createRange(); 707 range->selectNode(node, ASSERT_NO_EXCEPTION); 708 source->selection()->setSelection(VisibleSelection(range.get(), DOWNSTREAM)); 709 } 710 clipboard->declareAndWriteDragImage(node, !linkURL.isEmpty() ? linkURL : imageURL, label, source); 711} 712 713static IntPoint dragLocForDHTMLDrag(const IntPoint& mouseDraggedPoint, const IntPoint& dragOrigin, const IntPoint& dragImageOffset, bool isLinkImage) 714{ 715 // dragImageOffset is the cursor position relative to the lower-left corner of the image. 716#if PLATFORM(MAC) 717 // We add in the Y dimension because we are a flipped view, so adding moves the image down. 718 const int yOffset = dragImageOffset.y(); 719#else 720 const int yOffset = -dragImageOffset.y(); 721#endif 722 723 if (isLinkImage) 724 return IntPoint(mouseDraggedPoint.x() - dragImageOffset.x(), mouseDraggedPoint.y() + yOffset); 725 726 return IntPoint(dragOrigin.x() - dragImageOffset.x(), dragOrigin.y() + yOffset); 727} 728 729static IntPoint dragLocForSelectionDrag(Frame* src) 730{ 731 IntRect draggingRect = enclosingIntRect(src->selection()->bounds()); 732 int xpos = draggingRect.maxX(); 733 xpos = draggingRect.x() < xpos ? draggingRect.x() : xpos; 734 int ypos = draggingRect.maxY(); 735#if PLATFORM(MAC) 736 // Deal with flipped coordinates on Mac 737 ypos = draggingRect.y() > ypos ? draggingRect.y() : ypos; 738#else 739 ypos = draggingRect.y() < ypos ? draggingRect.y() : ypos; 740#endif 741 return IntPoint(xpos, ypos); 742} 743 744bool DragController::startDrag(Frame* src, const DragState& state, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin) 745{ 746 ASSERT(src); 747 748 if (!src->view() || !src->contentRenderer()) 749 return false; 750 751 HitTestResult hitTestResult = src->eventHandler()->hitTestResultAtPoint(dragOrigin, HitTestRequest::ReadOnly | HitTestRequest::Active); 752 if (!state.source->contains(hitTestResult.innerNode())) 753 // The original node being dragged isn't under the drag origin anymore... maybe it was 754 // hidden or moved out from under the cursor. Regardless, we don't want to start a drag on 755 // something that's not actually under the drag origin. 756 return false; 757 KURL linkURL = hitTestResult.absoluteLinkURL(); 758 KURL imageURL = hitTestResult.absoluteImageURL(); 759 760 IntPoint mouseDraggedPoint = src->view()->windowToContents(dragEvent.position()); 761 762 m_draggingImageURL = KURL(); 763 m_sourceDragOperation = srcOp; 764 765 DragImageRef dragImage = 0; 766 IntPoint dragLoc(0, 0); 767 IntPoint dragImageOffset(0, 0); 768 769 Clipboard* clipboard = state.clipboard.get(); 770 if (state.type == DragSourceActionDHTML) 771 dragImage = clipboard->createDragImage(dragImageOffset); 772 if (state.type == DragSourceActionSelection || !imageURL.isEmpty() || !linkURL.isEmpty()) 773 // Selection, image, and link drags receive a default set of allowed drag operations that 774 // follows from: 775 // http://trac.webkit.org/browser/trunk/WebKit/mac/WebView/WebHTMLView.mm?rev=48526#L3430 776 m_sourceDragOperation = static_cast<DragOperation>(m_sourceDragOperation | DragOperationGeneric | DragOperationCopy); 777 778 // We allow DHTML/JS to set the drag image, even if its a link, image or text we're dragging. 779 // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp. 780 if (dragImage) { 781 dragLoc = dragLocForDHTMLDrag(mouseDraggedPoint, dragOrigin, dragImageOffset, !linkURL.isEmpty()); 782 m_dragOffset = dragImageOffset; 783 } 784 785 bool startedDrag = true; // optimism - we almost always manage to start the drag 786 787 Element* element = state.source.get(); 788 789 Image* image = getImage(element); 790 if (state.type == DragSourceActionSelection) { 791 if (!clipboard->hasData()) { 792 RefPtr<Range> selectionRange = src->selection()->toNormalizedRange(); 793 ASSERT(selectionRange); 794 795 src->editor().willWriteSelectionToPasteboard(selectionRange.get()); 796 797 if (enclosingTextFormControl(src->selection()->start())) 798 clipboard->writePlainText(src->editor().selectedTextForClipboard()); 799 else 800 clipboard->writeRange(selectionRange.get(), src); 801 802 src->editor().didWriteSelectionToPasteboard(); 803 } 804 m_client->willPerformDragSourceAction(DragSourceActionSelection, dragOrigin, clipboard); 805 if (!dragImage) { 806 dragImage = dissolveDragImageToFraction(src->dragImageForSelection(), DragImageAlpha); 807 dragLoc = dragLocForSelectionDrag(src); 808 m_dragOffset = IntPoint(dragOrigin.x() - dragLoc.x(), dragOrigin.y() - dragLoc.y()); 809 } 810 doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); 811 } else if (!src->document()->securityOrigin()->canDisplay(linkURL)) { 812 src->document()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, "Not allowed to drag local resource: " + linkURL.stringCenterEllipsizedToLength()); 813 startedDrag = false; 814 } else if (!imageURL.isEmpty() && element && image && !image->isNull() 815 && (m_dragSourceAction & DragSourceActionImage)) { 816 // We shouldn't be starting a drag for an image that can't provide an extension. 817 // This is an early detection for problems encountered later upon drop. 818 ASSERT(!image->filenameExtension().isEmpty()); 819 if (!clipboard->hasData()) { 820 m_draggingImageURL = imageURL; 821 prepareClipboardForImageDrag(src, clipboard, element, linkURL, imageURL, hitTestResult.altDisplayString()); 822 } 823 824 m_client->willPerformDragSourceAction(DragSourceActionImage, dragOrigin, clipboard); 825 826 if (!dragImage) { 827 IntRect imageRect = hitTestResult.imageRect(); 828 imageRect.setLocation(m_page->mainFrame()->view()->rootViewToContents(src->view()->contentsToRootView(imageRect.location()))); 829 doImageDrag(element, dragOrigin, hitTestResult.imageRect(), clipboard, src, m_dragOffset); 830 } else 831 // DHTML defined drag image 832 doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); 833 834 } else if (!linkURL.isEmpty() && (m_dragSourceAction & DragSourceActionLink)) { 835 if (!clipboard->hasData()) 836 // Simplify whitespace so the title put on the clipboard resembles what the user sees 837 // on the web page. This includes replacing newlines with spaces. 838 clipboard->writeURL(linkURL, hitTestResult.textContent().simplifyWhiteSpace(), src); 839 840 if (src->selection()->isCaret() && src->selection()->isContentEditable()) { 841 // a user can initiate a drag on a link without having any text 842 // selected. In this case, we should expand the selection to 843 // the enclosing anchor element 844 Position pos = src->selection()->base(); 845 Node* node = enclosingAnchorElement(pos); 846 if (node) 847 src->selection()->setSelection(VisibleSelection::selectionFromContentsOfNode(node)); 848 } 849 850 m_client->willPerformDragSourceAction(DragSourceActionLink, dragOrigin, clipboard); 851 if (!dragImage) { 852 dragImage = createDragImageForLink(linkURL, hitTestResult.textContent(), src->settings() ? src->settings()->fontRenderingMode() : NormalRenderingMode); 853 IntSize size = dragImageSize(dragImage); 854 m_dragOffset = IntPoint(-size.width() / 2, -LinkDragBorderInset); 855 dragLoc = IntPoint(mouseDraggedPoint.x() + m_dragOffset.x(), mouseDraggedPoint.y() + m_dragOffset.y()); 856 } 857 doSystemDrag(dragImage, dragLoc, mouseDraggedPoint, clipboard, src, true); 858 } else if (state.type == DragSourceActionDHTML) { 859 if (dragImage) { 860 ASSERT(m_dragSourceAction & DragSourceActionDHTML); 861 m_client->willPerformDragSourceAction(DragSourceActionDHTML, dragOrigin, clipboard); 862 doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); 863 } else 864 startedDrag = false; 865 } else { 866 // draggableElement() determined an image or link node was draggable, but it turns out the 867 // image or link had no URL, so there is nothing to drag. 868 startedDrag = false; 869 } 870 871 if (dragImage) 872 deleteDragImage(dragImage); 873 return startedDrag; 874} 875 876void DragController::doImageDrag(Element* element, const IntPoint& dragOrigin, const IntRect& rect, Clipboard* clipboard, Frame* frame, IntPoint& dragImageOffset) 877{ 878 IntPoint mouseDownPoint = dragOrigin; 879 DragImageRef dragImage = 0; 880 IntPoint origin; 881 882 Image* image = getImage(element); 883 if (image && image->size().height() * image->size().width() <= MaxOriginalImageArea 884 && (dragImage = createDragImageFromImage(image, element->renderer() ? element->renderer()->shouldRespectImageOrientation() : DoNotRespectImageOrientation))) { 885 IntSize originalSize = rect.size(); 886 origin = rect.location(); 887 888 dragImage = fitDragImageToMaxSize(dragImage, rect.size(), maxDragImageSize()); 889 dragImage = dissolveDragImageToFraction(dragImage, DragImageAlpha); 890 IntSize newSize = dragImageSize(dragImage); 891 892 // Properly orient the drag image and orient it differently if it's smaller than the original 893 float scale = newSize.width() / (float)originalSize.width(); 894 float dx = origin.x() - mouseDownPoint.x(); 895 dx *= scale; 896 origin.setX((int)(dx + 0.5)); 897#if PLATFORM(MAC) 898 //Compensate for accursed flipped coordinates in cocoa 899 origin.setY(origin.y() + originalSize.height()); 900#endif 901 float dy = origin.y() - mouseDownPoint.y(); 902 dy *= scale; 903 origin.setY((int)(dy + 0.5)); 904 } else { 905 if (CachedImage* cachedImage = getCachedImage(element)) { 906 dragImage = createDragImageIconForCachedImageFilename(cachedImage->response().suggestedFilename()); 907 if (dragImage) 908 origin = IntPoint(DragIconRightInset - dragImageSize(dragImage).width(), DragIconBottomInset); 909 } 910 } 911 912 dragImageOffset = mouseDownPoint + origin; 913 doSystemDrag(dragImage, dragImageOffset, dragOrigin, clipboard, frame, false); 914 915 deleteDragImage(dragImage); 916} 917 918void DragController::doSystemDrag(DragImageRef image, const IntPoint& dragLoc, const IntPoint& eventPos, Clipboard* clipboard, Frame* frame, bool forLink) 919{ 920 m_didInitiateDrag = true; 921 m_dragInitiator = frame->document(); 922 // Protect this frame and view, as a load may occur mid drag and attempt to unload this frame 923 RefPtr<Frame> frameProtector = m_page->mainFrame(); 924 RefPtr<FrameView> viewProtector = frameProtector->view(); 925 m_client->startDrag(image, viewProtector->rootViewToContents(frame->view()->contentsToRootView(dragLoc)), 926 viewProtector->rootViewToContents(frame->view()->contentsToRootView(eventPos)), clipboard, frameProtector.get(), forLink); 927 // DragClient::startDrag can cause our Page to dispear, deallocating |this|. 928 if (!frameProtector->page()) 929 return; 930 931 cleanupAfterSystemDrag(); 932} 933 934// Manual drag caret manipulation 935void DragController::placeDragCaret(const IntPoint& windowPoint) 936{ 937 mouseMovedIntoDocument(m_page->mainFrame()->documentAtPoint(windowPoint)); 938 if (!m_documentUnderMouse) 939 return; 940 Frame* frame = m_documentUnderMouse->frame(); 941 FrameView* frameView = frame->view(); 942 if (!frameView) 943 return; 944 IntPoint framePoint = frameView->windowToContents(windowPoint); 945 946 m_page->dragCaretController()->setCaretPosition(frame->visiblePositionForPoint(framePoint)); 947} 948 949} // namespace WebCore 950 951#endif // ENABLE(DRAG_SUPPORT) 952