1 /* 2 * Copyright (C) 2007, 2009 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 #include "CSSStyleDeclaration.h" 31 #include "Clipboard.h" 32 #include "ClipboardAccessPolicy.h" 33 #include "DocLoader.h" 34 #include "Document.h" 35 #include "DocumentFragment.h" 36 #include "DragActions.h" 37 #include "DragClient.h" 38 #include "DragData.h" 39 #include "Editor.h" 40 #include "EditorClient.h" 41 #include "Element.h" 42 #include "EventHandler.h" 43 #include "FloatRect.h" 44 #include "Frame.h" 45 #include "FrameLoader.h" 46 #include "FrameView.h" 47 #include "HTMLAnchorElement.h" 48 #include "HTMLInputElement.h" 49 #include "HTMLNames.h" 50 #include "HitTestRequest.h" 51 #include "HitTestResult.h" 52 #include "Image.h" 53 #include "MoveSelectionCommand.h" 54 #include "Node.h" 55 #include "Page.h" 56 #include "RenderFileUploadControl.h" 57 #include "RenderImage.h" 58 #include "RenderView.h" 59 #include "ReplaceSelectionCommand.h" 60 #include "ResourceRequest.h" 61 #include "SelectionController.h" 62 #include "Settings.h" 63 #include "Text.h" 64 #include "htmlediting.h" 65 #include "markup.h" 66 #include <wtf/CurrentTime.h> 67 #include <wtf/RefPtr.h> 68 69 namespace WebCore { 70 71 static PlatformMouseEvent createMouseEvent(DragData* dragData) 72 { 73 // FIXME: We should fake modifier keys here. 74 return PlatformMouseEvent(dragData->clientPosition(), dragData->globalPosition(), 75 LeftButton, MouseEventMoved, 0, false, false, false, false, currentTime()); 76 77 } 78 79 DragController::DragController(Page* page, DragClient* client) 80 : m_page(page) 81 , m_client(client) 82 , m_documentUnderMouse(0) 83 , m_dragInitiator(0) 84 , m_dragDestinationAction(DragDestinationActionNone) 85 , m_dragSourceAction(DragSourceActionNone) 86 , m_didInitiateDrag(false) 87 , m_isHandlingDrag(false) 88 , m_sourceDragOperation(DragOperationNone) 89 { 90 } 91 92 DragController::~DragController() 93 { 94 m_client->dragControllerDestroyed(); 95 } 96 97 static PassRefPtr<DocumentFragment> documentFragmentFromDragData(DragData* dragData, RefPtr<Range> context, 98 bool allowPlainText, bool& chosePlainText) 99 { 100 ASSERT(dragData); 101 chosePlainText = false; 102 103 Document* document = context->ownerDocument(); 104 ASSERT(document); 105 if (document && dragData->containsCompatibleContent()) { 106 if (PassRefPtr<DocumentFragment> fragment = dragData->asFragment(document)) 107 return fragment; 108 109 if (dragData->containsURL()) { 110 String title; 111 String url = dragData->asURL(&title); 112 if (!url.isEmpty()) { 113 RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::create(document); 114 anchor->setHref(url); 115 ExceptionCode ec; 116 RefPtr<Node> anchorText = document->createTextNode(title); 117 anchor->appendChild(anchorText, ec); 118 RefPtr<DocumentFragment> fragment = document->createDocumentFragment(); 119 fragment->appendChild(anchor, ec); 120 return fragment.get(); 121 } 122 } 123 } 124 if (allowPlainText && dragData->containsPlainText()) { 125 chosePlainText = true; 126 return createFragmentFromText(context.get(), dragData->asPlainText()).get(); 127 } 128 129 return 0; 130 } 131 132 bool DragController::dragIsMove(SelectionController* selection) 133 { 134 return m_documentUnderMouse == m_dragInitiator && selection->isContentEditable() && !isCopyKeyDown(); 135 } 136 137 // FIXME: This method is poorly named. We're just clearing the selection from the document this drag is exiting. 138 void DragController::cancelDrag() 139 { 140 m_page->dragCaretController()->clear(); 141 } 142 143 void DragController::dragEnded() 144 { 145 m_dragInitiator = 0; 146 m_didInitiateDrag = false; 147 m_page->dragCaretController()->clear(); 148 } 149 150 DragOperation DragController::dragEntered(DragData* dragData) 151 { 152 return dragEnteredOrUpdated(dragData); 153 } 154 155 void DragController::dragExited(DragData* dragData) 156 { 157 ASSERT(dragData); 158 Frame* mainFrame = m_page->mainFrame(); 159 160 if (RefPtr<FrameView> v = mainFrame->view()) { 161 ClipboardAccessPolicy policy = (!m_documentUnderMouse || m_documentUnderMouse->securityOrigin()->isLocal()) ? ClipboardReadable : ClipboardTypesReadable; 162 RefPtr<Clipboard> clipboard = dragData->createClipboard(policy); 163 clipboard->setSourceOperation(dragData->draggingSourceOperationMask()); 164 mainFrame->eventHandler()->cancelDragAndDrop(createMouseEvent(dragData), clipboard.get()); 165 clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security 166 } 167 mouseMovedIntoDocument(0); 168 } 169 170 DragOperation DragController::dragUpdated(DragData* dragData) 171 { 172 return dragEnteredOrUpdated(dragData); 173 } 174 175 bool DragController::performDrag(DragData* dragData) 176 { 177 ASSERT(dragData); 178 m_documentUnderMouse = m_page->mainFrame()->documentAtPoint(dragData->clientPosition()); 179 if (m_isHandlingDrag) { 180 ASSERT(m_dragDestinationAction & DragDestinationActionDHTML); 181 m_client->willPerformDragDestinationAction(DragDestinationActionDHTML, dragData); 182 RefPtr<Frame> mainFrame = m_page->mainFrame(); 183 if (mainFrame->view()) { 184 // Sending an event can result in the destruction of the view and part. 185 RefPtr<Clipboard> clipboard = dragData->createClipboard(ClipboardReadable); 186 clipboard->setSourceOperation(dragData->draggingSourceOperationMask()); 187 mainFrame->eventHandler()->performDragAndDrop(createMouseEvent(dragData), clipboard.get()); 188 clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security 189 } 190 m_documentUnderMouse = 0; 191 return true; 192 } 193 194 if ((m_dragDestinationAction & DragDestinationActionEdit) && concludeEditDrag(dragData)) { 195 m_documentUnderMouse = 0; 196 return true; 197 } 198 199 m_documentUnderMouse = 0; 200 201 if (operationForLoad(dragData) == DragOperationNone) 202 return false; 203 204 m_client->willPerformDragDestinationAction(DragDestinationActionLoad, dragData); 205 m_page->mainFrame()->loader()->load(ResourceRequest(dragData->asURL()), false); 206 return true; 207 } 208 209 void DragController::mouseMovedIntoDocument(Document* newDocument) 210 { 211 if (m_documentUnderMouse == newDocument) 212 return; 213 214 // If we were over another document clear the selection 215 if (m_documentUnderMouse) 216 cancelDrag(); 217 m_documentUnderMouse = newDocument; 218 } 219 220 DragOperation DragController::dragEnteredOrUpdated(DragData* dragData) 221 { 222 ASSERT(dragData); 223 ASSERT(m_page->mainFrame()); // It is not possible in Mac WebKit to have a Page without a mainFrame() 224 mouseMovedIntoDocument(m_page->mainFrame()->documentAtPoint(dragData->clientPosition())); 225 226 m_dragDestinationAction = m_client->actionMaskForDrag(dragData); 227 if (m_dragDestinationAction == DragDestinationActionNone) { 228 cancelDrag(); // FIXME: Why not call mouseMovedIntoDocument(0)? 229 return DragOperationNone; 230 } 231 232 DragOperation operation = DragOperationNone; 233 bool handledByDocument = tryDocumentDrag(dragData, m_dragDestinationAction, operation); 234 if (!handledByDocument && (m_dragDestinationAction & DragDestinationActionLoad)) 235 return operationForLoad(dragData); 236 return operation; 237 } 238 239 static HTMLInputElement* asFileInput(Node* node) 240 { 241 ASSERT(node); 242 243 // The button for a FILE input is a sub element with no set input type 244 // In order to get around this problem we assume any non-FILE input element 245 // is this internal button, and try querying the shadow parent node. 246 if (node->hasTagName(HTMLNames::inputTag) && node->isShadowNode() && static_cast<HTMLInputElement*>(node)->inputType() != HTMLInputElement::FILE) 247 node = node->shadowParentNode(); 248 249 if (!node || !node->hasTagName(HTMLNames::inputTag)) 250 return 0; 251 252 HTMLInputElement* inputElem = static_cast<HTMLInputElement*>(node); 253 if (inputElem->inputType() == HTMLInputElement::FILE) 254 return inputElem; 255 256 return 0; 257 } 258 259 static Element* elementUnderMouse(Document* documentUnderMouse, const IntPoint& p) 260 { 261 float zoomFactor = documentUnderMouse->frame()->pageZoomFactor(); 262 IntPoint point = roundedIntPoint(FloatPoint(p.x() * zoomFactor, p.y() * zoomFactor)); 263 264 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active); 265 HitTestResult result(point); 266 documentUnderMouse->renderView()->layer()->hitTest(request, result); 267 268 Node* n = result.innerNode(); 269 while (n && !n->isElementNode()) 270 n = n->parentNode(); 271 if (n) 272 n = n->shadowAncestorNode(); 273 274 ASSERT(n); 275 return static_cast<Element*>(n); 276 } 277 278 bool DragController::tryDocumentDrag(DragData* dragData, DragDestinationAction actionMask, DragOperation& operation) 279 { 280 ASSERT(dragData); 281 282 if (!m_documentUnderMouse) 283 return false; 284 285 m_isHandlingDrag = false; 286 if (actionMask & DragDestinationActionDHTML) { 287 m_isHandlingDrag = tryDHTMLDrag(dragData, operation); 288 // Do not continue if m_documentUnderMouse has been reset by tryDHTMLDrag. 289 // tryDHTMLDrag fires dragenter event. The event listener that listens 290 // to this event may create a nested message loop (open a modal dialog), 291 // which could process dragleave event and reset m_documentUnderMouse in 292 // dragExited. 293 if (!m_documentUnderMouse) 294 return false; 295 } 296 297 // It's unclear why this check is after tryDHTMLDrag. 298 // We send drag events in tryDHTMLDrag and that may be the reason. 299 RefPtr<FrameView> frameView = m_documentUnderMouse->view(); 300 if (!frameView) 301 return false; 302 303 if (m_isHandlingDrag) { 304 m_page->dragCaretController()->clear(); 305 return true; 306 } else if ((actionMask & DragDestinationActionEdit) && canProcessDrag(dragData)) { 307 if (dragData->containsColor()) { 308 operation = DragOperationGeneric; 309 return true; 310 } 311 312 IntPoint point = frameView->windowToContents(dragData->clientPosition()); 313 Element* element = elementUnderMouse(m_documentUnderMouse, point); 314 if (!asFileInput(element)) { 315 VisibleSelection dragCaret = m_documentUnderMouse->frame()->visiblePositionForPoint(point); 316 m_page->dragCaretController()->setSelection(dragCaret); 317 } 318 319 Frame* innerFrame = element->document()->frame(); 320 operation = dragIsMove(innerFrame->selection()) ? DragOperationMove : DragOperationCopy; 321 return true; 322 } 323 // If we're not over an editable region, make sure we're clearing any prior drag cursor. 324 m_page->dragCaretController()->clear(); 325 return false; 326 } 327 328 DragSourceAction DragController::delegateDragSourceAction(const IntPoint& windowPoint) 329 { 330 m_dragSourceAction = m_client->dragSourceActionMaskForPoint(windowPoint); 331 return m_dragSourceAction; 332 } 333 334 DragOperation DragController::operationForLoad(DragData* dragData) 335 { 336 ASSERT(dragData); 337 Document* doc = m_page->mainFrame()->documentAtPoint(dragData->clientPosition()); 338 if (doc && (m_didInitiateDrag || doc->isPluginDocument() || (doc->frame() && doc->frame()->editor()->clientIsEditable()))) 339 return DragOperationNone; 340 return dragOperation(dragData); 341 } 342 343 static bool setSelectionToDragCaret(Frame* frame, VisibleSelection& dragCaret, RefPtr<Range>& range, const IntPoint& point) 344 { 345 frame->selection()->setSelection(dragCaret); 346 if (frame->selection()->isNone()) { 347 dragCaret = frame->visiblePositionForPoint(point); 348 frame->selection()->setSelection(dragCaret); 349 range = dragCaret.toNormalizedRange(); 350 } 351 return !frame->selection()->isNone() && frame->selection()->isContentEditable(); 352 } 353 354 bool DragController::concludeEditDrag(DragData* dragData) 355 { 356 ASSERT(dragData); 357 ASSERT(!m_isHandlingDrag); 358 359 if (!m_documentUnderMouse) 360 return false; 361 362 IntPoint point = m_documentUnderMouse->view()->windowToContents(dragData->clientPosition()); 363 Element* element = elementUnderMouse(m_documentUnderMouse, point); 364 Frame* innerFrame = element->ownerDocument()->frame(); 365 ASSERT(innerFrame); 366 367 if (dragData->containsColor()) { 368 Color color = dragData->asColor(); 369 if (!color.isValid()) 370 return false; 371 if (!innerFrame) 372 return false; 373 RefPtr<Range> innerRange = innerFrame->selection()->toNormalizedRange(); 374 RefPtr<CSSStyleDeclaration> style = m_documentUnderMouse->createCSSStyleDeclaration(); 375 ExceptionCode ec; 376 style->setProperty("color", color.name(), ec); 377 if (!innerFrame->editor()->shouldApplyStyle(style.get(), innerRange.get())) 378 return false; 379 m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData); 380 innerFrame->editor()->applyStyle(style.get(), EditActionSetColor); 381 return true; 382 } 383 384 if (!m_page->dragController()->canProcessDrag(dragData)) { 385 m_page->dragCaretController()->clear(); 386 return false; 387 } 388 389 if (HTMLInputElement* fileInput = asFileInput(element)) { 390 if (!fileInput->isEnabledFormControl()) 391 return false; 392 393 if (!dragData->containsFiles()) 394 return false; 395 396 Vector<String> filenames; 397 dragData->asFilenames(filenames); 398 if (filenames.isEmpty()) 399 return false; 400 401 // Ugly. For security none of the APIs available to us can set the input value 402 // on file inputs. Even forcing a change in HTMLInputElement doesn't work as 403 // RenderFileUploadControl clears the file when doing updateFromElement(). 404 RenderFileUploadControl* renderer = toRenderFileUploadControl(fileInput->renderer()); 405 if (!renderer) 406 return false; 407 408 renderer->receiveDroppedFiles(filenames); 409 return true; 410 } 411 412 VisibleSelection dragCaret(m_page->dragCaretController()->selection()); 413 m_page->dragCaretController()->clear(); 414 RefPtr<Range> range = dragCaret.toNormalizedRange(); 415 416 // For range to be null a WebKit client must have done something bad while 417 // manually controlling drag behaviour 418 if (!range) 419 return false; 420 DocLoader* loader = range->ownerDocument()->docLoader(); 421 loader->setAllowStaleResources(true); 422 if (dragIsMove(innerFrame->selection()) || dragCaret.isContentRichlyEditable()) { 423 bool chosePlainText = false; 424 RefPtr<DocumentFragment> fragment = documentFragmentFromDragData(dragData, range, true, chosePlainText); 425 if (!fragment || !innerFrame->editor()->shouldInsertFragment(fragment, range, EditorInsertActionDropped)) { 426 loader->setAllowStaleResources(false); 427 return false; 428 } 429 430 m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData); 431 if (dragIsMove(innerFrame->selection())) { 432 bool smartMove = innerFrame->selectionGranularity() == WordGranularity 433 && innerFrame->editor()->smartInsertDeleteEnabled() 434 && dragData->canSmartReplace(); 435 applyCommand(MoveSelectionCommand::create(fragment, dragCaret.base(), smartMove)); 436 } else { 437 if (setSelectionToDragCaret(innerFrame, dragCaret, range, point)) 438 applyCommand(ReplaceSelectionCommand::create(m_documentUnderMouse, fragment, true, dragData->canSmartReplace(), chosePlainText)); 439 } 440 } else { 441 String text = dragData->asPlainText(); 442 if (text.isEmpty() || !innerFrame->editor()->shouldInsertText(text, range.get(), EditorInsertActionDropped)) { 443 loader->setAllowStaleResources(false); 444 return false; 445 } 446 447 m_client->willPerformDragDestinationAction(DragDestinationActionEdit, dragData); 448 if (setSelectionToDragCaret(innerFrame, dragCaret, range, point)) 449 applyCommand(ReplaceSelectionCommand::create(m_documentUnderMouse, createFragmentFromText(range.get(), text), true, false, true)); 450 } 451 loader->setAllowStaleResources(false); 452 453 return true; 454 } 455 456 bool DragController::canProcessDrag(DragData* dragData) 457 { 458 ASSERT(dragData); 459 460 if (!dragData->containsCompatibleContent()) 461 return false; 462 463 IntPoint point = m_page->mainFrame()->view()->windowToContents(dragData->clientPosition()); 464 HitTestResult result = HitTestResult(point); 465 if (!m_page->mainFrame()->contentRenderer()) 466 return false; 467 468 result = m_page->mainFrame()->eventHandler()->hitTestResultAtPoint(point, true); 469 470 if (!result.innerNonSharedNode()) 471 return false; 472 473 if (dragData->containsFiles() && asFileInput(result.innerNonSharedNode())) 474 return true; 475 476 if (!result.innerNonSharedNode()->isContentEditable()) 477 return false; 478 479 if (m_didInitiateDrag && m_documentUnderMouse == m_dragInitiator && result.isSelected()) 480 return false; 481 482 return true; 483 } 484 485 bool DragController::tryDHTMLDrag(DragData* dragData, DragOperation& operation) 486 { 487 ASSERT(dragData); 488 ASSERT(m_documentUnderMouse); 489 RefPtr<Frame> mainFrame = m_page->mainFrame(); 490 RefPtr<FrameView> viewProtector = mainFrame->view(); 491 if (!viewProtector) 492 return false; 493 494 ClipboardAccessPolicy policy = m_documentUnderMouse->securityOrigin()->isLocal() ? ClipboardReadable : ClipboardTypesReadable; 495 RefPtr<Clipboard> clipboard = dragData->createClipboard(policy); 496 DragOperation srcOpMask = dragData->draggingSourceOperationMask(); 497 clipboard->setSourceOperation(srcOpMask); 498 499 PlatformMouseEvent event = createMouseEvent(dragData); 500 if (!mainFrame->eventHandler()->updateDragAndDrop(event, clipboard.get())) { 501 clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security 502 return false; 503 } 504 505 operation = clipboard->destinationOperation(); 506 if (!(srcOpMask & operation)) { 507 // The element picked an operation which is not supported by the source 508 operation = DragOperationNone; 509 } 510 511 clipboard->setAccessPolicy(ClipboardNumb); // invalidate clipboard here for security 512 return true; 513 } 514 515 bool DragController::mayStartDragAtEventLocation(const Frame* frame, const IntPoint& framePos) 516 { 517 ASSERT(frame); 518 ASSERT(frame->settings()); 519 520 if (!frame->view() || !frame->contentRenderer()) 521 return false; 522 523 HitTestResult mouseDownTarget = HitTestResult(framePos); 524 525 mouseDownTarget = frame->eventHandler()->hitTestResultAtPoint(framePos, true); 526 527 if (mouseDownTarget.image() 528 && !mouseDownTarget.absoluteImageURL().isEmpty() 529 && frame->settings()->loadsImagesAutomatically() 530 && m_dragSourceAction & DragSourceActionImage) 531 return true; 532 533 if (!mouseDownTarget.absoluteLinkURL().isEmpty() 534 && m_dragSourceAction & DragSourceActionLink 535 && mouseDownTarget.isLiveLink()) 536 return true; 537 538 if (mouseDownTarget.isSelected() 539 && m_dragSourceAction & DragSourceActionSelection) 540 return true; 541 542 return false; 543 544 } 545 546 static CachedImage* getCachedImage(Element* element) 547 { 548 ASSERT(element); 549 RenderObject* renderer = element->renderer(); 550 if (!renderer || !renderer->isImage()) 551 return 0; 552 RenderImage* image = toRenderImage(renderer); 553 return image->cachedImage(); 554 } 555 556 static Image* getImage(Element* element) 557 { 558 ASSERT(element); 559 RenderObject* renderer = element->renderer(); 560 if (!renderer || !renderer->isImage()) 561 return 0; 562 563 RenderImage* image = toRenderImage(renderer); 564 if (image->cachedImage() && !image->cachedImage()->errorOccurred()) 565 return image->cachedImage()->image(); 566 return 0; 567 } 568 569 static void prepareClipboardForImageDrag(Frame* src, Clipboard* clipboard, Element* node, const KURL& linkURL, const KURL& imageURL, const String& label) 570 { 571 RefPtr<Range> range = src->document()->createRange(); 572 ExceptionCode ec = 0; 573 range->selectNode(node, ec); 574 ASSERT(!ec); 575 src->selection()->setSelection(VisibleSelection(range.get(), DOWNSTREAM)); 576 clipboard->declareAndWriteDragImage(node, !linkURL.isEmpty() ? linkURL : imageURL, label, src); 577 } 578 579 static IntPoint dragLocForDHTMLDrag(const IntPoint& mouseDraggedPoint, const IntPoint& dragOrigin, const IntPoint& dragImageOffset, bool isLinkImage) 580 { 581 // dragImageOffset is the cursor position relative to the lower-left corner of the image. 582 #if PLATFORM(MAC) 583 // We add in the Y dimension because we are a flipped view, so adding moves the image down. 584 const int yOffset = dragImageOffset.y(); 585 #else 586 const int yOffset = -dragImageOffset.y(); 587 #endif 588 589 if (isLinkImage) 590 return IntPoint(mouseDraggedPoint.x() - dragImageOffset.x(), mouseDraggedPoint.y() + yOffset); 591 592 return IntPoint(dragOrigin.x() - dragImageOffset.x(), dragOrigin.y() + yOffset); 593 } 594 595 static IntPoint dragLocForSelectionDrag(Frame* src) 596 { 597 IntRect draggingRect = enclosingIntRect(src->selectionBounds()); 598 int xpos = draggingRect.right(); 599 xpos = draggingRect.x() < xpos ? draggingRect.x() : xpos; 600 int ypos = draggingRect.bottom(); 601 #if PLATFORM(MAC) 602 // Deal with flipped coordinates on Mac 603 ypos = draggingRect.y() > ypos ? draggingRect.y() : ypos; 604 #else 605 ypos = draggingRect.y() < ypos ? draggingRect.y() : ypos; 606 #endif 607 return IntPoint(xpos, ypos); 608 } 609 610 bool DragController::startDrag(Frame* src, Clipboard* clipboard, DragOperation srcOp, const PlatformMouseEvent& dragEvent, const IntPoint& dragOrigin, bool isDHTMLDrag) 611 { 612 ASSERT(src); 613 ASSERT(clipboard); 614 615 if (!src->view() || !src->contentRenderer()) 616 return false; 617 618 HitTestResult dragSource = HitTestResult(dragOrigin); 619 dragSource = src->eventHandler()->hitTestResultAtPoint(dragOrigin, true); 620 KURL linkURL = dragSource.absoluteLinkURL(); 621 KURL imageURL = dragSource.absoluteImageURL(); 622 bool isSelected = dragSource.isSelected(); 623 624 IntPoint mouseDraggedPoint = src->view()->windowToContents(dragEvent.pos()); 625 626 m_draggingImageURL = KURL(); 627 m_sourceDragOperation = srcOp; 628 629 DragImageRef dragImage = 0; 630 IntPoint dragLoc(0, 0); 631 IntPoint dragImageOffset(0, 0); 632 633 if (isDHTMLDrag) 634 dragImage = clipboard->createDragImage(dragImageOffset); 635 else { 636 // This drag operation is not a DHTML drag and may go outside the WebView. 637 // We provide a default set of allowed drag operations that follows from: 638 // http://trac.webkit.org/browser/trunk/WebKit/mac/WebView/WebHTMLView.mm?rev=48526#L3430 639 m_sourceDragOperation = (DragOperation)(DragOperationGeneric | DragOperationCopy); 640 } 641 642 // We allow DHTML/JS to set the drag image, even if its a link, image or text we're dragging. 643 // This is in the spirit of the IE API, which allows overriding of pasteboard data and DragOp. 644 if (dragImage) { 645 dragLoc = dragLocForDHTMLDrag(mouseDraggedPoint, dragOrigin, dragImageOffset, !linkURL.isEmpty()); 646 m_dragOffset = dragImageOffset; 647 } 648 649 bool startedDrag = true; // optimism - we almost always manage to start the drag 650 651 Node* node = dragSource.innerNonSharedNode(); 652 653 Image* image = getImage(static_cast<Element*>(node)); 654 if (!imageURL.isEmpty() && node && node->isElementNode() && image 655 && (m_dragSourceAction & DragSourceActionImage)) { 656 // We shouldn't be starting a drag for an image that can't provide an extension. 657 // This is an early detection for problems encountered later upon drop. 658 ASSERT(!image->filenameExtension().isEmpty()); 659 Element* element = static_cast<Element*>(node); 660 if (!clipboard->hasData()) { 661 m_draggingImageURL = imageURL; 662 prepareClipboardForImageDrag(src, clipboard, element, linkURL, imageURL, dragSource.altDisplayString()); 663 } 664 665 m_client->willPerformDragSourceAction(DragSourceActionImage, dragOrigin, clipboard); 666 667 if (!dragImage) { 668 IntRect imageRect = dragSource.imageRect(); 669 imageRect.setLocation(m_page->mainFrame()->view()->windowToContents(src->view()->contentsToWindow(imageRect.location()))); 670 doImageDrag(element, dragOrigin, dragSource.imageRect(), clipboard, src, m_dragOffset); 671 } else 672 // DHTML defined drag image 673 doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); 674 675 } else if (!linkURL.isEmpty() && (m_dragSourceAction & DragSourceActionLink)) { 676 if (!clipboard->hasData()) 677 // Simplify whitespace so the title put on the clipboard resembles what the user sees 678 // on the web page. This includes replacing newlines with spaces. 679 clipboard->writeURL(linkURL, dragSource.textContent().simplifyWhiteSpace(), src); 680 681 if (src->selection()->isCaret() && src->selection()->isContentEditable()) { 682 // a user can initiate a drag on a link without having any text 683 // selected. In this case, we should expand the selection to 684 // the enclosing anchor element 685 Position pos = src->selection()->base(); 686 Node* node = enclosingAnchorElement(pos); 687 if (node) 688 src->selection()->setSelection(VisibleSelection::selectionFromContentsOfNode(node)); 689 } 690 691 m_client->willPerformDragSourceAction(DragSourceActionLink, dragOrigin, clipboard); 692 if (!dragImage) { 693 dragImage = m_client->createDragImageForLink(linkURL, dragSource.textContent(), src); 694 IntSize size = dragImageSize(dragImage); 695 m_dragOffset = IntPoint(-size.width() / 2, -LinkDragBorderInset); 696 dragLoc = IntPoint(mouseDraggedPoint.x() + m_dragOffset.x(), mouseDraggedPoint.y() + m_dragOffset.y()); 697 } 698 doSystemDrag(dragImage, dragLoc, mouseDraggedPoint, clipboard, src, true); 699 } else if (isSelected && (m_dragSourceAction & DragSourceActionSelection)) { 700 if (!clipboard->hasData()) { 701 if (isNodeInTextFormControl(src->selection()->start().node())) 702 clipboard->writePlainText(src->selectedText()); 703 else { 704 RefPtr<Range> selectionRange = src->selection()->toNormalizedRange(); 705 ASSERT(selectionRange); 706 707 clipboard->writeRange(selectionRange.get(), src); 708 } 709 } 710 m_client->willPerformDragSourceAction(DragSourceActionSelection, dragOrigin, clipboard); 711 if (!dragImage) { 712 dragImage = createDragImageForSelection(src); 713 dragLoc = dragLocForSelectionDrag(src); 714 m_dragOffset = IntPoint((int)(dragOrigin.x() - dragLoc.x()), (int)(dragOrigin.y() - dragLoc.y())); 715 } 716 doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); 717 } else if (isDHTMLDrag) { 718 ASSERT(m_dragSourceAction & DragSourceActionDHTML); 719 m_client->willPerformDragSourceAction(DragSourceActionDHTML, dragOrigin, clipboard); 720 doSystemDrag(dragImage, dragLoc, dragOrigin, clipboard, src, false); 721 } else { 722 // Only way I know to get here is if to get here is if the original element clicked on in the mousedown is no longer 723 // under the mousedown point, so linkURL, imageURL and isSelected are all false/empty. 724 startedDrag = false; 725 } 726 727 if (dragImage) 728 deleteDragImage(dragImage); 729 return startedDrag; 730 } 731 732 void DragController::doImageDrag(Element* element, const IntPoint& dragOrigin, const IntRect& rect, Clipboard* clipboard, Frame* frame, IntPoint& dragImageOffset) 733 { 734 IntPoint mouseDownPoint = dragOrigin; 735 DragImageRef dragImage; 736 IntPoint origin; 737 738 Image* image = getImage(element); 739 if (image && image->size().height() * image->size().width() <= MaxOriginalImageArea 740 && (dragImage = createDragImageFromImage(image))) { 741 IntSize originalSize = rect.size(); 742 origin = rect.location(); 743 744 dragImage = fitDragImageToMaxSize(dragImage, rect.size(), maxDragImageSize()); 745 dragImage = dissolveDragImageToFraction(dragImage, DragImageAlpha); 746 IntSize newSize = dragImageSize(dragImage); 747 748 // Properly orient the drag image and orient it differently if it's smaller than the original 749 float scale = newSize.width() / (float)originalSize.width(); 750 float dx = origin.x() - mouseDownPoint.x(); 751 dx *= scale; 752 origin.setX((int)(dx + 0.5)); 753 #if PLATFORM(MAC) 754 //Compensate for accursed flipped coordinates in cocoa 755 origin.setY(origin.y() + originalSize.height()); 756 #endif 757 float dy = origin.y() - mouseDownPoint.y(); 758 dy *= scale; 759 origin.setY((int)(dy + 0.5)); 760 } else { 761 dragImage = createDragImageIconForCachedImage(getCachedImage(element)); 762 if (dragImage) 763 origin = IntPoint(DragIconRightInset - dragImageSize(dragImage).width(), DragIconBottomInset); 764 } 765 766 dragImageOffset.setX(mouseDownPoint.x() + origin.x()); 767 dragImageOffset.setY(mouseDownPoint.y() + origin.y()); 768 doSystemDrag(dragImage, dragImageOffset, dragOrigin, clipboard, frame, false); 769 770 deleteDragImage(dragImage); 771 } 772 773 void DragController::doSystemDrag(DragImageRef image, const IntPoint& dragLoc, const IntPoint& eventPos, Clipboard* clipboard, Frame* frame, bool forLink) 774 { 775 m_didInitiateDrag = true; 776 m_dragInitiator = frame->document(); 777 // Protect this frame and view, as a load may occur mid drag and attempt to unload this frame 778 RefPtr<Frame> frameProtector = m_page->mainFrame(); 779 RefPtr<FrameView> viewProtector = frameProtector->view(); 780 m_client->startDrag(image, viewProtector->windowToContents(frame->view()->contentsToWindow(dragLoc)), 781 viewProtector->windowToContents(frame->view()->contentsToWindow(eventPos)), clipboard, frameProtector.get(), forLink); 782 783 cleanupAfterSystemDrag(); 784 } 785 786 // Manual drag caret manipulation 787 void DragController::placeDragCaret(const IntPoint& windowPoint) 788 { 789 mouseMovedIntoDocument(m_page->mainFrame()->documentAtPoint(windowPoint)); 790 if (!m_documentUnderMouse) 791 return; 792 Frame* frame = m_documentUnderMouse->frame(); 793 FrameView* frameView = frame->view(); 794 if (!frameView) 795 return; 796 IntPoint framePoint = frameView->windowToContents(windowPoint); 797 VisibleSelection dragCaret(frame->visiblePositionForPoint(framePoint)); 798 m_page->dragCaretController()->setSelection(dragCaret); 799 } 800 801 } // namespace WebCore 802 803 #endif // ENABLE(DRAG_SUPPORT) 804