gecko-dev/editor/base/nsHTMLEditor.cpp

3897 строки
118 KiB
C++

/* -*- 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 <pp@ludusdesign.com>
*/
#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<nsIEditActionListener> 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
nsCOMPtr<nsISelection>selection;
nsresult result = GetSelection(getter_AddRefs(selection));
// if we don't get the selection, just skip this
if (NS_SUCCEEDED(result) && selection)
{
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
nsCOMPtr<nsISelectionListener>listener;
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<nsIPresContext> context;
aPresShell->GetPresContext(getter_AddRefs(context));
if (!context) return NS_ERROR_NULL_POINTER;
if (!(mFlags & eEditorPlaintextMask))
context->SetLinkHandler(0);
nsCOMPtr<nsIDOMElement> 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);
nsCOMPtr<nsISelection>selection;
result = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(result)) { return result; }
if (selection)
{
nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
nsCOMPtr<nsISelectionListener>listener;
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<nsIDOMEventReceiver> 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))
{
nsCOMPtr<nsISelection>selection;
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
PRInt32 offset;
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMElement> 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<nsIDOMNode> tbl = GetEnclosingTable(cellElement);
if (!tbl) return res;
// advance to next cell
// first create an iterator over the table
nsCOMPtr<nsIContentIterator> 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<nsIContent> cTbl = do_QueryInterface(tbl);
nsCOMPtr<nsIContent> 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<nsIDOMNode> node;
nsCOMPtr<nsIContent> 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...
nsCOMPtr<nsISelection>selection;
nsCOMPtr<nsIDOMElement> tblElement;
nsCOMPtr<nsIDOMElement> 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<nsIDOMNode> *aInOutParent, PRInt32 *aInOutOffset, nsCOMPtr<nsIDOMNode> *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<nsIDOMNode> node = *aInOutParent;
PRInt32 theOffset = *aInOutOffset;
nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(node);
nsAutoString brType; brType.AssignWithConversion("br");
nsCOMPtr<nsIDOMNode> brNode;
if (nodeAsText)
{
nsCOMPtr<nsIDOMNode> 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<nsISelection> selection;
nsCOMPtr<nsIDOMNode> parent;
PRInt32 offset;
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsISelectionPrivate> 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<nsIDOMNode> *outBRNode, EDirection aSelect)
{
nsCOMPtr<nsIDOMNode> parent = aNode;
PRInt32 offset = aOffset;
return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect);
}
NS_IMETHODIMP nsHTMLEditor::InsertBR(nsCOMPtr<nsIDOMNode> *outBRNode)
{
PRBool bCollapsed;
nsCOMPtr<nsISelection> 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<nsISelectionPrivate> 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<nsIDOMNode> 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<nsIDOMElement> 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<nsIContent> content = do_QueryInterface(rootElement);
if (content)
{
nsCOMPtr<nsIContent> 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<nsIDOMDocument> 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<nsIDOMElement> bodyElement;
nsresult res = nsEditor::GetRootElement(getter_AddRefs(bodyElement));
if (NS_FAILED(res)) return res;
if (!bodyElement) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIDOMNode> 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<nsISelection> selection;
if (aSelection)
{
selection = aSelection;
} else {
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
if (!selection) return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDOMNode> node = aNode;
nsCOMPtr<nsIDOMNode> 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 <head> node
NS_IMETHODIMP
nsHTMLEditor::ReplaceHeadContentsWithHTML(const nsString &aSourceToInsert)
{
nsCOMPtr<nsISelection> 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 <head>
// Use the head node as a parent and delete/insert directly
nsCOMPtr<nsIDOMNodeList>nodeList;
nsAutoString headTag; headTag.AssignWithConversion("head");
nsCOMPtr<nsIDOMDocument> 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<nsIDOMNode> 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<nsIDOMRange> range;
res = selection->GetRangeAt(0, getter_AddRefs(range));
if (NS_FAILED(res))
return res;
nsCOMPtr<nsIDOMNSRange> nsrange (do_QueryInterface(range));
if (!nsrange)
return NS_ERROR_NO_INTERFACE;
nsCOMPtr<nsIDOMDocumentFragment> docfrag;
res = nsrange->CreateContextualFragment(inputString,
getter_AddRefs(docfrag));
//XXXX BUG 50965: This is not returning the text between <title> ... </title>
// 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<nsIDOMNode> 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<nsIDOMNode> 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();
nsCOMPtr<nsISelection>selection;
nsresult res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMElement> bodyElement;
res = GetRootElement(getter_AddRefs(bodyElement));
if (NS_FAILED(res)) return res;
if (!bodyElement) return NS_ERROR_NULL_POINTER;
// Find where the <body> tag starts.
// If user mangled that, then abort
PRInt32 bodyStart = aSourceString.Find(NS_ConvertASCIItoUCS2("<body"), PR_TRUE);
if (bodyStart == -1) return NS_ERROR_FAILURE;
PRInt32 headStart = aSourceString.Find(NS_ConvertASCIItoUCS2("<head"), PR_TRUE);
if (headStart == -1) return NS_ERROR_FAILURE;
// Find the index after "<head>"
PRInt32 headEnd = aSourceString.Find(NS_ConvertASCIItoUCS2("</head"), PR_TRUE);
// We'll be forgiving and assume head ends before body
if (headEnd == -1) headEnd = bodyStart;
nsAutoString headString;
aSourceString.Mid(headString, headStart, (headEnd - headStart));
nsAutoString bodyString;
aSourceString.Mid(bodyString, bodyStart, (aSourceString.Length() - bodyStart - 1));
// Time to change the document
nsAutoEditBatch beginBatching(this);
// Try to replace body contents first
res = SelectAll();
if (NS_FAILED(res)) return res;
res = InsertHTML(bodyString);
if (NS_FAILED(res)) return res;
selection->Collapse(bodyElement, 0);
res = ReplaceHeadContentsWithHTML(headString);
if (NS_FAILED(res)) return res;
// Now we must copy attributes user might have edited on the <body> 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<nsIDOMRange> range;
res = selection->GetRangeAt(0, getter_AddRefs(range));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMNSRange> nsrange (do_QueryInterface(range));
if (!nsrange) return NS_ERROR_NO_INTERFACE;
nsCOMPtr<nsIDOMDocumentFragment> docfrag;
res = nsrange->CreateContextualFragment(bodyString, getter_AddRefs(docfrag));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMNode> fragmentAsNode (do_QueryInterface(docfrag));
if (!fragmentAsNode) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> node = do_QueryInterface(aElement);
ForceCompositionEnd();
nsAutoEditBatch beginBatching(this);
nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
nsCOMPtr<nsISelection>selection;
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<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMNode> parent = aParent;
nsCOMPtr<nsIDOMNode> topChild = aParent;
nsCOMPtr<nsIDOMNode> 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<nsISelection> selection;
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
if (!selection) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIDOMNode>parent;
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<nsISelection> selection;
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
if (!selection) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIDOMNode>parent;
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;
nsCOMPtr<nsISelection>selection;
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
if (!selection) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsISelectionPrivate> 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<nsIDOMNode> node, blockParent;
PRInt32 offset;
res = GetStartNodeAndOffset(selection, address_of(node), &offset);
if (!node) res = NS_ERROR_FAILURE;
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMElement> 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<nsIEnumerator> enumerator;
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
if (NS_FAILED(res)) return res;
if (!enumerator) return NS_ERROR_NULL_POINTER;
enumerator->First();
nsCOMPtr<nsISupports> currentItem;
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
if (NS_FAILED(res)) return res;
//XXX: should be while loop?
if (currentItem)
{
nsCOMPtr<nsIDOMRange> 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)
{
nsCOMPtr<nsIDOMNode>startParent;
res = subRange->GetStartContainer(getter_AddRefs(startParent));
if (NS_SUCCEEDED(res) && startParent)
{
nsCOMPtr<nsIDOMElement> 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<nsIHTMLEditRules> 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<nsIDOMElement> 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<nsIDOMNode> 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<nsIHTMLEditRules> 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<nsIHTMLEditRules> 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<nsIHTMLEditRules> 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<nsIHTMLEditRules> 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<nsISelection> 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<nsIDOMNode> 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<nsIDOMNode> parent = node;
nsCOMPtr<nsIDOMNode> topChild = node;
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMNode> 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<nsISelection> 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<nsISelection> 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<nsISelection> 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<nsIDOMNode> 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<nsIDOMNode> parent = node;
nsCOMPtr<nsIDOMNode> topChild = node;
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> 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<nsISelection> 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<nsIDOMNode> 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<nsIDOMNode> parent = node;
nsCOMPtr<nsIDOMNode> topChild = node;
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMNode> node;
PRBool cancel, handled;
// Find out if the selection is collapsed:
nsCOMPtr<nsISelection> 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<nsIDOMNode> currentNode;
if (aNode)
currentNode = aNode;
else
{
// If no node supplied, get it from anchor node of current selection
nsCOMPtr<nsISelection>selection;
res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
if (!selection) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMElement> 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
nsCOMPtr<nsISelection>selection;
nsresult res = GetSelection(getter_AddRefs(selection));
if (NS_FAILED(res)) return res;
if (!selection) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsISelectionPrivate> 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<nsIDOMElement> selectedElement;
nsCOMPtr<nsIDOMRange> range;
res = selection->GetRangeAt(0, getter_AddRefs(range));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMNode> anchorNode;
res = selection->GetAnchorNode(getter_AddRefs(anchorNode));
if (NS_FAILED(res)) return res;
PRInt32 anchorOffset = -1;
if (anchorNode)
selection->GetAnchorOffset(&anchorOffset);
nsCOMPtr<nsIDOMNode> 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<nsIDOMElement> 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<nsIDOMElement> 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<nsIDOMNode> 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<nsIEnumerator> enumerator;
res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
if (NS_SUCCEEDED(res))
{
if(!enumerator)
return NS_ERROR_NULL_POINTER;
enumerator->First();
nsCOMPtr<nsISupports> currentItem;
res = enumerator->CurrentItem(getter_AddRefs(currentItem));
if ((NS_SUCCEEDED(res)) && currentItem)
{
nsCOMPtr<nsIDOMRange> currange( do_QueryInterface(currentItem) );
nsCOMPtr<nsIContentIterator> 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<nsIContent> 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<nsIDOMNode> 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
nsCOMPtr<nsIDOMElement>newElement;
nsCOMPtr<nsIContent> newContent;
nsCOMPtr<nsIDOMDocument> 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<nsISelection> 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<nsIDOMHTMLAnchorElement> 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<nsIDOMElement> 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<nsIDOMElement> 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<nsIDOMElement> 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<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIDocument> document;
nsresult rv = ps->GetDocument(getter_AddRefs(document));
if (NS_FAILED(rv)) return rv;
if (!document) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIStyleSet> styleSet;
rv = ps->GetStyleSet(getter_AddRefs(styleSet));
if (NS_FAILED(rv)) return rv;
if (!styleSet) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsIStyleSheet> 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<nsIURI> uaURL;
rv = NS_NewURI(getter_AddRefs(uaURL), aURL);
if (NS_SUCCEEDED(rv)) {
nsCOMPtr<nsIDocument> document;
if (!mPresShellWeak) return NS_ERROR_NOT_INITIALIZED;
nsCOMPtr<nsIPresShell> 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<nsIHTMLContentContainer> container = do_QueryInterface(document);
if (!container) return NS_ERROR_NULL_POINTER;
nsCOMPtr<nsICSSLoader> cssLoader;
nsCOMPtr<nsICSSStyleSheet> 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<nsIStyleSheet> styleSheet;
styleSheet = do_QueryInterface(cssStyleSheet);
nsCOMPtr<nsIStyleSet> 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<nsIContentIterator> 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<nsIContent> rootContent;
nsCOMPtr<nsIDOMDocument> domdoc;
nsEditor::GetDocument(getter_AddRefs(domdoc));
if (!domdoc)
return NS_ERROR_UNEXPECTED;
nsCOMPtr<nsIDocument> 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<nsIContent> content;
res = iter->CurrentNode(getter_AddRefs(content));
if (NS_FAILED(res))
break;
nsCOMPtr<nsIDOMNode> 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<nsIDOMHTMLAnchorElement> 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<nsISelection> aSelection, nsWeakPtr aDocWeak)
{
nsresult res = NS_OK;
// Set selection around <head> node
nsCOMPtr<nsIDOMNodeList>nodeList;
nsAutoString headTag; headTag.AssignWithConversion("head");
nsCOMPtr<nsIDOMDocument> 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<nsIDOMNode> 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<nsIDOMNodeList> 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<nsISelection> 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 <body></body>,
// so terminate there
PRInt32 offset = aOutputString.Find(NS_ConvertASCIItoUCS2("<body"), PR_TRUE);
if (offset > 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<nsICaret> 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<nsISelection> 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<nsIPresShell> 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<nsISelection> 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<nsIDOMRange> 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
nsCOMPtr<nsIDOMElement>bodyElement;
res = GetRootElement(getter_AddRefs(bodyElement));
if (NS_FAILED(res)) return res;
nsCOMPtr<nsIDOMNode>bodyNode = 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<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
if (!ps) return NS_ERROR_NOT_INITIALIZED;
if ((nsnull!=aNode))
{ // get the content interface
nsCOMPtr<nsIContent> 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 <Table border> 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);
nsCOMPtr<nsIDOMNode>node = aNode;
while (node)
{
nsCOMPtr<nsIDOMElement>element;
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;
}
}
}
nsCOMPtr<nsIDOMNode>temp;
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<nsIContent> content = do_QueryInterface(aElement);
if (content)
{
nsCOMPtr<nsIAtom> 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<nsIDOMNode> node = do_QueryInterface(aElement);
nsCOMPtr<nsIDOMNode> parent;
// This MUST succeed if IsElementInBody was TRUE
node->GetParentNode(getter_AddRefs(parent));
nsCOMPtr<nsIDOMNode>firstChild;
// 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<nsISelection> 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<nsIDOMNode>
nsHTMLEditor::GetEnclosingTable(nsIDOMNode *aNode)
{
NS_PRECONDITION(aNode, "null node passed to nsHTMLEditor::GetEnclosingTable");
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMElement> nsHTMLEditor::FindPreElement()
{
nsCOMPtr<nsIDOMDocument> domdoc;
nsEditor::GetDocument(getter_AddRefs(domdoc));
if (!domdoc)
return 0;
nsCOMPtr<nsIDocument> doc (do_QueryInterface(domdoc));
if (!doc)
return 0;
nsIContent* rootContent = doc->GetRootContent();
if (!rootContent)
return 0;
nsCOMPtr<nsIDOMNode> rootNode (do_QueryInterface(rootContent));
if (!rootNode)
return 0;
nsString prestr ("PRE"); // GetFirstNodeOfType requires capitals
nsCOMPtr<nsIDOMNode> 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<nsIContentIterator> 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<nsIContent> content;
result = iter->CurrentNode(getter_AddRefs(content));
if (!content) return NS_OK;
while (NS_ENUMERATOR_FALSE == iter->IsDone())
{
nsCOMPtr<nsIDOMCharacterData> text = do_QueryInterface(content);
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> prevSibOfRightNode;
result = GetPriorHTMLSibling(rightTextNode, address_of(prevSibOfRightNode));
if (NS_FAILED(result)) return result;
if (prevSibOfRightNode && (prevSibOfRightNode.get() == leftTextNode))
{
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> currentNode = do_QueryInterface(aCurrentElement);
if (!currentNode)
return NS_ERROR_FAILURE;
*aReturn = nsnull;
nsCOMPtr<nsIDOMNode> nextNode;
PRBool done = PR_FALSE;
do {
res = GetNextNode(currentNode, PR_TRUE, getter_AddRefs(nextNode));
if (NS_FAILED(res)) return res;
if (!nextNode) break;
nsCOMPtr<nsIAtom> atom = GetTag(currentNode);
if (tagAtom==atom.get())
{
nsCOMPtr<nsIDOMElement> 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<nsIDOMElement> 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<nsIDOMNode> *outNode)
{
if (!outNode || !inNode) return NS_ERROR_NULL_POINTER;
nsresult res = NS_OK;
*outNode = nsnull;
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> *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<nsIDOMNode> 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<nsIDOMNode> *outNode)
{
if (!outNode) return NS_ERROR_NULL_POINTER;
nsresult res = NS_OK;
*outNode = nsnull;
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> *outNode)
{
if (!outNode || !inParent) return NS_ERROR_NULL_POINTER;
nsresult res = NS_OK;
*outNode = nsnull;
nsCOMPtr<nsIDOMNode> 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 <body>
//
nsresult
nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *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<nsIDOMNode> *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 <body>
//
nsresult
nsHTMLEditor::GetNextHTMLNode(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *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<nsIDOMNode> *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<nsIDOMNode> 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<nsIDOMNode> 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<nsIDOMNode> *aOutFirstChild)
{
// check parms
if (!aOutFirstChild || !aNode) return NS_ERROR_NULL_POINTER;
// init out parms
*aOutFirstChild = nsnull;
// find first editable child
nsCOMPtr<nsIDOMNode> child;
nsresult res = aNode->GetFirstChild(getter_AddRefs(child));
if (NS_FAILED(res)) return res;
while (child && !IsEditable(child))
{
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> *aOutLastChild)
{
// check parms
if (!aOutLastChild || !aNode) return NS_ERROR_NULL_POINTER;
// init out parms
*aOutLastChild = nsnull;
// find last editable child
nsCOMPtr<nsIDOMNode> child;
nsresult res = aNode->GetLastChild(getter_AddRefs(child));
if (NS_FAILED(res)) return res;
while (child && !IsEditable(child))
{
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> *aOutFirstLeaf)
{
// check parms
if (!aOutFirstLeaf || !aNode) return NS_ERROR_NULL_POINTER;
// init out parms
*aOutFirstLeaf = nsnull;
// find leftmost leaf
nsCOMPtr<nsIDOMNode> child;
nsresult res = GetLeftmostChild(aNode, getter_AddRefs(child));
if (NS_FAILED(res)) return res;
while (child && (!IsEditable(child) || !nsHTMLEditUtils::IsLeafNode(child)))
{
nsCOMPtr<nsIDOMNode> 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<nsIDOMNode> *aOutLastLeaf)
{
// check parms
if (!aOutLastLeaf || !aNode) return NS_ERROR_NULL_POINTER;
// init out parms
*aOutLastLeaf = nsnull;
// find leftmost leaf
nsCOMPtr<nsIDOMNode> child;
nsresult res = GetRightmostChild(aNode, getter_AddRefs(child));
if (NS_FAILED(res)) return res;
while (child && (!IsEditable(child) || !nsHTMLEditUtils::IsLeafNode(child)))
{
nsCOMPtr<nsIDOMNode> 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;
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
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 <hr>, or <br>, 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<nsIContentIterator> iter;
nsCOMPtr<nsIContent> 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<nsIDOMNode> node;
nsCOMPtr<nsIContent> 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;
nsCOMPtr<nsIDOMCharacterData>nodeAsText;
nodeAsText = do_QueryInterface(node);
nodeAsText->GetLength(&length);
if (aSafeToAskFrames)
{
nsCOMPtr<nsISelectionController> 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;
}