Bug 1686495 [wpt PR 27132] - Add tests for proposed WebDriver Shadow DOM support...
[gecko.git] / editor / libeditor / CreateElementTransaction.cpp
blob2dee25c7361fd7fc26bfcc57f85369334fa28322
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"
8 #include <algorithm>
9 #include <stdio.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"
20 #include "nsDebug.h"
21 #include "nsError.h"
22 #include "nsIContent.h"
23 #include "nsINode.h"
24 #include "nsISupportsUtils.h"
25 #include "nsMemory.h"
26 #include "nsReadableUtils.h"
27 #include "nsStringFwd.h"
28 #include "nsString.h"
30 namespace mozilla {
32 using namespace dom;
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),
56 mTag(&aTag),
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);
81 if (!mNewElement) {
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
96 ErrorResult error;
97 InsertNewNode(error);
98 if (error.Failed()) {
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
106 return NS_OK;
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
118 // in this method?
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");
125 return NS_OK;
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");
139 return;
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();
151 return;
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);
163 return;
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();
183 ErrorResult error;
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
201 ErrorResult error;
202 InsertNewNode(error);
203 NS_WARNING_ASSERTION(!error.Failed(),
204 "CreateElementTransaction::InsertNewNode() failed");
205 return error.StealNSResult();
208 } // namespace mozilla