CLOSED TREE: TraceMonkey merge head. (a=blockers)
[mozilla-central.git] / editor / libeditor / html / nsHTMLEditor.cpp
blob3ce49c6676c4053ca4eee0e0d5f6df366f80e59b
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Pierre Phaneuf <pp@ludusdesign.com>
24 * Daniel Glazman <glazman@netscape.com>
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
39 #include "nsCRT.h"
41 #include "nsReadableUtils.h"
42 #include "nsUnicharUtils.h"
44 #include "nsHTMLEditor.h"
45 #include "nsHTMLEditRules.h"
46 #include "nsTextEditUtils.h"
47 #include "nsHTMLEditUtils.h"
49 #include "nsHTMLEditorEventListener.h"
50 #include "TypeInState.h"
52 #include "nsHTMLURIRefObject.h"
54 #include "nsIDOMText.h"
55 #include "nsIDOMNodeList.h"
56 #include "nsIDOMDocument.h"
57 #include "nsIDOMAttr.h"
58 #include "nsIDocument.h"
59 #include "nsIDOMEventTarget.h"
60 #include "nsIDOM3EventTarget.h"
61 #include "nsIDOMKeyEvent.h"
62 #include "nsIDOMKeyListener.h"
63 #include "nsIDOMMouseListener.h"
64 #include "nsIDOMMouseEvent.h"
65 #include "nsISelection.h"
66 #include "nsISelectionPrivate.h"
67 #include "nsIDOMHTMLAnchorElement.h"
68 #include "nsISelectionController.h"
69 #include "nsIDOMHTMLDocument.h"
70 #include "nsIDOMHTMLHtmlElement.h"
71 #include "nsGUIEvent.h"
72 #include "nsIDOMEventGroup.h"
73 #include "nsILinkHandler.h"
75 #include "mozilla/css/Loader.h"
76 #include "nsCSSStyleSheet.h"
77 #include "nsIDOMStyleSheet.h"
78 #include "nsIDocumentObserver.h"
79 #include "nsIDocumentStateListener.h"
81 #include "nsIEnumerator.h"
82 #include "nsIContent.h"
83 #include "nsIContentIterator.h"
84 #include "nsIDOMRange.h"
85 #include "nsIDOMNSRange.h"
86 #include "nsIRangeUtils.h"
87 #include "nsISupportsArray.h"
88 #include "nsContentUtils.h"
89 #include "nsIURL.h"
90 #include "nsIComponentManager.h"
91 #include "nsIServiceManager.h"
92 #include "nsIDocumentEncoder.h"
93 #include "nsIDOMDocumentFragment.h"
94 #include "nsIPresShell.h"
95 #include "nsPresContext.h"
96 #include "nsXPCOM.h"
97 #include "nsISupportsPrimitives.h"
98 #include "SetDocTitleTxn.h"
99 #include "nsGUIEvent.h"
100 #include "nsTextFragment.h"
101 #include "nsFocusManager.h"
102 #include "nsPIDOMWindow.h"
104 // netwerk
105 #include "nsIURI.h"
106 #include "nsNetUtil.h"
108 // Drag & Drop, Clipboard
109 #include "nsIClipboard.h"
110 #include "nsITransferable.h"
111 #include "nsIDragService.h"
112 #include "nsIDOMNSUIEvent.h"
113 #include "nsIContentFilter.h"
115 // Transactionas
116 #include "nsStyleSheetTxns.h"
118 // Misc
119 #include "TextEditorTest.h"
120 #include "nsEditorUtils.h"
121 #include "nsWSRunObject.h"
122 #include "nsHTMLObjectResizer.h"
123 #include "nsGkAtoms.h"
125 #include "nsIFrame.h"
126 #include "nsIView.h"
127 #include "nsIParserService.h"
128 #include "nsIEventStateManager.h"
130 // Some utilities to handle annoying overloading of "A" tag for link and named anchor
131 static char hrefText[] = "href";
132 static char anchorTxt[] = "anchor";
133 static char namedanchorText[] = "namedanchor";
135 nsIRangeUtils* nsHTMLEditor::sRangeHelper;
137 // some prototypes for rules creation shortcuts
138 nsresult NS_NewTextEditRules(nsIEditRules** aInstancePtrResult);
139 nsresult NS_NewHTMLEditRules(nsIEditRules** aInstancePtrResult);
141 #define IsLinkTag(s) (s.EqualsIgnoreCase(hrefText))
142 #define IsNamedAnchorTag(s) (s.EqualsIgnoreCase(anchorTxt) || s.EqualsIgnoreCase(namedanchorText))
144 nsHTMLEditor::nsHTMLEditor()
145 : nsPlaintextEditor()
146 , mIgnoreSpuriousDragEvent(PR_FALSE)
147 , mCRInParagraphCreatesParagraph(PR_FALSE)
148 , mSelectedCellIndex(0)
149 , mIsObjectResizingEnabled(PR_TRUE)
150 , mIsResizing(PR_FALSE)
151 , mIsAbsolutelyPositioningEnabled(PR_TRUE)
152 , mResizedObjectIsAbsolutelyPositioned(PR_FALSE)
153 , mGrabberClicked(PR_FALSE)
154 , mIsMoving(PR_FALSE)
155 , mSnapToGridEnabled(PR_FALSE)
156 , mIsInlineTableEditingEnabled(PR_TRUE)
157 , mInfoXIncrement(20)
158 , mInfoYIncrement(20)
159 , mGridSize(0)
163 nsHTMLEditor::~nsHTMLEditor()
165 // remove the rules as an action listener. Else we get a bad
166 // ownership loop later on. it's ok if the rules aren't a listener;
167 // we ignore the error.
168 nsCOMPtr<nsIEditActionListener> mListener = do_QueryInterface(mRules);
169 RemoveEditActionListener(mListener);
171 // Clean up after our anonymous content -- we don't want these nodes to
172 // stay around (which they would, since the frames have an owning reference).
174 if (mAbsolutelyPositionedObject)
175 HideGrabber();
176 if (mInlineEditedCell)
177 HideInlineTableEditingUI();
178 if (mResizedObject)
179 HideResizers();
181 //the autopointers will clear themselves up.
182 //but we need to also remove the listeners or we have a leak
183 nsCOMPtr<nsISelection>selection;
184 nsresult result = GetSelection(getter_AddRefs(selection));
185 // if we don't get the selection, just skip this
186 if (NS_SUCCEEDED(result) && selection)
188 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
189 nsCOMPtr<nsISelectionListener>listener;
190 listener = do_QueryInterface(mTypeInState);
191 if (listener)
193 selPriv->RemoveSelectionListener(listener);
195 listener = do_QueryInterface(mSelectionListenerP);
196 if (listener)
198 selPriv->RemoveSelectionListener(listener);
202 mTypeInState = nsnull;
203 mSelectionListenerP = nsnull;
205 // free any default style propItems
206 RemoveAllDefaultProperties();
208 while (mStyleSheetURLs.Length())
210 RemoveOverrideStyleSheet(mStyleSheetURLs[0]);
213 if (mLinkHandler && mPresShellWeak)
215 nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
217 if (ps && ps->GetPresContext())
219 ps->GetPresContext()->SetLinkHandler(mLinkHandler);
223 RemoveEventListeners();
226 /* static */
227 void
228 nsHTMLEditor::Shutdown()
230 NS_IF_RELEASE(sRangeHelper);
233 NS_IMPL_ADDREF_INHERITED(nsHTMLEditor, nsEditor)
234 NS_IMPL_RELEASE_INHERITED(nsHTMLEditor, nsEditor)
236 NS_INTERFACE_MAP_BEGIN(nsHTMLEditor)
237 NS_INTERFACE_MAP_ENTRY(nsIHTMLEditor)
238 NS_INTERFACE_MAP_ENTRY(nsIHTMLEditor_MOZILLA_2_0_BRANCH)
239 NS_INTERFACE_MAP_ENTRY(nsIHTMLObjectResizer)
240 NS_INTERFACE_MAP_ENTRY(nsIHTMLAbsPosEditor)
241 NS_INTERFACE_MAP_ENTRY(nsIHTMLInlineTableEditor)
242 NS_INTERFACE_MAP_ENTRY(nsITableEditor)
243 NS_INTERFACE_MAP_ENTRY(nsIEditorStyleSheets)
244 NS_INTERFACE_MAP_ENTRY(nsICSSLoaderObserver)
245 NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
246 NS_INTERFACE_MAP_END_INHERITING(nsPlaintextEditor)
249 NS_IMETHODIMP
250 nsHTMLEditor::Init(nsIDOMDocument *aDoc, nsIPresShell *aPresShell,
251 nsIContent *aRoot, nsISelectionController *aSelCon,
252 PRUint32 aFlags)
254 NS_PRECONDITION(aDoc && aPresShell, "bad arg");
255 NS_ENSURE_TRUE(aDoc && aPresShell, NS_ERROR_NULL_POINTER);
257 nsresult result = NS_OK, rulesRes = NS_OK;
259 // make a range util object for comparing dom points
260 if (!sRangeHelper) {
261 result = CallGetService("@mozilla.org/content/range-utils;1",
262 &sRangeHelper);
263 NS_ENSURE_TRUE(sRangeHelper, result);
266 if (1)
268 // block to scope nsAutoEditInitRulesTrigger
269 nsAutoEditInitRulesTrigger rulesTrigger(static_cast<nsPlaintextEditor*>(this), rulesRes);
271 // Init the plaintext editor
272 result = nsPlaintextEditor::Init(aDoc, aPresShell, aRoot, aSelCon, aFlags);
273 if (NS_FAILED(result)) { return result; }
275 // Init mutation observer
276 nsCOMPtr<nsINode> document = do_QueryInterface(aDoc);
277 document->AddMutationObserverUnlessExists(this);
279 // disable Composer-only features
280 if (IsMailEditor())
282 SetAbsolutePositioningEnabled(PR_FALSE);
283 SetSnapToGridEnabled(PR_FALSE);
286 // Init the HTML-CSS utils
287 result = NS_NewHTMLCSSUtils(getter_Transfers(mHTMLCSSUtils));
288 if (NS_FAILED(result)) { return result; }
289 mHTMLCSSUtils->Init(this);
291 // disable links
292 nsPresContext *context = aPresShell->GetPresContext();
293 NS_ENSURE_TRUE(context, NS_ERROR_NULL_POINTER);
294 if (!IsPlaintextEditor() && !IsInteractionAllowed()) {
295 mLinkHandler = context->GetLinkHandler();
297 context->SetLinkHandler(nsnull);
300 // init the type-in state
301 mTypeInState = new TypeInState();
302 if (!mTypeInState) {return NS_ERROR_NULL_POINTER;}
304 // init the selection listener for image resizing
305 mSelectionListenerP = new ResizerSelectionListener(this);
306 if (!mSelectionListenerP) {return NS_ERROR_NULL_POINTER;}
308 if (!IsInteractionAllowed()) {
309 // ignore any errors from this in case the file is missing
310 AddOverrideStyleSheet(NS_LITERAL_STRING("resource://gre/res/EditorOverride.css"));
313 nsCOMPtr<nsISelection>selection;
314 result = GetSelection(getter_AddRefs(selection));
315 if (NS_FAILED(result)) { return result; }
316 if (selection)
318 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
319 nsCOMPtr<nsISelectionListener>listener;
320 listener = do_QueryInterface(mTypeInState);
321 if (listener) {
322 selPriv->AddSelectionListener(listener);
324 listener = do_QueryInterface(mSelectionListenerP);
325 if (listener) {
326 selPriv->AddSelectionListener(listener);
331 NS_ENSURE_SUCCESS(rulesRes, rulesRes);
332 return result;
335 NS_IMETHODIMP
336 nsHTMLEditor::PreDestroy(PRBool aDestroyingFrames)
338 if (mDidPreDestroy) {
339 return NS_OK;
342 nsCOMPtr<nsINode> document = do_QueryReferent(mDocWeak);
343 if (document) {
344 document->RemoveMutationObserver(this);
347 return nsPlaintextEditor::PreDestroy(aDestroyingFrames);
350 NS_IMETHODIMP
351 nsHTMLEditor::GetRootElement(nsIDOMElement **aRootElement)
353 NS_ENSURE_ARG_POINTER(aRootElement);
355 if (mRootElement) {
356 return nsEditor::GetRootElement(aRootElement);
359 *aRootElement = nsnull;
361 // Use the HTML documents body element as the editor root if we didn't
362 // get a root element during initialization.
364 nsCOMPtr<nsIDOMHTMLElement> bodyElement;
365 nsresult rv = GetBodyElement(getter_AddRefs(bodyElement));
366 NS_ENSURE_SUCCESS(rv, rv);
368 if (bodyElement) {
369 mRootElement = bodyElement;
370 } else {
371 // If there is no HTML body element,
372 // we should use the document root element instead.
373 nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak);
374 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
376 rv = doc->GetDocumentElement(getter_AddRefs(mRootElement));
377 NS_ENSURE_SUCCESS(rv, rv);
378 // Document can have no elements
379 if (!mRootElement) {
380 return NS_ERROR_NOT_AVAILABLE;
384 *aRootElement = mRootElement;
385 NS_ADDREF(*aRootElement);
387 return NS_OK;
390 already_AddRefed<nsIContent>
391 nsHTMLEditor::FindSelectionRoot(nsINode *aNode)
393 NS_PRECONDITION(aNode->IsNodeOfType(nsINode::eDOCUMENT) ||
394 aNode->IsNodeOfType(nsINode::eCONTENT),
395 "aNode must be content or document node");
397 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
398 nsCOMPtr<nsIDocument> doc = aNode->GetCurrentDoc();
399 if (!doc) {
400 return nsnull;
403 if (doc->HasFlag(NODE_IS_EDITABLE) || !content) {
404 content = doc->GetRootElement();
405 return content.forget();
408 // XXX If we have readonly flag, shouldn't return the element which has
409 // contenteditable="true"? However, such case isn't there without chrome
410 // permission script.
411 if (IsReadonly()) {
412 // We still want to allow selection in a readonly editor.
413 content = do_QueryInterface(GetRoot());
414 return content.forget();
417 if (!content->HasFlag(NODE_IS_EDITABLE)) {
418 return nsnull;
421 // For non-readonly editors we want to find the root of the editable subtree
422 // containing aContent.
423 content = content->GetEditingHost();
424 return content.forget();
427 nsresult
428 nsHTMLEditor::CreateEventListeners()
430 // Don't create the handler twice
431 if (mEventListener)
432 return NS_OK;
433 mEventListener = do_QueryInterface(
434 static_cast<nsIDOMKeyListener*>(new nsHTMLEditorEventListener()));
435 NS_ENSURE_TRUE(mEventListener, NS_ERROR_OUT_OF_MEMORY);
436 return NS_OK;
439 nsresult
440 nsHTMLEditor::InstallEventListeners()
442 NS_ENSURE_TRUE(mDocWeak && mPresShellWeak && mEventListener,
443 NS_ERROR_NOT_INITIALIZED);
445 // NOTE: nsHTMLEditor doesn't need to initialize mEventTarget here because
446 // the target must be document node and it must be referenced as weak pointer.
448 nsHTMLEditorEventListener* listener =
449 reinterpret_cast<nsHTMLEditorEventListener*>(mEventListener.get());
450 return listener->Connect(this);
453 void
454 nsHTMLEditor::RemoveEventListeners()
456 if (!mDocWeak)
458 return;
461 nsCOMPtr<nsPIDOMEventTarget> piTarget = GetPIDOMEventTarget();
462 nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(piTarget);
464 if (piTarget && target)
466 // Both mMouseMotionListenerP and mResizeEventListenerP can be
467 // registerd with other targets than the DOM event receiver that
468 // we can reach from here. But nonetheless, unregister the event
469 // listeners with the DOM event reveiver (if it's registerd with
470 // other targets, it'll get unregisterd once the target goes
471 // away).
473 if (mMouseMotionListenerP)
475 // mMouseMotionListenerP might be registerd either by IID or
476 // name, unregister by both.
477 piTarget->RemoveEventListenerByIID(mMouseMotionListenerP,
478 NS_GET_IID(nsIDOMMouseMotionListener));
480 target->RemoveEventListener(NS_LITERAL_STRING("mousemove"),
481 mMouseMotionListenerP, PR_TRUE);
484 if (mResizeEventListenerP)
486 target->RemoveEventListener(NS_LITERAL_STRING("resize"),
487 mResizeEventListenerP, PR_FALSE);
491 mMouseMotionListenerP = nsnull;
492 mResizeEventListenerP = nsnull;
494 nsPlaintextEditor::RemoveEventListeners();
497 NS_IMETHODIMP
498 nsHTMLEditor::SetFlags(PRUint32 aFlags)
500 nsresult rv = nsPlaintextEditor::SetFlags(aFlags);
501 NS_ENSURE_SUCCESS(rv, rv);
503 // Sets mCSSAware to correspond to aFlags. This toggles whether CSS is
504 // used to style elements in the editor. Note that the editor is only CSS
505 // aware by default in Composer and in the mail editor.
506 mCSSAware = !NoCSS() && !IsMailEditor();
508 return NS_OK;
511 NS_IMETHODIMP
512 nsHTMLEditor::InitRules()
514 // instantiate the rules for the html editor
515 nsresult res = NS_NewHTMLEditRules(getter_AddRefs(mRules));
516 NS_ENSURE_SUCCESS(res, res);
517 NS_ENSURE_TRUE(mRules, NS_ERROR_UNEXPECTED);
518 res = mRules->Init(static_cast<nsPlaintextEditor*>(this));
520 return res;
523 NS_IMETHODIMP
524 nsHTMLEditor::BeginningOfDocument()
526 if (!mDocWeak || !mPresShellWeak) { return NS_ERROR_NOT_INITIALIZED; }
528 // get the selection
529 nsCOMPtr<nsISelection> selection;
530 nsresult res = GetSelection(getter_AddRefs(selection));
531 NS_ENSURE_SUCCESS(res, res);
532 NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
534 // Get the root element.
535 nsIDOMElement *rootElement = GetRoot();
536 if (!rootElement) {
537 NS_WARNING("GetRoot() returned a null pointer (mRootElement is null)");
538 return NS_OK;
541 // find first editable thingy
542 PRBool done = PR_FALSE;
543 nsCOMPtr<nsIDOMNode> curNode(rootElement), selNode;
544 PRInt32 curOffset = 0, selOffset;
545 while (!done)
547 nsWSRunObject wsObj(this, curNode, curOffset);
548 nsCOMPtr<nsIDOMNode> visNode;
549 PRInt32 visOffset=0;
550 PRInt16 visType=0;
551 wsObj.NextVisibleNode(curNode, curOffset, address_of(visNode), &visOffset, &visType);
552 if ((visType==nsWSRunObject::eNormalWS) ||
553 (visType==nsWSRunObject::eText))
555 selNode = visNode;
556 selOffset = visOffset;
557 done = PR_TRUE;
559 else if ((visType==nsWSRunObject::eBreak) ||
560 (visType==nsWSRunObject::eSpecial))
562 res = GetNodeLocation(visNode, address_of(selNode), &selOffset);
563 NS_ENSURE_SUCCESS(res, res);
564 done = PR_TRUE;
566 else if (visType==nsWSRunObject::eOtherBlock)
568 // By definition of nsWSRunObject, a block element terminates
569 // a whitespace run. That is, although we are calling a method
570 // that is named "NextVisibleNode", the node returned
571 // might not be visible/editable!
572 // If the given block does not contain any visible/editable items,
573 // we want to skip it and continue our search.
575 if (!IsContainer(visNode))
577 // However, we were given a block that is not a container.
578 // Since the block can not contain anything that's visible,
579 // such a block only makes sense if it is visible by itself,
580 // like a <hr>
581 // We want to place the caret in front of that block.
583 res = GetNodeLocation(visNode, address_of(selNode), &selOffset);
584 NS_ENSURE_SUCCESS(res, res);
585 done = PR_TRUE;
587 else
589 PRBool isEmptyBlock;
590 if (NS_SUCCEEDED(IsEmptyNode(visNode, &isEmptyBlock)) &&
591 isEmptyBlock)
593 // skip the empty block
594 res = GetNodeLocation(visNode, address_of(curNode), &curOffset);
595 NS_ENSURE_SUCCESS(res, res);
596 ++curOffset;
598 else
600 curNode = visNode;
601 curOffset = 0;
603 // keep looping
606 else
608 // else we found nothing useful
609 selNode = curNode;
610 selOffset = curOffset;
611 done = PR_TRUE;
614 return selection->Collapse(selNode, selOffset);
617 nsresult
618 nsHTMLEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent)
620 // NOTE: When you change this method, you should also change:
621 // * editor/libeditor/html/tests/test_htmleditor_keyevent_handling.html
623 if (IsReadonly() || IsDisabled()) {
624 // When we're not editable, the events are handled on nsEditor, so, we can
625 // bypass nsPlaintextEditor.
626 return nsEditor::HandleKeyPressEvent(aKeyEvent);
629 nsKeyEvent* nativeKeyEvent = GetNativeKeyEvent(aKeyEvent);
630 NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED);
631 NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS,
632 "HandleKeyPressEvent gets non-keypress event");
634 switch (nativeKeyEvent->keyCode) {
635 case nsIDOMKeyEvent::DOM_VK_META:
636 case nsIDOMKeyEvent::DOM_VK_SHIFT:
637 case nsIDOMKeyEvent::DOM_VK_CONTROL:
638 case nsIDOMKeyEvent::DOM_VK_ALT:
639 case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
640 case nsIDOMKeyEvent::DOM_VK_DELETE:
641 // These keys are handled on nsEditor, so, we can bypass
642 // nsPlaintextEditor.
643 return nsEditor::HandleKeyPressEvent(aKeyEvent);
644 case nsIDOMKeyEvent::DOM_VK_TAB: {
645 if (IsPlaintextEditor()) {
646 // If this works as plain text editor, e.g., mail editor for plain
647 // text, should be handled on nsPlaintextEditor.
648 return nsPlaintextEditor::HandleKeyPressEvent(aKeyEvent);
651 if (IsTabbable()) {
652 return NS_OK; // let it be used for focus switching
655 if (nativeKeyEvent->isControl || nativeKeyEvent->isAlt ||
656 nativeKeyEvent->isMeta) {
657 return NS_OK;
660 nsCOMPtr<nsISelection> selection;
661 nsresult rv = GetSelection(getter_AddRefs(selection));
662 NS_ENSURE_SUCCESS(rv, rv);
663 PRInt32 offset;
664 nsCOMPtr<nsIDOMNode> node, blockParent;
665 rv = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset);
666 NS_ENSURE_SUCCESS(rv, rv);
667 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
669 PRBool isBlock = PR_FALSE;
670 NodeIsBlock(node, &isBlock);
671 if (isBlock) {
672 blockParent = node;
673 } else {
674 blockParent = GetBlockNodeParent(node);
677 if (!blockParent) {
678 break;
681 PRBool handled = PR_FALSE;
682 if (nsHTMLEditUtils::IsTableElement(blockParent)) {
683 rv = TabInTable(nativeKeyEvent->isShift, &handled);
684 if (handled) {
685 ScrollSelectionIntoView(PR_FALSE);
687 } else if (nsHTMLEditUtils::IsListItem(blockParent)) {
688 rv = Indent(nativeKeyEvent->isShift ?
689 NS_LITERAL_STRING("outdent") :
690 NS_LITERAL_STRING("indent"));
691 handled = PR_TRUE;
693 NS_ENSURE_SUCCESS(rv, rv);
694 if (handled) {
695 return aKeyEvent->PreventDefault(); // consumed
697 if (nativeKeyEvent->isShift) {
698 return NS_OK; // don't type text for shift tabs
700 aKeyEvent->PreventDefault();
701 return TypedText(NS_LITERAL_STRING("\t"), eTypedText);
703 case nsIDOMKeyEvent::DOM_VK_RETURN:
704 case nsIDOMKeyEvent::DOM_VK_ENTER:
705 if (nativeKeyEvent->isControl || nativeKeyEvent->isAlt ||
706 nativeKeyEvent->isMeta) {
707 return NS_OK;
709 aKeyEvent->PreventDefault(); // consumed
710 if (nativeKeyEvent->isShift && !IsPlaintextEditor()) {
711 // only inserts a br node
712 return TypedText(EmptyString(), eTypedBR);
714 // uses rules to figure out what to insert
715 return TypedText(EmptyString(), eTypedBreak);
718 // NOTE: On some keyboard layout, some characters are inputted with Control
719 // key or Alt key, but at that time, widget sets FALSE to these keys.
720 if (nativeKeyEvent->charCode == 0 || nativeKeyEvent->isControl ||
721 nativeKeyEvent->isAlt || nativeKeyEvent->isMeta) {
722 // we don't PreventDefault() here or keybindings like control-x won't work
723 return NS_OK;
725 aKeyEvent->PreventDefault();
726 nsAutoString str(nativeKeyEvent->charCode);
727 return TypedText(str, eTypedText);
731 * Returns true if the id represents an element of block type.
732 * Can be used to determine if a new paragraph should be started.
734 nsresult
735 nsHTMLEditor::NodeIsBlockStatic(nsIDOMNode *aNode, PRBool *aIsBlock)
737 if (!aNode || !aIsBlock) { return NS_ERROR_NULL_POINTER; }
739 *aIsBlock = PR_FALSE;
741 #define USE_PARSER_FOR_BLOCKNESS 1
742 #ifdef USE_PARSER_FOR_BLOCKNESS
743 nsresult rv;
745 nsCOMPtr<nsIDOMElement>element = do_QueryInterface(aNode);
746 if (!element)
748 // We don't have an element -- probably a text node
749 return NS_OK;
752 nsIAtom *tagAtom = GetTag(aNode);
753 NS_ENSURE_TRUE(tagAtom, NS_ERROR_NULL_POINTER);
755 // Nodes we know we want to treat as block
756 // even though the parser says they're not:
757 if (tagAtom==nsEditProperty::body ||
758 tagAtom==nsEditProperty::head ||
759 tagAtom==nsEditProperty::tbody ||
760 tagAtom==nsEditProperty::thead ||
761 tagAtom==nsEditProperty::tfoot ||
762 tagAtom==nsEditProperty::tr ||
763 tagAtom==nsEditProperty::th ||
764 tagAtom==nsEditProperty::td ||
765 tagAtom==nsEditProperty::li ||
766 tagAtom==nsEditProperty::dt ||
767 tagAtom==nsEditProperty::dd ||
768 tagAtom==nsEditProperty::pre)
770 *aIsBlock = PR_TRUE;
771 return NS_OK;
774 rv = nsContentUtils::GetParserService()->
775 IsBlock(nsContentUtils::GetParserService()->HTMLAtomTagToId(tagAtom),
776 *aIsBlock);
778 #ifdef DEBUG
779 // Check this against what we would have said with the old code:
780 if (tagAtom==nsEditProperty::p ||
781 tagAtom==nsEditProperty::div ||
782 tagAtom==nsEditProperty::blockquote ||
783 tagAtom==nsEditProperty::h1 ||
784 tagAtom==nsEditProperty::h2 ||
785 tagAtom==nsEditProperty::h3 ||
786 tagAtom==nsEditProperty::h4 ||
787 tagAtom==nsEditProperty::h5 ||
788 tagAtom==nsEditProperty::h6 ||
789 tagAtom==nsEditProperty::ul ||
790 tagAtom==nsEditProperty::ol ||
791 tagAtom==nsEditProperty::dl ||
792 tagAtom==nsEditProperty::noscript ||
793 tagAtom==nsEditProperty::form ||
794 tagAtom==nsEditProperty::hr ||
795 tagAtom==nsEditProperty::table ||
796 tagAtom==nsEditProperty::fieldset ||
797 tagAtom==nsEditProperty::address ||
798 tagAtom==nsEditProperty::caption ||
799 tagAtom==nsEditProperty::col ||
800 tagAtom==nsEditProperty::colgroup ||
801 tagAtom==nsEditProperty::li ||
802 tagAtom==nsEditProperty::dt ||
803 tagAtom==nsEditProperty::dd ||
804 tagAtom==nsEditProperty::legend )
806 if (!(*aIsBlock))
808 nsAutoString assertmsg (NS_LITERAL_STRING("Parser and editor disagree on blockness: "));
810 nsAutoString tagName;
811 rv = element->GetTagName(tagName);
812 NS_ENSURE_SUCCESS(rv, rv);
814 assertmsg.Append(tagName);
815 char* assertstr = ToNewCString(assertmsg);
816 NS_ASSERTION(*aIsBlock, assertstr);
817 NS_Free(assertstr);
820 #endif /* DEBUG */
822 return rv;
823 #else /* USE_PARSER_FOR_BLOCKNESS */
824 nsresult result = NS_ERROR_FAILURE;
825 *aIsBlock = PR_FALSE;
826 nsCOMPtr<nsIDOMElement>element;
827 element = do_QueryInterface(aNode);
828 if (element)
830 nsAutoString tagName;
831 result = element->GetTagName(tagName);
832 if (NS_SUCCEEDED(result))
834 ToLowerCase(tagName);
835 nsCOMPtr<nsIAtom> tagAtom = do_GetAtom(tagName);
836 if (!tagAtom) { return NS_ERROR_NULL_POINTER; }
838 if (tagAtom==nsEditProperty::p ||
839 tagAtom==nsEditProperty::div ||
840 tagAtom==nsEditProperty::blockquote ||
841 tagAtom==nsEditProperty::h1 ||
842 tagAtom==nsEditProperty::h2 ||
843 tagAtom==nsEditProperty::h3 ||
844 tagAtom==nsEditProperty::h4 ||
845 tagAtom==nsEditProperty::h5 ||
846 tagAtom==nsEditProperty::h6 ||
847 tagAtom==nsEditProperty::ul ||
848 tagAtom==nsEditProperty::ol ||
849 tagAtom==nsEditProperty::dl ||
850 tagAtom==nsEditProperty::pre ||
851 tagAtom==nsEditProperty::noscript ||
852 tagAtom==nsEditProperty::form ||
853 tagAtom==nsEditProperty::hr ||
854 tagAtom==nsEditProperty::fieldset ||
855 tagAtom==nsEditProperty::address ||
856 tagAtom==nsEditProperty::body ||
857 tagAtom==nsEditProperty::caption ||
858 tagAtom==nsEditProperty::table ||
859 tagAtom==nsEditProperty::tbody ||
860 tagAtom==nsEditProperty::thead ||
861 tagAtom==nsEditProperty::tfoot ||
862 tagAtom==nsEditProperty::tr ||
863 tagAtom==nsEditProperty::td ||
864 tagAtom==nsEditProperty::th ||
865 tagAtom==nsEditProperty::col ||
866 tagAtom==nsEditProperty::colgroup ||
867 tagAtom==nsEditProperty::li ||
868 tagAtom==nsEditProperty::dt ||
869 tagAtom==nsEditProperty::dd ||
870 tagAtom==nsEditProperty::legend )
872 *aIsBlock = PR_TRUE;
874 result = NS_OK;
876 } else {
877 // We don't have an element -- probably a text node
878 nsCOMPtr<nsIDOMCharacterData>nodeAsText = do_QueryInterface(aNode);
879 if (nodeAsText)
881 *aIsBlock = PR_FALSE;
882 result = NS_OK;
885 return result;
887 #endif /* USE_PARSER_FOR_BLOCKNESS */
890 NS_IMETHODIMP
891 nsHTMLEditor::NodeIsBlock(nsIDOMNode *aNode, PRBool *aIsBlock)
893 return NodeIsBlockStatic(aNode, aIsBlock);
896 PRBool
897 nsHTMLEditor::IsBlockNode(nsIDOMNode *aNode)
899 PRBool isBlock;
900 NodeIsBlockStatic(aNode, &isBlock);
901 return isBlock;
904 // Non-static version for the nsIEditor interface and JavaScript
905 NS_IMETHODIMP
906 nsHTMLEditor::SetDocumentTitle(const nsAString &aTitle)
908 nsRefPtr<SetDocTitleTxn> txn = new SetDocTitleTxn();
909 NS_ENSURE_TRUE(txn, NS_ERROR_OUT_OF_MEMORY);
911 nsresult result = txn->Init(this, &aTitle);
912 NS_ENSURE_SUCCESS(result, result);
914 //Don't let Rules System change the selection
915 nsAutoTxnsConserveSelection dontChangeSelection(this);
916 return nsEditor::DoTransaction(txn);
919 /* ------------ Block methods moved from nsEditor -------------- */
920 ///////////////////////////////////////////////////////////////////////////
921 // GetBlockNodeParent: returns enclosing block level ancestor, if any
923 nsCOMPtr<nsIDOMNode>
924 nsHTMLEditor::GetBlockNodeParent(nsIDOMNode *aNode)
926 nsCOMPtr<nsIDOMNode> tmp;
927 nsCOMPtr<nsIDOMNode> p;
929 if (!aNode)
931 NS_NOTREACHED("null node passed to GetBlockNodeParent()");
932 return PR_FALSE;
935 if (NS_FAILED(aNode->GetParentNode(getter_AddRefs(p)))) // no parent, ran off top of tree
936 return tmp;
938 while (p)
940 PRBool isBlock;
941 if (NS_FAILED(NodeIsBlockStatic(p, &isBlock)) || isBlock)
942 break;
943 if ( NS_FAILED(p->GetParentNode(getter_AddRefs(tmp))) || !tmp) // no parent, ran off top of tree
944 return p;
946 p = tmp;
948 return p;
951 ///////////////////////////////////////////////////////////////////////////
952 // GetBlockSection: return leftmost/rightmost nodes in aChild's block
954 nsresult
955 nsHTMLEditor::GetBlockSection(nsIDOMNode *aChild,
956 nsIDOMNode **aLeftNode,
957 nsIDOMNode **aRightNode)
959 nsresult result = NS_OK;
960 if (!aChild || !aLeftNode || !aRightNode) {return NS_ERROR_NULL_POINTER;}
961 *aLeftNode = aChild;
962 *aRightNode = aChild;
964 nsCOMPtr<nsIDOMNode>sibling;
965 result = aChild->GetPreviousSibling(getter_AddRefs(sibling));
966 while ((NS_SUCCEEDED(result)) && sibling)
968 PRBool isBlock;
969 NodeIsBlockStatic(sibling, &isBlock);
970 if (isBlock)
972 nsCOMPtr<nsIDOMCharacterData>nodeAsText = do_QueryInterface(sibling);
973 if (!nodeAsText) {
974 break;
976 // XXX: needs some logic to work for other leaf nodes besides text!
978 *aLeftNode = sibling;
979 result = (*aLeftNode)->GetPreviousSibling(getter_AddRefs(sibling));
981 NS_ADDREF((*aLeftNode));
982 // now do the right side
983 result = aChild->GetNextSibling(getter_AddRefs(sibling));
984 while ((NS_SUCCEEDED(result)) && sibling)
986 PRBool isBlock;
987 NodeIsBlockStatic(sibling, &isBlock);
988 if (isBlock)
990 nsCOMPtr<nsIDOMCharacterData>nodeAsText = do_QueryInterface(sibling);
991 if (!nodeAsText) {
992 break;
995 *aRightNode = sibling;
996 result = (*aRightNode)->GetNextSibling(getter_AddRefs(sibling));
998 NS_ADDREF((*aRightNode));
1000 return result;
1004 ///////////////////////////////////////////////////////////////////////////
1005 // GetBlockSectionsForRange: return list of block sections that intersect
1006 // this range
1007 nsresult
1008 nsHTMLEditor::GetBlockSectionsForRange(nsIDOMRange *aRange,
1009 nsCOMArray<nsIDOMRange>& aSections)
1011 if (!aRange) {return NS_ERROR_NULL_POINTER;}
1013 nsresult result;
1014 nsCOMPtr<nsIContentIterator>iter =
1015 do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &result);
1016 if ((NS_SUCCEEDED(result)) && iter)
1018 nsCOMPtr<nsIDOMRange> lastRange;
1019 iter->Init(aRange);
1020 while (iter->IsDone())
1022 nsCOMPtr<nsIContent> currentContent =
1023 do_QueryInterface(iter->GetCurrentNode());
1025 nsCOMPtr<nsIDOMNode>currentNode = do_QueryInterface(currentContent);
1026 if (currentNode)
1028 // <BR> divides block content ranges. We can achieve this by nulling out lastRange
1029 if (currentContent->Tag() == nsEditProperty::br)
1031 lastRange = nsnull;
1033 else
1035 PRBool isNotInlineOrText;
1036 result = NodeIsBlockStatic(currentNode, &isNotInlineOrText);
1037 if (isNotInlineOrText)
1039 PRUint16 nodeType;
1040 currentNode->GetNodeType(&nodeType);
1041 if (nsIDOMNode::TEXT_NODE == nodeType) {
1042 isNotInlineOrText = PR_TRUE;
1045 if (PR_FALSE==isNotInlineOrText)
1047 nsCOMPtr<nsIDOMNode>leftNode;
1048 nsCOMPtr<nsIDOMNode>rightNode;
1049 result = GetBlockSection(currentNode,
1050 getter_AddRefs(leftNode),
1051 getter_AddRefs(rightNode));
1052 if ((NS_SUCCEEDED(result)) && leftNode && rightNode)
1054 // add range to the list if it doesn't overlap with the previous range
1055 PRBool addRange=PR_TRUE;
1056 if (lastRange)
1058 nsCOMPtr<nsIDOMNode> lastStartNode;
1059 nsCOMPtr<nsIDOMElement> blockParentOfLastStartNode;
1060 lastRange->GetStartContainer(getter_AddRefs(lastStartNode));
1061 blockParentOfLastStartNode = do_QueryInterface(GetBlockNodeParent(lastStartNode));
1062 if (blockParentOfLastStartNode)
1064 nsCOMPtr<nsIDOMElement> blockParentOfLeftNode;
1065 blockParentOfLeftNode = do_QueryInterface(GetBlockNodeParent(leftNode));
1066 if (blockParentOfLeftNode)
1068 if (blockParentOfLastStartNode==blockParentOfLeftNode) {
1069 addRange = PR_FALSE;
1074 if (PR_TRUE==addRange)
1076 nsCOMPtr<nsIDOMRange> range =
1077 do_CreateInstance("@mozilla.org/content/range;1", &result);
1078 if ((NS_SUCCEEDED(result)) && range)
1079 { // initialize the range
1080 range->SetStart(leftNode, 0);
1081 range->SetEnd(rightNode, 0);
1082 aSections.AppendObject(range);
1083 lastRange = do_QueryInterface(range);
1090 /* do not check result here, and especially do not return the result code.
1091 * we rely on iter->IsDone to tell us when the iteration is complete
1093 iter->Next();
1096 return result;
1100 ///////////////////////////////////////////////////////////////////////////
1101 // NextNodeInBlock: gets the next/prev node in the block, if any. Next node
1102 // must be an element or text node, others are ignored
1103 nsCOMPtr<nsIDOMNode>
1104 nsHTMLEditor::NextNodeInBlock(nsIDOMNode *aNode, IterDirection aDir)
1106 nsCOMPtr<nsIDOMNode> nullNode;
1107 nsCOMPtr<nsIContent> content;
1108 nsCOMPtr<nsIContent> blockContent;
1109 nsCOMPtr<nsIDOMNode> node;
1110 nsCOMPtr<nsIDOMNode> blockParent;
1112 NS_ENSURE_TRUE(aNode, nullNode);
1114 nsresult rv;
1115 nsCOMPtr<nsIContentIterator> iter =
1116 do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &rv);
1117 NS_ENSURE_SUCCESS(rv, nullNode);
1119 // much gnashing of teeth as we twit back and forth between content and domnode types
1120 content = do_QueryInterface(aNode);
1121 PRBool isBlock;
1122 if (NS_SUCCEEDED(NodeIsBlockStatic(aNode, &isBlock)) && isBlock)
1124 blockParent = aNode;
1126 else
1128 blockParent = GetBlockNodeParent(aNode);
1130 NS_ENSURE_TRUE(blockParent, nullNode);
1131 blockContent = do_QueryInterface(blockParent);
1132 NS_ENSURE_TRUE(blockContent, nullNode);
1134 if (NS_FAILED(iter->Init(blockContent))) return nullNode;
1135 if (NS_FAILED(iter->PositionAt(content))) return nullNode;
1137 while (!iter->IsDone())
1139 // ignore nodes that aren't elements or text, or that are the
1140 // block parent
1141 node = do_QueryInterface(iter->GetCurrentNode());
1142 if (node && IsTextOrElementNode(node) && node != blockParent &&
1143 node != aNode)
1144 return node;
1146 if (aDir == kIterForward)
1147 iter->Next();
1148 else
1149 iter->Prev();
1152 return nullNode;
1155 static const PRUnichar nbsp = 160;
1157 ///////////////////////////////////////////////////////////////////////////
1158 // IsNextCharWhitespace: checks the adjacent content in the same block
1159 // to see if following selection is whitespace or nbsp
1160 nsresult
1161 nsHTMLEditor::IsNextCharWhitespace(nsIDOMNode *aParentNode,
1162 PRInt32 aOffset,
1163 PRBool *outIsSpace,
1164 PRBool *outIsNBSP,
1165 nsCOMPtr<nsIDOMNode> *outNode,
1166 PRInt32 *outOffset)
1168 NS_ENSURE_TRUE(outIsSpace && outIsNBSP, NS_ERROR_NULL_POINTER);
1169 *outIsSpace = PR_FALSE;
1170 *outIsNBSP = PR_FALSE;
1171 if (outNode) *outNode = nsnull;
1172 if (outOffset) *outOffset = -1;
1174 nsAutoString tempString;
1175 PRUint32 strLength;
1176 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(aParentNode);
1177 if (textNode)
1179 textNode->GetLength(&strLength);
1180 if ((PRUint32)aOffset < strLength)
1182 // easy case: next char is in same node
1183 textNode->SubstringData(aOffset,aOffset+1,tempString);
1184 *outIsSpace = nsCRT::IsAsciiSpace(tempString.First());
1185 *outIsNBSP = (tempString.First() == nbsp);
1186 if (outNode) *outNode = do_QueryInterface(aParentNode);
1187 if (outOffset) *outOffset = aOffset+1; // yes, this is _past_ the character;
1188 return NS_OK;
1192 // harder case: next char in next node.
1193 nsCOMPtr<nsIDOMNode> node = NextNodeInBlock(aParentNode, kIterForward);
1194 nsCOMPtr<nsIDOMNode> tmp;
1195 while (node)
1197 PRBool isBlock (PR_FALSE);
1198 NodeIsBlock(node, &isBlock);
1199 if (isBlock) // skip over bold, italic, link, ect nodes
1201 if (IsTextNode(node) && IsEditable(node))
1203 textNode = do_QueryInterface(node);
1204 textNode->GetLength(&strLength);
1205 if (strLength)
1207 textNode->SubstringData(0,1,tempString);
1208 *outIsSpace = nsCRT::IsAsciiSpace(tempString.First());
1209 *outIsNBSP = (tempString.First() == nbsp);
1210 if (outNode) *outNode = do_QueryInterface(node);
1211 if (outOffset) *outOffset = 1; // yes, this is _past_ the character;
1212 return NS_OK;
1214 // else it's an empty text node, or not editable; skip it.
1216 else // node is an image or some other thingy that doesn't count as whitespace
1218 break;
1221 tmp = node;
1222 node = NextNodeInBlock(tmp, kIterForward);
1225 return NS_OK;
1229 ///////////////////////////////////////////////////////////////////////////
1230 // IsPrevCharWhitespace: checks the adjacent content in the same block
1231 // to see if following selection is whitespace
1232 nsresult
1233 nsHTMLEditor::IsPrevCharWhitespace(nsIDOMNode *aParentNode,
1234 PRInt32 aOffset,
1235 PRBool *outIsSpace,
1236 PRBool *outIsNBSP,
1237 nsCOMPtr<nsIDOMNode> *outNode,
1238 PRInt32 *outOffset)
1240 NS_ENSURE_TRUE(outIsSpace && outIsNBSP, NS_ERROR_NULL_POINTER);
1241 *outIsSpace = PR_FALSE;
1242 *outIsNBSP = PR_FALSE;
1243 if (outNode) *outNode = nsnull;
1244 if (outOffset) *outOffset = -1;
1246 nsAutoString tempString;
1247 PRUint32 strLength;
1248 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(aParentNode);
1249 if (textNode)
1251 if (aOffset > 0)
1253 // easy case: prev char is in same node
1254 textNode->SubstringData(aOffset-1,aOffset,tempString);
1255 *outIsSpace = nsCRT::IsAsciiSpace(tempString.First());
1256 *outIsNBSP = (tempString.First() == nbsp);
1257 if (outNode) *outNode = do_QueryInterface(aParentNode);
1258 if (outOffset) *outOffset = aOffset-1;
1259 return NS_OK;
1263 // harder case: prev char in next node
1264 nsCOMPtr<nsIDOMNode> node = NextNodeInBlock(aParentNode, kIterBackward);
1265 nsCOMPtr<nsIDOMNode> tmp;
1266 while (node)
1268 PRBool isBlock (PR_FALSE);
1269 NodeIsBlock(node, &isBlock);
1270 if (isBlock) // skip over bold, italic, link, ect nodes
1272 if (IsTextNode(node) && IsEditable(node))
1274 textNode = do_QueryInterface(node);
1275 textNode->GetLength(&strLength);
1276 if (strLength)
1278 // you could use nsIContent::TextIsOnlyWhitespace here
1279 textNode->SubstringData(strLength-1,strLength,tempString);
1280 *outIsSpace = nsCRT::IsAsciiSpace(tempString.First());
1281 *outIsNBSP = (tempString.First() == nbsp);
1282 if (outNode) *outNode = do_QueryInterface(aParentNode);
1283 if (outOffset) *outOffset = strLength-1;
1284 return NS_OK;
1286 // else it's an empty text node, or not editable; skip it.
1288 else // node is an image or some other thingy that doesn't count as whitespace
1290 break;
1293 // otherwise we found a node we want to skip, keep going
1294 tmp = node;
1295 node = NextNodeInBlock(tmp, kIterBackward);
1298 return NS_OK;
1304 /* ------------ End Block methods -------------- */
1307 PRBool nsHTMLEditor::IsVisBreak(nsIDOMNode *aNode)
1309 NS_ENSURE_TRUE(aNode, PR_FALSE);
1310 if (!nsTextEditUtils::IsBreak(aNode))
1311 return PR_FALSE;
1312 // check if there is a later node in block after br
1313 nsCOMPtr<nsIDOMNode> priorNode, nextNode;
1314 GetPriorHTMLNode(aNode, address_of(priorNode), PR_TRUE);
1315 GetNextHTMLNode(aNode, address_of(nextNode), PR_TRUE);
1316 // if we are next to another break, we are visible
1317 if (priorNode && nsTextEditUtils::IsBreak(priorNode))
1318 return PR_TRUE;
1319 if (nextNode && nsTextEditUtils::IsBreak(nextNode))
1320 return PR_TRUE;
1322 // if we are right before block boundary, then br not visible
1323 NS_ENSURE_TRUE(nextNode, PR_FALSE); // this break is trailer in block, it's not visible
1324 if (IsBlockNode(nextNode))
1325 return PR_FALSE; // break is right before a block, it's not visible
1327 // sigh. We have to use expensive whitespace calculation code to
1328 // determine what is going on
1329 nsCOMPtr<nsIDOMNode> selNode, tmp;
1330 PRInt32 selOffset;
1331 GetNodeLocation(aNode, address_of(selNode), &selOffset);
1332 selOffset++; // lets look after the break
1333 nsWSRunObject wsObj(this, selNode, selOffset);
1334 nsCOMPtr<nsIDOMNode> visNode;
1335 PRInt32 visOffset=0;
1336 PRInt16 visType=0;
1337 wsObj.NextVisibleNode(selNode, selOffset, address_of(visNode), &visOffset, &visType);
1338 if (visType & nsWSRunObject::eBlock)
1339 return PR_FALSE;
1341 return PR_TRUE;
1344 NS_IMETHODIMP
1345 nsHTMLEditor::BreakIsVisible(nsIDOMNode *aNode, PRBool *aIsVisible)
1347 NS_ENSURE_ARG_POINTER(aNode && aIsVisible);
1349 *aIsVisible = IsVisBreak(aNode);
1351 return NS_OK;
1355 NS_IMETHODIMP
1356 nsHTMLEditor::GetIsDocumentEditable(PRBool *aIsDocumentEditable)
1358 NS_ENSURE_ARG_POINTER(aIsDocumentEditable);
1360 nsCOMPtr<nsIDOMDocument> doc;
1361 GetDocument(getter_AddRefs(doc));
1362 *aIsDocumentEditable = doc ? IsModifiable() : PR_FALSE;
1364 return NS_OK;
1367 PRBool nsHTMLEditor::IsModifiable()
1369 return !IsReadonly();
1372 #ifdef XP_MAC
1373 #pragma mark -
1374 #pragma mark nsIHTMLEditor methods
1375 #pragma mark -
1376 #endif
1378 NS_IMETHODIMP
1379 nsHTMLEditor::UpdateBaseURL()
1381 nsCOMPtr<nsIDOMDocument> domDoc;
1382 GetDocument(getter_AddRefs(domDoc));
1383 NS_ENSURE_TRUE(domDoc, NS_ERROR_FAILURE);
1385 // Look for an HTML <base> tag
1386 nsCOMPtr<nsIDOMNodeList> nodeList;
1387 nsresult rv = domDoc->GetElementsByTagName(NS_LITERAL_STRING("base"), getter_AddRefs(nodeList));
1388 NS_ENSURE_SUCCESS(rv, rv);
1390 nsCOMPtr<nsIDOMNode> baseNode;
1391 if (nodeList)
1393 PRUint32 count;
1394 nodeList->GetLength(&count);
1395 if (count >= 1)
1397 rv = nodeList->Item(0, getter_AddRefs(baseNode));
1398 NS_ENSURE_SUCCESS(rv, rv);
1401 // If no base tag, then set baseURL to the document's URL
1402 // This is very important, else relative URLs for links and images are wrong
1403 if (!baseNode)
1405 nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc);
1406 NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
1408 return doc->SetBaseURI(doc->GetDocumentURI());
1410 return NS_OK;
1413 /* This routine is needed to provide a bottleneck for typing for logging
1414 purposes. Can't use HandleKeyPress() (above) for that since it takes
1415 a nsIDOMKeyEvent* parameter. So instead we pass enough info through
1416 to TypedText() to determine what action to take, but without passing
1417 an event.
1419 NS_IMETHODIMP nsHTMLEditor::TypedText(const nsAString& aString,
1420 PRInt32 aAction)
1422 nsAutoPlaceHolderBatch batch(this, nsGkAtoms::TypingTxnName);
1424 switch (aAction)
1426 case eTypedText:
1427 case eTypedBreak:
1429 return nsPlaintextEditor::TypedText(aString, aAction);
1431 case eTypedBR:
1433 nsCOMPtr<nsIDOMNode> brNode;
1434 return InsertBR(address_of(brNode)); // only inserts a br node
1437 return NS_ERROR_FAILURE;
1440 NS_IMETHODIMP nsHTMLEditor::TabInTable(PRBool inIsShift, PRBool *outHandled)
1442 NS_ENSURE_TRUE(outHandled, NS_ERROR_NULL_POINTER);
1443 *outHandled = PR_FALSE;
1445 // Find enclosing table cell from the selection (cell may be the selected element)
1446 nsCOMPtr<nsIDOMElement> cellElement;
1447 // can't use |NS_LITERAL_STRING| here until |GetElementOrParentByTagName| is fixed to accept readables
1448 nsresult res = GetElementOrParentByTagName(NS_LITERAL_STRING("td"), nsnull, getter_AddRefs(cellElement));
1449 NS_ENSURE_SUCCESS(res, res);
1450 // Do nothing -- we didn't find a table cell
1451 NS_ENSURE_TRUE(cellElement, NS_OK);
1453 // find enclosing table
1454 nsCOMPtr<nsIDOMNode> tbl = GetEnclosingTable(cellElement);
1455 NS_ENSURE_TRUE(tbl, res);
1457 // advance to next cell
1458 // first create an iterator over the table
1459 nsCOMPtr<nsIContentIterator> iter =
1460 do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res);
1461 NS_ENSURE_SUCCESS(res, res);
1462 NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER);
1463 nsCOMPtr<nsIContent> cTbl = do_QueryInterface(tbl);
1464 nsCOMPtr<nsIContent> cBlock = do_QueryInterface(cellElement);
1465 res = iter->Init(cTbl);
1466 NS_ENSURE_SUCCESS(res, res);
1467 // position iter at block
1468 res = iter->PositionAt(cBlock);
1469 NS_ENSURE_SUCCESS(res, res);
1471 nsCOMPtr<nsIDOMNode> node;
1474 if (inIsShift)
1475 iter->Prev();
1476 else
1477 iter->Next();
1479 node = do_QueryInterface(iter->GetCurrentNode());
1481 if (node && nsHTMLEditUtils::IsTableCell(node) &&
1482 GetEnclosingTable(node) == tbl)
1484 res = CollapseSelectionToDeepestNonTableFirstChild(nsnull, node);
1485 NS_ENSURE_SUCCESS(res, res);
1486 *outHandled = PR_TRUE;
1487 return NS_OK;
1489 } while (!iter->IsDone());
1491 if (!(*outHandled) && !inIsShift)
1493 // if we havent handled it yet then we must have run off the end of
1494 // the table. Insert a new row.
1495 res = InsertTableRow(1, PR_TRUE);
1496 NS_ENSURE_SUCCESS(res, res);
1497 *outHandled = PR_TRUE;
1498 // put selection in right place
1499 // Use table code to get selection and index to new row...
1500 nsCOMPtr<nsISelection>selection;
1501 nsCOMPtr<nsIDOMElement> tblElement;
1502 nsCOMPtr<nsIDOMElement> cell;
1503 PRInt32 row;
1504 res = GetCellContext(getter_AddRefs(selection),
1505 getter_AddRefs(tblElement),
1506 getter_AddRefs(cell),
1507 nsnull, nsnull,
1508 &row, nsnull);
1509 NS_ENSURE_SUCCESS(res, res);
1510 // ...so that we can ask for first cell in that row...
1511 res = GetCellAt(tblElement, row, 0, getter_AddRefs(cell));
1512 NS_ENSURE_SUCCESS(res, res);
1513 // ...and then set selection there.
1514 // (Note that normally you should use CollapseSelectionToDeepestNonTableFirstChild(),
1515 // but we know cell is an empty new cell, so this works fine)
1516 node = do_QueryInterface(cell);
1517 if (node) selection->Collapse(node,0);
1518 return NS_OK;
1521 return res;
1524 NS_IMETHODIMP nsHTMLEditor::CreateBRImpl(nsCOMPtr<nsIDOMNode> *aInOutParent,
1525 PRInt32 *aInOutOffset,
1526 nsCOMPtr<nsIDOMNode> *outBRNode,
1527 EDirection aSelect)
1529 NS_ENSURE_TRUE(aInOutParent && *aInOutParent && aInOutOffset && outBRNode, NS_ERROR_NULL_POINTER);
1530 *outBRNode = nsnull;
1531 nsresult res;
1533 // we need to insert a br. unfortunately, we may have to split a text node to do it.
1534 nsCOMPtr<nsIDOMNode> node = *aInOutParent;
1535 PRInt32 theOffset = *aInOutOffset;
1536 nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(node);
1537 NS_NAMED_LITERAL_STRING(brType, "br");
1538 nsCOMPtr<nsIDOMNode> brNode;
1539 if (nodeAsText)
1541 nsCOMPtr<nsIDOMNode> tmp;
1542 PRInt32 offset;
1543 PRUint32 len;
1544 nodeAsText->GetLength(&len);
1545 GetNodeLocation(node, address_of(tmp), &offset);
1546 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
1547 if (!theOffset)
1549 // we are already set to go
1551 else if (theOffset == (PRInt32)len)
1553 // update offset to point AFTER the text node
1554 offset++;
1556 else
1558 // split the text node
1559 res = SplitNode(node, theOffset, getter_AddRefs(tmp));
1560 NS_ENSURE_SUCCESS(res, res);
1561 res = GetNodeLocation(node, address_of(tmp), &offset);
1562 NS_ENSURE_SUCCESS(res, res);
1564 // create br
1565 res = CreateNode(brType, tmp, offset, getter_AddRefs(brNode));
1566 NS_ENSURE_SUCCESS(res, res);
1567 *aInOutParent = tmp;
1568 *aInOutOffset = offset+1;
1570 else
1572 res = CreateNode(brType, node, theOffset, getter_AddRefs(brNode));
1573 NS_ENSURE_SUCCESS(res, res);
1574 (*aInOutOffset)++;
1577 *outBRNode = brNode;
1578 if (*outBRNode && (aSelect != eNone))
1580 nsCOMPtr<nsISelection> selection;
1581 nsCOMPtr<nsIDOMNode> parent;
1582 PRInt32 offset;
1583 res = GetSelection(getter_AddRefs(selection));
1584 NS_ENSURE_SUCCESS(res, res);
1585 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
1586 res = GetNodeLocation(*outBRNode, address_of(parent), &offset);
1587 NS_ENSURE_SUCCESS(res, res);
1588 if (aSelect == eNext)
1590 // position selection after br
1591 selPriv->SetInterlinePosition(PR_TRUE);
1592 res = selection->Collapse(parent, offset+1);
1594 else if (aSelect == ePrevious)
1596 // position selection before br
1597 selPriv->SetInterlinePosition(PR_TRUE);
1598 res = selection->Collapse(parent, offset);
1601 return NS_OK;
1605 NS_IMETHODIMP nsHTMLEditor::CreateBR(nsIDOMNode *aNode, PRInt32 aOffset, nsCOMPtr<nsIDOMNode> *outBRNode, EDirection aSelect)
1607 nsCOMPtr<nsIDOMNode> parent = aNode;
1608 PRInt32 offset = aOffset;
1609 return CreateBRImpl(address_of(parent), &offset, outBRNode, aSelect);
1612 NS_IMETHODIMP nsHTMLEditor::InsertBR(nsCOMPtr<nsIDOMNode> *outBRNode)
1614 PRBool bCollapsed;
1615 nsCOMPtr<nsISelection> selection;
1617 NS_ENSURE_TRUE(outBRNode, NS_ERROR_NULL_POINTER);
1618 *outBRNode = nsnull;
1620 // calling it text insertion to trigger moz br treatment by rules
1621 nsAutoRules beginRulesSniffing(this, kOpInsertText, nsIEditor::eNext);
1623 nsresult res = GetSelection(getter_AddRefs(selection));
1624 NS_ENSURE_SUCCESS(res, res);
1625 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
1626 res = selection->GetIsCollapsed(&bCollapsed);
1627 NS_ENSURE_SUCCESS(res, res);
1628 if (!bCollapsed)
1630 res = DeleteSelection(nsIEditor::eNone);
1631 NS_ENSURE_SUCCESS(res, res);
1633 nsCOMPtr<nsIDOMNode> selNode;
1634 PRInt32 selOffset;
1635 res = GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
1636 NS_ENSURE_SUCCESS(res, res);
1638 res = CreateBR(selNode, selOffset, outBRNode);
1639 NS_ENSURE_SUCCESS(res, res);
1641 // position selection after br
1642 res = GetNodeLocation(*outBRNode, address_of(selNode), &selOffset);
1643 NS_ENSURE_SUCCESS(res, res);
1644 selPriv->SetInterlinePosition(PR_TRUE);
1645 res = selection->Collapse(selNode, selOffset+1);
1647 return res;
1650 nsresult
1651 nsHTMLEditor::CollapseSelectionToDeepestNonTableFirstChild(nsISelection *aSelection, nsIDOMNode *aNode)
1653 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
1654 nsresult res;
1656 nsCOMPtr<nsISelection> selection;
1657 if (aSelection)
1659 selection = aSelection;
1660 } else {
1661 res = GetSelection(getter_AddRefs(selection));
1662 NS_ENSURE_SUCCESS(res, res);
1663 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
1665 nsCOMPtr<nsIDOMNode> node = aNode;
1666 nsCOMPtr<nsIDOMNode> child;
1668 do {
1669 node->GetFirstChild(getter_AddRefs(child));
1671 if (child)
1673 // Stop if we find a table
1674 // don't want to go into nested tables
1675 if (nsHTMLEditUtils::IsTable(child)) break;
1676 // hey, it'g gotta be a container too!
1677 if (!IsContainer(child)) break;
1678 node = child;
1681 while (child);
1683 selection->Collapse(node,0);
1684 return NS_OK;
1688 // This is mostly like InsertHTMLWithCharsetAndContext,
1689 // but we can't use that because it is selection-based and
1690 // the rules code won't let us edit under the <head> node
1691 NS_IMETHODIMP
1692 nsHTMLEditor::ReplaceHeadContentsWithHTML(const nsAString& aSourceToInsert)
1694 nsAutoRules beginRulesSniffing(this, kOpIgnore, nsIEditor::eNone); // don't do any post processing, rules get confused
1695 nsCOMPtr<nsISelection> selection;
1696 nsresult res = GetSelection(getter_AddRefs(selection));
1697 NS_ENSURE_SUCCESS(res, res);
1698 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1700 ForceCompositionEnd();
1702 // Do not use nsAutoRules -- rules code won't let us insert in <head>
1703 // Use the head node as a parent and delete/insert directly
1704 nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak);
1705 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
1707 nsCOMPtr<nsIDOMNodeList>nodeList;
1708 res = doc->GetElementsByTagName(NS_LITERAL_STRING("head"), getter_AddRefs(nodeList));
1709 NS_ENSURE_SUCCESS(res, res);
1710 NS_ENSURE_TRUE(nodeList, NS_ERROR_NULL_POINTER);
1712 PRUint32 count;
1713 nodeList->GetLength(&count);
1714 if (count < 1) return NS_ERROR_FAILURE;
1716 nsCOMPtr<nsIDOMNode> headNode;
1717 res = nodeList->Item(0, getter_AddRefs(headNode));
1718 NS_ENSURE_SUCCESS(res, res);
1719 NS_ENSURE_TRUE(headNode, NS_ERROR_NULL_POINTER);
1721 // First, make sure there are no return chars in the source.
1722 // Bad things happen if you insert returns (instead of dom newlines, \n)
1723 // into an editor document.
1724 nsAutoString inputString (aSourceToInsert); // hope this does copy-on-write
1726 // Windows linebreaks: Map CRLF to LF:
1727 inputString.ReplaceSubstring(NS_LITERAL_STRING("\r\n").get(),
1728 NS_LITERAL_STRING("\n").get());
1730 // Mac linebreaks: Map any remaining CR to LF:
1731 inputString.ReplaceSubstring(NS_LITERAL_STRING("\r").get(),
1732 NS_LITERAL_STRING("\n").get());
1734 nsAutoEditBatch beginBatching(this);
1736 res = GetSelection(getter_AddRefs(selection));
1737 NS_ENSURE_SUCCESS(res, res);
1738 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1740 // Get the first range in the selection, for context:
1741 nsCOMPtr<nsIDOMRange> range;
1742 res = selection->GetRangeAt(0, getter_AddRefs(range));
1743 NS_ENSURE_SUCCESS(res, res);
1745 nsCOMPtr<nsIDOMNSRange> nsrange (do_QueryInterface(range));
1746 NS_ENSURE_TRUE(nsrange, NS_ERROR_NO_INTERFACE);
1747 nsCOMPtr<nsIDOMDocumentFragment> docfrag;
1748 res = nsrange->CreateContextualFragment(inputString,
1749 getter_AddRefs(docfrag));
1751 //XXXX BUG 50965: This is not returning the text between <title> ... </title>
1752 // Special code is needed in JS to handle title anyway, so it really doesn't matter!
1754 if (NS_FAILED(res))
1756 #ifdef DEBUG
1757 printf("Couldn't create contextual fragment: error was %d\n", res);
1758 #endif
1759 return res;
1761 NS_ENSURE_TRUE(docfrag, NS_ERROR_NULL_POINTER);
1763 nsCOMPtr<nsIDOMNode> child;
1765 // First delete all children in head
1766 do {
1767 res = headNode->GetFirstChild(getter_AddRefs(child));
1768 NS_ENSURE_SUCCESS(res, res);
1769 if (child)
1771 res = DeleteNode(child);
1772 NS_ENSURE_SUCCESS(res, res);
1774 } while (child);
1776 // Now insert the new nodes
1777 PRInt32 offsetOfNewNode = 0;
1778 nsCOMPtr<nsIDOMNode> fragmentAsNode (do_QueryInterface(docfrag));
1780 // Loop over the contents of the fragment and move into the document
1781 do {
1782 res = fragmentAsNode->GetFirstChild(getter_AddRefs(child));
1783 NS_ENSURE_SUCCESS(res, res);
1784 if (child)
1786 res = InsertNode(child, headNode, offsetOfNewNode++);
1787 NS_ENSURE_SUCCESS(res, res);
1789 } while (child);
1791 return res;
1794 NS_IMETHODIMP
1795 nsHTMLEditor::RebuildDocumentFromSource(const nsAString& aSourceString)
1797 ForceCompositionEnd();
1799 nsCOMPtr<nsISelection>selection;
1800 nsresult res = GetSelection(getter_AddRefs(selection));
1801 NS_ENSURE_SUCCESS(res, res);
1803 nsIDOMElement *bodyElement = GetRoot();
1804 NS_ENSURE_SUCCESS(res, res);
1805 NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER);
1807 // Find where the <body> tag starts.
1808 nsReadingIterator<PRUnichar> beginbody;
1809 nsReadingIterator<PRUnichar> endbody;
1810 aSourceString.BeginReading(beginbody);
1811 aSourceString.EndReading(endbody);
1812 PRBool foundbody = CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<body"),
1813 beginbody, endbody);
1815 nsReadingIterator<PRUnichar> beginhead;
1816 nsReadingIterator<PRUnichar> endhead;
1817 aSourceString.BeginReading(beginhead);
1818 aSourceString.EndReading(endhead);
1819 PRBool foundhead = CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<head"),
1820 beginhead, endhead);
1822 nsReadingIterator<PRUnichar> beginclosehead;
1823 nsReadingIterator<PRUnichar> endclosehead;
1824 aSourceString.BeginReading(beginclosehead);
1825 aSourceString.EndReading(endclosehead);
1827 // Find the index after "<head>"
1828 PRBool foundclosehead = CaseInsensitiveFindInReadable(
1829 NS_LITERAL_STRING("</head>"), beginclosehead, endclosehead);
1831 // Time to change the document
1832 nsAutoEditBatch beginBatching(this);
1834 nsReadingIterator<PRUnichar> endtotal;
1835 aSourceString.EndReading(endtotal);
1837 if (foundhead) {
1838 if (foundclosehead)
1839 res = ReplaceHeadContentsWithHTML(Substring(beginhead, beginclosehead));
1840 else if (foundbody)
1841 res = ReplaceHeadContentsWithHTML(Substring(beginhead, beginbody));
1842 else
1843 // XXX Without recourse to some parser/content sink/docshell hackery
1844 // we don't really know where the head ends and the body begins
1845 // so we assume that there is no body
1846 res = ReplaceHeadContentsWithHTML(Substring(beginhead, endtotal));
1847 } else {
1848 nsReadingIterator<PRUnichar> begintotal;
1849 aSourceString.BeginReading(begintotal);
1850 NS_NAMED_LITERAL_STRING(head, "<head>");
1851 if (foundclosehead)
1852 res = ReplaceHeadContentsWithHTML(head + Substring(begintotal, beginclosehead));
1853 else if (foundbody)
1854 res = ReplaceHeadContentsWithHTML(head + Substring(begintotal, beginbody));
1855 else
1856 // XXX Without recourse to some parser/content sink/docshell hackery
1857 // we don't really know where the head ends and the body begins
1858 // so we assume that there is no head
1859 res = ReplaceHeadContentsWithHTML(head);
1861 NS_ENSURE_SUCCESS(res, res);
1863 res = SelectAll();
1864 NS_ENSURE_SUCCESS(res, res);
1866 if (!foundbody) {
1867 NS_NAMED_LITERAL_STRING(body, "<body>");
1868 // XXX Without recourse to some parser/content sink/docshell hackery
1869 // we don't really know where the head ends and the body begins
1870 if (foundclosehead) // assume body starts after the head ends
1871 res = LoadHTML(body + Substring(endclosehead, endtotal));
1872 else if (foundhead) // assume there is no body
1873 res = LoadHTML(body);
1874 else // assume there is no head, the entire source is body
1875 res = LoadHTML(body + aSourceString);
1876 NS_ENSURE_SUCCESS(res, res);
1878 nsCOMPtr<nsIDOMElement> divElement;
1879 res = CreateElementWithDefaults(NS_LITERAL_STRING("div"), getter_AddRefs(divElement));
1880 NS_ENSURE_SUCCESS(res, res);
1882 res = CloneAttributes(bodyElement, divElement);
1883 NS_ENSURE_SUCCESS(res, res);
1885 return BeginningOfDocument();
1888 res = LoadHTML(Substring(beginbody, endtotal));
1889 NS_ENSURE_SUCCESS(res, res);
1891 // Now we must copy attributes user might have edited on the <body> tag
1892 // because InsertHTML (actually, CreateContextualFragment())
1893 // will never return a body node in the DOM fragment
1895 // We already know where "<body" begins
1896 nsReadingIterator<PRUnichar> beginclosebody = beginbody;
1897 nsReadingIterator<PRUnichar> endclosebody;
1898 aSourceString.EndReading(endclosebody);
1899 if (!FindInReadable(NS_LITERAL_STRING(">"),beginclosebody,endclosebody))
1900 return NS_ERROR_FAILURE;
1902 // Truncate at the end of the body tag
1903 // Kludge of the year: fool the parser by replacing "body" with "div" so we get a node
1904 nsAutoString bodyTag;
1905 bodyTag.AssignLiteral("<div ");
1906 bodyTag.Append(Substring(endbody, endclosebody));
1908 nsCOMPtr<nsIDOMRange> range;
1909 res = selection->GetRangeAt(0, getter_AddRefs(range));
1910 NS_ENSURE_SUCCESS(res, res);
1912 nsCOMPtr<nsIDOMNSRange> nsrange (do_QueryInterface(range));
1913 NS_ENSURE_TRUE(nsrange, NS_ERROR_NO_INTERFACE);
1915 nsCOMPtr<nsIDOMDocumentFragment> docfrag;
1916 res = nsrange->CreateContextualFragment(bodyTag, getter_AddRefs(docfrag));
1917 NS_ENSURE_SUCCESS(res, res);
1919 nsCOMPtr<nsIDOMNode> fragmentAsNode (do_QueryInterface(docfrag));
1920 NS_ENSURE_TRUE(fragmentAsNode, NS_ERROR_NULL_POINTER);
1922 nsCOMPtr<nsIDOMNode> child;
1923 res = fragmentAsNode->GetFirstChild(getter_AddRefs(child));
1924 NS_ENSURE_SUCCESS(res, res);
1925 NS_ENSURE_TRUE(child, NS_ERROR_NULL_POINTER);
1927 // Copy all attributes from the div child to current body element
1928 res = CloneAttributes(bodyElement, child);
1929 NS_ENSURE_SUCCESS(res, res);
1931 // place selection at first editable content
1932 return BeginningOfDocument();
1935 void
1936 nsHTMLEditor::NormalizeEOLInsertPosition(nsIDOMNode *firstNodeToInsert,
1937 nsCOMPtr<nsIDOMNode> *insertParentNode,
1938 PRInt32 *insertOffset)
1941 This function will either correct the position passed in,
1942 or leave the position unchanged.
1944 When the (first) item to insert is a block level element,
1945 and our insertion position is after the last visible item in a line,
1946 i.e. the insertion position is just before a visible line break <br>,
1947 we want to skip to the position just after the line break (see bug 68767)
1949 However, our logic to detect whether we should skip or not
1950 needs to be more clever.
1951 We must not skip when the caret appears to be positioned at the beginning
1952 of a block, in that case skipping the <br> would not insert the <br>
1953 at the caret position, but after the current empty line.
1955 So we have several cases to test:
1957 1) We only ever want to skip, if the next visible thing after the current position is a break
1959 2) We do not want to skip if there is no previous visible thing at all
1960 That is detected if the call to PriorVisibleNode gives us an offset of zero.
1961 Because PriorVisibleNode always positions after the prior node, we would
1962 see an offset > 0, if there were a prior node.
1964 3) We do not want to skip, if both the next and the previous visible things are breaks.
1966 4) We do not want to skip if the previous visible thing is in a different block
1967 than the insertion position.
1970 if (!IsBlockNode(firstNodeToInsert))
1971 return;
1973 nsWSRunObject wsObj(this, *insertParentNode, *insertOffset);
1974 nsCOMPtr<nsIDOMNode> nextVisNode;
1975 nsCOMPtr<nsIDOMNode> prevVisNode;
1976 PRInt32 nextVisOffset=0;
1977 PRInt16 nextVisType=0;
1978 PRInt32 prevVisOffset=0;
1979 PRInt16 prevVisType=0;
1981 wsObj.NextVisibleNode(*insertParentNode, *insertOffset, address_of(nextVisNode), &nextVisOffset, &nextVisType);
1982 if (!nextVisNode)
1983 return;
1985 if (! (nextVisType & nsWSRunObject::eBreak))
1986 return;
1988 wsObj.PriorVisibleNode(*insertParentNode, *insertOffset, address_of(prevVisNode), &prevVisOffset, &prevVisType);
1989 if (!prevVisNode)
1990 return;
1992 if (prevVisType & nsWSRunObject::eBreak)
1993 return;
1995 if (prevVisType & nsWSRunObject::eThisBlock)
1996 return;
1998 nsCOMPtr<nsIDOMNode> brNode;
1999 PRInt32 brOffset=0;
2001 GetNodeLocation(nextVisNode, address_of(brNode), &brOffset);
2003 *insertParentNode = brNode;
2004 *insertOffset = brOffset + 1;
2007 NS_IMETHODIMP
2008 nsHTMLEditor::InsertElementAtSelection(nsIDOMElement* aElement, PRBool aDeleteSelection)
2010 // Protect the edit rules object from dying
2011 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
2013 nsresult res = NS_ERROR_NOT_INITIALIZED;
2015 NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
2017 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
2019 ForceCompositionEnd();
2020 nsAutoEditBatch beginBatching(this);
2021 nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
2023 nsCOMPtr<nsISelection>selection;
2024 res = GetSelection(getter_AddRefs(selection));
2025 if (NS_FAILED(res) || !selection)
2026 return NS_ERROR_FAILURE;
2028 // hand off to the rules system, see if it has anything to say about this
2029 PRBool cancel, handled;
2030 nsTextRulesInfo ruleInfo(nsTextEditRules::kInsertElement);
2031 ruleInfo.insertElement = aElement;
2032 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
2033 if (cancel || (NS_FAILED(res))) return res;
2035 if (!handled)
2037 if (aDeleteSelection)
2039 nsCOMPtr<nsIDOMNode> tempNode;
2040 PRInt32 tempOffset;
2041 nsresult result = DeleteSelectionAndPrepareToCreateNode(tempNode,tempOffset);
2042 NS_ENSURE_SUCCESS(result, result);
2045 // If deleting, selection will be collapsed.
2046 // so if not, we collapse it
2047 if (!aDeleteSelection)
2049 // Named Anchor is a special case,
2050 // We collapse to insert element BEFORE the selection
2051 // For all other tags, we insert AFTER the selection
2052 if (nsHTMLEditUtils::IsNamedAnchor(node))
2054 selection->CollapseToStart();
2055 } else {
2056 selection->CollapseToEnd();
2060 nsCOMPtr<nsIDOMNode> parentSelectedNode;
2061 PRInt32 offsetForInsert;
2062 res = selection->GetAnchorNode(getter_AddRefs(parentSelectedNode));
2063 // XXX: ERROR_HANDLING bad XPCOM usage
2064 if (NS_SUCCEEDED(res) && NS_SUCCEEDED(selection->GetAnchorOffset(&offsetForInsert)) && parentSelectedNode)
2066 #ifdef DEBUG_cmanske
2068 nsAutoString name;
2069 parentSelectedNode->GetNodeName(name);
2070 printf("InsertElement: Anchor node of selection: ");
2071 wprintf(name.get());
2072 printf(" Offset: %d\n", offsetForInsert);
2074 #endif
2076 // Adjust position based on the node we are going to insert.
2077 NormalizeEOLInsertPosition(node, address_of(parentSelectedNode), &offsetForInsert);
2079 res = InsertNodeAtPoint(node, address_of(parentSelectedNode), &offsetForInsert, PR_FALSE);
2080 NS_ENSURE_SUCCESS(res, res);
2081 // Set caret after element, but check for special case
2082 // of inserting table-related elements: set in first cell instead
2083 if (!SetCaretInTableCell(aElement))
2085 res = SetCaretAfterElement(aElement);
2086 NS_ENSURE_SUCCESS(res, res);
2088 // check for inserting a whole table at the end of a block. If so insert a br after it.
2089 if (nsHTMLEditUtils::IsTable(node))
2091 PRBool isLast;
2092 res = IsLastEditableChild(node, &isLast);
2093 NS_ENSURE_SUCCESS(res, res);
2094 if (isLast)
2096 nsCOMPtr<nsIDOMNode> brNode;
2097 res = CreateBR(parentSelectedNode, offsetForInsert+1, address_of(brNode));
2098 NS_ENSURE_SUCCESS(res, res);
2099 selection->Collapse(parentSelectedNode, offsetForInsert+1);
2104 res = mRules->DidDoAction(selection, &ruleInfo, res);
2105 return res;
2110 InsertNodeAtPoint: attempts to insert aNode into the document, at a point specified by
2111 {*ioParent,*ioOffset}. Checks with strict dtd to see if containment is allowed. If not
2112 allowed, will attempt to find a parent in the parent hierarchy of *ioParent that will
2113 accept aNode as a child. If such a parent is found, will split the document tree from
2114 {*ioParent,*ioOffset} up to parent, and then insert aNode. ioParent & ioOffset are then
2115 adjusted to point to the actual location that aNode was inserted at. aNoEmptyNodes
2116 specifies if the splitting process is allowed to reslt in empty nodes.
2117 nsIDOMNode *aNode node to insert
2118 nsCOMPtr<nsIDOMNode> *ioParent insertion parent
2119 PRInt32 *ioOffset insertion offset
2120 PRBool aNoEmptyNodes splitting can result in empty nodes?
2122 nsresult
2123 nsHTMLEditor::InsertNodeAtPoint(nsIDOMNode *aNode,
2124 nsCOMPtr<nsIDOMNode> *ioParent,
2125 PRInt32 *ioOffset,
2126 PRBool aNoEmptyNodes)
2128 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
2129 NS_ENSURE_TRUE(ioParent, NS_ERROR_NULL_POINTER);
2130 NS_ENSURE_TRUE(*ioParent, NS_ERROR_NULL_POINTER);
2131 NS_ENSURE_TRUE(ioOffset, NS_ERROR_NULL_POINTER);
2133 nsresult res = NS_OK;
2134 nsAutoString tagName;
2135 aNode->GetNodeName(tagName);
2136 ToLowerCase(tagName);
2137 nsCOMPtr<nsIDOMNode> parent = *ioParent;
2138 nsCOMPtr<nsIDOMNode> topChild = *ioParent;
2139 nsCOMPtr<nsIDOMNode> tmp;
2140 PRInt32 offsetOfInsert = *ioOffset;
2142 // Search up the parent chain to find a suitable container
2143 while (!CanContainTag(parent, tagName))
2145 // If the current parent is a root (body or table element)
2146 // then go no further - we can't insert
2147 if (nsTextEditUtils::IsBody(parent) || nsHTMLEditUtils::IsTableElement(parent))
2148 return NS_ERROR_FAILURE;
2149 // Get the next parent
2150 parent->GetParentNode(getter_AddRefs(tmp));
2151 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
2152 topChild = parent;
2153 parent = tmp;
2155 if (parent != topChild)
2157 // we need to split some levels above the original selection parent
2158 res = SplitNodeDeep(topChild, *ioParent, *ioOffset, &offsetOfInsert, aNoEmptyNodes);
2159 NS_ENSURE_SUCCESS(res, res);
2160 *ioParent = parent;
2161 *ioOffset = offsetOfInsert;
2163 // Now we can insert the new node
2164 res = InsertNode(aNode, parent, offsetOfInsert);
2165 return res;
2168 NS_IMETHODIMP
2169 nsHTMLEditor::SelectElement(nsIDOMElement* aElement)
2171 nsresult res = NS_ERROR_NULL_POINTER;
2173 // Must be sure that element is contained in the document body
2174 if (IsElementInBody(aElement))
2176 nsCOMPtr<nsISelection> selection;
2177 res = GetSelection(getter_AddRefs(selection));
2178 NS_ENSURE_SUCCESS(res, res);
2179 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2180 nsCOMPtr<nsIDOMNode>parent;
2181 res = aElement->GetParentNode(getter_AddRefs(parent));
2182 if (NS_SUCCEEDED(res) && parent)
2184 PRInt32 offsetInParent;
2185 res = GetChildOffset(aElement, parent, offsetInParent);
2187 if (NS_SUCCEEDED(res))
2189 // Collapse selection to just before desired element,
2190 res = selection->Collapse(parent, offsetInParent);
2191 if (NS_SUCCEEDED(res)) {
2192 // then extend it to just after
2193 res = selection->Extend(parent, offsetInParent+1);
2198 return res;
2201 NS_IMETHODIMP
2202 nsHTMLEditor::SetCaretAfterElement(nsIDOMElement* aElement)
2204 nsresult res = NS_ERROR_NULL_POINTER;
2206 // Be sure the element is contained in the document body
2207 if (aElement && IsElementInBody(aElement))
2209 nsCOMPtr<nsISelection> selection;
2210 res = GetSelection(getter_AddRefs(selection));
2211 NS_ENSURE_SUCCESS(res, res);
2212 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2213 nsCOMPtr<nsIDOMNode>parent;
2214 res = aElement->GetParentNode(getter_AddRefs(parent));
2215 NS_ENSURE_SUCCESS(res, res);
2216 NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
2217 PRInt32 offsetInParent;
2218 res = GetChildOffset(aElement, parent, offsetInParent);
2219 if (NS_SUCCEEDED(res))
2221 // Collapse selection to just after desired element,
2222 res = selection->Collapse(parent, offsetInParent+1);
2223 #if 0 //def DEBUG_cmanske
2225 nsAutoString name;
2226 parent->GetNodeName(name);
2227 printf("SetCaretAfterElement: Parent node: ");
2228 wprintf(name.get());
2229 printf(" Offset: %d\n\nHTML:\n", offsetInParent+1);
2230 nsAutoString Format("text/html");
2231 nsAutoString ContentsAs;
2232 OutputToString(Format, 2, ContentsAs);
2233 wprintf(ContentsAs.get());
2235 #endif
2238 return res;
2241 NS_IMETHODIMP
2242 nsHTMLEditor::SetParagraphFormat(const nsAString& aParagraphFormat)
2244 nsAutoString tag; tag.Assign(aParagraphFormat);
2245 ToLowerCase(tag);
2246 if (tag.EqualsLiteral("dd") || tag.EqualsLiteral("dt"))
2247 return MakeDefinitionItem(tag);
2248 else
2249 return InsertBasicBlock(tag);
2252 NS_IMETHODIMP
2253 nsHTMLEditor::GetParagraphState(PRBool *aMixed, nsAString &outFormat)
2255 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
2256 NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
2257 nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
2258 NS_ENSURE_TRUE(htmlRules, NS_ERROR_FAILURE);
2260 return htmlRules->GetParagraphState(aMixed, outFormat);
2263 NS_IMETHODIMP
2264 nsHTMLEditor::GetBackgroundColorState(PRBool *aMixed, nsAString &aOutColor)
2266 nsresult res;
2267 PRBool useCSS;
2268 GetIsCSSEnabled(&useCSS);
2269 if (useCSS) {
2270 // if we are in CSS mode, we have to check if the containing block defines
2271 // a background color
2272 res = GetCSSBackgroundColorState(aMixed, aOutColor, PR_TRUE);
2274 else {
2275 // in HTML mode, we look only at page's background
2276 res = GetHTMLBackgroundColorState(aMixed, aOutColor);
2278 return res;
2281 NS_IMETHODIMP
2282 nsHTMLEditor::GetHighlightColorState(PRBool *aMixed, nsAString &aOutColor)
2284 nsresult res = NS_OK;
2285 PRBool useCSS;
2286 GetIsCSSEnabled(&useCSS);
2287 *aMixed = PR_FALSE;
2288 aOutColor.AssignLiteral("transparent");
2289 if (useCSS) {
2290 // in CSS mode, text background can be added by the Text Highlight button
2291 // we need to query the background of the selection without looking for
2292 // the block container of the ranges in the selection
2293 res = GetCSSBackgroundColorState(aMixed, aOutColor, PR_FALSE);
2295 return res;
2298 nsresult
2299 nsHTMLEditor::GetCSSBackgroundColorState(PRBool *aMixed, nsAString &aOutColor, PRBool aBlockLevel)
2301 NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
2302 *aMixed = PR_FALSE;
2303 // the default background color is transparent
2304 aOutColor.AssignLiteral("transparent");
2306 // get selection
2307 nsCOMPtr<nsISelection>selection;
2308 nsresult res = GetSelection(getter_AddRefs(selection));
2309 NS_ENSURE_SUCCESS(res, res);
2311 // get selection location
2312 nsCOMPtr<nsIDOMNode> parent;
2313 PRInt32 offset;
2314 res = GetStartNodeAndOffset(selection, getter_AddRefs(parent), &offset);
2315 NS_ENSURE_SUCCESS(res, res);
2316 NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
2318 // is the selection collapsed?
2319 PRBool bCollapsed;
2320 res = selection->GetIsCollapsed(&bCollapsed);
2321 NS_ENSURE_SUCCESS(res, res);
2322 nsCOMPtr<nsIDOMNode> nodeToExamine;
2323 if (bCollapsed || IsTextNode(parent))
2325 // we want to look at the parent and ancestors
2326 nodeToExamine = parent;
2328 else
2330 // otherwise we want to look at the first editable node after
2331 // {parent,offset} and it's ancestors for divs with alignment on them
2332 nodeToExamine = GetChildAt(parent, offset);
2333 //GetNextNode(parent, offset, PR_TRUE, address_of(nodeToExamine));
2336 NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER);
2338 // is the node to examine a block ?
2339 PRBool isBlock;
2340 res = NodeIsBlockStatic(nodeToExamine, &isBlock);
2341 NS_ENSURE_SUCCESS(res, res);
2343 nsCOMPtr<nsIDOMNode> tmp;
2345 if (aBlockLevel) {
2346 // we are querying the block background (and not the text background), let's
2347 // climb to the block container
2348 nsCOMPtr<nsIDOMNode> blockParent = nodeToExamine;
2349 if (!isBlock) {
2350 blockParent = GetBlockNodeParent(nodeToExamine);
2351 NS_ENSURE_TRUE(blockParent, NS_OK);
2354 // Make sure to not walk off onto the Document node
2355 nsCOMPtr<nsIDOMElement> element;
2356 do {
2357 // retrieve the computed style of background-color for blockParent
2358 mHTMLCSSUtils->GetComputedProperty(blockParent,
2359 nsEditProperty::cssBackgroundColor,
2360 aOutColor);
2361 tmp.swap(blockParent);
2362 res = tmp->GetParentNode(getter_AddRefs(blockParent));
2363 element = do_QueryInterface(blockParent);
2364 // look at parent if the queried color is transparent and if the node to
2365 // examine is not the root of the document
2366 } while (aOutColor.EqualsLiteral("transparent") && element);
2367 if (aOutColor.EqualsLiteral("transparent")) {
2368 // we have hit the root of the document and the color is still transparent !
2369 // Grumble... Let's look at the default background color because that's the
2370 // color we are looking for
2371 mHTMLCSSUtils->GetDefaultBackgroundColor(aOutColor);
2374 else {
2375 // no, we are querying the text background for the Text Highlight button
2376 if (IsTextNode(nodeToExamine)) {
2377 // if the node of interest is a text node, let's climb a level
2378 res = nodeToExamine->GetParentNode(getter_AddRefs(parent));
2379 NS_ENSURE_SUCCESS(res, res);
2380 nodeToExamine = parent;
2382 do {
2383 // is the node to examine a block ?
2384 res = NodeIsBlockStatic(nodeToExamine, &isBlock);
2385 NS_ENSURE_SUCCESS(res, res);
2386 if (isBlock) {
2387 // yes it is a block; in that case, the text background color is transparent
2388 aOutColor.AssignLiteral("transparent");
2389 break;
2391 else {
2392 // no, it's not; let's retrieve the computed style of background-color for the
2393 // node to examine
2394 mHTMLCSSUtils->GetComputedProperty(nodeToExamine, nsEditProperty::cssBackgroundColor,
2395 aOutColor);
2396 if (!aOutColor.EqualsLiteral("transparent")) {
2397 break;
2400 tmp.swap(nodeToExamine);
2401 res = tmp->GetParentNode(getter_AddRefs(nodeToExamine));
2402 NS_ENSURE_SUCCESS(res, res);
2403 } while ( aOutColor.EqualsLiteral("transparent") && nodeToExamine );
2405 return NS_OK;
2408 NS_IMETHODIMP
2409 nsHTMLEditor::GetHTMLBackgroundColorState(PRBool *aMixed, nsAString &aOutColor)
2411 //TODO: We don't handle "mixed" correctly!
2412 NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
2413 *aMixed = PR_FALSE;
2414 aOutColor.Truncate();
2416 nsCOMPtr<nsIDOMElement> element;
2417 PRInt32 selectedCount;
2418 nsAutoString tagName;
2419 nsresult res = GetSelectedOrParentTableElement(tagName,
2420 &selectedCount,
2421 getter_AddRefs(element));
2422 NS_ENSURE_SUCCESS(res, res);
2424 NS_NAMED_LITERAL_STRING(styleName, "bgcolor");
2426 while (element)
2428 // We are in a cell or selected table
2429 res = element->GetAttribute(styleName, aOutColor);
2430 NS_ENSURE_SUCCESS(res, res);
2432 // Done if we have a color explicitly set
2433 if (!aOutColor.IsEmpty())
2434 return NS_OK;
2436 // Once we hit the body, we're done
2437 if(nsTextEditUtils::IsBody(element)) return NS_OK;
2439 // No color is set, but we need to report visible color inherited
2440 // from nested cells/tables, so search up parent chain
2441 nsCOMPtr<nsIDOMNode> parentNode;
2442 res = element->GetParentNode(getter_AddRefs(parentNode));
2443 NS_ENSURE_SUCCESS(res, res);
2444 element = do_QueryInterface(parentNode);
2447 // If no table or cell found, get page body
2448 element = GetRoot();
2449 NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER);
2451 return element->GetAttribute(styleName, aOutColor);
2454 NS_IMETHODIMP
2455 nsHTMLEditor::GetListState(PRBool *aMixed, PRBool *aOL, PRBool *aUL, PRBool *aDL)
2457 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
2458 NS_ENSURE_TRUE(aMixed && aOL && aUL && aDL, NS_ERROR_NULL_POINTER);
2459 nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
2460 NS_ENSURE_TRUE(htmlRules, NS_ERROR_FAILURE);
2462 return htmlRules->GetListState(aMixed, aOL, aUL, aDL);
2465 NS_IMETHODIMP
2466 nsHTMLEditor::GetListItemState(PRBool *aMixed, PRBool *aLI, PRBool *aDT, PRBool *aDD)
2468 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
2469 NS_ENSURE_TRUE(aMixed && aLI && aDT && aDD, NS_ERROR_NULL_POINTER);
2471 nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
2472 NS_ENSURE_TRUE(htmlRules, NS_ERROR_FAILURE);
2474 return htmlRules->GetListItemState(aMixed, aLI, aDT, aDD);
2477 NS_IMETHODIMP
2478 nsHTMLEditor::GetAlignment(PRBool *aMixed, nsIHTMLEditor::EAlignment *aAlign)
2480 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
2481 NS_ENSURE_TRUE(aMixed && aAlign, NS_ERROR_NULL_POINTER);
2482 nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
2483 NS_ENSURE_TRUE(htmlRules, NS_ERROR_FAILURE);
2485 return htmlRules->GetAlignment(aMixed, aAlign);
2489 NS_IMETHODIMP
2490 nsHTMLEditor::GetIndentState(PRBool *aCanIndent, PRBool *aCanOutdent)
2492 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
2493 NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_NULL_POINTER);
2495 nsCOMPtr<nsIHTMLEditRules> htmlRules = do_QueryInterface(mRules);
2496 NS_ENSURE_TRUE(htmlRules, NS_ERROR_FAILURE);
2498 return htmlRules->GetIndentState(aCanIndent, aCanOutdent);
2501 NS_IMETHODIMP
2502 nsHTMLEditor::MakeOrChangeList(const nsAString& aListType, PRBool entireList, const nsAString& aBulletType)
2504 nsresult res;
2505 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
2507 // Protect the edit rules object from dying
2508 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
2510 nsCOMPtr<nsISelection> selection;
2511 PRBool cancel, handled;
2513 nsAutoEditBatch beginBatching(this);
2514 nsAutoRules beginRulesSniffing(this, kOpMakeList, nsIEditor::eNext);
2516 // pre-process
2517 res = GetSelection(getter_AddRefs(selection));
2518 NS_ENSURE_SUCCESS(res, res);
2519 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2521 nsTextRulesInfo ruleInfo(nsTextEditRules::kMakeList);
2522 ruleInfo.blockType = &aListType;
2523 ruleInfo.entireList = entireList;
2524 ruleInfo.bulletType = &aBulletType;
2525 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
2526 if (cancel || (NS_FAILED(res))) return res;
2528 if (!handled)
2530 // Find out if the selection is collapsed:
2531 PRBool isCollapsed;
2532 res = selection->GetIsCollapsed(&isCollapsed);
2533 NS_ENSURE_SUCCESS(res, res);
2535 nsCOMPtr<nsIDOMNode> node;
2536 PRInt32 offset;
2538 res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset);
2539 if (!node) res = NS_ERROR_FAILURE;
2540 NS_ENSURE_SUCCESS(res, res);
2542 if (isCollapsed)
2544 // have to find a place to put the list
2545 nsCOMPtr<nsIDOMNode> parent = node;
2546 nsCOMPtr<nsIDOMNode> topChild = node;
2547 nsCOMPtr<nsIDOMNode> tmp;
2549 while ( !CanContainTag(parent, aListType))
2551 parent->GetParentNode(getter_AddRefs(tmp));
2552 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
2553 topChild = parent;
2554 parent = tmp;
2557 if (parent != node)
2559 // we need to split up to the child of parent
2560 res = SplitNodeDeep(topChild, node, offset, &offset);
2561 NS_ENSURE_SUCCESS(res, res);
2564 // make a list
2565 nsCOMPtr<nsIDOMNode> newList;
2566 res = CreateNode(aListType, parent, offset, getter_AddRefs(newList));
2567 NS_ENSURE_SUCCESS(res, res);
2568 // make a list item
2569 nsCOMPtr<nsIDOMNode> newItem;
2570 res = CreateNode(NS_LITERAL_STRING("li"), newList, 0, getter_AddRefs(newItem));
2571 NS_ENSURE_SUCCESS(res, res);
2572 res = selection->Collapse(newItem,0);
2573 NS_ENSURE_SUCCESS(res, res);
2577 res = mRules->DidDoAction(selection, &ruleInfo, res);
2578 return res;
2582 NS_IMETHODIMP
2583 nsHTMLEditor::RemoveList(const nsAString& aListType)
2585 nsresult res;
2586 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
2588 // Protect the edit rules object from dying
2589 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
2591 nsCOMPtr<nsISelection> selection;
2592 PRBool cancel, handled;
2594 nsAutoEditBatch beginBatching(this);
2595 nsAutoRules beginRulesSniffing(this, kOpRemoveList, nsIEditor::eNext);
2597 // pre-process
2598 res = GetSelection(getter_AddRefs(selection));
2599 NS_ENSURE_SUCCESS(res, res);
2600 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2602 nsTextRulesInfo ruleInfo(nsTextEditRules::kRemoveList);
2603 if (aListType.LowerCaseEqualsLiteral("ol"))
2604 ruleInfo.bOrdered = PR_TRUE;
2605 else ruleInfo.bOrdered = PR_FALSE;
2606 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
2607 if (cancel || (NS_FAILED(res))) return res;
2609 // no default behavior for this yet. what would it mean?
2611 res = mRules->DidDoAction(selection, &ruleInfo, res);
2612 return res;
2615 nsresult
2616 nsHTMLEditor::MakeDefinitionItem(const nsAString& aItemType)
2618 nsresult res;
2619 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
2621 // Protect the edit rules object from dying
2622 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
2624 nsCOMPtr<nsISelection> selection;
2625 PRBool cancel, handled;
2627 nsAutoEditBatch beginBatching(this);
2628 nsAutoRules beginRulesSniffing(this, kOpMakeDefListItem, nsIEditor::eNext);
2630 // pre-process
2631 res = GetSelection(getter_AddRefs(selection));
2632 NS_ENSURE_SUCCESS(res, res);
2633 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2634 nsTextRulesInfo ruleInfo(nsTextEditRules::kMakeDefListItem);
2635 ruleInfo.blockType = &aItemType;
2636 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
2637 if (cancel || (NS_FAILED(res))) return res;
2639 if (!handled)
2641 // todo: no default for now. we count on rules to handle it.
2644 res = mRules->DidDoAction(selection, &ruleInfo, res);
2645 return res;
2648 nsresult
2649 nsHTMLEditor::InsertBasicBlock(const nsAString& aBlockType)
2651 nsresult res;
2652 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
2654 // Protect the edit rules object from dying
2655 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
2657 nsCOMPtr<nsISelection> selection;
2658 PRBool cancel, handled;
2660 nsAutoEditBatch beginBatching(this);
2661 nsAutoRules beginRulesSniffing(this, kOpMakeBasicBlock, nsIEditor::eNext);
2663 // pre-process
2664 res = GetSelection(getter_AddRefs(selection));
2665 NS_ENSURE_SUCCESS(res, res);
2666 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2667 nsTextRulesInfo ruleInfo(nsTextEditRules::kMakeBasicBlock);
2668 ruleInfo.blockType = &aBlockType;
2669 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
2670 if (cancel || (NS_FAILED(res))) return res;
2672 if (!handled)
2674 // Find out if the selection is collapsed:
2675 PRBool isCollapsed;
2676 res = selection->GetIsCollapsed(&isCollapsed);
2677 NS_ENSURE_SUCCESS(res, res);
2679 nsCOMPtr<nsIDOMNode> node;
2680 PRInt32 offset;
2682 res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset);
2683 if (!node) res = NS_ERROR_FAILURE;
2684 NS_ENSURE_SUCCESS(res, res);
2686 if (isCollapsed)
2688 // have to find a place to put the block
2689 nsCOMPtr<nsIDOMNode> parent = node;
2690 nsCOMPtr<nsIDOMNode> topChild = node;
2691 nsCOMPtr<nsIDOMNode> tmp;
2693 while ( !CanContainTag(parent, aBlockType))
2695 parent->GetParentNode(getter_AddRefs(tmp));
2696 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
2697 topChild = parent;
2698 parent = tmp;
2701 if (parent != node)
2703 // we need to split up to the child of parent
2704 res = SplitNodeDeep(topChild, node, offset, &offset);
2705 NS_ENSURE_SUCCESS(res, res);
2708 // make a block
2709 nsCOMPtr<nsIDOMNode> newBlock;
2710 res = CreateNode(aBlockType, parent, offset, getter_AddRefs(newBlock));
2711 NS_ENSURE_SUCCESS(res, res);
2713 // reposition selection to inside the block
2714 res = selection->Collapse(newBlock,0);
2715 NS_ENSURE_SUCCESS(res, res);
2719 res = mRules->DidDoAction(selection, &ruleInfo, res);
2720 return res;
2723 NS_IMETHODIMP
2724 nsHTMLEditor::Indent(const nsAString& aIndent)
2726 nsresult res;
2727 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
2729 // Protect the edit rules object from dying
2730 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
2732 PRBool cancel, handled;
2733 PRInt32 theAction = nsTextEditRules::kIndent;
2734 PRInt32 opID = kOpIndent;
2735 if (aIndent.LowerCaseEqualsLiteral("outdent"))
2737 theAction = nsTextEditRules::kOutdent;
2738 opID = kOpOutdent;
2740 nsAutoEditBatch beginBatching(this);
2741 nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext);
2743 // pre-process
2744 nsCOMPtr<nsISelection> selection;
2745 res = GetSelection(getter_AddRefs(selection));
2746 NS_ENSURE_SUCCESS(res, res);
2747 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2749 nsTextRulesInfo ruleInfo(theAction);
2750 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
2751 if (cancel || (NS_FAILED(res))) return res;
2753 if (!handled)
2755 // Do default - insert a blockquote node if selection collapsed
2756 nsCOMPtr<nsIDOMNode> node;
2757 PRInt32 offset;
2758 PRBool isCollapsed;
2759 res = selection->GetIsCollapsed(&isCollapsed);
2760 NS_ENSURE_SUCCESS(res, res);
2762 res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset);
2763 if (!node) res = NS_ERROR_FAILURE;
2764 NS_ENSURE_SUCCESS(res, res);
2766 if (aIndent.EqualsLiteral("indent"))
2768 if (isCollapsed)
2770 // have to find a place to put the blockquote
2771 nsCOMPtr<nsIDOMNode> parent = node;
2772 nsCOMPtr<nsIDOMNode> topChild = node;
2773 nsCOMPtr<nsIDOMNode> tmp;
2774 NS_NAMED_LITERAL_STRING(bq, "blockquote");
2775 while ( !CanContainTag(parent, bq))
2777 parent->GetParentNode(getter_AddRefs(tmp));
2778 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
2779 topChild = parent;
2780 parent = tmp;
2783 if (parent != node)
2785 // we need to split up to the child of parent
2786 res = SplitNodeDeep(topChild, node, offset, &offset);
2787 NS_ENSURE_SUCCESS(res, res);
2790 // make a blockquote
2791 nsCOMPtr<nsIDOMNode> newBQ;
2792 res = CreateNode(bq, parent, offset, getter_AddRefs(newBQ));
2793 NS_ENSURE_SUCCESS(res, res);
2794 // put a space in it so layout will draw the list item
2795 res = selection->Collapse(newBQ,0);
2796 NS_ENSURE_SUCCESS(res, res);
2797 res = InsertText(NS_LITERAL_STRING(" "));
2798 NS_ENSURE_SUCCESS(res, res);
2799 // reposition selection to before the space character
2800 res = GetStartNodeAndOffset(selection, getter_AddRefs(node), &offset);
2801 NS_ENSURE_SUCCESS(res, res);
2802 res = selection->Collapse(node,0);
2803 NS_ENSURE_SUCCESS(res, res);
2807 res = mRules->DidDoAction(selection, &ruleInfo, res);
2808 return res;
2811 //TODO: IMPLEMENT ALIGNMENT!
2813 NS_IMETHODIMP
2814 nsHTMLEditor::Align(const nsAString& aAlignType)
2816 // Protect the edit rules object from dying
2817 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
2819 nsAutoEditBatch beginBatching(this);
2820 nsAutoRules beginRulesSniffing(this, kOpAlign, nsIEditor::eNext);
2822 nsCOMPtr<nsIDOMNode> node;
2823 PRBool cancel, handled;
2825 // Find out if the selection is collapsed:
2826 nsCOMPtr<nsISelection> selection;
2827 nsresult res = GetSelection(getter_AddRefs(selection));
2828 NS_ENSURE_SUCCESS(res, res);
2829 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2830 nsTextRulesInfo ruleInfo(nsTextEditRules::kAlign);
2831 ruleInfo.alignType = &aAlignType;
2832 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
2833 if (cancel || NS_FAILED(res))
2834 return res;
2836 res = mRules->DidDoAction(selection, &ruleInfo, res);
2837 return res;
2840 NS_IMETHODIMP
2841 nsHTMLEditor::GetElementOrParentByTagName(const nsAString& aTagName, nsIDOMNode *aNode, nsIDOMElement** aReturn)
2843 if (aTagName.IsEmpty() || !aReturn )
2844 return NS_ERROR_NULL_POINTER;
2846 nsresult res = NS_OK;
2847 nsCOMPtr<nsIDOMNode> currentNode;
2849 if (aNode)
2850 currentNode = aNode;
2851 else
2853 // If no node supplied, get it from anchor node of current selection
2854 nsCOMPtr<nsISelection>selection;
2855 res = GetSelection(getter_AddRefs(selection));
2856 NS_ENSURE_SUCCESS(res, res);
2857 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2859 nsCOMPtr<nsIDOMNode> anchorNode;
2860 res = selection->GetAnchorNode(getter_AddRefs(anchorNode));
2861 if(NS_FAILED(res)) return res;
2862 NS_ENSURE_TRUE(anchorNode, NS_ERROR_FAILURE);
2864 // Try to get the actual selected node
2865 PRBool hasChildren = PR_FALSE;
2866 anchorNode->HasChildNodes(&hasChildren);
2867 if (hasChildren)
2869 PRInt32 offset;
2870 res = selection->GetAnchorOffset(&offset);
2871 if(NS_FAILED(res)) return res;
2872 currentNode = nsEditor::GetChildAt(anchorNode, offset);
2874 // anchor node is probably a text node - just use that
2875 if (!currentNode)
2876 currentNode = anchorNode;
2879 nsAutoString TagName(aTagName);
2880 ToLowerCase(TagName);
2881 PRBool getLink = IsLinkTag(TagName);
2882 PRBool getNamedAnchor = IsNamedAnchorTag(TagName);
2883 if ( getLink || getNamedAnchor)
2885 TagName.AssignLiteral("a");
2887 PRBool findTableCell = TagName.EqualsLiteral("td");
2888 PRBool findList = TagName.EqualsLiteral("list");
2890 // default is null - no element found
2891 *aReturn = nsnull;
2893 nsCOMPtr<nsIDOMNode> parent;
2894 PRBool bNodeFound = PR_FALSE;
2896 while (PR_TRUE)
2898 nsAutoString currentTagName;
2899 // Test if we have a link (an anchor with href set)
2900 if ( (getLink && nsHTMLEditUtils::IsLink(currentNode)) ||
2901 (getNamedAnchor && nsHTMLEditUtils::IsNamedAnchor(currentNode)) )
2903 bNodeFound = PR_TRUE;
2904 break;
2905 } else {
2906 if (findList)
2908 // Match "ol", "ul", or "dl" for lists
2909 if (nsHTMLEditUtils::IsList(currentNode))
2910 goto NODE_FOUND;
2912 } else if (findTableCell)
2914 // Table cells are another special case:
2915 // Match either "td" or "th" for them
2916 if (nsHTMLEditUtils::IsTableCell(currentNode))
2917 goto NODE_FOUND;
2919 } else {
2920 currentNode->GetNodeName(currentTagName);
2921 if (currentTagName.Equals(TagName, nsCaseInsensitiveStringComparator()))
2923 NODE_FOUND:
2924 bNodeFound = PR_TRUE;
2925 break;
2929 // Search up the parent chain
2930 // We should never fail because of root test below, but lets be safe
2931 // XXX: ERROR_HANDLING error return code lost
2932 if (NS_FAILED(currentNode->GetParentNode(getter_AddRefs(parent))) || !parent)
2933 break;
2935 // Stop searching if parent is a body tag
2936 nsAutoString parentTagName;
2937 parent->GetNodeName(parentTagName);
2938 // Note: Originally used IsRoot to stop at table cells,
2939 // but that's too messy when you are trying to find the parent table
2940 //PRBool isRoot;
2941 //if (NS_FAILED(IsRootTag(parentTagName, isRoot)) || isRoot)
2942 if(parentTagName.LowerCaseEqualsLiteral("body"))
2943 break;
2945 currentNode = parent;
2947 if (bNodeFound)
2949 nsCOMPtr<nsIDOMElement> currentElement = do_QueryInterface(currentNode);
2950 if (currentElement)
2952 *aReturn = currentElement;
2953 // Getters must addref
2954 NS_ADDREF(*aReturn);
2957 else res = NS_EDITOR_ELEMENT_NOT_FOUND;
2959 return res;
2962 NS_IMETHODIMP
2963 nsHTMLEditor::GetSelectedElement(const nsAString& aTagName, nsIDOMElement** aReturn)
2965 NS_ENSURE_TRUE(aReturn , NS_ERROR_NULL_POINTER);
2967 // default is null - no element found
2968 *aReturn = nsnull;
2970 // First look for a single element in selection
2971 nsCOMPtr<nsISelection>selection;
2972 nsresult res = GetSelection(getter_AddRefs(selection));
2973 NS_ENSURE_SUCCESS(res, res);
2974 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2975 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
2977 PRBool bNodeFound = PR_FALSE;
2978 res=NS_ERROR_NOT_INITIALIZED;
2979 PRBool isCollapsed;
2980 selection->GetIsCollapsed(&isCollapsed);
2982 nsAutoString domTagName;
2983 nsAutoString TagName(aTagName);
2984 ToLowerCase(TagName);
2985 // Empty string indicates we should match any element tag
2986 PRBool anyTag = (TagName.IsEmpty());
2987 PRBool isLinkTag = IsLinkTag(TagName);
2988 PRBool isNamedAnchorTag = IsNamedAnchorTag(TagName);
2990 nsCOMPtr<nsIDOMElement> selectedElement;
2991 nsCOMPtr<nsIDOMRange> range;
2992 res = selection->GetRangeAt(0, getter_AddRefs(range));
2993 NS_ENSURE_SUCCESS(res, res);
2995 nsCOMPtr<nsIDOMNode> startParent;
2996 PRInt32 startOffset, endOffset;
2997 res = range->GetStartContainer(getter_AddRefs(startParent));
2998 NS_ENSURE_SUCCESS(res, res);
2999 res = range->GetStartOffset(&startOffset);
3000 NS_ENSURE_SUCCESS(res, res);
3002 nsCOMPtr<nsIDOMNode> endParent;
3003 res = range->GetEndContainer(getter_AddRefs(endParent));
3004 NS_ENSURE_SUCCESS(res, res);
3005 res = range->GetEndOffset(&endOffset);
3006 NS_ENSURE_SUCCESS(res, res);
3008 // Optimization for a single selected element
3009 if (startParent && startParent == endParent && (endOffset-startOffset) == 1)
3011 nsCOMPtr<nsIDOMNode> selectedNode = GetChildAt(startParent, startOffset);
3012 NS_ENSURE_SUCCESS(res, NS_OK);
3013 if (selectedNode)
3015 selectedNode->GetNodeName(domTagName);
3016 ToLowerCase(domTagName);
3018 // Test for appropriate node type requested
3019 if (anyTag || (TagName == domTagName) ||
3020 (isLinkTag && nsHTMLEditUtils::IsLink(selectedNode)) ||
3021 (isNamedAnchorTag && nsHTMLEditUtils::IsNamedAnchor(selectedNode)))
3023 bNodeFound = PR_TRUE;
3024 selectedElement = do_QueryInterface(selectedNode);
3029 if (!bNodeFound)
3031 if (isLinkTag)
3033 // Link tag is a special case - we return the anchor node
3034 // found for any selection that is totally within a link,
3035 // included a collapsed selection (just a caret in a link)
3036 nsCOMPtr<nsIDOMNode> anchorNode;
3037 res = selection->GetAnchorNode(getter_AddRefs(anchorNode));
3038 NS_ENSURE_SUCCESS(res, res);
3039 PRInt32 anchorOffset = -1;
3040 if (anchorNode)
3041 selection->GetAnchorOffset(&anchorOffset);
3043 nsCOMPtr<nsIDOMNode> focusNode;
3044 res = selection->GetFocusNode(getter_AddRefs(focusNode));
3045 NS_ENSURE_SUCCESS(res, res);
3046 PRInt32 focusOffset = -1;
3047 if (focusNode)
3048 selection->GetFocusOffset(&focusOffset);
3050 // Link node must be the same for both ends of selection
3051 if (NS_SUCCEEDED(res) && anchorNode)
3053 #ifdef DEBUG_cmanske
3055 nsAutoString name;
3056 anchorNode->GetNodeName(name);
3057 printf("GetSelectedElement: Anchor node of selection: ");
3058 wprintf(name.get());
3059 printf(" Offset: %d\n", anchorOffset);
3060 focusNode->GetNodeName(name);
3061 printf("Focus node of selection: ");
3062 wprintf(name.get());
3063 printf(" Offset: %d\n", focusOffset);
3065 #endif
3066 nsCOMPtr<nsIDOMElement> parentLinkOfAnchor;
3067 res = GetElementOrParentByTagName(NS_LITERAL_STRING("href"), anchorNode, getter_AddRefs(parentLinkOfAnchor));
3068 // XXX: ERROR_HANDLING can parentLinkOfAnchor be null?
3069 if (NS_SUCCEEDED(res) && parentLinkOfAnchor)
3071 if (isCollapsed)
3073 // We have just a caret in the link
3074 bNodeFound = PR_TRUE;
3075 } else if(focusNode)
3076 { // Link node must be the same for both ends of selection
3077 nsCOMPtr<nsIDOMElement> parentLinkOfFocus;
3078 res = GetElementOrParentByTagName(NS_LITERAL_STRING("href"), focusNode, getter_AddRefs(parentLinkOfFocus));
3079 if (NS_SUCCEEDED(res) && parentLinkOfFocus == parentLinkOfAnchor)
3080 bNodeFound = PR_TRUE;
3083 // We found a link node parent
3084 if (bNodeFound) {
3085 // GetElementOrParentByTagName addref'd this, so we don't need to do it here
3086 *aReturn = parentLinkOfAnchor;
3087 NS_IF_ADDREF(*aReturn);
3088 return NS_OK;
3091 else if (anchorOffset >= 0) // Check if link node is the only thing selected
3093 nsCOMPtr<nsIDOMNode> anchorChild;
3094 anchorChild = GetChildAt(anchorNode,anchorOffset);
3095 if (anchorChild && nsHTMLEditUtils::IsLink(anchorChild) &&
3096 (anchorNode == focusNode) && focusOffset == (anchorOffset+1))
3098 selectedElement = do_QueryInterface(anchorChild);
3099 bNodeFound = PR_TRUE;
3105 if (!isCollapsed) // Don't bother to examine selection if it is collapsed
3107 nsCOMPtr<nsIEnumerator> enumerator;
3108 res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
3109 if (NS_SUCCEEDED(res))
3111 if(!enumerator)
3112 return NS_ERROR_NULL_POINTER;
3114 enumerator->First();
3115 nsCOMPtr<nsISupports> currentItem;
3116 res = enumerator->CurrentItem(getter_AddRefs(currentItem));
3117 if ((NS_SUCCEEDED(res)) && currentItem)
3119 nsCOMPtr<nsIDOMRange> currange( do_QueryInterface(currentItem) );
3120 nsCOMPtr<nsIContentIterator> iter =
3121 do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res);
3122 NS_ENSURE_SUCCESS(res, res);
3124 iter->Init(currange);
3125 // loop through the content iterator for each content node
3126 while (!iter->IsDone())
3128 // Query interface to cast nsIContent to nsIDOMNode
3129 // then get tagType to compare to aTagName
3130 // Clone node of each desired type and append it to the aDomFrag
3131 selectedElement = do_QueryInterface(iter->GetCurrentNode());
3132 if (selectedElement)
3134 // If we already found a node, then we have another element,
3135 // thus there's not just one element selected
3136 if (bNodeFound)
3138 bNodeFound = PR_FALSE;
3139 break;
3142 selectedElement->GetNodeName(domTagName);
3143 ToLowerCase(domTagName);
3145 if (anyTag)
3147 // Get name of first selected element
3148 selectedElement->GetTagName(TagName);
3149 ToLowerCase(TagName);
3150 anyTag = PR_FALSE;
3153 // The "A" tag is a pain,
3154 // used for both link(href is set) and "Named Anchor"
3155 nsCOMPtr<nsIDOMNode> selectedNode = do_QueryInterface(selectedElement);
3156 if ( (isLinkTag && nsHTMLEditUtils::IsLink(selectedNode)) ||
3157 (isNamedAnchorTag && nsHTMLEditUtils::IsNamedAnchor(selectedNode)) )
3159 bNodeFound = PR_TRUE;
3160 } else if (TagName == domTagName) { // All other tag names are handled here
3161 bNodeFound = PR_TRUE;
3163 if (!bNodeFound)
3165 // Check if node we have is really part of the selection???
3166 break;
3169 iter->Next();
3171 } else {
3172 // Should never get here?
3173 isCollapsed = PR_TRUE;
3174 printf("isCollapsed was FALSE, but no elements found in selection\n");
3176 } else {
3177 printf("Could not create enumerator for GetSelectionProperties\n");
3181 if (bNodeFound)
3184 *aReturn = selectedElement;
3185 if (selectedElement)
3187 // Getters must addref
3188 NS_ADDREF(*aReturn);
3191 else res = NS_EDITOR_ELEMENT_NOT_FOUND;
3193 return res;
3196 NS_IMETHODIMP
3197 nsHTMLEditor::CreateElementWithDefaults(const nsAString& aTagName, nsIDOMElement** aReturn)
3199 nsresult res=NS_ERROR_NOT_INITIALIZED;
3200 if (aReturn)
3201 *aReturn = nsnull;
3203 // NS_ENSURE_TRUE(aTagName && aReturn, NS_ERROR_NULL_POINTER);
3204 NS_ENSURE_TRUE(!aTagName.IsEmpty() && aReturn, NS_ERROR_NULL_POINTER);
3206 nsAutoString TagName(aTagName);
3207 ToLowerCase(TagName);
3208 nsAutoString realTagName;
3210 if (IsLinkTag(TagName) || IsNamedAnchorTag(TagName))
3212 realTagName.AssignLiteral("a");
3213 } else {
3214 realTagName = TagName;
3216 //We don't use editor's CreateElement because we don't want to
3217 // go through the transaction system
3219 nsCOMPtr<nsIDOMElement>newElement;
3220 nsCOMPtr<nsIContent> newContent;
3221 nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak);
3222 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
3224 //new call to use instead to get proper HTML element, bug# 39919
3225 res = CreateHTMLContent(realTagName, getter_AddRefs(newContent));
3226 newElement = do_QueryInterface(newContent);
3227 if (NS_FAILED(res) || !newElement)
3228 return NS_ERROR_FAILURE;
3230 // Mark the new element dirty, so it will be formatted
3231 newElement->SetAttribute(NS_LITERAL_STRING("_moz_dirty"), EmptyString());
3233 // Set default values for new elements
3234 if (TagName.EqualsLiteral("hr"))
3236 // Note that we read the user's attributes for these from prefs (in InsertHLine JS)
3237 res = SetAttributeOrEquivalent(newElement, NS_LITERAL_STRING("width"),
3238 NS_LITERAL_STRING("100%"), PR_TRUE);
3239 NS_ENSURE_SUCCESS(res, res);
3240 res = SetAttributeOrEquivalent(newElement, NS_LITERAL_STRING("size"),
3241 NS_LITERAL_STRING("2"), PR_TRUE);
3242 } else if (TagName.EqualsLiteral("table"))
3244 res = newElement->SetAttribute(NS_LITERAL_STRING("cellpadding"),NS_LITERAL_STRING("2"));
3245 NS_ENSURE_SUCCESS(res, res);
3246 res = newElement->SetAttribute(NS_LITERAL_STRING("cellspacing"),NS_LITERAL_STRING("2"));
3247 NS_ENSURE_SUCCESS(res, res);
3248 res = newElement->SetAttribute(NS_LITERAL_STRING("border"),NS_LITERAL_STRING("1"));
3249 } else if (TagName.EqualsLiteral("td"))
3251 res = SetAttributeOrEquivalent(newElement, NS_LITERAL_STRING("valign"),
3252 NS_LITERAL_STRING("top"), PR_TRUE);
3254 // ADD OTHER TAGS HERE
3256 if (NS_SUCCEEDED(res))
3258 *aReturn = newElement;
3259 // Getters must addref
3260 NS_ADDREF(*aReturn);
3263 return res;
3266 NS_IMETHODIMP
3267 nsHTMLEditor::InsertLinkAroundSelection(nsIDOMElement* aAnchorElement)
3269 nsresult res=NS_ERROR_NULL_POINTER;
3270 nsCOMPtr<nsISelection> selection;
3272 NS_ENSURE_TRUE(aAnchorElement, NS_ERROR_NULL_POINTER);
3275 // We must have a real selection
3276 res = GetSelection(getter_AddRefs(selection));
3277 if (!selection)
3279 res = NS_ERROR_NULL_POINTER;
3281 NS_ENSURE_SUCCESS(res, res);
3282 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
3284 PRBool isCollapsed;
3285 res = selection->GetIsCollapsed(&isCollapsed);
3286 if (NS_FAILED(res))
3287 isCollapsed = PR_TRUE;
3289 if (isCollapsed)
3291 printf("InsertLinkAroundSelection called but there is no selection!!!\n");
3292 res = NS_OK;
3293 } else {
3294 // Be sure we were given an anchor element
3295 nsCOMPtr<nsIDOMHTMLAnchorElement> anchor = do_QueryInterface(aAnchorElement);
3296 if (anchor)
3298 nsAutoString href;
3299 res = anchor->GetHref(href);
3300 NS_ENSURE_SUCCESS(res, res);
3301 if (!href.IsEmpty())
3303 nsAutoEditBatch beginBatching(this);
3305 // Set all attributes found on the supplied anchor element
3306 nsCOMPtr<nsIDOMNamedNodeMap> attrMap;
3307 aAnchorElement->GetAttributes(getter_AddRefs(attrMap));
3308 NS_ENSURE_TRUE(attrMap, NS_ERROR_FAILURE);
3310 PRUint32 count, i;
3311 attrMap->GetLength(&count);
3312 nsAutoString name, value;
3314 for (i = 0; i < count; i++)
3316 nsCOMPtr<nsIDOMNode> attrNode;
3317 res = attrMap->Item(i, getter_AddRefs(attrNode));
3318 NS_ENSURE_SUCCESS(res, res);
3320 if (attrNode)
3322 nsCOMPtr<nsIDOMAttr> attribute = do_QueryInterface(attrNode);
3323 if (attribute)
3325 // We must clear the string buffers
3326 // because GetName, GetValue appends to previous string!
3327 name.Truncate();
3328 value.Truncate();
3330 res = attribute->GetName(name);
3331 NS_ENSURE_SUCCESS(res, res);
3333 res = attribute->GetValue(value);
3334 NS_ENSURE_SUCCESS(res, res);
3336 res = SetInlineProperty(nsEditProperty::a, name, value);
3337 NS_ENSURE_SUCCESS(res, res);
3344 return res;
3347 NS_IMETHODIMP
3348 nsHTMLEditor::SetHTMLBackgroundColor(const nsAString& aColor)
3350 NS_PRECONDITION(mDocWeak, "Missing Editor DOM Document");
3352 // Find a selected or enclosing table element to set background on
3353 nsCOMPtr<nsIDOMElement> element;
3354 PRInt32 selectedCount;
3355 nsAutoString tagName;
3356 nsresult res = GetSelectedOrParentTableElement(tagName, &selectedCount,
3357 getter_AddRefs(element));
3358 NS_ENSURE_SUCCESS(res, res);
3360 PRBool setColor = !aColor.IsEmpty();
3362 NS_NAMED_LITERAL_STRING(bgcolor, "bgcolor");
3363 if (element)
3365 if (selectedCount > 0)
3367 // Traverse all selected cells
3368 nsCOMPtr<nsIDOMElement> cell;
3369 res = GetFirstSelectedCell(nsnull, getter_AddRefs(cell));
3370 if (NS_SUCCEEDED(res) && cell)
3372 while(cell)
3374 if (setColor)
3375 res = SetAttribute(cell, bgcolor, aColor);
3376 else
3377 res = RemoveAttribute(cell, bgcolor);
3378 if (NS_FAILED(res)) break;
3380 GetNextSelectedCell(nsnull, getter_AddRefs(cell));
3382 return res;
3385 // If we failed to find a cell, fall through to use originally-found element
3386 } else {
3387 // No table element -- set the background color on the body tag
3388 element = GetRoot();
3389 NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER);
3391 // Use the editor method that goes through the transaction system
3392 if (setColor)
3393 res = SetAttribute(element, bgcolor, aColor);
3394 else
3395 res = RemoveAttribute(element, bgcolor);
3397 return res;
3400 NS_IMETHODIMP nsHTMLEditor::SetBodyAttribute(const nsAString& aAttribute, const nsAString& aValue)
3402 // TODO: Check selection for Cell, Row, Column or table and do color on appropriate level
3404 NS_ASSERTION(mDocWeak, "Missing Editor DOM Document");
3406 // Set the background color attribute on the body tag
3407 nsIDOMElement *bodyElement = GetRoot();
3409 NS_ENSURE_TRUE(bodyElement, NS_ERROR_NULL_POINTER);
3411 // Use the editor method that goes through the transaction system
3412 return SetAttribute(bodyElement, aAttribute, aValue);
3415 NS_IMETHODIMP
3416 nsHTMLEditor::GetLinkedObjects(nsISupportsArray** aNodeList)
3418 NS_ENSURE_TRUE(aNodeList, NS_ERROR_NULL_POINTER);
3420 nsresult res;
3422 res = NS_NewISupportsArray(aNodeList);
3423 NS_ENSURE_SUCCESS(res, res);
3424 NS_ENSURE_TRUE(*aNodeList, NS_ERROR_NULL_POINTER);
3426 nsCOMPtr<nsIContentIterator> iter =
3427 do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res);
3428 NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER);
3429 if ((NS_SUCCEEDED(res)))
3431 nsCOMPtr<nsIDOMDocument> domdoc;
3432 nsEditor::GetDocument(getter_AddRefs(domdoc));
3433 NS_ENSURE_TRUE(domdoc, NS_ERROR_UNEXPECTED);
3435 nsCOMPtr<nsIDocument> doc (do_QueryInterface(domdoc));
3436 NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
3438 iter->Init(doc->GetRootElement());
3440 // loop through the content iterator for each content node
3441 while (!iter->IsDone())
3443 nsCOMPtr<nsIDOMNode> node (do_QueryInterface(iter->GetCurrentNode()));
3444 if (node)
3446 // Let nsURIRefObject make the hard decisions:
3447 nsCOMPtr<nsIURIRefObject> refObject;
3448 res = NS_NewHTMLURIRefObject(getter_AddRefs(refObject), node);
3449 if (NS_SUCCEEDED(res))
3451 nsCOMPtr<nsISupports> isupp (do_QueryInterface(refObject));
3453 (*aNodeList)->AppendElement(isupp);
3456 iter->Next();
3460 return NS_OK;
3463 #ifdef XP_MAC
3464 #pragma mark -
3465 #pragma mark nsIEditorStyleSheets methods
3466 #pragma mark -
3467 #endif
3469 NS_IMETHODIMP
3470 nsHTMLEditor::AddStyleSheet(const nsAString &aURL)
3472 // Enable existing sheet if already loaded.
3473 if (EnableExistingStyleSheet(aURL))
3474 return NS_OK;
3476 // Lose the previously-loaded sheet so there's nothing to replace
3477 // This pattern is different from Override methods because
3478 // we must wait to remove mLastStyleSheetURL and add new sheet
3479 // at the same time (in StyleSheetLoaded callback) so they are undoable together
3480 mLastStyleSheetURL.Truncate();
3481 return ReplaceStyleSheet(aURL);
3484 NS_IMETHODIMP
3485 nsHTMLEditor::ReplaceStyleSheet(const nsAString& aURL)
3487 // Enable existing sheet if already loaded.
3488 if (EnableExistingStyleSheet(aURL))
3490 // Disable last sheet if not the same as new one
3491 if (!mLastStyleSheetURL.IsEmpty() && !mLastStyleSheetURL.Equals(aURL))
3492 return EnableStyleSheet(mLastStyleSheetURL, PR_FALSE);
3494 return NS_OK;
3497 // Make sure the pres shell doesn't disappear during the load.
3498 NS_ENSURE_TRUE(mPresShellWeak, NS_ERROR_NOT_INITIALIZED);
3499 nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
3500 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
3502 nsCOMPtr<nsIURI> uaURI;
3503 nsresult rv = NS_NewURI(getter_AddRefs(uaURI), aURL);
3504 NS_ENSURE_SUCCESS(rv, rv);
3506 return ps->GetDocument()->CSSLoader()->
3507 LoadSheet(uaURI, nsnull, EmptyCString(), this);
3510 NS_IMETHODIMP
3511 nsHTMLEditor::RemoveStyleSheet(const nsAString &aURL)
3513 nsRefPtr<nsCSSStyleSheet> sheet;
3514 nsresult rv = GetStyleSheetForURL(aURL, getter_AddRefs(sheet));
3515 NS_ENSURE_SUCCESS(rv, rv);
3516 NS_ENSURE_TRUE(sheet, NS_ERROR_UNEXPECTED);
3518 nsRefPtr<RemoveStyleSheetTxn> txn;
3519 rv = CreateTxnForRemoveStyleSheet(sheet, getter_AddRefs(txn));
3520 if (!txn) rv = NS_ERROR_NULL_POINTER;
3521 if (NS_SUCCEEDED(rv))
3523 rv = DoTransaction(txn);
3524 if (NS_SUCCEEDED(rv))
3525 mLastStyleSheetURL.Truncate(); // forget it
3527 // Remove it from our internal list
3528 rv = RemoveStyleSheetFromList(aURL);
3531 return rv;
3535 NS_IMETHODIMP
3536 nsHTMLEditor::AddOverrideStyleSheet(const nsAString& aURL)
3538 // Enable existing sheet if already loaded.
3539 if (EnableExistingStyleSheet(aURL))
3540 return NS_OK;
3542 // Make sure the pres shell doesn't disappear during the load.
3543 nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
3544 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
3546 nsCOMPtr<nsIURI> uaURI;
3547 nsresult rv = NS_NewURI(getter_AddRefs(uaURI), aURL);
3548 NS_ENSURE_SUCCESS(rv, rv);
3550 // We MUST ONLY load synchronous local files (no @import)
3551 // XXXbz Except this will actually try to load remote files
3552 // synchronously, of course..
3553 nsRefPtr<nsCSSStyleSheet> sheet;
3554 // Editor override style sheets may want to style Gecko anonymous boxes
3555 rv = ps->GetDocument()->CSSLoader()->
3556 LoadSheetSync(uaURI, PR_TRUE, PR_TRUE, getter_AddRefs(sheet));
3558 // Synchronous loads should ALWAYS return completed
3559 NS_ENSURE_TRUE(sheet, NS_ERROR_NULL_POINTER);
3561 // Add the override style sheet
3562 // (This checks if already exists)
3563 ps->AddOverrideStyleSheet(sheet);
3565 ps->ReconstructStyleData();
3567 // Save as the last-loaded sheet
3568 mLastOverrideStyleSheetURL = aURL;
3570 //Add URL and style sheet to our lists
3571 return AddNewStyleSheetToList(aURL, sheet);
3574 NS_IMETHODIMP
3575 nsHTMLEditor::ReplaceOverrideStyleSheet(const nsAString& aURL)
3577 // Enable existing sheet if already loaded.
3578 if (EnableExistingStyleSheet(aURL))
3580 // Disable last sheet if not the same as new one
3581 if (!mLastOverrideStyleSheetURL.IsEmpty() && !mLastOverrideStyleSheetURL.Equals(aURL))
3582 return EnableStyleSheet(mLastOverrideStyleSheetURL, PR_FALSE);
3584 return NS_OK;
3586 // Remove the previous sheet
3587 if (!mLastOverrideStyleSheetURL.IsEmpty())
3588 RemoveOverrideStyleSheet(mLastOverrideStyleSheetURL);
3590 return AddOverrideStyleSheet(aURL);
3593 // Do NOT use transaction system for override style sheets
3594 NS_IMETHODIMP
3595 nsHTMLEditor::RemoveOverrideStyleSheet(const nsAString &aURL)
3597 nsRefPtr<nsCSSStyleSheet> sheet;
3598 GetStyleSheetForURL(aURL, getter_AddRefs(sheet));
3600 // Make sure we remove the stylesheet from our internal list in all
3601 // cases.
3602 nsresult rv = RemoveStyleSheetFromList(aURL);
3604 NS_ENSURE_TRUE(sheet, NS_OK); /// Don't fail if sheet not found
3606 NS_ENSURE_TRUE(mPresShellWeak, NS_ERROR_NOT_INITIALIZED);
3607 nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
3608 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
3610 ps->RemoveOverrideStyleSheet(sheet);
3611 ps->ReconstructStyleData();
3613 // Remove it from our internal list
3614 return rv;
3617 NS_IMETHODIMP
3618 nsHTMLEditor::EnableStyleSheet(const nsAString &aURL, PRBool aEnable)
3620 nsRefPtr<nsCSSStyleSheet> sheet;
3621 nsresult rv = GetStyleSheetForURL(aURL, getter_AddRefs(sheet));
3622 NS_ENSURE_SUCCESS(rv, rv);
3623 NS_ENSURE_TRUE(sheet, NS_OK); // Don't fail if sheet not found
3625 // Ensure the style sheet is owned by our document.
3626 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
3627 sheet->SetOwningDocument(doc);
3629 return sheet->SetDisabled(!aEnable);
3632 PRBool
3633 nsHTMLEditor::EnableExistingStyleSheet(const nsAString &aURL)
3635 nsRefPtr<nsCSSStyleSheet> sheet;
3636 nsresult rv = GetStyleSheetForURL(aURL, getter_AddRefs(sheet));
3637 NS_ENSURE_SUCCESS(rv, PR_FALSE);
3639 // Enable sheet if already loaded.
3640 if (sheet)
3642 // Ensure the style sheet is owned by our document.
3643 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
3644 sheet->SetOwningDocument(doc);
3646 sheet->SetDisabled(PR_FALSE);
3647 return PR_TRUE;
3649 return PR_FALSE;
3652 nsresult
3653 nsHTMLEditor::AddNewStyleSheetToList(const nsAString &aURL,
3654 nsCSSStyleSheet *aStyleSheet)
3656 PRUint32 countSS = mStyleSheets.Length();
3657 PRUint32 countU = mStyleSheetURLs.Length();
3659 if (countSS != countU)
3660 return NS_ERROR_UNEXPECTED;
3662 if (!mStyleSheetURLs.AppendElement(aURL))
3663 return NS_ERROR_UNEXPECTED;
3665 return mStyleSheets.AppendElement(aStyleSheet) ? NS_OK : NS_ERROR_UNEXPECTED;
3668 nsresult
3669 nsHTMLEditor::RemoveStyleSheetFromList(const nsAString &aURL)
3671 // is it already in the list?
3672 PRUint32 foundIndex;
3673 foundIndex = mStyleSheetURLs.IndexOf(aURL);
3674 if (foundIndex == mStyleSheetURLs.NoIndex)
3675 return NS_ERROR_FAILURE;
3677 // Attempt both removals; if one fails there's not much we can do.
3678 mStyleSheets.RemoveElementAt(foundIndex);
3679 mStyleSheetURLs.RemoveElementAt(foundIndex);
3681 return NS_OK;
3684 NS_IMETHODIMP
3685 nsHTMLEditor::GetStyleSheetForURL(const nsAString &aURL,
3686 nsCSSStyleSheet **aStyleSheet)
3688 NS_ENSURE_ARG_POINTER(aStyleSheet);
3689 *aStyleSheet = 0;
3691 // is it already in the list?
3692 PRUint32 foundIndex;
3693 foundIndex = mStyleSheetURLs.IndexOf(aURL);
3694 if (foundIndex == mStyleSheetURLs.NoIndex)
3695 return NS_OK; //No sheet -- don't fail!
3697 *aStyleSheet = mStyleSheets[foundIndex];
3698 NS_ENSURE_TRUE(*aStyleSheet, NS_ERROR_FAILURE);
3700 NS_ADDREF(*aStyleSheet);
3702 return NS_OK;
3705 NS_IMETHODIMP
3706 nsHTMLEditor::GetURLForStyleSheet(nsCSSStyleSheet *aStyleSheet,
3707 nsAString &aURL)
3709 // is it already in the list?
3710 PRInt32 foundIndex = mStyleSheets.IndexOf(aStyleSheet);
3712 // Don't fail if we don't find it in our list
3713 // Note: mStyleSheets is nsCOMArray, so its IndexOf() method
3714 // returns -1 on failure.
3715 if (foundIndex == -1)
3716 return NS_OK;
3718 // Found it in the list!
3719 aURL = mStyleSheetURLs[foundIndex];
3720 return NS_OK;
3724 * nsIEditorMailSupport methods
3727 NS_IMETHODIMP
3728 nsHTMLEditor::GetEmbeddedObjects(nsISupportsArray** aNodeList)
3730 NS_ENSURE_TRUE(aNodeList, NS_ERROR_NULL_POINTER);
3732 nsresult res;
3734 res = NS_NewISupportsArray(aNodeList);
3735 NS_ENSURE_SUCCESS(res, res);
3736 NS_ENSURE_TRUE(*aNodeList, NS_ERROR_NULL_POINTER);
3738 nsCOMPtr<nsIContentIterator> iter =
3739 do_CreateInstance("@mozilla.org/content/post-content-iterator;1", &res);
3740 NS_ENSURE_TRUE(iter, NS_ERROR_NULL_POINTER);
3741 if ((NS_SUCCEEDED(res)))
3743 nsCOMPtr<nsIDOMDocument> domdoc;
3744 nsEditor::GetDocument(getter_AddRefs(domdoc));
3745 NS_ENSURE_TRUE(domdoc, NS_ERROR_UNEXPECTED);
3747 nsCOMPtr<nsIDocument> doc (do_QueryInterface(domdoc));
3748 NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
3750 iter->Init(doc->GetRootElement());
3752 // loop through the content iterator for each content node
3753 while (!iter->IsDone())
3755 nsCOMPtr<nsIDOMNode> node (do_QueryInterface(iter->GetCurrentNode()));
3756 if (node)
3758 nsAutoString tagName;
3759 node->GetNodeName(tagName);
3760 ToLowerCase(tagName);
3762 // See if it's an image or an embed and also include all links.
3763 // Let mail decide which link to send or not
3764 if (tagName.EqualsLiteral("img") || tagName.EqualsLiteral("embed") ||
3765 tagName.EqualsLiteral("a"))
3766 (*aNodeList)->AppendElement(node);
3767 else if (tagName.EqualsLiteral("body"))
3769 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(node);
3770 if (element)
3772 PRBool hasBackground = PR_FALSE;
3773 if (NS_SUCCEEDED(element->HasAttribute(NS_LITERAL_STRING("background"), &hasBackground)) && hasBackground)
3774 (*aNodeList)->AppendElement(node);
3778 iter->Next();
3782 return res;
3786 #ifdef XP_MAC
3787 #pragma mark -
3788 #pragma mark nsIEditor overrides
3789 #pragma mark -
3790 #endif
3792 NS_IMETHODIMP nsHTMLEditor::DeleteNode(nsIDOMNode * aNode)
3794 // do nothing if the node is read-only
3795 if (!IsModifiableNode(aNode) && !IsMozEditorBogusNode(aNode)) {
3796 return NS_ERROR_FAILURE;
3799 nsCOMPtr<nsIDOMNode> selectAllNode = FindUserSelectAllNode(aNode);
3801 if (selectAllNode)
3803 return nsEditor::DeleteNode(selectAllNode);
3805 return nsEditor::DeleteNode(aNode);
3808 NS_IMETHODIMP nsHTMLEditor::DeleteText(nsIDOMCharacterData *aTextNode,
3809 PRUint32 aOffset,
3810 PRUint32 aLength)
3812 // do nothing if the node is read-only
3813 if (!IsModifiableNode(aTextNode)) {
3814 return NS_ERROR_FAILURE;
3817 nsCOMPtr<nsIDOMNode> selectAllNode = FindUserSelectAllNode(aTextNode);
3819 if (selectAllNode)
3821 return nsEditor::DeleteNode(selectAllNode);
3823 return nsEditor::DeleteText(aTextNode, aOffset, aLength);
3826 NS_IMETHODIMP nsHTMLEditor::InsertTextImpl(const nsAString& aStringToInsert,
3827 nsCOMPtr<nsIDOMNode> *aInOutNode,
3828 PRInt32 *aInOutOffset,
3829 nsIDOMDocument *aDoc)
3831 // do nothing if the node is read-only
3832 if (!IsModifiableNode(*aInOutNode)) {
3833 return NS_ERROR_FAILURE;
3836 return nsEditor::InsertTextImpl(aStringToInsert, aInOutNode, aInOutOffset, aDoc);
3839 #ifdef XP_MAC
3840 #pragma mark -
3841 #pragma mark nsStubMutationObserver overrides
3842 #pragma mark -
3843 #endif
3845 void
3846 nsHTMLEditor::ContentAppended(nsIDocument *aDocument, nsIContent* aContainer,
3847 nsIContent* aFirstNewContent,
3848 PRInt32 /* unused */)
3850 ContentInserted(aDocument, aContainer, aFirstNewContent, 0);
3853 void
3854 nsHTMLEditor::ContentInserted(nsIDocument *aDocument, nsIContent* aContainer,
3855 nsIContent* aChild, PRInt32 /* unused */)
3857 if (!aChild) {
3858 return;
3861 nsCOMPtr<nsIHTMLEditor> kungFuDeathGrip(this);
3863 if (ShouldReplaceRootElement()) {
3864 ResetRootElementAndEventTarget();
3866 // We don't need to handle our own modifications
3867 else if (!mAction && (aContainer ? aContainer->IsEditable() : aDocument->IsEditable())) {
3868 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aChild);
3869 if (node && IsMozEditorBogusNode(node)) {
3870 // Ignore insertion of the bogus node
3871 return;
3873 mRules->DocumentModified();
3877 void
3878 nsHTMLEditor::ContentRemoved(nsIDocument *aDocument, nsIContent* aContainer,
3879 nsIContent* aChild, PRInt32 aIndexInContainer,
3880 nsIContent* aPreviousSibling)
3882 nsCOMPtr<nsIHTMLEditor> kungFuDeathGrip(this);
3884 if (SameCOMIdentity(aChild, mRootElement)) {
3885 ResetRootElementAndEventTarget();
3887 // We don't need to handle our own modifications
3888 else if (!mAction && (aContainer ? aContainer->IsEditable() : aDocument->IsEditable())) {
3889 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aChild);
3890 if (node && IsMozEditorBogusNode(node)) {
3891 // Ignore removal of the bogus node
3892 return;
3894 mRules->DocumentModified();
3898 #ifdef XP_MAC
3899 #pragma mark -
3900 #pragma mark support utils
3901 #pragma mark -
3902 #endif
3904 /* This routine examines aNode and it's ancestors looking for any node which has the
3905 -moz-user-select: all style lit. Return the highest such ancestor. */
3906 nsCOMPtr<nsIDOMNode> nsHTMLEditor::FindUserSelectAllNode(nsIDOMNode *aNode)
3908 nsCOMPtr<nsIDOMNode> resultNode; // starts out empty
3909 nsCOMPtr<nsIDOMNode> node = aNode;
3910 nsIDOMElement *root = GetRoot();
3911 if (!nsEditorUtils::IsDescendantOf(aNode, root))
3912 return nsnull;
3914 // retrieve the computed style of -moz-user-select for aNode
3915 nsAutoString mozUserSelectValue;
3916 while (node)
3918 mHTMLCSSUtils->GetComputedProperty(node, nsEditProperty::cssMozUserSelect, mozUserSelectValue);
3919 if (mozUserSelectValue.EqualsLiteral("all"))
3921 resultNode = node;
3923 if (node != root)
3925 nsCOMPtr<nsIDOMNode> tmp;
3926 node->GetParentNode(getter_AddRefs(tmp));
3927 node = tmp;
3929 else
3931 node = nsnull;
3935 return resultNode;
3938 NS_IMETHODIMP_(PRBool)
3939 nsHTMLEditor::IsModifiableNode(nsIDOMNode *aNode)
3941 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
3943 return !content || !content->IntrinsicState().HasState(NS_EVENT_STATE_MOZ_READONLY);
3946 static nsresult SetSelectionAroundHeadChildren(nsCOMPtr<nsISelection> aSelection, nsWeakPtr aDocWeak)
3948 nsresult res = NS_OK;
3949 // Set selection around <head> node
3950 nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(aDocWeak);
3951 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
3953 nsCOMPtr<nsIDOMNodeList>nodeList;
3954 res = doc->GetElementsByTagName(NS_LITERAL_STRING("head"), getter_AddRefs(nodeList));
3955 NS_ENSURE_SUCCESS(res, res);
3956 NS_ENSURE_TRUE(nodeList, NS_ERROR_NULL_POINTER);
3958 PRUint32 count;
3959 nodeList->GetLength(&count);
3960 if (count < 1) return NS_ERROR_FAILURE;
3962 nsCOMPtr<nsIDOMNode> headNode;
3963 res = nodeList->Item(0, getter_AddRefs(headNode));
3964 NS_ENSURE_SUCCESS(res, res);
3965 NS_ENSURE_TRUE(headNode, NS_ERROR_NULL_POINTER);
3967 // Collapse selection to before first child of the head,
3968 res = aSelection->Collapse(headNode, 0);
3969 NS_ENSURE_SUCCESS(res, res);
3971 // then extend it to just after
3972 nsCOMPtr<nsIDOMNodeList> childNodes;
3973 res = headNode->GetChildNodes(getter_AddRefs(childNodes));
3974 NS_ENSURE_SUCCESS(res, res);
3975 NS_ENSURE_TRUE(childNodes, NS_ERROR_NULL_POINTER);
3976 PRUint32 childCount;
3977 childNodes->GetLength(&childCount);
3979 return aSelection->Extend(headNode, childCount+1);
3982 NS_IMETHODIMP
3983 nsHTMLEditor::GetHeadContentsAsHTML(nsAString& aOutputString)
3985 nsCOMPtr<nsISelection> selection;
3986 nsresult res = GetSelection(getter_AddRefs(selection));
3987 NS_ENSURE_SUCCESS(res, res);
3988 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
3990 // Save current selection
3991 nsAutoSelectionReset selectionResetter(selection, this);
3993 res = SetSelectionAroundHeadChildren(selection, mDocWeak);
3994 NS_ENSURE_SUCCESS(res, res);
3996 res = OutputToString(NS_LITERAL_STRING("text/html"),
3997 nsIDocumentEncoder::OutputSelectionOnly,
3998 aOutputString);
3999 if (NS_SUCCEEDED(res))
4001 // Selection always includes <body></body>,
4002 // so terminate there
4003 nsReadingIterator<PRUnichar> findIter,endFindIter;
4004 aOutputString.BeginReading(findIter);
4005 aOutputString.EndReading(endFindIter);
4006 //counting on our parser to always lower case!!!
4007 if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("<body"),
4008 findIter, endFindIter))
4010 nsReadingIterator<PRUnichar> beginIter;
4011 aOutputString.BeginReading(beginIter);
4012 PRInt32 offset = Distance(beginIter, findIter);//get the distance
4014 nsWritingIterator<PRUnichar> writeIter;
4015 aOutputString.BeginWriting(writeIter);
4016 // Ensure the string ends in a newline
4017 PRUnichar newline ('\n');
4018 findIter.advance(-1);
4019 if (offset ==0 || (offset >0 && (*findIter) != newline)) //check for 0
4021 writeIter.advance(offset);
4022 *writeIter = newline;
4023 aOutputString.Truncate(offset+1);
4027 return res;
4030 NS_IMETHODIMP
4031 nsHTMLEditor::DebugUnitTests(PRInt32 *outNumTests, PRInt32 *outNumTestsFailed)
4033 #ifdef DEBUG
4034 NS_ENSURE_TRUE(outNumTests && outNumTestsFailed, NS_ERROR_NULL_POINTER);
4036 TextEditorTest *tester = new TextEditorTest();
4037 NS_ENSURE_TRUE(tester, NS_ERROR_OUT_OF_MEMORY);
4039 tester->Run(this, outNumTests, outNumTestsFailed);
4040 delete tester;
4041 return NS_OK;
4042 #else
4043 return NS_ERROR_NOT_IMPLEMENTED;
4044 #endif
4047 #ifdef XP_MAC
4048 #pragma mark -
4049 #pragma mark StyleSheet utils
4050 #pragma mark -
4051 #endif
4054 NS_IMETHODIMP
4055 nsHTMLEditor::StyleSheetLoaded(nsCSSStyleSheet* aSheet, PRBool aWasAlternate,
4056 nsresult aStatus)
4058 nsresult rv = NS_OK;
4059 nsAutoEditBatch batchIt(this);
4061 if (!mLastStyleSheetURL.IsEmpty())
4062 RemoveStyleSheet(mLastStyleSheetURL);
4064 nsRefPtr<AddStyleSheetTxn> txn;
4065 rv = CreateTxnForAddStyleSheet(aSheet, getter_AddRefs(txn));
4066 if (!txn) rv = NS_ERROR_NULL_POINTER;
4067 if (NS_SUCCEEDED(rv))
4069 rv = DoTransaction(txn);
4070 if (NS_SUCCEEDED(rv))
4072 // Get the URI, then url spec from the sheet
4073 nsCAutoString spec;
4074 rv = aSheet->GetSheetURI()->GetSpec(spec);
4076 if (NS_SUCCEEDED(rv))
4078 // Save it so we can remove before applying the next one
4079 mLastStyleSheetURL.AssignWithConversion(spec.get());
4081 // Also save in our arrays of urls and sheets
4082 AddNewStyleSheetToList(mLastStyleSheetURL, aSheet);
4087 return NS_OK;
4090 #ifdef XP_MAC
4091 #pragma mark -
4092 #pragma mark nsEditor overrides
4093 #pragma mark -
4094 #endif
4097 /** All editor operations which alter the doc should be prefaced
4098 * with a call to StartOperation, naming the action and direction */
4099 NS_IMETHODIMP
4100 nsHTMLEditor::StartOperation(PRInt32 opID, nsIEditor::EDirection aDirection)
4102 // Protect the edit rules object from dying
4103 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
4105 nsEditor::StartOperation(opID, aDirection); // will set mAction, mDirection
4106 if (mRules) return mRules->BeforeEdit(mAction, mDirection);
4107 return NS_OK;
4111 /** All editor operations which alter the doc should be followed
4112 * with a call to EndOperation */
4113 NS_IMETHODIMP
4114 nsHTMLEditor::EndOperation()
4116 // Protect the edit rules object from dying
4117 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
4119 // post processing
4120 nsresult res = NS_OK;
4121 if (mRules) res = mRules->AfterEdit(mAction, mDirection);
4122 nsEditor::EndOperation(); // will clear mAction, mDirection
4123 return res;
4126 PRBool
4127 nsHTMLEditor::TagCanContainTag(const nsAString& aParentTag, const nsAString& aChildTag)
4129 nsIParserService* parserService = nsContentUtils::GetParserService();
4131 PRInt32 childTagEnum;
4132 // XXX Should this handle #cdata-section too?
4133 if (aChildTag.EqualsLiteral("#text")) {
4134 childTagEnum = eHTMLTag_text;
4136 else {
4137 childTagEnum = parserService->HTMLStringTagToId(aChildTag);
4140 PRInt32 parentTagEnum = parserService->HTMLStringTagToId(aParentTag);
4141 NS_ASSERTION(parentTagEnum < NS_HTML_TAG_MAX,
4142 "Fix the caller, this type of node can never contain children.");
4144 return nsHTMLEditUtils::CanContain(parentTagEnum, childTagEnum);
4147 PRBool
4148 nsHTMLEditor::IsContainer(nsIDOMNode *aNode)
4150 if (!aNode) {
4151 return PR_FALSE;
4154 nsAutoString stringTag;
4156 nsresult rv = aNode->GetNodeName(stringTag);
4157 NS_ENSURE_SUCCESS(rv, PR_FALSE);
4159 PRInt32 tagEnum;
4160 // XXX Should this handle #cdata-section too?
4161 if (stringTag.EqualsLiteral("#text")) {
4162 tagEnum = eHTMLTag_text;
4164 else {
4165 tagEnum = nsContentUtils::GetParserService()->HTMLStringTagToId(stringTag);
4168 return nsHTMLEditUtils::IsContainer(tagEnum);
4172 NS_IMETHODIMP
4173 nsHTMLEditor::SelectEntireDocument(nsISelection *aSelection)
4175 if (!aSelection || !mRules) { return NS_ERROR_NULL_POINTER; }
4177 // Protect the edit rules object from dying
4178 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
4180 // get editor root node
4181 nsIDOMElement *rootElement = GetRoot();
4183 // is doc empty?
4184 PRBool bDocIsEmpty;
4185 nsresult res = mRules->DocumentIsEmpty(&bDocIsEmpty);
4186 NS_ENSURE_SUCCESS(res, res);
4188 if (bDocIsEmpty)
4190 // if its empty dont select entire doc - that would select the bogus node
4191 return aSelection->Collapse(rootElement, 0);
4194 return nsEditor::SelectEntireDocument(aSelection);
4197 NS_IMETHODIMP
4198 nsHTMLEditor::SelectAll()
4200 ForceCompositionEnd();
4202 nsresult rv;
4203 nsCOMPtr<nsISelectionController> selCon = do_QueryReferent(mSelConWeak, &rv);
4204 NS_ENSURE_SUCCESS(rv, rv);
4206 nsCOMPtr<nsISelection> selection;
4207 rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
4208 getter_AddRefs(selection));
4209 NS_ENSURE_SUCCESS(rv, rv);
4211 nsCOMPtr<nsIDOMNode> anchorNode;
4212 rv = selection->GetAnchorNode(getter_AddRefs(anchorNode));
4213 NS_ENSURE_SUCCESS(rv, rv);
4215 nsCOMPtr<nsIContent> anchorContent = do_QueryInterface(anchorNode, &rv);
4216 NS_ENSURE_SUCCESS(rv, rv);
4218 // If the anchor content has independent selection, we never need to explicitly
4219 // select its children.
4220 if (anchorContent->HasIndependentSelection()) {
4221 nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(selection);
4222 NS_ENSURE_TRUE(selPriv, NS_ERROR_UNEXPECTED);
4223 rv = selPriv->SetAncestorLimiter(nsnull);
4224 NS_ENSURE_SUCCESS(rv, rv);
4225 return selection->SelectAllChildren(mRootElement);
4228 nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
4229 nsIContent *rootContent = anchorContent->GetSelectionRootContent(ps);
4230 NS_ENSURE_TRUE(rootContent, NS_ERROR_UNEXPECTED);
4232 nsCOMPtr<nsIDOMNode> rootElement = do_QueryInterface(rootContent, &rv);
4233 NS_ENSURE_SUCCESS(rv, rv);
4235 return selection->SelectAllChildren(rootElement);
4239 #ifdef XP_MAC
4240 #pragma mark -
4241 #pragma mark Random methods
4242 #pragma mark -
4243 #endif
4245 // this will NOT find aAttribute unless aAttribute has a non-null value
4246 // so singleton attributes like <Table border> will not be matched!
4247 void nsHTMLEditor::IsTextPropertySetByContent(nsIDOMNode *aNode,
4248 nsIAtom *aProperty,
4249 const nsAString *aAttribute,
4250 const nsAString *aValue,
4251 PRBool &aIsSet,
4252 nsIDOMNode **aStyleNode,
4253 nsAString *outValue)
4255 nsresult result;
4256 aIsSet = PR_FALSE; // must be initialized to false for code below to work
4257 nsAutoString propName;
4258 aProperty->ToString(propName);
4259 nsCOMPtr<nsIDOMNode>node = aNode;
4261 while (node)
4263 nsCOMPtr<nsIDOMElement>element;
4264 element = do_QueryInterface(node);
4265 if (element)
4267 nsAutoString tag, value;
4268 element->GetTagName(tag);
4269 if (propName.Equals(tag, nsCaseInsensitiveStringComparator()))
4271 PRBool found = PR_FALSE;
4272 if (aAttribute && 0!=aAttribute->Length())
4274 element->GetAttribute(*aAttribute, value);
4275 if (outValue) *outValue = value;
4276 if (!value.IsEmpty())
4278 if (!aValue) {
4279 found = PR_TRUE;
4281 else
4283 nsString tString(*aValue);
4284 if (tString.Equals(value, nsCaseInsensitiveStringComparator())) {
4285 found = PR_TRUE;
4287 else { // we found the prop with the attribute, but the value doesn't match
4288 break;
4293 else {
4294 found = PR_TRUE;
4296 if (found)
4298 aIsSet = PR_TRUE;
4299 break;
4303 nsCOMPtr<nsIDOMNode>temp;
4304 result = node->GetParentNode(getter_AddRefs(temp));
4305 if (NS_SUCCEEDED(result) && temp) {
4306 node = temp;
4308 else {
4309 node = nsnull;
4314 #ifdef XP_MAC
4315 #pragma mark -
4316 #endif
4318 //================================================================
4319 // HTML Editor methods
4321 // Note: Table Editing methods are implemented in nsTableEditor.cpp
4325 PRBool nsHTMLEditor::IsElementInBody(nsIDOMElement* aElement)
4327 return nsTextEditUtils::InBody(aElement, this);
4330 PRBool
4331 nsHTMLEditor::SetCaretInTableCell(nsIDOMElement* aElement)
4333 PRBool caretIsSet = PR_FALSE;
4335 if (aElement && IsElementInBody(aElement))
4337 nsresult res = NS_OK;
4338 nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
4339 if (content)
4341 nsIAtom *atom = content->Tag();
4342 if (atom == nsEditProperty::table ||
4343 atom == nsEditProperty::tbody ||
4344 atom == nsEditProperty::thead ||
4345 atom == nsEditProperty::tfoot ||
4346 atom == nsEditProperty::caption ||
4347 atom == nsEditProperty::tr ||
4348 atom == nsEditProperty::td )
4350 nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
4351 nsCOMPtr<nsIDOMNode> parent;
4352 // This MUST succeed if IsElementInBody was TRUE
4353 node->GetParentNode(getter_AddRefs(parent));
4354 nsCOMPtr<nsIDOMNode>firstChild;
4355 // Find deepest child
4356 PRBool hasChild;
4357 while (NS_SUCCEEDED(node->HasChildNodes(&hasChild)) && hasChild)
4359 if (NS_SUCCEEDED(node->GetFirstChild(getter_AddRefs(firstChild))))
4361 parent = node;
4362 node = firstChild;
4365 // Set selection at beginning of deepest node
4366 nsCOMPtr<nsISelection> selection;
4367 res = GetSelection(getter_AddRefs(selection));
4368 if (NS_SUCCEEDED(res) && selection && firstChild)
4370 res = selection->Collapse(firstChild, 0);
4371 if (NS_SUCCEEDED(res))
4372 caretIsSet = PR_TRUE;
4377 return caretIsSet;
4382 NS_IMETHODIMP
4383 nsHTMLEditor::IsRootTag(nsString &aTag, PRBool &aIsTag)
4385 static char bodyTag[] = "body";
4386 static char tdTag[] = "td";
4387 static char thTag[] = "th";
4388 static char captionTag[] = "caption";
4389 if (aTag.EqualsIgnoreCase(bodyTag) ||
4390 aTag.EqualsIgnoreCase(tdTag) ||
4391 aTag.EqualsIgnoreCase(thTag) ||
4392 aTag.EqualsIgnoreCase(captionTag) )
4394 aIsTag = PR_TRUE;
4396 else {
4397 aIsTag = PR_FALSE;
4399 return NS_OK;
4402 ///////////////////////////////////////////////////////////////////////////
4403 // GetEnclosingTable: find ancestor who is a table, if any
4405 nsCOMPtr<nsIDOMNode>
4406 nsHTMLEditor::GetEnclosingTable(nsIDOMNode *aNode)
4408 NS_PRECONDITION(aNode, "null node passed to nsHTMLEditor::GetEnclosingTable");
4409 nsCOMPtr<nsIDOMNode> tbl, tmp, node = aNode;
4411 while (!tbl)
4413 tmp = GetBlockNodeParent(node);
4414 if (!tmp) break;
4415 if (nsHTMLEditUtils::IsTable(tmp)) tbl = tmp;
4416 node = tmp;
4418 return tbl;
4421 #ifdef XP_MAC
4422 #pragma mark -
4423 #endif
4425 #ifdef PRE_NODE_IN_BODY
4426 nsCOMPtr<nsIDOMElement> nsHTMLEditor::FindPreElement()
4428 nsCOMPtr<nsIDOMDocument> domdoc;
4429 nsEditor::GetDocument(getter_AddRefs(domdoc));
4430 NS_ENSURE_TRUE(domdoc, 0);
4432 nsCOMPtr<nsIDocument> doc (do_QueryInterface(domdoc));
4433 NS_ENSURE_TRUE(doc, 0);
4435 nsCOMPtr<nsIContent> rootContent = doc->GetRootElement();
4436 NS_ENSURE_TRUE(rootContent, 0);
4438 nsCOMPtr<nsIDOMNode> rootNode (do_QueryInterface(rootContent));
4439 NS_ENSURE_TRUE(rootNode, 0);
4441 nsString prestr ("PRE"); // GetFirstNodeOfType requires capitals
4442 nsCOMPtr<nsIDOMNode> preNode;
4443 if (NS_FAILED(nsEditor::GetFirstNodeOfType(rootNode, prestr,
4444 getter_AddRefs(preNode))))
4445 return 0;
4447 return do_QueryInterface(preNode);
4449 #endif /* PRE_NODE_IN_BODY */
4451 /* this method scans the selection for adjacent text nodes
4452 * and collapses them into a single text node.
4453 * "adjacent" means literally adjacent siblings of the same parent.
4454 * Uses nsEditor::JoinNodes so action is undoable.
4455 * Should be called within the context of a batch transaction.
4457 NS_IMETHODIMP
4458 nsHTMLEditor::CollapseAdjacentTextNodes(nsIDOMRange *aInRange)
4460 NS_ENSURE_TRUE(aInRange, NS_ERROR_NULL_POINTER);
4461 nsAutoTxnsConserveSelection dontSpazMySelection(this);
4462 nsTArray<nsIDOMNode*> textNodes;
4463 // we can't actually do anything during iteration, so store the text nodes in an array
4464 // don't bother ref counting them because we know we can hold them for the
4465 // lifetime of this method
4468 // build a list of editable text nodes
4469 nsresult result;
4470 nsCOMPtr<nsIContentIterator> iter =
4471 do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &result);
4472 NS_ENSURE_SUCCESS(result, result);
4474 iter->Init(aInRange);
4476 while (!iter->IsDone())
4478 nsCOMPtr<nsIDOMCharacterData> text = do_QueryInterface(iter->GetCurrentNode());
4479 if (text && IsEditable(text))
4481 textNodes.AppendElement(text);
4484 iter->Next();
4487 // now that I have a list of text nodes, collapse adjacent text nodes
4488 // NOTE: assumption that JoinNodes keeps the righthand node
4489 while (textNodes.Length() > 1)
4491 // we assume a textNodes entry can't be nsnull
4492 nsIDOMNode *leftTextNode = textNodes[0];
4493 nsIDOMNode *rightTextNode = textNodes[1];
4494 NS_ASSERTION(leftTextNode && rightTextNode,"left or rightTextNode null in CollapseAdjacentTextNodes");
4496 // get the prev sibling of the right node, and see if it's leftTextNode
4497 nsCOMPtr<nsIDOMNode> prevSibOfRightNode;
4498 result =
4499 rightTextNode->GetPreviousSibling(getter_AddRefs(prevSibOfRightNode));
4500 NS_ENSURE_SUCCESS(result, result);
4501 if (prevSibOfRightNode && (prevSibOfRightNode == leftTextNode))
4503 nsCOMPtr<nsIDOMNode> parent;
4504 result = rightTextNode->GetParentNode(getter_AddRefs(parent));
4505 NS_ENSURE_SUCCESS(result, result);
4506 NS_ENSURE_TRUE(parent, NS_ERROR_NULL_POINTER);
4507 result = JoinNodes(leftTextNode, rightTextNode, parent);
4508 NS_ENSURE_SUCCESS(result, result);
4511 textNodes.RemoveElementAt(0); // remove the leftmost text node from the list
4514 return result;
4517 NS_IMETHODIMP
4518 nsHTMLEditor::SetSelectionAtDocumentStart(nsISelection *aSelection)
4520 nsIDOMElement *rootElement = GetRoot();
4521 NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER);
4523 return aSelection->Collapse(rootElement,0);
4526 #ifdef XP_MAC
4527 #pragma mark -
4528 #endif
4530 ///////////////////////////////////////////////////////////////////////////
4531 // RemoveBlockContainer: remove inNode, reparenting it's children into their
4532 // the parent of inNode. In addition, INSERT ANY BR's NEEDED
4533 // TO PRESERVE IDENTITY OF REMOVED BLOCK.
4535 nsresult
4536 nsHTMLEditor::RemoveBlockContainer(nsIDOMNode *inNode)
4538 NS_ENSURE_TRUE(inNode, NS_ERROR_NULL_POINTER);
4539 nsresult res;
4540 nsCOMPtr<nsIDOMNode> sibling, child, unused;
4542 // Two possibilities: the container cold be empty of editable content.
4543 // If that is the case, we need to compare what is before and after inNode
4544 // to determine if we need a br.
4545 // Or it could not be empty, in which case we have to compare previous
4546 // sibling and first child to determine if we need a leading br,
4547 // and compare following sibling and last child to determine if we need a
4548 // trailing br.
4550 res = GetFirstEditableChild(inNode, address_of(child));
4551 NS_ENSURE_SUCCESS(res, res);
4553 if (child) // the case of inNode not being empty
4555 // we need a br at start unless:
4556 // 1) previous sibling of inNode is a block, OR
4557 // 2) previous sibling of inNode is a br, OR
4558 // 3) first child of inNode is a block OR
4559 // 4) either is null
4561 res = GetPriorHTMLSibling(inNode, address_of(sibling));
4562 NS_ENSURE_SUCCESS(res, res);
4563 if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling))
4565 res = GetFirstEditableChild(inNode, address_of(child));
4566 NS_ENSURE_SUCCESS(res, res);
4567 if (child && !IsBlockNode(child))
4569 // insert br node
4570 res = CreateBR(inNode, 0, address_of(unused));
4571 NS_ENSURE_SUCCESS(res, res);
4575 // we need a br at end unless:
4576 // 1) following sibling of inNode is a block, OR
4577 // 2) last child of inNode is a block, OR
4578 // 3) last child of inNode is a block OR
4579 // 4) either is null
4581 res = GetNextHTMLSibling(inNode, address_of(sibling));
4582 NS_ENSURE_SUCCESS(res, res);
4583 if (sibling && !IsBlockNode(sibling))
4585 res = GetLastEditableChild(inNode, address_of(child));
4586 NS_ENSURE_SUCCESS(res, res);
4587 if (child && !IsBlockNode(child) && !nsTextEditUtils::IsBreak(child))
4589 // insert br node
4590 PRUint32 len;
4591 res = GetLengthOfDOMNode(inNode, len);
4592 NS_ENSURE_SUCCESS(res, res);
4593 res = CreateBR(inNode, (PRInt32)len, address_of(unused));
4594 NS_ENSURE_SUCCESS(res, res);
4598 else // the case of inNode being empty
4600 // we need a br at start unless:
4601 // 1) previous sibling of inNode is a block, OR
4602 // 2) previous sibling of inNode is a br, OR
4603 // 3) following sibling of inNode is a block, OR
4604 // 4) following sibling of inNode is a br OR
4605 // 5) either is null
4606 res = GetPriorHTMLSibling(inNode, address_of(sibling));
4607 NS_ENSURE_SUCCESS(res, res);
4608 if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling))
4610 res = GetNextHTMLSibling(inNode, address_of(sibling));
4611 NS_ENSURE_SUCCESS(res, res);
4612 if (sibling && !IsBlockNode(sibling) && !nsTextEditUtils::IsBreak(sibling))
4614 // insert br node
4615 res = CreateBR(inNode, 0, address_of(unused));
4616 NS_ENSURE_SUCCESS(res, res);
4621 // now remove container
4622 return RemoveContainer(inNode);
4626 ///////////////////////////////////////////////////////////////////////////
4627 // GetPriorHTMLSibling: returns the previous editable sibling, if there is
4628 // one within the parent
4630 nsresult
4631 nsHTMLEditor::GetPriorHTMLSibling(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode)
4633 NS_ENSURE_TRUE(outNode && inNode, NS_ERROR_NULL_POINTER);
4634 nsresult res = NS_OK;
4635 *outNode = nsnull;
4636 nsCOMPtr<nsIDOMNode> temp, node = do_QueryInterface(inNode);
4638 while (1)
4640 res = node->GetPreviousSibling(getter_AddRefs(temp));
4641 NS_ENSURE_SUCCESS(res, res);
4642 NS_ENSURE_TRUE(temp, NS_OK); // return null sibling
4643 // if it's editable, we're done
4644 if (IsEditable(temp)) break;
4645 // otherwise try again
4646 node = temp;
4648 *outNode = temp;
4649 return res;
4654 ///////////////////////////////////////////////////////////////////////////
4655 // GetPriorHTMLSibling: returns the previous editable sibling, if there is
4656 // one within the parent. just like above routine but
4657 // takes a parent/offset instead of a node.
4659 nsresult
4660 nsHTMLEditor::GetPriorHTMLSibling(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outNode)
4662 NS_ENSURE_TRUE(outNode && inParent, NS_ERROR_NULL_POINTER);
4663 nsresult res = NS_OK;
4664 *outNode = nsnull;
4665 NS_ENSURE_TRUE(inOffset, NS_OK); // return null sibling if at offset zero
4666 nsCOMPtr<nsIDOMNode> node = nsEditor::GetChildAt(inParent,inOffset-1);
4667 if (IsEditable(node))
4669 *outNode = node;
4670 return res;
4672 // else
4673 return GetPriorHTMLSibling(node, outNode);
4678 ///////////////////////////////////////////////////////////////////////////
4679 // GetNextHTMLSibling: returns the next editable sibling, if there is
4680 // one within the parent
4682 nsresult
4683 nsHTMLEditor::GetNextHTMLSibling(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode)
4685 NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER);
4686 nsresult res = NS_OK;
4687 *outNode = nsnull;
4688 nsCOMPtr<nsIDOMNode> temp, node = do_QueryInterface(inNode);
4690 while (1)
4692 res = node->GetNextSibling(getter_AddRefs(temp));
4693 NS_ENSURE_SUCCESS(res, res);
4694 NS_ENSURE_TRUE(temp, NS_OK); // return null sibling
4695 // if it's editable, we're done
4696 if (IsEditable(temp)) break;
4697 // otherwise try again
4698 node = temp;
4700 *outNode = temp;
4701 return res;
4706 ///////////////////////////////////////////////////////////////////////////
4707 // GetNextHTMLSibling: returns the next editable sibling, if there is
4708 // one within the parent. just like above routine but
4709 // takes a parent/offset instead of a node.
4711 nsresult
4712 nsHTMLEditor::GetNextHTMLSibling(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outNode)
4714 NS_ENSURE_TRUE(outNode && inParent, NS_ERROR_NULL_POINTER);
4715 nsresult res = NS_OK;
4716 *outNode = nsnull;
4717 nsCOMPtr<nsIDOMNode> node = nsEditor::GetChildAt(inParent,inOffset);
4718 NS_ENSURE_TRUE(node, NS_OK); // return null sibling if no sibling
4719 if (IsEditable(node))
4721 *outNode = node;
4722 return res;
4724 // else
4725 return GetPriorHTMLSibling(node, outNode);
4730 ///////////////////////////////////////////////////////////////////////////
4731 // GetPriorHTMLNode: returns the previous editable leaf node, if there is
4732 // one within the <body>
4734 nsresult
4735 nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode, PRBool bNoBlockCrossing)
4737 NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER);
4738 nsresult res = GetPriorNode(inNode, PR_TRUE, address_of(*outNode), bNoBlockCrossing);
4739 NS_ENSURE_SUCCESS(res, res);
4741 // if it's not in the body, then zero it out
4742 if (*outNode && !nsTextEditUtils::InBody(*outNode, this))
4744 *outNode = nsnull;
4746 return res;
4750 ///////////////////////////////////////////////////////////////////////////
4751 // GetPriorHTMLNode: same as above but takes {parent,offset} instead of node
4753 nsresult
4754 nsHTMLEditor::GetPriorHTMLNode(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outNode, PRBool bNoBlockCrossing)
4756 NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER);
4757 nsresult res = GetPriorNode(inParent, inOffset, PR_TRUE, address_of(*outNode), bNoBlockCrossing);
4758 NS_ENSURE_SUCCESS(res, res);
4760 // if it's not in the body, then zero it out
4761 if (*outNode && !nsTextEditUtils::InBody(*outNode, this))
4763 *outNode = nsnull;
4765 return res;
4769 ///////////////////////////////////////////////////////////////////////////
4770 // GetNextHTMLNode: returns the next editable leaf node, if there is
4771 // one within the <body>
4773 nsresult
4774 nsHTMLEditor::GetNextHTMLNode(nsIDOMNode *inNode, nsCOMPtr<nsIDOMNode> *outNode, PRBool bNoBlockCrossing)
4776 NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER);
4777 nsresult res = GetNextNode(inNode, PR_TRUE, address_of(*outNode), bNoBlockCrossing);
4778 NS_ENSURE_SUCCESS(res, res);
4780 // if it's not in the body, then zero it out
4781 if (*outNode && !nsTextEditUtils::InBody(*outNode, this))
4783 *outNode = nsnull;
4785 return res;
4789 ///////////////////////////////////////////////////////////////////////////
4790 // GetNHTMLextNode: same as above but takes {parent,offset} instead of node
4792 nsresult
4793 nsHTMLEditor::GetNextHTMLNode(nsIDOMNode *inParent, PRInt32 inOffset, nsCOMPtr<nsIDOMNode> *outNode, PRBool bNoBlockCrossing)
4795 NS_ENSURE_TRUE(outNode, NS_ERROR_NULL_POINTER);
4796 nsresult res = GetNextNode(inParent, inOffset, PR_TRUE, address_of(*outNode), bNoBlockCrossing);
4797 NS_ENSURE_SUCCESS(res, res);
4799 // if it's not in the body, then zero it out
4800 if (*outNode && !nsTextEditUtils::InBody(*outNode, this))
4802 *outNode = nsnull;
4804 return res;
4808 nsresult
4809 nsHTMLEditor::IsFirstEditableChild( nsIDOMNode *aNode, PRBool *aOutIsFirst)
4811 // check parms
4812 NS_ENSURE_TRUE(aOutIsFirst && aNode, NS_ERROR_NULL_POINTER);
4814 // init out parms
4815 *aOutIsFirst = PR_FALSE;
4817 // find first editable child and compare it to aNode
4818 nsCOMPtr<nsIDOMNode> parent, firstChild;
4819 nsresult res = aNode->GetParentNode(getter_AddRefs(parent));
4820 NS_ENSURE_SUCCESS(res, res);
4821 NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE);
4822 res = GetFirstEditableChild(parent, address_of(firstChild));
4823 NS_ENSURE_SUCCESS(res, res);
4825 *aOutIsFirst = (firstChild.get() == aNode);
4826 return res;
4830 nsresult
4831 nsHTMLEditor::IsLastEditableChild( nsIDOMNode *aNode, PRBool *aOutIsLast)
4833 // check parms
4834 NS_ENSURE_TRUE(aOutIsLast && aNode, NS_ERROR_NULL_POINTER);
4836 // init out parms
4837 *aOutIsLast = PR_FALSE;
4839 // find last editable child and compare it to aNode
4840 nsCOMPtr<nsIDOMNode> parent, lastChild;
4841 nsresult res = aNode->GetParentNode(getter_AddRefs(parent));
4842 NS_ENSURE_SUCCESS(res, res);
4843 NS_ENSURE_TRUE(parent, NS_ERROR_FAILURE);
4844 res = GetLastEditableChild(parent, address_of(lastChild));
4845 NS_ENSURE_SUCCESS(res, res);
4847 *aOutIsLast = (lastChild.get() == aNode);
4848 return res;
4852 nsresult
4853 nsHTMLEditor::GetFirstEditableChild( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutFirstChild)
4855 // check parms
4856 NS_ENSURE_TRUE(aOutFirstChild && aNode, NS_ERROR_NULL_POINTER);
4858 // init out parms
4859 *aOutFirstChild = nsnull;
4861 // find first editable child
4862 nsCOMPtr<nsIDOMNode> child;
4863 nsresult res = aNode->GetFirstChild(getter_AddRefs(child));
4864 NS_ENSURE_SUCCESS(res, res);
4866 while (child && !IsEditable(child))
4868 nsCOMPtr<nsIDOMNode> tmp;
4869 res = child->GetNextSibling(getter_AddRefs(tmp));
4870 NS_ENSURE_SUCCESS(res, res);
4871 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
4872 child = tmp;
4875 *aOutFirstChild = child;
4876 return res;
4880 nsresult
4881 nsHTMLEditor::GetLastEditableChild( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutLastChild)
4883 // check parms
4884 NS_ENSURE_TRUE(aOutLastChild && aNode, NS_ERROR_NULL_POINTER);
4886 // init out parms
4887 *aOutLastChild = aNode;
4889 // find last editable child
4890 nsCOMPtr<nsIDOMNode> child;
4891 nsresult res = aNode->GetLastChild(getter_AddRefs(child));
4892 NS_ENSURE_SUCCESS(res, res);
4894 while (child && !IsEditable(child))
4896 nsCOMPtr<nsIDOMNode> tmp;
4897 res = child->GetPreviousSibling(getter_AddRefs(tmp));
4898 NS_ENSURE_SUCCESS(res, res);
4899 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
4900 child = tmp;
4903 *aOutLastChild = child;
4904 return res;
4907 nsresult
4908 nsHTMLEditor::GetFirstEditableLeaf( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutFirstLeaf)
4910 // check parms
4911 NS_ENSURE_TRUE(aOutFirstLeaf && aNode, NS_ERROR_NULL_POINTER);
4913 // init out parms
4914 *aOutFirstLeaf = aNode;
4916 // find leftmost leaf
4917 nsCOMPtr<nsIDOMNode> child;
4918 nsresult res = NS_OK;
4919 child = GetLeftmostChild(aNode);
4920 while (child && (!IsEditable(child) || !nsEditorUtils::IsLeafNode(child)))
4922 nsCOMPtr<nsIDOMNode> tmp;
4923 res = GetNextHTMLNode(child, address_of(tmp));
4924 NS_ENSURE_SUCCESS(res, res);
4925 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
4927 // only accept nodes that are descendants of aNode
4928 if (nsEditorUtils::IsDescendantOf(tmp, aNode))
4929 child = tmp;
4930 else
4932 child = nsnull; // this will abort the loop
4936 *aOutFirstLeaf = child;
4937 return res;
4941 nsresult
4942 nsHTMLEditor::GetLastEditableLeaf( nsIDOMNode *aNode, nsCOMPtr<nsIDOMNode> *aOutLastLeaf)
4944 // check parms
4945 NS_ENSURE_TRUE(aOutLastLeaf && aNode, NS_ERROR_NULL_POINTER);
4947 // init out parms
4948 *aOutLastLeaf = nsnull;
4950 // find rightmost leaf
4951 nsCOMPtr<nsIDOMNode> child;
4952 nsresult res = NS_OK;
4953 child = GetRightmostChild(aNode, PR_FALSE);
4954 while (child && (!IsEditable(child) || !nsEditorUtils::IsLeafNode(child)))
4956 nsCOMPtr<nsIDOMNode> tmp;
4957 res = GetPriorHTMLNode(child, address_of(tmp));
4958 NS_ENSURE_SUCCESS(res, res);
4959 NS_ENSURE_TRUE(tmp, NS_ERROR_FAILURE);
4961 // only accept nodes that are descendants of aNode
4962 if (nsEditorUtils::IsDescendantOf(tmp, aNode))
4963 child = tmp;
4964 else
4966 child = nsnull;
4970 *aOutLastLeaf = child;
4971 return res;
4974 PRBool
4975 nsHTMLEditor::IsTextInDirtyFrameVisible(nsIDOMNode *aNode)
4977 PRBool isEmptyTextNode;
4978 nsresult res = IsVisTextNode(aNode, &isEmptyTextNode, PR_FALSE);
4979 if (NS_FAILED(res))
4981 // We are following the historical decision:
4982 // if we don't know, we say it's visible...
4984 return PR_TRUE;
4987 return !isEmptyTextNode;
4991 ///////////////////////////////////////////////////////////////////////////
4992 // IsVisTextNode: figure out if textnode aTextNode has any visible content.
4994 nsresult
4995 nsHTMLEditor::IsVisTextNode( nsIDOMNode *aNode,
4996 PRBool *outIsEmptyNode,
4997 PRBool aSafeToAskFrames)
4999 NS_ENSURE_TRUE(aNode && outIsEmptyNode, NS_ERROR_NULL_POINTER);
5000 *outIsEmptyNode = PR_TRUE;
5001 nsresult res = NS_OK;
5003 nsCOMPtr<nsIContent> textContent = do_QueryInterface(aNode);
5004 // callers job to only call us with text nodes
5005 if (!textContent || !textContent->IsNodeOfType(nsINode::eTEXT))
5006 return NS_ERROR_NULL_POINTER;
5007 PRUint32 length = textContent->TextLength();
5008 if (aSafeToAskFrames)
5010 nsCOMPtr<nsISelectionController> selCon;
5011 res = GetSelectionController(getter_AddRefs(selCon));
5012 NS_ENSURE_SUCCESS(res, res);
5013 NS_ENSURE_TRUE(selCon, NS_ERROR_FAILURE);
5014 PRBool isVisible = PR_FALSE;
5015 // ask the selection controller for information about whether any
5016 // of the data in the node is really rendered. This is really
5017 // something that frames know about, but we aren't supposed to talk to frames.
5018 // So we put a call in the selection controller interface, since it's already
5019 // in bed with frames anyway. (this is a fix for bug 22227, and a
5020 // partial fix for bug 46209)
5021 res = selCon->CheckVisibility(aNode, 0, length, &isVisible);
5022 NS_ENSURE_SUCCESS(res, res);
5023 if (isVisible)
5025 *outIsEmptyNode = PR_FALSE;
5028 else if (length)
5030 if (textContent->TextIsOnlyWhitespace())
5032 nsWSRunObject wsRunObj(this, aNode, 0);
5033 nsCOMPtr<nsIDOMNode> visNode;
5034 PRInt32 outVisOffset=0;
5035 PRInt16 visType=0;
5036 res = wsRunObj.NextVisibleNode(aNode, 0, address_of(visNode), &outVisOffset, &visType);
5037 NS_ENSURE_SUCCESS(res, res);
5038 if ( (visType == nsWSRunObject::eNormalWS) ||
5039 (visType == nsWSRunObject::eText) )
5041 *outIsEmptyNode = (aNode != visNode);
5044 else
5046 *outIsEmptyNode = PR_FALSE;
5049 return NS_OK;
5053 ///////////////////////////////////////////////////////////////////////////
5054 // IsEmptyNode: figure out if aNode is an empty node.
5055 // A block can have children and still be considered empty,
5056 // if the children are empty or non-editable.
5058 nsresult
5059 nsHTMLEditor::IsEmptyNode( nsIDOMNode *aNode,
5060 PRBool *outIsEmptyNode,
5061 PRBool aSingleBRDoesntCount,
5062 PRBool aListOrCellNotEmpty,
5063 PRBool aSafeToAskFrames)
5065 NS_ENSURE_TRUE(aNode && outIsEmptyNode, NS_ERROR_NULL_POINTER);
5066 *outIsEmptyNode = PR_TRUE;
5067 PRBool seenBR = PR_FALSE;
5068 return IsEmptyNodeImpl(aNode, outIsEmptyNode, aSingleBRDoesntCount,
5069 aListOrCellNotEmpty, aSafeToAskFrames, &seenBR);
5072 ///////////////////////////////////////////////////////////////////////////
5073 // IsEmptyNodeImpl: workhorse for IsEmptyNode.
5075 nsresult
5076 nsHTMLEditor::IsEmptyNodeImpl( nsIDOMNode *aNode,
5077 PRBool *outIsEmptyNode,
5078 PRBool aSingleBRDoesntCount,
5079 PRBool aListOrCellNotEmpty,
5080 PRBool aSafeToAskFrames,
5081 PRBool *aSeenBR)
5083 NS_ENSURE_TRUE(aNode && outIsEmptyNode && aSeenBR, NS_ERROR_NULL_POINTER);
5084 nsresult res = NS_OK;
5086 if (nsEditor::IsTextNode(aNode))
5088 res = IsVisTextNode(aNode, outIsEmptyNode, aSafeToAskFrames);
5089 return res;
5092 // if it's not a text node (handled above) and it's not a container,
5093 // then we don't call it empty (it's an <hr>, or <br>, etc).
5094 // Also, if it's an anchor then don't treat it as empty - even though
5095 // anchors are containers, named anchors are "empty" but we don't
5096 // want to treat them as such. Also, don't call ListItems or table
5097 // cells empty if caller desires. Form Widgets not empty.
5098 if (!IsContainer(aNode) || nsHTMLEditUtils::IsNamedAnchor(aNode) ||
5099 nsHTMLEditUtils::IsFormWidget(aNode) ||
5100 (aListOrCellNotEmpty && nsHTMLEditUtils::IsListItem(aNode)) ||
5101 (aListOrCellNotEmpty && nsHTMLEditUtils::IsTableCell(aNode)) )
5103 *outIsEmptyNode = PR_FALSE;
5104 return NS_OK;
5107 // need this for later
5108 PRBool isListItemOrCell =
5109 nsHTMLEditUtils::IsListItem(aNode) || nsHTMLEditUtils::IsTableCell(aNode);
5111 // loop over children of node. if no children, or all children are either
5112 // empty text nodes or non-editable, then node qualifies as empty
5113 nsCOMPtr<nsIDOMNode> child;
5114 aNode->GetFirstChild(getter_AddRefs(child));
5116 while (child)
5118 nsCOMPtr<nsIDOMNode> node = child;
5119 // is the node editable and non-empty? if so, return false
5120 if (nsEditor::IsEditable(node))
5122 if (nsEditor::IsTextNode(node))
5124 res = IsVisTextNode(node, outIsEmptyNode, aSafeToAskFrames);
5125 NS_ENSURE_SUCCESS(res, res);
5126 NS_ENSURE_TRUE(*outIsEmptyNode, NS_OK); // break out if we find we aren't emtpy
5128 else // an editable, non-text node. we need to check it's content.
5130 // is it the node we are iterating over?
5131 if (node == aNode) break;
5132 else if (aSingleBRDoesntCount && !*aSeenBR && nsTextEditUtils::IsBreak(node))
5134 // the first br in a block doesn't count if the caller so indicated
5135 *aSeenBR = PR_TRUE;
5137 else
5139 // is it an empty node of some sort?
5140 // note: list items or table cells are not considered empty
5141 // if they contain other lists or tables
5142 if (isListItemOrCell)
5144 if (nsHTMLEditUtils::IsList(node) || nsHTMLEditUtils::IsTable(node))
5145 { // break out if we find we aren't empty
5146 *outIsEmptyNode = PR_FALSE;
5147 return NS_OK;
5150 // is it a form widget?
5151 else if (nsHTMLEditUtils::IsFormWidget(aNode))
5152 { // break out if we find we aren't empty
5153 *outIsEmptyNode = PR_FALSE;
5154 return NS_OK;
5157 PRBool isEmptyNode = PR_TRUE;
5158 res = IsEmptyNodeImpl(node, &isEmptyNode, aSingleBRDoesntCount,
5159 aListOrCellNotEmpty, aSafeToAskFrames, aSeenBR);
5160 NS_ENSURE_SUCCESS(res, res);
5161 if (!isEmptyNode)
5163 // otherwise it ain't empty
5164 *outIsEmptyNode = PR_FALSE;
5165 return NS_OK;
5170 node->GetNextSibling(getter_AddRefs(child));
5173 return NS_OK;
5176 // add to aElement the CSS inline styles corresponding to the HTML attribute
5177 // aAttribute with its value aValue
5178 nsresult
5179 nsHTMLEditor::SetAttributeOrEquivalent(nsIDOMElement * aElement,
5180 const nsAString & aAttribute,
5181 const nsAString & aValue,
5182 PRBool aSuppressTransaction)
5184 PRBool useCSS;
5185 nsresult res = NS_OK;
5186 GetIsCSSEnabled(&useCSS);
5187 if (useCSS && mHTMLCSSUtils) {
5188 PRInt32 count;
5189 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(aElement, nsnull, &aAttribute, &aValue, &count,
5190 aSuppressTransaction);
5191 NS_ENSURE_SUCCESS(res, res);
5192 if (count) {
5193 // we found an equivalence ; let's remove the HTML attribute itself if it is set
5194 nsAutoString existingValue;
5195 PRBool wasSet = PR_FALSE;
5196 res = GetAttributeValue(aElement, aAttribute, existingValue, &wasSet);
5197 NS_ENSURE_SUCCESS(res, res);
5198 if (wasSet) {
5199 if (aSuppressTransaction)
5200 res = aElement->RemoveAttribute(aAttribute);
5201 else
5202 res = RemoveAttribute(aElement, aAttribute);
5205 else {
5206 // count is an integer that represents the number of CSS declarations applied to the
5207 // element. If it is zero, we found no equivalence in this implementation for the
5208 // attribute
5209 if (aAttribute.EqualsLiteral("style")) {
5210 // if it is the style attribute, just add the new value to the existing style
5211 // attribute's value
5212 nsAutoString existingValue;
5213 PRBool wasSet = PR_FALSE;
5214 res = GetAttributeValue(aElement, NS_LITERAL_STRING("style"), existingValue, &wasSet);
5215 NS_ENSURE_SUCCESS(res, res);
5216 existingValue.AppendLiteral(" ");
5217 existingValue.Append(aValue);
5218 if (aSuppressTransaction)
5219 res = aElement->SetAttribute(aAttribute, existingValue);
5220 else
5221 res = SetAttribute(aElement, aAttribute, existingValue);
5223 else {
5224 // we have no CSS equivalence for this attribute and it is not the style
5225 // attribute; let's set it the good'n'old HTML way
5226 if (aSuppressTransaction)
5227 res = aElement->SetAttribute(aAttribute, aValue);
5228 else
5229 res = SetAttribute(aElement, aAttribute, aValue);
5233 else {
5234 // we are not in an HTML+CSS editor; let's set the attribute the HTML way
5235 if (aSuppressTransaction)
5236 res = aElement->SetAttribute(aAttribute, aValue);
5237 else
5238 res = SetAttribute(aElement, aAttribute, aValue);
5240 return res;
5243 nsresult
5244 nsHTMLEditor::RemoveAttributeOrEquivalent(nsIDOMElement * aElement,
5245 const nsAString & aAttribute,
5246 PRBool aSuppressTransaction)
5248 PRBool useCSS;
5249 nsresult res = NS_OK;
5250 GetIsCSSEnabled(&useCSS);
5251 if (useCSS && mHTMLCSSUtils) {
5252 res = mHTMLCSSUtils->RemoveCSSEquivalentToHTMLStyle(aElement, nsnull, &aAttribute, nsnull,
5253 aSuppressTransaction);
5254 NS_ENSURE_SUCCESS(res, res);
5257 nsAutoString existingValue;
5258 PRBool wasSet = PR_FALSE;
5259 res = GetAttributeValue(aElement, aAttribute, existingValue, &wasSet);
5260 NS_ENSURE_SUCCESS(res, res);
5261 if (wasSet) {
5262 if (aSuppressTransaction)
5263 res = aElement->RemoveAttribute(aAttribute);
5264 else
5265 res = RemoveAttribute(aElement, aAttribute);
5267 return res;
5270 nsresult
5271 nsHTMLEditor::SetIsCSSEnabled(PRBool aIsCSSPrefChecked)
5273 nsresult err = NS_ERROR_NOT_INITIALIZED;
5274 if (mHTMLCSSUtils)
5276 err = mHTMLCSSUtils->SetCSSEnabled(aIsCSSPrefChecked);
5278 // Disable the eEditorNoCSSMask flag if we're enabling StyleWithCSS.
5279 if (NS_SUCCEEDED(err)) {
5280 PRUint32 flags = mFlags;
5281 if (aIsCSSPrefChecked) {
5282 // Turn off NoCSS as we're enabling CSS
5283 flags &= ~eEditorNoCSSMask;
5284 } else {
5285 // Turn on NoCSS, as we're disabling CSS.
5286 flags |= eEditorNoCSSMask;
5289 err = SetFlags(flags);
5290 NS_ENSURE_SUCCESS(err, err);
5292 return err;
5295 // Set the block background color
5296 NS_IMETHODIMP
5297 nsHTMLEditor::SetCSSBackgroundColor(const nsAString& aColor)
5299 if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
5300 ForceCompositionEnd();
5302 // Protect the edit rules object from dying
5303 nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
5305 nsresult res;
5306 nsCOMPtr<nsISelection>selection;
5307 res = GetSelection(getter_AddRefs(selection));
5308 NS_ENSURE_SUCCESS(res, res);
5309 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
5310 nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
5312 PRBool isCollapsed;
5313 selection->GetIsCollapsed(&isCollapsed);
5315 nsAutoEditBatch batchIt(this);
5316 nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
5317 nsAutoSelectionReset selectionResetter(selection, this);
5318 nsAutoTxnsConserveSelection dontSpazMySelection(this);
5320 PRBool cancel, handled;
5321 nsTextRulesInfo ruleInfo(nsTextEditRules::kSetTextProperty);
5322 res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
5323 NS_ENSURE_SUCCESS(res, res);
5324 if (!cancel && !handled)
5326 // get selection range enumerator
5327 nsCOMPtr<nsIEnumerator> enumerator;
5328 res = selPriv->GetEnumerator(getter_AddRefs(enumerator));
5329 NS_ENSURE_SUCCESS(res, res);
5330 NS_ENSURE_TRUE(enumerator, NS_ERROR_FAILURE);
5332 // loop thru the ranges in the selection
5333 enumerator->First();
5334 nsCOMPtr<nsISupports> currentItem;
5335 nsAutoString bgcolor; bgcolor.AssignLiteral("bgcolor");
5336 nsCOMPtr<nsIDOMNode> cachedBlockParent = nsnull;
5337 while ((NS_ENUMERATOR_FALSE == enumerator->IsDone()))
5339 res = enumerator->CurrentItem(getter_AddRefs(currentItem));
5340 NS_ENSURE_SUCCESS(res, res);
5341 NS_ENSURE_TRUE(currentItem, NS_ERROR_FAILURE);
5343 nsCOMPtr<nsIDOMRange> range( do_QueryInterface(currentItem) );
5345 // check for easy case: both range endpoints in same text node
5346 nsCOMPtr<nsIDOMNode> startNode, endNode;
5347 PRInt32 startOffset, endOffset;
5348 res = range->GetStartContainer(getter_AddRefs(startNode));
5349 NS_ENSURE_SUCCESS(res, res);
5350 res = range->GetEndContainer(getter_AddRefs(endNode));
5351 NS_ENSURE_SUCCESS(res, res);
5352 res = range->GetStartOffset(&startOffset);
5353 NS_ENSURE_SUCCESS(res, res);
5354 res = range->GetEndOffset(&endOffset);
5355 NS_ENSURE_SUCCESS(res, res);
5356 if ((startNode == endNode) && IsTextNode(startNode))
5358 // let's find the block container of the text node
5359 nsCOMPtr<nsIDOMNode> blockParent;
5360 blockParent = GetBlockNodeParent(startNode);
5361 // and apply the background color to that block container
5362 if (cachedBlockParent != blockParent)
5364 cachedBlockParent = blockParent;
5365 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent);
5366 PRInt32 count;
5367 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nsnull, &bgcolor, &aColor, &count, PR_FALSE);
5368 NS_ENSURE_SUCCESS(res, res);
5371 else if ((startNode == endNode) && nsTextEditUtils::IsBody(startNode) && isCollapsed)
5373 // we have no block in the document, let's apply the background to the body
5374 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(startNode);
5375 PRInt32 count;
5376 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nsnull, &bgcolor, &aColor, &count, PR_FALSE);
5377 NS_ENSURE_SUCCESS(res, res);
5379 else if ((startNode == endNode) && (((endOffset-startOffset) == 1) || (!startOffset && !endOffset)))
5381 // a unique node is selected, let's also apply the background color
5382 // to the containing block, possibly the node itself
5383 nsCOMPtr<nsIDOMNode> selectedNode = GetChildAt(startNode, startOffset);
5384 PRBool isBlock =PR_FALSE;
5385 res = NodeIsBlockStatic(selectedNode, &isBlock);
5386 NS_ENSURE_SUCCESS(res, res);
5387 nsCOMPtr<nsIDOMNode> blockParent = selectedNode;
5388 if (!isBlock) {
5389 blockParent = GetBlockNodeParent(selectedNode);
5391 if (cachedBlockParent != blockParent)
5393 cachedBlockParent = blockParent;
5394 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent);
5395 PRInt32 count;
5396 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nsnull, &bgcolor, &aColor, &count, PR_FALSE);
5397 NS_ENSURE_SUCCESS(res, res);
5400 else
5402 // not the easy case. range not contained in single text node.
5403 // there are up to three phases here. There are all the nodes
5404 // reported by the subtree iterator to be processed. And there
5405 // are potentially a starting textnode and an ending textnode
5406 // which are only partially contained by the range.
5408 // lets handle the nodes reported by the iterator. These nodes
5409 // are entirely contained in the selection range. We build up
5410 // a list of them (since doing operations on the document during
5411 // iteration would perturb the iterator).
5413 nsCOMPtr<nsIContentIterator> iter =
5414 do_CreateInstance("@mozilla.org/content/subtree-content-iterator;1", &res);
5415 NS_ENSURE_SUCCESS(res, res);
5416 NS_ENSURE_TRUE(iter, NS_ERROR_FAILURE);
5418 nsCOMArray<nsIDOMNode> arrayOfNodes;
5419 nsCOMPtr<nsIDOMNode> node;
5421 // iterate range and build up array
5422 res = iter->Init(range);
5423 // init returns an error if no nodes in range.
5424 // this can easily happen with the subtree
5425 // iterator if the selection doesn't contain
5426 // any *whole* nodes.
5427 if (NS_SUCCEEDED(res))
5429 while (!iter->IsDone())
5431 node = do_QueryInterface(iter->GetCurrentNode());
5432 NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
5434 if (IsEditable(node))
5436 arrayOfNodes.AppendObject(node);
5439 iter->Next();
5442 // first check the start parent of the range to see if it needs to
5443 // be separately handled (it does if it's a text node, due to how the
5444 // subtree iterator works - it will not have reported it).
5445 if (IsTextNode(startNode) && IsEditable(startNode))
5447 nsCOMPtr<nsIDOMNode> blockParent;
5448 blockParent = GetBlockNodeParent(startNode);
5449 if (cachedBlockParent != blockParent)
5451 cachedBlockParent = blockParent;
5452 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent);
5453 PRInt32 count;
5454 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nsnull, &bgcolor, &aColor, &count, PR_FALSE);
5455 NS_ENSURE_SUCCESS(res, res);
5459 // then loop through the list, set the property on each node
5460 PRInt32 listCount = arrayOfNodes.Count();
5461 PRInt32 j;
5462 for (j = 0; j < listCount; j++)
5464 node = arrayOfNodes[j];
5465 // do we have a block here ?
5466 PRBool isBlock =PR_FALSE;
5467 res = NodeIsBlockStatic(node, &isBlock);
5468 NS_ENSURE_SUCCESS(res, res);
5469 nsCOMPtr<nsIDOMNode> blockParent = node;
5470 if (!isBlock) {
5471 // no we don't, let's find the block ancestor
5472 blockParent = GetBlockNodeParent(node);
5474 if (cachedBlockParent != blockParent)
5476 cachedBlockParent = blockParent;
5477 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent);
5478 PRInt32 count;
5479 // and set the property on it
5480 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nsnull, &bgcolor, &aColor, &count, PR_FALSE);
5481 NS_ENSURE_SUCCESS(res, res);
5484 arrayOfNodes.Clear();
5486 // last check the end parent of the range to see if it needs to
5487 // be separately handled (it does if it's a text node, due to how the
5488 // subtree iterator works - it will not have reported it).
5489 if (IsTextNode(endNode) && IsEditable(endNode))
5491 nsCOMPtr<nsIDOMNode> blockParent;
5492 blockParent = GetBlockNodeParent(endNode);
5493 if (cachedBlockParent != blockParent)
5495 cachedBlockParent = blockParent;
5496 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(blockParent);
5497 PRInt32 count;
5498 res = mHTMLCSSUtils->SetCSSEquivalentToHTMLStyle(element, nsnull, &bgcolor, &aColor, &count, PR_FALSE);
5499 NS_ENSURE_SUCCESS(res, res);
5503 enumerator->Next();
5506 if (!cancel)
5508 // post-process
5509 res = mRules->DidDoAction(selection, &ruleInfo, res);
5511 return res;
5514 NS_IMETHODIMP
5515 nsHTMLEditor::SetBackgroundColor(const nsAString& aColor)
5517 nsresult res;
5518 PRBool useCSS;
5519 GetIsCSSEnabled(&useCSS);
5520 if (useCSS) {
5521 // if we are in CSS mode, we have to apply the background color to the
5522 // containing block (or the body if we have no block-level element in
5523 // the document)
5524 res = SetCSSBackgroundColor(aColor);
5526 else {
5527 // but in HTML mode, we can only set the document's background color
5528 res = SetHTMLBackgroundColor(aColor);
5530 return res;
5533 ///////////////////////////////////////////////////////////////////////////
5534 // NodesSameType: do these nodes have the same tag?
5536 PRBool
5537 nsHTMLEditor::NodesSameType(nsIDOMNode *aNode1, nsIDOMNode *aNode2)
5539 if (!aNode1 || !aNode2)
5541 NS_NOTREACHED("null node passed to nsEditor::NodesSameType()");
5542 return PR_FALSE;
5545 PRBool useCSS;
5546 GetIsCSSEnabled(&useCSS);
5548 nsIAtom *tag1 = GetTag(aNode1);
5550 if (tag1 == GetTag(aNode2)) {
5551 if (useCSS && tag1 == nsEditProperty::span) {
5552 if (mHTMLCSSUtils->ElementsSameStyle(aNode1, aNode2)) {
5553 return PR_TRUE;
5556 else {
5557 return PR_TRUE;
5560 return PR_FALSE;
5563 NS_IMETHODIMP
5564 nsHTMLEditor::CopyLastEditableChildStyles(nsIDOMNode * aPreviousBlock, nsIDOMNode * aNewBlock,
5565 nsIDOMNode **aOutBrNode)
5567 *aOutBrNode = nsnull;
5568 nsCOMPtr<nsIDOMNode> child, tmp;
5569 nsresult res;
5570 // first, clear out aNewBlock. Contract is that we want only the styles from previousBlock.
5571 res = aNewBlock->GetFirstChild(getter_AddRefs(child));
5572 while (NS_SUCCEEDED(res) && child)
5574 res = DeleteNode(child);
5575 NS_ENSURE_SUCCESS(res, res);
5576 res = aNewBlock->GetFirstChild(getter_AddRefs(child));
5578 // now find and clone the styles
5579 child = aPreviousBlock;
5580 tmp = aPreviousBlock;
5581 while (tmp) {
5582 child = tmp;
5583 res = GetLastEditableChild(child, address_of(tmp));
5584 NS_ENSURE_SUCCESS(res, res);
5586 while (child && nsTextEditUtils::IsBreak(child)) {
5587 nsCOMPtr<nsIDOMNode> priorNode;
5588 res = GetPriorHTMLNode(child, address_of(priorNode));
5589 NS_ENSURE_SUCCESS(res, res);
5590 child = priorNode;
5592 nsCOMPtr<nsIDOMNode> newStyles = nsnull, deepestStyle = nsnull;
5593 while (child && (child != aPreviousBlock)) {
5594 if (nsHTMLEditUtils::IsInlineStyle(child) ||
5595 nsEditor::NodeIsType(child, nsEditProperty::span)) {
5596 nsAutoString domTagName;
5597 child->GetNodeName(domTagName);
5598 ToLowerCase(domTagName);
5599 if (newStyles) {
5600 nsCOMPtr<nsIDOMNode> newContainer;
5601 res = InsertContainerAbove(newStyles, address_of(newContainer), domTagName);
5602 NS_ENSURE_SUCCESS(res, res);
5603 newStyles = newContainer;
5605 else {
5606 res = CreateNode(domTagName, aNewBlock, 0, getter_AddRefs(newStyles));
5607 NS_ENSURE_SUCCESS(res, res);
5608 deepestStyle = newStyles;
5610 res = CloneAttributes(newStyles, child);
5611 NS_ENSURE_SUCCESS(res, res);
5613 nsCOMPtr<nsIDOMNode> tmp;
5614 res = child->GetParentNode(getter_AddRefs(tmp));
5615 NS_ENSURE_SUCCESS(res, res);
5616 child = tmp;
5618 if (deepestStyle) {
5619 nsCOMPtr<nsIDOMNode> outBRNode;
5620 res = CreateBR(deepestStyle, 0, address_of(outBRNode));
5621 NS_ENSURE_SUCCESS(res, res);
5622 // Getters must addref
5623 *aOutBrNode = outBRNode;
5624 NS_ADDREF(*aOutBrNode);
5626 return NS_OK;
5629 nsresult
5630 nsHTMLEditor::GetElementOrigin(nsIDOMElement * aElement, PRInt32 & aX, PRInt32 & aY)
5632 aX = 0;
5633 aY = 0;
5635 NS_ENSURE_TRUE(mPresShellWeak, NS_ERROR_NOT_INITIALIZED);
5636 nsCOMPtr<nsIPresShell> ps = do_QueryReferent(mPresShellWeak);
5637 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
5639 nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
5640 nsIFrame *frame = content->GetPrimaryFrame();
5641 NS_ENSURE_TRUE(frame, NS_OK);
5643 nsIFrame *container = ps->GetAbsoluteContainingBlock(frame);
5644 NS_ENSURE_TRUE(container, NS_OK);
5645 nsPoint off = frame->GetOffsetTo(container);
5646 aX = nsPresContext::AppUnitsToIntCSSPixels(off.x);
5647 aY = nsPresContext::AppUnitsToIntCSSPixels(off.y);
5649 return NS_OK;
5652 nsresult
5653 nsHTMLEditor::EndUpdateViewBatch()
5655 nsresult res = nsEditor::EndUpdateViewBatch();
5656 NS_ENSURE_SUCCESS(res, res);
5658 // We may need to show resizing handles or update existing ones after
5659 // all transactions are done. This way of doing is preferred to DOM
5660 // mutation events listeners because all the changes the user can apply
5661 // to a document may result in multiple events, some of them quite hard
5662 // to listen too (in particular when an ancestor of the selection is
5663 // changed but the selection itself is not changed).
5664 if (mUpdateCount == 0) {
5665 nsCOMPtr<nsISelection> selection;
5666 res = GetSelection(getter_AddRefs(selection));
5667 NS_ENSURE_SUCCESS(res, res);
5668 NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
5669 res = CheckSelectionStateForAnonymousButtons(selection);
5671 return res;
5674 NS_IMETHODIMP
5675 nsHTMLEditor::IgnoreSpuriousDragEvent(PRBool aIgnoreSpuriousDragEvent)
5677 mIgnoreSpuriousDragEvent = aIgnoreSpuriousDragEvent;
5678 return NS_OK;
5681 NS_IMETHODIMP
5682 nsHTMLEditor::GetSelectionContainer(nsIDOMElement ** aReturn)
5684 nsCOMPtr<nsISelection>selection;
5685 nsresult res = GetSelection(getter_AddRefs(selection));
5686 // if we don't get the selection, just skip this
5687 if (NS_FAILED(res) || !selection) return res;
5689 PRBool bCollapsed;
5690 res = selection->GetIsCollapsed(&bCollapsed);
5691 NS_ENSURE_SUCCESS(res, res);
5693 nsCOMPtr<nsIDOMNode> focusNode;
5695 if (bCollapsed) {
5696 res = selection->GetFocusNode(getter_AddRefs(focusNode));
5697 NS_ENSURE_SUCCESS(res, res);
5699 else {
5701 PRInt32 rangeCount;
5702 res = selection->GetRangeCount(&rangeCount);
5703 NS_ENSURE_SUCCESS(res, res);
5705 if (rangeCount == 1) {
5707 nsCOMPtr<nsIDOMRange> range;
5708 res = selection->GetRangeAt(0, getter_AddRefs(range));
5709 NS_ENSURE_SUCCESS(res, res);
5710 NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
5712 nsCOMPtr<nsIDOMNode> startContainer, endContainer;
5713 res = range->GetStartContainer(getter_AddRefs(startContainer));
5714 NS_ENSURE_SUCCESS(res, res);
5715 res = range->GetEndContainer(getter_AddRefs(endContainer));
5716 NS_ENSURE_SUCCESS(res, res);
5717 PRInt32 startOffset, endOffset;
5718 res = range->GetStartOffset(&startOffset);
5719 NS_ENSURE_SUCCESS(res, res);
5720 res = range->GetEndOffset(&endOffset);
5721 NS_ENSURE_SUCCESS(res, res);
5723 nsCOMPtr<nsIDOMElement> focusElement;
5724 if (startContainer == endContainer && startOffset + 1 == endOffset) {
5725 res = GetSelectedElement(EmptyString(), getter_AddRefs(focusElement));
5726 NS_ENSURE_SUCCESS(res, res);
5727 if (focusElement)
5728 focusNode = do_QueryInterface(focusElement);
5730 if (!focusNode) {
5731 res = range->GetCommonAncestorContainer(getter_AddRefs(focusNode));
5732 NS_ENSURE_SUCCESS(res, res);
5735 else {
5736 PRInt32 i;
5737 nsCOMPtr<nsIDOMRange> range;
5738 for (i = 0; i < rangeCount; i++)
5740 res = selection->GetRangeAt(i, getter_AddRefs(range));
5741 NS_ENSURE_SUCCESS(res, res);
5742 nsCOMPtr<nsIDOMNode> startContainer;
5743 res = range->GetStartContainer(getter_AddRefs(startContainer));
5744 if (NS_FAILED(res)) continue;
5745 if (!focusNode)
5746 focusNode = startContainer;
5747 else if (focusNode != startContainer) {
5748 res = startContainer->GetParentNode(getter_AddRefs(focusNode));
5749 NS_ENSURE_SUCCESS(res, res);
5750 break;
5756 if (focusNode) {
5757 PRUint16 nodeType;
5758 focusNode->GetNodeType(&nodeType);
5759 if (nsIDOMNode::TEXT_NODE == nodeType) {
5760 nsCOMPtr<nsIDOMNode> parent;
5761 res = focusNode->GetParentNode(getter_AddRefs(parent));
5762 NS_ENSURE_SUCCESS(res, res);
5763 focusNode = parent;
5767 nsCOMPtr<nsIDOMElement> focusElement = do_QueryInterface(focusNode);
5768 *aReturn = focusElement;
5769 NS_IF_ADDREF(*aReturn);
5771 return NS_OK;
5774 NS_IMETHODIMP
5775 nsHTMLEditor::IsAnonymousElement(nsIDOMElement * aElement, PRBool * aReturn)
5777 NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
5778 nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
5779 *aReturn = content->IsRootOfNativeAnonymousSubtree();
5780 return NS_OK;
5783 nsresult
5784 nsHTMLEditor::SetReturnInParagraphCreatesNewParagraph(PRBool aCreatesNewParagraph)
5786 mCRInParagraphCreatesParagraph = aCreatesNewParagraph;
5787 return NS_OK;
5790 nsresult
5791 nsHTMLEditor::GetReturnInParagraphCreatesNewParagraph(PRBool *aCreatesNewParagraph)
5793 *aCreatesNewParagraph = mCRInParagraphCreatesParagraph;
5794 return NS_OK;
5797 already_AddRefed<nsIContent>
5798 nsHTMLEditor::GetFocusedContent()
5800 NS_ENSURE_TRUE(mDocWeak, nsnull);
5802 nsFocusManager* fm = nsFocusManager::GetFocusManager();
5803 NS_ENSURE_TRUE(fm, nsnull);
5805 nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedContent();
5807 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
5808 PRBool inDesignMode = doc->HasFlag(NODE_IS_EDITABLE);
5809 if (!focusedContent) {
5810 // in designMode, nobody gets focus in most cases.
5811 if (inDesignMode && OurWindowHasFocus()) {
5812 nsCOMPtr<nsIContent> docRoot = doc->GetRootElement();
5813 return docRoot.forget();
5815 return nsnull;
5818 if (inDesignMode) {
5819 return OurWindowHasFocus() &&
5820 nsContentUtils::ContentIsDescendantOf(focusedContent, doc) ?
5821 focusedContent.forget() : nsnull;
5824 // We're HTML editor for contenteditable
5826 // If the focused content isn't editable, or it has independent selection,
5827 // we don't have focus.
5828 if (!focusedContent->HasFlag(NODE_IS_EDITABLE) ||
5829 focusedContent->HasIndependentSelection()) {
5830 return nsnull;
5832 // If our window is focused, we're focused.
5833 return OurWindowHasFocus() ? focusedContent.forget() : nsnull;
5836 PRBool
5837 nsHTMLEditor::IsActiveInDOMWindow()
5839 NS_ENSURE_TRUE(mDocWeak, PR_FALSE);
5841 nsFocusManager* fm = nsFocusManager::GetFocusManager();
5842 NS_ENSURE_TRUE(fm, PR_FALSE);
5844 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
5845 PRBool inDesignMode = doc->HasFlag(NODE_IS_EDITABLE);
5847 // If we're in designMode, we're always active in the DOM window.
5848 if (inDesignMode) {
5849 return PR_TRUE;
5852 nsPIDOMWindow* ourWindow = doc->GetWindow();
5853 nsCOMPtr<nsPIDOMWindow> win;
5854 nsIContent* content =
5855 nsFocusManager::GetFocusedDescendant(ourWindow, PR_FALSE,
5856 getter_AddRefs(win));
5857 if (!content) {
5858 return PR_FALSE;
5861 // We're HTML editor for contenteditable
5863 // If the active content isn't editable, or it has independent selection,
5864 // we're not active).
5865 if (!content->HasFlag(NODE_IS_EDITABLE) ||
5866 content->HasIndependentSelection()) {
5867 return PR_FALSE;
5869 return PR_TRUE;
5872 already_AddRefed<nsPIDOMEventTarget>
5873 nsHTMLEditor::GetPIDOMEventTarget()
5875 // Don't use getDocument here, because we have no way of knowing
5876 // whether Init() was ever called. So we need to get the document
5877 // ourselves, if it exists.
5878 NS_PRECONDITION(mDocWeak, "This editor has not been initialized yet");
5879 nsCOMPtr<nsPIDOMEventTarget> piTarget = do_QueryReferent(mDocWeak.get());
5880 return piTarget.forget();
5883 PRBool
5884 nsHTMLEditor::ShouldReplaceRootElement()
5886 if (!mRootElement) {
5887 // If we don't know what is our root element, we should find our root.
5888 return PR_TRUE;
5891 // If we temporary set document root element to mRootElement, but there is
5892 // body element now, we should replace the root element by the body element.
5893 nsCOMPtr<nsIDOMHTMLElement> docBody;
5894 GetBodyElement(getter_AddRefs(docBody));
5895 return !SameCOMIdentity(docBody, mRootElement);
5898 void
5899 nsHTMLEditor::ResetRootElementAndEventTarget()
5901 nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
5903 // Need to remove the event listeners first because BeginningOfDocument
5904 // could set a new root (and event target is set by InstallEventListeners())
5905 // and we won't be able to remove them from the old event target then.
5906 RemoveEventListeners();
5907 mRootElement = nsnull;
5908 nsresult rv = InstallEventListeners();
5909 NS_ENSURE_SUCCESS(rv, );
5911 // We must have mRootElement now.
5912 nsCOMPtr<nsIDOMElement> root;
5913 rv = GetRootElement(getter_AddRefs(root));
5914 NS_ENSURE_SUCCESS(rv, );
5915 NS_ENSURE_TRUE(mRootElement, );
5917 rv = BeginningOfDocument();
5918 NS_ENSURE_SUCCESS(rv, );
5920 // When this editor has focus, we need to reset the selection limiter to
5921 // new root. Otherwise, that is going to be done when this gets focus.
5922 nsCOMPtr<nsINode> node = GetFocusedNode();
5923 nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(node);
5924 if (target) {
5925 InitializeSelection(target);
5928 SyncRealTimeSpell();
5931 nsresult
5932 nsHTMLEditor::GetBodyElement(nsIDOMHTMLElement** aBody)
5934 NS_PRECONDITION(mDocWeak, "bad state, null mDocWeak");
5935 nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryReferent(mDocWeak);
5936 if (!htmlDoc) {
5937 return NS_ERROR_NOT_INITIALIZED;
5939 nsCOMPtr<nsIDOMHTMLElement> bodyElement;
5940 return htmlDoc->GetBody(aBody);
5943 already_AddRefed<nsINode>
5944 nsHTMLEditor::GetFocusedNode()
5946 nsCOMPtr<nsIContent> focusedContent = GetFocusedContent();
5947 if (!focusedContent) {
5948 return nsnull;
5951 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
5952 NS_ASSERTION(fm, "Focus manager is null");
5953 nsCOMPtr<nsIDOMElement> focusedElement;
5954 fm->GetFocusedElement(getter_AddRefs(focusedElement));
5955 if (focusedElement) {
5956 nsCOMPtr<nsINode> node = do_QueryInterface(focusedElement);
5957 return node.forget();
5960 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
5961 nsCOMPtr<nsINode> node = do_QueryInterface(doc);
5962 return node.forget();
5965 PRBool
5966 nsHTMLEditor::OurWindowHasFocus()
5968 NS_ENSURE_TRUE(mDocWeak, PR_FALSE);
5969 nsIFocusManager* fm = nsFocusManager::GetFocusManager();
5970 NS_ENSURE_TRUE(fm, PR_FALSE);
5971 nsCOMPtr<nsIDOMWindow> focusedWindow;
5972 fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
5973 if (!focusedWindow) {
5974 return PR_FALSE;
5976 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
5977 nsCOMPtr<nsIDOMWindow> ourWindow = do_QueryInterface(doc->GetWindow());
5978 return ourWindow == focusedWindow;
5981 PRBool
5982 nsHTMLEditor::IsAcceptableInputEvent(nsIDOMEvent* aEvent)
5984 if (!nsEditor::IsAcceptableInputEvent(aEvent)) {
5985 return PR_FALSE;
5988 NS_ENSURE_TRUE(mDocWeak, PR_FALSE);
5990 nsCOMPtr<nsIDOMEventTarget> target;
5991 aEvent->GetTarget(getter_AddRefs(target));
5992 NS_ENSURE_TRUE(target, PR_FALSE);
5994 nsCOMPtr<nsIDocument> document = do_QueryReferent(mDocWeak);
5995 if (document->HasFlag(NODE_IS_EDITABLE)) {
5996 // If this editor is in designMode and the event target is the document,
5997 // the event is for this editor.
5998 nsCOMPtr<nsIDocument> targetDocument = do_QueryInterface(target);
5999 if (targetDocument) {
6000 return targetDocument == document;
6002 // Otherwise, check whether the event target is in this document or not.
6003 nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
6004 NS_ENSURE_TRUE(targetContent, PR_FALSE);
6005 return document == targetContent->GetCurrentDoc();
6008 // If this is for contenteditable, we should check whether the target is
6009 // editable or not.
6010 nsCOMPtr<nsIContent> targetContent = do_QueryInterface(target);
6011 NS_ENSURE_TRUE(targetContent, PR_FALSE);
6012 if (!targetContent->HasFlag(NODE_IS_EDITABLE) ||
6013 targetContent->HasIndependentSelection()) {
6014 return PR_FALSE;
6017 // Finally, check whether we're actually focused or not. When we're not
6018 // focused, we should ignore the dispatched event by script (or something)
6019 // because content editable element needs selection in itself for editing.
6020 // However, when we're not focused, it's not guaranteed.
6021 return IsActiveInDOMWindow();
6024 NS_IMETHODIMP
6025 nsHTMLEditor::GetPreferredIMEState(PRUint32 *aState)
6027 if (IsReadonly() || IsDisabled()) {
6028 *aState = nsIContent::IME_STATUS_DISABLE;
6029 return NS_OK;
6032 // HTML editor don't prefer the CSS ime-mode because IE didn't do so too.
6033 *aState = nsIContent::IME_STATUS_ENABLE;
6034 return NS_OK;