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 "CreateElementTransaction.h"
11 #include "mozilla/dom/Element.h"
12 #include "mozilla/dom/Selection.h"
14 #include "mozilla/Casting.h"
15 #include "mozilla/EditorBase.h"
16 #include "mozilla/EditorDOMPoint.h"
18 #include "nsAlgorithm.h"
19 #include "nsAString.h"
22 #include "nsIContent.h"
24 #include "nsISupportsUtils.h"
26 #include "nsReadableUtils.h"
27 #include "nsStringFwd.h"
34 template already_AddRefed
<CreateElementTransaction
>
35 CreateElementTransaction::Create(EditorBase
& aEditorBase
, nsAtom
& aTag
,
36 const EditorDOMPoint
& aPointToInsert
);
37 template already_AddRefed
<CreateElementTransaction
>
38 CreateElementTransaction::Create(EditorBase
& aEditorBase
, nsAtom
& aTag
,
39 const EditorRawDOMPoint
& aPointToInsert
);
41 template <typename PT
, typename CT
>
42 already_AddRefed
<CreateElementTransaction
> CreateElementTransaction::Create(
43 EditorBase
& aEditorBase
, nsAtom
& aTag
,
44 const EditorDOMPointBase
<PT
, CT
>& aPointToInsert
) {
45 RefPtr
<CreateElementTransaction
> transaction
=
46 new CreateElementTransaction(aEditorBase
, aTag
, aPointToInsert
);
47 return transaction
.forget();
50 template <typename PT
, typename CT
>
51 CreateElementTransaction::CreateElementTransaction(
52 EditorBase
& aEditorBase
, nsAtom
& aTag
,
53 const EditorDOMPointBase
<PT
, CT
>& aPointToInsert
)
54 : EditTransactionBase(),
55 mEditorBase(&aEditorBase
),
57 mPointToInsert(aPointToInsert
) {
58 MOZ_ASSERT(!mPointToInsert
.IsInDataNode());
59 // We only need the child node at inserting new node.
60 AutoEditorDOMPointOffsetInvalidator
lockChild(mPointToInsert
);
63 NS_IMPL_CYCLE_COLLECTION_INHERITED(CreateElementTransaction
,
64 EditTransactionBase
, mEditorBase
,
65 mPointToInsert
, mNewElement
)
67 NS_IMPL_ADDREF_INHERITED(CreateElementTransaction
, EditTransactionBase
)
68 NS_IMPL_RELEASE_INHERITED(CreateElementTransaction
, EditTransactionBase
)
69 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CreateElementTransaction
)
70 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase
)
72 NS_IMETHODIMP
CreateElementTransaction::DoTransaction() {
73 if (NS_WARN_IF(!mEditorBase
) || NS_WARN_IF(!mTag
) ||
74 NS_WARN_IF(!mPointToInsert
.IsSet())) {
75 return NS_ERROR_NOT_INITIALIZED
;
78 OwningNonNull
<EditorBase
> editorBase
= *mEditorBase
;
80 mNewElement
= editorBase
->CreateHTMLContent(mTag
);
82 NS_WARNING("EditorBase::CreateHTMLContent() failed");
83 return NS_ERROR_FAILURE
;
86 // Try to insert formatting white-space for the new node:
87 OwningNonNull
<Element
> newElement
= *mNewElement
;
88 nsresult rv
= editorBase
->MarkElementDirty(newElement
);
89 if (NS_WARN_IF(rv
== NS_ERROR_EDITOR_DESTROYED
)) {
90 return EditorBase::ToGenericNSResult(rv
);
92 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv
),
93 "EditorBase::MarkElementDirty() failed, but ignored");
95 // Insert the new node
99 NS_WARNING("CreateElementTransaction::InsertNewNode() failed");
100 return error
.StealNSResult();
103 // Only set selection to insertion point if editor gives permission
104 if (!editorBase
->AllowsTransactionsToChangeSelection()) {
105 // Do nothing - DOM range gravity will adjust selection
109 RefPtr
<Selection
> selection
= editorBase
->GetSelection();
110 if (NS_WARN_IF(!selection
)) {
111 return NS_ERROR_FAILURE
;
114 EditorRawDOMPoint
afterNewNode(EditorRawDOMPoint::After(newElement
));
115 if (NS_WARN_IF(!afterNewNode
.IsSet())) {
116 // If mutation observer or mutation event listener moved or removed the
117 // new node, we hit this case. Should we use script blocker while we're
119 return NS_ERROR_FAILURE
;
121 IgnoredErrorResult ignoredError
;
122 selection
->CollapseInLimiter(afterNewNode
, ignoredError
);
123 NS_WARNING_ASSERTION(!ignoredError
.Failed(),
124 "Selection::CollapseInLimiter() failed, but ignored");
128 void CreateElementTransaction::InsertNewNode(ErrorResult
& aError
) {
129 MOZ_ASSERT(mNewElement
);
130 MOZ_ASSERT(mPointToInsert
.IsSet());
132 if (mPointToInsert
.IsSetAndValid()) {
133 if (mPointToInsert
.IsEndOfContainer()) {
134 OwningNonNull
<nsINode
> container
= *mPointToInsert
.GetContainer();
135 OwningNonNull
<Element
> newElement
= *mNewElement
;
136 container
->AppendChild(newElement
, aError
);
137 NS_WARNING_ASSERTION(!aError
.Failed(),
138 "nsINode::AppendChild() failed, but ignored");
141 MOZ_ASSERT(mPointToInsert
.GetChild());
142 OwningNonNull
<nsINode
> container
= *mPointToInsert
.GetContainer();
143 OwningNonNull
<nsIContent
> child
= *mPointToInsert
.GetChild();
144 OwningNonNull
<Element
> newElement
= *mNewElement
;
145 container
->InsertBefore(newElement
, child
, aError
);
146 NS_WARNING_ASSERTION(!aError
.Failed(),
147 "nsINode::InsertBefore() failed, but ignored");
148 // InsertBefore() may call MightThrowJSException() even if there is no
149 // error. We don't need the flag here.
150 aError
.WouldReportJSException();
154 // We still know a child, but the child is different element's child,
155 // we should just return error.
156 if (NS_WARN_IF(mPointToInsert
.GetChild() &&
157 mPointToInsert
.GetContainer() !=
158 mPointToInsert
.GetChild()->GetParentNode())) {
159 // XXX Is NS_ERROR_EDITOR_UNEXPECTED_DOM_TREE better? Since it won't
160 // cause throwing exception even if editor user throws an error
161 // returned from editor's public method.
162 aError
.Throw(NS_ERROR_FAILURE
);
166 // If mPointToInsert has only offset and it's not valid, we need to treat
167 // it as pointing end of the container.
168 OwningNonNull
<nsINode
> container
= *mPointToInsert
.GetContainer();
169 OwningNonNull
<Element
> newElement
= *mNewElement
;
170 container
->AppendChild(newElement
, aError
);
171 NS_WARNING_ASSERTION(!aError
.Failed(),
172 "nsINode::AppendChild() failed, but ignored");
175 NS_IMETHODIMP
CreateElementTransaction::UndoTransaction() {
176 if (NS_WARN_IF(!mEditorBase
) || NS_WARN_IF(!mPointToInsert
.IsSet()) ||
177 NS_WARN_IF(!mNewElement
)) {
178 return NS_ERROR_NOT_AVAILABLE
;
181 OwningNonNull
<Element
> newElement
= *mNewElement
;
182 OwningNonNull
<nsINode
> containerNode
= *mPointToInsert
.GetContainer();
184 containerNode
->RemoveChild(newElement
, error
);
185 NS_WARNING_ASSERTION(!error
.Failed(), "nsINode::RemoveChild() failed");
186 return error
.StealNSResult();
189 NS_IMETHODIMP
CreateElementTransaction::RedoTransaction() {
190 if (NS_WARN_IF(!mEditorBase
) || NS_WARN_IF(!mPointToInsert
.IsSet()) ||
191 NS_WARN_IF(!mNewElement
)) {
192 return NS_ERROR_NOT_AVAILABLE
;
195 // First, reset mNewElement so it has no attributes or content
196 // XXX We never actually did this, we only cleared mNewElement's contents if
197 // it was a CharacterData node (which it's not, it's an Element)
198 // XXX Don't we need to set selection like DoTransaction()?
200 // Now, reinsert mNewElement
202 InsertNewNode(error
);
203 NS_WARNING_ASSERTION(!error
.Failed(),
204 "CreateElementTransaction::InsertNewNode() failed");
205 return error
.StealNSResult();
208 } // namespace mozilla