Back to index

lightning-sunbird  0.9+nobinonly
nsContentAreaDragDrop.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications.
00019  * Portions created by the Initial Developer are Copyright (C) 2002
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *  Mike Pinkerton <pinkerton@netscape.com>
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either the GNU General Public License Version 2 or later (the "GPL"), or
00027  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 #include "nsReadableUtils.h"
00040 
00041 // Local Includes
00042 #include "nsContentAreaDragDrop.h"
00043 
00044 // Helper Classes
00045 #include "nsString.h"
00046 
00047 // Interfaces needed to be included
00048 #include "nsIDOMNSUIEvent.h"
00049 #include "nsIDOMUIEvent.h"
00050 #include "nsISelection.h"
00051 #include "nsIDOMNode.h"
00052 #include "nsIDOMNodeList.h"
00053 #include "nsIDOMEventReceiver.h"
00054 #include "nsIDOMEvent.h"
00055 #include "nsIDOMNSEvent.h"
00056 #include "nsIDOMMouseEvent.h"
00057 #include "nsIDOMAbstractView.h"
00058 #include "nsIDOMWindow.h"
00059 #include "nsIDOMDocument.h"
00060 #include "nsIDOMDocumentRange.h"
00061 #include "nsIDOMRange.h"
00062 #include "nsIDocumentEncoder.h"
00063 #include "nsIFormControl.h"
00064 #include "nsISelectionPrivate.h"
00065 #include "nsIDOMHTMLAreaElement.h"
00066 #include "nsIDOMHTMLAnchorElement.h"
00067 #include "nsITransferable.h"
00068 #include "nsIDragService.h"
00069 #include "nsIDragSession.h"
00070 #include "nsComponentManagerUtils.h"
00071 #include "nsXPCOM.h"
00072 #include "nsISupportsPrimitives.h"
00073 #include "nsServiceManagerUtils.h"
00074 #include "nsNetUtil.h"
00075 #include "nsIFile.h"
00076 #include "nsIWebNavigation.h"
00077 #include "nsIClipboardDragDropHooks.h"
00078 #include "nsIClipboardDragDropHookList.h"
00079 #include "nsIDocShell.h"
00080 #include "nsIContent.h"
00081 #include "nsIImageLoadingContent.h"
00082 #include "nsIXMLContent.h"
00083 #include "nsINameSpaceManager.h"
00084 #include "nsUnicharUtils.h"
00085 #include "nsHTMLAtoms.h"
00086 #include "nsIURL.h"
00087 #include "nsIImage.h"
00088 #include "nsIDocument.h"
00089 #include "nsIScriptSecurityManager.h"
00090 #include "nsIPresShell.h"
00091 #include "nsPresContext.h"
00092 #include "nsIScriptGlobalObject.h"
00093 #include "nsIDocShellTreeItem.h"
00094 #include "nsIFrame.h"
00095 #include "nsLayoutAtoms.h"
00096 #include "nsIDocumentEncoder.h"
00097 #include "nsRange.h"
00098 #include "nsIWebBrowserPersist.h"
00099 #include "nsEscape.h"
00100 #include "nsContentUtils.h"
00101 #include "nsIMIMEService.h"
00102 #include "imgIRequest.h"
00103 
00104 // private clipboard data flavors for html copy, used by editor when pasting
00105 #define kHTMLContext   "text/_moz_htmlcontext"
00106 #define kHTMLInfo      "text/_moz_htmlinfo"
00107 
00108 
00109 NS_IMPL_ADDREF(nsContentAreaDragDrop)
00110 NS_IMPL_RELEASE(nsContentAreaDragDrop)
00111 
00112 NS_INTERFACE_MAP_BEGIN(nsContentAreaDragDrop)
00113     NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMDragListener)
00114     NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIDOMEventListener, nsIDOMDragListener)
00115     NS_INTERFACE_MAP_ENTRY(nsIDOMDragListener)
00116     NS_INTERFACE_MAP_ENTRY(nsIFlavorDataProvider)
00117     NS_INTERFACE_MAP_ENTRY(nsIDragDropHandler)
00118 NS_INTERFACE_MAP_END
00119 
00120 
00121 class nsTransferableFactory
00122 {
00123 public:
00124   static nsresult CreateFromEvent(nsIDOMEvent* inMouseEvent,
00125                                   nsIFlavorDataProvider *inFlavorDataProvider,
00126                                   nsITransferable** outTrans);
00127 
00128 protected:
00129   nsTransferableFactory(nsIDOMEvent* inMouseEvent,
00130                         nsIFlavorDataProvider *inFlavorDataProvider);
00131   nsresult Produce(nsITransferable** outTrans);
00132 
00133 private:
00134   nsresult ConvertStringsToTransferable(nsITransferable** outTrans);
00135   static nsresult GetDraggableSelectionData(nsISelection* inSelection,
00136                                             nsIDOMNode* inRealTargetNode,
00137                                             nsIDOMNode **outImageOrLinkNode,
00138                                             PRBool* outDragSelectedText);
00139   static already_AddRefed<nsIDOMNode> FindParentLinkNode(nsIDOMNode* inNode);
00140   static void GetAnchorURL(nsIDOMNode* inNode, nsAString& outURL);
00141   static void GetNodeString(nsIDOMNode* inNode, nsAString & outNodeString);
00142   static void CreateLinkText(const nsAString& inURL, const nsAString & inText,
00143                               nsAString& outLinkText);
00144   static void GetSelectedLink(nsISelection* inSelection,
00145                               nsIDOMNode **outLinkNode);
00146 
00147   enum serializationMode {serializeAsText, serializeAsHTML};
00148   // if inNode is null, use the selection from the window
00149   static nsresult SerializeNodeOrSelection(serializationMode inMode,
00150                                            PRUint32 inFlags,
00151                                            nsIDOMWindow* inWindow,
00152                                            nsIDOMNode* inNode,
00153                                            nsAString& outResultString,
00154                                            nsAString& outHTMLContext,
00155                                            nsAString& outHTMLInfo);
00156 
00157   PRBool mInstanceAlreadyUsed;
00158 
00159   nsCOMPtr<nsIDOMEvent> mMouseEvent;
00160   nsCOMPtr<nsIFlavorDataProvider> mFlavorDataProvider;
00161 
00162   nsString mUrlString;
00163   nsString mImageSourceString;
00164   nsString mImageDestFileName;
00165   nsString mTitleString;
00166   // will be filled automatically if you fill urlstring
00167   nsString mHtmlString;
00168   nsString mContextString;
00169   nsString mInfoString;
00170 
00171   PRBool mIsAnchor;
00172   nsCOMPtr<nsIImage> mImage;
00173 };
00174 
00175 
00176 //
00177 // nsContentAreaDragDrop ctor
00178 //
00179 nsContentAreaDragDrop::nsContentAreaDragDrop()
00180   : mListenerInstalled(PR_FALSE), mNavigator(nsnull)
00181 {
00182 } // ctor
00183 
00184 
00185 //
00186 // ChromeTooltipListener dtor
00187 //
00188 nsContentAreaDragDrop::~nsContentAreaDragDrop()
00189 {
00190   RemoveDragListener();
00191 
00192 } // dtor
00193 
00194 
00195 NS_IMETHODIMP
00196 nsContentAreaDragDrop::HookupTo(nsIDOMEventTarget *inAttachPoint,
00197                                 nsIWebNavigation* inNavigator)
00198 {
00199   NS_ASSERTION(inAttachPoint, "Can't hookup Drag Listeners to NULL receiver");
00200   mEventReceiver = do_QueryInterface(inAttachPoint);
00201   NS_ASSERTION(mEventReceiver,
00202                "Target doesn't implement nsIDOMEventReceiver as needed");
00203   mNavigator = inNavigator;
00204 
00205   return AddDragListener();
00206 }
00207 
00208 
00209 NS_IMETHODIMP
00210 nsContentAreaDragDrop::Detach()
00211 {
00212   return RemoveDragListener();
00213 }
00214 
00215 
00216 //
00217 // AddDragListener
00218 //
00219 // Subscribe to the events that will allow us to track drags.
00220 //
00221 nsresult
00222 nsContentAreaDragDrop::AddDragListener()
00223 {
00224   nsresult rv = NS_ERROR_FAILURE;
00225 
00226   if ( mEventReceiver ) {
00227     nsIDOMDragListener *pListener = NS_STATIC_CAST(nsIDOMDragListener *, this);
00228     rv = mEventReceiver->AddEventListenerByIID(pListener,
00229                                                NS_GET_IID(nsIDOMDragListener));
00230     if (NS_SUCCEEDED(rv))
00231       mListenerInstalled = PR_TRUE;
00232   }
00233 
00234   return rv;
00235 }
00236 
00237 
00238 //
00239 // RemoveDragListener
00240 //
00241 // Unsubscribe from all the various drag events that we were listening to.
00242 //
00243 nsresult
00244 nsContentAreaDragDrop::RemoveDragListener()
00245 {
00246   nsresult rv = NS_ERROR_FAILURE;
00247 
00248   if (mEventReceiver) {
00249     nsIDOMDragListener *pListener = NS_STATIC_CAST(nsIDOMDragListener *, this);
00250     rv =
00251       mEventReceiver->RemoveEventListenerByIID(pListener,
00252                                                NS_GET_IID(nsIDOMDragListener));
00253     if (NS_SUCCEEDED(rv))
00254       mListenerInstalled = PR_FALSE;
00255     mEventReceiver = nsnull;
00256   }
00257 
00258   return rv;
00259 }
00260 
00261 
00262 
00263 //
00264 // DragEnter
00265 //
00266 // Called when an OS drag is in process and the mouse enters a gecko
00267 // window. We don't care so much about dragEnters.
00268 //
00269 NS_IMETHODIMP
00270 nsContentAreaDragDrop::DragEnter(nsIDOMEvent* aMouseEvent)
00271 {
00272   // nothing really to do here.
00273   return NS_OK;
00274 }
00275 
00276 
00277 //
00278 // DragOver
00279 //
00280 // Called when an OS drag is in process and the mouse is over a gecko
00281 // window.  The main purpose of this routine is to set the |canDrop|
00282 // property on the drag session to false if we want to disallow the
00283 // drop so that the OS can provide the appropriate feedback. All this
00284 // does is show feedback, it doesn't actually cancel the drop; that
00285 // comes later.
00286 //
00287 NS_IMETHODIMP
00288 nsContentAreaDragDrop::DragOver(nsIDOMEvent* inEvent)
00289 {
00290   // first check that someone hasn't already handled this event
00291   PRBool preventDefault = PR_TRUE;
00292   nsCOMPtr<nsIDOMNSUIEvent> nsuiEvent(do_QueryInterface(inEvent));
00293   if ( nsuiEvent )
00294     nsuiEvent->GetPreventDefault(&preventDefault);
00295   if ( preventDefault )
00296     return NS_OK;
00297 
00298   // if the drag originated w/in this content area, bail
00299   // early. This avoids loading a URL dragged from the content
00300   // area into the very same content area (which is almost never
00301   // the desired action).
00302   nsCOMPtr<nsIDragService> dragService =
00303     do_GetService("@mozilla.org/widget/dragservice;1");
00304   if (!dragService)
00305     return NS_ERROR_FAILURE;
00306 
00307   nsCOMPtr<nsIDragSession> session;
00308   dragService->GetCurrentSession(getter_AddRefs(session));
00309 
00310   if (session) {
00311     // if the client has provided an override callback, check if we
00312     // the drop is allowed. If it allows it, we should still protect
00313     // against dropping w/in the same document.
00314     PRBool dropAllowed = PR_TRUE;
00315     nsCOMPtr<nsISimpleEnumerator> enumerator;
00316     GetHookEnumeratorFromEvent(inEvent, getter_AddRefs(enumerator));
00317 
00318     if (enumerator) {
00319       PRBool hasMoreHooks = PR_FALSE;
00320       while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreHooks))
00321              && hasMoreHooks) {
00322         nsCOMPtr<nsISupports> isupp;
00323         if (NS_FAILED(enumerator->GetNext(getter_AddRefs(isupp)))) {
00324           break;
00325         }
00326 
00327         nsCOMPtr<nsIClipboardDragDropHooks> override =
00328           do_QueryInterface(isupp);
00329 
00330         if (override) {
00331 #ifdef DEBUG
00332           nsresult hookResult =
00333 #endif
00334           override->AllowDrop(inEvent, session, &dropAllowed);
00335           NS_ASSERTION(NS_SUCCEEDED(hookResult), "hook failure in AllowDrop");
00336 
00337           if (!dropAllowed) {
00338             break;
00339           }
00340         }
00341       }
00342     }
00343 
00344     nsCOMPtr<nsIDOMDocument> sourceDoc;
00345     session->GetSourceDocument(getter_AddRefs(sourceDoc));
00346     nsCOMPtr<nsIDOMDocument> eventDoc;
00347     GetEventDocument(inEvent, getter_AddRefs(eventDoc));
00348 
00349     if (sourceDoc == eventDoc) {  // common case
00350       dropAllowed = PR_FALSE;
00351     } else if (sourceDoc && eventDoc) {
00352       // dig deeper
00353       // XXXbz we need better ways to get from a document to the docshell!
00354       nsCOMPtr<nsIDocument> sourceDocument(do_QueryInterface(sourceDoc));
00355       nsCOMPtr<nsIDocument> eventDocument(do_QueryInterface(eventDoc));
00356       NS_ASSERTION(sourceDocument, "Confused document object");
00357       NS_ASSERTION(eventDocument, "Confused document object");
00358 
00359       nsIScriptGlobalObject * sourceGlobal =
00360         sourceDocument->GetScriptGlobalObject();
00361       nsIScriptGlobalObject* eventGlobal =
00362         eventDocument->GetScriptGlobalObject();
00363 
00364       if (sourceGlobal && eventGlobal) {
00365         nsCOMPtr<nsIDocShellTreeItem> sourceShell =
00366           do_QueryInterface(sourceGlobal->GetDocShell());
00367         nsCOMPtr<nsIDocShellTreeItem> eventShell =
00368           do_QueryInterface(eventGlobal->GetDocShell());
00369 
00370         if (sourceShell && eventShell) {
00371           // Whew.  Almost there.  Get the roots that are of the same type
00372           // (otherwise we'll always end up with the root docshell for the
00373           // window, and drag/drop from chrom to content won't work).
00374           nsCOMPtr<nsIDocShellTreeItem> sourceRoot;
00375           nsCOMPtr<nsIDocShellTreeItem> eventRoot;
00376           sourceShell->GetSameTypeRootTreeItem(getter_AddRefs(sourceRoot));
00377           eventShell->GetSameTypeRootTreeItem(getter_AddRefs(eventRoot));
00378 
00379           if (sourceRoot && sourceRoot == eventRoot) {
00380             dropAllowed = PR_FALSE;
00381           }
00382         }
00383       }
00384     }
00385 
00386     session->SetCanDrop(dropAllowed);
00387   }
00388 
00389   return NS_OK;
00390 }
00391 
00392 
00393 //
00394 // DragExit
00395 //
00396 // Called when an OS drag is in process and the mouse is over a gecko
00397 // window. We don't care so much about dragExits.
00398 //
00399 NS_IMETHODIMP
00400 nsContentAreaDragDrop::DragExit(nsIDOMEvent* aMouseEvent)
00401 {
00402   // nothing really to do here.
00403   return NS_OK;
00404 }
00405 
00406 
00407 //
00408 // ExtractURLFromData
00409 //
00410 // build up a url from whatever data we get from the OS. How we
00411 // interpret the data depends on the flavor as it tells us the
00412 // nsISupports* primitive type we have.
00413 //
00414 void
00415 nsContentAreaDragDrop::ExtractURLFromData(const nsACString & inFlavor,
00416                                           nsISupports* inDataWrapper,
00417                                           PRUint32 inDataLen,
00418                                           nsAString & outURL)
00419 {
00420   if (!inDataWrapper) {
00421     return;
00422   }
00423 
00424   outURL.Truncate();
00425 
00426   if (inFlavor.Equals(kUnicodeMime)  || inFlavor.Equals(kURLDataMime)) {
00427     // the data is regular unicode, just go with what we get. It may
00428     // be a url, it may not be. *shrug*
00429     nsCOMPtr<nsISupportsString> stringData(do_QueryInterface(inDataWrapper));
00430     if (stringData) {
00431       stringData->GetData(outURL);
00432     }
00433   }
00434   else if (inFlavor.Equals(kURLMime)) {
00435     // the data is an internet shortcut of the form
00436     // <url>\n<title>. Strip out the url piece and return that.
00437     nsCOMPtr<nsISupportsString> stringData(do_QueryInterface(inDataWrapper));
00438 
00439     if (stringData) {
00440       nsAutoString data;
00441       stringData->GetData(data);
00442       PRInt32 separator = data.FindChar('\n');
00443 
00444       if (separator >= 0)
00445         outURL = Substring(data, 0, separator);
00446       else
00447         outURL = data;
00448     }
00449   }
00450   else if (inFlavor.Equals(kFileMime)) {
00451     // the data is a file. Use the necko parsing utils to get a file:// url
00452     // from the OS data.
00453     nsCOMPtr<nsIFile> file(do_QueryInterface(inDataWrapper));
00454     if (file) {
00455       nsCAutoString url;
00456       NS_GetURLSpecFromFile(file, url);
00457       CopyUTF8toUTF16(url, outURL);
00458     }
00459   }
00460 }
00461 
00462 
00463 //
00464 // DragDrop
00465 //
00466 // Called when an OS drag is in process and the mouse is released a
00467 // gecko window.  Extract the data from the OS and do something with
00468 // it.
00469 //
00470 NS_IMETHODIMP
00471 nsContentAreaDragDrop::DragDrop(nsIDOMEvent* inMouseEvent)
00472 {
00473   // if we don't have a nsIWebNavigation object to do anything with,
00474   // just bail. The client will have to have another way to deal with it
00475   if (!mNavigator) {
00476     return NS_OK;
00477   }
00478 
00479   // check that someone hasn't already handled this event
00480   PRBool preventDefault = PR_TRUE;
00481   nsCOMPtr<nsIDOMNSUIEvent> nsuiEvent(do_QueryInterface(inMouseEvent));
00482   if (nsuiEvent) {
00483     nsuiEvent->GetPreventDefault(&preventDefault);
00484   }
00485 
00486   if (preventDefault) {
00487     return NS_OK;
00488   }
00489 
00490   // pull the transferable out of the drag service. at the moment, we
00491   // only care about the first item of the drag. We don't allow
00492   // dropping multiple items into a content area.
00493   nsCOMPtr<nsIDragService> dragService =
00494     do_GetService("@mozilla.org/widget/dragservice;1");
00495   if (!dragService) {
00496     return NS_ERROR_FAILURE;
00497   }
00498 
00499   nsCOMPtr<nsIDragSession> session;
00500   dragService->GetCurrentSession(getter_AddRefs(session));
00501   if (!session) {
00502     return NS_ERROR_FAILURE;
00503   }
00504 
00505   nsCOMPtr<nsITransferable> trans =
00506     do_CreateInstance("@mozilla.org/widget/transferable;1");
00507   if (!trans) {
00508     return NS_ERROR_FAILURE;
00509   }
00510 
00511   // add the relevant flavors. order is important (highest fidelity to lowest)
00512   trans->AddDataFlavor(kURLDataMime);
00513   trans->AddDataFlavor(kURLMime);
00514   trans->AddDataFlavor(kFileMime);
00515   trans->AddDataFlavor(kUnicodeMime);
00516 
00517   // again, we only care about the first object
00518   nsresult rv = session->GetData(trans, 0);
00519 
00520   if (NS_SUCCEEDED(rv)) {
00521     // if the client has provided an override callback, call it. It may
00522     // still return that we should continue processing.
00523     nsCOMPtr<nsISimpleEnumerator> enumerator;
00524     GetHookEnumeratorFromEvent(inMouseEvent, getter_AddRefs(enumerator));
00525 
00526     if (enumerator) {
00527       PRBool actionCanceled = PR_TRUE;
00528       PRBool hasMoreHooks = PR_FALSE;
00529       while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreHooks))
00530              && hasMoreHooks) {
00531         nsCOMPtr<nsISupports> isupp;
00532         if (NS_FAILED(enumerator->GetNext(getter_AddRefs(isupp))))
00533           break;
00534         nsCOMPtr<nsIClipboardDragDropHooks> override =
00535           do_QueryInterface(isupp);
00536 
00537         if (override) {
00538 #ifdef DEBUG
00539           nsresult hookResult =
00540 #endif
00541           override->OnPasteOrDrop(inMouseEvent, trans, &actionCanceled);
00542           NS_ASSERTION(NS_SUCCEEDED(hookResult),
00543                        "hook failure in OnPasteOrDrop");
00544           if (!actionCanceled)
00545             return NS_OK;
00546         }
00547       }
00548     }
00549 
00550     nsXPIDLCString flavor;
00551     nsCOMPtr<nsISupports> dataWrapper;
00552     PRUint32 dataLen = 0;
00553     rv = trans->GetAnyTransferData(getter_Copies(flavor),
00554                                    getter_AddRefs(dataWrapper), &dataLen);
00555     if (NS_SUCCEEDED(rv) && dataLen > 0) {
00556       // get the url from one of several possible formats
00557       nsAutoString url;
00558       ExtractURLFromData(flavor, dataWrapper, dataLen, url);
00559       NS_ASSERTION(!url.IsEmpty(), "Didn't get anything we can use as a url");
00560 
00561       // valid urls don't have spaces. bail if this does.
00562       if (url.IsEmpty() || url.FindChar(' ') >= 0)
00563         return NS_OK;
00564 
00565       nsCOMPtr<nsIDOMDocument> sourceDocument;
00566       session->GetSourceDocument(getter_AddRefs(sourceDocument));
00567 
00568       nsCOMPtr<nsIDocument> sourceDoc(do_QueryInterface(sourceDocument));
00569       if (sourceDoc && sourceDoc->GetPrincipal()) {
00570         nsCOMPtr<nsIURI> sourceUri;
00571         sourceDoc->GetPrincipal()->GetURI(getter_AddRefs(sourceUri));
00572 
00573         if (sourceUri) {
00574           nsCAutoString sourceUriStr;
00575           sourceUri->GetSpec(sourceUriStr);
00576 
00577           rv = nsContentUtils::GetSecurityManager()->
00578             CheckLoadURIStr(sourceUriStr, NS_ConvertUTF16toUTF8(url),
00579                             nsIScriptSecurityManager::STANDARD);
00580 
00581           if (NS_FAILED(rv)) {
00582             // Security check failed, stop even propagation right here
00583             // and return the error.
00584             inMouseEvent->StopPropagation();
00585 
00586             return rv;
00587           }
00588         }
00589       }
00590 
00591       // ok, we have the url, load it.
00592       mNavigator->LoadURI(url.get(), nsIWebNavigation::LOAD_FLAGS_NONE, nsnull,
00593                           nsnull, nsnull);
00594     }
00595   }
00596 
00597   return NS_OK;
00598 }
00599 
00600 //
00601 // NormalizeSelection
00602 //
00603 void
00604 nsContentAreaDragDrop::NormalizeSelection(nsIDOMNode* inBaseNode,
00605                                           nsISelection* inSelection)
00606 {
00607   nsCOMPtr<nsIDOMNode> parent;
00608   inBaseNode->GetParentNode(getter_AddRefs(parent));
00609   if (!parent || !inSelection)
00610     return;
00611 
00612   nsCOMPtr<nsIDOMNodeList> childNodes;
00613   parent->GetChildNodes(getter_AddRefs(childNodes));
00614   if (!childNodes)
00615     return;
00616   PRUint32 listLen = 0;
00617   childNodes->GetLength(&listLen);
00618 
00619   PRUint32 index = 0;
00620   for (; index < listLen; ++index) {
00621     nsCOMPtr<nsIDOMNode> indexedNode;
00622     childNodes->Item(index, getter_AddRefs(indexedNode));
00623     if (indexedNode == inBaseNode) {
00624       break;
00625     }
00626   }
00627 
00628   if (index >= listLen) {
00629     return;
00630   }
00631 
00632   // now make the selection contain all of |inBaseNode|'s siblings up
00633   // to and including |inBaseNode|
00634   inSelection->Collapse(parent, index);
00635   inSelection->Extend(parent, index+1);
00636 }
00637 
00638 
00639 //
00640 // GetEventDocument
00641 //
00642 // Get the DOM document associated with a given DOM event
00643 //
00644 void
00645 nsContentAreaDragDrop::GetEventDocument(nsIDOMEvent* inEvent,
00646                                         nsIDOMDocument** outDocument)
00647 {
00648   *outDocument = nsnull;
00649 
00650   nsCOMPtr<nsIDOMUIEvent> uiEvent(do_QueryInterface(inEvent));
00651   if (uiEvent) {
00652     nsCOMPtr<nsIDOMAbstractView> view;
00653     uiEvent->GetView(getter_AddRefs(view));
00654     nsCOMPtr<nsIDOMWindow> window(do_QueryInterface(view));
00655 
00656     if (window) {
00657       window->GetDocument(outDocument);
00658     }
00659   }
00660 }
00661 
00662 nsresult
00663 nsContentAreaDragDrop::CreateTransferable(nsIDOMEvent* inMouseEvent,
00664                                           nsITransferable** outTrans)
00665 {
00666   return nsTransferableFactory::CreateFromEvent(inMouseEvent,
00667                                                 NS_STATIC_CAST(nsIFlavorDataProvider*, this),
00668                                                 outTrans);
00669 }
00670 
00671 nsresult
00672 nsContentAreaDragDrop::GetHookEnumeratorFromEvent(nsIDOMEvent* inEvent,
00673                                                   nsISimpleEnumerator **outEnumerator)
00674 {
00675   *outEnumerator = nsnull;
00676 
00677   nsCOMPtr<nsIDOMDocument> domdoc;
00678   GetEventDocument(inEvent, getter_AddRefs(domdoc));
00679   nsCOMPtr<nsIDocument> doc = do_QueryInterface(domdoc);
00680   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
00681 
00682   nsCOMPtr<nsISupports> container = doc->GetContainer();
00683   nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(container);
00684   NS_ENSURE_TRUE(docShell, NS_ERROR_FAILURE);
00685 
00686   nsCOMPtr<nsIClipboardDragDropHookList> hookList = do_GetInterface(docShell);
00687   NS_ENSURE_TRUE(hookList, NS_ERROR_FAILURE);
00688   nsCOMPtr<nsISimpleEnumerator> enumerator;
00689   hookList->GetHookEnumerator(getter_AddRefs(enumerator));
00690   NS_ENSURE_TRUE(enumerator, NS_ERROR_FAILURE);
00691 
00692   *outEnumerator = enumerator;
00693   NS_ADDREF(*outEnumerator);
00694 
00695   return NS_OK;
00696 }
00697 
00698 //
00699 // DragGesture
00700 //
00701 // Determine if the user has started to drag something and kick off
00702 // an OS-level drag if it's applicable
00703 //
00704 NS_IMETHODIMP
00705 nsContentAreaDragDrop::DragGesture(nsIDOMEvent* inMouseEvent)
00706 {
00707   // first check that someone hasn't already handled this event
00708   PRBool preventDefault = PR_TRUE;
00709   nsCOMPtr<nsIDOMNSUIEvent> nsuiEvent(do_QueryInterface(inMouseEvent));
00710   if (nsuiEvent) {
00711     nsuiEvent->GetPreventDefault(&preventDefault);
00712   }
00713 
00714   if (preventDefault) {
00715     return NS_OK;
00716   }
00717 
00718   // if the client has provided an override callback, check if we
00719   // should continue
00720   nsCOMPtr<nsISimpleEnumerator> enumerator;
00721   GetHookEnumeratorFromEvent(inMouseEvent, getter_AddRefs(enumerator));
00722 
00723   if (enumerator) {
00724     PRBool allow = PR_TRUE;
00725     PRBool hasMoreHooks = PR_FALSE;
00726     while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreHooks))
00727            && hasMoreHooks) {
00728       nsCOMPtr<nsISupports> isupp;
00729       if (NS_FAILED(enumerator->GetNext(getter_AddRefs(isupp))))
00730         break;
00731 
00732       nsCOMPtr<nsIClipboardDragDropHooks> override = do_QueryInterface(isupp);
00733       if (override) {
00734 #ifdef DEBUG
00735         nsresult hookResult =
00736 #endif
00737         override->AllowStartDrag(inMouseEvent, &allow);
00738         NS_ASSERTION(NS_SUCCEEDED(hookResult),
00739                      "hook failure in AllowStartDrag");
00740 
00741         if (!allow)
00742           return NS_OK;
00743       }
00744     }
00745   }
00746 
00747   nsCOMPtr<nsITransferable> trans;
00748   nsresult rv = CreateTransferable(inMouseEvent, getter_AddRefs(trans));
00749   if (NS_FAILED(rv))
00750     return rv;
00751 
00752   if (trans) {
00753     // if the client has provided an override callback, let them manipulate
00754     // the flavors or drag data
00755     nsCOMPtr<nsISimpleEnumerator> enumerator;
00756     GetHookEnumeratorFromEvent(inMouseEvent, getter_AddRefs(enumerator));
00757     if (enumerator) {
00758       PRBool hasMoreHooks = PR_FALSE;
00759       PRBool doContinueDrag = PR_TRUE;
00760       while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMoreHooks))
00761              && hasMoreHooks) {
00762         nsCOMPtr<nsISupports> isupp;
00763         if (NS_FAILED(enumerator->GetNext(getter_AddRefs(isupp))))
00764           break;
00765         nsCOMPtr<nsIClipboardDragDropHooks> override =
00766           do_QueryInterface(isupp);
00767 
00768         if (override) {
00769 #ifdef DEBUG
00770           nsresult hookResult =
00771 #endif
00772           override->OnCopyOrDrag(inMouseEvent, trans, &doContinueDrag);
00773           NS_ASSERTION(NS_SUCCEEDED(hookResult),
00774                        "hook failure in OnCopyOrDrag");
00775 
00776           if (!doContinueDrag) {
00777             return NS_OK;
00778           }
00779         }
00780       }
00781     }
00782 
00783     nsCOMPtr<nsISupportsArray> transArray =
00784       do_CreateInstance("@mozilla.org/supports-array;1");
00785     if (!transArray) {
00786       return NS_ERROR_FAILURE;
00787     }
00788 
00789     transArray->InsertElementAt(trans, 0);
00790 
00791     // kick off the drag
00792     nsCOMPtr<nsIDOMEventTarget> target;
00793     inMouseEvent->GetTarget(getter_AddRefs(target));
00794     nsCOMPtr<nsIDOMNode> targetNode(do_QueryInterface(target));
00795     nsCOMPtr<nsIDragService> dragService =
00796       do_GetService("@mozilla.org/widget/dragservice;1");
00797 
00798     if (!dragService) {
00799       return NS_ERROR_FAILURE;
00800     }
00801 
00802     dragService->InvokeDragSession(targetNode, transArray, nsnull,
00803                                    nsIDragService::DRAGDROP_ACTION_COPY +
00804                                    nsIDragService::DRAGDROP_ACTION_MOVE +
00805                                    nsIDragService::DRAGDROP_ACTION_LINK);
00806   }
00807 
00808   return NS_OK;
00809 }
00810 
00811 
00812 NS_IMETHODIMP
00813 nsContentAreaDragDrop::HandleEvent(nsIDOMEvent *event)
00814 {
00815   return NS_OK;
00816 
00817 }
00818 
00819 #if 0
00820 #pragma mark -
00821 #endif
00822 
00823 // SaveURIToFile
00824 // used on platforms where it's possible to drag items (e.g. images)
00825 // into the file system
00826 nsresult
00827 nsContentAreaDragDrop::SaveURIToFile(nsAString& inSourceURIString,
00828                                      nsIFile* inDestFile)
00829 {
00830   nsCOMPtr<nsIURI> sourceURI;
00831   nsresult rv = NS_NewURI(getter_AddRefs(sourceURI), inSourceURIString);
00832   if (NS_FAILED(rv)) {
00833     return NS_ERROR_FAILURE;
00834   }
00835 
00836   nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
00837   if (!sourceURL) {
00838     return NS_ERROR_NO_INTERFACE;
00839   }
00840 
00841   rv = inDestFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
00842   NS_ENSURE_SUCCESS(rv, rv);
00843 
00844   // we rely on the fact that the WPB is refcounted by the channel etc,
00845   // so we don't keep a ref to it. It will die when finished.
00846   nsCOMPtr<nsIWebBrowserPersist> persist =
00847     do_CreateInstance("@mozilla.org/embedding/browser/nsWebBrowserPersist;1",
00848                       &rv);
00849   NS_ENSURE_SUCCESS(rv, rv);
00850 
00851   return persist->SaveURI(sourceURI, nsnull, nsnull, nsnull, nsnull,
00852                           inDestFile);
00853 }
00854 
00855 // This is our nsIFlavorDataProvider callback. There are several
00856 // assumptions here that make this work:
00857 //
00858 // 1. Someone put a kFilePromiseURLMime flavor into the transferable
00859 //    with the source URI of the file to save (as a string). We did
00860 //    that above.
00861 //
00862 // 2. Someone put a kFilePromiseDirectoryMime flavor into the
00863 //    transferable with an nsILocalFile for the directory we are to
00864 //    save in. That has to be done by platform-specific code (in
00865 //    widget), // which gets the destination directory from
00866 //    OS-specific drag // information.
00867 //
00868 NS_IMETHODIMP
00869 nsContentAreaDragDrop::GetFlavorData(nsITransferable *aTransferable,
00870                                      const char *aFlavor, nsISupports **aData,
00871                                      PRUint32 *aDataLen)
00872 {
00873   NS_ENSURE_ARG_POINTER(aData && aDataLen);
00874   *aData = nsnull;
00875   *aDataLen = 0;
00876 
00877   nsresult rv = NS_ERROR_NOT_IMPLEMENTED;
00878 
00879   if (strcmp(aFlavor, kFilePromiseMime) == 0) {
00880     // get the URI from the kFilePromiseURLMime flavor
00881     NS_ENSURE_ARG(aTransferable);
00882     nsCOMPtr<nsISupports> tmp;
00883     PRUint32 dataSize = 0;
00884     aTransferable->GetTransferData(kFilePromiseURLMime,
00885                                    getter_AddRefs(tmp), &dataSize);
00886     nsCOMPtr<nsISupportsString> supportsString =
00887       do_QueryInterface(tmp);
00888     if (!supportsString)
00889       return NS_ERROR_FAILURE;
00890 
00891     nsAutoString sourceURLString;
00892     supportsString->GetData(sourceURLString);
00893     if (sourceURLString.IsEmpty())
00894       return NS_ERROR_FAILURE;
00895 
00896     aTransferable->GetTransferData(kFilePromiseDestFilename,
00897                                    getter_AddRefs(tmp), &dataSize);
00898     supportsString = do_QueryInterface(tmp);
00899     if (!supportsString)
00900       return NS_ERROR_FAILURE;
00901 
00902     nsAutoString targetFilename;
00903     supportsString->GetData(targetFilename);
00904     if (targetFilename.IsEmpty())
00905       return NS_ERROR_FAILURE;
00906 
00907     // get the target directory from the kFilePromiseDirectoryMime
00908     // flavor
00909     nsCOMPtr<nsISupports> dirPrimitive;
00910     dataSize = 0;
00911     aTransferable->GetTransferData(kFilePromiseDirectoryMime,
00912                                    getter_AddRefs(dirPrimitive), &dataSize);
00913     nsCOMPtr<nsILocalFile> destDirectory = do_QueryInterface(dirPrimitive);
00914     if (!destDirectory)
00915       return NS_ERROR_FAILURE;
00916 
00917     nsCOMPtr<nsIFile> file;
00918     rv = destDirectory->Clone(getter_AddRefs(file));
00919     NS_ENSURE_SUCCESS(rv, rv);
00920 
00921     file->Append(targetFilename);
00922 
00923     // now save the file
00924     rv = SaveURIToFile(sourceURLString, file);
00925 
00926     // send back an nsILocalFile
00927     if (NS_SUCCEEDED(rv)) {
00928       CallQueryInterface(file, aData);
00929       *aDataLen = sizeof(nsIFile*);
00930     }
00931   }
00932 
00933   return rv;
00934 }
00935 
00936 
00937 nsresult
00938 nsTransferableFactory::CreateFromEvent(nsIDOMEvent* inMouseEvent,
00939                                        nsIFlavorDataProvider *dataProvider,
00940                                        nsITransferable** outTrans)
00941 {
00942   nsTransferableFactory factory(inMouseEvent, dataProvider);
00943   return factory.Produce(outTrans);
00944 }
00945 
00946 nsTransferableFactory::nsTransferableFactory(nsIDOMEvent* inMouseEvent,
00947                                              nsIFlavorDataProvider *dataProvider)
00948   : mInstanceAlreadyUsed(PR_FALSE),
00949     mMouseEvent(inMouseEvent),
00950     mFlavorDataProvider(dataProvider)
00951 {
00952 }
00953 
00954 
00955 //
00956 // FindParentLinkNode
00957 //
00958 // Finds the parent with the given link tag starting at |inNode|. If
00959 // it gets up to the root without finding it, we stop looking and
00960 // return null.
00961 //
00962 already_AddRefed<nsIDOMNode>
00963 nsTransferableFactory::FindParentLinkNode(nsIDOMNode* inNode)
00964 {
00965   nsCOMPtr<nsIContent> content(do_QueryInterface(inNode));
00966   if (!content) {
00967     // That must have been the document node; nothing else to do here;
00968     return nsnull;
00969   }
00970 
00971   for (; content; content = content->GetParent()) {
00972     if (nsContentUtils::IsDraggableLink(content)) {
00973       nsIDOMNode* node = nsnull;
00974       CallQueryInterface(content, &node);
00975       return node;
00976     }
00977   }
00978 
00979   return nsnull;
00980 }
00981 
00982 
00983 //
00984 // GetAnchorURL
00985 //
00986 // Get the url for this anchor. First try the href, and if that's
00987 // empty, go for the name.
00988 //
00989 void
00990 nsTransferableFactory::GetAnchorURL(nsIDOMNode* inNode, nsAString& outURL)
00991 {
00992   outURL.Truncate();
00993   nsCOMPtr<nsIContent> content(do_QueryInterface(inNode));
00994   if (!content) {
00995     // Not a link
00996     return;
00997   }
00998 
00999   nsCOMPtr<nsIURI> linkURI = nsContentUtils::GetLinkURI(content);
01000   if (!linkURI) {
01001     return;
01002   }
01003 
01004   nsCAutoString spec;
01005   linkURI->GetSpec(spec);
01006   CopyUTF8toUTF16(spec, outURL);
01007 }
01008 
01009 
01010 //
01011 // CreateLinkText
01012 //
01013 // Creates the html for an anchor in the form
01014 //  <a href="inURL">inText</a>
01015 //
01016 void
01017 nsTransferableFactory::CreateLinkText(const nsAString& inURL,
01018                                       const nsAString & inText,
01019                                       nsAString& outLinkText)
01020 {
01021   // use a temp var in case |inText| is the same string as
01022   // |outLinkText| to avoid overwriting it while building up the
01023   // string in pieces.
01024   nsAutoString linkText(NS_LITERAL_STRING("<a href=\"") +
01025                         inURL +
01026                         NS_LITERAL_STRING("\">") +
01027                         inText +
01028                         NS_LITERAL_STRING("</a>") );
01029 
01030   outLinkText = linkText;
01031 }
01032 
01033 
01034 //
01035 // GetNodeString
01036 //
01037 // Gets the text associated with a node
01038 //
01039 void
01040 nsTransferableFactory::GetNodeString(nsIDOMNode* inNode,
01041                                      nsAString & outNodeString)
01042 {
01043   outNodeString.Truncate();
01044 
01045   // use a range to get the text-equivalent of the node
01046   nsCOMPtr<nsIDOMDocument> doc;
01047   inNode->GetOwnerDocument(getter_AddRefs(doc));
01048   nsCOMPtr<nsIDOMDocumentRange> docRange(do_QueryInterface(doc));
01049   if (docRange) {
01050     nsCOMPtr<nsIDOMRange> range;
01051     docRange->CreateRange(getter_AddRefs(range));
01052     if (range) {
01053       range->SelectNode(inNode);
01054       range->ToString(outNodeString);
01055     }
01056   }
01057 }
01058 
01059 
01060 nsresult
01061 nsTransferableFactory::Produce(nsITransferable** outTrans)
01062 {
01063   if (mInstanceAlreadyUsed) {
01064     return NS_ERROR_FAILURE;
01065   }
01066 
01067   if (!outTrans || !mMouseEvent || !mFlavorDataProvider) {
01068     return NS_ERROR_FAILURE;
01069   }
01070 
01071   mInstanceAlreadyUsed = PR_TRUE;
01072   *outTrans = nsnull;
01073 
01074   nsCOMPtr<nsIDOMWindow> window;
01075   PRBool isAltKeyDown = PR_FALSE;
01076   mIsAnchor = PR_FALSE;
01077 
01078   {
01079     nsCOMPtr<nsIDOMUIEvent> uiEvent(do_QueryInterface(mMouseEvent));
01080     if (!uiEvent) {
01081       return NS_OK;
01082     }
01083 
01084     // find the selection to see what we could be dragging and if
01085     // what we're dragging is in what is selected.
01086     nsCOMPtr<nsIDOMAbstractView> view;
01087     uiEvent->GetView(getter_AddRefs(view));
01088     window = do_QueryInterface(view);
01089     if (!window) {
01090       return NS_OK;
01091     }
01092   }
01093 
01094   {
01095     nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(mMouseEvent));
01096     if (mouseEvent) {
01097       mouseEvent->GetAltKey(&isAltKeyDown);
01098     }
01099   }
01100 
01101   nsCOMPtr<nsISelection> selection;
01102   window->GetSelection(getter_AddRefs(selection));
01103   if (!selection) {
01104     return NS_OK;
01105   }
01106 
01107   // if set, serialize the content under this node
01108   nsCOMPtr<nsIDOMNode> nodeToSerialize;
01109   PRBool useSelectedText = PR_FALSE;
01110 
01111   {
01112     PRBool haveSelectedContent = PR_FALSE;
01113 
01114     // possible parent link node
01115     nsCOMPtr<nsIDOMNode> parentLink;
01116     nsCOMPtr<nsIDOMNode> draggedNode;
01117 
01118     {
01119       nsCOMPtr<nsIDOMEventTarget> target;
01120       mMouseEvent->GetTarget(getter_AddRefs(target));
01121 
01122       // only drag form elements by using the alt key,
01123       // otherwise buttons and select widgets are hard to use
01124 
01125       // Note that while <object> elements implement nsIFormControl, we should
01126       // really allow dragging them if they happen to be images.
01127       nsCOMPtr<nsIFormControl> form(do_QueryInterface(target));
01128       if (form && !isAltKeyDown && form->GetType() != NS_FORM_OBJECT) {
01129         return NS_OK;
01130       }
01131 
01132       draggedNode = do_QueryInterface(target);
01133     }
01134 
01135     nsCOMPtr<nsIDOMHTMLAreaElement>   area;   // client-side image map
01136     nsCOMPtr<nsIImageLoadingContent>  image;
01137     nsCOMPtr<nsIDOMHTMLAnchorElement> link;
01138 
01139     {
01140       // Get the real target and see if it is in the selection
01141       nsCOMPtr<nsIDOMNode> realTargetNode;
01142 
01143       {
01144         nsCOMPtr<nsIDOMNSEvent> internalEvent = do_QueryInterface(mMouseEvent);
01145         if (internalEvent) {
01146           nsCOMPtr<nsIDOMEventTarget> realTarget;
01147           internalEvent->GetExplicitOriginalTarget(getter_AddRefs(realTarget));
01148           realTargetNode = do_QueryInterface(realTarget);
01149         }
01150       }
01151 
01152       {
01153         nsCOMPtr<nsIDOMNode> selectedImageOrLinkNode;
01154         GetDraggableSelectionData(selection, realTargetNode,
01155                                   getter_AddRefs(selectedImageOrLinkNode),
01156                                   &haveSelectedContent);
01157 
01158         // either plain text or anchor text is selected
01159         if (haveSelectedContent) {
01160           link = do_QueryInterface(selectedImageOrLinkNode);
01161           if (link && isAltKeyDown) {
01162             return NS_OK;
01163           }
01164 
01165           useSelectedText = PR_TRUE;
01166         } else if (selectedImageOrLinkNode) {
01167           // an image is selected
01168           image = do_QueryInterface(selectedImageOrLinkNode);
01169         } else {
01170           // nothing is selected -
01171           //
01172           // look for draggable elements under the mouse
01173           //
01174           // if the alt key is down, don't start a drag if we're in an
01175           // anchor because we want to do selection.
01176           parentLink = FindParentLinkNode(draggedNode);
01177           if (parentLink && isAltKeyDown) {
01178             return NS_OK;
01179           }
01180 
01181           area  = do_QueryInterface(draggedNode);
01182           image = do_QueryInterface(draggedNode);
01183           link  = do_QueryInterface(draggedNode);
01184         }
01185       }
01186     }
01187 
01188     {
01189       // set for linked images, and links
01190       nsCOMPtr<nsIDOMNode> linkNode;
01191 
01192       if (area) {
01193         // use the alt text (or, if missing, the href) as the title
01194         area->GetAttribute(NS_LITERAL_STRING("alt"), mTitleString);
01195         if (mTitleString.IsEmpty()) {
01196           // this can be a relative link
01197           area->GetAttribute(NS_LITERAL_STRING("href"), mTitleString);
01198         }
01199 
01200         // we'll generate HTML like <a href="absurl">alt text</a>
01201         mIsAnchor = PR_TRUE;
01202 
01203         // gives an absolute link
01204         GetAnchorURL(area, mUrlString);
01205 
01206         mHtmlString.AssignLiteral("<a href=\"");
01207         mHtmlString.Append(mUrlString);
01208         mHtmlString.AppendLiteral("\">");
01209         mHtmlString.Append(mTitleString);
01210         mHtmlString.AppendLiteral("</a>");
01211       } else if (image) {
01212         mIsAnchor = PR_TRUE;
01213         // grab the href as the url, use alt text as the title of the
01214         // area if it's there.  the drag data is the image tag and src
01215         // attribute.
01216         nsCOMPtr<nsIURI> imageURI;
01217         image->GetCurrentURI(getter_AddRefs(imageURI));
01218         if (imageURI) {
01219           nsCAutoString spec;
01220           imageURI->GetSpec(spec);
01221           CopyUTF8toUTF16(spec, mUrlString);
01222         }
01223 
01224         nsCOMPtr<nsIDOMElement> imageElement(do_QueryInterface(image));
01225         // XXXbz Shouldn't we use the "title" attr for title?  Using
01226         // "alt" seems very wrong....
01227         if (imageElement) {
01228           imageElement->GetAttribute(NS_LITERAL_STRING("alt"), mTitleString);
01229         }
01230 
01231         if (mTitleString.IsEmpty()) {
01232           mTitleString = mUrlString;
01233         }
01234 
01235         nsCOMPtr<imgIRequest> imgRequest;
01236 
01237         // grab the image data, and its request.
01238         nsCOMPtr<nsIImage> img =
01239           nsContentUtils::GetImageFromContent(image,
01240                                               getter_AddRefs(imgRequest));
01241 
01242         nsCOMPtr<nsIMIMEService> mimeService =
01243           do_GetService("@mozilla.org/mime;1");
01244 
01245         // Fix the file extension in the URL if necessary
01246         if (imgRequest && mimeService) {
01247           nsCOMPtr<nsIURI> imgUri;
01248           imgRequest->GetURI(getter_AddRefs(imgUri));
01249 
01250           nsCOMPtr<nsIURL> imgUrl(do_QueryInterface(imgUri));
01251 
01252           if (imgUrl) {
01253             nsCAutoString extension;
01254             imgUrl->GetFileExtension(extension);
01255 
01256             nsXPIDLCString mimeType;
01257             imgRequest->GetMimeType(getter_Copies(mimeType));
01258 
01259             nsCOMPtr<nsIMIMEInfo> mimeInfo;
01260             mimeService->GetFromTypeAndExtension(mimeType, EmptyCString(),
01261                                                  getter_AddRefs(mimeInfo));
01262 
01263             if (mimeInfo) {
01264               nsCAutoString spec;
01265               imgUrl->GetSpec(spec);
01266 
01267               // pass out the image source string
01268               CopyUTF8toUTF16(spec, mImageSourceString);
01269 
01270               PRBool validExtension;
01271               if (NS_FAILED(mimeInfo->ExtensionExists(extension,
01272                                                       &validExtension)) ||
01273                   !validExtension) {
01274                 // Fix the file extension in the URL
01275                 nsresult rv = imgUrl->Clone(getter_AddRefs(imgUri));
01276                 NS_ENSURE_SUCCESS(rv, rv);
01277 
01278                 imgUrl = do_QueryInterface(imgUri);
01279 
01280                 nsCAutoString primaryExtension;
01281                 mimeInfo->GetPrimaryExtension(primaryExtension);
01282 
01283                 imgUrl->SetFileExtension(primaryExtension);
01284               }
01285 
01286               nsCAutoString fileName;
01287               imgUrl->GetFileName(fileName);
01288 
01289               NS_UnescapeURL(fileName);
01290 
01291               // make the filename safe for the filesystem
01292               fileName.ReplaceChar(FILE_PATH_SEPARATOR FILE_ILLEGAL_CHARACTERS,
01293                                    '-');
01294 
01295               CopyUTF8toUTF16(fileName, mImageDestFileName);
01296 
01297               // and the image object
01298               mImage = img;
01299             }
01300           }
01301         }
01302 
01303         if (parentLink) {
01304           // If we are dragging around an image in an anchor, then we
01305           // are dragging the entire anchor
01306           linkNode = parentLink;
01307           nodeToSerialize = linkNode;
01308         } else {
01309           nodeToSerialize = draggedNode;
01310         }
01311       } else if (link) {
01312         // set linkNode. The code below will handle this
01313         linkNode = link;    // XXX test this
01314         GetNodeString(draggedNode, mTitleString);
01315       } else if (parentLink) {
01316         // parentLink will always be null if there's selected content
01317         linkNode = parentLink;
01318         nodeToSerialize = linkNode;
01319       } else if (!haveSelectedContent) {
01320         // nothing draggable
01321         return NS_OK;
01322       }
01323 
01324       if (linkNode) {
01325         mIsAnchor = PR_TRUE;
01326         GetAnchorURL(linkNode, mUrlString);
01327       }
01328     }
01329   }
01330 
01331   if (nodeToSerialize || useSelectedText) {
01332     // if we have selected text, use it in preference to the node
01333     if (useSelectedText) {
01334       nodeToSerialize = nsnull;
01335     }
01336 
01337     SerializeNodeOrSelection(serializeAsHTML,
01338                              nsIDocumentEncoder::OutputAbsoluteLinks |
01339                              nsIDocumentEncoder::OutputEncodeW3CEntities,
01340                              window, nodeToSerialize,
01341                              mHtmlString, mContextString, mInfoString);
01342 
01343     nsAutoString dummy1, dummy2;
01344     SerializeNodeOrSelection(serializeAsText, 0,
01345                              window, nodeToSerialize,
01346                              mTitleString, dummy1, dummy2);
01347 
01348 #ifdef CHANGE_SELECTION_ON_DRAG
01349     // We used to change the selection to wrap the dragged node (mainly
01350     // to work around now-fixed issues with dragging unselected elements).
01351     // There is no reason to do this any more.
01352     NormalizeSelection(selectionNormalizeNode, selection);
01353 #endif
01354   }
01355 
01356   // default text value is the URL
01357   if (mTitleString.IsEmpty()) {
01358     mTitleString = mUrlString;
01359   }
01360 
01361   // if we haven't constructed a html version, make one now
01362   if (mHtmlString.IsEmpty() && !mUrlString.IsEmpty())
01363     CreateLinkText(mUrlString, mTitleString, mHtmlString);
01364 
01365   return ConvertStringsToTransferable(outTrans);
01366 }
01367 
01368 nsresult
01369 nsTransferableFactory::ConvertStringsToTransferable(nsITransferable** outTrans)
01370 {
01371   // now create the transferable and stuff data into it.
01372   nsCOMPtr<nsITransferable> trans =
01373     do_CreateInstance("@mozilla.org/widget/transferable;1");
01374   NS_ENSURE_TRUE(trans, NS_ERROR_FAILURE);
01375 
01376   // add a special flavor if we're an anchor to indicate that we have
01377   // a URL in the drag data
01378   if (!mUrlString.IsEmpty() && mIsAnchor) {
01379     nsAutoString dragData(mUrlString);
01380     dragData.AppendLiteral("\n");
01381     dragData += mTitleString;
01382 
01383     nsCOMPtr<nsISupportsString> urlPrimitive =
01384       do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
01385     NS_ENSURE_TRUE(urlPrimitive, NS_ERROR_FAILURE);
01386 
01387     urlPrimitive->SetData(dragData);
01388     trans->SetTransferData(kURLMime, urlPrimitive,
01389                            dragData.Length() * sizeof(PRUnichar));
01390 
01391     nsCOMPtr<nsISupportsString> urlDataPrimitive =
01392       do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
01393     NS_ENSURE_TRUE(urlDataPrimitive, NS_ERROR_FAILURE);
01394 
01395     urlDataPrimitive->SetData(mUrlString);
01396     trans->SetTransferData(kURLDataMime, urlDataPrimitive,
01397                            mUrlString.Length() * sizeof(PRUnichar));
01398 
01399     nsCOMPtr<nsISupportsString> urlDescPrimitive =
01400       do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
01401     NS_ENSURE_TRUE(urlDescPrimitive, NS_ERROR_FAILURE);
01402 
01403     urlDescPrimitive->SetData(mTitleString);
01404     trans->SetTransferData(kURLDescriptionMime, urlDescPrimitive,
01405                            mTitleString.Length() * sizeof(PRUnichar));
01406   }
01407 
01408   // add a special flavor, even if we don't have html context data
01409   nsCOMPtr<nsISupportsString> context =
01410     do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
01411   NS_ENSURE_TRUE(context, NS_ERROR_FAILURE);
01412 
01413   nsAutoString contextData(mContextString);
01414   context->SetData(contextData);
01415   trans->SetTransferData(kHTMLContext, context, contextData.Length() * 2);
01416 
01417   // add a special flavor if we have html info data
01418   if (!mInfoString.IsEmpty()) {
01419     nsCOMPtr<nsISupportsString> info =
01420       do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
01421     NS_ENSURE_TRUE(info, NS_ERROR_FAILURE);
01422 
01423     nsAutoString infoData(mInfoString);
01424     info->SetData(infoData);
01425     trans->SetTransferData(kHTMLInfo, info, infoData.Length() * 2);
01426   }
01427 
01428   // add the full html
01429   nsCOMPtr<nsISupportsString> htmlPrimitive =
01430     do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
01431   NS_ENSURE_TRUE(htmlPrimitive, NS_ERROR_FAILURE);
01432 
01433   htmlPrimitive->SetData(mHtmlString);
01434   trans->SetTransferData(kHTMLMime, htmlPrimitive,
01435                          mHtmlString.Length() * sizeof(PRUnichar));
01436 
01437   // add the plain (unicode) text. we use the url for text/unicode
01438   // data if an anchor is being dragged, rather than the title text of
01439   // the link or the alt text for an anchor image.
01440   nsCOMPtr<nsISupportsString> textPrimitive =
01441     do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
01442   NS_ENSURE_TRUE(textPrimitive, NS_ERROR_FAILURE);
01443 
01444   textPrimitive->SetData(mIsAnchor ? mUrlString : mTitleString);
01445   trans->SetTransferData(kUnicodeMime, textPrimitive,
01446                          (mIsAnchor ? mUrlString.Length() :
01447                           mTitleString.Length()) * sizeof(PRUnichar));
01448 
01449   // add image data, if present. For now, all we're going to do with
01450   // this is turn it into a native data flavor, so indicate that with
01451   // a new flavor so as not to confuse anyone who is really registered
01452   // for image/gif or image/jpg.
01453   if (mImage) {
01454     nsCOMPtr<nsISupportsInterfacePointer> ptrPrimitive =
01455       do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID);
01456     NS_ENSURE_TRUE(ptrPrimitive, NS_ERROR_FAILURE);
01457 
01458     ptrPrimitive->SetData(mImage);
01459     trans->SetTransferData(kNativeImageMime, ptrPrimitive,
01460                            sizeof(nsISupportsInterfacePointer*));
01461     // assume the image comes from a file, and add a file promise. We
01462     // register ourselves as a nsIFlavorDataProvider, and will use the
01463     // GetFlavorData callback to save the image to disk.
01464     trans->SetTransferData(kFilePromiseMime, mFlavorDataProvider,
01465                            nsITransferable::kFlavorHasDataProvider);
01466 
01467     nsCOMPtr<nsISupportsString> imageUrlPrimitive =
01468       do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
01469     NS_ENSURE_TRUE(imageUrlPrimitive, NS_ERROR_FAILURE);
01470 
01471     imageUrlPrimitive->SetData(mImageSourceString);
01472     trans->SetTransferData(kFilePromiseURLMime, imageUrlPrimitive,
01473                            mImageSourceString.Length() * sizeof(PRUnichar));
01474 
01475     nsCOMPtr<nsISupportsString> imageFileNamePrimitive =
01476       do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
01477     NS_ENSURE_TRUE(imageFileNamePrimitive, NS_ERROR_FAILURE);
01478 
01479     imageFileNamePrimitive->SetData(mImageDestFileName);
01480     trans->SetTransferData(kFilePromiseDestFilename, imageFileNamePrimitive,
01481                            mImageDestFileName.Length() * sizeof(PRUnichar));
01482 
01483     // if not an anchor, add the image url
01484     if (!mIsAnchor) {
01485       nsCOMPtr<nsISupportsString> urlDataPrimitive =
01486         do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
01487       NS_ENSURE_TRUE(urlDataPrimitive, NS_ERROR_FAILURE);
01488 
01489       urlDataPrimitive->SetData(mUrlString);
01490       trans->SetTransferData(kURLDataMime, urlDataPrimitive,
01491                              mUrlString.Length() * sizeof(PRUnichar));
01492     }
01493   }
01494 
01495   *outTrans = trans;
01496   NS_IF_ADDREF(*outTrans);
01497 
01498   return NS_OK;
01499 }
01500 
01501 // note that this can return NS_OK, but a null out param (by design)
01502 // static
01503 nsresult
01504 nsTransferableFactory::GetDraggableSelectionData(nsISelection* inSelection,
01505                                                  nsIDOMNode* inRealTargetNode,
01506                                                  nsIDOMNode **outImageOrLinkNode,
01507                                                  PRBool* outDragSelectedText)
01508 {
01509   NS_ENSURE_ARG(inSelection);
01510   NS_ENSURE_ARG(inRealTargetNode);
01511   NS_ENSURE_ARG_POINTER(outImageOrLinkNode);
01512 
01513   *outImageOrLinkNode = nsnull;
01514   *outDragSelectedText = PR_FALSE;
01515 
01516   PRBool selectionContainsTarget = PR_FALSE;
01517 
01518   PRBool isCollapsed = PR_FALSE;
01519   inSelection->GetIsCollapsed(&isCollapsed);
01520   if (!isCollapsed) {
01521     inSelection->ContainsNode(inRealTargetNode, PR_FALSE,
01522                               &selectionContainsTarget);
01523 
01524     if (selectionContainsTarget) {
01525       // track down the anchor node, if any, for the url
01526       nsCOMPtr<nsIDOMNode> selectionStart;
01527       inSelection->GetAnchorNode(getter_AddRefs(selectionStart));
01528 
01529       nsCOMPtr<nsIDOMNode> selectionEnd;
01530       inSelection->GetFocusNode(getter_AddRefs(selectionEnd));
01531 
01532       // look for a selection around a single node, like an image.
01533       // in this case, drag the image, rather than a serialization of the HTML
01534       // XXX generalize this to other draggable element types?
01535       if (selectionStart == selectionEnd) {
01536         PRBool hasChildren;
01537         selectionStart->HasChildNodes(&hasChildren);
01538         if (hasChildren) {
01539           // see if just one node is selected
01540           PRInt32 anchorOffset, focusOffset;
01541           inSelection->GetAnchorOffset(&anchorOffset);
01542           inSelection->GetFocusOffset(&focusOffset);
01543           if (abs(anchorOffset - focusOffset) == 1) {
01544             nsCOMPtr<nsIContent> selStartContent =
01545               do_QueryInterface(selectionStart);
01546 
01547             if (selStartContent) {
01548               PRInt32 childOffset =
01549                 (anchorOffset < focusOffset) ? anchorOffset : focusOffset;
01550               nsIContent *childContent =
01551                 selStartContent->GetChildAt(childOffset);
01552               // if we find an image, we'll fall into the node-dragging code,
01553               // rather the the selection-dragging code
01554               if (nsContentUtils::IsDraggableImage(childContent)) {
01555                 CallQueryInterface(childContent, outImageOrLinkNode);
01556 
01557                 return NS_OK;
01558               }
01559             }
01560           }
01561         }
01562       }
01563 
01564       // see if the selection is a link;  if so, its node will be returned
01565       GetSelectedLink(inSelection, outImageOrLinkNode);
01566 
01567       // indicate that a link or text is selected
01568       *outDragSelectedText = PR_TRUE;
01569     }
01570   }
01571 
01572   return NS_OK;
01573 }
01574 
01575 // static
01576 void nsTransferableFactory::GetSelectedLink(nsISelection* inSelection,
01577                                             nsIDOMNode **outLinkNode)
01578 {
01579   *outLinkNode = nsnull;
01580 
01581   nsCOMPtr<nsIDOMNode> selectionStart;
01582   inSelection->GetAnchorNode(getter_AddRefs(selectionStart));
01583   nsCOMPtr<nsIDOMNode> selectionEnd;
01584   inSelection->GetFocusNode(getter_AddRefs(selectionEnd));
01585 
01586   // simple case:  only one node is selected
01587   // see if it or its parent is an anchor, then exit
01588 
01589   if (selectionStart == selectionEnd) {
01590     nsCOMPtr<nsIDOMNode> link = FindParentLinkNode(selectionStart);
01591     if (link) {
01592       link.swap(*outLinkNode);
01593     }
01594 
01595     return;
01596   }
01597 
01598   // more complicated case:  multiple nodes are selected
01599 
01600   // Unless you use the Alt key while selecting anchor text, it is
01601   // nearly impossible to avoid overlapping into adjacent nodes.
01602   // Deal with this by trimming off the leading and/or trailing
01603   // nodes of the selection if the strings they produce are empty.
01604 
01605   // first, use a range determine if the selection was marked LTR or RTL;
01606   // if the latter, swap endpoints so we trim in the right direction
01607 
01608   PRInt32 startOffset, endOffset;
01609   {
01610     nsCOMPtr<nsIDOMRange> range;
01611     inSelection->GetRangeAt(0, getter_AddRefs(range));
01612     if (!range) {
01613       return;
01614     }
01615 
01616     nsCOMPtr<nsIDOMNode> tempNode;
01617     range->GetStartContainer( getter_AddRefs(tempNode));
01618     if (tempNode != selectionStart) {
01619       selectionEnd = selectionStart;
01620       selectionStart = tempNode;
01621       inSelection->GetAnchorOffset(&endOffset);
01622       inSelection->GetFocusOffset(&startOffset);
01623     } else {
01624       inSelection->GetAnchorOffset(&startOffset);
01625       inSelection->GetFocusOffset(&endOffset);
01626     }
01627   }
01628 
01629   // trim leading node if the string is empty or
01630   // the selection starts at the end of the text
01631 
01632   nsAutoString nodeStr;
01633   selectionStart->GetNodeValue(nodeStr);
01634   if (nodeStr.IsEmpty() ||
01635       startOffset+1 >= NS_STATIC_CAST(PRInt32, nodeStr.Length())) {
01636     nsCOMPtr<nsIDOMNode> curr = selectionStart;
01637     nsIDOMNode* next;
01638 
01639     while (curr) {
01640       curr->GetNextSibling(&next);
01641 
01642       if (next) {
01643         selectionStart = dont_AddRef(next);
01644         break;
01645       }
01646 
01647       curr->GetParentNode(&next);
01648       curr = dont_AddRef(next);
01649     }
01650   }
01651 
01652   // trim trailing node if the selection ends before its text begins
01653 
01654   if (endOffset == 0) {
01655     nsCOMPtr<nsIDOMNode> curr = selectionEnd;
01656     nsIDOMNode* next;
01657 
01658     while (curr) {
01659       curr->GetPreviousSibling(&next);
01660 
01661       if (next){
01662         selectionEnd = dont_AddRef(next);
01663         break;
01664       }
01665 
01666       curr->GetParentNode(&next);
01667       curr = dont_AddRef(next);
01668     }
01669   }
01670 
01671   // see if the leading & trailing nodes are part of the
01672   // same anchor - if so, return the anchor node
01673   nsCOMPtr<nsIDOMNode> link = FindParentLinkNode(selectionStart);
01674   if (link) {
01675     nsCOMPtr<nsIDOMNode> link2 = FindParentLinkNode(selectionEnd);
01676 
01677     if (link == link2) {
01678       NS_IF_ADDREF(*outLinkNode = link);
01679     }
01680   }
01681 
01682   return;
01683 }
01684 
01685 // static
01686 nsresult
01687 nsTransferableFactory::SerializeNodeOrSelection(serializationMode inMode,
01688                                                 PRUint32 inFlags,
01689                                                 nsIDOMWindow* inWindow,
01690                                                 nsIDOMNode* inNode,
01691                                                 nsAString& outResultString,
01692                                                 nsAString& outContext,
01693                                                 nsAString& outInfo)
01694 {
01695   NS_ENSURE_ARG_POINTER(inWindow);
01696 
01697   nsresult rv;
01698   nsCOMPtr<nsIDocumentEncoder> encoder;
01699   static const char *textplain = "text/plain";
01700 
01701   if (inMode == serializeAsText) {
01702     nsCAutoString formatType(NS_DOC_ENCODER_CONTRACTID_BASE);
01703     formatType.Append(textplain);
01704     encoder = do_CreateInstance(formatType.get(), &rv);
01705   } else {
01706     encoder = do_CreateInstance(NS_HTMLCOPY_ENCODER_CONTRACTID, &rv);
01707   }
01708   NS_ENSURE_SUCCESS(rv, rv);
01709 
01710   nsCOMPtr<nsIDOMDocument> domDoc;
01711   inWindow->GetDocument(getter_AddRefs(domDoc));
01712   nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
01713   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
01714 
01715   nsCOMPtr<nsIDOMRange> range;
01716   nsCOMPtr<nsISelection> selection;
01717   if (inNode) {
01718     // make a range around this node
01719     rv = NS_NewRange(getter_AddRefs(range));
01720     NS_ENSURE_SUCCESS(rv, rv);
01721     rv = range->SelectNode(inNode);
01722     NS_ENSURE_SUCCESS(rv, rv);
01723   } else {
01724     inWindow->GetSelection(getter_AddRefs(selection));
01725     inFlags |= nsIDocumentEncoder::OutputSelectionOnly;
01726   }
01727 
01728   if (inMode == serializeAsText) {
01729     rv = encoder->Init(doc, NS_ConvertASCIItoUCS2(textplain), inFlags);
01730   } else {
01731     rv = encoder->Init(doc, NS_LITERAL_STRING(kHTMLMime), inFlags);
01732   }
01733   NS_ENSURE_SUCCESS(rv, rv);
01734 
01735   if (range) {
01736     encoder->SetRange(range);
01737   } else if (selection) {
01738     encoder->SetSelection(selection);
01739   }
01740 
01741   if (inMode == serializeAsText) {
01742     outContext.Truncate();
01743     outInfo.Truncate();
01744     return encoder->EncodeToString(outResultString);
01745   }
01746 
01747   return encoder->EncodeToStringWithContext(outResultString, outContext,
01748                                             outInfo);
01749 }