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/. */
5 
6 #include "InsertNodeTransaction.h"
7 
8 #include "mozilla/EditorBase.h"      // for EditorBase
9 #include "mozilla/EditorDOMPoint.h"  // for EditorDOMPoint
10 
11 #include "mozilla/dom/Selection.h"  // for Selection
12 
13 #include "nsAString.h"
14 #include "nsDebug.h"          // for NS_ENSURE_TRUE, etc.
15 #include "nsError.h"          // for NS_ERROR_NULL_POINTER, etc.
16 #include "nsIContent.h"       // for nsIContent
17 #include "nsMemory.h"         // for nsMemory
18 #include "nsReadableUtils.h"  // for ToNewCString
19 #include "nsString.h"         // for nsString
20 
21 namespace mozilla {
22 
23 using namespace dom;
24 
25 // static
Create(EditorBase & aEditorBase,nsIContent & aContentToInsert,const EditorRawDOMPoint & aPointToInsert)26 already_AddRefed<InsertNodeTransaction> InsertNodeTransaction::Create(
27     EditorBase& aEditorBase, nsIContent& aContentToInsert,
28     const EditorRawDOMPoint& aPointToInsert) {
29   RefPtr<InsertNodeTransaction> transaction =
30       new InsertNodeTransaction(aEditorBase, aContentToInsert, aPointToInsert);
31   return transaction.forget();
32 }
33 
InsertNodeTransaction(EditorBase & aEditorBase,nsIContent & aContentToInsert,const EditorRawDOMPoint & aPointToInsert)34 InsertNodeTransaction::InsertNodeTransaction(
35     EditorBase& aEditorBase, nsIContent& aContentToInsert,
36     const EditorRawDOMPoint& aPointToInsert)
37     : mContentToInsert(&aContentToInsert),
38       mPointToInsert(aPointToInsert),
39       mEditorBase(&aEditorBase) {
40   MOZ_ASSERT(mPointToInsert.IsSetAndValid());
41   // Ensure mPointToInsert stores child at offset.
42   Unused << mPointToInsert.GetChild();
43 }
44 
~InsertNodeTransaction()45 InsertNodeTransaction::~InsertNodeTransaction() {}
46 
NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertNodeTransaction,EditTransactionBase,mEditorBase,mContentToInsert,mPointToInsert)47 NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertNodeTransaction, EditTransactionBase,
48                                    mEditorBase, mContentToInsert,
49                                    mPointToInsert)
50 
51 NS_IMPL_ADDREF_INHERITED(InsertNodeTransaction, EditTransactionBase)
52 NS_IMPL_RELEASE_INHERITED(InsertNodeTransaction, EditTransactionBase)
53 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertNodeTransaction)
54 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
55 
56 NS_IMETHODIMP
57 InsertNodeTransaction::DoTransaction() {
58   if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mContentToInsert) ||
59       NS_WARN_IF(!mPointToInsert.IsSet())) {
60     return NS_ERROR_NOT_INITIALIZED;
61   }
62 
63   if (!mPointToInsert.IsSetAndValid()) {
64     // It seems that DOM tree has been changed after first DoTransaction()
65     // and current RedoTranaction() call.
66     if (mPointToInsert.GetChild()) {
67       EditorDOMPoint newPointToInsert(mPointToInsert.GetChild());
68       if (!newPointToInsert.IsSet()) {
69         // The insertion point has been removed from the DOM tree.
70         // In this case, we should append the node to the container instead.
71         newPointToInsert.SetToEndOf(mPointToInsert.GetContainer());
72         if (NS_WARN_IF(!newPointToInsert.IsSet())) {
73           return NS_ERROR_FAILURE;
74         }
75       }
76       mPointToInsert = newPointToInsert;
77     } else {
78       mPointToInsert.SetToEndOf(mPointToInsert.GetContainer());
79       if (NS_WARN_IF(!mPointToInsert.IsSet())) {
80         return NS_ERROR_FAILURE;
81       }
82     }
83   }
84 
85   mEditorBase->MarkNodeDirty(GetAsDOMNode(mContentToInsert));
86 
87   ErrorResult error;
88   mPointToInsert.GetContainer()->InsertBefore(*mContentToInsert,
89                                               mPointToInsert.GetChild(), error);
90   error.WouldReportJSException();
91   if (NS_WARN_IF(error.Failed())) {
92     return error.StealNSResult();
93   }
94 
95   // Only set selection to insertion point if editor gives permission
96   if (mEditorBase->GetShouldTxnSetSelection()) {
97     RefPtr<Selection> selection = mEditorBase->GetSelection();
98     if (NS_WARN_IF(!selection)) {
99       return NS_ERROR_FAILURE;
100     }
101     // Place the selection just after the inserted element
102     EditorRawDOMPoint afterInsertedNode(mContentToInsert);
103     DebugOnly<bool> advanced = afterInsertedNode.AdvanceOffset();
104     NS_WARNING_ASSERTION(advanced,
105                          "Failed to advance offset after the inserted node");
106     selection->Collapse(afterInsertedNode, error);
107     if (NS_WARN_IF(error.Failed())) {
108       error.SuppressException();
109     }
110   }
111   return NS_OK;
112 }
113 
114 NS_IMETHODIMP
UndoTransaction()115 InsertNodeTransaction::UndoTransaction() {
116   if (NS_WARN_IF(!mContentToInsert) || NS_WARN_IF(!mPointToInsert.IsSet())) {
117     return NS_ERROR_NOT_INITIALIZED;
118   }
119   // XXX If the inserted node has been moved to different container node or
120   //     just removed from the DOM tree, this always fails.
121   ErrorResult error;
122   mPointToInsert.GetContainer()->RemoveChild(*mContentToInsert, error);
123   if (NS_WARN_IF(error.Failed())) {
124     return error.StealNSResult();
125   }
126   return NS_OK;
127 }
128 
129 }  // namespace mozilla
130