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