/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
if (NS_WARN_IF(Destroyed())) {
aRv.Throw(NS_ERROR_EDITOR_DESTROYED); return;
}
if (NS_WARN_IF(!mInitSucceeded)) { return;
}
if (aTopLevelEditSubAction == EditSubAction::eSetText) { // SetText replaces all text, so spell checker handles starting from the // start of new value.
SetSpellCheckRestartPoint(EditorDOMPoint(mRootElement, 0)); return;
}
if (aTopLevelEditSubAction == EditSubAction::eInsertText ||
aTopLevelEditSubAction == EditSubAction::eInsertTextComingFromIME) { // For spell checker, previous selected node should be text node if // possible. If anchor is root of editor, it may become invalid offset // after inserting text. const EditorRawDOMPoint point =
FindBetterInsertionPoint(EditorRawDOMPoint(SelectionRef().AnchorRef())); if (point.IsSet()) {
SetSpellCheckRestartPoint(point); return;
}
NS_WARNING("TextEditor::FindBetterInsertionPoint() failed, but ignored");
} if (SelectionRef().AnchorRef().IsSet()) {
SetSpellCheckRestartPoint(EditorRawDOMPoint(SelectionRef().AnchorRef()));
}
}
nsresult rv; while (true) { if (NS_WARN_IF(Destroyed())) {
rv = NS_ERROR_EDITOR_DESTROYED; break;
}
// XXX Probably, we should spellcheck again after edit action (not top-level // sub-action) is handled because the ranges can be referred only by // users. if (NS_FAILED(rv = HandleInlineSpellCheckAfterEdit())) {
NS_WARNING("TextEditor::HandleInlineSpellCheckAfterEdit() failed"); break;
}
if (mMaxTextLength >= 0) {
nsAutoString insertionString(u"\n"_ns);
Result<EditActionResult, nsresult> result =
MaybeTruncateInsertionStringForMaxLength(insertionString); if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING( "TextEditor::MaybeTruncateInsertionStringForMaxLength() failed"); return result;
} if (result.inspect().Handled()) { // Don't return as handled since we stopped inserting the line break. return EditActionResult::CanceledResult();
}
}
// if the selection isn't collapsed, delete it. if (!SelectionRef().IsCollapsed()) {
nsresult rv =
DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip); if (NS_FAILED(rv)) {
NS_WARNING( "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed"); return Err(rv);
}
}
RefPtr<Document> document = GetDocument(); if (NS_WARN_IF(!document)) { return Err(NS_ERROR_NOT_INITIALIZED);
}
// Insert a linefeed character.
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextWithTransaction(*document, u"\n"_ns, pointToInsert,
InsertTextTo::ExistingTextNodeIfAvailable); if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("TextEditor::InsertTextWithTransaction(\"\\n\") failed"); return insertTextResult.propagateErr();
}
insertTextResult.inspect().IgnoreCaretPointSuggestion();
EditorDOMPoint pointToPutCaret =
insertTextResult.inspect().Handled()
? insertTextResult.inspect().EndOfInsertedTextRef()
: pointToInsert; if (NS_WARN_IF(!pointToPutCaret.IsSetAndValid())) { return Err(NS_ERROR_FAILURE);
} // XXX I don't think we still need this. This must have been required when // `<textarea>` was implemented with text nodes and `<br>` elements. // We want the caret to stick to the content on the "right". We want the // caret to stick to whatever is past the break. This is because the break is // on the same line we were on, but the next content will be on the following // line.
pointToPutCaret.SetInterlinePosition(InterlinePosition::StartOfNextLine);
nsresult rv = CollapseSelectionTo(pointToPutCaret); if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::CollapseSelectionTo() failed"); return Err(rv);
} return EditActionResult::HandledResult();
}
// If there is no selection ranges, we should set to the end of the editor. // This is usually performed in InitEditorContentAndSelection(), however, // if the editor is reframed, this may be called by // OnEndHandlingTopLevelEditSubAction(). if (SelectionRef().RangeCount()) { return NS_OK;
}
nsresult rv = CollapseSelectionToEndOfTextNode(); if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING( "TextEditor::CollapseSelectionToEndOfTextNode() caused destroying the " "editor"); return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "TextEditor::CollapseSelectionToEndOfTextNode() failed, but ignored");
// First of all, check if aString contains '\n' since if the string // does not include it, we don't need to do nothing here.
int32_t firstLF = aString.FindChar(kLF, 0); if (firstLF == kNotFound) { return;
}
switch (mNewlineHandling) { case nsIEditor::eNewlinesReplaceWithSpaces: // Default of Firefox: // Strip trailing newlines first so we don't wind up with trailing spaces
aString.Trim(LFSTR, false, true);
aString.ReplaceChar(kLF, ' '); break; case nsIEditor::eNewlinesStrip:
aString.StripChar(kLF); break; case nsIEditor::eNewlinesPasteToFirst: default: { // we get first *non-empty* line.
int32_t offset = 0; while (firstLF == offset) {
offset++;
firstLF = aString.FindChar(kLF, offset);
} if (firstLF > 0) {
aString.Truncate(firstLF);
} if (offset > 0) {
aString.Cut(0, offset);
} break;
} case nsIEditor::eNewlinesReplaceWithCommas: // Default of Thunderbird:
aString.Trim(LFSTR, true, true);
aString.ReplaceChar(kLF, ','); break; case nsIEditor::eNewlinesStripSurroundingWhitespace: {
nsAutoString result;
uint32_t offset = 0; while (offset < aString.Length()) {
int32_t nextLF = !offset ? firstLF : aString.FindChar(kLF, offset); if (nextLF < 0) {
result.Append(nsDependentSubstring(aString, offset)); break;
}
uint32_t wsBegin = nextLF; // look backwards for the first non-white-space char while (wsBegin > offset && NS_IS_SPACE(aString[wsBegin - 1])) {
--wsBegin;
}
result.Append(nsDependentSubstring(aString, offset, wsBegin - offset));
offset = nextLF + 1; while (offset < aString.Length() && NS_IS_SPACE(aString[offset])) {
++offset;
}
}
aString = result; break;
} case nsIEditor::eNewlinesPasteIntact: // even if we're pasting newlines, don't paste leading/trailing ones
aString.Trim(LFSTR, true, true); break;
}
}
nsAutoString insertionString(aInsertionString); if (!aInsertionString.IsEmpty() && mMaxTextLength >= 0) {
Result<EditActionResult, nsresult> result =
MaybeTruncateInsertionStringForMaxLength(insertionString); if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING( "TextEditor::MaybeTruncateInsertionStringForMaxLength() failed");
EditActionResult unwrappedResult = result.unwrap();
unwrappedResult.MarkAsHandled(); return unwrappedResult;
} // If we're exceeding the maxlength when composing IME, we need to clean up // the composing text, so we shouldn't return early. if (result.inspect().Handled() && insertionString.IsEmpty() &&
aEditSubAction != EditSubAction::eInsertTextComingFromIME) { return EditActionResult::CanceledResult();
}
}
uint32_t start = 0; if (IsPasswordEditor()) { if (GetComposition() && !GetComposition()->String().IsEmpty()) {
start = GetComposition()->XPOffsetInTextNode();
} else {
uint32_t end = 0;
nsContentUtils::GetSelectionInTextControl(&SelectionRef(), GetRoot(),
start, end);
}
}
// if the selection isn't collapsed, delete it. if (!SelectionRef().IsCollapsed() &&
aSelectionHandling == SelectionHandling::Delete) {
nsresult rv =
DeleteSelectionAsSubAction(nsIEditor::eNone, nsIEditor::eNoStrip); if (NS_FAILED(rv)) {
NS_WARNING( "EditorBase::DeleteSelectionAsSubAction(eNone, eNoStrip) failed"); return Err(rv);
}
}
if (aInsertionString.IsEmpty() &&
aEditSubAction != EditSubAction::eInsertTextComingFromIME) { // HACK: this is a fix for bug 19395 // I can't outlaw all empty insertions // because IME transaction depend on them // There is more work to do to make the // world safe for IME. return EditActionResult::CanceledResult();
}
// XXX Why don't we cancel here? Shouldn't we do this first?
CANCEL_OPERATION_AND_RETURN_EDIT_ACTION_RESULT_IF_READONLY
MaybeDoAutoPasswordMasking();
// People have lots of different ideas about what text fields // should do with multiline pastes. See bugs 21032, 23485, 23485, 50935. // The six possible options are: // 0. paste newlines intact // 1. paste up to the first newline (default) // 2. replace newlines with spaces // 3. strip newlines // 4. replace with commas // 5. strip newlines and surrounding white-space // So find out what we're expected to do: if (IsSingleLineEditor()) { // XXX Some callers of TextEditor::InsertTextAsAction() already make the // string use only \n as a linebreaker. However, they are not hot // path and nsContentUtils::PlatformToDOMLineBreaks() does nothing // if the string doesn't include \r. So, let's convert linebreakers // here. Note that there are too many callers of // TextEditor::InsertTextAsAction(). So, it's difficult to keep // maintaining all of them won't reach here without \r nor \r\n. // XXX Should we handle do this before truncating the string for // `maxlength`?
nsContentUtils::PlatformToDOMLineBreaks(insertionString);
HandleNewLinesInStringForSingleLineEditor(insertionString);
}
Result<InsertTextResult, nsresult> insertTextResult =
InsertTextWithTransaction(*document, insertionString,
atStartOfSelection,
InsertTextTo::ExistingTextNodeIfAvailable); if (MOZ_UNLIKELY(insertTextResult.isErr())) {
NS_WARNING("EditorBase::InsertTextWithTransaction() failed"); return insertTextResult.propagateErr();
} // Ignore caret suggestion because there was // AutoTransactionsConserveSelection.
insertTextResult.inspect().IgnoreCaretPointSuggestion(); if (insertTextResult.inspect().Handled()) { // Make the caret attach to the inserted text, unless this text ends with // a LF, in which case make the caret attach to the next line. constbool endsWithLF =
!insertionString.IsEmpty() && insertionString.Last() == nsCRT::LF;
EditorDOMPoint pointToPutCaret =
insertTextResult.inspect().EndOfInsertedTextRef();
pointToPutCaret.SetInterlinePosition(
endsWithLF ? InterlinePosition::StartOfNextLine
: InterlinePosition::EndOfLine);
MOZ_ASSERT(pointToPutCaret.IsInTextNode(), "After inserting text into a text node, insertTextResult " "should return a point in a text node");
nsresult rv = CollapseSelectionTo(pointToPutCaret); if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) { return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "EditorBase::CollapseSelectionTo() failed, but ignored");
}
}
// Unmask inputted character(s) if necessary. if (IsPasswordEditor() && IsMaskingPassword() && CanEchoPasswordNow()) {
nsresult rv = SetUnmaskRangeAndNotify(start, insertionString.Length(),
LookAndFeel::GetPasswordMaskDelay()); if (NS_FAILED(rv)) {
NS_WARNING("TextEditor::SetUnmaskRangeAndNotify() failed"); return Err(rv);
} return EditActionResult::HandledResult();
}
// We can use this fast path only when: // - we need to insert a text node. // - we need to replace content of existing text node. // Additionally, for avoiding odd result, we should check whether we're in // usual condition. if (!IsSingleLineEditor()) { // If we're a multiline text editor, i.e., <textarea>, there is a padding // <br> element for empty last line followed by scrollbar/resizer elements. // Otherwise, a text node is followed by them. if (!textNode->GetNextSibling() ||
!EditorUtils::IsPaddingBRElementForEmptyLastLine(
*textNode->GetNextSibling())) { return EditActionResult::IgnoredResult();
}
}
// XXX Password fields accept line breaks as normal characters with this code. // Is this intentional?
nsAutoString sanitizedValue(aValue); if (IsSingleLineEditor() && !IsPasswordEditor()) {
HandleNewLinesInStringForSingleLineEditor(sanitizedValue);
}
if (IsEmpty()) { return EditActionResult::CanceledResult();
}
Result<EditActionResult, nsresult> result =
HandleDeleteSelectionInternal(aDirectionAndAmount, nsIEditor::eNoStrip); // HandleDeleteSelectionInternal() creates SelectionBatcher. Therefore, // quitting from it might cause having destroyed the editor. if (NS_WARN_IF(Destroyed())) { return Err(NS_ERROR_EDITOR_DESTROYED);
}
NS_WARNING_ASSERTION(
result.isOk(), "TextEditor::HandleDeleteSelectionInternal(eNoStrip) failed"); return result;
}
// If the current selection is empty (e.g the user presses backspace with // a collapsed selection), then we want to avoid sending the selectstart // event to the user, so we hide selection changes. However, we still // want to send a single selectionchange event to the document, so we // batch the selectionchange events, such that a single event fires after // the AutoHideSelectionChanges destructor has been run.
SelectionBatcher selectionBatcher(SelectionRef(), __FUNCTION__);
AutoHideSelectionChanges hideSelection(SelectionRef());
nsAutoScriptBlocker scriptBlocker;
if (IsPasswordEditor() && IsMaskingPassword()) {
MaskAllCharacters();
} else { constauto selectionStartPoint =
GetFirstSelectionStartPoint<EditorRawDOMPoint>(); if (NS_WARN_IF(!selectionStartPoint.IsSet())) { return Err(NS_ERROR_FAILURE);
}
if (!SelectionRef().IsCollapsed()) {
nsresult rv = DeleteSelectionWithTransaction(aDirectionAndAmount,
nsIEditor::eNoStrip); if (NS_FAILED(rv)) {
NS_WARNING( "EditorBase::DeleteSelectionWithTransaction(eNoStrip) failed"); return Err(rv);
} return EditActionResult::HandledResult();
}
// Test for distance between caret and text that will be deleted
AutoCaretBidiLevelManager bidiLevelManager(*this, aDirectionAndAmount,
selectionStartPoint); if (MOZ_UNLIKELY(bidiLevelManager.Failed())) {
NS_WARNING("EditorBase::AutoCaretBidiLevelManager() failed"); return Err(NS_ERROR_FAILURE);
}
bidiLevelManager.MaybeUpdateCaretBidiLevel(*this); if (bidiLevelManager.Canceled()) { return EditActionResult::CanceledResult();
}
}
AutoClonedSelectionRangeArray rangesToDelete(SelectionRef());
Result<nsIEditor::EDirection, nsresult> result =
rangesToDelete.ExtendAnchorFocusRangeFor(*this, aDirectionAndAmount); if (result.isErr()) {
NS_WARNING( "AutoClonedSelectionRangeArray::ExtendAnchorFocusRangeFor() failed"); return result.propagateErr();
} if (const Text* theTextNode = GetTextNode()) {
rangesToDelete.EnsureRangesInTextNode(*theTextNode);
}
if (!textNode->Length()) {
aValue.Truncate(); return EditActionResult::HandledResult();
}
nsIContent* firstChildExceptText = textNode->GetNextSibling(); // If the DOM tree is unexpected, fall back to the expensive path. bool isInput = IsSingleLineEditor(); bool isTextarea = !isInput; if (NS_WARN_IF(isInput && firstChildExceptText) ||
NS_WARN_IF(isTextarea && !firstChildExceptText) ||
NS_WARN_IF(isTextarea &&
!EditorUtils::IsPaddingBRElementForEmptyLastLine(
*firstChildExceptText) &&
!firstChildExceptText->IsXULElement(nsGkAtoms::scrollbar))) { return EditActionResult::IgnoredResult();
}
// Otherwise, the text data is the value.
textNode->GetData(aValue); return EditActionResult::HandledResult();
}
if (IsIMEComposing()) { return EditActionResult::IgnoredResult();
}
// Ignore user pastes switch (GetEditAction()) { case EditAction::ePaste: case EditAction::ePasteAsQuotation: case EditAction::eDrop: case EditAction::eReplaceText: // EditActionPrinciple() is non-null iff the edit was requested by // javascript. if (!GetEditActionPrincipal()) { // By now we are certain that this is a user paste, before we ignore it, // lets check if the user explictly enabled truncating user pastes. if (!StaticPrefs::editor_truncate_user_pastes()) { return EditActionResult::IgnoredResult();
}
}
[[fallthrough]]; default: break;
}
const uint32_t kSelectionLength = selectionEnd - selectionStart; // XXX This computation must be wrong. If we'll support non-collapsed // selection even during composition for Korean IME, kSelectionLength // is part of kOldCompositionStringLength. const uint32_t kNewLength =
currentLength - kSelectionLength - kOldCompositionStringLength; if (kNewLength >= AssertedCast<uint32_t>(mMaxTextLength)) {
aInsertionString.Truncate(); // Too long, we cannot accept new character. return EditActionResult::HandledResult();
}
if (aInsertionString.Length() + kNewLength <=
AssertedCast<uint32_t>(mMaxTextLength)) { return EditActionResult::IgnoredResult(); // Enough short string.
}
int32_t newInsertionStringLength = mMaxTextLength - kNewLength;
MOZ_ASSERT(newInsertionStringLength > 0);
char16_t maybeHighSurrogate =
aInsertionString.CharAt(newInsertionStringLength - 1);
char16_t maybeLowSurrogate =
aInsertionString.CharAt(newInsertionStringLength); // Don't split the surrogate pair. if (NS_IS_SURROGATE_PAIR(maybeHighSurrogate, maybeLowSurrogate)) {
newInsertionStringLength--;
} // XXX What should we do if we're removing IVS but its preceding // character won't be removed?
aInsertionString.Truncate(newInsertionStringLength); return EditActionResult::HandledResult();
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.