1 /* 2 * Copyright (C) 2007, 2009, 2010 Apple Inc. All rights reserved. 3 * Copyright (C) 2008 Google Inc. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27 #include "config.h" 28 #include "core/page/DragController.h" 29 30 #include "HTMLNames.h" 31 #include "bindings/v8/ExceptionStatePlaceholder.h" 32 #include "core/dom/Clipboard.h" 33 #include "core/dom/ClipboardAccessPolicy.h" 34 #include "core/dom/Document.h" 35 #include "core/dom/DocumentFragment.h" 36 #include "core/dom/Element.h" 37 #include "core/dom/Node.h" 38 #include "core/dom/Text.h" 39 #include "core/dom/shadow/ShadowRoot.h" 40 #include "core/editing/Editor.h" 41 #include "core/editing/FrameSelection.h" 42 #include "core/editing/MoveSelectionCommand.h" 43 #include "core/editing/ReplaceSelectionCommand.h" 44 #include "core/editing/htmlediting.h" 45 #include "core/editing/markup.h" 46 #include "core/events/TextEvent.h" 47 #include "core/fetch/ImageResource.h" 48 #include "core/fetch/ResourceFetcher.h" 49 #include "core/frame/Frame.h" 50 #include "core/frame/FrameView.h" 51 #include "core/html/HTMLAnchorElement.h" 52 #include "core/html/HTMLFormElement.h" 53 #include "core/html/HTMLInputElement.h" 54 #include "core/html/HTMLPlugInElement.h" 55 #include "core/loader/FrameLoadRequest.h" 56 #include "core/loader/FrameLoader.h" 57 #include "core/page/DragClient.h" 58 #include "core/page/DragData.h" 59 #include "core/page/DragSession.h" 60 #include "core/page/DragState.h" 61 #include "core/page/EventHandler.h" 62 #include "core/page/Page.h" 63 #include "core/frame/Settings.h" 64 #include "core/platform/DragImage.h" 65 #include "core/platform/chromium/ChromiumDataObject.h" 66 #include "core/rendering/HitTestRequest.h" 67 #include "core/rendering/HitTestResult.h" 68 #include "core/rendering/RenderImage.h" 69 #include "core/rendering/RenderTheme.h" 70 #include "core/rendering/RenderView.h" 71 #include "platform/geometry/FloatRect.h" 72 #include "platform/graphics/Image.h" 73 #include "platform/graphics/ImageOrientation.h" 74 #include "platform/network/ResourceRequest.h" 75 #include "platform/weborigin/SecurityOrigin.h" 76 #include "wtf/CurrentTime.h" 77 #include "wtf/OwnPtr.h" 78 #include "wtf/PassOwnPtr.h" 79 #include "wtf/RefPtr.h" 80 81 #if OS(WIN) 82 #include <windows.h> 83 #endif 84 85 namespace WebCore { 86 87 const int DragController::DragIconRightInset = 7; 88 const int DragController::DragIconBottomInset = 3; 89 90 static const int MaxOriginalImageArea = 1500 * 1500; 91 static const int LinkDragBorderInset = 2; 92 static const float DragImageAlpha = 0.75f; 93 94 #if !ASSERT_DISABLED 95 static bool dragTypeIsValid(DragSourceAction action) 96 { 97 switch (action) { 98 case DragSourceActionDHTML: 99 case DragSourceActionImage: 100 case DragSourceActionLink: 101 case DragSourceActionSelection: 102 return true; 103 case DragSourceActionNone: 104 return false; 105 } 106 // Make sure MSVC doesn't complain that not all control paths return a value. 107 return false; 108 } 109 #endif 110 111 static PlatformMouseEvent createMouseEvent(DragData* dragData) 112 { 113 bool shiftKey, ctrlKey, altKey, metaKey; 114 shiftKey = ctrlKey = altKey = metaKey = false; 115 int keyState = dragData->modifierKeyState(); 116 shiftKey = static_cast<bool>(keyState & PlatformEvent::ShiftKey); 117 ctrlKey = static_cast<bool>(keyState & PlatformEvent::CtrlKey); 118 altKey = static_cast<bool>(keyState & PlatformEvent::AltKey); 119 metaKey = static_cast<bool>(keyState & PlatformEvent::MetaKey); 120 121 return PlatformMouseEvent(dragData->clientPosition(), dragData->globalPosition(), 122 LeftButton, PlatformEvent::MouseMoved, 0, shiftKey, ctrlKey, altKey, 123 metaKey, currentTime()); 124 } 125 126 static PassRefPtr<Clipboard> createDraggingClipboard(ClipboardAccessPolicy policy, DragData* dragData) 127 { 128 return Clipboard::create(Clipboard::DragAndDrop, policy, dragData->platformData()); 129 } 130 131 DragController::DragController(Page* page, DragClient* client) 132 : m_page(page) 133 , m_client(client) 134 , m_documentUnderMouse(0) 135 , m_dragInitiator(0) 136 , m_fileInputElementUnderMouse(0) 137 , m_documentIsHandlingDrag(false) 138 , m_dragDestinationAction(DragDestinationActionNone) 139 , m_didInitiateDrag(false) 140 { 141 ASSERT(m_client); 142 } 143 144 DragController::~DragController() 145 { 146 } 147 148 PassOwnPtr<DragController> DragController::create(Page* page, DragClient* client) 149 { 150 return adoptPtr(new DragController(page, client)); 151 } 152 153 static PassRefPtr<DocumentFragment> documentFragmentFromDragData(DragData* dragData, Frame* frame, RefPtr<Range> context, 154 bool allowPlainText, bool& chosePlainText) 155 { 156 ASSERT(dragData); 157 chosePlainText = false; 158 159 Document& document = context->ownerDocument(); 160 if (dragData->containsCompatibleContent()) { 161 if (PassRefPtr<DocumentFragment> fragment = dragData->asFragment(frame, context, allowPlainText, chosePlainText)) 162 return fragment; 163 164 if (dragData->containsURL(DragData::DoNotConvertFilenames)) { 165 String title; 166 String url = dragData->asURL(DragData::DoNotConvertFilenames, &title); 167 if (!url.isEmpty()) { 168 RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::create(document); 169 anchor->setHref(url); 170 if (title.isEmpty()) { 171 // Try the plain text first because the url might be normalized or escaped. 172 if (dragData->containsPlainText()) 173 title = dragData->asPlainText(); 174 if (title.isEmpty()) 175 title = url; 176 } 177 RefPtr<Node> anchorText = document.createTextNode(title); 178 anchor->appendChild(anchorText); 179 RefPtr<DocumentFragment> fragment = document.createDocumentFragment(); 180 fragment->appendChild(anchor); 181 return fragment.release(); 182 } 183 } 184 } 185 if (allowPlainText && dragData->containsPlainText()) { 186 chosePlainText = true; 187 return createFragmentFromText(context.get(), dragData->asPlainText()).get(); 188 } 189 190 return 0; 191 } 192 193 bool DragController::dragIsMove(FrameSelection& selection, DragData* dragData) 194 { 195 return m_documentUnderMouse == m_dragInitiator && selection.isContentEditable() && selection.isRange() && !isCopyKeyDown(dragData); 196 } 197 198 // FIXME: This method is poorly named. We're just clearing the selection from the document this drag is exiting. 199 void DragController::cancelDrag() 200 { 201 m_page->dragCaretController().clear(); 202 } 203 204 void DragController::dragEnded() 205 { 206 m_dragInitiator = 0; 207 m_didInitiateDrag = false; 208 m_page->dragCaretController().clear(); 209 } 210 211 DragSession DragController::dragEntered(DragData* dragData) 212 { 213 return dragEnteredOrUpdated(dragData); 214 } 215 216 void DragController::dragExited(DragData* dragData) 217 { 218 ASSERT(dragData); 219 Frame* mainFrame = m_page->mainFrame(); 220 221 if (RefPtr<FrameView> v = mainFrame->view()) { 222 ClipboardAccessPolicy policy = (!m_documentUnderMouse || m_documentUnderMouse->securityOrigin()->isLocal()) ? ClipboardReadable : ClipboardTypesReadable; 223 RefPtr<Clipboard> clipboard = createDraggingClipboard(policy, dragData); 224 clipboard->setSourceOperation(dragData->draggingSourceOperationMask()); 225 mainFrame->eventHandler().cancelDragAndDrop(createMouseEvent(dragData), clipboard.get()); 226 clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security 227 } 228 mouseMovedIntoDocument(0); 229 if (m_fileInputElementUnderMouse) 230 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false); 231 m_fileInputElementUnderMouse = 0; 232 } 233 234 DragSession DragController::dragUpdated(DragData* dragData) 235 { 236 return dragEnteredOrUpdated(dragData); 237 } 238 239 bool DragController::performDrag(DragData* dragData) 240 { 241 ASSERT(dragData); 242 m_documentUnderMouse = m_page->mainFrame()->documentAtPoint(dragData->clientPosition()); 243 if ((m_dragDestinationAction & DragDestinationActionDHTML) && m_documentIsHandlingDrag) { 244 RefPtr<Frame> mainFrame = m_page->mainFrame(); 245 bool preventedDefault = false; 246 if (mainFrame->view()) { 247 // Sending an event can result in the destruction of the view and part. 248 RefPtr<Clipboard> clipboard = createDraggingClipboard(ClipboardReadable, dragData); 249 clipboard->setSourceOperation(dragData->draggingSourceOperationMask()); 250 preventedDefault = mainFrame->eventHandler().performDragAndDrop(createMouseEvent(dragData), clipboard.get()); 251 clipboard->setAccessPolicy(ClipboardNumb); // Invalidate clipboard here for security 252 } 253 if (preventedDefault) { 254 m_documentUnderMouse = 0; 255 return true; 256 } 257 } 258 259 if ((m_dragDestinationAction & DragDestinationActionEdit) && concludeEditDrag(dragData)) { 260 m_documentUnderMouse = 0; 261 return true; 262 } 263 264 m_documentUnderMouse = 0; 265 266 if (operationForLoad(dragData) == DragOperationNone) 267 return false; 268 269 m_page->mainFrame()->loader().load(FrameLoadRequest(0, ResourceRequest(dragData->asURL()))); 270 return true; 271 } 272 273 void DragController::mouseMovedIntoDocument(Document* newDocument) 274 { 275 if (m_documentUnderMouse == newDocument) 276 return; 277 278 // If we were over another document clear the selection 279 if (m_documentUnderMouse) 280 cancelDrag(); 281 m_documentUnderMouse = newDocument; 282 } 283 284 DragSession DragController::dragEnteredOrUpdated(DragData* dragData) 285 { 286 ASSERT(dragData); 287 ASSERT(m_page->mainFrame()); 288 mouseMovedIntoDocument(m_page->mainFrame()->documentAtPoint(dragData->clientPosition())); 289 290 m_dragDestinationAction = m_client->actionMaskForDrag(dragData); 291 if (m_dragDestinationAction == DragDestinationActionNone) { 292 cancelDrag(); // FIXME: Why not call mouseMovedIntoDocument(0)? 293 return DragSession(); 294 } 295 296 DragSession dragSession; 297 m_documentIsHandlingDrag = tryDocumentDrag(dragData, m_dragDestinationAction, dragSession); 298 if (!m_documentIsHandlingDrag && (m_dragDestinationAction & DragDestinationActionLoad)) 299 dragSession.operation = operationForLoad(dragData); 300 return dragSession; 301 } 302 303 static HTMLInputElement* asFileInput(Node* node) 304 { 305 ASSERT(node); 306 for (; node; node = node->shadowHost()) { 307 if (node->hasTagName(HTMLNames::inputTag) && toHTMLInputElement(node)->isFileUpload()) 308 return toHTMLInputElement(node); 309 } 310 return 0; 311 } 312 313 // This can return null if an empty document is loaded. 314 static Element* elementUnderMouse(Document* documentUnderMouse, const IntPoint& p) 315 { 316 Frame* frame = documentUnderMouse->frame(); 317 float zoomFactor = frame ? frame->pageZoomFactor() : 1; 318 LayoutPoint point = roundedLayoutPoint(FloatPoint(p.x() * zoomFactor, p.y() * zoomFactor)); 319 320 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::ConfusingAndOftenMisusedDisallowShadowContent); 321 HitTestResult result(point); 322 documentUnderMouse->renderView()->hitTest(request, result); 323 324 Node* n = result.innerNode(); 325 while (n && !n->isElementNode()) 326 n = n->parentOrShadowHostNode(); 327 if (n) 328 n = n->deprecatedShadowAncestorNode(); 329 330 return toElement(n); 331 } 332 333 bool DragController::tryDocumentDrag(DragData* dragData, DragDestinationAction actionMask, DragSession& dragSession) 334 { 335 ASSERT(dragData); 336 337 if (!m_documentUnderMouse) 338 return false; 339 340 if (m_dragInitiator && !m_documentUnderMouse->securityOrigin()->canReceiveDragData(m_dragInitiator->securityOrigin())) 341 return false; 342 343 bool isHandlingDrag = false; 344 if (actionMask & DragDestinationActionDHTML) { 345 isHandlingDrag = tryDHTMLDrag(dragData, dragSession.operation); 346 // Do not continue if m_documentUnderMouse has been reset by tryDHTMLDrag. 347 // tryDHTMLDrag fires dragenter event. The event listener that listens 348 // to this event may create a nested message loop (open a modal dialog), 349 // which could process dragleave event and reset m_documentUnderMouse in 350 // dragExited. 351 if (!m_documentUnderMouse) 352 return false; 353 } 354 355 // It's unclear why this check is after tryDHTMLDrag. 356 // We send drag events in tryDHTMLDrag and that may be the reason. 357 RefPtr<FrameView> frameView = m_documentUnderMouse->view(); 358 if (!frameView) 359 return false; 360 361 if (isHandlingDrag) { 362 m_page->dragCaretController().clear(); 363 return true; 364 } 365 366 if ((actionMask & DragDestinationActionEdit) && canProcessDrag(dragData)) { 367 IntPoint point = frameView->windowToContents(dragData->clientPosition()); 368 Element* element = elementUnderMouse(m_documentUnderMouse.get(), point); 369 if (!element) 370 return false; 371 372 HTMLInputElement* elementAsFileInput = asFileInput(element); 373 if (m_fileInputElementUnderMouse != elementAsFileInput) { 374 if (m_fileInputElementUnderMouse) 375 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false); 376 m_fileInputElementUnderMouse = elementAsFileInput; 377 } 378 379 if (!m_fileInputElementUnderMouse) 380 m_page->dragCaretController().setCaretPosition(m_documentUnderMouse->frame()->visiblePositionForPoint(point)); 381 382 Frame* innerFrame = element->document().frame(); 383 dragSession.operation = dragIsMove(innerFrame->selection(), dragData) ? DragOperationMove : DragOperationCopy; 384 dragSession.mouseIsOverFileInput = m_fileInputElementUnderMouse; 385 dragSession.numberOfItemsToBeAccepted = 0; 386 387 unsigned numberOfFiles = dragData->numberOfFiles(); 388 if (m_fileInputElementUnderMouse) { 389 if (m_fileInputElementUnderMouse->isDisabledFormControl()) 390 dragSession.numberOfItemsToBeAccepted = 0; 391 else if (m_fileInputElementUnderMouse->multiple()) 392 dragSession.numberOfItemsToBeAccepted = numberOfFiles; 393 else if (numberOfFiles > 1) 394 dragSession.numberOfItemsToBeAccepted = 0; 395 else 396 dragSession.numberOfItemsToBeAccepted = 1; 397 398 if (!dragSession.numberOfItemsToBeAccepted) 399 dragSession.operation = DragOperationNone; 400 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(dragSession.numberOfItemsToBeAccepted); 401 } else { 402 // We are not over a file input element. The dragged item(s) will only 403 // be loaded into the view the number of dragged items is 1. 404 dragSession.numberOfItemsToBeAccepted = numberOfFiles != 1 ? 0 : 1; 405 } 406 407 return true; 408 } 409 410 // We are not over an editable region. Make sure we're clearing any prior drag cursor. 411 m_page->dragCaretController().clear(); 412 if (m_fileInputElementUnderMouse) 413 m_fileInputElementUnderMouse->setCanReceiveDroppedFiles(false); 414 m_fileInputElementUnderMouse = 0; 415 return false; 416 } 417 418 DragOperation DragController::operationForLoad(DragData* dragData) 419 { 420 ASSERT(dragData); 421 Document* doc = m_page->mainFrame()->documentAtPoint(dragData->clientPosition()); 422 423 if (doc && (m_didInitiateDrag || doc->isPluginDocument() || doc->rendererIsEditable())) 424 return DragOperationNone; 425 return dragOperation(dragData); 426 } 427 428 static 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 439 bool DragController::dispatchTextInputEventFor(Frame* innerFrame, DragData* dragData) 440 { 441 ASSERT(m_page->dragCaretController().hasCaret()); 442 String text = m_page->dragCaretController().isContentRichlyEditable() ? "" : dragData->asPlainText(); 443 Node* target = innerFrame->editor().findEventTargetFrom(m_page->dragCaretController().caretPosition()); 444 return target->dispatchEvent(TextEvent::createForDrop(innerFrame->domWindow(), text), IGNORE_EXCEPTION); 445 } 446 447 bool 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->containsFiles() && fileInput) { 471 // fileInput should be the element we hit tested for, unless it was made 472 // display:none in a drop event handler. 473 ASSERT(fileInput == element || !fileInput->renderer()); 474 if (fileInput->isDisabledFormControl()) 475 return false; 476 477 return fileInput->receiveDroppedFiles(dragData); 478 } 479 480 if (!m_page->dragController().canProcessDrag(dragData)) { 481 m_page->dragCaretController().clear(); 482 return false; 483 } 484 485 VisibleSelection dragCaret = m_page->dragCaretController().caretPosition(); 486 m_page->dragCaretController().clear(); 487 RefPtr<Range> range = dragCaret.toNormalizedRange(); 488 RefPtr<Element> rootEditableElement = innerFrame->selection().rootEditableElement(); 489 490 // For range to be null a WebKit client must have done something bad while 491 // manually controlling drag behaviour 492 if (!range) 493 return false; 494 ResourceFetcher* fetcher = range->ownerDocument().fetcher(); 495 ResourceCacheValidationSuppressor validationSuppressor(fetcher); 496 if (dragIsMove(innerFrame->selection(), dragData) || dragCaret.isContentRichlyEditable()) { 497 bool chosePlainText = false; 498 RefPtr<DocumentFragment> fragment = documentFragmentFromDragData(dragData, innerFrame.get(), range, true, chosePlainText); 499 if (!fragment) 500 return false; 501 502 if (dragIsMove(innerFrame->selection(), dragData)) { 503 // NSTextView behavior is to always smart delete on moving a selection, 504 // but only to smart insert if the selection granularity is word granularity. 505 bool smartDelete = innerFrame->editor().smartInsertDeleteEnabled(); 506 bool smartInsert = smartDelete && innerFrame->selection().granularity() == WordGranularity && dragData->canSmartReplace(); 507 MoveSelectionCommand::create(fragment, dragCaret.base(), smartInsert, smartDelete)->apply(); 508 } else { 509 if (setSelectionToDragCaret(innerFrame.get(), dragCaret, range, point)) { 510 ReplaceSelectionCommand::CommandOptions options = ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::PreventNesting; 511 if (dragData->canSmartReplace()) 512 options |= ReplaceSelectionCommand::SmartReplace; 513 if (chosePlainText) 514 options |= ReplaceSelectionCommand::MatchStyle; 515 ASSERT(m_documentUnderMouse); 516 ReplaceSelectionCommand::create(*m_documentUnderMouse.get(), fragment, options)->apply(); 517 } 518 } 519 } else { 520 String text = dragData->asPlainText(); 521 if (text.isEmpty()) 522 return false; 523 524 if (setSelectionToDragCaret(innerFrame.get(), dragCaret, range, point)) { 525 ASSERT(m_documentUnderMouse); 526 ReplaceSelectionCommand::create(*m_documentUnderMouse.get(), createFragmentFromText(range.get(), text), ReplaceSelectionCommand::SelectReplacement | ReplaceSelectionCommand::MatchStyle | ReplaceSelectionCommand::PreventNesting)->apply(); 527 } 528 } 529 530 if (rootEditableElement) { 531 if (Frame* frame = rootEditableElement->document().frame()) 532 frame->eventHandler().updateDragStateAfterEditDragIfNeeded(rootEditableElement.get()); 533 } 534 535 return true; 536 } 537 538 bool DragController::canProcessDrag(DragData* dragData) 539 { 540 ASSERT(dragData); 541 542 if (!dragData->containsCompatibleContent()) 543 return false; 544 545 IntPoint point = m_page->mainFrame()->view()->windowToContents(dragData->clientPosition()); 546 HitTestResult result = HitTestResult(point); 547 if (!m_page->mainFrame()->contentRenderer()) 548 return false; 549 550 result = m_page->mainFrame()->eventHandler().hitTestResultAtPoint(point); 551 552 if (!result.innerNonSharedNode()) 553 return false; 554 555 if (dragData->containsFiles() && asFileInput(result.innerNonSharedNode())) 556 return true; 557 558 if (result.innerNonSharedNode()->isPluginElement()) { 559 HTMLPlugInElement* plugin = toHTMLPlugInElement(result.innerNonSharedNode()); 560 if (!plugin->canProcessDrag() && !result.innerNonSharedNode()->rendererIsEditable()) 561 return false; 562 } else if (!result.innerNonSharedNode()->rendererIsEditable()) 563 return false; 564 565 if (m_didInitiateDrag && m_documentUnderMouse == m_dragInitiator && result.isSelected()) 566 return false; 567 568 return true; 569 } 570 571 static DragOperation defaultOperationForDrag(DragOperation srcOpMask) 572 { 573 // This is designed to match IE's operation fallback for the case where 574 // the page calls preventDefault() in a drag event but doesn't set dropEffect. 575 if (srcOpMask == DragOperationEvery) 576 return DragOperationCopy; 577 if (srcOpMask == DragOperationNone) 578 return DragOperationNone; 579 if (srcOpMask & DragOperationMove || srcOpMask & DragOperationGeneric) 580 return DragOperationMove; 581 if (srcOpMask & DragOperationCopy) 582 return DragOperationCopy; 583 if (srcOpMask & DragOperationLink) 584 return DragOperationLink; 585 586 // FIXME: Does IE really return "generic" even if no operations were allowed by the source? 587 return DragOperationGeneric; 588 } 589 590 bool DragController::tryDHTMLDrag(DragData* dragData, DragOperation& operation) 591 { 592 ASSERT(dragData); 593 ASSERT(m_documentUnderMouse); 594 RefPtr<Frame> mainFrame = m_page->mainFrame(); 595 RefPtr<FrameView> viewProtector = mainFrame->view(); 596 if (!viewProtector) 597 return false; 598 599 ClipboardAccessPolicy policy = m_documentUnderMouse->securityOrigin()->isLocal() ? ClipboardReadable : ClipboardTypesReadable; 600 RefPtr<Clipboard> clipboard = createDraggingClipboard(policy, dragData); 601 DragOperation srcOpMask = dragData->draggingSourceOperationMask(); 602 clipboard->setSourceOperation(srcOpMask); 603 604 PlatformMouseEvent event = createMouseEvent(dragData); 605 if (!mainFrame->eventHandler().updateDragAndDrop(event, clipboard.get())) { 606 clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security 607 return false; 608 } 609 610 operation = clipboard->destinationOperation(); 611 if (clipboard->dropEffectIsUninitialized()) 612 operation = defaultOperationForDrag(srcOpMask); 613 else if (!(srcOpMask & operation)) { 614 // The element picked an operation which is not supported by the source 615 operation = DragOperationNone; 616 } 617 618 clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security 619 return true; 620 } 621 622 Node* DragController::draggableNode(const Frame* src, Node* startNode, const IntPoint& dragOrigin, SelectionDragPolicy selectionDragPolicy, DragSourceAction& dragType) const 623 { 624 if (src->selection().contains(dragOrigin)) { 625 dragType = DragSourceActionSelection; 626 if (selectionDragPolicy == ImmediateSelectionDragResolution) 627 return startNode; 628 } else { 629 dragType = DragSourceActionNone; 630 } 631 632 Node* node = 0; 633 DragSourceAction candidateDragType = DragSourceActionNone; 634 for (const RenderObject* renderer = startNode->renderer(); renderer; renderer = renderer->parent()) { 635 node = renderer->nonPseudoNode(); 636 if (!node) { 637 // Anonymous render blocks don't correspond to actual DOM nodes, so we skip over them 638 // for the purposes of finding a draggable node. 639 continue; 640 } 641 if (dragType != DragSourceActionSelection && node->isTextNode() && node->canStartSelection()) { 642 // In this case we have a click in the unselected portion of text. If this text is 643 // selectable, we want to start the selection process instead of looking for a parent 644 // to try to drag. 645 return 0; 646 } 647 if (node->isElementNode()) { 648 EUserDrag dragMode = renderer->style()->userDrag(); 649 if (dragMode == DRAG_NONE) 650 continue; 651 // Even if the image is part of a selection, we always only drag the image in this case. 652 if (renderer->isImage() 653 && src->settings() 654 && src->settings()->loadsImagesAutomatically()) { 655 dragType = DragSourceActionImage; 656 return node; 657 } 658 // Other draggable elements are considered unselectable. 659 if (isHTMLAnchorElement(node) 660 && toHTMLAnchorElement(node)->isLiveLink()) { 661 candidateDragType = DragSourceActionLink; 662 break; 663 } 664 if (dragMode == DRAG_ELEMENT) { 665 candidateDragType = DragSourceActionDHTML; 666 break; 667 } 668 } 669 } 670 671 if (candidateDragType == DragSourceActionNone) { 672 // Either: 673 // 1) Nothing under the cursor is considered draggable, so we bail out. 674 // 2) There was a selection under the cursor but selectionDragPolicy is set to 675 // DelayedSelectionDragResolution and no other draggable element could be found, so bail 676 // out and allow text selection to start at the cursor instead. 677 return 0; 678 } 679 680 ASSERT(node); 681 if (dragType == DragSourceActionSelection) { 682 // Dragging unselectable elements in a selection has special behavior if selectionDragPolicy 683 // is DelayedSelectionDragResolution and this drag was flagged as a potential selection 684 // drag. In that case, don't allow selection and just drag the entire selection instead. 685 ASSERT(selectionDragPolicy == DelayedSelectionDragResolution); 686 node = startNode; 687 } else { 688 // If the cursor isn't over a selection, then just drag the node we found earlier. 689 ASSERT(dragType == DragSourceActionNone); 690 dragType = candidateDragType; 691 } 692 return node; 693 } 694 695 static ImageResource* getImageResource(Element* element) 696 { 697 ASSERT(element); 698 RenderObject* renderer = element->renderer(); 699 if (!renderer || !renderer->isImage()) 700 return 0; 701 RenderImage* image = toRenderImage(renderer); 702 return image->cachedImage(); 703 } 704 705 static Image* getImage(Element* element) 706 { 707 ASSERT(element); 708 ImageResource* cachedImage = getImageResource(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() : 0; 714 } 715 716 static void prepareClipboardForImageDrag(Frame* source, Clipboard* clipboard, Element* node, const KURL& linkURL, const KURL& imageURL, const String& label) 717 { 718 if (node->isContentRichlyEditable()) { 719 RefPtr<Range> range = source->document()->createRange(); 720 range->selectNode(node, ASSERT_NO_EXCEPTION); 721 source->selection().setSelection(VisibleSelection(range.get(), DOWNSTREAM)); 722 } 723 clipboard->declareAndWriteDragImage(node, !linkURL.isEmpty() ? linkURL : imageURL, label); 724 } 725 726 bool DragController::populateDragClipboard(Frame* src, const DragState& state, const IntPoint& dragOrigin) 727 { 728 ASSERT(dragTypeIsValid(state.m_dragType)); 729 ASSERT(src); 730 if (!src->view() || !src->contentRenderer()) 731 return false; 732 733 HitTestResult hitTestResult = src->eventHandler().hitTestResultAtPoint(dragOrigin); 734 // FIXME: Can this even happen? I guess it's possible, but should verify 735 // with a layout test. 736 if (!state.m_dragSrc->contains(hitTestResult.innerNode())) { 737 // The original node being dragged isn't under the drag origin anymore... maybe it was 738 // hidden or moved out from under the cursor. Regardless, we don't want to start a drag on 739 // something that's not actually under the drag origin. 740 return false; 741 } 742 const KURL& linkURL = hitTestResult.absoluteLinkURL(); 743 const KURL& imageURL = hitTestResult.absoluteImageURL(); 744 745 Clipboard* clipboard = state.m_dragClipboard.get(); 746 Node* node = state.m_dragSrc.get(); 747 748 if (state.m_dragType == DragSourceActionSelection) { 749 if (enclosingTextFormControl(src->selection().start())) { 750 clipboard->writePlainText(src->selectedTextForClipboard()); 751 } else { 752 RefPtr<Range> selectionRange = src->selection().toNormalizedRange(); 753 ASSERT(selectionRange); 754 755 clipboard->writeRange(selectionRange.get(), src); 756 } 757 } else if (state.m_dragType == DragSourceActionImage) { 758 if (imageURL.isEmpty() || !node || !node->isElementNode()) 759 return false; 760 Element* element = toElement(node); 761 prepareClipboardForImageDrag(src, clipboard, element, linkURL, imageURL, hitTestResult.altDisplayString()); 762 } else if (state.m_dragType == DragSourceActionLink) { 763 if (linkURL.isEmpty()) 764 return false; 765 // Simplify whitespace so the title put on the clipboard resembles what the user sees 766 // on the web page. This includes replacing newlines with spaces. 767 clipboard->writeURL(linkURL, hitTestResult.textContent().simplifyWhiteSpace()); 768 } 769 // FIXME: For DHTML/draggable element drags, write element markup to clipboard. 770 return true; 771 } 772 773 static IntPoint dragLocationForDHTMLDrag(const IntPoint& mouseDraggedPoint, const IntPoint& dragOrigin, const IntPoint& dragImageOffset, bool isLinkImage) 774 { 775 // dragImageOffset is the cursor position relative to the lower-left corner of the image. 776 const int yOffset = -dragImageOffset.y(); 777 778 if (isLinkImage) 779 return IntPoint(mouseDraggedPoint.x() - dragImageOffset.x(), mouseDraggedPoint.y() + yOffset); 780 781 return IntPoint(dragOrigin.x() - dragImageOffset.x(), dragOrigin.y() + yOffset); 782 } 783 784 static IntPoint dragLocationForSelectionDrag(Frame* sourceFrame) 785 { 786 IntRect draggingRect = enclosingIntRect(sourceFrame->selection().bounds()); 787 int xpos = draggingRect.maxX(); 788 xpos = draggingRect.x() < xpos ? draggingRect.x() : xpos; 789 int ypos = draggingRect.maxY(); 790 ypos = draggingRect.y() < ypos ? draggingRect.y() : ypos; 791 return IntPoint(xpos, ypos); 792 } 793 794 static const IntSize& maxDragImageSize() 795 { 796 #if OS(MACOSX) 797 // Match Safari's drag image size. 798 static const IntSize maxDragImageSize(400, 400); 799 #else 800 static const IntSize maxDragImageSize(200, 200); 801 #endif 802 return maxDragImageSize; 803 } 804 805 static PassOwnPtr<DragImage> dragImageForImage(Element* element, Image* image, const IntPoint& dragOrigin, const IntRect& imageRect, IntPoint& dragLocation) 806 { 807 OwnPtr<DragImage> dragImage; 808 IntPoint origin; 809 810 if (image->size().height() * image->size().width() <= MaxOriginalImageArea 811 && (dragImage = DragImage::create(image, element->renderer() ? element->renderer()->shouldRespectImageOrientation() : DoNotRespectImageOrientation))) { 812 IntSize originalSize = imageRect.size(); 813 origin = imageRect.location(); 814 815 dragImage->fitToMaxSize(imageRect.size(), maxDragImageSize()); 816 dragImage->dissolveToFraction(DragImageAlpha); 817 IntSize newSize = dragImage->size(); 818 819 // Properly orient the drag image and orient it differently if it's smaller than the original 820 float scale = newSize.width() / (float)originalSize.width(); 821 float dx = origin.x() - dragOrigin.x(); 822 dx *= scale; 823 origin.setX((int)(dx + 0.5)); 824 float dy = origin.y() - dragOrigin.y(); 825 dy *= scale; 826 origin.setY((int)(dy + 0.5)); 827 } 828 829 dragLocation = dragOrigin + origin; 830 return dragImage.release(); 831 } 832 833 static PassOwnPtr<DragImage> dragImageForLink(const KURL& linkURL, const String& linkText, float deviceScaleFactor, const IntPoint& mouseDraggedPoint, IntPoint& dragLoc) 834 { 835 FontDescription fontDescription; 836 RenderTheme::theme().systemFont(WebCore::CSSValueNone, fontDescription); 837 OwnPtr<DragImage> dragImage = DragImage::create(linkURL, linkText, fontDescription, deviceScaleFactor); 838 839 IntSize size = dragImage ? dragImage->size() : IntSize(); 840 IntPoint dragImageOffset(-size.width() / 2, -LinkDragBorderInset); 841 dragLoc = IntPoint(mouseDraggedPoint.x() + dragImageOffset.x(), mouseDraggedPoint.y() + dragImageOffset.y()); 842 843 return dragImage.release(); 844 } 845 846 bool DragController::startDrag(Frame* src, const DragState& state, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin) 847 { 848 ASSERT(dragTypeIsValid(state.m_dragType)); 849 ASSERT(src); 850 if (!src->view() || !src->contentRenderer()) 851 return false; 852 853 HitTestResult hitTestResult = src->eventHandler().hitTestResultAtPoint(dragOrigin); 854 if (!state.m_dragSrc->contains(hitTestResult.innerNode())) { 855 // The original node being dragged isn't under the drag origin anymore... maybe it was 856 // hidden or moved out from under the cursor. Regardless, we don't want to start a drag on 857 // something that's not actually under the drag origin. 858 return false; 859 } 860 const KURL& linkURL = hitTestResult.absoluteLinkURL(); 861 const KURL& imageURL = hitTestResult.absoluteImageURL(); 862 863 IntPoint mouseDraggedPoint = src->view()->windowToContents(dragEvent.position()); 864 865 IntPoint dragLocation; 866 IntPoint dragOffset; 867 868 Clipboard* clipboard = state.m_dragClipboard.get(); 869 // We allow DHTML/JS to set the drag image, even if its a link, image or text we're dragging. 870 // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp. 871 OwnPtr<DragImage> dragImage = clipboard->createDragImage(dragOffset, src); 872 if (dragImage) { 873 dragLocation = dragLocationForDHTMLDrag(mouseDraggedPoint, dragOrigin, dragOffset, !linkURL.isEmpty()); 874 } 875 876 Node* node = state.m_dragSrc.get(); 877 if (state.m_dragType == DragSourceActionSelection) { 878 if (!dragImage) { 879 dragImage = src->dragImageForSelection(); 880 if (dragImage) 881 dragImage->dissolveToFraction(DragImageAlpha); 882 dragLocation = dragLocationForSelectionDrag(src); 883 } 884 doSystemDrag(dragImage.get(), dragLocation, dragOrigin, clipboard, src, false); 885 } else if (state.m_dragType == DragSourceActionImage) { 886 if (imageURL.isEmpty() || !node || !node->isElementNode()) 887 return false; 888 Element* element = toElement(node); 889 Image* image = getImage(element); 890 if (!image || image->isNull()) 891 return false; 892 // We shouldn't be starting a drag for an image that can't provide an extension. 893 // This is an early detection for problems encountered later upon drop. 894 ASSERT(!image->filenameExtension().isEmpty()); 895 if (!dragImage) { 896 dragImage = dragImageForImage(element, image, dragOrigin, hitTestResult.imageRect(), dragLocation); 897 } 898 doSystemDrag(dragImage.get(), dragLocation, dragOrigin, clipboard, src, false); 899 } else if (state.m_dragType == DragSourceActionLink) { 900 if (linkURL.isEmpty()) 901 return false; 902 if (src->selection().isCaret() && src->selection().isContentEditable()) { 903 // a user can initiate a drag on a link without having any text 904 // selected. In this case, we should expand the selection to 905 // the enclosing anchor element 906 if (Node* node = enclosingAnchorElement(src->selection().base())) 907 src->selection().setSelection(VisibleSelection::selectionFromContentsOfNode(node)); 908 } 909 910 if (!dragImage) { 911 ASSERT(src->page()); 912 float deviceScaleFactor = src->page()->deviceScaleFactor(); 913 dragImage = dragImageForLink(linkURL, hitTestResult.textContent(), deviceScaleFactor, mouseDraggedPoint, dragLocation); 914 } 915 doSystemDrag(dragImage.get(), dragLocation, mouseDraggedPoint, clipboard, src, true); 916 } else if (state.m_dragType == DragSourceActionDHTML) { 917 if (!dragImage) 918 return false; 919 doSystemDrag(dragImage.get(), dragLocation, dragOrigin, clipboard, src, false); 920 } else { 921 ASSERT_NOT_REACHED(); 922 return false; 923 } 924 925 return true; 926 } 927 928 void DragController::doSystemDrag(DragImage* image, const IntPoint& dragLocation, const IntPoint& eventPos, Clipboard* clipboard, Frame* frame, bool forLink) 929 { 930 m_didInitiateDrag = true; 931 m_dragInitiator = frame->document(); 932 // Protect this frame and view, as a load may occur mid drag and attempt to unload this frame 933 RefPtr<Frame> frameProtector = m_page->mainFrame(); 934 RefPtr<FrameView> viewProtector = frameProtector->view(); 935 m_client->startDrag(image, viewProtector->rootViewToContents(frame->view()->contentsToRootView(dragLocation)), 936 viewProtector->rootViewToContents(frame->view()->contentsToRootView(eventPos)), clipboard, frameProtector.get(), forLink); 937 // DragClient::startDrag can cause our Page to dispear, deallocating |this|. 938 if (!frameProtector->page()) 939 return; 940 941 cleanupAfterSystemDrag(); 942 } 943 944 DragOperation DragController::dragOperation(DragData* dragData) 945 { 946 // FIXME: To match the MacOS behaviour we should return DragOperationNone 947 // if we are a modal window, we are the drag source, or the window is an 948 // attached sheet If this can be determined from within WebCore 949 // operationForDrag can be pulled into WebCore itself 950 ASSERT(dragData); 951 return dragData->containsURL() && !m_didInitiateDrag ? DragOperationCopy : DragOperationNone; 952 } 953 954 bool DragController::isCopyKeyDown(DragData*) 955 { 956 // FIXME: This should not be OS specific. Delegate to the embedder instead. 957 #if OS(WIN) 958 return ::GetAsyncKeyState(VK_CONTROL); 959 #else 960 return false; 961 #endif 962 } 963 964 void DragController::cleanupAfterSystemDrag() 965 { 966 } 967 968 } // namespace WebCore 969 970