/* -*- 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/. */
TextEditor::TextEditor() : EditorBase(EditorBase::EditorType::Text) { // printf("Size of TextEditor: %zu\n", sizeof(TextEditor));
static_assert( sizeof(TextEditor) <= 512, "TextEditor instance should be allocatable in the quantum class bins");
}
TextEditor::~TextEditor() { // Remove event listeners. Note that if we had an HTML editor, // it installed its own instead of these
RemoveEventListeners();
}
NS_IMPL_CYCLE_COLLECTION_CLASS(TextEditor)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(TextEditor, EditorBase) if (tmp->mPasswordMaskData) {
tmp->mPasswordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::No);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPasswordMaskData->mTimer)
}
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(TextEditor, EditorBase) if (tmp->mPasswordMaskData) {
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPasswordMaskData->mTimer)
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMETHODIMP TextEditor::EndOfDocument() {
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_NOT_INITIALIZED;
}
nsresult rv = CollapseSelectionToEndOfTextNode();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "TextEditor::CollapseSelectionToEndOfTextNode() failed"); // This is low level API for embedders and chrome script so that we can return // raw error code here. return rv;
}
nsresult TextEditor::Init(Document& aDocument, Element& aAnonymousDivElement,
nsISelectionController& aSelectionController,
uint32_t aFlags,
UniquePtr<PasswordMaskData>&& aPasswordMaskData) {
MOZ_ASSERT(!mInitSucceeded, "TextEditor::Init() called again without calling PreDestroy()?");
MOZ_ASSERT(!(aFlags & nsIEditor::eEditorPasswordMask) == !aPasswordMaskData);
mPasswordMaskData = std::move(aPasswordMaskData);
// Init the base editor
nsresult rv = InitInternal(aDocument, &aAnonymousDivElement,
aSelectionController, aFlags); if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::InitInternal() failed"); return rv;
}
AutoEditActionDataSetter editActionData(*this, EditAction::eInitializing); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_FAILURE;
}
// We set mInitSucceeded here rather than at the end of the function, // since InitEditorContentAndSelection() can perform some transactions // and can warn if mInitSucceeded is still false.
MOZ_ASSERT(!mInitSucceeded, "TextEditor::Init() shouldn't be nested");
mInitSucceeded = true;
rv = InitEditorContentAndSelection(); if (NS_FAILED(rv)) {
NS_WARNING("TextEditor::InitEditorContentAndSelection() failed"); // XXX Shouldn't we expose `NS_ERROR_EDITOR_DESTROYED` even though this // is a public method?
mInitSucceeded = false; return EditorBase::ToGenericNSResult(rv);
}
// Throw away the old transaction manager if this is not the first time that // we're initializing the editor.
ClearUndoRedo();
EnableUndoRedo(); return NS_OK;
}
// If the selection hasn't been set up yet, set it up collapsed to the end of // our editable content. if (!SelectionRef().RangeCount()) {
nsresult rv = CollapseSelectionToEndOfTextNode(); if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::CollapseSelectionToEndOfTextNode() failed"); return rv;
}
}
if (!IsSingleLineEditor()) {
nsresult rv = EnsurePaddingBRElementInMultilineEditor(); if (NS_FAILED(rv)) {
NS_WARNING( "EditorBase::EnsurePaddingBRElementInMultilineEditor() failed"); return rv;
}
}
// Restore unmasked range if there is. if (IsPasswordEditor() && !IsAllMasked()) {
DebugOnly<nsresult> rvIgnored =
SetUnmaskRangeAndNotify(UnmaskedStart(), UnmaskedLength());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "TextEditor::SetUnmaskRangeAndNotify() failed to " "restore unmasked range, but ignored");
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::PostCreateInternal() failed"); return rv;
}
UniquePtr<PasswordMaskData> TextEditor::PreDestroy() { if (mDidPreDestroy) { return nullptr;
}
UniquePtr<PasswordMaskData> passwordMaskData = std::move(mPasswordMaskData); if (passwordMaskData) { // Disable auto-masking timer since nobody can catch the notification // from the timer and canceling the unmasking.
passwordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::Yes); // Similary, keeping preventing echoing password temporarily across // TextEditor instances is hard. So, we should forget it.
passwordMaskData->mEchoingPasswordPrevented = false;
}
PreDestroyInternal();
return passwordMaskData;
}
nsresult TextEditor::HandleKeyPressEvent(WidgetKeyboardEvent* aKeyboardEvent) { // NOTE: When you change this method, you should also change: // * editor/libeditor/tests/test_texteditor_keyevent_handling.html // * editor/libeditor/tests/test_htmleditor_keyevent_handling.html // // And also when you add new key handling, you need to change the subclass's // HandleKeyPressEvent()'s switch statement.
if (NS_WARN_IF(!aKeyboardEvent)) { return NS_ERROR_UNEXPECTED;
}
if (IsReadonly()) {
HandleKeyPressEventInReadOnlyMode(*aKeyboardEvent); return NS_OK;
}
switch (aKeyboardEvent->mKeyCode) { case NS_VK_META: case NS_VK_WIN: case NS_VK_SHIFT: case NS_VK_CONTROL: case NS_VK_ALT: // FYI: This shouldn't occur since modifier key shouldn't cause eKeyPress // event.
aKeyboardEvent->PreventDefault(); return NS_OK;
case NS_VK_BACK: case NS_VK_DELETE: case NS_VK_TAB: {
nsresult rv = EditorBase::HandleKeyPressEvent(aKeyboardEvent);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::HandleKeyPressEvent() failed"); return rv;
} case NS_VK_RETURN: { if (!aKeyboardEvent->IsInputtingLineBreak()) { return NS_OK;
} if (!IsSingleLineEditor()) {
aKeyboardEvent->PreventDefault();
} // We need to dispatch "beforeinput" event at least even if we're a // single line text editor.
nsresult rv = InsertLineBreakAsAction();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "TextEditor::InsertLineBreakAsAction() failed"); return rv;
}
}
if (!aKeyboardEvent->IsInputtingText()) { // we don't PreventDefault() here or keybindings like control-x won't work return NS_OK;
}
aKeyboardEvent->PreventDefault(); // If we dispatch 2 keypress events for a surrogate pair and we set only // first `.key` value to the surrogate pair, the preceding one has it and the // other has empty string. In this case, we should handle only the first one // with the key value. if (!StaticPrefs::dom_event_keypress_dispatch_once_per_surrogate_pair() &&
!StaticPrefs::dom_event_keypress_key_allow_lone_surrogate() &&
aKeyboardEvent->mKeyValue.IsEmpty() &&
IS_SURROGATE(aKeyboardEvent->mCharCode)) { return NS_OK;
} // Our widget shouldn't set `\r` to `mKeyValue`, but it may be synthesized // keyboard event and its value may be `\r`. In such case, we should treat // it as `\n` for the backward compatibility because we stopped converting // `\r` and `\r\n` to `\n` at getting `HTMLInputElement.value` and // `HTMLTextAreaElement.value` for the performance (i.e., we don't need to // take care in `HTMLEditor`).
nsAutoString str(aKeyboardEvent->mKeyValue); if (str.IsEmpty()) {
MOZ_ASSERT(aKeyboardEvent->mCharCode <= 0xFFFF, "Non-BMP character needs special handling");
str.Assign(aKeyboardEvent->mCharCode == nsCRT::CR
? static_cast<char16_t>(nsCRT::LF)
: static_cast<char16_t>(aKeyboardEvent->mCharCode));
} else {
MOZ_ASSERT(str.Find(u"\r\n"_ns) == kNotFound, "This assumes that typed text does not include CRLF");
str.ReplaceChar('\r', '\n');
}
nsresult rv = OnInputText(str);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::OnInputText() failed"); return rv;
}
// XXX This may be called by execCommand() with "insertParagraph". // In such case, naming the transaction "TypingTxnName" is odd.
AutoPlaceholderBatch treatAsOneTransaction(*this, *nsGkAtoms::TypingTxnName,
ScrollSelectionIntoView::Yes,
__FUNCTION__);
rv = InsertLineBreakAsSubAction();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "EditorBase::InsertLineBreakAsSubAction() failed"); return EditorBase::ToGenericNSResult(rv);
}
if (NS_WARN_IF(!mInitSucceeded)) { return NS_ERROR_NOT_INITIALIZED;
}
IgnoredErrorResult ignoredError;
AutoEditSubActionNotifier startToHandleEditSubAction(
*this, EditSubAction::eSetText, nsIEditor::eNext, ignoredError); if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { return ignoredError.StealNSResult();
}
NS_WARNING_ASSERTION(
!ignoredError.Failed(), "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
if (!IsIMEComposing() && !IsUndoRedoEnabled() &&
GetEditAction() != EditAction::eReplaceText && mMaxTextLength < 0) {
Result<EditActionResult, nsresult> result =
SetTextWithoutTransaction(aString); if (MOZ_UNLIKELY(result.isErr())) {
NS_WARNING("TextEditor::SetTextWithoutTransaction() failed"); return result.unwrapErr();
} if (!result.inspect().Ignored()) { return NS_OK;
}
}
{ // Note that do not notify selectionchange caused by selecting all text // because it's preparation of our delete implementation so web apps // shouldn't receive such selectionchange before the first mutation.
AutoUpdateViewBatch preventSelectionChangeEvent(*this, __FUNCTION__);
// XXX We should make ReplaceSelectionAsSubAction() take range. Then, // we can saving the expensive cost of modifying `Selection` here. if (NS_SUCCEEDED(SelectEntireDocument())) {
DebugOnly<nsresult> rvIgnored = ReplaceSelectionAsSubAction(aString);
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rvIgnored), "EditorBase::ReplaceSelectionAsSubAction() failed, but ignored");
}
}
// Destroying AutoUpdateViewBatch may cause destroying us. return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
}
bool TextEditor::IsEmpty() const { // Even if there is no padding <br> element for empty editor, we should be // detected as empty editor if all the children are text nodes and these // have no content.
Element* anonymousDivElement = GetRoot(); if (!anonymousDivElement) { returntrue; // Don't warn it, this is possible, e.g., 997805.html
}
// special-case for empty document, to account for the padding <br> element // for empty editor. // XXX This should be overridden by `HTMLEditor` and we should return the // first text node's length from `TextEditor` instead. The following // code is too expensive. if (IsEmpty()) { return NS_OK;
}
Element* rootElement = GetRoot(); if (NS_WARN_IF(!rootElement)) { return NS_ERROR_FAILURE;
}
bool TextEditor::IsCopyToClipboardAllowedInternal() const {
MOZ_ASSERT(IsEditActionDataAvailable()); if (!EditorBase::IsCopyToClipboardAllowedInternal()) { returnfalse;
}
if (!IsSingleLineEditor() || !IsPasswordEditor() ||
NS_WARN_IF(!mPasswordMaskData)) { returntrue;
}
// If we're a password editor, we should allow selected text to be copied // to the clipboard only when selection range is in unmasked range. if (IsAllMasked() || IsMaskingPassword() || !UnmaskedLength()) { returnfalse;
}
// If there are 2 or more ranges, we don't allow to copy/cut for now since // we need to check whether all ranges are in unmasked range or not. // Anyway, such operation in password field does not make sense. if (SelectionRef().RangeCount() > 1) { returnfalse;
}
// Get the nsITransferable interface for getting the data from the clipboard
Result<nsCOMPtr<nsITransferable>, nsresult> maybeTransferable =
EditorUtils::CreateTransferableForPlainText(*GetDocument()); if (maybeTransferable.isErr()) {
NS_WARNING("EditorUtils::CreateTransferableForPlainText() failed"); return maybeTransferable.unwrapErr();
}
nsCOMPtr<nsITransferable> trans(maybeTransferable.unwrap()); if (!trans) {
NS_WARNING( "EditorUtils::CreateTransferableForPlainText() returned nullptr, but " "ignored"); return NS_OK;
}
// Get the Data from the clipboard
nsresult rv =
GetDataFromDataTransferOrClipboard(aDataTransfer, trans, aClipboardType);
// Now we ask the transferable for the data // it still owns the data, we just have a pointer to it. // If it can't support a "text" output of the data the call will fail
nsCOMPtr<nsISupports> genericDataObj;
nsAutoCString flavor;
rv = trans->GetAnyTransferData(flavor, getter_AddRefs(genericDataObj)); if (NS_FAILED(rv)) {
NS_WARNING("nsITransferable::GetAnyTransferData() failed"); return rv;
}
if (!flavor.EqualsLiteral(kTextMime) &&
!flavor.EqualsLiteral(kMozTextInternal)) { return NS_OK;
}
nsCOMPtr<nsISupportsString> text = do_QueryInterface(genericDataObj); if (!text) { return NS_OK;
}
nsString stuffToPaste;
DebugOnly<nsresult> rvIgnored = text->GetData(stuffToPaste);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsISupportsString::GetData() failed, but ignored"); if (stuffToPaste.IsEmpty()) { return NS_OK;
}
aEditActionData.SetData(stuffToPaste); if (!stuffToPaste.IsEmpty()) {
nsContentUtils::PlatformToDOMLineBreaks(stuffToPaste);
}
rv = aEditActionData.MaybeDispatchBeforeInputEvent(); if (NS_FAILED(rv)) {
NS_WARNING_ASSERTION(rv == NS_ERROR_EDITOR_ACTION_CANCELED, "MaybeDispatchBeforeInputEvent() failed"); return rv;
}
// Let the citer quote it for us:
nsString quotedStuff;
InternetCiter::GetCiteString(aQuotedText, quotedStuff);
// It's best to put a blank line after the quoted text so that mails // written without thinking won't be so ugly. if (!aQuotedText.IsEmpty() && (aQuotedText.Last() != char16_t('\n'))) {
quotedStuff.Append(char16_t('\n'));
}
IgnoredErrorResult ignoredError;
AutoEditSubActionNotifier startToHandleEditSubAction(
*this, EditSubAction::eInsertText, nsIEditor::eNext, ignoredError); if (NS_WARN_IF(ignoredError.ErrorCodeIs(NS_ERROR_EDITOR_DESTROYED))) { return ignoredError.StealNSResult();
}
NS_WARNING_ASSERTION(
!ignoredError.Failed(), "TextEditor::OnStartToHandleTopLevelEditSubAction() failed, but ignored");
// XXX Do we need to support paste-as-quotation in password editor (and // also in single line editor)?
MaybeDoAutoPasswordMasking();
void TextEditor::ReinitializeSelection(Element& aElement) { if (NS_WARN_IF(Destroyed())) { return;
}
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!editActionData.CanHandle())) { return;
}
// We don't need to flush pending notifications here and we don't need to // handle spellcheck at first focus. Therefore, we don't need to call // `TextEditor::OnFocus` here.
EditorBase::OnFocus(aElement);
// If previous focused editor turn on spellcheck and this editor doesn't // turn on it, spellcheck state is mismatched. So we need to re-sync it.
SyncRealTimeSpell();
}
nsresult TextEditor::OnFocus(const nsINode& aOriginalEventTargetNode) {
RefPtr<PresShell> presShell = GetPresShell(); if (NS_WARN_IF(!presShell)) { return NS_ERROR_FAILURE;
} // Let's update the layout information right now because there are some // pending notifications and flushing them may cause destroying the editor.
presShell->FlushPendingNotifications(FlushType::Layout); if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) { return NS_OK;
}
AutoEditActionDataSetter editActionData(*this, EditAction::eNotEditing); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_FAILURE;
}
// Spell check a textarea the first time that it is focused.
nsresult rv = FlushPendingSpellCheck(); if (MOZ_UNLIKELY(rv == NS_ERROR_EDITOR_DESTROYED)) {
NS_WARNING("EditorBase::FlushPendingSpellCheck() failed"); return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv), "EditorBase::FlushPendingSpellCheck() failed, but ignored"); if (MOZ_UNLIKELY(!CanKeepHandlingFocusEvent(aOriginalEventTargetNode))) { return NS_OK;
}
nsresult TextEditor::OnBlur(const EventTarget* aEventTarget) { // check if something else is focused. If another element is focused, then // we should not change the selection. If another element already has focus, // we should not maintain the selection because we may not have the rights // doing it. if (nsFocusManager::GetFocusedElementStatic()) { return NS_OK;
}
Element* const anonymousDivElement = GetRoot(); if (aPoint.GetContainer() == anonymousDivElement) { // In some cases, aPoint points start of the anonymous <div>. To avoid // injecting unneeded text nodes, we first look to see if we have one // available. In that case, we'll just adjust node and offset accordingly. if (aPoint.IsStartOfContainer()) { if (aPoint.GetContainer()->HasChildren() &&
aPoint.GetContainer()->GetFirstChild()->IsText()) { return EditorDOMPointType(aPoint.GetContainer()->GetFirstChild(), 0u);
}
} // In some other cases, aPoint points the terminating padding <br> element // for empty last line in the anonymous <div>. In that case, we'll adjust // aInOutNode and aInOutOffset to the preceding text node, if any. else {
nsIContent* child = aPoint.GetContainer()->GetLastChild(); while (child) { if (child->IsText()) { return EditorDOMPointType::AtEndOf(*child);
}
child = child->GetPreviousSibling();
}
}
}
// Sometimes, aPoint points the padding <br> element. In that case, we'll // adjust the insertion point to the previous text node, if one exists, or to // the parent anonymous DIV. if (EditorUtils::IsPaddingBRElementForEmptyLastLine(
*aPoint.template ContainerAs<nsIContent>()) &&
aPoint.IsStartOfContainer()) {
nsIContent* previousSibling = aPoint.GetContainer()->GetPreviousSibling(); if (previousSibling && previousSibling->IsText()) { return EditorDOMPointType::AtEndOf(*previousSibling);
}
uint32_t unmaskStart = UINT32_MAX, unmaskLength = 0; const TextEditor* const textEditor =
nsContentUtils::GetExtantTextEditorFromAnonymousNode(&aTextNode); if (textEditor && textEditor->UnmaskedLength() > 0) {
unmaskStart = textEditor->UnmaskedStart();
unmaskLength = textEditor->UnmaskedLength(); // If text is copied from after unmasked range, we can treat this case // as mask all. if (aStartOffsetInText >= unmaskStart + unmaskLength) {
unmaskLength = 0;
unmaskStart = UINT32_MAX;
} else { // If text is copied from middle of unmasked range, reduce the length // and adjust start offset. if (aStartOffsetInText > unmaskStart) {
unmaskLength = unmaskStart + unmaskLength - aStartOffsetInText;
unmaskStart = 0;
} // If text is copied from before start of unmasked range, just adjust // the start offset. else {
unmaskStart -= aStartOffsetInText;
} // Make the range is in the string.
unmaskStart += aStartOffsetInString;
}
}
const char16_t kPasswordMask = TextEditor::PasswordMask(); for (uint32_t i = aStartOffsetInString; i < aString.Length(); ++i) { bool isSurrogatePair = NS_IS_HIGH_SURROGATE(aString.CharAt(i)) &&
i < aString.Length() - 1 &&
NS_IS_LOW_SURROGATE(aString.CharAt(i + 1)); if (i < unmaskStart || i >= unmaskStart + unmaskLength) { if (isSurrogatePair) {
aString.SetCharAt(kPasswordMask, i);
aString.SetCharAt(kPasswordMask, i + 1);
} else {
aString.SetCharAt(kPasswordMask, i);
}
}
// Skip the following low surrogate. if (isSurrogatePair) {
++i;
}
}
}
// We cannot manage multiple unmasked ranges so that shrink the previous // range first. if (!IsAllMasked()) {
mPasswordMaskData->mUnmaskedLength = 0;
mPasswordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::No);
}
}
// If we're not a password editor, return error since this call does not // make sense. if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData)) {
mPasswordMaskData->CancelTimer(PasswordMaskData::ReleaseTimer::Yes); return NS_ERROR_NOT_AVAILABLE;
}
Element* rootElement = GetRoot(); if (NS_WARN_IF(!rootElement)) { return NS_ERROR_NOT_INITIALIZED;
}
Text* text = Text::FromNodeOrNull(rootElement->GetFirstChild()); if (!text || !text->Length()) { // There is no anonymous text node in the editor. return aStart > 0 && aStart != UINT32_MAX ? NS_ERROR_INVALID_ARG : NS_OK;
}
if (aStart < UINT32_MAX) {
uint32_t valueLength = text->Length(); if (aStart >= valueLength) { return NS_ERROR_INVALID_ARG; // There is no character can be masked.
} // If aStart is middle of a surrogate pair, expand it to include the // preceding high surrogate because the caller may want to show a // character before the character at `aStart + 1`. const nsTextFragment* textFragment = text->GetText(); if (textFragment->IsLowSurrogateFollowingHighSurrogateAt(aStart)) {
mPasswordMaskData->mUnmaskedStart = aStart - 1; // If caller collapses the range, keep it. Otherwise, expand the length. if (aLength > 0) {
++aLength;
}
} else {
mPasswordMaskData->mUnmaskedStart = aStart;
}
mPasswordMaskData->mUnmaskedLength =
std::min(valueLength - UnmaskedStart(), aLength); // If unmasked end is middle of a surrogate pair, expand it to include // the following low surrogate because the caller may want to show a // character after the character at `aStart + aLength`. if (UnmaskedEnd() < valueLength &&
textFragment->IsLowSurrogateFollowingHighSurrogateAt(UnmaskedEnd())) {
mPasswordMaskData->mUnmaskedLength++;
} // If it's first time to mask the unmasking characters with timer, create // the timer now. Then, we'll keep using it for saving the creation cost. if (!HasAutoMaskingTimer() && aLength && aTimeout && UnmaskedLength()) {
mPasswordMaskData->mTimer = NS_NewTimer();
}
} else { if (NS_WARN_IF(aLength != 0)) { return NS_ERROR_INVALID_ARG;
}
mPasswordMaskData->MaskAll();
}
// Notify nsTextFrame of this update if the caller wants this to do it. // Only in this case, script may run. if (aNotify) {
MOZ_ASSERT(IsEditActionDataAvailable());
RefPtr<Document> document = GetDocument(); if (NS_WARN_IF(!document)) { return NS_ERROR_NOT_INITIALIZED;
} // Notify nsTextFrame of masking range change. if (RefPtr<PresShell> presShell = document->GetObservingPresShell()) {
nsAutoScriptBlocker blockRunningScript;
uint32_t valueLength = text->Length();
CharacterDataChangeInfo changeInfo = {false, 0, valueLength, valueLength,
nullptr};
presShell->CharacterDataChanged(text, changeInfo);
}
// Scroll caret into the view since masking or unmasking character may // move caret to outside of the view.
nsresult rv = ScrollSelectionFocusIntoView(); if (NS_FAILED(rv)) {
NS_WARNING("EditorBase::ScrollSelectionFocusIntoView() failed"); return rv;
}
}
if (!IsAllMasked() && aTimeout != 0) { // Initialize the timer to mask the range automatically.
MOZ_ASSERT(HasAutoMaskingTimer());
DebugOnly<nsresult> rvIgnored = mPasswordMaskData->mTimer->InitWithCallback( this, aTimeout, nsITimer::TYPE_ONE_SHOT);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "nsITimer::InitWithCallback() failed, but ignored");
}
return NS_OK;
}
// static
char16_t TextEditor::PasswordMask() {
char16_t ret = LookAndFeel::GetPasswordCharacter(); if (!ret) {
ret = '*';
} return ret;
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP TextEditor::Notify(nsITimer* aTimer) { // Check whether our text editor's password flag was changed before this // "hide password character" timer actually fires. if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData)) { return NS_OK;
}
if (IsAllMasked()) { return NS_OK;
}
AutoEditActionDataSetter editActionData(*this, EditAction::eHidePassword); if (NS_WARN_IF(!editActionData.CanHandle())) { return NS_ERROR_NOT_INITIALIZED;
}
if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData) || IsAllMasked()) { return;
}
// Adjust unmasked range before deletion since DOM mutation may cause // layout referring the range in old text.
// If we need to mask automatically, mask all now. if (IsMaskingPassword()) {
DebugOnly<nsresult> rvIgnored = MaskAllCharacters();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "TextEditor::MaskAllCharacters() failed, but ignored"); return;
}
if (aRemoveStartOffset < UnmaskedStart()) { // If removing range is before the unmasked range, move it. if (aRemoveStartOffset + aRemoveLength <= UnmaskedStart()) {
DebugOnly<nsresult> rvIgnored =
SetUnmaskRange(UnmaskedStart() - aRemoveLength, UnmaskedLength());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "TextEditor::SetUnmaskRange() failed, but ignored"); return;
}
// If removing range starts before unmasked range, and ends in unmasked // range, move and shrink the range. if (aRemoveStartOffset + aRemoveLength < UnmaskedEnd()) {
uint32_t unmaskedLengthInRemovingRange =
aRemoveStartOffset + aRemoveLength - UnmaskedStart();
DebugOnly<nsresult> rvIgnored = SetUnmaskRange(
aRemoveStartOffset, UnmaskedLength() - unmaskedLengthInRemovingRange);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "TextEditor::SetUnmaskRange() failed, but ignored"); return;
}
// If removing range includes all unmasked range, collapse it to the // remove offset.
DebugOnly<nsresult> rvIgnored = SetUnmaskRange(aRemoveStartOffset, 0);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "TextEditor::SetUnmaskRange() failed, but ignored"); return;
}
if (aRemoveStartOffset < UnmaskedEnd()) { // If removing range is in unmasked range, shrink the range. if (aRemoveStartOffset + aRemoveLength <= UnmaskedEnd()) {
DebugOnly<nsresult> rvIgnored =
SetUnmaskRange(UnmaskedStart(), UnmaskedLength() - aRemoveLength);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "TextEditor::SetUnmaskRange() failed, but ignored"); return;
}
// If removing range starts from unmasked range, and ends after it, // shrink it.
DebugOnly<nsresult> rvIgnored =
SetUnmaskRange(UnmaskedStart(), aRemoveStartOffset - UnmaskedStart());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored), "TextEditor::SetUnmaskRange() failed, but ignored"); return;
}
// If removing range is after the unmasked range, keep it.
}
if (!IsPasswordEditor() || NS_WARN_IF(!mPasswordMaskData) || IsAllMasked()) { return NS_OK;
}
if (IsMaskingPassword()) { // If we need to mask password, mask all right now.
nsresult rv = MaskAllCharactersAndNotify();
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "TextEditor::MaskAllCharacters() failed"); return rv;
}
if (aInsertedOffset < UnmaskedStart()) { // If insertion point is before unmasked range, expand the unmasked range // to include the new text.
nsresult rv = SetUnmaskRangeAndNotify(
aInsertedOffset, UnmaskedEnd() + aInsertedLength - aInsertedOffset);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "TextEditor::SetUnmaskRangeAndNotify() failed"); return rv;
}
if (aInsertedOffset <= UnmaskedEnd()) { // If insertion point is in unmasked range, unmask new text.
nsresult rv = SetUnmaskRangeAndNotify(UnmaskedStart(),
UnmaskedLength() + aInsertedLength);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "TextEditor::SetUnmaskRangeAndNotify() failed"); return rv;
}
// If insertion point is after unmasked range, extend the unmask range to // include the new text.
nsresult rv = SetUnmaskRangeAndNotify(
UnmaskedStart(), aInsertedOffset + aInsertedLength - UnmaskedStart());
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "TextEditor::SetUnmaskRangeAndNotify() failed"); return rv;
}
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 und die Messung sind noch experimentell.