/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * 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 Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): * Pierre Phaneuf */ #include "nsICaret.h" #include "nsHTMLEditor.h" #include "nsHTMLEditRules.h" #include "nsHTMLEditUtils.h" #include "nsEditorEventListeners.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 "nsIDOMHTMLImageElement.h" #include "nsISelectionController.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 "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 "nsIParser.h" #include "nsParserCIID.h" #include "nsIImage.h" #include "nsAOLCiter.h" #include "nsInternetCiter.h" #include "nsISupportsPrimitives.h" #include "InsertTextTxn.h" #include "SetDocTitleTxn.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 "PlaceholderTxn.h" #include "nsStyleSheetTxns.h" // Misc #include "TextEditorTest.h" #include "nsEditorUtils.h" #include "nsIPref.h" const PRUnichar nbsp = 160; // 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_CID(kCDOMSelectionCID, NS_DOMSELECTION_CID); static NS_DEFINE_CID(kPrefServiceCID, NS_PREF_CID); static NS_DEFINE_IID(kCParserIID, NS_IPARSER_IID); static NS_DEFINE_CID(kCParserCID, NS_PARSER_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; 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; } NS_IMETHODIMP nsHTMLEditor::SetDocumentTitle(const PRUnichar *aTitle) { SetDocTitleTxn *txn; nsresult result = TransactionFactory::GetNewTransaction(SetDocTitleTxn::GetCID(), (EditTxn **)&txn); if (NS_SUCCEEDED(result)) { nsAutoString title(aTitle); result = txn->Init(this, &title); if (NS_SUCCEEDED(result)) { result = nsEditor::Do(txn); } // The transaction system (if any) has taken ownwership of txn NS_IF_RELEASE(txn); } return result; } 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; if (IsBlockNode(node)) 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.GetUnicode(), eTypedBR); // only inserts a br node } else { return TypedText(empty.GetUnicode(), 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.GetUnicode(), 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.GetUnicode(), 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 PRUnichar* 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 nsString &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 nsString& 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 PRInt32 bodyStart = aSourceString.Find(NS_ConvertASCIItoUCS2("" PRInt32 headEnd = aSourceString.Find(NS_ConvertASCIItoUCS2("Collapse(bodyElement, 0); res = ReplaceHeadContentsWithHTML(headString); 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 nsAutoString bodyTag; // Truncate at the end of the body tag PRInt32 bodyTagEnd = bodyString.FindChar((PRUnichar)'>', PR_FALSE, 5); if (bodyTagEnd == -1) return NS_ERROR_FAILURE; bodyString.Truncate(bodyTagEnd+1); // Kludge of the year: fool the parser by replacing "body" with "div" so we get a node bodyString.ToLowerCase(); bodyString.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(bodyString, 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.GetUnicode()); printf(" Offset: %d\n", offsetForInsert); } #endif res = InsertNodeAtPoint(node, 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); } } res = mRules->DidDoAction(selection, &ruleInfo, res); return res; } nsresult nsHTMLEditor::InsertNodeAtPoint(nsIDOMNode *aNode, nsIDOMNode *aParent, PRInt32 aOffset, PRBool aNoEmptyNodes) { NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(aParent, NS_ERROR_NULL_POINTER); nsresult res = NS_OK; nsAutoString tagName; aNode->GetNodeName(tagName); tagName.ToLowerCase(); nsCOMPtr parent = aParent; nsCOMPtr topChild = aParent; nsCOMPtr tmp; PRInt32 offsetOfInsert = aOffset; // 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 (nsHTMLEditUtils::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, aParent, aOffset, &offsetOfInsert, aNoEmptyNodes); if (NS_FAILED(res)) return res; } // 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.GetUnicode()); printf(" Offset: %d\n\nHTML:\n", offsetInParent+1); nsAutoString Format("text/html"); nsAutoString ContentsAs; OutputToString(ContentsAs, Format, 2); wprintf(ContentsAs.GetUnicode()); } #endif } } return res; } NS_IMETHODIMP nsHTMLEditor::SetParagraphFormat(const nsString& 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 { if (IsBlockNode(node)) 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, nsString &outFormat) { if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } nsCOMPtr htmlRules = do_QueryInterface(mRules); if (!htmlRules) return NS_ERROR_FAILURE; return htmlRules->GetParagraphState(aMixed, outFormat); } NS_IMETHODIMP nsHTMLEditor::GetBackgroundColorState(PRBool &aMixed, nsString &aOutColor) { //TODO: We don't handle "mixed" correctly! aMixed = PR_FALSE; aOutColor.AssignWithConversion(""); 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(nsHTMLEditUtils::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; } 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; } 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; } 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; } nsCOMPtr htmlRules = do_QueryInterface(mRules); if (!htmlRules) return NS_ERROR_FAILURE; return htmlRules->GetIndentState(aCanIndent, aCanOutdent); } NS_IMETHODIMP nsHTMLEditor::MakeOrChangeList(const nsString& 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 nsString& 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 (aListType.EqualsWithConversion("ol")) 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 nsString& 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 nsString& 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 nsString& aIndent) { nsresult res; if (!mRules) { return NS_ERROR_NOT_INITIALIZED; } PRBool cancel, handled; PRInt32 theAction = nsTextEditRules::kIndent; PRInt32 opID = kOpIndent; if (aIndent.EqualsWithConversion("outdent")) { 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(" ").get()); 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 nsString& 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 nsString& 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.GetUnicode()); printf(" Offset: %d\n", anchorOffset); focusNode->GetNodeName(name); printf("Focus node of selection: "); wprintf(name.GetUnicode()); 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("align"),NS_LITERAL_STRING("center")); 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.GetUnicode() && href.Length() > 0) { nsAutoEditBatch beginBatching(this); nsString attribute; attribute.AssignWithConversion("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 nsString& 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 nsString& aAttribute, const nsString& 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; } #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 nsString& aURL, nsICSSStyleSheet **aStyleSheet) { return ApplyDocumentOrOverrideStyleSheet(aURL, PR_TRUE, aStyleSheet); } NS_IMETHODIMP nsHTMLEditor::ApplyStyleSheet(const nsString& 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 nsString& 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; rootContent = doc->GetRootContent(); 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")) { // XXX 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(nsString& 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 PRInt32 offset = aOutputString.Find(NS_ConvertASCIItoUCS2(" 0) { // Ensure the string ends in a newline PRUnichar newline ('\n'); if (aOutputString.CharAt(offset-1) != newline) aOutputString.SetCharAt(newline, offset++); aOutputString.Truncate(offset); } } 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 nsString& 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.GetUnicode()); 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::eTopLevelWindowCoordinates, 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.GetUnicode(), (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 nsString &aParentTag, const nsString &aChildTag) { // COtherDTD gives some unwanted results. We override them here. nsAutoString olStr, ulStr, liStr; olStr = NS_LITERAL_STRING("ol"); ulStr = NS_LITERAL_STRING("ul"); liStr = NS_LITERAL_STRING("li"); if ( aParentTag.EqualsIgnoreCase(olStr) || aParentTag.EqualsIgnoreCase(ulStr) ) { // if parent is a list and tag is also a list, say "yes". // This is because the editor does sublists illegally for now. if (aChildTag.EqualsIgnoreCase(olStr) || aChildTag.EqualsIgnoreCase(ulStr) ) return PR_TRUE; } if ( aParentTag.EqualsIgnoreCase(liStr) ) { // list items cant contain list items if (aChildTag.EqualsIgnoreCase(liStr) ) 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 nsString *aAttribute, const nsString *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 if (aValue->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 nsString *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 nsHTMLEditUtils::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; nsIContent* rootContent = doc->GetRootContent(); 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 nsString *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, getter_AddRefs(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 /////////////////////////////////////////////////////////////////////////// // 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_ERROR_FAILURE; // 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) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = GetPriorNode(inNode, PR_TRUE, getter_AddRefs(*outNode)); if (NS_FAILED(res)) return res; // if it's not in the body, then zero it out if (*outNode && !nsHTMLEditUtils::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) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = GetPriorNode(inParent, inOffset, PR_TRUE, getter_AddRefs(*outNode)); if (NS_FAILED(res)) return res; // if it's not in the body, then zero it out if (*outNode && !nsHTMLEditUtils::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) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = GetNextNode(inNode, PR_TRUE, getter_AddRefs(*outNode)); if (NS_FAILED(res)) return res; // if it's not in the body, then zero it out if (*outNode && !nsHTMLEditUtils::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) { if (!outNode) return NS_ERROR_NULL_POINTER; nsresult res = GetNextNode(inParent, inOffset, PR_TRUE, getter_AddRefs(*outNode)); if (NS_FAILED(res)) return res; // if it's not in the body, then zero it out if (*outNode && !nsHTMLEditUtils::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 = GetLeftmostChild(aNode, getter_AddRefs(child)); if (NS_FAILED(res)) return res; 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 leftmost leaf nsCOMPtr child; nsresult res = GetRightmostChild(aNode, getter_AddRefs(child)); if (NS_FAILED(res)) return res; 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 aMozBRDoesntCount, 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::IsAnchor(aNode) || nsHTMLEditUtils::IsTextarea(aNode) || nsHTMLEditUtils::IsMap(aNode) || (aListOrCellNotEmpty && nsHTMLEditUtils::IsListItem(aNode)) || (aListOrCellNotEmpty && nsHTMLEditUtils::IsTableCell(aNode)) ) { *outIsEmptyNode = PR_FALSE; return NS_OK; } // 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; // is it a moz-BR and did the caller ask us not to consider those relevant? if (!(aMozBRDoesntCount && nsHTMLEditUtils::IsMozBR(node))) { // is it an empty node of some sort? PRBool isEmptyNode; res = IsEmptyNode(node, &isEmptyNode, aMozBRDoesntCount, 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; }