editor/libeditor/text/nsTextEditRules.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 3 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 5
michael@0 6 #include "mozilla/Assertions.h"
michael@0 7 #include "mozilla/LookAndFeel.h"
michael@0 8 #include "mozilla/Preferences.h"
michael@0 9 #include "mozilla/dom/Selection.h"
michael@0 10 #include "mozilla/TextComposition.h"
michael@0 11 #include "mozilla/dom/Element.h"
michael@0 12 #include "nsAString.h"
michael@0 13 #include "nsAutoPtr.h"
michael@0 14 #include "nsCOMPtr.h"
michael@0 15 #include "nsCRT.h"
michael@0 16 #include "nsCRTGlue.h"
michael@0 17 #include "nsComponentManagerUtils.h"
michael@0 18 #include "nsContentUtils.h"
michael@0 19 #include "nsDebug.h"
michael@0 20 #include "nsEditor.h"
michael@0 21 #include "nsEditorUtils.h"
michael@0 22 #include "nsError.h"
michael@0 23 #include "nsGkAtoms.h"
michael@0 24 #include "nsIContent.h"
michael@0 25 #include "nsIDOMCharacterData.h"
michael@0 26 #include "nsIDOMDocument.h"
michael@0 27 #include "nsIDOMElement.h"
michael@0 28 #include "nsIDOMNode.h"
michael@0 29 #include "nsIDOMNodeFilter.h"
michael@0 30 #include "nsIDOMNodeIterator.h"
michael@0 31 #include "nsIDOMNodeList.h"
michael@0 32 #include "nsIDOMText.h"
michael@0 33 #include "nsNameSpaceManager.h"
michael@0 34 #include "nsINode.h"
michael@0 35 #include "nsIPlaintextEditor.h"
michael@0 36 #include "nsISelection.h"
michael@0 37 #include "nsISelectionPrivate.h"
michael@0 38 #include "nsISupportsBase.h"
michael@0 39 #include "nsLiteralString.h"
michael@0 40 #include "mozilla/dom/NodeIterator.h"
michael@0 41 #include "nsTextEditRules.h"
michael@0 42 #include "nsTextEditUtils.h"
michael@0 43 #include "nsUnicharUtils.h"
michael@0 44
michael@0 45 using namespace mozilla;
michael@0 46 using namespace mozilla::dom;
michael@0 47
michael@0 48 #define CANCEL_OPERATION_IF_READONLY_OR_DISABLED \
michael@0 49 if (IsReadonly() || IsDisabled()) \
michael@0 50 { \
michael@0 51 *aCancel = true; \
michael@0 52 return NS_OK; \
michael@0 53 };
michael@0 54
michael@0 55
michael@0 56 /********************************************************
michael@0 57 * Constructor/Destructor
michael@0 58 ********************************************************/
michael@0 59
michael@0 60 nsTextEditRules::nsTextEditRules()
michael@0 61 {
michael@0 62 InitFields();
michael@0 63 }
michael@0 64
michael@0 65 void
michael@0 66 nsTextEditRules::InitFields()
michael@0 67 {
michael@0 68 mEditor = nullptr;
michael@0 69 mPasswordText.Truncate();
michael@0 70 mPasswordIMEText.Truncate();
michael@0 71 mPasswordIMEIndex = 0;
michael@0 72 mBogusNode = nullptr;
michael@0 73 mCachedSelectionNode = nullptr;
michael@0 74 mCachedSelectionOffset = 0;
michael@0 75 mActionNesting = 0;
michael@0 76 mLockRulesSniffing = false;
michael@0 77 mDidExplicitlySetInterline = false;
michael@0 78 mDeleteBidiImmediately = false;
michael@0 79 mTheAction = EditAction::none;
michael@0 80 mTimer = nullptr;
michael@0 81 mLastStart = 0;
michael@0 82 mLastLength = 0;
michael@0 83 }
michael@0 84
michael@0 85 nsTextEditRules::~nsTextEditRules()
michael@0 86 {
michael@0 87 // do NOT delete mEditor here. We do not hold a ref count to mEditor. mEditor owns our lifespan.
michael@0 88
michael@0 89 if (mTimer)
michael@0 90 mTimer->Cancel();
michael@0 91 }
michael@0 92
michael@0 93 /********************************************************
michael@0 94 * XPCOM Cruft
michael@0 95 ********************************************************/
michael@0 96
michael@0 97 NS_IMPL_CYCLE_COLLECTION(nsTextEditRules, mBogusNode, mCachedSelectionNode)
michael@0 98
michael@0 99 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsTextEditRules)
michael@0 100 NS_INTERFACE_MAP_ENTRY(nsIEditRules)
michael@0 101 NS_INTERFACE_MAP_ENTRY(nsITimerCallback)
michael@0 102 NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIEditRules)
michael@0 103 NS_INTERFACE_MAP_END
michael@0 104
michael@0 105 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsTextEditRules)
michael@0 106 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsTextEditRules)
michael@0 107
michael@0 108 /********************************************************
michael@0 109 * Public methods
michael@0 110 ********************************************************/
michael@0 111
michael@0 112 NS_IMETHODIMP
michael@0 113 nsTextEditRules::Init(nsPlaintextEditor *aEditor)
michael@0 114 {
michael@0 115 if (!aEditor) { return NS_ERROR_NULL_POINTER; }
michael@0 116
michael@0 117 InitFields();
michael@0 118
michael@0 119 mEditor = aEditor; // we hold a non-refcounted reference back to our editor
michael@0 120 nsCOMPtr<nsISelection> selection;
michael@0 121 mEditor->GetSelection(getter_AddRefs(selection));
michael@0 122 NS_WARN_IF_FALSE(selection, "editor cannot get selection");
michael@0 123
michael@0 124 // Put in a magic br if needed. This method handles null selection,
michael@0 125 // which should never happen anyway
michael@0 126 nsresult res = CreateBogusNodeIfNeeded(selection);
michael@0 127 NS_ENSURE_SUCCESS(res, res);
michael@0 128
michael@0 129 // If the selection hasn't been set up yet, set it up collapsed to the end of
michael@0 130 // our editable content.
michael@0 131 int32_t rangeCount;
michael@0 132 res = selection->GetRangeCount(&rangeCount);
michael@0 133 NS_ENSURE_SUCCESS(res, res);
michael@0 134 if (!rangeCount) {
michael@0 135 res = mEditor->EndOfDocument();
michael@0 136 NS_ENSURE_SUCCESS(res, res);
michael@0 137 }
michael@0 138
michael@0 139 if (IsPlaintextEditor())
michael@0 140 {
michael@0 141 // ensure trailing br node
michael@0 142 res = CreateTrailingBRIfNeeded();
michael@0 143 NS_ENSURE_SUCCESS(res, res);
michael@0 144 }
michael@0 145
michael@0 146 mDeleteBidiImmediately =
michael@0 147 Preferences::GetBool("bidi.edit.delete_immediately", false);
michael@0 148
michael@0 149 return res;
michael@0 150 }
michael@0 151
michael@0 152 NS_IMETHODIMP
michael@0 153 nsTextEditRules::SetInitialValue(const nsAString& aValue)
michael@0 154 {
michael@0 155 if (IsPasswordEditor()) {
michael@0 156 mPasswordText = aValue;
michael@0 157 }
michael@0 158 return NS_OK;
michael@0 159 }
michael@0 160
michael@0 161 NS_IMETHODIMP
michael@0 162 nsTextEditRules::DetachEditor()
michael@0 163 {
michael@0 164 if (mTimer)
michael@0 165 mTimer->Cancel();
michael@0 166
michael@0 167 mEditor = nullptr;
michael@0 168 return NS_OK;
michael@0 169 }
michael@0 170
michael@0 171 NS_IMETHODIMP
michael@0 172 nsTextEditRules::BeforeEdit(EditAction action,
michael@0 173 nsIEditor::EDirection aDirection)
michael@0 174 {
michael@0 175 if (mLockRulesSniffing) return NS_OK;
michael@0 176
michael@0 177 nsAutoLockRulesSniffing lockIt(this);
michael@0 178 mDidExplicitlySetInterline = false;
michael@0 179 if (!mActionNesting)
michael@0 180 {
michael@0 181 // let rules remember the top level action
michael@0 182 mTheAction = action;
michael@0 183 }
michael@0 184 mActionNesting++;
michael@0 185
michael@0 186 // get the selection and cache the position before editing
michael@0 187 nsCOMPtr<nsISelection> selection;
michael@0 188 NS_ENSURE_STATE(mEditor);
michael@0 189 nsresult res = mEditor->GetSelection(getter_AddRefs(selection));
michael@0 190 NS_ENSURE_SUCCESS(res, res);
michael@0 191
michael@0 192 selection->GetAnchorNode(getter_AddRefs(mCachedSelectionNode));
michael@0 193 selection->GetAnchorOffset(&mCachedSelectionOffset);
michael@0 194
michael@0 195 return NS_OK;
michael@0 196 }
michael@0 197
michael@0 198
michael@0 199 NS_IMETHODIMP
michael@0 200 nsTextEditRules::AfterEdit(EditAction action,
michael@0 201 nsIEditor::EDirection aDirection)
michael@0 202 {
michael@0 203 if (mLockRulesSniffing) return NS_OK;
michael@0 204
michael@0 205 nsAutoLockRulesSniffing lockIt(this);
michael@0 206
michael@0 207 NS_PRECONDITION(mActionNesting>0, "bad action nesting!");
michael@0 208 nsresult res = NS_OK;
michael@0 209 if (!--mActionNesting)
michael@0 210 {
michael@0 211 nsCOMPtr<nsISelection>selection;
michael@0 212 NS_ENSURE_STATE(mEditor);
michael@0 213 res = mEditor->GetSelection(getter_AddRefs(selection));
michael@0 214 NS_ENSURE_SUCCESS(res, res);
michael@0 215
michael@0 216 NS_ENSURE_STATE(mEditor);
michael@0 217 res = mEditor->HandleInlineSpellCheck(action, selection,
michael@0 218 mCachedSelectionNode, mCachedSelectionOffset,
michael@0 219 nullptr, 0, nullptr, 0);
michael@0 220 NS_ENSURE_SUCCESS(res, res);
michael@0 221
michael@0 222 // if only trailing <br> remaining remove it
michael@0 223 res = RemoveRedundantTrailingBR();
michael@0 224 if (NS_FAILED(res))
michael@0 225 return res;
michael@0 226
michael@0 227 // detect empty doc
michael@0 228 res = CreateBogusNodeIfNeeded(selection);
michael@0 229 NS_ENSURE_SUCCESS(res, res);
michael@0 230
michael@0 231 // ensure trailing br node
michael@0 232 res = CreateTrailingBRIfNeeded();
michael@0 233 NS_ENSURE_SUCCESS(res, res);
michael@0 234
michael@0 235 // collapse the selection to the trailing BR if it's at the end of our text node
michael@0 236 CollapseSelectionToTrailingBRIfNeeded(selection);
michael@0 237 }
michael@0 238 return res;
michael@0 239 }
michael@0 240
michael@0 241
michael@0 242 NS_IMETHODIMP
michael@0 243 nsTextEditRules::WillDoAction(Selection* aSelection,
michael@0 244 nsRulesInfo* aInfo,
michael@0 245 bool* aCancel,
michael@0 246 bool* aHandled)
michael@0 247 {
michael@0 248 // null selection is legal
michael@0 249 MOZ_ASSERT(aInfo && aCancel && aHandled);
michael@0 250
michael@0 251 *aCancel = false;
michael@0 252 *aHandled = false;
michael@0 253
michael@0 254 // my kingdom for dynamic cast
michael@0 255 nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
michael@0 256
michael@0 257 switch (info->action) {
michael@0 258 case EditAction::insertBreak:
michael@0 259 return WillInsertBreak(aSelection, aCancel, aHandled, info->maxLength);
michael@0 260 case EditAction::insertText:
michael@0 261 case EditAction::insertIMEText:
michael@0 262 return WillInsertText(info->action, aSelection, aCancel, aHandled,
michael@0 263 info->inString, info->outString, info->maxLength);
michael@0 264 case EditAction::deleteSelection:
michael@0 265 return WillDeleteSelection(aSelection, info->collapsedAction,
michael@0 266 aCancel, aHandled);
michael@0 267 case EditAction::undo:
michael@0 268 return WillUndo(aSelection, aCancel, aHandled);
michael@0 269 case EditAction::redo:
michael@0 270 return WillRedo(aSelection, aCancel, aHandled);
michael@0 271 case EditAction::setTextProperty:
michael@0 272 return WillSetTextProperty(aSelection, aCancel, aHandled);
michael@0 273 case EditAction::removeTextProperty:
michael@0 274 return WillRemoveTextProperty(aSelection, aCancel, aHandled);
michael@0 275 case EditAction::outputText:
michael@0 276 return WillOutputText(aSelection, info->outputFormat, info->outString,
michael@0 277 aCancel, aHandled);
michael@0 278 case EditAction::insertElement:
michael@0 279 // i had thought this would be html rules only. but we put pre elements
michael@0 280 // into plaintext mail when doing quoting for reply! doh!
michael@0 281 return WillInsert(aSelection, aCancel);
michael@0 282 default:
michael@0 283 return NS_ERROR_FAILURE;
michael@0 284 }
michael@0 285 }
michael@0 286
michael@0 287 NS_IMETHODIMP
michael@0 288 nsTextEditRules::DidDoAction(nsISelection *aSelection,
michael@0 289 nsRulesInfo *aInfo, nsresult aResult)
michael@0 290 {
michael@0 291 NS_ENSURE_STATE(mEditor);
michael@0 292 // don't let any txns in here move the selection around behind our back.
michael@0 293 // Note that this won't prevent explicit selection setting from working.
michael@0 294 nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
michael@0 295
michael@0 296 NS_ENSURE_TRUE(aSelection && aInfo, NS_ERROR_NULL_POINTER);
michael@0 297
michael@0 298 // my kingdom for dynamic cast
michael@0 299 nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
michael@0 300
michael@0 301 switch (info->action)
michael@0 302 {
michael@0 303 case EditAction::insertBreak:
michael@0 304 return DidInsertBreak(aSelection, aResult);
michael@0 305 case EditAction::insertText:
michael@0 306 case EditAction::insertIMEText:
michael@0 307 return DidInsertText(aSelection, aResult);
michael@0 308 case EditAction::deleteSelection:
michael@0 309 return DidDeleteSelection(aSelection, info->collapsedAction, aResult);
michael@0 310 case EditAction::undo:
michael@0 311 return DidUndo(aSelection, aResult);
michael@0 312 case EditAction::redo:
michael@0 313 return DidRedo(aSelection, aResult);
michael@0 314 case EditAction::setTextProperty:
michael@0 315 return DidSetTextProperty(aSelection, aResult);
michael@0 316 case EditAction::removeTextProperty:
michael@0 317 return DidRemoveTextProperty(aSelection, aResult);
michael@0 318 case EditAction::outputText:
michael@0 319 return DidOutputText(aSelection, aResult);
michael@0 320 default:
michael@0 321 // Don't fail on transactions we don't handle here!
michael@0 322 return NS_OK;
michael@0 323 }
michael@0 324 }
michael@0 325
michael@0 326
michael@0 327 NS_IMETHODIMP
michael@0 328 nsTextEditRules::DocumentIsEmpty(bool *aDocumentIsEmpty)
michael@0 329 {
michael@0 330 NS_ENSURE_TRUE(aDocumentIsEmpty, NS_ERROR_NULL_POINTER);
michael@0 331
michael@0 332 *aDocumentIsEmpty = (mBogusNode != nullptr);
michael@0 333 return NS_OK;
michael@0 334 }
michael@0 335
michael@0 336 /********************************************************
michael@0 337 * Protected methods
michael@0 338 ********************************************************/
michael@0 339
michael@0 340
michael@0 341 nsresult
michael@0 342 nsTextEditRules::WillInsert(nsISelection *aSelection, bool *aCancel)
michael@0 343 {
michael@0 344 NS_ENSURE_TRUE(aSelection && aCancel, NS_ERROR_NULL_POINTER);
michael@0 345
michael@0 346 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
michael@0 347
michael@0 348 // initialize out param
michael@0 349 *aCancel = false;
michael@0 350
michael@0 351 // check for the magic content node and delete it if it exists
michael@0 352 if (mBogusNode)
michael@0 353 {
michael@0 354 NS_ENSURE_STATE(mEditor);
michael@0 355 mEditor->DeleteNode(mBogusNode);
michael@0 356 mBogusNode = nullptr;
michael@0 357 }
michael@0 358
michael@0 359 return NS_OK;
michael@0 360 }
michael@0 361
michael@0 362 nsresult
michael@0 363 nsTextEditRules::DidInsert(nsISelection *aSelection, nsresult aResult)
michael@0 364 {
michael@0 365 return NS_OK;
michael@0 366 }
michael@0 367
michael@0 368 nsresult
michael@0 369 nsTextEditRules::WillInsertBreak(Selection* aSelection,
michael@0 370 bool *aCancel,
michael@0 371 bool *aHandled,
michael@0 372 int32_t aMaxLength)
michael@0 373 {
michael@0 374 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 375 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
michael@0 376 *aHandled = false;
michael@0 377 if (IsSingleLineEditor()) {
michael@0 378 *aCancel = true;
michael@0 379 }
michael@0 380 else
michael@0 381 {
michael@0 382 // handle docs with a max length
michael@0 383 // NOTE, this function copies inString into outString for us.
michael@0 384 NS_NAMED_LITERAL_STRING(inString, "\n");
michael@0 385 nsAutoString outString;
michael@0 386 bool didTruncate;
michael@0 387 nsresult res = TruncateInsertionIfNeeded(aSelection, &inString, &outString,
michael@0 388 aMaxLength, &didTruncate);
michael@0 389 NS_ENSURE_SUCCESS(res, res);
michael@0 390 if (didTruncate) {
michael@0 391 *aCancel = true;
michael@0 392 return NS_OK;
michael@0 393 }
michael@0 394
michael@0 395 *aCancel = false;
michael@0 396
michael@0 397 // if the selection isn't collapsed, delete it.
michael@0 398 bool bCollapsed;
michael@0 399 res = aSelection->GetIsCollapsed(&bCollapsed);
michael@0 400 NS_ENSURE_SUCCESS(res, res);
michael@0 401 if (!bCollapsed)
michael@0 402 {
michael@0 403 NS_ENSURE_STATE(mEditor);
michael@0 404 res = mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
michael@0 405 NS_ENSURE_SUCCESS(res, res);
michael@0 406 }
michael@0 407
michael@0 408 res = WillInsert(aSelection, aCancel);
michael@0 409 NS_ENSURE_SUCCESS(res, res);
michael@0 410 // initialize out param
michael@0 411 // we want to ignore result of WillInsert()
michael@0 412 *aCancel = false;
michael@0 413
michael@0 414 }
michael@0 415 return NS_OK;
michael@0 416 }
michael@0 417
michael@0 418 nsresult
michael@0 419 nsTextEditRules::DidInsertBreak(nsISelection *aSelection, nsresult aResult)
michael@0 420 {
michael@0 421 return NS_OK;
michael@0 422 }
michael@0 423
michael@0 424 nsresult
michael@0 425 nsTextEditRules::CollapseSelectionToTrailingBRIfNeeded(nsISelection* aSelection)
michael@0 426 {
michael@0 427 // we only need to execute the stuff below if we are a plaintext editor.
michael@0 428 // html editors have a different mechanism for putting in mozBR's
michael@0 429 // (because there are a bunch more places you have to worry about it in html)
michael@0 430 if (!IsPlaintextEditor()) {
michael@0 431 return NS_OK;
michael@0 432 }
michael@0 433
michael@0 434 // if we are at the end of the textarea, we need to set the
michael@0 435 // selection to stick to the mozBR at the end of the textarea.
michael@0 436 int32_t selOffset;
michael@0 437 nsCOMPtr<nsIDOMNode> selNode;
michael@0 438 nsresult res;
michael@0 439 NS_ENSURE_STATE(mEditor);
michael@0 440 res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
michael@0 441 NS_ENSURE_SUCCESS(res, res);
michael@0 442
michael@0 443 nsCOMPtr<nsIDOMText> nodeAsText = do_QueryInterface(selNode);
michael@0 444 if (!nodeAsText) return NS_OK; // nothing to do if we're not at a text node
michael@0 445
michael@0 446 uint32_t length;
michael@0 447 res = nodeAsText->GetLength(&length);
michael@0 448 NS_ENSURE_SUCCESS(res, res);
michael@0 449
michael@0 450 // nothing to do if we're not at the end of the text node
michael@0 451 if (selOffset != int32_t(length))
michael@0 452 return NS_OK;
michael@0 453
michael@0 454 int32_t parentOffset;
michael@0 455 nsCOMPtr<nsIDOMNode> parentNode = nsEditor::GetNodeLocation(selNode, &parentOffset);
michael@0 456
michael@0 457 NS_ENSURE_STATE(mEditor);
michael@0 458 nsCOMPtr<nsIDOMNode> root = do_QueryInterface(mEditor->GetRoot());
michael@0 459 NS_ENSURE_TRUE(root, NS_ERROR_NULL_POINTER);
michael@0 460 if (parentNode != root) return NS_OK;
michael@0 461
michael@0 462 nsCOMPtr<nsIDOMNode> nextNode = mEditor->GetChildAt(parentNode,
michael@0 463 parentOffset + 1);
michael@0 464 if (nextNode && nsTextEditUtils::IsMozBR(nextNode))
michael@0 465 {
michael@0 466 res = aSelection->Collapse(parentNode, parentOffset + 1);
michael@0 467 NS_ENSURE_SUCCESS(res, res);
michael@0 468 }
michael@0 469 return res;
michael@0 470 }
michael@0 471
michael@0 472 static inline already_AddRefed<nsIDOMNode>
michael@0 473 GetTextNode(nsISelection *selection, nsEditor *editor) {
michael@0 474 int32_t selOffset;
michael@0 475 nsCOMPtr<nsIDOMNode> selNode;
michael@0 476 nsresult res = editor->GetStartNodeAndOffset(selection, getter_AddRefs(selNode), &selOffset);
michael@0 477 NS_ENSURE_SUCCESS(res, nullptr);
michael@0 478 if (!editor->IsTextNode(selNode)) {
michael@0 479 // Get an nsINode from the nsIDOMNode
michael@0 480 nsCOMPtr<nsINode> node = do_QueryInterface(selNode);
michael@0 481 // if node is null, return it to indicate there's no text
michael@0 482 NS_ENSURE_TRUE(node, nullptr);
michael@0 483 // This should be the root node, walk the tree looking for text nodes
michael@0 484 mozilla::dom::NodeFilterHolder filter;
michael@0 485 mozilla::dom::NodeIterator iter(node, nsIDOMNodeFilter::SHOW_TEXT, filter);
michael@0 486 while (!editor->IsTextNode(selNode)) {
michael@0 487 if (NS_FAILED(res = iter.NextNode(getter_AddRefs(selNode))) || !selNode) {
michael@0 488 return nullptr;
michael@0 489 }
michael@0 490 }
michael@0 491 }
michael@0 492 return selNode.forget();
michael@0 493 }
michael@0 494 #ifdef DEBUG
michael@0 495 #define ASSERT_PASSWORD_LENGTHS_EQUAL() \
michael@0 496 if (IsPasswordEditor() && mEditor->GetRoot()) { \
michael@0 497 int32_t txtLen; \
michael@0 498 mEditor->GetTextLength(&txtLen); \
michael@0 499 NS_ASSERTION(mPasswordText.Length() == uint32_t(txtLen), \
michael@0 500 "password length not equal to number of asterisks"); \
michael@0 501 }
michael@0 502 #else
michael@0 503 #define ASSERT_PASSWORD_LENGTHS_EQUAL()
michael@0 504 #endif
michael@0 505
michael@0 506 // static
michael@0 507 void
michael@0 508 nsTextEditRules::HandleNewLines(nsString &aString,
michael@0 509 int32_t aNewlineHandling)
michael@0 510 {
michael@0 511 if (aNewlineHandling < 0) {
michael@0 512 int32_t caretStyle;
michael@0 513 nsPlaintextEditor::GetDefaultEditorPrefs(aNewlineHandling, caretStyle);
michael@0 514 }
michael@0 515
michael@0 516 switch(aNewlineHandling)
michael@0 517 {
michael@0 518 case nsIPlaintextEditor::eNewlinesReplaceWithSpaces:
michael@0 519 // Strip trailing newlines first so we don't wind up with trailing spaces
michael@0 520 aString.Trim(CRLF, false, true);
michael@0 521 aString.ReplaceChar(CRLF, ' ');
michael@0 522 break;
michael@0 523 case nsIPlaintextEditor::eNewlinesStrip:
michael@0 524 aString.StripChars(CRLF);
michael@0 525 break;
michael@0 526 case nsIPlaintextEditor::eNewlinesPasteToFirst:
michael@0 527 default:
michael@0 528 {
michael@0 529 int32_t firstCRLF = aString.FindCharInSet(CRLF);
michael@0 530
michael@0 531 // we get first *non-empty* line.
michael@0 532 int32_t offset = 0;
michael@0 533 while (firstCRLF == offset)
michael@0 534 {
michael@0 535 offset++;
michael@0 536 firstCRLF = aString.FindCharInSet(CRLF, offset);
michael@0 537 }
michael@0 538 if (firstCRLF > 0)
michael@0 539 aString.Truncate(firstCRLF);
michael@0 540 if (offset > 0)
michael@0 541 aString.Cut(0, offset);
michael@0 542 }
michael@0 543 break;
michael@0 544 case nsIPlaintextEditor::eNewlinesReplaceWithCommas:
michael@0 545 aString.Trim(CRLF, true, true);
michael@0 546 aString.ReplaceChar(CRLF, ',');
michael@0 547 break;
michael@0 548 case nsIPlaintextEditor::eNewlinesStripSurroundingWhitespace:
michael@0 549 {
michael@0 550 nsString result;
michael@0 551 uint32_t offset = 0;
michael@0 552 while (offset < aString.Length())
michael@0 553 {
michael@0 554 int32_t nextCRLF = aString.FindCharInSet(CRLF, offset);
michael@0 555 if (nextCRLF < 0) {
michael@0 556 result.Append(nsDependentSubstring(aString, offset));
michael@0 557 break;
michael@0 558 }
michael@0 559 uint32_t wsBegin = nextCRLF;
michael@0 560 // look backwards for the first non-whitespace char
michael@0 561 while (wsBegin > offset && NS_IS_SPACE(aString[wsBegin - 1]))
michael@0 562 --wsBegin;
michael@0 563 result.Append(nsDependentSubstring(aString, offset, wsBegin - offset));
michael@0 564 offset = nextCRLF + 1;
michael@0 565 while (offset < aString.Length() && NS_IS_SPACE(aString[offset]))
michael@0 566 ++offset;
michael@0 567 }
michael@0 568 aString = result;
michael@0 569 }
michael@0 570 break;
michael@0 571 case nsIPlaintextEditor::eNewlinesPasteIntact:
michael@0 572 // even if we're pasting newlines, don't paste leading/trailing ones
michael@0 573 aString.Trim(CRLF, true, true);
michael@0 574 break;
michael@0 575 }
michael@0 576 }
michael@0 577
michael@0 578 nsresult
michael@0 579 nsTextEditRules::WillInsertText(EditAction aAction,
michael@0 580 Selection* aSelection,
michael@0 581 bool *aCancel,
michael@0 582 bool *aHandled,
michael@0 583 const nsAString *inString,
michael@0 584 nsAString *outString,
michael@0 585 int32_t aMaxLength)
michael@0 586 {
michael@0 587 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 588
michael@0 589 if (inString->IsEmpty() && aAction != EditAction::insertIMEText) {
michael@0 590 // HACK: this is a fix for bug 19395
michael@0 591 // I can't outlaw all empty insertions
michael@0 592 // because IME transaction depend on them
michael@0 593 // There is more work to do to make the
michael@0 594 // world safe for IME.
michael@0 595 *aCancel = true;
michael@0 596 *aHandled = false;
michael@0 597 return NS_OK;
michael@0 598 }
michael@0 599
michael@0 600 // initialize out param
michael@0 601 *aCancel = false;
michael@0 602 *aHandled = true;
michael@0 603
michael@0 604 // handle docs with a max length
michael@0 605 // NOTE, this function copies inString into outString for us.
michael@0 606 bool truncated = false;
michael@0 607 nsresult res = TruncateInsertionIfNeeded(aSelection, inString, outString,
michael@0 608 aMaxLength, &truncated);
michael@0 609 NS_ENSURE_SUCCESS(res, res);
michael@0 610 // If we're exceeding the maxlength when composing IME, we need to clean up
michael@0 611 // the composing text, so we shouldn't return early.
michael@0 612 if (truncated && outString->IsEmpty() &&
michael@0 613 aAction != EditAction::insertIMEText) {
michael@0 614 *aCancel = true;
michael@0 615 return NS_OK;
michael@0 616 }
michael@0 617
michael@0 618 int32_t start = 0;
michael@0 619 int32_t end = 0;
michael@0 620
michael@0 621 // handle password field docs
michael@0 622 if (IsPasswordEditor()) {
michael@0 623 NS_ENSURE_STATE(mEditor);
michael@0 624 nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(),
michael@0 625 start, end);
michael@0 626 }
michael@0 627
michael@0 628 // if the selection isn't collapsed, delete it.
michael@0 629 bool bCollapsed;
michael@0 630 res = aSelection->GetIsCollapsed(&bCollapsed);
michael@0 631 NS_ENSURE_SUCCESS(res, res);
michael@0 632 if (!bCollapsed)
michael@0 633 {
michael@0 634 NS_ENSURE_STATE(mEditor);
michael@0 635 res = mEditor->DeleteSelection(nsIEditor::eNone, nsIEditor::eStrip);
michael@0 636 NS_ENSURE_SUCCESS(res, res);
michael@0 637 }
michael@0 638
michael@0 639 res = WillInsert(aSelection, aCancel);
michael@0 640 NS_ENSURE_SUCCESS(res, res);
michael@0 641 // initialize out param
michael@0 642 // we want to ignore result of WillInsert()
michael@0 643 *aCancel = false;
michael@0 644
michael@0 645 // handle password field data
michael@0 646 // this has the side effect of changing all the characters in aOutString
michael@0 647 // to the replacement character
michael@0 648 if (IsPasswordEditor())
michael@0 649 {
michael@0 650 if (aAction == EditAction::insertIMEText) {
michael@0 651 RemoveIMETextFromPWBuf(start, outString);
michael@0 652 }
michael@0 653 }
michael@0 654
michael@0 655 // People have lots of different ideas about what text fields
michael@0 656 // should do with multiline pastes. See bugs 21032, 23485, 23485, 50935.
michael@0 657 // The six possible options are:
michael@0 658 // 0. paste newlines intact
michael@0 659 // 1. paste up to the first newline (default)
michael@0 660 // 2. replace newlines with spaces
michael@0 661 // 3. strip newlines
michael@0 662 // 4. replace with commas
michael@0 663 // 5. strip newlines and surrounding whitespace
michael@0 664 // So find out what we're expected to do:
michael@0 665 if (IsSingleLineEditor())
michael@0 666 {
michael@0 667 nsAutoString tString(*outString);
michael@0 668
michael@0 669 NS_ENSURE_STATE(mEditor);
michael@0 670 HandleNewLines(tString, mEditor->mNewlineHandling);
michael@0 671
michael@0 672 outString->Assign(tString);
michael@0 673 }
michael@0 674
michael@0 675 if (IsPasswordEditor())
michael@0 676 {
michael@0 677 // manage the password buffer
michael@0 678 mPasswordText.Insert(*outString, start);
michael@0 679
michael@0 680 if (LookAndFeel::GetEchoPassword() && !DontEchoPassword()) {
michael@0 681 HideLastPWInput();
michael@0 682 mLastStart = start;
michael@0 683 mLastLength = outString->Length();
michael@0 684 if (mTimer)
michael@0 685 {
michael@0 686 mTimer->Cancel();
michael@0 687 }
michael@0 688 else
michael@0 689 {
michael@0 690 mTimer = do_CreateInstance("@mozilla.org/timer;1", &res);
michael@0 691 NS_ENSURE_SUCCESS(res, res);
michael@0 692 }
michael@0 693 mTimer->InitWithCallback(this, LookAndFeel::GetPasswordMaskDelay(),
michael@0 694 nsITimer::TYPE_ONE_SHOT);
michael@0 695 }
michael@0 696 else
michael@0 697 {
michael@0 698 FillBufWithPWChars(outString, outString->Length());
michael@0 699 }
michael@0 700 }
michael@0 701
michael@0 702 // get the (collapsed) selection location
michael@0 703 nsCOMPtr<nsIDOMNode> selNode;
michael@0 704 int32_t selOffset;
michael@0 705 NS_ENSURE_STATE(mEditor);
michael@0 706 res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(selNode), &selOffset);
michael@0 707 NS_ENSURE_SUCCESS(res, res);
michael@0 708
michael@0 709 // don't put text in places that can't have it
michael@0 710 NS_ENSURE_STATE(mEditor);
michael@0 711 if (!mEditor->IsTextNode(selNode) &&
michael@0 712 !mEditor->CanContainTag(selNode, nsGkAtoms::textTagName)) {
michael@0 713 return NS_ERROR_FAILURE;
michael@0 714 }
michael@0 715
michael@0 716 // we need to get the doc
michael@0 717 NS_ENSURE_STATE(mEditor);
michael@0 718 nsCOMPtr<nsIDOMDocument> doc = mEditor->GetDOMDocument();
michael@0 719 NS_ENSURE_TRUE(doc, NS_ERROR_NOT_INITIALIZED);
michael@0 720
michael@0 721 if (aAction == EditAction::insertIMEText) {
michael@0 722 NS_ENSURE_STATE(mEditor);
michael@0 723 res = mEditor->InsertTextImpl(*outString, address_of(selNode), &selOffset, doc);
michael@0 724 NS_ENSURE_SUCCESS(res, res);
michael@0 725 } else {
michael@0 726 // aAction == EditAction::insertText; find where we are
michael@0 727 nsCOMPtr<nsIDOMNode> curNode = selNode;
michael@0 728 int32_t curOffset = selOffset;
michael@0 729
michael@0 730 // don't spaz my selection in subtransactions
michael@0 731 NS_ENSURE_STATE(mEditor);
michael@0 732 nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
michael@0 733
michael@0 734 res = mEditor->InsertTextImpl(*outString, address_of(curNode),
michael@0 735 &curOffset, doc);
michael@0 736 NS_ENSURE_SUCCESS(res, res);
michael@0 737
michael@0 738 if (curNode)
michael@0 739 {
michael@0 740 // Make the caret attach to the inserted text, unless this text ends with a LF,
michael@0 741 // in which case make the caret attach to the next line.
michael@0 742 bool endsWithLF =
michael@0 743 !outString->IsEmpty() && outString->Last() == nsCRT::LF;
michael@0 744 aSelection->SetInterlinePosition(endsWithLF);
michael@0 745
michael@0 746 aSelection->Collapse(curNode, curOffset);
michael@0 747 }
michael@0 748 }
michael@0 749 ASSERT_PASSWORD_LENGTHS_EQUAL()
michael@0 750 return res;
michael@0 751 }
michael@0 752
michael@0 753 nsresult
michael@0 754 nsTextEditRules::DidInsertText(nsISelection *aSelection,
michael@0 755 nsresult aResult)
michael@0 756 {
michael@0 757 return DidInsert(aSelection, aResult);
michael@0 758 }
michael@0 759
michael@0 760
michael@0 761
michael@0 762 nsresult
michael@0 763 nsTextEditRules::WillSetTextProperty(nsISelection *aSelection, bool *aCancel, bool *aHandled)
michael@0 764 {
michael@0 765 if (!aSelection || !aCancel || !aHandled)
michael@0 766 { return NS_ERROR_NULL_POINTER; }
michael@0 767
michael@0 768 // XXX: should probably return a success value other than NS_OK that means "not allowed"
michael@0 769 if (IsPlaintextEditor()) {
michael@0 770 *aCancel = true;
michael@0 771 }
michael@0 772 return NS_OK;
michael@0 773 }
michael@0 774
michael@0 775 nsresult
michael@0 776 nsTextEditRules::DidSetTextProperty(nsISelection *aSelection, nsresult aResult)
michael@0 777 {
michael@0 778 return NS_OK;
michael@0 779 }
michael@0 780
michael@0 781 nsresult
michael@0 782 nsTextEditRules::WillRemoveTextProperty(nsISelection *aSelection, bool *aCancel, bool *aHandled)
michael@0 783 {
michael@0 784 if (!aSelection || !aCancel || !aHandled)
michael@0 785 { return NS_ERROR_NULL_POINTER; }
michael@0 786
michael@0 787 // XXX: should probably return a success value other than NS_OK that means "not allowed"
michael@0 788 if (IsPlaintextEditor()) {
michael@0 789 *aCancel = true;
michael@0 790 }
michael@0 791 return NS_OK;
michael@0 792 }
michael@0 793
michael@0 794 nsresult
michael@0 795 nsTextEditRules::DidRemoveTextProperty(nsISelection *aSelection, nsresult aResult)
michael@0 796 {
michael@0 797 return NS_OK;
michael@0 798 }
michael@0 799
michael@0 800 nsresult
michael@0 801 nsTextEditRules::WillDeleteSelection(Selection* aSelection,
michael@0 802 nsIEditor::EDirection aCollapsedAction,
michael@0 803 bool *aCancel,
michael@0 804 bool *aHandled)
michael@0 805 {
michael@0 806 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 807 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
michael@0 808
michael@0 809 // initialize out param
michael@0 810 *aCancel = false;
michael@0 811 *aHandled = false;
michael@0 812
michael@0 813 // if there is only bogus content, cancel the operation
michael@0 814 if (mBogusNode) {
michael@0 815 *aCancel = true;
michael@0 816 return NS_OK;
michael@0 817 }
michael@0 818
michael@0 819 nsresult res = NS_OK;
michael@0 820 nsAutoScriptBlocker scriptBlocker;
michael@0 821
michael@0 822 if (IsPasswordEditor())
michael@0 823 {
michael@0 824 NS_ENSURE_STATE(mEditor);
michael@0 825 res = mEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction);
michael@0 826 NS_ENSURE_SUCCESS(res, res);
michael@0 827
michael@0 828 // manage the password buffer
michael@0 829 int32_t start, end;
michael@0 830 nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(),
michael@0 831 start, end);
michael@0 832
michael@0 833 if (LookAndFeel::GetEchoPassword()) {
michael@0 834 HideLastPWInput();
michael@0 835 mLastStart = start;
michael@0 836 mLastLength = 0;
michael@0 837 if (mTimer)
michael@0 838 {
michael@0 839 mTimer->Cancel();
michael@0 840 }
michael@0 841 }
michael@0 842
michael@0 843 if (end == start)
michael@0 844 { // collapsed selection
michael@0 845 if (nsIEditor::ePrevious==aCollapsedAction && 0<start) { // del back
michael@0 846 mPasswordText.Cut(start-1, 1);
michael@0 847 }
michael@0 848 else if (nsIEditor::eNext==aCollapsedAction) { // del forward
michael@0 849 mPasswordText.Cut(start, 1);
michael@0 850 }
michael@0 851 // otherwise nothing to do for this collapsed selection
michael@0 852 }
michael@0 853 else { // extended selection
michael@0 854 mPasswordText.Cut(start, end-start);
michael@0 855 }
michael@0 856 }
michael@0 857 else
michael@0 858 {
michael@0 859 nsCOMPtr<nsIDOMNode> startNode;
michael@0 860 int32_t startOffset;
michael@0 861 NS_ENSURE_STATE(mEditor);
michael@0 862 res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
michael@0 863 NS_ENSURE_SUCCESS(res, res);
michael@0 864 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
michael@0 865
michael@0 866 bool bCollapsed;
michael@0 867 res = aSelection->GetIsCollapsed(&bCollapsed);
michael@0 868 NS_ENSURE_SUCCESS(res, res);
michael@0 869
michael@0 870 if (!bCollapsed)
michael@0 871 return NS_OK;
michael@0 872
michael@0 873 // Test for distance between caret and text that will be deleted
michael@0 874 res = CheckBidiLevelForDeletion(aSelection, startNode, startOffset, aCollapsedAction, aCancel);
michael@0 875 NS_ENSURE_SUCCESS(res, res);
michael@0 876 if (*aCancel) return NS_OK;
michael@0 877
michael@0 878 NS_ENSURE_STATE(mEditor);
michael@0 879 res = mEditor->ExtendSelectionForDelete(aSelection, &aCollapsedAction);
michael@0 880 NS_ENSURE_SUCCESS(res, res);
michael@0 881 }
michael@0 882
michael@0 883 NS_ENSURE_STATE(mEditor);
michael@0 884 res = mEditor->DeleteSelectionImpl(aCollapsedAction, nsIEditor::eStrip);
michael@0 885 NS_ENSURE_SUCCESS(res, res);
michael@0 886
michael@0 887 *aHandled = true;
michael@0 888 ASSERT_PASSWORD_LENGTHS_EQUAL()
michael@0 889 return NS_OK;
michael@0 890 }
michael@0 891
michael@0 892 nsresult
michael@0 893 nsTextEditRules::DidDeleteSelection(nsISelection *aSelection,
michael@0 894 nsIEditor::EDirection aCollapsedAction,
michael@0 895 nsresult aResult)
michael@0 896 {
michael@0 897 nsCOMPtr<nsIDOMNode> startNode;
michael@0 898 int32_t startOffset;
michael@0 899 NS_ENSURE_STATE(mEditor);
michael@0 900 nsresult res = mEditor->GetStartNodeAndOffset(aSelection, getter_AddRefs(startNode), &startOffset);
michael@0 901 NS_ENSURE_SUCCESS(res, res);
michael@0 902 NS_ENSURE_TRUE(startNode, NS_ERROR_FAILURE);
michael@0 903
michael@0 904 // delete empty text nodes at selection
michael@0 905 if (mEditor->IsTextNode(startNode))
michael@0 906 {
michael@0 907 nsCOMPtr<nsIDOMText> textNode = do_QueryInterface(startNode);
michael@0 908 uint32_t strLength;
michael@0 909 res = textNode->GetLength(&strLength);
michael@0 910 NS_ENSURE_SUCCESS(res, res);
michael@0 911
michael@0 912 // are we in an empty text node?
michael@0 913 if (!strLength)
michael@0 914 {
michael@0 915 res = mEditor->DeleteNode(startNode);
michael@0 916 NS_ENSURE_SUCCESS(res, res);
michael@0 917 }
michael@0 918 }
michael@0 919 if (!mDidExplicitlySetInterline)
michael@0 920 {
michael@0 921 // We prevent the caret from sticking on the left of prior BR
michael@0 922 // (i.e. the end of previous line) after this deletion. Bug 92124
michael@0 923 nsCOMPtr<nsISelectionPrivate> selPriv = do_QueryInterface(aSelection);
michael@0 924 if (selPriv) res = selPriv->SetInterlinePosition(true);
michael@0 925 }
michael@0 926 return res;
michael@0 927 }
michael@0 928
michael@0 929 nsresult
michael@0 930 nsTextEditRules::WillUndo(nsISelection *aSelection, bool *aCancel, bool *aHandled)
michael@0 931 {
michael@0 932 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 933 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
michael@0 934 // initialize out param
michael@0 935 *aCancel = false;
michael@0 936 *aHandled = false;
michael@0 937 return NS_OK;
michael@0 938 }
michael@0 939
michael@0 940 /* the idea here is to see if the magic empty node has suddenly reappeared as the result of the undo.
michael@0 941 * if it has, set our state so we remember it.
michael@0 942 * There is a tradeoff between doing here and at redo, or doing it everywhere else that might care.
michael@0 943 * Since undo and redo are relatively rare, it makes sense to take the (small) performance hit here.
michael@0 944 */
michael@0 945 nsresult
michael@0 946 nsTextEditRules::DidUndo(nsISelection *aSelection, nsresult aResult)
michael@0 947 {
michael@0 948 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
michael@0 949 // If aResult is an error, we return it.
michael@0 950 NS_ENSURE_SUCCESS(aResult, aResult);
michael@0 951
michael@0 952 NS_ENSURE_STATE(mEditor);
michael@0 953 dom::Element* theRoot = mEditor->GetRoot();
michael@0 954 NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
michael@0 955 nsIContent* node = mEditor->GetLeftmostChild(theRoot);
michael@0 956 if (node && mEditor->IsMozEditorBogusNode(node)) {
michael@0 957 mBogusNode = do_QueryInterface(node);
michael@0 958 } else {
michael@0 959 mBogusNode = nullptr;
michael@0 960 }
michael@0 961 return aResult;
michael@0 962 }
michael@0 963
michael@0 964 nsresult
michael@0 965 nsTextEditRules::WillRedo(nsISelection *aSelection, bool *aCancel, bool *aHandled)
michael@0 966 {
michael@0 967 if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
michael@0 968 CANCEL_OPERATION_IF_READONLY_OR_DISABLED
michael@0 969 // initialize out param
michael@0 970 *aCancel = false;
michael@0 971 *aHandled = false;
michael@0 972 return NS_OK;
michael@0 973 }
michael@0 974
michael@0 975 nsresult
michael@0 976 nsTextEditRules::DidRedo(nsISelection *aSelection, nsresult aResult)
michael@0 977 {
michael@0 978 nsresult res = aResult; // if aResult is an error, we return it.
michael@0 979 if (!aSelection) { return NS_ERROR_NULL_POINTER; }
michael@0 980 if (NS_SUCCEEDED(res))
michael@0 981 {
michael@0 982 NS_ENSURE_STATE(mEditor);
michael@0 983 nsCOMPtr<nsIDOMElement> theRoot = do_QueryInterface(mEditor->GetRoot());
michael@0 984 NS_ENSURE_TRUE(theRoot, NS_ERROR_FAILURE);
michael@0 985
michael@0 986 nsCOMPtr<nsIDOMHTMLCollection> nodeList;
michael@0 987 res = theRoot->GetElementsByTagName(NS_LITERAL_STRING("br"),
michael@0 988 getter_AddRefs(nodeList));
michael@0 989 NS_ENSURE_SUCCESS(res, res);
michael@0 990 if (nodeList)
michael@0 991 {
michael@0 992 uint32_t len;
michael@0 993 nodeList->GetLength(&len);
michael@0 994
michael@0 995 if (len != 1) {
michael@0 996 // only in the case of one br could there be the bogus node
michael@0 997 mBogusNode = nullptr;
michael@0 998 return NS_OK;
michael@0 999 }
michael@0 1000
michael@0 1001 nsCOMPtr<nsIDOMNode> node;
michael@0 1002 nodeList->Item(0, getter_AddRefs(node));
michael@0 1003 nsCOMPtr<nsIContent> content = do_QueryInterface(node);
michael@0 1004 MOZ_ASSERT(content);
michael@0 1005 if (mEditor->IsMozEditorBogusNode(content)) {
michael@0 1006 mBogusNode = node;
michael@0 1007 } else {
michael@0 1008 mBogusNode = nullptr;
michael@0 1009 }
michael@0 1010 }
michael@0 1011 }
michael@0 1012 return res;
michael@0 1013 }
michael@0 1014
michael@0 1015 nsresult
michael@0 1016 nsTextEditRules::WillOutputText(nsISelection *aSelection,
michael@0 1017 const nsAString *aOutputFormat,
michael@0 1018 nsAString *aOutString,
michael@0 1019 bool *aCancel,
michael@0 1020 bool *aHandled)
michael@0 1021 {
michael@0 1022 // null selection ok
michael@0 1023 if (!aOutString || !aOutputFormat || !aCancel || !aHandled)
michael@0 1024 { return NS_ERROR_NULL_POINTER; }
michael@0 1025
michael@0 1026 // initialize out param
michael@0 1027 *aCancel = false;
michael@0 1028 *aHandled = false;
michael@0 1029
michael@0 1030 nsAutoString outputFormat(*aOutputFormat);
michael@0 1031 ToLowerCase(outputFormat);
michael@0 1032 if (outputFormat.EqualsLiteral("text/plain"))
michael@0 1033 { // only use these rules for plain text output
michael@0 1034 if (IsPasswordEditor())
michael@0 1035 {
michael@0 1036 *aOutString = mPasswordText;
michael@0 1037 *aHandled = true;
michael@0 1038 }
michael@0 1039 else if (mBogusNode)
michael@0 1040 { // this means there's no content, so output null string
michael@0 1041 aOutString->Truncate();
michael@0 1042 *aHandled = true;
michael@0 1043 }
michael@0 1044 }
michael@0 1045 return NS_OK;
michael@0 1046 }
michael@0 1047
michael@0 1048 nsresult
michael@0 1049 nsTextEditRules::DidOutputText(nsISelection *aSelection, nsresult aResult)
michael@0 1050 {
michael@0 1051 return NS_OK;
michael@0 1052 }
michael@0 1053
michael@0 1054 nsresult
michael@0 1055 nsTextEditRules::RemoveRedundantTrailingBR()
michael@0 1056 {
michael@0 1057 // If the bogus node exists, we have no work to do
michael@0 1058 if (mBogusNode)
michael@0 1059 return NS_OK;
michael@0 1060
michael@0 1061 // Likewise, nothing to be done if we could never have inserted a trailing br
michael@0 1062 if (IsSingleLineEditor())
michael@0 1063 return NS_OK;
michael@0 1064
michael@0 1065 NS_ENSURE_STATE(mEditor);
michael@0 1066 nsRefPtr<dom::Element> body = mEditor->GetRoot();
michael@0 1067 if (!body)
michael@0 1068 return NS_ERROR_NULL_POINTER;
michael@0 1069
michael@0 1070 uint32_t childCount = body->GetChildCount();
michael@0 1071 if (childCount > 1) {
michael@0 1072 // The trailing br is redundant if it is the only remaining child node
michael@0 1073 return NS_OK;
michael@0 1074 }
michael@0 1075
michael@0 1076 nsRefPtr<nsIContent> child = body->GetFirstChild();
michael@0 1077 if (!child || !child->IsElement()) {
michael@0 1078 return NS_OK;
michael@0 1079 }
michael@0 1080
michael@0 1081 dom::Element* elem = child->AsElement();
michael@0 1082 if (!nsTextEditUtils::IsMozBR(elem)) {
michael@0 1083 return NS_OK;
michael@0 1084 }
michael@0 1085
michael@0 1086 // Rather than deleting this node from the DOM tree we should instead
michael@0 1087 // morph this br into the bogus node
michael@0 1088 elem->UnsetAttr(kNameSpaceID_None, nsGkAtoms::type, true);
michael@0 1089
michael@0 1090 // set mBogusNode to be this <br>
michael@0 1091 mBogusNode = do_QueryInterface(elem);
michael@0 1092
michael@0 1093 // give it the bogus node attribute
michael@0 1094 elem->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom,
michael@0 1095 kMOZEditorBogusNodeValue, false);
michael@0 1096 return NS_OK;
michael@0 1097 }
michael@0 1098
michael@0 1099 nsresult
michael@0 1100 nsTextEditRules::CreateTrailingBRIfNeeded()
michael@0 1101 {
michael@0 1102 // but only if we aren't a single line edit field
michael@0 1103 if (IsSingleLineEditor()) {
michael@0 1104 return NS_OK;
michael@0 1105 }
michael@0 1106
michael@0 1107 NS_ENSURE_STATE(mEditor);
michael@0 1108 dom::Element* body = mEditor->GetRoot();
michael@0 1109 NS_ENSURE_TRUE(body, NS_ERROR_NULL_POINTER);
michael@0 1110
michael@0 1111 nsIContent* lastChild = body->GetLastChild();
michael@0 1112 // assuming CreateBogusNodeIfNeeded() has been called first
michael@0 1113 NS_ENSURE_TRUE(lastChild, NS_ERROR_NULL_POINTER);
michael@0 1114
michael@0 1115 if (!lastChild->IsHTML(nsGkAtoms::br)) {
michael@0 1116 nsAutoTxnsConserveSelection dontSpazMySelection(mEditor);
michael@0 1117 nsCOMPtr<nsIDOMNode> domBody = do_QueryInterface(body);
michael@0 1118 return CreateMozBR(domBody, body->Length());
michael@0 1119 }
michael@0 1120
michael@0 1121 // Check to see if the trailing BR is a former bogus node - this will have
michael@0 1122 // stuck around if we previously morphed a trailing node into a bogus node.
michael@0 1123 if (!mEditor->IsMozEditorBogusNode(lastChild)) {
michael@0 1124 return NS_OK;
michael@0 1125 }
michael@0 1126
michael@0 1127 // Morph it back to a mozBR
michael@0 1128 lastChild->UnsetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom, false);
michael@0 1129 lastChild->SetAttr(kNameSpaceID_None, nsGkAtoms::type,
michael@0 1130 NS_LITERAL_STRING("_moz"), true);
michael@0 1131 return NS_OK;
michael@0 1132 }
michael@0 1133
michael@0 1134 nsresult
michael@0 1135 nsTextEditRules::CreateBogusNodeIfNeeded(nsISelection *aSelection)
michael@0 1136 {
michael@0 1137 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
michael@0 1138 NS_ENSURE_TRUE(mEditor, NS_ERROR_NULL_POINTER);
michael@0 1139
michael@0 1140 if (mBogusNode) {
michael@0 1141 // Let's not create more than one, ok?
michael@0 1142 return NS_OK;
michael@0 1143 }
michael@0 1144
michael@0 1145 // tell rules system to not do any post-processing
michael@0 1146 nsAutoRules beginRulesSniffing(mEditor, EditAction::ignore, nsIEditor::eNone);
michael@0 1147
michael@0 1148 nsCOMPtr<dom::Element> body = mEditor->GetRoot();
michael@0 1149 if (!body) {
michael@0 1150 // We don't even have a body yet, don't insert any bogus nodes at
michael@0 1151 // this point.
michael@0 1152 return NS_OK;
michael@0 1153 }
michael@0 1154
michael@0 1155 // Now we've got the body element. Iterate over the body element's children,
michael@0 1156 // looking for editable content. If no editable content is found, insert the
michael@0 1157 // bogus node.
michael@0 1158 for (nsCOMPtr<nsIContent> bodyChild = body->GetFirstChild();
michael@0 1159 bodyChild;
michael@0 1160 bodyChild = bodyChild->GetNextSibling()) {
michael@0 1161 if (mEditor->IsMozEditorBogusNode(bodyChild) ||
michael@0 1162 !mEditor->IsEditable(body) || // XXX hoist out of the loop?
michael@0 1163 mEditor->IsEditable(bodyChild)) {
michael@0 1164 return NS_OK;
michael@0 1165 }
michael@0 1166 }
michael@0 1167
michael@0 1168 // Skip adding the bogus node if body is read-only.
michael@0 1169 if (!mEditor->IsModifiableNode(body)) {
michael@0 1170 return NS_OK;
michael@0 1171 }
michael@0 1172
michael@0 1173 // Create a br.
michael@0 1174 nsCOMPtr<dom::Element> newContent;
michael@0 1175 nsresult rv = mEditor->CreateHTMLContent(NS_LITERAL_STRING("br"), getter_AddRefs(newContent));
michael@0 1176 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1177
michael@0 1178 // set mBogusNode to be the newly created <br>
michael@0 1179 mBogusNode = do_QueryInterface(newContent);
michael@0 1180 NS_ENSURE_TRUE(mBogusNode, NS_ERROR_NULL_POINTER);
michael@0 1181
michael@0 1182 // Give it a special attribute.
michael@0 1183 newContent->SetAttr(kNameSpaceID_None, kMOZEditorBogusNodeAttrAtom,
michael@0 1184 kMOZEditorBogusNodeValue, false);
michael@0 1185
michael@0 1186 // Put the node in the document.
michael@0 1187 nsCOMPtr<nsIDOMNode> bodyNode = do_QueryInterface(body);
michael@0 1188 rv = mEditor->InsertNode(mBogusNode, bodyNode, 0);
michael@0 1189 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1190
michael@0 1191 // Set selection.
michael@0 1192 aSelection->CollapseNative(body, 0);
michael@0 1193 return NS_OK;
michael@0 1194 }
michael@0 1195
michael@0 1196
michael@0 1197 nsresult
michael@0 1198 nsTextEditRules::TruncateInsertionIfNeeded(Selection* aSelection,
michael@0 1199 const nsAString *aInString,
michael@0 1200 nsAString *aOutString,
michael@0 1201 int32_t aMaxLength,
michael@0 1202 bool *aTruncated)
michael@0 1203 {
michael@0 1204 if (!aSelection || !aInString || !aOutString) {return NS_ERROR_NULL_POINTER;}
michael@0 1205
michael@0 1206 nsresult res = NS_OK;
michael@0 1207 *aOutString = *aInString;
michael@0 1208 if (aTruncated) {
michael@0 1209 *aTruncated = false;
michael@0 1210 }
michael@0 1211
michael@0 1212 NS_ENSURE_STATE(mEditor);
michael@0 1213 if ((-1 != aMaxLength) && IsPlaintextEditor() && !mEditor->IsIMEComposing() )
michael@0 1214 {
michael@0 1215 // Get the current text length.
michael@0 1216 // Get the length of inString.
michael@0 1217 // Get the length of the selection.
michael@0 1218 // If selection is collapsed, it is length 0.
michael@0 1219 // Subtract the length of the selection from the len(doc)
michael@0 1220 // since we'll delete the selection on insert.
michael@0 1221 // This is resultingDocLength.
michael@0 1222 // Get old length of IME composing string
michael@0 1223 // which will be replaced by new one.
michael@0 1224 // If (resultingDocLength) is at or over max, cancel the insert
michael@0 1225 // If (resultingDocLength) + (length of input) > max,
michael@0 1226 // set aOutString to subset of inString so length = max
michael@0 1227 int32_t docLength;
michael@0 1228 res = mEditor->GetTextLength(&docLength);
michael@0 1229 if (NS_FAILED(res)) { return res; }
michael@0 1230
michael@0 1231 int32_t start, end;
michael@0 1232 nsContentUtils::GetSelectionInTextControl(aSelection, mEditor->GetRoot(),
michael@0 1233 start, end);
michael@0 1234
michael@0 1235 TextComposition* composition = mEditor->GetComposition();
michael@0 1236 int32_t oldCompStrLength = composition ? composition->String().Length() : 0;
michael@0 1237
michael@0 1238 const int32_t selectionLength = end - start;
michael@0 1239 const int32_t resultingDocLength = docLength - selectionLength - oldCompStrLength;
michael@0 1240 if (resultingDocLength >= aMaxLength)
michael@0 1241 {
michael@0 1242 aOutString->Truncate();
michael@0 1243 if (aTruncated) {
michael@0 1244 *aTruncated = true;
michael@0 1245 }
michael@0 1246 }
michael@0 1247 else
michael@0 1248 {
michael@0 1249 int32_t inCount = aOutString->Length();
michael@0 1250 if (inCount + resultingDocLength > aMaxLength)
michael@0 1251 {
michael@0 1252 aOutString->Truncate(aMaxLength - resultingDocLength);
michael@0 1253 if (aTruncated) {
michael@0 1254 *aTruncated = true;
michael@0 1255 }
michael@0 1256 }
michael@0 1257 }
michael@0 1258 }
michael@0 1259 return res;
michael@0 1260 }
michael@0 1261
michael@0 1262 void
michael@0 1263 nsTextEditRules::ResetIMETextPWBuf()
michael@0 1264 {
michael@0 1265 mPasswordIMEText.Truncate();
michael@0 1266 }
michael@0 1267
michael@0 1268 void
michael@0 1269 nsTextEditRules::RemoveIMETextFromPWBuf(int32_t &aStart, nsAString *aIMEString)
michael@0 1270 {
michael@0 1271 MOZ_ASSERT(aIMEString);
michael@0 1272
michael@0 1273 // initialize PasswordIME
michael@0 1274 if (mPasswordIMEText.IsEmpty()) {
michael@0 1275 mPasswordIMEIndex = aStart;
michael@0 1276 }
michael@0 1277 else {
michael@0 1278 // manage the password buffer
michael@0 1279 mPasswordText.Cut(mPasswordIMEIndex, mPasswordIMEText.Length());
michael@0 1280 aStart = mPasswordIMEIndex;
michael@0 1281 }
michael@0 1282
michael@0 1283 mPasswordIMEText.Assign(*aIMEString);
michael@0 1284 }
michael@0 1285
michael@0 1286 NS_IMETHODIMP nsTextEditRules::Notify(nsITimer *)
michael@0 1287 {
michael@0 1288 MOZ_ASSERT(mTimer);
michael@0 1289
michael@0 1290 // Check whether our text editor's password flag was changed before this
michael@0 1291 // "hide password character" timer actually fires.
michael@0 1292 nsresult res = IsPasswordEditor() ? HideLastPWInput() : NS_OK;
michael@0 1293 ASSERT_PASSWORD_LENGTHS_EQUAL();
michael@0 1294 mLastLength = 0;
michael@0 1295 return res;
michael@0 1296 }
michael@0 1297
michael@0 1298 nsresult nsTextEditRules::HideLastPWInput() {
michael@0 1299 if (!mLastLength) {
michael@0 1300 // Special case, we're trying to replace a range that no longer exists
michael@0 1301 return NS_OK;
michael@0 1302 }
michael@0 1303
michael@0 1304 nsAutoString hiddenText;
michael@0 1305 FillBufWithPWChars(&hiddenText, mLastLength);
michael@0 1306
michael@0 1307 NS_ENSURE_STATE(mEditor);
michael@0 1308 nsRefPtr<Selection> selection = mEditor->GetSelection();
michael@0 1309 NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
michael@0 1310 int32_t start, end;
michael@0 1311 nsContentUtils::GetSelectionInTextControl(selection, mEditor->GetRoot(),
michael@0 1312 start, end);
michael@0 1313
michael@0 1314 nsCOMPtr<nsIDOMNode> selNode = GetTextNode(selection, mEditor);
michael@0 1315 NS_ENSURE_TRUE(selNode, NS_OK);
michael@0 1316
michael@0 1317 nsCOMPtr<nsIDOMCharacterData> nodeAsText(do_QueryInterface(selNode));
michael@0 1318 NS_ENSURE_TRUE(nodeAsText, NS_OK);
michael@0 1319
michael@0 1320 nodeAsText->ReplaceData(mLastStart, mLastLength, hiddenText);
michael@0 1321 selection->Collapse(selNode, start);
michael@0 1322 if (start != end)
michael@0 1323 selection->Extend(selNode, end);
michael@0 1324 return NS_OK;
michael@0 1325 }
michael@0 1326
michael@0 1327 // static
michael@0 1328 void
michael@0 1329 nsTextEditRules::FillBufWithPWChars(nsAString *aOutString, int32_t aLength)
michael@0 1330 {
michael@0 1331 MOZ_ASSERT(aOutString);
michael@0 1332
michael@0 1333 // change the output to the platform password character
michael@0 1334 char16_t passwordChar = LookAndFeel::GetPasswordCharacter();
michael@0 1335
michael@0 1336 int32_t i;
michael@0 1337 aOutString->Truncate();
michael@0 1338 for (i=0; i < aLength; i++)
michael@0 1339 aOutString->Append(passwordChar);
michael@0 1340 }
michael@0 1341
michael@0 1342
michael@0 1343 ///////////////////////////////////////////////////////////////////////////
michael@0 1344 // CreateMozBR: put a BR node with moz attribute at {aNode, aOffset}
michael@0 1345 //
michael@0 1346 nsresult
michael@0 1347 nsTextEditRules::CreateMozBR(nsIDOMNode* inParent, int32_t inOffset,
michael@0 1348 nsIDOMNode** outBRNode)
michael@0 1349 {
michael@0 1350 NS_ENSURE_TRUE(inParent, NS_ERROR_NULL_POINTER);
michael@0 1351
michael@0 1352 nsCOMPtr<nsIDOMNode> brNode;
michael@0 1353 NS_ENSURE_STATE(mEditor);
michael@0 1354 nsresult res = mEditor->CreateBR(inParent, inOffset, address_of(brNode));
michael@0 1355 NS_ENSURE_SUCCESS(res, res);
michael@0 1356
michael@0 1357 // give it special moz attr
michael@0 1358 nsCOMPtr<nsIDOMElement> brElem = do_QueryInterface(brNode);
michael@0 1359 if (brElem) {
michael@0 1360 res = mEditor->SetAttribute(brElem, NS_LITERAL_STRING("type"), NS_LITERAL_STRING("_moz"));
michael@0 1361 NS_ENSURE_SUCCESS(res, res);
michael@0 1362 }
michael@0 1363
michael@0 1364 if (outBRNode) {
michael@0 1365 brNode.forget(outBRNode);
michael@0 1366 }
michael@0 1367 return NS_OK;
michael@0 1368 }
michael@0 1369
michael@0 1370 NS_IMETHODIMP
michael@0 1371 nsTextEditRules::DocumentModified()
michael@0 1372 {
michael@0 1373 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 1374 }

mercurial