/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */
/*static*/ bool TextControlElement::GetWrapPropertyEnum(
nsIContent* aContent, TextControlElement::nsHTMLTextWrap& aWrapProp) { // soft is the default; "physical" defaults to soft as well because all other // browsers treat it that way and there is no real reason to maintain physical // and virtual as separate entities if no one else does. Only hard and off // do anything different.
aWrapProp = eHTMLTextWrap_Soft; // the default
TextControlElement::FocusTristate TextControlElement::FocusState() { // We can't be focused if we aren't in a (composed) document
Document* doc = GetComposedDoc(); if (!doc) { return FocusTristate::eUnfocusable;
}
// first see if we are disabled or not. If disabled then do nothing. if (IsDisabled()) { return FocusTristate::eUnfocusable;
}
class MOZ_STACK_CLASS AutoInputEventSuppresser final { public: explicit AutoInputEventSuppresser(TextEditor* aTextEditor)
: mTextEditor(aTextEditor), // To protect against a reentrant call to SetValue, we check whether // another SetValue is already happening for this editor. If it is, // we must wait until we unwind to re-enable oninput events.
mOuterTransaction(aTextEditor->IsSuppressingDispatchingInputEvent()) {
MOZ_ASSERT(mTextEditor);
mTextEditor->SuppressDispatchingInputEvent(true);
}
~AutoInputEventSuppresser() {
mTextEditor->SuppressDispatchingInputEvent(mOuterTransaction);
}
if (mFrame) { // EnsureEditorInitialized and SetSelectionRange leads to // Selection::AddRangeAndSelectFramesAndNotifyListeners which flushes // Layout - need to block script to avoid nested PrepareEditor calls (bug // 642800).
nsAutoScriptBlocker scriptBlocker;
mFrame->EnsureEditorInitialized();
TextControlState::SelectionProperties& properties =
mTextControlState->GetSelectionProperties(); if (properties.IsDirty()) {
mFrame->SetSelectionRange(properties.GetStart(), properties.GetEnd(),
properties.GetDirection());
}
}
if (mTextControlState) {
mTextControlState->FinishedRestoringSelection();
} return NS_OK;
}
// Let the text editor tell us we're no longer relevant - avoids use of // AutoWeakFrame void Revoke() {
mFrame = nullptr;
mTextControlState = nullptr;
}
class MOZ_RAII AutoRestoreEditorState final { public:
MOZ_CAN_RUN_SCRIPT explicit AutoRestoreEditorState(TextEditor* aTextEditor)
: mTextEditor(aTextEditor),
mSavedFlags(mTextEditor->Flags()),
mSavedMaxLength(mTextEditor->MaxTextLength()),
mSavedEchoingPasswordPrevented(
mTextEditor->EchoingPasswordPrevented()) {
MOZ_ASSERT(mTextEditor);
// EditorBase::SetFlags() is a virtual method. Even though it does nothing // if new flags and current flags are same, the calling cost causes // appearing the method in profile. So, this class should check if it's // necessary to call.
uint32_t flags = mSavedFlags;
flags &= ~nsIEditor::eEditorReadonlyMask; if (mSavedFlags != flags) { // It's aTextEditor and whose lifetime must be guaranteed by the caller.
MOZ_KnownLive(mTextEditor)->SetFlags(flags);
}
mTextEditor->PreventToEchoPassword();
mTextEditor->SetMaxTextLength(-1);
}
MOZ_CAN_RUN_SCRIPT ~AutoRestoreEditorState() { if (!mSavedEchoingPasswordPrevented) {
mTextEditor->AllowToEchoPassword();
}
mTextEditor->SetMaxTextLength(mSavedMaxLength); // mTextEditor's lifetime must be guaranteed by owner of the instance // since the constructor is marked as `MOZ_CAN_RUN_SCRIPT` and this is // a stack only class.
SetEditorFlagsIfNecessary(MOZ_KnownLive(*mTextEditor), mSavedFlags);
}
~AutoDisableUndo() { // Don't change enable/disable of undo/redo if it's enabled after // it's disabled by the constructor because we shouldn't change // the maximum undo/redo count to the old value. if (mTextEditor->IsUndoRedoEnabled()) { return;
} // If undo/redo was enabled, mNumberOfMaximumTransactions is -1 or lager // than 0. Only when it's 0, it was disabled. if (mNumberOfMaximumTransactions) {
DebugOnly<bool> enabledUndoRedo =
mTextEditor->EnableUndoRedo(mNumberOfMaximumTransactions);
NS_WARNING_ASSERTION(enabledUndoRedo, "Failed to enable undo/redo transactions");
} else {
DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
NS_WARNING_ASSERTION(disabledUndoRedo, "Failed to disable undo/redo transactions");
}
}
NS_IMETHODIMP
TextInputSelectionController::SetCaretEnabled(bool enabled) { if (!mPresShellWeak) { return NS_ERROR_NOT_INITIALIZED;
}
RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak); if (!presShell) { return NS_ERROR_FAILURE;
}
// tell the pres shell to enable the caret, rather than settings its // visibility directly. this way the presShell's idea of caret visibility is // maintained.
presShell->SetCaretEnabled(enabled);
NS_IMETHODIMP
TextInputSelectionController::PageMove(bool aForward, bool aExtend) { // expected behavior for PageMove is to scroll AND move the caret // and to remain relative position of the caret in view. see Bug 4302. if (mScrollContainerFrame) {
RefPtr<nsFrameSelection> frameSelection = mFrameSelection; // We won't scroll parent scrollable element of mScrollContainerFrame. // Therefore, this may be handled when mScrollContainerFrame is completely // outside of the view. In such case, user may be confused since they might // have wanted to scroll a parent scrollable element. For making clearer // which element handles PageDown/PageUp, we should move selection into view // even if selection is not changed. return frameSelection->PageMove(aForward, aExtend, mScrollContainerFrame,
nsFrameSelection::SelectionIntoView::Yes);
} // Similarly, if there is no scrollable frame, we should move the editor // frame into the view for making it clearer which element handles // PageDown/PageUp. return ScrollSelectionIntoView(SelectionType::eNormal,
nsISelectionController::SELECTION_FOCUS_REGION,
SelectionScrollMode::SyncFlush);
}
NS_IMETHODIMP
TextInputSelectionController::CompleteScroll(bool aForward) { if (!mScrollContainerFrame) { return NS_ERROR_NOT_INITIALIZED;
}
// grab the parent / root DIV for this text widget
nsIContent* parentDIV = frameSelection->GetLimiter(); if (!parentDIV) { return NS_ERROR_UNEXPECTED;
}
// make the caret be either at the very beginning (0) or the very end
int32_t offset = 0;
CaretAssociationHint hint = CaretAssociationHint::Before; if (aForward) {
offset = parentDIV->GetChildCount();
// Prevent the caret from being placed after the last // BR node in the content tree!
if (offset > 0) {
nsIContent* child = parentDIV->GetLastChild();
if (child->IsHTMLElement(nsGkAtoms::br)) {
--offset;
hint = CaretAssociationHint::After; // for Bug 106855
}
}
}
// Fire the select event // The specs don't exactly say when we should fire the select event. // IE: Whenever you add/remove a character to/from the selection. Also // each time for select all. Also if you get to the end of the text // field you will get new event for each keypress or a continuous // stream of events if you use the mouse. IE will fire select event // when the selection collapses to nothing if you are holding down // the shift or mouse button. // Mozilla: If we have non-empty selection we will fire a new event for each // keypress (or mouseup) if the selection changed. Mozilla will also // create the event each time select all is called, even if // everything was previously selected, because technically select all // will first collapse and then extend. Mozilla will never create an // event if the selection collapses to nothing. // FYI: If you want to skip dispatching eFormSelect event and if there are no // event listeners, you can refer // nsPIDOMWindow::HasFormSelectEventListeners(), but be careful about // some C++ event handlers, e.g., HTMLTextAreaElement::PostHandleEvent(). bool collapsed = aSelection.IsCollapsed(); if (!collapsed && (aReason & (nsISelectionListener::MOUSEUP_REASON |
nsISelectionListener::KEYPRESS_REASON |
nsISelectionListener::SELECTALL_REASON))) { if (nsCOMPtr<nsIContent> content = mFrame->GetContent()) { if (nsCOMPtr<Document> doc = content->GetComposedDoc()) { if (RefPtr<PresShell> presShell = doc->GetPresShell()) {
nsEventStatus status = nsEventStatus_eIgnore;
WidgetEvent event(true, eFormSelect);
RefPtr<KeyboardEvent> keyEvent = aEvent->AsKeyboardEvent(); if (!keyEvent) { return NS_ERROR_UNEXPECTED;
}
WidgetKeyboardEvent* widgetKeyEvent =
aEvent->WidgetEventPtr()->AsKeyboardEvent(); if (!widgetKeyEvent) { return NS_ERROR_UNEXPECTED;
}
{ auto* input = HTMLInputElement::FromNode(mTxtCtrlElement); if (input && input->StepsInputValue(*widgetKeyEvent)) { // As an special case, don't handle key events that would step the value // of our <input type=number>. return NS_OK;
}
}
RefPtr<nsAtom> eventTypeAtom =
ShortcutKeys::ConvertEventToDOMEventType(widgetKeyEvent); for (KeyEventHandler* handler = keyHandlers; handler;
handler = handler->GetNextHandler()) { if (!handler->EventTypeEquals(eventTypeAtom)) { continue;
}
if (!handler->KeyEventMatched(keyEvent, 0, IgnoreModifierState())) { continue;
}
// XXX Do we execute only one handler even if the handler neither stops // propagation nor prevents default of the event?
nsresult rv = handler->ExecuteHandler(&aTextControlElement, aEvent); if (NS_SUCCEEDED(rv)) { returntrue;
}
} returnfalse;
};
auto ExecuteNativeKeyBindings =
[&](TextControlElement& aTextControlElement)
MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> bool { if (widgetKeyEvent->mMessage != eKeyPress) { returnfalse;
}
nsIWidget* widget = widgetKeyEvent->mWidget; // If the event is created by chrome script, the widget is nullptr. if (MOZ_UNLIKELY(!widget)) {
widget = mFrame->GetNearestWidget(); if (MOZ_UNLIKELY(NS_WARN_IF(!widget))) { returnfalse;
}
}
// WidgetKeyboardEvent::ExecuteEditCommands() requires non-nullptr mWidget. // If the event is created by chrome script, it is nullptr but we need to // execute native key bindings. Therefore, we need to set widget to // WidgetEvent::mWidget temporarily.
AutoRestore<nsCOMPtr<nsIWidget>> saveWidget(widgetKeyEvent->mWidget);
widgetKeyEvent->mWidget = widget; if (widgetKeyEvent->ExecuteEditCommands(nativeKeyBindingsType,
DoCommandCallback, mFrame)) {
aEvent->PreventDefault(); returntrue;
} returnfalse;
};
OwningNonNull<TextControlElement> textControlElement(*mTxtCtrlElement); if (StaticPrefs::
ui_key_textcontrol_prefer_native_key_bindings_over_builtin_shortcut_key_definitions()) { if (!ExecuteNativeKeyBindings(textControlElement)) {
ExecuteOurShortcutKeys(textControlElement);
}
} else { if (!ExecuteOurShortcutKeys(textControlElement)) {
ExecuteNativeKeyBindings(textControlElement);
}
} return NS_OK;
}
nsresult TextInputListener::OnEditActionHandled(TextEditor& aTextEditor) { if (mFrame) { // XXX Do we still need this or can we just remove the mFrame and // frame.IsAlive() conditions below?
AutoWeakFrame weakFrame = mFrame;
// Update the undo / redo menus //
size_t numUndoItems = aTextEditor.NumberOfUndoItems();
size_t numRedoItems = aTextEditor.NumberOfRedoItems(); if ((numUndoItems && !mHadUndoItems) || (!numUndoItems && mHadUndoItems) ||
(numRedoItems && !mHadRedoItems) || (!numRedoItems && mHadRedoItems)) { // Modify the menu if undo or redo items are different
UpdateTextInputCommands(u"undo"_ns);
void TextInputListener::HandleValueChanged(TextEditor& aTextEditor) { // Make sure we know we were changed (do NOT set this to false if there are // no undo items; JS could change the value and we'd still need to save it) if (mSetValueChanged) {
mTxtCtrlElement->SetValueChanged(true);
}
if (!mSettingValue) { // NOTE(emilio): execCommand might get here even though it might not be a // "proper" user-interactive change. Might be worth reconsidering which // ValueChangeKind are we passing down.
mTxtCtrlElement->OnValueChanged(ValueChangeKind::UserInteraction,
aTextEditor.IsEmpty(), nullptr); if (mTextControlState) {
mTextControlState->ClearLastInteractiveValue();
}
}
}
/***************************************************************************** * mozilla::AutoTextControlHandlingState * * This class is temporarily created in the stack and can manage nested * handling state of TextControlState. While this instance exists, lifetime of * TextControlState which created the instance is guaranteed. In other words, * you can use this class as "kungFuDeathGrip" for TextControlState.
*****************************************************************************/
/** * Generic constructor. If TextControlAction does not require additional * data, must use this constructor.
*/
MOZ_CAN_RUN_SCRIPT AutoTextControlHandlingState(
TextControlState& aTextControlState, TextControlAction aTextControlAction)
: mParent(aTextControlState.mHandlingState),
mTextControlState(aTextControlState),
mTextCtrlElement(aTextControlState.mTextCtrlElement),
mTextInputListener(aTextControlState.mTextListener),
mTextControlAction(aTextControlAction) {
MOZ_ASSERT(aTextControlAction != TextControlAction::SetValue, "Use specific constructor");
MOZ_DIAGNOSTIC_ASSERT_IF(
!aTextControlState.mTextListener,
!aTextControlState.mBoundFrame || !aTextControlState.mTextEditor);
mTextControlState.mHandlingState = this; if (Is(TextControlAction::CommitComposition)) {
MOZ_ASSERT(mParent);
MOZ_ASSERT(mParent->Is(TextControlAction::SetValue)); // If we're trying to commit composition before handling SetValue, // the parent old values will be outdated so that we need to clear // them.
mParent->InvalidateOldValue();
}
}
/** * TextControlAction::SetValue specific constructor. Current setting value * must be specified and the creator should check whether we succeeded to * allocate memory for line breaker conversion.
*/
MOZ_CAN_RUN_SCRIPT AutoTextControlHandlingState(
TextControlState& aTextControlState, TextControlAction aTextControlAction, const nsAString& aSettingValue, const nsAString* aOldValue, const ValueSetterOptions& aOptions, ErrorResult& aRv)
: mParent(aTextControlState.mHandlingState),
mTextControlState(aTextControlState),
mTextCtrlElement(aTextControlState.mTextCtrlElement),
mTextInputListener(aTextControlState.mTextListener),
mSettingValue(aSettingValue),
mOldValue(aOldValue),
mValueSetterOptions(aOptions),
mTextControlAction(aTextControlAction) {
MOZ_ASSERT(aTextControlAction == TextControlAction::SetValue, "Use generic constructor");
MOZ_DIAGNOSTIC_ASSERT_IF(
!aTextControlState.mTextListener,
!aTextControlState.mBoundFrame || !aTextControlState.mTextEditor);
mTextControlState.mHandlingState = this; if (!nsContentUtils::PlatformToDOMLineBreaks(mSettingValue, fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return;
} // Update all setting value's new value because older value shouldn't // overwrite newer value. if (mParent) { // If SetValue is nested, parents cannot trust their old value anymore. // So, we need to clear them.
mParent->UpdateSettingValueAndInvalidateOldValue(mSettingValue);
}
}
void OnDestroyTextControlState() { if (IsHandling(TextControlAction::Destructor)) { // Do nothing since mTextContrlState.DeleteOrCacheForReuse() has // already been called. return;
}
mTextControlStateDestroyed = true; if (mParent) {
mParent->OnDestroyTextControlState();
}
}
void PrepareEditorLater() {
MOZ_ASSERT(IsHandling(TextControlAction::SetValue));
MOZ_ASSERT(!IsHandling(TextControlAction::PrepareEditor)); // Look for the top most SetValue.
AutoTextControlHandlingState* settingValue = nullptr; for (AutoTextControlHandlingState* handlingSomething = this;
handlingSomething; handlingSomething = handlingSomething->mParent) { if (handlingSomething->Is(TextControlAction::SetValue)) {
settingValue = handlingSomething;
}
}
settingValue->mPrepareEditorLater = true;
}
/** * WillSetValueWithTextEditor() is called when TextControlState sets * value with its mTextEditor.
*/ void WillSetValueWithTextEditor() {
MOZ_ASSERT(Is(TextControlAction::SetValue));
MOZ_ASSERT(mTextControlState.mBoundFrame);
mTextControlFrame = mTextControlState.mBoundFrame; // If we'reemulating user input, we don't need to manage mTextInputListener // by ourselves since everything should be handled by TextEditor as normal // user input. if (mValueSetterOptions.contains(ValueSetterOption::BySetUserInputAPI)) { return;
} // Otherwise, if we're setting the value programatically, we need to manage // mTextInputListener by ourselves since TextEditor users special path // for the performance.
mTextInputListener->SettingValue(true);
mTextInputListener->SetValueChanged(
mValueSetterOptions.contains(ValueSetterOption::SetValueChanged));
mEditActionHandled = false; // Even if falling back to `TextControlState::SetValueWithoutTextEditor()` // due to editor destruction, it shouldn't dispatch "beforeinput" event // anymore. Therefore, we should mark that we've already dispatched // "beforeinput" event.
WillDispatchBeforeInputEvent();
}
/** * WillDispatchBeforeInputEvent() is called immediately before dispatching * "beforeinput" event in `TextControlState`.
*/ void WillDispatchBeforeInputEvent() {
mBeforeInputEventHasBeenDispatched = true;
}
/** * OnEditActionHandled() is called when the TextEditor handles something * and immediately before dispatching "input" event.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT nsresult OnEditActionHandled() {
MOZ_ASSERT(!mEditActionHandled);
mEditActionHandled = true; if (!Is(TextControlAction::SetValue)) { return NS_OK;
} if (!mValueSetterOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
mTextInputListener->SetValueChanged(true);
mTextInputListener->SettingValue(
mParent && mParent->IsHandling(TextControlAction::SetValue));
} if (!IsOriginalTextControlFrameAlive()) { return SetValueWithoutTextEditorAgain() ? NS_OK : NS_ERROR_OUT_OF_MEMORY;
} // The new value never includes line breaks caused by hard-wrap. // So, mCachedValue can always cache the new value.
nsTextControlFrame* textControlFrame =
do_QueryFrame(mTextControlFrame.GetFrame()); return textControlFrame->CacheValue(mSettingValue, fallible)
? NS_OK
: NS_ERROR_OUT_OF_MEMORY;
}
/** * SetValueWithoutTextEditorAgain() should be called if the frame for * mTextControlState was destroyed during setting value.
*/
[[nodiscard]] MOZ_CAN_RUN_SCRIPT bool SetValueWithoutTextEditorAgain() {
MOZ_ASSERT(!IsOriginalTextControlFrameAlive()); // If the frame was destroyed because of a flush somewhere inside // TextEditor, mBoundFrame here will be nullptr. But it's also // possible for the frame to go away because of another reason (such // as deleting the existing selection -- see bug 574558), in which // case we don't need to reset the value here. if (mTextControlState.mBoundFrame) { returntrue;
} // XXX It's odd to drop flags except // ValueSetterOption::SetValueChanged. // Probably, this intended to drop ValueSetterOption::BySetUserInputAPI // and ValueSetterOption::ByContentAPI, but other flags are added later.
ErrorResult error;
AutoTextControlHandlingState handlingSetValueWithoutEditor(
mTextControlState, TextControlAction::SetValue, mSettingValue,
mOldValue, mValueSetterOptions & ValueSetterOption::SetValueChanged,
error); if (error.Failed()) {
MOZ_ASSERT(error.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY));
error.SuppressException(); returnfalse;
} return mTextControlState.SetValueWithoutTextEditor(
handlingSetValueWithoutEditor);
}
AutoTextControlHandlingState* const mParent;
TextControlState& mTextControlState; // mTextControlFrame should be set immediately before calling methods // which may destroy the frame. Then, you can check whether the frame // was destroyed/replaced.
AutoWeakFrame mTextControlFrame; // mTextCtrlElement grabs TextControlState::mTextCtrlElement since // if the text control element releases mTextControlState, only this // can guarantee the instance of the text control element.
RefPtr<TextControlElement> const mTextCtrlElement; // mTextInputListener grabs TextControlState::mTextListener because if // TextControlState is unbind from the frame, it's released.
RefPtr<TextInputListener> const mTextInputListener;
nsAutoString mSettingValue; const nsAString* mOldValue = nullptr;
ValueSetterOptions mValueSetterOptions;
TextControlAction const mTextControlAction; bool mTextControlStateDestroyed = false; bool mEditActionHandled = false; bool mPrepareEditorLater = false; bool mBeforeInputEventHasBeenDispatched = false;
};
/** * For avoiding allocation cost of the instance, we should reuse instances * as far as possible. * * FYI: `25` is just a magic number considered without enough investigation, * but at least, this value must not make damage for footprint. * Feel free to change it if you find better number.
*/ static constexpr size_t kMaxCountOfCacheToReuse = 25; static AutoTArray<void*, kMaxCountOfCacheToReuse>* sReleasedInstances = nullptr; staticbool sHasShutDown = false;
TextControlState::TextControlState(TextControlElement* aOwningElement)
: mTextCtrlElement(aOwningElement),
mEverInited(false),
mEditorInitialized(false),
mValueTransferInProgress(false),
mSelectionCached(true) // When adding more member variable initializations here, add the same // also to ::Construct.
{
MOZ_COUNT_CTOR(TextControlState);
static_assert(sizeof(*this) <= 128, "Please keep small TextControlState as far as possible");
}
TextControlState* TextControlState::Construct(
TextControlElement* aOwningElement) { void* mem; if (sReleasedInstances && !sReleasedInstances->IsEmpty()) {
mem = sReleasedInstances->PopLastElement();
} else {
mem = moz_xmalloc(sizeof(TextControlState));
}
void TextControlState::Shutdown() {
sHasShutDown = true; if (sReleasedInstances) { for (void* mem : *sReleasedInstances) {
free(mem);
} delete sReleasedInstances;
}
}
void TextControlState::Destroy() { // If we're handling something, we should be deleted later. if (mHandlingState) {
mHandlingState->OnDestroyTextControlState(); return;
}
DeleteOrCacheForReuse(); // Note that this instance may have already been deleted here. Don't touch // any members.
}
// If we can cache this instance, we should do it instead of deleting it. if (!sHasShutDown && (!sReleasedInstances || sReleasedInstances->Length() <
kMaxCountOfCacheToReuse)) { // Put this instance to the cache. Note that now, the array may be full, // but it's not problem to cache more instances than kMaxCountOfCacheToReuse // because it just requires reallocation cost of the array buffer. if (!sReleasedInstances) {
sReleasedInstances = new AutoTArray<void*, kMaxCountOfCacheToReuse>;
}
sReleasedInstances->AppendElement(mem);
} else {
free(mem);
}
}
if (mBoundFrame) { // Oops, we still have a frame! // This should happen when the type of a text input control is being changed // to something which is not a text control. In this case, we should // pretend that a frame is being destroyed, and clean up after ourselves // properly.
UnbindFromFrame(mBoundFrame);
mTextEditor = nullptr;
} else { // If we have a bound frame around, UnbindFromFrame will call DestroyEditor // for us.
DestroyEditor();
MOZ_DIAGNOSTIC_ASSERT(!mBoundFrame || !mTextEditor);
}
mTextListener = nullptr;
}
TextEditor* TextControlState::GetTextEditor() { // Note that if the instance is destroyed in PrepareEditor(), it returns // NS_ERROR_NOT_INITIALIZED so that we don't need to create kungFuDeathGrip // in this hot path. if (!mTextEditor && NS_WARN_IF(NS_FAILED(PrepareEditor()))) { return nullptr;
} return mTextEditor;
}
nsresult TextControlState::BindToFrame(nsTextControlFrame* aFrame) {
MOZ_ASSERT(
!nsContentUtils::IsSafeToRunScript(), "TextControlState::BindToFrame() has to be called with script blocker");
NS_ASSERTION(aFrame, "The frame to bind to should be valid"); if (!aFrame) { return NS_ERROR_INVALID_ARG;
}
NS_ASSERTION(!mBoundFrame, "Cannot bind twice, need to unbind first"); if (mBoundFrame) { return NS_ERROR_FAILURE;
}
// If we'll need to transfer our current value to the editor, save it before // binding to the frame.
nsAutoString currentValue; if (mTextEditor) {
GetValue(currentValue, true, /* aForDisplay = */ false);
}
// Create a SelectionController
mSelCon = new TextInputSelectionController(presShell, editorRootAnonymousDiv);
MOZ_ASSERT(!mTextListener, "Should not overwrite the object");
mTextListener = new TextInputListener(mTextCtrlElement);
mTextListener->SetFrame(mBoundFrame);
// Editor will override this as needed from InitializeSelection.
mSelCon->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
// Get the caret and make it a selection listener. // FYI: It's safe to use raw pointer for calling // Selection::AddSelectionListner() because it only appends the listener // to its internal array.
Selection* selection = mSelCon->GetSelection(SelectionType::eNormal); if (selection) {
RefPtr<nsCaret> caret = presShell->GetCaret(); if (caret) {
selection->AddSelectionListener(caret);
}
mTextListener->StartToListenToSelectionChange();
}
// If an editor exists from before, prepare it for usage if (mTextEditor) { if (NS_WARN_IF(!mTextCtrlElement)) { return NS_ERROR_FAILURE;
}
// Set the correct direction on the newly created root node if (mTextEditor->IsRightToLeft()) {
editorRootAnonymousDiv.SetAttr(kNameSpaceID_None, nsGkAtoms::dir,
u"rtl"_ns, false);
} elseif (mTextEditor->IsLeftToRight()) {
editorRootAnonymousDiv.SetAttr(kNameSpaceID_None, nsGkAtoms::dir,
u"ltr"_ns, false);
} else { // otherwise, inherit the content node's direction
}
nsContentUtils::AddScriptRunner( new PrepareEditorEvent(*this, mTextCtrlElement, currentValue));
}
return NS_OK;
}
struct MOZ_STACK_CLASS PreDestroyer { void Init(TextEditor* aTextEditor) { mTextEditor = aTextEditor; }
~PreDestroyer() { if (mTextEditor) { // In this case, we don't need to restore the unmasked range of password // editor.
UniquePtr<PasswordMaskData> passwordMaskData = mTextEditor->PreDestroy();
}
} void Swap(RefPtr<TextEditor>& aTextEditor) { return mTextEditor.swap(aTextEditor);
}
private:
RefPtr<TextEditor> mTextEditor;
};
nsresult TextControlState::PrepareEditor(const nsAString* aValue) { if (!mBoundFrame) { // Cannot create an editor without a bound frame. // Don't return a failure code, because js callers can't handle that. return NS_OK;
}
if (mEditorInitialized) { // Do not initialize the editor multiple times. return NS_OK;
}
// Note that we don't check mTextEditor here, because we might already have // one around, in which case we don't create a new one, and we'll just tie // the required machinery to it.
// Spell check is diabled at creation time. It is enabled once // the editor comes into focus.
uint32_t editorFlags = nsIEditor::eEditorSkipSpellCheck;
if (IsSingleLineTextControl()) {
editorFlags |= nsIEditor::eEditorSingleLineMask;
} if (IsPasswordTextControl()) {
editorFlags |= nsIEditor::eEditorPasswordMask;
}
bool shouldInitializeEditor = false;
RefPtr<TextEditor> newTextEditor; // the editor that we might create
PreDestroyer preDestroyer; if (!mTextEditor) {
shouldInitializeEditor = true;
// Create an editor
newTextEditor = new TextEditor();
preDestroyer.Init(newTextEditor);
// Make sure we clear out the non-breaking space before we initialize the // editor
nsresult rv = mBoundFrame->UpdateValueDisplay(true, true); if (NS_FAILED(rv)) {
NS_WARNING("nsTextControlFrame::UpdateValueDisplay() failed"); return rv;
}
} else { if (aValue || !mEditorInitialized) { // Set the correct value in the root node
nsresult rv =
mBoundFrame->UpdateValueDisplay(true, !mEditorInitialized, aValue); if (NS_FAILED(rv)) {
NS_WARNING("nsTextControlFrame::UpdateValueDisplay() failed"); return rv;
}
}
newTextEditor = mTextEditor; // just pretend that we have a new editor!
// Don't lose application flags in the process. if (newTextEditor->IsMailEditor()) {
editorFlags |= nsIEditor::eEditorMailMask;
}
}
// Get the current value of the textfield from the content. // Note that if we've created a new editor, mTextEditor is null at this stage, // so we will get the real value from the content.
nsAutoString defaultValue; if (aValue) {
defaultValue = *aValue;
} else {
GetValue(defaultValue, true, /* aForDisplay = */ true);
}
if (!mEditorInitialized) { // Now initialize the editor. // // NOTE: Conversion of '\n' to <BR> happens inside the // editor's Init() call.
// Get the DOM document
nsCOMPtr<Document> doc = presShell->GetDocument(); if (NS_WARN_IF(!doc)) { return NS_ERROR_FAILURE;
}
// What follows is a bit of a hack. The editor uses the public DOM APIs // for its content manipulations, and it causes it to fail some security // checks deep inside when initializing. So we explicitly make it clear that // we're native code. // Note that any script that's directly trying to access our value // has to be going through some scriptable object to do that and that // already does the relevant security checks.
AutoNoJSAPI nojsapi;
if (controllers) { // XXX Oddly, nsresult value is overwritten in the following loop, and // only the last result or `found` decides the value.
uint32_t numControllers; bool found = false;
rv = controllers->GetControllerCount(&numControllers); for (uint32_t i = 0; i < numControllers; i++) {
nsCOMPtr<nsIController> controller;
rv = controllers->GetControllerAt(i, getter_AddRefs(controller)); if (NS_SUCCEEDED(rv) && controller) {
nsCOMPtr<nsIControllerContext> editController =
do_QueryInterface(controller); if (editController) {
editController->SetCommandContext( static_cast<nsIEditor*>(newTextEditor));
found = true;
}
}
} if (!found) {
rv = NS_ERROR_FAILURE;
}
}
}
// Initialize the plaintext editor if (shouldInitializeEditor) { const int32_t wrapCols = GetWrapCols();
MOZ_ASSERT(wrapCols >= 0);
newTextEditor->SetWrapColumn(wrapCols);
}
// Set max text field length
newTextEditor->SetMaxTextLength(mTextCtrlElement->UsedMaxLength());
editorFlags = newTextEditor->Flags();
// Check if the readonly/disabled attributes are set. if (mTextCtrlElement->IsDisabledOrReadOnly()) {
editorFlags |= nsIEditor::eEditorReadonlyMask;
}
if (shouldInitializeEditor) { // Hold on to the newly created editor
preDestroyer.Swap(mTextEditor);
}
// If we have a default value, insert it under the div we created // above, but be sure to use the editor so that '*' characters get
--> --------------------
--> maximum size reached
--> --------------------
¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.31Angebot
Wie Sie bei der Firma Beratungs- und Dienstleistungen beauftragen können
¤
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.