Back to index

lightning-sunbird  0.9+nobinonly
nsHTMLDataTransfer.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.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either of the GNU General Public License Version 2 or later (the "GPL"),
00026  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 #include "nsICaret.h"
00038 
00039 
00040 #include "nsHTMLEditor.h"
00041 #include "nsHTMLEditRules.h"
00042 #include "nsTextEditUtils.h"
00043 #include "nsHTMLEditUtils.h"
00044 #include "nsWSRunObject.h"
00045 
00046 #include "nsEditorEventListeners.h"
00047 
00048 #include "nsIDOMText.h"
00049 #include "nsIDOMNodeList.h"
00050 #include "nsIDOMDocument.h"
00051 #include "nsIDOMAttr.h"
00052 #include "nsIDocument.h"
00053 #include "nsIDOMEventReceiver.h" 
00054 #include "nsIDOMNSEvent.h"
00055 #include "nsIDOMKeyEvent.h"
00056 #include "nsIDOMKeyListener.h" 
00057 #include "nsIDOMMouseListener.h"
00058 #include "nsIDOMMouseEvent.h"
00059 #include "nsISelection.h"
00060 #include "nsISelectionPrivate.h"
00061 #include "nsIDOMHTMLAnchorElement.h"
00062 #include "nsIDOMHTMLImageElement.h"
00063 #include "nsISelectionController.h"
00064 #include "nsIFileChannel.h"
00065 #include "nsIFrameSelection.h"  // For TABLESELECTION_ defines
00066 
00067 #include "nsICSSLoader.h"
00068 #include "nsICSSStyleSheet.h"
00069 #include "nsIDocumentObserver.h"
00070 #include "nsIDocumentStateListener.h"
00071 
00072 #include "nsIEnumerator.h"
00073 #include "nsIContent.h"
00074 #include "nsIContentIterator.h"
00075 #include "nsIDOMRange.h"
00076 #include "nsIDOMNSRange.h"
00077 #include "nsISupportsArray.h"
00078 #include "nsCOMArray.h"
00079 #include "nsVoidArray.h"
00080 #include "nsIFile.h"
00081 #include "nsIURL.h"
00082 #include "nsIComponentManager.h"
00083 #include "nsIServiceManager.h"
00084 #include "nsIDocumentEncoder.h"
00085 #include "nsIDOMDocumentFragment.h"
00086 #include "nsIPresShell.h"
00087 #include "nsPresContext.h"
00088 #include "nsIParser.h"
00089 #include "nsParserCIID.h"
00090 #include "nsIImage.h"
00091 #include "nsAOLCiter.h"
00092 #include "nsInternetCiter.h"
00093 #include "nsXPCOM.h"
00094 #include "nsISupportsPrimitives.h"
00095 #include "nsLinebreakConverter.h"
00096 #include "nsIFragmentContentSink.h"
00097 #include "nsIContentSink.h"
00098 
00099 // netwerk
00100 #include "nsIURI.h"
00101 #include "nsNetUtil.h"
00102 
00103 // Drag & Drop, Clipboard
00104 #include "nsIClipboard.h"
00105 #include "nsITransferable.h"
00106 #include "nsIDragService.h"
00107 #include "nsIDOMNSUIEvent.h"
00108 #include "nsIOutputStream.h"
00109 #include "nsIInputStream.h"
00110 #include "nsDirectoryServiceDefs.h"
00111 
00112 // for relativization
00113 #include "nsUnicharUtils.h"
00114 #include "nsIDOMDocumentTraversal.h"
00115 #include "nsIDOMTreeWalker.h"
00116 #include "nsIDOMNodeFilter.h"
00117 #include "nsIDOMNamedNodeMap.h"
00118 #include "nsIDOMHTMLLinkElement.h"
00119 #include "nsIDOMHTMLObjectElement.h"
00120 #include "nsIDOMHTMLFrameElement.h"
00121 #include "nsIDOMHTMLIFrameElement.h"
00122 #include "nsIDOMHTMLInputElement.h"
00123 #include "nsIDOMHTMLScriptElement.h"
00124 #include "nsIDOMHTMLEmbedElement.h"
00125 #include "nsIDOMHTMLTableCellElement.h"
00126 #include "nsIDOMHTMLTableRowElement.h"
00127 #include "nsIDOMHTMLTableElement.h"
00128 #include "nsIDOMHTMLBodyElement.h"
00129 
00130 // Misc
00131 #include "TextEditorTest.h"
00132 #include "nsEditorUtils.h"
00133 #include "nsIPrefBranch.h"
00134 #include "nsIPrefService.h"
00135 #include "nsIContentFilter.h"
00136 
00137 const PRUnichar nbsp = 160;
00138 
00139 static NS_DEFINE_CID(kCParserCID,     NS_PARSER_CID);
00140 
00141 // private clipboard data flavors for html copy/paste
00142 #define kHTMLContext   "text/_moz_htmlcontext"
00143 #define kHTMLInfo      "text/_moz_htmlinfo"
00144 
00145 // some little helpers
00146 static PRInt32 FindPositiveIntegerAfterString(const char *aLeadingString, nsCString &aCStr);
00147 static nsresult RemoveFragComments(nsCString &theStr);
00148 static void RemoveBodyAndHead(nsIDOMNode *aNode);
00149 
00150 static nsCOMPtr<nsIDOMNode> GetListParent(nsIDOMNode* aNode)
00151 {
00152   if (!aNode) return nsnull;
00153   nsCOMPtr<nsIDOMNode> parent, tmp;
00154   aNode->GetParentNode(getter_AddRefs(parent));
00155   while (parent)
00156   {
00157     if (nsHTMLEditUtils::IsList(parent)) return parent;
00158     parent->GetParentNode(getter_AddRefs(tmp));
00159     parent = tmp;
00160   }
00161   return nsnull;
00162 }
00163 
00164 static nsCOMPtr<nsIDOMNode> GetTableParent(nsIDOMNode* aNode)
00165 {
00166   if (!aNode) return nsnull;
00167   nsCOMPtr<nsIDOMNode> parent, tmp;
00168   aNode->GetParentNode(getter_AddRefs(parent));
00169   while (parent)
00170   {
00171     if (nsHTMLEditUtils::IsTable(parent)) return parent;
00172     parent->GetParentNode(getter_AddRefs(tmp));
00173     parent = tmp;
00174   }
00175   return nsnull;
00176 }
00177 
00178 
00179 NS_IMETHODIMP nsHTMLEditor::LoadHTML(const nsAString & aInputString)
00180 {
00181   if (!mRules) return NS_ERROR_NOT_INITIALIZED;
00182 
00183   // force IME commit; set up rules sniffing and batching
00184   ForceCompositionEnd();
00185   nsAutoEditBatch beginBatching(this);
00186   nsAutoRules beginRulesSniffing(this, kOpLoadHTML, nsIEditor::eNext);
00187   
00188   // Get selection
00189   nsCOMPtr<nsISelection>selection;
00190   nsresult res = GetSelection(getter_AddRefs(selection));
00191   if (NS_FAILED(res)) return res;
00192   
00193   nsTextRulesInfo ruleInfo(nsTextEditRules::kLoadHTML);
00194   PRBool cancel, handled;
00195   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
00196   if (NS_FAILED(res)) return res;
00197   if (cancel) return NS_OK; // rules canceled the operation
00198   if (!handled)
00199   {
00200     PRBool isCollapsed;
00201     res = selection->GetIsCollapsed(&isCollapsed);
00202     if (NS_FAILED(res)) return res;
00203 
00204     // Delete Selection, but only if it isn't collapsed, see bug #106269
00205     if (!isCollapsed) 
00206     {
00207       res = DeleteSelection(eNone);
00208       if (NS_FAILED(res)) return res;
00209     }
00210 
00211     // Get the first range in the selection, for context:
00212     nsCOMPtr<nsIDOMRange> range;
00213     res = selection->GetRangeAt(0, getter_AddRefs(range));
00214     NS_ENSURE_SUCCESS(res, res);
00215     if (!range)
00216       return NS_ERROR_NULL_POINTER;
00217     nsCOMPtr<nsIDOMNSRange> nsrange (do_QueryInterface(range));
00218     if (!nsrange)
00219       return NS_ERROR_NO_INTERFACE;
00220 
00221     // create fragment for pasted html
00222     nsCOMPtr<nsIDOMDocumentFragment> docfrag;
00223     {
00224       res = nsrange->CreateContextualFragment(aInputString, getter_AddRefs(docfrag));
00225       NS_ENSURE_SUCCESS(res, res);
00226     }
00227     // put the fragment into the document
00228     nsCOMPtr<nsIDOMNode> parent, junk;
00229     res = range->GetStartContainer(getter_AddRefs(parent));
00230     NS_ENSURE_SUCCESS(res, res);
00231     if (!parent)
00232       return NS_ERROR_NULL_POINTER;
00233     PRInt32 childOffset;
00234     res = range->GetStartOffset(&childOffset);
00235     NS_ENSURE_SUCCESS(res, res);
00236 
00237     nsCOMPtr<nsIDOMNode> nodeToInsert;
00238     docfrag->GetFirstChild(getter_AddRefs(nodeToInsert));
00239     while (nodeToInsert)
00240     {
00241       res = InsertNode(nodeToInsert, parent, childOffset++);
00242       NS_ENSURE_SUCCESS(res, res);
00243       docfrag->GetFirstChild(getter_AddRefs(nodeToInsert));
00244     }
00245   }
00246 
00247   return mRules->DidDoAction(selection, &ruleInfo, res);
00248 }
00249 
00250 
00251 NS_IMETHODIMP nsHTMLEditor::InsertHTML(const nsAString & aInString)
00252 {
00253   const nsAFlatString& empty = EmptyString();
00254 
00255   return InsertHTMLWithContext(aInString, empty, empty, empty,
00256                                nsnull,  nsnull, 0, PR_TRUE);
00257 }
00258 
00259 
00260 nsresult
00261 nsHTMLEditor::InsertHTMLWithContext(const nsAString & aInputString,
00262                                     const nsAString & aContextStr,
00263                                     const nsAString & aInfoStr,
00264                                     const nsAString & aFlavor,
00265                                     nsIDOMDocument *aSourceDoc,
00266                                     nsIDOMNode *aDestNode,
00267                                     PRInt32 aDestOffset,
00268                                     PRBool aDeleteSelection)
00269 {
00270   if (!mRules) return NS_ERROR_NOT_INITIALIZED;
00271 
00272   // force IME commit; set up rules sniffing and batching
00273   ForceCompositionEnd();
00274   nsAutoEditBatch beginBatching(this);
00275   nsAutoRules beginRulesSniffing(this, kOpHTMLPaste, nsIEditor::eNext);
00276   
00277   // Get selection
00278   nsresult res;
00279   nsCOMPtr<nsISelection>selection;
00280   res = GetSelection(getter_AddRefs(selection));
00281   if (NS_FAILED(res)) return res;
00282   
00283   // create a dom document fragment that represents the structure to paste
00284   nsCOMPtr<nsIDOMNode> fragmentAsNode;
00285   PRInt32 rangeStartHint, rangeEndHint;
00286 
00287   nsAutoString contextStr;
00288   contextStr.Assign(aContextStr);
00289 
00290 #ifdef MOZ_THUNDERBIRD
00291   // See Bug #228920 --> editor / parser has trouble inserting single cell data from Excel.
00292   // The details are in the bug. Until we figure out why the parser is not building the right 
00293   // document structure for the single cell paste case, we can explicitly check for just such  
00294   // a condition and work around it. By setting the contextStr to an empty string we end up 
00295   // pasting just the cell text which is what we want anyway. 
00296   // A paste from an excel cell always starts with a new line, two spaces and then the td tag
00297   if (StringBeginsWith(aInputString, NS_LITERAL_STRING("\n  <td"))) 
00298     contextStr.Truncate();
00299 #endif
00300 
00301   res = CreateDOMFragmentFromPaste(aInputString, contextStr, aInfoStr, 
00302                                             address_of(fragmentAsNode),
00303                                             &rangeStartHint, &rangeEndHint);
00304   NS_ENSURE_SUCCESS(res, res);
00305 
00306   nsCOMPtr<nsIDOMNode> targetNode, streamStartParent, streamEndParent, tempNode;
00307   PRInt32 targetOffset=0, streamStartOffset=0, streamEndOffset=0, k;
00308   
00309   if (!aDestNode)
00310   {
00311     // if caller didn't provide the destination/target node,
00312     // fetch the paste insertion point from our selection
00313     res = GetStartNodeAndOffset(selection, address_of(targetNode), &targetOffset);
00314     if (!targetNode) res = NS_ERROR_FAILURE;
00315     if (NS_FAILED(res)) return res;
00316   }
00317   else
00318   {
00319     targetNode = aDestNode;
00320     targetOffset = aDestOffset;
00321   }
00322   
00323   // fetch the start parent/offset by walking down the leading edge of 
00324   // the fragmentAsNode tree (rangeStartHint # of times)
00325   streamStartParent = fragmentAsNode;
00326   for (k = 0; k < rangeStartHint; k++)
00327   {
00328     streamStartParent->GetFirstChild(getter_AddRefs(tempNode));
00329     if (!tempNode) return NS_ERROR_FAILURE;
00330     streamStartParent = tempNode;
00331   }
00332   // streamStartOffset is just always zero at this point
00333   
00334   // fetch the end parent/offset by walking down the trailing edge of 
00335   // the fragmentAsNode tree (rangeEndHint # of times)
00336   streamEndParent = fragmentAsNode;
00337   for (k = 0; k < rangeEndHint; k++)
00338   {
00339     streamEndParent->GetLastChild(getter_AddRefs(tempNode));
00340     if (!tempNode) return NS_ERROR_FAILURE;
00341     streamEndParent = tempNode;
00342   }
00343   // streamEndOffset is just always after last child of streamEndParent at this point
00344   res = GetLengthOfDOMNode(streamEndParent, (PRUint32&)streamEndOffset);
00345   if (NS_FAILED(res)) return res;
00346 
00347   PRBool doContinue = PR_TRUE;
00348 
00349   res = DoContentFilterCallback(aFlavor, aSourceDoc, aDeleteSelection,
00350                                 (nsIDOMNode **)address_of(fragmentAsNode), 
00351                                 (nsIDOMNode **)address_of(streamStartParent), 
00352                                 &streamStartOffset,
00353                                 (nsIDOMNode **)address_of(streamEndParent),
00354                                 &streamEndOffset, 
00355                                 (nsIDOMNode **)address_of(targetNode), 
00356                                 &targetOffset, &doContinue);
00357 
00358   NS_ENSURE_SUCCESS(res, res);
00359   if (!doContinue)
00360     return NS_OK;
00361 
00362   // if we have a destination / target node, we want to insert there
00363   // rather than in place of the selection
00364   // ignore aDeleteSelection here if no aDestNode since deletion will
00365   // also occur later; this block is intended to cover the various
00366   // scenarios where we are dropping in an editor (and may want to delete
00367   // the selection before collapsing the selection in the new destination)
00368   if (aDestNode)
00369   {
00370     if (aDeleteSelection)
00371     {
00372       // Use an auto tracker so that our drop point is correctly
00373       // positioned after the delete.
00374       nsAutoTrackDOMPoint tracker(mRangeUpdater, &targetNode, &targetOffset);
00375       res = DeleteSelection(eNone);
00376       NS_ENSURE_SUCCESS(res, res);
00377     }
00378 
00379     res = selection->Collapse(targetNode, targetOffset);
00380     NS_ENSURE_SUCCESS(res, res);
00381   }
00382 
00383   // we need to recalculate various things based on potentially new offsets
00384   // this is work to be completed at a later date (probably by jfrancis)
00385 
00386   // make a list of what nodes in docFrag we need to move
00387   nsCOMArray<nsIDOMNode> nodeList;
00388   res = CreateListOfNodesToPaste(fragmentAsNode, nodeList,
00389                                  streamStartParent, streamStartOffset,
00390                                  streamEndParent, streamEndOffset);
00391   NS_ENSURE_SUCCESS(res, res);
00392 
00393   if (nodeList.Count() == 0)
00394     return NS_OK;
00395 
00396   // walk list of nodes; perform surgery on nodes (relativize) with _mozattr
00397   res = RelativizeURIInFragmentList(nodeList, aFlavor, aSourceDoc, targetNode);
00398   // ignore results from this call, try to paste/insert anyways
00399  
00400   // are there any table elements in the list?  
00401   // node and offset for insertion
00402   nsCOMPtr<nsIDOMNode> parentNode;
00403   PRInt32 offsetOfNewNode;
00404   
00405   // check for table cell selection mode
00406   PRBool cellSelectionMode = PR_FALSE;
00407   nsCOMPtr<nsIDOMElement> cell;
00408   res = GetFirstSelectedCell(nsnull, getter_AddRefs(cell));
00409   if (NS_SUCCEEDED(res) && cell)
00410   {
00411     cellSelectionMode = PR_TRUE;
00412   }
00413   
00414   if (cellSelectionMode)
00415   {
00416     // do we have table content to paste?  If so, we want to delete
00417     // the selected table cells and replace with new table elements;
00418     // but if not we want to delete _contents_ of cells and replace
00419     // with non-table elements.  Use cellSelectionMode bool to 
00420     // indicate results.
00421     nsIDOMNode* firstNode = nodeList[0];
00422     if (!nsHTMLEditUtils::IsTableElement(firstNode))
00423       cellSelectionMode = PR_FALSE;
00424   }
00425 
00426   if (!cellSelectionMode)
00427   {
00428     res = DeleteSelectionAndPrepareToCreateNode(parentNode, offsetOfNewNode);
00429     NS_ENSURE_SUCCESS(res, res);
00430 
00431     // pasting does not inherit local inline styles
00432     res = RemoveAllInlineProperties();
00433     NS_ENSURE_SUCCESS(res, res);
00434   }
00435   else
00436   {
00437     // delete whole cells: we will replace with new table content
00438     if (1)
00439     {
00440       // Save current selection since DeleteTableCell perturbs it
00441       nsAutoSelectionReset selectionResetter(selection, this);
00442       res = DeleteTableCell(1);
00443       NS_ENSURE_SUCCESS(res, res);
00444     }
00445     // collapse selection to beginning of deleted table content
00446     selection->CollapseToStart();
00447   }
00448   
00449   // give rules a chance to handle or cancel
00450   nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
00451   PRBool cancel, handled;
00452   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
00453   if (NS_FAILED(res)) return res;
00454   if (cancel) return NS_OK; // rules canceled the operation
00455   if (!handled)
00456   {
00457     // The rules code (WillDoAction above) might have changed the selection.  
00458     // refresh our memory...
00459     res = GetStartNodeAndOffset(selection, address_of(parentNode), &offsetOfNewNode);
00460     if (!parentNode) res = NS_ERROR_FAILURE;
00461     if (NS_FAILED(res)) return res;
00462 
00463     // Adjust position based on the first node we are going to insert.
00464     NormalizeEOLInsertPosition(nodeList[0], address_of(parentNode), &offsetOfNewNode);
00465 
00466     // if there are any invisible br's after our insertion point, remove them.
00467     // this is because if there is a br at end of what we paste, it will make
00468     // the invisible br visible.
00469     nsWSRunObject wsObj(this, parentNode, offsetOfNewNode);
00470     if (nsTextEditUtils::IsBreak(wsObj.mEndReasonNode) && 
00471         !IsVisBreak(wsObj.mEndReasonNode) )
00472     {
00473       res = DeleteNode(wsObj.mEndReasonNode);
00474       if (NS_FAILED(res)) return res;
00475     }
00476     
00477     // remeber if we are in a link.  
00478     PRBool bStartedInLink = IsInLink(parentNode);
00479   
00480     // are we in a text node?  If so, split it.
00481     if (IsTextNode(parentNode))
00482     {
00483       nsCOMPtr<nsIDOMNode> temp;
00484       res = SplitNodeDeep(parentNode, parentNode, offsetOfNewNode, &offsetOfNewNode);
00485       if (NS_FAILED(res)) return res;
00486       res = parentNode->GetParentNode(getter_AddRefs(temp));
00487       if (NS_FAILED(res)) return res;
00488       parentNode = temp;
00489     }
00490 
00491     // build up list of parents of first node in list that are either
00492     // lists or tables.  First examine front of paste node list.
00493     nsCOMArray<nsIDOMNode> startListAndTableArray;
00494     res = GetListAndTableParents(PR_FALSE, nodeList, startListAndTableArray);
00495     NS_ENSURE_SUCCESS(res, res);
00496     
00497     // remember number of lists and tables above us
00498     PRInt32 highWaterMark = -1;
00499     if (startListAndTableArray.Count() > 0)
00500     {
00501       res = DiscoverPartialListsAndTables(nodeList, startListAndTableArray, &highWaterMark);
00502       NS_ENSURE_SUCCESS(res, res);
00503     }
00504 
00505     // if we have pieces of tables or lists to be inserted, let's force the paste 
00506     // to deal with table elements right away, so that it doesn't orphan some 
00507     // table or list contents outside the table or list.
00508     if (highWaterMark >= 0)
00509     {
00510       res = ReplaceOrphanedStructure(PR_FALSE, nodeList, startListAndTableArray, highWaterMark);
00511       NS_ENSURE_SUCCESS(res, res);
00512     }
00513     
00514     // Now go through the same process again for the end of the paste node list.
00515     nsCOMArray<nsIDOMNode> endListAndTableArray;
00516     res = GetListAndTableParents(PR_TRUE, nodeList, endListAndTableArray);
00517     NS_ENSURE_SUCCESS(res, res);
00518     highWaterMark = -1;
00519    
00520     // remember number of lists and tables above us
00521     if (endListAndTableArray.Count() > 0)
00522     {
00523       res = DiscoverPartialListsAndTables(nodeList, endListAndTableArray, &highWaterMark);
00524       NS_ENSURE_SUCCESS(res, res);
00525     }
00526     
00527     // don't orphan partial list or table structure
00528     if (highWaterMark >= 0)
00529     {
00530       res = ReplaceOrphanedStructure(PR_TRUE, nodeList, endListAndTableArray, highWaterMark);
00531       NS_ENSURE_SUCCESS(res, res);
00532     }
00533 
00534     // Loop over the node list and paste the nodes:
00535     PRBool bDidInsert = PR_FALSE;
00536     nsCOMPtr<nsIDOMNode> parentBlock, lastInsertNode, insertedContextParent;
00537     PRInt32 listCount = nodeList.Count();
00538     PRInt32 j;
00539     if (IsBlockNode(parentNode))
00540       parentBlock = parentNode;
00541     else
00542       parentBlock = GetBlockNodeParent(parentNode);
00543       
00544     for (j=0; j<listCount; j++)
00545     {
00546       nsCOMPtr<nsIDOMNode> curNode = nodeList[j];
00547 
00548       NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE);
00549       NS_ENSURE_TRUE(curNode != fragmentAsNode, NS_ERROR_FAILURE);
00550       NS_ENSURE_TRUE(!nsTextEditUtils::IsBody(curNode), NS_ERROR_FAILURE);
00551       
00552       if (insertedContextParent)
00553       {
00554         // if we had to insert something higher up in the paste hierarchy, we want to 
00555         // skip any further paste nodes that descend from that.  Else we will paste twice.
00556         if (nsEditorUtils::IsDescendantOf(curNode, insertedContextParent))
00557           continue;
00558       }
00559       
00560       // give the user a hand on table element insertion.  if they have
00561       // a table or table row on the clipboard, and are trying to insert
00562       // into a table or table row, insert the appropriate children instead.
00563       if (  (nsHTMLEditUtils::IsTableRow(curNode) && nsHTMLEditUtils::IsTableRow(parentNode))
00564          && (nsHTMLEditUtils::IsTable(curNode)    || nsHTMLEditUtils::IsTable(parentNode)) )
00565       {
00566         nsCOMPtr<nsIDOMNode> child;
00567         curNode->GetFirstChild(getter_AddRefs(child));
00568         while (child)
00569         {
00570           res = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, PR_TRUE);
00571           if (NS_SUCCEEDED(res)) 
00572           {
00573             bDidInsert = PR_TRUE;
00574             lastInsertNode = child;
00575             offsetOfNewNode++;
00576           }
00577           curNode->GetFirstChild(getter_AddRefs(child));
00578         }
00579       }
00580       // give the user a hand on list insertion.  if they have
00581       // a list on the clipboard, and are trying to insert
00582       // into a list or list item, insert the appropriate children instead,
00583       // ie, merge the lists instead of pasting in a sublist.
00584       else if (nsHTMLEditUtils::IsList(curNode) && 
00585               (nsHTMLEditUtils::IsList(parentNode)  || nsHTMLEditUtils::IsListItem(parentNode)) )
00586       {
00587         nsCOMPtr<nsIDOMNode> child, tmp;
00588         curNode->GetFirstChild(getter_AddRefs(child));
00589         while (child)
00590         {
00591           if (nsHTMLEditUtils::IsListItem(child) || nsHTMLEditUtils::IsList(child))
00592           {
00593             // check if we are pasting into empty list item. If so
00594             // delete it and paste into parent list instead.
00595             if (nsHTMLEditUtils::IsListItem(parentNode))
00596             {
00597               PRBool isEmpty;
00598               res = IsEmptyNode(parentNode, &isEmpty, PR_TRUE);
00599               if ((NS_SUCCEEDED(res)) && isEmpty)
00600               {
00601                 nsCOMPtr<nsIDOMNode> listNode;
00602                 PRInt32 newOffset;
00603                 GetNodeLocation(parentNode, address_of(listNode), &newOffset);
00604                 if (listNode)
00605                 {
00606                   DeleteNode(parentNode);
00607                   parentNode = listNode;
00608                   offsetOfNewNode = newOffset;
00609                 }
00610               }
00611             } 
00612             res = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, PR_TRUE);
00613             if (NS_SUCCEEDED(res)) 
00614             {
00615               bDidInsert = PR_TRUE;
00616               lastInsertNode = child;
00617               offsetOfNewNode++;
00618             }
00619           }
00620           else
00621           {
00622             curNode->RemoveChild(child, getter_AddRefs(tmp));
00623           }
00624           curNode->GetFirstChild(getter_AddRefs(child));
00625         }
00626         
00627       }
00628       // check for pre's going into pre's.  
00629       else if (nsHTMLEditUtils::IsPre(parentBlock) && nsHTMLEditUtils::IsPre(curNode))
00630       {
00631         nsCOMPtr<nsIDOMNode> child, tmp;
00632         curNode->GetFirstChild(getter_AddRefs(child));
00633         while (child)
00634         {
00635           res = InsertNodeAtPoint(child, address_of(parentNode), &offsetOfNewNode, PR_TRUE);
00636           if (NS_SUCCEEDED(res)) 
00637           {
00638             bDidInsert = PR_TRUE;
00639             lastInsertNode = child;
00640             offsetOfNewNode++;
00641           }
00642           curNode->GetFirstChild(getter_AddRefs(child));
00643         }
00644       }
00645       else
00646       {
00647         
00648         // try to insert
00649         res = InsertNodeAtPoint(curNode, address_of(parentNode), &offsetOfNewNode, PR_TRUE);
00650         if (NS_SUCCEEDED(res)) 
00651         {
00652           bDidInsert = PR_TRUE;
00653           lastInsertNode = curNode;
00654         }
00655           
00656         // assume failure means no legal parent in the document heirarchy.
00657         // try again with the parent of curNode in the paste heirarchy.
00658         nsCOMPtr<nsIDOMNode> parent;
00659         while (NS_FAILED(res) && curNode)
00660         {
00661           curNode->GetParentNode(getter_AddRefs(parent));
00662           if (parent && !nsTextEditUtils::IsBody(parent))
00663           {
00664             res = InsertNodeAtPoint(parent, address_of(parentNode), &offsetOfNewNode, PR_TRUE);
00665             if (NS_SUCCEEDED(res)) 
00666             {
00667               bDidInsert = PR_TRUE;
00668               insertedContextParent = parent;
00669               lastInsertNode = parent;
00670             }
00671           }
00672           curNode = parent;
00673         }
00674       }
00675       if (bDidInsert)
00676       {
00677         res = GetNodeLocation(lastInsertNode, address_of(parentNode), &offsetOfNewNode);
00678         NS_ENSURE_SUCCESS(res, res);
00679         offsetOfNewNode++;
00680       }
00681     }
00682 
00683     // Now collapse the selection to the end of what we just inserted:
00684     if (lastInsertNode) 
00685     {
00686       // set selection to the end of what we just pasted.
00687       nsCOMPtr<nsIDOMNode> selNode, tmp, visNode, highTable;
00688       PRInt32 selOffset;
00689       
00690       // but dont cross tables
00691       if (!nsHTMLEditUtils::IsTable(lastInsertNode))
00692       {
00693         res = GetLastEditableLeaf(lastInsertNode, address_of(selNode));
00694         if (NS_FAILED(res)) return res;
00695         tmp = selNode;
00696         while (tmp && (tmp != lastInsertNode))
00697         {
00698           if (nsHTMLEditUtils::IsTable(tmp))
00699             highTable = tmp;
00700           nsCOMPtr<nsIDOMNode> parent = tmp;
00701           tmp->GetParentNode(getter_AddRefs(parent));
00702           tmp = parent;
00703         }
00704         if (highTable)
00705           selNode = highTable;
00706       }
00707       if (!selNode) 
00708         selNode = lastInsertNode;
00709       if (IsTextNode(selNode) || (IsContainer(selNode) && !nsHTMLEditUtils::IsTable(selNode)))  
00710       {
00711         res = GetLengthOfDOMNode(selNode, (PRUint32&)selOffset);
00712         if (NS_FAILED(res)) return res;
00713       }
00714       else // we need to find a container for selection.  Look up.
00715       {
00716         tmp = selNode;
00717         res = GetNodeLocation(tmp, address_of(selNode), &selOffset);
00718         ++selOffset;  // want to be *after* last leaf node in paste
00719         if (NS_FAILED(res)) return res;
00720       }
00721       
00722       // make sure we dont end up with selection collapsed after an invisible break node
00723       nsWSRunObject wsRunObj(this, selNode, selOffset);
00724       PRInt32 outVisOffset=0;
00725       PRInt16 visType=0;
00726       res = wsRunObj.PriorVisibleNode(selNode, selOffset, address_of(visNode), &outVisOffset, &visType);
00727       if (NS_FAILED(res)) return res;
00728       if (visType == nsWSRunObject::eBreak)
00729       {
00730         // we are after a break.  Is it visible?  Despite the name, 
00731         // PriorVisibleNode does not make that determination for breaks.
00732         // It also may not return the break in visNode.  We have to pull it
00733         // out of the nsWSRunObject's state.
00734         if (!IsVisBreak(wsRunObj.mStartReasonNode))
00735         {
00736           // dont leave selection past an invisible break;
00737           // reset {selNode,selOffset} to point before break
00738           res = GetNodeLocation(wsRunObj.mStartReasonNode, address_of(selNode), &selOffset);
00739           // we want to be inside any inline style prior to break
00740           nsWSRunObject wsRunObj(this, selNode, selOffset);
00741           res = wsRunObj.PriorVisibleNode(selNode, selOffset, address_of(visNode), &outVisOffset, &visType);
00742           if (NS_FAILED(res)) return res;
00743           if (visType == nsWSRunObject::eText ||
00744               visType == nsWSRunObject::eNormalWS)
00745           {
00746             selNode = visNode;
00747             selOffset = outVisOffset;  // PriorVisibleNode already set offset to _after_ the text or ws
00748           }
00749           else if (visType == nsWSRunObject::eSpecial)
00750           {
00751             // prior visible thing is an image or some other non-text thingy.  
00752             // We want to be right after it.
00753             res = GetNodeLocation(wsRunObj.mStartReasonNode, address_of(selNode), &selOffset);
00754             ++selOffset;
00755           }
00756         }
00757       }
00758       selection->Collapse(selNode, selOffset);
00759       
00760       // if we just pasted a link, discontinue link style
00761       nsCOMPtr<nsIDOMNode> link;
00762       if (!bStartedInLink && IsInLink(selNode, address_of(link)))
00763       {
00764         // so, if we just pasted a link, I split it.  Why do that instead of just
00765         // nudging selection point beyond it?  Because it might have ended in a BR
00766         // that is not visible.  If so, the code above just placed selection
00767         // inside that.  So I split it instead.
00768         nsCOMPtr<nsIDOMNode> leftLink;
00769         PRInt32 linkOffset;
00770         res = SplitNodeDeep(link, selNode, selOffset, &linkOffset, PR_TRUE, address_of(leftLink));
00771         if (NS_FAILED(res)) return res;
00772         res = GetNodeLocation(leftLink, address_of(selNode), &selOffset);
00773         if (NS_FAILED(res)) return res;
00774         selection->Collapse(selNode, selOffset+1);
00775       }
00776     }
00777   }
00778   
00779   res = mRules->DidDoAction(selection, &ruleInfo, res);
00780   return res;
00781 }
00782 
00783 // returns empty string if nothing to modify on node
00784 nsresult
00785 nsHTMLEditor::GetAttributeToModifyOnNode(nsIDOMNode *aNode, nsAString &aAttr)
00786 {
00787   aAttr.Truncate();
00788 
00789   NS_NAMED_LITERAL_STRING(srcStr, "src");
00790   nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNode);
00791   if (nodeAsImage)
00792   {
00793     aAttr = srcStr;
00794     return NS_OK;
00795   }
00796 
00797   nsCOMPtr<nsIDOMHTMLAnchorElement> nodeAsAnchor = do_QueryInterface(aNode);
00798   if (nodeAsAnchor)
00799   {
00800     aAttr.AssignLiteral("href");
00801     return NS_OK;
00802   }
00803 
00804   NS_NAMED_LITERAL_STRING(bgStr, "background");
00805   nsCOMPtr<nsIDOMHTMLBodyElement> nodeAsBody = do_QueryInterface(aNode);
00806   if (nodeAsBody)
00807   {
00808     aAttr = bgStr;
00809     return NS_OK;
00810   }
00811 
00812   nsCOMPtr<nsIDOMHTMLTableElement> nodeAsTable = do_QueryInterface(aNode);
00813   if (nodeAsTable)
00814   {
00815     aAttr = bgStr;
00816     return NS_OK;
00817   }
00818 
00819   nsCOMPtr<nsIDOMHTMLTableRowElement> nodeAsTableRow = do_QueryInterface(aNode);
00820   if (nodeAsTableRow)
00821   {
00822     aAttr = bgStr;
00823     return NS_OK;
00824   }
00825 
00826   nsCOMPtr<nsIDOMHTMLTableCellElement> nodeAsTableCell = do_QueryInterface(aNode);
00827   if (nodeAsTableCell)
00828   {
00829     aAttr = bgStr;
00830     return NS_OK;
00831   }
00832 
00833   nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNode);
00834   if (nodeAsScript)
00835   {
00836     aAttr = srcStr;
00837     return NS_OK;
00838   }
00839   
00840   nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNode);
00841   if (nodeAsEmbed)
00842   {
00843     aAttr = srcStr;
00844     return NS_OK;
00845   }
00846   
00847   nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNode);
00848   if (nodeAsObject)
00849   {
00850     aAttr.AssignLiteral("data");
00851     return NS_OK;
00852   }
00853   
00854   nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNode);
00855   if (nodeAsLink)
00856   {
00857     // Test if the link has a rel value indicating it to be a stylesheet
00858     nsAutoString linkRel;
00859     if (NS_SUCCEEDED(nodeAsLink->GetRel(linkRel)) && !linkRel.IsEmpty())
00860     {
00861       nsReadingIterator<PRUnichar> start;
00862       nsReadingIterator<PRUnichar> end;
00863       nsReadingIterator<PRUnichar> current;
00864 
00865       linkRel.BeginReading(start);
00866       linkRel.EndReading(end);
00867 
00868       // Walk through space delimited string looking for "stylesheet"
00869       for (current = start; current != end; ++current)
00870       {
00871         // Ignore whitespace
00872         if (nsCRT::IsAsciiSpace(*current))
00873           continue;
00874 
00875         // Grab the next space delimited word
00876         nsReadingIterator<PRUnichar> startWord = current;
00877         do {
00878           ++current;
00879         } while (current != end && !nsCRT::IsAsciiSpace(*current));
00880 
00881         // Store the link for fix up if it says "stylesheet"
00882         if (Substring(startWord, current).LowerCaseEqualsLiteral("stylesheet"))
00883         {
00884           aAttr.AssignLiteral("href");
00885           return NS_OK;
00886         }
00887         if (current == end)
00888           break;
00889       }
00890     }
00891     return NS_OK;
00892   }
00893 
00894   nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNode);
00895   if (nodeAsFrame)
00896   {
00897     aAttr = srcStr;
00898     return NS_OK;
00899   }
00900 
00901   nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNode);
00902   if (nodeAsIFrame)
00903   {
00904     aAttr = srcStr;
00905     return NS_OK;
00906   }
00907 
00908   nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNode);
00909   if (nodeAsInput)
00910   {
00911     aAttr = srcStr;
00912     return NS_OK;
00913   }
00914 
00915   return NS_OK;
00916 }
00917 
00918 nsresult
00919 nsHTMLEditor::RelativizeURIForNode(nsIDOMNode *aNode, nsIURL *aDestURL)
00920 {
00921   nsAutoString attributeToModify;
00922   GetAttributeToModifyOnNode(aNode, attributeToModify);
00923   if (attributeToModify.IsEmpty())
00924     return NS_OK;
00925 
00926   nsCOMPtr<nsIDOMNamedNodeMap> attrMap;
00927   nsresult rv = aNode->GetAttributes(getter_AddRefs(attrMap));
00928   NS_ENSURE_SUCCESS(rv, NS_OK);
00929   if (!attrMap) return NS_OK; // assume errors here shouldn't cancel insertion
00930 
00931   nsCOMPtr<nsIDOMNode> attrNode;
00932   rv = attrMap->GetNamedItem(attributeToModify, getter_AddRefs(attrNode));
00933   NS_ENSURE_SUCCESS(rv, NS_OK); // assume errors here shouldn't cancel insertion
00934 
00935   if (attrNode)
00936   {
00937     nsAutoString oldValue;
00938     attrNode->GetNodeValue(oldValue);
00939     if (!oldValue.IsEmpty())
00940     {
00941       NS_ConvertUCS2toUTF8 oldCValue(oldValue);
00942       nsCOMPtr<nsIURI> currentNodeURI;
00943       rv = NS_NewURI(getter_AddRefs(currentNodeURI), oldCValue);
00944       if (NS_SUCCEEDED(rv))
00945       {
00946         nsCAutoString newRelativePath;
00947         aDestURL->GetRelativeSpec(currentNodeURI, newRelativePath);
00948         if (!newRelativePath.IsEmpty())
00949         {
00950           NS_ConvertUTF8toUCS2 newCValue(newRelativePath);
00951           attrNode->SetNodeValue(newCValue);
00952         }
00953       }
00954     }
00955   }
00956 
00957   return NS_OK;
00958 }
00959 
00960 nsresult
00961 nsHTMLEditor::RelativizeURIInFragmentList(nsCOMArray<nsIDOMNode> aNodeList,
00962                                           const nsAString &aFlavor,
00963                                           nsIDOMDocument *aSourceDoc,
00964                                           nsIDOMNode *aTargetNode)
00965 {
00966   // determine destination URL
00967   nsCOMPtr<nsIDOMDocument> domDoc;
00968   GetDocument(getter_AddRefs(domDoc));
00969   nsCOMPtr<nsIDocument> destDoc = do_QueryInterface(domDoc);
00970   if (!destDoc) return NS_ERROR_FAILURE;
00971 
00972   nsCOMPtr<nsIURL> destURL = do_QueryInterface(destDoc->GetDocumentURI());
00973   if (!destURL) return NS_ERROR_FAILURE;
00974 
00975   // brade: eventually should look for a base url in the document if present
00976 
00977   nsresult rv;
00978   nsCOMPtr<nsIDOMDocumentTraversal> trav = do_QueryInterface(domDoc, &rv);
00979   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
00980 
00981   PRInt32 listCount = aNodeList.Count();
00982   PRInt32 j;
00983   for (j = 0; j < listCount; j++)
00984   {
00985     nsIDOMNode* somenode = aNodeList[j];
00986 
00987     nsCOMPtr<nsIDOMTreeWalker> walker;
00988     rv = trav->CreateTreeWalker(somenode, nsIDOMNodeFilter::SHOW_ELEMENT,
00989                                 nsnull, PR_TRUE, getter_AddRefs(walker));
00990     NS_ENSURE_SUCCESS(rv, rv);
00991 
00992     nsCOMPtr<nsIDOMNode> currentNode;
00993     walker->GetCurrentNode(getter_AddRefs(currentNode));
00994     while (currentNode)
00995     {
00996       rv = RelativizeURIForNode(currentNode, destURL);
00997       NS_ENSURE_SUCCESS(rv, rv);
00998 
00999       walker->NextNode(getter_AddRefs(currentNode));
01000     }
01001   }
01002 
01003   return NS_OK;
01004 }
01005 
01006 nsresult
01007 nsHTMLEditor::AddInsertionListener(nsIContentFilter *aListener)
01008 {
01009   if (!aListener)
01010     return NS_ERROR_NULL_POINTER;
01011 
01012   // don't let a listener be added more than once
01013   if (mContentFilters.IndexOfObject(aListener) == -1)
01014   {
01015     if (!mContentFilters.AppendObject(aListener))
01016       return NS_ERROR_FAILURE;
01017   }
01018 
01019   return NS_OK;
01020 }
01021  
01022 nsresult
01023 nsHTMLEditor::RemoveInsertionListener(nsIContentFilter *aListener)
01024 {
01025   if (!aListener)
01026     return NS_ERROR_FAILURE;
01027 
01028   if (!mContentFilters.RemoveObject(aListener))
01029     return NS_ERROR_FAILURE;
01030 
01031   return NS_OK;
01032 }
01033  
01034 nsresult
01035 nsHTMLEditor::DoContentFilterCallback(const nsAString &aFlavor, 
01036                                       nsIDOMDocument *sourceDoc,
01037                                       PRBool aWillDeleteSelection,
01038                                       nsIDOMNode **aFragmentAsNode, 
01039                                       nsIDOMNode **aFragStartNode, 
01040                                       PRInt32 *aFragStartOffset,
01041                                       nsIDOMNode **aFragEndNode, 
01042                                       PRInt32 *aFragEndOffset,
01043                                       nsIDOMNode **aTargetNode,
01044                                       PRInt32 *aTargetOffset,
01045                                       PRBool *aDoContinue)
01046 {
01047   *aDoContinue = PR_TRUE;
01048 
01049   PRInt32 i;
01050   nsIContentFilter *listener;
01051   for (i=0; i < mContentFilters.Count() && *aDoContinue; i++)
01052   {
01053     listener = (nsIContentFilter *)mContentFilters[i];
01054     if (listener)
01055       listener->NotifyOfInsertion(aFlavor, nsnull, sourceDoc,
01056                                   aWillDeleteSelection, aFragmentAsNode,
01057                                   aFragStartNode, aFragStartOffset, 
01058                                   aFragEndNode, aFragEndOffset,
01059                                   aTargetNode, aTargetOffset, aDoContinue);
01060   }
01061 
01062   return NS_OK;
01063 }
01064 
01065 PRBool
01066 nsHTMLEditor::IsInLink(nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *outLink)
01067 {
01068   if (!aNode) 
01069     return PR_FALSE;
01070   if (outLink)
01071     *outLink = nsnull;
01072   nsCOMPtr<nsIDOMNode> tmp, node = aNode;
01073   while (node)
01074   {
01075     if (nsHTMLEditUtils::IsLink(node)) 
01076     {
01077       if (outLink)
01078         *outLink = node;
01079       return PR_TRUE;
01080     }
01081     tmp = node;
01082     tmp->GetParentNode(getter_AddRefs(node));
01083   }
01084   return PR_FALSE;
01085 }
01086 
01087 
01088 nsresult
01089 nsHTMLEditor::StripFormattingNodes(nsIDOMNode *aNode, PRBool aListOnly)
01090 {
01091   NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
01092 
01093   nsresult res = NS_OK;
01094   nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
01095   if (IsEmptyTextContent(content))
01096   {
01097     nsCOMPtr<nsIDOMNode> parent, ignored;
01098     aNode->GetParentNode(getter_AddRefs(parent));
01099     if (parent)
01100     {
01101       if (!aListOnly || nsHTMLEditUtils::IsList(parent))
01102         res = parent->RemoveChild(aNode, getter_AddRefs(ignored));
01103       return res;
01104     }
01105   }
01106   
01107   if (!nsHTMLEditUtils::IsPre(aNode))
01108   {
01109     nsCOMPtr<nsIDOMNode> child;
01110     aNode->GetLastChild(getter_AddRefs(child));
01111   
01112     while (child)
01113     {
01114       nsCOMPtr<nsIDOMNode> tmp;
01115       child->GetPreviousSibling(getter_AddRefs(tmp));
01116       res = StripFormattingNodes(child, aListOnly);
01117       NS_ENSURE_SUCCESS(res, res);
01118       child = tmp;
01119     }
01120   }
01121   return res;
01122 }
01123 
01124 NS_IMETHODIMP nsHTMLEditor::PrepareTransferable(nsITransferable **transferable)
01125 {
01126   return NS_OK;
01127 }
01128 
01129 NS_IMETHODIMP nsHTMLEditor::PrepareHTMLTransferable(nsITransferable **aTransferable, 
01130                                                     PRBool aHavePrivFlavor)
01131 {
01132   // Create generic Transferable for getting the data
01133   nsresult rv = CallCreateInstance("@mozilla.org/widget/transferable;1", aTransferable);
01134   if (NS_FAILED(rv))
01135     return rv;
01136 
01137   // Get the nsITransferable interface for getting the data from the clipboard
01138   if (aTransferable)
01139   {
01140     // Create the desired DataFlavor for the type of data
01141     // we want to get out of the transferable
01142     if ((mFlags & eEditorPlaintextMask) == 0)  // This should only happen in html editors, not plaintext
01143     {
01144       if (!aHavePrivFlavor) 
01145       {
01146         (*aTransferable)->AddDataFlavor(kNativeHTMLMime);
01147       }
01148       (*aTransferable)->AddDataFlavor(kHTMLMime);
01149       (*aTransferable)->AddDataFlavor(kFileMime);
01150 #ifdef XP_WIN32
01151       // image pasting from the clipboard is only implemented on Windows right now.
01152       (*aTransferable)->AddDataFlavor(kJPEGImageMime);
01153 #endif
01154     }
01155     (*aTransferable)->AddDataFlavor(kUnicodeMime);
01156   }
01157   
01158   return NS_OK;
01159 }
01160 
01161 PRInt32
01162 FindPositiveIntegerAfterString(const char *aLeadingString, nsCString &aCStr)
01163 {
01164   // first obtain offsets from cfhtml str
01165   PRInt32 numFront = aCStr.Find(aLeadingString);
01166   if (numFront == -1)
01167     return -1;
01168   numFront += strlen(aLeadingString); 
01169   
01170   PRInt32 numBack = aCStr.FindCharInSet(CRLF, numFront);
01171   if (numBack == -1)
01172     return -1;
01173    
01174   nsCAutoString numStr(Substring(aCStr, numFront, numBack-numFront));
01175   PRInt32 errorCode;
01176   return numStr.ToInteger(&errorCode);
01177 }
01178 
01179 nsresult
01180 RemoveFragComments(nsCString & aStr)
01181 {
01182   // remove the StartFragment/EndFragment comments from the str, if present
01183   PRInt32 startCommentIndx = aStr.Find("<!--StartFragment");
01184   if (startCommentIndx >= 0)
01185   {
01186     PRInt32 startCommentEnd = aStr.Find("-->", PR_FALSE, startCommentIndx);
01187     if (startCommentEnd > startCommentIndx)
01188       aStr.Cut(startCommentIndx, (startCommentEnd+3)-startCommentIndx);
01189   }  
01190   PRInt32 endCommentIndx = aStr.Find("<!--EndFragment");
01191   if (endCommentIndx >= 0)
01192   {
01193     PRInt32 endCommentEnd = aStr.Find("-->", PR_FALSE, endCommentIndx);
01194     if (endCommentEnd > endCommentIndx)
01195       aStr.Cut(endCommentIndx, (endCommentEnd+3)-endCommentIndx);
01196   }  
01197   return NS_OK;
01198 }
01199 
01200 nsresult
01201 nsHTMLEditor::ParseCFHTML(nsCString & aCfhtml, PRUnichar **aStuffToPaste, PRUnichar **aCfcontext)
01202 {
01203   // first obtain offsets from cfhtml str
01204   PRInt32 startHTML     = FindPositiveIntegerAfterString("StartHTML:", aCfhtml);
01205   PRInt32 endHTML       = FindPositiveIntegerAfterString("EndHTML:", aCfhtml);
01206   PRInt32 startFragment = FindPositiveIntegerAfterString("StartFragment:", aCfhtml);
01207   PRInt32 endFragment   = FindPositiveIntegerAfterString("EndFragment:", aCfhtml);
01208 
01209   if ((startHTML<0) || (endHTML<0) || (startFragment<0) || (endFragment<0))
01210     return NS_ERROR_FAILURE;
01211  
01212   // create context string
01213   nsCAutoString contextUTF8(Substring(aCfhtml, startHTML, startFragment - startHTML) +
01214                             Substring(aCfhtml, endFragment, endHTML - endFragment));
01215   
01216   // validate startFragment
01217   // make sure it's not in the middle of a HTML tag
01218   // see bug #228879 for more details
01219   PRInt32 curPos = startFragment;
01220   while (curPos > startHTML)
01221   {
01222       if (aCfhtml[curPos] == '>')
01223       {
01224           // working backwards, the first thing we see is the end of a tag
01225           // so StartFragment is good, so do nothing.
01226           break;
01227       }
01228       else if (aCfhtml[curPos] == '<') 
01229       {
01230           // if we are at the start, then we want to see the '<'
01231           if (curPos != startFragment) 
01232           {
01233               // working backwards, the first thing we see is the start of a tag
01234               // so StartFragment is bad, so we need to update it.
01235               NS_ASSERTION(0, "StartFragment byte count in the clipboard looks bad, see bug #228879");
01236               startFragment = curPos - 1;
01237           }
01238           break;
01239       }
01240       else 
01241       {
01242           curPos--;
01243       }
01244   }
01245 
01246   // create fragment string
01247   nsCAutoString fragmentUTF8(Substring(aCfhtml, startFragment, endFragment-startFragment));
01248   
01249   // remove the StartFragment/EndFragment comments from the fragment, if present
01250   RemoveFragComments(fragmentUTF8);
01251 
01252   // remove the StartFragment/EndFragment comments from the context, if present
01253   RemoveFragComments(contextUTF8);
01254 
01255   // convert both strings to usc2
01256   const nsAFlatString& fragUcs2Str = NS_ConvertUTF8toUCS2(fragmentUTF8);
01257   const nsAFlatString& cntxtUcs2Str = NS_ConvertUTF8toUCS2(contextUTF8);
01258   
01259   // translate platform linebreaks for fragment
01260   PRInt32 oldLengthInChars = fragUcs2Str.Length() + 1;  // +1 to include null terminator
01261   PRInt32 newLengthInChars = 0;
01262   *aStuffToPaste = nsLinebreakConverter::ConvertUnicharLineBreaks(fragUcs2Str.get(),
01263                                                            nsLinebreakConverter::eLinebreakAny, 
01264                                                            nsLinebreakConverter::eLinebreakContent, 
01265                                                            oldLengthInChars, &newLengthInChars);
01266   if (!aStuffToPaste)
01267   {
01268     return NS_ERROR_FAILURE;
01269   }
01270   
01271   // translate platform linebreaks for context
01272   oldLengthInChars = cntxtUcs2Str.Length() + 1;  // +1 to include null terminator
01273   newLengthInChars = 0;
01274   *aCfcontext = nsLinebreakConverter::ConvertUnicharLineBreaks(cntxtUcs2Str.get(),
01275                                                            nsLinebreakConverter::eLinebreakAny, 
01276                                                            nsLinebreakConverter::eLinebreakContent, 
01277                                                            oldLengthInChars, &newLengthInChars);
01278   // it's ok for context to be empty.  frag might be whole doc and contain all it's context.
01279   
01280   // we're done!  
01281   return NS_OK;
01282 }
01283 
01284 NS_IMETHODIMP nsHTMLEditor::InsertFromTransferable(nsITransferable *transferable, 
01285                                                    nsIDOMDocument *aSourceDoc,
01286                                                    const nsAString & aContextStr,
01287                                                    const nsAString & aInfoStr,
01288                                                    nsIDOMNode *aDestinationNode,
01289                                                    PRInt32 aDestOffset,
01290                                                    PRBool aDoDeleteSelection)
01291 {
01292   nsresult rv = NS_OK;
01293   nsXPIDLCString bestFlavor;
01294   nsCOMPtr<nsISupports> genericDataObj;
01295   PRUint32 len = 0;
01296   if ( NS_SUCCEEDED(transferable->GetAnyTransferData(getter_Copies(bestFlavor), getter_AddRefs(genericDataObj), &len)) )
01297   {
01298     nsAutoTxnsConserveSelection dontSpazMySelection(this);
01299     nsAutoString flavor;
01300     flavor.AssignWithConversion(bestFlavor);
01301     nsAutoString stuffToPaste;
01302 #ifdef DEBUG_clipboard
01303     printf("Got flavor [%s]\n", bestFlavor);
01304 #endif
01305     if (0 == nsCRT::strcmp(bestFlavor, kNativeHTMLMime))
01306     {
01307       // note cf_html uses utf8, hence use length = len, not len/2 as in flavors below
01308       nsCOMPtr<nsISupportsCString> textDataObj(do_QueryInterface(genericDataObj));
01309       if (textDataObj && len > 0)
01310       {
01311         nsCAutoString cfhtml;
01312         textDataObj->GetData(cfhtml);
01313         NS_ASSERTION(cfhtml.Length() <= (len), "Invalid length!");
01314         nsXPIDLString cfcontext, cffragment, cfselection; // cfselection left emtpy for now
01315          
01316         rv = ParseCFHTML(cfhtml, getter_Copies(cffragment), getter_Copies(cfcontext));
01317         if (NS_SUCCEEDED(rv) && !cffragment.IsEmpty())
01318         {
01319           nsAutoEditBatch beginBatching(this);
01320           rv = InsertHTMLWithContext(cffragment,
01321                                      cfcontext, cfselection, flavor,
01322                                      aSourceDoc,
01323                                      aDestinationNode, aDestOffset,
01324                                      aDoDeleteSelection);
01325         }
01326       }
01327     }
01328     else if (0 == nsCRT::strcmp(bestFlavor, kHTMLMime))
01329     {
01330       nsCOMPtr<nsISupportsString> textDataObj(do_QueryInterface(genericDataObj));
01331       if (textDataObj && len > 0)
01332       {
01333         nsAutoString text;
01334         textDataObj->GetData(text);
01335         NS_ASSERTION(text.Length() <= (len/2), "Invalid length!");
01336         stuffToPaste.Assign(text.get(), len / 2);
01337         nsAutoEditBatch beginBatching(this);
01338         rv = InsertHTMLWithContext(stuffToPaste,
01339                                    aContextStr, aInfoStr, flavor,
01340                                    aSourceDoc,
01341                                    aDestinationNode, aDestOffset,
01342                                    aDoDeleteSelection);
01343       }
01344     }
01345     else if (0 == nsCRT::strcmp(bestFlavor, kUnicodeMime))
01346     {
01347       nsCOMPtr<nsISupportsString> textDataObj(do_QueryInterface(genericDataObj));
01348       if (textDataObj && len > 0)
01349       {
01350         nsAutoString text;
01351         textDataObj->GetData(text);
01352         NS_ASSERTION(text.Length() <= (len/2), "Invalid length!");
01353         stuffToPaste.Assign(text.get(), len / 2);
01354         nsAutoEditBatch beginBatching(this);
01355         // need to provide a hook from this point
01356         rv = InsertTextAt(stuffToPaste, aDestinationNode, aDestOffset, aDoDeleteSelection);
01357       }
01358     }
01359     else if (0 == nsCRT::strcmp(bestFlavor, kFileMime))
01360     {
01361       nsCOMPtr<nsIFile> fileObj(do_QueryInterface(genericDataObj));
01362       if (fileObj && len > 0)
01363       {
01364         
01365         nsCOMPtr<nsIURI> uri;
01366         rv = NS_NewFileURI(getter_AddRefs(uri), fileObj);
01367         if (NS_FAILED(rv))
01368           return rv;
01369         
01370         nsCOMPtr<nsIURL> fileURL(do_QueryInterface(uri));
01371         if (fileURL)
01372         {
01373           PRBool insertAsImage = PR_FALSE;
01374           nsCAutoString fileextension;
01375           rv = fileURL->GetFileExtension(fileextension);
01376           if (NS_SUCCEEDED(rv) && !fileextension.IsEmpty())
01377           {
01378             if ( (nsCRT::strcasecmp(fileextension.get(), "jpg") == 0 )
01379               || (nsCRT::strcasecmp(fileextension.get(), "jpeg") == 0 )
01380               || (nsCRT::strcasecmp(fileextension.get(), "gif") == 0 )
01381               || (nsCRT::strcasecmp(fileextension.get(), "png") == 0 ) )
01382             {
01383               insertAsImage = PR_TRUE;
01384             }
01385           }
01386           
01387           nsCAutoString urltext;
01388           rv = fileURL->GetSpec(urltext);
01389           if (NS_SUCCEEDED(rv) && !urltext.IsEmpty())
01390           {
01391             if (insertAsImage)
01392             {
01393               stuffToPaste.AssignLiteral("<IMG src=\"");
01394               AppendUTF8toUTF16(urltext, stuffToPaste);
01395               stuffToPaste.AppendLiteral("\" alt=\"\" >");
01396             }
01397             else /* insert as link */
01398             {
01399               stuffToPaste.AssignLiteral("<A href=\"");
01400               AppendUTF8toUTF16(urltext, stuffToPaste);
01401               stuffToPaste.AppendLiteral("\">");
01402               AppendUTF8toUTF16(urltext, stuffToPaste);
01403               stuffToPaste.AppendLiteral("</A>");
01404             }
01405             nsAutoEditBatch beginBatching(this);
01406 
01407             const nsAFlatString& empty = EmptyString();
01408             rv = InsertHTMLWithContext(stuffToPaste,
01409                                        empty, empty, flavor, 
01410                                        aSourceDoc,
01411                                        aDestinationNode, aDestOffset,
01412                                        aDoDeleteSelection);
01413           }
01414         }
01415       }
01416     }
01417     else if (0 == nsCRT::strcmp(bestFlavor, kJPEGImageMime))
01418     {
01419       nsCOMPtr<nsIInputStream> imageStream(do_QueryInterface(genericDataObj));
01420       NS_ENSURE_TRUE(imageStream, NS_ERROR_FAILURE);
01421 
01422       nsCOMPtr<nsIFile> fileToUse;
01423       NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(fileToUse));
01424       fileToUse->Append(NS_LITERAL_STRING("moz-screenshot.jpg"));
01425       nsCOMPtr<nsILocalFile> path = do_QueryInterface(fileToUse);
01426       path->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
01427 
01428       nsCOMPtr<nsIOutputStream> outputStream;
01429       rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), fileToUse);
01430       NS_ENSURE_SUCCESS(rv, rv);
01431 
01432       PRUint32 length;
01433       imageStream->Available(&length);
01434 
01435       nsCOMPtr<nsIOutputStream> bufferedOutputStream;
01436       rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream), outputStream, length);
01437       NS_ENSURE_SUCCESS(rv, rv);
01438 
01439       PRUint32 numWritten;
01440       rv = bufferedOutputStream->WriteFrom(imageStream, length, &numWritten);
01441       NS_ENSURE_SUCCESS(rv, rv);
01442 
01443       // force the stream close before we try to insert the image
01444       // into the document.
01445       rv = bufferedOutputStream->Close();
01446       NS_ENSURE_SUCCESS(rv, rv);
01447       
01448       nsCOMPtr<nsIURI> uri;
01449       rv = NS_NewFileURI(getter_AddRefs(uri), fileToUse);
01450       NS_ENSURE_SUCCESS(rv, rv);
01451       nsCOMPtr<nsIURL> fileURL(do_QueryInterface(uri));
01452       if (fileURL)
01453       {
01454         nsCAutoString urltext;
01455         rv = fileURL->GetSpec(urltext);
01456         if (NS_SUCCEEDED(rv) && !urltext.IsEmpty())
01457         {
01458           stuffToPaste.AssignLiteral("<IMG src=\"");
01459           AppendUTF8toUTF16(urltext, stuffToPaste);
01460           stuffToPaste.AppendLiteral("\" alt=\"\" >");
01461           nsAutoEditBatch beginBatching(this);
01462           rv = InsertHTMLWithContext(stuffToPaste, EmptyString(), EmptyString(), 
01463                                      NS_LITERAL_STRING(kFileMime),
01464                                      aSourceDoc,
01465                                      aDestinationNode, aDestOffset,
01466                                      aDoDeleteSelection);
01467         }
01468       }
01469     }
01470   }
01471       
01472   // Try to scroll the selection into view if the paste/drop succeeded
01473   if (NS_SUCCEEDED(rv))
01474     ScrollSelectionIntoView(PR_FALSE);
01475 
01476   return rv;
01477 }
01478 
01479 NS_IMETHODIMP nsHTMLEditor::InsertFromDrop(nsIDOMEvent* aDropEvent)
01480 {
01481   ForceCompositionEnd();
01482   
01483   nsresult rv;
01484   nsCOMPtr<nsIDragService> dragService = 
01485            do_GetService("@mozilla.org/widget/dragservice;1", &rv);
01486   if (NS_FAILED(rv)) return rv;
01487 
01488   nsCOMPtr<nsIDragSession> dragSession;
01489   dragService->GetCurrentSession(getter_AddRefs(dragSession)); 
01490   if (!dragSession) return NS_OK;
01491 
01492   // transferable hooks here
01493   nsCOMPtr<nsIDOMDocument> domdoc;
01494   GetDocument(getter_AddRefs(domdoc));
01495   if (!nsEditorHookUtils::DoAllowDropHook(domdoc, aDropEvent, dragSession))
01496     return NS_OK;
01497 
01498   // find out if we have our internal html flavor on the clipboard.  We don't want to mess
01499   // around with cfhtml if we do.
01500   PRBool bHavePrivateHTMLFlavor = PR_FALSE;
01501   rv = dragSession->IsDataFlavorSupported(kHTMLContext, &bHavePrivateHTMLFlavor);
01502   if (NS_FAILED(rv)) return rv;
01503   
01504   // Get the nsITransferable interface for getting the data from the drop
01505   nsCOMPtr<nsITransferable> trans;
01506   rv = PrepareHTMLTransferable(getter_AddRefs(trans), bHavePrivateHTMLFlavor);
01507   if (NS_FAILED(rv)) return rv;
01508   if (!trans) return NS_OK;  // NS_ERROR_FAILURE; SHOULD WE FAIL?
01509 
01510   PRUint32 numItems = 0; 
01511   rv = dragSession->GetNumDropItems(&numItems);
01512   if (NS_FAILED(rv)) return rv;
01513 
01514   // Combine any deletion and drop insertion into one transaction
01515   nsAutoEditBatch beginBatching(this);
01516 
01517   // We never have to delete if selection is already collapsed
01518   PRBool deleteSelection = PR_FALSE;
01519   nsCOMPtr<nsIDOMNode> newSelectionParent;
01520   PRInt32 newSelectionOffset = 0;
01521 
01522   // Source doc is null if source is *not* the current editor document
01523   nsCOMPtr<nsIDOMDocument> srcdomdoc;
01524   rv = dragSession->GetSourceDocument(getter_AddRefs(srcdomdoc));
01525   if (NS_FAILED(rv)) return rv;
01526 
01527   PRUint32 i; 
01528   PRBool doPlaceCaret = PR_TRUE;
01529   for (i = 0; i < numItems; ++i)
01530   {
01531     rv = dragSession->GetData(trans, i);
01532     if (NS_FAILED(rv)) return rv;
01533     if (!trans) return NS_OK; // NS_ERROR_FAILURE; Should we fail?
01534 
01535     // get additional html copy hints, if present
01536     nsAutoString contextStr, infoStr;
01537     nsCOMPtr<nsISupports> contextDataObj, infoDataObj;
01538     PRUint32 contextLen, infoLen;
01539     nsCOMPtr<nsISupportsString> textDataObj;
01540     
01541     nsCOMPtr<nsITransferable> contextTrans =
01542                       do_CreateInstance("@mozilla.org/widget/transferable;1");
01543     NS_ENSURE_TRUE(contextTrans, NS_ERROR_NULL_POINTER);
01544     contextTrans->AddDataFlavor(kHTMLContext);
01545     dragSession->GetData(contextTrans, i);
01546     contextTrans->GetTransferData(kHTMLContext, getter_AddRefs(contextDataObj), &contextLen);
01547 
01548     nsCOMPtr<nsITransferable> infoTrans =
01549                       do_CreateInstance("@mozilla.org/widget/transferable;1");
01550     NS_ENSURE_TRUE(infoTrans, NS_ERROR_NULL_POINTER);
01551     infoTrans->AddDataFlavor(kHTMLInfo);
01552     dragSession->GetData(infoTrans, i);
01553     infoTrans->GetTransferData(kHTMLInfo, getter_AddRefs(infoDataObj), &infoLen);
01554     
01555     if (contextDataObj)
01556     {
01557       nsAutoString text;
01558       textDataObj = do_QueryInterface(contextDataObj);
01559       textDataObj->GetData(text);
01560       NS_ASSERTION(text.Length() <= (contextLen/2), "Invalid length!");
01561       contextStr.Assign(text.get(), contextLen / 2);
01562     }
01563     
01564     if (infoDataObj)
01565     {
01566       nsAutoString text;
01567       textDataObj = do_QueryInterface(infoDataObj);
01568       textDataObj->GetData(text);
01569       NS_ASSERTION(text.Length() <= (infoLen/2), "Invalid length!");
01570       infoStr.Assign(text.get(), infoLen / 2);
01571     }
01572 
01573     if (doPlaceCaret)
01574     {
01575       // check if the user pressed the key to force a copy rather than a move
01576       // if we run into problems here, we'll just assume the user doesn't want a copy
01577       PRBool userWantsCopy = PR_FALSE;
01578 
01579       nsCOMPtr<nsIDOMNSUIEvent> nsuiEvent(do_QueryInterface(aDropEvent));
01580       if (!nsuiEvent) return NS_ERROR_FAILURE;
01581 
01582       nsCOMPtr<nsIDOMMouseEvent> mouseEvent(do_QueryInterface(aDropEvent));
01583       if (mouseEvent)
01584 
01585 #if defined(XP_MAC) || defined(XP_MACOSX)
01586         mouseEvent->GetAltKey(&userWantsCopy);
01587 #else
01588         mouseEvent->GetCtrlKey(&userWantsCopy);
01589 #endif
01590 
01591       // Current doc is destination
01592       nsCOMPtr<nsIDOMDocument>destdomdoc; 
01593       rv = GetDocument(getter_AddRefs(destdomdoc)); 
01594       if (NS_FAILED(rv)) return rv;
01595 
01596       nsCOMPtr<nsISelection> selection;
01597       rv = GetSelection(getter_AddRefs(selection));
01598       if (NS_FAILED(rv)) return rv;
01599       if (!selection) return NS_ERROR_FAILURE;
01600 
01601       PRBool isCollapsed;
01602       rv = selection->GetIsCollapsed(&isCollapsed);
01603       if (NS_FAILED(rv)) return rv;
01604       
01605       // Parent and offset under the mouse cursor
01606       rv = nsuiEvent->GetRangeParent(getter_AddRefs(newSelectionParent));
01607       if (NS_FAILED(rv)) return rv;
01608       if (!newSelectionParent) return NS_ERROR_FAILURE;
01609 
01610       rv = nsuiEvent->GetRangeOffset(&newSelectionOffset);
01611       if (NS_FAILED(rv)) return rv;
01612 
01613       // XXX: This userSelectNode code is a workaround for bug 195957.
01614       //
01615       // Check to see if newSelectionParent is part of a "-moz-user-select: all"
01616       // subtree. If it is, we need to make sure we don't drop into it!
01617 
01618       nsCOMPtr<nsIDOMNode> userSelectNode = FindUserSelectAllNode(newSelectionParent);
01619 
01620       if (userSelectNode)
01621       {
01622         // The drop is happening over a "-moz-user-select: all"
01623         // subtree so make sure the content we insert goes before
01624         // the root of the subtree.
01625         //
01626         // XXX: Note that inserting before the subtree matches the
01627         //      current behavior when dropping on top of an image.
01628         //      The decision for dropping before or after the
01629         //      subtree should really be done based on coordinates.
01630 
01631         rv = GetNodeLocation(userSelectNode, address_of(newSelectionParent),
01632                              &newSelectionOffset);
01633 
01634         if (NS_FAILED(rv)) return rv;
01635         if (!newSelectionParent) return NS_ERROR_FAILURE;
01636       }
01637 
01638       // We never have to delete if selection is already collapsed
01639       PRBool cursorIsInSelection = PR_FALSE;
01640 
01641       // Check if mouse is in the selection
01642       if (!isCollapsed)
01643       {
01644         PRInt32 rangeCount;
01645         rv = selection->GetRangeCount(&rangeCount);
01646         if (NS_FAILED(rv)) 
01647           return rv;
01648 
01649         for (PRInt32 j = 0; j < rangeCount; j++)
01650         {
01651           nsCOMPtr<nsIDOMRange> range;
01652 
01653           rv = selection->GetRangeAt(j, getter_AddRefs(range));
01654           if (NS_FAILED(rv) || !range) 
01655             continue;//dont bail yet, iterate through them all
01656 
01657           nsCOMPtr<nsIDOMNSRange> nsrange(do_QueryInterface(range));
01658           if (NS_FAILED(rv) || !nsrange) 
01659             continue;//dont bail yet, iterate through them all
01660 
01661           rv = nsrange->IsPointInRange(newSelectionParent, newSelectionOffset, &cursorIsInSelection);
01662           if(cursorIsInSelection)
01663             break;
01664         }
01665         if (cursorIsInSelection)
01666         {
01667           // Dragging within same doc can't drop on itself -- leave!
01668           // (We shouldn't get here - drag event shouldn't have started if over selection)
01669           if (srcdomdoc == destdomdoc)
01670             return NS_OK;
01671           
01672           // Dragging from another window onto a selection
01673           // XXX Decision made to NOT do this,
01674           //     note that 4.x does replace if dropped on
01675           //deleteSelection = PR_TRUE;
01676         }
01677         else 
01678         {
01679           // We are NOT over the selection
01680           if (srcdomdoc == destdomdoc)
01681           {
01682             // Within the same doc: delete if user doesn't want to copy
01683             deleteSelection = !userWantsCopy;
01684           }
01685           else
01686           {
01687             // Different source doc: Don't delete
01688             deleteSelection = PR_FALSE;
01689           }
01690         }
01691       }
01692 
01693       // We have to figure out whether to delete/relocate caret only once
01694       doPlaceCaret = PR_FALSE;
01695     }
01696     
01697     // handle transferable hooks
01698     if (!nsEditorHookUtils::DoInsertionHook(domdoc, aDropEvent, trans))
01699       return NS_OK;
01700 
01701     rv = InsertFromTransferable(trans, srcdomdoc, contextStr, infoStr,
01702                                 newSelectionParent,
01703                                 newSelectionOffset, deleteSelection);
01704   }
01705 
01706   return rv;
01707 }
01708 
01709 NS_IMETHODIMP nsHTMLEditor::CanDrag(nsIDOMEvent *aDragEvent, PRBool *aCanDrag)
01710 {
01711   return nsPlaintextEditor::CanDrag(aDragEvent, aCanDrag);
01712 }
01713 
01714 nsresult
01715 nsHTMLEditor::PutDragDataInTransferable(nsITransferable **aTransferable)
01716 {
01717   NS_ENSURE_ARG_POINTER(aTransferable);
01718   *aTransferable = nsnull;
01719   nsCOMPtr<nsIDocumentEncoder> docEncoder;
01720   nsresult rv = SetupDocEncoder(getter_AddRefs(docEncoder));
01721   if (NS_FAILED(rv)) return rv;
01722   NS_ENSURE_TRUE(docEncoder, NS_ERROR_FAILURE);
01723 
01724   // grab a string
01725   nsAutoString buffer, parents, info;
01726 
01727   // find out if we're a plaintext control or not
01728   PRUint32 editorFlags = 0;
01729   rv = GetFlags(&editorFlags);
01730   if (NS_FAILED(rv)) return rv;
01731 
01732   PRBool bIsPlainTextControl = ((editorFlags & eEditorPlaintextMask) != 0);
01733   if (!bIsPlainTextControl)
01734   {
01735     // encode the selection as html with contextual info
01736     rv = docEncoder->EncodeToStringWithContext(buffer, parents, info);
01737     if (NS_FAILED(rv)) return rv;
01738   }
01739   else
01740   {
01741     // encode the selection
01742     rv = docEncoder->EncodeToString(buffer);
01743     if (NS_FAILED(rv)) return rv;
01744   }
01745 
01746   // if we have an empty string, we're done; otherwise continue
01747   if ( buffer.IsEmpty() )
01748     return NS_OK;
01749 
01750   nsCOMPtr<nsISupportsString> dataWrapper, contextWrapper, infoWrapper;
01751 
01752   dataWrapper = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
01753   NS_ENSURE_SUCCESS(rv, rv);
01754   rv = dataWrapper->SetData(buffer);
01755   if (NS_FAILED(rv)) return rv;
01756 
01757   /* create html flavor transferable */
01758   nsCOMPtr<nsITransferable> trans = do_CreateInstance("@mozilla.org/widget/transferable;1");
01759   NS_ENSURE_TRUE(trans, NS_ERROR_FAILURE);
01760 
01761   if (bIsPlainTextControl)
01762   {
01763     // Add the unicode flavor to the transferable
01764     rv = trans->AddDataFlavor(kUnicodeMime);
01765     if (NS_FAILED(rv)) return rv;
01766 
01767     // QI the data object an |nsISupports| so that when the transferable holds
01768     // onto it, it will addref the correct interface.
01769     nsCOMPtr<nsISupports> genericDataObj(do_QueryInterface(dataWrapper));
01770     rv = trans->SetTransferData(kUnicodeMime, genericDataObj,
01771                                 buffer.Length() * sizeof(PRUnichar));
01772     if (NS_FAILED(rv)) return rv;
01773   }
01774   else
01775   {
01776     contextWrapper = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
01777     NS_ENSURE_TRUE(contextWrapper, NS_ERROR_FAILURE);
01778     infoWrapper = do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
01779     NS_ENSURE_TRUE(infoWrapper, NS_ERROR_FAILURE);
01780 
01781     contextWrapper->SetData(parents);
01782     infoWrapper->SetData(info);
01783 
01784     rv = trans->AddDataFlavor(kHTMLMime);
01785     if (NS_FAILED(rv)) return rv;
01786 
01787     nsCOMPtr<nsIFormatConverter> htmlConverter =
01788              do_CreateInstance("@mozilla.org/widget/htmlformatconverter;1");
01789     NS_ENSURE_TRUE(htmlConverter, NS_ERROR_FAILURE);
01790 
01791     rv = trans->SetConverter(htmlConverter);
01792     if (NS_FAILED(rv)) return rv;
01793 
01794     nsCOMPtr<nsISupports> genericDataObj(do_QueryInterface(dataWrapper));
01795     rv = trans->SetTransferData(kHTMLMime, genericDataObj,
01796                                 buffer.Length() * sizeof(PRUnichar));
01797     if (NS_FAILED(rv)) return rv;
01798 
01799     if (!parents.IsEmpty())
01800     {
01801       // Add the htmlcontext DataFlavor to the transferable
01802       trans->AddDataFlavor(kHTMLContext);
01803       genericDataObj = do_QueryInterface(contextWrapper);
01804       trans->SetTransferData(kHTMLContext, genericDataObj,
01805                              parents.Length() * sizeof(PRUnichar));
01806     }
01807     if (!info.IsEmpty())
01808     {
01809       // Add the htmlinfo DataFlavor to the transferable
01810       trans->AddDataFlavor(kHTMLInfo);
01811       genericDataObj = do_QueryInterface(infoWrapper);
01812       trans->SetTransferData(kHTMLInfo, genericDataObj,
01813                              info.Length() * sizeof(PRUnichar));
01814     }
01815   }
01816 
01817   *aTransferable = trans; 
01818   NS_ADDREF(*aTransferable);
01819   return NS_OK;
01820 }
01821 
01822 NS_IMETHODIMP nsHTMLEditor::DoDrag(nsIDOMEvent *aDragEvent)
01823 {
01824   return nsPlaintextEditor::DoDrag(aDragEvent);
01825 }
01826 
01827 PRBool nsHTMLEditor::HavePrivateHTMLFlavor(nsIClipboard *aClipboard)
01828 {
01829   // check the clipboard for our special kHTMLContext flavor.  If that is there, we know
01830   // we have our own internal html format on clipboard.
01831   
01832   if (!aClipboard) return PR_FALSE;
01833   PRBool bHavePrivateHTMLFlavor = PR_FALSE;
01834   nsCOMPtr<nsISupportsArray> flavArray;
01835   
01836   nsresult res = NS_NewISupportsArray(getter_AddRefs(flavArray));
01837   if (NS_FAILED(res)) return PR_FALSE;
01838   
01839   nsCOMPtr<nsISupportsCString> contextString = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
01840   if (!contextString) return PR_FALSE;
01841   
01842   contextString->SetData(NS_LITERAL_CSTRING(kHTMLContext));
01843   
01844   flavArray->AppendElement(contextString);
01845   
01846   if (NS_SUCCEEDED(aClipboard->HasDataMatchingFlavors (flavArray, nsIClipboard::kGlobalClipboard, &bHavePrivateHTMLFlavor )))
01847     return bHavePrivateHTMLFlavor;
01848     
01849   return PR_FALSE;
01850 }
01851 
01852 
01853 NS_IMETHODIMP nsHTMLEditor::Paste(PRInt32 aSelectionType)
01854 {
01855   ForceCompositionEnd();
01856 
01857   // Get Clipboard Service
01858   nsresult rv;
01859   nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
01860   if (NS_FAILED(rv))
01861     return rv;
01862   
01863   // find out if we have our internal html flavor on the clipboard.  We don't want to mess
01864   // around with cfhtml if we do.
01865   PRBool bHavePrivateHTMLFlavor = HavePrivateHTMLFlavor(clipboard);
01866 
01867   // Get the nsITransferable interface for getting the data from the clipboard
01868   nsCOMPtr<nsITransferable> trans;
01869   rv = PrepareHTMLTransferable(getter_AddRefs(trans), bHavePrivateHTMLFlavor);
01870   if (NS_SUCCEEDED(rv) && trans)
01871   {
01872     // Get the Data from the clipboard  
01873     if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable())
01874     {
01875       // also get additional html copy hints, if present
01876       nsAutoString contextStr, infoStr;
01877 
01878       // also get additional html copy hints, if present
01879       if (bHavePrivateHTMLFlavor)
01880       {
01881         nsCOMPtr<nsISupports> contextDataObj, infoDataObj;
01882         PRUint32 contextLen, infoLen;
01883         nsCOMPtr<nsISupportsString> textDataObj;
01884         
01885         nsCOMPtr<nsITransferable> contextTrans =
01886                       do_CreateInstance("@mozilla.org/widget/transferable;1");
01887         NS_ENSURE_TRUE(contextTrans, NS_ERROR_NULL_POINTER);
01888         contextTrans->AddDataFlavor(kHTMLContext);
01889         clipboard->GetData(contextTrans, aSelectionType);
01890         contextTrans->GetTransferData(kHTMLContext, getter_AddRefs(contextDataObj), &contextLen);
01891 
01892         nsCOMPtr<nsITransferable> infoTrans =
01893                       do_CreateInstance("@mozilla.org/widget/transferable;1");
01894         NS_ENSURE_TRUE(infoTrans, NS_ERROR_NULL_POINTER);
01895         infoTrans->AddDataFlavor(kHTMLInfo);
01896         clipboard->GetData(infoTrans, aSelectionType);
01897         infoTrans->GetTransferData(kHTMLInfo, getter_AddRefs(infoDataObj), &infoLen);
01898         
01899         if (contextDataObj)
01900         {
01901           nsAutoString text;
01902           textDataObj = do_QueryInterface(contextDataObj);
01903           textDataObj->GetData(text);
01904           NS_ASSERTION(text.Length() <= (contextLen/2), "Invalid length!");
01905           contextStr.Assign(text.get(), contextLen / 2);
01906         }
01907         
01908         if (infoDataObj)
01909         {
01910           nsAutoString text;
01911           textDataObj = do_QueryInterface(infoDataObj);
01912           textDataObj->GetData(text);
01913           NS_ASSERTION(text.Length() <= (infoLen/2), "Invalid length!");
01914           infoStr.Assign(text.get(), infoLen / 2);
01915         }
01916       }
01917 
01918       // handle transferable hooks
01919       nsCOMPtr<nsIDOMDocument> domdoc;
01920       GetDocument(getter_AddRefs(domdoc));
01921       if (!nsEditorHookUtils::DoInsertionHook(domdoc, nsnull, trans))
01922         return NS_OK;
01923 
01924       rv = InsertFromTransferable(trans, nsnull, contextStr, infoStr,
01925                                   nsnull, 0, PR_TRUE);
01926     }
01927   }
01928 
01929   return rv;
01930 }
01931 
01932 // 
01933 // HTML PasteNoFormatting. Ignore any HTML styles and formating in paste source
01934 //
01935 NS_IMETHODIMP nsHTMLEditor::PasteNoFormatting(PRInt32 aSelectionType)
01936 {
01937   ForceCompositionEnd();
01938 
01939   // Get Clipboard Service
01940   nsresult rv;
01941   nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
01942   if (NS_FAILED(rv))
01943     return rv;
01944     
01945   // Get the nsITransferable interface for getting the data from the clipboard.
01946   // use nsPlaintextEditor::PrepareTransferable() to force unicode plaintext data.
01947   nsCOMPtr<nsITransferable> trans;
01948   rv = nsPlaintextEditor::PrepareTransferable(getter_AddRefs(trans));
01949   if (NS_SUCCEEDED(rv) && trans)
01950   {
01951     // Get the Data from the clipboard  
01952     if (NS_SUCCEEDED(clipboard->GetData(trans, aSelectionType)) && IsModifiable())
01953     {
01954       const nsAFlatString& empty = EmptyString();
01955       rv = InsertFromTransferable(trans, nsnull, empty, empty, nsnull, 0,
01956                                   PR_TRUE);
01957     }
01958   }
01959 
01960   return rv;
01961 }
01962 
01963 
01964 NS_IMETHODIMP nsHTMLEditor::CanPaste(PRInt32 aSelectionType, PRBool *aCanPaste)
01965 {
01966   if (!aCanPaste)
01967     return NS_ERROR_NULL_POINTER;
01968   *aCanPaste = PR_FALSE;
01969   
01970   // can't paste if readonly
01971   if (!IsModifiable())
01972     return NS_OK;
01973     
01974   nsresult rv;
01975   nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
01976   if (NS_FAILED(rv)) return rv;
01977   
01978   // the flavors that we can deal with
01979   const char* const textEditorFlavors[] = { kUnicodeMime, nsnull };
01980   const char* const htmlEditorFlavors[] = { kHTMLMime, kJPEGImageMime, nsnull };
01981 
01982   nsCOMPtr<nsISupportsArray> flavorsList =
01983                            do_CreateInstance(NS_SUPPORTSARRAY_CONTRACTID, &rv);
01984   if (NS_FAILED(rv)) return rv;
01985   
01986   PRUint32 editorFlags;
01987   GetFlags(&editorFlags);
01988   
01989   // add the flavors for all editors
01990   for (const char* const* flavor = textEditorFlavors; *flavor; flavor++)
01991   {
01992     nsCOMPtr<nsISupportsCString> flavorString  = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
01993     if (flavorString)
01994     {
01995       flavorString->SetData(nsDependentCString(*flavor));
01996       flavorsList->AppendElement(flavorString);
01997     }
01998   }
01999   
02000   // add the HTML-editor only flavors
02001   if ((editorFlags & eEditorPlaintextMask) == 0)
02002   {
02003     for (const char* const* htmlFlavor = htmlEditorFlavors;
02004          *htmlFlavor;
02005          htmlFlavor++)
02006     {
02007       nsCOMPtr<nsISupportsCString> flavorString  = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID);
02008       if (flavorString)
02009       {
02010         flavorString->SetData(nsDependentCString(*htmlFlavor));
02011         flavorsList->AppendElement(flavorString);
02012       }
02013     }
02014   }
02015   
02016   PRBool haveFlavors;
02017   rv = clipboard->HasDataMatchingFlavors(flavorsList, aSelectionType, &haveFlavors);
02018   if (NS_FAILED(rv)) return rv;
02019   
02020   *aCanPaste = haveFlavors;
02021   return NS_OK;
02022 }
02023 
02024 
02025 // 
02026 // HTML PasteAsQuotation: Paste in a blockquote type=cite
02027 //
02028 NS_IMETHODIMP nsHTMLEditor::PasteAsQuotation(PRInt32 aSelectionType)
02029 {
02030   if (mFlags & eEditorPlaintextMask)
02031     return PasteAsPlaintextQuotation(aSelectionType);
02032 
02033   nsAutoString citation;
02034   return PasteAsCitedQuotation(citation, aSelectionType);
02035 }
02036 
02037 NS_IMETHODIMP nsHTMLEditor::PasteAsCitedQuotation(const nsAString & aCitation,
02038                                                   PRInt32 aSelectionType)
02039 {
02040   nsAutoEditBatch beginBatching(this);
02041   nsAutoRules beginRulesSniffing(this, kOpInsertQuotation, nsIEditor::eNext);
02042 
02043   // get selection
02044   nsCOMPtr<nsISelection> selection;
02045   nsresult res = GetSelection(getter_AddRefs(selection));
02046   if (NS_FAILED(res)) return res;
02047   if (!selection) return NS_ERROR_NULL_POINTER;
02048 
02049   // give rules a chance to handle or cancel
02050   nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
02051   PRBool cancel, handled;
02052   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
02053   if (NS_FAILED(res)) return res;
02054   if (cancel) return NS_OK; // rules canceled the operation
02055   if (!handled)
02056   {
02057     nsCOMPtr<nsIDOMNode> newNode;
02058     res = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("blockquote"), getter_AddRefs(newNode));
02059     if (NS_FAILED(res)) return res;
02060     if (!newNode) return NS_ERROR_NULL_POINTER;
02061 
02062     // Try to set type=cite.  Ignore it if this fails.
02063     nsCOMPtr<nsIDOMElement> newElement (do_QueryInterface(newNode));
02064     if (newElement)
02065     {
02066       newElement->SetAttribute(NS_LITERAL_STRING("type"), NS_LITERAL_STRING("cite"));
02067     }
02068 
02069     // Set the selection to the underneath the node we just inserted:
02070     res = selection->Collapse(newNode, 0);
02071     if (NS_FAILED(res))
02072     {
02073 #ifdef DEBUG_akkana
02074       printf("Couldn't collapse");
02075 #endif
02076       // XXX: error result:  should res be returned here?
02077     }
02078 
02079     res = Paste(aSelectionType);
02080   }
02081   return res;
02082 }
02083 
02084 //
02085 // Paste a plaintext quotation
02086 //
02087 NS_IMETHODIMP nsHTMLEditor::PasteAsPlaintextQuotation(PRInt32 aSelectionType)
02088 {
02089   // Get Clipboard Service
02090   nsresult rv;
02091   nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1", &rv));
02092   if (NS_FAILED(rv)) return rv;
02093 
02094   // Create generic Transferable for getting the data
02095   nsCOMPtr<nsITransferable> trans =
02096                  do_CreateInstance("@mozilla.org/widget/transferable;1", &rv);
02097   if (NS_SUCCEEDED(rv) && trans)
02098   {
02099     // We only handle plaintext pastes here
02100     trans->AddDataFlavor(kUnicodeMime);
02101 
02102     // Get the Data from the clipboard
02103     clipboard->GetData(trans, aSelectionType);
02104 
02105     // Now we ask the transferable for the data
02106     // it still owns the data, we just have a pointer to it.
02107     // If it can't support a "text" output of the data the call will fail
02108     nsCOMPtr<nsISupports> genericDataObj;
02109     PRUint32 len = 0;
02110     char* flav = 0;
02111     rv = trans->GetAnyTransferData(&flav, getter_AddRefs(genericDataObj),
02112                                    &len);
02113     if (NS_FAILED(rv))
02114     {
02115 #ifdef DEBUG_akkana
02116       printf("PasteAsPlaintextQuotation: GetAnyTransferData failed, %d\n", rv);
02117 #endif
02118       return rv;
02119     }
02120 
02121     if (flav && 0 == nsCRT::strcmp((flav), kUnicodeMime))
02122     {
02123 #ifdef DEBUG_clipboard
02124     printf("Got flavor [%s]\n", flav);
02125 #endif
02126       nsCOMPtr<nsISupportsString> textDataObj(do_QueryInterface(genericDataObj));
02127       if (textDataObj && len > 0)
02128       {
02129         nsAutoString stuffToPaste;
02130         textDataObj->GetData(stuffToPaste);
02131         NS_ASSERTION(stuffToPaste.Length() <= (len/2), "Invalid length!");
02132         nsAutoEditBatch beginBatching(this);
02133         rv = InsertAsPlaintextQuotation(stuffToPaste, PR_TRUE, 0);
02134       }
02135     }
02136     NS_Free(flav);
02137   }
02138 
02139   return rv;
02140 }
02141 
02142 NS_IMETHODIMP
02143 nsHTMLEditor::InsertTextWithQuotations(const nsAString &aStringToInsert)
02144 {
02145   if (mWrapToWindow)
02146     return InsertText(aStringToInsert);
02147 
02148   // The whole operation should be undoable in one transaction:
02149   BeginTransaction();
02150 
02151   // We're going to loop over the string, collecting up a "hunk"
02152   // that's all the same type (quoted or not),
02153   // Whenever the quotedness changes (or we reach the string's end)
02154   // we will insert the hunk all at once, quoted or non.
02155 
02156   static const PRUnichar cite('>');
02157   PRBool curHunkIsQuoted = (aStringToInsert.First() == cite);
02158 
02159   nsAString::const_iterator hunkStart, strEnd;
02160   aStringToInsert.BeginReading(hunkStart);
02161   aStringToInsert.EndReading(strEnd);
02162 
02163   // In the loop below, we only look for DOM newlines (\n),
02164   // because we don't have a FindChars method that can look
02165   // for both \r and \n.  \r is illegal in the dom anyway,
02166   // but in debug builds, let's take the time to verify that
02167   // there aren't any there:
02168 #ifdef DEBUG
02169   nsAString::const_iterator dbgStart (hunkStart);
02170   if (FindCharInReadable('\r', dbgStart, strEnd))
02171     NS_ASSERTION(PR_FALSE,
02172             "Return characters in DOM! InsertTextWithQuotations may be wrong");
02173 #endif /* DEBUG */
02174 
02175   // Loop over lines:
02176   nsresult rv = NS_OK;
02177   nsAString::const_iterator lineStart (hunkStart);
02178   while (1)   // we will break from inside when we run out of newlines
02179   {
02180     // Search for the end of this line (dom newlines, see above):
02181     PRBool found = FindCharInReadable('\n', lineStart, strEnd);
02182     PRBool quoted = PR_FALSE;
02183     if (found)
02184     {
02185       // if there's another newline, lineStart now points there.
02186       // Loop over any consecutive newline chars:
02187       nsAString::const_iterator firstNewline (lineStart);
02188       while (*lineStart == '\n')
02189         ++lineStart;
02190       quoted = (*lineStart == cite);
02191       if (quoted == curHunkIsQuoted)
02192         continue;
02193       // else we're changing state, so we need to insert
02194       // from curHunk to lineStart then loop around.
02195 
02196       // But if the current hunk is quoted, then we want to make sure
02197       // that any extra newlines on the end do not get included in
02198       // the quoted section: blank lines flaking a quoted section
02199       // should be considered unquoted, so that if the user clicks
02200       // there and starts typing, the new text will be outside of
02201       // the quoted block.
02202       if (curHunkIsQuoted)
02203         lineStart = firstNewline;
02204     }
02205 
02206     // If no newline found, lineStart is now strEnd and we can finish up,
02207     // inserting from curHunk to lineStart then returning.
02208     const nsAString &curHunk = Substring(hunkStart, lineStart);
02209     nsCOMPtr<nsIDOMNode> dummyNode;
02210 #ifdef DEBUG_akkana_verbose
02211     printf("==== Inserting text as %squoted: ---\n%s---\n",
02212            curHunkIsQuoted ? "" : "non-",
02213            NS_LossyConvertUCS2toASCII(curHunk).get());
02214 #endif
02215     if (curHunkIsQuoted)
02216       rv = InsertAsPlaintextQuotation(curHunk, PR_FALSE,
02217                                       getter_AddRefs(dummyNode));
02218     else
02219       rv = InsertText(curHunk);
02220 
02221     if (!found)
02222       break;
02223 
02224     curHunkIsQuoted = quoted;
02225     hunkStart = lineStart;
02226   }
02227 
02228   EndTransaction();
02229 
02230   return rv;
02231 }
02232 
02233 NS_IMETHODIMP nsHTMLEditor::InsertAsQuotation(const nsAString & aQuotedText,
02234                                               nsIDOMNode **aNodeInserted)
02235 {
02236   if (mFlags & eEditorPlaintextMask)
02237     return InsertAsPlaintextQuotation(aQuotedText, PR_TRUE, aNodeInserted);
02238 
02239   nsAutoString citation;
02240   return InsertAsCitedQuotation(aQuotedText, citation, PR_FALSE,
02241                                 aNodeInserted);
02242 }
02243 
02244 // Insert plaintext as a quotation, with cite marks (e.g. "> ").
02245 // This differs from its corresponding method in nsPlaintextEditor
02246 // in that here, quoted material is enclosed in a <pre> tag
02247 // in order to preserve the original line wrapping.
02248 NS_IMETHODIMP
02249 nsHTMLEditor::InsertAsPlaintextQuotation(const nsAString & aQuotedText,
02250                                          PRBool aAddCites,
02251                                          nsIDOMNode **aNodeInserted)
02252 {
02253   if (mWrapToWindow)
02254     return nsPlaintextEditor::InsertAsQuotation(aQuotedText, aNodeInserted);
02255 
02256   nsresult rv;
02257 
02258   // The quotesPreformatted pref is a temporary measure. See bug 69638.
02259   // Eventually we'll pick one way or the other.
02260   PRBool quotesInPre;
02261   nsCOMPtr<nsIPrefBranch> prefBranch =
02262     do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
02263   if (NS_SUCCEEDED(rv) && prefBranch)
02264     prefBranch->GetBoolPref("editor.quotesPreformatted", &quotesInPre);
02265 
02266   nsCOMPtr<nsIDOMNode> preNode;
02267   // get selection
02268   nsCOMPtr<nsISelection> selection;
02269   rv = GetSelection(getter_AddRefs(selection));
02270   if (NS_FAILED(rv)) return rv;
02271   if (!selection) return NS_ERROR_NULL_POINTER;
02272   else
02273   {
02274     nsAutoEditBatch beginBatching(this);
02275     nsAutoRules beginRulesSniffing(this, kOpInsertQuotation, nsIEditor::eNext);
02276 
02277     // give rules a chance to handle or cancel
02278     nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
02279     PRBool cancel, handled;
02280     rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
02281     if (NS_FAILED(rv)) return rv;
02282     if (cancel) return NS_OK; // rules canceled the operation
02283     if (!handled)
02284     {
02285       // Wrap the inserted quote in a <pre> so it won't be wrapped:
02286       nsAutoString tag;
02287       if (quotesInPre)
02288         tag.AssignLiteral("pre");
02289       else
02290         tag.AssignLiteral("span");
02291 
02292       rv = DeleteSelectionAndCreateNode(tag, getter_AddRefs(preNode));
02293       
02294       // If this succeeded, then set selection inside the pre
02295       // so the inserted text will end up there.
02296       // If it failed, we don't care what the return value was,
02297       // but we'll fall through and try to insert the text anyway.
02298       if (NS_SUCCEEDED(rv) && preNode)
02299       {
02300         // Add an attribute on the pre node so we'll know it's a quotation.
02301         // Do this after the insertion, so that 
02302         nsCOMPtr<nsIDOMElement> preElement (do_QueryInterface(preNode));
02303         if (preElement)
02304         {
02305           preElement->SetAttribute(NS_LITERAL_STRING("_moz_quote"),
02306                                    NS_LITERAL_STRING("true"));
02307           if (quotesInPre)
02308           {
02309             // set style to not have unwanted vertical margins
02310             preElement->SetAttribute(NS_LITERAL_STRING("style"),
02311                                      NS_LITERAL_STRING("margin: 0 0 0 0px;"));
02312           }
02313           else
02314           {
02315             // turn off wrapping on spans
02316             preElement->SetAttribute(NS_LITERAL_STRING("style"),
02317                                      NS_LITERAL_STRING("white-space: pre;"));
02318           }
02319         }
02320 
02321         // and set the selection inside it:
02322         selection->Collapse(preNode, 0);
02323       }
02324 
02325       if (aAddCites)
02326         rv = nsPlaintextEditor::InsertAsQuotation(aQuotedText, aNodeInserted);
02327       else
02328         rv = nsPlaintextEditor::InsertText(aQuotedText);
02329       // Note that if !aAddCites, aNodeInserted isn't set.
02330       // That's okay because the routines that use aAddCites
02331       // don't need to know the inserted node.
02332 
02333       if (aNodeInserted && NS_SUCCEEDED(rv))
02334       {
02335         *aNodeInserted = preNode;
02336         NS_IF_ADDREF(*aNodeInserted);
02337       }
02338     }
02339   }
02340     
02341   // Set the selection to just after the inserted node:
02342   if (NS_SUCCEEDED(rv) && preNode)
02343   {
02344     nsCOMPtr<nsIDOMNode> parent;
02345     PRInt32 offset;
02346     if (NS_SUCCEEDED(GetNodeLocation(preNode, address_of(parent), &offset)) && parent)
02347       selection->Collapse(parent, offset+1);
02348   }
02349   return rv;
02350 }
02351 
02352 NS_IMETHODIMP    
02353 nsHTMLEditor::StripCites()
02354 {
02355   return nsPlaintextEditor::StripCites();
02356 }
02357 
02358 NS_IMETHODIMP    
02359 nsHTMLEditor::Rewrap(PRBool aRespectNewlines)
02360 {
02361   return nsPlaintextEditor::Rewrap(aRespectNewlines);
02362 }
02363 
02364 NS_IMETHODIMP
02365 nsHTMLEditor::InsertAsCitedQuotation(const nsAString & aQuotedText,
02366                                      const nsAString & aCitation,
02367                                      PRBool aInsertHTML,
02368                                      nsIDOMNode **aNodeInserted)
02369 {
02370   // Don't let anyone insert html into a "plaintext" editor:
02371   if (mFlags & eEditorPlaintextMask)
02372   {
02373     NS_ASSERTION(!aInsertHTML, "InsertAsCitedQuotation: trying to insert html into plaintext editor");
02374     return InsertAsPlaintextQuotation(aQuotedText, PR_TRUE, aNodeInserted);
02375   }
02376 
02377   nsCOMPtr<nsIDOMNode> newNode;
02378   nsresult res = NS_OK;
02379 
02380   // get selection
02381   nsCOMPtr<nsISelection> selection;
02382   res = GetSelection(getter_AddRefs(selection));
02383   if (NS_FAILED(res)) return res;
02384   if (!selection) return NS_ERROR_NULL_POINTER;
02385   else
02386   {
02387     nsAutoEditBatch beginBatching(this);
02388     nsAutoRules beginRulesSniffing(this, kOpInsertQuotation, nsIEditor::eNext);
02389 
02390     // give rules a chance to handle or cancel
02391     nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
02392     PRBool cancel, handled;
02393     res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
02394     if (NS_FAILED(res)) return res;
02395     if (cancel) return NS_OK; // rules canceled the operation
02396     if (!handled)
02397     {
02398       res = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("blockquote"), getter_AddRefs(newNode));
02399       if (NS_FAILED(res)) return res;
02400       if (!newNode) return NS_ERROR_NULL_POINTER;
02401 
02402       // Try to set type=cite.  Ignore it if this fails.
02403       nsCOMPtr<nsIDOMElement> newElement (do_QueryInterface(newNode));
02404       if (newElement)
02405       {
02406         NS_NAMED_LITERAL_STRING(citestr, "cite");
02407         newElement->SetAttribute(NS_LITERAL_STRING("type"), citestr);
02408 
02409         if (!aCitation.IsEmpty())
02410           newElement->SetAttribute(citestr, aCitation);
02411 
02412         // Set the selection inside the blockquote so aQuotedText will go there:
02413         selection->Collapse(newNode, 0);
02414       }
02415 
02416       if (aInsertHTML)
02417         res = LoadHTML(aQuotedText);
02418 
02419       else
02420         res = InsertText(aQuotedText);  // XXX ignore charset
02421 
02422       if (aNodeInserted)
02423       {
02424         if (NS_SUCCEEDED(res))
02425         {
02426           *aNodeInserted = newNode;
02427           NS_IF_ADDREF(*aNodeInserted);
02428         }
02429       }
02430     }
02431   }
02432 
02433   // Set the selection to just after the inserted node:
02434   if (NS_SUCCEEDED(res) && newNode)
02435   {
02436     nsCOMPtr<nsIDOMNode> parent;
02437     PRInt32 offset;
02438     if (NS_SUCCEEDED(GetNodeLocation(newNode, address_of(parent), &offset)) && parent)
02439       selection->Collapse(parent, offset+1);
02440   }
02441   return res;
02442 }
02443 
02444 
02445 void RemoveBodyAndHead(nsIDOMNode *aNode)
02446 {
02447   if (!aNode) 
02448     return;
02449     
02450   nsCOMPtr<nsIDOMNode> tmp, child, body, head;  
02451   // find the body and head nodes if any.
02452   // look only at immediate children of aNode.
02453   aNode->GetFirstChild(getter_AddRefs(child));
02454   while (child)
02455   {
02456     if (nsTextEditUtils::IsBody(child))
02457     {
02458       body = child;
02459     }
02460     else if (nsEditor::NodeIsType(child, nsEditProperty::head))
02461     {
02462       head = child;
02463     }
02464     child->GetNextSibling(getter_AddRefs(tmp));
02465     child = tmp;
02466   }
02467   if (head) 
02468   {
02469     aNode->RemoveChild(head, getter_AddRefs(tmp));
02470   }
02471   if (body)
02472   {
02473     body->GetFirstChild(getter_AddRefs(child));
02474     while (child)
02475     {
02476       aNode->InsertBefore(child, body, getter_AddRefs(tmp));
02477       body->GetFirstChild(getter_AddRefs(child));
02478     }
02479     aNode->RemoveChild(body, getter_AddRefs(tmp));
02480   }
02481 }
02482 
02483 
02484 nsresult nsHTMLEditor::CreateDOMFragmentFromPaste(const nsAString &aInputString,
02485                                                   const nsAString & aContextStr,
02486                                                   const nsAString & aInfoStr,
02487                                                   nsCOMPtr<nsIDOMNode> *outFragNode,
02488                                                   PRInt32 *outRangeStartHint,
02489                                                   PRInt32 *outRangeEndHint)
02490 {
02491   if (!outFragNode || !outRangeStartHint || !outRangeEndHint) 
02492     return NS_ERROR_NULL_POINTER;
02493   nsCOMPtr<nsIDOMDocumentFragment> docfrag;
02494   nsCOMPtr<nsIDOMNode> contextAsNode, tmp;  
02495   nsresult res = NS_OK;
02496 
02497   nsCOMPtr<nsIDOMDocument> domDoc;
02498   GetDocument(getter_AddRefs(domDoc));
02499 
02500   nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
02501   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
02502   
02503   // if we have context info, create a fragment for that
02504   nsVoidArray tagStack;
02505   nsCOMPtr<nsIDOMDocumentFragment> contextfrag;
02506   nsCOMPtr<nsIDOMNode> contextLeaf, junk;
02507   PRInt32 contextDepth = 0;
02508   if (!aContextStr.IsEmpty())
02509   {
02510     res = ParseFragment(aContextStr, tagStack, doc, address_of(contextAsNode));
02511     NS_ENSURE_SUCCESS(res, res);
02512     NS_ENSURE_TRUE(contextAsNode, NS_ERROR_FAILURE);
02513 
02514     res = StripFormattingNodes(contextAsNode);
02515     NS_ENSURE_SUCCESS(res, res);
02516     
02517     RemoveBodyAndHead(contextAsNode);
02518     
02519     // cache the deepest leaf in the context
02520     tmp = contextAsNode;
02521     while (tmp)
02522     {
02523       contextDepth++;
02524       contextLeaf = tmp;
02525       contextLeaf->GetFirstChild(getter_AddRefs(tmp));
02526     }
02527   }
02528  
02529   // get the tagstack for the context
02530   res = CreateTagStack(tagStack, contextLeaf);
02531   if (NS_FAILED(res))
02532   {
02533     FreeTagStackStrings(tagStack);
02534     return res;
02535   }
02536   // create fragment for pasted html
02537   res = ParseFragment(aInputString, tagStack, doc, outFragNode);
02538   FreeTagStackStrings(tagStack);
02539   NS_ENSURE_SUCCESS(res, res);
02540   NS_ENSURE_TRUE(*outFragNode, NS_ERROR_FAILURE);
02541       
02542   RemoveBodyAndHead(*outFragNode);
02543   
02544   if (contextAsNode)
02545   {
02546     // unite the two trees
02547     contextLeaf->AppendChild(*outFragNode, getter_AddRefs(junk));
02548     *outFragNode = contextAsNode;
02549     // no longer have fragmentAsNode in tree
02550     contextDepth--;
02551   }
02552  
02553   res = StripFormattingNodes(*outFragNode, PR_TRUE);
02554   NS_ENSURE_SUCCESS(res, res);
02555  
02556   // get the infoString contents
02557   nsAutoString numstr1, numstr2;
02558   if (!aInfoStr.IsEmpty())
02559   {
02560     PRInt32 err, sep;
02561     sep = aInfoStr.FindChar((PRUnichar)',');
02562     numstr1 = Substring(aInfoStr, 0, sep);
02563     numstr2 = Substring(aInfoStr, sep+1, aInfoStr.Length() - (sep+1));
02564     *outRangeStartHint = numstr1.ToInteger(&err) + contextDepth;
02565     *outRangeEndHint   = numstr2.ToInteger(&err) + contextDepth;
02566   }
02567   else
02568   {
02569     *outRangeStartHint = contextDepth;
02570     *outRangeEndHint = contextDepth;
02571   }
02572   return res;
02573 }
02574 
02575 
02576 nsresult nsHTMLEditor::ParseFragment(const nsAString & aFragStr,
02577                                      nsVoidArray &aTagStack,
02578                                      nsIDocument* aTargetDocument,
02579                                      nsCOMPtr<nsIDOMNode> *outNode)
02580 {
02581   // figure out if we are parsing full context or not
02582   PRBool bContext = (aTagStack.Count()==0);
02583 
02584   // create the parser to do the conversion.
02585   nsresult res;
02586   nsCOMPtr<nsIParser> parser = do_CreateInstance(kCParserCID, &res);
02587   NS_ENSURE_SUCCESS(res, res);
02588   NS_ENSURE_TRUE(parser, NS_ERROR_FAILURE);
02589 
02590   // create the html fragment sink
02591   nsCOMPtr<nsIContentSink> sink;
02592   if (bContext)
02593     sink = do_CreateInstance(NS_HTMLFRAGMENTSINK2_CONTRACTID);
02594   else
02595     sink = do_CreateInstance(NS_HTMLFRAGMENTSINK_CONTRACTID);
02596 
02597   NS_ENSURE_TRUE(sink, NS_ERROR_FAILURE);
02598   nsCOMPtr<nsIFragmentContentSink> fragSink(do_QueryInterface(sink));
02599   NS_ENSURE_TRUE(fragSink, NS_ERROR_FAILURE);
02600 
02601   fragSink->SetTargetDocument(aTargetDocument);
02602   
02603   // parse the fragment
02604   parser->SetContentSink(sink);
02605   if (bContext)
02606     parser->Parse(aFragStr, (void*)0, NS_LITERAL_CSTRING("text/html"), PR_FALSE, PR_TRUE, eDTDMode_fragment);
02607   else
02608     parser->ParseFragment(aFragStr, 0, aTagStack, PR_FALSE, NS_LITERAL_CSTRING("text/html"), eDTDMode_quirks);
02609   // get the fragment node
02610   nsCOMPtr<nsIDOMDocumentFragment> contextfrag;
02611   res = fragSink->GetFragment(getter_AddRefs(contextfrag));
02612   NS_ENSURE_SUCCESS(res, res);
02613   *outNode = do_QueryInterface(contextfrag);
02614   
02615   return res;
02616 }
02617 
02618 nsresult nsHTMLEditor::CreateTagStack(nsVoidArray &aTagStack, nsIDOMNode *aNode)
02619 {
02620   nsresult res = NS_OK;
02621   nsCOMPtr<nsIDOMNode> node= aNode;
02622   PRBool bSeenBody = PR_FALSE;
02623   
02624   while (node) 
02625   {
02626     if (nsTextEditUtils::IsBody(node))
02627       bSeenBody = PR_TRUE;
02628     nsCOMPtr<nsIDOMNode> temp = node;
02629     PRUint16 nodeType;
02630     
02631     node->GetNodeType(&nodeType);
02632     if (nsIDOMNode::ELEMENT_NODE == nodeType)
02633     {
02634       nsAutoString tagName;
02635       node->GetNodeName(tagName);
02636       // XXX Wish we didn't have to allocate here
02637       PRUnichar* name = ToNewUnicode(tagName);
02638       if (!name) 
02639              return NS_ERROR_OUT_OF_MEMORY;
02640 
02641       aTagStack.AppendElement(name);
02642       // printf("%s\n",NS_LossyConvertUCS2toASCII(tagName).get());
02643     }
02644 
02645     res = temp->GetParentNode(getter_AddRefs(node));
02646     NS_ENSURE_SUCCESS(res, res);  
02647   }
02648   
02649   if (!bSeenBody)
02650   {
02651       PRUnichar* bodyname = ToNewUnicode(NS_LITERAL_STRING("BODY"));
02652       aTagStack.AppendElement(bodyname);
02653   }
02654   return res;
02655 }
02656 
02657 
02658 void nsHTMLEditor::FreeTagStackStrings(nsVoidArray &tagStack)
02659 {
02660   PRInt32 count = tagStack.Count();
02661   for (PRInt32 i = 0; i < count; i++) 
02662   {
02663     PRUnichar* str = (PRUnichar*)tagStack.ElementAt(i);
02664     if (str) {
02665       NS_Free(str);
02666     }
02667   }
02668 }
02669 
02670 nsresult nsHTMLEditor::CreateListOfNodesToPaste(nsIDOMNode  *aFragmentAsNode,
02671                                                 nsCOMArray<nsIDOMNode>& outNodeList,
02672                                                 nsIDOMNode *aStartNode,
02673                                                 PRInt32 aStartOffset,
02674                                                 nsIDOMNode *aEndNode,
02675                                                 PRInt32 aEndOffset)
02676 {
02677   if (!aFragmentAsNode) 
02678     return NS_ERROR_NULL_POINTER;
02679 
02680   nsresult res;
02681 
02682   // if no info was provided about the boundary between context and stream,
02683   // then assume all is stream.
02684   if (!aStartNode)
02685   {
02686     PRInt32 fragLen;
02687     res = GetLengthOfDOMNode(aFragmentAsNode, (PRUint32&)fragLen);
02688     NS_ENSURE_SUCCESS(res, res);
02689 
02690     aStartNode = aFragmentAsNode;
02691     aStartOffset = 0;
02692     aEndNode = aFragmentAsNode;
02693     aEndOffset = fragLen;
02694   }
02695 
02696   nsCOMPtr<nsIDOMRange> docFragRange =
02697                           do_CreateInstance("@mozilla.org/content/range;1");
02698   if (!docFragRange) return NS_ERROR_OUT_OF_MEMORY;
02699 
02700   res = docFragRange->SetStart(aStartNode, aStartOffset);
02701   NS_ENSURE_SUCCESS(res, res);
02702   res = docFragRange->SetEnd(aEndNode, aEndOffset);
02703   NS_ENSURE_SUCCESS(res, res);
02704 
02705   // now use a subtree iterator over the range to create a list of nodes
02706   nsTrivialFunctor functor;
02707   nsDOMSubtreeIterator iter;
02708   res = iter.Init(docFragRange);
02709   NS_ENSURE_SUCCESS(res, res);
02710   res = iter.AppendList(functor, outNodeList);
02711 
02712   return res;
02713 }
02714 
02715 nsresult 
02716 nsHTMLEditor::GetListAndTableParents(PRBool aEnd, 
02717                                      nsCOMArray<nsIDOMNode>& aListOfNodes,
02718                                      nsCOMArray<nsIDOMNode>& outArray)
02719 {
02720   PRInt32 listCount = aListOfNodes.Count();
02721   if (listCount <= 0)
02722     return NS_ERROR_FAILURE;  // no empty lists, please
02723     
02724   // build up list of parents of first (or last) node in list 
02725   // that are either lists, or tables.  
02726   PRInt32 idx = 0;
02727   if (aEnd) idx = listCount-1;
02728   
02729   nsCOMPtr<nsIDOMNode>  pNode = aListOfNodes[idx];
02730   while (pNode)
02731   {
02732     if (nsHTMLEditUtils::IsList(pNode) || nsHTMLEditUtils::IsTable(pNode))
02733     {
02734       if (!outArray.AppendObject(pNode))
02735       {
02736         return NS_ERROR_FAILURE;
02737       }
02738     }
02739     nsCOMPtr<nsIDOMNode> parent;
02740     pNode->GetParentNode(getter_AddRefs(parent));
02741     pNode = parent;
02742   }
02743   return NS_OK;
02744 }
02745 
02746 nsresult
02747 nsHTMLEditor::DiscoverPartialListsAndTables(nsCOMArray<nsIDOMNode>& aPasteNodes,
02748                                             nsCOMArray<nsIDOMNode>& aListsAndTables,
02749                                             PRInt32 *outHighWaterMark)
02750 {
02751   NS_ENSURE_TRUE(outHighWaterMark, NS_ERROR_NULL_POINTER);
02752   
02753   *outHighWaterMark = -1;
02754   PRInt32 listAndTableParents = aListsAndTables.Count();
02755   
02756   // scan insertion list for table elements (other than table).
02757   PRInt32 listCount = aPasteNodes.Count();
02758   PRInt32 j;  
02759   for (j=0; j<listCount; j++)
02760   {
02761     nsCOMPtr<nsIDOMNode> curNode = aPasteNodes[j];
02762 
02763     NS_ENSURE_TRUE(curNode, NS_ERROR_FAILURE);
02764     if (nsHTMLEditUtils::IsTableElement(curNode) && !nsHTMLEditUtils::IsTable(curNode))
02765     {
02766       nsCOMPtr<nsIDOMNode> theTable = GetTableParent(curNode);
02767       if (theTable)
02768       {
02769         PRInt32 indexT = aListsAndTables.IndexOf(theTable);
02770         if (indexT >= 0)
02771         {
02772           *outHighWaterMark = indexT;
02773           if (*outHighWaterMark == listAndTableParents-1) break;
02774         }
02775         else
02776         {
02777           break;
02778         }
02779       }
02780     }
02781     if (nsHTMLEditUtils::IsListItem(curNode))
02782     {
02783       nsCOMPtr<nsIDOMNode> theList = GetListParent(curNode);
02784       if (theList)
02785       {
02786         PRInt32 indexL = aListsAndTables.IndexOf(theList);
02787         if (indexL >= 0)
02788         {
02789           *outHighWaterMark = indexL;
02790           if (*outHighWaterMark == listAndTableParents-1) break;
02791         }
02792         else
02793         {
02794           break;
02795         }
02796       }
02797     }
02798   }
02799   return NS_OK;
02800 }
02801 
02802 nsresult
02803 nsHTMLEditor::ScanForListAndTableStructure( PRBool aEnd,
02804                                             nsCOMArray<nsIDOMNode>& aNodes,
02805                                             nsIDOMNode *aListOrTable,
02806                                             nsCOMPtr<nsIDOMNode> *outReplaceNode)
02807 {
02808   NS_ENSURE_TRUE(aListOrTable, NS_ERROR_NULL_POINTER);
02809   NS_ENSURE_TRUE(outReplaceNode, NS_ERROR_NULL_POINTER);
02810 
02811   *outReplaceNode = 0;
02812   
02813   // look upward from first/last paste node for a piece of this list/table
02814   PRInt32 listCount = aNodes.Count(), idx = 0;
02815   if (aEnd) idx = listCount-1;
02816   PRBool bList = nsHTMLEditUtils::IsList(aListOrTable);
02817   
02818   nsCOMPtr<nsIDOMNode>  pNode = aNodes[idx];
02819   nsCOMPtr<nsIDOMNode>  originalNode = pNode;
02820   while (pNode)
02821   {
02822     if ( (bList && nsHTMLEditUtils::IsListItem(pNode)) ||
02823          (!bList && (nsHTMLEditUtils::IsTableElement(pNode) && !nsHTMLEditUtils::IsTable(pNode))) )
02824     {
02825       nsCOMPtr<nsIDOMNode> structureNode;
02826       if (bList) structureNode = GetListParent(pNode);
02827       else structureNode = GetTableParent(pNode);
02828       if (structureNode == aListOrTable)
02829       {
02830         if (bList)
02831           *outReplaceNode = structureNode;
02832         else
02833           *outReplaceNode = pNode;
02834         break;
02835       }
02836     }
02837     nsCOMPtr<nsIDOMNode> parent;
02838     pNode->GetParentNode(getter_AddRefs(parent));
02839     pNode = parent;
02840   }
02841   return NS_OK;
02842 }    
02843 
02844 nsresult
02845 nsHTMLEditor::ReplaceOrphanedStructure(PRBool aEnd,
02846                                        nsCOMArray<nsIDOMNode>& aNodeArray,
02847                                        nsCOMArray<nsIDOMNode>& aListAndTableArray,
02848                                        PRInt32 aHighWaterMark)
02849 {
02850   nsCOMPtr<nsIDOMNode> curNode = aListAndTableArray[aHighWaterMark];
02851   NS_ENSURE_TRUE(curNode, NS_ERROR_NULL_POINTER);
02852   
02853   nsCOMPtr<nsIDOMNode> replaceNode, originalNode;
02854   
02855   // find substructure of list or table that must be included in paste.
02856   nsresult res = ScanForListAndTableStructure(aEnd, aNodeArray, 
02857                                  curNode, address_of(replaceNode));
02858   NS_ENSURE_SUCCESS(res, res);
02859   
02860   // if we found substructure, paste it instead of it's descendants
02861   if (replaceNode)
02862   {
02863     // postprocess list to remove any descendants of this node
02864     // so that we dont insert them twice.
02865     nsCOMPtr<nsIDOMNode> endpoint;
02866     do
02867     {
02868       endpoint = GetArrayEndpoint(aEnd, aNodeArray);
02869       if (!endpoint) break;
02870       if (nsEditorUtils::IsDescendantOf(endpoint, replaceNode))
02871         aNodeArray.RemoveObject(endpoint);
02872       else
02873         break;
02874     } while(endpoint);
02875     
02876     // now replace the removed nodes with the structural parent
02877     if (aEnd) aNodeArray.AppendObject(replaceNode);
02878     else aNodeArray.InsertObjectAt(replaceNode, 0);
02879   }
02880   return NS_OK;
02881 }
02882 
02883 nsIDOMNode* nsHTMLEditor::GetArrayEndpoint(PRBool aEnd,
02884                                            nsCOMArray<nsIDOMNode>& aNodeArray)
02885 {
02886   PRInt32 listCount = aNodeArray.Count();
02887   if (listCount <= 0) 
02888     return nsnull;
02889 
02890   if (aEnd)
02891   {
02892     return aNodeArray[listCount-1];
02893   }
02894   
02895   return aNodeArray[0];
02896 }