CompositionTransaction.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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. #include "CompositionTransaction.h"
  6. #include "mozilla/EditorBase.h" // mEditorBase
  7. #include "mozilla/SelectionState.h" // RangeUpdater
  8. #include "mozilla/dom/Selection.h" // local var
  9. #include "mozilla/dom/Text.h" // mTextNode
  10. #include "nsAString.h" // params
  11. #include "nsDebug.h" // for NS_ASSERTION, etc
  12. #include "nsError.h" // for NS_SUCCEEDED, NS_FAILED, etc
  13. #include "nsIPresShell.h" // nsISelectionController constants
  14. #include "nsRange.h" // local var
  15. #include "nsQueryObject.h" // for do_QueryObject
  16. namespace mozilla {
  17. using namespace dom;
  18. CompositionTransaction::CompositionTransaction(
  19. Text& aTextNode,
  20. uint32_t aOffset,
  21. uint32_t aReplaceLength,
  22. TextRangeArray* aTextRangeArray,
  23. const nsAString& aStringToInsert,
  24. EditorBase& aEditorBase,
  25. RangeUpdater* aRangeUpdater)
  26. : mTextNode(&aTextNode)
  27. , mOffset(aOffset)
  28. , mReplaceLength(aReplaceLength)
  29. , mRanges(aTextRangeArray)
  30. , mStringToInsert(aStringToInsert)
  31. , mEditorBase(&aEditorBase)
  32. , mRangeUpdater(aRangeUpdater)
  33. , mFixed(false)
  34. {
  35. MOZ_ASSERT(mTextNode->TextLength() >= mOffset);
  36. }
  37. CompositionTransaction::~CompositionTransaction()
  38. {
  39. }
  40. NS_IMPL_CYCLE_COLLECTION_INHERITED(CompositionTransaction, EditTransactionBase,
  41. mEditorBase,
  42. mTextNode)
  43. // mRangeList can't lead to cycles
  44. NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompositionTransaction)
  45. if (aIID.Equals(NS_GET_IID(CompositionTransaction))) {
  46. foundInterface = static_cast<nsITransaction*>(this);
  47. } else
  48. NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
  49. NS_IMPL_ADDREF_INHERITED(CompositionTransaction, EditTransactionBase)
  50. NS_IMPL_RELEASE_INHERITED(CompositionTransaction, EditTransactionBase)
  51. NS_IMETHODIMP
  52. CompositionTransaction::DoTransaction()
  53. {
  54. if (NS_WARN_IF(!mEditorBase)) {
  55. return NS_ERROR_NOT_INITIALIZED;
  56. }
  57. // Fail before making any changes if there's no selection controller
  58. nsCOMPtr<nsISelectionController> selCon;
  59. mEditorBase->GetSelectionController(getter_AddRefs(selCon));
  60. NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
  61. // Advance caret: This requires the presentation shell to get the selection.
  62. if (mReplaceLength == 0) {
  63. nsresult rv = mTextNode->InsertData(mOffset, mStringToInsert);
  64. if (NS_WARN_IF(NS_FAILED(rv))) {
  65. return rv;
  66. }
  67. mRangeUpdater->SelAdjInsertText(*mTextNode, mOffset, mStringToInsert);
  68. } else {
  69. uint32_t replaceableLength = mTextNode->TextLength() - mOffset;
  70. nsresult rv =
  71. mTextNode->ReplaceData(mOffset, mReplaceLength, mStringToInsert);
  72. if (NS_WARN_IF(NS_FAILED(rv))) {
  73. return rv;
  74. }
  75. mRangeUpdater->SelAdjDeleteText(mTextNode, mOffset, mReplaceLength);
  76. mRangeUpdater->SelAdjInsertText(*mTextNode, mOffset, mStringToInsert);
  77. // If IME text node is multiple node, ReplaceData doesn't remove all IME
  78. // text. So we need remove remained text into other text node.
  79. if (replaceableLength < mReplaceLength) {
  80. int32_t remainLength = mReplaceLength - replaceableLength;
  81. nsCOMPtr<nsINode> node = mTextNode->GetNextSibling();
  82. while (node && node->IsNodeOfType(nsINode::eTEXT) &&
  83. remainLength > 0) {
  84. Text* text = static_cast<Text*>(node.get());
  85. uint32_t textLength = text->TextLength();
  86. text->DeleteData(0, remainLength);
  87. mRangeUpdater->SelAdjDeleteText(text, 0, remainLength);
  88. remainLength -= textLength;
  89. node = node->GetNextSibling();
  90. }
  91. }
  92. }
  93. nsresult rv = SetSelectionForRanges();
  94. NS_ENSURE_SUCCESS(rv, rv);
  95. return NS_OK;
  96. }
  97. NS_IMETHODIMP
  98. CompositionTransaction::UndoTransaction()
  99. {
  100. if (NS_WARN_IF(!mEditorBase)) {
  101. return NS_ERROR_NOT_INITIALIZED;
  102. }
  103. // Get the selection first so we'll fail before making any changes if we
  104. // can't get it
  105. RefPtr<Selection> selection = mEditorBase->GetSelection();
  106. NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
  107. nsresult rv = mTextNode->DeleteData(mOffset, mStringToInsert.Length());
  108. NS_ENSURE_SUCCESS(rv, rv);
  109. // set the selection to the insertion point where the string was removed
  110. rv = selection->Collapse(mTextNode, mOffset);
  111. NS_ASSERTION(NS_SUCCEEDED(rv),
  112. "Selection could not be collapsed after undo of IME insert.");
  113. NS_ENSURE_SUCCESS(rv, rv);
  114. return NS_OK;
  115. }
  116. NS_IMETHODIMP
  117. CompositionTransaction::Merge(nsITransaction* aTransaction,
  118. bool* aDidMerge)
  119. {
  120. NS_ENSURE_ARG_POINTER(aTransaction && aDidMerge);
  121. // Check to make sure we aren't fixed, if we are then nothing gets absorbed
  122. if (mFixed) {
  123. *aDidMerge = false;
  124. return NS_OK;
  125. }
  126. // If aTransaction is another CompositionTransaction then absorb it
  127. RefPtr<CompositionTransaction> otherTransaction =
  128. do_QueryObject(aTransaction);
  129. if (otherTransaction) {
  130. // We absorb the next IME transaction by adopting its insert string
  131. mStringToInsert = otherTransaction->mStringToInsert;
  132. mRanges = otherTransaction->mRanges;
  133. *aDidMerge = true;
  134. return NS_OK;
  135. }
  136. *aDidMerge = false;
  137. return NS_OK;
  138. }
  139. void
  140. CompositionTransaction::MarkFixed()
  141. {
  142. mFixed = true;
  143. }
  144. NS_IMETHODIMP
  145. CompositionTransaction::GetTxnDescription(nsAString& aString)
  146. {
  147. aString.AssignLiteral("CompositionTransaction: ");
  148. aString += mStringToInsert;
  149. return NS_OK;
  150. }
  151. /* ============ private methods ================== */
  152. nsresult
  153. CompositionTransaction::SetSelectionForRanges()
  154. {
  155. if (NS_WARN_IF(!mEditorBase)) {
  156. return NS_ERROR_NOT_INITIALIZED;
  157. }
  158. return SetIMESelection(*mEditorBase, mTextNode, mOffset,
  159. mStringToInsert.Length(), mRanges);
  160. }
  161. // static
  162. nsresult
  163. CompositionTransaction::SetIMESelection(EditorBase& aEditorBase,
  164. Text* aTextNode,
  165. uint32_t aOffsetInNode,
  166. uint32_t aLengthOfCompositionString,
  167. const TextRangeArray* aRanges)
  168. {
  169. RefPtr<Selection> selection = aEditorBase.GetSelection();
  170. NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
  171. nsresult rv = selection->StartBatchChanges();
  172. NS_ENSURE_SUCCESS(rv, rv);
  173. // First, remove all selections of IME composition.
  174. static const RawSelectionType kIMESelections[] = {
  175. nsISelectionController::SELECTION_IME_RAWINPUT,
  176. nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT,
  177. nsISelectionController::SELECTION_IME_CONVERTEDTEXT,
  178. nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT
  179. };
  180. nsCOMPtr<nsISelectionController> selCon;
  181. aEditorBase.GetSelectionController(getter_AddRefs(selCon));
  182. NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
  183. for (uint32_t i = 0; i < ArrayLength(kIMESelections); ++i) {
  184. nsCOMPtr<nsISelection> selectionOfIME;
  185. if (NS_FAILED(selCon->GetSelection(kIMESelections[i],
  186. getter_AddRefs(selectionOfIME)))) {
  187. continue;
  188. }
  189. rv = selectionOfIME->RemoveAllRanges();
  190. NS_ASSERTION(NS_SUCCEEDED(rv),
  191. "Failed to remove all ranges of IME selection");
  192. }
  193. // Set caret position and selection of IME composition with TextRangeArray.
  194. bool setCaret = false;
  195. uint32_t countOfRanges = aRanges ? aRanges->Length() : 0;
  196. #ifdef DEBUG
  197. // Bounds-checking on debug builds
  198. uint32_t maxOffset = aTextNode->Length();
  199. #endif
  200. // NOTE: composition string may be truncated when it's committed and
  201. // maxlength attribute value doesn't allow input of all text of this
  202. // composition.
  203. for (uint32_t i = 0; i < countOfRanges; ++i) {
  204. const TextRange& textRange = aRanges->ElementAt(i);
  205. // Caret needs special handling since its length may be 0 and if it's not
  206. // specified explicitly, we need to handle it ourselves later.
  207. if (textRange.mRangeType == TextRangeType::eCaret) {
  208. NS_ASSERTION(!setCaret, "The ranges already has caret position");
  209. NS_ASSERTION(!textRange.Length(),
  210. "EditorBase doesn't support wide caret");
  211. int32_t caretOffset = static_cast<int32_t>(
  212. aOffsetInNode +
  213. std::min(textRange.mStartOffset, aLengthOfCompositionString));
  214. MOZ_ASSERT(caretOffset >= 0 &&
  215. static_cast<uint32_t>(caretOffset) <= maxOffset);
  216. rv = selection->Collapse(aTextNode, caretOffset);
  217. setCaret = setCaret || NS_SUCCEEDED(rv);
  218. if (NS_WARN_IF(!setCaret)) {
  219. continue;
  220. }
  221. // If caret range is specified explicitly, we should show the caret if
  222. // it should be so.
  223. aEditorBase.HideCaret(false);
  224. continue;
  225. }
  226. // If the clause length is 0, it should be a bug.
  227. if (!textRange.Length()) {
  228. NS_WARNING("Any clauses must not be empty");
  229. continue;
  230. }
  231. RefPtr<nsRange> clauseRange;
  232. int32_t startOffset = static_cast<int32_t>(
  233. aOffsetInNode +
  234. std::min(textRange.mStartOffset, aLengthOfCompositionString));
  235. MOZ_ASSERT(startOffset >= 0 &&
  236. static_cast<uint32_t>(startOffset) <= maxOffset);
  237. int32_t endOffset = static_cast<int32_t>(
  238. aOffsetInNode +
  239. std::min(textRange.mEndOffset, aLengthOfCompositionString));
  240. MOZ_ASSERT(endOffset >= startOffset &&
  241. static_cast<uint32_t>(endOffset) <= maxOffset);
  242. rv = nsRange::CreateRange(aTextNode, startOffset,
  243. aTextNode, endOffset,
  244. getter_AddRefs(clauseRange));
  245. if (NS_FAILED(rv)) {
  246. NS_WARNING("Failed to create a DOM range for a clause of composition");
  247. break;
  248. }
  249. // Set the range of the clause to selection.
  250. nsCOMPtr<nsISelection> selectionOfIME;
  251. rv = selCon->GetSelection(ToRawSelectionType(textRange.mRangeType),
  252. getter_AddRefs(selectionOfIME));
  253. if (NS_FAILED(rv)) {
  254. NS_WARNING("Failed to get IME selection");
  255. break;
  256. }
  257. rv = selectionOfIME->AddRange(clauseRange);
  258. if (NS_FAILED(rv)) {
  259. NS_WARNING("Failed to add selection range for a clause of composition");
  260. break;
  261. }
  262. // Set the style of the clause.
  263. nsCOMPtr<nsISelectionPrivate> selectionOfIMEPriv =
  264. do_QueryInterface(selectionOfIME);
  265. if (!selectionOfIMEPriv) {
  266. NS_WARNING("Failed to get nsISelectionPrivate interface from selection");
  267. continue; // Since this is additional feature, we can continue this job.
  268. }
  269. rv = selectionOfIMEPriv->SetTextRangeStyle(clauseRange,
  270. textRange.mRangeStyle);
  271. if (NS_FAILED(rv)) {
  272. NS_WARNING("Failed to set selection style");
  273. break; // but this is unexpected...
  274. }
  275. }
  276. // If the ranges doesn't include explicit caret position, let's set the
  277. // caret to the end of composition string.
  278. if (!setCaret) {
  279. int32_t caretOffset =
  280. static_cast<int32_t>(aOffsetInNode + aLengthOfCompositionString);
  281. MOZ_ASSERT(caretOffset >= 0 &&
  282. static_cast<uint32_t>(caretOffset) <= maxOffset);
  283. rv = selection->Collapse(aTextNode, caretOffset);
  284. NS_ASSERTION(NS_SUCCEEDED(rv),
  285. "Failed to set caret at the end of composition string");
  286. // If caret range isn't specified explicitly, we should hide the caret.
  287. // Hiding the caret benefits a Windows build (see bug 555642 comment #6).
  288. // However, when there is no range, we should keep showing caret.
  289. if (countOfRanges) {
  290. aEditorBase.HideCaret(true);
  291. }
  292. }
  293. rv = selection->EndBatchChangesInternal();
  294. NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to end batch changes");
  295. return rv;
  296. }
  297. } // namespace mozilla