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
createMouseEvent(DragData * dragData)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
DragController(Page * page,DragClient * client)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
~DragController()92 DragController::~DragController()
93 {
94 m_client->dragControllerDestroyed();
95 }
96
documentFragmentFromDragData(DragData * dragData,RefPtr<Range> context,bool allowPlainText,bool & chosePlainText)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
dragIsMove(SelectionController * selection)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.
cancelDrag()138 void DragController::cancelDrag()
139 {
140 m_page->dragCaretController()->clear();
141 }
142
dragEnded()143 void DragController::dragEnded()
144 {
145 m_dragInitiator = 0;
146 m_didInitiateDrag = false;
147 m_page->dragCaretController()->clear();
148 }
149
dragEntered(DragData * dragData)150 DragOperation DragController::dragEntered(DragData* dragData)
151 {
152 return dragEnteredOrUpdated(dragData);
153 }
154
dragExited(DragData * dragData)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
dragUpdated(DragData * dragData)170 DragOperation DragController::dragUpdated(DragData* dragData)
171 {
172 return dragEnteredOrUpdated(dragData);
173 }
174
performDrag(DragData * dragData)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
mouseMovedIntoDocument(Document * newDocument)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
dragEnteredOrUpdated(DragData * dragData)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
asFileInput(Node * node)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
elementUnderMouse(Document * documentUnderMouse,const IntPoint & p)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
tryDocumentDrag(DragData * dragData,DragDestinationAction actionMask,DragOperation & operation)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
delegateDragSourceAction(const IntPoint & windowPoint)328 DragSourceAction DragController::delegateDragSourceAction(const IntPoint& windowPoint)
329 {
330 m_dragSourceAction = m_client->dragSourceActionMaskForPoint(windowPoint);
331 return m_dragSourceAction;
332 }
333
operationForLoad(DragData * dragData)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
setSelectionToDragCaret(Frame * frame,VisibleSelection & dragCaret,RefPtr<Range> & range,const IntPoint & point)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
concludeEditDrag(DragData * dragData)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
canProcessDrag(DragData * dragData)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
tryDHTMLDrag(DragData * dragData,DragOperation & operation)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
mayStartDragAtEventLocation(const Frame * frame,const IntPoint & framePos)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
getCachedImage(Element * element)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
getImage(Element * element)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
prepareClipboardForImageDrag(Frame * src,Clipboard * clipboard,Element * node,const KURL & linkURL,const KURL & imageURL,const String & label)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
dragLocForDHTMLDrag(const IntPoint & mouseDraggedPoint,const IntPoint & dragOrigin,const IntPoint & dragImageOffset,bool isLinkImage)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
dragLocForSelectionDrag(Frame * src)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
startDrag(Frame * src,Clipboard * clipboard,DragOperation srcOp,const PlatformMouseEvent & dragEvent,const IntPoint & dragOrigin,bool isDHTMLDrag)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
doImageDrag(Element * element,const IntPoint & dragOrigin,const IntRect & rect,Clipboard * clipboard,Frame * frame,IntPoint & dragImageOffset)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
doSystemDrag(DragImageRef image,const IntPoint & dragLoc,const IntPoint & eventPos,Clipboard * clipboard,Frame * frame,bool forLink)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
placeDragCaret(const IntPoint & windowPoint)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