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