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