1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/editor/libeditor/text/nsTextEditRules.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1374 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "mozilla/Assertions.h" 1.10 +#include "mozilla/LookAndFeel.h" 1.11 +#include "mozilla/Preferences.h" 1.12 +#include "mozilla/dom/Selection.h" 1.13 +#include "mozilla/TextComposition.h" 1.14 +#include "mozilla/dom/Element.h" 1.15 +#include "nsAString.h" 1.16 +#include "nsAutoPtr.h" 1.17 +#include "nsCOMPtr.h" 1.18 +#include "nsCRT.h" 1.19 +#include "nsCRTGlue.h" 1.20 +#include "nsComponentManagerUtils.h" 1.21 +#include "nsContentUtils.h" 1.22 +#include "nsDebug.h" 1.23 +#include "nsEditor.h" 1.24 +#include "nsEditorUtils.h" 1.25 +#include "nsError.h" 1.26 +#include "nsGkAtoms.h" 1.27 +#include "nsIContent.h" 1.28 +#include "nsIDOMCharacterData.h" 1.29 +#include "nsIDOMDocument.h" 1.30 +#include "nsIDOMElement.h" 1.31 +#include "nsIDOMNode.h" 1.32 +#include "nsIDOMNodeFilter.h" 1.33 +#include "nsIDOMNodeIterator.h" 1.34 +#include "nsIDOMNodeList.h" 1.35 +#include "nsIDOMText.h" 1.36 +#include "nsNameSpaceManager.h" 1.37 +#include "nsINode.h" 1.38 +#include "nsIPlaintextEditor.h" 1.39 +#include "nsISelection.h" 1.40 +#include "nsISelectionPrivate.h" 1.41 +#include "nsISupportsBase.h" 1.42 +#include "nsLiteralString.h" 1.43 +#include "mozilla/dom/NodeIterator.h" 1.44 +#include "nsTextEditRules.h" 1.45 +#include "nsTextEditUtils.h" 1.46 +#include "nsUnicharUtils.h" 1.47 + 1.48 +using namespace mozilla; 1.49 +using namespace mozilla::dom; 1.50 + 1.51 +#define CANCEL_OPERATION_IF_READONLY_OR_DISABLED \ 1.52 + if (IsReadonly() || IsDisabled()) \ 1.53 + { \ 1.54 + *aCancel = true; \ 1.55 + return NS_OK; \ 1.56 + }; 1.57 + 1.58 + 1.59 +/******************************************************** 1.60 + * Constructor/Destructor 1.61 + ********************************************************/ 1.62 + 1.63 +nsTextEditRules::nsTextEditRules() 1.64 +{ 1.65 + InitFields(); 1.66 +} 1.67 + 1.68 +void 1.69 +nsTextEditRules::InitFields() 1.70 +{ 1.71 + mEditor = nullptr; 1.72 + mPasswordText.Truncate(); 1.73 + mPasswordIMEText.Truncate(); 1.74 + mPasswordIMEIndex = 0; 1.75 + mBogusNode = nullptr; 1.76 + mCachedSelectionNode = nullptr; 1.77 + mCachedSelectionOffset = 0; 1.78 + mActionNesting = 0; 1.79 + mLockRulesSniffing = false; 1.80 + mDidExplicitlySetInterline = false; 1.81 + mDeleteBidiImmediately = false; 1.82 + mTheAction = EditAction::none; 1.83 + mTimer = nullptr; 1.84 + mLastStart = 0; 1.85 + mLastLength = 0; 1.86 +} 1.87 + 1.88 +nsTextEditRules::~nsTextEditRules() 1.89 +{ 1.90 + // do NOT delete mEditor here. We do not hold a ref count to mEditor. mEditor owns our lifespan. 1.91 + 1.92 + if (mTimer) 1.93 + mTimer->Cancel(); 1.94 +} 1.95 + 1.96 +/******************************************************** 1.97 + * XPCOM Cruft 1.98 + ********************************************************/ 1.99 + 1.100 +NS_IMPL_CYCLE_COLLECTION(nsTextEditRules, mBogusNode, mCachedSelectionNode) 1.101 + 1.102 +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTextEditRules) 1.103 + NS_INTERFACE_MAP_ENTRY(nsIEditRules) 1.104 + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) 1.105 + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditRules) 1.106 +NS_INTERFACE_MAP_END 1.107 + 1.108 +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextEditRules) 1.109 +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextEditRules) 1.110 + 1.111 +/******************************************************** 1.112 + * Public methods 1.113 + ********************************************************/ 1.114 + 1.115 +NS_IMETHODIMP 1.116 +nsTextEditRules::Init(nsPlaintextEditor *aEditor) 1.117 +{ 1.118 + if (!aEditor) { return NS_ERROR_NULL_POINTER; } 1.119 + 1.120 + InitFields(); 1.121 + 1.122 + mEditor = aEditor; // we hold a non-refcounted reference back to our editor 1.123 + nsCOMPtr<nsISelection> selection; 1.124 + mEditor->GetSelection(getter_AddRefs(selection)); 1.125 + NS_WARN_IF_FALSE(selection, "editor cannot get selection"); 1.126 + 1.127 + // Put in a magic br if needed. This method handles null selection, 1.128 + // which should never happen anyway 1.129 + nsresult res = CreateBogusNodeIfNeeded(selection); 1.130 + NS_ENSURE_SUCCESS(res, res); 1.131 + 1.132 + // If the selection hasn't been set up yet, set it up collapsed to the end of 1.133 + // our editable content. 1.134 + int32_t rangeCount; 1.135 + res = selection->GetRangeCount(&rangeCount); 1.136 + NS_ENSURE_SUCCESS(res, res); 1.137 + if (!rangeCount) { 1.138 + res = mEditor->EndOfDocument(); 1.139 + NS_ENSURE_SUCCESS(res, res); 1.140 + } 1.141 + 1.142 + if (IsPlaintextEditor()) 1.143 + { 1.144 + // ensure trailing br node 1.145 + res = CreateTrailingBRIfNeeded(); 1.146 + NS_ENSURE_SUCCESS(res, res); 1.147 + } 1.148 + 1.149 + mDeleteBidiImmediately = 1.150 + Preferences::GetBool("bidi.edit.delete_immediately", false); 1.151 + 1.152 + return res; 1.153 +} 1.154 + 1.155 +NS_IMETHODIMP 1.156 +nsTextEditRules::SetInitialValue(const nsAString& aValue) 1.157 +{ 1.158 + if (IsPasswordEditor()) { 1.159 + mPasswordText = aValue; 1.160 + } 1.161 + return NS_OK; 1.162 +} 1.163 + 1.164 +NS_IMETHODIMP 1.165 +nsTextEditRules::DetachEditor() 1.166 +{ 1.167 + if (mTimer) 1.168 + mTimer->Cancel(); 1.169 + 1.170 + mEditor = nullptr; 1.171 + return NS_OK; 1.172 +} 1.173 + 1.174 +NS_IMETHODIMP 1.175 +nsTextEditRules::BeforeEdit(EditAction action, 1.176 + nsIEditor::EDirection aDirection) 1.177 +{ 1.178 + if (mLockRulesSniffing) return NS_OK; 1.179 + 1.180 + nsAutoLockRulesSniffing lockIt(this); 1.181 + mDidExplicitlySetInterline = false; 1.182 + if (!mActionNesting) 1.183 + { 1.184 + // let rules remember the top level action 1.185 + mTheAction = action; 1.186 + } 1.187 + mActionNesting++; 1.188 + 1.189 + // get the selection and cache the position before editing 1.190 + nsCOMPtr<nsISelection> selection; 1.191 + NS_ENSURE_STATE(mEditor); 1.192 + nsresult res = mEditor->GetSelection(getter_AddRefs(selection)); 1.193 + NS_ENSURE_SUCCESS(res, res); 1.194 + 1.195 + selection->GetAnchorNode(getter_AddRefs(mCachedSelectionNode)); 1.196 + selection->GetAnchorOffset(&mCachedSelectionOffset); 1.197 + 1.198 + return NS_OK; 1.199 +} 1.200 + 1.201 + 1.202 +NS_IMETHODIMP 1.203 +nsTextEditRules::AfterEdit(EditAction action, 1.204 + nsIEditor::EDirection aDirection) 1.205 +{ 1.206 + if (mLockRulesSniffing) return NS_OK; 1.207 + 1.208 + nsAutoLockRulesSniffing lockIt(this); 1.209 + 1.210 + NS_PRECONDITION(mActionNesting>0, "bad action nesting!"); 1.211 + nsresult res = NS_OK; 1.212 + if (!--mActionNesting) 1.213 + { 1.214 + nsCOMPtr<nsISelection>selection; 1.215 + NS_ENSURE_STATE(mEditor); 1.216 + res = mEditor->GetSelection(getter_AddRefs(selection)); 1.217 + NS_ENSURE_SUCCESS(res, res); 1.218 + 1.219 + NS_ENSURE_STATE(mEditor); 1.220 + res = mEditor->HandleInlineSpellCheck(action, selection, 1.221 + mCachedSelectionNode, mCachedSelectionOffset, 1.222 + nullptr, 0, nullptr, 0); 1.223 + NS_ENSURE_SUCCESS(res, res); 1.224 + 1.225 + // if only trailing <br> remaining remove it 1.226 + res = RemoveRedundantTrailingBR(); 1.227 + if (NS_FAILED(res)) 1.228 + return res; 1.229 + 1.230 + // detect empty doc 1.231 + res = CreateBogusNodeIfNeeded(selection); 1.232 + NS_ENSURE_SUCCESS(res, res); 1.233 + 1.234 + // ensure trailing br node 1.235 + res = CreateTrailingBRIfNeeded(); 1.236 + NS_ENSURE_SUCCESS(res, res); 1.237 + 1.238 + // collapse the selection to the trailing BR if it's at the end of our text node 1.239 + CollapseSelectionToTrailingBRIfNeeded(selection); 1.240 + } 1.241 + return res; 1.242 +} 1.243 + 1.244 + 1.245 +NS_IMETHODIMP 1.246 +nsTextEditRules::WillDoAction(Selection* aSelection, 1.247 + nsRulesInfo* aInfo, 1.248 + bool* aCancel, 1.249 + bool* aHandled) 1.250 +{ 1.251 + // null selection is legal 1.252 + MOZ_ASSERT(aInfo && aCancel && aHandled); 1.253 + 1.254 + *aCancel = false; 1.255 + *aHandled = false; 1.256 + 1.257 + // my kingdom for dynamic cast 1.258 + nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo); 1.259 + 1.260 + switch (info->action) { 1.261 + case EditAction::insertBreak: 1.262 + return WillInsertBreak(aSelection, aCancel, aHandled, info->maxLength); 1.263 + case EditAction::insertText: 1.264 + case EditAction::insertIMEText: 1.265 + return WillInsertText(info->action, aSelection, aCancel, aHandled, 1.266 + info->inString, info->outString, info->maxLength); 1.267 + case EditAction::deleteSelection: 1.268 + return WillDeleteSelection(aSelection, info->collapsedAction, 1.269 + aCancel, aHandled); 1.270 + case EditAction::undo: 1.271 + return WillUndo(aSelection, aCancel, aHandled); 1.272 + case EditAction::redo: 1.273 + return WillRedo(aSelection, aCancel, aHandled); 1.274 + case EditAction::setTextProperty: 1.275 + return WillSetTextProperty(aSelection, aCancel, aHandled); 1.276 + case EditAction::removeTextProperty: 1.277 + return WillRemoveTextProperty(aSelection, aCancel, aHandled); 1.278 + case EditAction::outputText: 1.279 + return WillOutputText(aSelection, info->outputFormat, info->outString, 1.280 + aCancel, aHandled); 1.281 + case EditAction::insertElement: 1.282 + // i had thought this would be html rules only. but we put pre elements 1.283 + // into plaintext mail when doing quoting for reply! doh! 1.284 + return WillInsert(aSelection, aCancel); 1.285 + default: 1.286 + return NS_ERROR_FAILURE; 1.287 + } 1.288 +} 1.289 + 1.290 +NS_IMETHODIMP 1.291 +nsTextEditRules::DidDoAction(nsISelection *aSelection, 1.292 + nsRulesInfo *aInfo, nsresult aResult) 1.293 +{ 1.294 + NS_ENSURE_STATE(mEditor); 1.295 + // don't let any txns in here move the selection around behind our back. 1.296 + // Note that this won't prevent explicit selection setting from working. 1.297 + nsAutoTxnsConserveSelection dontSpazMySelection(mEditor); 1.298 + 1.299 + NS_ENSURE_TRUE(aSelection && aInfo, NS_ERROR_NULL_POINTER); 1.300 + 1.301 + // my kingdom for dynamic cast 1.302 + nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo); 1.303 + 1.304 + switch (info->action) 1.305 + { 1.306 + case EditAction::insertBreak: 1.307 + return DidInsertBreak(aSelection, aResult); 1.308 + case EditAction::insertText: 1.309 + case EditAction::insertIMEText: 1.310 + return DidInsertText(aSelection, aResult); 1.311 + case EditAction::deleteSelection: 1.312 + return DidDeleteSelection(aSelection, info->collapsedAction, aResult); 1.313 + case EditAction::undo: 1.314 + return DidUndo(aSelection, aResult); 1.315 + case EditAction::redo: 1.316 + return DidRedo(aSelection, aResult); 1.317 + case EditAction::setTextProperty: 1.318 + return DidSetTextProperty(aSelection, aResult); 1.319 + case EditAction::removeTextProperty: 1.320 + return DidRemoveTextProperty(aSelection, aResult); 1.321 + case EditAction::outputText: 1.322 + return DidOutputText(aSelection, aResult); 1.323 + default: 1.324 + // Don't fail on transactions we don't handle here! 1.325 + return NS_OK; 1.326 + } 1.327 +} 1.328 + 1.329 + 1.330 +NS_IMETHODIMP 1.331 +nsTextEditRules::DocumentIsEmpty(bool *aDocumentIsEmpty) 1.332 +{ 1.333 + NS_ENSURE_TRUE(aDocumentIsEmpty, NS_ERROR_NULL_POINTER); 1.334 + 1.335 + *aDocumentIsEmpty = (mBogusNode != nullptr); 1.336 + return NS_OK; 1.337 +} 1.338 + 1.339 +/******************************************************** 1.340 + * Protected methods 1.341 + ********************************************************/ 1.342 + 1.343 + 1.344 +nsresult 1.345 +nsTextEditRules::WillInsert(nsISelection *aSelection, bool *aCancel) 1.346 +{ 1.347 + NS_ENSURE_TRUE(aSelection && aCancel, NS_ERROR_NULL_POINTER); 1.348 + 1.349 + CANCEL_OPERATION_IF_READONLY_OR_DISABLED 1.350 + 1.351 + // initialize out param 1.352 + *aCancel = false; 1.353 + 1.354 + // check for the magic content node and delete it if it exists 1.355 + if (mBogusNode) 1.356 + { 1.357 + NS_ENSURE_STATE(mEditor); 1.358 + mEditor->DeleteNode(mBogusNode); 1.359 + mBogusNode = nullptr; 1.360 + } 1.361 + 1.362 + return NS_OK; 1.363 +} 1.364 + 1.365 +nsresult 1.366 +nsTextEditRules::DidInsert(nsISelection *aSelection, nsresult aResult) 1.367 +{ 1.368 + return NS_OK; 1.369 +} 1.370 + 1.371 +nsresult 1.372 +nsTextEditRules::WillInsertBreak(Selection* aSelection, 1.373 + bool *aCancel, 1.374 + bool *aHandled, 1.375 + int32_t aMaxLength) 1.376 +{ 1.377 + if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } 1.378 + CANCEL_OPERATION_IF_READONLY_OR_DISABLED 1.379 + *aHandled = false; 1.380 + if (IsSingleLineEditor()) { 1.381 + *aCancel = true; 1.382 + } 1.383 + else 1.384 + { 1.385 + // handle docs with a max length 1.386 + // NOTE, this function copies inString into outString for us. 1.387 + NS_NAMED_LITERAL_STRING(inString, "\n"); 1.388 + nsAutoString outString; 1.389 + bool didTruncate; 1.390 + nsresult res = TruncateInsertionIfNeeded(aSelection, &inString, &outString, 1.391 + aMaxLength, &didTruncate); 1.392 + NS_ENSURE_SUCCESS(res, res); 1.393 + if (didTruncate) { 1.394 + *aCancel = true; 1.395 + return NS_OK; 1.396 + } 1.397 + 1.398 + *aCancel = false; 1.399 + 1.400 + // if the selection isn't collapsed, delete it. 1.401 + bool bCollapsed; 1.402 + res = aSelection->GetIsCollapsed(&bCollapsed); 1.403 + NS_ENSURE_SUCCESS(res, res); 1.404 + if (!bCollapsed) 1.405 + { 1.406 + NS_ENSURE_STATE(mEditor); 1.407 + res = mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); 1.408 + NS_ENSURE_SUCCESS(res, res); 1.409 + } 1.410 + 1.411 + res = WillInsert(aSelection, aCancel); 1.412 + NS_ENSURE_SUCCESS(res, res); 1.413 + // initialize out param 1.414 + // we want to ignore result of WillInsert() 1.415 + *aCancel = false; 1.416 + 1.417 + } 1.418 + return NS_OK; 1.419 +} 1.420 + 1.421 +nsresult 1.422 +nsTextEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult) 1.423 +{ 1.424 + return NS_OK; 1.425 +} 1.426 + 1.427 +nsresult 1.428 +nsTextEditRules::CollapseSelectionToTrailingBRIfNeeded(nsISelection* aSelection) 1.429 +{ 1.430 + // we only need to execute the stuff below if we are a plaintext editor. 1.431 + // html editors have a different mechanism for putting in mozBR's 1.432 + // (because there are a bunch more places you have to worry about it in html) 1.433 + if (!IsPlaintextEditor()) { 1.434 + return NS_OK; 1.435 + } 1.436 + 1.437 + // if we are at the end of the textarea, we need to set the 1.438 + // selection to stick to the mozBR at the end of the textarea. 1.439 + int32_t selOffset; 1.440 + nsCOMPtr<nsIDOMNode> selNode; 1.441 + nsresult res; 1.442 + NS_ENSURE_STATE(mEditor); 1.443 + res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); 1.444 + NS_ENSURE_SUCCESS(res, res); 1.445 + 1.446 + nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(selNode); 1.447 + if (!nodeAsText) return NS_OK; // nothing to do if we're not at a text node 1.448 + 1.449 + uint32_t length; 1.450 + res = nodeAsText->GetLength(&length); 1.451 + NS_ENSURE_SUCCESS(res, res); 1.452 + 1.453 + // nothing to do if we're not at the end of the text node 1.454 + if (selOffset != int32_t(length)) 1.455 + return NS_OK; 1.456 + 1.457 + int32_t parentOffset; 1.458 + nsCOMPtr<nsIDOMNode> parentNode = nsEditor::GetNodeLocation(selNode, &parentOffset); 1.459 + 1.460 + NS_ENSURE_STATE(mEditor); 1.461 + nsCOMPtr<nsIDOMNode> root = do_QueryInterface(mEditor->GetRoot()); 1.462 + NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER); 1.463 + if (parentNode != root) return NS_OK; 1.464 + 1.465 + nsCOMPtr<nsIDOMNode> nextNode = mEditor->GetChildAt(parentNode, 1.466 + parentOffset + 1); 1.467 + if (nextNode && nsTextEditUtils::IsMozBR(nextNode)) 1.468 + { 1.469 + res = aSelection->Collapse(parentNode, parentOffset + 1); 1.470 + NS_ENSURE_SUCCESS(res, res); 1.471 + } 1.472 + return res; 1.473 +} 1.474 + 1.475 +static inline already_AddRefed<nsIDOMNode> 1.476 +GetTextNode(nsISelection *selection, nsEditor *editor) { 1.477 + int32_t selOffset; 1.478 + nsCOMPtr<nsIDOMNode> selNode; 1.479 + nsresult res = editor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset); 1.480 + NS_ENSURE_SUCCESS(res, nullptr); 1.481 + if (!editor->IsTextNode(selNode)) { 1.482 + // Get an nsINode from the nsIDOMNode 1.483 + nsCOMPtr<nsINode> node = do_QueryInterface(selNode); 1.484 + // if node is null, return it to indicate there's no text 1.485 + NS_ENSURE_TRUE(node, nullptr); 1.486 + // This should be the root node, walk the tree looking for text nodes 1.487 + mozilla::dom::NodeFilterHolder filter; 1.488 + mozilla::dom::NodeIterator iter(node, nsIDOMNodeFilter::SHOW_TEXT, filter); 1.489 + while (!editor->IsTextNode(selNode)) { 1.490 + if (NS_FAILED(res = iter.NextNode(getter_AddRefs(selNode))) || !selNode) { 1.491 + return nullptr; 1.492 + } 1.493 + } 1.494 + } 1.495 + return selNode.forget(); 1.496 +} 1.497 +#ifdef DEBUG 1.498 +#define ASSERT_PASSWORD_LENGTHS_EQUAL() \ 1.499 + if (IsPasswordEditor() && mEditor->GetRoot()) { \ 1.500 + int32_t txtLen; \ 1.501 + mEditor->GetTextLength(&txtLen); \ 1.502 + NS_ASSERTION(mPasswordText.Length() == uint32_t(txtLen), \ 1.503 + "password length not equal to number of asterisks"); \ 1.504 + } 1.505 +#else 1.506 +#define ASSERT_PASSWORD_LENGTHS_EQUAL() 1.507 +#endif 1.508 + 1.509 +// static 1.510 +void 1.511 +nsTextEditRules::HandleNewLines(nsString &aString, 1.512 + int32_t aNewlineHandling) 1.513 +{ 1.514 + if (aNewlineHandling < 0) { 1.515 + int32_t caretStyle; 1.516 + nsPlaintextEditor::GetDefaultEditorPrefs(aNewlineHandling, caretStyle); 1.517 + } 1.518 + 1.519 + switch(aNewlineHandling) 1.520 + { 1.521 + case nsIPlaintextEditor::eNewlinesReplaceWithSpaces: 1.522 + // Strip trailing newlines first so we don't wind up with trailing spaces 1.523 + aString.Trim(CRLF, false, true); 1.524 + aString.ReplaceChar(CRLF, ' '); 1.525 + break; 1.526 + case nsIPlaintextEditor::eNewlinesStrip: 1.527 + aString.StripChars(CRLF); 1.528 + break; 1.529 + case nsIPlaintextEditor::eNewlinesPasteToFirst: 1.530 + default: 1.531 + { 1.532 + int32_t firstCRLF = aString.FindCharInSet(CRLF); 1.533 + 1.534 + // we get first *non-empty* line. 1.535 + int32_t offset = 0; 1.536 + while (firstCRLF == offset) 1.537 + { 1.538 + offset++; 1.539 + firstCRLF = aString.FindCharInSet(CRLF, offset); 1.540 + } 1.541 + if (firstCRLF > 0) 1.542 + aString.Truncate(firstCRLF); 1.543 + if (offset > 0) 1.544 + aString.Cut(0, offset); 1.545 + } 1.546 + break; 1.547 + case nsIPlaintextEditor::eNewlinesReplaceWithCommas: 1.548 + aString.Trim(CRLF, true, true); 1.549 + aString.ReplaceChar(CRLF, ','); 1.550 + break; 1.551 + case nsIPlaintextEditor::eNewlinesStripSurroundingWhitespace: 1.552 + { 1.553 + nsString result; 1.554 + uint32_t offset = 0; 1.555 + while (offset < aString.Length()) 1.556 + { 1.557 + int32_t nextCRLF = aString.FindCharInSet(CRLF, offset); 1.558 + if (nextCRLF < 0) { 1.559 + result.Append(nsDependentSubstring(aString, offset)); 1.560 + break; 1.561 + } 1.562 + uint32_t wsBegin = nextCRLF; 1.563 + // look backwards for the first non-whitespace char 1.564 + while (wsBegin > offset && NS_IS_SPACE(aString[wsBegin - 1])) 1.565 + --wsBegin; 1.566 + result.Append(nsDependentSubstring(aString, offset, wsBegin - offset)); 1.567 + offset = nextCRLF + 1; 1.568 + while (offset < aString.Length() && NS_IS_SPACE(aString[offset])) 1.569 + ++offset; 1.570 + } 1.571 + aString = result; 1.572 + } 1.573 + break; 1.574 + case nsIPlaintextEditor::eNewlinesPasteIntact: 1.575 + // even if we're pasting newlines, don't paste leading/trailing ones 1.576 + aString.Trim(CRLF, true, true); 1.577 + break; 1.578 + } 1.579 +} 1.580 + 1.581 +nsresult 1.582 +nsTextEditRules::WillInsertText(EditAction aAction, 1.583 + Selection* aSelection, 1.584 + bool *aCancel, 1.585 + bool *aHandled, 1.586 + const nsAString *inString, 1.587 + nsAString *outString, 1.588 + int32_t aMaxLength) 1.589 +{ 1.590 + if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } 1.591 + 1.592 + if (inString->IsEmpty() && aAction != EditAction::insertIMEText) { 1.593 + // HACK: this is a fix for bug 19395 1.594 + // I can't outlaw all empty insertions 1.595 + // because IME transaction depend on them 1.596 + // There is more work to do to make the 1.597 + // world safe for IME. 1.598 + *aCancel = true; 1.599 + *aHandled = false; 1.600 + return NS_OK; 1.601 + } 1.602 + 1.603 + // initialize out param 1.604 + *aCancel = false; 1.605 + *aHandled = true; 1.606 + 1.607 + // handle docs with a max length 1.608 + // NOTE, this function copies inString into outString for us. 1.609 + bool truncated = false; 1.610 + nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString, 1.611 + aMaxLength, &truncated); 1.612 + NS_ENSURE_SUCCESS(res, res); 1.613 + // If we're exceeding the maxlength when composing IME, we need to clean up 1.614 + // the composing text, so we shouldn't return early. 1.615 + if (truncated && outString->IsEmpty() && 1.616 + aAction != EditAction::insertIMEText) { 1.617 + *aCancel = true; 1.618 + return NS_OK; 1.619 + } 1.620 + 1.621 + int32_t start = 0; 1.622 + int32_t end = 0; 1.623 + 1.624 + // handle password field docs 1.625 + if (IsPasswordEditor()) { 1.626 + NS_ENSURE_STATE(mEditor); 1.627 + nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(), 1.628 + start, end); 1.629 + } 1.630 + 1.631 + // if the selection isn't collapsed, delete it. 1.632 + bool bCollapsed; 1.633 + res = aSelection->GetIsCollapsed(&bCollapsed); 1.634 + NS_ENSURE_SUCCESS(res, res); 1.635 + if (!bCollapsed) 1.636 + { 1.637 + NS_ENSURE_STATE(mEditor); 1.638 + res = mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip); 1.639 + NS_ENSURE_SUCCESS(res, res); 1.640 + } 1.641 + 1.642 + res = WillInsert(aSelection, aCancel); 1.643 + NS_ENSURE_SUCCESS(res, res); 1.644 + // initialize out param 1.645 + // we want to ignore result of WillInsert() 1.646 + *aCancel = false; 1.647 + 1.648 + // handle password field data 1.649 + // this has the side effect of changing all the characters in aOutString 1.650 + // to the replacement character 1.651 + if (IsPasswordEditor()) 1.652 + { 1.653 + if (aAction == EditAction::insertIMEText) { 1.654 + RemoveIMETextFromPWBuf(start, outString); 1.655 + } 1.656 + } 1.657 + 1.658 + // People have lots of different ideas about what text fields 1.659 + // should do with multiline pastes. See bugs 21032, 23485, 23485, 50935. 1.660 + // The six possible options are: 1.661 + // 0. paste newlines intact 1.662 + // 1. paste up to the first newline (default) 1.663 + // 2. replace newlines with spaces 1.664 + // 3. strip newlines 1.665 + // 4. replace with commas 1.666 + // 5. strip newlines and surrounding whitespace 1.667 + // So find out what we're expected to do: 1.668 + if (IsSingleLineEditor()) 1.669 + { 1.670 + nsAutoString tString(*outString); 1.671 + 1.672 + NS_ENSURE_STATE(mEditor); 1.673 + HandleNewLines(tString, mEditor->mNewlineHandling); 1.674 + 1.675 + outString->Assign(tString); 1.676 + } 1.677 + 1.678 + if (IsPasswordEditor()) 1.679 + { 1.680 + // manage the password buffer 1.681 + mPasswordText.Insert(*outString, start); 1.682 + 1.683 + if (LookAndFeel::GetEchoPassword() && !DontEchoPassword()) { 1.684 + HideLastPWInput(); 1.685 + mLastStart = start; 1.686 + mLastLength = outString->Length(); 1.687 + if (mTimer) 1.688 + { 1.689 + mTimer->Cancel(); 1.690 + } 1.691 + else 1.692 + { 1.693 + mTimer = do_CreateInstance("@mozilla.org/timer;1", &res); 1.694 + NS_ENSURE_SUCCESS(res, res); 1.695 + } 1.696 + mTimer->InitWithCallback(this, LookAndFeel::GetPasswordMaskDelay(), 1.697 + nsITimer::TYPE_ONE_SHOT); 1.698 + } 1.699 + else 1.700 + { 1.701 + FillBufWithPWChars(outString, outString->Length()); 1.702 + } 1.703 + } 1.704 + 1.705 + // get the (collapsed) selection location 1.706 + nsCOMPtr<nsIDOMNode> selNode; 1.707 + int32_t selOffset; 1.708 + NS_ENSURE_STATE(mEditor); 1.709 + res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset); 1.710 + NS_ENSURE_SUCCESS(res, res); 1.711 + 1.712 + // don't put text in places that can't have it 1.713 + NS_ENSURE_STATE(mEditor); 1.714 + if (!mEditor->IsTextNode(selNode) && 1.715 + !mEditor->CanContainTag(selNode, nsGkAtoms::textTagName)) { 1.716 + return NS_ERROR_FAILURE; 1.717 + } 1.718 + 1.719 + // we need to get the doc 1.720 + NS_ENSURE_STATE(mEditor); 1.721 + nsCOMPtr<nsIDOMDocument> doc = mEditor->GetDOMDocument(); 1.722 + NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED); 1.723 + 1.724 + if (aAction == EditAction::insertIMEText) { 1.725 + NS_ENSURE_STATE(mEditor); 1.726 + res = mEditor->InsertTextImpl(*outString, address_of(selNode), &selOffset, doc); 1.727 + NS_ENSURE_SUCCESS(res, res); 1.728 + } else { 1.729 + // aAction == EditAction::insertText; find where we are 1.730 + nsCOMPtr<nsIDOMNode> curNode = selNode; 1.731 + int32_t curOffset = selOffset; 1.732 + 1.733 + // don't spaz my selection in subtransactions 1.734 + NS_ENSURE_STATE(mEditor); 1.735 + nsAutoTxnsConserveSelection dontSpazMySelection(mEditor); 1.736 + 1.737 + res = mEditor->InsertTextImpl(*outString, address_of(curNode), 1.738 + &curOffset, doc); 1.739 + NS_ENSURE_SUCCESS(res, res); 1.740 + 1.741 + if (curNode) 1.742 + { 1.743 + // Make the caret attach to the inserted text, unless this text ends with a LF, 1.744 + // in which case make the caret attach to the next line. 1.745 + bool endsWithLF = 1.746 + !outString->IsEmpty() && outString->Last() == nsCRT::LF; 1.747 + aSelection->SetInterlinePosition(endsWithLF); 1.748 + 1.749 + aSelection->Collapse(curNode, curOffset); 1.750 + } 1.751 + } 1.752 + ASSERT_PASSWORD_LENGTHS_EQUAL() 1.753 + return res; 1.754 +} 1.755 + 1.756 +nsresult 1.757 +nsTextEditRules::DidInsertText(nsISelection *aSelection, 1.758 + nsresult aResult) 1.759 +{ 1.760 + return DidInsert(aSelection, aResult); 1.761 +} 1.762 + 1.763 + 1.764 + 1.765 +nsresult 1.766 +nsTextEditRules::WillSetTextProperty(nsISelection *aSelection, bool *aCancel, bool *aHandled) 1.767 +{ 1.768 + if (!aSelection || !aCancel || !aHandled) 1.769 + { return NS_ERROR_NULL_POINTER; } 1.770 + 1.771 + // XXX: should probably return a success value other than NS_OK that means "not allowed" 1.772 + if (IsPlaintextEditor()) { 1.773 + *aCancel = true; 1.774 + } 1.775 + return NS_OK; 1.776 +} 1.777 + 1.778 +nsresult 1.779 +nsTextEditRules::DidSetTextProperty(nsISelection *aSelection, nsresult aResult) 1.780 +{ 1.781 + return NS_OK; 1.782 +} 1.783 + 1.784 +nsresult 1.785 +nsTextEditRules::WillRemoveTextProperty(nsISelection *aSelection, bool *aCancel, bool *aHandled) 1.786 +{ 1.787 + if (!aSelection || !aCancel || !aHandled) 1.788 + { return NS_ERROR_NULL_POINTER; } 1.789 + 1.790 + // XXX: should probably return a success value other than NS_OK that means "not allowed" 1.791 + if (IsPlaintextEditor()) { 1.792 + *aCancel = true; 1.793 + } 1.794 + return NS_OK; 1.795 +} 1.796 + 1.797 +nsresult 1.798 +nsTextEditRules::DidRemoveTextProperty(nsISelection *aSelection, nsresult aResult) 1.799 +{ 1.800 + return NS_OK; 1.801 +} 1.802 + 1.803 +nsresult 1.804 +nsTextEditRules::WillDeleteSelection(Selection* aSelection, 1.805 + nsIEditor::EDirection aCollapsedAction, 1.806 + bool *aCancel, 1.807 + bool *aHandled) 1.808 +{ 1.809 + if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } 1.810 + CANCEL_OPERATION_IF_READONLY_OR_DISABLED 1.811 + 1.812 + // initialize out param 1.813 + *aCancel = false; 1.814 + *aHandled = false; 1.815 + 1.816 + // if there is only bogus content, cancel the operation 1.817 + if (mBogusNode) { 1.818 + *aCancel = true; 1.819 + return NS_OK; 1.820 + } 1.821 + 1.822 + nsresult res = NS_OK; 1.823 + nsAutoScriptBlocker scriptBlocker; 1.824 + 1.825 + if (IsPasswordEditor()) 1.826 + { 1.827 + NS_ENSURE_STATE(mEditor); 1.828 + res = mEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction); 1.829 + NS_ENSURE_SUCCESS(res, res); 1.830 + 1.831 + // manage the password buffer 1.832 + int32_t start, end; 1.833 + nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(), 1.834 + start, end); 1.835 + 1.836 + if (LookAndFeel::GetEchoPassword()) { 1.837 + HideLastPWInput(); 1.838 + mLastStart = start; 1.839 + mLastLength = 0; 1.840 + if (mTimer) 1.841 + { 1.842 + mTimer->Cancel(); 1.843 + } 1.844 + } 1.845 + 1.846 + if (end == start) 1.847 + { // collapsed selection 1.848 + if (nsIEditor::ePrevious==aCollapsedAction && 0<start) { // del back 1.849 + mPasswordText.Cut(start-1, 1); 1.850 + } 1.851 + else if (nsIEditor::eNext==aCollapsedAction) { // del forward 1.852 + mPasswordText.Cut(start, 1); 1.853 + } 1.854 + // otherwise nothing to do for this collapsed selection 1.855 + } 1.856 + else { // extended selection 1.857 + mPasswordText.Cut(start, end-start); 1.858 + } 1.859 + } 1.860 + else 1.861 + { 1.862 + nsCOMPtr<nsIDOMNode> startNode; 1.863 + int32_t startOffset; 1.864 + NS_ENSURE_STATE(mEditor); 1.865 + res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); 1.866 + NS_ENSURE_SUCCESS(res, res); 1.867 + NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); 1.868 + 1.869 + bool bCollapsed; 1.870 + res = aSelection->GetIsCollapsed(&bCollapsed); 1.871 + NS_ENSURE_SUCCESS(res, res); 1.872 + 1.873 + if (!bCollapsed) 1.874 + return NS_OK; 1.875 + 1.876 + // Test for distance between caret and text that will be deleted 1.877 + res = CheckBidiLevelForDeletion(aSelection, startNode, startOffset, aCollapsedAction, aCancel); 1.878 + NS_ENSURE_SUCCESS(res, res); 1.879 + if (*aCancel) return NS_OK; 1.880 + 1.881 + NS_ENSURE_STATE(mEditor); 1.882 + res = mEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction); 1.883 + NS_ENSURE_SUCCESS(res, res); 1.884 + } 1.885 + 1.886 + NS_ENSURE_STATE(mEditor); 1.887 + res = mEditor->DeleteSelectionImpl(aCollapsedAction, nsIEditor::eStrip); 1.888 + NS_ENSURE_SUCCESS(res, res); 1.889 + 1.890 + *aHandled = true; 1.891 + ASSERT_PASSWORD_LENGTHS_EQUAL() 1.892 + return NS_OK; 1.893 +} 1.894 + 1.895 +nsresult 1.896 +nsTextEditRules::DidDeleteSelection(nsISelection *aSelection, 1.897 + nsIEditor::EDirection aCollapsedAction, 1.898 + nsresult aResult) 1.899 +{ 1.900 + nsCOMPtr<nsIDOMNode> startNode; 1.901 + int32_t startOffset; 1.902 + NS_ENSURE_STATE(mEditor); 1.903 + nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset); 1.904 + NS_ENSURE_SUCCESS(res, res); 1.905 + NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE); 1.906 + 1.907 + // delete empty text nodes at selection 1.908 + if (mEditor->IsTextNode(startNode)) 1.909 + { 1.910 + nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(startNode); 1.911 + uint32_t strLength; 1.912 + res = textNode->GetLength(&strLength); 1.913 + NS_ENSURE_SUCCESS(res, res); 1.914 + 1.915 + // are we in an empty text node? 1.916 + if (!strLength) 1.917 + { 1.918 + res = mEditor->DeleteNode(startNode); 1.919 + NS_ENSURE_SUCCESS(res, res); 1.920 + } 1.921 + } 1.922 + if (!mDidExplicitlySetInterline) 1.923 + { 1.924 + // We prevent the caret from sticking on the left of prior BR 1.925 + // (i.e. the end of previous line) after this deletion. Bug 92124 1.926 + nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(aSelection); 1.927 + if (selPriv) res = selPriv->SetInterlinePosition(true); 1.928 + } 1.929 + return res; 1.930 +} 1.931 + 1.932 +nsresult 1.933 +nsTextEditRules::WillUndo(nsISelection *aSelection, bool *aCancel, bool *aHandled) 1.934 +{ 1.935 + if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } 1.936 + CANCEL_OPERATION_IF_READONLY_OR_DISABLED 1.937 + // initialize out param 1.938 + *aCancel = false; 1.939 + *aHandled = false; 1.940 + return NS_OK; 1.941 +} 1.942 + 1.943 +/* the idea here is to see if the magic empty node has suddenly reappeared as the result of the undo. 1.944 + * if it has, set our state so we remember it. 1.945 + * There is a tradeoff between doing here and at redo, or doing it everywhere else that might care. 1.946 + * Since undo and redo are relatively rare, it makes sense to take the (small) performance hit here. 1.947 + */ 1.948 +nsresult 1.949 +nsTextEditRules::DidUndo(nsISelection *aSelection, nsresult aResult) 1.950 +{ 1.951 + NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); 1.952 + // If aResult is an error, we return it. 1.953 + NS_ENSURE_SUCCESS(aResult, aResult); 1.954 + 1.955 + NS_ENSURE_STATE(mEditor); 1.956 + dom::Element* theRoot = mEditor->GetRoot(); 1.957 + NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE); 1.958 + nsIContent* node = mEditor->GetLeftmostChild(theRoot); 1.959 + if (node && mEditor->IsMozEditorBogusNode(node)) { 1.960 + mBogusNode = do_QueryInterface(node); 1.961 + } else { 1.962 + mBogusNode = nullptr; 1.963 + } 1.964 + return aResult; 1.965 +} 1.966 + 1.967 +nsresult 1.968 +nsTextEditRules::WillRedo(nsISelection *aSelection, bool *aCancel, bool *aHandled) 1.969 +{ 1.970 + if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; } 1.971 + CANCEL_OPERATION_IF_READONLY_OR_DISABLED 1.972 + // initialize out param 1.973 + *aCancel = false; 1.974 + *aHandled = false; 1.975 + return NS_OK; 1.976 +} 1.977 + 1.978 +nsresult 1.979 +nsTextEditRules::DidRedo(nsISelection *aSelection, nsresult aResult) 1.980 +{ 1.981 + nsresult res = aResult; // if aResult is an error, we return it. 1.982 + if (!aSelection) { return NS_ERROR_NULL_POINTER; } 1.983 + if (NS_SUCCEEDED(res)) 1.984 + { 1.985 + NS_ENSURE_STATE(mEditor); 1.986 + nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot()); 1.987 + NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE); 1.988 + 1.989 + nsCOMPtr<nsIDOMHTMLCollection> nodeList; 1.990 + res = theRoot->GetElementsByTagName(NS_LITERAL_STRING("br"), 1.991 + getter_AddRefs(nodeList)); 1.992 + NS_ENSURE_SUCCESS(res, res); 1.993 + if (nodeList) 1.994 + { 1.995 + uint32_t len; 1.996 + nodeList->GetLength(&len); 1.997 + 1.998 + if (len != 1) { 1.999 + // only in the case of one br could there be the bogus node 1.1000 + mBogusNode = nullptr; 1.1001 + return NS_OK; 1.1002 + } 1.1003 + 1.1004 + nsCOMPtr<nsIDOMNode> node; 1.1005 + nodeList->Item(0, getter_AddRefs(node)); 1.1006 + nsCOMPtr<nsIContent> content = do_QueryInterface(node); 1.1007 + MOZ_ASSERT(content); 1.1008 + if (mEditor->IsMozEditorBogusNode(content)) { 1.1009 + mBogusNode = node; 1.1010 + } else { 1.1011 + mBogusNode = nullptr; 1.1012 + } 1.1013 + } 1.1014 + } 1.1015 + return res; 1.1016 +} 1.1017 + 1.1018 +nsresult 1.1019 +nsTextEditRules::WillOutputText(nsISelection *aSelection, 1.1020 + const nsAString *aOutputFormat, 1.1021 + nsAString *aOutString, 1.1022 + bool *aCancel, 1.1023 + bool *aHandled) 1.1024 +{ 1.1025 + // null selection ok 1.1026 + if (!aOutString || !aOutputFormat || !aCancel || !aHandled) 1.1027 + { return NS_ERROR_NULL_POINTER; } 1.1028 + 1.1029 + // initialize out param 1.1030 + *aCancel = false; 1.1031 + *aHandled = false; 1.1032 + 1.1033 + nsAutoString outputFormat(*aOutputFormat); 1.1034 + ToLowerCase(outputFormat); 1.1035 + if (outputFormat.EqualsLiteral("text/plain")) 1.1036 + { // only use these rules for plain text output 1.1037 + if (IsPasswordEditor()) 1.1038 + { 1.1039 + *aOutString = mPasswordText; 1.1040 + *aHandled = true; 1.1041 + } 1.1042 + else if (mBogusNode) 1.1043 + { // this means there's no content, so output null string 1.1044 + aOutString->Truncate(); 1.1045 + *aHandled = true; 1.1046 + } 1.1047 + } 1.1048 + return NS_OK; 1.1049 +} 1.1050 + 1.1051 +nsresult 1.1052 +nsTextEditRules::DidOutputText(nsISelection *aSelection, nsresult aResult) 1.1053 +{ 1.1054 + return NS_OK; 1.1055 +} 1.1056 + 1.1057 +nsresult 1.1058 +nsTextEditRules::RemoveRedundantTrailingBR() 1.1059 +{ 1.1060 + // If the bogus node exists, we have no work to do 1.1061 + if (mBogusNode) 1.1062 + return NS_OK; 1.1063 + 1.1064 + // Likewise, nothing to be done if we could never have inserted a trailing br 1.1065 + if (IsSingleLineEditor()) 1.1066 + return NS_OK; 1.1067 + 1.1068 + NS_ENSURE_STATE(mEditor); 1.1069 + nsRefPtr<dom::Element> body = mEditor->GetRoot(); 1.1070 + if (!body) 1.1071 + return NS_ERROR_NULL_POINTER; 1.1072 + 1.1073 + uint32_t childCount = body->GetChildCount(); 1.1074 + if (childCount > 1) { 1.1075 + // The trailing br is redundant if it is the only remaining child node 1.1076 + return NS_OK; 1.1077 + } 1.1078 + 1.1079 + nsRefPtr<nsIContent> child = body->GetFirstChild(); 1.1080 + if (!child || !child->IsElement()) { 1.1081 + return NS_OK; 1.1082 + } 1.1083 + 1.1084 + dom::Element* elem = child->AsElement(); 1.1085 + if (!nsTextEditUtils::IsMozBR(elem)) { 1.1086 + return NS_OK; 1.1087 + } 1.1088 + 1.1089 + // Rather than deleting this node from the DOM tree we should instead 1.1090 + // morph this br into the bogus node 1.1091 + elem->UnsetAttr(kNameSpaceID_None, nsGkAtoms::type, true); 1.1092 + 1.1093 + // set mBogusNode to be this <br> 1.1094 + mBogusNode = do_QueryInterface(elem); 1.1095 + 1.1096 + // give it the bogus node attribute 1.1097 + elem->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom, 1.1098 + kMOZEditorBogusNodeValue, false); 1.1099 + return NS_OK; 1.1100 +} 1.1101 + 1.1102 +nsresult 1.1103 +nsTextEditRules::CreateTrailingBRIfNeeded() 1.1104 +{ 1.1105 + // but only if we aren't a single line edit field 1.1106 + if (IsSingleLineEditor()) { 1.1107 + return NS_OK; 1.1108 + } 1.1109 + 1.1110 + NS_ENSURE_STATE(mEditor); 1.1111 + dom::Element* body = mEditor->GetRoot(); 1.1112 + NS_ENSURE_TRUE(body, NS_ERROR_NULL_POINTER); 1.1113 + 1.1114 + nsIContent* lastChild = body->GetLastChild(); 1.1115 + // assuming CreateBogusNodeIfNeeded() has been called first 1.1116 + NS_ENSURE_TRUE(lastChild, NS_ERROR_NULL_POINTER); 1.1117 + 1.1118 + if (!lastChild->IsHTML(nsGkAtoms::br)) { 1.1119 + nsAutoTxnsConserveSelection dontSpazMySelection(mEditor); 1.1120 + nsCOMPtr<nsIDOMNode> domBody = do_QueryInterface(body); 1.1121 + return CreateMozBR(domBody, body->Length()); 1.1122 + } 1.1123 + 1.1124 + // Check to see if the trailing BR is a former bogus node - this will have 1.1125 + // stuck around if we previously morphed a trailing node into a bogus node. 1.1126 + if (!mEditor->IsMozEditorBogusNode(lastChild)) { 1.1127 + return NS_OK; 1.1128 + } 1.1129 + 1.1130 + // Morph it back to a mozBR 1.1131 + lastChild->UnsetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom, false); 1.1132 + lastChild->SetAttr(kNameSpaceID_None, nsGkAtoms::type, 1.1133 + NS_LITERAL_STRING("_moz"), true); 1.1134 + return NS_OK; 1.1135 +} 1.1136 + 1.1137 +nsresult 1.1138 +nsTextEditRules::CreateBogusNodeIfNeeded(nsISelection *aSelection) 1.1139 +{ 1.1140 + NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); 1.1141 + NS_ENSURE_TRUE(mEditor, NS_ERROR_NULL_POINTER); 1.1142 + 1.1143 + if (mBogusNode) { 1.1144 + // Let's not create more than one, ok? 1.1145 + return NS_OK; 1.1146 + } 1.1147 + 1.1148 + // tell rules system to not do any post-processing 1.1149 + nsAutoRules beginRulesSniffing(mEditor, EditAction::ignore, nsIEditor::eNone); 1.1150 + 1.1151 + nsCOMPtr<dom::Element> body = mEditor->GetRoot(); 1.1152 + if (!body) { 1.1153 + // We don't even have a body yet, don't insert any bogus nodes at 1.1154 + // this point. 1.1155 + return NS_OK; 1.1156 + } 1.1157 + 1.1158 + // Now we've got the body element. Iterate over the body element's children, 1.1159 + // looking for editable content. If no editable content is found, insert the 1.1160 + // bogus node. 1.1161 + for (nsCOMPtr<nsIContent> bodyChild = body->GetFirstChild(); 1.1162 + bodyChild; 1.1163 + bodyChild = bodyChild->GetNextSibling()) { 1.1164 + if (mEditor->IsMozEditorBogusNode(bodyChild) || 1.1165 + !mEditor->IsEditable(body) || // XXX hoist out of the loop? 1.1166 + mEditor->IsEditable(bodyChild)) { 1.1167 + return NS_OK; 1.1168 + } 1.1169 + } 1.1170 + 1.1171 + // Skip adding the bogus node if body is read-only. 1.1172 + if (!mEditor->IsModifiableNode(body)) { 1.1173 + return NS_OK; 1.1174 + } 1.1175 + 1.1176 + // Create a br. 1.1177 + nsCOMPtr<dom::Element> newContent; 1.1178 + nsresult rv = mEditor->CreateHTMLContent(NS_LITERAL_STRING("br"), getter_AddRefs(newContent)); 1.1179 + NS_ENSURE_SUCCESS(rv, rv); 1.1180 + 1.1181 + // set mBogusNode to be the newly created <br> 1.1182 + mBogusNode = do_QueryInterface(newContent); 1.1183 + NS_ENSURE_TRUE(mBogusNode, NS_ERROR_NULL_POINTER); 1.1184 + 1.1185 + // Give it a special attribute. 1.1186 + newContent->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom, 1.1187 + kMOZEditorBogusNodeValue, false); 1.1188 + 1.1189 + // Put the node in the document. 1.1190 + nsCOMPtr<nsIDOMNode> bodyNode = do_QueryInterface(body); 1.1191 + rv = mEditor->InsertNode(mBogusNode, bodyNode, 0); 1.1192 + NS_ENSURE_SUCCESS(rv, rv); 1.1193 + 1.1194 + // Set selection. 1.1195 + aSelection->CollapseNative(body, 0); 1.1196 + return NS_OK; 1.1197 +} 1.1198 + 1.1199 + 1.1200 +nsresult 1.1201 +nsTextEditRules::TruncateInsertionIfNeeded(Selection* aSelection, 1.1202 + const nsAString *aInString, 1.1203 + nsAString *aOutString, 1.1204 + int32_t aMaxLength, 1.1205 + bool *aTruncated) 1.1206 +{ 1.1207 + if (!aSelection || !aInString || !aOutString) {return NS_ERROR_NULL_POINTER;} 1.1208 + 1.1209 + nsresult res = NS_OK; 1.1210 + *aOutString = *aInString; 1.1211 + if (aTruncated) { 1.1212 + *aTruncated = false; 1.1213 + } 1.1214 + 1.1215 + NS_ENSURE_STATE(mEditor); 1.1216 + if ((-1 != aMaxLength) && IsPlaintextEditor() && !mEditor->IsIMEComposing() ) 1.1217 + { 1.1218 + // Get the current text length. 1.1219 + // Get the length of inString. 1.1220 + // Get the length of the selection. 1.1221 + // If selection is collapsed, it is length 0. 1.1222 + // Subtract the length of the selection from the len(doc) 1.1223 + // since we'll delete the selection on insert. 1.1224 + // This is resultingDocLength. 1.1225 + // Get old length of IME composing string 1.1226 + // which will be replaced by new one. 1.1227 + // If (resultingDocLength) is at or over max, cancel the insert 1.1228 + // If (resultingDocLength) + (length of input) > max, 1.1229 + // set aOutString to subset of inString so length = max 1.1230 + int32_t docLength; 1.1231 + res = mEditor->GetTextLength(&docLength); 1.1232 + if (NS_FAILED(res)) { return res; } 1.1233 + 1.1234 + int32_t start, end; 1.1235 + nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(), 1.1236 + start, end); 1.1237 + 1.1238 + TextComposition* composition = mEditor->GetComposition(); 1.1239 + int32_t oldCompStrLength = composition ? composition->String().Length() : 0; 1.1240 + 1.1241 + const int32_t selectionLength = end - start; 1.1242 + const int32_t resultingDocLength = docLength - selectionLength - oldCompStrLength; 1.1243 + if (resultingDocLength >= aMaxLength) 1.1244 + { 1.1245 + aOutString->Truncate(); 1.1246 + if (aTruncated) { 1.1247 + *aTruncated = true; 1.1248 + } 1.1249 + } 1.1250 + else 1.1251 + { 1.1252 + int32_t inCount = aOutString->Length(); 1.1253 + if (inCount + resultingDocLength > aMaxLength) 1.1254 + { 1.1255 + aOutString->Truncate(aMaxLength - resultingDocLength); 1.1256 + if (aTruncated) { 1.1257 + *aTruncated = true; 1.1258 + } 1.1259 + } 1.1260 + } 1.1261 + } 1.1262 + return res; 1.1263 +} 1.1264 + 1.1265 +void 1.1266 +nsTextEditRules::ResetIMETextPWBuf() 1.1267 +{ 1.1268 + mPasswordIMEText.Truncate(); 1.1269 +} 1.1270 + 1.1271 +void 1.1272 +nsTextEditRules::RemoveIMETextFromPWBuf(int32_t &aStart, nsAString *aIMEString) 1.1273 +{ 1.1274 + MOZ_ASSERT(aIMEString); 1.1275 + 1.1276 + // initialize PasswordIME 1.1277 + if (mPasswordIMEText.IsEmpty()) { 1.1278 + mPasswordIMEIndex = aStart; 1.1279 + } 1.1280 + else { 1.1281 + // manage the password buffer 1.1282 + mPasswordText.Cut(mPasswordIMEIndex, mPasswordIMEText.Length()); 1.1283 + aStart = mPasswordIMEIndex; 1.1284 + } 1.1285 + 1.1286 + mPasswordIMEText.Assign(*aIMEString); 1.1287 +} 1.1288 + 1.1289 +NS_IMETHODIMP nsTextEditRules::Notify(nsITimer *) 1.1290 +{ 1.1291 + MOZ_ASSERT(mTimer); 1.1292 + 1.1293 + // Check whether our text editor's password flag was changed before this 1.1294 + // "hide password character" timer actually fires. 1.1295 + nsresult res = IsPasswordEditor() ? HideLastPWInput() : NS_OK; 1.1296 + ASSERT_PASSWORD_LENGTHS_EQUAL(); 1.1297 + mLastLength = 0; 1.1298 + return res; 1.1299 +} 1.1300 + 1.1301 +nsresult nsTextEditRules::HideLastPWInput() { 1.1302 + if (!mLastLength) { 1.1303 + // Special case, we're trying to replace a range that no longer exists 1.1304 + return NS_OK; 1.1305 + } 1.1306 + 1.1307 + nsAutoString hiddenText; 1.1308 + FillBufWithPWChars(&hiddenText, mLastLength); 1.1309 + 1.1310 + NS_ENSURE_STATE(mEditor); 1.1311 + nsRefPtr<Selection> selection = mEditor->GetSelection(); 1.1312 + NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER); 1.1313 + int32_t start, end; 1.1314 + nsContentUtils::GetSelectionInTextControl(selection, mEditor->GetRoot(), 1.1315 + start, end); 1.1316 + 1.1317 + nsCOMPtr<nsIDOMNode> selNode = GetTextNode(selection, mEditor); 1.1318 + NS_ENSURE_TRUE(selNode, NS_OK); 1.1319 + 1.1320 + nsCOMPtr<nsIDOMCharacterData> nodeAsText(do_QueryInterface(selNode)); 1.1321 + NS_ENSURE_TRUE(nodeAsText, NS_OK); 1.1322 + 1.1323 + nodeAsText->ReplaceData(mLastStart, mLastLength, hiddenText); 1.1324 + selection->Collapse(selNode, start); 1.1325 + if (start != end) 1.1326 + selection->Extend(selNode, end); 1.1327 + return NS_OK; 1.1328 +} 1.1329 + 1.1330 +// static 1.1331 +void 1.1332 +nsTextEditRules::FillBufWithPWChars(nsAString *aOutString, int32_t aLength) 1.1333 +{ 1.1334 + MOZ_ASSERT(aOutString); 1.1335 + 1.1336 + // change the output to the platform password character 1.1337 + char16_t passwordChar = LookAndFeel::GetPasswordCharacter(); 1.1338 + 1.1339 + int32_t i; 1.1340 + aOutString->Truncate(); 1.1341 + for (i=0; i < aLength; i++) 1.1342 + aOutString->Append(passwordChar); 1.1343 +} 1.1344 + 1.1345 + 1.1346 +/////////////////////////////////////////////////////////////////////////// 1.1347 +// CreateMozBR: put a BR node with moz attribute at {aNode, aOffset} 1.1348 +// 1.1349 +nsresult 1.1350 +nsTextEditRules::CreateMozBR(nsIDOMNode* inParent, int32_t inOffset, 1.1351 + nsIDOMNode** outBRNode) 1.1352 +{ 1.1353 + NS_ENSURE_TRUE(inParent, NS_ERROR_NULL_POINTER); 1.1354 + 1.1355 + nsCOMPtr<nsIDOMNode> brNode; 1.1356 + NS_ENSURE_STATE(mEditor); 1.1357 + nsresult res = mEditor->CreateBR(inParent, inOffset, address_of(brNode)); 1.1358 + NS_ENSURE_SUCCESS(res, res); 1.1359 + 1.1360 + // give it special moz attr 1.1361 + nsCOMPtr<nsIDOMElement> brElem = do_QueryInterface(brNode); 1.1362 + if (brElem) { 1.1363 + res = mEditor->SetAttribute(brElem, NS_LITERAL_STRING("type"), NS_LITERAL_STRING("_moz")); 1.1364 + NS_ENSURE_SUCCESS(res, res); 1.1365 + } 1.1366 + 1.1367 + if (outBRNode) { 1.1368 + brNode.forget(outBRNode); 1.1369 + } 1.1370 + return NS_OK; 1.1371 +} 1.1372 + 1.1373 +NS_IMETHODIMP 1.1374 +nsTextEditRules::DocumentModified() 1.1375 +{ 1.1376 + return NS_ERROR_NOT_IMPLEMENTED; 1.1377 +}