Bumping manifests a=b2g-bump
[gecko.git] / editor / libeditor / nsEditor.cpp
blob25926466d5641a97b25a4d85112e8e6905918207
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "mozilla/DebugOnly.h" // for DebugOnly
8 #include <stdio.h> // for nullptr, stdout
9 #include <string.h> // for strcmp
11 #include "ChangeAttributeTxn.h" // for ChangeAttributeTxn
12 #include "CreateElementTxn.h" // for CreateElementTxn
13 #include "DeleteNodeTxn.h" // for DeleteNodeTxn
14 #include "DeleteRangeTxn.h" // for DeleteRangeTxn
15 #include "DeleteTextTxn.h" // for DeleteTextTxn
16 #include "EditAggregateTxn.h" // for EditAggregateTxn
17 #include "EditTxn.h" // for EditTxn
18 #include "IMETextTxn.h" // for IMETextTxn
19 #include "InsertNodeTxn.h" // for InsertNodeTxn
20 #include "InsertTextTxn.h" // for InsertTextTxn
21 #include "JoinElementTxn.h" // for JoinElementTxn
22 #include "PlaceholderTxn.h" // for PlaceholderTxn
23 #include "SplitElementTxn.h" // for SplitElementTxn
24 #include "mozFlushType.h" // for mozFlushType::Flush_Frames
25 #include "mozISpellCheckingEngine.h"
26 #include "mozInlineSpellChecker.h" // for mozInlineSpellChecker
27 #include "mozilla/IMEStateManager.h" // for IMEStateManager
28 #include "mozilla/Preferences.h" // for Preferences
29 #include "mozilla/dom/Selection.h" // for Selection, etc
30 #include "mozilla/Services.h" // for GetObserverService
31 #include "mozilla/TextComposition.h" // for TextComposition
32 #include "mozilla/TextEvents.h"
33 #include "mozilla/dom/Element.h" // for Element, nsINode::AsElement
34 #include "mozilla/dom/Text.h"
35 #include "mozilla/mozalloc.h" // for operator new, etc
36 #include "nsAString.h" // for nsAString_internal::Length, etc
37 #include "nsCCUncollectableMarker.h" // for nsCCUncollectableMarker
38 #include "nsCaret.h" // for nsCaret
39 #include "nsCaseTreatment.h"
40 #include "nsCharTraits.h" // for NS_IS_HIGH_SURROGATE, etc
41 #include "nsComponentManagerUtils.h" // for do_CreateInstance
42 #include "nsComputedDOMStyle.h" // for nsComputedDOMStyle
43 #include "nsContentUtils.h" // for nsContentUtils
44 #include "nsDOMString.h" // for DOMStringIsNull
45 #include "nsDebug.h" // for NS_ENSURE_TRUE, etc
46 #include "nsEditor.h"
47 #include "nsEditorEventListener.h" // for nsEditorEventListener
48 #include "nsEditorUtils.h" // for nsAutoRules, etc
49 #include "nsError.h" // for NS_OK, etc
50 #include "nsFocusManager.h" // for nsFocusManager
51 #include "nsFrameSelection.h" // for nsFrameSelection
52 #include "nsGkAtoms.h" // for nsGkAtoms, nsGkAtoms::dir
53 #include "nsIAbsorbingTransaction.h" // for nsIAbsorbingTransaction
54 #include "nsIAtom.h" // for nsIAtom
55 #include "nsIContent.h" // for nsIContent
56 #include "nsIDOMAttr.h" // for nsIDOMAttr
57 #include "nsIDOMCharacterData.h" // for nsIDOMCharacterData
58 #include "nsIDOMDocument.h" // for nsIDOMDocument
59 #include "nsIDOMElement.h" // for nsIDOMElement
60 #include "nsIDOMEvent.h" // for nsIDOMEvent
61 #include "nsIDOMEventListener.h" // for nsIDOMEventListener
62 #include "nsIDOMEventTarget.h" // for nsIDOMEventTarget
63 #include "nsIDOMHTMLElement.h" // for nsIDOMHTMLElement
64 #include "nsIDOMKeyEvent.h" // for nsIDOMKeyEvent, etc
65 #include "nsIDOMMozNamedAttrMap.h" // for nsIDOMMozNamedAttrMap
66 #include "nsIDOMMouseEvent.h" // for nsIDOMMouseEvent
67 #include "nsIDOMNode.h" // for nsIDOMNode, etc
68 #include "nsIDOMNodeList.h" // for nsIDOMNodeList
69 #include "nsIDOMRange.h" // for nsIDOMRange
70 #include "nsIDOMText.h" // for nsIDOMText
71 #include "nsIDocument.h" // for nsIDocument
72 #include "nsIDocumentStateListener.h" // for nsIDocumentStateListener
73 #include "nsIEditActionListener.h" // for nsIEditActionListener
74 #include "nsIEditorObserver.h" // for nsIEditorObserver
75 #include "nsIEditorSpellCheck.h" // for nsIEditorSpellCheck
76 #include "nsIFrame.h" // for nsIFrame
77 #include "nsIHTMLDocument.h" // for nsIHTMLDocument
78 #include "nsIInlineSpellChecker.h" // for nsIInlineSpellChecker, etc
79 #include "nsNameSpaceManager.h" // for kNameSpaceID_None, etc
80 #include "nsINode.h" // for nsINode, etc
81 #include "nsIObserverService.h" // for nsIObserverService
82 #include "nsIPlaintextEditor.h" // for nsIPlaintextEditor, etc
83 #include "nsIPresShell.h" // for nsIPresShell
84 #include "nsISelection.h" // for nsISelection, etc
85 #include "nsISelectionController.h" // for nsISelectionController, etc
86 #include "nsISelectionDisplay.h" // for nsISelectionDisplay, etc
87 #include "nsISelectionPrivate.h" // for nsISelectionPrivate, etc
88 #include "nsISupportsBase.h" // for nsISupports
89 #include "nsISupportsUtils.h" // for NS_ADDREF, NS_IF_ADDREF
90 #include "nsITransaction.h" // for nsITransaction
91 #include "nsITransactionManager.h"
92 #include "nsIWeakReference.h" // for nsISupportsWeakReference
93 #include "nsIWidget.h" // for nsIWidget, IMEState, etc
94 #include "nsPIDOMWindow.h" // for nsPIDOMWindow
95 #include "nsPresContext.h" // for nsPresContext
96 #include "nsRange.h" // for nsRange
97 #include "nsReadableUtils.h" // for EmptyString, ToNewCString
98 #include "nsString.h" // for nsAutoString, nsString, etc
99 #include "nsStringFwd.h" // for nsAFlatString
100 #include "nsStyleConsts.h" // for NS_STYLE_DIRECTION_RTL, etc
101 #include "nsStyleContext.h" // for nsStyleContext
102 #include "nsStyleSheetTxns.h" // for AddStyleSheetTxn, etc
103 #include "nsStyleStruct.h" // for nsStyleDisplay, nsStyleText, etc
104 #include "nsStyleStructFwd.h" // for nsIFrame::StyleUIReset, etc
105 #include "nsTextEditUtils.h" // for nsTextEditUtils
106 #include "nsTextNode.h" // for nsTextNode
107 #include "nsThreadUtils.h" // for nsRunnable
108 #include "nsTransactionManager.h" // for nsTransactionManager
109 #include "prtime.h" // for PR_Now
111 class nsIOutputStream;
112 class nsIParserService;
113 class nsITransferable;
115 #ifdef DEBUG
116 #include "nsIDOMHTMLDocument.h" // for nsIDOMHTMLDocument
117 #endif
119 using namespace mozilla;
120 using namespace mozilla::dom;
121 using namespace mozilla::widget;
123 // Defined in nsEditorRegistration.cpp
124 extern nsIParserService *sParserService;
126 //---------------------------------------------------------------------------
128 // nsEditor: base editor class implementation
130 //---------------------------------------------------------------------------
132 nsEditor::nsEditor()
133 : mPlaceHolderName(nullptr)
134 , mSelState(nullptr)
135 , mPhonetic(nullptr)
136 , mModCount(0)
137 , mFlags(0)
138 , mUpdateCount(0)
139 , mPlaceHolderBatch(0)
140 , mAction(EditAction::none)
141 , mIMETextOffset(0)
142 , mDirection(eNone)
143 , mDocDirtyState(-1)
144 , mSpellcheckCheckboxState(eTriUnset)
145 , mShouldTxnSetSelection(true)
146 , mDidPreDestroy(false)
147 , mDidPostCreate(false)
148 , mDispatchInputEvent(true)
149 , mIsInEditAction(false)
153 nsEditor::~nsEditor()
155 NS_ASSERTION(!mDocWeak || mDidPreDestroy, "Why PreDestroy hasn't been called?");
157 mTxnMgr = nullptr;
159 delete mPhonetic;
162 NS_IMPL_CYCLE_COLLECTION_CLASS(nsEditor)
164 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsEditor)
165 NS_IMPL_CYCLE_COLLECTION_UNLINK(mRootElement)
166 NS_IMPL_CYCLE_COLLECTION_UNLINK(mInlineSpellChecker)
167 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTxnMgr)
168 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIMETextNode)
169 NS_IMPL_CYCLE_COLLECTION_UNLINK(mActionListeners)
170 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditorObservers)
171 NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocStateListeners)
172 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventTarget)
173 NS_IMPL_CYCLE_COLLECTION_UNLINK(mEventListener)
174 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
176 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsEditor)
177 nsIDocument* currentDoc =
178 tmp->mRootElement ? tmp->mRootElement->GetCurrentDoc() : nullptr;
179 if (currentDoc &&
180 nsCCUncollectableMarker::InGeneration(cb, currentDoc->GetMarkedCCGeneration())) {
181 return NS_SUCCESS_INTERRUPTED_TRAVERSE;
183 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mRootElement)
184 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInlineSpellChecker)
185 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTxnMgr)
186 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIMETextNode)
187 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mActionListeners)
188 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorObservers)
189 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocStateListeners)
190 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventTarget)
191 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventListener)
192 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
194 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsEditor)
195 NS_INTERFACE_MAP_ENTRY(nsIPhonetic)
196 NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
197 NS_INTERFACE_MAP_ENTRY(nsIEditorIMESupport)
198 NS_INTERFACE_MAP_ENTRY(nsIEditor)
199 NS_INTERFACE_MAP_ENTRY(nsIObserver)
200 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditor)
201 NS_INTERFACE_MAP_END
203 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsEditor)
204 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsEditor)
207 NS_IMETHODIMP
208 nsEditor::Init(nsIDOMDocument *aDoc, nsIContent *aRoot,
209 nsISelectionController *aSelCon, uint32_t aFlags,
210 const nsAString& aValue)
212 NS_PRECONDITION(aDoc, "bad arg");
213 if (!aDoc)
214 return NS_ERROR_NULL_POINTER;
216 // First only set flags, but other stuff shouldn't be initialized now.
217 // Don't move this call after initializing mDocWeak.
218 // SetFlags() can check whether it's called during initialization or not by
219 // them. Note that SetFlags() will be called by PostCreate().
220 #ifdef DEBUG
221 nsresult rv =
222 #endif
223 SetFlags(aFlags);
224 NS_ASSERTION(NS_SUCCEEDED(rv), "SetFlags() failed");
226 mDocWeak = do_GetWeakReference(aDoc); // weak reference to doc
227 // HTML editors currently don't have their own selection controller,
228 // so they'll pass null as aSelCon, and we'll get the selection controller
229 // off of the presshell.
230 nsCOMPtr<nsISelectionController> selCon;
231 if (aSelCon) {
232 mSelConWeak = do_GetWeakReference(aSelCon); // weak reference to selectioncontroller
233 selCon = aSelCon;
234 } else {
235 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
236 selCon = do_QueryInterface(presShell);
238 NS_ASSERTION(selCon, "Selection controller should be available at this point");
240 //set up root element if we are passed one.
241 if (aRoot)
242 mRootElement = do_QueryInterface(aRoot);
244 mUpdateCount=0;
246 /* initialize IME stuff */
247 mIMETextNode = nullptr;
248 mIMETextOffset = 0;
249 /* Show the caret */
250 selCon->SetCaretReadOnly(false);
251 selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
253 selCon->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL);//we want to see all the selection reflected to user
255 NS_POSTCONDITION(mDocWeak, "bad state");
257 // Make sure that the editor will be destroyed properly
258 mDidPreDestroy = false;
259 // Make sure that the ediotr will be created properly
260 mDidPostCreate = false;
262 return NS_OK;
266 NS_IMETHODIMP
267 nsEditor::PostCreate()
269 // Synchronize some stuff for the flags. SetFlags() will initialize
270 // something by the flag difference. This is first time of that, so, all
271 // initializations must be run. For such reason, we need to invert mFlags
272 // value first.
273 mFlags = ~mFlags;
274 nsresult rv = SetFlags(~mFlags);
275 NS_ENSURE_SUCCESS(rv, rv);
277 // These operations only need to happen on the first PostCreate call
278 if (!mDidPostCreate) {
279 mDidPostCreate = true;
281 // Set up listeners
282 CreateEventListeners();
283 rv = InstallEventListeners();
284 NS_ENSURE_SUCCESS(rv, rv);
286 // nuke the modification count, so the doc appears unmodified
287 // do this before we notify listeners
288 ResetModificationCount();
290 // update the UI with our state
291 NotifyDocumentListeners(eDocumentCreated);
292 NotifyDocumentListeners(eDocumentStateChanged);
294 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
295 if (obs) {
296 obs->AddObserver(this,
297 SPELLCHECK_DICTIONARY_UPDATE_NOTIFICATION,
298 false);
302 // update nsTextStateManager and caret if we have focus
303 nsCOMPtr<nsIContent> focusedContent = GetFocusedContent();
304 if (focusedContent) {
305 nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(focusedContent);
306 if (target) {
307 InitializeSelection(target);
310 // If the text control gets reframed during focus, Focus() would not be
311 // called, so take a chance here to see if we need to spell check the text
312 // control.
313 nsEditorEventListener* listener =
314 reinterpret_cast<nsEditorEventListener*> (mEventListener.get());
315 listener->SpellCheckIfNeeded();
317 IMEState newState;
318 rv = GetPreferredIMEState(&newState);
319 NS_ENSURE_SUCCESS(rv, NS_OK);
320 nsCOMPtr<nsIContent> content = GetFocusedContentForIME();
321 IMEStateManager::UpdateIMEState(newState, content);
323 return NS_OK;
326 /* virtual */
327 void
328 nsEditor::CreateEventListeners()
330 // Don't create the handler twice
331 if (!mEventListener) {
332 mEventListener = new nsEditorEventListener();
336 nsresult
337 nsEditor::InstallEventListeners()
339 NS_ENSURE_TRUE(mDocWeak && mEventListener,
340 NS_ERROR_NOT_INITIALIZED);
342 // Initialize the event target.
343 nsCOMPtr<nsIContent> rootContent = GetRoot();
344 NS_ENSURE_TRUE(rootContent, NS_ERROR_NOT_AVAILABLE);
345 mEventTarget = do_QueryInterface(rootContent->GetParent());
346 NS_ENSURE_TRUE(mEventTarget, NS_ERROR_NOT_AVAILABLE);
348 nsEditorEventListener* listener =
349 reinterpret_cast<nsEditorEventListener*>(mEventListener.get());
350 return listener->Connect(this);
353 void
354 nsEditor::RemoveEventListeners()
356 if (!mDocWeak || !mEventListener) {
357 return;
359 reinterpret_cast<nsEditorEventListener*>(mEventListener.get())->Disconnect();
360 if (mComposition) {
361 mComposition->EndHandlingComposition(this);
362 mComposition = nullptr;
364 mEventTarget = nullptr;
367 bool
368 nsEditor::GetDesiredSpellCheckState()
370 // Check user override on this element
371 if (mSpellcheckCheckboxState != eTriUnset) {
372 return (mSpellcheckCheckboxState == eTriTrue);
375 // Check user preferences
376 int32_t spellcheckLevel = Preferences::GetInt("layout.spellcheckDefault", 1);
378 if (spellcheckLevel == 0) {
379 return false; // Spellchecking forced off globally
382 if (!CanEnableSpellCheck()) {
383 return false;
386 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
387 if (presShell) {
388 nsPresContext* context = presShell->GetPresContext();
389 if (context && !context->IsDynamic()) {
390 return false;
394 // Check DOM state
395 nsCOMPtr<nsIContent> content = GetExposedRoot();
396 if (!content) {
397 return false;
400 nsCOMPtr<nsIDOMHTMLElement> element = do_QueryInterface(content);
401 if (!element) {
402 return false;
405 if (!IsPlaintextEditor()) {
406 // Some of the page content might be editable and some not, if spellcheck=
407 // is explicitly set anywhere, so if there's anything editable on the page,
408 // return true and let the spellchecker figure it out.
409 nsCOMPtr<nsIHTMLDocument> doc = do_QueryInterface(content->GetCurrentDoc());
410 return doc && doc->IsEditingOn();
413 bool enable;
414 element->GetSpellcheck(&enable);
416 return enable;
419 NS_IMETHODIMP
420 nsEditor::PreDestroy(bool aDestroyingFrames)
422 if (mDidPreDestroy)
423 return NS_OK;
425 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
426 if (obs) {
427 obs->RemoveObserver(this,
428 SPELLCHECK_DICTIONARY_UPDATE_NOTIFICATION);
431 // Let spellchecker clean up its observers etc. It is important not to
432 // actually free the spellchecker here, since the spellchecker could have
433 // caused flush notifications, which could have gotten here if a textbox
434 // is being removed. Setting the spellchecker to nullptr could free the
435 // object that is still in use! It will be freed when the editor is
436 // destroyed.
437 if (mInlineSpellChecker)
438 mInlineSpellChecker->Cleanup(aDestroyingFrames);
440 // tell our listeners that the doc is going away
441 NotifyDocumentListeners(eDocumentToBeDestroyed);
443 // Unregister event listeners
444 RemoveEventListeners();
445 mActionListeners.Clear();
446 mEditorObservers.Clear();
447 mDocStateListeners.Clear();
448 mInlineSpellChecker = nullptr;
449 mSpellcheckCheckboxState = eTriUnset;
450 mRootElement = nullptr;
452 mDidPreDestroy = true;
453 return NS_OK;
456 NS_IMETHODIMP
457 nsEditor::GetFlags(uint32_t *aFlags)
459 *aFlags = mFlags;
460 return NS_OK;
463 NS_IMETHODIMP
464 nsEditor::SetFlags(uint32_t aFlags)
466 if (mFlags == aFlags) {
467 return NS_OK;
470 bool spellcheckerWasEnabled = CanEnableSpellCheck();
471 mFlags = aFlags;
473 if (!mDocWeak) {
474 // If we're initializing, we shouldn't do anything now.
475 // SetFlags() will be called by PostCreate(),
476 // we should synchronize some stuff for the flags at that time.
477 return NS_OK;
480 // The flag change may cause the spellchecker state change
481 if (CanEnableSpellCheck() != spellcheckerWasEnabled) {
482 nsresult rv = SyncRealTimeSpell();
483 NS_ENSURE_SUCCESS(rv, rv);
486 // If this is called from PostCreate(), it will update the IME state if it's
487 // necessary.
488 if (!mDidPostCreate) {
489 return NS_OK;
492 // Might be changing editable state, so, we need to reset current IME state
493 // if we're focused and the flag change causes IME state change.
494 nsCOMPtr<nsIContent> focusedContent = GetFocusedContent();
495 if (focusedContent) {
496 IMEState newState;
497 nsresult rv = GetPreferredIMEState(&newState);
498 if (NS_SUCCEEDED(rv)) {
499 // NOTE: When the enabled state isn't going to be modified, this method
500 // is going to do nothing.
501 nsCOMPtr<nsIContent> content = GetFocusedContentForIME();
502 IMEStateManager::UpdateIMEState(newState, content);
506 return NS_OK;
509 NS_IMETHODIMP
510 nsEditor::GetIsSelectionEditable(bool *aIsSelectionEditable)
512 NS_ENSURE_ARG_POINTER(aIsSelectionEditable);
514 // get current selection
515 nsRefPtr<Selection> selection = GetSelection();
516 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
518 // XXX we just check that the anchor node is editable at the moment
519 // we should check that all nodes in the selection are editable
520 nsCOMPtr<nsINode> anchorNode = selection->GetAnchorNode();
521 *aIsSelectionEditable = anchorNode && IsEditable(anchorNode);
523 return NS_OK;
526 NS_IMETHODIMP
527 nsEditor::GetIsDocumentEditable(bool *aIsDocumentEditable)
529 NS_ENSURE_ARG_POINTER(aIsDocumentEditable);
530 nsCOMPtr<nsIDocument> doc = GetDocument();
531 *aIsDocumentEditable = !!doc;
533 return NS_OK;
536 already_AddRefed<nsIDocument>
537 nsEditor::GetDocument()
539 NS_PRECONDITION(mDocWeak, "bad state, mDocWeak weak pointer not initialized");
540 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
541 return doc.forget();
544 already_AddRefed<nsIDOMDocument>
545 nsEditor::GetDOMDocument()
547 NS_PRECONDITION(mDocWeak, "bad state, mDocWeak weak pointer not initialized");
548 nsCOMPtr<nsIDOMDocument> doc = do_QueryReferent(mDocWeak);
549 return doc.forget();
552 NS_IMETHODIMP
553 nsEditor::GetDocument(nsIDOMDocument **aDoc)
555 *aDoc = GetDOMDocument().take();
556 return *aDoc ? NS_OK : NS_ERROR_NOT_INITIALIZED;
559 already_AddRefed<nsIPresShell>
560 nsEditor::GetPresShell()
562 NS_PRECONDITION(mDocWeak, "bad state, null mDocWeak");
563 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
564 NS_ENSURE_TRUE(doc, nullptr);
565 nsCOMPtr<nsIPresShell> ps = doc->GetShell();
566 return ps.forget();
569 already_AddRefed<nsIWidget>
570 nsEditor::GetWidget()
572 nsCOMPtr<nsIPresShell> ps = GetPresShell();
573 NS_ENSURE_TRUE(ps, nullptr);
574 nsPresContext* pc = ps->GetPresContext();
575 NS_ENSURE_TRUE(pc, nullptr);
576 nsCOMPtr<nsIWidget> widget = pc->GetRootWidget();
577 NS_ENSURE_TRUE(widget.get(), nullptr);
578 return widget.forget();
581 /* attribute string contentsMIMEType; */
582 NS_IMETHODIMP
583 nsEditor::GetContentsMIMEType(char * *aContentsMIMEType)
585 NS_ENSURE_ARG_POINTER(aContentsMIMEType);
586 *aContentsMIMEType = ToNewCString(mContentMIMEType);
587 return NS_OK;
590 NS_IMETHODIMP
591 nsEditor::SetContentsMIMEType(const char * aContentsMIMEType)
593 mContentMIMEType.Assign(aContentsMIMEType ? aContentsMIMEType : "");
594 return NS_OK;
597 NS_IMETHODIMP
598 nsEditor::GetSelectionController(nsISelectionController **aSel)
600 NS_ENSURE_TRUE(aSel, NS_ERROR_NULL_POINTER);
601 *aSel = nullptr; // init out param
602 nsCOMPtr<nsISelectionController> selCon;
603 if (mSelConWeak) {
604 selCon = do_QueryReferent(mSelConWeak);
605 } else {
606 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
607 selCon = do_QueryInterface(presShell);
609 NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
610 NS_ADDREF(*aSel = selCon);
611 return NS_OK;
615 NS_IMETHODIMP
616 nsEditor::DeleteSelection(EDirection aAction, EStripWrappers aStripWrappers)
618 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
619 return DeleteSelectionImpl(aAction, aStripWrappers);
624 NS_IMETHODIMP
625 nsEditor::GetSelection(nsISelection **aSelection)
627 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
628 *aSelection = nullptr;
629 nsCOMPtr<nsISelectionController> selcon;
630 GetSelectionController(getter_AddRefs(selcon));
631 NS_ENSURE_TRUE(selcon, NS_ERROR_NOT_INITIALIZED);
632 return selcon->GetSelection(nsISelectionController::SELECTION_NORMAL, aSelection); // does an addref
635 Selection*
636 nsEditor::GetSelection()
638 nsCOMPtr<nsISelection> sel;
639 nsresult res = GetSelection(getter_AddRefs(sel));
640 NS_ENSURE_SUCCESS(res, nullptr);
642 return static_cast<Selection*>(sel.get());
645 NS_IMETHODIMP
646 nsEditor::DoTransaction(nsITransaction* aTxn)
648 if (mPlaceHolderBatch && !mPlaceHolderTxn) {
649 nsCOMPtr<nsIAbsorbingTransaction> plcTxn = new PlaceholderTxn();
651 // save off weak reference to placeholder txn
652 mPlaceHolderTxn = do_GetWeakReference(plcTxn);
653 plcTxn->Init(mPlaceHolderName, mSelState, this);
654 // placeholder txn took ownership of this pointer
655 mSelState = nullptr;
657 // QI to an nsITransaction since that's what DoTransaction() expects
658 nsCOMPtr<nsITransaction> theTxn = do_QueryInterface(plcTxn);
659 // we will recurse, but will not hit this case in the nested call
660 DoTransaction(theTxn);
662 if (mTxnMgr) {
663 nsCOMPtr<nsITransaction> topTxn = mTxnMgr->PeekUndoStack();
664 if (topTxn) {
665 plcTxn = do_QueryInterface(topTxn);
666 if (plcTxn) {
667 // there is a placeholder transaction on top of the undo stack. It
668 // is either the one we just created, or an earlier one that we are
669 // now merging into. From here on out remember this placeholder
670 // instead of the one we just created.
671 mPlaceHolderTxn = do_GetWeakReference(plcTxn);
677 if (aTxn) {
678 // XXX: Why are we doing selection specific batching stuff here?
679 // XXX: Most entry points into the editor have auto variables that
680 // XXX: should trigger Begin/EndUpdateViewBatch() calls that will make
681 // XXX: these selection batch calls no-ops.
682 // XXX:
683 // XXX: I suspect that this was placed here to avoid multiple
684 // XXX: selection changed notifications from happening until after
685 // XXX: the transaction was done. I suppose that can still happen
686 // XXX: if an embedding application called DoTransaction() directly
687 // XXX: to pump its own transactions through the system, but in that
688 // XXX: case, wouldn't we want to use Begin/EndUpdateViewBatch() or
689 // XXX: its auto equivalent nsAutoUpdateViewBatch to ensure that
690 // XXX: selection listeners have access to accurate frame data?
691 // XXX:
692 // XXX: Note that if we did add Begin/EndUpdateViewBatch() calls
693 // XXX: we will need to make sure that they are disabled during
694 // XXX: the init of the editor for text widgets to avoid layout
695 // XXX: re-entry during initial reflow. - kin
697 // get the selection and start a batch change
698 nsRefPtr<Selection> selection = GetSelection();
699 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
701 selection->StartBatchChanges();
703 nsresult res;
704 if (mTxnMgr) {
705 res = mTxnMgr->DoTransaction(aTxn);
706 } else {
707 res = aTxn->DoTransaction();
709 if (NS_SUCCEEDED(res)) {
710 DoAfterDoTransaction(aTxn);
713 // no need to check res here, don't lose result of operation
714 selection->EndBatchChanges();
716 NS_ENSURE_SUCCESS(res, res);
719 return NS_OK;
723 NS_IMETHODIMP
724 nsEditor::EnableUndo(bool aEnable)
726 if (aEnable) {
727 if (!mTxnMgr) {
728 mTxnMgr = new nsTransactionManager();
730 mTxnMgr->SetMaxTransactionCount(-1);
731 } else if (mTxnMgr) {
732 // disable the transaction manager if it is enabled
733 mTxnMgr->Clear();
734 mTxnMgr->SetMaxTransactionCount(0);
737 return NS_OK;
740 NS_IMETHODIMP
741 nsEditor::GetNumberOfUndoItems(int32_t* aNumItems)
743 *aNumItems = 0;
744 return mTxnMgr ? mTxnMgr->GetNumberOfUndoItems(aNumItems) : NS_OK;
747 NS_IMETHODIMP
748 nsEditor::GetNumberOfRedoItems(int32_t* aNumItems)
750 *aNumItems = 0;
751 return mTxnMgr ? mTxnMgr->GetNumberOfRedoItems(aNumItems) : NS_OK;
754 NS_IMETHODIMP
755 nsEditor::GetTransactionManager(nsITransactionManager* *aTxnManager)
757 NS_ENSURE_ARG_POINTER(aTxnManager);
759 *aTxnManager = nullptr;
760 NS_ENSURE_TRUE(mTxnMgr, NS_ERROR_FAILURE);
762 NS_ADDREF(*aTxnManager = mTxnMgr);
763 return NS_OK;
766 NS_IMETHODIMP
767 nsEditor::SetTransactionManager(nsITransactionManager *aTxnManager)
769 NS_ENSURE_TRUE(aTxnManager, NS_ERROR_FAILURE);
771 // nsITransactionManager is builtinclass, so this is safe
772 mTxnMgr = static_cast<nsTransactionManager*>(aTxnManager);
773 return NS_OK;
776 NS_IMETHODIMP
777 nsEditor::Undo(uint32_t aCount)
779 ForceCompositionEnd();
781 bool hasTxnMgr, hasTransaction = false;
782 CanUndo(&hasTxnMgr, &hasTransaction);
783 NS_ENSURE_TRUE(hasTransaction, NS_OK);
785 nsAutoRules beginRulesSniffing(this, EditAction::undo, nsIEditor::eNone);
787 if (!mTxnMgr) {
788 return NS_OK;
791 for (uint32_t i = 0; i < aCount; ++i) {
792 nsresult rv = mTxnMgr->UndoTransaction();
793 NS_ENSURE_SUCCESS(rv, rv);
795 DoAfterUndoTransaction();
798 return NS_OK;
802 NS_IMETHODIMP nsEditor::CanUndo(bool *aIsEnabled, bool *aCanUndo)
804 NS_ENSURE_TRUE(aIsEnabled && aCanUndo, NS_ERROR_NULL_POINTER);
805 *aIsEnabled = !!mTxnMgr;
806 if (*aIsEnabled) {
807 int32_t numTxns = 0;
808 mTxnMgr->GetNumberOfUndoItems(&numTxns);
809 *aCanUndo = !!numTxns;
810 } else {
811 *aCanUndo = false;
813 return NS_OK;
817 NS_IMETHODIMP
818 nsEditor::Redo(uint32_t aCount)
820 bool hasTxnMgr, hasTransaction = false;
821 CanRedo(&hasTxnMgr, &hasTransaction);
822 NS_ENSURE_TRUE(hasTransaction, NS_OK);
824 nsAutoRules beginRulesSniffing(this, EditAction::redo, nsIEditor::eNone);
826 if (!mTxnMgr) {
827 return NS_OK;
830 for (uint32_t i = 0; i < aCount; ++i) {
831 nsresult rv = mTxnMgr->RedoTransaction();
832 NS_ENSURE_SUCCESS(rv, rv);
834 DoAfterRedoTransaction();
837 return NS_OK;
841 NS_IMETHODIMP nsEditor::CanRedo(bool *aIsEnabled, bool *aCanRedo)
843 NS_ENSURE_TRUE(aIsEnabled && aCanRedo, NS_ERROR_NULL_POINTER);
845 *aIsEnabled = !!mTxnMgr;
846 if (*aIsEnabled) {
847 int32_t numTxns = 0;
848 mTxnMgr->GetNumberOfRedoItems(&numTxns);
849 *aCanRedo = !!numTxns;
850 } else {
851 *aCanRedo = false;
853 return NS_OK;
857 NS_IMETHODIMP
858 nsEditor::BeginTransaction()
860 BeginUpdateViewBatch();
862 if (mTxnMgr) {
863 mTxnMgr->BeginBatch(nullptr);
866 return NS_OK;
869 NS_IMETHODIMP
870 nsEditor::EndTransaction()
872 if (mTxnMgr) {
873 mTxnMgr->EndBatch(false);
876 EndUpdateViewBatch();
878 return NS_OK;
882 // These two routines are similar to the above, but do not use
883 // the transaction managers batching feature. Instead we use
884 // a placeholder transaction to wrap up any further transaction
885 // while the batch is open. The advantage of this is that
886 // placeholder transactions can later merge, if needed. Merging
887 // is unavailable between transaction manager batches.
889 NS_IMETHODIMP
890 nsEditor::BeginPlaceHolderTransaction(nsIAtom *aName)
892 NS_PRECONDITION(mPlaceHolderBatch >= 0, "negative placeholder batch count!");
893 if (!mPlaceHolderBatch)
895 NotifyEditorObservers(eNotifyEditorObserversOfBefore);
896 // time to turn on the batch
897 BeginUpdateViewBatch();
898 mPlaceHolderTxn = nullptr;
899 mPlaceHolderName = aName;
900 nsRefPtr<Selection> selection = GetSelection();
901 if (selection) {
902 mSelState = new nsSelectionState();
903 mSelState->SaveSelection(selection);
906 mPlaceHolderBatch++;
908 return NS_OK;
911 NS_IMETHODIMP
912 nsEditor::EndPlaceHolderTransaction()
914 NS_PRECONDITION(mPlaceHolderBatch > 0, "zero or negative placeholder batch count when ending batch!");
915 if (mPlaceHolderBatch == 1)
917 nsCOMPtr<nsISelection>selection;
918 GetSelection(getter_AddRefs(selection));
920 nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(selection));
922 // By making the assumption that no reflow happens during the calls
923 // to EndUpdateViewBatch and ScrollSelectionIntoView, we are able to
924 // allow the selection to cache a frame offset which is used by the
925 // caret drawing code. We only enable this cache here; at other times,
926 // we have no way to know whether reflow invalidates it
927 // See bugs 35296 and 199412.
928 if (selPrivate) {
929 selPrivate->SetCanCacheFrameOffset(true);
933 // Hide the caret here to avoid hiding it twice, once in EndUpdateViewBatch
934 // and once in ScrollSelectionIntoView.
935 nsRefPtr<nsCaret> caret;
936 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
938 if (presShell)
939 caret = presShell->GetCaret();
941 // time to turn off the batch
942 EndUpdateViewBatch();
943 // make sure selection is in view
945 // After ScrollSelectionIntoView(), the pending notifications might be
946 // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
947 ScrollSelectionIntoView(false);
950 // cached for frame offset are Not available now
951 if (selPrivate) {
952 selPrivate->SetCanCacheFrameOffset(false);
955 if (mSelState)
957 // we saved the selection state, but never got to hand it to placeholder
958 // (else we ould have nulled out this pointer), so destroy it to prevent leaks.
959 delete mSelState;
960 mSelState = nullptr;
962 if (mPlaceHolderTxn) // we might have never made a placeholder if no action took place
964 nsCOMPtr<nsIAbsorbingTransaction> plcTxn = do_QueryReferent(mPlaceHolderTxn);
965 if (plcTxn)
967 plcTxn->EndPlaceHolderBatch();
969 else
971 // in the future we will check to make sure undo is off here,
972 // since that is the only known case where the placeholdertxn would disappear on us.
973 // For now just removing the assert.
975 // notify editor observers of action but if composing, it's done by
976 // text event handler.
977 if (!mComposition) {
978 NotifyEditorObservers(eNotifyEditorObserversOfEnd);
980 } else {
981 NotifyEditorObservers(eNotifyEditorObserversOfCancel);
984 mPlaceHolderBatch--;
986 return NS_OK;
989 NS_IMETHODIMP
990 nsEditor::ShouldTxnSetSelection(bool *aResult)
992 NS_ENSURE_TRUE(aResult, NS_ERROR_NULL_POINTER);
993 *aResult = mShouldTxnSetSelection;
994 return NS_OK;
997 NS_IMETHODIMP
998 nsEditor::SetShouldTxnSetSelection(bool aShould)
1000 mShouldTxnSetSelection = aShould;
1001 return NS_OK;
1004 NS_IMETHODIMP
1005 nsEditor::GetDocumentIsEmpty(bool *aDocumentIsEmpty)
1007 *aDocumentIsEmpty = true;
1009 dom::Element* root = GetRoot();
1010 NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER);
1012 *aDocumentIsEmpty = !root->HasChildren();
1013 return NS_OK;
1017 // XXX: the rule system should tell us which node to select all on (ie, the root, or the body)
1018 NS_IMETHODIMP nsEditor::SelectAll()
1020 if (!mDocWeak) { return NS_ERROR_NOT_INITIALIZED; }
1021 ForceCompositionEnd();
1023 nsCOMPtr<nsISelectionController> selCon;
1024 GetSelectionController(getter_AddRefs(selCon));
1025 NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
1026 nsCOMPtr<nsISelection> selection;
1027 nsresult result = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL, getter_AddRefs(selection));
1028 if (NS_SUCCEEDED(result) && selection)
1030 result = SelectEntireDocument(selection);
1032 return result;
1035 NS_IMETHODIMP nsEditor::BeginningOfDocument()
1037 if (!mDocWeak) { return NS_ERROR_NOT_INITIALIZED; }
1039 // get the selection
1040 nsCOMPtr<nsISelection> selection;
1041 nsresult result = GetSelection(getter_AddRefs(selection));
1042 NS_ENSURE_SUCCESS(result, result);
1043 NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
1045 // get the root element
1046 dom::Element* rootElement = GetRoot();
1047 NS_ENSURE_TRUE(rootElement, NS_ERROR_NULL_POINTER);
1049 // find first editable thingy
1050 nsCOMPtr<nsINode> firstNode = GetFirstEditableNode(rootElement);
1051 if (!firstNode) {
1052 // just the root node, set selection to inside the root
1053 return selection->CollapseNative(rootElement, 0);
1056 if (firstNode->NodeType() == nsIDOMNode::TEXT_NODE) {
1057 // If firstNode is text, set selection to beginning of the text node.
1058 return selection->CollapseNative(firstNode, 0);
1061 // Otherwise, it's a leaf node and we set the selection just in front of it.
1062 nsCOMPtr<nsIContent> parent = firstNode->GetParent();
1063 if (!parent) {
1064 return NS_ERROR_NULL_POINTER;
1067 int32_t offsetInParent = parent->IndexOf(firstNode);
1068 return selection->CollapseNative(parent, offsetInParent);
1071 NS_IMETHODIMP
1072 nsEditor::EndOfDocument()
1074 NS_ENSURE_TRUE(mDocWeak, NS_ERROR_NOT_INITIALIZED);
1076 // get selection
1077 nsCOMPtr<nsISelection> selection;
1078 nsresult rv = GetSelection(getter_AddRefs(selection));
1079 NS_ENSURE_SUCCESS(rv, rv);
1080 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
1082 // get the root element
1083 nsINode* node = GetRoot();
1084 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
1085 nsINode* child = node->GetLastChild();
1087 while (child && IsContainer(child->AsDOMNode())) {
1088 node = child;
1089 child = node->GetLastChild();
1092 uint32_t length = node->Length();
1093 return selection->CollapseNative(node, int32_t(length));
1096 NS_IMETHODIMP
1097 nsEditor::GetDocumentModified(bool *outDocModified)
1099 NS_ENSURE_TRUE(outDocModified, NS_ERROR_NULL_POINTER);
1101 int32_t modCount = 0;
1102 GetModificationCount(&modCount);
1104 *outDocModified = (modCount != 0);
1105 return NS_OK;
1108 NS_IMETHODIMP
1109 nsEditor::GetDocumentCharacterSet(nsACString &characterSet)
1111 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
1112 NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
1114 characterSet = doc->GetDocumentCharacterSet();
1115 return NS_OK;
1118 NS_IMETHODIMP
1119 nsEditor::SetDocumentCharacterSet(const nsACString& characterSet)
1121 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
1122 NS_ENSURE_TRUE(doc, NS_ERROR_UNEXPECTED);
1124 doc->SetDocumentCharacterSet(characterSet);
1125 return NS_OK;
1128 NS_IMETHODIMP
1129 nsEditor::Cut()
1131 return NS_ERROR_NOT_IMPLEMENTED;
1134 NS_IMETHODIMP
1135 nsEditor::CanCut(bool *aCanCut)
1137 return NS_ERROR_NOT_IMPLEMENTED;
1140 NS_IMETHODIMP
1141 nsEditor::Copy()
1143 return NS_ERROR_NOT_IMPLEMENTED;
1146 NS_IMETHODIMP
1147 nsEditor::CanCopy(bool *aCanCut)
1149 return NS_ERROR_NOT_IMPLEMENTED;
1152 NS_IMETHODIMP
1153 nsEditor::Paste(int32_t aSelectionType)
1155 return NS_ERROR_NOT_IMPLEMENTED;
1158 NS_IMETHODIMP
1159 nsEditor::PasteTransferable(nsITransferable *aTransferable)
1161 return NS_ERROR_NOT_IMPLEMENTED;
1164 NS_IMETHODIMP
1165 nsEditor::CanPaste(int32_t aSelectionType, bool *aCanPaste)
1167 return NS_ERROR_NOT_IMPLEMENTED;
1170 NS_IMETHODIMP
1171 nsEditor::CanPasteTransferable(nsITransferable *aTransferable, bool *aCanPaste)
1173 return NS_ERROR_NOT_IMPLEMENTED;
1176 NS_IMETHODIMP
1177 nsEditor::SetAttribute(nsIDOMElement* aElement, const nsAString& aAttribute,
1178 const nsAString& aValue)
1180 nsCOMPtr<Element> element = do_QueryInterface(aElement);
1181 NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER);
1182 nsCOMPtr<nsIAtom> attribute = do_GetAtom(aAttribute);
1184 nsRefPtr<ChangeAttributeTxn> txn =
1185 CreateTxnForSetAttribute(*element, *attribute, aValue);
1186 return DoTransaction(txn);
1189 NS_IMETHODIMP
1190 nsEditor::GetAttributeValue(nsIDOMElement *aElement,
1191 const nsAString & aAttribute,
1192 nsAString & aResultValue,
1193 bool *aResultIsSet)
1195 NS_ENSURE_TRUE(aResultIsSet, NS_ERROR_NULL_POINTER);
1196 *aResultIsSet = false;
1197 if (!aElement) {
1198 return NS_OK;
1200 nsAutoString value;
1201 nsresult rv = aElement->GetAttribute(aAttribute, value);
1202 NS_ENSURE_SUCCESS(rv, rv);
1203 if (!DOMStringIsNull(value)) {
1204 *aResultIsSet = true;
1205 aResultValue = value;
1207 return rv;
1210 NS_IMETHODIMP
1211 nsEditor::RemoveAttribute(nsIDOMElement* aElement, const nsAString& aAttribute)
1213 nsCOMPtr<Element> element = do_QueryInterface(aElement);
1214 NS_ENSURE_TRUE(element, NS_ERROR_NULL_POINTER);
1215 nsCOMPtr<nsIAtom> attribute = do_GetAtom(aAttribute);
1217 nsRefPtr<ChangeAttributeTxn> txn =
1218 CreateTxnForRemoveAttribute(*element, *attribute);
1219 return DoTransaction(txn);
1223 bool
1224 nsEditor::OutputsMozDirty()
1226 // Return true for Composer (!eEditorAllowInteraction) or mail
1227 // (eEditorMailMask), but false for webpages.
1228 return !(mFlags & nsIPlaintextEditor::eEditorAllowInteraction) ||
1229 (mFlags & nsIPlaintextEditor::eEditorMailMask);
1233 NS_IMETHODIMP
1234 nsEditor::MarkNodeDirty(nsIDOMNode* aNode)
1236 // Mark the node dirty, but not for webpages (bug 599983)
1237 if (!OutputsMozDirty()) {
1238 return NS_OK;
1240 nsCOMPtr<dom::Element> element = do_QueryInterface(aNode);
1241 if (element) {
1242 element->SetAttr(kNameSpaceID_None, nsGkAtoms::mozdirty,
1243 EmptyString(), false);
1245 return NS_OK;
1248 NS_IMETHODIMP nsEditor::GetInlineSpellChecker(bool autoCreate,
1249 nsIInlineSpellChecker ** aInlineSpellChecker)
1251 NS_ENSURE_ARG_POINTER(aInlineSpellChecker);
1253 if (mDidPreDestroy) {
1254 // Don't allow people to get or create the spell checker once the editor
1255 // is going away.
1256 *aInlineSpellChecker = nullptr;
1257 return autoCreate ? NS_ERROR_NOT_AVAILABLE : NS_OK;
1260 // We don't want to show the spell checking UI if there are no spell check dictionaries available.
1261 bool canSpell = mozInlineSpellChecker::CanEnableInlineSpellChecking();
1262 if (!canSpell) {
1263 *aInlineSpellChecker = nullptr;
1264 return NS_ERROR_FAILURE;
1267 nsresult rv;
1268 if (!mInlineSpellChecker && autoCreate) {
1269 mInlineSpellChecker = do_CreateInstance(MOZ_INLINESPELLCHECKER_CONTRACTID, &rv);
1270 NS_ENSURE_SUCCESS(rv, rv);
1273 if (mInlineSpellChecker) {
1274 rv = mInlineSpellChecker->Init(this);
1275 if (NS_FAILED(rv))
1276 mInlineSpellChecker = nullptr;
1277 NS_ENSURE_SUCCESS(rv, rv);
1280 NS_IF_ADDREF(*aInlineSpellChecker = mInlineSpellChecker);
1282 return NS_OK;
1285 NS_IMETHODIMP nsEditor::Observe(nsISupports* aSubj, const char *aTopic,
1286 const char16_t *aData)
1288 NS_ASSERTION(!strcmp(aTopic,
1289 SPELLCHECK_DICTIONARY_UPDATE_NOTIFICATION),
1290 "Unexpected observer topic");
1292 // When mozInlineSpellChecker::CanEnableInlineSpellChecking changes
1293 SyncRealTimeSpell();
1295 // When nsIEditorSpellCheck::GetCurrentDictionary changes
1296 if (mInlineSpellChecker) {
1297 // if the current dictionary is no longer available, find another one
1298 nsCOMPtr<nsIEditorSpellCheck> editorSpellCheck;
1299 mInlineSpellChecker->GetSpellChecker(getter_AddRefs(editorSpellCheck));
1300 if (editorSpellCheck) {
1301 // Note: This might change the current dictionary, which may call
1302 // this observer recursively.
1303 editorSpellCheck->CheckCurrentDictionary();
1306 // update the inline spell checker to reflect the new current dictionary
1307 mInlineSpellChecker->SpellCheckRange(nullptr); // causes recheck
1310 return NS_OK;
1313 NS_IMETHODIMP nsEditor::SyncRealTimeSpell()
1315 bool enable = GetDesiredSpellCheckState();
1317 // Initializes mInlineSpellChecker
1318 nsCOMPtr<nsIInlineSpellChecker> spellChecker;
1319 GetInlineSpellChecker(enable, getter_AddRefs(spellChecker));
1321 if (mInlineSpellChecker) {
1322 // We might have a mInlineSpellChecker even if there are no dictionaries
1323 // available since we don't destroy the mInlineSpellChecker when the last
1324 // dictionariy is removed, but in that case spellChecker is null
1325 mInlineSpellChecker->SetEnableRealTimeSpell(enable && spellChecker);
1328 return NS_OK;
1331 NS_IMETHODIMP nsEditor::SetSpellcheckUserOverride(bool enable)
1333 mSpellcheckCheckboxState = enable ? eTriTrue : eTriFalse;
1335 return SyncRealTimeSpell();
1338 NS_IMETHODIMP
1339 nsEditor::CreateNode(const nsAString& aTag,
1340 nsIDOMNode* aParent,
1341 int32_t aPosition,
1342 nsIDOMNode** aNewNode)
1344 nsCOMPtr<nsIAtom> tag = do_GetAtom(aTag);
1345 nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
1346 NS_ENSURE_STATE(parent);
1347 *aNewNode = GetAsDOMNode(CreateNode(tag, parent, aPosition).take());
1348 NS_ENSURE_STATE(*aNewNode);
1349 return NS_OK;
1352 already_AddRefed<Element>
1353 nsEditor::CreateNode(nsIAtom* aTag,
1354 nsINode* aParent,
1355 int32_t aPosition)
1357 MOZ_ASSERT(aTag && aParent);
1359 nsAutoRules beginRulesSniffing(this, EditAction::createNode, nsIEditor::eNext);
1361 for (int32_t i = 0; i < mActionListeners.Count(); i++) {
1362 mActionListeners[i]->WillCreateNode(nsDependentAtomString(aTag),
1363 GetAsDOMNode(aParent), aPosition);
1366 nsCOMPtr<Element> ret;
1368 nsRefPtr<CreateElementTxn> txn =
1369 CreateTxnForCreateElement(*aTag, *aParent, aPosition);
1370 nsresult res = DoTransaction(txn);
1371 if (NS_SUCCEEDED(res)) {
1372 ret = txn->GetNewNode();
1373 MOZ_ASSERT(ret);
1376 mRangeUpdater.SelAdjCreateNode(aParent, aPosition);
1378 for (int32_t i = 0; i < mActionListeners.Count(); i++) {
1379 mActionListeners[i]->DidCreateNode(nsDependentAtomString(aTag),
1380 GetAsDOMNode(ret),
1381 GetAsDOMNode(aParent), aPosition,
1382 res);
1385 return ret.forget();
1389 NS_IMETHODIMP
1390 nsEditor::InsertNode(nsIDOMNode* aNode, nsIDOMNode* aParent, int32_t aPosition)
1392 nsCOMPtr<nsIContent> node = do_QueryInterface(aNode);
1393 nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
1394 NS_ENSURE_TRUE(node && parent, NS_ERROR_NULL_POINTER);
1396 return InsertNode(*node, *parent, aPosition);
1399 nsresult
1400 nsEditor::InsertNode(nsIContent& aNode, nsINode& aParent, int32_t aPosition)
1402 nsAutoRules beginRulesSniffing(this, EditAction::insertNode, nsIEditor::eNext);
1404 for (int32_t i = 0; i < mActionListeners.Count(); i++) {
1405 mActionListeners[i]->WillInsertNode(aNode.AsDOMNode(), aParent.AsDOMNode(),
1406 aPosition);
1409 nsRefPtr<InsertNodeTxn> txn = CreateTxnForInsertNode(aNode, aParent,
1410 aPosition);
1411 nsresult res = DoTransaction(txn);
1413 mRangeUpdater.SelAdjInsertNode(aParent.AsDOMNode(), aPosition);
1415 for (int32_t i = 0; i < mActionListeners.Count(); i++) {
1416 mActionListeners[i]->DidInsertNode(aNode.AsDOMNode(), aParent.AsDOMNode(),
1417 aPosition, res);
1420 return res;
1424 NS_IMETHODIMP
1425 nsEditor::SplitNode(nsIDOMNode * aNode,
1426 int32_t aOffset,
1427 nsIDOMNode **aNewLeftNode)
1429 int32_t i;
1430 nsAutoRules beginRulesSniffing(this, EditAction::splitNode, nsIEditor::eNext);
1432 for (i = 0; i < mActionListeners.Count(); i++)
1433 mActionListeners[i]->WillSplitNode(aNode, aOffset);
1435 nsRefPtr<SplitElementTxn> txn;
1436 nsresult result = CreateTxnForSplitNode(aNode, aOffset, getter_AddRefs(txn));
1437 if (NS_SUCCEEDED(result))
1439 result = DoTransaction(txn);
1440 if (NS_SUCCEEDED(result))
1442 result = txn->GetNewNode(aNewLeftNode);
1443 NS_ASSERTION((NS_SUCCEEDED(result)), "result must succeeded for GetNewNode");
1447 mRangeUpdater.SelAdjSplitNode(aNode, aOffset, *aNewLeftNode);
1449 for (i = 0; i < mActionListeners.Count(); i++)
1451 nsIDOMNode *ptr = *aNewLeftNode;
1452 mActionListeners[i]->DidSplitNode(aNode, aOffset, ptr, result);
1455 return result;
1459 nsresult
1460 nsEditor::JoinNodes(nsINode* aNodeToKeep, nsIContent* aNodeToMove)
1462 // We don't really need aNodeToMove's parent to be non-null -- we could just
1463 // skip adjusting any ranges in aNodeToMove's parent if there is none. But
1464 // the current implementation requires it.
1465 MOZ_ASSERT(aNodeToKeep && aNodeToMove && aNodeToMove->GetParentNode());
1466 nsresult res = JoinNodes(aNodeToKeep->AsDOMNode(), aNodeToMove->AsDOMNode(),
1467 aNodeToMove->GetParentNode()->AsDOMNode());
1468 NS_ASSERTION(NS_SUCCEEDED(res), "JoinNodes failed");
1469 NS_ENSURE_SUCCESS(res, res);
1470 return NS_OK;
1473 NS_IMETHODIMP
1474 nsEditor::JoinNodes(nsIDOMNode * aLeftNode,
1475 nsIDOMNode * aRightNode,
1476 nsIDOMNode * aParent)
1478 int32_t i;
1479 nsAutoRules beginRulesSniffing(this, EditAction::joinNode, nsIEditor::ePrevious);
1481 // remember some values; later used for saved selection updating.
1482 // find the offset between the nodes to be joined.
1483 int32_t offset = GetChildOffset(aRightNode, aParent);
1484 // find the number of children of the lefthand node
1485 uint32_t oldLeftNodeLen;
1486 nsresult result = GetLengthOfDOMNode(aLeftNode, oldLeftNodeLen);
1487 NS_ENSURE_SUCCESS(result, result);
1489 for (i = 0; i < mActionListeners.Count(); i++)
1490 mActionListeners[i]->WillJoinNodes(aLeftNode, aRightNode, aParent);
1492 nsRefPtr<JoinElementTxn> txn;
1493 result = CreateTxnForJoinNode(aLeftNode, aRightNode, getter_AddRefs(txn));
1494 if (NS_SUCCEEDED(result)) {
1495 result = DoTransaction(txn);
1498 mRangeUpdater.SelAdjJoinNodes(aLeftNode, aRightNode, aParent, offset, (int32_t)oldLeftNodeLen);
1500 for (i = 0; i < mActionListeners.Count(); i++)
1501 mActionListeners[i]->DidJoinNodes(aLeftNode, aRightNode, aParent, result);
1503 return result;
1507 NS_IMETHODIMP
1508 nsEditor::DeleteNode(nsIDOMNode* aNode)
1510 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
1511 NS_ENSURE_STATE(node);
1512 return DeleteNode(node);
1515 nsresult
1516 nsEditor::DeleteNode(nsINode* aNode)
1518 nsAutoRules beginRulesSniffing(this, EditAction::createNode, nsIEditor::ePrevious);
1520 // save node location for selection updating code.
1521 for (int32_t i = 0; i < mActionListeners.Count(); i++) {
1522 mActionListeners[i]->WillDeleteNode(aNode->AsDOMNode());
1525 nsRefPtr<DeleteNodeTxn> txn;
1526 nsresult res = CreateTxnForDeleteNode(aNode, getter_AddRefs(txn));
1527 if (NS_SUCCEEDED(res)) {
1528 res = DoTransaction(txn);
1531 for (int32_t i = 0; i < mActionListeners.Count(); i++) {
1532 mActionListeners[i]->DidDeleteNode(aNode->AsDOMNode(), res);
1535 NS_ENSURE_SUCCESS(res, res);
1536 return NS_OK;
1539 ///////////////////////////////////////////////////////////////////////////
1540 // ReplaceContainer: replace inNode with a new node (outNode) which is contructed
1541 // to be of type aNodeType. Put inNodes children into outNode.
1542 // Callers responsibility to make sure inNode's children can
1543 // go in outNode.
1544 already_AddRefed<Element>
1545 nsEditor::ReplaceContainer(Element* aOldContainer,
1546 nsIAtom* aNodeType,
1547 nsIAtom* aAttribute,
1548 const nsAString* aValue,
1549 ECloneAttributes aCloneAttributes)
1551 MOZ_ASSERT(aOldContainer && aNodeType);
1553 nsCOMPtr<nsIContent> parent = aOldContainer->GetParent();
1554 NS_ENSURE_TRUE(parent, nullptr);
1556 int32_t offset = parent->IndexOf(aOldContainer);
1558 // create new container
1559 nsCOMPtr<Element> ret = CreateHTMLContent(aNodeType);
1560 NS_ENSURE_TRUE(ret, nullptr);
1562 // set attribute if needed
1563 nsresult res;
1564 if (aAttribute && aValue && aAttribute != nsGkAtoms::_empty) {
1565 res = ret->SetAttr(kNameSpaceID_None, aAttribute, *aValue, true);
1566 NS_ENSURE_SUCCESS(res, nullptr);
1568 if (aCloneAttributes == eCloneAttributes) {
1569 CloneAttributes(ret, aOldContainer);
1572 // notify our internal selection state listener
1573 // (Note: A nsAutoSelectionReset object must be created
1574 // before calling this to initialize mRangeUpdater)
1575 AutoReplaceContainerSelNotify selStateNotify(mRangeUpdater, aOldContainer,
1576 ret);
1578 nsAutoTxnsConserveSelection conserveSelection(this);
1579 while (aOldContainer->HasChildren()) {
1580 nsCOMPtr<nsIContent> child = aOldContainer->GetFirstChild();
1582 res = DeleteNode(child);
1583 NS_ENSURE_SUCCESS(res, nullptr);
1585 res = InsertNode(*child, *ret, -1);
1586 NS_ENSURE_SUCCESS(res, nullptr);
1590 // insert new container into tree
1591 res = InsertNode(*ret, *parent, offset);
1592 NS_ENSURE_SUCCESS(res, nullptr);
1594 // delete old container
1595 res = DeleteNode(aOldContainer);
1596 NS_ENSURE_SUCCESS(res, nullptr);
1598 return ret.forget();
1601 ///////////////////////////////////////////////////////////////////////////////
1602 // RemoveContainer: remove inNode, reparenting its children (if any) into the
1603 // parent of inNode
1605 nsresult
1606 nsEditor::RemoveContainer(nsIContent* aNode)
1608 MOZ_ASSERT(aNode);
1610 nsCOMPtr<nsINode> parent = aNode->GetParentNode();
1611 NS_ENSURE_STATE(parent);
1613 int32_t offset = parent->IndexOf(aNode);
1615 // Loop through the children of inNode and promote them into inNode's parent
1616 uint32_t nodeOrigLen = aNode->GetChildCount();
1618 // notify our internal selection state listener
1619 nsAutoRemoveContainerSelNotify selNotify(mRangeUpdater, aNode, parent,
1620 offset, nodeOrigLen);
1622 while (aNode->HasChildren()) {
1623 nsCOMPtr<nsIContent> child = aNode->GetLastChild();
1624 nsresult rv = DeleteNode(child);
1625 NS_ENSURE_SUCCESS(rv, rv);
1627 rv = InsertNode(*child, *parent, offset);
1628 NS_ENSURE_SUCCESS(rv, rv);
1631 return DeleteNode(aNode);
1635 ///////////////////////////////////////////////////////////////////////////////
1636 // InsertContainerAbove: Insert a new parent for inNode, which is contructed to
1637 // be of type aNodeType. outNode becomes a child of
1638 // inNode's earlier parent. Caller's responsibility to
1639 // make sure inNode's can be child of outNode, and
1640 // outNode can be child of old parent.
1641 already_AddRefed<Element>
1642 nsEditor::InsertContainerAbove(nsIContent* aNode,
1643 nsIAtom* aNodeType,
1644 nsIAtom* aAttribute,
1645 const nsAString* aValue)
1647 MOZ_ASSERT(aNode && aNodeType);
1649 nsCOMPtr<nsIContent> parent = aNode->GetParent();
1650 NS_ENSURE_TRUE(parent, nullptr);
1651 int32_t offset = parent->IndexOf(aNode);
1653 // Create new container
1654 nsCOMPtr<Element> newContent = CreateHTMLContent(aNodeType);
1655 NS_ENSURE_TRUE(newContent, nullptr);
1657 // Set attribute if needed
1658 nsresult res;
1659 if (aAttribute && aValue && aAttribute != nsGkAtoms::_empty) {
1660 res = newContent->SetAttr(kNameSpaceID_None, aAttribute, *aValue, true);
1661 NS_ENSURE_SUCCESS(res, nullptr);
1664 // Notify our internal selection state listener
1665 nsAutoInsertContainerSelNotify selNotify(mRangeUpdater);
1667 // Put inNode in new parent, outNode
1668 res = DeleteNode(aNode);
1669 NS_ENSURE_SUCCESS(res, nullptr);
1672 nsAutoTxnsConserveSelection conserveSelection(this);
1673 res = InsertNode(*aNode, *newContent, 0);
1674 NS_ENSURE_SUCCESS(res, nullptr);
1677 // Put new parent in doc
1678 res = InsertNode(*newContent, *parent, offset);
1679 NS_ENSURE_SUCCESS(res, nullptr);
1681 return newContent.forget();
1684 ///////////////////////////////////////////////////////////////////////////
1685 // MoveNode: move aNode to {aParent,aOffset}
1686 nsresult
1687 nsEditor::MoveNode(nsIContent* aNode, nsINode* aParent, int32_t aOffset)
1689 MOZ_ASSERT(aNode);
1690 MOZ_ASSERT(aParent);
1691 MOZ_ASSERT(aOffset == -1 ||
1692 (0 <= aOffset &&
1693 AssertedCast<uint32_t>(aOffset) <= aParent->Length()));
1695 nsCOMPtr<nsINode> oldParent = aNode->GetParentNode();
1696 int32_t oldOffset = oldParent ? oldParent->IndexOf(aNode) : -1;
1698 if (aOffset == -1) {
1699 // Magic value meaning "move to end of aParent"
1700 aOffset = AssertedCast<int32_t>(aParent->Length());
1703 // Don't do anything if it's already in right place
1704 if (aParent == oldParent && aOffset == oldOffset) {
1705 return NS_OK;
1708 // Notify our internal selection state listener
1709 nsAutoMoveNodeSelNotify selNotify(mRangeUpdater, oldParent, oldOffset,
1710 aParent, aOffset);
1712 // Need to adjust aOffset if we're moving aNode later in its current parent
1713 if (aParent == oldParent && oldOffset < aOffset) {
1714 // When we delete aNode, it will make the offsets after it off by one
1715 aOffset--;
1718 // Hold a reference so aNode doesn't go away when we remove it (bug 772282)
1719 nsCOMPtr<nsINode> kungFuDeathGrip = aNode;
1721 nsresult rv = DeleteNode(aNode);
1722 NS_ENSURE_SUCCESS(rv, rv);
1724 return InsertNode(*aNode, *aParent, aOffset);
1728 NS_IMETHODIMP
1729 nsEditor::AddEditorObserver(nsIEditorObserver *aObserver)
1731 // we don't keep ownership of the observers. They must
1732 // remove themselves as observers before they are destroyed.
1734 NS_ENSURE_TRUE(aObserver, NS_ERROR_NULL_POINTER);
1736 // Make sure the listener isn't already on the list
1737 if (mEditorObservers.IndexOf(aObserver) == -1)
1739 if (!mEditorObservers.AppendObject(aObserver))
1740 return NS_ERROR_FAILURE;
1743 return NS_OK;
1747 NS_IMETHODIMP
1748 nsEditor::RemoveEditorObserver(nsIEditorObserver *aObserver)
1750 NS_ENSURE_TRUE(aObserver, NS_ERROR_FAILURE);
1752 if (!mEditorObservers.RemoveObject(aObserver))
1753 return NS_ERROR_FAILURE;
1755 return NS_OK;
1758 class EditorInputEventDispatcher : public nsRunnable
1760 public:
1761 EditorInputEventDispatcher(nsEditor* aEditor,
1762 nsIContent* aTarget,
1763 bool aIsComposing)
1764 : mEditor(aEditor)
1765 , mTarget(aTarget)
1766 , mIsComposing(aIsComposing)
1770 NS_IMETHOD Run()
1772 // Note that we don't need to check mDispatchInputEvent here. We need
1773 // to check it only when the editor requests to dispatch the input event.
1775 if (!mTarget->IsInDoc()) {
1776 return NS_OK;
1779 nsCOMPtr<nsIPresShell> ps = mEditor->GetPresShell();
1780 if (!ps) {
1781 return NS_OK;
1784 nsCOMPtr<nsIWidget> widget = mEditor->GetWidget();
1785 if (!widget) {
1786 return NS_OK;
1789 // Even if the change is caused by untrusted event, we need to dispatch
1790 // trusted input event since it's a fact.
1791 InternalEditorInputEvent inputEvent(true, NS_EDITOR_INPUT, widget);
1792 inputEvent.time = static_cast<uint64_t>(PR_Now() / 1000);
1793 inputEvent.mIsComposing = mIsComposing;
1794 nsEventStatus status = nsEventStatus_eIgnore;
1795 nsresult rv =
1796 ps->HandleEventWithTarget(&inputEvent, nullptr, mTarget, &status);
1797 NS_ENSURE_SUCCESS(rv, NS_OK); // print the warning if error
1798 return NS_OK;
1801 private:
1802 nsRefPtr<nsEditor> mEditor;
1803 nsCOMPtr<nsIContent> mTarget;
1804 bool mIsComposing;
1807 void
1808 nsEditor::NotifyEditorObservers(NotificationForEditorObservers aNotification)
1810 switch (aNotification) {
1811 case eNotifyEditorObserversOfEnd:
1812 mIsInEditAction = false;
1813 for (int32_t i = 0; i < mEditorObservers.Count(); i++) {
1814 mEditorObservers[i]->EditAction();
1817 if (!mDispatchInputEvent) {
1818 return;
1821 FireInputEvent();
1822 break;
1823 case eNotifyEditorObserversOfBefore:
1824 mIsInEditAction = true;
1825 for (int32_t i = 0; i < mEditorObservers.Count(); i++) {
1826 mEditorObservers[i]->BeforeEditAction();
1828 break;
1829 case eNotifyEditorObserversOfCancel:
1830 mIsInEditAction = false;
1831 for (int32_t i = 0; i < mEditorObservers.Count(); i++) {
1832 mEditorObservers[i]->CancelEditAction();
1834 break;
1835 default:
1836 MOZ_CRASH("Handle all notifications here");
1837 break;
1841 void
1842 nsEditor::FireInputEvent()
1844 // We don't need to dispatch multiple input events if there is a pending
1845 // input event. However, it may have different event target. If we resolved
1846 // this issue, we need to manage the pending events in an array. But it's
1847 // overwork. We don't need to do it for the very rare case.
1849 nsCOMPtr<nsIContent> target = GetInputEventTargetContent();
1850 NS_ENSURE_TRUE_VOID(target);
1852 // NOTE: Don't refer IsIMEComposing() because it returns false even before
1853 // compositionend. However, DOM Level 3 Events defines it should be
1854 // true after compositionstart and before compositionend.
1855 nsContentUtils::AddScriptRunner(
1856 new EditorInputEventDispatcher(this, target, !!GetComposition()));
1859 NS_IMETHODIMP
1860 nsEditor::AddEditActionListener(nsIEditActionListener *aListener)
1862 NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
1864 // Make sure the listener isn't already on the list
1865 if (mActionListeners.IndexOf(aListener) == -1)
1867 if (!mActionListeners.AppendObject(aListener))
1868 return NS_ERROR_FAILURE;
1871 return NS_OK;
1875 NS_IMETHODIMP
1876 nsEditor::RemoveEditActionListener(nsIEditActionListener *aListener)
1878 NS_ENSURE_TRUE(aListener, NS_ERROR_FAILURE);
1880 if (!mActionListeners.RemoveObject(aListener))
1881 return NS_ERROR_FAILURE;
1883 return NS_OK;
1887 NS_IMETHODIMP
1888 nsEditor::AddDocumentStateListener(nsIDocumentStateListener *aListener)
1890 NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
1892 if (mDocStateListeners.IndexOf(aListener) == -1)
1894 if (!mDocStateListeners.AppendObject(aListener))
1895 return NS_ERROR_FAILURE;
1898 return NS_OK;
1902 NS_IMETHODIMP
1903 nsEditor::RemoveDocumentStateListener(nsIDocumentStateListener *aListener)
1905 NS_ENSURE_TRUE(aListener, NS_ERROR_NULL_POINTER);
1907 if (!mDocStateListeners.RemoveObject(aListener))
1908 return NS_ERROR_FAILURE;
1910 return NS_OK;
1914 NS_IMETHODIMP nsEditor::OutputToString(const nsAString& aFormatType,
1915 uint32_t aFlags,
1916 nsAString& aOutputString)
1918 // these should be implemented by derived classes.
1919 return NS_ERROR_NOT_IMPLEMENTED;
1922 NS_IMETHODIMP
1923 nsEditor::OutputToStream(nsIOutputStream* aOutputStream,
1924 const nsAString& aFormatType,
1925 const nsACString& aCharsetOverride,
1926 uint32_t aFlags)
1928 // these should be implemented by derived classes.
1929 return NS_ERROR_NOT_IMPLEMENTED;
1932 NS_IMETHODIMP
1933 nsEditor::DumpContentTree()
1935 #ifdef DEBUG
1936 if (mRootElement) {
1937 mRootElement->List(stdout);
1939 #endif
1940 return NS_OK;
1944 NS_IMETHODIMP
1945 nsEditor::DebugDumpContent()
1947 #ifdef DEBUG
1948 nsCOMPtr<nsIDOMHTMLDocument> doc = do_QueryReferent(mDocWeak);
1949 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
1951 nsCOMPtr<nsIDOMHTMLElement>bodyElem;
1952 doc->GetBody(getter_AddRefs(bodyElem));
1953 nsCOMPtr<nsIContent> content = do_QueryInterface(bodyElem);
1954 if (content)
1955 content->List();
1956 #endif
1957 return NS_OK;
1961 NS_IMETHODIMP
1962 nsEditor::DebugUnitTests(int32_t *outNumTests, int32_t *outNumTestsFailed)
1964 #ifdef DEBUG
1965 NS_NOTREACHED("This should never get called. Overridden by subclasses");
1966 #endif
1967 return NS_OK;
1971 bool
1972 nsEditor::ArePreservingSelection()
1974 return !(mSavedSel.IsEmpty());
1977 void
1978 nsEditor::PreserveSelectionAcrossActions(Selection* aSel)
1980 mSavedSel.SaveSelection(aSel);
1981 mRangeUpdater.RegisterSelectionState(mSavedSel);
1984 nsresult
1985 nsEditor::RestorePreservedSelection(nsISelection *aSel)
1987 if (mSavedSel.IsEmpty()) return NS_ERROR_FAILURE;
1988 mSavedSel.RestoreSelection(aSel);
1989 StopPreservingSelection();
1990 return NS_OK;
1993 void
1994 nsEditor::StopPreservingSelection()
1996 mRangeUpdater.DropSelectionState(mSavedSel);
1997 mSavedSel.MakeEmpty();
2000 void
2001 nsEditor::EnsureComposition(mozilla::WidgetGUIEvent* aEvent)
2003 if (mComposition) {
2004 return;
2006 // The compositionstart event must cause creating new TextComposition
2007 // instance at being dispatched by IMEStateManager.
2008 mComposition = IMEStateManager::GetTextCompositionFor(aEvent);
2009 if (!mComposition) {
2010 MOZ_CRASH("IMEStateManager doesn't return proper composition");
2012 mComposition->StartHandlingComposition(this);
2015 nsresult
2016 nsEditor::BeginIMEComposition(WidgetCompositionEvent* aCompositionEvent)
2018 MOZ_ASSERT(!mComposition, "There is composition already");
2019 EnsureComposition(aCompositionEvent);
2020 if (mPhonetic) {
2021 mPhonetic->Truncate(0);
2023 return NS_OK;
2026 void
2027 nsEditor::EndIMEComposition()
2029 NS_ENSURE_TRUE_VOID(mComposition); // nothing to do
2031 // commit the IME transaction..we can get at it via the transaction mgr.
2032 // Note that this means IME won't work without an undo stack!
2033 if (mTxnMgr) {
2034 nsCOMPtr<nsITransaction> txn = mTxnMgr->PeekUndoStack();
2035 nsCOMPtr<nsIAbsorbingTransaction> plcTxn = do_QueryInterface(txn);
2036 if (plcTxn) {
2037 DebugOnly<nsresult> rv = plcTxn->Commit();
2038 NS_ASSERTION(NS_SUCCEEDED(rv),
2039 "nsIAbsorbingTransaction::Commit() failed");
2043 /* reset the data we need to construct a transaction */
2044 mIMETextNode = nullptr;
2045 mIMETextOffset = 0;
2046 mComposition->EndHandlingComposition(this);
2047 mComposition = nullptr;
2049 // notify editor observers of action
2050 NotifyEditorObservers(eNotifyEditorObserversOfEnd);
2054 NS_IMETHODIMP
2055 nsEditor::GetPhonetic(nsAString& aPhonetic)
2057 if (mPhonetic)
2058 aPhonetic = *mPhonetic;
2059 else
2060 aPhonetic.Truncate(0);
2062 return NS_OK;
2065 NS_IMETHODIMP
2066 nsEditor::ForceCompositionEnd()
2068 nsCOMPtr<nsIPresShell> ps = GetPresShell();
2069 if (!ps) {
2070 return NS_ERROR_NOT_AVAILABLE;
2072 nsPresContext* pc = ps->GetPresContext();
2073 if (!pc) {
2074 return NS_ERROR_NOT_AVAILABLE;
2077 if (!mComposition) {
2078 // XXXmnakano see bug 558976, ResetInputState() has two meaning which are
2079 // "commit the composition" and "cursor is moved". This method name is
2080 // "ForceCompositionEnd", so, ResetInputState() should be used only for the
2081 // former here. However, ResetInputState() is also used for the latter here
2082 // because even if we don't have composition, we call ResetInputState() on
2083 // Linux. Currently, nsGtkIMModule can know the timing of the cursor move,
2084 // so, the latter meaning should be gone.
2085 // XXX This may commit a composition in another editor.
2086 return IMEStateManager::NotifyIME(NOTIFY_IME_OF_CURSOR_POS_CHANGED, pc);
2089 return IMEStateManager::NotifyIME(REQUEST_TO_COMMIT_COMPOSITION, pc);
2092 NS_IMETHODIMP
2093 nsEditor::GetPreferredIMEState(IMEState *aState)
2095 NS_ENSURE_ARG_POINTER(aState);
2096 aState->mEnabled = IMEState::ENABLED;
2097 aState->mOpen = IMEState::DONT_CHANGE_OPEN_STATE;
2099 if (IsReadonly() || IsDisabled()) {
2100 aState->mEnabled = IMEState::DISABLED;
2101 return NS_OK;
2104 nsCOMPtr<nsIContent> content = GetRoot();
2105 NS_ENSURE_TRUE(content, NS_ERROR_FAILURE);
2107 nsIFrame* frame = content->GetPrimaryFrame();
2108 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
2110 switch (frame->StyleUIReset()->mIMEMode) {
2111 case NS_STYLE_IME_MODE_AUTO:
2112 if (IsPasswordEditor())
2113 aState->mEnabled = IMEState::PASSWORD;
2114 break;
2115 case NS_STYLE_IME_MODE_DISABLED:
2116 // we should use password state for |ime-mode: disabled;|.
2117 aState->mEnabled = IMEState::PASSWORD;
2118 break;
2119 case NS_STYLE_IME_MODE_ACTIVE:
2120 aState->mOpen = IMEState::OPEN;
2121 break;
2122 case NS_STYLE_IME_MODE_INACTIVE:
2123 aState->mOpen = IMEState::CLOSED;
2124 break;
2127 return NS_OK;
2130 NS_IMETHODIMP
2131 nsEditor::GetComposing(bool* aResult)
2133 NS_ENSURE_ARG_POINTER(aResult);
2134 *aResult = IsIMEComposing();
2135 return NS_OK;
2139 /* Non-interface, public methods */
2141 NS_IMETHODIMP
2142 nsEditor::GetRootElement(nsIDOMElement **aRootElement)
2144 NS_ENSURE_ARG_POINTER(aRootElement);
2145 NS_ENSURE_TRUE(mRootElement, NS_ERROR_NOT_AVAILABLE);
2146 nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(mRootElement);
2147 rootElement.forget(aRootElement);
2148 return NS_OK;
2152 /** All editor operations which alter the doc should be prefaced
2153 * with a call to StartOperation, naming the action and direction */
2154 NS_IMETHODIMP
2155 nsEditor::StartOperation(EditAction opID, nsIEditor::EDirection aDirection)
2157 mAction = opID;
2158 mDirection = aDirection;
2159 return NS_OK;
2163 /** All editor operations which alter the doc should be followed
2164 * with a call to EndOperation */
2165 NS_IMETHODIMP
2166 nsEditor::EndOperation()
2168 mAction = EditAction::none;
2169 mDirection = eNone;
2170 return NS_OK;
2173 NS_IMETHODIMP
2174 nsEditor::CloneAttribute(const nsAString & aAttribute,
2175 nsIDOMNode *aDestNode, nsIDOMNode *aSourceNode)
2177 NS_ENSURE_TRUE(aDestNode && aSourceNode, NS_ERROR_NULL_POINTER);
2179 nsCOMPtr<nsIDOMElement> destElement = do_QueryInterface(aDestNode);
2180 nsCOMPtr<nsIDOMElement> sourceElement = do_QueryInterface(aSourceNode);
2181 NS_ENSURE_TRUE(destElement && sourceElement, NS_ERROR_NO_INTERFACE);
2183 nsAutoString attrValue;
2184 bool isAttrSet;
2185 nsresult rv = GetAttributeValue(sourceElement,
2186 aAttribute,
2187 attrValue,
2188 &isAttrSet);
2189 NS_ENSURE_SUCCESS(rv, rv);
2190 if (isAttrSet)
2191 rv = SetAttribute(destElement, aAttribute, attrValue);
2192 else
2193 rv = RemoveAttribute(destElement, aAttribute);
2195 return rv;
2198 // Objects must be DOM elements
2199 NS_IMETHODIMP
2200 nsEditor::CloneAttributes(nsIDOMNode* aDest, nsIDOMNode* aSource)
2202 NS_ENSURE_TRUE(aDest && aSource, NS_ERROR_NULL_POINTER);
2204 nsCOMPtr<Element> dest = do_QueryInterface(aDest);
2205 nsCOMPtr<Element> source = do_QueryInterface(aSource);
2206 NS_ENSURE_TRUE(dest && source, NS_ERROR_NO_INTERFACE);
2208 CloneAttributes(dest, source);
2210 return NS_OK;
2213 void
2214 nsEditor::CloneAttributes(Element* aDest, Element* aSource)
2216 MOZ_ASSERT(aDest && aSource);
2218 nsAutoEditBatch beginBatching(this);
2220 // Use transaction system for undo only if destination is already in the
2221 // document
2222 NS_ENSURE_TRUE(GetRoot(), );
2223 bool destInBody = GetRoot()->Contains(aDest);
2225 // Clear existing attributes
2226 nsRefPtr<nsDOMAttributeMap> destAttributes = aDest->Attributes();
2227 while (nsRefPtr<Attr> attr = destAttributes->Item(0)) {
2228 if (destInBody) {
2229 RemoveAttribute(static_cast<nsIDOMElement*>(GetAsDOMNode(aDest)),
2230 attr->NodeName());
2231 } else {
2232 ErrorResult ignored;
2233 aDest->RemoveAttribute(attr->NodeName(), ignored);
2237 // Set just the attributes that the source element has
2238 nsRefPtr<nsDOMAttributeMap> sourceAttributes = aSource->Attributes();
2239 uint32_t sourceCount = sourceAttributes->Length();
2240 for (uint32_t i = 0; i < sourceCount; i++) {
2241 nsRefPtr<Attr> attr = sourceAttributes->Item(i);
2242 nsAutoString value;
2243 attr->GetValue(value);
2244 if (destInBody) {
2245 SetAttributeOrEquivalent(static_cast<nsIDOMElement*>(GetAsDOMNode(aDest)),
2246 attr->NodeName(), value, false);
2247 } else {
2248 // The element is not inserted in the document yet, we don't want to put
2249 // a transaction on the UndoStack
2250 SetAttributeOrEquivalent(static_cast<nsIDOMElement*>(GetAsDOMNode(aDest)),
2251 attr->NodeName(), value, true);
2257 NS_IMETHODIMP nsEditor::ScrollSelectionIntoView(bool aScrollToAnchor)
2259 nsCOMPtr<nsISelectionController> selCon;
2260 if (NS_SUCCEEDED(GetSelectionController(getter_AddRefs(selCon))) && selCon)
2262 int16_t region = nsISelectionController::SELECTION_FOCUS_REGION;
2264 if (aScrollToAnchor)
2265 region = nsISelectionController::SELECTION_ANCHOR_REGION;
2267 selCon->ScrollSelectionIntoView(nsISelectionController::SELECTION_NORMAL,
2268 region, nsISelectionController::SCROLL_OVERFLOW_HIDDEN);
2271 return NS_OK;
2274 NS_IMETHODIMP
2275 nsEditor::InsertTextImpl(const nsAString& aStringToInsert,
2276 nsCOMPtr<nsIDOMNode>* aInOutNode,
2277 int32_t* aInOutOffset,
2278 nsIDOMDocument* aDoc)
2280 // NOTE: caller *must* have already used nsAutoTxnsConserveSelection
2281 // stack-based class to turn off txn selection updating. Caller also turned
2282 // on rules sniffing if desired.
2284 NS_ENSURE_TRUE(aInOutNode && *aInOutNode && aInOutOffset && aDoc,
2285 NS_ERROR_NULL_POINTER);
2286 if (!mComposition && aStringToInsert.IsEmpty()) {
2287 return NS_OK;
2290 nsCOMPtr<nsINode> node = do_QueryInterface(*aInOutNode);
2291 NS_ENSURE_STATE(node);
2292 uint32_t offset = static_cast<uint32_t>(*aInOutOffset);
2294 if (!node->IsNodeOfType(nsINode::eTEXT) && IsPlaintextEditor()) {
2295 nsCOMPtr<nsINode> root = GetRoot();
2296 // In some cases, node is the anonymous DIV, and offset is 0. To avoid
2297 // injecting unneeded text nodes, we first look to see if we have one
2298 // available. In that case, we'll just adjust node and offset accordingly.
2299 if (node == root && offset == 0 && node->HasChildren() &&
2300 node->GetFirstChild()->IsNodeOfType(nsINode::eTEXT)) {
2301 node = node->GetFirstChild();
2303 // In some other cases, node is the anonymous DIV, and offset points to the
2304 // terminating mozBR. In that case, we'll adjust aInOutNode and
2305 // aInOutOffset to the preceding text node, if any.
2306 if (node == root && offset > 0 && node->GetChildAt(offset - 1) &&
2307 node->GetChildAt(offset - 1)->IsNodeOfType(nsINode::eTEXT)) {
2308 node = node->GetChildAt(offset - 1);
2309 offset = node->Length();
2311 // Sometimes, node is the mozBR element itself. In that case, we'll adjust
2312 // the insertion point to the previous text node, if one exists, or to the
2313 // parent anonymous DIV.
2314 if (nsTextEditUtils::IsMozBR(node) && offset == 0) {
2315 if (node->GetPreviousSibling() &&
2316 node->GetPreviousSibling()->IsNodeOfType(nsINode::eTEXT)) {
2317 node = node->GetPreviousSibling();
2318 offset = node->Length();
2319 } else if (node->GetParentNode() && node->GetParentNode() == root) {
2320 node = node->GetParentNode();
2325 nsresult res;
2326 if (mComposition) {
2327 if (!node->IsNodeOfType(nsINode::eTEXT)) {
2328 // create a text node
2329 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
2330 NS_ENSURE_STATE(doc);
2331 nsRefPtr<nsTextNode> newNode = doc->CreateTextNode(EmptyString());
2332 // then we insert it into the dom tree
2333 res = InsertNode(newNode->AsDOMNode(), node->AsDOMNode(), offset);
2334 NS_ENSURE_SUCCESS(res, res);
2335 node = newNode;
2336 offset = 0;
2338 res = InsertTextIntoTextNodeImpl(aStringToInsert, *node->GetAsText(),
2339 offset);
2340 NS_ENSURE_SUCCESS(res, res);
2341 offset += aStringToInsert.Length();
2342 } else {
2343 if (node->IsNodeOfType(nsINode::eTEXT)) {
2344 // we are inserting text into an existing text node.
2345 res = InsertTextIntoTextNodeImpl(aStringToInsert, *node->GetAsText(),
2346 offset);
2347 NS_ENSURE_SUCCESS(res, res);
2348 offset += aStringToInsert.Length();
2349 } else {
2350 // we are inserting text into a non-text node. first we have to create a
2351 // textnode (this also populates it with the text)
2352 nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
2353 NS_ENSURE_STATE(doc);
2354 nsRefPtr<nsTextNode> newNode = doc->CreateTextNode(aStringToInsert);
2355 // then we insert it into the dom tree
2356 res = InsertNode(newNode->AsDOMNode(), node->AsDOMNode(), offset);
2357 NS_ENSURE_SUCCESS(res, res);
2358 node = newNode;
2359 offset = aStringToInsert.Length();
2363 *aInOutNode = node->AsDOMNode();
2364 *aInOutOffset = static_cast<int32_t>(offset);
2365 return NS_OK;
2369 nsresult
2370 nsEditor::InsertTextIntoTextNodeImpl(const nsAString& aStringToInsert,
2371 Text& aTextNode,
2372 int32_t aOffset, bool aSuppressIME)
2374 nsRefPtr<EditTxn> txn;
2375 bool isIMETransaction = false;
2376 // aSuppressIME is used when editor must insert text, yet this text is not
2377 // part of the current IME operation. Example: adjusting whitespace around an
2378 // IME insertion.
2379 if (mComposition && !aSuppressIME) {
2380 if (!mIMETextNode) {
2381 mIMETextNode = &aTextNode;
2382 mIMETextOffset = aOffset;
2384 // Modify mPhonetic with raw text input clauses.
2385 const TextRangeArray* ranges = mComposition->GetRanges();
2386 for (uint32_t i = 0; i < (ranges ? ranges->Length() : 0); ++i) {
2387 const TextRange& textRange = ranges->ElementAt(i);
2388 if (!textRange.Length() ||
2389 textRange.mRangeType != NS_TEXTRANGE_RAWINPUT) {
2390 continue;
2392 if (!mPhonetic) {
2393 mPhonetic = new nsString();
2395 nsAutoString stringToInsert(aStringToInsert);
2396 stringToInsert.Mid(*mPhonetic,
2397 textRange.mStartOffset, textRange.Length());
2400 txn = CreateTxnForIMEText(aStringToInsert);
2401 isIMETransaction = true;
2402 } else {
2403 txn = CreateTxnForInsertText(aStringToInsert, aTextNode, aOffset);
2406 // Let listeners know what's up
2407 for (int32_t i = 0; i < mActionListeners.Count(); i++) {
2408 mActionListeners[i]->WillInsertText(
2409 static_cast<nsIDOMCharacterData*>(aTextNode.AsDOMNode()), aOffset,
2410 aStringToInsert);
2413 // XXX We may not need these view batches anymore. This is handled at a
2414 // higher level now I believe.
2415 BeginUpdateViewBatch();
2416 nsresult res = DoTransaction(txn);
2417 EndUpdateViewBatch();
2419 mRangeUpdater.SelAdjInsertText(aTextNode, aOffset, aStringToInsert);
2421 // let listeners know what happened
2422 for (int32_t i = 0; i < mActionListeners.Count(); i++) {
2423 mActionListeners[i]->DidInsertText(
2424 static_cast<nsIDOMCharacterData*>(aTextNode.AsDOMNode()),
2425 aOffset, aStringToInsert, res);
2428 // Added some cruft here for bug 43366. Layout was crashing because we left
2429 // an empty text node lying around in the document. So I delete empty text
2430 // nodes caused by IME. I have to mark the IME transaction as "fixed", which
2431 // means that furure IME txns won't merge with it. This is because we don't
2432 // want future IME txns trying to put their text into a node that is no
2433 // longer in the document. This does not break undo/redo, because all these
2434 // txns are wrapped in a parent PlaceHolder txn, and placeholder txns are
2435 // already savvy to having multiple ime txns inside them.
2437 // Delete empty IME text node if there is one
2438 if (isIMETransaction && mIMETextNode) {
2439 uint32_t len = mIMETextNode->Length();
2440 if (!len) {
2441 DeleteNode(mIMETextNode);
2442 mIMETextNode = nullptr;
2443 static_cast<IMETextTxn*>(txn.get())->MarkFixed();
2447 return res;
2451 NS_IMETHODIMP nsEditor::SelectEntireDocument(nsISelection *aSelection)
2453 if (!aSelection) { return NS_ERROR_NULL_POINTER; }
2455 nsCOMPtr<nsIDOMElement> rootElement = do_QueryInterface(GetRoot());
2456 if (!rootElement) { return NS_ERROR_NOT_INITIALIZED; }
2458 return aSelection->SelectAllChildren(rootElement);
2462 nsINode*
2463 nsEditor::GetFirstEditableNode(nsINode* aRoot)
2465 MOZ_ASSERT(aRoot);
2467 nsIContent* node = GetLeftmostChild(aRoot);
2468 if (node && !IsEditable(node)) {
2469 node = GetNextNode(node, /* aEditableNode = */ true);
2472 return (node != aRoot) ? node : nullptr;
2476 NS_IMETHODIMP
2477 nsEditor::NotifyDocumentListeners(TDocumentListenerNotification aNotificationType)
2479 int32_t numListeners = mDocStateListeners.Count();
2480 if (!numListeners) // maybe there just aren't any.
2481 return NS_OK;
2483 nsCOMArray<nsIDocumentStateListener> listeners(mDocStateListeners);
2484 nsresult rv = NS_OK;
2485 int32_t i;
2487 switch (aNotificationType)
2489 case eDocumentCreated:
2490 for (i = 0; i < numListeners;i++)
2492 rv = listeners[i]->NotifyDocumentCreated();
2493 if (NS_FAILED(rv))
2494 break;
2496 break;
2498 case eDocumentToBeDestroyed:
2499 for (i = 0; i < numListeners;i++)
2501 rv = listeners[i]->NotifyDocumentWillBeDestroyed();
2502 if (NS_FAILED(rv))
2503 break;
2505 break;
2507 case eDocumentStateChanged:
2509 bool docIsDirty;
2510 rv = GetDocumentModified(&docIsDirty);
2511 NS_ENSURE_SUCCESS(rv, rv);
2513 if (static_cast<int8_t>(docIsDirty) == mDocDirtyState)
2514 return NS_OK;
2516 mDocDirtyState = docIsDirty;
2518 for (i = 0; i < numListeners;i++)
2520 rv = listeners[i]->NotifyDocumentStateChanged(mDocDirtyState);
2521 if (NS_FAILED(rv))
2522 break;
2525 break;
2527 default:
2528 NS_NOTREACHED("Unknown notification");
2531 return rv;
2535 already_AddRefed<InsertTextTxn>
2536 nsEditor::CreateTxnForInsertText(const nsAString& aStringToInsert,
2537 Text& aTextNode, int32_t aOffset)
2539 nsRefPtr<InsertTextTxn> txn = new InsertTextTxn(aTextNode, aOffset,
2540 aStringToInsert, *this);
2541 return txn.forget();
2545 nsresult
2546 nsEditor::DeleteText(nsGenericDOMDataNode& aCharData, uint32_t aOffset,
2547 uint32_t aLength)
2549 nsRefPtr<DeleteTextTxn> txn =
2550 CreateTxnForDeleteText(aCharData, aOffset, aLength);
2551 NS_ENSURE_STATE(txn);
2553 nsAutoRules beginRulesSniffing(this, EditAction::deleteText, nsIEditor::ePrevious);
2555 // Let listeners know what's up
2556 for (int32_t i = 0; i < mActionListeners.Count(); i++) {
2557 mActionListeners[i]->WillDeleteText(
2558 static_cast<nsIDOMCharacterData*>(GetAsDOMNode(&aCharData)), aOffset,
2559 aLength);
2562 nsresult res = DoTransaction(txn);
2564 // Let listeners know what happened
2565 for (int32_t i = 0; i < mActionListeners.Count(); i++) {
2566 mActionListeners[i]->DidDeleteText(
2567 static_cast<nsIDOMCharacterData*>(GetAsDOMNode(&aCharData)), aOffset,
2568 aLength, res);
2571 return res;
2575 already_AddRefed<DeleteTextTxn>
2576 nsEditor::CreateTxnForDeleteText(nsGenericDOMDataNode& aCharData,
2577 uint32_t aOffset, uint32_t aLength)
2579 nsRefPtr<DeleteTextTxn> txn =
2580 new DeleteTextTxn(*this, aCharData, aOffset, aLength, &mRangeUpdater);
2582 nsresult res = txn->Init();
2583 NS_ENSURE_SUCCESS(res, nullptr);
2585 return txn.forget();
2591 NS_IMETHODIMP nsEditor::CreateTxnForSplitNode(nsIDOMNode *aNode,
2592 uint32_t aOffset,
2593 SplitElementTxn **aTxn)
2595 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
2597 nsRefPtr<SplitElementTxn> txn = new SplitElementTxn();
2599 nsresult rv = txn->Init(this, aNode, aOffset);
2600 if (NS_SUCCEEDED(rv))
2602 txn.forget(aTxn);
2605 return rv;
2608 NS_IMETHODIMP nsEditor::CreateTxnForJoinNode(nsIDOMNode *aLeftNode,
2609 nsIDOMNode *aRightNode,
2610 JoinElementTxn **aTxn)
2612 NS_ENSURE_TRUE(aLeftNode && aRightNode, NS_ERROR_NULL_POINTER);
2614 nsRefPtr<JoinElementTxn> txn = new JoinElementTxn();
2616 nsresult rv = txn->Init(this, aLeftNode, aRightNode);
2617 if (NS_SUCCEEDED(rv))
2619 txn.forget(aTxn);
2622 return rv;
2626 // END nsEditor core implementation
2629 // BEGIN nsEditor public helper methods
2631 nsresult
2632 nsEditor::SplitNodeImpl(nsIDOMNode * aExistingRightNode,
2633 int32_t aOffset,
2634 nsIDOMNode* aNewLeftNode,
2635 nsIDOMNode* aParent)
2637 NS_ASSERTION(((nullptr!=aExistingRightNode) &&
2638 (nullptr!=aNewLeftNode) &&
2639 (nullptr!=aParent)),
2640 "null arg");
2641 nsresult result;
2642 if ((nullptr!=aExistingRightNode) &&
2643 (nullptr!=aNewLeftNode) &&
2644 (nullptr!=aParent))
2646 // get selection
2647 nsCOMPtr<nsISelection> selection;
2648 result = GetSelection(getter_AddRefs(selection));
2649 NS_ENSURE_SUCCESS(result, result);
2650 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2652 // remember some selection points
2653 nsCOMPtr<nsIDOMNode> selStartNode, selEndNode;
2654 int32_t selStartOffset, selEndOffset;
2655 result = GetStartNodeAndOffset(selection, getter_AddRefs(selStartNode), &selStartOffset);
2656 if (NS_FAILED(result)) selStartNode = nullptr; // if selection is cleared, remember that
2657 result = GetEndNodeAndOffset(selection, getter_AddRefs(selEndNode), &selEndOffset);
2658 if (NS_FAILED(result)) selStartNode = nullptr; // if selection is cleared, remember that
2660 nsCOMPtr<nsIDOMNode> resultNode;
2661 result = aParent->InsertBefore(aNewLeftNode, aExistingRightNode, getter_AddRefs(resultNode));
2662 //printf(" after insert\n"); content->List(); // DEBUG
2663 if (NS_SUCCEEDED(result))
2665 // split the children between the 2 nodes
2666 // at this point, aExistingRightNode has all the children
2667 // move all the children whose index is < aOffset to aNewLeftNode
2668 if (0<=aOffset) // don't bother unless we're going to move at least one child
2670 // if it's a text node, just shuffle around some text
2671 nsCOMPtr<nsIDOMCharacterData> rightNodeAsText( do_QueryInterface(aExistingRightNode) );
2672 nsCOMPtr<nsIDOMCharacterData> leftNodeAsText( do_QueryInterface(aNewLeftNode) );
2673 if (leftNodeAsText && rightNodeAsText)
2675 // fix right node
2676 nsAutoString leftText;
2677 rightNodeAsText->SubstringData(0, aOffset, leftText);
2678 rightNodeAsText->DeleteData(0, aOffset);
2679 // fix left node
2680 leftNodeAsText->SetData(leftText);
2681 // moose
2683 else
2684 { // otherwise it's an interior node, so shuffle around the children
2685 // go through list backwards so deletes don't interfere with the iteration
2686 nsCOMPtr<nsIDOMNodeList> childNodes;
2687 result = aExistingRightNode->GetChildNodes(getter_AddRefs(childNodes));
2688 if ((NS_SUCCEEDED(result)) && (childNodes))
2690 int32_t i=aOffset-1;
2691 for ( ; ((NS_SUCCEEDED(result)) && (0<=i)); i--)
2693 nsCOMPtr<nsIDOMNode> childNode;
2694 result = childNodes->Item(i, getter_AddRefs(childNode));
2695 if ((NS_SUCCEEDED(result)) && (childNode))
2697 result = aExistingRightNode->RemoveChild(childNode, getter_AddRefs(resultNode));
2698 //printf(" after remove\n"); content->List(); // DEBUG
2699 if (NS_SUCCEEDED(result))
2701 nsCOMPtr<nsIDOMNode> firstChild;
2702 aNewLeftNode->GetFirstChild(getter_AddRefs(firstChild));
2703 result = aNewLeftNode->InsertBefore(childNode, firstChild, getter_AddRefs(resultNode));
2704 //printf(" after append\n"); content->List(); // DEBUG
2710 // handle selection
2711 nsCOMPtr<nsIPresShell> ps = GetPresShell();
2712 if (ps)
2713 ps->FlushPendingNotifications(Flush_Frames);
2715 if (GetShouldTxnSetSelection())
2717 // editor wants us to set selection at split point
2718 selection->Collapse(aNewLeftNode, aOffset);
2720 else if (selStartNode)
2722 // else adjust the selection if needed. if selStartNode is null, then there was no selection.
2723 // HACK: this is overly simplified - multi-range selections need more work than this
2724 if (selStartNode.get() == aExistingRightNode)
2726 if (selStartOffset < aOffset)
2728 selStartNode = aNewLeftNode;
2730 else
2732 selStartOffset -= aOffset;
2735 if (selEndNode.get() == aExistingRightNode)
2737 if (selEndOffset < aOffset)
2739 selEndNode = aNewLeftNode;
2741 else
2743 selEndOffset -= aOffset;
2746 selection->Collapse(selStartNode,selStartOffset);
2747 selection->Extend(selEndNode,selEndOffset);
2752 else
2753 result = NS_ERROR_INVALID_ARG;
2755 return result;
2758 nsresult
2759 nsEditor::JoinNodesImpl(nsINode* aNodeToKeep,
2760 nsINode* aNodeToJoin,
2761 nsINode* aParent)
2763 MOZ_ASSERT(aNodeToKeep);
2764 MOZ_ASSERT(aNodeToJoin);
2765 MOZ_ASSERT(aParent);
2767 nsRefPtr<Selection> selection = GetSelection();
2768 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
2770 // remember some selection points
2771 nsCOMPtr<nsINode> selStartNode;
2772 int32_t selStartOffset;
2773 nsresult result = GetStartNodeAndOffset(selection, getter_AddRefs(selStartNode), &selStartOffset);
2774 if (NS_FAILED(result)) {
2775 selStartNode = nullptr;
2778 nsCOMPtr<nsINode> selEndNode;
2779 int32_t selEndOffset;
2780 result = GetEndNodeAndOffset(selection, getter_AddRefs(selEndNode), &selEndOffset);
2781 // Joe or Kin should comment here on why the following line is not a copy/paste error
2782 if (NS_FAILED(result)) {
2783 selStartNode = nullptr;
2786 uint32_t firstNodeLength = aNodeToJoin->Length();
2788 int32_t joinOffset;
2789 GetNodeLocation(aNodeToJoin, &joinOffset);
2790 int32_t keepOffset;
2791 nsINode* parent = GetNodeLocation(aNodeToKeep, &keepOffset);
2793 // if selection endpoint is between the nodes, remember it as being
2794 // in the one that is going away instead. This simplifies later selection
2795 // adjustment logic at end of this method.
2796 if (selStartNode) {
2797 if (selStartNode == parent &&
2798 joinOffset < selStartOffset && selStartOffset <= keepOffset) {
2799 selStartNode = aNodeToJoin;
2800 selStartOffset = firstNodeLength;
2802 if (selEndNode == parent &&
2803 joinOffset < selEndOffset && selEndOffset <= keepOffset) {
2804 selEndNode = aNodeToJoin;
2805 selEndOffset = firstNodeLength;
2809 // ok, ready to do join now.
2810 // if it's a text node, just shuffle around some text
2811 nsCOMPtr<nsIDOMCharacterData> keepNodeAsText( do_QueryInterface(aNodeToKeep) );
2812 nsCOMPtr<nsIDOMCharacterData> joinNodeAsText( do_QueryInterface(aNodeToJoin) );
2813 if (keepNodeAsText && joinNodeAsText) {
2814 nsAutoString rightText;
2815 nsAutoString leftText;
2816 keepNodeAsText->GetData(rightText);
2817 joinNodeAsText->GetData(leftText);
2818 leftText += rightText;
2819 keepNodeAsText->SetData(leftText);
2820 } else {
2821 // otherwise it's an interior node, so shuffle around the children
2822 nsCOMPtr<nsINodeList> childNodes = aNodeToJoin->ChildNodes();
2823 MOZ_ASSERT(childNodes);
2825 // remember the first child in aNodeToKeep, we'll insert all the children of aNodeToJoin in front of it
2826 // GetFirstChild returns nullptr firstNode if aNodeToKeep has no children, that's ok.
2827 nsCOMPtr<nsIContent> firstNode = aNodeToKeep->GetFirstChild();
2829 // have to go through the list backwards to keep deletes from interfering with iteration
2830 for (uint32_t i = childNodes->Length(); i > 0; --i) {
2831 nsCOMPtr<nsIContent> childNode = childNodes->Item(i - 1);
2832 if (childNode) {
2833 // prepend children of aNodeToJoin
2834 ErrorResult err;
2835 aNodeToKeep->InsertBefore(*childNode, firstNode, err);
2836 NS_ENSURE_SUCCESS(err.ErrorCode(), err.ErrorCode());
2837 firstNode = childNode.forget();
2842 // delete the extra node
2843 ErrorResult err;
2844 aParent->RemoveChild(*aNodeToJoin, err);
2846 if (GetShouldTxnSetSelection()) {
2847 // editor wants us to set selection at join point
2848 selection->Collapse(aNodeToKeep, AssertedCast<int32_t>(firstNodeLength));
2849 } else if (selStartNode) {
2850 // and adjust the selection if needed
2851 // HACK: this is overly simplified - multi-range selections need more work than this
2852 bool bNeedToAdjust = false;
2854 // check to see if we joined nodes where selection starts
2855 if (selStartNode == aNodeToJoin) {
2856 bNeedToAdjust = true;
2857 selStartNode = aNodeToKeep;
2858 } else if (selStartNode == aNodeToKeep) {
2859 bNeedToAdjust = true;
2860 selStartOffset += firstNodeLength;
2863 // check to see if we joined nodes where selection ends
2864 if (selEndNode == aNodeToJoin) {
2865 bNeedToAdjust = true;
2866 selEndNode = aNodeToKeep;
2867 } else if (selEndNode == aNodeToKeep) {
2868 bNeedToAdjust = true;
2869 selEndOffset += firstNodeLength;
2872 // adjust selection if needed
2873 if (bNeedToAdjust) {
2874 selection->Collapse(selStartNode, selStartOffset);
2875 selection->Extend(selEndNode, selEndOffset);
2879 return err.ErrorCode();
2883 int32_t
2884 nsEditor::GetChildOffset(nsIDOMNode* aChild, nsIDOMNode* aParent)
2886 MOZ_ASSERT(aChild && aParent);
2888 nsCOMPtr<nsINode> parent = do_QueryInterface(aParent);
2889 nsCOMPtr<nsINode> child = do_QueryInterface(aChild);
2890 MOZ_ASSERT(parent && child);
2892 int32_t idx = parent->IndexOf(child);
2893 MOZ_ASSERT(idx != -1);
2894 return idx;
2897 // static
2898 already_AddRefed<nsIDOMNode>
2899 nsEditor::GetNodeLocation(nsIDOMNode* aChild, int32_t* outOffset)
2901 MOZ_ASSERT(aChild && outOffset);
2902 NS_ENSURE_TRUE(aChild && outOffset, nullptr);
2903 *outOffset = -1;
2905 nsCOMPtr<nsIDOMNode> parent;
2907 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
2908 aChild->GetParentNode(getter_AddRefs(parent))));
2909 if (parent) {
2910 *outOffset = GetChildOffset(aChild, parent);
2913 return parent.forget();
2916 nsINode*
2917 nsEditor::GetNodeLocation(nsINode* aChild, int32_t* aOffset)
2919 MOZ_ASSERT(aChild);
2920 MOZ_ASSERT(aOffset);
2922 nsINode* parent = aChild->GetParentNode();
2923 if (parent) {
2924 *aOffset = parent->IndexOf(aChild);
2925 MOZ_ASSERT(*aOffset != -1);
2926 } else {
2927 *aOffset = -1;
2929 return parent;
2932 // returns the number of things inside aNode.
2933 // If aNode is text, returns number of characters. If not, returns number of children nodes.
2934 nsresult
2935 nsEditor::GetLengthOfDOMNode(nsIDOMNode *aNode, uint32_t &aCount)
2937 aCount = 0;
2938 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
2939 NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
2940 aCount = node->Length();
2941 return NS_OK;
2945 nsresult
2946 nsEditor::GetPriorNode(nsIDOMNode *aParentNode,
2947 int32_t aOffset,
2948 bool aEditableNode,
2949 nsCOMPtr<nsIDOMNode> *aResultNode,
2950 bool bNoBlockCrossing)
2952 NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER);
2953 *aResultNode = nullptr;
2955 nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode);
2956 NS_ENSURE_TRUE(parentNode, NS_ERROR_NULL_POINTER);
2958 *aResultNode = do_QueryInterface(GetPriorNode(parentNode, aOffset,
2959 aEditableNode,
2960 bNoBlockCrossing));
2961 return NS_OK;
2964 nsIContent*
2965 nsEditor::GetPriorNode(nsINode* aParentNode,
2966 int32_t aOffset,
2967 bool aEditableNode,
2968 bool aNoBlockCrossing)
2970 MOZ_ASSERT(aParentNode);
2972 // If we are at the beginning of the node, or it is a text node, then just
2973 // look before it.
2974 if (!aOffset || aParentNode->NodeType() == nsIDOMNode::TEXT_NODE) {
2975 if (aNoBlockCrossing && IsBlockNode(aParentNode)) {
2976 // If we aren't allowed to cross blocks, don't look before this block.
2977 return nullptr;
2979 return GetPriorNode(aParentNode, aEditableNode, aNoBlockCrossing);
2982 // else look before the child at 'aOffset'
2983 if (nsIContent* child = aParentNode->GetChildAt(aOffset)) {
2984 return GetPriorNode(child, aEditableNode, aNoBlockCrossing);
2987 // unless there isn't one, in which case we are at the end of the node
2988 // and want the deep-right child.
2989 nsIContent* resultNode = GetRightmostChild(aParentNode, aNoBlockCrossing);
2990 if (!resultNode || !aEditableNode || IsEditable(resultNode)) {
2991 return resultNode;
2994 // restart the search from the non-editable node we just found
2995 return GetPriorNode(resultNode, aEditableNode, aNoBlockCrossing);
2999 nsresult
3000 nsEditor::GetNextNode(nsIDOMNode *aParentNode,
3001 int32_t aOffset,
3002 bool aEditableNode,
3003 nsCOMPtr<nsIDOMNode> *aResultNode,
3004 bool bNoBlockCrossing)
3006 NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER);
3007 *aResultNode = nullptr;
3009 nsCOMPtr<nsINode> parentNode = do_QueryInterface(aParentNode);
3010 NS_ENSURE_TRUE(parentNode, NS_ERROR_NULL_POINTER);
3012 *aResultNode = do_QueryInterface(GetNextNode(parentNode, aOffset,
3013 aEditableNode,
3014 bNoBlockCrossing));
3015 return NS_OK;
3018 nsIContent*
3019 nsEditor::GetNextNode(nsINode* aParentNode,
3020 int32_t aOffset,
3021 bool aEditableNode,
3022 bool aNoBlockCrossing)
3024 MOZ_ASSERT(aParentNode);
3026 // if aParentNode is a text node, use its location instead
3027 if (aParentNode->NodeType() == nsIDOMNode::TEXT_NODE) {
3028 nsINode* parent = aParentNode->GetParentNode();
3029 NS_ENSURE_TRUE(parent, nullptr);
3030 aOffset = parent->IndexOf(aParentNode) + 1; // _after_ the text node
3031 aParentNode = parent;
3034 // look at the child at 'aOffset'
3035 nsIContent* child = aParentNode->GetChildAt(aOffset);
3036 if (child) {
3037 if (aNoBlockCrossing && IsBlockNode(child)) {
3038 return child;
3041 nsIContent* resultNode = GetLeftmostChild(child, aNoBlockCrossing);
3042 if (!resultNode) {
3043 return child;
3046 if (!IsDescendantOfEditorRoot(resultNode)) {
3047 return nullptr;
3050 if (!aEditableNode || IsEditable(resultNode)) {
3051 return resultNode;
3054 // restart the search from the non-editable node we just found
3055 return GetNextNode(resultNode, aEditableNode, aNoBlockCrossing);
3058 // unless there isn't one, in which case we are at the end of the node
3059 // and want the next one.
3060 if (aNoBlockCrossing && IsBlockNode(aParentNode)) {
3061 // don't cross out of parent block
3062 return nullptr;
3065 return GetNextNode(aParentNode, aEditableNode, aNoBlockCrossing);
3069 nsresult
3070 nsEditor::GetPriorNode(nsIDOMNode *aCurrentNode,
3071 bool aEditableNode,
3072 nsCOMPtr<nsIDOMNode> *aResultNode,
3073 bool bNoBlockCrossing)
3075 NS_ENSURE_TRUE(aResultNode, NS_ERROR_NULL_POINTER);
3077 nsCOMPtr<nsINode> currentNode = do_QueryInterface(aCurrentNode);
3078 NS_ENSURE_TRUE(currentNode, NS_ERROR_NULL_POINTER);
3080 *aResultNode = do_QueryInterface(GetPriorNode(currentNode, aEditableNode,
3081 bNoBlockCrossing));
3082 return NS_OK;
3085 nsIContent*
3086 nsEditor::GetPriorNode(nsINode* aCurrentNode, bool aEditableNode,
3087 bool aNoBlockCrossing /* = false */)
3089 MOZ_ASSERT(aCurrentNode);
3091 if (!IsDescendantOfEditorRoot(aCurrentNode)) {
3092 return nullptr;
3095 return FindNode(aCurrentNode, false, aEditableNode, aNoBlockCrossing);
3098 nsIContent*
3099 nsEditor::FindNextLeafNode(nsINode *aCurrentNode,
3100 bool aGoForward,
3101 bool bNoBlockCrossing)
3103 // called only by GetPriorNode so we don't need to check params.
3104 NS_PRECONDITION(IsDescendantOfEditorRoot(aCurrentNode) &&
3105 !IsEditorRoot(aCurrentNode),
3106 "Bogus arguments");
3108 nsINode* cur = aCurrentNode;
3109 for (;;) {
3110 // if aCurrentNode has a sibling in the right direction, return
3111 // that sibling's closest child (or itself if it has no children)
3112 nsIContent* sibling =
3113 aGoForward ? cur->GetNextSibling() : cur->GetPreviousSibling();
3114 if (sibling) {
3115 if (bNoBlockCrossing && IsBlockNode(sibling)) {
3116 // don't look inside prevsib, since it is a block
3117 return sibling;
3119 nsIContent *leaf =
3120 aGoForward ? GetLeftmostChild(sibling, bNoBlockCrossing) :
3121 GetRightmostChild(sibling, bNoBlockCrossing);
3122 if (!leaf) {
3123 return sibling;
3126 return leaf;
3129 nsINode *parent = cur->GetParentNode();
3130 if (!parent) {
3131 return nullptr;
3134 NS_ASSERTION(IsDescendantOfEditorRoot(parent),
3135 "We started with a proper descendant of root, and should stop "
3136 "if we ever hit the root, so we better have a descendant of "
3137 "root now!");
3138 if (IsEditorRoot(parent) ||
3139 (bNoBlockCrossing && IsBlockNode(parent))) {
3140 return nullptr;
3143 cur = parent;
3146 NS_NOTREACHED("What part of for(;;) do you not understand?");
3147 return nullptr;
3150 nsresult
3151 nsEditor::GetNextNode(nsIDOMNode* aCurrentNode,
3152 bool aEditableNode,
3153 nsCOMPtr<nsIDOMNode> *aResultNode,
3154 bool bNoBlockCrossing)
3156 nsCOMPtr<nsINode> currentNode = do_QueryInterface(aCurrentNode);
3157 if (!currentNode || !aResultNode) {
3158 return NS_ERROR_NULL_POINTER;
3161 *aResultNode = do_QueryInterface(GetNextNode(currentNode, aEditableNode,
3162 bNoBlockCrossing));
3163 return NS_OK;
3166 nsIContent*
3167 nsEditor::GetNextNode(nsINode* aCurrentNode,
3168 bool aEditableNode,
3169 bool bNoBlockCrossing)
3171 MOZ_ASSERT(aCurrentNode);
3173 if (!IsDescendantOfEditorRoot(aCurrentNode)) {
3174 return nullptr;
3177 return FindNode(aCurrentNode, true, aEditableNode, bNoBlockCrossing);
3180 nsIContent*
3181 nsEditor::FindNode(nsINode *aCurrentNode,
3182 bool aGoForward,
3183 bool aEditableNode,
3184 bool bNoBlockCrossing)
3186 if (IsEditorRoot(aCurrentNode)) {
3187 // Don't allow traversal above the root node! This helps
3188 // prevent us from accidentally editing browser content
3189 // when the editor is in a text widget.
3191 return nullptr;
3194 nsCOMPtr<nsIContent> candidate =
3195 FindNextLeafNode(aCurrentNode, aGoForward, bNoBlockCrossing);
3197 if (!candidate) {
3198 return nullptr;
3201 if (!aEditableNode || IsEditable(candidate)) {
3202 return candidate;
3205 return FindNode(candidate, aGoForward, aEditableNode, bNoBlockCrossing);
3208 nsIDOMNode*
3209 nsEditor::GetRightmostChild(nsIDOMNode* aCurrentNode,
3210 bool bNoBlockCrossing)
3212 nsCOMPtr<nsINode> currentNode = do_QueryInterface(aCurrentNode);
3213 nsIContent* result = GetRightmostChild(currentNode, bNoBlockCrossing);
3214 return result ? result->AsDOMNode() : nullptr;
3217 nsIContent*
3218 nsEditor::GetRightmostChild(nsINode *aCurrentNode,
3219 bool bNoBlockCrossing)
3221 NS_ENSURE_TRUE(aCurrentNode, nullptr);
3222 nsIContent *cur = aCurrentNode->GetLastChild();
3223 if (!cur) {
3224 return nullptr;
3226 for (;;) {
3227 if (bNoBlockCrossing && IsBlockNode(cur)) {
3228 return cur;
3230 nsIContent* next = cur->GetLastChild();
3231 if (!next) {
3232 return cur;
3234 cur = next;
3237 NS_NOTREACHED("What part of for(;;) do you not understand?");
3238 return nullptr;
3241 nsIContent*
3242 nsEditor::GetLeftmostChild(nsINode *aCurrentNode,
3243 bool bNoBlockCrossing)
3245 NS_ENSURE_TRUE(aCurrentNode, nullptr);
3246 nsIContent *cur = aCurrentNode->GetFirstChild();
3247 if (!cur) {
3248 return nullptr;
3250 for (;;) {
3251 if (bNoBlockCrossing && IsBlockNode(cur)) {
3252 return cur;
3254 nsIContent *next = cur->GetFirstChild();
3255 if (!next) {
3256 return cur;
3258 cur = next;
3261 NS_NOTREACHED("What part of for(;;) do you not understand?");
3262 return nullptr;
3265 nsIDOMNode*
3266 nsEditor::GetLeftmostChild(nsIDOMNode* aCurrentNode,
3267 bool bNoBlockCrossing)
3269 nsCOMPtr<nsINode> currentNode = do_QueryInterface(aCurrentNode);
3270 nsIContent* result = GetLeftmostChild(currentNode, bNoBlockCrossing);
3271 return result ? result->AsDOMNode() : nullptr;
3274 bool
3275 nsEditor::IsBlockNode(nsIDOMNode* aNode)
3277 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
3278 return IsBlockNode(node);
3281 bool
3282 nsEditor::IsBlockNode(nsINode* aNode)
3284 // stub to be overridden in nsHTMLEditor.
3285 // screwing around with the class hierarchy here in order
3286 // to not duplicate the code in GetNextNode/GetPrevNode
3287 // across both nsEditor/nsHTMLEditor.
3288 return false;
3291 bool
3292 nsEditor::CanContain(nsIDOMNode* aParent, nsIDOMNode* aChild)
3294 nsCOMPtr<nsIContent> parent = do_QueryInterface(aParent);
3295 NS_ENSURE_TRUE(parent, false);
3297 switch (parent->NodeType()) {
3298 case nsIDOMNode::ELEMENT_NODE:
3299 case nsIDOMNode::DOCUMENT_FRAGMENT_NODE:
3300 return TagCanContain(parent->Tag(), aChild);
3302 return false;
3305 bool
3306 nsEditor::CanContainTag(nsIDOMNode* aParent, nsIAtom* aChildTag)
3308 nsCOMPtr<nsIContent> parent = do_QueryInterface(aParent);
3309 NS_ENSURE_TRUE(parent, false);
3311 switch (parent->NodeType()) {
3312 case nsIDOMNode::ELEMENT_NODE:
3313 case nsIDOMNode::DOCUMENT_FRAGMENT_NODE:
3314 return TagCanContainTag(parent->Tag(), aChildTag);
3316 return false;
3319 bool
3320 nsEditor::TagCanContain(nsIAtom* aParentTag, nsIDOMNode* aChild)
3322 nsCOMPtr<nsIContent> child = do_QueryInterface(aChild);
3323 NS_ENSURE_TRUE(child, false);
3325 switch (child->NodeType()) {
3326 case nsIDOMNode::TEXT_NODE:
3327 case nsIDOMNode::ELEMENT_NODE:
3328 case nsIDOMNode::DOCUMENT_FRAGMENT_NODE:
3329 return TagCanContainTag(aParentTag, child->Tag());
3331 return false;
3334 bool
3335 nsEditor::TagCanContainTag(nsIAtom* aParentTag, nsIAtom* aChildTag)
3337 return true;
3340 bool
3341 nsEditor::IsRoot(nsIDOMNode* inNode)
3343 NS_ENSURE_TRUE(inNode, false);
3345 nsCOMPtr<nsIDOMNode> rootNode = do_QueryInterface(GetRoot());
3347 return inNode == rootNode;
3350 bool
3351 nsEditor::IsRoot(nsINode* inNode)
3353 NS_ENSURE_TRUE(inNode, false);
3355 nsCOMPtr<nsINode> rootNode = GetRoot();
3357 return inNode == rootNode;
3360 bool
3361 nsEditor::IsEditorRoot(nsINode* aNode)
3363 NS_ENSURE_TRUE(aNode, false);
3364 nsCOMPtr<nsINode> rootNode = GetEditorRoot();
3365 return aNode == rootNode;
3368 bool
3369 nsEditor::IsDescendantOfRoot(nsIDOMNode* inNode)
3371 nsCOMPtr<nsINode> node = do_QueryInterface(inNode);
3372 return IsDescendantOfRoot(node);
3375 bool
3376 nsEditor::IsDescendantOfRoot(nsINode* inNode)
3378 NS_ENSURE_TRUE(inNode, false);
3379 nsCOMPtr<nsIContent> root = GetRoot();
3380 NS_ENSURE_TRUE(root, false);
3382 return nsContentUtils::ContentIsDescendantOf(inNode, root);
3385 bool
3386 nsEditor::IsDescendantOfEditorRoot(nsIDOMNode* aNode)
3388 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
3389 return IsDescendantOfEditorRoot(node);
3392 bool
3393 nsEditor::IsDescendantOfEditorRoot(nsINode* aNode)
3395 NS_ENSURE_TRUE(aNode, false);
3396 nsCOMPtr<nsIContent> root = GetEditorRoot();
3397 NS_ENSURE_TRUE(root, false);
3399 return nsContentUtils::ContentIsDescendantOf(aNode, root);
3402 bool
3403 nsEditor::IsContainer(nsINode* aNode)
3405 return aNode ? true : false;
3408 bool
3409 nsEditor::IsContainer(nsIDOMNode* aNode)
3411 return aNode ? true : false;
3414 static inline bool
3415 IsElementVisible(dom::Element* aElement)
3417 if (aElement->GetPrimaryFrame()) {
3418 // It's visible, for our purposes
3419 return true;
3422 nsIContent *cur = aElement;
3423 for (; ;) {
3424 // Walk up the tree looking for the nearest ancestor with a frame.
3425 // The state of the child right below it will determine whether
3426 // we might possibly have a frame or not.
3427 bool haveLazyBitOnChild = cur->HasFlag(NODE_NEEDS_FRAME);
3428 cur = cur->GetFlattenedTreeParent();
3429 if (!cur) {
3430 if (!haveLazyBitOnChild) {
3431 // None of our ancestors have lazy bits set, so we shouldn't
3432 // have a frame
3433 return false;
3436 // The root has a lazy frame construction bit. We need to check
3437 // our style.
3438 break;
3441 if (cur->GetPrimaryFrame()) {
3442 if (!haveLazyBitOnChild) {
3443 // Our ancestor directly under |cur| doesn't have lazy bits;
3444 // that means we won't get a frame
3445 return false;
3448 if (cur->GetPrimaryFrame()->IsLeaf()) {
3449 // Nothing under here will ever get frames
3450 return false;
3453 // Otherwise, we might end up with a frame when that lazy bit is
3454 // processed. Figure out our actual style.
3455 break;
3459 // Now it might be that we have no frame because we're in a
3460 // display:none subtree, or it might be that we're just dealing with
3461 // lazy frame construction and it hasn't happened yet. Check which
3462 // one it is.
3463 nsRefPtr<nsStyleContext> styleContext =
3464 nsComputedDOMStyle::GetStyleContextForElementNoFlush(aElement,
3465 nullptr, nullptr);
3466 if (styleContext) {
3467 return styleContext->StyleDisplay()->mDisplay != NS_STYLE_DISPLAY_NONE;
3469 return false;
3472 bool
3473 nsEditor::IsEditable(nsIDOMNode *aNode)
3475 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
3476 return IsEditable(content);
3479 bool
3480 nsEditor::IsEditable(nsINode* aNode)
3482 NS_ENSURE_TRUE(aNode, false);
3484 if (!aNode->IsNodeOfType(nsINode::eCONTENT) || IsMozEditorBogusNode(aNode) ||
3485 !IsModifiableNode(aNode)) {
3486 return false;
3489 // see if it has a frame. If so, we'll edit it.
3490 // special case for textnodes: frame must have width.
3491 if (aNode->IsElement() && !IsElementVisible(aNode->AsElement())) {
3492 // If the element has no frame, it's not editable. Note that we
3493 // need to check IsElement() here, because some of our tests
3494 // rely on frameless textnodes being visible.
3495 return false;
3497 switch (aNode->NodeType()) {
3498 case nsIDOMNode::ELEMENT_NODE:
3499 case nsIDOMNode::TEXT_NODE:
3500 return true; // element or text node; not invisible
3501 default:
3502 return false;
3506 bool
3507 nsEditor::IsMozEditorBogusNode(nsINode* element)
3509 return element && element->IsElement() &&
3510 element->AsElement()->AttrValueIs(kNameSpaceID_None,
3511 kMOZEditorBogusNodeAttrAtom, kMOZEditorBogusNodeValue,
3512 eCaseMatters);
3515 uint32_t
3516 nsEditor::CountEditableChildren(nsINode* aNode)
3518 MOZ_ASSERT(aNode);
3519 uint32_t count = 0;
3520 for (nsIContent* child = aNode->GetFirstChild();
3521 child;
3522 child = child->GetNextSibling()) {
3523 if (IsEditable(child)) {
3524 ++count;
3527 return count;
3530 //END nsEditor static utility methods
3533 NS_IMETHODIMP nsEditor::IncrementModificationCount(int32_t inNumMods)
3535 uint32_t oldModCount = mModCount;
3537 mModCount += inNumMods;
3539 if ((oldModCount == 0 && mModCount != 0)
3540 || (oldModCount != 0 && mModCount == 0))
3541 NotifyDocumentListeners(eDocumentStateChanged);
3542 return NS_OK;
3546 NS_IMETHODIMP nsEditor::GetModificationCount(int32_t *outModCount)
3548 NS_ENSURE_ARG_POINTER(outModCount);
3549 *outModCount = mModCount;
3550 return NS_OK;
3554 NS_IMETHODIMP nsEditor::ResetModificationCount()
3556 bool doNotify = (mModCount != 0);
3558 mModCount = 0;
3560 if (doNotify)
3561 NotifyDocumentListeners(eDocumentStateChanged);
3562 return NS_OK;
3565 //END nsEditor Private methods
3569 ///////////////////////////////////////////////////////////////////////////
3570 // GetTag: digs out the atom for the tag of this node
3572 nsIAtom *
3573 nsEditor::GetTag(nsIDOMNode *aNode)
3575 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
3577 if (!content)
3579 NS_ASSERTION(aNode, "null node passed to nsEditor::Tag()");
3581 return nullptr;
3584 return content->Tag();
3588 ///////////////////////////////////////////////////////////////////////////
3589 // GetTagString: digs out string for the tag of this node
3591 nsresult
3592 nsEditor::GetTagString(nsIDOMNode *aNode, nsAString& outString)
3594 if (!aNode)
3596 NS_NOTREACHED("null node passed to nsEditor::GetTag()");
3597 return NS_ERROR_NULL_POINTER;
3600 nsIAtom *atom = GetTag(aNode);
3601 if (!atom)
3603 return NS_ERROR_FAILURE;
3606 atom->ToString(outString);
3607 return NS_OK;
3611 ///////////////////////////////////////////////////////////////////////////
3612 // NodesSameType: do these nodes have the same tag?
3614 bool
3615 nsEditor::NodesSameType(nsIDOMNode *aNode1, nsIDOMNode *aNode2)
3617 if (!aNode1 || !aNode2) {
3618 NS_NOTREACHED("null node passed to nsEditor::NodesSameType()");
3619 return false;
3622 nsCOMPtr<nsIContent> content1 = do_QueryInterface(aNode1);
3623 NS_ENSURE_TRUE(content1, false);
3625 nsCOMPtr<nsIContent> content2 = do_QueryInterface(aNode2);
3626 NS_ENSURE_TRUE(content2, false);
3628 return AreNodesSameType(content1, content2);
3631 /* virtual */
3632 bool
3633 nsEditor::AreNodesSameType(nsIContent* aNode1, nsIContent* aNode2)
3635 MOZ_ASSERT(aNode1);
3636 MOZ_ASSERT(aNode2);
3637 return aNode1->Tag() == aNode2->Tag();
3641 ///////////////////////////////////////////////////////////////////////////
3642 // IsTextNode: true if node of dom type text
3644 bool
3645 nsEditor::IsTextNode(nsIDOMNode *aNode)
3647 if (!aNode)
3649 NS_NOTREACHED("null node passed to IsTextNode()");
3650 return false;
3653 uint16_t nodeType;
3654 aNode->GetNodeType(&nodeType);
3655 return (nodeType == nsIDOMNode::TEXT_NODE);
3658 bool
3659 nsEditor::IsTextNode(nsINode *aNode)
3661 return aNode->NodeType() == nsIDOMNode::TEXT_NODE;
3664 ///////////////////////////////////////////////////////////////////////////
3665 // GetChildAt: returns the node at this position index in the parent
3667 nsCOMPtr<nsIDOMNode>
3668 nsEditor::GetChildAt(nsIDOMNode *aParent, int32_t aOffset)
3670 nsCOMPtr<nsIDOMNode> resultNode;
3672 nsCOMPtr<nsIContent> parent = do_QueryInterface(aParent);
3674 NS_ENSURE_TRUE(parent, resultNode);
3676 resultNode = do_QueryInterface(parent->GetChildAt(aOffset));
3678 return resultNode;
3681 ///////////////////////////////////////////////////////////////////////////
3682 // GetNodeAtRangeOffsetPoint: returns the node at this position in a range,
3683 // assuming that aParentOrNode is the node itself if it's a text node, or
3684 // the node's parent otherwise.
3686 nsCOMPtr<nsIDOMNode>
3687 nsEditor::GetNodeAtRangeOffsetPoint(nsIDOMNode* aParentOrNode, int32_t aOffset)
3689 if (IsTextNode(aParentOrNode)) {
3690 return aParentOrNode;
3692 return GetChildAt(aParentOrNode, aOffset);
3696 ///////////////////////////////////////////////////////////////////////////
3697 // GetStartNodeAndOffset: returns whatever the start parent & offset is of
3698 // the first range in the selection.
3699 nsresult
3700 nsEditor::GetStartNodeAndOffset(nsISelection *aSelection,
3701 nsIDOMNode **outStartNode,
3702 int32_t *outStartOffset)
3704 NS_ENSURE_TRUE(outStartNode && outStartOffset && aSelection, NS_ERROR_NULL_POINTER);
3706 nsCOMPtr<nsINode> startNode;
3707 nsresult rv = GetStartNodeAndOffset(static_cast<Selection*>(aSelection),
3708 getter_AddRefs(startNode),
3709 outStartOffset);
3710 NS_ENSURE_SUCCESS(rv, rv);
3712 if (startNode) {
3713 NS_ADDREF(*outStartNode = startNode->AsDOMNode());
3714 } else {
3715 *outStartNode = nullptr;
3717 return NS_OK;
3720 nsresult
3721 nsEditor::GetStartNodeAndOffset(Selection* aSelection, nsINode** aStartNode,
3722 int32_t* aStartOffset)
3724 MOZ_ASSERT(aSelection);
3725 MOZ_ASSERT(aStartNode);
3726 MOZ_ASSERT(aStartOffset);
3728 *aStartNode = nullptr;
3729 *aStartOffset = 0;
3731 NS_ENSURE_TRUE(aSelection->GetRangeCount(), NS_ERROR_FAILURE);
3733 const nsRange* range = aSelection->GetRangeAt(0);
3734 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
3736 NS_ENSURE_TRUE(range->IsPositioned(), NS_ERROR_FAILURE);
3738 NS_IF_ADDREF(*aStartNode = range->GetStartParent());
3739 *aStartOffset = range->StartOffset();
3740 return NS_OK;
3744 ///////////////////////////////////////////////////////////////////////////
3745 // GetEndNodeAndOffset: returns whatever the end parent & offset is of
3746 // the first range in the selection.
3747 nsresult
3748 nsEditor::GetEndNodeAndOffset(nsISelection *aSelection,
3749 nsIDOMNode **outEndNode,
3750 int32_t *outEndOffset)
3752 NS_ENSURE_TRUE(outEndNode && outEndOffset && aSelection, NS_ERROR_NULL_POINTER);
3754 nsCOMPtr<nsINode> endNode;
3755 nsresult rv = GetEndNodeAndOffset(static_cast<Selection*>(aSelection),
3756 getter_AddRefs(endNode),
3757 outEndOffset);
3758 NS_ENSURE_SUCCESS(rv, rv);
3760 if (endNode) {
3761 NS_ADDREF(*outEndNode = endNode->AsDOMNode());
3762 } else {
3763 *outEndNode = nullptr;
3765 return NS_OK;
3768 nsresult
3769 nsEditor::GetEndNodeAndOffset(Selection* aSelection, nsINode** aEndNode,
3770 int32_t* aEndOffset)
3772 MOZ_ASSERT(aSelection);
3773 MOZ_ASSERT(aEndNode);
3774 MOZ_ASSERT(aEndOffset);
3776 *aEndNode = nullptr;
3777 *aEndOffset = 0;
3779 NS_ENSURE_TRUE(aSelection->GetRangeCount(), NS_ERROR_FAILURE);
3781 const nsRange* range = aSelection->GetRangeAt(0);
3782 NS_ENSURE_TRUE(range, NS_ERROR_FAILURE);
3784 NS_ENSURE_TRUE(range->IsPositioned(), NS_ERROR_FAILURE);
3786 NS_IF_ADDREF(*aEndNode = range->GetEndParent());
3787 *aEndOffset = range->EndOffset();
3788 return NS_OK;
3792 ///////////////////////////////////////////////////////////////////////////
3793 // IsPreformatted: checks the style info for the node for the preformatted
3794 // text style.
3795 nsresult
3796 nsEditor::IsPreformatted(nsIDOMNode *aNode, bool *aResult)
3798 nsCOMPtr<nsIContent> content = do_QueryInterface(aNode);
3800 NS_ENSURE_TRUE(aResult && content, NS_ERROR_NULL_POINTER);
3802 nsCOMPtr<nsIPresShell> ps = GetPresShell();
3803 NS_ENSURE_TRUE(ps, NS_ERROR_NOT_INITIALIZED);
3805 // Look at the node (and its parent if it's not an element), and grab its style context
3806 nsRefPtr<nsStyleContext> elementStyle;
3807 if (!content->IsElement()) {
3808 content = content->GetParent();
3810 if (content && content->IsElement()) {
3811 elementStyle = nsComputedDOMStyle::GetStyleContextForElementNoFlush(content->AsElement(),
3812 nullptr,
3813 ps);
3816 if (!elementStyle)
3818 // Consider nodes without a style context to be NOT preformatted:
3819 // For instance, this is true of JS tags inside the body (which show
3820 // up as #text nodes but have no style context).
3821 *aResult = false;
3822 return NS_OK;
3825 const nsStyleText* styleText = elementStyle->StyleText();
3827 *aResult = styleText->WhiteSpaceIsSignificant();
3828 return NS_OK;
3832 ///////////////////////////////////////////////////////////////////////////
3833 // SplitNodeDeep: this splits a node "deeply", splitting children as
3834 // appropriate. The place to split is represented by
3835 // a dom point at {splitPointParent, splitPointOffset}.
3836 // That dom point must be inside aNode, which is the node to
3837 // split. outOffset is set to the offset in the parent of aNode where
3838 // the split terminates - where you would want to insert
3839 // a new element, for instance, if that's why you were splitting
3840 // the node.
3842 nsresult
3843 nsEditor::SplitNodeDeep(nsIDOMNode *aNode,
3844 nsIDOMNode *aSplitPointParent,
3845 int32_t aSplitPointOffset,
3846 int32_t *outOffset,
3847 bool aNoEmptyContainers,
3848 nsCOMPtr<nsIDOMNode> *outLeftNode,
3849 nsCOMPtr<nsIDOMNode> *outRightNode)
3851 nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
3852 NS_ENSURE_TRUE(node && aSplitPointParent && outOffset, NS_ERROR_NULL_POINTER);
3853 int32_t offset = aSplitPointOffset;
3855 if (outLeftNode) *outLeftNode = nullptr;
3856 if (outRightNode) *outRightNode = nullptr;
3858 nsCOMPtr<nsINode> nodeToSplit = do_QueryInterface(aSplitPointParent);
3859 while (nodeToSplit) {
3860 // need to insert rules code call here to do things like
3861 // not split a list if you are after the last <li> or before the first, etc.
3862 // for now we just have some smarts about unneccessarily splitting
3863 // textnodes, which should be universal enough to put straight in
3864 // this nsEditor routine.
3866 nsCOMPtr<nsIDOMCharacterData> nodeAsText = do_QueryInterface(nodeToSplit);
3867 uint32_t len = nodeToSplit->Length();
3868 bool bDoSplit = false;
3870 if (!(aNoEmptyContainers || nodeAsText) || (offset && (offset != (int32_t)len)))
3872 bDoSplit = true;
3873 nsCOMPtr<nsIDOMNode> tempNode;
3874 nsresult rv = SplitNode(nodeToSplit->AsDOMNode(), offset,
3875 getter_AddRefs(tempNode));
3876 NS_ENSURE_SUCCESS(rv, rv);
3878 if (outRightNode) {
3879 *outRightNode = nodeToSplit->AsDOMNode();
3881 if (outLeftNode) {
3882 *outLeftNode = tempNode;
3886 nsINode* parentNode = nodeToSplit->GetParentNode();
3887 NS_ENSURE_TRUE(parentNode, NS_ERROR_FAILURE);
3889 if (!bDoSplit && offset) {
3890 // must be "end of text node" case, we didn't split it, just move past it
3891 offset = parentNode->IndexOf(nodeToSplit) + 1;
3892 if (outLeftNode) {
3893 *outLeftNode = nodeToSplit->AsDOMNode();
3895 } else {
3896 offset = parentNode->IndexOf(nodeToSplit);
3897 if (outRightNode) {
3898 *outRightNode = nodeToSplit->AsDOMNode();
3902 if (nodeToSplit == node) {
3903 // we split all the way up to (and including) aNode; we're done
3904 break;
3907 nodeToSplit = parentNode;
3910 if (!nodeToSplit) {
3911 NS_NOTREACHED("null node obtained in nsEditor::SplitNodeDeep()");
3912 return NS_ERROR_FAILURE;
3915 *outOffset = offset;
3916 return NS_OK;
3920 ///////////////////////////////////////////////////////////////////////////
3921 // JoinNodeDeep: this joins two like nodes "deeply", joining children as
3922 // appropriate.
3923 nsresult
3924 nsEditor::JoinNodeDeep(nsIDOMNode *aLeftNode,
3925 nsIDOMNode *aRightNode,
3926 nsCOMPtr<nsIDOMNode> *aOutJoinNode,
3927 int32_t *outOffset)
3929 NS_ENSURE_TRUE(aLeftNode && aRightNode && aOutJoinNode && outOffset, NS_ERROR_NULL_POINTER);
3931 // while the rightmost children and their descendants of the left node
3932 // match the leftmost children and their descendants of the right node
3933 // join them up. Can you say that three times fast?
3935 nsCOMPtr<nsIDOMNode> leftNodeToJoin = do_QueryInterface(aLeftNode);
3936 nsCOMPtr<nsIDOMNode> rightNodeToJoin = do_QueryInterface(aRightNode);
3937 nsCOMPtr<nsIDOMNode> parentNode,tmp;
3938 nsresult res = NS_OK;
3940 rightNodeToJoin->GetParentNode(getter_AddRefs(parentNode));
3942 while (leftNodeToJoin && rightNodeToJoin && parentNode &&
3943 NodesSameType(leftNodeToJoin, rightNodeToJoin))
3945 // adjust out params
3946 uint32_t length;
3947 res = GetLengthOfDOMNode(leftNodeToJoin, length);
3948 NS_ENSURE_SUCCESS(res, res);
3950 *aOutJoinNode = rightNodeToJoin;
3951 *outOffset = length;
3953 // do the join
3954 res = JoinNodes(leftNodeToJoin, rightNodeToJoin, parentNode);
3955 NS_ENSURE_SUCCESS(res, res);
3957 if (IsTextNode(parentNode)) // we've joined all the way down to text nodes, we're done!
3958 return NS_OK;
3960 else
3962 // get new left and right nodes, and begin anew
3963 parentNode = rightNodeToJoin;
3964 leftNodeToJoin = GetChildAt(parentNode, length-1);
3965 rightNodeToJoin = GetChildAt(parentNode, length);
3967 // skip over non-editable nodes
3968 while (leftNodeToJoin && !IsEditable(leftNodeToJoin))
3970 leftNodeToJoin->GetPreviousSibling(getter_AddRefs(tmp));
3971 leftNodeToJoin = tmp;
3973 if (!leftNodeToJoin) break;
3975 while (rightNodeToJoin && !IsEditable(rightNodeToJoin))
3977 rightNodeToJoin->GetNextSibling(getter_AddRefs(tmp));
3978 rightNodeToJoin = tmp;
3980 if (!rightNodeToJoin) break;
3984 return res;
3987 void
3988 nsEditor::BeginUpdateViewBatch()
3990 NS_PRECONDITION(mUpdateCount >= 0, "bad state");
3992 if (0 == mUpdateCount)
3994 // Turn off selection updates and notifications.
3996 nsCOMPtr<nsISelection> selection;
3997 GetSelection(getter_AddRefs(selection));
3999 if (selection)
4001 nsCOMPtr<nsISelectionPrivate> selPrivate(do_QueryInterface(selection));
4002 selPrivate->StartBatchChanges();
4006 mUpdateCount++;
4010 nsresult nsEditor::EndUpdateViewBatch()
4012 NS_PRECONDITION(mUpdateCount > 0, "bad state");
4014 if (mUpdateCount <= 0)
4016 mUpdateCount = 0;
4017 return NS_ERROR_FAILURE;
4020 mUpdateCount--;
4022 if (0 == mUpdateCount)
4024 // Turn selection updating and notifications back on.
4026 nsCOMPtr<nsISelection>selection;
4027 GetSelection(getter_AddRefs(selection));
4029 if (selection) {
4030 nsCOMPtr<nsISelectionPrivate>selPrivate(do_QueryInterface(selection));
4031 selPrivate->EndBatchChanges();
4035 return NS_OK;
4038 bool
4039 nsEditor::GetShouldTxnSetSelection()
4041 return mShouldTxnSetSelection;
4045 NS_IMETHODIMP
4046 nsEditor::DeleteSelectionImpl(EDirection aAction,
4047 EStripWrappers aStripWrappers)
4049 MOZ_ASSERT(aStripWrappers == eStrip || aStripWrappers == eNoStrip);
4051 nsCOMPtr<nsISelection>selection;
4052 nsresult res = GetSelection(getter_AddRefs(selection));
4053 NS_ENSURE_SUCCESS(res, res);
4054 nsRefPtr<EditAggregateTxn> txn;
4055 nsCOMPtr<nsINode> deleteNode;
4056 int32_t deleteCharOffset = 0, deleteCharLength = 0;
4057 res = CreateTxnForDeleteSelection(aAction, getter_AddRefs(txn),
4058 getter_AddRefs(deleteNode),
4059 &deleteCharOffset, &deleteCharLength);
4060 nsCOMPtr<nsIDOMCharacterData> deleteCharData(do_QueryInterface(deleteNode));
4062 if (NS_SUCCEEDED(res))
4064 nsAutoRules beginRulesSniffing(this, EditAction::deleteSelection, aAction);
4065 int32_t i;
4066 // Notify nsIEditActionListener::WillDelete[Selection|Text|Node]
4067 if (!deleteNode)
4068 for (i = 0; i < mActionListeners.Count(); i++)
4069 mActionListeners[i]->WillDeleteSelection(selection);
4070 else if (deleteCharData)
4071 for (i = 0; i < mActionListeners.Count(); i++)
4072 mActionListeners[i]->WillDeleteText(deleteCharData, deleteCharOffset, 1);
4073 else
4074 for (i = 0; i < mActionListeners.Count(); i++)
4075 mActionListeners[i]->WillDeleteNode(deleteNode->AsDOMNode());
4077 // Delete the specified amount
4078 res = DoTransaction(txn);
4080 // Notify nsIEditActionListener::DidDelete[Selection|Text|Node]
4081 if (!deleteNode)
4082 for (i = 0; i < mActionListeners.Count(); i++)
4083 mActionListeners[i]->DidDeleteSelection(selection);
4084 else if (deleteCharData)
4085 for (i = 0; i < mActionListeners.Count(); i++)
4086 mActionListeners[i]->DidDeleteText(deleteCharData, deleteCharOffset, 1, res);
4087 else
4088 for (i = 0; i < mActionListeners.Count(); i++)
4089 mActionListeners[i]->DidDeleteNode(deleteNode->AsDOMNode(), res);
4092 return res;
4095 // XXX: error handling in this routine needs to be cleaned up!
4096 NS_IMETHODIMP
4097 nsEditor::DeleteSelectionAndCreateNode(const nsAString& aTag,
4098 nsIDOMNode ** aNewNode)
4100 nsCOMPtr<nsIAtom> tag = do_GetAtom(aTag);
4102 nsresult result = DeleteSelectionAndPrepareToCreateNode();
4103 NS_ENSURE_SUCCESS(result, result);
4105 nsRefPtr<Selection> selection = GetSelection();
4106 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
4108 nsCOMPtr<nsINode> node = selection->GetAnchorNode();
4109 uint32_t offset = selection->AnchorOffset();
4111 nsCOMPtr<nsIDOMNode> newNode;
4112 *aNewNode = GetAsDOMNode(CreateNode(tag, node, offset).take());
4113 // XXX: ERROR_HANDLING check result, and make sure aNewNode is set correctly
4114 // in success/failure cases
4116 // we want the selection to be just after the new node
4117 return selection->Collapse(node, offset + 1);
4121 /* Non-interface, protected methods */
4123 TextComposition*
4124 nsEditor::GetComposition() const
4126 return mComposition;
4129 bool
4130 nsEditor::IsIMEComposing() const
4132 return mComposition && mComposition->IsComposing();
4135 nsresult
4136 nsEditor::DeleteSelectionAndPrepareToCreateNode()
4138 nsresult res;
4139 nsRefPtr<Selection> selection = GetSelection();
4140 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
4141 MOZ_ASSERT(selection->GetAnchorFocusRange());
4143 if (!selection->GetAnchorFocusRange()->Collapsed()) {
4144 res = DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
4145 NS_ENSURE_SUCCESS(res, res);
4147 MOZ_ASSERT(selection->GetAnchorFocusRange() &&
4148 selection->GetAnchorFocusRange()->Collapsed(),
4149 "Selection not collapsed after delete");
4152 // If the selection is a chardata node, split it if necessary and compute
4153 // where to put the new node
4154 nsCOMPtr<nsINode> node = selection->GetAnchorNode();
4155 MOZ_ASSERT(node, "Selection has no ranges in it");
4157 if (node && node->IsNodeOfType(nsINode::eDATA_NODE)) {
4158 NS_ASSERTION(node->GetParentNode(),
4159 "It's impossible to insert into chardata with no parent -- "
4160 "fix the caller");
4161 NS_ENSURE_STATE(node->GetParentNode());
4163 uint32_t offset = selection->AnchorOffset();
4165 if (offset == 0) {
4166 res = selection->Collapse(node->GetParentNode(),
4167 node->GetParentNode()->IndexOf(node));
4168 MOZ_ASSERT(NS_SUCCEEDED(res));
4169 NS_ENSURE_SUCCESS(res, res);
4170 } else if (offset == node->Length()) {
4171 res = selection->Collapse(node->GetParentNode(),
4172 node->GetParentNode()->IndexOf(node) + 1);
4173 MOZ_ASSERT(NS_SUCCEEDED(res));
4174 NS_ENSURE_SUCCESS(res, res);
4175 } else {
4176 nsCOMPtr<nsIDOMNode> tmp;
4177 res = SplitNode(node->AsDOMNode(), offset, getter_AddRefs(tmp));
4178 NS_ENSURE_SUCCESS(res, res);
4179 res = selection->Collapse(node->GetParentNode(),
4180 node->GetParentNode()->IndexOf(node));
4181 MOZ_ASSERT(NS_SUCCEEDED(res));
4182 NS_ENSURE_SUCCESS(res, res);
4185 return NS_OK;
4190 void
4191 nsEditor::DoAfterDoTransaction(nsITransaction *aTxn)
4193 bool isTransientTransaction;
4194 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
4195 aTxn->GetIsTransient(&isTransientTransaction)));
4197 if (!isTransientTransaction)
4199 // we need to deal here with the case where the user saved after some
4200 // edits, then undid one or more times. Then, the undo count is -ve,
4201 // but we can't let a do take it back to zero. So we flip it up to
4202 // a +ve number.
4203 int32_t modCount;
4204 GetModificationCount(&modCount);
4205 if (modCount < 0)
4206 modCount = -modCount;
4208 // don't count transient transactions
4209 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
4210 IncrementModificationCount(1)));
4215 void
4216 nsEditor::DoAfterUndoTransaction()
4218 // all undoable transactions are non-transient
4219 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
4220 IncrementModificationCount(-1)));
4223 void
4224 nsEditor::DoAfterRedoTransaction()
4226 // all redoable transactions are non-transient
4227 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
4228 IncrementModificationCount(1)));
4231 already_AddRefed<ChangeAttributeTxn>
4232 nsEditor::CreateTxnForSetAttribute(Element& aElement, nsIAtom& aAttribute,
4233 const nsAString& aValue)
4235 nsRefPtr<ChangeAttributeTxn> txn =
4236 new ChangeAttributeTxn(aElement, aAttribute, &aValue);
4238 return txn.forget();
4242 already_AddRefed<ChangeAttributeTxn>
4243 nsEditor::CreateTxnForRemoveAttribute(Element& aElement, nsIAtom& aAttribute)
4245 nsRefPtr<ChangeAttributeTxn> txn =
4246 new ChangeAttributeTxn(aElement, aAttribute, nullptr);
4248 return txn.forget();
4252 already_AddRefed<CreateElementTxn>
4253 nsEditor::CreateTxnForCreateElement(nsIAtom& aTag,
4254 nsINode& aParent,
4255 int32_t aPosition)
4257 nsRefPtr<CreateElementTxn> txn =
4258 new CreateElementTxn(*this, aTag, aParent, aPosition);
4260 return txn.forget();
4264 already_AddRefed<InsertNodeTxn>
4265 nsEditor::CreateTxnForInsertNode(nsIContent& aNode,
4266 nsINode& aParent,
4267 int32_t aPosition)
4269 nsRefPtr<InsertNodeTxn> txn = new InsertNodeTxn(aNode, aParent, aPosition,
4270 *this);
4271 return txn.forget();
4274 nsresult
4275 nsEditor::CreateTxnForDeleteNode(nsINode* aNode, DeleteNodeTxn** aTxn)
4277 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
4279 nsRefPtr<DeleteNodeTxn> txn = new DeleteNodeTxn();
4281 nsresult res = txn->Init(this, aNode, &mRangeUpdater);
4282 NS_ENSURE_SUCCESS(res, res);
4284 txn.forget(aTxn);
4285 return NS_OK;
4288 already_AddRefed<IMETextTxn>
4289 nsEditor::CreateTxnForIMEText(const nsAString& aStringToInsert)
4291 // During handling IME composition, mComposition must have been initialized.
4292 // TODO: We can simplify IMETextTxn::Init() with TextComposition class.
4293 nsRefPtr<IMETextTxn> txn = new IMETextTxn(*mIMETextNode, mIMETextOffset,
4294 mComposition->String().Length(),
4295 mComposition->GetRanges(),
4296 aStringToInsert, *this);
4297 return txn.forget();
4301 NS_IMETHODIMP
4302 nsEditor::CreateTxnForAddStyleSheet(CSSStyleSheet* aSheet, AddStyleSheetTxn* *aTxn)
4304 nsRefPtr<AddStyleSheetTxn> txn = new AddStyleSheetTxn();
4306 nsresult rv = txn->Init(this, aSheet);
4307 if (NS_SUCCEEDED(rv))
4309 txn.forget(aTxn);
4312 return rv;
4317 NS_IMETHODIMP
4318 nsEditor::CreateTxnForRemoveStyleSheet(CSSStyleSheet* aSheet, RemoveStyleSheetTxn* *aTxn)
4320 nsRefPtr<RemoveStyleSheetTxn> txn = new RemoveStyleSheetTxn();
4322 nsresult rv = txn->Init(this, aSheet);
4323 if (NS_SUCCEEDED(rv))
4325 txn.forget(aTxn);
4328 return rv;
4332 nsresult
4333 nsEditor::CreateTxnForDeleteSelection(EDirection aAction,
4334 EditAggregateTxn** aTxn,
4335 nsINode** aNode,
4336 int32_t* aOffset,
4337 int32_t* aLength)
4339 MOZ_ASSERT(aTxn);
4340 *aTxn = nullptr;
4342 nsRefPtr<Selection> selection = GetSelection();
4343 NS_ENSURE_STATE(selection);
4345 // Check whether the selection is collapsed and we should do nothing:
4346 if (selection->Collapsed() && aAction == eNone) {
4347 return NS_OK;
4350 // allocate the out-param transaction
4351 nsRefPtr<EditAggregateTxn> aggTxn = new EditAggregateTxn();
4353 for (int32_t rangeIdx = 0; rangeIdx < selection->GetRangeCount(); ++rangeIdx) {
4354 nsRefPtr<nsRange> range = selection->GetRangeAt(rangeIdx);
4355 NS_ENSURE_STATE(range);
4357 // Same with range as with selection; if it is collapsed and action
4358 // is eNone, do nothing.
4359 if (!range->Collapsed()) {
4360 nsRefPtr<DeleteRangeTxn> txn = new DeleteRangeTxn();
4361 txn->Init(this, range, &mRangeUpdater);
4362 aggTxn->AppendChild(txn);
4363 } else if (aAction != eNone) {
4364 // we have an insertion point. delete the thing in front of it or
4365 // behind it, depending on aAction
4366 nsresult res = CreateTxnForDeleteInsertionPoint(range, aAction, aggTxn,
4367 aNode, aOffset, aLength);
4368 NS_ENSURE_SUCCESS(res, res);
4372 aggTxn.forget(aTxn);
4374 return NS_OK;
4377 already_AddRefed<DeleteTextTxn>
4378 nsEditor::CreateTxnForDeleteCharacter(nsGenericDOMDataNode& aData,
4379 uint32_t aOffset, EDirection aDirection)
4381 NS_ASSERTION(aDirection == eNext || aDirection == ePrevious,
4382 "Invalid direction");
4383 nsAutoString data;
4384 aData.GetData(data);
4385 NS_ASSERTION(data.Length(), "Trying to delete from a zero-length node");
4386 NS_ENSURE_TRUE(data.Length(), nullptr);
4388 uint32_t segOffset = aOffset, segLength = 1;
4389 if (aDirection == eNext) {
4390 if (segOffset + 1 < data.Length() &&
4391 NS_IS_HIGH_SURROGATE(data[segOffset]) &&
4392 NS_IS_LOW_SURROGATE(data[segOffset+1])) {
4393 // Delete both halves of the surrogate pair
4394 ++segLength;
4396 } else if (aOffset > 0) {
4397 --segOffset;
4398 if (segOffset > 0 &&
4399 NS_IS_LOW_SURROGATE(data[segOffset]) &&
4400 NS_IS_HIGH_SURROGATE(data[segOffset-1])) {
4401 ++segLength;
4402 --segOffset;
4404 } else {
4405 return nullptr;
4407 return CreateTxnForDeleteText(aData, segOffset, segLength);
4410 //XXX: currently, this doesn't handle edge conditions because GetNext/GetPrior
4411 //are not implemented
4412 nsresult
4413 nsEditor::CreateTxnForDeleteInsertionPoint(nsRange* aRange,
4414 EDirection aAction,
4415 EditAggregateTxn* aTxn,
4416 nsINode** aNode,
4417 int32_t* aOffset,
4418 int32_t* aLength)
4420 MOZ_ASSERT(aAction != eNone);
4422 nsresult res;
4424 // get the node and offset of the insertion point
4425 nsCOMPtr<nsINode> node = aRange->GetStartParent();
4426 NS_ENSURE_STATE(node);
4428 int32_t offset = aRange->StartOffset();
4430 // determine if the insertion point is at the beginning, middle, or end of
4431 // the node
4433 uint32_t count = node->Length();
4435 bool isFirst = (0 == offset);
4436 bool isLast = (count == (uint32_t)offset);
4438 // XXX: if isFirst && isLast, then we'll need to delete the node
4439 // as well as the 1 child
4441 // build a transaction for deleting the appropriate data
4442 // XXX: this has to come from rule section
4443 if (aAction == ePrevious && isFirst) {
4444 // we're backspacing from the beginning of the node. Delete the first
4445 // thing to our left
4446 nsCOMPtr<nsIContent> priorNode = GetPriorNode(node, true);
4447 NS_ENSURE_STATE(priorNode);
4449 // there is a priorNode, so delete its last child (if chardata, delete the
4450 // last char). if it has no children, delete it
4451 if (priorNode->IsNodeOfType(nsINode::eDATA_NODE)) {
4452 nsRefPtr<nsGenericDOMDataNode> priorNodeAsCharData =
4453 static_cast<nsGenericDOMDataNode*>(priorNode.get());
4454 uint32_t length = priorNode->Length();
4455 // Bail out for empty chardata XXX: Do we want to do something else?
4456 NS_ENSURE_STATE(length);
4457 nsRefPtr<DeleteTextTxn> txn =
4458 CreateTxnForDeleteCharacter(*priorNodeAsCharData, length, ePrevious);
4459 NS_ENSURE_STATE(txn);
4461 *aOffset = txn->GetOffset();
4462 *aLength = txn->GetNumCharsToDelete();
4463 aTxn->AppendChild(txn);
4464 } else {
4465 // priorNode is not chardata, so tell its parent to delete it
4466 nsRefPtr<DeleteNodeTxn> txn;
4467 res = CreateTxnForDeleteNode(priorNode, getter_AddRefs(txn));
4468 NS_ENSURE_SUCCESS(res, res);
4470 aTxn->AppendChild(txn);
4473 NS_ADDREF(*aNode = priorNode);
4475 return NS_OK;
4478 if (aAction == eNext && isLast) {
4479 // we're deleting from the end of the node. Delete the first thing to our
4480 // right
4481 nsCOMPtr<nsIContent> nextNode = GetNextNode(node, true);
4482 NS_ENSURE_STATE(nextNode);
4484 // there is a nextNode, so delete its first child (if chardata, delete the
4485 // first char). if it has no children, delete it
4486 if (nextNode->IsNodeOfType(nsINode::eDATA_NODE)) {
4487 nsRefPtr<nsGenericDOMDataNode> nextNodeAsCharData =
4488 static_cast<nsGenericDOMDataNode*>(nextNode.get());
4489 uint32_t length = nextNode->Length();
4490 // Bail out for empty chardata XXX: Do we want to do something else?
4491 NS_ENSURE_STATE(length);
4492 nsRefPtr<DeleteTextTxn> txn =
4493 CreateTxnForDeleteCharacter(*nextNodeAsCharData, 0, eNext);
4494 NS_ENSURE_STATE(txn);
4496 *aOffset = txn->GetOffset();
4497 *aLength = txn->GetNumCharsToDelete();
4498 aTxn->AppendChild(txn);
4499 } else {
4500 // nextNode is not chardata, so tell its parent to delete it
4501 nsRefPtr<DeleteNodeTxn> txn;
4502 res = CreateTxnForDeleteNode(nextNode, getter_AddRefs(txn));
4503 NS_ENSURE_SUCCESS(res, res);
4504 aTxn->AppendChild(txn);
4507 NS_ADDREF(*aNode = nextNode);
4509 return NS_OK;
4512 if (node->IsNodeOfType(nsINode::eDATA_NODE)) {
4513 nsRefPtr<nsGenericDOMDataNode> nodeAsCharData =
4514 static_cast<nsGenericDOMDataNode*>(node.get());
4515 // we have chardata, so delete a char at the proper offset
4516 nsRefPtr<DeleteTextTxn> txn = CreateTxnForDeleteCharacter(*nodeAsCharData,
4517 offset, aAction);
4518 NS_ENSURE_STATE(txn);
4520 aTxn->AppendChild(txn);
4521 NS_ADDREF(*aNode = node);
4522 *aOffset = txn->GetOffset();
4523 *aLength = txn->GetNumCharsToDelete();
4524 } else {
4525 // we're either deleting a node or chardata, need to dig into the next/prev
4526 // node to find out
4527 nsCOMPtr<nsINode> selectedNode;
4528 if (aAction == ePrevious) {
4529 selectedNode = GetPriorNode(node, offset, true);
4530 } else if (aAction == eNext) {
4531 selectedNode = GetNextNode(node, offset, true);
4534 while (selectedNode &&
4535 selectedNode->IsNodeOfType(nsINode::eDATA_NODE) &&
4536 !selectedNode->Length()) {
4537 // Can't delete an empty chardata node (bug 762183)
4538 if (aAction == ePrevious) {
4539 selectedNode = GetPriorNode(selectedNode, true);
4540 } else if (aAction == eNext) {
4541 selectedNode = GetNextNode(selectedNode, true);
4544 NS_ENSURE_STATE(selectedNode);
4546 if (selectedNode->IsNodeOfType(nsINode::eDATA_NODE)) {
4547 nsRefPtr<nsGenericDOMDataNode> selectedNodeAsCharData =
4548 static_cast<nsGenericDOMDataNode*>(selectedNode.get());
4549 // we are deleting from a chardata node, so do a character deletion
4550 uint32_t position = 0;
4551 if (aAction == ePrevious) {
4552 position = selectedNode->Length();
4554 nsRefPtr<DeleteTextTxn> delTextTxn =
4555 CreateTxnForDeleteCharacter(*selectedNodeAsCharData, position,
4556 aAction);
4557 NS_ENSURE_TRUE(delTextTxn, NS_ERROR_NULL_POINTER);
4559 aTxn->AppendChild(delTextTxn);
4560 *aOffset = delTextTxn->GetOffset();
4561 *aLength = delTextTxn->GetNumCharsToDelete();
4562 } else {
4563 nsRefPtr<DeleteNodeTxn> delElementTxn;
4564 res = CreateTxnForDeleteNode(selectedNode, getter_AddRefs(delElementTxn));
4565 NS_ENSURE_SUCCESS(res, res);
4566 NS_ENSURE_TRUE(delElementTxn, NS_ERROR_NULL_POINTER);
4568 aTxn->AppendChild(delElementTxn);
4571 NS_ADDREF(*aNode = selectedNode);
4574 return NS_OK;
4577 nsresult
4578 nsEditor::CreateRange(nsIDOMNode *aStartParent, int32_t aStartOffset,
4579 nsIDOMNode *aEndParent, int32_t aEndOffset,
4580 nsIDOMRange **aRange)
4582 return nsRange::CreateRange(aStartParent, aStartOffset, aEndParent,
4583 aEndOffset, aRange);
4586 nsresult
4587 nsEditor::AppendNodeToSelectionAsRange(nsIDOMNode *aNode)
4589 NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
4590 nsCOMPtr<nsISelection> selection;
4591 nsresult res = GetSelection(getter_AddRefs(selection));
4592 NS_ENSURE_SUCCESS(res, res);
4593 if(!selection) return NS_ERROR_FAILURE;
4595 nsCOMPtr<nsIDOMNode> parentNode;
4596 res = aNode->GetParentNode(getter_AddRefs(parentNode));
4597 NS_ENSURE_SUCCESS(res, res);
4598 NS_ENSURE_TRUE(parentNode, NS_ERROR_NULL_POINTER);
4600 int32_t offset = GetChildOffset(aNode, parentNode);
4602 nsCOMPtr<nsIDOMRange> range;
4603 res = CreateRange(parentNode, offset, parentNode, offset+1, getter_AddRefs(range));
4604 NS_ENSURE_SUCCESS(res, res);
4605 NS_ENSURE_TRUE(range, NS_ERROR_NULL_POINTER);
4607 return selection->AddRange(range);
4610 nsresult nsEditor::ClearSelection()
4612 nsCOMPtr<nsISelection> selection;
4613 nsresult res = nsEditor::GetSelection(getter_AddRefs(selection));
4614 NS_ENSURE_SUCCESS(res, res);
4615 NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
4616 return selection->RemoveAllRanges();
4619 already_AddRefed<Element>
4620 nsEditor::CreateHTMLContent(nsIAtom* aTag)
4622 MOZ_ASSERT(aTag);
4624 nsCOMPtr<nsIDocument> doc = GetDocument();
4625 if (!doc) {
4626 return nullptr;
4629 // XXX Wallpaper over editor bug (editor tries to create elements with an
4630 // empty nodename).
4631 if (aTag == nsGkAtoms::_empty) {
4632 NS_ERROR("Don't pass an empty tag to nsEditor::CreateHTMLContent, "
4633 "check caller.");
4634 return nullptr;
4637 nsCOMPtr<nsIContent> ret;
4638 nsresult res = doc->CreateElem(nsDependentAtomString(aTag), nullptr,
4639 kNameSpaceID_XHTML, getter_AddRefs(ret));
4640 NS_ENSURE_SUCCESS(res, nullptr);
4641 return dont_AddRef(ret.forget().take()->AsElement());
4644 nsresult
4645 nsEditor::SetAttributeOrEquivalent(nsIDOMElement * aElement,
4646 const nsAString & aAttribute,
4647 const nsAString & aValue,
4648 bool aSuppressTransaction)
4650 return SetAttribute(aElement, aAttribute, aValue);
4653 nsresult
4654 nsEditor::RemoveAttributeOrEquivalent(nsIDOMElement * aElement,
4655 const nsAString & aAttribute,
4656 bool aSuppressTransaction)
4658 return RemoveAttribute(aElement, aAttribute);
4661 nsresult
4662 nsEditor::HandleKeyPressEvent(nsIDOMKeyEvent* aKeyEvent)
4664 // NOTE: When you change this method, you should also change:
4665 // * editor/libeditor/tests/test_texteditor_keyevent_handling.html
4666 // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html
4668 // And also when you add new key handling, you need to change the subclass's
4669 // HandleKeyPressEvent()'s switch statement.
4671 WidgetKeyboardEvent* nativeKeyEvent =
4672 aKeyEvent->GetInternalNSEvent()->AsKeyboardEvent();
4673 NS_ENSURE_TRUE(nativeKeyEvent, NS_ERROR_UNEXPECTED);
4674 NS_ASSERTION(nativeKeyEvent->message == NS_KEY_PRESS,
4675 "HandleKeyPressEvent gets non-keypress event");
4677 // if we are readonly or disabled, then do nothing.
4678 if (IsReadonly() || IsDisabled()) {
4679 // consume backspace for disabled and readonly textfields, to prevent
4680 // back in history, which could be confusing to users
4681 if (nativeKeyEvent->keyCode == nsIDOMKeyEvent::DOM_VK_BACK_SPACE) {
4682 aKeyEvent->PreventDefault();
4684 return NS_OK;
4687 switch (nativeKeyEvent->keyCode) {
4688 case nsIDOMKeyEvent::DOM_VK_META:
4689 case nsIDOMKeyEvent::DOM_VK_WIN:
4690 case nsIDOMKeyEvent::DOM_VK_SHIFT:
4691 case nsIDOMKeyEvent::DOM_VK_CONTROL:
4692 case nsIDOMKeyEvent::DOM_VK_ALT:
4693 aKeyEvent->PreventDefault(); // consumed
4694 return NS_OK;
4695 case nsIDOMKeyEvent::DOM_VK_BACK_SPACE:
4696 if (nativeKeyEvent->IsControl() || nativeKeyEvent->IsAlt() ||
4697 nativeKeyEvent->IsMeta() || nativeKeyEvent->IsOS()) {
4698 return NS_OK;
4700 DeleteSelection(nsIEditor::ePrevious, nsIEditor::eStrip);
4701 aKeyEvent->PreventDefault(); // consumed
4702 return NS_OK;
4703 case nsIDOMKeyEvent::DOM_VK_DELETE:
4704 // on certain platforms (such as windows) the shift key
4705 // modifies what delete does (cmd_cut in this case).
4706 // bailing here to allow the keybindings to do the cut.
4707 if (nativeKeyEvent->IsShift() || nativeKeyEvent->IsControl() ||
4708 nativeKeyEvent->IsAlt() || nativeKeyEvent->IsMeta() ||
4709 nativeKeyEvent->IsOS()) {
4710 return NS_OK;
4712 DeleteSelection(nsIEditor::eNext, nsIEditor::eStrip);
4713 aKeyEvent->PreventDefault(); // consumed
4714 return NS_OK;
4716 return NS_OK;
4719 nsresult
4720 nsEditor::HandleInlineSpellCheck(EditAction action,
4721 nsISelection *aSelection,
4722 nsIDOMNode *previousSelectedNode,
4723 int32_t previousSelectedOffset,
4724 nsIDOMNode *aStartNode,
4725 int32_t aStartOffset,
4726 nsIDOMNode *aEndNode,
4727 int32_t aEndOffset)
4729 // Have to cast action here because this method is from an IDL
4730 return mInlineSpellChecker ? mInlineSpellChecker->SpellCheckAfterEditorChange(
4731 (int32_t)action, aSelection,
4732 previousSelectedNode, previousSelectedOffset,
4733 aStartNode, aStartOffset, aEndNode,
4734 aEndOffset)
4735 : NS_OK;
4738 already_AddRefed<nsIContent>
4739 nsEditor::FindSelectionRoot(nsINode *aNode)
4741 nsCOMPtr<nsIContent> rootContent = GetRoot();
4742 return rootContent.forget();
4745 nsresult
4746 nsEditor::InitializeSelection(nsIDOMEventTarget* aFocusEventTarget)
4748 nsCOMPtr<nsINode> targetNode = do_QueryInterface(aFocusEventTarget);
4749 NS_ENSURE_TRUE(targetNode, NS_ERROR_INVALID_ARG);
4750 nsCOMPtr<nsIContent> selectionRootContent = FindSelectionRoot(targetNode);
4751 if (!selectionRootContent) {
4752 return NS_OK;
4755 bool isTargetDoc =
4756 targetNode->NodeType() == nsIDOMNode::DOCUMENT_NODE &&
4757 targetNode->HasFlag(NODE_IS_EDITABLE);
4759 nsCOMPtr<nsISelection> selection;
4760 nsresult rv = GetSelection(getter_AddRefs(selection));
4761 NS_ENSURE_SUCCESS(rv, rv);
4763 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
4764 NS_ENSURE_TRUE(presShell, NS_ERROR_NOT_INITIALIZED);
4766 nsCOMPtr<nsISelectionController> selCon;
4767 rv = GetSelectionController(getter_AddRefs(selCon));
4768 NS_ENSURE_SUCCESS(rv, rv);
4770 nsCOMPtr<nsISelectionPrivate> selectionPrivate =
4771 do_QueryInterface(selection);
4772 NS_ENSURE_TRUE(selectionPrivate, NS_ERROR_UNEXPECTED);
4774 // Init the caret
4775 nsRefPtr<nsCaret> caret = presShell->GetCaret();
4776 NS_ENSURE_TRUE(caret, NS_ERROR_UNEXPECTED);
4777 caret->SetIgnoreUserModify(false);
4778 caret->SetSelection(selection);
4779 selCon->SetCaretReadOnly(IsReadonly());
4780 selCon->SetCaretEnabled(true);
4782 // Init selection
4783 selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
4784 selCon->SetSelectionFlags(nsISelectionDisplay::DISPLAY_ALL);
4785 selCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL);
4786 // If the computed selection root isn't root content, we should set it
4787 // as selection ancestor limit. However, if that is root element, it means
4788 // there is not limitation of the selection, then, we must set nullptr.
4789 // NOTE: If we set a root element to the ancestor limit, some selection
4790 // methods don't work fine.
4791 if (selectionRootContent->GetParent()) {
4792 selectionPrivate->SetAncestorLimiter(selectionRootContent);
4793 } else {
4794 selectionPrivate->SetAncestorLimiter(nullptr);
4797 // XXX What case needs this?
4798 if (isTargetDoc) {
4799 int32_t rangeCount;
4800 selection->GetRangeCount(&rangeCount);
4801 if (rangeCount == 0) {
4802 BeginningOfDocument();
4806 return NS_OK;
4809 void
4810 nsEditor::FinalizeSelection()
4812 nsCOMPtr<nsISelectionController> selCon;
4813 nsresult rv = GetSelectionController(getter_AddRefs(selCon));
4814 NS_ENSURE_SUCCESS_VOID(rv);
4816 nsCOMPtr<nsISelection> selection;
4817 rv = selCon->GetSelection(nsISelectionController::SELECTION_NORMAL,
4818 getter_AddRefs(selection));
4819 NS_ENSURE_SUCCESS_VOID(rv);
4821 nsCOMPtr<nsISelectionPrivate> selectionPrivate = do_QueryInterface(selection);
4822 NS_ENSURE_TRUE_VOID(selectionPrivate);
4824 selectionPrivate->SetAncestorLimiter(nullptr);
4826 nsCOMPtr<nsIPresShell> presShell = GetPresShell();
4827 NS_ENSURE_TRUE_VOID(presShell);
4829 selCon->SetCaretEnabled(false);
4831 nsFocusManager* fm = nsFocusManager::GetFocusManager();
4832 NS_ENSURE_TRUE_VOID(fm);
4833 fm->UpdateCaretForCaretBrowsingMode();
4835 if (!HasIndependentSelection()) {
4836 // If this editor doesn't have an independent selection, i.e., it must
4837 // mean that it is an HTML editor, the selection controller is shared with
4838 // presShell. So, even this editor loses focus, other part of the document
4839 // may still have focus.
4840 nsCOMPtr<nsIDocument> doc = GetDocument();
4841 ErrorResult ret;
4842 if (!doc || !doc->HasFocus(ret)) {
4843 // If the document already lost focus, mark the selection as disabled.
4844 selCon->SetDisplaySelection(nsISelectionController::SELECTION_DISABLED);
4845 } else {
4846 // Otherwise, mark selection as normal because outside of a
4847 // contenteditable element should be selected with normal selection
4848 // color after here.
4849 selCon->SetDisplaySelection(nsISelectionController::SELECTION_ON);
4851 } else if (IsFormWidget() || IsPasswordEditor() ||
4852 IsReadonly() || IsDisabled() || IsInputFiltered()) {
4853 // In <input> or <textarea>, the independent selection should be hidden
4854 // while this editor doesn't have focus.
4855 selCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
4856 } else {
4857 // Otherwise, although we're not sure how this case happens, the
4858 // independent selection should be marked as disabled.
4859 selCon->SetDisplaySelection(nsISelectionController::SELECTION_DISABLED);
4862 selCon->RepaintSelection(nsISelectionController::SELECTION_NORMAL);
4865 dom::Element *
4866 nsEditor::GetRoot()
4868 if (!mRootElement)
4870 nsCOMPtr<nsIDOMElement> root;
4872 // Let GetRootElement() do the work
4873 GetRootElement(getter_AddRefs(root));
4876 return mRootElement;
4879 dom::Element*
4880 nsEditor::GetEditorRoot()
4882 return GetRoot();
4885 Element*
4886 nsEditor::GetExposedRoot()
4888 Element* rootElement = GetRoot();
4890 // For plaintext editors, we need to ask the input/textarea element directly.
4891 if (rootElement && rootElement->IsRootOfNativeAnonymousSubtree()) {
4892 rootElement = rootElement->GetParent()->AsElement();
4895 return rootElement;
4898 nsresult
4899 nsEditor::DetermineCurrentDirection()
4901 // Get the current root direction from its frame
4902 nsIContent* rootElement = GetExposedRoot();
4903 NS_ENSURE_TRUE(rootElement, NS_ERROR_FAILURE);
4905 // If we don't have an explicit direction, determine our direction
4906 // from the content's direction
4907 if (!(mFlags & (nsIPlaintextEditor::eEditorLeftToRight |
4908 nsIPlaintextEditor::eEditorRightToLeft))) {
4910 nsIFrame* frame = rootElement->GetPrimaryFrame();
4911 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
4913 // Set the flag here, to enable us to use the same code path below.
4914 // It will be flipped before returning from the function.
4915 if (frame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
4916 mFlags |= nsIPlaintextEditor::eEditorRightToLeft;
4917 } else {
4918 mFlags |= nsIPlaintextEditor::eEditorLeftToRight;
4922 return NS_OK;
4925 NS_IMETHODIMP
4926 nsEditor::SwitchTextDirection()
4928 // Get the current root direction from its frame
4929 nsIContent* rootElement = GetExposedRoot();
4931 nsresult rv = DetermineCurrentDirection();
4932 NS_ENSURE_SUCCESS(rv, rv);
4934 // Apply the opposite direction
4935 if (mFlags & nsIPlaintextEditor::eEditorRightToLeft) {
4936 NS_ASSERTION(!(mFlags & nsIPlaintextEditor::eEditorLeftToRight),
4937 "Unexpected mutually exclusive flag");
4938 mFlags &= ~nsIPlaintextEditor::eEditorRightToLeft;
4939 mFlags |= nsIPlaintextEditor::eEditorLeftToRight;
4940 rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("ltr"), true);
4941 } else if (mFlags & nsIPlaintextEditor::eEditorLeftToRight) {
4942 NS_ASSERTION(!(mFlags & nsIPlaintextEditor::eEditorRightToLeft),
4943 "Unexpected mutually exclusive flag");
4944 mFlags |= nsIPlaintextEditor::eEditorRightToLeft;
4945 mFlags &= ~nsIPlaintextEditor::eEditorLeftToRight;
4946 rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("rtl"), true);
4949 if (NS_SUCCEEDED(rv)) {
4950 FireInputEvent();
4953 return rv;
4956 void
4957 nsEditor::SwitchTextDirectionTo(uint32_t aDirection)
4959 // Get the current root direction from its frame
4960 nsIContent* rootElement = GetExposedRoot();
4962 nsresult rv = DetermineCurrentDirection();
4963 NS_ENSURE_SUCCESS_VOID(rv);
4965 // Apply the requested direction
4966 if (aDirection == nsIPlaintextEditor::eEditorLeftToRight &&
4967 (mFlags & nsIPlaintextEditor::eEditorRightToLeft)) {
4968 NS_ASSERTION(!(mFlags & nsIPlaintextEditor::eEditorLeftToRight),
4969 "Unexpected mutually exclusive flag");
4970 mFlags &= ~nsIPlaintextEditor::eEditorRightToLeft;
4971 mFlags |= nsIPlaintextEditor::eEditorLeftToRight;
4972 rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("ltr"), true);
4973 } else if (aDirection == nsIPlaintextEditor::eEditorRightToLeft &&
4974 (mFlags & nsIPlaintextEditor::eEditorLeftToRight)) {
4975 NS_ASSERTION(!(mFlags & nsIPlaintextEditor::eEditorRightToLeft),
4976 "Unexpected mutually exclusive flag");
4977 mFlags |= nsIPlaintextEditor::eEditorRightToLeft;
4978 mFlags &= ~nsIPlaintextEditor::eEditorLeftToRight;
4979 rv = rootElement->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, NS_LITERAL_STRING("rtl"), true);
4982 if (NS_SUCCEEDED(rv)) {
4983 FireInputEvent();
4987 #if DEBUG_JOE
4988 void
4989 nsEditor::DumpNode(nsIDOMNode *aNode, int32_t indent)
4991 int32_t i;
4992 for (i=0; i<indent; i++)
4993 printf(" ");
4995 nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aNode);
4996 nsCOMPtr<nsIDOMDocumentFragment> docfrag = do_QueryInterface(aNode);
4998 if (element || docfrag)
5000 if (element)
5002 nsAutoString tag;
5003 element->GetTagName(tag);
5004 printf("<%s>\n", NS_LossyConvertUTF16toASCII(tag).get());
5006 else
5008 printf("<document fragment>\n");
5010 nsCOMPtr<nsIDOMNodeList> childList;
5011 aNode->GetChildNodes(getter_AddRefs(childList));
5012 NS_ENSURE_TRUE(childList, NS_ERROR_NULL_POINTER);
5013 uint32_t numChildren;
5014 childList->GetLength(&numChildren);
5015 nsCOMPtr<nsIDOMNode> child, tmp;
5016 aNode->GetFirstChild(getter_AddRefs(child));
5017 for (i=0; i<numChildren; i++)
5019 DumpNode(child, indent+1);
5020 child->GetNextSibling(getter_AddRefs(tmp));
5021 child = tmp;
5024 else if (IsTextNode(aNode))
5026 nsCOMPtr<nsIDOMCharacterData> textNode = do_QueryInterface(aNode);
5027 nsAutoString str;
5028 textNode->GetData(str);
5029 nsAutoCString cstr;
5030 LossyCopyUTF16toASCII(str, cstr);
5031 cstr.ReplaceChar('\n', ' ');
5032 printf("<textnode> %s\n", cstr.get());
5035 #endif
5037 bool
5038 nsEditor::IsModifiableNode(nsIDOMNode *aNode)
5040 return true;
5043 bool
5044 nsEditor::IsModifiableNode(nsINode *aNode)
5046 return true;
5049 already_AddRefed<nsIContent>
5050 nsEditor::GetFocusedContent()
5052 nsCOMPtr<nsIDOMEventTarget> piTarget = GetDOMEventTarget();
5053 if (!piTarget) {
5054 return nullptr;
5057 nsFocusManager* fm = nsFocusManager::GetFocusManager();
5058 NS_ENSURE_TRUE(fm, nullptr);
5060 nsCOMPtr<nsIContent> content = fm->GetFocusedContent();
5061 return SameCOMIdentity(content, piTarget) ? content.forget() : nullptr;
5064 already_AddRefed<nsIContent>
5065 nsEditor::GetFocusedContentForIME()
5067 return GetFocusedContent();
5070 bool
5071 nsEditor::IsActiveInDOMWindow()
5073 nsCOMPtr<nsIDOMEventTarget> piTarget = GetDOMEventTarget();
5074 if (!piTarget) {
5075 return false;
5078 nsFocusManager* fm = nsFocusManager::GetFocusManager();
5079 NS_ENSURE_TRUE(fm, false);
5081 nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocWeak);
5082 nsPIDOMWindow* ourWindow = doc->GetWindow();
5083 nsCOMPtr<nsPIDOMWindow> win;
5084 nsIContent* content =
5085 nsFocusManager::GetFocusedDescendant(ourWindow, false,
5086 getter_AddRefs(win));
5087 return SameCOMIdentity(content, piTarget);
5090 bool
5091 nsEditor::IsAcceptableInputEvent(nsIDOMEvent* aEvent)
5093 // If the event is trusted, the event should always cause input.
5094 NS_ENSURE_TRUE(aEvent, false);
5096 WidgetEvent* widgetEvent = aEvent->GetInternalNSEvent();
5097 if (NS_WARN_IF(!widgetEvent)) {
5098 return false;
5101 // If this is dispatched by using cordinates but this editor doesn't have
5102 // focus, we shouldn't handle it.
5103 if (widgetEvent->IsUsingCoordinates()) {
5104 nsCOMPtr<nsIContent> focusedContent = GetFocusedContent();
5105 if (!focusedContent) {
5106 return false;
5110 // If composition event or text event isn't dispatched via widget,
5111 // we need to ignore them since they cannot be managed by TextComposition.
5112 // E.g., the event was created by chrome JS.
5113 // Note that if we allow to handle such events, editor may be confused by
5114 // strange event order.
5115 bool needsWidget = false;
5116 WidgetGUIEvent* widgetGUIEvent = nullptr;
5117 switch (widgetEvent->message) {
5118 case NS_USER_DEFINED_EVENT:
5119 // If events are not created with proper event interface, their message
5120 // are initialized with NS_USER_DEFINED_EVENT. Let's ignore such event.
5121 return false;
5122 case NS_TEXT_TEXT:
5123 // Don't allow text events whose internal event are not
5124 // WidgetTextEvent.
5125 widgetGUIEvent = aEvent->GetInternalNSEvent()->AsTextEvent();
5126 needsWidget = true;
5127 break;
5128 case NS_COMPOSITION_START:
5129 case NS_COMPOSITION_END:
5130 case NS_COMPOSITION_UPDATE:
5131 // Don't allow composition events whose internal event are not
5132 // WidgetCompositionEvent.
5133 widgetGUIEvent = aEvent->GetInternalNSEvent()->AsCompositionEvent();
5134 needsWidget = true;
5135 break;
5136 default:
5137 break;
5139 if (needsWidget &&
5140 (!widgetGUIEvent || !widgetGUIEvent->widget)) {
5141 return false;
5144 // Accept all trusted events.
5145 if (widgetEvent->mFlags.mIsTrusted) {
5146 return true;
5149 // Ignore untrusted mouse event.
5150 // XXX Why are we handling other untrusted input events?
5151 if (widgetEvent->AsMouseEventBase()) {
5152 return false;
5155 // Otherwise, we shouldn't handle any input events when we're not an active
5156 // element of the DOM window.
5157 return IsActiveInDOMWindow();
5160 void
5161 nsEditor::OnFocus(nsIDOMEventTarget* aFocusEventTarget)
5163 InitializeSelection(aFocusEventTarget);
5164 if (mInlineSpellChecker) {
5165 mInlineSpellChecker->UpdateCurrentDictionary();
5169 NS_IMETHODIMP
5170 nsEditor::GetSuppressDispatchingInputEvent(bool *aSuppressed)
5172 NS_ENSURE_ARG_POINTER(aSuppressed);
5173 *aSuppressed = !mDispatchInputEvent;
5174 return NS_OK;
5177 NS_IMETHODIMP
5178 nsEditor::SetSuppressDispatchingInputEvent(bool aSuppress)
5180 mDispatchInputEvent = !aSuppress;
5181 return NS_OK;
5184 NS_IMETHODIMP
5185 nsEditor::GetIsInEditAction(bool* aIsInEditAction)
5187 MOZ_ASSERT(aIsInEditAction, "aIsInEditAction must not be null");
5188 *aIsInEditAction = mIsInEditAction;
5189 return NS_OK;