/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: NPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Netscape Public License * Version 1.1 (the "License"); you may not use this file except in * compliance with the License. You may obtain a copy of the License at * http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Pierre Phaneuf * * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the NPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the NPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsICaret.h" #include "nsReadableUtils.h" #include "nsHTMLEditor.h" #include "nsHTMLEditRules.h" #include "nsTextEditUtils.h" #include "nsHTMLEditUtils.h" #include "nsEditorEventListeners.h" #include "TypeInState.h" #include "nsHTMLURIRefObject.h" #include "nsIDOMText.h" #include "nsIDOMNodeList.h" #include "nsIDOMDocument.h" #include "nsIDOMAttr.h" #include "nsIDocument.h" #include "nsIDOMEventReceiver.h" #include "nsIDOMKeyEvent.h" #include "nsIDOMKeyListener.h" #include "nsIDOMMouseListener.h" #include "nsIDOMMouseEvent.h" #include "nsISelection.h" #include "nsISelectionPrivate.h" #include "nsIDOMHTMLAnchorElement.h" #include "nsISelectionController.h" #include "TransactionFactory.h" #include "nsIIndependentSelection.h" //domselections answer to frameselection #include "nsICSSLoader.h" #include "nsICSSStyleSheet.h" #include "nsIHTMLContentContainer.h" #include "nsIDocumentObserver.h" #include "nsIDocumentStateListener.h" #include "nsIStyleContext.h" #include "nsIEnumerator.h" #include "nsIContent.h" #include "nsIContentIterator.h" #include "nsEditorCID.h" #include "nsLayoutCID.h" #include "nsContentCID.h" #include "nsIDOMRange.h" #include "nsIDOMNSRange.h" #include "nsISupportsArray.h" #include "nsVoidArray.h" #include "nsFileSpec.h" #include "nsIFile.h" #include "nsIURL.h" #include "nsIComponentManager.h" #include "nsIServiceManager.h" #include "nsWidgetsCID.h" #include "nsIDocumentEncoder.h" #include "nsIDOMDocumentFragment.h" #include "nsIPresShell.h" #include "nsIPresContext.h" #include "nsIParserService.h" #include "nsParserCIID.h" #include "nsIImage.h" #include "nsAOLCiter.h" #include "nsInternetCiter.h" #include "nsISupportsPrimitives.h" #include "SetDocTitleTxn.h" #include "nsGUIEvent.h" // netwerk #include "nsIURI.h" #include "nsNetUtil.h" // Drag & Drop, Clipboard #include "nsWidgetsCID.h" #include "nsIClipboard.h" #include "nsITransferable.h" #include "nsIDragService.h" #include "nsIDOMNSUIEvent.h" // Transactionas #include "nsStyleSheetTxns.h" // Misc #include "TextEditorTest.h" #include "nsEditorUtils.h" #include "nsIPref.h" // HACK - CID for NS_CTRANSITIONAL_DTD_CID so that we can get at transitional dtd #define NS_CTRANSITIONAL_DTD_CID \ { 0x4611d482, 0x960a, 0x11d4, { 0x8e, 0xb0, 0xb6, 0x17, 0x66, 0x1b, 0x6f, 0x7c } } static NS_DEFINE_CID(kHTMLEditorCID, NS_HTMLEDITOR_CID); static NS_DEFINE_CID(kCContentIteratorCID, NS_CONTENTITERATOR_CID); static NS_DEFINE_IID(kSubtreeIteratorCID, NS_SUBTREEITERATOR_CID); static NS_DEFINE_CID(kCRangeCID, NS_RANGE_CID); static NS_DEFINE_IID(kRangeUtilsCID, NS_RANGEUTILS_CID); static NS_DEFINE_CID(kCDOMSelectionCID, NS_DOMSELECTION_CID); static NS_DEFINE_CID(kPrefServiceCID, NS_PREF_CID); static NS_DEFINE_CID(kParserServiceCID, NS_PARSERSERVICE_CID); static NS_DEFINE_CID(kCTransitionalDTDCID, NS_CTRANSITIONAL_DTD_CID); #if defined(NS_DEBUG) && defined(DEBUG_buster) static PRBool gNoisy = PR_FALSE; #else static const PRBool gNoisy = PR_FALSE; #endif // Some utilities to handle annoying overloading of "A" tag for link and named anchor static char hrefText[] = "href"; static char anchorTxt[] = "anchor"; static char namedanchorText[] = "namedanchor"; // some prototypes for rules creation shortcuts nsresult NS_NewTextEditRules(nsIEditRules** aInstancePtrResult); nsresult NS_NewHTMLEditRules(nsIEditRules** aInstancePtrResult); #define IsLinkTag(s) (s.EqualsIgnoreCase(hrefText)) #define IsNamedAnchorTag(s) (s.EqualsIgnoreCase(anchorTxt) || s.EqualsIgnoreCase(namedanchorText)) nsHTMLEditor::nsHTMLEditor() : nsPlaintextEditor() , mIgnoreSpuriousDragEvent(PR_FALSE) , mTypeInState(nsnull) , mSelectedCellIndex(0) { // Done in nsEditor // NS_INIT_REFCNT(); mBoldAtom = getter_AddRefs(NS_NewAtom("b")); mItalicAtom = getter_AddRefs(NS_NewAtom("i")); mUnderlineAtom = getter_AddRefs(NS_NewAtom("u")); mFontAtom = getter_AddRefs(NS_NewAtom("font")); mLinkAtom = getter_AddRefs(NS_NewAtom("a")); } nsHTMLEditor::~nsHTMLEditor() { // remove the rules as an action listener. Else we get a bad ownership loop later on. // it's ok if the rules aren't a listener; we ignore the error. nsCOMPtr mListener = do_QueryInterface(mRules); RemoveEditActionListener(mListener); //the autopointers will clear themselves up. //but we need to also remove the listeners or we have a leak nsCOMPtrselection; nsresult result = GetSelection(getter_AddRefs(selection)); // if we don't get the selection, just skip this if (NS_SUCCEEDED(result) && selection) { nsCOMPtr selPriv(do_QueryInterface(selection)); nsCOMPtrlistener; listener = do_QueryInterface(mTypeInState); if (listener) { selPriv->RemoveSelectionListener(listener); } } NS_IF_RELEASE(mTypeInState); } NS_IMPL_ADDREF_INHERITED(nsHTMLEditor, nsEditor) NS_IMPL_RELEASE_INHERITED(nsHTMLEditor, nsEditor) NS_IMETHODIMP nsHTMLEditor::QueryInterface(REFNSIID aIID, void** aInstancePtr) { if (!aInstancePtr) return NS_ERROR_NULL_POINTER; *aInstancePtr = nsnull; if (aIID.Equals(NS_GET_IID(nsIPlaintextEditor))) { *aInstancePtr = NS_STATIC_CAST(nsIPlaintextEditor*, this); NS_ADDREF_THIS(); return NS_OK; } if (aIID.Equals(NS_GET_IID(nsIHTMLEditor))) { *aInstancePtr = NS_STATIC_CAST(nsIHTMLEditor*, this); NS_ADDREF_THIS(); return NS_OK; } if (aIID.Equals(NS_GET_IID(nsIEditorMailSupport))) { *aInstancePtr = NS_STATIC_CAST(nsIEditorMailSupport*, this); NS_ADDREF_THIS(); return NS_OK; } if (aIID.Equals(NS_GET_IID(nsITableEditor))) { *aInstancePtr = NS_STATIC_CAST(nsITableEditor*, this); NS_ADDREF_THIS(); return NS_OK; } if (aIID.Equals(NS_GET_IID(nsIEditorStyleSheets))) { *aInstancePtr = NS_STATIC_CAST(nsIEditorStyleSheets*, this); NS_ADDREF_THIS(); return NS_OK; } if (aIID.Equals(NS_GET_IID(nsICSSLoaderObserver))) { *aInstancePtr = NS_STATIC_CAST(nsICSSLoaderObserver*, this); NS_ADDREF_THIS(); return NS_OK; } return nsEditor::QueryInterface(aIID, aInstancePtr); } NS_IMETHODIMP nsHTMLEditor::Init(nsIDOMDocument *aDoc, nsIPresShell *aPresShell, nsIContent *aRoot, nsISelectionController *aSelCon, PRUint32 aFlags) { NS_PRECONDITION(aDoc && aPresShell, "bad arg"); if (!aDoc || !aPresShell) return NS_ERROR_NULL_POINTER; nsresult result = NS_OK, rulesRes = NS_OK; // make a range util object for comparing dom points mRangeHelper = do_CreateInstance(kRangeUtilsCID); if (!mRangeHelper) return NS_ERROR_NULL_POINTER; // Init mEditProperty result = NS_NewEditProperty(getter_AddRefs(mEditProperty)); if (NS_FAILED(result)) { return result; } if (!mEditProperty) {return NS_ERROR_NULL_POINTER;} if (1) { // block to scope nsAutoEditInitRulesTrigger nsAutoEditInitRulesTrigger rulesTrigger(NS_STATIC_CAST(nsPlaintextEditor*,this), rulesRes); // Init the plaintext editor result = nsPlaintextEditor::Init(aDoc, aPresShell, aRoot, aSelCon, aFlags); if (NS_FAILED(result)) { return result; } // disable links nsCOMPtr context; aPresShell->GetPresContext(getter_AddRefs(context)); if (!context) return NS_ERROR_NULL_POINTER; if (!(mFlags & eEditorPlaintextMask)) context->SetLinkHandler(0); nsCOMPtr bodyElement; result = nsEditor::GetRootElement(getter_AddRefs(bodyElement)); if (NS_FAILED(result)) { return result; } if (!bodyElement) { return NS_ERROR_NULL_POINTER; } // init the type-in state mTypeInState = new TypeInState(); if (!mTypeInState) {return NS_ERROR_NULL_POINTER;} NS_ADDREF(mTypeInState); nsCOMPtrselection; result = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(result)) { return result; } if (selection) { nsCOMPtr selPriv(do_QueryInterface(selection)); nsCOMPtrlistener; listener = do_QueryInterface(mTypeInState); if (listener) { selPriv->AddSelectionListener(listener); } } // Set up a DTD mDTD = do_CreateInstance(kCTransitionalDTDCID); if (!mDTD) result = NS_ERROR_FAILURE; } if (NS_FAILED(rulesRes)) return rulesRes; return result; } NS_IMETHODIMP nsHTMLEditor::PostCreate() { nsresult result = InstallEventListeners(); if (NS_FAILED(result)) return result; result = nsEditor::PostCreate(); return result; } NS_IMETHODIMP nsHTMLEditor::InstallEventListeners() { NS_ASSERTION(mDocWeak, "no document set on this editor"); if (!mDocWeak) return NS_ERROR_NOT_INITIALIZED; nsresult result; // get a key listener result = NS_NewEditorKeyListener(getter_AddRefs(mKeyListenerP), this); if (NS_FAILED(result)) { HandleEventListenerError(); return result; } // get a mouse listener result = NS_NewEditorMouseListener(getter_AddRefs(mMouseListenerP), this); if (NS_FAILED(result)) { HandleEventListenerError(); return result; } // get a text listener result = NS_NewEditorTextListener(getter_AddRefs(mTextListenerP),this); if (NS_FAILED(result)) { #ifdef DEBUG_TAGUE printf("nsTextEditor.cpp: failed to get TextEvent Listener\n"); #endif HandleEventListenerError(); return result; } // get a composition listener result = NS_NewEditorCompositionListener(getter_AddRefs(mCompositionListenerP),this); if (NS_FAILED(result)) { #ifdef DEBUG_TAGUE printf("nsTextEditor.cpp: failed to get TextEvent Listener\n"); #endif HandleEventListenerError(); return result; } // get a drag listener result = NS_NewEditorDragListener(getter_AddRefs(mDragListenerP), this); if (NS_FAILED(result)) { HandleEventListenerError(); return result; } // get a focus listener result = NS_NewEditorFocusListener(getter_AddRefs(mFocusListenerP), this); if (NS_FAILED(result)) { HandleEventListenerError(); return result; } nsCOMPtr erP; result = GetDOMEventReceiver(getter_AddRefs(erP)); //end hack if (NS_FAILED(result)) { HandleEventListenerError(); return result; } // register the event listeners with the DOM event reveiver result = erP->AddEventListenerByIID(mKeyListenerP, NS_GET_IID(nsIDOMKeyListener)); NS_ASSERTION(NS_SUCCEEDED(result), "failed to register key listener"); if (NS_SUCCEEDED(result)) { result = erP->AddEventListenerByIID(mMouseListenerP, NS_GET_IID(nsIDOMMouseListener)); NS_ASSERTION(NS_SUCCEEDED(result), "failed to register mouse listener"); if (NS_SUCCEEDED(result)) { result = erP->AddEventListenerByIID(mFocusListenerP, NS_GET_IID(nsIDOMFocusListener)); NS_ASSERTION(NS_SUCCEEDED(result), "failed to register focus listener"); if (NS_SUCCEEDED(result)) { result = erP->AddEventListenerByIID(mTextListenerP, NS_GET_IID(nsIDOMTextListener)); NS_ASSERTION(NS_SUCCEEDED(result), "failed to register text listener"); if (NS_SUCCEEDED(result)) { result = erP->AddEventListenerByIID(mCompositionListenerP, NS_GET_IID(nsIDOMCompositionListener)); NS_ASSERTION(NS_SUCCEEDED(result), "failed to register composition listener"); if (NS_SUCCEEDED(result)) { result = erP->AddEventListenerByIID(mDragListenerP, NS_GET_IID(nsIDOMDragListener)); NS_ASSERTION(NS_SUCCEEDED(result), "failed to register drag listener"); } } } } } if (NS_FAILED(result)) { HandleEventListenerError(); } return result; } NS_IMETHODIMP nsHTMLEditor::GetFlags(PRUint32 *aFlags) { if (!mRules || !aFlags) { return NS_ERROR_NULL_POINTER; } return mRules->GetFlags(aFlags); } NS_IMETHODIMP nsHTMLEditor::SetFlags(PRUint32 aFlags) { if (!mRules) { return NS_ERROR_NULL_POINTER; } return mRules->SetFlags(aFlags); } NS_IMETHODIMP nsHTMLEditor::InitRules() { // instantiate the rules for the html editor nsresult res = NS_NewHTMLEditRules(getter_AddRefs(mRules)); if (NS_FAILED(res)) return res; if (!mRules) return NS_ERROR_UNEXPECTED; res = mRules->Init(NS_STATIC_CAST(nsPlaintextEditor*,this), mFlags); return res; } /** * Returns true if the id represents an element of block type. * Can be used to determine if a new paragraph should be started. */ nsresult nsHTMLEditor::NodeIsBlockStatic(nsIDOMNode *aNode, PRBool *aIsBlock) { if (!aNode || !aIsBlock) { return NS_ERROR_NULL_POINTER; } //#define USE_PARSER_FOR_BLOCKNESS 1 #ifdef USE_PARSER_FOR_BLOCKNESS // We want to use the parser rather than keeping this info // here in the editor, but the problem is that the ownership // model for the parser service is unclear; we don't want to // have to get it every time, but if we keep it statically // here, it may show up as a leak. nsresult rv; nsCOMPtrelement; element = do_QueryInterface(aNode); if (!element) { // We don't have an element -- probably a text node *aIsBlock = PR_FALSE; return NS_OK; } *aIsBlock = PR_FALSE; // Get the node name and atom: nsAutoString tagName; rv = element->GetTagName(tagName); if (NS_FAILED(rv)) return rv; tagName.ToLowerCase(); nsCOMPtr tagAtom = getter_AddRefs(NS_NewAtom(tagName)); if (!tagAtom) return NS_ERROR_NULL_POINTER; static nsCOMPtr sParserService; if (!sParserService) { sParserService = do_GetService(kParserServiceCID, &rv); if (NS_FAILED(rv)) return rv; } // Nodes we know we want to treat as block // even though the parser says they're not: if (tagAtom==nsIEditProperty::body || tagAtom==nsIEditProperty::tbody || tagAtom==nsIEditProperty::thead || tagAtom==nsIEditProperty::tfoot || tagAtom==nsIEditProperty::tr || tagAtom==nsIEditProperty::th || tagAtom==nsIEditProperty::td || tagAtom==nsIEditProperty::pre) { *aIsBlock = PR_TRUE; return NS_OK; } // This sucks. The parser service's isBlock requires a string, // so we have to get the name atom, convert it into a string, call // the parser service to get the id, in order to call the parser // service to ask about blockness. // Harish is working on a more efficient API we can use. PRInt32 id; rv = sParserService->HTMLStringTagToId(tagName, &id); if (NS_FAILED(rv)) return rv; rv = sParserService->IsBlock(id, *aIsBlock); #ifdef DEBUG // Check this against what we would have said with the old code: if (tagAtom==nsIEditProperty::p || tagAtom==nsIEditProperty::div || tagAtom==nsIEditProperty::blockquote || tagAtom==nsIEditProperty::h1 || tagAtom==nsIEditProperty::h2 || tagAtom==nsIEditProperty::h3 || tagAtom==nsIEditProperty::h4 || tagAtom==nsIEditProperty::h5 || tagAtom==nsIEditProperty::h6 || tagAtom==nsIEditProperty::ul || tagAtom==nsIEditProperty::ol || tagAtom==nsIEditProperty::dl || tagAtom==nsIEditProperty::noscript || tagAtom==nsIEditProperty::form || tagAtom==nsIEditProperty::hr || tagAtom==nsIEditProperty::table || tagAtom==nsIEditProperty::fieldset || tagAtom==nsIEditProperty::address || tagAtom==nsIEditProperty::caption || tagAtom==nsIEditProperty::col || tagAtom==nsIEditProperty::colgroup || tagAtom==nsIEditProperty::li || tagAtom==nsIEditProperty::dt || tagAtom==nsIEditProperty::dd || tagAtom==nsIEditProperty::legend ) { if (!(*aIsBlock)) { nsAutoString assertmsg (NS_LITERAL_STRING("Parser and editor disagree on blockness: ")); assertmsg.Append(tagName); char* assertstr = ToNewCString(assertmsg); NS_ASSERTION(*aIsBlock, assertstr); Recycle(assertstr); } } #endif /* DEBUG */ return rv; #else /* USE_PARSER_FOR_BLOCKNESS */ nsresult result = NS_ERROR_FAILURE; *aIsBlock = PR_FALSE; nsCOMPtrelement; element = do_QueryInterface(aNode); if (element) { nsAutoString tagName; result = element->GetTagName(tagName); if (NS_SUCCEEDED(result)) { tagName.ToLowerCase(); nsIAtom *tagAtom = NS_NewAtom(tagName); if (!tagAtom) { return NS_ERROR_NULL_POINTER; } if (tagAtom==nsIEditProperty::p || tagAtom==nsIEditProperty::div || tagAtom==nsIEditProperty::blockquote || tagAtom==nsIEditProperty::h1 || tagAtom==nsIEditProperty::h2 || tagAtom==nsIEditProperty::h3 || tagAtom==nsIEditProperty::h4 || tagAtom==nsIEditProperty::h5 || tagAtom==nsIEditProperty::h6 || tagAtom==nsIEditProperty::ul || tagAtom==nsIEditProperty::ol || tagAtom==nsIEditProperty::dl || tagAtom==nsIEditProperty::pre || tagAtom==nsIEditProperty::noscript || tagAtom==nsIEditProperty::form || tagAtom==nsIEditProperty::hr || tagAtom==nsIEditProperty::table || tagAtom==nsIEditProperty::fieldset || tagAtom==nsIEditProperty::address || tagAtom==nsIEditProperty::body || tagAtom==nsIEditProperty::tr || tagAtom==nsIEditProperty::td || tagAtom==nsIEditProperty::th || tagAtom==nsIEditProperty::caption || tagAtom==nsIEditProperty::col || tagAtom==nsIEditProperty::colgroup || tagAtom==nsIEditProperty::tbody || tagAtom==nsIEditProperty::thead || tagAtom==nsIEditProperty::tfoot || tagAtom==nsIEditProperty::li || tagAtom==nsIEditProperty::dt || tagAtom==nsIEditProperty::dd || tagAtom==nsIEditProperty::legend ) { *aIsBlock = PR_TRUE; } NS_RELEASE(tagAtom); result = NS_OK; } } else { // We don't have an element -- probably a text node nsCOMPtrnodeAsText = do_QueryInterface(aNode); if (nodeAsText) { *aIsBlock = PR_FALSE; result = NS_OK; } } return result; #endif /* USE_PARSER_FOR_BLOCKNESS */ } NS_IMETHODIMP nsHTMLEditor::NodeIsBlock(nsIDOMNode *aNode, PRBool *aIsBlock) { return NodeIsBlockStatic(aNode, aIsBlock); } PRBool nsHTMLEditor::IsBlockNode(nsIDOMNode *aNode) { PRBool isBlock; NodeIsBlockStatic(aNode, &isBlock); return isBlock; } // Non-static version for the nsIEditor interface and JavaScript NS_IMETHODIMP nsHTMLEditor::SetDocumentTitle(const nsAReadableString &aTitle) { SetDocTitleTxn *txn; nsresult result = TransactionFactory::GetNewTransaction(SetDocTitleTxn::GetCID(), (EditTxn **)&txn); if (NS_SUCCEEDED(result)) { result = txn->Init(this, &aTitle); if (NS_SUCCEEDED(result)) { //Don't let Rules System change the selection nsAutoTxnsConserveSelection dontChangeSelection(this); result = nsEditor::Do(txn); } // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); } return result; } /* ------------ Block methods moved from nsEditor -------------- */ /////////////////////////////////////////////////////////////////////////// // GetBlockNodeParent: returns enclosing block level ancestor, if any // nsCOMPtr nsHTMLEditor::GetBlockNodeParent(nsIDOMNode *aNode) { nsCOMPtr tmp; nsCOMPtr p; if (!aNode) { NS_NOTREACHED("null node passed to GetBlockNodeParent()"); return PR_FALSE; } if (NS_FAILED(aNode->GetParentNode(getter_AddRefs(p)))) // no parent, ran off top of tree return tmp; while (p) { PRBool isBlock; if (NS_FAILED(NodeIsBlockStatic(p, &isBlock)) || isBlock) break; if ( NS_FAILED(p->GetParentNode(getter_AddRefs(tmp))) || !tmp) // no parent, ran off top of tree return p; p = tmp; } return p; } /////////////////////////////////////////////////////////////////////////// // HasSameBlockNodeParent: true if nodes have same block level ancestor // PRBool nsHTMLEditor::HasSameBlockNodeParent(nsIDOMNode *aNode1, nsIDOMNode *aNode2) { if (!aNode1 || !aNode2) { NS_NOTREACHED("null node passed to HasSameBlockNodeParent()"); return PR_FALSE; } if (aNode1 == aNode2) return PR_TRUE; nsCOMPtr p1 = GetBlockNodeParent(aNode1); nsCOMPtr p2 = GetBlockNodeParent(aNode2); return (p1 == p2); } /////////////////////////////////////////////////////////////////////////// // GetBlockSection: return leftmost/rightmost nodes in aChild's block // nsresult nsHTMLEditor::GetBlockSection(nsIDOMNode *aChild, nsIDOMNode **aLeftNode, nsIDOMNode **aRightNode) { nsresult result = NS_OK; if (!aChild || !aLeftNode || !aRightNode) {return NS_ERROR_NULL_POINTER;} *aLeftNode = aChild; *aRightNode = aChild; nsCOMPtrsibling; result = aChild->GetPreviousSibling(getter_AddRefs(sibling)); while ((NS_SUCCEEDED(result)) && sibling) { PRBool isBlock; NodeIsBlockStatic(sibling, &isBlock); if (isBlock) { nsCOMPtrnodeAsText = do_QueryInterface(sibling); if (!nodeAsText) { break; } // XXX: needs some logic to work for other leaf nodes besides text! } *aLeftNode = sibling; result = (*aLeftNode)->GetPreviousSibling(getter_AddRefs(sibling)); } NS_ADDREF((*aLeftNode)); // now do the right side result = aChild->GetNextSibling(getter_AddRefs(sibling)); while ((NS_SUCCEEDED(result)) && sibling) { PRBool isBlock; NodeIsBlockStatic(sibling, &isBlock); if (isBlock) { nsCOMPtrnodeAsText = do_QueryInterface(sibling); if (!nodeAsText) { break; } } *aRightNode = sibling; result = (*aRightNode)->GetNextSibling(getter_AddRefs(sibling)); } NS_ADDREF((*aRightNode)); if (gNoisy) { printf("GetBlockSection returning %p %p\n", (void*)(*aLeftNode), (void*)(*aRightNode)); } return result; } /////////////////////////////////////////////////////////////////////////// // GetBlockSectionsForRange: return list of block sections that intersect // this range nsresult nsHTMLEditor::GetBlockSectionsForRange(nsIDOMRange *aRange, nsISupportsArray *aSections) { if (!aRange || !aSections) {return NS_ERROR_NULL_POINTER;} nsresult result; nsCOMPtriter; result = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull, NS_GET_IID(nsIContentIterator), getter_AddRefs(iter)); if ((NS_SUCCEEDED(result)) && iter) { nsCOMPtr lastRange; iter->Init(aRange); nsCOMPtr currentContent; iter->CurrentNode(getter_AddRefs(currentContent)); while (NS_ENUMERATOR_FALSE == iter->IsDone()) { nsCOMPtrcurrentNode = do_QueryInterface(currentContent); if (currentNode) { nsCOMPtr currentContentTag; currentContent->GetTag(*getter_AddRefs(currentContentTag)); //
divides block content ranges. We can achieve this by nulling out lastRange if (nsIEditProperty::br==currentContentTag.get()) { lastRange = do_QueryInterface(nsnull); } else { PRBool isNotInlineOrText; result = NodeIsBlockStatic(currentNode, &isNotInlineOrText); if (isNotInlineOrText) { PRUint16 nodeType; currentNode->GetNodeType(&nodeType); if (nsIDOMNode::TEXT_NODE == nodeType) { isNotInlineOrText = PR_TRUE; } } if (PR_FALSE==isNotInlineOrText) { nsCOMPtrleftNode; nsCOMPtrrightNode; result = GetBlockSection(currentNode, getter_AddRefs(leftNode), getter_AddRefs(rightNode)); if (gNoisy) {printf("currentNode %p has block content (%p,%p)\n", (void*)currentNode.get(), (void*)leftNode.get(), (void*)rightNode.get());} if ((NS_SUCCEEDED(result)) && leftNode && rightNode) { // add range to the list if it doesn't overlap with the previous range PRBool addRange=PR_TRUE; if (lastRange) { nsCOMPtr lastStartNode; nsCOMPtr blockParentOfLastStartNode; lastRange->GetStartContainer(getter_AddRefs(lastStartNode)); blockParentOfLastStartNode = do_QueryInterface(GetBlockNodeParent(lastStartNode)); if (blockParentOfLastStartNode) { if (gNoisy) {printf("lastStartNode %p has block parent %p\n", (void*)lastStartNode.get(), (void*)blockParentOfLastStartNode.get());} nsCOMPtr blockParentOfLeftNode; blockParentOfLeftNode = do_QueryInterface(GetBlockNodeParent(leftNode)); if (blockParentOfLeftNode) { if (gNoisy) {printf("leftNode %p has block parent %p\n", (void*)leftNode.get(), (void*)blockParentOfLeftNode.get());} if (blockParentOfLastStartNode==blockParentOfLeftNode) { addRange = PR_FALSE; } } } } if (PR_TRUE==addRange) { if (gNoisy) {printf("adding range, setting lastRange with start node %p\n", (void*)leftNode.get());} nsCOMPtr range; result = nsComponentManager::CreateInstance(kCRangeCID, nsnull, NS_GET_IID(nsIDOMRange), getter_AddRefs(range)); if ((NS_SUCCEEDED(result)) && range) { // initialize the range range->SetStart(leftNode, 0); range->SetEnd(rightNode, 0); aSections->AppendElement(range); lastRange = do_QueryInterface(range); } } } } } } /* do not check result here, and especially do not return the result code. * we rely on iter->IsDone to tell us when the iteration is complete */ iter->Next(); iter->CurrentNode(getter_AddRefs(currentContent)); } } return result; } /////////////////////////////////////////////////////////////////////////// // NextNodeInBlock: gets the next/prev node in the block, if any. Next node // must be an element or text node, others are ignored nsCOMPtr nsHTMLEditor::NextNodeInBlock(nsIDOMNode *aNode, IterDirection aDir) { nsCOMPtr nullNode; nsCOMPtr content; nsCOMPtr blockContent; nsCOMPtr node; nsCOMPtr blockParent; if (!aNode) return nullNode; nsCOMPtr iter; if (NS_FAILED(nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull, NS_GET_IID(nsIContentIterator), getter_AddRefs(iter)))) return nullNode; // much gnashing of teeth as we twit back and forth between content and domnode types content = do_QueryInterface(aNode); PRBool isBlock; if (NS_SUCCEEDED(NodeIsBlockStatic(aNode, &isBlock)) && isBlock) { blockParent = do_QueryInterface(aNode); } else { blockParent = GetBlockNodeParent(aNode); } if (!blockParent) return nullNode; blockContent = do_QueryInterface(blockParent); if (!blockContent) return nullNode; if (NS_FAILED(iter->Init(blockContent))) return nullNode; if (NS_FAILED(iter->PositionAt(content))) return nullNode; while (NS_ENUMERATOR_FALSE == iter->IsDone()) { if (NS_FAILED(iter->CurrentNode(getter_AddRefs(content)))) return nullNode; // ignore nodes that aren't elements or text, or that are the block parent node = do_QueryInterface(content); if (node && IsTextOrElementNode(node) && (node != blockParent) && (node.get() != aNode)) return node; if (aDir == kIterForward) iter->Next(); else iter->Prev(); } return nullNode; } static const PRUnichar nbsp = 160; /////////////////////////////////////////////////////////////////////////// // IsNextCharWhitespace: checks the adjacent content in the same block // to see if following selection is whitespace or nbsp nsresult nsHTMLEditor::IsNextCharWhitespace(nsIDOMNode *aParentNode, PRInt32 aOffset, PRBool *outIsSpace, PRBool *outIsNBSP, nsCOMPtr *outNode, PRInt32 *outOffset) { if (!outIsSpace || !outIsNBSP) return NS_ERROR_NULL_POINTER; *outIsSpace = PR_FALSE; *outIsNBSP = PR_FALSE; if (outNode) *outNode = nsnull; if (outOffset) *outOffset = -1; nsAutoString tempString; PRUint32 strLength; nsCOMPtr textNode = do_QueryInterface(aParentNode); if (textNode) { textNode->GetLength(&strLength); if ((PRUint32)aOffset < strLength) { // easy case: next char is in same node textNode->SubstringData(aOffset,aOffset+1,tempString); *outIsSpace = nsCRT::IsAsciiSpace(tempString.First()); *outIsNBSP = (tempString.First() == nbsp); if (outNode) *outNode = do_QueryInterface(aParentNode); if (outOffset) *outOffset = aOffset+1; // yes, this is _past_ the character; return NS_OK; } } // harder case: next char in next node. nsCOMPtr node = NextNodeInBlock(aParentNode, kIterForward); nsCOMPtr tmp; while (node) { PRBool isBlock (PR_FALSE); NodeIsBlock(node, &isBlock); if (isBlock) // skip over bold, italic, link, ect nodes { if (IsTextNode(node) && IsEditable(node)) { textNode = do_QueryInterface(node); textNode->GetLength(&strLength); if (strLength) { textNode->SubstringData(0,1,tempString); *outIsSpace = nsCRT::IsAsciiSpace(tempString.First()); *outIsNBSP = (tempString.First() == nbsp); if (outNode) *outNode = do_QueryInterface(node); if (outOffset) *outOffset = 1; // yes, this is _past_ the character; return NS_OK; } // else it's an empty text node, or not editable; skip it. } else // node is an image or some other thingy that doesn't count as whitespace { break; } } tmp = node; node = NextNodeInBlock(tmp, kIterForward); } return NS_OK; } /////////////////////////////////////////////////////////////////////////// // IsPrevCharWhitespace: checks the adjacent content in the same block // to see if following selection is whitespace nsresult nsHTMLEditor::IsPrevCharWhitespace(nsIDOMNode *aParentNode, PRInt32 aOffset, PRBool *outIsSpace, PRBool *outIsNBSP, nsCOMPtr *outNode, PRInt32 *outOffset) { if (!outIsSpace || !outIsNBSP) return NS_ERROR_NULL_POINTER; *outIsSpace = PR_FALSE; *outIsNBSP = PR_FALSE; if (outNode) *outNode = nsnull; if (outOffset) *outOffset = -1; nsAutoString tempString; PRUint32 strLength; nsCOMPtr textNode = do_QueryInterface(aParentNode); if (textNode) { if (aOffset > 0) { // easy case: prev char is in same node textNode->SubstringData(aOffset-1,aOffset,tempString); *outIsSpace = nsCRT::IsAsciiSpace(tempString.First()); *outIsNBSP = (tempString.First() == nbsp); if (outNode) *outNode = do_QueryInterface(aParentNode); if (outOffset) *outOffset = aOffset-1; return NS_OK; } } // harder case: prev char in next node nsCOMPtr node = NextNodeInBlock(aParentNode, kIterBackward); nsCOMPtr tmp; while (node) { PRBool isBlock (PR_FALSE); NodeIsBlock(node, &isBlock); if (isBlock) // skip over bold, italic, link, ect nodes { if (IsTextNode(node) && IsEditable(node)) { textNode = do_QueryInterface(node); textNode->GetLength(&strLength); if (strLength) { // you could use nsITextContent::IsOnlyWhitespace here textNode->SubstringData(strLength-1,strLength,tempString); *outIsSpace = nsCRT::IsAsciiSpace(tempString.First()); *outIsNBSP = (tempString.First() == nbsp); if (outNode) *outNode = do_QueryInterface(aParentNode); if (outOffset) *outOffset = strLength-1; return NS_OK; } // else it's an empty text node, or not editable; skip it. } else // node is an image or some other thingy that doesn't count as whitespace { break; } } // otherwise we found a node we want to skip, keep going tmp = node; node = NextNodeInBlock(tmp, kIterBackward); } return NS_OK; } /* ------------ End Block methods -------------- */ PRBool nsHTMLEditor::IsModifiable() { PRUint32 flags; if (NS_SUCCEEDED(GetFlags(&flags))) return ((flags & eEditorReadonlyMask) == 0); else return PR_FALSE; } #ifdef XP_MAC #pragma mark - #pragma mark nsIHTMLEditor methods #pragma mark - #endif NS_IMETHODIMP nsHTMLEditor::HandleKeyPress(nsIDOMKeyEvent* aKeyEvent) { PRUint32 keyCode, character; PRBool isShift, ctrlKey, altKey, metaKey; nsresult res; if (!aKeyEvent) return NS_ERROR_NULL_POINTER; if (NS_SUCCEEDED(aKeyEvent->GetKeyCode(&keyCode)) && NS_SUCCEEDED(aKeyEvent->GetShiftKey(&isShift)) && NS_SUCCEEDED(aKeyEvent->GetCtrlKey(&ctrlKey)) && NS_SUCCEEDED(aKeyEvent->GetAltKey(&altKey)) && NS_SUCCEEDED(aKeyEvent->GetMetaKey(&metaKey))) { // this royally blows: because tabs come in from keyDowns instead // of keyPress, and because GetCharCode refuses to work for keyDown // i have to play games. if (keyCode == nsIDOMKeyEvent::DOM_VK_TAB) character = '\t'; else aKeyEvent->GetCharCode(&character); if (keyCode == nsIDOMKeyEvent::DOM_VK_TAB && !(mFlags&eEditorPlaintextBit)) { nsCOMPtrselection; res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; PRInt32 offset; nsCOMPtr node, blockParent; res = GetStartNodeAndOffset(selection, address_of(node), &offset); if (NS_FAILED(res)) return res; if (!node) return NS_ERROR_FAILURE; PRBool isBlock (PR_FALSE); NodeIsBlock(node, &isBlock); if (isBlock) blockParent = node; else blockParent = GetBlockNodeParent(node); if (blockParent) { PRBool bHandled = PR_FALSE; if (nsHTMLEditUtils::IsTableElement(blockParent)) res = TabInTable(isShift, &bHandled); else if (nsHTMLEditUtils::IsListItem(blockParent)) { nsAutoString indentstr; if (isShift) indentstr.AssignWithConversion("outdent"); else indentstr.AssignWithConversion("indent"); res = Indent(indentstr); bHandled = PR_TRUE; } if (NS_FAILED(res)) return res; if (bHandled) return res; } } else if (keyCode == nsIDOMKeyEvent::DOM_VK_RETURN || keyCode == nsIDOMKeyEvent::DOM_VK_ENTER) { nsString empty; if (isShift && !(mFlags&eEditorPlaintextBit)) { return TypedText(empty, eTypedBR); // only inserts a br node } else { return TypedText(empty, eTypedBreak); // uses rules to figure out what to insert } } else if (keyCode == nsIDOMKeyEvent::DOM_VK_ESCAPE) { // pass escape keypresses through as empty strings: needed forime support nsString empty; return TypedText(empty, eTypedText); } // if we got here we either fell out of the tab case or have a normal character. // Either way, treat as normal character. if (character && !altKey && !ctrlKey && !isShift && !metaKey) { nsAutoString key(character); return TypedText(key, eTypedText); } } return NS_ERROR_FAILURE; } /* This routine is needed to provide a bottleneck for typing for logging purposes. Can't use EditorKeyPress() (above) for that since it takes a nsIDOMUIEvent* parameter. So instead we pass enough info through to TypedText() to determine what action to take, but without passing an event. */ NS_IMETHODIMP nsHTMLEditor::TypedText(const nsAReadableString& aString, PRInt32 aAction) { nsAutoPlaceHolderBatch batch(this, gTypingTxnName); switch (aAction) { case eTypedText: case eTypedBreak: { return nsPlaintextEditor::TypedText(aString, aAction); } case eTypedBR: { nsCOMPtr brNode; return InsertBR(address_of(brNode)); // only inserts a br node } } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsHTMLEditor::TabInTable(PRBool inIsShift, PRBool *outHandled) { if (!outHandled) return NS_ERROR_NULL_POINTER; *outHandled = PR_FALSE; // Find enclosing table cell from the selection (cell may be the selected element) nsCOMPtr cellElement; // can't use |NS_LITERAL_STRING| here until |GetElementOrParentByTagName| is fixed to accept readables nsresult res = GetElementOrParentByTagName(NS_ConvertASCIItoUCS2("td"), nsnull, getter_AddRefs(cellElement)); if (NS_FAILED(res)) return res; // Do nothing -- we didn't find a table cell if (!cellElement) return NS_OK; // find enclosing table nsCOMPtr tbl = GetEnclosingTable(cellElement); if (!tbl) return res; // advance to next cell // first create an iterator over the table nsCOMPtr iter; res = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull, NS_GET_IID(nsIContentIterator), getter_AddRefs(iter)); if (NS_FAILED(res)) return res; if (!iter) return NS_ERROR_NULL_POINTER; nsCOMPtr cTbl = do_QueryInterface(tbl); nsCOMPtr cBlock = do_QueryInterface(cellElement); res = iter->Init(cTbl); if (NS_FAILED(res)) return res; // position iter at block res = iter->PositionAt(cBlock); if (NS_FAILED(res)) return res; nsCOMPtr node; nsCOMPtr cNode; do { if (inIsShift) res = iter->Prev(); else res = iter->Next(); if (NS_FAILED(res)) break; res = iter->CurrentNode(getter_AddRefs(cNode)); if (NS_FAILED(res)) break; node = do_QueryInterface(cNode); if (nsHTMLEditUtils::IsTableCell(node) && (GetEnclosingTable(node) == tbl)) { res = CollapseSelectionToDeepestNonTableFirstChild(nsnull, node); if (NS_FAILED(res)) return res; *outHandled = PR_TRUE; return NS_OK; } } while (iter->IsDone() == NS_ENUMERATOR_FALSE); if (!(*outHandled) && !inIsShift) { // if we havent handled it yet then we must have run off the end of // the table. Insert a new row. res = InsertTableRow(1, PR_TRUE); if (NS_FAILED(res)) return res; *outHandled = PR_TRUE; // put selection in right place // Use table code to get selection and index to new row... nsCOMPtrselection; nsCOMPtr tblElement; nsCOMPtr cell; PRInt32 row; res = GetCellContext(getter_AddRefs(selection), getter_AddRefs(tblElement), getter_AddRefs(cell), nsnull, nsnull, &row, nsnull); if (NS_FAILED(res)) return res; // ...so that we can ask for first cell in that row... res = GetCellAt(tblElement, row, 0, getter_AddRefs(cell)); if (NS_FAILED(res)) return res; // ...and then set selection there. // (Note that normally you should use CollapseSelectionToDeepestNonTableFirstChild(), // but we know cell is an empty new cell, so this works fine) node = do_QueryInterface(cell); if (node) selection->Collapse(node,0); return NS_OK; } return res; } NS_IMETHODIMP nsHTMLEditor::CreateBRImpl(nsCOMPtr *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr *outBRNode, EDirection aSelect) { if (!aInOutParent || !*aInOutParent || !aInOutOffset || !outBRNode) return NS_ERROR_NULL_POINTER; *outBRNode = nsnull; nsresult res; // we need to insert a br. unfortunately, we may have to split a text node to do it. nsCOMPtr node = *aInOutParent; PRInt32 theOffset = *aInOutOffset; nsCOMPtr nodeAsText = do_QueryInterface(node); nsAutoString brType; brType.AssignWithConversion("br"); nsCOMPtr brNode; if (nodeAsText) { nsCOMPtr tmp; PRInt32 offset; PRUint32 len; nodeAsText->GetLength(&len); GetNodeLocation(node, address_of(tmp), &offset); if (!tmp) return NS_ERROR_FAILURE; if (!theOffset) { // we are already set to go } else if (theOffset == (PRInt32)len) { // update offset to point AFTER the text node offset++; } else { // split the text node res = SplitNode(node, theOffset, getter_AddRefs(tmp)); if (NS_FAILED(res)) return res; res = GetNodeLocation(node, address_of(tmp), &offset); if (NS_FAILED(res)) return res; } // create br res = CreateNode(brType, tmp, offset, getter_AddRefs(brNode)); if (NS_FAILED(res)) return res; *aInOutParent = tmp; *aInOutOffset = offset+1; } else { res = CreateNode(brType, node, theOffset, getter_AddRefs(brNode)); if (NS_FAILED(res)) return res; (*aInOutOffset)++; } *outBRNode = brNode; if (*outBRNode && (aSelect != eNone)) { nsCOMPtr selection; nsCOMPtr parent; PRInt32 offset; res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; nsCOMPtr selPriv(do_QueryInterface(selection)); res = GetNodeLocation(*outBRNode, address_of(parent), &offset); if (NS_FAILED(res)) return res; if (aSelect == eNext) { // position selection after br selPriv->SetInterlinePosition(PR_TRUE); res = selection->Collapse(parent, offset+1); } else if (aSelect == ePrevious) { // position selection before br selPriv->SetInterlinePosition(PR_TRUE); res = selection->Collapse(parent, offset); } } return NS_OK; } NS_IMETHODIMP nsHTMLEditor::CreateBR(nsIDOMNode *aNode, PRInt32 aOffset, nsCOMPtr *outBRNode, EDirection aSelect) { nsCOMPtr parent = aNode; PRInt32 offset = aOffset; return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect); } NS_IMETHODIMP nsHTMLEditor::InsertBR(nsCOMPtr *outBRNode) { PRBool bCollapsed; nsCOMPtr selection; if (!outBRNode) return NS_ERROR_NULL_POINTER; *outBRNode = nsnull; // calling it text insertion to trigger moz br treatment by rules nsAutoRules beginRulesSniffing(this, kOpInsertText, nsIEditor::eNext); nsresult res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; nsCOMPtr selPriv(do_QueryInterface(selection)); res = selection->GetIsCollapsed(&bCollapsed); if (NS_FAILED(res)) return res; if (!bCollapsed) { res = DeleteSelection(nsIEditor::eNone); if (NS_FAILED(res)) return res; } nsCOMPtr selNode; PRInt32 selOffset; res = GetStartNodeAndOffset(selection, address_of(selNode), &selOffset); if (NS_FAILED(res)) return res; res = CreateBR(selNode, selOffset, outBRNode); if (NS_FAILED(res)) return res; // position selection after br res = GetNodeLocation(*outBRNode, address_of(selNode), &selOffset); if (NS_FAILED(res)) return res; selPriv->SetInterlinePosition(PR_TRUE); res = selection->Collapse(selNode, selOffset+1); return res; } nsresult nsHTMLEditor::GetDOMEventReceiver(nsIDOMEventReceiver **aEventReceiver) { if (!aEventReceiver) return NS_ERROR_NULL_POINTER; *aEventReceiver = 0; nsCOMPtr rootElement; nsresult result = GetRootElement(getter_AddRefs(rootElement)); if (NS_FAILED(result)) return result; if (!rootElement) return NS_ERROR_FAILURE; // Now hack to make sure we are not anonymous content. // If we are grab the parent of root element for our observer. nsCOMPtr content = do_QueryInterface(rootElement); if (content) { nsCOMPtr parent; if (NS_SUCCEEDED(content->GetParent(*getter_AddRefs(parent))) && parent) { PRInt32 index; if (NS_FAILED(parent->IndexOf(content, index)) || index < 0 ) { rootElement = do_QueryInterface(parent); //this will put listener on the form element basically result = rootElement->QueryInterface(NS_GET_IID(nsIDOMEventReceiver), (void **)aEventReceiver); } else rootElement = 0; // Let the event receiver work on the document instead of the root element } } else rootElement = 0; if (!rootElement && mDocWeak) { // Don't use getDocument here, because we have no way of knowing if // Init() was ever called. So we need to get the document ourselves, // if it exists. nsCOMPtr domdoc = do_QueryReferent(mDocWeak); if (!domdoc) return NS_ERROR_FAILURE; result = domdoc->QueryInterface(NS_GET_IID(nsIDOMEventReceiver), (void **)aEventReceiver); } return result; } NS_IMETHODIMP nsHTMLEditor::CollapseSelectionToStart() { nsCOMPtr bodyElement; nsresult res = nsEditor::GetRootElement(getter_AddRefs(bodyElement)); if (NS_FAILED(res)) return res; if (!bodyElement) return NS_ERROR_NULL_POINTER; nsCOMPtr bodyNode = do_QueryInterface(bodyElement); return CollapseSelectionToDeepestNonTableFirstChild(nsnull, bodyNode); } nsresult nsHTMLEditor::CollapseSelectionToDeepestNonTableFirstChild(nsISelection *aSelection, nsIDOMNode *aNode) { if (!aNode) return NS_ERROR_NULL_POINTER; nsresult res; nsCOMPtr selection; if (aSelection) { selection = aSelection; } else { res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_FAILURE; } nsCOMPtr node = aNode; nsCOMPtr child; do { node->GetFirstChild(getter_AddRefs(child)); if (child) { // Stop if we find a table // don't want to go into nested tables if (nsHTMLEditUtils::IsTable(child)) break; // hey, it'g gotta be a container too! if (!IsContainer(child)) break; node = child; } } while (child); selection->Collapse(node,0); return NS_OK; } // This is mostly like InsertHTMLWithCharset, // but we can't use that because it is selection-based and // the rules code won't let us edit under the node NS_IMETHODIMP nsHTMLEditor::ReplaceHeadContentsWithHTML(const nsAReadableString& aSourceToInsert) { nsCOMPtr selection; nsresult res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; ForceCompositionEnd(); // Do not use nsAutoRules -- rules code won't let us insert in // Use the head node as a parent and delete/insert directly nsCOMPtrnodeList; nsAutoString headTag; headTag.AssignWithConversion("head"); nsCOMPtr doc = do_QueryReferent(mDocWeak); if (!doc) return NS_ERROR_NOT_INITIALIZED; res = doc->GetElementsByTagName(headTag, getter_AddRefs(nodeList)); if (NS_FAILED(res)) return res; if (!nodeList) return NS_ERROR_NULL_POINTER; PRUint32 count; nodeList->GetLength(&count); if (count < 1) return NS_ERROR_FAILURE; nsCOMPtr headNode; res = nodeList->Item(0, getter_AddRefs(headNode)); if (NS_FAILED(res)) return res; if (!headNode) return NS_ERROR_NULL_POINTER; // First, make sure there are no return chars in the source. // Bad things happen if you insert returns (instead of dom newlines, \n) // into an editor document. nsAutoString inputString (aSourceToInsert); // hope this does copy-on-write // Windows linebreaks: Map CRLF to LF: inputString.ReplaceSubstring(NS_ConvertASCIItoUCS2("\r\n"), NS_ConvertASCIItoUCS2("\n")); // Mac linebreaks: Map any remaining CR to LF: inputString.ReplaceSubstring(NS_ConvertASCIItoUCS2("\r"), NS_ConvertASCIItoUCS2("\n")); nsAutoEditBatch beginBatching(this); res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; // Get the first range in the selection, for context: nsCOMPtr range; res = selection->GetRangeAt(0, getter_AddRefs(range)); if (NS_FAILED(res)) return res; nsCOMPtr nsrange (do_QueryInterface(range)); if (!nsrange) return NS_ERROR_NO_INTERFACE; nsCOMPtr docfrag; res = nsrange->CreateContextualFragment(inputString, getter_AddRefs(docfrag)); //XXXX BUG 50965: This is not returning the text between ... // Special code is needed in JS to handle title anyway, so it really doesn't matter! if (NS_FAILED(res)) { #ifdef DEBUG printf("Couldn't create contextual fragment: error was %d\n", res); #endif return res; } if (!docfrag) return NS_ERROR_NULL_POINTER; nsCOMPtr child; // First delete all children in head do { res = headNode->GetFirstChild(getter_AddRefs(child)); if (NS_FAILED(res)) return res; if (child) { res = DeleteNode(child); if (NS_FAILED(res)) return res; } } while (child); // Now insert the new nodes PRInt32 offsetOfNewNode = 0; nsCOMPtr fragmentAsNode (do_QueryInterface(docfrag)); // Loop over the contents of the fragment and move into the document do { res = fragmentAsNode->GetFirstChild(getter_AddRefs(child)); if (NS_FAILED(res)) return res; if (child) { res = InsertNode(child, headNode, offsetOfNewNode++); if (NS_FAILED(res)) return res; } } while (child); return res; } NS_IMETHODIMP nsHTMLEditor::RebuildDocumentFromSource(const nsAReadableString& aSourceString) { ForceCompositionEnd(); nsCOMPtrselection; nsresult res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; nsCOMPtr bodyElement; res = GetRootElement(getter_AddRefs(bodyElement)); if (NS_FAILED(res)) return res; if (!bodyElement) return NS_ERROR_NULL_POINTER; // Find where the tag starts. // If user mangled that, then abort nsReadingIterator beginbody; nsReadingIterator endbody; aSourceString.BeginReading(beginbody); aSourceString.EndReading(endbody); if (!FindInReadable(NS_LITERAL_STRING(" beginhead; nsReadingIterator endhead; aSourceString.BeginReading(beginhead); aSourceString.EndReading(endhead); if (!FindInReadable(NS_LITERAL_STRING(" beginclosehead; nsReadingIterator endclosehead; aSourceString.BeginReading(beginclosehead); aSourceString.EndReading(endclosehead); // Find the index after "" if (!FindInReadable(NS_LITERAL_STRING(" endtotal; aSourceString.EndReading(endtotal); res = InsertHTML(Substring(beginbody,endtotal)); if (NS_FAILED(res)) return res; selection->Collapse(bodyElement, 0); res = ReplaceHeadContentsWithHTML(Substring(beginhead,beginclosehead)); if (NS_FAILED(res)) return res; // Now we must copy attributes user might have edited on the tag // because InsertHTML (actually, CreateContextualFragment()) // will never return a body node in the DOM fragment // We already know where " beginclosebody = beginbody; nsReadingIterator endclosebody; aSourceString.EndReading(endclosebody); if (!FindInReadable(NS_LITERAL_STRING(">"),beginclosebody,endclosebody)) return NS_ERROR_FAILURE; nsAutoString bodyTag(Substring(beginbody,endclosebody));// // Truncate at the end of the body tag // Kludge of the year: fool the parser by replacing "body" with "div" so we get a node bodyTag.ReplaceSubstring(NS_ConvertASCIItoUCS2("body"), NS_ConvertASCIItoUCS2("div")); nsCOMPtr range; res = selection->GetRangeAt(0, getter_AddRefs(range)); if (NS_FAILED(res)) return res; nsCOMPtr nsrange (do_QueryInterface(range)); if (!nsrange) return NS_ERROR_NO_INTERFACE; nsCOMPtr docfrag; res = nsrange->CreateContextualFragment(bodyTag, getter_AddRefs(docfrag)); if (NS_FAILED(res)) return res; nsCOMPtr fragmentAsNode (do_QueryInterface(docfrag)); if (!fragmentAsNode) return NS_ERROR_NULL_POINTER; nsCOMPtr child; res = fragmentAsNode->GetFirstChild(getter_AddRefs(child)); if (NS_FAILED(res)) return res; if (!child) return NS_ERROR_NULL_POINTER; // Copy all attributes from the div child to current body element return CloneAttributes(bodyElement, child); } NS_IMETHODIMP nsHTMLEditor::InsertElementAtSelection(nsIDOMElement* aElement, PRBool aDeleteSelection) { nsresult res = NS_ERROR_NOT_INITIALIZED; if (!aElement) return NS_ERROR_NULL_POINTER; nsCOMPtr node = do_QueryInterface(aElement); ForceCompositionEnd(); nsAutoEditBatch beginBatching(this); nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext); nsCOMPtrselection; res = GetSelection(getter_AddRefs(selection)); if (!NS_SUCCEEDED(res) || !selection) return NS_ERROR_FAILURE; // hand off to the rules system, see if it has anything to say about this PRBool cancel, handled; nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement); ruleInfo.insertElement = aElement; res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (cancel || (NS_FAILED(res))) return res; if (!handled) { if (aDeleteSelection) { nsCOMPtr tempNode; PRInt32 tempOffset; nsresult result = DeleteSelectionAndPrepareToCreateNode(tempNode,tempOffset); if (!NS_SUCCEEDED(result)) return result; } // If deleting, selection will be collapsed. // so if not, we collapse it if (!aDeleteSelection) { // Named Anchor is a special case, // We collapse to insert element BEFORE the selection // For all other tags, we insert AFTER the selection if (nsHTMLEditUtils::IsNamedAnchor(node)) { selection->CollapseToStart(); } else { selection->CollapseToEnd(); } } nsCOMPtr parentSelectedNode; PRInt32 offsetForInsert; res = selection->GetAnchorNode(getter_AddRefs(parentSelectedNode)); // XXX: ERROR_HANDLING bad XPCOM usage if (NS_SUCCEEDED(res) && NS_SUCCEEDED(selection->GetAnchorOffset(&offsetForInsert)) && parentSelectedNode) { #ifdef DEBUG_cmanske { nsAutoString name; parentSelectedNode->GetNodeName(name); printf("InsertElement: Anchor node of selection: "); wprintf(name.get()); printf(" Offset: %d\n", offsetForInsert); } #endif res = InsertNodeAtPoint(node, address_of(parentSelectedNode), &offsetForInsert, PR_FALSE); NS_ENSURE_SUCCESS(res, res); // Set caret after element, but check for special case // of inserting table-related elements: set in first cell instead if (!SetCaretInTableCell(aElement)) { res = SetCaretAfterElement(aElement); if (NS_FAILED(res)) return res; } // check for inserting a whole table at the end of a block. If so insert a br after it. if (nsHTMLEditUtils::IsTable(node)) { PRBool isLast; res = IsLastEditableChild(node, &isLast); if (NS_FAILED(res)) return res; if (isLast) { nsCOMPtr brNode; res = CreateBR(parentSelectedNode, offsetForInsert+1, address_of(brNode)); if (NS_FAILED(res)) return res; selection->Collapse(parentSelectedNode, offsetForInsert+1); } } } } res = mRules->DidDoAction(selection, &ruleInfo, res); return res; } /* InsertNodeAtPoint: attempts to insert aNode into the document, at a point specified by {*ioParent,*ioOffset}. Checks with strict dtd to see if containment is allowed. If not allowed, will attempt to find a parent in the parent heirarchy of *ioParent that will accept aNode as a child. If such a parent is found, will split the document tree from {*ioParent,*ioOffset} up to parent, and then insert aNode. ioParent & ioOffset are then adjusted to point to the actual location that aNode was inserted at. aNoEmptyNodes specifies if the splitting process is allowed to reslt in empty nodes. nsIDOMNode *aNode node to insert nsCOMPtr *ioParent insertion parent PRInt32 *ioOffset insertion offset PRBool aNoEmptyNodes splitting can result in empty nodes? */ nsresult nsHTMLEditor::InsertNodeAtPoint(nsIDOMNode *aNode, nsCOMPtr *ioParent, PRInt32 *ioOffset, PRBool aNoEmptyNodes) { NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(ioParent, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(*ioParent, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(ioOffset, NS_ERROR_NULL_POINTER); nsresult res = NS_OK; nsAutoString tagName; aNode->GetNodeName(tagName); tagName.ToLowerCase(); nsCOMPtr parent = *ioParent; nsCOMPtr topChild = *ioParent; nsCOMPtr tmp; PRInt32 offsetOfInsert = *ioOffset; // Search up the parent chain to find a suitable container while (!CanContainTag(parent, tagName)) { // If the current parent is a root (body or table element) // then go no further - we can't insert if (nsTextEditUtils::IsBody(parent) || nsHTMLEditUtils::IsTableElement(parent)) return NS_ERROR_FAILURE; // Get the next parent parent->GetParentNode(getter_AddRefs(tmp)); NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE); topChild = parent; parent = tmp; } if (parent != topChild) { // we need to split some levels above the original selection parent res = SplitNodeDeep(topChild, *ioParent, *ioOffset, &offsetOfInsert, aNoEmptyNodes); if (NS_FAILED(res)) return res; *ioParent = parent; *ioOffset = offsetOfInsert; } // Now we can insert the new node res = InsertNode(aNode, parent, offsetOfInsert); return res; } NS_IMETHODIMP nsHTMLEditor::SelectElement(nsIDOMElement* aElement) { nsresult res = NS_ERROR_NULL_POINTER; // Must be sure that element is contained in the document body if (IsElementInBody(aElement)) { nsCOMPtr selection; res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsCOMPtrparent; res = aElement->GetParentNode(getter_AddRefs(parent)); if (NS_SUCCEEDED(res) && parent) { PRInt32 offsetInParent; res = GetChildOffset(aElement, parent, offsetInParent); if (NS_SUCCEEDED(res)) { // Collapse selection to just before desired element, res = selection->Collapse(parent, offsetInParent); if (NS_SUCCEEDED(res)) { // then extend it to just after res = selection->Extend(parent, offsetInParent+1); } } } } return res; } NS_IMETHODIMP nsHTMLEditor::SetCaretAfterElement(nsIDOMElement* aElement) { nsresult res = NS_ERROR_NULL_POINTER; // Be sure the element is contained in the document body if (aElement && IsElementInBody(aElement)) { nsCOMPtr selection; res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsCOMPtrparent; res = aElement->GetParentNode(getter_AddRefs(parent)); if (NS_FAILED(res)) return res; if (!parent) return NS_ERROR_NULL_POINTER; PRInt32 offsetInParent; res = GetChildOffset(aElement, parent, offsetInParent); if (NS_SUCCEEDED(res)) { // Collapse selection to just after desired element, res = selection->Collapse(parent, offsetInParent+1); #if 0 //def DEBUG_cmanske { nsAutoString name; parent->GetNodeName(name); printf("SetCaretAfterElement: Parent node: "); wprintf(name.get()); printf(" Offset: %d\n\nHTML:\n", offsetInParent+1); nsAutoString Format("text/html"); nsAutoString ContentsAs; OutputToString(ContentsAs, Format, 2); wprintf(ContentsAs.get()); } #endif } } return res; } NS_IMETHODIMP nsHTMLEditor::SetParagraphFormat(const nsAReadableString& aParagraphFormat) { nsAutoString tag; tag.Assign(aParagraphFormat); tag.ToLowerCase(); if (tag.EqualsWithConversion("dd") || tag.EqualsWithConversion("dt")) return MakeDefinitionItem(tag); else return InsertBasicBlock(tag); } // XXX: ERROR_HANDLING -- this method needs a little work to ensure all error codes are // checked properly, all null pointers are checked, and no memory leaks occur NS_IMETHODIMP nsHTMLEditor::GetParentBlockTags(nsStringArray *aTagList, PRBool aGetLists) { if (!aTagList) { return NS_ERROR_NULL_POINTER; } nsresult res; nsCOMPtrselection; res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsCOMPtr selPriv(do_QueryInterface(selection)); // Find out if the selection is collapsed: PRBool isCollapsed; res = selection->GetIsCollapsed(&isCollapsed); if (NS_FAILED(res)) return res; if (isCollapsed) { nsCOMPtr node, blockParent; PRInt32 offset; res = GetStartNodeAndOffset(selection, address_of(node), &offset); if (!node) res = NS_ERROR_FAILURE; if (NS_FAILED(res)) return res; nsCOMPtr blockParentElem; if (aGetLists) { // Get the "ol", "ul", or "dl" parent element res = GetElementOrParentByTagName(NS_ConvertASCIItoUCS2("list"), node, getter_AddRefs(blockParentElem)); if (NS_FAILED(res)) return res; } else { PRBool isBlock (PR_FALSE); NodeIsBlock(node, &isBlock); if (isBlock) blockParent = node; else blockParent = GetBlockNodeParent(node); blockParentElem = do_QueryInterface(blockParent); } if (blockParentElem) { nsAutoString blockParentTag; blockParentElem->GetTagName(blockParentTag); aTagList->AppendString(blockParentTag); } return res; } // else non-collapsed selection nsCOMPtr enumerator; res = selPriv->GetEnumerator(getter_AddRefs(enumerator)); if (NS_FAILED(res)) return res; if (!enumerator) return NS_ERROR_NULL_POINTER; enumerator->First(); nsCOMPtr currentItem; res = enumerator->CurrentItem(getter_AddRefs(currentItem)); if (NS_FAILED(res)) return res; //XXX: should be while loop? if (currentItem) { nsCOMPtr range( do_QueryInterface(currentItem) ); // scan the range for all the independent block content blockSections // and get the block parent of each nsISupportsArray *blockSections; res = NS_NewISupportsArray(&blockSections); if (NS_FAILED(res)) return res; if (!blockSections) return NS_ERROR_NULL_POINTER; res = GetBlockSectionsForRange(range, blockSections); if (NS_SUCCEEDED(res)) { nsIDOMRange *subRange; subRange = (nsIDOMRange *)(blockSections->ElementAt(0)); while (subRange) { nsCOMPtrstartParent; res = subRange->GetStartContainer(getter_AddRefs(startParent)); if (NS_SUCCEEDED(res) && startParent) { nsCOMPtr blockParent; if (aGetLists) { // Get the "ol", "ul", or "dl" parent element res = GetElementOrParentByTagName(NS_ConvertASCIItoUCS2("list"), startParent, getter_AddRefs(blockParent)); } else { blockParent = do_QueryInterface(GetBlockNodeParent(startParent)); } if (NS_SUCCEEDED(res) && blockParent) { nsAutoString blockParentTag; blockParent->GetTagName(blockParentTag); PRBool isRoot; IsRootTag(blockParentTag, isRoot); if ((!isRoot) && (-1==aTagList->IndexOf(blockParentTag))) { aTagList->AppendString(blockParentTag); } } } NS_RELEASE(subRange); if (NS_FAILED(res)) break; // don't return here, need to release blockSections blockSections->RemoveElementAt(0); subRange = (nsIDOMRange *)(blockSections->ElementAt(0)); } } NS_RELEASE(blockSections); } return res; } NS_IMETHODIMP nsHTMLEditor::GetParagraphState(PRBool *aMixed, nsAWritableString &outFormat) { if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } if (!aMixed) return NS_ERROR_NULL_POINTER; nsCOMPtr htmlRules = do_QueryInterface(mRules); if (!htmlRules) return NS_ERROR_FAILURE; return htmlRules->GetParagraphState(aMixed, outFormat); } NS_IMETHODIMP nsHTMLEditor::GetBackgroundColorState(PRBool *aMixed, nsAWritableString &aOutColor) { //TODO: We don't handle "mixed" correctly! if (!aMixed) return NS_ERROR_NULL_POINTER; *aMixed = PR_FALSE; aOutColor.Assign(NS_LITERAL_STRING("")); nsCOMPtr element; PRInt32 selectedCount; nsAutoString tagName; nsresult res = GetSelectedOrParentTableElement(*getter_AddRefs(element), tagName, selectedCount); if (NS_FAILED(res)) return res; nsAutoString styleName; styleName.AssignWithConversion("bgcolor"); while (element) { // We are in a cell or selected table res = element->GetAttribute(styleName, aOutColor); if (NS_FAILED(res)) return res; // Done if we have a color explicitly set if (aOutColor.Length() > 0) return NS_OK; // Once we hit the body, we're done if(nsTextEditUtils::IsBody(element)) return NS_OK; // No color is set, but we need to report visible color inherited // from nested cells/tables, so search up parent chain nsCOMPtr parentNode; res = element->GetParentNode(getter_AddRefs(parentNode)); if (NS_FAILED(res)) return res; element = do_QueryInterface(parentNode); } // If no table or cell found, get page body res = nsEditor::GetRootElement(getter_AddRefs(element)); if (NS_FAILED(res)) return res; if (!element) return NS_ERROR_NULL_POINTER; return element->GetAttribute(styleName, aOutColor); } NS_IMETHODIMP nsHTMLEditor::GetListState(PRBool *aMixed, PRBool *aOL, PRBool *aUL, PRBool *aDL) { if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } if (!aMixed || !aOL || !aUL || !aDL) return NS_ERROR_NULL_POINTER; nsCOMPtr htmlRules = do_QueryInterface(mRules); if (!htmlRules) return NS_ERROR_FAILURE; return htmlRules->GetListState(aMixed, aOL, aUL, aDL); } NS_IMETHODIMP nsHTMLEditor::GetListItemState(PRBool *aMixed, PRBool *aLI, PRBool *aDT, PRBool *aDD) { if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } if (!aMixed || !aLI || !aDT || !aDD) return NS_ERROR_NULL_POINTER; nsCOMPtr htmlRules = do_QueryInterface(mRules); if (!htmlRules) return NS_ERROR_FAILURE; return htmlRules->GetListItemState(aMixed, aLI, aDT, aDD); } NS_IMETHODIMP nsHTMLEditor::GetAlignment(PRBool *aMixed, nsIHTMLEditor::EAlignment *aAlign) { if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } if (!aMixed || !aAlign) return NS_ERROR_NULL_POINTER; nsCOMPtr htmlRules = do_QueryInterface(mRules); if (!htmlRules) return NS_ERROR_FAILURE; return htmlRules->GetAlignment(aMixed, aAlign); } NS_IMETHODIMP nsHTMLEditor::GetIndentState(PRBool *aCanIndent, PRBool *aCanOutdent) { if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } if (!aCanIndent || !aCanOutdent) return NS_ERROR_NULL_POINTER; nsCOMPtr htmlRules = do_QueryInterface(mRules); if (!htmlRules) return NS_ERROR_FAILURE; return htmlRules->GetIndentState(aCanIndent, aCanOutdent); } NS_IMETHODIMP nsHTMLEditor::MakeOrChangeList(const nsAReadableString& aListType, PRBool entireList) { nsresult res; if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } nsCOMPtr selection; PRBool cancel, handled; nsAutoEditBatch beginBatching(this); nsAutoRules beginRulesSniffing(this, kOpMakeList, nsIEditor::eNext); // pre-process res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsTextRulesInfo ruleInfo(nsTextEditRules::kMakeList); ruleInfo.blockType = &aListType; ruleInfo.entireList = entireList; res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (cancel || (NS_FAILED(res))) return res; if (!handled) { // Find out if the selection is collapsed: PRBool isCollapsed; res = selection->GetIsCollapsed(&isCollapsed); if (NS_FAILED(res)) return res; nsCOMPtr node; PRInt32 offset; res = GetStartNodeAndOffset(selection, address_of(node), &offset); if (!node) res = NS_ERROR_FAILURE; if (NS_FAILED(res)) return res; if (isCollapsed) { // have to find a place to put the list nsCOMPtr parent = node; nsCOMPtr topChild = node; nsCOMPtr tmp; while ( !CanContainTag(parent, aListType)) { parent->GetParentNode(getter_AddRefs(tmp)); if (!tmp) return NS_ERROR_FAILURE; topChild = parent; parent = tmp; } if (parent != node) { // we need to split up to the child of parent res = SplitNodeDeep(topChild, node, offset, &offset); if (NS_FAILED(res)) return res; } // make a list nsCOMPtr newList; res = CreateNode(aListType, parent, offset, getter_AddRefs(newList)); if (NS_FAILED(res)) return res; // make a list item nsAutoString tag; tag.AssignWithConversion("li"); nsCOMPtr newItem; res = CreateNode(tag, newList, 0, getter_AddRefs(newItem)); if (NS_FAILED(res)) return res; res = selection->Collapse(newItem,0); if (NS_FAILED(res)) return res; } } res = mRules->DidDoAction(selection, &ruleInfo, res); return res; } NS_IMETHODIMP nsHTMLEditor::RemoveList(const nsAReadableString& aListType) { nsresult res; if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } nsCOMPtr selection; PRBool cancel, handled; nsAutoEditBatch beginBatching(this); nsAutoRules beginRulesSniffing(this, kOpRemoveList, nsIEditor::eNext); // pre-process res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsTextRulesInfo ruleInfo(nsTextEditRules::kRemoveList); if (!Compare(aListType,NS_LITERAL_STRING("ol"),nsCaseInsensitiveStringComparator())) ruleInfo.bOrdered = PR_TRUE; else ruleInfo.bOrdered = PR_FALSE; res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (cancel || (NS_FAILED(res))) return res; // no default behavior for this yet. what would it mean? res = mRules->DidDoAction(selection, &ruleInfo, res); return res; } nsresult nsHTMLEditor::MakeDefinitionItem(const nsAReadableString& aItemType) { nsresult res; if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } nsCOMPtr selection; PRBool cancel, handled; nsAutoEditBatch beginBatching(this); nsAutoRules beginRulesSniffing(this, kOpMakeDefListItem, nsIEditor::eNext); // pre-process res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsTextRulesInfo ruleInfo(nsTextEditRules::kMakeDefListItem); ruleInfo.blockType = &aItemType; res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (cancel || (NS_FAILED(res))) return res; if (!handled) { // todo: no default for now. we count on rules to handle it. } res = mRules->DidDoAction(selection, &ruleInfo, res); return res; } nsresult nsHTMLEditor::InsertBasicBlock(const nsAReadableString& aBlockType) { nsresult res; if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } nsCOMPtr selection; PRBool cancel, handled; nsAutoEditBatch beginBatching(this); nsAutoRules beginRulesSniffing(this, kOpMakeBasicBlock, nsIEditor::eNext); // pre-process res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsTextRulesInfo ruleInfo(nsTextEditRules::kMakeBasicBlock); ruleInfo.blockType = &aBlockType; res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (cancel || (NS_FAILED(res))) return res; if (!handled) { // Find out if the selection is collapsed: PRBool isCollapsed; res = selection->GetIsCollapsed(&isCollapsed); if (NS_FAILED(res)) return res; nsCOMPtr node; PRInt32 offset; res = GetStartNodeAndOffset(selection, address_of(node), &offset); if (!node) res = NS_ERROR_FAILURE; if (NS_FAILED(res)) return res; if (isCollapsed) { // have to find a place to put the block nsCOMPtr parent = node; nsCOMPtr topChild = node; nsCOMPtr tmp; while ( !CanContainTag(parent, aBlockType)) { parent->GetParentNode(getter_AddRefs(tmp)); if (!tmp) return NS_ERROR_FAILURE; topChild = parent; parent = tmp; } if (parent != node) { // we need to split up to the child of parent res = SplitNodeDeep(topChild, node, offset, &offset); if (NS_FAILED(res)) return res; } // make a block nsCOMPtr newBlock; res = CreateNode(aBlockType, parent, offset, getter_AddRefs(newBlock)); if (NS_FAILED(res)) return res; // reposition selection to inside the block res = selection->Collapse(newBlock,0); if (NS_FAILED(res)) return res; } } res = mRules->DidDoAction(selection, &ruleInfo, res); return res; } NS_IMETHODIMP nsHTMLEditor::Indent(const nsAReadableString& aIndent) { nsresult res; if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } PRBool cancel, handled; PRInt32 theAction = nsTextEditRules::kIndent; PRInt32 opID = kOpIndent; if (!Compare(aIndent,NS_LITERAL_STRING("outdent"),nsCaseInsensitiveStringComparator())) { theAction = nsTextEditRules::kOutdent; opID = kOpOutdent; } nsAutoEditBatch beginBatching(this); nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext); // pre-process nsCOMPtr selection; res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsTextRulesInfo ruleInfo(theAction); res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (cancel || (NS_FAILED(res))) return res; if (!handled) { // Do default - insert a blockquote node if selection collapsed nsCOMPtr node; PRInt32 offset; PRBool isCollapsed; res = selection->GetIsCollapsed(&isCollapsed); if (NS_FAILED(res)) return res; res = GetStartNodeAndOffset(selection, address_of(node), &offset); if (!node) res = NS_ERROR_FAILURE; if (NS_FAILED(res)) return res; nsAutoString inward; inward.AssignWithConversion("indent"); if (aIndent == inward) { if (isCollapsed) { // have to find a place to put the blockquote nsCOMPtr parent = node; nsCOMPtr topChild = node; nsCOMPtr tmp; nsAutoString bq(NS_LITERAL_STRING("blockquote")); while ( !CanContainTag(parent, bq)) { parent->GetParentNode(getter_AddRefs(tmp)); if (!tmp) return NS_ERROR_FAILURE; topChild = parent; parent = tmp; } if (parent != node) { // we need to split up to the child of parent res = SplitNodeDeep(topChild, node, offset, &offset); if (NS_FAILED(res)) return res; } // make a blockquote nsCOMPtr newBQ; res = CreateNode(bq, parent, offset, getter_AddRefs(newBQ)); if (NS_FAILED(res)) return res; // put a space in it so layout will draw the list item res = selection->Collapse(newBQ,0); if (NS_FAILED(res)) return res; res = InsertText(NS_LITERAL_STRING(" ")); if (NS_FAILED(res)) return res; // reposition selection to before the space character res = GetStartNodeAndOffset(selection, address_of(node), &offset); if (NS_FAILED(res)) return res; res = selection->Collapse(node,0); if (NS_FAILED(res)) return res; } } } res = mRules->DidDoAction(selection, &ruleInfo, res); return res; } //TODO: IMPLEMENT ALIGNMENT! NS_IMETHODIMP nsHTMLEditor::Align(const nsAReadableString& aAlignType) { nsAutoEditBatch beginBatching(this); nsAutoRules beginRulesSniffing(this, kOpAlign, nsIEditor::eNext); nsCOMPtr node; PRBool cancel, handled; // Find out if the selection is collapsed: nsCOMPtr selection; nsresult res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsTextRulesInfo ruleInfo(nsTextEditRules::kAlign); ruleInfo.alignType = &aAlignType; res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled); if (cancel || NS_FAILED(res)) return res; res = mRules->DidDoAction(selection, &ruleInfo, res); return res; } NS_IMETHODIMP nsHTMLEditor::GetElementOrParentByTagName(const nsAReadableString& aTagName, nsIDOMNode *aNode, nsIDOMElement** aReturn) { if (aTagName.Length() == 0 || !aReturn ) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; nsCOMPtr currentNode; if (aNode) currentNode = aNode; else { // If no node supplied, get it from anchor node of current selection nsCOMPtrselection; res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsCOMPtr anchorNode; res = selection->GetAnchorNode(getter_AddRefs(anchorNode)); if(NS_FAILED(res)) return res; if (!anchorNode) return NS_ERROR_FAILURE; // Try to get the actual selected node PRBool hasChildren = PR_FALSE; anchorNode->HasChildNodes(&hasChildren); if (hasChildren) { PRInt32 offset; res = selection->GetAnchorOffset(&offset); if(NS_FAILED(res)) return res; currentNode = nsEditor::GetChildAt(anchorNode, offset); } // anchor node is probably a text node - just use that if (!currentNode) currentNode = anchorNode; } nsAutoString TagName(aTagName); TagName.ToLowerCase(); PRBool getLink = IsLinkTag(TagName); PRBool getNamedAnchor = IsNamedAnchorTag(TagName); if ( getLink || getNamedAnchor) { TagName.AssignWithConversion("a"); } PRBool findTableCell = TagName.EqualsWithConversion("td"); PRBool findList = TagName.EqualsWithConversion("list"); // default is null - no element found *aReturn = nsnull; nsCOMPtr parent; PRBool bNodeFound = PR_FALSE; while (PR_TRUE) { nsAutoString currentTagName; // Test if we have a link (an anchor with href set) if ( (getLink && nsHTMLEditUtils::IsLink(currentNode)) || (getNamedAnchor && nsHTMLEditUtils::IsNamedAnchor(currentNode)) ) { bNodeFound = PR_TRUE; break; } else { if (findList) { // Match "ol", "ul", or "dl" for lists if (nsHTMLEditUtils::IsList(currentNode)) goto NODE_FOUND; } else if (findTableCell) { // Table cells are another special case: // Match either "td" or "th" for them if (nsHTMLEditUtils::IsTableCell(currentNode)) goto NODE_FOUND; } else { currentNode->GetNodeName(currentTagName); if (currentTagName.EqualsIgnoreCase(TagName)) { NODE_FOUND: bNodeFound = PR_TRUE; break; } } } // Search up the parent chain // We should never fail because of root test below, but lets be safe // XXX: ERROR_HANDLING error return code lost if (!NS_SUCCEEDED(currentNode->GetParentNode(getter_AddRefs(parent))) || !parent) break; // Stop searching if parent is a body tag nsAutoString parentTagName; parent->GetNodeName(parentTagName); // Note: Originally used IsRoot to stop at table cells, // but that's too messy when you are trying to find the parent table //PRBool isRoot; //if (!NS_SUCCEEDED(IsRootTag(parentTagName, isRoot)) || isRoot) if(parentTagName.EqualsIgnoreCase("body")) break; currentNode = parent; } if (bNodeFound) { nsCOMPtr currentElement = do_QueryInterface(currentNode); if (currentElement) { *aReturn = currentElement; // Getters must addref NS_ADDREF(*aReturn); } } else res = NS_EDITOR_ELEMENT_NOT_FOUND; return res; } NS_IMETHODIMP nsHTMLEditor::GetSelectedElement(const nsAReadableString& aTagName, nsIDOMElement** aReturn) { if (!aReturn ) return NS_ERROR_NULL_POINTER; // default is null - no element found *aReturn = nsnull; // First look for a single element in selection nsCOMPtrselection; nsresult res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; nsCOMPtr selPriv(do_QueryInterface(selection)); PRBool bNodeFound = PR_FALSE; res=NS_ERROR_NOT_INITIALIZED; PRBool isCollapsed; selection->GetIsCollapsed(&isCollapsed); nsAutoString domTagName; nsAutoString TagName(aTagName); TagName.ToLowerCase(); // Empty string indicates we should match any element tag PRBool anyTag = (TagName.IsEmpty()); PRBool isLinkTag = IsLinkTag(TagName); PRBool isNamedAnchorTag = IsNamedAnchorTag(TagName); nsCOMPtr selectedElement; nsCOMPtr range; res = selection->GetRangeAt(0, getter_AddRefs(range)); if (NS_FAILED(res)) return res; nsCOMPtr startParent; PRInt32 startOffset, endOffset; res = range->GetStartContainer(getter_AddRefs(startParent)); if (NS_FAILED(res)) return res; res = range->GetStartOffset(&startOffset); if (NS_FAILED(res)) return res; nsCOMPtr endParent; res = range->GetEndContainer(getter_AddRefs(endParent)); if (NS_FAILED(res)) return res; res = range->GetEndOffset(&endOffset); if (NS_FAILED(res)) return res; // Optimization for a single selected element if (startParent && startParent == endParent && (endOffset-startOffset) == 1) { nsCOMPtr selectedNode = GetChildAt(startParent, startOffset); if (NS_FAILED(res)) return NS_OK; if (selectedNode) { selectedNode->GetNodeName(domTagName); domTagName.ToLowerCase(); // Test for appropriate node type requested if (anyTag || (TagName == domTagName) || (isLinkTag && nsHTMLEditUtils::IsLink(selectedNode)) || (isNamedAnchorTag && nsHTMLEditUtils::IsNamedAnchor(selectedNode))) { bNodeFound = PR_TRUE; selectedElement = do_QueryInterface(selectedNode); } } } if (!bNodeFound) { if (isLinkTag) { // Link tag is a special case - we return the anchor node // found for any selection that is totally within a link, // included a collapsed selection (just a caret in a link) nsCOMPtr anchorNode; res = selection->GetAnchorNode(getter_AddRefs(anchorNode)); if (NS_FAILED(res)) return res; PRInt32 anchorOffset = -1; if (anchorNode) selection->GetAnchorOffset(&anchorOffset); nsCOMPtr focusNode; res = selection->GetFocusNode(getter_AddRefs(focusNode)); if (NS_FAILED(res)) return res; PRInt32 focusOffset = -1; if (focusNode) selection->GetFocusOffset(&focusOffset); // Link node must be the same for both ends of selection if (NS_SUCCEEDED(res) && anchorNode) { #ifdef DEBUG_cmanske { nsAutoString name; anchorNode->GetNodeName(name); printf("GetSelectedElement: Anchor node of selection: "); wprintf(name.get()); printf(" Offset: %d\n", anchorOffset); focusNode->GetNodeName(name); printf("Focus node of selection: "); wprintf(name.get()); printf(" Offset: %d\n", focusOffset); } #endif nsCOMPtr parentLinkOfAnchor; res = GetElementOrParentByTagName(NS_ConvertASCIItoUCS2("href"), anchorNode, getter_AddRefs(parentLinkOfAnchor)); // XXX: ERROR_HANDLING can parentLinkOfAnchor be null? if (NS_SUCCEEDED(res) && parentLinkOfAnchor) { if (isCollapsed) { // We have just a caret in the link bNodeFound = PR_TRUE; } else if(focusNode) { // Link node must be the same for both ends of selection nsCOMPtr parentLinkOfFocus; res = GetElementOrParentByTagName(NS_ConvertASCIItoUCS2("href"), focusNode, getter_AddRefs(parentLinkOfFocus)); if (NS_SUCCEEDED(res) && parentLinkOfFocus == parentLinkOfAnchor) bNodeFound = PR_TRUE; } // We found a link node parent if (bNodeFound) { // GetElementOrParentByTagName addref'd this, so we don't need to do it here *aReturn = parentLinkOfAnchor; NS_IF_ADDREF(*aReturn); return NS_OK; } } else if (anchorOffset >= 0) // Check if link node is the only thing selected { nsCOMPtr anchorChild; anchorChild = GetChildAt(anchorNode,anchorOffset); if (anchorChild && nsHTMLEditUtils::IsLink(anchorChild) && (anchorNode == focusNode) && focusOffset == (anchorOffset+1)) { selectedElement = do_QueryInterface(anchorChild); bNodeFound = PR_TRUE; } } } } if (!isCollapsed) // Don't bother to examine selection if it is collapsed { nsCOMPtr enumerator; res = selPriv->GetEnumerator(getter_AddRefs(enumerator)); if (NS_SUCCEEDED(res)) { if(!enumerator) return NS_ERROR_NULL_POINTER; enumerator->First(); nsCOMPtr currentItem; res = enumerator->CurrentItem(getter_AddRefs(currentItem)); if ((NS_SUCCEEDED(res)) && currentItem) { nsCOMPtr currange( do_QueryInterface(currentItem) ); nsCOMPtr iter; res = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull, NS_GET_IID(nsIContentIterator), getter_AddRefs(iter)); if (NS_FAILED(res)) return res; if (iter) { iter->Init(currange); // loop through the content iterator for each content node nsCOMPtr content; while (NS_ENUMERATOR_FALSE == iter->IsDone()) { res = iter->CurrentNode(getter_AddRefs(content)); // Note likely! if (NS_FAILED(res)) return NS_ERROR_FAILURE; // Query interface to cast nsIContent to nsIDOMNode // then get tagType to compare to aTagName // Clone node of each desired type and append it to the aDomFrag selectedElement = do_QueryInterface(content); if (selectedElement) { // If we already found a node, then we have another element, // thus there's not just one element selected if (bNodeFound) { bNodeFound = PR_FALSE; break; } selectedElement->GetNodeName(domTagName); domTagName.ToLowerCase(); if (anyTag) { // Get name of first selected element selectedElement->GetTagName(TagName); TagName.ToLowerCase(); anyTag = PR_FALSE; } // The "A" tag is a pain, // used for both link(href is set) and "Named Anchor" nsCOMPtr selectedNode = do_QueryInterface(selectedElement); if ( (isLinkTag && nsHTMLEditUtils::IsLink(selectedNode)) || (isNamedAnchorTag && nsHTMLEditUtils::IsNamedAnchor(selectedNode)) ) { bNodeFound = PR_TRUE; } else if (TagName == domTagName) { // All other tag names are handled here bNodeFound = PR_TRUE; } if (!bNodeFound) { // Check if node we have is really part of the selection??? break; } } iter->Next(); } } } else { // Should never get here? isCollapsed = PR_TRUE; printf("isCollapsed was FALSE, but no elements found in selection\n"); } } else { printf("Could not create enumerator for GetSelectionProperties\n"); } } } if (bNodeFound) { *aReturn = selectedElement; if (selectedElement) { // Getters must addref NS_ADDREF(*aReturn); } } else res = NS_EDITOR_ELEMENT_NOT_FOUND; return res; } NS_IMETHODIMP nsHTMLEditor::CreateElementWithDefaults(const nsAReadableString& aTagName, nsIDOMElement** aReturn) { nsresult res=NS_ERROR_NOT_INITIALIZED; if (aReturn) *aReturn = nsnull; if (aTagName.IsEmpty() || !aReturn) // if (!aTagName || !aReturn) return NS_ERROR_NULL_POINTER; nsAutoString TagName(aTagName); TagName.ToLowerCase(); nsAutoString realTagName; if (IsLinkTag(TagName) || IsNamedAnchorTag(TagName)) { realTagName.AssignWithConversion("a"); } else { realTagName = TagName; } //We don't use editor's CreateElement because we don't want to // go through the transaction system nsCOMPtrnewElement; nsCOMPtr newContent; nsCOMPtr doc = do_QueryReferent(mDocWeak); if (!doc) return NS_ERROR_NOT_INITIALIZED; //new call to use instead to get proper HTML element, bug# 39919 res = CreateHTMLContent(realTagName, getter_AddRefs(newContent)); newElement = do_QueryInterface(newContent); if (NS_FAILED(res) || !newElement) return NS_ERROR_FAILURE; // Mark the new element dirty, so it will be formatted newElement->SetAttribute(NS_LITERAL_STRING("_moz_dirty"), nsAutoString()); // Set default values for new elements if (TagName.EqualsWithConversion("hr")) { // Note that we read the user's attributes for these from prefs (in InsertHLine JS) newElement->SetAttribute(NS_LITERAL_STRING("width"),NS_LITERAL_STRING("100%")); newElement->SetAttribute(NS_LITERAL_STRING("size"),NS_LITERAL_STRING("2")); } else if (TagName.EqualsWithConversion("table")) { newElement->SetAttribute(NS_LITERAL_STRING("cellpadding"),NS_LITERAL_STRING("2")); newElement->SetAttribute(NS_LITERAL_STRING("cellspacing"),NS_LITERAL_STRING("2")); newElement->SetAttribute(NS_LITERAL_STRING("border"),NS_LITERAL_STRING("1")); } else if (TagName.EqualsWithConversion("td")) { newElement->SetAttribute(NS_LITERAL_STRING("valign"),NS_LITERAL_STRING("top")); } // ADD OTHER TAGS HERE if (NS_SUCCEEDED(res)) { *aReturn = newElement; // Getters must addref NS_ADDREF(*aReturn); } return res; } NS_IMETHODIMP nsHTMLEditor::InsertLinkAroundSelection(nsIDOMElement* aAnchorElement) { nsresult res=NS_ERROR_NULL_POINTER; nsCOMPtr selection; if (!aAnchorElement) return NS_ERROR_NULL_POINTER; // We must have a real selection res = GetSelection(getter_AddRefs(selection)); if (!selection) { res = NS_ERROR_NULL_POINTER; } if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; PRBool isCollapsed; res = selection->GetIsCollapsed(&isCollapsed); if (NS_FAILED(res)) isCollapsed = PR_TRUE; if (isCollapsed) { printf("InsertLinkAroundSelection called but there is no selection!!!\n"); res = NS_OK; } else { // Be sure we were given an anchor element nsCOMPtr anchor = do_QueryInterface(aAnchorElement); if (anchor) { nsAutoString href; res = anchor->GetHref(href); if (NS_FAILED(res)) return res; if (!href.IsEmpty()) { nsAutoEditBatch beginBatching(this); nsString attribute(NS_LITERAL_STRING("href")); SetInlineProperty(nsIEditProperty::a, attribute, href); //TODO: Enumerate through other properties of the anchor tag // and set those as well. // Optimization: Modify SetTextProperty to set all attributes at once? } } } return res; } NS_IMETHODIMP nsHTMLEditor::SetBackgroundColor(const nsAReadableString& aColor) { NS_PRECONDITION(mDocWeak, "Missing Editor DOM Document"); // Find a selected or enclosing table element to set background on nsCOMPtr element; PRInt32 selectedCount; nsAutoString tagName; nsresult res = GetSelectedOrParentTableElement(*getter_AddRefs(element), tagName, selectedCount); if (NS_FAILED(res)) return res; PRBool setColor = (aColor.Length() > 0); if (element) { if (selectedCount > 0) { // Traverse all selected cells nsCOMPtr cell; res = GetFirstSelectedCell(getter_AddRefs(cell), nsnull); if (NS_SUCCEEDED(res) && cell) { while(cell) { if (setColor) res = SetAttribute(cell, NS_ConvertASCIItoUCS2("bgcolor"), aColor); else res = RemoveAttribute(cell, NS_ConvertASCIItoUCS2("bgcolor")); if (NS_FAILED(res)) break; GetNextSelectedCell(getter_AddRefs(cell), nsnull); }; return res; } } // If we failed to find a cell, fall through to use originally-found element } else { // No table element -- set the background color on the body tag res = nsEditor::GetRootElement(getter_AddRefs(element)); if (NS_FAILED(res)) return res; if (!element) return NS_ERROR_NULL_POINTER; } // Use the editor method that goes through the transaction system if (setColor) res = SetAttribute(element, NS_ConvertASCIItoUCS2("bgcolor"), aColor); else res = RemoveAttribute(element, NS_ConvertASCIItoUCS2("bgcolor")); return res; } NS_IMETHODIMP nsHTMLEditor::SetBodyAttribute(const nsAReadableString& aAttribute, const nsAReadableString& aValue) { nsresult res; // TODO: Check selection for Cell, Row, Column or table and do color on appropriate level NS_ASSERTION(mDocWeak, "Missing Editor DOM Document"); // Set the background color attribute on the body tag nsCOMPtr bodyElement; res = nsEditor::GetRootElement(getter_AddRefs(bodyElement)); if (!bodyElement) res = NS_ERROR_NULL_POINTER; if (NS_SUCCEEDED(res)) { // Use the editor method that goes through the transaction system res = SetAttribute(bodyElement, aAttribute, aValue); } return res; } NS_IMETHODIMP nsHTMLEditor::GetLinkedObjects(nsISupportsArray** aNodeList) { if (!aNodeList) return NS_ERROR_NULL_POINTER; nsresult res; res = NS_NewISupportsArray(aNodeList); if (NS_FAILED(res)) return res; if (!*aNodeList) return NS_ERROR_NULL_POINTER; nsCOMPtr iter; res = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull, NS_GET_IID(nsIContentIterator), getter_AddRefs(iter)); if (!iter) return NS_ERROR_NULL_POINTER; if ((NS_SUCCEEDED(res))) { // get the root content nsCOMPtr rootContent; nsCOMPtr domdoc; nsEditor::GetDocument(getter_AddRefs(domdoc)); if (!domdoc) return NS_ERROR_UNEXPECTED; nsCOMPtr doc (do_QueryInterface(domdoc)); if (!doc) return NS_ERROR_UNEXPECTED; doc->GetRootContent(getter_AddRefs(rootContent)); iter->Init(rootContent); // loop through the content iterator for each content node while (NS_ENUMERATOR_FALSE == iter->IsDone()) { nsCOMPtr content; res = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(res)) break; nsCOMPtr node (do_QueryInterface(content)); if (node) { // Let nsURIRefObject make the hard decisions: nsCOMPtr refObject; res = NS_NewHTMLURIRefObject(getter_AddRefs(refObject), node); if (NS_SUCCEEDED(res)) { nsCOMPtr isupp (do_QueryInterface(refObject)); if (isupp) (*aNodeList)->AppendElement(isupp); } } iter->Next(); } } return NS_OK; } #ifdef XP_MAC #pragma mark - #pragma mark nsIEditorStyleSheets methods #pragma mark - #endif NS_IMETHODIMP nsHTMLEditor::AddStyleSheet(nsICSSStyleSheet* aSheet) { AddStyleSheetTxn* txn; nsresult rv = CreateTxnForAddStyleSheet(aSheet, &txn); if (!txn) rv = NS_ERROR_NULL_POINTER; if (NS_SUCCEEDED(rv)) { rv = Do(txn); if (NS_SUCCEEDED(rv)) { mLastStyleSheet = do_QueryInterface(aSheet); // save it so we can remove before applying the next one } } // The transaction system (if any) has taken ownwership of txns NS_IF_RELEASE(txn); return rv; } NS_IMETHODIMP nsHTMLEditor::RemoveStyleSheet(nsICSSStyleSheet* aSheet) { RemoveStyleSheetTxn* txn; nsresult rv = CreateTxnForRemoveStyleSheet(aSheet, &txn); if (!txn) rv = NS_ERROR_NULL_POINTER; if (NS_SUCCEEDED(rv)) { rv = Do(txn); if (NS_SUCCEEDED(rv)) { mLastStyleSheet = nsnull; // forget it } } // The transaction system (if any) has taken ownwership of txns NS_IF_RELEASE(txn); return rv; } // Do NOT use transaction system for override style sheets NS_IMETHODIMP nsHTMLEditor::RemoveOverrideStyleSheet(nsICSSStyleSheet* aSheet) { if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; nsCOMPtr ps = do_QueryReferent(mPresShellWeak); if (!ps) return NS_ERROR_NOT_INITIALIZED; nsCOMPtr document; nsresult rv = ps->GetDocument(getter_AddRefs(document)); if (NS_FAILED(rv)) return rv; if (!document) return NS_ERROR_NULL_POINTER; nsCOMPtr styleSet; rv = ps->GetStyleSet(getter_AddRefs(styleSet)); if (NS_FAILED(rv)) return rv; if (!styleSet) return NS_ERROR_NULL_POINTER; nsCOMPtr styleSheet = do_QueryInterface(aSheet); if (!styleSheet) return NS_ERROR_NULL_POINTER; styleSet->RemoveOverrideStyleSheet(styleSheet); // This notifies document observers to rebuild all frames // (this doesn't affect style sheet because it is not a doc sheet) document->SetStyleSheetDisabledState(styleSheet, PR_FALSE); return NS_OK; } NS_IMETHODIMP nsHTMLEditor::ApplyOverrideStyleSheet(const nsAReadableString& aURL, nsICSSStyleSheet **aStyleSheet) { return ApplyDocumentOrOverrideStyleSheet(aURL, PR_TRUE, aStyleSheet); } NS_IMETHODIMP nsHTMLEditor::ApplyStyleSheet(const nsAReadableString& aURL, nsICSSStyleSheet **aStyleSheet) { return ApplyDocumentOrOverrideStyleSheet(aURL, PR_FALSE, aStyleSheet); } //Note: Loading a document style sheet is undoable, loading an override sheet is not nsresult nsHTMLEditor::ApplyDocumentOrOverrideStyleSheet(const nsAReadableString& aURL, PRBool aOverride, nsICSSStyleSheet **aStyleSheet) { nsresult rv = NS_OK; nsCOMPtr uaURL; rv = NS_NewURI(getter_AddRefs(uaURL), aURL); if (NS_SUCCEEDED(rv)) { nsCOMPtr document; if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; nsCOMPtr ps = do_QueryReferent(mPresShellWeak); if (!ps) return NS_ERROR_NOT_INITIALIZED; rv = ps->GetDocument(getter_AddRefs(document)); if (NS_FAILED(rv)) return rv; if (!document) return NS_ERROR_NULL_POINTER; nsCOMPtr container = do_QueryInterface(document); if (!container) return NS_ERROR_NULL_POINTER; nsCOMPtr cssLoader; nsCOMPtr cssStyleSheet; rv = container->GetCSSLoader(*getter_AddRefs(cssLoader)); if (NS_FAILED(rv)) return rv; if (!cssLoader) return NS_ERROR_NULL_POINTER; PRBool complete; if (aOverride) { // We use null for the callback and data pointer because // we MUST ONLY load synchronous local files (no @import) rv = cssLoader->LoadAgentSheet(uaURL, *getter_AddRefs(cssStyleSheet), complete, nsnull); // Synchronous loads should ALWAYS return completed if (!complete || !cssStyleSheet) return NS_ERROR_NULL_POINTER; nsCOMPtr styleSheet; styleSheet = do_QueryInterface(cssStyleSheet); nsCOMPtr styleSet; rv = ps->GetStyleSet(getter_AddRefs(styleSet)); if (NS_FAILED(rv)) return rv; if (!styleSet) return NS_ERROR_NULL_POINTER; // Add the override style sheet // (This checks if already exists) styleSet->AppendOverrideStyleSheet(styleSheet); // Save doc pointer to be able to use nsIStyleSheet::SetEnabled() styleSheet->SetOwningDocument(document); // This notifies document observers to rebuild all frames // (this doesn't affect style sheet because it is not a doc sheet) document->SetStyleSheetDisabledState(styleSheet, PR_FALSE); } else { rv = cssLoader->LoadAgentSheet(uaURL, *getter_AddRefs(cssStyleSheet), complete, this); if (NS_FAILED(rv)) return rv; if (complete) ApplyStyleSheetToPresShellDocument(cssStyleSheet,this); // // If not complete, we will be notified later // with a call to ApplyStyleSheetToPresShellDocument(). // } if (aStyleSheet) { *aStyleSheet = cssStyleSheet; NS_ADDREF(*aStyleSheet); } } return rv; } #ifdef XP_MAC #pragma mark - #pragma mark nsIEditorMailSupport methods #pragma mark - #endif NS_IMETHODIMP nsHTMLEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList) { if (!aNodeList) return NS_ERROR_NULL_POINTER; nsresult res; res = NS_NewISupportsArray(aNodeList); if (NS_FAILED(res)) return res; if (!*aNodeList) return NS_ERROR_NULL_POINTER; nsCOMPtr iter; res = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull, NS_GET_IID(nsIContentIterator), getter_AddRefs(iter)); if (!iter) return NS_ERROR_NULL_POINTER; if ((NS_SUCCEEDED(res))) { // get the root content nsCOMPtr rootContent; nsCOMPtr domdoc; nsEditor::GetDocument(getter_AddRefs(domdoc)); if (!domdoc) return NS_ERROR_UNEXPECTED; nsCOMPtr doc (do_QueryInterface(domdoc)); if (!doc) return NS_ERROR_UNEXPECTED; doc->GetRootContent(getter_AddRefs(rootContent)); iter->Init(rootContent); // loop through the content iterator for each content node while (NS_ENUMERATOR_FALSE == iter->IsDone()) { nsCOMPtr content; res = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(res)) break; nsCOMPtr node (do_QueryInterface(content)); if (node) { nsAutoString tagName; node->GetNodeName(tagName); tagName.ToLowerCase(); // See if it's an image or an embed if (tagName.EqualsWithConversion("img") || tagName.EqualsWithConversion("embed")) (*aNodeList)->AppendElement(node); else if (tagName.EqualsWithConversion("a")) { // Only include links if they're links to file: URLs nsCOMPtr anchor (do_QueryInterface(content)); if (anchor) { nsAutoString href; if (NS_SUCCEEDED(anchor->GetHref(href))) if (href.CompareWithConversion("file:", PR_TRUE, 5) == 0) (*aNodeList)->AppendElement(node); } } } iter->Next(); } } return res; } #ifdef XP_MAC #pragma mark - #pragma mark nsIEditor overrides #pragma mark - #endif // Undo, Redo, Cut, CanCut, Copy, CanCopy, all inherited from nsPlaintextEditor static nsresult SetSelectionAroundHeadChildren(nsCOMPtr aSelection, nsWeakPtr aDocWeak) { nsresult res = NS_OK; // Set selection around node nsCOMPtrnodeList; nsAutoString headTag; headTag.AssignWithConversion("head"); nsCOMPtr doc = do_QueryReferent(aDocWeak); if (!doc) return NS_ERROR_NOT_INITIALIZED; res = doc->GetElementsByTagName(headTag, getter_AddRefs(nodeList)); if (NS_FAILED(res)) return res; if (!nodeList) return NS_ERROR_NULL_POINTER; PRUint32 count; nodeList->GetLength(&count); if (count < 1) return NS_ERROR_FAILURE; nsCOMPtr headNode; res = nodeList->Item(0, getter_AddRefs(headNode)); if (NS_FAILED(res)) return res; if (!headNode) return NS_ERROR_NULL_POINTER; // Collapse selection to before first child of the head, res = aSelection->Collapse(headNode, 0); if (NS_FAILED(res)) return res; // then extend it to just after nsCOMPtr childNodes; res = headNode->GetChildNodes(getter_AddRefs(childNodes)); if (NS_FAILED(res)) return res; if (!childNodes) return NS_ERROR_NULL_POINTER; PRUint32 childCount; childNodes->GetLength(&childCount); return aSelection->Extend(headNode, childCount+1); } NS_IMETHODIMP nsHTMLEditor::GetHeadContentsAsHTML(nsAWritableString& aOutputString) { nsCOMPtr selection; nsresult res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res)) return res; if (!selection) return NS_ERROR_NULL_POINTER; // Save current selection nsAutoSelectionReset selectionResetter(selection, this); res = SetSelectionAroundHeadChildren(selection, mDocWeak); if (NS_FAILED(res)) return res; res = OutputToString(aOutputString, NS_ConvertASCIItoUCS2("text/html"), nsIDocumentEncoder::OutputSelectionOnly); if (NS_SUCCEEDED(res)) { // Selection always includes , // so terminate there nsReadingIterator findIter,endFindIter; aOutputString.BeginReading(findIter); aOutputString.EndReading(endFindIter); //counting on our parser to always lower case!!! if (FindInReadable(NS_LITERAL_STRING(" beginIter; aOutputString.BeginReading(beginIter); PRInt32 offset = Distance(beginIter, findIter);//get the distance nsWritingIterator writeIter; aOutputString.BeginWriting(writeIter); // Ensure the string ends in a newline PRUnichar newline ('\n'); findIter.advance(-1); if (offset ==0 || (offset >0 && (*findIter) != newline)) //check for 0 { writeIter.advance(offset); *writeIter = newline; aOutputString.Truncate(offset+1); } } } return res; } NS_IMETHODIMP nsHTMLEditor::DebugUnitTests(PRInt32 *outNumTests, PRInt32 *outNumTestsFailed) { #ifdef DEBUG if (!outNumTests || !outNumTestsFailed) return NS_ERROR_NULL_POINTER; TextEditorTest *tester = new TextEditorTest(); if (!tester) return NS_ERROR_OUT_OF_MEMORY; tester->Run(this, outNumTests, outNumTestsFailed); delete tester; return NS_OK; #else return NS_ERROR_NOT_IMPLEMENTED; #endif } #ifdef XP_MAC #pragma mark - #pragma mark nsIEditorIMESupport overrides #pragma mark - #endif NS_IMETHODIMP nsHTMLEditor::SetCompositionString(const nsAReadableString& aCompositionString, nsIPrivateTextRangeList* aTextRangeList,nsTextEventReply* aReply) { NS_ASSERTION(aTextRangeList, "null ptr"); if(nsnull == aTextRangeList) return NS_ERROR_NULL_POINTER; nsCOMPtr caretP; // workaround for windows ime bug 23558: we get every ime event twice. // for escape keypress, this causes an empty string to be passed // twice, which freaks out the editor. This is to detect and aviod that // situation: if (aCompositionString.IsEmpty() && !mIMETextNode) { return NS_OK; } nsCOMPtr selection; nsresult result = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(result)) return result; mIMETextRangeList = aTextRangeList; nsAutoPlaceHolderBatch batch(this, gIMETxnName); result = InsertText(aCompositionString); mIMEBufferLength = aCompositionString.Length(); if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; nsCOMPtr ps = do_QueryReferent(mPresShellWeak); if (!ps) return NS_ERROR_NOT_INITIALIZED; ps->GetCaret(getter_AddRefs(caretP)); caretP->SetCaretDOMSelection(selection); result = caretP->GetCaretCoordinates(nsICaret::eIMECoordinates, selection, &(aReply->mCursorPosition), &(aReply->mCursorIsCollapsed)); // second part of 23558 fix: if (aCompositionString.IsEmpty()) { mIMETextNode = nsnull; } return result; } NS_IMETHODIMP nsHTMLEditor::GetReconversionString(nsReconversionEventReply* aReply) { nsresult res; nsCOMPtr selection; res = GetSelection(getter_AddRefs(selection)); if (NS_FAILED(res) || !selection) return (res == NS_OK) ? NS_ERROR_FAILURE : res; // get the first range in the selection. Since it is // unclear what to do if reconversion happens with a // multirange selection, we will ignore any additional ranges. nsCOMPtr range; res = selection->GetRangeAt(0, getter_AddRefs(range)); if (NS_FAILED(res) || !range) return (res == NS_OK) ? NS_ERROR_FAILURE : res; nsAutoString textValue; res = range->ToString(textValue); if (NS_FAILED(res)) return res; aReply->mReconversionString = (PRUnichar*) nsMemory::Clone(textValue.get(), (textValue.Length() + 1) * sizeof(PRUnichar)); if (!aReply->mReconversionString) return NS_ERROR_OUT_OF_MEMORY; // delete the selection res = DeleteSelection(eNone); return res; } #ifdef XP_MAC #pragma mark - #pragma mark StyleSheet utils #pragma mark - #endif NS_IMETHODIMP nsHTMLEditor::ReplaceStyleSheet(nsICSSStyleSheet *aNewSheet) { nsresult rv = NS_OK; nsAutoEditBatch batchIt(this); if (mLastStyleSheet) { rv = RemoveStyleSheet(mLastStyleSheet); //XXX: rv is ignored here, why? } rv = AddStyleSheet(aNewSheet); return rv; } NS_IMETHODIMP nsHTMLEditor::StyleSheetLoaded(nsICSSStyleSheet*aSheet, PRBool aNotify) { ApplyStyleSheetToPresShellDocument(aSheet, this); return NS_OK; } /* static callback */ void nsHTMLEditor::ApplyStyleSheetToPresShellDocument(nsICSSStyleSheet* aSheet, void *aData) { nsresult rv = NS_OK; nsHTMLEditor *editor = NS_STATIC_CAST(nsHTMLEditor*, aData); if (editor) { rv = editor->ReplaceStyleSheet(aSheet); } // XXX: we lose the return value here. Set a flag in the editor? } #ifdef XP_MAC #pragma mark - #pragma mark nsEditor overrides #pragma mark - #endif /** All editor operations which alter the doc should be prefaced * with a call to StartOperation, naming the action and direction */ NS_IMETHODIMP nsHTMLEditor::StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection) { nsEditor::StartOperation(opID, aDirection); // will set mAction, mDirection if (! ((mAction==kOpInsertText) || (mAction==kOpInsertIMEText)) ) ClearInlineStylesCache(); if (mRules) return mRules->BeforeEdit(mAction, mDirection); return NS_OK; } /** All editor operations which alter the doc should be followed * with a call to EndOperation */ NS_IMETHODIMP nsHTMLEditor::EndOperation() { // post processing if (! ((mAction==kOpInsertText) || (mAction==kOpInsertIMEText) || (mAction==kOpIgnore)) ) ClearInlineStylesCache(); nsresult res = NS_OK; if (mRules) res = mRules->AfterEdit(mAction, mDirection); nsEditor::EndOperation(); // will clear mAction, mDirection return res; } PRBool nsHTMLEditor::TagCanContainTag(const nsAReadableString& aParentTag, const nsAReadableString& aChildTag) { // COtherDTD gives some unwanted results. We override them here. if (!Compare(aParentTag,NS_LITERAL_STRING("ol"),nsCaseInsensitiveStringComparator()) || !Compare(aParentTag,NS_LITERAL_STRING("ul"),nsCaseInsensitiveStringComparator())) { // if parent is a list and tag is also a list, say "yes". // This is because the editor does sublists illegally for now. if (!Compare(aChildTag,NS_LITERAL_STRING("ol"),nsCaseInsensitiveStringComparator()) || !Compare(aChildTag,NS_LITERAL_STRING("ul"),nsCaseInsensitiveStringComparator())) return PR_TRUE; } if (!Compare(aParentTag,NS_LITERAL_STRING("li"),nsCaseInsensitiveStringComparator())) { // list items cant contain list items if (!Compare(aChildTag,NS_LITERAL_STRING("li"),nsCaseInsensitiveStringComparator())) return PR_FALSE; } /* // if parent is a pre, and child is not inline, say "no" if ( aParentTag.EqualsWithConversion("pre") ) { if (aChildTag.EqualsWithConversion("__moz_text")) return PR_TRUE; PRInt32 childTagEnum, parentTagEnum; nsAutoString non_const_childTag(aChildTag); nsAutoString non_const_parentTag(aParentTag); nsresult res = mDTD->StringTagToIntTag(non_const_childTag,&childTagEnum); if (NS_FAILED(res)) return PR_FALSE; res = mDTD->StringTagToIntTag(non_const_parentTag,&parentTagEnum); if (NS_FAILED(res)) return PR_FALSE; if (!mDTD->IsInlineElement(childTagEnum,parentTagEnum)) return PR_FALSE; } */ // else fall thru return nsEditor::TagCanContainTag(aParentTag, aChildTag); } NS_IMETHODIMP nsHTMLEditor::SelectEntireDocument(nsISelection *aSelection) { nsresult res; if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; } // get body node nsCOMPtrbodyElement; res = GetRootElement(getter_AddRefs(bodyElement)); if (NS_FAILED(res)) return res; nsCOMPtrbodyNode = do_QueryInterface(bodyElement); if (!bodyNode) return NS_ERROR_FAILURE; // is doc empty? PRBool bDocIsEmpty; res = mRules->DocumentIsEmpty(&bDocIsEmpty); if (NS_FAILED(res)) return res; if (bDocIsEmpty) { // if its empty dont select entire doc - that would select the bogus node return aSelection->Collapse(bodyNode, 0); } else { return nsEditor::SelectEntireDocument(aSelection); } return res; } #ifdef XP_MAC #pragma mark - #pragma mark Random methods #pragma mark - #endif NS_IMETHODIMP nsHTMLEditor::GetLayoutObject(nsIDOMNode *aNode, nsISupports **aLayoutObject) { nsresult result = NS_ERROR_FAILURE; // we return an error unless we get the index if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED; nsCOMPtr ps = do_QueryReferent(mPresShellWeak); if (!ps) return NS_ERROR_NOT_INITIALIZED; if ((nsnull!=aNode)) { // get the content interface nsCOMPtr nodeAsContent( do_QueryInterface(aNode) ); if (nodeAsContent) { // get the frame from the content interface //Note: frames are not ref counted, so don't use an nsCOMPtr *aLayoutObject = nsnull; result = ps->GetLayoutObjectFor(nodeAsContent, aLayoutObject); } } else { result = NS_ERROR_NULL_POINTER; } return result; } // this will NOT find aAttribute unless aAttribute has a non-null value // so singleton attributes like will not be matched! void nsHTMLEditor::IsTextPropertySetByContent(nsIDOMNode *aNode, nsIAtom *aProperty, const nsAReadableString *aAttribute, const nsAReadableString *aValue, PRBool &aIsSet, nsIDOMNode **aStyleNode, nsString *outValue) const { nsresult result; aIsSet = PR_FALSE; // must be initialized to false for code below to work nsAutoString propName; aProperty->ToString(propName); nsCOMPtrnode = aNode; while (node) { nsCOMPtrelement; element = do_QueryInterface(node); if (element) { nsAutoString tag, value; element->GetTagName(tag); if (propName.EqualsIgnoreCase(tag)) { PRBool found = PR_FALSE; if (aAttribute && 0!=aAttribute->Length()) { element->GetAttribute(*aAttribute, value); if (outValue) *outValue = value; if (value.Length()) { if (!aValue) { found = PR_TRUE; } else { nsString tString(*aValue); if (tString.EqualsIgnoreCase(value)) { found = PR_TRUE; } else { // we found the prop with the attribute, but the value doesn't match break; } } } } else { found = PR_TRUE; } if (found) { aIsSet = PR_TRUE; break; } } } nsCOMPtrtemp; result = node->GetParentNode(getter_AddRefs(temp)); if (NS_SUCCEEDED(result) && temp) { node = do_QueryInterface(temp); } else { node = do_QueryInterface(nsnull); } } } void nsHTMLEditor::IsTextStyleSet(nsIStyleContext *aSC, nsIAtom *aProperty, const nsAReadableString *aAttribute, PRBool &aIsSet) const { aIsSet = PR_FALSE; if (aSC && aProperty) { nsStyleFont* font = (nsStyleFont*)aSC->GetStyleData(eStyleStruct_Font); if (nsIEditProperty::i==aProperty) { aIsSet = PRBool(font->mFont.style & NS_FONT_STYLE_ITALIC); } else if (nsIEditProperty::b==aProperty) { // XXX: check this logic with Peter aIsSet = PRBool(font->mFont.weight > NS_FONT_WEIGHT_NORMAL); } } } #ifdef XP_MAC #pragma mark - #endif //================================================================ // HTML Editor methods // // Note: Table Editing methods are implemented in nsTableEditor.cpp // PRBool nsHTMLEditor::IsElementInBody(nsIDOMElement* aElement) { return nsTextEditUtils::InBody(aElement, this); } PRBool nsHTMLEditor::SetCaretInTableCell(nsIDOMElement* aElement) { PRBool caretIsSet = PR_FALSE; if (aElement && IsElementInBody(aElement)) { nsresult res = NS_OK; nsCOMPtr content = do_QueryInterface(aElement); if (content) { nsCOMPtr atom; content->GetTag(*getter_AddRefs(atom)); if (atom.get() == nsIEditProperty::table || atom.get() == nsIEditProperty::tbody || atom.get() == nsIEditProperty::thead || atom.get() == nsIEditProperty::tfoot || atom.get() == nsIEditProperty::caption || atom.get() == nsIEditProperty::tr || atom.get() == nsIEditProperty::td ) { nsCOMPtr node = do_QueryInterface(aElement); nsCOMPtr parent; // This MUST succeed if IsElementInBody was TRUE node->GetParentNode(getter_AddRefs(parent)); nsCOMPtrfirstChild; // Find deepest child PRBool hasChild; while (NS_SUCCEEDED(node->HasChildNodes(&hasChild)) && hasChild) { if (NS_SUCCEEDED(node->GetFirstChild(getter_AddRefs(firstChild)))) { parent = node; node = firstChild; } } // Set selection at beginning of deepest node nsCOMPtr selection; res = GetSelection(getter_AddRefs(selection)); if (NS_SUCCEEDED(res) && selection && firstChild) { res = selection->Collapse(firstChild, 0); if (NS_SUCCEEDED(res)) caretIsSet = PR_TRUE; } } } } return caretIsSet; } NS_IMETHODIMP nsHTMLEditor::IsRootTag(nsString &aTag, PRBool &aIsTag) { static char bodyTag[] = "body"; static char tdTag[] = "td"; static char thTag[] = "th"; static char captionTag[] = "caption"; if (aTag.EqualsIgnoreCase(bodyTag) || aTag.EqualsIgnoreCase(tdTag) || aTag.EqualsIgnoreCase(thTag) || aTag.EqualsIgnoreCase(captionTag) ) { aIsTag = PR_TRUE; } else { aIsTag = PR_FALSE; } return NS_OK; } NS_IMETHODIMP nsHTMLEditor::IsSubordinateBlock(nsString &aTag, PRBool &aIsTag) { static char p[] = "p"; static char h1[] = "h1"; static char h2[] = "h2"; static char h3[] = "h3"; static char h4[] = "h4"; static char h5[] = "h5"; static char h6[] = "h6"; static char address[] = "address"; static char pre[] = "pre"; static char li[] = "li"; static char dt[] = "dt"; static char dd[] = "dd"; if (aTag.EqualsIgnoreCase(p) || aTag.EqualsIgnoreCase(h1) || aTag.EqualsIgnoreCase(h2) || aTag.EqualsIgnoreCase(h3) || aTag.EqualsIgnoreCase(h4) || aTag.EqualsIgnoreCase(h5) || aTag.EqualsIgnoreCase(h6) || aTag.EqualsIgnoreCase(address) || aTag.EqualsIgnoreCase(pre) || aTag.EqualsIgnoreCase(li) || aTag.EqualsIgnoreCase(dt) || aTag.EqualsIgnoreCase(dd) ) { aIsTag = PR_TRUE; } else { aIsTag = PR_FALSE; } return NS_OK; } /////////////////////////////////////////////////////////////////////////// // GetEnclosingTable: find ancestor who is a table, if any // nsCOMPtr nsHTMLEditor::GetEnclosingTable(nsIDOMNode *aNode) { NS_PRECONDITION(aNode, "null node passed to nsHTMLEditor::GetEnclosingTable"); nsCOMPtr tbl, tmp, node = aNode; while (!tbl) { tmp = GetBlockNodeParent(node); if (!tmp) break; if (nsHTMLEditUtils::IsTable(tmp)) tbl = tmp; node = tmp; } return tbl; } #ifdef XP_MAC #pragma mark - #endif void nsHTMLEditor::CacheInlineStyles(nsIDOMNode *aNode) { if (!aNode) return; nsCOMPtr resultNode; mCachedNode = do_QueryInterface(aNode); IsTextPropertySetByContent(aNode, mBoldAtom, 0, 0, mCachedBoldStyle, getter_AddRefs(resultNode)); IsTextPropertySetByContent(aNode, mItalicAtom, 0, 0, mCachedItalicStyle, getter_AddRefs(resultNode)); IsTextPropertySetByContent(aNode, mUnderlineAtom, 0, 0, mCachedUnderlineStyle, getter_AddRefs(resultNode)); } void nsHTMLEditor::ClearInlineStylesCache() { mCachedNode = nsnull; } #ifdef PRE_NODE_IN_BODY nsCOMPtr nsHTMLEditor::FindPreElement() { nsCOMPtr domdoc; nsEditor::GetDocument(getter_AddRefs(domdoc)); if (!domdoc) return 0; nsCOMPtr doc (do_QueryInterface(domdoc)); if (!doc) return 0; nsCOMPtr rootContent; doc->GetRootContent(getter_AddRefs(rootContent)); if (!rootContent) return 0; nsCOMPtr rootNode (do_QueryInterface(rootContent)); if (!rootNode) return 0; nsString prestr ("PRE"); // GetFirstNodeOfType requires capitals nsCOMPtr preNode; if (!NS_SUCCEEDED(nsEditor::GetFirstNodeOfType(rootNode, prestr, getter_AddRefs(preNode)))) return 0; return do_QueryInterface(preNode); } #endif /* PRE_NODE_IN_BODY */ void nsHTMLEditor::HandleEventListenerError() { if (gNoisy) { printf("failed to add event listener\n"); } // null out the nsCOMPtrs mKeyListenerP = nsnull; mMouseListenerP = nsnull; mTextListenerP = nsnull; mDragListenerP = nsnull; mCompositionListenerP = nsnull; mFocusListenerP = nsnull; } /* this method scans the selection for adjacent text nodes * and collapses them into a single text node. * "adjacent" means literally adjacent siblings of the same parent. * Uses nsEditor::JoinNodes so action is undoable. * Should be called within the context of a batch transaction. */ NS_IMETHODIMP nsHTMLEditor::CollapseAdjacentTextNodes(nsIDOMRange *aInRange) { if (!aInRange) return NS_ERROR_NULL_POINTER; nsAutoTxnsConserveSelection dontSpazMySelection(this); nsVoidArray textNodes; // we can't actually do anything during iteration, so store the text nodes in an array // don't bother ref counting them because we know we can hold them for the // lifetime of this method // build a list of editable text nodes nsCOMPtr iter; nsresult result = nsComponentManager::CreateInstance(kSubtreeIteratorCID, nsnull, NS_GET_IID(nsIContentIterator), getter_AddRefs(iter)); if (NS_FAILED(result)) return result; if (!iter) return NS_ERROR_NULL_POINTER; iter->Init(aInRange); nsCOMPtr content; result = iter->CurrentNode(getter_AddRefs(content)); if (!content) return NS_OK; while (NS_ENUMERATOR_FALSE == iter->IsDone()) { nsCOMPtr text = do_QueryInterface(content); nsCOMPtr node = do_QueryInterface(content); if (text && node && IsEditable(node)) { textNodes.AppendElement((void*)(node.get())); } iter->Next(); iter->CurrentNode(getter_AddRefs(content)); } // now that I have a list of text nodes, collapse adjacent text nodes // NOTE: assumption that JoinNodes keeps the righthand node nsIDOMNode *leftTextNode = (nsIDOMNode *)(textNodes.ElementAt(0)); nsIDOMNode *rightTextNode = (nsIDOMNode *)(textNodes.ElementAt(1)); while (leftTextNode && rightTextNode) { // get the prev sibling of the right node, and see if it's leftTextNode nsCOMPtr prevSibOfRightNode; result = GetPriorHTMLSibling(rightTextNode, address_of(prevSibOfRightNode)); if (NS_FAILED(result)) return result; if (prevSibOfRightNode && (prevSibOfRightNode.get() == leftTextNode)) { nsCOMPtr parent; result = rightTextNode->GetParentNode(getter_AddRefs(parent)); if (NS_FAILED(result)) return result; if (!parent) return NS_ERROR_NULL_POINTER; result = JoinNodes(leftTextNode, rightTextNode, parent); if (NS_FAILED(result)) return result; } textNodes.RemoveElementAt(0); // remove the leftmost text node from the list leftTextNode = (nsIDOMNode *)(textNodes.ElementAt(0)); rightTextNode = (nsIDOMNode *)(textNodes.ElementAt(1)); } return result; } NS_IMETHODIMP nsHTMLEditor::GetNextElementByTagName(nsIDOMElement *aCurrentElement, const nsAReadableString *aTagName, nsIDOMElement **aReturn) { nsresult res = NS_OK; if (!aCurrentElement || !aTagName || !aReturn) return NS_ERROR_NULL_POINTER; nsIAtom *tagAtom = NS_NewAtom(*aTagName); if (!tagAtom) { return NS_ERROR_NULL_POINTER; } if (tagAtom==nsIEditProperty::th) tagAtom=nsIEditProperty::td; nsCOMPtr currentNode = do_QueryInterface(aCurrentElement); if (!currentNode) return NS_ERROR_FAILURE; *aReturn = nsnull; nsCOMPtr nextNode; PRBool done = PR_FALSE; do { res = GetNextNode(currentNode, PR_TRUE, address_of(nextNode)); if (NS_FAILED(res)) return res; if (!nextNode) break; nsCOMPtr atom = GetTag(currentNode); if (tagAtom==atom.get()) { nsCOMPtr element = do_QueryInterface(currentNode); if (!element) return NS_ERROR_NULL_POINTER; *aReturn = element; NS_ADDREF(*aReturn); done = PR_TRUE; return NS_OK; } currentNode = nextNode; } while (!done); return res; } NS_IMETHODIMP nsHTMLEditor::SetSelectionAtDocumentStart(nsISelection *aSelection) { nsCOMPtr bodyElement; nsresult res = GetRootElement(getter_AddRefs(bodyElement)); if (NS_SUCCEEDED(res)) { if (!bodyElement) return NS_ERROR_NULL_POINTER; res = aSelection->Collapse(bodyElement,0); } return res; } #ifdef XP_MAC #pragma mark - #endif /////////////////////////////////////////////////////////////////////////// // RemoveBlockContainer: remove inNode, reparenting it's children into their // the parent of inNode. In addition, INSERT ANY BR's NEEDED // TO PRESERVE IDENTITY OF REMOVED BLOCK. // nsresult nsHTMLEditor::RemoveBlockContainer(nsIDOMNode *inNode) { if (!inNode) return NS_ERROR_NULL_POINTER; nsresult res; nsCOMPtr sibling, child, unused; // Two possibilities: the container cold be empty of editable content. // If that is the case, we need to compare what is before and after inNode // to determine if we need a br. // Or it could not be empty, in which case we have to compare previous // sibling and first child to determine if we need a leading br, // and compare following sibling and last child to determine if we need a // trailing br. res = GetFirstEditableChild(inNode, address_of(child)); if (NS_FAILED(res)) return res; if (child) // the case of inNode not being empty { // we need a br at start unless: // 1) previous sibling of inNode is a block, OR // 2) previous sibling of inNode is a br, OR // 3) first child of inNode is a block OR // 4) either is null res = GetPriorHTMLSibling(inNode, address_of(sibling)); if (NS_FAILED(res)) return res; if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling)) { res = GetFirstEditableChild(inNode, address_of(child)); if (NS_FAILED(res)) return res; if (child && !IsBlockNode(child)) { // insert br node res = CreateBR(inNode, 0, address_of(unused)); if (NS_FAILED(res)) return res; } } // we need a br at end unless: // 1) following sibling of inNode is a block, OR // 2) last child of inNode is a block, OR // 3) last child of inNode is a block OR // 4) either is null res = GetNextHTMLSibling(inNode, address_of(sibling)); if (NS_FAILED(res)) return res; if (sibling && !IsBlockNode(sibling)) { res = GetLastEditableChild(inNode, address_of(child)); if (NS_FAILED(res)) return res; if (child && !IsBlockNode(child) && !nsTextEditUtils::IsBreak(child)) { // insert br node PRUint32 len; res = GetLengthOfDOMNode(inNode, len); if (NS_FAILED(res)) return res; res = CreateBR(inNode, (PRInt32)len, address_of(unused)); if (NS_FAILED(res)) return res; } } } else // the case of inNode being empty { // we need a br at start unless: // 1) previous sibling of inNode is a block, OR // 2) previous sibling of inNode is a br, OR // 3) following sibling of inNode is a block, OR // 4) following sibling of inNode is a br OR // 5) either is null res = GetPriorHTMLSibling(inNode, address_of(sibling)); if (NS_FAILED(res)) return res; if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling)) { res = GetNextHTMLSibling(inNode, address_of(sibling)); if (NS_FAILED(res)) return res; if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling)) { // insert br node res = CreateBR(inNode, 0, address_of(unused)); if (NS_FAILED(res)) return res; } } } // now remove container return RemoveContainer(inNode); } /////////////////////////////////////////////////////////////////////////// // GetPriorHTMLSibling: returns the previous editable sibling, if there is // one within the parent // nsresult nsHTMLEditor::GetPriorHTMLSibling(nsIDOMNode *inNode, nsCOMPtr *outNode) { if (!outNode || !inNode) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; *outNode = nsnull; nsCOMPtr temp, node = do_QueryInterface(inNode); while (1) { res = node->GetPreviousSibling(getter_AddRefs(temp)); if (NS_FAILED(res)) return res; if (!temp) return NS_OK; // return null sibling // if it's editable, we're done if (IsEditable(temp)) break; // otherwise try again node = temp; } *outNode = temp; return res; } /////////////////////////////////////////////////////////////////////////// // GetPriorHTMLSibling: returns the previous editable sibling, if there is // one within the parent. just like above routine but // takes a parent/offset instead of a node. // nsresult nsHTMLEditor::GetPriorHTMLSibling(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr *outNode) { if (!outNode || !inParent) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; *outNode = nsnull; if (!inOffset) return NS_OK; // return null sibling if at offset zero nsCOMPtr node = nsEditor::GetChildAt(inParent,inOffset-1); if (IsEditable(node)) { *outNode = node; return res; } // else return GetPriorHTMLSibling(node, outNode); } /////////////////////////////////////////////////////////////////////////// // GetNextHTMLSibling: returns the next editable sibling, if there is // one within the parent // nsresult nsHTMLEditor::GetNextHTMLSibling(nsIDOMNode *inNode, nsCOMPtr *outNode) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; *outNode = nsnull; nsCOMPtr temp, node = do_QueryInterface(inNode); while (1) { res = node->GetNextSibling(getter_AddRefs(temp)); if (NS_FAILED(res)) return res; if (!temp) return NS_OK; // return null sibling // if it's editable, we're done if (IsEditable(temp)) break; // otherwise try again node = temp; } *outNode = temp; return res; } /////////////////////////////////////////////////////////////////////////// // GetNextHTMLSibling: returns the next editable sibling, if there is // one within the parent. just like above routine but // takes a parent/offset instead of a node. // nsresult nsHTMLEditor::GetNextHTMLSibling(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr *outNode) { if (!outNode || !inParent) return NS_ERROR_NULL_POINTER; nsresult res = NS_OK; *outNode = nsnull; nsCOMPtr node = nsEditor::GetChildAt(inParent,inOffset); if (!node) return NS_OK; // return null sibling if no sibling if (IsEditable(node)) { *outNode = node; return res; } // else return GetPriorHTMLSibling(node, outNode); } /////////////////////////////////////////////////////////////////////////// // GetPriorHTMLNode: returns the previous editable leaf node, if there is // one within the // nsresult nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode *inNode, nsCOMPtr *outNode, PRBool bNoBlockCrossing) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = GetPriorNode(inNode, PR_TRUE, address_of(*outNode), bNoBlockCrossing); if (NS_FAILED(res)) return res; // if it's not in the body, then zero it out if (*outNode && !nsTextEditUtils::InBody(*outNode, this)) { *outNode = nsnull; } return res; } /////////////////////////////////////////////////////////////////////////// // GetPriorHTMLNode: same as above but takes {parent,offset} instead of node // nsresult nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr *outNode, PRBool bNoBlockCrossing) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = GetPriorNode(inParent, inOffset, PR_TRUE, address_of(*outNode), bNoBlockCrossing); if (NS_FAILED(res)) return res; // if it's not in the body, then zero it out if (*outNode && !nsTextEditUtils::InBody(*outNode, this)) { *outNode = nsnull; } return res; } /////////////////////////////////////////////////////////////////////////// // GetNextHTMLNode: returns the next editable leaf node, if there is // one within the // nsresult nsHTMLEditor::GetNextHTMLNode(nsIDOMNode *inNode, nsCOMPtr *outNode, PRBool bNoBlockCrossing) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = GetNextNode(inNode, PR_TRUE, address_of(*outNode), bNoBlockCrossing); if (NS_FAILED(res)) return res; // if it's not in the body, then zero it out if (*outNode && !nsTextEditUtils::InBody(*outNode, this)) { *outNode = nsnull; } return res; } /////////////////////////////////////////////////////////////////////////// // GetNHTMLextNode: same as above but takes {parent,offset} instead of node // nsresult nsHTMLEditor::GetNextHTMLNode(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr *outNode, PRBool bNoBlockCrossing) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = GetNextNode(inParent, inOffset, PR_TRUE, address_of(*outNode), bNoBlockCrossing); if (NS_FAILED(res)) return res; // if it's not in the body, then zero it out if (*outNode && !nsTextEditUtils::InBody(*outNode, this)) { *outNode = nsnull; } return res; } nsresult nsHTMLEditor::IsFirstEditableChild( nsIDOMNode *aNode, PRBool *aOutIsFirst) { // check parms if (!aOutIsFirst || !aNode) return NS_ERROR_NULL_POINTER; // init out parms *aOutIsFirst = PR_FALSE; // find first editable child and compare it to aNode nsCOMPtr parent, firstChild; nsresult res = aNode->GetParentNode(getter_AddRefs(parent)); if (NS_FAILED(res)) return res; if (!parent) return NS_ERROR_FAILURE; res = GetFirstEditableChild(parent, address_of(firstChild)); if (NS_FAILED(res)) return res; *aOutIsFirst = (firstChild.get() == aNode); return res; } nsresult nsHTMLEditor::IsLastEditableChild( nsIDOMNode *aNode, PRBool *aOutIsLast) { // check parms if (!aOutIsLast || !aNode) return NS_ERROR_NULL_POINTER; // init out parms *aOutIsLast = PR_FALSE; // find last editable child and compare it to aNode nsCOMPtr parent, lastChild; nsresult res = aNode->GetParentNode(getter_AddRefs(parent)); if (NS_FAILED(res)) return res; if (!parent) return NS_ERROR_FAILURE; res = GetLastEditableChild(parent, address_of(lastChild)); if (NS_FAILED(res)) return res; *aOutIsLast = (lastChild.get() == aNode); return res; } nsresult nsHTMLEditor::GetFirstEditableChild( nsIDOMNode *aNode, nsCOMPtr *aOutFirstChild) { // check parms if (!aOutFirstChild || !aNode) return NS_ERROR_NULL_POINTER; // init out parms *aOutFirstChild = nsnull; // find first editable child nsCOMPtr child; nsresult res = aNode->GetFirstChild(getter_AddRefs(child)); if (NS_FAILED(res)) return res; while (child && !IsEditable(child)) { nsCOMPtr tmp; res = child->GetNextSibling(getter_AddRefs(tmp)); if (NS_FAILED(res)) return res; if (!tmp) return NS_ERROR_FAILURE; child = tmp; } *aOutFirstChild = child; return res; } nsresult nsHTMLEditor::GetLastEditableChild( nsIDOMNode *aNode, nsCOMPtr *aOutLastChild) { // check parms if (!aOutLastChild || !aNode) return NS_ERROR_NULL_POINTER; // init out parms *aOutLastChild = nsnull; // find last editable child nsCOMPtr child; nsresult res = aNode->GetLastChild(getter_AddRefs(child)); if (NS_FAILED(res)) return res; while (child && !IsEditable(child)) { nsCOMPtr tmp; res = child->GetPreviousSibling(getter_AddRefs(tmp)); if (NS_FAILED(res)) return res; if (!tmp) return NS_ERROR_FAILURE; child = tmp; } *aOutLastChild = child; return res; } nsresult nsHTMLEditor::GetFirstEditableLeaf( nsIDOMNode *aNode, nsCOMPtr *aOutFirstLeaf) { // check parms if (!aOutFirstLeaf || !aNode) return NS_ERROR_NULL_POINTER; // init out parms *aOutFirstLeaf = nsnull; // find leftmost leaf nsCOMPtr child; nsresult res = NS_OK; child = GetLeftmostChild(aNode); while (child && (!IsEditable(child) || !nsHTMLEditUtils::IsLeafNode(child))) { nsCOMPtr tmp; res = GetNextHTMLNode(child, address_of(tmp)); if (NS_FAILED(res)) return res; if (!tmp) return NS_ERROR_FAILURE; // only accept nodes that are descendants of aNode if (nsHTMLEditUtils::IsDescendantOf(tmp, aNode)) child = tmp; else { child = nsnull; // this will abort the loop } } *aOutFirstLeaf = child; return res; } nsresult nsHTMLEditor::GetLastEditableLeaf( nsIDOMNode *aNode, nsCOMPtr *aOutLastLeaf) { // check parms if (!aOutLastLeaf || !aNode) return NS_ERROR_NULL_POINTER; // init out parms *aOutLastLeaf = nsnull; // find rightmost leaf nsCOMPtr child; nsresult res = NS_OK; child = GetRightmostChild(aNode); while (child && (!IsEditable(child) || !nsHTMLEditUtils::IsLeafNode(child))) { nsCOMPtr tmp; res = GetPriorHTMLNode(child, address_of(tmp)); if (NS_FAILED(res)) return res; if (!tmp) return NS_ERROR_FAILURE; // only accept nodes that are descendants of aNode if (nsHTMLEditUtils::IsDescendantOf(tmp, aNode)) child = tmp; else { child = nsnull; } } *aOutLastLeaf = child; return res; } /////////////////////////////////////////////////////////////////////////// // IsEmptyNode: figure out if aNode is an empty node. // A block can have children and still be considered empty, // if the children are empty or non-editable. // nsresult nsHTMLEditor::IsEmptyNode( nsIDOMNode *aNode, PRBool *outIsEmptyNode, PRBool aSingleBRDoesntCount, PRBool aListOrCellNotEmpty, PRBool aSafeToAskFrames) { if (!aNode || !outIsEmptyNode) return NS_ERROR_NULL_POINTER; *outIsEmptyNode = PR_TRUE; // effeciency hack - special case if it's a text node if (nsEditor::IsTextNode(aNode)) { PRUint32 length = 0; nsCOMPtrnodeAsText; nodeAsText = do_QueryInterface(aNode); nodeAsText->GetLength(&length); if (length) *outIsEmptyNode = PR_FALSE; return NS_OK; } // if it's not a text node (handled above) and it's not a container, // then we dont call it empty (it's an
, or
, etc). // Also, if it's an anchor then dont treat it as empty - even though // anchors are containers, named anchors are "empty" but we don't // want to treat them as such. Also, don't call ListItems or table // cells empty if caller desires. if (!IsContainer(aNode) || nsHTMLEditUtils::IsNamedAnchor(aNode) || (aListOrCellNotEmpty && nsHTMLEditUtils::IsListItem(aNode)) || (aListOrCellNotEmpty && nsHTMLEditUtils::IsTableCell(aNode)) ) { *outIsEmptyNode = PR_FALSE; return NS_OK; } // are we checking a block node? PRBool isBlock = IsBlockNode(aNode); // if so, we allow one br without violating emptiness PRBool seenBR = PR_FALSE; // need this for later PRBool isListItemOrCell = nsHTMLEditUtils::IsListItem(aNode) || nsHTMLEditUtils::IsTableCell(aNode); // iterate over node. if no children, or all children are either // empty text nodes or non-editable, then node qualifies as empty nsCOMPtr iter; nsCOMPtr nodeAsContent = do_QueryInterface(aNode); if (!nodeAsContent) return NS_ERROR_FAILURE; nsresult res = nsComponentManager::CreateInstance(kCContentIteratorCID, nsnull, NS_GET_IID(nsIContentIterator), getter_AddRefs(iter)); if (NS_FAILED(res)) return res; res = iter->Init(nodeAsContent); if (NS_FAILED(res)) return res; while (NS_ENUMERATOR_FALSE == iter->IsDone()) { nsCOMPtr node; nsCOMPtr content; res = iter->CurrentNode(getter_AddRefs(content)); if (NS_FAILED(res)) return res; node = do_QueryInterface(content); if (!node) return NS_ERROR_FAILURE; // is the node editable and non-empty? if so, return false if (nsEditor::IsEditable(node)) { if (nsEditor::IsTextNode(node)) { PRUint32 length = 0; nsCOMPtrnodeAsText; nodeAsText = do_QueryInterface(node); nodeAsText->GetLength(&length); if (aSafeToAskFrames) { nsCOMPtr selCon; res = GetSelectionController(getter_AddRefs(selCon)); if (NS_FAILED(res)) return res; if (!selCon) return NS_ERROR_FAILURE; PRBool isVisible = PR_FALSE; // ask the selection controller for information about whether any // of the data in the node is really rendered. This is really // something that frames know about, but we aren't supposed to talk to frames. // So we put a call in the selection controller interface, since it's already // in bed with frames anyway. (this is a fix for bug 22227, and a // partial fix for bug 46209) res = selCon->CheckVisibility(node, 0, length, &isVisible); if (NS_FAILED(res)) return res; if (isVisible) { *outIsEmptyNode = PR_FALSE; break; } } else if (length) { *outIsEmptyNode = PR_FALSE; break; } } else // an editable, non-text node. we aren't an empty block { // is it the node we are iterating over? if (node.get() == aNode) break; else if (aSingleBRDoesntCount && isBlock && !seenBR && nsTextEditUtils::IsBreak(node)) { // the first br in a block doesn't count if the caller so indicated seenBR = PR_TRUE; } else { // is it an empty node of some sort? // note: list items or table cells are not considered empty // if they contain other lists or tables if (isListItemOrCell) { if (nsHTMLEditUtils::IsList(node) || nsHTMLEditUtils::IsTable(node)) { *outIsEmptyNode = PR_FALSE; break; } } PRBool isEmptyNode; res = IsEmptyNode(node, &isEmptyNode, aSingleBRDoesntCount, aListOrCellNotEmpty); if (NS_FAILED(res)) return res; if (!isEmptyNode) { // otherwise it ain't empty *outIsEmptyNode = PR_FALSE; break; } } } } res = iter->Next(); if (NS_FAILED(res)) return res; } return NS_OK; }