Back to index

lightning-sunbird  0.9+nobinonly
nsHTMLEditor.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  *   Pierre Phaneuf <pp@ludusdesign.com>
00024  *   Daniel Glazman <glazman@netscape.com>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either of the GNU General Public License Version 2 or later (the "GPL"),
00028  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 #include "nsICaret.h"
00040 #include "nsCRT.h"
00041 
00042 #include "nsReadableUtils.h"
00043 #include "nsUnicharUtils.h"
00044 
00045 #include "nsHTMLEditor.h"
00046 #include "nsHTMLEditRules.h"
00047 #include "nsTextEditUtils.h"
00048 #include "nsHTMLEditUtils.h"
00049 
00050 #include "nsEditorEventListeners.h"
00051 #include "nsHTMLEditorMouseListener.h"
00052 #include "TypeInState.h"
00053 
00054 #include "nsHTMLURIRefObject.h"
00055 
00056 #include "nsIDOMText.h"
00057 #include "nsITextContent.h"
00058 #include "nsIDOMNodeList.h"
00059 #include "nsIDOMDocument.h"
00060 #include "nsIDOMAttr.h"
00061 #include "nsIDocument.h"
00062 #include "nsIDOMEventReceiver.h" 
00063 #include "nsIDOM3EventTarget.h" 
00064 #include "nsIDOMKeyEvent.h"
00065 #include "nsIDOMKeyListener.h" 
00066 #include "nsIDOMMouseListener.h"
00067 #include "nsIDOMMouseEvent.h"
00068 #include "nsISelection.h"
00069 #include "nsISelectionPrivate.h"
00070 #include "nsIDOMHTMLAnchorElement.h"
00071 #include "nsISelectionController.h"
00072 #include "nsIDOMHTMLHtmlElement.h"
00073 #include "nsGUIEvent.h"
00074 #include "nsIDOMEventGroup.h"
00075 #include "nsILinkHandler.h"
00076 
00077 #include "TransactionFactory.h"
00078 
00079 #include "nsICSSLoader.h"
00080 #include "nsICSSStyleSheet.h"
00081 #include "nsIDocumentObserver.h"
00082 #include "nsIDocumentStateListener.h"
00083 
00084 #include "nsIEnumerator.h"
00085 #include "nsIContent.h"
00086 #include "nsIContentIterator.h"
00087 #include "nsIDOMRange.h"
00088 #include "nsIDOMNSRange.h"
00089 #include "nsIRangeUtils.h"
00090 #include "nsISupportsArray.h"
00091 #include "nsVoidArray.h"
00092 #include "nsIURL.h"
00093 #include "nsIComponentManager.h"
00094 #include "nsIServiceManager.h"
00095 #include "nsIDocumentEncoder.h"
00096 #include "nsIDOMDocumentFragment.h"
00097 #include "nsIPresShell.h"
00098 #include "nsPresContext.h"
00099 #include "nsParserCIID.h"
00100 #include "nsIImage.h"
00101 #include "nsAOLCiter.h"
00102 #include "nsInternetCiter.h"
00103 #include "nsXPCOM.h"
00104 #include "nsISupportsPrimitives.h"
00105 #include "SetDocTitleTxn.h"
00106 #include "nsGUIEvent.h"
00107 #include "nsTextFragment.h"
00108 
00109 // netwerk
00110 #include "nsIURI.h"
00111 #include "nsNetUtil.h"
00112 
00113 // Drag & Drop, Clipboard
00114 #include "nsIClipboard.h"
00115 #include "nsITransferable.h"
00116 #include "nsIDragService.h"
00117 #include "nsIDOMNSUIEvent.h"
00118 #include "nsIContentFilter.h"
00119 
00120 // Transactionas
00121 #include "nsStyleSheetTxns.h"
00122 
00123 // Misc
00124 #include "TextEditorTest.h"
00125 #include "nsEditorUtils.h"
00126 #include "nsITextContent.h"
00127 #include "nsWSRunObject.h"
00128 #include "nsHTMLObjectResizer.h"
00129 
00130 #include "nsIFrame.h"
00131 #include "nsIView.h"
00132 #include "nsIWidget.h"
00133 #include "nsIParserService.h"
00134 
00135 static NS_DEFINE_CID(kCTransitionalDTDCID,  NS_CTRANSITIONAL_DTD_CID);
00136 
00137 // Some utilities to handle annoying overloading of "A" tag for link and named anchor
00138 static char hrefText[] = "href";
00139 static char anchorTxt[] = "anchor";
00140 static char namedanchorText[] = "namedanchor";
00141 
00142 nsIRangeUtils* nsHTMLEditor::sRangeHelper;
00143 
00144 // Defined in nsEditorRegistration.cpp
00145 extern nsIParserService *sParserService;
00146 
00147 // some prototypes for rules creation shortcuts
00148 nsresult NS_NewTextEditRules(nsIEditRules** aInstancePtrResult);
00149 nsresult NS_NewHTMLEditRules(nsIEditRules** aInstancePtrResult);
00150 
00151 #define IsLinkTag(s) (s.EqualsIgnoreCase(hrefText))
00152 #define IsNamedAnchorTag(s) (s.EqualsIgnoreCase(anchorTxt) || s.EqualsIgnoreCase(namedanchorText))
00153 
00154 nsHTMLEditor::nsHTMLEditor()
00155 : nsPlaintextEditor()
00156 , mIgnoreSpuriousDragEvent(PR_FALSE)
00157 , mTypeInState(nsnull)
00158 , mCRInParagraphCreatesParagraph(PR_FALSE)
00159 , mHTMLCSSUtils(nsnull)
00160 , mSelectedCellIndex(0)
00161 , mIsObjectResizingEnabled(PR_TRUE)
00162 , mIsResizing(PR_FALSE)
00163 , mIsAbsolutelyPositioningEnabled(PR_TRUE)
00164 , mResizedObjectIsAbsolutelyPositioned(PR_FALSE)
00165 , mGrabberClicked(PR_FALSE)
00166 , mIsMoving(PR_FALSE)
00167 , mSnapToGridEnabled(PR_FALSE)
00168 , mIsInlineTableEditingEnabled(PR_TRUE)
00169 , mGridSize(0)
00170 {
00171 } 
00172 
00173 nsHTMLEditor::~nsHTMLEditor()
00174 {
00175   // remove the rules as an action listener.  Else we get a bad
00176   // ownership loop later on.  it's ok if the rules aren't a listener;
00177   // we ignore the error.
00178   nsCOMPtr<nsIEditActionListener> mListener = do_QueryInterface(mRules);
00179   RemoveEditActionListener(mListener);
00180   
00181   // Clean up after our anonymous content -- we don't want these nodes to
00182   // stay around (which they would, since the frames have an owning reference).
00183 
00184   if (mAbsolutelyPositionedObject)
00185     HideGrabber();
00186   if (mInlineEditedCell)
00187     HideInlineTableEditingUI();
00188   if (mResizedObject)
00189     HideResizers();
00190 
00191   //the autopointers will clear themselves up. 
00192   //but we need to also remove the listeners or we have a leak
00193   nsCOMPtr<nsISelection>selection;
00194   nsresult result = GetSelection(getter_AddRefs(selection));
00195   // if we don't get the selection, just skip this
00196   if (NS_SUCCEEDED(result) && selection) 
00197   {
00198     nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
00199     nsCOMPtr<nsISelectionListener>listener;
00200     listener = do_QueryInterface(mTypeInState);
00201     if (listener)
00202     {
00203       selPriv->RemoveSelectionListener(listener); 
00204     }
00205     listener = do_QueryInterface(mSelectionListenerP);
00206     if (listener)
00207     {
00208       selPriv->RemoveSelectionListener(listener); 
00209     }
00210   }
00211 
00212   NS_IF_RELEASE(mTypeInState);
00213   mSelectionListenerP = nsnull;
00214 
00215   delete mHTMLCSSUtils;
00216 
00217   // free any default style propItems
00218   RemoveAllDefaultProperties();
00219 
00220   while (mStyleSheetURLs.Count())
00221   {
00222     nsAString* strp = mStyleSheetURLs.StringAt(0);
00223 
00224     if (strp)
00225     {
00226       RemoveOverrideStyleSheet(*strp);
00227     }
00228   }
00229 
00230   if (mLinkHandler && mPresShellWeak)
00231   {
00232     nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
00233 
00234     if (ps && ps->GetPresContext())
00235     {
00236       ps->GetPresContext()->SetLinkHandler(mLinkHandler);
00237     }
00238   }
00239 
00240   RemoveEventListeners();
00241 }
00242 
00243 /* static */
00244 void
00245 nsHTMLEditor::Shutdown()
00246 {
00247   NS_IF_RELEASE(sRangeHelper);
00248 }
00249 
00250 NS_IMPL_ADDREF_INHERITED(nsHTMLEditor, nsEditor)
00251 NS_IMPL_RELEASE_INHERITED(nsHTMLEditor, nsEditor)
00252 
00253 NS_INTERFACE_MAP_BEGIN(nsHTMLEditor)
00254   NS_INTERFACE_MAP_ENTRY(nsIHTMLEditor)
00255   NS_INTERFACE_MAP_ENTRY(nsIHTMLObjectResizer)
00256   NS_INTERFACE_MAP_ENTRY(nsIHTMLAbsPosEditor)
00257   NS_INTERFACE_MAP_ENTRY(nsIHTMLInlineTableEditor)
00258   NS_INTERFACE_MAP_ENTRY(nsITableEditor)
00259   NS_INTERFACE_MAP_ENTRY(nsIEditorStyleSheets)
00260   NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
00261 NS_INTERFACE_MAP_END_INHERITING(nsPlaintextEditor)
00262 
00263 
00264 NS_IMETHODIMP
00265 nsHTMLEditor::Init(nsIDOMDocument *aDoc, nsIPresShell *aPresShell,
00266                    nsIContent *aRoot, nsISelectionController *aSelCon,
00267                    PRUint32 aFlags)
00268 {
00269   NS_PRECONDITION(aDoc && aPresShell, "bad arg");
00270   if (!aDoc || !aPresShell)
00271     return NS_ERROR_NULL_POINTER;
00272 
00273   nsresult result = NS_OK, rulesRes = NS_OK;
00274 
00275   // make a range util object for comparing dom points
00276   if (!sRangeHelper) {
00277     result = CallGetService("@mozilla.org/content/range-utils;1",
00278                             &sRangeHelper);
00279     if (!sRangeHelper) return result;
00280   }
00281    
00282   if (1)
00283   {
00284     // block to scope nsAutoEditInitRulesTrigger
00285     nsAutoEditInitRulesTrigger rulesTrigger(NS_STATIC_CAST(nsPlaintextEditor*,this), rulesRes);
00286 
00287     // Set up a DTD   
00288     mDTD = do_CreateInstance(kCTransitionalDTDCID);
00289     if (!mDTD) result = NS_ERROR_FAILURE;
00290 
00291     // Init the plaintext editor
00292     result = nsPlaintextEditor::Init(aDoc, aPresShell, aRoot, aSelCon, aFlags);
00293     if (NS_FAILED(result)) { return result; }
00294 
00295     // the HTML Editor is CSS-aware only in the case of Composer
00296     mCSSAware = (0 == aFlags);
00297 
00298     // disable Composer-only features
00299     if (aFlags & eEditorMailMask)
00300     {
00301       SetAbsolutePositioningEnabled(PR_FALSE);
00302       SetSnapToGridEnabled(PR_FALSE);
00303     }
00304 
00305     // Init the HTML-CSS utils
00306     if (mHTMLCSSUtils)
00307       delete mHTMLCSSUtils;
00308     result = NS_NewHTMLCSSUtils(&mHTMLCSSUtils);
00309     if (NS_FAILED(result)) { return result; }
00310     mHTMLCSSUtils->Init(this);
00311 
00312     // disable links
00313     nsPresContext *context = aPresShell->GetPresContext();
00314     if (!context) return NS_ERROR_NULL_POINTER;
00315     if (!(mFlags & eEditorPlaintextMask)) {
00316       mLinkHandler = context->GetLinkHandler();
00317 
00318       context->SetLinkHandler(nsnull);
00319     }
00320 
00321     // init the type-in state
00322     mTypeInState = new TypeInState();
00323     if (!mTypeInState) {return NS_ERROR_NULL_POINTER;}
00324     NS_ADDREF(mTypeInState);
00325 
00326     // init the selection listener for image resizing
00327     mSelectionListenerP = new ResizerSelectionListener(this);
00328     if (!mSelectionListenerP) {return NS_ERROR_NULL_POINTER;}
00329 
00330     // ignore any errors from this in case the file is missing
00331     AddOverrideStyleSheet(NS_LITERAL_STRING("resource:/res/EditorOverride.css"));
00332 
00333     nsCOMPtr<nsISelection>selection;
00334     result = GetSelection(getter_AddRefs(selection));
00335     if (NS_FAILED(result)) { return result; }
00336     if (selection) 
00337     {
00338       nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
00339       nsCOMPtr<nsISelectionListener>listener;
00340       listener = do_QueryInterface(mTypeInState);
00341       if (listener) {
00342         selPriv->AddSelectionListener(listener); 
00343       }
00344       listener = do_QueryInterface(mSelectionListenerP);
00345       if (listener) {
00346         selPriv->AddSelectionListener(listener); 
00347       }
00348     }
00349   }
00350 
00351   if (NS_FAILED(rulesRes)) return rulesRes;
00352   return result;
00353 }
00354 
00355 nsresult
00356 nsHTMLEditor::CreateEventListeners()
00357 {
00358   nsresult rv = NS_OK;
00359 
00360   if (!mMouseListenerP)
00361   {
00362     // get a mouse listener
00363     rv = NS_NewHTMLEditorMouseListener(getter_AddRefs(mMouseListenerP), this);
00364 
00365     if (NS_FAILED(rv))
00366     {
00367       return rv;
00368     }
00369   }
00370 
00371   return nsPlaintextEditor::CreateEventListeners();
00372 }
00373 
00374 void
00375 nsHTMLEditor::RemoveEventListeners()
00376 {
00377   if (!mDocWeak)
00378   {
00379     return;
00380   }
00381 
00382   nsCOMPtr<nsIDOMEventReceiver> erP = GetDOMEventReceiver();
00383 
00384   if (erP)
00385   {
00386     // Both mMouseMotionListenerP and mResizeEventListenerP can be
00387     // registerd with other targets than the DOM event receiver that
00388     // we can reach from here. But nonetheless, unregister the event
00389     // listeners with the DOM event reveiver (if it's registerd with
00390     // other targets, it'll get unregisterd once the target goes
00391     // away).
00392 
00393     if (mMouseMotionListenerP)
00394     {
00395       // mMouseMotionListenerP might be registerd either by IID or
00396       // name, unregister by both.
00397       erP->RemoveEventListenerByIID(mMouseMotionListenerP,
00398                                     NS_GET_IID(nsIDOMMouseMotionListener));
00399 
00400       erP->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
00401                                mMouseMotionListenerP, PR_TRUE);
00402     }
00403 
00404     if (mResizeEventListenerP)
00405     {
00406       erP->RemoveEventListener(NS_LITERAL_STRING("resize"),
00407                                mResizeEventListenerP, PR_FALSE);
00408     }
00409   }
00410 
00411   mMouseMotionListenerP = nsnull;
00412   mResizeEventListenerP = nsnull;
00413 
00414   nsPlaintextEditor::RemoveEventListeners();
00415 }
00416 
00417 NS_IMETHODIMP 
00418 nsHTMLEditor::GetFlags(PRUint32 *aFlags)
00419 {
00420   if (!mRules || !aFlags) { return NS_ERROR_NULL_POINTER; }
00421   return mRules->GetFlags(aFlags);
00422 }
00423 
00424 
00425 NS_IMETHODIMP 
00426 nsHTMLEditor::SetFlags(PRUint32 aFlags)
00427 {
00428   if (!mRules) { return NS_ERROR_NULL_POINTER; }
00429   mCSSAware = ((aFlags & (eEditorNoCSSMask | eEditorMailMask)) == 0);
00430 
00431   return mRules->SetFlags(aFlags);
00432 }
00433 
00434 NS_IMETHODIMP
00435 nsHTMLEditor::InitRules()
00436 {
00437   // instantiate the rules for the html editor
00438   nsresult res = NS_NewHTMLEditRules(getter_AddRefs(mRules));
00439   if (NS_FAILED(res)) return res;
00440   if (!mRules) return NS_ERROR_UNEXPECTED;
00441   res = mRules->Init(NS_STATIC_CAST(nsPlaintextEditor*,this), mFlags);
00442   
00443   return res;
00444 }
00445 
00446 NS_IMETHODIMP
00447 nsHTMLEditor::BeginningOfDocument()
00448 {
00449   if (!mDocWeak || !mPresShellWeak) { return NS_ERROR_NOT_INITIALIZED; }
00450 
00451   // get the selection
00452   nsCOMPtr<nsISelection> selection;
00453   nsresult res = GetSelection(getter_AddRefs(selection));
00454   if (NS_FAILED(res))
00455     return res;
00456   if (!selection)
00457     return NS_ERROR_NOT_INITIALIZED;
00458     
00459   // get the root element 
00460   nsIDOMElement *rootElement = GetRoot(); 
00461   if (!rootElement)   return NS_ERROR_NULL_POINTER; 
00462   
00463   // find first editable thingy
00464   PRBool done = PR_FALSE;
00465   nsCOMPtr<nsIDOMNode> curNode(rootElement), selNode;
00466   PRInt32 curOffset = 0, selOffset;
00467   while (!done)
00468   {
00469     nsWSRunObject wsObj(this, curNode, curOffset);
00470     nsCOMPtr<nsIDOMNode> visNode;
00471     PRInt32 visOffset=0;
00472     PRInt16 visType=0;
00473     wsObj.NextVisibleNode(curNode, curOffset, address_of(visNode), &visOffset, &visType);
00474     if ((visType==nsWSRunObject::eNormalWS) || 
00475         (visType==nsWSRunObject::eText))
00476     {
00477       selNode = visNode;
00478       selOffset = visOffset;
00479       done = PR_TRUE;
00480     }
00481     else if ((visType==nsWSRunObject::eBreak)    ||
00482              (visType==nsWSRunObject::eSpecial))
00483     {
00484       res = GetNodeLocation(visNode, address_of(selNode), &selOffset);
00485       if (NS_FAILED(res)) return res; 
00486       done = PR_TRUE;
00487     }
00488     else if (visType==nsWSRunObject::eOtherBlock)
00489     {
00490       // By definition of nsWSRunObject, a block element terminates 
00491       // a whitespace run. That is, although we are calling a method 
00492       // that is named "NextVisibleNode", the node returned
00493       // might not be visible/editable!
00494       // If the given block does not contain any visible/editable items,
00495       // we want to skip it and continue our search.
00496 
00497       if (!IsContainer(visNode))
00498       {
00499         // However, we were given a block that is not a container.
00500         // Since the block can not contain anything that's visible,
00501         // such a block only makes sense if it is visible by itself,
00502         // like a <hr>
00503         // We want to place the caret in front of that block.
00504 
00505         res = GetNodeLocation(visNode, address_of(selNode), &selOffset);
00506         if (NS_FAILED(res)) return res; 
00507         done = PR_TRUE;
00508       }
00509       else
00510       {
00511         PRBool isEmptyBlock;
00512         if (NS_SUCCEEDED(IsEmptyNode(visNode, &isEmptyBlock)) &&
00513             isEmptyBlock)
00514         {
00515           // skip the empty block
00516           res = GetNodeLocation(visNode, address_of(curNode), &curOffset);
00517           if (NS_FAILED(res)) return res; 
00518           ++curOffset;
00519         }
00520         else
00521         {
00522           curNode = visNode;
00523           curOffset = 0;
00524         }
00525         // keep looping
00526       }
00527     }
00528     else
00529     {
00530       // else we found nothing useful
00531       selNode = curNode;
00532       selOffset = curOffset;
00533       done = PR_TRUE;
00534     }
00535   }
00536   return selection->Collapse(selNode, selOffset);
00537 }
00538 
00543 nsresult
00544 nsHTMLEditor::NodeIsBlockStatic(nsIDOMNode *aNode, PRBool *aIsBlock)
00545 {
00546   if (!aNode || !aIsBlock) { return NS_ERROR_NULL_POINTER; }
00547 
00548   *aIsBlock = PR_FALSE;
00549 
00550 #define USE_PARSER_FOR_BLOCKNESS 1
00551 #ifdef USE_PARSER_FOR_BLOCKNESS
00552   nsresult rv;
00553 
00554   nsCOMPtr<nsIDOMElement>element = do_QueryInterface(aNode);
00555   if (!element)
00556   {
00557     // We don't have an element -- probably a text node
00558     return NS_OK;
00559   }
00560 
00561   nsIAtom *tagAtom = GetTag(aNode);
00562   if (!tagAtom) return NS_ERROR_NULL_POINTER;
00563 
00564   // Nodes we know we want to treat as block
00565   // even though the parser says they're not:
00566   if (tagAtom==nsEditProperty::body       ||
00567       tagAtom==nsEditProperty::head       ||
00568       tagAtom==nsEditProperty::tbody      ||
00569       tagAtom==nsEditProperty::thead      ||
00570       tagAtom==nsEditProperty::tfoot      ||
00571       tagAtom==nsEditProperty::tr         ||
00572       tagAtom==nsEditProperty::th         ||
00573       tagAtom==nsEditProperty::td         ||
00574       tagAtom==nsEditProperty::li         ||
00575       tagAtom==nsEditProperty::dt         ||
00576       tagAtom==nsEditProperty::dd         ||
00577       tagAtom==nsEditProperty::pre)
00578   {
00579     *aIsBlock = PR_TRUE;
00580     return NS_OK;
00581   }
00582 
00583   rv = sParserService->IsBlock(sParserService->HTMLAtomTagToId(tagAtom),
00584                                *aIsBlock);
00585 
00586 #ifdef DEBUG
00587   // Check this against what we would have said with the old code:
00588   if (tagAtom==nsEditProperty::p          ||
00589       tagAtom==nsEditProperty::div        ||
00590       tagAtom==nsEditProperty::blockquote ||
00591       tagAtom==nsEditProperty::h1         ||
00592       tagAtom==nsEditProperty::h2         ||
00593       tagAtom==nsEditProperty::h3         ||
00594       tagAtom==nsEditProperty::h4         ||
00595       tagAtom==nsEditProperty::h5         ||
00596       tagAtom==nsEditProperty::h6         ||
00597       tagAtom==nsEditProperty::ul         ||
00598       tagAtom==nsEditProperty::ol         ||
00599       tagAtom==nsEditProperty::dl         ||
00600       tagAtom==nsEditProperty::noscript   ||
00601       tagAtom==nsEditProperty::form       ||
00602       tagAtom==nsEditProperty::hr         ||
00603       tagAtom==nsEditProperty::table      ||
00604       tagAtom==nsEditProperty::fieldset   ||
00605       tagAtom==nsEditProperty::address    ||
00606       tagAtom==nsEditProperty::caption    ||
00607       tagAtom==nsEditProperty::col        ||
00608       tagAtom==nsEditProperty::colgroup   ||
00609       tagAtom==nsEditProperty::li         ||
00610       tagAtom==nsEditProperty::dt         ||
00611       tagAtom==nsEditProperty::dd         ||
00612       tagAtom==nsEditProperty::legend     )
00613   {
00614     if (!(*aIsBlock))
00615     {
00616       nsAutoString assertmsg (NS_LITERAL_STRING("Parser and editor disagree on blockness: "));
00617 
00618       nsAutoString tagName;
00619       rv = element->GetTagName(tagName);
00620       if (NS_FAILED(rv)) return rv;
00621 
00622       assertmsg.Append(tagName);
00623       char* assertstr = ToNewCString(assertmsg);
00624       NS_ASSERTION(*aIsBlock, assertstr);
00625       Recycle(assertstr);
00626     }
00627   }
00628 #endif /* DEBUG */
00629 
00630   return rv;
00631 #else /* USE_PARSER_FOR_BLOCKNESS */
00632   nsresult result = NS_ERROR_FAILURE;
00633   *aIsBlock = PR_FALSE;
00634   nsCOMPtr<nsIDOMElement>element;
00635   element = do_QueryInterface(aNode);
00636   if (element)
00637   {
00638     nsAutoString tagName;
00639     result = element->GetTagName(tagName);
00640     if (NS_SUCCEEDED(result))
00641     {
00642       ToLowerCase(tagName);
00643       nsCOMPtr<nsIAtom> tagAtom = do_GetAtom(tagName);
00644       if (!tagAtom) { return NS_ERROR_NULL_POINTER; }
00645 
00646       if (tagAtom==nsEditProperty::p          ||
00647           tagAtom==nsEditProperty::div        ||
00648           tagAtom==nsEditProperty::blockquote ||
00649           tagAtom==nsEditProperty::h1         ||
00650           tagAtom==nsEditProperty::h2         ||
00651           tagAtom==nsEditProperty::h3         ||
00652           tagAtom==nsEditProperty::h4         ||
00653           tagAtom==nsEditProperty::h5         ||
00654           tagAtom==nsEditProperty::h6         ||
00655           tagAtom==nsEditProperty::ul         ||
00656           tagAtom==nsEditProperty::ol         ||
00657           tagAtom==nsEditProperty::dl         ||
00658           tagAtom==nsEditProperty::pre        ||
00659           tagAtom==nsEditProperty::noscript   ||
00660           tagAtom==nsEditProperty::form       ||
00661           tagAtom==nsEditProperty::hr         ||
00662           tagAtom==nsEditProperty::fieldset   ||
00663           tagAtom==nsEditProperty::address    ||
00664           tagAtom==nsEditProperty::body       ||
00665           tagAtom==nsEditProperty::caption    ||
00666           tagAtom==nsEditProperty::table      ||
00667           tagAtom==nsEditProperty::tbody      ||
00668           tagAtom==nsEditProperty::thead      ||
00669           tagAtom==nsEditProperty::tfoot      ||
00670           tagAtom==nsEditProperty::tr         ||
00671           tagAtom==nsEditProperty::td         ||
00672           tagAtom==nsEditProperty::th         ||
00673           tagAtom==nsEditProperty::col        ||
00674           tagAtom==nsEditProperty::colgroup   ||
00675           tagAtom==nsEditProperty::li         ||
00676           tagAtom==nsEditProperty::dt         ||
00677           tagAtom==nsEditProperty::dd         ||
00678           tagAtom==nsEditProperty::legend     )
00679       {
00680         *aIsBlock = PR_TRUE;
00681       }
00682       result = NS_OK;
00683     }
00684   } else {
00685     // We don't have an element -- probably a text node
00686     nsCOMPtr<nsIDOMCharacterData>nodeAsText = do_QueryInterface(aNode);
00687     if (nodeAsText)
00688     {
00689       *aIsBlock = PR_FALSE;
00690       result = NS_OK;
00691     }
00692   }
00693   return result;
00694 
00695 #endif /* USE_PARSER_FOR_BLOCKNESS */
00696 }
00697 
00698 NS_IMETHODIMP
00699 nsHTMLEditor::NodeIsBlock(nsIDOMNode *aNode, PRBool *aIsBlock)
00700 {
00701   return NodeIsBlockStatic(aNode, aIsBlock);
00702 }
00703 
00704 PRBool
00705 nsHTMLEditor::IsBlockNode(nsIDOMNode *aNode)
00706 {
00707   PRBool isBlock;
00708   NodeIsBlockStatic(aNode, &isBlock);
00709   return isBlock;
00710 }
00711 
00712 // Non-static version for the nsIEditor interface and JavaScript
00713 NS_IMETHODIMP 
00714 nsHTMLEditor::SetDocumentTitle(const nsAString &aTitle)
00715 {
00716   nsRefPtr<EditTxn> txn;
00717   nsresult result = TransactionFactory::GetNewTransaction(SetDocTitleTxn::GetCID(), getter_AddRefs(txn));
00718   if (NS_SUCCEEDED(result))  
00719   {
00720     result =
00721       NS_STATIC_CAST(SetDocTitleTxn*,
00722                      NS_STATIC_CAST(EditTxn*, txn.get()))->Init(this, &aTitle);
00723 
00724     if (NS_SUCCEEDED(result)) 
00725     {
00726       //Don't let Rules System change the selection
00727       nsAutoTxnsConserveSelection dontChangeSelection(this);
00728 
00729       result = nsEditor::DoTransaction(txn);  
00730     }
00731   }
00732   return result;
00733 }
00734 
00735 /* ------------ Block methods moved from nsEditor -------------- */
00737 // GetBlockNodeParent: returns enclosing block level ancestor, if any
00738 //
00739 nsCOMPtr<nsIDOMNode>
00740 nsHTMLEditor::GetBlockNodeParent(nsIDOMNode *aNode)
00741 {
00742   nsCOMPtr<nsIDOMNode> tmp;
00743   nsCOMPtr<nsIDOMNode> p;
00744 
00745   if (!aNode)
00746   {
00747     NS_NOTREACHED("null node passed to GetBlockNodeParent()");
00748     return PR_FALSE;
00749   }
00750 
00751   if (NS_FAILED(aNode->GetParentNode(getter_AddRefs(p))))  // no parent, ran off top of tree
00752     return tmp;
00753 
00754   while (p)
00755   {
00756     PRBool isBlock;
00757     if (NS_FAILED(NodeIsBlockStatic(p, &isBlock)) || isBlock)
00758       break;
00759     if ( NS_FAILED(p->GetParentNode(getter_AddRefs(tmp))) || !tmp) // no parent, ran off top of tree
00760       return p;
00761 
00762     p = tmp;
00763   }
00764   return p;
00765 }
00766 
00767 
00769 // HasSameBlockNodeParent: true if nodes have same block level ancestor
00770 //               
00771 PRBool
00772 nsHTMLEditor::HasSameBlockNodeParent(nsIDOMNode *aNode1, nsIDOMNode *aNode2)
00773 {
00774   if (!aNode1 || !aNode2)
00775   {
00776     NS_NOTREACHED("null node passed to HasSameBlockNodeParent()");
00777     return PR_FALSE;
00778   }
00779   
00780   if (aNode1 == aNode2)
00781     return PR_TRUE;
00782     
00783   nsCOMPtr<nsIDOMNode> p1 = GetBlockNodeParent(aNode1);
00784   nsCOMPtr<nsIDOMNode> p2 = GetBlockNodeParent(aNode2);
00785 
00786   return (p1 == p2);
00787 }
00788 
00789 
00791 // GetBlockSection: return leftmost/rightmost nodes in aChild's block
00792 //               
00793 nsresult
00794 nsHTMLEditor::GetBlockSection(nsIDOMNode *aChild,
00795                               nsIDOMNode **aLeftNode, 
00796                               nsIDOMNode **aRightNode) 
00797 {
00798   nsresult result = NS_OK;
00799   if (!aChild || !aLeftNode || !aRightNode) {return NS_ERROR_NULL_POINTER;}
00800   *aLeftNode = aChild;
00801   *aRightNode = aChild;
00802 
00803   nsCOMPtr<nsIDOMNode>sibling;
00804   result = aChild->GetPreviousSibling(getter_AddRefs(sibling));
00805   while ((NS_SUCCEEDED(result)) && sibling)
00806   {
00807     PRBool isBlock;
00808     NodeIsBlockStatic(sibling, &isBlock);
00809     if (isBlock)
00810     {
00811       nsCOMPtr<nsIDOMCharacterData>nodeAsText = do_QueryInterface(sibling);
00812       if (!nodeAsText) {
00813         break;
00814       }
00815       // XXX: needs some logic to work for other leaf nodes besides text!
00816     }
00817     *aLeftNode = sibling;
00818     result = (*aLeftNode)->GetPreviousSibling(getter_AddRefs(sibling)); 
00819   }
00820   NS_ADDREF((*aLeftNode));
00821   // now do the right side
00822   result = aChild->GetNextSibling(getter_AddRefs(sibling));
00823   while ((NS_SUCCEEDED(result)) && sibling)
00824   {
00825     PRBool isBlock;
00826     NodeIsBlockStatic(sibling, &isBlock);
00827     if (isBlock) 
00828     {
00829       nsCOMPtr<nsIDOMCharacterData>nodeAsText = do_QueryInterface(sibling);
00830       if (!nodeAsText) {
00831         break;
00832       }
00833     }
00834     *aRightNode = sibling;
00835     result = (*aRightNode)->GetNextSibling(getter_AddRefs(sibling)); 
00836   }
00837   NS_ADDREF((*aRightNode));
00838 
00839   return result;
00840 }
00841 
00842 
00844 // GetBlockSectionsForRange: return list of block sections that intersect 
00845 //                           this range
00846 nsresult
00847 nsHTMLEditor::GetBlockSectionsForRange(nsIDOMRange *aRange,
00848                                        nsCOMArray<nsIDOMRange>& aSections) 
00849 {
00850   if (!aRange) {return NS_ERROR_NULL_POINTER;}
00851 
00852   nsresult result;
00853   nsCOMPtr<nsIContentIterator>iter =
00854     do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &result);
00855   if ((NS_SUCCEEDED(result)) && iter)
00856   {
00857     nsCOMPtr<nsIDOMRange> lastRange;
00858     iter->Init(aRange);
00859     while (iter->IsDone())
00860     {
00861       nsCOMPtr<nsIContent> currentContent = iter->GetCurrentNode();
00862 
00863       nsCOMPtr<nsIDOMNode>currentNode = do_QueryInterface(currentContent);
00864       if (currentNode)
00865       {
00866         // <BR> divides block content ranges.  We can achieve this by nulling out lastRange
00867         if (currentContent->Tag() == nsEditProperty::br)
00868         {
00869           lastRange = nsnull;
00870         }
00871         else
00872         {
00873           PRBool isNotInlineOrText;
00874           result = NodeIsBlockStatic(currentNode, &isNotInlineOrText);
00875           if (isNotInlineOrText)
00876           {
00877             PRUint16 nodeType;
00878             currentNode->GetNodeType(&nodeType);
00879             if (nsIDOMNode::TEXT_NODE == nodeType) {
00880               isNotInlineOrText = PR_TRUE;
00881             }
00882           }
00883           if (PR_FALSE==isNotInlineOrText)
00884           {
00885             nsCOMPtr<nsIDOMNode>leftNode;
00886             nsCOMPtr<nsIDOMNode>rightNode;
00887             result = GetBlockSection(currentNode,
00888                                      getter_AddRefs(leftNode),
00889                                      getter_AddRefs(rightNode));
00890             if ((NS_SUCCEEDED(result)) && leftNode && rightNode)
00891             {
00892               // add range to the list if it doesn't overlap with the previous range
00893               PRBool addRange=PR_TRUE;
00894               if (lastRange)
00895               {
00896                 nsCOMPtr<nsIDOMNode> lastStartNode;
00897                 nsCOMPtr<nsIDOMElement> blockParentOfLastStartNode;
00898                 lastRange->GetStartContainer(getter_AddRefs(lastStartNode));
00899                 blockParentOfLastStartNode = do_QueryInterface(GetBlockNodeParent(lastStartNode));
00900                 if (blockParentOfLastStartNode)
00901                 {
00902                   nsCOMPtr<nsIDOMElement> blockParentOfLeftNode;
00903                   blockParentOfLeftNode = do_QueryInterface(GetBlockNodeParent(leftNode));
00904                   if (blockParentOfLeftNode)
00905                   {
00906                     if (blockParentOfLastStartNode==blockParentOfLeftNode) {
00907                       addRange = PR_FALSE;
00908                     }
00909                   }
00910                 }
00911               }
00912               if (PR_TRUE==addRange) 
00913               {
00914                 nsCOMPtr<nsIDOMRange> range =
00915                      do_CreateInstance("@mozilla.org/content/range;1", &result);
00916                 if ((NS_SUCCEEDED(result)) && range)
00917                 { // initialize the range
00918                   range->SetStart(leftNode, 0);
00919                   range->SetEnd(rightNode, 0);
00920                   aSections.AppendObject(range);
00921                   lastRange = do_QueryInterface(range);
00922                 }
00923               }        
00924             }
00925           }
00926         }
00927       }
00928       /* do not check result here, and especially do not return the result code.
00929        * we rely on iter->IsDone to tell us when the iteration is complete
00930        */
00931       iter->Next();
00932     }
00933   }
00934   return result;
00935 }
00936 
00937 
00939 // NextNodeInBlock: gets the next/prev node in the block, if any.  Next node
00940 //                  must be an element or text node, others are ignored
00941 nsCOMPtr<nsIDOMNode>
00942 nsHTMLEditor::NextNodeInBlock(nsIDOMNode *aNode, IterDirection aDir)
00943 {
00944   nsCOMPtr<nsIDOMNode> nullNode;
00945   nsCOMPtr<nsIContent> content;
00946   nsCOMPtr<nsIContent> blockContent;
00947   nsCOMPtr<nsIDOMNode> node;
00948   nsCOMPtr<nsIDOMNode> blockParent;
00949   
00950   if (!aNode)  return nullNode;
00951 
00952   nsresult rv;
00953   nsCOMPtr<nsIContentIterator> iter =
00954        do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv);
00955   if (NS_FAILED(rv))
00956     return nullNode;
00957 
00958   // much gnashing of teeth as we twit back and forth between content and domnode types
00959   content = do_QueryInterface(aNode);
00960   PRBool isBlock;
00961   if (NS_SUCCEEDED(NodeIsBlockStatic(aNode, &isBlock)) && isBlock)
00962   {
00963     blockParent = aNode;
00964   }
00965   else
00966   {
00967     blockParent = GetBlockNodeParent(aNode);
00968   }
00969   if (!blockParent) return nullNode;
00970   blockContent = do_QueryInterface(blockParent);
00971   if (!blockContent) return nullNode;
00972   
00973   if (NS_FAILED(iter->Init(blockContent)))  return nullNode;
00974   if (NS_FAILED(iter->PositionAt(content)))  return nullNode;
00975   
00976   while (!iter->IsDone())
00977   {
00978     // ignore nodes that aren't elements or text, or that are the
00979     // block parent
00980     node = do_QueryInterface(iter->GetCurrentNode());
00981     if (node && IsTextOrElementNode(node) && node != blockParent &&
00982         node != aNode)
00983       return node;
00984 
00985     if (aDir == kIterForward)
00986       iter->Next();
00987     else
00988       iter->Prev();
00989   }
00990   
00991   return nullNode;
00992 }
00993 
00994 static const PRUnichar nbsp = 160;
00995 
00997 // IsNextCharWhitespace: checks the adjacent content in the same block
00998 //                       to see if following selection is whitespace or nbsp
00999 nsresult 
01000 nsHTMLEditor::IsNextCharWhitespace(nsIDOMNode *aParentNode, 
01001                                    PRInt32 aOffset,
01002                                    PRBool *outIsSpace,
01003                                    PRBool *outIsNBSP,
01004                                    nsCOMPtr<nsIDOMNode> *outNode,
01005                                    PRInt32 *outOffset)
01006 {
01007   if (!outIsSpace || !outIsNBSP) return NS_ERROR_NULL_POINTER;
01008   *outIsSpace = PR_FALSE;
01009   *outIsNBSP = PR_FALSE;
01010   if (outNode) *outNode = nsnull;
01011   if (outOffset) *outOffset = -1;
01012   
01013   nsAutoString tempString;
01014   PRUint32 strLength;
01015   nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(aParentNode);
01016   if (textNode)
01017   {
01018     textNode->GetLength(&strLength);
01019     if ((PRUint32)aOffset < strLength)
01020     {
01021       // easy case: next char is in same node
01022       textNode->SubstringData(aOffset,aOffset+1,tempString);
01023       *outIsSpace = nsCRT::IsAsciiSpace(tempString.First());
01024       *outIsNBSP = (tempString.First() == nbsp);
01025       if (outNode) *outNode = do_QueryInterface(aParentNode);
01026       if (outOffset) *outOffset = aOffset+1;  // yes, this is _past_ the character; 
01027       return NS_OK;
01028     }
01029   }
01030   
01031   // harder case: next char in next node.
01032   nsCOMPtr<nsIDOMNode> node = NextNodeInBlock(aParentNode, kIterForward);
01033   nsCOMPtr<nsIDOMNode> tmp;
01034   while (node) 
01035   {
01036     PRBool isBlock (PR_FALSE);
01037     NodeIsBlock(node, &isBlock);
01038     if (isBlock)  // skip over bold, italic, link, ect nodes
01039     {
01040       if (IsTextNode(node) && IsEditable(node))
01041       {
01042         textNode = do_QueryInterface(node);
01043         textNode->GetLength(&strLength);
01044         if (strLength)
01045         {
01046           textNode->SubstringData(0,1,tempString);
01047           *outIsSpace = nsCRT::IsAsciiSpace(tempString.First());
01048           *outIsNBSP = (tempString.First() == nbsp);
01049           if (outNode) *outNode = do_QueryInterface(node);
01050           if (outOffset) *outOffset = 1;  // yes, this is _past_ the character; 
01051           return NS_OK;
01052         }
01053         // else it's an empty text node, or not editable; skip it.
01054       }
01055       else  // node is an image or some other thingy that doesn't count as whitespace
01056       {
01057         break;
01058       }
01059     }
01060     tmp = node;
01061     node = NextNodeInBlock(tmp, kIterForward);
01062   }
01063   
01064   return NS_OK;
01065 }
01066 
01067 
01069 // IsPrevCharWhitespace: checks the adjacent content in the same block
01070 //                       to see if following selection is whitespace
01071 nsresult 
01072 nsHTMLEditor::IsPrevCharWhitespace(nsIDOMNode *aParentNode, 
01073                                    PRInt32 aOffset,
01074                                    PRBool *outIsSpace,
01075                                    PRBool *outIsNBSP,
01076                                    nsCOMPtr<nsIDOMNode> *outNode,
01077                                    PRInt32 *outOffset)
01078 {
01079   if (!outIsSpace || !outIsNBSP) return NS_ERROR_NULL_POINTER;
01080   *outIsSpace = PR_FALSE;
01081   *outIsNBSP = PR_FALSE;
01082   if (outNode) *outNode = nsnull;
01083   if (outOffset) *outOffset = -1;
01084   
01085   nsAutoString tempString;
01086   PRUint32 strLength;
01087   nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(aParentNode);
01088   if (textNode)
01089   {
01090     if (aOffset > 0)
01091     {
01092       // easy case: prev char is in same node
01093       textNode->SubstringData(aOffset-1,aOffset,tempString);
01094       *outIsSpace = nsCRT::IsAsciiSpace(tempString.First());
01095       *outIsNBSP = (tempString.First() == nbsp);
01096       if (outNode) *outNode = do_QueryInterface(aParentNode);
01097       if (outOffset) *outOffset = aOffset-1;  
01098       return NS_OK;
01099     }
01100   }
01101   
01102   // harder case: prev char in next node
01103   nsCOMPtr<nsIDOMNode> node = NextNodeInBlock(aParentNode, kIterBackward);
01104   nsCOMPtr<nsIDOMNode> tmp;
01105   while (node) 
01106   {
01107     PRBool isBlock (PR_FALSE);
01108     NodeIsBlock(node, &isBlock);
01109     if (isBlock)  // skip over bold, italic, link, ect nodes
01110     {
01111       if (IsTextNode(node) && IsEditable(node))
01112       {
01113         textNode = do_QueryInterface(node);
01114         textNode->GetLength(&strLength);
01115         if (strLength)
01116         {
01117           // you could use nsITextContent::IsOnlyWhitespace here
01118           textNode->SubstringData(strLength-1,strLength,tempString);
01119           *outIsSpace = nsCRT::IsAsciiSpace(tempString.First());
01120           *outIsNBSP = (tempString.First() == nbsp);
01121           if (outNode) *outNode = do_QueryInterface(aParentNode);
01122           if (outOffset) *outOffset = strLength-1;  
01123           return NS_OK;
01124         }
01125         // else it's an empty text node, or not editable; skip it.
01126       }
01127       else  // node is an image or some other thingy that doesn't count as whitespace
01128       {
01129         break;
01130       }
01131     }
01132     // otherwise we found a node we want to skip, keep going
01133     tmp = node;
01134     node = NextNodeInBlock(tmp, kIterBackward);
01135   }
01136   
01137   return NS_OK;
01138   
01139 }
01140 
01141 
01142 
01143 /* ------------ End Block methods -------------- */
01144 
01145 
01146 PRBool nsHTMLEditor::IsVisBreak(nsIDOMNode *aNode)
01147 {
01148   if (!aNode) 
01149     return PR_FALSE;
01150   if (!nsTextEditUtils::IsBreak(aNode)) 
01151     return PR_FALSE;
01152   // check if there is a later node in block after br
01153   nsCOMPtr<nsIDOMNode> priorNode, nextNode;
01154   GetPriorHTMLNode(aNode, address_of(priorNode), PR_TRUE); 
01155   GetNextHTMLNode(aNode, address_of(nextNode), PR_TRUE); 
01156   // if we are next to another break, we are visible
01157   if (priorNode && nsTextEditUtils::IsBreak(priorNode))
01158     return PR_TRUE;
01159   if (nextNode && nsTextEditUtils::IsBreak(nextNode))
01160     return PR_TRUE;
01161   
01162   // if we are right before block boundary, then br not visible
01163   if (!nextNode) 
01164     return PR_FALSE;  // this break is trailer in block, it's not visible
01165   if (IsBlockNode(nextNode))
01166     return PR_FALSE; // break is right before a block, it's not visible
01167     
01168   // sigh.  We have to use expensive whitespace calculation code to 
01169   // determine what is going on
01170   nsCOMPtr<nsIDOMNode> selNode, tmp;
01171   PRInt32 selOffset;
01172   GetNodeLocation(aNode, address_of(selNode), &selOffset);
01173   selOffset++; // lets look after the break
01174   nsWSRunObject wsObj(this, selNode, selOffset);
01175   nsCOMPtr<nsIDOMNode> visNode;
01176   PRInt32 visOffset=0;
01177   PRInt16 visType=0;
01178   wsObj.NextVisibleNode(selNode, selOffset, address_of(visNode), &visOffset, &visType);
01179   if (visType & nsWSRunObject::eBlock)
01180     return PR_FALSE;
01181   
01182   return PR_TRUE;
01183 }
01184 
01185 
01186 NS_IMETHODIMP
01187 nsHTMLEditor::GetIsDocumentEditable(PRBool *aIsDocumentEditable)
01188 {
01189   NS_ENSURE_ARG_POINTER(aIsDocumentEditable);
01190 
01191   nsCOMPtr<nsIDOMDocument> doc;
01192   GetDocument(getter_AddRefs(doc));
01193   *aIsDocumentEditable = doc ? IsModifiable() : PR_FALSE;
01194 
01195   return NS_OK;
01196 }
01197 
01198 PRBool nsHTMLEditor::IsModifiable()
01199 {
01200   PRUint32 flags;
01201   if (NS_SUCCEEDED(GetFlags(&flags)))
01202     return ((flags & nsIPlaintextEditor::eEditorReadonlyMask) == 0);
01203   else
01204     return PR_FALSE;
01205 }
01206 
01207 #ifdef XP_MAC
01208 #pragma mark -
01209 #pragma mark  nsIHTMLEditor methods 
01210 #pragma mark -
01211 #endif
01212 
01213 NS_IMETHODIMP
01214 nsHTMLEditor::UpdateBaseURL()
01215 {
01216   nsCOMPtr<nsIDOMDocument> domDoc;
01217   GetDocument(getter_AddRefs(domDoc));
01218   if (!domDoc) return NS_ERROR_FAILURE;
01219 
01220   // Look for an HTML <base> tag
01221   nsCOMPtr<nsIDOMNodeList> nodeList;
01222   nsresult rv = domDoc->GetElementsByTagName(NS_LITERAL_STRING("base"), getter_AddRefs(nodeList));
01223   NS_ENSURE_SUCCESS(rv, rv);
01224 
01225   nsCOMPtr<nsIDOMNode> baseNode;
01226   if (nodeList)
01227   {
01228     PRUint32 count;
01229     nodeList->GetLength(&count);
01230     if (count >= 1)
01231     {
01232       rv = nodeList->Item(0, getter_AddRefs(baseNode));
01233       NS_ENSURE_SUCCESS(rv, rv);
01234     }
01235   }
01236   // If no base tag, then set baseURL to the document's URL
01237   // This is very important, else relative URLs for links and images are wrong
01238   if (!baseNode)
01239   {
01240     nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
01241     if (!doc) return NS_ERROR_FAILURE;
01242 
01243     return doc->SetBaseURI(doc->GetDocumentURI());
01244   }
01245   return NS_OK;
01246 }
01247 
01248 NS_IMETHODIMP nsHTMLEditor::HandleKeyPress(nsIDOMKeyEvent* aKeyEvent)
01249 {
01250   PRUint32 keyCode, character;
01251   PRBool   isShift, ctrlKey, altKey, metaKey;
01252   nsresult res;
01253 
01254   if (!aKeyEvent) return NS_ERROR_NULL_POINTER;
01255 
01256   if (NS_SUCCEEDED(aKeyEvent->GetKeyCode(&keyCode)) && 
01257       NS_SUCCEEDED(aKeyEvent->GetShiftKey(&isShift)) &&
01258       NS_SUCCEEDED(aKeyEvent->GetCtrlKey(&ctrlKey)) &&
01259       NS_SUCCEEDED(aKeyEvent->GetAltKey(&altKey)) &&
01260       NS_SUCCEEDED(aKeyEvent->GetMetaKey(&metaKey)))
01261   {
01262     // this royally blows: because tabs come in from keyDowns instead
01263     // of keyPress, and because GetCharCode refuses to work for keyDown
01264     // i have to play games.
01265     if (keyCode == nsIDOMKeyEvent::DOM_VK_TAB) character = '\t';
01266     else aKeyEvent->GetCharCode(&character);
01267     
01268     if (keyCode == nsIDOMKeyEvent::DOM_VK_TAB)
01269     {
01270       if (!(mFlags & eEditorPlaintextMask)) {
01271         nsCOMPtr<nsISelection>selection;
01272         res = GetSelection(getter_AddRefs(selection));
01273         if (NS_FAILED(res)) return res;
01274         PRInt32 offset;
01275         nsCOMPtr<nsIDOMNode> node, blockParent;
01276         res = GetStartNodeAndOffset(selection, address_of(node), &offset);
01277         if (NS_FAILED(res)) return res;
01278         if (!node) return NS_ERROR_FAILURE;
01279 
01280         PRBool isBlock = PR_FALSE;
01281         NodeIsBlock(node, &isBlock);
01282         if (isBlock) blockParent = node;
01283         else blockParent = GetBlockNodeParent(node);
01284         
01285         if (blockParent)
01286         {
01287           PRBool bHandled = PR_FALSE;
01288           
01289           if (nsHTMLEditUtils::IsTableElement(blockParent))
01290           {
01291             res = TabInTable(isShift, &bHandled);
01292             if (bHandled)
01293               ScrollSelectionIntoView(PR_FALSE);
01294           }
01295           else if (nsHTMLEditUtils::IsListItem(blockParent))
01296           {
01297             nsAutoString indentstr;
01298             if (isShift) indentstr.AssignLiteral("outdent");
01299             else         indentstr.AssignLiteral("indent");
01300             res = Indent(indentstr);
01301             bHandled = PR_TRUE;
01302           }
01303           if (NS_FAILED(res)) return res;
01304           if (bHandled)
01305             return aKeyEvent->PreventDefault(); // consumed
01306         }
01307       }
01308       if (isShift)
01309         return NS_OK; // don't type text for shift tabs
01310     }
01311     else if (keyCode == nsIDOMKeyEvent::DOM_VK_RETURN
01312              || keyCode == nsIDOMKeyEvent::DOM_VK_ENTER)
01313     {
01314       aKeyEvent->PreventDefault();
01315       nsString empty;
01316       if (isShift && !(mFlags&eEditorPlaintextMask))
01317       {
01318         return TypedText(empty, eTypedBR);  // only inserts a br node
01319       }
01320       else 
01321       {
01322         return TypedText(empty, eTypedBreak);  // uses rules to figure out what to insert
01323       }
01324     }
01325     else if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE)
01326     {
01327       aKeyEvent->PreventDefault();
01328       // pass escape keypresses through as empty strings: needed forime support
01329       nsString empty;
01330       return TypedText(empty, eTypedText);
01331     }
01332     
01333     // if we got here we either fell out of the tab case or have a normal character.
01334     // Either way, treat as normal character.
01335     if (character && !altKey && !ctrlKey && !metaKey)
01336     {
01337       aKeyEvent->PreventDefault();
01338       nsAutoString key(character);
01339       return TypedText(key, eTypedText);
01340     }
01341   }
01342   return NS_ERROR_FAILURE;
01343 }
01344 
01345 /* This routine is needed to provide a bottleneck for typing for logging
01346    purposes.  Can't use HandleKeyPress() (above) for that since it takes
01347    a nsIDOMKeyEvent* parameter.  So instead we pass enough info through
01348    to TypedText() to determine what action to take, but without passing
01349    an event.
01350    */
01351 NS_IMETHODIMP nsHTMLEditor::TypedText(const nsAString& aString,
01352                                       PRInt32 aAction)
01353 {
01354   nsAutoPlaceHolderBatch batch(this, gTypingTxnName);
01355 
01356   switch (aAction)
01357   {
01358     case eTypedText:
01359     case eTypedBreak:
01360       {
01361         return nsPlaintextEditor::TypedText(aString, aAction);
01362       }
01363     case eTypedBR:
01364       {
01365         nsCOMPtr<nsIDOMNode> brNode;
01366         return InsertBR(address_of(brNode));  // only inserts a br node
01367       }
01368   } 
01369   return NS_ERROR_FAILURE; 
01370 }
01371 
01372 NS_IMETHODIMP nsHTMLEditor::TabInTable(PRBool inIsShift, PRBool *outHandled)
01373 {
01374   if (!outHandled) return NS_ERROR_NULL_POINTER;
01375   *outHandled = PR_FALSE;
01376 
01377   // Find enclosing table cell from the selection (cell may be the selected element)
01378   nsCOMPtr<nsIDOMElement> cellElement;
01379     // can't use |NS_LITERAL_STRING| here until |GetElementOrParentByTagName| is fixed to accept readables
01380   nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nsnull, getter_AddRefs(cellElement));
01381   if (NS_FAILED(res)) return res;
01382   // Do nothing -- we didn't find a table cell
01383   if (!cellElement) return NS_OK;
01384 
01385   // find enclosing table
01386   nsCOMPtr<nsIDOMNode> tbl = GetEnclosingTable(cellElement);
01387   if (!tbl) return res;
01388 
01389   // advance to next cell
01390   // first create an iterator over the table
01391   nsCOMPtr<nsIContentIterator> iter =
01392       do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res);
01393   if (NS_FAILED(res)) return res;
01394   if (!iter) return NS_ERROR_NULL_POINTER;
01395   nsCOMPtr<nsIContent> cTbl = do_QueryInterface(tbl);
01396   nsCOMPtr<nsIContent> cBlock = do_QueryInterface(cellElement);
01397   res = iter->Init(cTbl);
01398   if (NS_FAILED(res)) return res;
01399   // position iter at block
01400   res = iter->PositionAt(cBlock);
01401   if (NS_FAILED(res)) return res;
01402 
01403   nsCOMPtr<nsIDOMNode> node;
01404   do
01405   {
01406     if (inIsShift)
01407       iter->Prev();
01408     else
01409       iter->Next();
01410 
01411     node = do_QueryInterface(iter->GetCurrentNode());
01412 
01413     if (node && nsHTMLEditUtils::IsTableCell(node) &&
01414         GetEnclosingTable(node) == tbl)
01415     {
01416       res = CollapseSelectionToDeepestNonTableFirstChild(nsnull, node);
01417       if (NS_FAILED(res)) return res;
01418       *outHandled = PR_TRUE;
01419       return NS_OK;
01420     }
01421   } while (!iter->IsDone());
01422   
01423   if (!(*outHandled) && !inIsShift)
01424   {
01425     // if we havent handled it yet then we must have run off the end of
01426     // the table.  Insert a new row.
01427     res = InsertTableRow(1, PR_TRUE);
01428     if (NS_FAILED(res)) return res;
01429     *outHandled = PR_TRUE;
01430     // put selection in right place
01431     // Use table code to get selection and index to new row...
01432     nsCOMPtr<nsISelection>selection;
01433     nsCOMPtr<nsIDOMElement> tblElement;
01434     nsCOMPtr<nsIDOMElement> cell;
01435     PRInt32 row;
01436     res = GetCellContext(getter_AddRefs(selection), 
01437                          getter_AddRefs(tblElement),
01438                          getter_AddRefs(cell), 
01439                          nsnull, nsnull,
01440                          &row, nsnull);
01441     if (NS_FAILED(res)) return res;
01442     // ...so that we can ask for first cell in that row...
01443     res = GetCellAt(tblElement, row, 0, getter_AddRefs(cell));
01444     if (NS_FAILED(res)) return res;
01445     // ...and then set selection there.
01446     // (Note that normally you should use CollapseSelectionToDeepestNonTableFirstChild(),
01447     //  but we know cell is an empty new cell, so this works fine)
01448     node = do_QueryInterface(cell);
01449     if (node) selection->Collapse(node,0);
01450     return NS_OK;
01451   }
01452   
01453   return res;
01454 }
01455 
01456 NS_IMETHODIMP nsHTMLEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode> *aInOutParent, 
01457                                          PRInt32 *aInOutOffset, 
01458                                          nsCOMPtr<nsIDOMNode> *outBRNode, 
01459                                          EDirection aSelect)
01460 {
01461   if (!aInOutParent || !*aInOutParent || !aInOutOffset || !outBRNode) return NS_ERROR_NULL_POINTER;
01462   *outBRNode = nsnull;
01463   nsresult res;
01464   
01465   // we need to insert a br.  unfortunately, we may have to split a text node to do it.
01466   nsCOMPtr<nsIDOMNode> node = *aInOutParent;
01467   PRInt32 theOffset = *aInOutOffset;
01468   nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(node);
01469   NS_NAMED_LITERAL_STRING(brType, "br");
01470   nsCOMPtr<nsIDOMNode> brNode;
01471   if (nodeAsText)  
01472   {
01473     nsCOMPtr<nsIDOMNode> tmp;
01474     PRInt32 offset;
01475     PRUint32 len;
01476     nodeAsText->GetLength(&len);
01477     GetNodeLocation(node, address_of(tmp), &offset);
01478     if (!tmp) return NS_ERROR_FAILURE;
01479     if (!theOffset)
01480     {
01481       // we are already set to go
01482     }
01483     else if (theOffset == (PRInt32)len)
01484     {
01485       // update offset to point AFTER the text node
01486       offset++;
01487     }
01488     else
01489     {
01490       // split the text node
01491       res = SplitNode(node, theOffset, getter_AddRefs(tmp));
01492       if (NS_FAILED(res)) return res;
01493       res = GetNodeLocation(node, address_of(tmp), &offset);
01494       if (NS_FAILED(res)) return res;
01495     }
01496     // create br
01497     res = CreateNode(brType, tmp, offset, getter_AddRefs(brNode));
01498     if (NS_FAILED(res)) return res;
01499     *aInOutParent = tmp;
01500     *aInOutOffset = offset+1;
01501   }
01502   else
01503   {
01504     res = CreateNode(brType, node, theOffset, getter_AddRefs(brNode));
01505     if (NS_FAILED(res)) return res;
01506     (*aInOutOffset)++;
01507   }
01508 
01509   *outBRNode = brNode;
01510   if (*outBRNode && (aSelect != eNone))
01511   {
01512     nsCOMPtr<nsISelection> selection;
01513     nsCOMPtr<nsIDOMNode> parent;
01514     PRInt32 offset;
01515     res = GetSelection(getter_AddRefs(selection));
01516     if (NS_FAILED(res)) return res;
01517     nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
01518     res = GetNodeLocation(*outBRNode, address_of(parent), &offset);
01519     if (NS_FAILED(res)) return res;
01520     if (aSelect == eNext)
01521     {
01522       // position selection after br
01523       selPriv->SetInterlinePosition(PR_TRUE);
01524       res = selection->Collapse(parent, offset+1);
01525     }
01526     else if (aSelect == ePrevious)
01527     {
01528       // position selection before br
01529       selPriv->SetInterlinePosition(PR_TRUE);
01530       res = selection->Collapse(parent, offset);
01531     }
01532   }
01533   return NS_OK;
01534 }
01535 
01536 
01537 NS_IMETHODIMP nsHTMLEditor::CreateBR(nsIDOMNode *aNode, PRInt32 aOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect)
01538 {
01539   nsCOMPtr<nsIDOMNode> parent = aNode;
01540   PRInt32 offset = aOffset;
01541   return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect);
01542 }
01543 
01544 NS_IMETHODIMP nsHTMLEditor::InsertBR(nsCOMPtr<nsIDOMNode> *outBRNode)
01545 {
01546   PRBool bCollapsed;
01547   nsCOMPtr<nsISelection> selection;
01548 
01549   if (!outBRNode) return NS_ERROR_NULL_POINTER;
01550   *outBRNode = nsnull;
01551 
01552   // calling it text insertion to trigger moz br treatment by rules
01553   nsAutoRules beginRulesSniffing(this, kOpInsertText, nsIEditor::eNext);
01554 
01555   nsresult res = GetSelection(getter_AddRefs(selection));
01556   if (NS_FAILED(res)) return res;
01557   nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
01558   res = selection->GetIsCollapsed(&bCollapsed);
01559   if (NS_FAILED(res)) return res;
01560   if (!bCollapsed)
01561   {
01562     res = DeleteSelection(nsIEditor::eNone);
01563     if (NS_FAILED(res)) return res;
01564   }
01565   nsCOMPtr<nsIDOMNode> selNode;
01566   PRInt32 selOffset;
01567   res = GetStartNodeAndOffset(selection, address_of(selNode), &selOffset);
01568   if (NS_FAILED(res)) return res;
01569   
01570   res = CreateBR(selNode, selOffset, outBRNode);
01571   if (NS_FAILED(res)) return res;
01572     
01573   // position selection after br
01574   res = GetNodeLocation(*outBRNode, address_of(selNode), &selOffset);
01575   if (NS_FAILED(res)) return res;
01576   selPriv->SetInterlinePosition(PR_TRUE);
01577   res = selection->Collapse(selNode, selOffset+1);
01578   
01579   return res;
01580 }
01581 
01582 nsresult 
01583 nsHTMLEditor::CollapseSelectionToDeepestNonTableFirstChild(nsISelection *aSelection, nsIDOMNode *aNode)
01584 {
01585   if (!aNode) return NS_ERROR_NULL_POINTER;
01586   nsresult res;
01587 
01588   nsCOMPtr<nsISelection> selection;
01589   if (aSelection)
01590   {
01591     selection = aSelection;
01592   } else {
01593     res = GetSelection(getter_AddRefs(selection));
01594     if (NS_FAILED(res)) return res;
01595     if (!selection) return NS_ERROR_FAILURE;
01596   }
01597   nsCOMPtr<nsIDOMNode> node = aNode;
01598   nsCOMPtr<nsIDOMNode> child;
01599   
01600   do {
01601     node->GetFirstChild(getter_AddRefs(child));
01602     
01603     if (child)
01604     {
01605       // Stop if we find a table
01606       // don't want to go into nested tables
01607       if (nsHTMLEditUtils::IsTable(child)) break;
01608       // hey, it'g gotta be a container too!
01609       if (!IsContainer(child)) break;
01610       node = child;
01611     }
01612   }
01613   while (child);
01614 
01615   selection->Collapse(node,0);
01616   return NS_OK;
01617 }
01618 
01619 
01620 // This is mostly like InsertHTMLWithCharsetAndContext, 
01621 //  but we can't use that because it is selection-based and 
01622 //  the rules code won't let us edit under the <head> node
01623 NS_IMETHODIMP
01624 nsHTMLEditor::ReplaceHeadContentsWithHTML(const nsAString& aSourceToInsert)
01625 {
01626   nsAutoRules beginRulesSniffing(this, kOpIgnore, nsIEditor::eNone); // dont do any post processing, rules get confused
01627   nsCOMPtr<nsISelection> selection;
01628   nsresult res = GetSelection(getter_AddRefs(selection));
01629   if (NS_FAILED(res)) return res;
01630   if (!selection) return NS_ERROR_NULL_POINTER;
01631 
01632   ForceCompositionEnd();
01633 
01634   // Do not use nsAutoRules -- rules code won't let us insert in <head>
01635   // Use the head node as a parent and delete/insert directly
01636   nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak);
01637   if (!doc) return NS_ERROR_NOT_INITIALIZED;
01638 
01639   nsCOMPtr<nsIDOMNodeList>nodeList; 
01640   res = doc->GetElementsByTagName(NS_LITERAL_STRING("head"), getter_AddRefs(nodeList));
01641   if (NS_FAILED(res)) return res;
01642   if (!nodeList) return NS_ERROR_NULL_POINTER;
01643 
01644   PRUint32 count; 
01645   nodeList->GetLength(&count);
01646   if (count < 1) return NS_ERROR_FAILURE;
01647 
01648   nsCOMPtr<nsIDOMNode> headNode;
01649   res = nodeList->Item(0, getter_AddRefs(headNode)); 
01650   if (NS_FAILED(res)) return res;
01651   if (!headNode) return NS_ERROR_NULL_POINTER;
01652 
01653   // First, make sure there are no return chars in the source.
01654   // Bad things happen if you insert returns (instead of dom newlines, \n)
01655   // into an editor document.
01656   nsAutoString inputString (aSourceToInsert);  // hope this does copy-on-write
01657  
01658   // Windows linebreaks: Map CRLF to LF:
01659   inputString.ReplaceSubstring(NS_LITERAL_STRING("\r\n").get(),
01660                                NS_LITERAL_STRING("\n").get());
01661  
01662   // Mac linebreaks: Map any remaining CR to LF:
01663   inputString.ReplaceSubstring(NS_LITERAL_STRING("\r").get(),
01664                                NS_LITERAL_STRING("\n").get());
01665 
01666   nsAutoEditBatch beginBatching(this);
01667 
01668   res = GetSelection(getter_AddRefs(selection));
01669   if (NS_FAILED(res)) return res;
01670   if (!selection) return NS_ERROR_NULL_POINTER;
01671 
01672   // Get the first range in the selection, for context:
01673   nsCOMPtr<nsIDOMRange> range;
01674   res = selection->GetRangeAt(0, getter_AddRefs(range));
01675   if (NS_FAILED(res))
01676     return res;
01677 
01678   nsCOMPtr<nsIDOMNSRange> nsrange (do_QueryInterface(range));
01679   if (!nsrange)
01680     return NS_ERROR_NO_INTERFACE;
01681   nsCOMPtr<nsIDOMDocumentFragment> docfrag;
01682   res = nsrange->CreateContextualFragment(inputString,
01683                                           getter_AddRefs(docfrag));
01684 
01685   //XXXX BUG 50965: This is not returning the text between <title> ... </title>
01686   // Special code is needed in JS to handle title anyway, so it really doesn't matter!
01687 
01688   if (NS_FAILED(res))
01689   {
01690 #ifdef DEBUG
01691     printf("Couldn't create contextual fragment: error was %d\n", res);
01692 #endif
01693     return res;
01694   }
01695   if (!docfrag) return NS_ERROR_NULL_POINTER;
01696 
01697   nsCOMPtr<nsIDOMNode> child;
01698 
01699   // First delete all children in head
01700   do {
01701     res = headNode->GetFirstChild(getter_AddRefs(child));
01702     if (NS_FAILED(res)) return res;
01703     if (child)
01704     {
01705       res = DeleteNode(child);
01706       if (NS_FAILED(res)) return res;
01707     }
01708   } while (child);
01709 
01710   // Now insert the new nodes
01711   PRInt32 offsetOfNewNode = 0;
01712   nsCOMPtr<nsIDOMNode> fragmentAsNode (do_QueryInterface(docfrag));
01713 
01714   // Loop over the contents of the fragment and move into the document
01715   do {
01716     res = fragmentAsNode->GetFirstChild(getter_AddRefs(child));
01717     if (NS_FAILED(res)) return res;
01718     if (child)
01719     {
01720       res = InsertNode(child, headNode, offsetOfNewNode++);
01721       if (NS_FAILED(res)) return res;
01722     }
01723   } while (child);
01724 
01725   return res;
01726 }
01727 
01728 NS_IMETHODIMP
01729 nsHTMLEditor::RebuildDocumentFromSource(const nsAString& aSourceString)
01730 {
01731   ForceCompositionEnd();
01732 
01733   nsCOMPtr<nsISelection>selection;
01734   nsresult res = GetSelection(getter_AddRefs(selection));
01735   if (NS_FAILED(res)) return res;
01736 
01737   nsIDOMElement *bodyElement = GetRoot();
01738   if (NS_FAILED(res)) return res;
01739   if (!bodyElement) return NS_ERROR_NULL_POINTER;
01740 
01741   // Find where the <body> tag starts.
01742   nsReadingIterator<PRUnichar> beginbody;
01743   nsReadingIterator<PRUnichar> endbody;
01744   aSourceString.BeginReading(beginbody);
01745   aSourceString.EndReading(endbody);
01746   PRBool foundbody = CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<body"),
01747                                                    beginbody, endbody);
01748 
01749   nsReadingIterator<PRUnichar> beginhead;
01750   nsReadingIterator<PRUnichar> endhead;
01751   aSourceString.BeginReading(beginhead);
01752   aSourceString.EndReading(endhead);
01753   PRBool foundhead = CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<head"),
01754                                                    beginhead, endhead);
01755 
01756   nsReadingIterator<PRUnichar> beginclosehead;
01757   nsReadingIterator<PRUnichar> endclosehead;
01758   aSourceString.BeginReading(beginclosehead);
01759   aSourceString.EndReading(endclosehead);
01760 
01761   // Find the index after "<head>"
01762   PRBool foundclosehead = CaseInsensitiveFindInReadable(
01763            NS_LITERAL_STRING("</head>"), beginclosehead, endclosehead);
01764   
01765   // Time to change the document
01766   nsAutoEditBatch beginBatching(this);
01767 
01768   nsReadingIterator<PRUnichar> endtotal;
01769   aSourceString.EndReading(endtotal);
01770 
01771   if (foundhead) {
01772     if (foundclosehead)
01773       res = ReplaceHeadContentsWithHTML(Substring(beginhead, beginclosehead));
01774     else if (foundbody)
01775       res = ReplaceHeadContentsWithHTML(Substring(beginhead, beginbody));
01776     else
01777       // XXX Without recourse to some parser/content sink/docshell hackery
01778       // we don't really know where the head ends and the body begins
01779       // so we assume that there is no body
01780       res = ReplaceHeadContentsWithHTML(Substring(beginhead, endtotal));
01781   } else {
01782     nsReadingIterator<PRUnichar> begintotal;
01783     aSourceString.BeginReading(begintotal);
01784     NS_NAMED_LITERAL_STRING(head, "<head>");
01785     if (foundclosehead)
01786       res = ReplaceHeadContentsWithHTML(head + Substring(begintotal, beginclosehead));
01787     else if (foundbody)
01788       res = ReplaceHeadContentsWithHTML(head + Substring(begintotal, beginbody));
01789     else
01790       // XXX Without recourse to some parser/content sink/docshell hackery
01791       // we don't really know where the head ends and the body begins
01792       // so we assume that there is no head
01793       res = ReplaceHeadContentsWithHTML(head);
01794   }
01795   if (NS_FAILED(res)) return res;
01796 
01797   res = SelectAll();
01798   if (NS_FAILED(res)) return res;
01799 
01800   if (!foundbody) {
01801     NS_NAMED_LITERAL_STRING(body, "<body>");
01802     // XXX Without recourse to some parser/content sink/docshell hackery
01803     // we don't really know where the head ends and the body begins
01804     if (foundclosehead) // assume body starts after the head ends
01805       res = LoadHTML(body + Substring(endclosehead, endtotal));
01806     else if (foundhead) // assume there is no body
01807       res = LoadHTML(body);
01808     else // assume there is no head, the entire source is body
01809       res = LoadHTML(body + aSourceString);
01810     if (NS_FAILED(res)) return res;
01811 
01812     nsCOMPtr<nsIDOMElement> divElement;
01813     res = CreateElementWithDefaults(NS_LITERAL_STRING("div"), getter_AddRefs(divElement));
01814     if (NS_FAILED(res)) return res;
01815 
01816     res = CloneAttributes(bodyElement, divElement);
01817     if (NS_FAILED(res)) return res;
01818 
01819     return BeginningOfDocument();
01820   }
01821 
01822   res = LoadHTML(Substring(beginbody, endtotal));
01823   if (NS_FAILED(res)) return res;
01824 
01825   // Now we must copy attributes user might have edited on the <body> tag
01826   //  because InsertHTML (actually, CreateContextualFragment()) 
01827   //  will never return a body node in the DOM fragment
01828   
01829   // We already know where "<body" begins
01830   nsReadingIterator<PRUnichar> beginclosebody = beginbody;
01831   nsReadingIterator<PRUnichar> endclosebody;
01832   aSourceString.EndReading(endclosebody);
01833   if (!FindInReadable(NS_LITERAL_STRING(">"),beginclosebody,endclosebody))
01834     return NS_ERROR_FAILURE;
01835 
01836   // Truncate at the end of the body tag
01837   // Kludge of the year: fool the parser by replacing "body" with "div" so we get a node
01838   nsAutoString bodyTag;
01839   bodyTag.AssignLiteral("<div ");
01840   bodyTag.Append(Substring(endbody, endclosebody));
01841 
01842   nsCOMPtr<nsIDOMRange> range;
01843   res = selection->GetRangeAt(0, getter_AddRefs(range));
01844   if (NS_FAILED(res)) return res;
01845 
01846   nsCOMPtr<nsIDOMNSRange> nsrange (do_QueryInterface(range));
01847   if (!nsrange) return NS_ERROR_NO_INTERFACE;
01848 
01849   nsCOMPtr<nsIDOMDocumentFragment> docfrag;
01850   res = nsrange->CreateContextualFragment(bodyTag, getter_AddRefs(docfrag));
01851   if (NS_FAILED(res)) return res;
01852 
01853   nsCOMPtr<nsIDOMNode> fragmentAsNode (do_QueryInterface(docfrag));
01854   if (!fragmentAsNode) return NS_ERROR_NULL_POINTER;
01855   
01856   nsCOMPtr<nsIDOMNode> child;
01857   res = fragmentAsNode->GetFirstChild(getter_AddRefs(child));
01858   if (NS_FAILED(res)) return res;
01859   if (!child) return NS_ERROR_NULL_POINTER;
01860   
01861   // Copy all attributes from the div child to current body element
01862   res = CloneAttributes(bodyElement, child);
01863   if (NS_FAILED(res)) return res;
01864   
01865   // place selection at first editable content
01866   return BeginningOfDocument();
01867 }
01868 
01869 void
01870 nsHTMLEditor::NormalizeEOLInsertPosition(nsIDOMNode *firstNodeToInsert,
01871                                      nsCOMPtr<nsIDOMNode> *insertParentNode,
01872                                      PRInt32 *insertOffset)
01873 {
01874   /*
01875     This function will either correct the position passed in,
01876     or leave the position unchanged.
01877 
01878     When the (first) item to insert is a block level element, 
01879     and our insertion position is after the last visible item in a line, 
01880     i.e. the insertion position is just before a visible line break <br>, 
01881     we want to skip to the position just after the line break (see bug 68767)
01882 
01883     However, our logic to detect whether we should skip or not
01884     needs to be more clever.
01885     We must not skip when the caret appears to be positioned at the beginning
01886     of a block, in that case skipping the <br> would not insert the <br>
01887     at the caret position, but after the current empty line.
01888      
01889     So we have several cases to test:
01890      
01891     1) We only ever want to skip, if the next visible thing after the current position is a break
01892      
01893     2) We do not want to skip if there is no previous visible thing at all
01894        That is detected if the call to PriorVisibleNode gives us an offset of zero.
01895        Because PriorVisibleNode always positions after the prior node, we would
01896        see an offset > 0, if there were a prior node.
01897      
01898     3) We do not want to skip, if both the next and the previous visible things are breaks.
01899     
01900     4) We do not want to skip if the previous visible thing is in a different block
01901        than the insertion position.
01902   */
01903 
01904   if (!IsBlockNode(firstNodeToInsert))
01905     return;
01906 
01907   nsWSRunObject wsObj(this, *insertParentNode, *insertOffset);
01908   nsCOMPtr<nsIDOMNode> nextVisNode;
01909   nsCOMPtr<nsIDOMNode> prevVisNode;
01910   PRInt32 nextVisOffset=0;
01911   PRInt16 nextVisType=0;
01912   PRInt32 prevVisOffset=0;
01913   PRInt16 prevVisType=0;
01914 
01915   wsObj.NextVisibleNode(*insertParentNode, *insertOffset, address_of(nextVisNode), &nextVisOffset, &nextVisType);
01916   if (!nextVisNode)
01917     return;
01918 
01919   if (! (nextVisType & nsWSRunObject::eBreak))
01920     return;
01921 
01922   wsObj.PriorVisibleNode(*insertParentNode, *insertOffset, address_of(prevVisNode), &prevVisOffset, &prevVisType);
01923   if (!prevVisNode)
01924     return;
01925 
01926   if (prevVisType & nsWSRunObject::eBreak)
01927     return;
01928 
01929   if (prevVisType & nsWSRunObject::eThisBlock)
01930     return;
01931 
01932   nsCOMPtr<nsIDOMNode> brNode;
01933   PRInt32 brOffset=0;
01934 
01935   GetNodeLocation(nextVisNode, address_of(brNode), &brOffset);
01936 
01937   *insertParentNode = brNode;
01938   *insertOffset = brOffset + 1;
01939 }
01940 
01941 NS_IMETHODIMP
01942 nsHTMLEditor::InsertElementAtSelection(nsIDOMElement* aElement, PRBool aDeleteSelection)
01943 {
01944   nsresult res = NS_ERROR_NOT_INITIALIZED;
01945   
01946   if (!aElement)
01947     return NS_ERROR_NULL_POINTER;
01948   
01949   nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
01950   
01951   ForceCompositionEnd();
01952   nsAutoEditBatch beginBatching(this);
01953   nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
01954 
01955   nsCOMPtr<nsISelection>selection;
01956   res = GetSelection(getter_AddRefs(selection));
01957   if (NS_FAILED(res) || !selection)
01958     return NS_ERROR_FAILURE;
01959 
01960   // hand off to the rules system, see if it has anything to say about this
01961   PRBool cancel, handled;
01962   nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
01963   ruleInfo.insertElement = aElement;
01964   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
01965   if (cancel || (NS_FAILED(res))) return res;
01966 
01967   if (!handled)
01968   {
01969     if (aDeleteSelection)
01970     {
01971       nsCOMPtr<nsIDOMNode> tempNode;
01972       PRInt32 tempOffset;
01973       nsresult result = DeleteSelectionAndPrepareToCreateNode(tempNode,tempOffset);
01974       if (NS_FAILED(result))
01975         return result;
01976     }
01977 
01978     // If deleting, selection will be collapsed.
01979     // so if not, we collapse it
01980     if (!aDeleteSelection)
01981     {
01982       // Named Anchor is a special case,
01983       // We collapse to insert element BEFORE the selection
01984       // For all other tags, we insert AFTER the selection
01985       if (nsHTMLEditUtils::IsNamedAnchor(node))
01986       {
01987         selection->CollapseToStart();
01988       } else {
01989         selection->CollapseToEnd();
01990       }
01991     }
01992 
01993     nsCOMPtr<nsIDOMNode> parentSelectedNode;
01994     PRInt32 offsetForInsert;
01995     res = selection->GetAnchorNode(getter_AddRefs(parentSelectedNode));
01996     // XXX: ERROR_HANDLING bad XPCOM usage
01997     if (NS_SUCCEEDED(res) && NS_SUCCEEDED(selection->GetAnchorOffset(&offsetForInsert)) && parentSelectedNode)
01998     {
01999 #ifdef DEBUG_cmanske
02000       {
02001       nsAutoString name;
02002       parentSelectedNode->GetNodeName(name);
02003       printf("InsertElement: Anchor node of selection: ");
02004       wprintf(name.get());
02005       printf(" Offset: %d\n", offsetForInsert);
02006       }
02007 #endif
02008 
02009       // Adjust position based on the node we are going to insert.
02010       NormalizeEOLInsertPosition(node, address_of(parentSelectedNode), &offsetForInsert);
02011 
02012       res = InsertNodeAtPoint(node, address_of(parentSelectedNode), &offsetForInsert, PR_FALSE);
02013       NS_ENSURE_SUCCESS(res, res);
02014       // Set caret after element, but check for special case 
02015       //  of inserting table-related elements: set in first cell instead
02016       if (!SetCaretInTableCell(aElement))
02017       {
02018         res = SetCaretAfterElement(aElement);
02019         if (NS_FAILED(res)) return res;
02020       }
02021       // check for inserting a whole table at the end of a block. If so insert a br after it.
02022       if (nsHTMLEditUtils::IsTable(node))
02023       {
02024         PRBool isLast;
02025         res = IsLastEditableChild(node, &isLast);
02026         if (NS_FAILED(res)) return res;
02027         if (isLast)
02028         {
02029           nsCOMPtr<nsIDOMNode> brNode;
02030           res = CreateBR(parentSelectedNode, offsetForInsert+1, address_of(brNode));
02031           if (NS_FAILED(res)) return res;
02032           selection->Collapse(parentSelectedNode, offsetForInsert+1);
02033         }
02034       }
02035     }
02036   }
02037   res = mRules->DidDoAction(selection, &ruleInfo, res);
02038   return res;
02039 }
02040 
02041 
02042 /* 
02043   InsertNodeAtPoint: attempts to insert aNode into the document, at a point specified by 
02044       {*ioParent,*ioOffset}.  Checks with strict dtd to see if containment is allowed.  If not
02045       allowed, will attempt to find a parent in the parent heirarchy of *ioParent that will
02046       accept aNode as a child.  If such a parent is found, will split the document tree from
02047       {*ioParent,*ioOffset} up to parent, and then insert aNode.  ioParent & ioOffset are then
02048       adjusted to point to the actual location that aNode was inserted at.  aNoEmptyNodes
02049       specifies if the splitting process is allowed to reslt in empty nodes.
02050               nsIDOMNode            *aNode           node to insert
02051               nsCOMPtr<nsIDOMNode>  *ioParent        insertion parent
02052               PRInt32               *ioOffset        insertion offset
02053               PRBool                aNoEmptyNodes    splitting can result in empty nodes?
02054 */
02055 nsresult
02056 nsHTMLEditor::InsertNodeAtPoint(nsIDOMNode *aNode, 
02057                                 nsCOMPtr<nsIDOMNode> *ioParent, 
02058                                 PRInt32 *ioOffset, 
02059                                 PRBool aNoEmptyNodes)
02060 {
02061   NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
02062   NS_ENSURE_TRUE(ioParent, NS_ERROR_NULL_POINTER);
02063   NS_ENSURE_TRUE(*ioParent, NS_ERROR_NULL_POINTER);
02064   NS_ENSURE_TRUE(ioOffset, NS_ERROR_NULL_POINTER);
02065   
02066   nsresult res = NS_OK;
02067   nsAutoString tagName;
02068   aNode->GetNodeName(tagName);
02069   ToLowerCase(tagName);
02070   nsCOMPtr<nsIDOMNode> parent = *ioParent;
02071   nsCOMPtr<nsIDOMNode> topChild = *ioParent;
02072   nsCOMPtr<nsIDOMNode> tmp;
02073   PRInt32 offsetOfInsert = *ioOffset;
02074    
02075   // Search up the parent chain to find a suitable container      
02076   while (!CanContainTag(parent, tagName))
02077   {
02078     // If the current parent is a root (body or table element)
02079     // then go no further - we can't insert
02080     if (nsTextEditUtils::IsBody(parent) || nsHTMLEditUtils::IsTableElement(parent))
02081       return NS_ERROR_FAILURE;
02082     // Get the next parent
02083     parent->GetParentNode(getter_AddRefs(tmp));
02084     NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
02085     topChild = parent;
02086     parent = tmp;
02087   }
02088   if (parent != topChild)
02089   {
02090     // we need to split some levels above the original selection parent
02091     res = SplitNodeDeep(topChild, *ioParent, *ioOffset, &offsetOfInsert, aNoEmptyNodes);
02092     if (NS_FAILED(res))
02093       return res;
02094     *ioParent = parent;
02095     *ioOffset = offsetOfInsert;
02096   }
02097   // Now we can insert the new node
02098   res = InsertNode(aNode, parent, offsetOfInsert);
02099   return res;
02100 }
02101 
02102 NS_IMETHODIMP
02103 nsHTMLEditor::SelectElement(nsIDOMElement* aElement)
02104 {
02105   nsresult res = NS_ERROR_NULL_POINTER;
02106 
02107   // Must be sure that element is contained in the document body
02108   if (IsElementInBody(aElement))
02109   {
02110     nsCOMPtr<nsISelection> selection;
02111     res = GetSelection(getter_AddRefs(selection));
02112     if (NS_FAILED(res)) return res;
02113     if (!selection) return NS_ERROR_NULL_POINTER;
02114     nsCOMPtr<nsIDOMNode>parent;
02115     res = aElement->GetParentNode(getter_AddRefs(parent));
02116     if (NS_SUCCEEDED(res) && parent)
02117     {
02118       PRInt32 offsetInParent;
02119       res = GetChildOffset(aElement, parent, offsetInParent);
02120 
02121       if (NS_SUCCEEDED(res))
02122       {
02123         // Collapse selection to just before desired element,
02124         res = selection->Collapse(parent, offsetInParent);
02125         if (NS_SUCCEEDED(res)) {
02126           //  then extend it to just after
02127           res = selection->Extend(parent, offsetInParent+1);
02128         }
02129       }
02130     }
02131   }
02132   return res;
02133 }
02134 
02135 NS_IMETHODIMP
02136 nsHTMLEditor::SetCaretAfterElement(nsIDOMElement* aElement)
02137 {
02138   nsresult res = NS_ERROR_NULL_POINTER;
02139 
02140   // Be sure the element is contained in the document body
02141   if (aElement && IsElementInBody(aElement))
02142   {
02143     nsCOMPtr<nsISelection> selection;
02144     res = GetSelection(getter_AddRefs(selection));
02145     if (NS_FAILED(res)) return res;
02146     if (!selection) return NS_ERROR_NULL_POINTER;
02147     nsCOMPtr<nsIDOMNode>parent;
02148     res = aElement->GetParentNode(getter_AddRefs(parent));
02149     if (NS_FAILED(res)) return res;
02150     if (!parent) return NS_ERROR_NULL_POINTER;
02151     PRInt32 offsetInParent;
02152     res = GetChildOffset(aElement, parent, offsetInParent);
02153     if (NS_SUCCEEDED(res))
02154     {
02155       // Collapse selection to just after desired element,
02156       res = selection->Collapse(parent, offsetInParent+1);
02157 #if 0 //def DEBUG_cmanske
02158       {
02159       nsAutoString name;
02160       parent->GetNodeName(name);
02161       printf("SetCaretAfterElement: Parent node: ");
02162       wprintf(name.get());
02163       printf(" Offset: %d\n\nHTML:\n", offsetInParent+1);
02164       nsAutoString Format("text/html");
02165       nsAutoString ContentsAs;
02166       OutputToString(Format, 2, ContentsAs);
02167       wprintf(ContentsAs.get());
02168       }
02169 #endif
02170     }
02171   }
02172   return res;
02173 }
02174 
02175 NS_IMETHODIMP 
02176 nsHTMLEditor::SetParagraphFormat(const nsAString& aParagraphFormat)
02177 {
02178   nsAutoString tag; tag.Assign(aParagraphFormat);
02179   ToLowerCase(tag);
02180   if (tag.EqualsLiteral("dd") || tag.EqualsLiteral("dt"))
02181     return MakeDefinitionItem(tag);
02182   else
02183     return InsertBasicBlock(tag);
02184 }
02185 
02186 // XXX: ERROR_HANDLING -- this method needs a little work to ensure all error codes are 
02187 //                        checked properly, all null pointers are checked, and no memory leaks occur
02188 NS_IMETHODIMP 
02189 nsHTMLEditor::GetParentBlockTags(nsStringArray *aTagList, PRBool aGetLists)
02190 {
02191   if (!aTagList) { return NS_ERROR_NULL_POINTER; }
02192 
02193   nsresult res;
02194   nsCOMPtr<nsISelection>selection;
02195   res = GetSelection(getter_AddRefs(selection));
02196   if (NS_FAILED(res)) return res;
02197   if (!selection) return NS_ERROR_NULL_POINTER;
02198   nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
02199 
02200   // Find out if the selection is collapsed:
02201   PRBool isCollapsed;
02202   res = selection->GetIsCollapsed(&isCollapsed);
02203   if (NS_FAILED(res)) return res;
02204   if (isCollapsed)
02205   {
02206     nsCOMPtr<nsIDOMNode> node, blockParent;
02207     PRInt32 offset;
02208   
02209     res = GetStartNodeAndOffset(selection, address_of(node), &offset);
02210     if (!node) res = NS_ERROR_FAILURE;
02211     if (NS_FAILED(res)) return res;
02212   
02213     nsCOMPtr<nsIDOMElement> blockParentElem;
02214     if (aGetLists)
02215     {
02216       // Get the "ol", "ul", or "dl" parent element
02217       res = GetElementOrParentByTagName(NS_LITERAL_STRING("list"), node, getter_AddRefs(blockParentElem));
02218       if (NS_FAILED(res)) return res;
02219     } 
02220     else 
02221     {
02222       PRBool isBlock (PR_FALSE);
02223       NodeIsBlock(node, &isBlock);
02224       if (isBlock) blockParent = node;
02225       else blockParent = GetBlockNodeParent(node);
02226       blockParentElem = do_QueryInterface(blockParent);
02227     }
02228     if (blockParentElem)
02229     {
02230       nsAutoString blockParentTag;
02231       blockParentElem->GetTagName(blockParentTag);
02232       aTagList->AppendString(blockParentTag);
02233     }
02234     
02235     return res;
02236   }
02237 
02238   // else non-collapsed selection
02239   nsCOMPtr<nsIEnumerator> enumerator;
02240   res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
02241   if (NS_FAILED(res)) return res;
02242   if (!enumerator) return NS_ERROR_NULL_POINTER;
02243 
02244   enumerator->First(); 
02245   nsCOMPtr<nsISupports> currentItem;
02246   res = enumerator->CurrentItem(getter_AddRefs(currentItem));
02247   if (NS_FAILED(res)) return res;
02248   //XXX: should be while loop?
02249   if (currentItem)
02250   {
02251     nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
02252     // scan the range for all the independent block content blockSections
02253     // and get the block parent of each
02254     nsCOMArray<nsIDOMRange> blockSections;
02255     res = GetBlockSectionsForRange(range, blockSections);
02256     if (NS_SUCCEEDED(res))
02257     {
02258       nsCOMPtr<nsIDOMRange> subRange = blockSections[0];
02259       while (subRange)
02260       {
02261         nsCOMPtr<nsIDOMNode>startParent;
02262         res = subRange->GetStartContainer(getter_AddRefs(startParent));
02263         if (NS_SUCCEEDED(res) && startParent) 
02264         {
02265           nsCOMPtr<nsIDOMElement> blockParent;
02266           if (aGetLists)
02267           {
02268             // Get the "ol", "ul", or "dl" parent element
02269             res = GetElementOrParentByTagName(NS_LITERAL_STRING("list"), startParent, getter_AddRefs(blockParent));
02270           } 
02271           else 
02272           {
02273             blockParent = do_QueryInterface(GetBlockNodeParent(startParent));
02274           }
02275           if (NS_SUCCEEDED(res) && blockParent)
02276           {
02277             nsAutoString blockParentTag;
02278             blockParent->GetTagName(blockParentTag);
02279             PRBool isRoot;
02280             IsRootTag(blockParentTag, isRoot);
02281             if ((!isRoot) && (-1==aTagList->IndexOf(blockParentTag))) {
02282               aTagList->AppendString(blockParentTag);
02283             }
02284           }
02285         }
02286         if (NS_FAILED(res))
02287           return res;
02288         blockSections.RemoveObject(0);
02289         if (blockSections.Count() == 0)
02290           break;
02291         subRange = blockSections[0];
02292       }
02293     }
02294   }
02295   return res;
02296 }
02297 
02298 
02299 NS_IMETHODIMP 
02300 nsHTMLEditor::GetParagraphState(PRBool *aMixed, nsAString &outFormat)
02301 {
02302   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
02303   if (!aMixed) return NS_ERROR_NULL_POINTER;
02304   nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
02305   if (!htmlRules) return NS_ERROR_FAILURE;
02306   
02307   return htmlRules->GetParagraphState(aMixed, outFormat);
02308 }
02309 
02310 NS_IMETHODIMP
02311 nsHTMLEditor::GetBackgroundColorState(PRBool *aMixed, nsAString &aOutColor)
02312 {
02313   nsresult res;
02314   PRBool useCSS;
02315   GetIsCSSEnabled(&useCSS);
02316   if (useCSS) {
02317     // if we are in CSS mode, we have to check if the containing block defines
02318     // a background color
02319     res = GetCSSBackgroundColorState(aMixed, aOutColor, PR_TRUE);
02320   }
02321   else {
02322     // in HTML mode, we look only at page's background
02323     res = GetHTMLBackgroundColorState(aMixed, aOutColor);
02324   }
02325   return res;
02326 }
02327 
02328 NS_IMETHODIMP
02329 nsHTMLEditor::GetHighlightColorState(PRBool *aMixed, nsAString &aOutColor)
02330 {
02331   nsresult res = NS_OK;
02332   PRBool useCSS;
02333   GetIsCSSEnabled(&useCSS);
02334   *aMixed = PR_FALSE;
02335   aOutColor.AssignLiteral("transparent");
02336   if (useCSS) {
02337     // in CSS mode, text background can be added by the Text Highlight button
02338     // we need to query the background of the selection without looking for
02339     // the block container of the ranges in the selection
02340     res = GetCSSBackgroundColorState(aMixed, aOutColor, PR_FALSE);
02341   }
02342   return res;
02343 }
02344 
02345 NS_IMETHODIMP 
02346 nsHTMLEditor::GetHighlightColor(PRBool *aMixed, PRUnichar **_retval)
02347 {
02348   if (!aMixed || !_retval) return NS_ERROR_NULL_POINTER;
02349   nsAutoString outColorString(NS_LITERAL_STRING("transparent"));
02350   *aMixed = PR_FALSE;
02351 
02352   nsresult  err = NS_NOINTERFACE;
02353   err = GetHighlightColorState(aMixed, outColorString);
02354   *_retval = ToNewUnicode(outColorString);
02355   return err;
02356 }
02357 
02358 
02359 nsresult
02360 nsHTMLEditor::GetCSSBackgroundColorState(PRBool *aMixed, nsAString &aOutColor, PRBool aBlockLevel)
02361 {
02362   if (!aMixed) return NS_ERROR_NULL_POINTER;
02363   *aMixed = PR_FALSE;
02364   // the default background color is transparent
02365   aOutColor.AssignLiteral("transparent");
02366   
02367   // get selection
02368   nsCOMPtr<nsISelection>selection;
02369   nsresult res = GetSelection(getter_AddRefs(selection));
02370   if (NS_FAILED(res)) return res;
02371 
02372   // get selection location
02373   nsCOMPtr<nsIDOMNode> parent;
02374   PRInt32 offset;
02375   res = GetStartNodeAndOffset(selection, address_of(parent), &offset);
02376   if (NS_FAILED(res)) return res;
02377   
02378   // is the selection collapsed?
02379   PRBool bCollapsed;
02380   res = selection->GetIsCollapsed(&bCollapsed);
02381   if (NS_FAILED(res)) return res;
02382   nsCOMPtr<nsIDOMNode> nodeToExamine;
02383   if (bCollapsed || IsTextNode(parent))
02384   {
02385     // we want to look at the parent and ancestors
02386     nodeToExamine = parent;
02387   }
02388   else
02389   {
02390     // otherwise we want to look at the first editable node after
02391     // {parent,offset} and it's ancestors for divs with alignment on them
02392     nodeToExamine = GetChildAt(parent, offset);
02393     //GetNextNode(parent, offset, PR_TRUE, address_of(nodeToExamine));
02394   }
02395   
02396   if (!nodeToExamine) return NS_ERROR_NULL_POINTER;
02397 
02398   // is the node to examine a block ?
02399   PRBool isBlock;
02400   res = NodeIsBlockStatic(nodeToExamine, &isBlock);
02401   if (NS_FAILED(res)) return res;
02402 
02403   nsCOMPtr<nsIDOMNode> tmp;
02404 
02405   if (aBlockLevel) {
02406     // we are querying the block background (and not the text background), let's
02407     // climb to the block container
02408     nsCOMPtr<nsIDOMNode> blockParent = nodeToExamine;
02409     if (!isBlock) {
02410       blockParent = GetBlockNodeParent(nodeToExamine);
02411     }
02412 
02413     // Make sure to not walk off onto the Document node
02414     nsCOMPtr<nsIDOMElement> element;
02415     do {
02416       // retrieve the computed style of background-color for blockParent
02417       mHTMLCSSUtils->GetComputedProperty(blockParent,
02418                                          nsEditProperty::cssBackgroundColor,
02419                                          aOutColor);
02420       tmp.swap(blockParent);
02421       res = tmp->GetParentNode(getter_AddRefs(blockParent));
02422       element = do_QueryInterface(blockParent);
02423       // look at parent if the queried color is transparent and if the node to
02424       // examine is not the root of the document
02425     } while (aOutColor.EqualsLiteral("transparent") && element);
02426     if (aOutColor.EqualsLiteral("transparent")) {
02427       // we have hit the root of the document and the color is still transparent !
02428       // Grumble... Let's look at the default background color because that's the
02429       // color we are looking for
02430       mHTMLCSSUtils->GetDefaultBackgroundColor(aOutColor);
02431     }
02432   }
02433   else {
02434     // no, we are querying the text background for the Text Highlight button
02435     if (IsTextNode(nodeToExamine)) {
02436       // if the node of interest is a text node, let's climb a level
02437       res = nodeToExamine->GetParentNode(getter_AddRefs(parent));
02438       if (NS_FAILED(res)) return res;
02439       nodeToExamine = parent;
02440     }
02441     do {
02442       // is the node to examine a block ?
02443       res = NodeIsBlockStatic(nodeToExamine, &isBlock);
02444       if (NS_FAILED(res)) return res;
02445       if (isBlock) {
02446         // yes it is a block; in that case, the text background color is transparent
02447         aOutColor.AssignLiteral("transparent");
02448         break;
02449       }
02450       else {
02451         // no, it's not; let's retrieve the computed style of background-color for the
02452         // node to examine
02453         mHTMLCSSUtils->GetComputedProperty(nodeToExamine, nsEditProperty::cssBackgroundColor,
02454                             aOutColor);
02455         if (!aOutColor.EqualsLiteral("transparent")) {
02456           break;
02457         }
02458       }
02459       tmp.swap(nodeToExamine);
02460       res = tmp->GetParentNode(getter_AddRefs(nodeToExamine));
02461       if (NS_FAILED(res)) return res;
02462     } while ( aOutColor.EqualsLiteral("transparent") && nodeToExamine );
02463   }
02464   return NS_OK;
02465 }
02466 
02467 NS_IMETHODIMP 
02468 nsHTMLEditor::GetHTMLBackgroundColorState(PRBool *aMixed, nsAString &aOutColor)
02469 {
02470   //TODO: We don't handle "mixed" correctly!
02471   if (!aMixed) return NS_ERROR_NULL_POINTER;
02472   *aMixed = PR_FALSE;
02473   aOutColor.Truncate();
02474   
02475   nsCOMPtr<nsIDOMElement> element;
02476   PRInt32 selectedCount;
02477   nsAutoString tagName;
02478   nsresult res = GetSelectedOrParentTableElement(tagName,
02479                                                  &selectedCount,
02480                                                  getter_AddRefs(element));
02481   if (NS_FAILED(res)) return res;
02482 
02483   NS_NAMED_LITERAL_STRING(styleName, "bgcolor"); 
02484 
02485   while (element)
02486   {
02487     // We are in a cell or selected table
02488     res = element->GetAttribute(styleName, aOutColor);
02489     if (NS_FAILED(res)) return res;
02490 
02491     // Done if we have a color explicitly set
02492     if (!aOutColor.IsEmpty())
02493       return NS_OK;
02494 
02495     // Once we hit the body, we're done
02496     if(nsTextEditUtils::IsBody(element)) return NS_OK;
02497 
02498     // No color is set, but we need to report visible color inherited 
02499     // from nested cells/tables, so search up parent chain
02500     nsCOMPtr<nsIDOMNode> parentNode;
02501     res = element->GetParentNode(getter_AddRefs(parentNode));
02502     if (NS_FAILED(res)) return res;
02503     element = do_QueryInterface(parentNode);
02504   }
02505 
02506   // If no table or cell found, get page body
02507   element = GetRoot();
02508   if (!element) return NS_ERROR_NULL_POINTER;
02509 
02510   return element->GetAttribute(styleName, aOutColor);
02511 }
02512 
02513 NS_IMETHODIMP 
02514 nsHTMLEditor::GetListState(PRBool *aMixed, PRBool *aOL, PRBool *aUL, PRBool *aDL)
02515 {
02516   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
02517   if (!aMixed || !aOL || !aUL || !aDL) return NS_ERROR_NULL_POINTER;
02518   nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
02519   if (!htmlRules) return NS_ERROR_FAILURE;
02520   
02521   return htmlRules->GetListState(aMixed, aOL, aUL, aDL);
02522 }
02523 
02524 NS_IMETHODIMP 
02525 nsHTMLEditor::GetListItemState(PRBool *aMixed, PRBool *aLI, PRBool *aDT, PRBool *aDD)
02526 {
02527   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
02528   if (!aMixed || !aLI || !aDT || !aDD) return NS_ERROR_NULL_POINTER;
02529 
02530   nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
02531   if (!htmlRules) return NS_ERROR_FAILURE;
02532   
02533   return htmlRules->GetListItemState(aMixed, aLI, aDT, aDD);
02534 }
02535 
02536 NS_IMETHODIMP
02537 nsHTMLEditor::GetAlignment(PRBool *aMixed, nsIHTMLEditor::EAlignment *aAlign)
02538 {
02539   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
02540   if (!aMixed || !aAlign) return NS_ERROR_NULL_POINTER;
02541   nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
02542   if (!htmlRules) return NS_ERROR_FAILURE;
02543   
02544   return htmlRules->GetAlignment(aMixed, aAlign);
02545 }
02546 
02547 
02548 NS_IMETHODIMP 
02549 nsHTMLEditor::GetIndentState(PRBool *aCanIndent, PRBool *aCanOutdent)
02550 {
02551   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
02552   if (!aCanIndent || !aCanOutdent) return NS_ERROR_NULL_POINTER;
02553 
02554   nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
02555   if (!htmlRules) return NS_ERROR_FAILURE;
02556   
02557   return htmlRules->GetIndentState(aCanIndent, aCanOutdent);
02558 }
02559 
02560 NS_IMETHODIMP
02561 nsHTMLEditor::MakeOrChangeList(const nsAString& aListType, PRBool entireList, const nsAString& aBulletType)
02562 {
02563   nsresult res;
02564   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
02565 
02566   nsCOMPtr<nsISelection> selection;
02567   PRBool cancel, handled;
02568 
02569   nsAutoEditBatch beginBatching(this);
02570   nsAutoRules beginRulesSniffing(this, kOpMakeList, nsIEditor::eNext);
02571   
02572   // pre-process
02573   res = GetSelection(getter_AddRefs(selection));
02574   if (NS_FAILED(res)) return res;
02575   if (!selection) return NS_ERROR_NULL_POINTER;
02576 
02577   nsTextRulesInfo ruleInfo(nsTextEditRules::kMakeList);
02578   ruleInfo.blockType = &aListType;
02579   ruleInfo.entireList = entireList;
02580   ruleInfo.bulletType = &aBulletType;
02581   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
02582   if (cancel || (NS_FAILED(res))) return res;
02583 
02584   if (!handled)
02585   {
02586     // Find out if the selection is collapsed:
02587     PRBool isCollapsed;
02588     res = selection->GetIsCollapsed(&isCollapsed);
02589     if (NS_FAILED(res)) return res;
02590 
02591     nsCOMPtr<nsIDOMNode> node;
02592     PRInt32 offset;
02593   
02594     res = GetStartNodeAndOffset(selection, address_of(node), &offset);
02595     if (!node) res = NS_ERROR_FAILURE;
02596     if (NS_FAILED(res)) return res;
02597   
02598     if (isCollapsed)
02599     {
02600       // have to find a place to put the list
02601       nsCOMPtr<nsIDOMNode> parent = node;
02602       nsCOMPtr<nsIDOMNode> topChild = node;
02603       nsCOMPtr<nsIDOMNode> tmp;
02604     
02605       while ( !CanContainTag(parent, aListType))
02606       {
02607         parent->GetParentNode(getter_AddRefs(tmp));
02608         if (!tmp) return NS_ERROR_FAILURE;
02609         topChild = parent;
02610         parent = tmp;
02611       }
02612     
02613       if (parent != node)
02614       {
02615         // we need to split up to the child of parent
02616         res = SplitNodeDeep(topChild, node, offset, &offset);
02617         if (NS_FAILED(res)) return res;
02618       }
02619 
02620       // make a list
02621       nsCOMPtr<nsIDOMNode> newList;
02622       res = CreateNode(aListType, parent, offset, getter_AddRefs(newList));
02623       if (NS_FAILED(res)) return res;
02624       // make a list item
02625       nsCOMPtr<nsIDOMNode> newItem;
02626       res = CreateNode(NS_LITERAL_STRING("li"), newList, 0, getter_AddRefs(newItem));
02627       if (NS_FAILED(res)) return res;
02628       res = selection->Collapse(newItem,0);
02629       if (NS_FAILED(res)) return res;
02630     }
02631   }
02632   
02633   res = mRules->DidDoAction(selection, &ruleInfo, res);
02634   return res;
02635 }
02636 
02637 
02638 NS_IMETHODIMP
02639 nsHTMLEditor::RemoveList(const nsAString& aListType)
02640 {
02641   nsresult res;
02642   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
02643 
02644   nsCOMPtr<nsISelection> selection;
02645   PRBool cancel, handled;
02646 
02647   nsAutoEditBatch beginBatching(this);
02648   nsAutoRules beginRulesSniffing(this, kOpRemoveList, nsIEditor::eNext);
02649   
02650   // pre-process
02651   res = GetSelection(getter_AddRefs(selection));
02652   if (NS_FAILED(res)) return res;
02653   if (!selection) return NS_ERROR_NULL_POINTER;
02654 
02655   nsTextRulesInfo ruleInfo(nsTextEditRules::kRemoveList);
02656   if (aListType.LowerCaseEqualsLiteral("ol"))
02657     ruleInfo.bOrdered = PR_TRUE;
02658   else  ruleInfo.bOrdered = PR_FALSE;
02659   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
02660   if (cancel || (NS_FAILED(res))) return res;
02661 
02662   // no default behavior for this yet.  what would it mean?
02663 
02664   res = mRules->DidDoAction(selection, &ruleInfo, res);
02665   return res;
02666 }
02667 
02668 nsresult
02669 nsHTMLEditor::MakeDefinitionItem(const nsAString& aItemType)
02670 {
02671   nsresult res;
02672   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
02673 
02674   nsCOMPtr<nsISelection> selection;
02675   PRBool cancel, handled;
02676 
02677   nsAutoEditBatch beginBatching(this);
02678   nsAutoRules beginRulesSniffing(this, kOpMakeDefListItem, nsIEditor::eNext);
02679   
02680   // pre-process
02681   res = GetSelection(getter_AddRefs(selection));
02682   if (NS_FAILED(res)) return res;
02683   if (!selection) return NS_ERROR_NULL_POINTER;
02684   nsTextRulesInfo ruleInfo(nsTextEditRules::kMakeDefListItem);
02685   ruleInfo.blockType = &aItemType;
02686   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
02687   if (cancel || (NS_FAILED(res))) return res;
02688 
02689   if (!handled)
02690   {
02691     // todo: no default for now.  we count on rules to handle it.
02692   }
02693 
02694   res = mRules->DidDoAction(selection, &ruleInfo, res);
02695   return res;
02696 }
02697 
02698 nsresult
02699 nsHTMLEditor::InsertBasicBlock(const nsAString& aBlockType)
02700 {
02701   nsresult res;
02702   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
02703 
02704   nsCOMPtr<nsISelection> selection;
02705   PRBool cancel, handled;
02706 
02707   nsAutoEditBatch beginBatching(this);
02708   nsAutoRules beginRulesSniffing(this, kOpMakeBasicBlock, nsIEditor::eNext);
02709   
02710   // pre-process
02711   res = GetSelection(getter_AddRefs(selection));
02712   if (NS_FAILED(res)) return res;
02713   if (!selection) return NS_ERROR_NULL_POINTER;
02714   nsTextRulesInfo ruleInfo(nsTextEditRules::kMakeBasicBlock);
02715   ruleInfo.blockType = &aBlockType;
02716   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
02717   if (cancel || (NS_FAILED(res))) return res;
02718 
02719   if (!handled)
02720   {
02721     // Find out if the selection is collapsed:
02722     PRBool isCollapsed;
02723     res = selection->GetIsCollapsed(&isCollapsed);
02724     if (NS_FAILED(res)) return res;
02725 
02726     nsCOMPtr<nsIDOMNode> node;
02727     PRInt32 offset;
02728   
02729     res = GetStartNodeAndOffset(selection, address_of(node), &offset);
02730     if (!node) res = NS_ERROR_FAILURE;
02731     if (NS_FAILED(res)) return res;
02732   
02733     if (isCollapsed)
02734     {
02735       // have to find a place to put the block
02736       nsCOMPtr<nsIDOMNode> parent = node;
02737       nsCOMPtr<nsIDOMNode> topChild = node;
02738       nsCOMPtr<nsIDOMNode> tmp;
02739     
02740       while ( !CanContainTag(parent, aBlockType))
02741       {
02742         parent->GetParentNode(getter_AddRefs(tmp));
02743         if (!tmp) return NS_ERROR_FAILURE;
02744         topChild = parent;
02745         parent = tmp;
02746       }
02747     
02748       if (parent != node)
02749       {
02750         // we need to split up to the child of parent
02751         res = SplitNodeDeep(topChild, node, offset, &offset);
02752         if (NS_FAILED(res)) return res;
02753       }
02754 
02755       // make a block
02756       nsCOMPtr<nsIDOMNode> newBlock;
02757       res = CreateNode(aBlockType, parent, offset, getter_AddRefs(newBlock));
02758       if (NS_FAILED(res)) return res;
02759     
02760       // reposition selection to inside the block
02761       res = selection->Collapse(newBlock,0);
02762       if (NS_FAILED(res)) return res;  
02763     }
02764   }
02765 
02766   res = mRules->DidDoAction(selection, &ruleInfo, res);
02767   return res;
02768 }
02769 
02770 NS_IMETHODIMP
02771 nsHTMLEditor::Indent(const nsAString& aIndent)
02772 {
02773   nsresult res;
02774   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
02775 
02776   PRBool cancel, handled;
02777   PRInt32 theAction = nsTextEditRules::kIndent;
02778   PRInt32 opID = kOpIndent;
02779   if (aIndent.LowerCaseEqualsLiteral("outdent"))
02780   {
02781     theAction = nsTextEditRules::kOutdent;
02782     opID = kOpOutdent;
02783   }
02784   nsAutoEditBatch beginBatching(this);
02785   nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext);
02786   
02787   // pre-process
02788   nsCOMPtr<nsISelection> selection;
02789   res = GetSelection(getter_AddRefs(selection));
02790   if (NS_FAILED(res)) return res;
02791   if (!selection) return NS_ERROR_NULL_POINTER;
02792 
02793   nsTextRulesInfo ruleInfo(theAction);
02794   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
02795   if (cancel || (NS_FAILED(res))) return res;
02796   
02797   if (!handled)
02798   {
02799     // Do default - insert a blockquote node if selection collapsed
02800     nsCOMPtr<nsIDOMNode> node;
02801     PRInt32 offset;
02802     PRBool isCollapsed;
02803     res = selection->GetIsCollapsed(&isCollapsed);
02804     if (NS_FAILED(res)) return res;
02805 
02806     res = GetStartNodeAndOffset(selection, address_of(node), &offset);
02807     if (!node) res = NS_ERROR_FAILURE;
02808     if (NS_FAILED(res)) return res;
02809   
02810     if (aIndent.EqualsLiteral("indent"))
02811     {
02812       if (isCollapsed)
02813       {
02814         // have to find a place to put the blockquote
02815         nsCOMPtr<nsIDOMNode> parent = node;
02816         nsCOMPtr<nsIDOMNode> topChild = node;
02817         nsCOMPtr<nsIDOMNode> tmp;
02818         NS_NAMED_LITERAL_STRING(bq, "blockquote");
02819         while ( !CanContainTag(parent, bq))
02820         {
02821           parent->GetParentNode(getter_AddRefs(tmp));
02822           if (!tmp) return NS_ERROR_FAILURE;
02823           topChild = parent;
02824           parent = tmp;
02825         }
02826     
02827         if (parent != node)
02828         {
02829           // we need to split up to the child of parent
02830           res = SplitNodeDeep(topChild, node, offset, &offset);
02831           if (NS_FAILED(res)) return res;
02832         }
02833 
02834         // make a blockquote
02835         nsCOMPtr<nsIDOMNode> newBQ;
02836         res = CreateNode(bq, parent, offset, getter_AddRefs(newBQ));
02837         if (NS_FAILED(res)) return res;
02838         // put a space in it so layout will draw the list item
02839         res = selection->Collapse(newBQ,0);
02840         if (NS_FAILED(res)) return res;
02841         res = InsertText(NS_LITERAL_STRING(" "));
02842         if (NS_FAILED(res)) return res;
02843         // reposition selection to before the space character
02844         res = GetStartNodeAndOffset(selection, address_of(node), &offset);
02845         if (NS_FAILED(res)) return res;
02846         res = selection->Collapse(node,0);
02847         if (NS_FAILED(res)) return res;
02848       }
02849     }
02850   }
02851   res = mRules->DidDoAction(selection, &ruleInfo, res);
02852   return res;
02853 }
02854 
02855 //TODO: IMPLEMENT ALIGNMENT!
02856 
02857 NS_IMETHODIMP
02858 nsHTMLEditor::Align(const nsAString& aAlignType)
02859 {
02860   nsAutoEditBatch beginBatching(this);
02861   nsAutoRules beginRulesSniffing(this, kOpAlign, nsIEditor::eNext);
02862 
02863   nsCOMPtr<nsIDOMNode> node;
02864   PRBool cancel, handled;
02865   
02866   // Find out if the selection is collapsed:
02867   nsCOMPtr<nsISelection> selection;
02868   nsresult res = GetSelection(getter_AddRefs(selection));
02869   if (NS_FAILED(res)) return res;
02870   if (!selection) return NS_ERROR_NULL_POINTER;
02871   nsTextRulesInfo ruleInfo(nsTextEditRules::kAlign);
02872   ruleInfo.alignType = &aAlignType;
02873   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
02874   if (cancel || NS_FAILED(res))
02875     return res;
02876   
02877   res = mRules->DidDoAction(selection, &ruleInfo, res);
02878   return res;
02879 }
02880 
02881 NS_IMETHODIMP
02882 nsHTMLEditor::GetElementOrParentByTagName(const nsAString& aTagName, nsIDOMNode *aNode, nsIDOMElement** aReturn)
02883 {
02884   if (aTagName.IsEmpty() || !aReturn )
02885     return NS_ERROR_NULL_POINTER;
02886   
02887   nsresult res = NS_OK;
02888   nsCOMPtr<nsIDOMNode> currentNode;
02889 
02890   if (aNode)
02891     currentNode = aNode;
02892   else
02893   {
02894     // If no node supplied, get it from anchor node of current selection
02895     nsCOMPtr<nsISelection>selection;
02896     res = GetSelection(getter_AddRefs(selection));
02897     if (NS_FAILED(res)) return res;
02898     if (!selection) return NS_ERROR_NULL_POINTER;
02899 
02900     nsCOMPtr<nsIDOMNode> anchorNode;
02901     res = selection->GetAnchorNode(getter_AddRefs(anchorNode));
02902     if(NS_FAILED(res)) return res;
02903     if (!anchorNode)  return NS_ERROR_FAILURE;
02904 
02905     // Try to get the actual selected node
02906     PRBool hasChildren = PR_FALSE;
02907     anchorNode->HasChildNodes(&hasChildren);
02908     if (hasChildren)
02909     {
02910       PRInt32 offset;
02911       res = selection->GetAnchorOffset(&offset);
02912       if(NS_FAILED(res)) return res;
02913       currentNode = nsEditor::GetChildAt(anchorNode, offset);
02914     }
02915     // anchor node is probably a text node - just use that
02916     if (!currentNode)
02917       currentNode = anchorNode;
02918   }
02919    
02920   nsAutoString TagName(aTagName);
02921   ToLowerCase(TagName);
02922   PRBool getLink = IsLinkTag(TagName);
02923   PRBool getNamedAnchor = IsNamedAnchorTag(TagName);
02924   if ( getLink || getNamedAnchor)
02925   {
02926     TagName.AssignLiteral("a");  
02927   }
02928   PRBool findTableCell = TagName.EqualsLiteral("td");
02929   PRBool findList = TagName.EqualsLiteral("list");
02930 
02931   // default is null - no element found
02932   *aReturn = nsnull;
02933   
02934   nsCOMPtr<nsIDOMNode> parent;
02935   PRBool bNodeFound = PR_FALSE;
02936 
02937   while (PR_TRUE)
02938   {
02939     nsAutoString currentTagName; 
02940     // Test if we have a link (an anchor with href set)
02941     if ( (getLink && nsHTMLEditUtils::IsLink(currentNode)) ||
02942          (getNamedAnchor && nsHTMLEditUtils::IsNamedAnchor(currentNode)) )
02943     {
02944       bNodeFound = PR_TRUE;
02945       break;
02946     } else {
02947       if (findList)
02948       {
02949         // Match "ol", "ul", or "dl" for lists
02950         if (nsHTMLEditUtils::IsList(currentNode))
02951           goto NODE_FOUND;
02952 
02953       } else if (findTableCell)
02954       {
02955         // Table cells are another special case:
02956         // Match either "td" or "th" for them
02957         if (nsHTMLEditUtils::IsTableCell(currentNode))
02958           goto NODE_FOUND;
02959 
02960       } else {
02961         currentNode->GetNodeName(currentTagName);
02962         if (currentTagName.Equals(TagName, nsCaseInsensitiveStringComparator()))
02963         {
02964 NODE_FOUND:
02965           bNodeFound = PR_TRUE;
02966           break;
02967         } 
02968       }
02969     }
02970     // Search up the parent chain
02971     // We should never fail because of root test below, but lets be safe
02972     // XXX: ERROR_HANDLING error return code lost
02973     if (NS_FAILED(currentNode->GetParentNode(getter_AddRefs(parent))) || !parent)
02974       break;
02975 
02976     // Stop searching if parent is a body tag
02977     nsAutoString parentTagName;
02978     parent->GetNodeName(parentTagName);
02979     // Note: Originally used IsRoot to stop at table cells,
02980     //  but that's too messy when you are trying to find the parent table
02981     //PRBool isRoot;
02982     //if (NS_FAILED(IsRootTag(parentTagName, isRoot)) || isRoot)
02983     if(parentTagName.LowerCaseEqualsLiteral("body"))
02984       break;
02985 
02986     currentNode = parent;
02987   }
02988   if (bNodeFound)
02989   {
02990     nsCOMPtr<nsIDOMElement> currentElement = do_QueryInterface(currentNode);
02991     if (currentElement)
02992     {
02993       *aReturn = currentElement;
02994       // Getters must addref
02995       NS_ADDREF(*aReturn);
02996     }
02997   }
02998   else res = NS_EDITOR_ELEMENT_NOT_FOUND;
02999 
03000   return res;
03001 }
03002 
03003 NS_IMETHODIMP
03004 nsHTMLEditor::GetSelectedElement(const nsAString& aTagName, nsIDOMElement** aReturn)
03005 {
03006   if (!aReturn )
03007     return NS_ERROR_NULL_POINTER;
03008   
03009   // default is null - no element found
03010   *aReturn = nsnull;
03011   
03012   // First look for a single element in selection
03013   nsCOMPtr<nsISelection>selection;
03014   nsresult res = GetSelection(getter_AddRefs(selection));
03015   if (NS_FAILED(res)) return res;
03016   if (!selection) return NS_ERROR_NULL_POINTER;
03017   nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
03018 
03019   PRBool bNodeFound = PR_FALSE;
03020   res=NS_ERROR_NOT_INITIALIZED;
03021   PRBool isCollapsed;
03022   selection->GetIsCollapsed(&isCollapsed);
03023 
03024   nsAutoString domTagName;
03025   nsAutoString TagName(aTagName);
03026   ToLowerCase(TagName);
03027   // Empty string indicates we should match any element tag
03028   PRBool anyTag = (TagName.IsEmpty());
03029   PRBool isLinkTag = IsLinkTag(TagName);
03030   PRBool isNamedAnchorTag = IsNamedAnchorTag(TagName);
03031   
03032   nsCOMPtr<nsIDOMElement> selectedElement;
03033   nsCOMPtr<nsIDOMRange> range;
03034   res = selection->GetRangeAt(0, getter_AddRefs(range));
03035   if (NS_FAILED(res)) return res;
03036 
03037   nsCOMPtr<nsIDOMNode> startParent;
03038   PRInt32 startOffset, endOffset;
03039   res = range->GetStartContainer(getter_AddRefs(startParent));
03040   if (NS_FAILED(res)) return res;
03041   res = range->GetStartOffset(&startOffset);
03042   if (NS_FAILED(res)) return res;
03043 
03044   nsCOMPtr<nsIDOMNode> endParent;
03045   res = range->GetEndContainer(getter_AddRefs(endParent));
03046   if (NS_FAILED(res)) return res;
03047   res = range->GetEndOffset(&endOffset);
03048   if (NS_FAILED(res)) return res;
03049 
03050   // Optimization for a single selected element
03051   if (startParent && startParent == endParent && (endOffset-startOffset) == 1)
03052   {
03053     nsCOMPtr<nsIDOMNode> selectedNode = GetChildAt(startParent, startOffset);
03054     if (NS_FAILED(res)) return NS_OK;
03055     if (selectedNode)
03056     {
03057       selectedNode->GetNodeName(domTagName);
03058       ToLowerCase(domTagName);
03059 
03060       // Test for appropriate node type requested
03061       if (anyTag || (TagName == domTagName) ||
03062           (isLinkTag && nsHTMLEditUtils::IsLink(selectedNode)) ||
03063           (isNamedAnchorTag && nsHTMLEditUtils::IsNamedAnchor(selectedNode)))
03064       {
03065         bNodeFound = PR_TRUE;
03066         selectedElement = do_QueryInterface(selectedNode);
03067       }
03068     }
03069   }
03070 
03071   if (!bNodeFound)
03072   {
03073     if (isLinkTag)
03074     {
03075       // Link tag is a special case - we return the anchor node
03076       //  found for any selection that is totally within a link,
03077       //  included a collapsed selection (just a caret in a link)
03078       nsCOMPtr<nsIDOMNode> anchorNode;
03079       res = selection->GetAnchorNode(getter_AddRefs(anchorNode));
03080       if (NS_FAILED(res)) return res;
03081       PRInt32 anchorOffset = -1;
03082       if (anchorNode)
03083         selection->GetAnchorOffset(&anchorOffset);
03084     
03085       nsCOMPtr<nsIDOMNode> focusNode;
03086       res = selection->GetFocusNode(getter_AddRefs(focusNode));
03087       if (NS_FAILED(res)) return res;
03088       PRInt32 focusOffset = -1;
03089       if (focusNode)
03090         selection->GetFocusOffset(&focusOffset);
03091 
03092       // Link node must be the same for both ends of selection
03093       if (NS_SUCCEEDED(res) && anchorNode)
03094       {
03095   #ifdef DEBUG_cmanske
03096         {
03097         nsAutoString name;
03098         anchorNode->GetNodeName(name);
03099         printf("GetSelectedElement: Anchor node of selection: ");
03100         wprintf(name.get());
03101         printf(" Offset: %d\n", anchorOffset);
03102         focusNode->GetNodeName(name);
03103         printf("Focus node of selection: ");
03104         wprintf(name.get());
03105         printf(" Offset: %d\n", focusOffset);
03106         }
03107   #endif
03108         nsCOMPtr<nsIDOMElement> parentLinkOfAnchor;
03109         res = GetElementOrParentByTagName(NS_LITERAL_STRING("href"), anchorNode, getter_AddRefs(parentLinkOfAnchor));
03110         // XXX: ERROR_HANDLING  can parentLinkOfAnchor be null?
03111         if (NS_SUCCEEDED(res) && parentLinkOfAnchor)
03112         {
03113           if (isCollapsed)
03114           {
03115             // We have just a caret in the link
03116             bNodeFound = PR_TRUE;
03117           } else if(focusNode) 
03118           {  // Link node must be the same for both ends of selection
03119             nsCOMPtr<nsIDOMElement> parentLinkOfFocus;
03120             res = GetElementOrParentByTagName(NS_LITERAL_STRING("href"), focusNode, getter_AddRefs(parentLinkOfFocus));
03121             if (NS_SUCCEEDED(res) && parentLinkOfFocus == parentLinkOfAnchor)
03122               bNodeFound = PR_TRUE;
03123           }
03124       
03125           // We found a link node parent
03126           if (bNodeFound) {
03127             // GetElementOrParentByTagName addref'd this, so we don't need to do it here
03128             *aReturn = parentLinkOfAnchor;
03129             NS_IF_ADDREF(*aReturn);
03130             return NS_OK;
03131           }
03132         }
03133         else if (anchorOffset >= 0)  // Check if link node is the only thing selected
03134         {
03135           nsCOMPtr<nsIDOMNode> anchorChild;
03136           anchorChild = GetChildAt(anchorNode,anchorOffset);
03137           if (anchorChild && nsHTMLEditUtils::IsLink(anchorChild) && 
03138               (anchorNode == focusNode) && focusOffset == (anchorOffset+1))
03139           {
03140             selectedElement = do_QueryInterface(anchorChild);
03141             bNodeFound = PR_TRUE;
03142           }
03143         }
03144       }
03145     } 
03146 
03147     if (!isCollapsed)   // Don't bother to examine selection if it is collapsed
03148     {
03149       nsCOMPtr<nsIEnumerator> enumerator;
03150       res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
03151       if (NS_SUCCEEDED(res))
03152       {
03153         if(!enumerator)
03154           return NS_ERROR_NULL_POINTER;
03155 
03156         enumerator->First(); 
03157         nsCOMPtr<nsISupports> currentItem;
03158         res = enumerator->CurrentItem(getter_AddRefs(currentItem));
03159         if ((NS_SUCCEEDED(res)) && currentItem)
03160         {
03161           nsCOMPtr<nsIDOMRange> currange( do_QueryInterface(currentItem) );
03162           nsCOMPtr<nsIContentIterator> iter =
03163             do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res);
03164           if (NS_FAILED(res)) return res;
03165 
03166           iter->Init(currange);
03167           // loop through the content iterator for each content node
03168           while (!iter->IsDone())
03169           {
03170             // Query interface to cast nsIContent to nsIDOMNode
03171             //  then get tagType to compare to  aTagName
03172             // Clone node of each desired type and append it to the aDomFrag
03173             selectedElement = do_QueryInterface(iter->GetCurrentNode());
03174             if (selectedElement)
03175             {
03176               // If we already found a node, then we have another element,
03177               //  thus there's not just one element selected
03178               if (bNodeFound)
03179               {
03180                 bNodeFound = PR_FALSE;
03181                 break;
03182               }
03183 
03184               selectedElement->GetNodeName(domTagName);
03185               ToLowerCase(domTagName);
03186 
03187               if (anyTag)
03188               {
03189                 // Get name of first selected element
03190                 selectedElement->GetTagName(TagName);
03191                 ToLowerCase(TagName);
03192                 anyTag = PR_FALSE;
03193               }
03194 
03195               // The "A" tag is a pain,
03196               //  used for both link(href is set) and "Named Anchor"
03197               nsCOMPtr<nsIDOMNode> selectedNode = do_QueryInterface(selectedElement);
03198               if ( (isLinkTag && nsHTMLEditUtils::IsLink(selectedNode)) ||
03199                    (isNamedAnchorTag && nsHTMLEditUtils::IsNamedAnchor(selectedNode)) )
03200               {
03201                 bNodeFound = PR_TRUE;
03202               } else if (TagName == domTagName) { // All other tag names are handled here
03203                 bNodeFound = PR_TRUE;
03204               }
03205               if (!bNodeFound)
03206               {
03207                 // Check if node we have is really part of the selection???
03208                 break;
03209               }
03210             }
03211             iter->Next();
03212           }
03213         } else {
03214           // Should never get here?
03215           isCollapsed = PR_TRUE;
03216           printf("isCollapsed was FALSE, but no elements found in selection\n");
03217         }
03218       } else {
03219         printf("Could not create enumerator for GetSelectionProperties\n");
03220       }
03221     }
03222   }
03223   if (bNodeFound)
03224   {
03225     
03226     *aReturn = selectedElement;
03227     if (selectedElement)
03228     {  
03229       // Getters must addref
03230       NS_ADDREF(*aReturn);
03231     }
03232   } 
03233   else res = NS_EDITOR_ELEMENT_NOT_FOUND;
03234 
03235   return res;
03236 }
03237 
03238 NS_IMETHODIMP
03239 nsHTMLEditor::CreateElementWithDefaults(const nsAString& aTagName, nsIDOMElement** aReturn)
03240 {
03241   nsresult res=NS_ERROR_NOT_INITIALIZED;
03242   if (aReturn)
03243     *aReturn = nsnull;
03244 
03245   if (aTagName.IsEmpty() || !aReturn)
03246 //  if (!aTagName || !aReturn)
03247     return NS_ERROR_NULL_POINTER;
03248     
03249   nsAutoString TagName(aTagName);
03250   ToLowerCase(TagName);
03251   nsAutoString realTagName;
03252 
03253   if (IsLinkTag(TagName) || IsNamedAnchorTag(TagName))
03254   {
03255     realTagName.AssignLiteral("a");
03256   } else {
03257     realTagName = TagName;
03258   }
03259   //We don't use editor's CreateElement because we don't want to 
03260   //  go through the transaction system
03261 
03262   nsCOMPtr<nsIDOMElement>newElement;
03263   nsCOMPtr<nsIContent> newContent;
03264   nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak);
03265   if (!doc) return NS_ERROR_NOT_INITIALIZED;
03266 
03267   //new call to use instead to get proper HTML element, bug# 39919
03268   res = CreateHTMLContent(realTagName, getter_AddRefs(newContent));
03269   newElement = do_QueryInterface(newContent);
03270   if (NS_FAILED(res) || !newElement)
03271     return NS_ERROR_FAILURE;
03272 
03273   // Mark the new element dirty, so it will be formatted
03274   newElement->SetAttribute(NS_LITERAL_STRING("_moz_dirty"), EmptyString());
03275 
03276   // Set default values for new elements
03277   if (TagName.EqualsLiteral("hr"))
03278   {
03279     // Note that we read the user's attributes for these from prefs (in InsertHLine JS)
03280     res = SetAttributeOrEquivalent(newElement, NS_LITERAL_STRING("width"),
03281                                    NS_LITERAL_STRING("100%"), PR_TRUE);
03282     if (NS_FAILED(res)) return res;
03283     res = SetAttributeOrEquivalent(newElement, NS_LITERAL_STRING("size"),
03284                                    NS_LITERAL_STRING("2"), PR_TRUE);
03285   } else if (TagName.EqualsLiteral("table"))
03286   {
03287     res = newElement->SetAttribute(NS_LITERAL_STRING("cellpadding"),NS_LITERAL_STRING("2"));
03288     if (NS_FAILED(res)) return res;
03289     res = newElement->SetAttribute(NS_LITERAL_STRING("cellspacing"),NS_LITERAL_STRING("2"));
03290     if (NS_FAILED(res)) return res;
03291     res = newElement->SetAttribute(NS_LITERAL_STRING("border"),NS_LITERAL_STRING("1"));
03292   } else if (TagName.EqualsLiteral("td"))
03293   {
03294     res = SetAttributeOrEquivalent(newElement, NS_LITERAL_STRING("valign"),
03295                                    NS_LITERAL_STRING("top"), PR_TRUE);
03296   }
03297   // ADD OTHER TAGS HERE
03298 
03299   if (NS_SUCCEEDED(res))
03300   {
03301     *aReturn = newElement;
03302     // Getters must addref
03303     NS_ADDREF(*aReturn);
03304   }
03305 
03306   return res;
03307 }
03308 
03309 NS_IMETHODIMP
03310 nsHTMLEditor::InsertLinkAroundSelection(nsIDOMElement* aAnchorElement)
03311 {
03312   nsresult res=NS_ERROR_NULL_POINTER;
03313   nsCOMPtr<nsISelection> selection;
03314 
03315   if (!aAnchorElement) return NS_ERROR_NULL_POINTER; 
03316 
03317 
03318   // We must have a real selection
03319   res = GetSelection(getter_AddRefs(selection));
03320   if (!selection)
03321   {
03322     res = NS_ERROR_NULL_POINTER;
03323   }
03324   if (NS_FAILED(res)) return res;
03325   if (!selection) return NS_ERROR_NULL_POINTER;
03326 
03327   PRBool isCollapsed;
03328   res = selection->GetIsCollapsed(&isCollapsed);
03329   if (NS_FAILED(res))
03330     isCollapsed = PR_TRUE;
03331   
03332   if (isCollapsed)
03333   {
03334     printf("InsertLinkAroundSelection called but there is no selection!!!\n");     
03335     res = NS_OK;
03336   } else {
03337     // Be sure we were given an anchor element
03338     nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = do_QueryInterface(aAnchorElement);
03339     if (anchor)
03340     {
03341       nsAutoString href;
03342       res = anchor->GetHref(href);
03343       if (NS_FAILED(res)) return res;
03344       if (!href.IsEmpty())      
03345       {
03346         nsAutoEditBatch beginBatching(this);
03347 
03348         // Set all attributes found on the supplied anchor element
03349         nsCOMPtr<nsIDOMNamedNodeMap> attrMap;
03350         aAnchorElement->GetAttributes(getter_AddRefs(attrMap));
03351         if (!attrMap)
03352           return NS_ERROR_FAILURE;
03353 
03354         PRUint32 count, i;
03355         attrMap->GetLength(&count);
03356         nsAutoString name, value;
03357 
03358         for (i = 0; i < count; i++)
03359         {
03360           nsCOMPtr<nsIDOMNode> attrNode;
03361           res = attrMap->Item(i, getter_AddRefs(attrNode));
03362           if (NS_FAILED(res)) return res;
03363 
03364           if (attrNode)
03365           {
03366             nsCOMPtr<nsIDOMAttr> attribute = do_QueryInterface(attrNode);
03367             if (attribute)
03368             {
03369               // We must clear the string buffers
03370               //   because GetName, GetValue appends to previous string!
03371               name.Truncate();
03372               value.Truncate();
03373 
03374               res = attribute->GetName(name);
03375               if (NS_FAILED(res)) return res;
03376 
03377               res = attribute->GetValue(value);
03378               if (NS_FAILED(res)) return res;
03379 
03380               res = SetInlineProperty(nsEditProperty::a, name, value);
03381               if (NS_FAILED(res)) return res;
03382             }
03383           }
03384         }
03385       }
03386     }
03387   }
03388   return res;
03389 }
03390 
03391 NS_IMETHODIMP
03392 nsHTMLEditor::SetHTMLBackgroundColor(const nsAString& aColor)
03393 {
03394   NS_PRECONDITION(mDocWeak, "Missing Editor DOM Document");
03395   
03396   // Find a selected or enclosing table element to set background on
03397   nsCOMPtr<nsIDOMElement> element;
03398   PRInt32 selectedCount;
03399   nsAutoString tagName;
03400   nsresult res = GetSelectedOrParentTableElement(tagName, &selectedCount,
03401                                                  getter_AddRefs(element));
03402   if (NS_FAILED(res)) return res;
03403 
03404   PRBool setColor = !aColor.IsEmpty();
03405 
03406   NS_NAMED_LITERAL_STRING(bgcolor, "bgcolor");
03407   if (element)
03408   {
03409     if (selectedCount > 0)
03410     {
03411       // Traverse all selected cells
03412       nsCOMPtr<nsIDOMElement> cell;
03413       res = GetFirstSelectedCell(nsnull, getter_AddRefs(cell));
03414       if (NS_SUCCEEDED(res) && cell)
03415       {
03416         while(cell)
03417         {
03418           if (setColor)
03419             res = SetAttribute(cell, bgcolor, aColor);
03420           else
03421             res = RemoveAttribute(cell, bgcolor);
03422           if (NS_FAILED(res)) break;
03423 
03424           GetNextSelectedCell(nsnull, getter_AddRefs(cell));
03425         };
03426         return res;
03427       }
03428     }
03429     // If we failed to find a cell, fall through to use originally-found element
03430   } else {
03431     // No table element -- set the background color on the body tag
03432     element = GetRoot();
03433     if (!element)       return NS_ERROR_NULL_POINTER;
03434   }
03435   // Use the editor method that goes through the transaction system
03436   if (setColor)
03437     res = SetAttribute(element, bgcolor, aColor);
03438   else
03439     res = RemoveAttribute(element, bgcolor);
03440 
03441   return res;
03442 }
03443 
03444 NS_IMETHODIMP nsHTMLEditor::SetBodyAttribute(const nsAString& aAttribute, const nsAString& aValue)
03445 {
03446   // TODO: Check selection for Cell, Row, Column or table and do color on appropriate level
03447 
03448   NS_ASSERTION(mDocWeak, "Missing Editor DOM Document");
03449   
03450   // Set the background color attribute on the body tag
03451   nsIDOMElement *bodyElement = GetRoot();
03452 
03453   if (!bodyElement)
03454     return NS_ERROR_NULL_POINTER;
03455 
03456   // Use the editor method that goes through the transaction system
03457   return SetAttribute(bodyElement, aAttribute, aValue);
03458 }
03459 
03460 NS_IMETHODIMP
03461 nsHTMLEditor::GetLinkedObjects(nsISupportsArray** aNodeList)
03462 {
03463   if (!aNodeList)
03464     return NS_ERROR_NULL_POINTER;
03465 
03466   nsresult res;
03467 
03468   res = NS_NewISupportsArray(aNodeList);
03469   if (NS_FAILED(res)) return res;
03470   if (!*aNodeList) return NS_ERROR_NULL_POINTER;
03471 
03472   nsCOMPtr<nsIContentIterator> iter =
03473        do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res);
03474   if (!iter) return NS_ERROR_NULL_POINTER;
03475   if ((NS_SUCCEEDED(res)))
03476   {
03477     nsCOMPtr<nsIDOMDocument> domdoc;
03478     nsEditor::GetDocument(getter_AddRefs(domdoc));
03479     if (!domdoc)
03480       return NS_ERROR_UNEXPECTED;
03481 
03482     nsCOMPtr<nsIDocument> doc (do_QueryInterface(domdoc));
03483     if (!doc)
03484       return NS_ERROR_UNEXPECTED;
03485 
03486     iter->Init(doc->GetRootContent());
03487 
03488     // loop through the content iterator for each content node
03489     while (!iter->IsDone())
03490     {
03491       nsCOMPtr<nsIDOMNode> node (do_QueryInterface(iter->GetCurrentNode()));
03492       if (node)
03493       {
03494         // Let nsURIRefObject make the hard decisions:
03495         nsCOMPtr<nsIURIRefObject> refObject;
03496         res = NS_NewHTMLURIRefObject(getter_AddRefs(refObject), node);
03497         if (NS_SUCCEEDED(res))
03498         {
03499           nsCOMPtr<nsISupports> isupp (do_QueryInterface(refObject));
03500 
03501           (*aNodeList)->AppendElement(isupp);
03502         }
03503       }
03504       iter->Next();
03505     }
03506   }
03507 
03508   return NS_OK;
03509 }
03510 
03511 #ifdef XP_MAC
03512 #pragma mark -
03513 #pragma mark  nsIEditorStyleSheets methods 
03514 #pragma mark -
03515 #endif
03516 
03517 NS_IMETHODIMP
03518 nsHTMLEditor::AddStyleSheet(const nsAString &aURL)
03519 {
03520   // Enable existing sheet if already loaded.
03521   if (EnableExistingStyleSheet(aURL))
03522     return NS_OK;
03523 
03524   // Lose the previously-loaded sheet so there's nothing to replace
03525   // This pattern is different from Override methods because
03526   //  we must wait to remove mLastStyleSheetURL and add new sheet
03527   //  at the same time (in StyleSheetLoaded callback) so they are undoable together
03528   mLastStyleSheetURL.Truncate();
03529   return ReplaceStyleSheet(aURL);
03530 }
03531 
03532 NS_IMETHODIMP
03533 nsHTMLEditor::ReplaceStyleSheet(const nsAString& aURL)
03534 {
03535   // Enable existing sheet if already loaded.
03536   if (EnableExistingStyleSheet(aURL))
03537   {
03538     // Disable last sheet if not the same as new one
03539     if (!mLastStyleSheetURL.IsEmpty() && mLastStyleSheetURL.Equals(aURL))
03540         return EnableStyleSheet(mLastStyleSheetURL, PR_FALSE);
03541 
03542     return NS_OK;
03543   }
03544 
03545   nsCOMPtr<nsICSSLoader> cssLoader;
03546   nsresult rv = GetCSSLoader(aURL, getter_AddRefs(cssLoader));
03547   NS_ENSURE_SUCCESS(rv, rv);
03548 
03549   if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
03550   nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
03551   if (!ps) return NS_ERROR_NOT_INITIALIZED;
03552   nsIDocument *document = ps->GetDocument();
03553   if (!document)     return NS_ERROR_NULL_POINTER;
03554 
03555   nsCOMPtr<nsIURI> uaURI;
03556   rv = NS_NewURI(getter_AddRefs(uaURI), aURL);
03557   NS_ENSURE_SUCCESS(rv, rv);
03558 
03559   nsCOMPtr<nsICSSStyleSheet> sheet;
03560   rv = cssLoader->LoadAgentSheet(uaURI, this);
03561   NS_ENSURE_SUCCESS(rv, rv);
03562 
03563   return NS_OK;
03564 }
03565 
03566 NS_IMETHODIMP
03567 nsHTMLEditor::RemoveStyleSheet(const nsAString &aURL)
03568 {
03569   nsCOMPtr<nsICSSStyleSheet> sheet;
03570   nsresult rv = GetStyleSheetForURL(aURL, getter_AddRefs(sheet));
03571   NS_ENSURE_SUCCESS(rv, rv);
03572   if (!sheet)
03573     return NS_ERROR_UNEXPECTED;
03574 
03575   nsRefPtr<RemoveStyleSheetTxn> txn;
03576   rv = CreateTxnForRemoveStyleSheet(sheet, getter_AddRefs(txn));
03577   if (!txn) rv = NS_ERROR_NULL_POINTER;
03578   if (NS_SUCCEEDED(rv))
03579   {
03580     rv = DoTransaction(txn);
03581     if (NS_SUCCEEDED(rv))
03582       mLastStyleSheetURL.Truncate();        // forget it
03583 
03584     // Remove it from our internal list
03585     rv = RemoveStyleSheetFromList(aURL);
03586   }
03587   
03588   return rv;
03589 }
03590 
03591 
03592 NS_IMETHODIMP 
03593 nsHTMLEditor::AddOverrideStyleSheet(const nsAString& aURL)
03594 {
03595   // Enable existing sheet if already loaded.
03596   if (EnableExistingStyleSheet(aURL))
03597     return NS_OK;
03598 
03599   nsCOMPtr<nsICSSLoader> cssLoader;
03600   nsresult rv = GetCSSLoader(aURL, getter_AddRefs(cssLoader));
03601   NS_ENSURE_SUCCESS(rv, rv);
03602 
03603   nsCOMPtr<nsIURI> uaURI;
03604   rv = NS_NewURI(getter_AddRefs(uaURI), aURL);
03605   NS_ENSURE_SUCCESS(rv, rv);
03606 
03607   // We MUST ONLY load synchronous local files (no @import)
03608   nsCOMPtr<nsICSSStyleSheet> sheet;
03609   nsCOMPtr<nsICSSLoader_MOZILLA_1_8_BRANCH> loader = do_QueryInterface(cssLoader);
03610   rv = loader->LoadSheetSync(uaURI, PR_TRUE, getter_AddRefs(sheet));
03611 
03612   // Synchronous loads should ALWAYS return completed
03613   if (!sheet)
03614     return NS_ERROR_NULL_POINTER;
03615 
03616   nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
03617   if (!ps)
03618     return NS_ERROR_NOT_INITIALIZED;
03619 
03620   // Add the override style sheet
03621   // (This checks if already exists)
03622   ps->AddOverrideStyleSheet(sheet);
03623 
03624   // Save doc pointer to be able to use nsIStyleSheet::SetEnabled()
03625   nsIDocument *document = ps->GetDocument();
03626   if (!document)
03627     return NS_ERROR_NULL_POINTER;
03628   sheet->SetOwningDocument(document);
03629 
03630   ps->ReconstructStyleData();
03631 
03632   // Save as the last-loaded sheet
03633   mLastOverrideStyleSheetURL = aURL;
03634 
03635   //Add URL and style sheet to our lists
03636   return AddNewStyleSheetToList(aURL, sheet);
03637 }
03638 
03639 NS_IMETHODIMP
03640 nsHTMLEditor::ReplaceOverrideStyleSheet(const nsAString& aURL)
03641 {
03642   // Enable existing sheet if already loaded.
03643   if (EnableExistingStyleSheet(aURL))
03644   {
03645     // Disable last sheet if not the same as new one
03646     if (!mLastOverrideStyleSheetURL.IsEmpty() && !mLastOverrideStyleSheetURL.Equals(aURL))
03647       return EnableStyleSheet(mLastOverrideStyleSheetURL, PR_FALSE);
03648 
03649     return NS_OK;
03650   }
03651   // Remove the previous sheet
03652   if (!mLastOverrideStyleSheetURL.IsEmpty())
03653     RemoveOverrideStyleSheet(mLastOverrideStyleSheetURL);
03654 
03655   return AddOverrideStyleSheet(aURL);
03656 }
03657 
03658 // Do NOT use transaction system for override style sheets
03659 NS_IMETHODIMP
03660 nsHTMLEditor::RemoveOverrideStyleSheet(const nsAString &aURL)
03661 {
03662   nsCOMPtr<nsICSSStyleSheet> sheet;
03663   GetStyleSheetForURL(aURL, getter_AddRefs(sheet));
03664 
03665   // Make sure we remove the stylesheet from our internal list in all
03666   // cases.
03667   nsresult rv = RemoveStyleSheetFromList(aURL);
03668 
03669   if (!sheet)
03670     return NS_OK; 
03671 
03672   if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
03673   nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
03674   if (!ps) return NS_ERROR_NOT_INITIALIZED;
03675 
03676   ps->RemoveOverrideStyleSheet(sheet);
03677   ps->ReconstructStyleData();
03678 
03679   // Remove it from our internal list
03680   return rv;
03681 }
03682 
03683 NS_IMETHODIMP
03684 nsHTMLEditor::EnableStyleSheet(const nsAString &aURL, PRBool aEnable)
03685 {
03686   nsCOMPtr<nsICSSStyleSheet> sheet;
03687   nsresult rv = GetStyleSheetForURL(aURL, getter_AddRefs(sheet));
03688   NS_ENSURE_SUCCESS(rv, rv);
03689   if (!sheet)
03690     return NS_OK; // Don't fail if sheet not found
03691 
03692   nsCOMPtr<nsIStyleSheet> nsISheet = do_QueryInterface(sheet);
03693   return nsISheet->SetEnabled(aEnable);
03694 }
03695 
03696 
03697 PRBool
03698 nsHTMLEditor::EnableExistingStyleSheet(const nsAString &aURL)
03699 {
03700   nsCOMPtr<nsICSSStyleSheet> sheet;
03701   nsresult rv = GetStyleSheetForURL(aURL, getter_AddRefs(sheet));
03702   NS_ENSURE_SUCCESS(rv, rv);
03703 
03704   // Enable sheet if already loaded.
03705   if (sheet)
03706   {
03707     nsCOMPtr<nsIStyleSheet> nsISheet = do_QueryInterface(sheet);
03708     nsISheet->SetEnabled(PR_TRUE);
03709     return PR_TRUE;
03710   }
03711   return PR_FALSE;
03712 }
03713 
03714 nsresult
03715 nsHTMLEditor::AddNewStyleSheetToList(const nsAString &aURL,
03716                                      nsICSSStyleSheet *aStyleSheet)
03717 {
03718   PRInt32 countSS = mStyleSheets.Count();
03719   PRInt32 countU = mStyleSheetURLs.Count();
03720 
03721   if (countU < 0 || countSS != countU)
03722     return NS_ERROR_UNEXPECTED;
03723 
03724   if (!mStyleSheetURLs.AppendString(aURL))
03725     return NS_ERROR_UNEXPECTED;
03726 
03727   return mStyleSheets.AppendObject(aStyleSheet) ? NS_OK : NS_ERROR_UNEXPECTED;
03728 }
03729 
03730 nsresult
03731 nsHTMLEditor::RemoveStyleSheetFromList(const nsAString &aURL)
03732 {
03733   // is it already in the list?
03734   PRInt32 foundIndex;
03735   foundIndex = mStyleSheetURLs.IndexOf(aURL);
03736   if (foundIndex < 0)
03737     return NS_ERROR_FAILURE;
03738 
03739   // Attempt both removals; if one fails there's not much we can do.
03740   nsresult rv = NS_OK;
03741   if (!mStyleSheets.RemoveObjectAt(foundIndex))
03742     rv = NS_ERROR_FAILURE;
03743   if (!mStyleSheetURLs.RemoveStringAt(foundIndex))
03744     rv = NS_ERROR_FAILURE;
03745 
03746   return rv;
03747 }
03748 
03749 NS_IMETHODIMP
03750 nsHTMLEditor::GetStyleSheetForURL(const nsAString &aURL,
03751                                   nsICSSStyleSheet **aStyleSheet)
03752 {
03753   NS_ENSURE_ARG_POINTER(aStyleSheet);
03754   *aStyleSheet = 0;
03755 
03756   // is it already in the list?
03757   PRInt32 foundIndex;
03758   foundIndex = mStyleSheetURLs.IndexOf(aURL);
03759   if (foundIndex < 0)
03760     return NS_OK; //No sheet -- don't fail!
03761 
03762   *aStyleSheet = mStyleSheets[foundIndex];
03763   if (!*aStyleSheet)
03764     return NS_ERROR_FAILURE;
03765 
03766   NS_ADDREF(*aStyleSheet);
03767 
03768   return NS_OK;
03769 }
03770 
03771 NS_IMETHODIMP
03772 nsHTMLEditor::GetURLForStyleSheet(nsICSSStyleSheet *aStyleSheet,
03773                                   nsAString &aURL)
03774 {
03775   // is it already in the list?
03776   PRInt32 foundIndex = mStyleSheets.IndexOf(aStyleSheet);
03777 
03778   // Don't fail if we don't find it in our list
03779   if (foundIndex == -1)
03780     return NS_OK;
03781 
03782   // Found it in the list!
03783   nsAString* strp = mStyleSheetURLs.StringAt(foundIndex);
03784   if (!strp)
03785     return NS_ERROR_UNEXPECTED;
03786   aURL = *strp;
03787   return NS_OK;
03788 }
03789 
03790 nsresult
03791 nsHTMLEditor::GetCSSLoader(const nsAString& aURL, nsICSSLoader** aCSSLoader)
03792 {
03793   if (!aCSSLoader)
03794     return NS_ERROR_NULL_POINTER;
03795   *aCSSLoader = 0;
03796 
03797   if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
03798   nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
03799   if (!ps) return NS_ERROR_NOT_INITIALIZED;
03800   nsIDocument *document = ps->GetDocument();
03801   if (!document)     return NS_ERROR_NULL_POINTER;
03802 
03803   NS_ADDREF(*aCSSLoader = document->CSSLoader());
03804 
03805   return NS_OK;
03806 }
03807 
03808 #ifdef XP_MAC
03809 #pragma mark -
03810 #pragma mark  nsIEditorMailSupport methods 
03811 #pragma mark -
03812 #endif
03813 
03814 NS_IMETHODIMP
03815 nsHTMLEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList)
03816 {
03817   if (!aNodeList)
03818     return NS_ERROR_NULL_POINTER;
03819 
03820   nsresult res;
03821 
03822   res = NS_NewISupportsArray(aNodeList);
03823   if (NS_FAILED(res)) return res;
03824   if (!*aNodeList) return NS_ERROR_NULL_POINTER;
03825 
03826   nsCOMPtr<nsIContentIterator> iter =
03827       do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res);
03828   if (!iter) return NS_ERROR_NULL_POINTER;
03829   if ((NS_SUCCEEDED(res)))
03830   {
03831     nsCOMPtr<nsIDOMDocument> domdoc;
03832     nsEditor::GetDocument(getter_AddRefs(domdoc));
03833     if (!domdoc)
03834       return NS_ERROR_UNEXPECTED;
03835 
03836     nsCOMPtr<nsIDocument> doc (do_QueryInterface(domdoc));
03837     if (!doc)
03838       return NS_ERROR_UNEXPECTED;
03839 
03840     iter->Init(doc->GetRootContent());
03841 
03842     // loop through the content iterator for each content node
03843     while (!iter->IsDone())
03844     {
03845       nsIContent *content = iter->GetCurrentNode();
03846       nsCOMPtr<nsIDOMNode> node (do_QueryInterface(content));
03847       if (node)
03848       {
03849         nsAutoString tagName;
03850         node->GetNodeName(tagName);
03851         ToLowerCase(tagName);
03852 
03853         // See if it's an image or an embed and also include all links.
03854         // Let mail decide which link to send or not
03855         if (tagName.EqualsLiteral("img") || tagName.EqualsLiteral("embed") ||
03856             tagName.EqualsLiteral("a"))
03857           (*aNodeList)->AppendElement(node);
03858         else if (tagName.EqualsLiteral("body"))
03859         {
03860           nsCOMPtr<nsIDOMElement> element = do_QueryInterface(node);
03861           if (element)
03862           {
03863             PRBool hasBackground = PR_FALSE;
03864             if (NS_SUCCEEDED(element->HasAttribute(NS_LITERAL_STRING("background"), &hasBackground)) && hasBackground)
03865               (*aNodeList)->AppendElement(node);
03866           }
03867         }
03868       }
03869       iter->Next();
03870     }
03871   }
03872 
03873   return res;
03874 }
03875 
03876 
03877 #ifdef XP_MAC
03878 #pragma mark -
03879 #pragma mark  nsIEditor overrides 
03880 #pragma mark -
03881 #endif
03882 
03883 NS_IMETHODIMP nsHTMLEditor::DeleteNode(nsIDOMNode * aNode)
03884 {
03885   nsCOMPtr<nsIDOMNode> selectAllNode = FindUserSelectAllNode(aNode);
03886   
03887   if (selectAllNode)
03888   {
03889     return nsEditor::DeleteNode(selectAllNode);
03890   }
03891   return nsEditor::DeleteNode(aNode);
03892 }
03893 
03894 NS_IMETHODIMP nsHTMLEditor::DeleteText(nsIDOMCharacterData *aTextNode,
03895                                        PRUint32             aOffset,
03896                                        PRUint32             aLength)
03897 {
03898   nsCOMPtr<nsIDOMNode> selectAllNode = FindUserSelectAllNode(aTextNode);
03899   
03900   if (selectAllNode)
03901   {
03902     return nsEditor::DeleteNode(selectAllNode);
03903   }
03904   return nsEditor::DeleteText(aTextNode, aOffset, aLength);
03905 }
03906 
03907 
03908 #ifdef XP_MAC
03909 #pragma mark -
03910 #pragma mark  support utils
03911 #pragma mark -
03912 #endif
03913 
03914 /* This routine examines aNode and it's ancestors looking for any node which has the
03915    -moz-user-select: all style lit.  Return the highest such ancestor.  */
03916 nsCOMPtr<nsIDOMNode> nsHTMLEditor::FindUserSelectAllNode(nsIDOMNode *aNode)
03917 {
03918   nsCOMPtr<nsIDOMNode> resultNode;  // starts out empty
03919   nsCOMPtr<nsIDOMNode> node = aNode;
03920   nsIDOMElement *root = GetRoot();
03921   if (!nsEditorUtils::IsDescendantOf(aNode, root))
03922     return nsnull;
03923 
03924   // retrieve the computed style of -moz-user-select for aNode
03925   nsAutoString mozUserSelectValue;
03926   while (node)
03927   {
03928     mHTMLCSSUtils->GetComputedProperty(node, nsEditProperty::cssMozUserSelect, mozUserSelectValue);
03929     if (mozUserSelectValue.EqualsLiteral("all"))
03930     {
03931       resultNode = node;
03932     }
03933     if (node != root)
03934     {
03935       nsCOMPtr<nsIDOMNode> tmp;
03936       node->GetParentNode(getter_AddRefs(tmp));
03937       node = tmp;
03938     }
03939     else
03940     {
03941       node = nsnull;
03942     }
03943   } 
03944 
03945   return resultNode;
03946 }
03947 
03948 static nsresult SetSelectionAroundHeadChildren(nsCOMPtr<nsISelection> aSelection, nsWeakPtr aDocWeak)
03949 {
03950   nsresult res = NS_OK;
03951   // Set selection around <head> node
03952   nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(aDocWeak);
03953   if (!doc) return NS_ERROR_NOT_INITIALIZED;
03954 
03955   nsCOMPtr<nsIDOMNodeList>nodeList; 
03956   res = doc->GetElementsByTagName(NS_LITERAL_STRING("head"), getter_AddRefs(nodeList));
03957   if (NS_FAILED(res)) return res;
03958   if (!nodeList) return NS_ERROR_NULL_POINTER;
03959 
03960   PRUint32 count; 
03961   nodeList->GetLength(&count);
03962   if (count < 1) return NS_ERROR_FAILURE;
03963 
03964   nsCOMPtr<nsIDOMNode> headNode;
03965   res = nodeList->Item(0, getter_AddRefs(headNode)); 
03966   if (NS_FAILED(res)) return res;
03967   if (!headNode) return NS_ERROR_NULL_POINTER;
03968 
03969   // Collapse selection to before first child of the head,
03970   res = aSelection->Collapse(headNode, 0);
03971   if (NS_FAILED(res)) return res;
03972 
03973   //  then extend it to just after
03974   nsCOMPtr<nsIDOMNodeList> childNodes;
03975   res = headNode->GetChildNodes(getter_AddRefs(childNodes));
03976   if (NS_FAILED(res)) return res;
03977   if (!childNodes) return NS_ERROR_NULL_POINTER;
03978   PRUint32 childCount;
03979   childNodes->GetLength(&childCount);
03980 
03981   return aSelection->Extend(headNode, childCount+1);
03982 }
03983 
03984 NS_IMETHODIMP
03985 nsHTMLEditor::GetHeadContentsAsHTML(nsAString& aOutputString)
03986 {
03987   nsCOMPtr<nsISelection> selection;
03988   nsresult res = GetSelection(getter_AddRefs(selection));
03989   if (NS_FAILED(res)) return res;
03990   if (!selection) return NS_ERROR_NULL_POINTER;
03991 
03992   // Save current selection
03993   nsAutoSelectionReset selectionResetter(selection, this);
03994 
03995   res = SetSelectionAroundHeadChildren(selection, mDocWeak);
03996   if (NS_FAILED(res)) return res;
03997 
03998   res = OutputToString(NS_LITERAL_STRING("text/html"),
03999                        nsIDocumentEncoder::OutputSelectionOnly,
04000                        aOutputString);
04001   if (NS_SUCCEEDED(res))
04002   {
04003     // Selection always includes <body></body>,
04004     //  so terminate there
04005     nsReadingIterator<PRUnichar> findIter,endFindIter;
04006     aOutputString.BeginReading(findIter);
04007     aOutputString.EndReading(endFindIter);
04008     //counting on our parser to always lower case!!!
04009     if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<body"),
04010                                       findIter, endFindIter))
04011     {
04012       nsReadingIterator<PRUnichar> beginIter;
04013       aOutputString.BeginReading(beginIter);
04014       PRInt32 offset = Distance(beginIter, findIter);//get the distance
04015 
04016       nsWritingIterator<PRUnichar> writeIter;
04017       aOutputString.BeginWriting(writeIter);
04018       // Ensure the string ends in a newline
04019       PRUnichar newline ('\n');
04020       findIter.advance(-1);
04021       if (offset ==0 || (offset >0 &&  (*findIter) != newline)) //check for 0
04022       {
04023         writeIter.advance(offset);
04024         *writeIter = newline;
04025         aOutputString.Truncate(offset+1);
04026       }
04027     }
04028   }
04029   return res;
04030 }
04031 
04032 NS_IMETHODIMP
04033 nsHTMLEditor::DebugUnitTests(PRInt32 *outNumTests, PRInt32 *outNumTestsFailed)
04034 {
04035 #ifdef DEBUG
04036   if (!outNumTests || !outNumTestsFailed)
04037     return NS_ERROR_NULL_POINTER;
04038 
04039   TextEditorTest *tester = new TextEditorTest();
04040   if (!tester)
04041     return NS_ERROR_OUT_OF_MEMORY;
04042    
04043   tester->Run(this, outNumTests, outNumTestsFailed);
04044   delete tester;
04045   return NS_OK;
04046 #else
04047   return NS_ERROR_NOT_IMPLEMENTED;
04048 #endif
04049 }
04050 
04051 #ifdef XP_MAC
04052 #pragma mark -
04053 #pragma mark  StyleSheet utils 
04054 #pragma mark -
04055 #endif
04056 
04057 
04058 NS_IMETHODIMP 
04059 nsHTMLEditor::StyleSheetLoaded(nsICSSStyleSheet* aSheet, PRBool aNotify)
04060 {
04061   nsresult rv = NS_OK;
04062   nsAutoEditBatch batchIt(this);
04063 
04064   if (!mLastStyleSheetURL.IsEmpty())
04065     RemoveStyleSheet(mLastStyleSheetURL);
04066 
04067   nsRefPtr<AddStyleSheetTxn> txn;
04068   rv = CreateTxnForAddStyleSheet(aSheet, getter_AddRefs(txn));
04069   if (!txn) rv = NS_ERROR_NULL_POINTER;
04070   if (NS_SUCCEEDED(rv))
04071   {
04072     rv = DoTransaction(txn);
04073     if (NS_SUCCEEDED(rv))
04074     {
04075       // Get the URI, then url spec from the sheet
04076       nsCOMPtr<nsIStyleSheet> sheet = do_QueryInterface(aSheet);
04077       nsCOMPtr<nsIURI> uri;
04078       rv = sheet->GetSheetURI(getter_AddRefs(uri));
04079 
04080       if (NS_SUCCEEDED(rv))
04081       {
04082         nsCAutoString spec;
04083         rv = uri->GetSpec(spec);
04084 
04085         if (NS_SUCCEEDED(rv))
04086         {
04087           // Save it so we can remove before applying the next one
04088           mLastStyleSheetURL.AssignWithConversion(spec.get());
04089 
04090           // Also save in our arrays of urls and sheets
04091           AddNewStyleSheetToList(mLastStyleSheetURL, aSheet);
04092         }
04093       }
04094     }
04095   }
04096 
04097   return NS_OK;
04098 }
04099 
04100 #ifdef XP_MAC
04101 #pragma mark -
04102 #pragma mark  nsEditor overrides 
04103 #pragma mark -
04104 #endif
04105 
04106 
04109 NS_IMETHODIMP
04110 nsHTMLEditor::StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection)
04111 {
04112   nsEditor::StartOperation(opID, aDirection);  // will set mAction, mDirection
04113   if (! ((mAction==kOpInsertText) || (mAction==kOpInsertIMEText)) )
04114     ClearInlineStylesCache();
04115   if (mRules) return mRules->BeforeEdit(mAction, mDirection);
04116   return NS_OK;
04117 }
04118 
04119 
04122 NS_IMETHODIMP
04123 nsHTMLEditor::EndOperation()
04124 {
04125   // post processing
04126   if (! ((mAction==kOpInsertText) || (mAction==kOpInsertIMEText) || (mAction==kOpIgnore)) )
04127     ClearInlineStylesCache();
04128   nsresult res = NS_OK;
04129   if (mRules) res = mRules->AfterEdit(mAction, mDirection);
04130   nsEditor::EndOperation();  // will clear mAction, mDirection
04131   return res;
04132 }  
04133 
04134 PRBool 
04135 nsHTMLEditor::TagCanContainTag(const nsAString& aParentTag, const nsAString& aChildTag)  
04136 {
04137   // COtherDTD gives some unwanted results.  We override them here.
04138   if (aParentTag.LowerCaseEqualsLiteral("ol") ||
04139       aParentTag.LowerCaseEqualsLiteral("ul"))
04140   {
04141     // if parent is a list and tag is also a list, say "yes".
04142     // This is because the editor does sublists illegally for now. 
04143       if (aChildTag.LowerCaseEqualsLiteral("ol") ||
04144           aChildTag.LowerCaseEqualsLiteral("ul"))
04145       return PR_TRUE;
04146   }
04147 
04148   if (aParentTag.LowerCaseEqualsLiteral("li"))
04149   {
04150     // list items cant contain list items
04151     if (aChildTag.LowerCaseEqualsLiteral("li"))
04152       return PR_FALSE;
04153   }
04154 
04155 /*  
04156   // if parent is a pre, and child is not inline, say "no"
04157   if ( aParentTag.EqualsLiteral("pre") )
04158   {
04159     if (aChildTag.EqualsLiteral("#text"))
04160       return PR_TRUE;
04161 
04162     PRInt32 childTagEnum = sParserService->HTMLStringTagToId(aChildTag);
04163     PRInt32 parentTagEnum = sParserService->HTMLStringTagToId(aParentTag);
04164 
04165     if (!mDTD->IsInlineElement(childTagEnum, parentTagEnum))
04166       return PR_FALSE;
04167   }
04168 */
04169   // else fall thru
04170   return nsEditor::TagCanContainTag(aParentTag, aChildTag);
04171 }
04172 
04173 
04174 NS_IMETHODIMP 
04175 nsHTMLEditor::SelectEntireDocument(nsISelection *aSelection)
04176 {
04177   if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; }
04178   
04179   // get editor root node
04180   nsIDOMElement *rootElement = GetRoot();
04181   
04182   // is doc empty?
04183   PRBool bDocIsEmpty;
04184   nsresult res = mRules->DocumentIsEmpty(&bDocIsEmpty);
04185   if (NS_FAILED(res)) return res;
04186     
04187   if (bDocIsEmpty)
04188   {
04189     // if its empty dont select entire doc - that would select the bogus node
04190     return aSelection->Collapse(rootElement, 0);
04191   }
04192 
04193   return nsEditor::SelectEntireDocument(aSelection);
04194 }
04195 
04196 
04197 
04198 #ifdef XP_MAC
04199 #pragma mark -
04200 #pragma mark  Random methods 
04201 #pragma mark -
04202 #endif
04203 
04204 
04205 NS_IMETHODIMP nsHTMLEditor::GetLayoutObject(nsIDOMNode *aNode, nsISupports **aLayoutObject)
04206 {
04207   nsresult result = NS_ERROR_FAILURE;  // we return an error unless we get the index
04208   if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
04209   nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
04210   if (!ps) return NS_ERROR_NOT_INITIALIZED;
04211 
04212   if ((nsnull!=aNode))
04213   { // get the content interface
04214     nsCOMPtr<nsIContent> nodeAsContent( do_QueryInterface(aNode) );
04215     if (nodeAsContent)
04216     { // get the frame from the content interface
04217       //Note: frames are not ref counted, so don't use an nsCOMPtr
04218       *aLayoutObject = nsnull;
04219       result = ps->GetLayoutObjectFor(nodeAsContent, aLayoutObject);
04220     }
04221   }
04222   else {
04223     result = NS_ERROR_NULL_POINTER;
04224   }
04225 
04226   return result;
04227 }
04228 
04229 
04230 // this will NOT find aAttribute unless aAttribute has a non-null value
04231 // so singleton attributes like <Table border> will not be matched!
04232 void nsHTMLEditor::IsTextPropertySetByContent(nsIDOMNode        *aNode,
04233                                               nsIAtom           *aProperty, 
04234                                               const nsAString   *aAttribute, 
04235                                               const nsAString   *aValue, 
04236                                               PRBool            &aIsSet,
04237                                               nsIDOMNode       **aStyleNode,
04238                                               nsAString *outValue)
04239 {
04240   nsresult result;
04241   aIsSet = PR_FALSE;  // must be initialized to false for code below to work
04242   nsAutoString propName;
04243   aProperty->ToString(propName);
04244   nsCOMPtr<nsIDOMNode>node = aNode;
04245 
04246   while (node)
04247   {
04248     nsCOMPtr<nsIDOMElement>element;
04249     element = do_QueryInterface(node);
04250     if (element)
04251     {
04252       nsAutoString tag, value;
04253       element->GetTagName(tag);
04254       if (propName.Equals(tag, nsCaseInsensitiveStringComparator()))
04255       {
04256         PRBool found = PR_FALSE;
04257         if (aAttribute && 0!=aAttribute->Length())
04258         {
04259           element->GetAttribute(*aAttribute, value);
04260           if (outValue) *outValue = value;
04261           if (!value.IsEmpty())
04262           {
04263             if (!aValue) {
04264               found = PR_TRUE;
04265             }
04266             else
04267             {
04268               nsString tString(*aValue);
04269               if (tString.Equals(value, nsCaseInsensitiveStringComparator())) {
04270                 found = PR_TRUE;
04271               }
04272               else {  // we found the prop with the attribute, but the value doesn't match
04273                 break;
04274               }
04275             }
04276           }
04277         }
04278         else { 
04279           found = PR_TRUE;
04280         }
04281         if (found)
04282         {
04283           aIsSet = PR_TRUE;
04284           break;
04285         }
04286       }
04287     }
04288     nsCOMPtr<nsIDOMNode>temp;
04289     result = node->GetParentNode(getter_AddRefs(temp));
04290     if (NS_SUCCEEDED(result) && temp) {
04291       node = temp;
04292     }
04293     else {
04294       node = nsnull;
04295     }
04296   }
04297 }
04298 
04299 #ifdef XP_MAC
04300 #pragma mark -
04301 #endif
04302 
04303 //================================================================
04304 // HTML Editor methods
04305 //
04306 // Note: Table Editing methods are implemented in nsTableEditor.cpp
04307 //
04308 
04309 
04310 PRBool nsHTMLEditor::IsElementInBody(nsIDOMElement* aElement)
04311 {
04312   return nsTextEditUtils::InBody(aElement, this);
04313 }
04314 
04315 PRBool
04316 nsHTMLEditor::SetCaretInTableCell(nsIDOMElement* aElement)
04317 {
04318   PRBool caretIsSet = PR_FALSE;
04319 
04320   if (aElement && IsElementInBody(aElement))
04321   {
04322     nsresult res = NS_OK;
04323     nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
04324     if (content)
04325     {
04326       nsIAtom *atom = content->Tag();
04327       if (atom == nsEditProperty::table ||
04328           atom == nsEditProperty::tbody ||
04329           atom == nsEditProperty::thead ||
04330           atom == nsEditProperty::tfoot ||
04331           atom == nsEditProperty::caption ||
04332           atom == nsEditProperty::tr ||
04333           atom == nsEditProperty::td )
04334       {
04335         nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
04336         nsCOMPtr<nsIDOMNode> parent;
04337         // This MUST succeed if IsElementInBody was TRUE
04338         node->GetParentNode(getter_AddRefs(parent));
04339         nsCOMPtr<nsIDOMNode>firstChild;
04340         // Find deepest child
04341         PRBool hasChild;
04342         while (NS_SUCCEEDED(node->HasChildNodes(&hasChild)) && hasChild)
04343         {
04344           if (NS_SUCCEEDED(node->GetFirstChild(getter_AddRefs(firstChild))))
04345           {
04346             parent = node;
04347             node = firstChild;
04348           }
04349         }
04350         // Set selection at beginning of deepest node
04351         nsCOMPtr<nsISelection> selection;
04352         res = GetSelection(getter_AddRefs(selection));
04353         if (NS_SUCCEEDED(res) && selection && firstChild)
04354         {
04355           res = selection->Collapse(firstChild, 0);
04356           if (NS_SUCCEEDED(res))
04357             caretIsSet = PR_TRUE;
04358         }
04359       }
04360     }
04361   }
04362   return caretIsSet;
04363 }            
04364 
04365 
04366 
04367 NS_IMETHODIMP
04368 nsHTMLEditor::IsRootTag(nsString &aTag, PRBool &aIsTag)
04369 {
04370   static char bodyTag[] = "body";
04371   static char tdTag[] = "td";
04372   static char thTag[] = "th";
04373   static char captionTag[] = "caption";
04374   if (aTag.EqualsIgnoreCase(bodyTag) ||
04375       aTag.EqualsIgnoreCase(tdTag) ||
04376       aTag.EqualsIgnoreCase(thTag) ||
04377       aTag.EqualsIgnoreCase(captionTag) )
04378   {
04379     aIsTag = PR_TRUE;
04380   }
04381   else {
04382     aIsTag = PR_FALSE;
04383   }
04384   return NS_OK;
04385 }
04386 
04387 NS_IMETHODIMP
04388 nsHTMLEditor::IsSubordinateBlock(nsString &aTag, PRBool &aIsTag)
04389 {
04390   static char p[] = "p";
04391   static char h1[] = "h1";
04392   static char h2[] = "h2";
04393   static char h3[] = "h3";
04394   static char h4[] = "h4";
04395   static char h5[] = "h5";
04396   static char h6[] = "h6";
04397   static char address[] = "address";
04398   static char pre[] = "pre";
04399   static char li[] = "li";
04400   static char dt[] = "dt";
04401   static char dd[] = "dd";
04402   if (aTag.EqualsIgnoreCase(p)  ||
04403       aTag.EqualsIgnoreCase(h1) ||
04404       aTag.EqualsIgnoreCase(h2) ||
04405       aTag.EqualsIgnoreCase(h3) ||
04406       aTag.EqualsIgnoreCase(h4) ||
04407       aTag.EqualsIgnoreCase(h5) ||
04408       aTag.EqualsIgnoreCase(h6) ||
04409       aTag.EqualsIgnoreCase(address) ||
04410       aTag.EqualsIgnoreCase(pre) ||
04411       aTag.EqualsIgnoreCase(li) ||
04412       aTag.EqualsIgnoreCase(dt) ||
04413       aTag.EqualsIgnoreCase(dd) )
04414   {
04415     aIsTag = PR_TRUE;
04416   }
04417   else {
04418     aIsTag = PR_FALSE;
04419   }
04420   return NS_OK;
04421 }
04422 
04423 
04424 
04426 // GetEnclosingTable: find ancestor who is a table, if any
04427 //                  
04428 nsCOMPtr<nsIDOMNode> 
04429 nsHTMLEditor::GetEnclosingTable(nsIDOMNode *aNode)
04430 {
04431   NS_PRECONDITION(aNode, "null node passed to nsHTMLEditor::GetEnclosingTable");
04432   nsCOMPtr<nsIDOMNode> tbl, tmp, node = aNode;
04433 
04434   while (!tbl)
04435   {
04436     tmp = GetBlockNodeParent(node);
04437     if (!tmp) break;
04438     if (nsHTMLEditUtils::IsTable(tmp)) tbl = tmp;
04439     node = tmp;
04440   }
04441   return tbl;
04442 }
04443 
04444 #ifdef XP_MAC
04445 #pragma mark -
04446 #endif
04447 
04448 void nsHTMLEditor::ClearInlineStylesCache()
04449 {
04450   mCachedNode = nsnull;
04451 }
04452 
04453 #ifdef PRE_NODE_IN_BODY
04454 nsCOMPtr<nsIDOMElement> nsHTMLEditor::FindPreElement()
04455 {
04456   nsCOMPtr<nsIDOMDocument> domdoc;
04457   nsEditor::GetDocument(getter_AddRefs(domdoc));
04458   if (!domdoc)
04459     return 0;
04460 
04461   nsCOMPtr<nsIDocument> doc (do_QueryInterface(domdoc));
04462   if (!doc)
04463     return 0;
04464 
04465   nsCOMPtr<nsIContent> rootContent;
04466   doc->GetRootContent(getter_AddRefs(rootContent));
04467   if (!rootContent)
04468     return 0;
04469 
04470   nsCOMPtr<nsIDOMNode> rootNode (do_QueryInterface(rootContent));
04471   if (!rootNode)
04472     return 0;
04473 
04474   nsString prestr ("PRE");  // GetFirstNodeOfType requires capitals
04475   nsCOMPtr<nsIDOMNode> preNode;
04476   if (NS_FAILED(nsEditor::GetFirstNodeOfType(rootNode, prestr,
04477                                                  getter_AddRefs(preNode))))
04478     return 0;
04479 
04480   return do_QueryInterface(preNode);
04481 }
04482 #endif /* PRE_NODE_IN_BODY */
04483 
04484 /* this method scans the selection for adjacent text nodes
04485  * and collapses them into a single text node.
04486  * "adjacent" means literally adjacent siblings of the same parent.
04487  * Uses nsEditor::JoinNodes so action is undoable. 
04488  * Should be called within the context of a batch transaction.
04489  */
04490 NS_IMETHODIMP
04491 nsHTMLEditor::CollapseAdjacentTextNodes(nsIDOMRange *aInRange)
04492 {
04493   if (!aInRange) return NS_ERROR_NULL_POINTER;
04494   nsAutoTxnsConserveSelection dontSpazMySelection(this);
04495   nsVoidArray textNodes;  // we can't actually do anything during iteration, so store the text nodes in an array
04496                           // don't bother ref counting them because we know we can hold them for the 
04497                           // lifetime of this method
04498 
04499 
04500   // build a list of editable text nodes
04501   nsresult result;
04502   nsCOMPtr<nsIContentIterator> iter =
04503     do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &result);
04504   if (NS_FAILED(result)) return result;
04505 
04506   iter->Init(aInRange);
04507 
04508   while (!iter->IsDone())
04509   {
04510     nsIContent *content = iter->GetCurrentNode();  
04511 
04512     nsCOMPtr<nsIDOMCharacterData> text = do_QueryInterface(content);
04513     nsCOMPtr<nsIDOMNode>          node = do_QueryInterface(content);
04514     if (text && node && IsEditable(node))
04515     {
04516       textNodes.AppendElement(node.get());
04517     }
04518 
04519     iter->Next();
04520   }
04521 
04522   // now that I have a list of text nodes, collapse adjacent text nodes
04523   // NOTE: assumption that JoinNodes keeps the righthand node
04524   while (textNodes.Count() > 1)
04525   {
04526     // we assume a textNodes entry can't be nsnull
04527     nsIDOMNode *leftTextNode = (nsIDOMNode *)(textNodes.ElementAt(0));
04528     nsIDOMNode *rightTextNode = (nsIDOMNode *)(textNodes.ElementAt(1));
04529     NS_ASSERTION(leftTextNode && rightTextNode,"left or rightTextNode null in CollapseAdjacentTextNodes");
04530 
04531     // get the prev sibling of the right node, and see if it's leftTextNode
04532     nsCOMPtr<nsIDOMNode> prevSibOfRightNode;
04533     result = GetPriorHTMLSibling(rightTextNode, address_of(prevSibOfRightNode));
04534     if (NS_FAILED(result)) return result;
04535     if (prevSibOfRightNode && (prevSibOfRightNode == leftTextNode))
04536     {
04537       nsCOMPtr<nsIDOMNode> parent;
04538       result = rightTextNode->GetParentNode(getter_AddRefs(parent));
04539       if (NS_FAILED(result)) return result;
04540       if (!parent) return NS_ERROR_NULL_POINTER;
04541       result = JoinNodes(leftTextNode, rightTextNode, parent);
04542       if (NS_FAILED(result)) return result;
04543     }
04544 
04545     textNodes.RemoveElementAt(0); // remove the leftmost text node from the list
04546   }
04547 
04548   return result;
04549 }
04550 
04551 NS_IMETHODIMP
04552 nsHTMLEditor::GetNextElementByTagName(nsIDOMElement    *aCurrentElement,
04553                                       const nsAString   *aTagName,
04554                                       nsIDOMElement   **aReturn)
04555 {
04556   nsresult res = NS_OK;
04557   if (!aCurrentElement || !aTagName || !aReturn)
04558     return NS_ERROR_NULL_POINTER;
04559 
04560   nsCOMPtr<nsIAtom> tagAtom = do_GetAtom(*aTagName);
04561   if (!tagAtom) { return NS_ERROR_NULL_POINTER; }
04562   if (tagAtom==nsEditProperty::th)
04563     tagAtom=nsEditProperty::td;
04564 
04565   nsCOMPtr<nsIDOMNode> currentNode = do_QueryInterface(aCurrentElement);
04566   if (!currentNode)
04567     return NS_ERROR_FAILURE;
04568 
04569   *aReturn = nsnull;
04570 
04571   nsCOMPtr<nsIDOMNode> nextNode;
04572   PRBool done = PR_FALSE;
04573 
04574   do {
04575     res = GetNextNode(currentNode, PR_TRUE, address_of(nextNode));
04576     if (NS_FAILED(res)) return res;
04577     if (!nextNode) break;
04578 
04579     if (GetTag(currentNode) == tagAtom)
04580     {
04581       nsCOMPtr<nsIDOMElement> element = do_QueryInterface(currentNode);
04582       if (!element) return NS_ERROR_NULL_POINTER;
04583 
04584       *aReturn = element;
04585       NS_ADDREF(*aReturn);
04586       done = PR_TRUE;
04587       return NS_OK;
04588     }
04589     currentNode = nextNode;
04590   } while (!done);
04591 
04592   return res;
04593 }
04594 
04595 NS_IMETHODIMP 
04596 nsHTMLEditor::SetSelectionAtDocumentStart(nsISelection *aSelection)
04597 {
04598   nsIDOMElement *rootElement = GetRoot();  
04599   if (!rootElement)
04600     return NS_ERROR_NULL_POINTER;
04601 
04602   return aSelection->Collapse(rootElement,0);
04603 }
04604 
04605 #ifdef XP_MAC
04606 #pragma mark -
04607 #endif
04608 
04610 // RemoveBlockContainer: remove inNode, reparenting it's children into their
04611 //                  the parent of inNode.  In addition, INSERT ANY BR's NEEDED
04612 //                  TO PRESERVE IDENTITY OF REMOVED BLOCK.
04613 //
04614 nsresult
04615 nsHTMLEditor::RemoveBlockContainer(nsIDOMNode *inNode)
04616 {
04617   if (!inNode)
04618     return NS_ERROR_NULL_POINTER;
04619   nsresult res;
04620   nsCOMPtr<nsIDOMNode> sibling, child, unused;
04621   
04622   // Two possibilities: the container cold be empty of editable content.
04623   // If that is the case, we need to compare what is before and after inNode
04624   // to determine if we need a br.
04625   // Or it could not be empty, in which case we have to compare previous
04626   // sibling and first child to determine if we need a leading br,
04627   // and compare following sibling and last child to determine if we need a
04628   // trailing br.
04629   
04630   res = GetFirstEditableChild(inNode, address_of(child));
04631   if (NS_FAILED(res)) return res;
04632   
04633   if (child)  // the case of inNode not being empty
04634   {
04635     // we need a br at start unless:
04636     // 1) previous sibling of inNode is a block, OR
04637     // 2) previous sibling of inNode is a br, OR
04638     // 3) first child of inNode is a block OR
04639     // 4) either is null
04640     
04641     res = GetPriorHTMLSibling(inNode, address_of(sibling));
04642     if (NS_FAILED(res)) return res;
04643     if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling))
04644     {
04645       res = GetFirstEditableChild(inNode, address_of(child));
04646       if (NS_FAILED(res)) return res;
04647       if (child && !IsBlockNode(child))
04648       {
04649         // insert br node
04650         res = CreateBR(inNode, 0, address_of(unused));
04651         if (NS_FAILED(res)) return res;
04652       }
04653     }
04654     
04655     // we need a br at end unless:
04656     // 1) following sibling of inNode is a block, OR
04657     // 2) last child of inNode is a block, OR
04658     // 3) last child of inNode is a block OR
04659     // 4) either is null
04660 
04661     res = GetNextHTMLSibling(inNode, address_of(sibling));
04662     if (NS_FAILED(res)) return res;
04663     if (sibling && !IsBlockNode(sibling))
04664     {
04665       res = GetLastEditableChild(inNode, address_of(child));
04666       if (NS_FAILED(res)) return res;
04667       if (child && !IsBlockNode(child) && !nsTextEditUtils::IsBreak(child))
04668       {
04669         // insert br node
04670         PRUint32 len;
04671         res = GetLengthOfDOMNode(inNode, len);
04672         if (NS_FAILED(res)) return res;
04673         res = CreateBR(inNode, (PRInt32)len, address_of(unused));
04674         if (NS_FAILED(res)) return res;
04675       }
04676     }
04677   }
04678   else  // the case of inNode being empty
04679   {
04680     // we need a br at start unless:
04681     // 1) previous sibling of inNode is a block, OR
04682     // 2) previous sibling of inNode is a br, OR
04683     // 3) following sibling of inNode is a block, OR
04684     // 4) following sibling of inNode is a br OR
04685     // 5) either is null
04686     res = GetPriorHTMLSibling(inNode, address_of(sibling));
04687     if (NS_FAILED(res)) return res;
04688     if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling))
04689     {
04690       res = GetNextHTMLSibling(inNode, address_of(sibling));
04691       if (NS_FAILED(res)) return res;
04692       if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling))
04693       {
04694         // insert br node
04695         res = CreateBR(inNode, 0, address_of(unused));
04696         if (NS_FAILED(res)) return res;
04697       }
04698     }
04699   }
04700     
04701   // now remove container
04702   return RemoveContainer(inNode);
04703 }
04704 
04705 
04707 // GetPriorHTMLSibling: returns the previous editable sibling, if there is
04708 //                   one within the parent
04709 //                       
04710 nsresult
04711 nsHTMLEditor::GetPriorHTMLSibling(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode)
04712 {
04713   if (!outNode || !inNode) return NS_ERROR_NULL_POINTER;
04714   nsresult res = NS_OK;
04715   *outNode = nsnull;
04716   nsCOMPtr<nsIDOMNode> temp, node = do_QueryInterface(inNode);
04717   
04718   while (1)
04719   {
04720     res = node->GetPreviousSibling(getter_AddRefs(temp));
04721     if (NS_FAILED(res)) return res;
04722     if (!temp) return NS_OK;  // return null sibling
04723     // if it's editable, we're done
04724     if (IsEditable(temp)) break;
04725     // otherwise try again
04726     node = temp;
04727   }
04728   *outNode = temp;
04729   return res;
04730 }
04731 
04732 
04733 
04735 // GetPriorHTMLSibling: returns the previous editable sibling, if there is
04736 //                   one within the parent.  just like above routine but
04737 //                   takes a parent/offset instead of a node.
04738 //                       
04739 nsresult
04740 nsHTMLEditor::GetPriorHTMLSibling(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outNode)
04741 {
04742   if (!outNode || !inParent) return NS_ERROR_NULL_POINTER;
04743   nsresult res = NS_OK;
04744   *outNode = nsnull;
04745   if (!inOffset) return NS_OK;  // return null sibling if at offset zero
04746   nsCOMPtr<nsIDOMNode> node = nsEditor::GetChildAt(inParent,inOffset-1);
04747   if (IsEditable(node)) 
04748   {
04749     *outNode = node;
04750     return res;
04751   }
04752   // else
04753   return GetPriorHTMLSibling(node, outNode);
04754 }
04755 
04756 
04757 
04759 // GetNextHTMLSibling: returns the next editable sibling, if there is
04760 //                   one within the parent
04761 //                       
04762 nsresult
04763 nsHTMLEditor::GetNextHTMLSibling(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode)
04764 {
04765   if (!outNode) return NS_ERROR_NULL_POINTER;
04766   nsresult res = NS_OK;
04767   *outNode = nsnull;
04768   nsCOMPtr<nsIDOMNode> temp, node = do_QueryInterface(inNode);
04769   
04770   while (1)
04771   {
04772     res = node->GetNextSibling(getter_AddRefs(temp));
04773     if (NS_FAILED(res)) return res;
04774     if (!temp) return NS_OK;  // return null sibling
04775     // if it's editable, we're done
04776     if (IsEditable(temp)) break;
04777     // otherwise try again
04778     node = temp;
04779   }
04780   *outNode = temp;
04781   return res;
04782 }
04783 
04784 
04785 
04787 // GetNextHTMLSibling: returns the next editable sibling, if there is
04788 //                   one within the parent.  just like above routine but
04789 //                   takes a parent/offset instead of a node.
04790 //                       
04791 nsresult
04792 nsHTMLEditor::GetNextHTMLSibling(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outNode)
04793 {
04794   if (!outNode || !inParent) return NS_ERROR_NULL_POINTER;
04795   nsresult res = NS_OK;
04796   *outNode = nsnull;
04797   nsCOMPtr<nsIDOMNode> node = nsEditor::GetChildAt(inParent,inOffset);
04798   if (!node) return NS_OK; // return null sibling if no sibling
04799   if (IsEditable(node)) 
04800   {
04801     *outNode = node;
04802     return res;
04803   }
04804   // else
04805   return GetPriorHTMLSibling(node, outNode);
04806 }
04807 
04808 
04809 
04811 // GetPriorHTMLNode: returns the previous editable leaf node, if there is
04812 //                   one within the <body>
04813 //
04814 nsresult
04815 nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode, PRBool bNoBlockCrossing)
04816 {
04817   if (!outNode) return NS_ERROR_NULL_POINTER;
04818   nsresult res = GetPriorNode(inNode, PR_TRUE, address_of(*outNode), bNoBlockCrossing);
04819   if (NS_FAILED(res)) return res;
04820   
04821   // if it's not in the body, then zero it out
04822   if (*outNode && !nsTextEditUtils::InBody(*outNode, this))
04823   {
04824     *outNode = nsnull;
04825   }
04826   return res;
04827 }
04828 
04829 
04831 // GetPriorHTMLNode: same as above but takes {parent,offset} instead of node
04832 //                       
04833 nsresult
04834 nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outNode, PRBool bNoBlockCrossing)
04835 {
04836   if (!outNode) return NS_ERROR_NULL_POINTER;
04837   nsresult res = GetPriorNode(inParent, inOffset, PR_TRUE, address_of(*outNode), bNoBlockCrossing);
04838   if (NS_FAILED(res)) return res;
04839   
04840   // if it's not in the body, then zero it out
04841   if (*outNode && !nsTextEditUtils::InBody(*outNode, this))
04842   {
04843     *outNode = nsnull;
04844   }
04845   return res;
04846 }
04847 
04848 
04850 // GetNextHTMLNode: returns the next editable leaf node, if there is
04851 //                   one within the <body>
04852 //                       
04853 nsresult
04854 nsHTMLEditor::GetNextHTMLNode(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode, PRBool bNoBlockCrossing)
04855 {
04856   if (!outNode) return NS_ERROR_NULL_POINTER;
04857   nsresult res = GetNextNode(inNode, PR_TRUE, address_of(*outNode), bNoBlockCrossing);
04858   if (NS_FAILED(res)) return res;
04859   
04860   // if it's not in the body, then zero it out
04861   if (*outNode && !nsTextEditUtils::InBody(*outNode, this))
04862   {
04863     *outNode = nsnull;
04864   }
04865   return res;
04866 }
04867 
04868 
04870 // GetNHTMLextNode: same as above but takes {parent,offset} instead of node
04871 //                       
04872 nsresult
04873 nsHTMLEditor::GetNextHTMLNode(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outNode, PRBool bNoBlockCrossing)
04874 {
04875   if (!outNode) return NS_ERROR_NULL_POINTER;
04876   nsresult res = GetNextNode(inParent, inOffset, PR_TRUE, address_of(*outNode), bNoBlockCrossing);
04877   if (NS_FAILED(res)) return res;
04878   
04879   // if it's not in the body, then zero it out
04880   if (*outNode && !nsTextEditUtils::InBody(*outNode, this))
04881   {
04882     *outNode = nsnull;
04883   }
04884   return res;
04885 }
04886 
04887 
04888 nsresult 
04889 nsHTMLEditor::IsFirstEditableChild( nsIDOMNode *aNode, PRBool *aOutIsFirst)
04890 {
04891   // check parms
04892   if (!aOutIsFirst || !aNode) return NS_ERROR_NULL_POINTER;
04893   
04894   // init out parms
04895   *aOutIsFirst = PR_FALSE;
04896   
04897   // find first editable child and compare it to aNode
04898   nsCOMPtr<nsIDOMNode> parent, firstChild;
04899   nsresult res = aNode->GetParentNode(getter_AddRefs(parent));
04900   if (NS_FAILED(res)) return res;
04901   if (!parent) return NS_ERROR_FAILURE;
04902   res = GetFirstEditableChild(parent, address_of(firstChild));
04903   if (NS_FAILED(res)) return res;
04904   
04905   *aOutIsFirst = (firstChild.get() == aNode);
04906   return res;
04907 }
04908 
04909 
04910 nsresult 
04911 nsHTMLEditor::IsLastEditableChild( nsIDOMNode *aNode, PRBool *aOutIsLast)
04912 {
04913   // check parms
04914   if (!aOutIsLast || !aNode) return NS_ERROR_NULL_POINTER;
04915   
04916   // init out parms
04917   *aOutIsLast = PR_FALSE;
04918   
04919   // find last editable child and compare it to aNode
04920   nsCOMPtr<nsIDOMNode> parent, lastChild;
04921   nsresult res = aNode->GetParentNode(getter_AddRefs(parent));
04922   if (NS_FAILED(res)) return res;
04923   if (!parent) return NS_ERROR_FAILURE;
04924   res = GetLastEditableChild(parent, address_of(lastChild));
04925   if (NS_FAILED(res)) return res;
04926   
04927   *aOutIsLast = (lastChild.get() == aNode);
04928   return res;
04929 }
04930 
04931 
04932 nsresult 
04933 nsHTMLEditor::GetFirstEditableChild( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutFirstChild)
04934 {
04935   // check parms
04936   if (!aOutFirstChild || !aNode) return NS_ERROR_NULL_POINTER;
04937   
04938   // init out parms
04939   *aOutFirstChild = nsnull;
04940   
04941   // find first editable child
04942   nsCOMPtr<nsIDOMNode> child;
04943   nsresult res = aNode->GetFirstChild(getter_AddRefs(child));
04944   if (NS_FAILED(res)) return res;
04945   
04946   while (child && !IsEditable(child))
04947   {
04948     nsCOMPtr<nsIDOMNode> tmp;
04949     res = child->GetNextSibling(getter_AddRefs(tmp));
04950     if (NS_FAILED(res)) return res;
04951     if (!tmp) return NS_ERROR_FAILURE;
04952     child = tmp;
04953   }
04954   
04955   *aOutFirstChild = child;
04956   return res;
04957 }
04958 
04959 
04960 nsresult 
04961 nsHTMLEditor::GetLastEditableChild( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutLastChild)
04962 {
04963   // check parms
04964   if (!aOutLastChild || !aNode) return NS_ERROR_NULL_POINTER;
04965   
04966   // init out parms
04967   *aOutLastChild = aNode;
04968   
04969   // find last editable child
04970   nsCOMPtr<nsIDOMNode> child;
04971   nsresult res = aNode->GetLastChild(getter_AddRefs(child));
04972   if (NS_FAILED(res)) return res;
04973   
04974   while (child && !IsEditable(child))
04975   {
04976     nsCOMPtr<nsIDOMNode> tmp;
04977     res = child->GetPreviousSibling(getter_AddRefs(tmp));
04978     if (NS_FAILED(res)) return res;
04979     if (!tmp) return NS_ERROR_FAILURE;
04980     child = tmp;
04981   }
04982   
04983   *aOutLastChild = child;
04984   return res;
04985 }
04986 
04987 nsresult 
04988 nsHTMLEditor::GetFirstEditableLeaf( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutFirstLeaf)
04989 {
04990   // check parms
04991   if (!aOutFirstLeaf || !aNode) return NS_ERROR_NULL_POINTER;
04992   
04993   // init out parms
04994   *aOutFirstLeaf = aNode;
04995   
04996   // find leftmost leaf
04997   nsCOMPtr<nsIDOMNode> child;
04998   nsresult res = NS_OK;
04999   child = GetLeftmostChild(aNode);  
05000   while (child && (!IsEditable(child) || !nsEditorUtils::IsLeafNode(child)))
05001   {
05002     nsCOMPtr<nsIDOMNode> tmp;
05003     res = GetNextHTMLNode(child, address_of(tmp));
05004     if (NS_FAILED(res)) return res;
05005     if (!tmp) return NS_ERROR_FAILURE;
05006     
05007     // only accept nodes that are descendants of aNode
05008     if (nsEditorUtils::IsDescendantOf(tmp, aNode))
05009       child = tmp;
05010     else
05011     {
05012       child = nsnull;  // this will abort the loop
05013     }
05014   }
05015   
05016   *aOutFirstLeaf = child;
05017   return res;
05018 }
05019 
05020 
05021 nsresult 
05022 nsHTMLEditor::GetLastEditableLeaf( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutLastLeaf)
05023 {
05024   // check parms
05025   if (!aOutLastLeaf || !aNode) return NS_ERROR_NULL_POINTER;
05026   
05027   // init out parms
05028   *aOutLastLeaf = nsnull;
05029   
05030   // find rightmost leaf
05031   nsCOMPtr<nsIDOMNode> child;
05032   nsresult res = NS_OK;
05033   child = GetRightmostChild(aNode, PR_FALSE);  
05034   while (child && (!IsEditable(child) || !nsEditorUtils::IsLeafNode(child)))
05035   {
05036     nsCOMPtr<nsIDOMNode> tmp;
05037     res = GetPriorHTMLNode(child, address_of(tmp));
05038     if (NS_FAILED(res)) return res;
05039     if (!tmp) return NS_ERROR_FAILURE;
05040     
05041     // only accept nodes that are descendants of aNode
05042     if (nsEditorUtils::IsDescendantOf(tmp, aNode))
05043       child = tmp;
05044     else
05045     {
05046       child = nsnull;
05047     }
05048   }
05049   
05050   *aOutLastLeaf = child;
05051   return res;
05052 }
05053 
05054 PRBool
05055 nsHTMLEditor::IsTextInDirtyFrameVisible(nsIDOMNode *aNode)
05056 {
05057   PRBool isEmptyTextNode;
05058   nsresult res = IsVisTextNode(aNode, &isEmptyTextNode, PR_FALSE);
05059   if (NS_FAILED(res))
05060   {
05061     // We are following the historical decision:
05062     //   if we don't know, we say it's visible...
05063 
05064     return PR_TRUE;
05065   }
05066 
05067   return !isEmptyTextNode;
05068 }
05069 
05070 
05072 // IsVisTextNode: figure out if textnode aTextNode has any visible content.
05073 //                  
05074 nsresult
05075 nsHTMLEditor::IsVisTextNode( nsIDOMNode *aNode, 
05076                              PRBool *outIsEmptyNode, 
05077                              PRBool aSafeToAskFrames)
05078 {
05079   if (!aNode || !outIsEmptyNode) 
05080     return NS_ERROR_NULL_POINTER;
05081   *outIsEmptyNode = PR_TRUE;
05082   nsresult res = NS_OK;
05083 
05084   nsCOMPtr<nsITextContent> textContent = do_QueryInterface(aNode);
05085   // callers job to only call us with text nodes
05086   if (!textContent) 
05087     return NS_ERROR_NULL_POINTER;
05088   PRUint32 length = textContent->TextLength();
05089   if (aSafeToAskFrames)
05090   {
05091     nsCOMPtr<nsISelectionController> selCon;
05092     res = GetSelectionController(getter_AddRefs(selCon));
05093     if (NS_FAILED(res)) return res;
05094     if (!selCon) return NS_ERROR_FAILURE;
05095     PRBool isVisible = PR_FALSE;
05096     // ask the selection controller for information about whether any
05097     // of the data in the node is really rendered.  This is really
05098     // something that frames know about, but we aren't supposed to talk to frames.
05099     // So we put a call in the selection controller interface, since it's already
05100     // in bed with frames anyway.  (this is a fix for bug 22227, and a
05101     // partial fix for bug 46209)
05102     res = selCon->CheckVisibility(aNode, 0, length, &isVisible);
05103     if (NS_FAILED(res)) return res;
05104     if (isVisible) 
05105     {
05106       *outIsEmptyNode = PR_FALSE;
05107     }
05108   }
05109   else if (length)
05110   {
05111     if (textContent->IsOnlyWhitespace())
05112     {
05113       nsWSRunObject wsRunObj(this, aNode, 0);
05114       nsCOMPtr<nsIDOMNode> visNode;
05115       PRInt32 outVisOffset=0;
05116       PRInt16 visType=0;
05117       res = wsRunObj.NextVisibleNode(aNode, 0, address_of(visNode), &outVisOffset, &visType);
05118       if (NS_FAILED(res)) return res;
05119       if ( (visType == nsWSRunObject::eNormalWS) ||
05120            (visType == nsWSRunObject::eText) )
05121       {
05122         *outIsEmptyNode = (aNode != visNode);
05123       }
05124     }
05125     else
05126     {
05127       *outIsEmptyNode = PR_FALSE;
05128     }
05129   }
05130   return NS_OK;  
05131 }
05132   
05133 
05135 // IsEmptyNode: figure out if aNode is an empty node.
05136 //               A block can have children and still be considered empty,
05137 //               if the children are empty or non-editable.
05138 //                  
05139 nsresult
05140 nsHTMLEditor::IsEmptyNode( nsIDOMNode *aNode, 
05141                            PRBool *outIsEmptyNode, 
05142                            PRBool aSingleBRDoesntCount,
05143                            PRBool aListOrCellNotEmpty,
05144                            PRBool aSafeToAskFrames)
05145 {
05146   if (!aNode || !outIsEmptyNode) return NS_ERROR_NULL_POINTER;
05147   *outIsEmptyNode = PR_TRUE;
05148   PRBool seenBR = PR_FALSE;
05149   return IsEmptyNodeImpl(aNode, outIsEmptyNode, aSingleBRDoesntCount,
05150                          aListOrCellNotEmpty, aSafeToAskFrames, &seenBR);
05151 }
05152 
05154 // IsEmptyNodeImpl: workhorse for IsEmptyNode.
05155 //                  
05156 nsresult
05157 nsHTMLEditor::IsEmptyNodeImpl( nsIDOMNode *aNode, 
05158                                PRBool *outIsEmptyNode, 
05159                                PRBool aSingleBRDoesntCount,
05160                                PRBool aListOrCellNotEmpty,
05161                                PRBool aSafeToAskFrames,
05162                                PRBool *aSeenBR)
05163 {
05164   if (!aNode || !outIsEmptyNode || !aSeenBR) return NS_ERROR_NULL_POINTER;
05165   nsresult res = NS_OK;
05166 
05167   if (nsEditor::IsTextNode(aNode))
05168   {
05169     res = IsVisTextNode(aNode, outIsEmptyNode, aSafeToAskFrames);
05170     return res;
05171   }
05172 
05173   // if it's not a text node (handled above) and it's not a container,
05174   // then we dont call it empty (it's an <hr>, or <br>, etc).
05175   // Also, if it's an anchor then dont treat it as empty - even though
05176   // anchors are containers, named anchors are "empty" but we don't
05177   // want to treat them as such.  Also, don't call ListItems or table
05178   // cells empty if caller desires.  Form Widgets not empty.
05179   if (!IsContainer(aNode) || nsHTMLEditUtils::IsNamedAnchor(aNode) ||
05180         nsHTMLEditUtils::IsFormWidget(aNode)                       ||
05181        (aListOrCellNotEmpty && nsHTMLEditUtils::IsListItem(aNode)) ||
05182        (aListOrCellNotEmpty && nsHTMLEditUtils::IsTableCell(aNode)) ) 
05183   {
05184     *outIsEmptyNode = PR_FALSE;
05185     return NS_OK;
05186   }
05187     
05188   // need this for later
05189   PRBool isListItemOrCell = 
05190        nsHTMLEditUtils::IsListItem(aNode) || nsHTMLEditUtils::IsTableCell(aNode);
05191        
05192   // loop over children of node. if no children, or all children are either 
05193   // empty text nodes or non-editable, then node qualifies as empty
05194   nsCOMPtr<nsIDOMNode> child;
05195   aNode->GetFirstChild(getter_AddRefs(child));
05196    
05197   while (child)
05198   {
05199     nsCOMPtr<nsIDOMNode> node = child;
05200     // is the node editable and non-empty?  if so, return false
05201     if (nsEditor::IsEditable(node))
05202     {
05203       if (nsEditor::IsTextNode(node))
05204       {
05205         res = IsVisTextNode(node, outIsEmptyNode, aSafeToAskFrames);
05206         if (NS_FAILED(res)) return res;
05207         if (!*outIsEmptyNode) return NS_OK;  // break out if we find we aren't emtpy
05208       }
05209       else  // an editable, non-text node.  we need to check it's content.
05210       {
05211         // is it the node we are iterating over?
05212         if (node == aNode) break;
05213         else if (aSingleBRDoesntCount && !*aSeenBR && nsTextEditUtils::IsBreak(node))
05214         {
05215           // the first br in a block doesn't count if the caller so indicated
05216           *aSeenBR = PR_TRUE;
05217         }
05218         else
05219         {
05220           // is it an empty node of some sort?
05221           // note: list items or table cells are not considered empty
05222           // if they contain other lists or tables
05223           if (isListItemOrCell)
05224           {
05225             if (nsHTMLEditUtils::IsList(node) || nsHTMLEditUtils::IsTable(node))
05226             { // break out if we find we aren't empty
05227               *outIsEmptyNode = PR_FALSE;
05228               return NS_OK;
05229             }
05230           }
05231           // is it a form widget?
05232           else if (nsHTMLEditUtils::IsFormWidget(aNode))
05233           { // break out if we find we aren't empty
05234             *outIsEmptyNode = PR_FALSE;
05235             return NS_OK;
05236           }
05237           
05238           PRBool isEmptyNode = PR_TRUE;
05239           res = IsEmptyNodeImpl(node, &isEmptyNode, aSingleBRDoesntCount, 
05240                                 aListOrCellNotEmpty, aSafeToAskFrames, aSeenBR);
05241           if (NS_FAILED(res)) return res;
05242           if (!isEmptyNode) 
05243           { 
05244             // otherwise it ain't empty
05245             *outIsEmptyNode = PR_FALSE;
05246             return NS_OK;
05247           }
05248         }
05249       }
05250     }
05251     node->GetNextSibling(getter_AddRefs(child));
05252   }
05253   
05254   return NS_OK;
05255 }
05256 
05257 // add to aElement the CSS inline styles corresponding to the HTML attribute
05258 // aAttribute with its value aValue
05259 nsresult
05260 nsHTMLEditor::SetAttributeOrEquivalent(nsIDOMElement * aElement,
05261                                        const nsAString & aAttribute,
05262                                        const nsAString & aValue,
05263                                        PRBool aSuppressTransaction)
05264 {
05265   PRBool useCSS;
05266   nsresult res = NS_OK;
05267   GetIsCSSEnabled(&useCSS);
05268   if (useCSS && mHTMLCSSUtils) {
05269     PRInt32 count;
05270     res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(aElement, nsnull, &aAttribute, &aValue, &count,
05271                                                      aSuppressTransaction);
05272     if (NS_FAILED(res)) return res;
05273     if (count) {
05274       // we found an equivalence ; let's remove the HTML attribute itself if it is set
05275       nsAutoString existingValue;
05276       PRBool wasSet = PR_FALSE;
05277       res = GetAttributeValue(aElement, aAttribute, existingValue, &wasSet);
05278       if (NS_FAILED(res)) return res;
05279       if (wasSet) {
05280         if (aSuppressTransaction)
05281           res = aElement->RemoveAttribute(aAttribute);
05282         else
05283           res = RemoveAttribute(aElement, aAttribute);
05284       }
05285     }
05286     else {
05287       // count is an integer that represents the number of CSS declarations applied to the
05288       // element. If it is zero, we found no equivalence in this implementation for the
05289       // attribute
05290       if (aAttribute.EqualsLiteral("style")) {
05291         // if it is the style attribute, just add the new value to the existing style
05292         // attribute's value
05293         nsAutoString existingValue;
05294         PRBool wasSet = PR_FALSE;
05295         res = GetAttributeValue(aElement, NS_LITERAL_STRING("style"), existingValue, &wasSet);
05296         if (NS_FAILED(res)) return res;
05297         existingValue.AppendLiteral(" ");
05298         existingValue.Append(aValue);
05299         if (aSuppressTransaction)
05300           res = aElement->SetAttribute(aAttribute, existingValue);
05301         else
05302           res = SetAttribute(aElement, aAttribute, existingValue);
05303       }
05304       else {
05305         // we have no CSS equivalence for this attribute and it is not the style
05306         // attribute; let's set it the good'n'old HTML way
05307         if (aSuppressTransaction)
05308           res = aElement->SetAttribute(aAttribute, aValue);
05309         else
05310           res = SetAttribute(aElement, aAttribute, aValue);
05311       }
05312     }
05313   }
05314   else {
05315     // we are not in an HTML+CSS editor; let's set the attribute the HTML way
05316     if (aSuppressTransaction)
05317       res = aElement->SetAttribute(aAttribute, aValue);
05318     else
05319       res = SetAttribute(aElement, aAttribute, aValue);
05320   }  
05321   return res;
05322 }
05323 
05324 nsresult
05325 nsHTMLEditor::RemoveAttributeOrEquivalent(nsIDOMElement * aElement,
05326                                           const nsAString & aAttribute,
05327                                           PRBool aSuppressTransaction)
05328 {
05329   PRBool useCSS;
05330   nsresult res = NS_OK;
05331   GetIsCSSEnabled(&useCSS);
05332   if (useCSS && mHTMLCSSUtils) {
05333     res = mHTMLCSSUtils->RemoveCSSEquivalentToHTMLStyle(aElement, nsnull, &aAttribute, nsnull,
05334                                                         aSuppressTransaction);
05335     if (NS_FAILED(res)) return res;
05336   }
05337 
05338   nsAutoString existingValue;
05339   PRBool wasSet = PR_FALSE;
05340   res = GetAttributeValue(aElement, aAttribute, existingValue, &wasSet);
05341   if (NS_FAILED(res)) return res;
05342   if (wasSet) {
05343     if (aSuppressTransaction)
05344       res = aElement->RemoveAttribute(aAttribute);
05345     else
05346       res = RemoveAttribute(aElement, aAttribute);
05347   }
05348   return res;
05349 }
05350 
05351 nsresult
05352 nsHTMLEditor::SetIsCSSEnabled(PRBool aIsCSSPrefChecked)
05353 {
05354   nsresult  err = NS_ERROR_NOT_INITIALIZED;
05355   if (mHTMLCSSUtils)
05356   {
05357     err = mHTMLCSSUtils->SetCSSEnabled(aIsCSSPrefChecked);
05358   }
05359   return err;
05360 }
05361 
05362 // Set the block background color
05363 NS_IMETHODIMP
05364 nsHTMLEditor::SetCSSBackgroundColor(const nsAString& aColor)
05365 {
05366   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
05367   ForceCompositionEnd();
05368 
05369   nsresult res;
05370   nsCOMPtr<nsISelection>selection;
05371   res = GetSelection(getter_AddRefs(selection));
05372   if (NS_FAILED(res)) return res;
05373   if (!selection) return NS_ERROR_NULL_POINTER;
05374   nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
05375 
05376   PRBool isCollapsed;
05377   selection->GetIsCollapsed(&isCollapsed);
05378 
05379   nsAutoEditBatch batchIt(this);
05380   nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
05381   nsAutoSelectionReset selectionResetter(selection, this);
05382   nsAutoTxnsConserveSelection dontSpazMySelection(this);
05383   
05384   PRBool cancel, handled;
05385   nsTextRulesInfo ruleInfo(nsTextEditRules::kSetTextProperty);
05386   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
05387   if (NS_FAILED(res)) return res;
05388   if (!cancel && !handled)
05389   {
05390     // get selection range enumerator
05391     nsCOMPtr<nsIEnumerator> enumerator;
05392     res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
05393     if (NS_FAILED(res)) return res;
05394     if (!enumerator)    return NS_ERROR_FAILURE;
05395 
05396     // loop thru the ranges in the selection
05397     enumerator->First(); 
05398     nsCOMPtr<nsISupports> currentItem;
05399     nsAutoString bgcolor; bgcolor.AssignLiteral("bgcolor");
05400     nsCOMPtr<nsIDOMNode> cachedBlockParent = nsnull;
05401     while ((NS_ENUMERATOR_FALSE == enumerator->IsDone()))
05402     {
05403       res = enumerator->CurrentItem(getter_AddRefs(currentItem));
05404       if (NS_FAILED(res)) return res;
05405       if (!currentItem)   return NS_ERROR_FAILURE;
05406       
05407       nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
05408       
05409       // check for easy case: both range endpoints in same text node
05410       nsCOMPtr<nsIDOMNode> startNode, endNode;
05411       PRInt32 startOffset, endOffset;
05412       res = range->GetStartContainer(getter_AddRefs(startNode));
05413       if (NS_FAILED(res)) return res;
05414       res = range->GetEndContainer(getter_AddRefs(endNode));
05415       if (NS_FAILED(res)) return res;
05416       res = range->GetStartOffset(&startOffset);
05417       if (NS_FAILED(res)) return res;
05418       res = range->GetEndOffset(&endOffset);
05419       if (NS_FAILED(res)) return res;
05420       if ((startNode == endNode) && IsTextNode(startNode))
05421       {
05422         // let's find the block container of the text node
05423         nsCOMPtr<nsIDOMNode> blockParent;
05424         blockParent = GetBlockNodeParent(startNode);
05425         // and apply the background color to that block container
05426         if (cachedBlockParent != blockParent)
05427         {
05428           cachedBlockParent = blockParent;
05429           nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent);
05430           PRInt32 count;
05431           res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nsnull, &bgcolor, &aColor, &count, PR_FALSE);
05432           if (NS_FAILED(res)) return res;
05433         }
05434       }
05435       else if ((startNode == endNode) && nsTextEditUtils::IsBody(startNode) && isCollapsed)
05436       {
05437         // we have no block in the document, let's apply the background to the body 
05438         nsCOMPtr<nsIDOMElement> element = do_QueryInterface(startNode);
05439         PRInt32 count;
05440         res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nsnull, &bgcolor, &aColor, &count, PR_FALSE);
05441         if (NS_FAILED(res)) return res;
05442       }
05443       else if ((startNode == endNode) && (((endOffset-startOffset) == 1) || (!startOffset && !endOffset)))
05444       {
05445         // a unique node is selected, let's also apply the background color
05446         // to the containing block, possibly the node itself
05447         nsCOMPtr<nsIDOMNode> selectedNode = GetChildAt(startNode, startOffset);
05448         PRBool isBlock =PR_FALSE;
05449         res = NodeIsBlockStatic(selectedNode, &isBlock);
05450         if (NS_FAILED(res)) return res;
05451         nsCOMPtr<nsIDOMNode> blockParent = selectedNode;
05452         if (!isBlock) {
05453           blockParent = GetBlockNodeParent(selectedNode);
05454         }
05455         if (cachedBlockParent != blockParent)
05456         {
05457           cachedBlockParent = blockParent;
05458           nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent);
05459           PRInt32 count;
05460           res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nsnull, &bgcolor, &aColor, &count, PR_FALSE);
05461           if (NS_FAILED(res)) return res;
05462         }
05463       }
05464       else
05465       {
05466         // not the easy case.  range not contained in single text node. 
05467         // there are up to three phases here.  There are all the nodes
05468         // reported by the subtree iterator to be processed.  And there
05469         // are potentially a starting textnode and an ending textnode
05470         // which are only partially contained by the range.
05471         
05472         // lets handle the nodes reported by the iterator.  These nodes
05473         // are entirely contained in the selection range.  We build up
05474         // a list of them (since doing operations on the document during
05475         // iteration would perturb the iterator).
05476 
05477         nsCOMPtr<nsIContentIterator> iter =
05478           do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &res);
05479         if (NS_FAILED(res)) return res;
05480         if (!iter)          return NS_ERROR_FAILURE;
05481 
05482         nsCOMArray<nsIDOMNode> arrayOfNodes;
05483         nsCOMPtr<nsIDOMNode> node;
05484                 
05485         // iterate range and build up array
05486         res = iter->Init(range);
05487         // init returns an error if no nodes in range.
05488         // this can easily happen with the subtree 
05489         // iterator if the selection doesn't contain
05490         // any *whole* nodes.
05491         if (NS_SUCCEEDED(res))
05492         {
05493           while (!iter->IsDone())
05494           {
05495             node = do_QueryInterface(iter->GetCurrentNode());
05496             if (!node)
05497               return NS_ERROR_FAILURE;
05498 
05499             if (IsEditable(node))
05500             {
05501               arrayOfNodes.AppendObject(node);
05502             }
05503 
05504             iter->Next();
05505           }
05506         }
05507         // first check the start parent of the range to see if it needs to 
05508         // be separately handled (it does if it's a text node, due to how the
05509         // subtree iterator works - it will not have reported it).
05510         if (IsTextNode(startNode) && IsEditable(startNode))
05511         {
05512           nsCOMPtr<nsIDOMNode> blockParent;
05513           blockParent = GetBlockNodeParent(startNode);
05514           if (cachedBlockParent != blockParent)
05515           {
05516             cachedBlockParent = blockParent;
05517             nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent);
05518             PRInt32 count;
05519             res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nsnull, &bgcolor, &aColor, &count, PR_FALSE);
05520             if (NS_FAILED(res)) return res;
05521           }
05522         }
05523         
05524         // then loop through the list, set the property on each node
05525         PRInt32 listCount = arrayOfNodes.Count();
05526         PRInt32 j;
05527         for (j = 0; j < listCount; j++)
05528         {
05529           node = arrayOfNodes[j];
05530           // do we have a block here ?
05531           PRBool isBlock =PR_FALSE;
05532           res = NodeIsBlockStatic(node, &isBlock);
05533           if (NS_FAILED(res)) return res;
05534           nsCOMPtr<nsIDOMNode> blockParent = node;
05535           if (!isBlock) {
05536             // no we don't, let's find the block ancestor
05537             blockParent = GetBlockNodeParent(node);
05538           }
05539           if (cachedBlockParent != blockParent)
05540           {
05541             cachedBlockParent = blockParent;
05542             nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent);
05543             PRInt32 count;
05544             // and set the property on it
05545             res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nsnull, &bgcolor, &aColor, &count, PR_FALSE);
05546             if (NS_FAILED(res)) return res;
05547           }
05548         }
05549         arrayOfNodes.Clear();
05550         
05551         // last check the end parent of the range to see if it needs to 
05552         // be separately handled (it does if it's a text node, due to how the
05553         // subtree iterator works - it will not have reported it).
05554         if (IsTextNode(endNode) && IsEditable(endNode))
05555         {
05556           nsCOMPtr<nsIDOMNode> blockParent;
05557           blockParent = GetBlockNodeParent(endNode);
05558           if (cachedBlockParent != blockParent)
05559           {
05560             cachedBlockParent = blockParent;
05561             nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent);
05562             PRInt32 count;
05563             res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nsnull, &bgcolor, &aColor, &count, PR_FALSE);
05564             if (NS_FAILED(res)) return res;
05565           }
05566         }
05567       }
05568       enumerator->Next();
05569     }
05570   }
05571   if (!cancel)
05572   {
05573     // post-process
05574     res = mRules->DidDoAction(selection, &ruleInfo, res);
05575   }
05576   return res;
05577 }
05578 
05579 NS_IMETHODIMP
05580 nsHTMLEditor::SetBackgroundColor(const nsAString& aColor)
05581 {
05582   nsresult res;
05583   PRBool useCSS;
05584   GetIsCSSEnabled(&useCSS);
05585   if (useCSS) {
05586     // if we are in CSS mode, we have to apply the background color to the
05587     // containing block (or the body if we have no block-level element in
05588     // the document)
05589     res = SetCSSBackgroundColor(aColor);
05590   }
05591   else {
05592     // but in HTML mode, we can only set the document's background color
05593     res = SetHTMLBackgroundColor(aColor);
05594   }
05595   return res;
05596 }
05597 
05599 // NodesSameType: do these nodes have the same tag?
05600 //                    
05601 PRBool 
05602 nsHTMLEditor::NodesSameType(nsIDOMNode *aNode1, nsIDOMNode *aNode2)
05603 {
05604   if (!aNode1 || !aNode2) 
05605   {
05606     NS_NOTREACHED("null node passed to nsEditor::NodesSameType()");
05607     return PR_FALSE;
05608   }
05609 
05610   PRBool useCSS;
05611   GetIsCSSEnabled(&useCSS);
05612 
05613   nsIAtom *tag1 = GetTag(aNode1);
05614 
05615   if (tag1 == GetTag(aNode2)) {
05616     if (useCSS && tag1 == nsEditProperty::span) {
05617       if (mHTMLCSSUtils->ElementsSameStyle(aNode1, aNode2)) {
05618         return PR_TRUE;
05619       }
05620     }
05621     else {
05622       return PR_TRUE;
05623     }
05624   }
05625   return PR_FALSE;
05626 }
05627 
05628 NS_IMETHODIMP
05629 nsHTMLEditor::CopyLastEditableChildStyles(nsIDOMNode * aPreviousBlock, nsIDOMNode * aNewBlock,
05630                                           nsIDOMNode **aOutBrNode)
05631 {
05632   *aOutBrNode = nsnull;
05633   nsCOMPtr<nsIDOMNode> child, tmp;
05634   nsresult res;
05635   // first, clear out aNewBlock.  Contract is that we want only the styles from previousBlock.
05636   res = aNewBlock->GetFirstChild(getter_AddRefs(child));
05637   while (NS_SUCCEEDED(res) && child)
05638   {
05639     res = DeleteNode(child);
05640     if (NS_FAILED(res)) return res;
05641     res = aNewBlock->GetFirstChild(getter_AddRefs(child));
05642   }
05643   // now find and clone the styles
05644   child = aPreviousBlock;
05645   tmp = aPreviousBlock;
05646   while (tmp) {
05647     child = tmp;
05648     res = GetLastEditableChild(child, address_of(tmp));
05649     if (NS_FAILED(res)) return res;
05650   }
05651   while (child && nsTextEditUtils::IsBreak(child)) {
05652     nsCOMPtr<nsIDOMNode> priorNode;
05653     res = GetPriorHTMLNode(child, address_of(priorNode));
05654     if (NS_FAILED(res)) return res;
05655     child = priorNode;
05656   }
05657   nsCOMPtr<nsIDOMNode> newStyles = nsnull, deepestStyle = nsnull;
05658   while (child && (child != aPreviousBlock)) {
05659     if (nsHTMLEditUtils::IsInlineStyle(child) ||
05660         nsEditor::NodeIsType(child, nsEditProperty::span)) {
05661       nsAutoString domTagName;
05662       child->GetNodeName(domTagName);
05663       ToLowerCase(domTagName);
05664       if (newStyles) {
05665         nsCOMPtr<nsIDOMNode> newContainer;
05666         res = InsertContainerAbove(newStyles, address_of(newContainer), domTagName);
05667         if (NS_FAILED(res)) return res;
05668         newStyles = newContainer;
05669       }
05670       else {
05671         res = CreateNode(domTagName, aNewBlock, 0, getter_AddRefs(newStyles));
05672         if (NS_FAILED(res)) return res;
05673         deepestStyle = newStyles;
05674       }
05675       res = CloneAttributes(newStyles, child);
05676       if (NS_FAILED(res)) return res;
05677     }
05678     nsCOMPtr<nsIDOMNode> tmp;
05679     res = child->GetParentNode(getter_AddRefs(tmp));
05680     if (NS_FAILED(res)) return res;
05681     child = tmp;
05682   }
05683   if (deepestStyle) {
05684     nsCOMPtr<nsIDOMNode> outBRNode;
05685     res = CreateBR(deepestStyle, 0, address_of(outBRNode));
05686     if (NS_FAILED(res)) return res;
05687     // Getters must addref
05688     *aOutBrNode = outBRNode;
05689     NS_ADDREF(*aOutBrNode);
05690   }
05691   return NS_OK;
05692 }
05693 
05694 nsresult
05695 nsHTMLEditor::GetElementOrigin(nsIDOMElement * aElement, PRInt32 & aX, PRInt32 & aY)
05696 {
05697   // we are going to need the PresShell
05698   if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
05699   nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
05700   if (!ps) return NS_ERROR_NOT_INITIALIZED;
05701 
05702   nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
05703   nsIFrame *frame = 0; // not ref-counted
05704   ps->GetPrimaryFrameFor(content, &frame);
05705 
05706   float t2p = ps->GetPresContext()->TwipsToPixels();
05707 
05708   if (nsHTMLEditUtils::IsHR(aElement)) {
05709     frame = frame->GetNextSibling();
05710   }
05711   PRInt32 offsetX = 0, offsetY = 0;
05712   while (frame) {
05713     // Look for a widget so we can get screen coordinates
05714     nsIView* view = frame->GetViewExternal();
05715     if (view && view->HasWidget())
05716       break;
05717     
05718     // No widget yet, so count up the coordinates of the frame 
05719     nsPoint origin = frame->GetPosition();
05720     offsetX += origin.x;
05721     offsetY += origin.y;
05722 
05723     frame = frame->GetParent();
05724   }
05725 
05726   aX = NSTwipsToIntPixels(offsetX , t2p);
05727   aY = NSTwipsToIntPixels(offsetY , t2p);
05728 
05729   return NS_OK;
05730 }
05731 
05732 nsresult
05733 nsHTMLEditor::EndUpdateViewBatch()
05734 {
05735   nsresult res = nsEditor::EndUpdateViewBatch();
05736   if (NS_FAILED(res)) return res;
05737 
05738   // We may need to show resizing handles or update existing ones after
05739   // all transactions are done. This way of doing is preferred to DOM
05740   // mutation events listeners because all the changes the user can apply
05741   // to a document may result in multiple events, some of them quite hard
05742   // to listen too (in particular when an ancestor of the selection is
05743   // changed but the selection itself is not changed).
05744   if (mUpdateCount == 0) {
05745     nsCOMPtr<nsISelection> selection;
05746     res = GetSelection(getter_AddRefs(selection));
05747     if (NS_FAILED(res)) return res;
05748     if (!selection) return NS_ERROR_NOT_INITIALIZED;
05749     res = CheckSelectionStateForAnonymousButtons(selection);
05750   }
05751   return res;
05752 }
05753 
05754 NS_IMETHODIMP
05755 nsHTMLEditor::IgnoreSpuriousDragEvent(PRBool aIgnoreSpuriousDragEvent)
05756 {
05757   mIgnoreSpuriousDragEvent = aIgnoreSpuriousDragEvent;
05758   return NS_OK;
05759 }
05760 
05761 NS_IMETHODIMP
05762 nsHTMLEditor::GetSelectionContainer(nsIDOMElement ** aReturn)
05763 {
05764   nsCOMPtr<nsISelection>selection;
05765   nsresult res = GetSelection(getter_AddRefs(selection));
05766   // if we don't get the selection, just skip this
05767   if (NS_FAILED(res) || !selection) return res;
05768 
05769   PRBool bCollapsed;
05770   res = selection->GetIsCollapsed(&bCollapsed);
05771   if (NS_FAILED(res)) return res;
05772 
05773   nsCOMPtr<nsIDOMNode> focusNode;
05774 
05775   if (bCollapsed) {
05776     res = selection->GetFocusNode(getter_AddRefs(focusNode));
05777     if (NS_FAILED(res)) return res;
05778   }
05779   else {
05780 
05781     PRInt32 rangeCount;
05782     res = selection->GetRangeCount(&rangeCount);
05783     if (NS_FAILED(res)) return res;
05784 
05785     if (rangeCount == 1) {
05786 
05787       nsCOMPtr<nsIDOMRange> range;
05788       res = selection->GetRangeAt(0, getter_AddRefs(range));
05789       if (NS_FAILED(res)) return res;
05790       if (!range) return NS_ERROR_NULL_POINTER;
05791 
05792       nsCOMPtr<nsIDOMNode> startContainer, endContainer;
05793       res = range->GetStartContainer(getter_AddRefs(startContainer));
05794       if (NS_FAILED(res)) return res;
05795       res = range->GetEndContainer(getter_AddRefs(endContainer));
05796       if (NS_FAILED(res)) return res;
05797       PRInt32 startOffset, endOffset;
05798       res = range->GetStartOffset(&startOffset);
05799       if (NS_FAILED(res)) return res;
05800       res = range->GetEndOffset(&endOffset);
05801       if (NS_FAILED(res)) return res;
05802 
05803       nsCOMPtr<nsIDOMElement> focusElement;
05804       if (startContainer == endContainer && startOffset + 1 == endOffset) {
05805         res = GetSelectedElement(EmptyString(), getter_AddRefs(focusElement));
05806         if (NS_FAILED(res)) return res;
05807         if (focusElement)
05808           focusNode = do_QueryInterface(focusElement);
05809       }
05810       if (!focusNode) {
05811         res = range->GetCommonAncestorContainer(getter_AddRefs(focusNode));
05812         if (NS_FAILED(res)) return res;
05813       }
05814     }
05815     else {
05816       PRInt32 i;
05817       nsCOMPtr<nsIDOMRange> range;
05818       for (i = 0; i < rangeCount; i++)
05819       {
05820         res = selection->GetRangeAt(i, getter_AddRefs(range));
05821         if (NS_FAILED(res)) return res;
05822         nsCOMPtr<nsIDOMNode> startContainer;
05823         range->GetStartContainer(getter_AddRefs(startContainer));
05824         if (!focusNode)
05825           focusNode = startContainer;
05826         else if (focusNode != startContainer) {
05827           res = startContainer->GetParentNode(getter_AddRefs(focusNode));
05828           if (NS_FAILED(res)) return res;
05829           break;
05830         }
05831       }
05832     }
05833   }
05834 
05835   if (focusNode) {
05836     PRUint16 nodeType;
05837     focusNode->GetNodeType(&nodeType);
05838     if (nsIDOMNode::TEXT_NODE == nodeType) {
05839       nsCOMPtr<nsIDOMNode> parent;
05840       res = focusNode->GetParentNode(getter_AddRefs(parent));
05841       if (NS_FAILED(res)) return res;
05842       focusNode = parent;
05843     }
05844   }
05845 
05846   nsCOMPtr<nsIDOMElement> focusElement = do_QueryInterface(focusNode);
05847   *aReturn = focusElement;
05848   NS_IF_ADDREF(*aReturn);
05849 
05850   return NS_OK;
05851 }
05852 
05853 NS_IMETHODIMP
05854 nsHTMLEditor::IsAnonymousElement(nsIDOMElement * aElement, PRBool * aReturn)
05855 {
05856   NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
05857   nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
05858   *aReturn = content->IsNativeAnonymous();
05859   return NS_OK;
05860 }
05861 
05862 nsresult
05863 nsHTMLEditor::SetReturnInParagraphCreatesNewParagraph(PRBool aCreatesNewParagraph)
05864 {
05865   mCRInParagraphCreatesParagraph = aCreatesNewParagraph;
05866   return NS_OK;
05867 }
05868 
05869 nsresult
05870 nsHTMLEditor::GetReturnInParagraphCreatesNewParagraph(PRBool *aCreatesNewParagraph)
05871 {
05872   *aCreatesNewParagraph = mCRInParagraphCreatesParagraph;
05873   return NS_OK;
05874 }