mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	Also, simplify some callers of `GetScrollTargetFrame()` to drop `nsIScrollableFrame*` and unnecessary `do_QueryFrame`. We'll continue removing more `nsIScrollableFrame*` in later parts. Differential Revision: https://phabricator.services.mozilla.com/D211490
		
			
				
	
	
		
			3085 lines
		
	
	
	
		
			108 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			3085 lines
		
	
	
	
		
			108 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* -*- 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/. */
 | 
						|
 | 
						|
#include "TextControlState.h"
 | 
						|
#include "mozilla/Attributes.h"
 | 
						|
#include "mozilla/CaretAssociationHint.h"
 | 
						|
#include "mozilla/IMEContentObserver.h"
 | 
						|
#include "mozilla/IMEStateManager.h"
 | 
						|
#include "mozilla/TextInputListener.h"
 | 
						|
 | 
						|
#include "nsCOMPtr.h"
 | 
						|
#include "nsView.h"
 | 
						|
#include "nsCaret.h"
 | 
						|
#include "nsITextControlFrame.h"
 | 
						|
#include "nsContentCreatorFunctions.h"
 | 
						|
#include "nsTextControlFrame.h"
 | 
						|
#include "nsIControllers.h"
 | 
						|
#include "nsIControllerContext.h"
 | 
						|
#include "nsAttrValue.h"
 | 
						|
#include "nsAttrValueInlines.h"
 | 
						|
#include "nsGenericHTMLElement.h"
 | 
						|
#include "nsIDOMEventListener.h"
 | 
						|
#include "nsIWidget.h"
 | 
						|
#include "nsIDocumentEncoder.h"
 | 
						|
#include "nsPIDOMWindow.h"
 | 
						|
#include "nsServiceManagerUtils.h"
 | 
						|
#include "mozilla/dom/Selection.h"
 | 
						|
#include "mozilla/EventListenerManager.h"
 | 
						|
#include "nsContentUtils.h"
 | 
						|
#include "mozilla/Preferences.h"
 | 
						|
#include "nsTextNode.h"
 | 
						|
#include "nsIController.h"
 | 
						|
#include "mozilla/AutoRestore.h"
 | 
						|
#include "mozilla/InputEventOptions.h"
 | 
						|
#include "mozilla/NativeKeyBindingsType.h"
 | 
						|
#include "mozilla/PresShell.h"
 | 
						|
#include "mozilla/ScrollContainerFrame.h"
 | 
						|
#include "mozilla/TextEvents.h"
 | 
						|
#include "mozilla/dom/Event.h"
 | 
						|
#include "mozilla/dom/ScriptSettings.h"
 | 
						|
#include "mozilla/dom/HTMLInputElement.h"
 | 
						|
#include "mozilla/dom/HTMLTextAreaElement.h"
 | 
						|
#include "mozilla/dom/Text.h"
 | 
						|
#include "mozilla/StaticPrefs_dom.h"
 | 
						|
#include "mozilla/StaticPrefs_ui.h"
 | 
						|
#include "nsFrameSelection.h"
 | 
						|
#include "mozilla/ErrorResult.h"
 | 
						|
#include "mozilla/Telemetry.h"
 | 
						|
#include "mozilla/ShortcutKeys.h"
 | 
						|
#include "mozilla/KeyEventHandler.h"
 | 
						|
#include "mozilla/dom/KeyboardEvent.h"
 | 
						|
#include "mozilla/ScrollTypes.h"
 | 
						|
 | 
						|
namespace mozilla {
 | 
						|
 | 
						|
using namespace dom;
 | 
						|
using ValueSetterOption = TextControlState::ValueSetterOption;
 | 
						|
using ValueSetterOptions = TextControlState::ValueSetterOptions;
 | 
						|
using SelectionDirection = nsITextControlFrame::SelectionDirection;
 | 
						|
 | 
						|
/*****************************************************************************
 | 
						|
 * TextControlElement
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTION_CLASS(TextControlElement)
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
 | 
						|
    TextControlElement, nsGenericHTMLFormControlElementWithState)
 | 
						|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
 | 
						|
    TextControlElement, nsGenericHTMLFormControlElementWithState)
 | 
						|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 | 
						|
 | 
						|
NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(
 | 
						|
    TextControlElement, nsGenericHTMLFormControlElementWithState)
 | 
						|
 | 
						|
/*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
 | 
						|
 | 
						|
  if (!aContent->IsHTMLElement()) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  static mozilla::dom::Element::AttrValuesArray strings[] = {
 | 
						|
      nsGkAtoms::HARD, nsGkAtoms::OFF, nullptr};
 | 
						|
  switch (aContent->AsElement()->FindAttrValueIn(
 | 
						|
      kNameSpaceID_None, nsGkAtoms::wrap, strings, eIgnoreCase)) {
 | 
						|
    case 0:
 | 
						|
      aWrapProp = eHTMLTextWrap_Hard;
 | 
						|
      break;
 | 
						|
    case 1:
 | 
						|
      aWrapProp = eHTMLTextWrap_Off;
 | 
						|
      break;
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
/*static*/
 | 
						|
already_AddRefed<TextControlElement>
 | 
						|
TextControlElement::GetTextControlElementFromEditingHost(nsIContent* aHost) {
 | 
						|
  if (!aHost) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<TextControlElement> parent =
 | 
						|
      TextControlElement::FromNodeOrNull(aHost->GetParent());
 | 
						|
  return parent.forget();
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
  }
 | 
						|
 | 
						|
  return IsInActiveTab(doc) ? FocusTristate::eActiveWindow
 | 
						|
                            : FocusTristate::eInactiveWindow;
 | 
						|
}
 | 
						|
 | 
						|
using ValueChangeKind = TextControlElement::ValueChangeKind;
 | 
						|
 | 
						|
MOZ_CAN_RUN_SCRIPT inline nsresult SetEditorFlagsIfNecessary(
 | 
						|
    EditorBase& aEditorBase, uint32_t aFlags) {
 | 
						|
  if (aEditorBase.Flags() == aFlags) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
  return aEditorBase.SetFlags(aFlags);
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************************
 | 
						|
 * mozilla::AutoInputEventSuppresser
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
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);
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  RefPtr<TextEditor> mTextEditor;
 | 
						|
  bool mOuterTransaction;
 | 
						|
};
 | 
						|
 | 
						|
/*****************************************************************************
 | 
						|
 * mozilla::RestoreSelectionState
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
class RestoreSelectionState : public Runnable {
 | 
						|
 public:
 | 
						|
  RestoreSelectionState(TextControlState* aState, nsTextControlFrame* aFrame)
 | 
						|
      : Runnable("RestoreSelectionState"),
 | 
						|
        mFrame(aFrame),
 | 
						|
        mTextControlState(aState) {}
 | 
						|
 | 
						|
  MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
 | 
						|
    if (!mTextControlState) {
 | 
						|
      return NS_OK;
 | 
						|
    }
 | 
						|
 | 
						|
    AutoHideSelectionChanges hideSelectionChanges(
 | 
						|
        mFrame->GetConstFrameSelection());
 | 
						|
 | 
						|
    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;
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  nsTextControlFrame* mFrame;
 | 
						|
  TextControlState* mTextControlState;
 | 
						|
};
 | 
						|
 | 
						|
/*****************************************************************************
 | 
						|
 * mozilla::AutoRestoreEditorState
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
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);
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  TextEditor* mTextEditor;
 | 
						|
  uint32_t mSavedFlags;
 | 
						|
  int32_t mSavedMaxLength;
 | 
						|
  bool mSavedEchoingPasswordPrevented;
 | 
						|
};
 | 
						|
 | 
						|
/*****************************************************************************
 | 
						|
 * mozilla::AutoDisableUndo
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
class MOZ_RAII AutoDisableUndo final {
 | 
						|
 public:
 | 
						|
  explicit AutoDisableUndo(TextEditor* aTextEditor)
 | 
						|
      : mTextEditor(aTextEditor), mNumberOfMaximumTransactions(0) {
 | 
						|
    MOZ_ASSERT(mTextEditor);
 | 
						|
 | 
						|
    mNumberOfMaximumTransactions =
 | 
						|
        mTextEditor ? mTextEditor->NumberOfMaximumTransactions() : 0;
 | 
						|
    DebugOnly<bool> disabledUndoRedo = mTextEditor->DisableUndoRedo();
 | 
						|
    NS_WARNING_ASSERTION(disabledUndoRedo,
 | 
						|
                         "Failed to disable undo/redo transactions");
 | 
						|
  }
 | 
						|
 | 
						|
  ~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");
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  TextEditor* mTextEditor;
 | 
						|
  int32_t mNumberOfMaximumTransactions;
 | 
						|
};
 | 
						|
 | 
						|
static bool SuppressEventHandlers(nsPresContext* aPresContext) {
 | 
						|
  bool suppressHandlers = false;
 | 
						|
 | 
						|
  if (aPresContext) {
 | 
						|
    // Right now we only suppress event handlers and controller manipulation
 | 
						|
    // when in a print preview or print context!
 | 
						|
 | 
						|
    // In the current implementation, we only paginate when
 | 
						|
    // printing or in print preview.
 | 
						|
 | 
						|
    suppressHandlers = aPresContext->IsPaginated();
 | 
						|
  }
 | 
						|
 | 
						|
  return suppressHandlers;
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************************
 | 
						|
 * mozilla::TextInputSelectionController
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
class TextInputSelectionController final : public nsSupportsWeakReference,
 | 
						|
                                           public nsISelectionController {
 | 
						|
  ~TextInputSelectionController() = default;
 | 
						|
 | 
						|
 public:
 | 
						|
  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 | 
						|
  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TextInputSelectionController,
 | 
						|
                                           nsISelectionController)
 | 
						|
 | 
						|
  TextInputSelectionController(PresShell* aPresShell, nsIContent* aLimiter);
 | 
						|
 | 
						|
  void SetScrollContainerFrame(ScrollContainerFrame* aScrollContainerFrame);
 | 
						|
  nsFrameSelection* GetConstFrameSelection() { return mFrameSelection; }
 | 
						|
  // Will return null if !mFrameSelection.
 | 
						|
  Selection* GetSelection(SelectionType aSelectionType);
 | 
						|
 | 
						|
  // NSISELECTIONCONTROLLER INTERFACES
 | 
						|
  NS_IMETHOD SetDisplaySelection(int16_t toggle) override;
 | 
						|
  NS_IMETHOD GetDisplaySelection(int16_t* _retval) override;
 | 
						|
  NS_IMETHOD SetSelectionFlags(int16_t aInEnable) override;
 | 
						|
  NS_IMETHOD GetSelectionFlags(int16_t* aOutEnable) override;
 | 
						|
  NS_IMETHOD GetSelectionFromScript(RawSelectionType aRawSelectionType,
 | 
						|
                                    Selection** aSelection) override;
 | 
						|
  Selection* GetSelection(RawSelectionType aRawSelectionType) override;
 | 
						|
  NS_IMETHOD ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
 | 
						|
                                     int16_t aRegion, int16_t aFlags) override;
 | 
						|
  NS_IMETHOD RepaintSelection(RawSelectionType aRawSelectionType) override;
 | 
						|
  nsresult RepaintSelection(nsPresContext* aPresContext,
 | 
						|
                            SelectionType aSelectionType);
 | 
						|
  NS_IMETHOD SetCaretEnabled(bool enabled) override;
 | 
						|
  NS_IMETHOD SetCaretReadOnly(bool aReadOnly) override;
 | 
						|
  NS_IMETHOD GetCaretEnabled(bool* _retval) override;
 | 
						|
  NS_IMETHOD GetCaretVisible(bool* _retval) override;
 | 
						|
  NS_IMETHOD SetCaretVisibilityDuringSelection(bool aVisibility) override;
 | 
						|
  NS_IMETHOD PhysicalMove(int16_t aDirection, int16_t aAmount,
 | 
						|
                          bool aExtend) override;
 | 
						|
  NS_IMETHOD CharacterMove(bool aForward, bool aExtend) override;
 | 
						|
  NS_IMETHOD WordMove(bool aForward, bool aExtend) override;
 | 
						|
  MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD LineMove(bool aForward,
 | 
						|
                                                  bool aExtend) override;
 | 
						|
  NS_IMETHOD IntraLineMove(bool aForward, bool aExtend) override;
 | 
						|
  MOZ_CAN_RUN_SCRIPT
 | 
						|
  NS_IMETHOD PageMove(bool aForward, bool aExtend) override;
 | 
						|
  NS_IMETHOD CompleteScroll(bool aForward) override;
 | 
						|
  MOZ_CAN_RUN_SCRIPT NS_IMETHOD CompleteMove(bool aForward,
 | 
						|
                                             bool aExtend) override;
 | 
						|
  NS_IMETHOD ScrollPage(bool aForward) override;
 | 
						|
  NS_IMETHOD ScrollLine(bool aForward) override;
 | 
						|
  NS_IMETHOD ScrollCharacter(bool aRight) override;
 | 
						|
  void SelectionWillTakeFocus() override;
 | 
						|
  void SelectionWillLoseFocus() override;
 | 
						|
 | 
						|
 private:
 | 
						|
  RefPtr<nsFrameSelection> mFrameSelection;
 | 
						|
  ScrollContainerFrame* mScrollContainerFrame = nullptr;
 | 
						|
  nsWeakPtr mPresShellWeak;
 | 
						|
};
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputSelectionController)
 | 
						|
NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputSelectionController)
 | 
						|
NS_INTERFACE_TABLE_HEAD(TextInputSelectionController)
 | 
						|
  NS_INTERFACE_TABLE(TextInputSelectionController, nsISelectionController,
 | 
						|
                     nsISelectionDisplay, nsISupportsWeakReference)
 | 
						|
  NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(TextInputSelectionController)
 | 
						|
NS_INTERFACE_MAP_END
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTION_WEAK(TextInputSelectionController, mFrameSelection)
 | 
						|
 | 
						|
TextInputSelectionController::TextInputSelectionController(
 | 
						|
    PresShell* aPresShell, nsIContent* aLimiter) {
 | 
						|
  if (aPresShell) {
 | 
						|
    bool accessibleCaretEnabled =
 | 
						|
        PresShell::AccessibleCaretEnabled(aLimiter->OwnerDoc()->GetDocShell());
 | 
						|
    mFrameSelection =
 | 
						|
        new nsFrameSelection(aPresShell, aLimiter, accessibleCaretEnabled);
 | 
						|
    mPresShellWeak = do_GetWeakReference(aPresShell);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void TextInputSelectionController::SetScrollContainerFrame(
 | 
						|
    ScrollContainerFrame* aScrollContainerFrame) {
 | 
						|
  mScrollContainerFrame = aScrollContainerFrame;
 | 
						|
  if (!mScrollContainerFrame && mFrameSelection) {
 | 
						|
    mFrameSelection->DisconnectFromPresShell();
 | 
						|
    mFrameSelection = nullptr;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
Selection* TextInputSelectionController::GetSelection(
 | 
						|
    SelectionType aSelectionType) {
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  return mFrameSelection->GetSelection(aSelectionType);
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::SetDisplaySelection(int16_t aToggle) {
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return NS_ERROR_NULL_POINTER;
 | 
						|
  }
 | 
						|
  mFrameSelection->SetDisplaySelection(aToggle);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::GetDisplaySelection(int16_t* aToggle) {
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return NS_ERROR_NULL_POINTER;
 | 
						|
  }
 | 
						|
  *aToggle = mFrameSelection->GetDisplaySelection();
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::SetSelectionFlags(int16_t aToggle) {
 | 
						|
  return NS_OK;  // stub this out. not used in input
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::GetSelectionFlags(int16_t* aOutEnable) {
 | 
						|
  *aOutEnable = nsISelectionDisplay::DISPLAY_TEXT;
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::GetSelectionFromScript(
 | 
						|
    RawSelectionType aRawSelectionType, Selection** aSelection) {
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return NS_ERROR_NULL_POINTER;
 | 
						|
  }
 | 
						|
 | 
						|
  *aSelection =
 | 
						|
      mFrameSelection->GetSelection(ToSelectionType(aRawSelectionType));
 | 
						|
 | 
						|
  // GetSelection() fails only when aRawSelectionType is invalid value.
 | 
						|
  if (!(*aSelection)) {
 | 
						|
    return NS_ERROR_INVALID_ARG;
 | 
						|
  }
 | 
						|
 | 
						|
  NS_ADDREF(*aSelection);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
Selection* TextInputSelectionController::GetSelection(
 | 
						|
    RawSelectionType aRawSelectionType) {
 | 
						|
  return GetSelection(ToSelectionType(aRawSelectionType));
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::ScrollSelectionIntoView(
 | 
						|
    RawSelectionType aRawSelectionType, int16_t aRegion, int16_t aFlags) {
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return NS_ERROR_NULL_POINTER;
 | 
						|
  }
 | 
						|
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
 | 
						|
  return frameSelection->ScrollSelectionIntoView(
 | 
						|
      ToSelectionType(aRawSelectionType), aRegion, aFlags);
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::RepaintSelection(
 | 
						|
    RawSelectionType aRawSelectionType) {
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return NS_ERROR_NULL_POINTER;
 | 
						|
  }
 | 
						|
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
 | 
						|
  return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
 | 
						|
}
 | 
						|
 | 
						|
nsresult TextInputSelectionController::RepaintSelection(
 | 
						|
    nsPresContext* aPresContext, SelectionType aSelectionType) {
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return NS_ERROR_NULL_POINTER;
 | 
						|
  }
 | 
						|
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
 | 
						|
  return frameSelection->RepaintSelection(aSelectionType);
 | 
						|
}
 | 
						|
 | 
						|
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);
 | 
						|
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::SetCaretReadOnly(bool aReadOnly) {
 | 
						|
  if (!mPresShellWeak) {
 | 
						|
    return NS_ERROR_NOT_INITIALIZED;
 | 
						|
  }
 | 
						|
  nsresult rv;
 | 
						|
  RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
 | 
						|
  if (!presShell) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  RefPtr<nsCaret> caret = presShell->GetCaret();
 | 
						|
  if (!caret) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
 | 
						|
  Selection* selection = mFrameSelection->GetSelection(SelectionType::eNormal);
 | 
						|
  if (selection) {
 | 
						|
    caret->SetCaretReadOnly(aReadOnly);
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::GetCaretEnabled(bool* _retval) {
 | 
						|
  return GetCaretVisible(_retval);
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::GetCaretVisible(bool* _retval) {
 | 
						|
  if (!mPresShellWeak) {
 | 
						|
    return NS_ERROR_NOT_INITIALIZED;
 | 
						|
  }
 | 
						|
  nsresult rv;
 | 
						|
  RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
 | 
						|
  if (!presShell) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  RefPtr<nsCaret> caret = presShell->GetCaret();
 | 
						|
  if (!caret) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  *_retval = caret->IsVisible();
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::SetCaretVisibilityDuringSelection(
 | 
						|
    bool aVisibility) {
 | 
						|
  if (!mPresShellWeak) {
 | 
						|
    return NS_ERROR_NOT_INITIALIZED;
 | 
						|
  }
 | 
						|
  nsresult rv;
 | 
						|
  RefPtr<PresShell> presShell = do_QueryReferent(mPresShellWeak, &rv);
 | 
						|
  if (!presShell) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  RefPtr<nsCaret> caret = presShell->GetCaret();
 | 
						|
  if (!caret) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  Selection* selection = mFrameSelection->GetSelection(SelectionType::eNormal);
 | 
						|
  if (selection) {
 | 
						|
    caret->SetVisibilityDuringSelection(aVisibility);
 | 
						|
  }
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::PhysicalMove(int16_t aDirection, int16_t aAmount,
 | 
						|
                                           bool aExtend) {
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return NS_ERROR_NULL_POINTER;
 | 
						|
  }
 | 
						|
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
 | 
						|
  return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::CharacterMove(bool aForward, bool aExtend) {
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return NS_ERROR_NULL_POINTER;
 | 
						|
  }
 | 
						|
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
 | 
						|
  return frameSelection->CharacterMove(aForward, aExtend);
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::WordMove(bool aForward, bool aExtend) {
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return NS_ERROR_NULL_POINTER;
 | 
						|
  }
 | 
						|
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
 | 
						|
  return frameSelection->WordMove(aForward, aExtend);
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::LineMove(bool aForward, bool aExtend) {
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return NS_ERROR_NULL_POINTER;
 | 
						|
  }
 | 
						|
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
 | 
						|
  nsresult result = frameSelection->LineMove(aForward, aExtend);
 | 
						|
  if (NS_FAILED(result)) {
 | 
						|
    result = CompleteMove(aForward, aExtend);
 | 
						|
  }
 | 
						|
  return result;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::IntraLineMove(bool aForward, bool aExtend) {
 | 
						|
  if (!mFrameSelection) {
 | 
						|
    return NS_ERROR_NULL_POINTER;
 | 
						|
  }
 | 
						|
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
 | 
						|
  return frameSelection->IntraLineMove(aForward, aExtend);
 | 
						|
}
 | 
						|
 | 
						|
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(
 | 
						|
      nsISelectionController::SELECTION_NORMAL,
 | 
						|
      nsISelectionController::SELECTION_FOCUS_REGION,
 | 
						|
      nsISelectionController::SCROLL_SYNCHRONOUS |
 | 
						|
          nsISelectionController::SCROLL_FOR_CARET_MOVE);
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::CompleteScroll(bool aForward) {
 | 
						|
  if (!mScrollContainerFrame) {
 | 
						|
    return NS_ERROR_NOT_INITIALIZED;
 | 
						|
  }
 | 
						|
 | 
						|
  mScrollContainerFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
 | 
						|
                                  ScrollUnit::WHOLE, ScrollMode::Instant);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::CompleteMove(bool aForward, bool aExtend) {
 | 
						|
  if (NS_WARN_IF(!mFrameSelection)) {
 | 
						|
    return NS_ERROR_NULL_POINTER;
 | 
						|
  }
 | 
						|
  RefPtr<nsFrameSelection> frameSelection = mFrameSelection;
 | 
						|
 | 
						|
  // 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
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const RefPtr<nsIContent> pinnedParentDIV{parentDIV};
 | 
						|
  const nsFrameSelection::FocusMode focusMode =
 | 
						|
      aExtend ? nsFrameSelection::FocusMode::kExtendSelection
 | 
						|
              : nsFrameSelection::FocusMode::kCollapseToNewPoint;
 | 
						|
  frameSelection->HandleClick(pinnedParentDIV, offset, offset, focusMode, hint);
 | 
						|
 | 
						|
  // if we got this far, attempt to scroll no matter what the above result is
 | 
						|
  return CompleteScroll(aForward);
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::ScrollPage(bool aForward) {
 | 
						|
  if (!mScrollContainerFrame) {
 | 
						|
    return NS_ERROR_NOT_INITIALIZED;
 | 
						|
  }
 | 
						|
 | 
						|
  mScrollContainerFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
 | 
						|
                                  ScrollUnit::PAGES, ScrollMode::Smooth);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::ScrollLine(bool aForward) {
 | 
						|
  if (!mScrollContainerFrame) {
 | 
						|
    return NS_ERROR_NOT_INITIALIZED;
 | 
						|
  }
 | 
						|
 | 
						|
  mScrollContainerFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1),
 | 
						|
                                  ScrollUnit::LINES, ScrollMode::Smooth);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
NS_IMETHODIMP
 | 
						|
TextInputSelectionController::ScrollCharacter(bool aRight) {
 | 
						|
  if (!mScrollContainerFrame) {
 | 
						|
    return NS_ERROR_NOT_INITIALIZED;
 | 
						|
  }
 | 
						|
 | 
						|
  mScrollContainerFrame->ScrollBy(nsIntPoint(aRight ? 1 : -1, 0),
 | 
						|
                                  ScrollUnit::LINES, ScrollMode::Smooth);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
void TextInputSelectionController::SelectionWillTakeFocus() {
 | 
						|
  if (mFrameSelection) {
 | 
						|
    if (PresShell* shell = mFrameSelection->GetPresShell()) {
 | 
						|
      shell->FrameSelectionWillTakeFocus(*mFrameSelection);
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void TextInputSelectionController::SelectionWillLoseFocus() {
 | 
						|
  if (mFrameSelection) {
 | 
						|
    if (PresShell* shell = mFrameSelection->GetPresShell()) {
 | 
						|
      shell->FrameSelectionWillLoseFocus(*mFrameSelection);
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************************
 | 
						|
 * mozilla::TextInputListener
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
TextInputListener::TextInputListener(TextControlElement* aTxtCtrlElement)
 | 
						|
    : mFrame(nullptr),
 | 
						|
      mTxtCtrlElement(aTxtCtrlElement),
 | 
						|
      mTextControlState(aTxtCtrlElement ? aTxtCtrlElement->GetTextControlState()
 | 
						|
                                        : nullptr),
 | 
						|
      mSelectionWasCollapsed(true),
 | 
						|
      mHadUndoItems(false),
 | 
						|
      mHadRedoItems(false),
 | 
						|
      mSettingValue(false),
 | 
						|
      mSetValueChanged(true),
 | 
						|
      mListeningToSelectionChange(false) {}
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTING_ADDREF(TextInputListener)
 | 
						|
NS_IMPL_CYCLE_COLLECTING_RELEASE(TextInputListener)
 | 
						|
 | 
						|
NS_INTERFACE_MAP_BEGIN(TextInputListener)
 | 
						|
  NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 | 
						|
  NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener)
 | 
						|
  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventListener)
 | 
						|
  NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(TextInputListener)
 | 
						|
NS_INTERFACE_MAP_END
 | 
						|
 | 
						|
NS_IMPL_CYCLE_COLLECTION_CLASS(TextInputListener)
 | 
						|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(TextInputListener)
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
 | 
						|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 | 
						|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(TextInputListener)
 | 
						|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 | 
						|
 | 
						|
void TextInputListener::OnSelectionChange(Selection& aSelection,
 | 
						|
                                          int16_t aReason) {
 | 
						|
  if (!mListeningToSelectionChange) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  AutoWeakFrame weakFrame = mFrame;
 | 
						|
 | 
						|
  // 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);
 | 
						|
 | 
						|
          presShell->HandleEventWithTarget(&event, mFrame, content, &status);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // if the collapsed state did not change, don't fire notifications
 | 
						|
  if (collapsed == mSelectionWasCollapsed) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  mSelectionWasCollapsed = collapsed;
 | 
						|
 | 
						|
  if (!weakFrame.IsAlive() || !mFrame ||
 | 
						|
      !nsContentUtils::IsFocusedContent(mFrame->GetContent())) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  UpdateTextInputCommands(u"select"_ns);
 | 
						|
}
 | 
						|
 | 
						|
MOZ_CAN_RUN_SCRIPT
 | 
						|
static void DoCommandCallback(Command aCommand, void* aData) {
 | 
						|
  nsTextControlFrame* frame = static_cast<nsTextControlFrame*>(aData);
 | 
						|
  nsIContent* content = frame->GetContent();
 | 
						|
 | 
						|
  nsCOMPtr<nsIControllers> controllers;
 | 
						|
  HTMLInputElement* input = HTMLInputElement::FromNode(content);
 | 
						|
  if (input) {
 | 
						|
    input->GetControllers(getter_AddRefs(controllers));
 | 
						|
  } else {
 | 
						|
    HTMLTextAreaElement* textArea = HTMLTextAreaElement::FromNode(content);
 | 
						|
 | 
						|
    if (textArea) {
 | 
						|
      textArea->GetControllers(getter_AddRefs(controllers));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (!controllers) {
 | 
						|
    NS_WARNING("Could not get controllers");
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  const char* commandStr = WidgetKeyboardEvent::GetCommandStr(aCommand);
 | 
						|
 | 
						|
  nsCOMPtr<nsIController> controller;
 | 
						|
  controllers->GetControllerForCommand(commandStr, getter_AddRefs(controller));
 | 
						|
  if (!controller) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  bool commandEnabled;
 | 
						|
  if (NS_WARN_IF(NS_FAILED(
 | 
						|
          controller->IsCommandEnabled(commandStr, &commandEnabled)))) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  if (commandEnabled) {
 | 
						|
    controller->DoCommand(commandStr);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
 | 
						|
TextInputListener::HandleEvent(Event* aEvent) {
 | 
						|
  if (aEvent->DefaultPrevented()) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!aEvent->IsTrusted()) {
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
  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;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  auto ExecuteOurShortcutKeys = [&](TextControlElement& aTextControlElement)
 | 
						|
                                    MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> bool {
 | 
						|
    KeyEventHandler* keyHandlers = ShortcutKeys::GetHandlers(
 | 
						|
        aTextControlElement.IsTextArea() ? HandlerType::eTextArea
 | 
						|
                                         : HandlerType::eInput);
 | 
						|
 | 
						|
    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)) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  };
 | 
						|
 | 
						|
  auto ExecuteNativeKeyBindings =
 | 
						|
      [&](TextControlElement& aTextControlElement)
 | 
						|
          MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION -> bool {
 | 
						|
    if (widgetKeyEvent->mMessage != eKeyPress) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    NativeKeyBindingsType nativeKeyBindingsType =
 | 
						|
        aTextControlElement.IsTextArea()
 | 
						|
            ? NativeKeyBindingsType::MultiLineEditor
 | 
						|
            : NativeKeyBindingsType::SingleLineEditor;
 | 
						|
 | 
						|
    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))) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // 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();
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  };
 | 
						|
 | 
						|
  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);
 | 
						|
 | 
						|
      mHadUndoItems = numUndoItems != 0;
 | 
						|
      mHadRedoItems = numRedoItems != 0;
 | 
						|
    }
 | 
						|
 | 
						|
    if (weakFrame.IsAlive()) {
 | 
						|
      HandleValueChanged(aTextEditor);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return mTextControlState ? mTextControlState->OnEditActionHandled() : NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
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();
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
nsresult TextInputListener::UpdateTextInputCommands(
 | 
						|
    const nsAString& aCommandsToUpdate) {
 | 
						|
  nsIContent* content = mFrame->GetContent();
 | 
						|
  if (NS_WARN_IF(!content)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  nsCOMPtr<Document> doc = content->GetComposedDoc();
 | 
						|
  if (NS_WARN_IF(!doc)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  nsPIDOMWindowOuter* domWindow = doc->GetWindow();
 | 
						|
  if (NS_WARN_IF(!domWindow)) {
 | 
						|
    return NS_ERROR_FAILURE;
 | 
						|
  }
 | 
						|
  domWindow->UpdateCommands(aCommandsToUpdate);
 | 
						|
  return NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
/*****************************************************************************
 | 
						|
 * 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.
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
enum class TextControlAction {
 | 
						|
  CommitComposition,
 | 
						|
  Destructor,
 | 
						|
  PrepareEditor,
 | 
						|
  SetRangeText,
 | 
						|
  SetSelectionRange,
 | 
						|
  SetValue,
 | 
						|
  UnbindFromFrame,
 | 
						|
  Unlink,
 | 
						|
};
 | 
						|
 | 
						|
class MOZ_STACK_CLASS AutoTextControlHandlingState {
 | 
						|
 public:
 | 
						|
  AutoTextControlHandlingState() = delete;
 | 
						|
  explicit AutoTextControlHandlingState(const AutoTextControlHandlingState&) =
 | 
						|
      delete;
 | 
						|
  AutoTextControlHandlingState(AutoTextControlHandlingState&&) = delete;
 | 
						|
  void operator=(AutoTextControlHandlingState&) = delete;
 | 
						|
  void operator=(const AutoTextControlHandlingState&) = delete;
 | 
						|
 | 
						|
  /**
 | 
						|
   * 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);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_CAN_RUN_SCRIPT ~AutoTextControlHandlingState() {
 | 
						|
    mTextControlState.mHandlingState = mParent;
 | 
						|
    if (!mParent && mTextControlStateDestroyed) {
 | 
						|
      mTextControlState.DeleteOrCacheForReuse();
 | 
						|
    }
 | 
						|
    if (!mTextControlStateDestroyed && mPrepareEditorLater) {
 | 
						|
      MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
 | 
						|
      MOZ_ASSERT(Is(TextControlAction::SetValue));
 | 
						|
      mTextControlState.PrepareEditor(&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.
 | 
						|
    nsITextControlFrame* textControlFrame =
 | 
						|
        do_QueryFrame(mTextControlFrame.GetFrame());
 | 
						|
    return static_cast<nsTextControlFrame*>(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) {
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    // 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();
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    return mTextControlState.SetValueWithoutTextEditor(
 | 
						|
        handlingSetValueWithoutEditor);
 | 
						|
  }
 | 
						|
 | 
						|
  bool IsTextControlStateDestroyed() const {
 | 
						|
    return mTextControlStateDestroyed;
 | 
						|
  }
 | 
						|
  bool IsOriginalTextControlFrameAlive() const {
 | 
						|
    return const_cast<AutoTextControlHandlingState*>(this)
 | 
						|
        ->mTextControlFrame.IsAlive();
 | 
						|
  }
 | 
						|
  bool HasEditActionHandled() const { return mEditActionHandled; }
 | 
						|
  bool HasBeforeInputEventDispatched() const {
 | 
						|
    return mBeforeInputEventHasBeenDispatched;
 | 
						|
  }
 | 
						|
  bool Is(TextControlAction aTextControlAction) const {
 | 
						|
    return mTextControlAction == aTextControlAction;
 | 
						|
  }
 | 
						|
  bool IsHandling(TextControlAction aTextControlAction) const {
 | 
						|
    if (mTextControlAction == aTextControlAction) {
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    return mParent && mParent->IsHandling(aTextControlAction);
 | 
						|
  }
 | 
						|
  TextControlElement* GetTextControlElement() const { return mTextCtrlElement; }
 | 
						|
  TextInputListener* GetTextInputListener() const { return mTextInputListener; }
 | 
						|
  const ValueSetterOptions& ValueSetterOptionsRef() const {
 | 
						|
    MOZ_ASSERT(Is(TextControlAction::SetValue));
 | 
						|
    return mValueSetterOptions;
 | 
						|
  }
 | 
						|
  const nsAString* GetOldValue() const {
 | 
						|
    MOZ_ASSERT(Is(TextControlAction::SetValue));
 | 
						|
    return mOldValue;
 | 
						|
  }
 | 
						|
  const nsString& GetSettingValue() const {
 | 
						|
    MOZ_ASSERT(IsHandling(TextControlAction::SetValue));
 | 
						|
    if (mTextControlAction == TextControlAction::SetValue) {
 | 
						|
      return mSettingValue;
 | 
						|
    }
 | 
						|
    return mParent->GetSettingValue();
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  void UpdateSettingValueAndInvalidateOldValue(const nsString& aSettingValue) {
 | 
						|
    if (mTextControlAction == TextControlAction::SetValue) {
 | 
						|
      mSettingValue = aSettingValue;
 | 
						|
    }
 | 
						|
    mOldValue = nullptr;
 | 
						|
    if (mParent) {
 | 
						|
      mParent->UpdateSettingValueAndInvalidateOldValue(aSettingValue);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  void InvalidateOldValue() {
 | 
						|
    mOldValue = nullptr;
 | 
						|
    if (mParent) {
 | 
						|
      mParent->InvalidateOldValue();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  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;
 | 
						|
};
 | 
						|
 | 
						|
/*****************************************************************************
 | 
						|
 * mozilla::TextControlState
 | 
						|
 *****************************************************************************/
 | 
						|
 | 
						|
/**
 | 
						|
 * 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;
 | 
						|
static bool 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));
 | 
						|
  }
 | 
						|
 | 
						|
  return new (mem) TextControlState(aOwningElement);
 | 
						|
}
 | 
						|
 | 
						|
TextControlState::~TextControlState() {
 | 
						|
  MOZ_ASSERT(!mHandlingState);
 | 
						|
  MOZ_COUNT_DTOR(TextControlState);
 | 
						|
  AutoTextControlHandlingState handlingDesctructor(
 | 
						|
      *this, TextControlAction::Destructor);
 | 
						|
  Clear();
 | 
						|
}
 | 
						|
 | 
						|
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.
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::DeleteOrCacheForReuse() {
 | 
						|
  MOZ_ASSERT(!IsBusy());
 | 
						|
 | 
						|
  void* mem = this;
 | 
						|
  this->~TextControlState();
 | 
						|
 | 
						|
  // 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);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
nsresult TextControlState::OnEditActionHandled() {
 | 
						|
  return mHandlingState ? mHandlingState->OnEditActionHandled() : NS_OK;
 | 
						|
}
 | 
						|
 | 
						|
Element* TextControlState::GetRootNode() {
 | 
						|
  return mBoundFrame ? mBoundFrame->GetRootNode() : nullptr;
 | 
						|
}
 | 
						|
 | 
						|
Element* TextControlState::GetPreviewNode() {
 | 
						|
  return mBoundFrame ? mBoundFrame->GetPreviewNode() : nullptr;
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::Clear() {
 | 
						|
  MOZ_ASSERT(mHandlingState);
 | 
						|
  MOZ_ASSERT(mHandlingState->Is(TextControlAction::Destructor) ||
 | 
						|
             mHandlingState->Is(TextControlAction::Unlink));
 | 
						|
  if (mTextEditor) {
 | 
						|
    mTextEditor->SetTextInputListener(nullptr);
 | 
						|
  }
 | 
						|
 | 
						|
  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;
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::Unlink() {
 | 
						|
  AutoTextControlHandlingState handlingUnlink(*this, TextControlAction::Unlink);
 | 
						|
  UnlinkInternal();
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::UnlinkInternal() {
 | 
						|
  MOZ_ASSERT(mHandlingState);
 | 
						|
  MOZ_ASSERT(mHandlingState->Is(TextControlAction::Unlink));
 | 
						|
  TextControlState* tmp = this;
 | 
						|
  tmp->Clear();
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelCon)
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextEditor)
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::Traverse(nsCycleCollectionTraversalCallback& cb) {
 | 
						|
  TextControlState* tmp = this;
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelCon)
 | 
						|
  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextEditor)
 | 
						|
}
 | 
						|
 | 
						|
nsFrameSelection* TextControlState::GetConstFrameSelection() {
 | 
						|
  return mSelCon ? mSelCon->GetConstFrameSelection() : 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;
 | 
						|
}
 | 
						|
 | 
						|
TextEditor* TextControlState::GetTextEditorWithoutCreation() const {
 | 
						|
  return mTextEditor;
 | 
						|
}
 | 
						|
 | 
						|
nsISelectionController* TextControlState::GetSelectionController() const {
 | 
						|
  return mSelCon;
 | 
						|
}
 | 
						|
 | 
						|
// Helper class, used below in BindToFrame().
 | 
						|
class PrepareEditorEvent : public Runnable {
 | 
						|
 public:
 | 
						|
  PrepareEditorEvent(TextControlState& aState, nsIContent* aOwnerContent,
 | 
						|
                     const nsAString& aCurrentValue)
 | 
						|
      : Runnable("PrepareEditorEvent"),
 | 
						|
        mState(&aState),
 | 
						|
        mOwnerContent(aOwnerContent),
 | 
						|
        mCurrentValue(aCurrentValue) {
 | 
						|
    aState.mValueTransferInProgress = true;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
 | 
						|
    if (NS_WARN_IF(!mState)) {
 | 
						|
      return NS_ERROR_NULL_POINTER;
 | 
						|
    }
 | 
						|
 | 
						|
    // Transfer the saved value to the editor if we have one
 | 
						|
    const nsAString* value = nullptr;
 | 
						|
    if (!mCurrentValue.IsEmpty()) {
 | 
						|
      value = &mCurrentValue;
 | 
						|
    }
 | 
						|
 | 
						|
    nsAutoScriptBlocker scriptBlocker;
 | 
						|
 | 
						|
    mState->PrepareEditor(value);
 | 
						|
 | 
						|
    mState->mValueTransferInProgress = false;
 | 
						|
 | 
						|
    return NS_OK;
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  WeakPtr<TextControlState> mState;
 | 
						|
  nsCOMPtr<nsIContent> mOwnerContent;  // strong reference
 | 
						|
  nsAutoString mCurrentValue;
 | 
						|
};
 | 
						|
 | 
						|
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);
 | 
						|
  }
 | 
						|
 | 
						|
  mBoundFrame = aFrame;
 | 
						|
 | 
						|
  Element* rootNode = aFrame->GetRootNode();
 | 
						|
  MOZ_ASSERT(rootNode);
 | 
						|
 | 
						|
  PresShell* presShell = aFrame->PresContext()->GetPresShell();
 | 
						|
  MOZ_ASSERT(presShell);
 | 
						|
 | 
						|
  // Create a SelectionController
 | 
						|
  mSelCon = new TextInputSelectionController(presShell, rootNode);
 | 
						|
  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()) {
 | 
						|
      rootNode->SetAttr(kNameSpaceID_None, nsGkAtoms::dir, u"rtl"_ns, false);
 | 
						|
    } else if (mTextEditor->IsLeftToRight()) {
 | 
						|
      rootNode->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;
 | 
						|
  }
 | 
						|
 | 
						|
  AutoHideSelectionChanges hideSelectionChanges(GetConstFrameSelection());
 | 
						|
 | 
						|
  if (mHandlingState) {
 | 
						|
    // Don't attempt to initialize recursively!
 | 
						|
    if (mHandlingState->IsHandling(TextControlAction::PrepareEditor)) {
 | 
						|
      return NS_ERROR_NOT_INITIALIZED;
 | 
						|
    }
 | 
						|
    // Reschedule creating editor later if we're setting value.
 | 
						|
    if (mHandlingState->IsHandling(TextControlAction::SetValue)) {
 | 
						|
      mHandlingState->PrepareEditorLater();
 | 
						|
      return NS_ERROR_NOT_INITIALIZED;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(mTextCtrlElement);
 | 
						|
 | 
						|
  AutoTextControlHandlingState preparingEditor(
 | 
						|
      *this, TextControlAction::PrepareEditor);
 | 
						|
 | 
						|
  // 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.
 | 
						|
 | 
						|
  nsPresContext* presContext = mBoundFrame->PresContext();
 | 
						|
  PresShell* presShell = presContext->GetPresShell();
 | 
						|
 | 
						|
  // Setup the editor flags
 | 
						|
 | 
						|
  // 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 explictly 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;
 | 
						|
 | 
						|
    RefPtr<Element> anonymousDivElement = GetRootNode();
 | 
						|
    if (NS_WARN_IF(!anonymousDivElement) || NS_WARN_IF(!mSelCon)) {
 | 
						|
      return NS_ERROR_FAILURE;
 | 
						|
    }
 | 
						|
    OwningNonNull<TextInputSelectionController> selectionController(*mSelCon);
 | 
						|
    UniquePtr<PasswordMaskData> passwordMaskData;
 | 
						|
    if (editorFlags & nsIEditor::eEditorPasswordMask) {
 | 
						|
      if (mPasswordMaskData) {
 | 
						|
        passwordMaskData = std::move(mPasswordMaskData);
 | 
						|
      } else {
 | 
						|
        passwordMaskData = MakeUnique<PasswordMaskData>();
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      mPasswordMaskData = nullptr;
 | 
						|
    }
 | 
						|
    nsresult rv =
 | 
						|
        newTextEditor->Init(*doc, *anonymousDivElement, selectionController,
 | 
						|
                            editorFlags, std::move(passwordMaskData));
 | 
						|
    if (NS_FAILED(rv)) {
 | 
						|
      NS_WARNING("TextEditor::Init() failed");
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Initialize the controller for the editor
 | 
						|
 | 
						|
  nsresult rv = NS_OK;
 | 
						|
  if (!SuppressEventHandlers(presContext)) {
 | 
						|
    nsCOMPtr<nsIControllers> controllers;
 | 
						|
    if (auto* inputElement = HTMLInputElement::FromNode(mTextCtrlElement)) {
 | 
						|
      nsresult rv = inputElement->GetControllers(getter_AddRefs(controllers));
 | 
						|
      if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      auto* textAreaElement = HTMLTextAreaElement::FromNode(mTextCtrlElement);
 | 
						|
      if (!textAreaElement) {
 | 
						|
        return NS_ERROR_FAILURE;
 | 
						|
      }
 | 
						|
 | 
						|
      nsresult rv =
 | 
						|
          textAreaElement->GetControllers(getter_AddRefs(controllers));
 | 
						|
      if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
        return rv;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    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;
 | 
						|
  }
 | 
						|
 | 
						|
  SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
 | 
						|
 | 
						|
  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
 | 
						|
  // displayed for password fields, etc. SetValue() will call the
 | 
						|
  // editor for us.
 | 
						|
 | 
						|
  if (!defaultValue.IsEmpty()) {
 | 
						|
    // XXX rv may store error code which indicates there is no controller.
 | 
						|
    //     However, we overwrite it only in this case.
 | 
						|
    rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
 | 
						|
    if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
 | 
						|
    // Now call SetValue() which will make the necessary editor calls to set
 | 
						|
    // the default value.  Make sure to turn off undo before setting the default
 | 
						|
    // value, and turn it back on afterwards. This will make sure we can't undo
 | 
						|
    // past the default value.
 | 
						|
    // So, we use ValueSetterOption::ByInternalAPI only that it will turn off
 | 
						|
    // undo.
 | 
						|
 | 
						|
    if (NS_WARN_IF(!SetValue(defaultValue, ValueSetterOption::ByInternalAPI))) {
 | 
						|
      return NS_ERROR_OUT_OF_MEMORY;
 | 
						|
    }
 | 
						|
 | 
						|
    // Now restore the original editor flags.
 | 
						|
    rv = SetEditorFlagsIfNecessary(*newTextEditor, editorFlags);
 | 
						|
    if (NS_WARN_IF(NS_FAILED(rv))) {
 | 
						|
      return rv;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  // When the default value is empty, we don't call SetValue().  That means that
 | 
						|
  // we have not notified IMEContentObserver of the empty value when the
 | 
						|
  // <textarea> is not dirty (i.e., the default value is mirrored into the
 | 
						|
  // anonymous subtree asynchronously) and the value was changed during a
 | 
						|
  // reframe (i.e., while IMEContentObserver was not observing the mutation of
 | 
						|
  // the anonymous subtree).  Therefore, we notify IMEContentObserver here in
 | 
						|
  // that case.
 | 
						|
  else if (mTextCtrlElement && mTextCtrlElement->IsTextArea() &&
 | 
						|
           !mTextCtrlElement->ValueChanged()) {
 | 
						|
    MOZ_ASSERT(defaultValue.IsEmpty());
 | 
						|
    IMEContentObserver* observer = GetIMEContentObserver();
 | 
						|
    if (observer && observer->WasInitializedWith(*newTextEditor)) {
 | 
						|
      observer->OnTextControlValueChangedWhileNotObservable(defaultValue);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  DebugOnly<bool> enabledUndoRedo =
 | 
						|
      newTextEditor->EnableUndoRedo(TextControlElement::DEFAULT_UNDO_CAP);
 | 
						|
  NS_WARNING_ASSERTION(enabledUndoRedo,
 | 
						|
                       "Failed to enable undo/redo transaction");
 | 
						|
 | 
						|
  if (!mEditorInitialized) {
 | 
						|
    newTextEditor->PostCreate();
 | 
						|
    mEverInited = true;
 | 
						|
    mEditorInitialized = true;
 | 
						|
  }
 | 
						|
 | 
						|
  if (mTextListener) {
 | 
						|
    newTextEditor->SetTextInputListener(mTextListener);
 | 
						|
  }
 | 
						|
 | 
						|
  // Restore our selection after being bound to a new frame
 | 
						|
  if (mSelectionCached) {
 | 
						|
    if (mRestoringSelection) {  // paranoia
 | 
						|
      mRestoringSelection->Revoke();
 | 
						|
    }
 | 
						|
    mRestoringSelection = new RestoreSelectionState(this, mBoundFrame);
 | 
						|
    if (mRestoringSelection) {
 | 
						|
      nsContentUtils::AddScriptRunner(mRestoringSelection);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // The selection cache is no longer going to be valid.
 | 
						|
  //
 | 
						|
  // XXXbz Shouldn't we do this at the point when we're actually about to
 | 
						|
  // restore the properties or something?  As things stand, if UnbindFromFrame
 | 
						|
  // happens before our RestoreSelectionState runs, it looks like we'll lose our
 | 
						|
  // selection info, because we will think we don't have it cached and try to
 | 
						|
  // read it from the selection controller, which will not have it yet.
 | 
						|
  mSelectionCached = false;
 | 
						|
 | 
						|
  return preparingEditor.IsTextControlStateDestroyed()
 | 
						|
             ? NS_ERROR_NOT_INITIALIZED
 | 
						|
             : rv;
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::FinishedRestoringSelection() {
 | 
						|
  mRestoringSelection = nullptr;
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::SyncUpSelectionPropertiesBeforeDestruction() {
 | 
						|
  if (mBoundFrame) {
 | 
						|
    UnbindFromFrame(mBoundFrame);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::SetSelectionProperties(
 | 
						|
    TextControlState::SelectionProperties& aProps) {
 | 
						|
  if (mBoundFrame) {
 | 
						|
    mBoundFrame->SetSelectionRange(aProps.GetStart(), aProps.GetEnd(),
 | 
						|
                                   aProps.GetDirection());
 | 
						|
    // The instance may have already been deleted here.
 | 
						|
  } else {
 | 
						|
    mSelectionProperties = aProps;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::GetSelectionRange(uint32_t* aSelectionStart,
 | 
						|
                                         uint32_t* aSelectionEnd,
 | 
						|
                                         ErrorResult& aRv) {
 | 
						|
  MOZ_ASSERT(aSelectionStart);
 | 
						|
  MOZ_ASSERT(aSelectionEnd);
 | 
						|
  MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
 | 
						|
             "How can we not have a cached selection if we have no selection "
 | 
						|
             "controller?");
 | 
						|
 | 
						|
  // Note that we may have both IsSelectionCached() _and_
 | 
						|
  // GetSelectionController() if we haven't initialized our editor yet.
 | 
						|
  if (IsSelectionCached()) {
 | 
						|
    const SelectionProperties& props = GetSelectionProperties();
 | 
						|
    *aSelectionStart = props.GetStart();
 | 
						|
    *aSelectionEnd = props.GetEnd();
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
 | 
						|
  if (NS_WARN_IF(!sel)) {
 | 
						|
    aRv.Throw(NS_ERROR_FAILURE);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  Element* root = GetRootNode();
 | 
						|
  if (NS_WARN_IF(!root)) {
 | 
						|
    aRv.Throw(NS_ERROR_UNEXPECTED);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  nsContentUtils::GetSelectionInTextControl(sel, root, *aSelectionStart,
 | 
						|
                                            *aSelectionEnd);
 | 
						|
}
 | 
						|
 | 
						|
SelectionDirection TextControlState::GetSelectionDirection(ErrorResult& aRv) {
 | 
						|
  MOZ_ASSERT(IsSelectionCached() || GetSelectionController(),
 | 
						|
             "How can we not have a cached selection if we have no selection "
 | 
						|
             "controller?");
 | 
						|
 | 
						|
  // Note that we may have both IsSelectionCached() _and_
 | 
						|
  // GetSelectionController() if we haven't initialized our editor yet.
 | 
						|
  if (IsSelectionCached()) {
 | 
						|
    return GetSelectionProperties().GetDirection();
 | 
						|
  }
 | 
						|
 | 
						|
  Selection* sel = mSelCon->GetSelection(SelectionType::eNormal);
 | 
						|
  if (NS_WARN_IF(!sel)) {
 | 
						|
    aRv.Throw(NS_ERROR_FAILURE);
 | 
						|
    return SelectionDirection::Forward;
 | 
						|
  }
 | 
						|
 | 
						|
  nsDirection direction = sel->GetDirection();
 | 
						|
  if (direction == eDirNext) {
 | 
						|
    return SelectionDirection::Forward;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(direction == eDirPrevious);
 | 
						|
  return SelectionDirection::Backward;
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::SetSelectionRange(uint32_t aStart, uint32_t aEnd,
 | 
						|
                                         SelectionDirection aDirection,
 | 
						|
                                         ErrorResult& aRv,
 | 
						|
                                         ScrollAfterSelection aScroll) {
 | 
						|
  MOZ_ASSERT(IsSelectionCached() || mBoundFrame,
 | 
						|
             "How can we have a non-cached selection but no frame?");
 | 
						|
 | 
						|
  AutoTextControlHandlingState handlingSetSelectionRange(
 | 
						|
      *this, TextControlAction::SetSelectionRange);
 | 
						|
 | 
						|
  if (aStart > aEnd) {
 | 
						|
    aStart = aEnd;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!IsSelectionCached()) {
 | 
						|
    MOZ_ASSERT(mBoundFrame, "Our frame should still be valid");
 | 
						|
    aRv = mBoundFrame->SetSelectionRange(aStart, aEnd, aDirection);
 | 
						|
    if (aRv.Failed() ||
 | 
						|
        handlingSetSelectionRange.IsTextControlStateDestroyed()) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    if (aScroll == ScrollAfterSelection::Yes && mBoundFrame) {
 | 
						|
      // mBoundFrame could be gone if selection listeners flushed layout for
 | 
						|
      // example.
 | 
						|
      mBoundFrame->ScrollSelectionIntoViewAsync();
 | 
						|
    }
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  SelectionProperties& props = GetSelectionProperties();
 | 
						|
  if (!props.HasMaxLength()) {
 | 
						|
    // A clone without a dirty value flag may not have a max length yet
 | 
						|
    nsAutoString value;
 | 
						|
    GetValue(value, false, /* aForDisplay = */ true);
 | 
						|
    props.SetMaxLength(value.Length());
 | 
						|
  }
 | 
						|
 | 
						|
  bool changed = props.SetStart(aStart);
 | 
						|
  changed |= props.SetEnd(aEnd);
 | 
						|
  changed |= props.SetDirection(aDirection);
 | 
						|
 | 
						|
  if (!changed) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // It sure would be nice if we had an existing Element* or so to work with.
 | 
						|
  RefPtr<AsyncEventDispatcher> asyncDispatcher =
 | 
						|
      new AsyncEventDispatcher(mTextCtrlElement, eFormSelect, CanBubble::eYes);
 | 
						|
  asyncDispatcher->PostDOMEvent();
 | 
						|
 | 
						|
  // SelectionChangeEventDispatcher covers this when !IsSelectionCached().
 | 
						|
  // XXX(krosylight): Shouldn't it fire before select event?
 | 
						|
  // Currently Gecko and Blink both fire selectionchange after select.
 | 
						|
  if (IsSelectionCached() &&
 | 
						|
      StaticPrefs::dom_select_events_textcontrols_selectionchange_enabled() &&
 | 
						|
      !mTextCtrlElement->HasScheduledSelectionChangeEvent()) {
 | 
						|
    mTextCtrlElement->SetHasScheduledSelectionChangeEvent();
 | 
						|
    asyncDispatcher = new AsyncSelectionChangeEventDispatcher(
 | 
						|
        mTextCtrlElement, eSelectionChange, CanBubble::eYes);
 | 
						|
    asyncDispatcher->PostDOMEvent();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::SetSelectionStart(const Nullable<uint32_t>& aStart,
 | 
						|
                                         ErrorResult& aRv) {
 | 
						|
  uint32_t start = 0;
 | 
						|
  if (!aStart.IsNull()) {
 | 
						|
    start = aStart.Value();
 | 
						|
  }
 | 
						|
 | 
						|
  uint32_t ignored, end;
 | 
						|
  GetSelectionRange(&ignored, &end, aRv);
 | 
						|
  if (aRv.Failed()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  SelectionDirection dir = GetSelectionDirection(aRv);
 | 
						|
  if (aRv.Failed()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (end < start) {
 | 
						|
    end = start;
 | 
						|
  }
 | 
						|
 | 
						|
  SetSelectionRange(start, end, dir, aRv);
 | 
						|
  // The instance may have already been deleted here.
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::SetSelectionEnd(const Nullable<uint32_t>& aEnd,
 | 
						|
                                       ErrorResult& aRv) {
 | 
						|
  uint32_t end = 0;
 | 
						|
  if (!aEnd.IsNull()) {
 | 
						|
    end = aEnd.Value();
 | 
						|
  }
 | 
						|
 | 
						|
  uint32_t start, ignored;
 | 
						|
  GetSelectionRange(&start, &ignored, aRv);
 | 
						|
  if (aRv.Failed()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  SelectionDirection dir = GetSelectionDirection(aRv);
 | 
						|
  if (aRv.Failed()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  SetSelectionRange(start, end, dir, aRv);
 | 
						|
  // The instance may have already been deleted here.
 | 
						|
}
 | 
						|
 | 
						|
static void DirectionToName(SelectionDirection dir, nsAString& aDirection) {
 | 
						|
  switch (dir) {
 | 
						|
    case SelectionDirection::None:
 | 
						|
      // TODO(mbrodesser): this should be supported, see
 | 
						|
      // https://bugzilla.mozilla.org/show_bug.cgi?id=1541454.
 | 
						|
      NS_WARNING("We don't actually support this... how did we get it?");
 | 
						|
      return aDirection.AssignLiteral("none");
 | 
						|
    case SelectionDirection::Forward:
 | 
						|
      return aDirection.AssignLiteral("forward");
 | 
						|
    case SelectionDirection::Backward:
 | 
						|
      return aDirection.AssignLiteral("backward");
 | 
						|
  }
 | 
						|
  MOZ_ASSERT_UNREACHABLE("Invalid SelectionDirection value");
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::GetSelectionDirectionString(nsAString& aDirection,
 | 
						|
                                                   ErrorResult& aRv) {
 | 
						|
  SelectionDirection dir = GetSelectionDirection(aRv);
 | 
						|
  if (aRv.Failed()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  DirectionToName(dir, aDirection);
 | 
						|
}
 | 
						|
 | 
						|
static SelectionDirection DirectionStringToSelectionDirection(
 | 
						|
    const nsAString& aDirection) {
 | 
						|
  if (aDirection.EqualsLiteral("backward")) {
 | 
						|
    return SelectionDirection::Backward;
 | 
						|
  }
 | 
						|
  // We don't support directionless selections, see bug 1541454.
 | 
						|
  return SelectionDirection::Forward;
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::SetSelectionDirection(const nsAString& aDirection,
 | 
						|
                                             ErrorResult& aRv) {
 | 
						|
  SelectionDirection dir = DirectionStringToSelectionDirection(aDirection);
 | 
						|
 | 
						|
  uint32_t start, end;
 | 
						|
  GetSelectionRange(&start, &end, aRv);
 | 
						|
  if (aRv.Failed()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  SetSelectionRange(start, end, dir, aRv);
 | 
						|
  // The instance may have already been deleted here.
 | 
						|
}
 | 
						|
 | 
						|
static SelectionDirection DirectionStringToSelectionDirection(
 | 
						|
    const Optional<nsAString>& aDirection) {
 | 
						|
  if (!aDirection.WasPassed()) {
 | 
						|
    // We don't support directionless selections.
 | 
						|
    return SelectionDirection::Forward;
 | 
						|
  }
 | 
						|
 | 
						|
  return DirectionStringToSelectionDirection(aDirection.Value());
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::SetSelectionRange(uint32_t aSelectionStart,
 | 
						|
                                         uint32_t aSelectionEnd,
 | 
						|
                                         const Optional<nsAString>& aDirection,
 | 
						|
                                         ErrorResult& aRv,
 | 
						|
                                         ScrollAfterSelection aScroll) {
 | 
						|
  SelectionDirection dir = DirectionStringToSelectionDirection(aDirection);
 | 
						|
 | 
						|
  SetSelectionRange(aSelectionStart, aSelectionEnd, dir, aRv, aScroll);
 | 
						|
  // The instance may have already been deleted here.
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::SetRangeText(const nsAString& aReplacement,
 | 
						|
                                    ErrorResult& aRv) {
 | 
						|
  uint32_t start, end;
 | 
						|
  GetSelectionRange(&start, &end, aRv);
 | 
						|
  if (aRv.Failed()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  SetRangeText(aReplacement, start, end, SelectionMode::Preserve, aRv,
 | 
						|
               Some(start), Some(end));
 | 
						|
  // The instance may have already been deleted here.
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::SetRangeText(const nsAString& aReplacement,
 | 
						|
                                    uint32_t aStart, uint32_t aEnd,
 | 
						|
                                    SelectionMode aSelectMode, ErrorResult& aRv,
 | 
						|
                                    const Maybe<uint32_t>& aSelectionStart,
 | 
						|
                                    const Maybe<uint32_t>& aSelectionEnd) {
 | 
						|
  if (aStart > aEnd) {
 | 
						|
    aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  AutoTextControlHandlingState handlingSetRangeText(
 | 
						|
      *this, TextControlAction::SetRangeText);
 | 
						|
 | 
						|
  nsAutoString value;
 | 
						|
  mTextCtrlElement->GetValueFromSetRangeText(value);
 | 
						|
  uint32_t inputValueLength = value.Length();
 | 
						|
 | 
						|
  if (aStart > inputValueLength) {
 | 
						|
    aStart = inputValueLength;
 | 
						|
  }
 | 
						|
 | 
						|
  if (aEnd > inputValueLength) {
 | 
						|
    aEnd = inputValueLength;
 | 
						|
  }
 | 
						|
 | 
						|
  uint32_t selectionStart, selectionEnd;
 | 
						|
  if (!aSelectionStart) {
 | 
						|
    MOZ_ASSERT(!aSelectionEnd);
 | 
						|
    GetSelectionRange(&selectionStart, &selectionEnd, aRv);
 | 
						|
    if (aRv.Failed()) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    MOZ_ASSERT(aSelectionEnd);
 | 
						|
    selectionStart = *aSelectionStart;
 | 
						|
    selectionEnd = *aSelectionEnd;
 | 
						|
  }
 | 
						|
 | 
						|
  // Batch selectionchanges from SetValueFromSetRangeText and SetSelectionRange
 | 
						|
  Selection* selection =
 | 
						|
      mSelCon ? mSelCon->GetSelection(SelectionType::eNormal) : nullptr;
 | 
						|
  SelectionBatcher selectionBatcher(
 | 
						|
      selection, __FUNCTION__,
 | 
						|
      nsISelectionListener::JS_REASON);  // no-op if nullptr
 | 
						|
 | 
						|
  MOZ_ASSERT(aStart <= aEnd);
 | 
						|
  value.Replace(aStart, aEnd - aStart, aReplacement);
 | 
						|
  nsresult rv =
 | 
						|
      MOZ_KnownLive(mTextCtrlElement)->SetValueFromSetRangeText(value);
 | 
						|
  if (NS_FAILED(rv)) {
 | 
						|
    aRv.Throw(rv);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  uint32_t newEnd = aStart + aReplacement.Length();
 | 
						|
  int32_t delta = aReplacement.Length() - (aEnd - aStart);
 | 
						|
 | 
						|
  switch (aSelectMode) {
 | 
						|
    case SelectionMode::Select:
 | 
						|
      selectionStart = aStart;
 | 
						|
      selectionEnd = newEnd;
 | 
						|
      break;
 | 
						|
    case SelectionMode::Start:
 | 
						|
      selectionStart = selectionEnd = aStart;
 | 
						|
      break;
 | 
						|
    case SelectionMode::End:
 | 
						|
      selectionStart = selectionEnd = newEnd;
 | 
						|
      break;
 | 
						|
    case SelectionMode::Preserve:
 | 
						|
      if (selectionStart > aEnd) {
 | 
						|
        selectionStart += delta;
 | 
						|
      } else if (selectionStart > aStart) {
 | 
						|
        selectionStart = aStart;
 | 
						|
      }
 | 
						|
 | 
						|
      if (selectionEnd > aEnd) {
 | 
						|
        selectionEnd += delta;
 | 
						|
      } else if (selectionEnd > aStart) {
 | 
						|
        selectionEnd = newEnd;
 | 
						|
      }
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      MOZ_ASSERT_UNREACHABLE("Unknown mode!");
 | 
						|
  }
 | 
						|
 | 
						|
  SetSelectionRange(selectionStart, selectionEnd, Optional<nsAString>(), aRv);
 | 
						|
  if (IsSelectionCached()) {
 | 
						|
    // SetValueFromSetRangeText skipped SetMaxLength, set it here properly
 | 
						|
    GetSelectionProperties().SetMaxLength(value.Length());
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::DestroyEditor() {
 | 
						|
  // notify the editor that we are going away
 | 
						|
  if (mEditorInitialized) {
 | 
						|
    // FYI: TextEditor checks whether it's destroyed or not immediately after
 | 
						|
    //      changes the DOM tree or selection so that it's safe to call
 | 
						|
    //      PreDestroy() here even while we're handling actions with
 | 
						|
    //      mTextEditor.
 | 
						|
    MOZ_ASSERT(!mPasswordMaskData);
 | 
						|
    RefPtr<TextEditor> textEditor = mTextEditor;
 | 
						|
    mPasswordMaskData = textEditor->PreDestroy();
 | 
						|
    MOZ_ASSERT_IF(mPasswordMaskData, !mPasswordMaskData->mTimer);
 | 
						|
    mEditorInitialized = false;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::UnbindFromFrame(nsTextControlFrame* aFrame) {
 | 
						|
  if (NS_WARN_IF(!mBoundFrame)) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  // If it was, however, it should be unbounded from the same frame.
 | 
						|
  MOZ_ASSERT(aFrame == mBoundFrame, "Unbinding from the wrong frame");
 | 
						|
  if (aFrame && aFrame != mBoundFrame) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  AutoTextControlHandlingState handlingUnbindFromFrame(
 | 
						|
      *this, TextControlAction::UnbindFromFrame);
 | 
						|
 | 
						|
  if (mSelCon) {
 | 
						|
    mSelCon->SelectionWillLoseFocus();
 | 
						|
  }
 | 
						|
 | 
						|
  // We need to start storing the value outside of the editor if we're not
 | 
						|
  // going to use it anymore, so retrieve it for now.
 | 
						|
  nsAutoString value;
 | 
						|
  GetValue(value, true, /* aForDisplay = */ false);
 | 
						|
 | 
						|
  if (mRestoringSelection) {
 | 
						|
    mRestoringSelection->Revoke();
 | 
						|
    mRestoringSelection = nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  // Save our selection state if needed.
 | 
						|
  // Note that GetSelectionRange will attempt to work with our selection
 | 
						|
  // controller, so we should make sure we do it before we start doing things
 | 
						|
  // like destroying our editor (if we have one), tearing down the selection
 | 
						|
  // controller, and so forth.
 | 
						|
  if (!IsSelectionCached()) {
 | 
						|
    // Go ahead and cache it now.
 | 
						|
    uint32_t start = 0, end = 0;
 | 
						|
    GetSelectionRange(&start, &end, IgnoreErrors());
 | 
						|
 | 
						|
    nsITextControlFrame::SelectionDirection direction =
 | 
						|
        GetSelectionDirection(IgnoreErrors());
 | 
						|
 | 
						|
    SelectionProperties& props = GetSelectionProperties();
 | 
						|
    props.SetMaxLength(value.Length());
 | 
						|
    props.SetStart(start);
 | 
						|
    props.SetEnd(end);
 | 
						|
    props.SetDirection(direction);
 | 
						|
    mSelectionCached = true;
 | 
						|
  }
 | 
						|
 | 
						|
  // Destroy our editor
 | 
						|
  DestroyEditor();
 | 
						|
 | 
						|
  // Clean up the controller
 | 
						|
  if (!SuppressEventHandlers(mBoundFrame->PresContext())) {
 | 
						|
    nsCOMPtr<nsIControllers> controllers;
 | 
						|
    if (auto* inputElement = HTMLInputElement::FromNode(mTextCtrlElement)) {
 | 
						|
      inputElement->GetControllers(getter_AddRefs(controllers));
 | 
						|
    } else {
 | 
						|
      auto* textAreaElement = HTMLTextAreaElement::FromNode(mTextCtrlElement);
 | 
						|
      if (textAreaElement) {
 | 
						|
        textAreaElement->GetControllers(getter_AddRefs(controllers));
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (controllers) {
 | 
						|
      uint32_t numControllers;
 | 
						|
      nsresult rv = controllers->GetControllerCount(&numControllers);
 | 
						|
      NS_ASSERTION((NS_SUCCEEDED(rv)),
 | 
						|
                   "bad result in gfx text control destructor");
 | 
						|
      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(nullptr);
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (mSelCon) {
 | 
						|
    if (mTextListener) {
 | 
						|
      mTextListener->EndListeningToSelectionChange();
 | 
						|
    }
 | 
						|
 | 
						|
    mSelCon->SetScrollContainerFrame(nullptr);
 | 
						|
    mSelCon = nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  if (mTextListener) {
 | 
						|
    mTextListener->SetFrame(nullptr);
 | 
						|
 | 
						|
    EventListenerManager* manager =
 | 
						|
        mTextCtrlElement->GetExistingListenerManager();
 | 
						|
    if (manager) {
 | 
						|
      manager->RemoveEventListenerByType(mTextListener, u"keydown"_ns,
 | 
						|
                                         TrustedEventsAtSystemGroupBubble());
 | 
						|
      manager->RemoveEventListenerByType(mTextListener, u"keypress"_ns,
 | 
						|
                                         TrustedEventsAtSystemGroupBubble());
 | 
						|
      manager->RemoveEventListenerByType(mTextListener, u"keyup"_ns,
 | 
						|
                                         TrustedEventsAtSystemGroupBubble());
 | 
						|
    }
 | 
						|
 | 
						|
    mTextListener = nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  mBoundFrame = nullptr;
 | 
						|
 | 
						|
  // Now that we don't have a frame any more, store the value in the text
 | 
						|
  // buffer. The only case where we don't do this is if a value transfer is in
 | 
						|
  // progress.
 | 
						|
  if (!mValueTransferInProgress) {
 | 
						|
    DebugOnly<bool> ok = SetValue(value, ValueSetterOption::ByInternalAPI);
 | 
						|
    // TODO Find something better to do if this fails...
 | 
						|
    NS_WARNING_ASSERTION(ok, "SetValue() couldn't allocate memory");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::GetValue(nsAString& aValue, bool aIgnoreWrap,
 | 
						|
                                bool aForDisplay) const {
 | 
						|
  // While SetValue() is being called and requesting to commit composition to
 | 
						|
  // IME, GetValue() may be called for appending text or something.  Then, we
 | 
						|
  // need to return the latest aValue of SetValue() since the value hasn't
 | 
						|
  // been set to the editor yet.
 | 
						|
  // XXX After implementing "beforeinput" event, this becomes wrong.  The
 | 
						|
  //     value should be modified immediately after "beforeinput" event for
 | 
						|
  //     "insertReplacementText".
 | 
						|
  if (mHandlingState &&
 | 
						|
      mHandlingState->IsHandling(TextControlAction::CommitComposition)) {
 | 
						|
    aValue = mHandlingState->GetSettingValue();
 | 
						|
    MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (mTextEditor && mBoundFrame &&
 | 
						|
      (mEditorInitialized || !IsSingleLineTextControl())) {
 | 
						|
    if (aIgnoreWrap && !mBoundFrame->CachedValue().IsVoid()) {
 | 
						|
      aValue = mBoundFrame->CachedValue();
 | 
						|
      MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    aValue.Truncate();  // initialize out param
 | 
						|
 | 
						|
    uint32_t flags = (nsIDocumentEncoder::OutputLFLineBreak |
 | 
						|
                      nsIDocumentEncoder::OutputPreformatted |
 | 
						|
                      nsIDocumentEncoder::OutputPersistNBSP |
 | 
						|
                      nsIDocumentEncoder::OutputBodyOnly);
 | 
						|
    if (!aIgnoreWrap) {
 | 
						|
      TextControlElement::nsHTMLTextWrap wrapProp;
 | 
						|
      if (mTextCtrlElement &&
 | 
						|
          TextControlElement::GetWrapPropertyEnum(mTextCtrlElement, wrapProp) &&
 | 
						|
          wrapProp == TextControlElement::eHTMLTextWrap_Hard) {
 | 
						|
        flags |= nsIDocumentEncoder::OutputWrap;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // What follows is a bit of a hack.  The problem is that we could be in
 | 
						|
    // this method because we're being destroyed for whatever reason while
 | 
						|
    // script is executing.  If that happens, editor will run with the
 | 
						|
    // privileges of the executing script, which means it may not be able to
 | 
						|
    // access its own DOM nodes!  Let's try to deal with that by pushing a null
 | 
						|
    // JSContext on the JSContext stack to 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.
 | 
						|
    // XXXbz if we could just get the textContent of our anonymous content (eg
 | 
						|
    // if plaintext editor didn't create <br> nodes all over), we wouldn't need
 | 
						|
    // this.
 | 
						|
    { /* Scope for AutoNoJSAPI. */
 | 
						|
      AutoNoJSAPI nojsapi;
 | 
						|
 | 
						|
      DebugOnly<nsresult> rv = mTextEditor->ComputeTextValue(flags, aValue);
 | 
						|
      MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
 | 
						|
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Failed to get value");
 | 
						|
    }
 | 
						|
    // Only when the result doesn't include line breaks caused by hard-wrap,
 | 
						|
    // mCacheValue should cache the value.
 | 
						|
    if (!(flags & nsIDocumentEncoder::OutputWrap)) {
 | 
						|
      mBoundFrame->CacheValue(aValue);
 | 
						|
    } else {
 | 
						|
      mBoundFrame->ClearCachedValue();
 | 
						|
    }
 | 
						|
  } else if (!mTextCtrlElement->ValueChanged() || mValue.IsVoid()) {
 | 
						|
    // Use nsString to avoid copying string buffer at setting aValue.
 | 
						|
    nsString value;
 | 
						|
    mTextCtrlElement->GetDefaultValueFromContent(value, aForDisplay);
 | 
						|
    // TODO: We should make default value not include \r.
 | 
						|
    nsContentUtils::PlatformToDOMLineBreaks(value);
 | 
						|
    aValue = std::move(value);
 | 
						|
  } else {
 | 
						|
    aValue = mValue;
 | 
						|
    MOZ_ASSERT(aValue.FindChar(u'\r') == -1);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool TextControlState::ValueEquals(const nsAString& aValue) const {
 | 
						|
  nsAutoString value;
 | 
						|
  GetValue(value, true, /* aForDisplay = */ true);
 | 
						|
  return aValue.Equals(value);
 | 
						|
}
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
// @param aOptions TextControlState::ValueSetterOptions
 | 
						|
bool AreFlagsNotDemandingContradictingMovements(
 | 
						|
    const ValueSetterOptions& aOptions) {
 | 
						|
  return !aOptions.contains(
 | 
						|
      {ValueSetterOption::MoveCursorToBeginSetSelectionDirectionForward,
 | 
						|
       ValueSetterOption::MoveCursorToEndIfValueChanged});
 | 
						|
}
 | 
						|
#endif  // DEBUG
 | 
						|
 | 
						|
bool TextControlState::SetValue(const nsAString& aValue,
 | 
						|
                                const nsAString* aOldValue,
 | 
						|
                                const ValueSetterOptions& aOptions) {
 | 
						|
  if (mHandlingState &&
 | 
						|
      mHandlingState->IsHandling(TextControlAction::CommitComposition)) {
 | 
						|
    // GetValue doesn't return current text frame's content during committing.
 | 
						|
    // So we cannot trust this old value
 | 
						|
    aOldValue = nullptr;
 | 
						|
  }
 | 
						|
 | 
						|
  if (mPasswordMaskData) {
 | 
						|
    if (mHandlingState &&
 | 
						|
        mHandlingState->Is(TextControlAction::UnbindFromFrame)) {
 | 
						|
      // If we're called by UnbindFromFrame, we shouldn't reset unmasked range.
 | 
						|
    } else {
 | 
						|
      // Otherwise, we should mask the new password, even if it's same value
 | 
						|
      // since the same value may be one for different web app's.
 | 
						|
      mPasswordMaskData->Reset();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  const bool wasHandlingSetValue =
 | 
						|
      mHandlingState && mHandlingState->IsHandling(TextControlAction::SetValue);
 | 
						|
 | 
						|
  ErrorResult error;
 | 
						|
  AutoTextControlHandlingState handlingSetValue(
 | 
						|
      *this, TextControlAction::SetValue, aValue, aOldValue, aOptions, error);
 | 
						|
  if (error.Failed()) {
 | 
						|
    MOZ_ASSERT(error.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY));
 | 
						|
    error.SuppressException();
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  const auto changeKind = [&] {
 | 
						|
    if (aOptions.contains(ValueSetterOption::ByInternalAPI)) {
 | 
						|
      return ValueChangeKind::Internal;
 | 
						|
    }
 | 
						|
    if (aOptions.contains(ValueSetterOption::BySetUserInputAPI)) {
 | 
						|
      return ValueChangeKind::UserInteraction;
 | 
						|
    }
 | 
						|
    return ValueChangeKind::Script;
 | 
						|
  }();
 | 
						|
 | 
						|
  if (changeKind == ValueChangeKind::Script) {
 | 
						|
    // This value change will not be interactive. If we're an input that was
 | 
						|
    // interactively edited, save the last interactive value now before it goes
 | 
						|
    // away.
 | 
						|
    if (auto* input = HTMLInputElement::FromNode(mTextCtrlElement)) {
 | 
						|
      if (input->LastValueChangeWasInteractive()) {
 | 
						|
        GetValue(mLastInteractiveValue, /* aIgnoreWrap = */ true,
 | 
						|
                 /* aForDisplay = */ true);
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // Note that if this may be called during reframe of the editor.  In such
 | 
						|
  // case, we shouldn't commit composition.  Therefore, when this is called
 | 
						|
  // for internal processing, we shouldn't commit the composition.
 | 
						|
  // TODO: In strictly speaking, we should move committing composition into
 | 
						|
  //       editor because if "beforeinput" for this setting value is canceled,
 | 
						|
  //       we shouldn't commit composition.  However, in Firefox, we never
 | 
						|
  //       call this via `setUserInput` during composition.  Therefore, the
 | 
						|
  //       bug must not be reproducible actually.
 | 
						|
  if (aOptions.contains(ValueSetterOption::BySetUserInputAPI) ||
 | 
						|
      aOptions.contains(ValueSetterOption::ByContentAPI)) {
 | 
						|
    if (EditorHasComposition()) {
 | 
						|
      // When this is called recursively, there shouldn't be composition.
 | 
						|
      if (handlingSetValue.IsHandling(TextControlAction::CommitComposition)) {
 | 
						|
        // Don't request to commit composition again.  But if it occurs,
 | 
						|
        // we should skip to set the new value to the editor here.  It should
 | 
						|
        // be set later with the newest value.
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
      if (NS_WARN_IF(!mBoundFrame)) {
 | 
						|
        // We're not sure if this case is possible.
 | 
						|
      } else {
 | 
						|
        // If setting value won't change current value, we shouldn't commit
 | 
						|
        // composition for compatibility with the other browsers.
 | 
						|
        MOZ_ASSERT(!aOldValue || ValueEquals(*aOldValue));
 | 
						|
        bool isSameAsCurrentValue =
 | 
						|
            aOldValue ? aOldValue->Equals(handlingSetValue.GetSettingValue())
 | 
						|
                      : ValueEquals(handlingSetValue.GetSettingValue());
 | 
						|
        if (isSameAsCurrentValue) {
 | 
						|
          // Note that in this case, we shouldn't fire any events with setting
 | 
						|
          // value because event handlers may try to set value recursively but
 | 
						|
          // we cannot commit composition at that time due to unsafe to run
 | 
						|
          // script (see below).
 | 
						|
          return true;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      // If there is composition, need to commit composition first because
 | 
						|
      // other browsers do that.
 | 
						|
      // NOTE: We don't need to block nested calls of this because input nor
 | 
						|
      //       other events won't be fired by setting values and script blocker
 | 
						|
      //       is used during setting the value to the editor.  IE also allows
 | 
						|
      //       to set the editor value on the input event which is caused by
 | 
						|
      //       forcibly committing composition.
 | 
						|
      AutoTextControlHandlingState handlingCommitComposition(
 | 
						|
          *this, TextControlAction::CommitComposition);
 | 
						|
      if (nsContentUtils::IsSafeToRunScript()) {
 | 
						|
        // WARNING: During this call, compositionupdate, compositionend, input
 | 
						|
        // events will be fired.  Therefore, everything can occur.  E.g., the
 | 
						|
        // document may be unloaded.
 | 
						|
        RefPtr<TextEditor> textEditor = mTextEditor;
 | 
						|
        nsresult rv = textEditor->CommitComposition();
 | 
						|
        if (handlingCommitComposition.IsTextControlStateDestroyed()) {
 | 
						|
          return true;
 | 
						|
        }
 | 
						|
        if (NS_FAILED(rv)) {
 | 
						|
          NS_WARNING("TextControlState failed to commit composition");
 | 
						|
          return true;
 | 
						|
        }
 | 
						|
        // Note that if a composition event listener sets editor value again,
 | 
						|
        // we should use the new value here.  The new value is stored in
 | 
						|
        // handlingSetValue right now.
 | 
						|
      } else {
 | 
						|
        NS_WARNING(
 | 
						|
            "SetValue() is called when there is composition but "
 | 
						|
            "it's not safe to request to commit the composition");
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (mTextEditor && mBoundFrame) {
 | 
						|
    if (!SetValueWithTextEditor(handlingSetValue)) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
  } else if (!SetValueWithoutTextEditor(handlingSetValue)) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  // If we were handling SetValue() before, don't update the DOM state twice,
 | 
						|
  // just let the outer call do so.
 | 
						|
  if (!wasHandlingSetValue) {
 | 
						|
    handlingSetValue.GetTextControlElement()->OnValueChanged(
 | 
						|
        changeKind, handlingSetValue.GetSettingValue());
 | 
						|
  }
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
bool TextControlState::SetValueWithTextEditor(
 | 
						|
    AutoTextControlHandlingState& aHandlingSetValue) {
 | 
						|
  MOZ_ASSERT(aHandlingSetValue.Is(TextControlAction::SetValue));
 | 
						|
  MOZ_ASSERT(mTextEditor);
 | 
						|
  MOZ_ASSERT(mBoundFrame);
 | 
						|
  NS_WARNING_ASSERTION(!EditorHasComposition(),
 | 
						|
                       "Failed to commit composition before setting value.  "
 | 
						|
                       "Investigate the cause!");
 | 
						|
 | 
						|
#ifdef DEBUG
 | 
						|
  if (IsSingleLineTextControl()) {
 | 
						|
    NS_ASSERTION(mEditorInitialized || aHandlingSetValue.IsHandling(
 | 
						|
                                           TextControlAction::PrepareEditor),
 | 
						|
                 "We should never try to use the editor if we're not "
 | 
						|
                 "initialized unless we're being initialized");
 | 
						|
  }
 | 
						|
#endif
 | 
						|
 | 
						|
  MOZ_ASSERT(!aHandlingSetValue.GetOldValue() ||
 | 
						|
             ValueEquals(*aHandlingSetValue.GetOldValue()));
 | 
						|
  const bool isSameAsCurrentValue =
 | 
						|
      aHandlingSetValue.GetOldValue()
 | 
						|
          ? aHandlingSetValue.GetOldValue()->Equals(
 | 
						|
                aHandlingSetValue.GetSettingValue())
 | 
						|
          : ValueEquals(aHandlingSetValue.GetSettingValue());
 | 
						|
 | 
						|
  // this is necessary to avoid infinite recursion
 | 
						|
  if (isSameAsCurrentValue) {
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  RefPtr<TextEditor> textEditor = mTextEditor;
 | 
						|
 | 
						|
  nsCOMPtr<Document> document = textEditor->GetDocument();
 | 
						|
  if (NS_WARN_IF(!document)) {
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  // Time to mess with our security context... See comments in GetValue()
 | 
						|
  // for why this is needed.  Note that we have to do this up here, because
 | 
						|
  // otherwise SelectAll() will fail.
 | 
						|
  AutoNoJSAPI nojsapi;
 | 
						|
 | 
						|
  // FYI: It's safe to use raw pointer for selection here because
 | 
						|
  //      SelectionBatcher will grab it with RefPtr.
 | 
						|
  Selection* selection = mSelCon->GetSelection(SelectionType::eNormal);
 | 
						|
  SelectionBatcher selectionBatcher(selection, __FUNCTION__);
 | 
						|
 | 
						|
  // get the flags, remove readonly, disabled and max-length,
 | 
						|
  // set the value, restore flags
 | 
						|
  AutoRestoreEditorState restoreState(textEditor);
 | 
						|
 | 
						|
  aHandlingSetValue.WillSetValueWithTextEditor();
 | 
						|
 | 
						|
  if (aHandlingSetValue.ValueSetterOptionsRef().contains(
 | 
						|
          ValueSetterOption::BySetUserInputAPI)) {
 | 
						|
    // If the caller inserts text as part of user input, for example,
 | 
						|
    // autocomplete, we need to replace the text as "insert string"
 | 
						|
    // because undo should cancel only this operation (i.e., previous
 | 
						|
    // transactions typed by user shouldn't be merged with this).
 | 
						|
    // In this case, we need to dispatch "input" event because
 | 
						|
    // web apps may need to know the user's operation.
 | 
						|
    // In this case, we need to dispatch "beforeinput" events since
 | 
						|
    // we're emulating the user's input.  Passing nullptr as
 | 
						|
    // nsIPrincipal means that that may be user's input.  So, let's
 | 
						|
    // do it.
 | 
						|
    nsresult rv = textEditor->ReplaceTextAsAction(
 | 
						|
        aHandlingSetValue.GetSettingValue(), nullptr,
 | 
						|
        StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
 | 
						|
            ? TextEditor::AllowBeforeInputEventCancelable::Yes
 | 
						|
            : TextEditor::AllowBeforeInputEventCancelable::No,
 | 
						|
        nullptr);
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                         "EditorBase::ReplaceTextAsAction() failed");
 | 
						|
    return rv != NS_ERROR_OUT_OF_MEMORY;
 | 
						|
  }
 | 
						|
 | 
						|
  // Don't dispatch "beforeinput" event nor "input" event for setting value
 | 
						|
  // by script.
 | 
						|
  AutoInputEventSuppresser suppressInputEventDispatching(textEditor);
 | 
						|
 | 
						|
  // On <input> or <textarea>, we shouldn't preserve existing undo
 | 
						|
  // transactions because other browsers do not preserve them too
 | 
						|
  // and not preserving transactions makes setting value faster.
 | 
						|
  //
 | 
						|
  // (Except if chrome opts into this behavior).
 | 
						|
  Maybe<AutoDisableUndo> disableUndo;
 | 
						|
  if (!aHandlingSetValue.ValueSetterOptionsRef().contains(
 | 
						|
          ValueSetterOption::PreserveUndoHistory)) {
 | 
						|
    disableUndo.emplace(textEditor);
 | 
						|
  }
 | 
						|
 | 
						|
  if (selection) {
 | 
						|
    // Since we don't use undo transaction, we don't need to store
 | 
						|
    // selection state.  SetText will set selection to tail.
 | 
						|
    IgnoredErrorResult ignoredError;
 | 
						|
    MOZ_KnownLive(selection)->RemoveAllRanges(ignoredError);
 | 
						|
    NS_WARNING_ASSERTION(!ignoredError.Failed(),
 | 
						|
                         "Selection::RemoveAllRanges() failed, but ignored");
 | 
						|
  }
 | 
						|
 | 
						|
  // In this case, we makes the editor stop dispatching "input"
 | 
						|
  // event so that passing nullptr as nsIPrincipal is safe for now.
 | 
						|
  nsresult rv = textEditor->SetTextAsAction(
 | 
						|
      aHandlingSetValue.GetSettingValue(),
 | 
						|
      aHandlingSetValue.ValueSetterOptionsRef().contains(
 | 
						|
          ValueSetterOption::BySetUserInputAPI) &&
 | 
						|
              !StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
 | 
						|
          ? TextEditor::AllowBeforeInputEventCancelable::No
 | 
						|
          : TextEditor::AllowBeforeInputEventCancelable::Yes,
 | 
						|
      nullptr);
 | 
						|
  NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
 | 
						|
                       "TextEditor::SetTextAsAction() failed");
 | 
						|
 | 
						|
  // Call the listener's OnEditActionHandled() callback manually if
 | 
						|
  // OnEditActionHandled() hasn't been called yet since TextEditor don't use
 | 
						|
  // the transaction manager in this path and it could be that the editor
 | 
						|
  // would bypass calling the listener for that reason.
 | 
						|
  if (!aHandlingSetValue.HasEditActionHandled()) {
 | 
						|
    nsresult rvOnEditActionHandled =
 | 
						|
        MOZ_KnownLive(aHandlingSetValue.GetTextInputListener())
 | 
						|
            ->OnEditActionHandled(*textEditor);
 | 
						|
    NS_WARNING_ASSERTION(NS_SUCCEEDED(rvOnEditActionHandled),
 | 
						|
                         "TextInputListener::OnEditActionHandled() failed");
 | 
						|
    if (rv != NS_ERROR_OUT_OF_MEMORY) {
 | 
						|
      rv = rvOnEditActionHandled;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // When the <textarea> is not dirty, the default value is mirrored into the
 | 
						|
  // anonymous subtree asynchronously.  This may occur during a reframe.
 | 
						|
  // Therefore, if IMEContentObserver was initialized with our editor but our
 | 
						|
  // editor is being initialized, it has not been observing the new anonymous
 | 
						|
  // subtree.  In this case, we need to notify IMEContentObserver of the default
 | 
						|
  // value change.
 | 
						|
  if (mTextCtrlElement && mTextCtrlElement->IsTextArea() &&
 | 
						|
      !mTextCtrlElement->ValueChanged() && textEditor->IsBeingInitialized() &&
 | 
						|
      !textEditor->Destroyed()) {
 | 
						|
    IMEContentObserver* observer = GetIMEContentObserver();
 | 
						|
    if (observer && observer->WasInitializedWith(*textEditor)) {
 | 
						|
      nsAutoString currentValue;
 | 
						|
      textEditor->ComputeTextValue(0, currentValue);
 | 
						|
      observer->OnTextControlValueChangedWhileNotObservable(currentValue);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return rv != NS_ERROR_OUT_OF_MEMORY;
 | 
						|
}
 | 
						|
 | 
						|
bool TextControlState::SetValueWithoutTextEditor(
 | 
						|
    AutoTextControlHandlingState& aHandlingSetValue) {
 | 
						|
  MOZ_ASSERT(aHandlingSetValue.Is(TextControlAction::SetValue));
 | 
						|
  MOZ_ASSERT(!mTextEditor || !mBoundFrame);
 | 
						|
  NS_WARNING_ASSERTION(!EditorHasComposition(),
 | 
						|
                       "Failed to commit composition before setting value.  "
 | 
						|
                       "Investigate the cause!");
 | 
						|
 | 
						|
  if (mValue.IsVoid()) {
 | 
						|
    mValue.SetIsVoid(false);
 | 
						|
  }
 | 
						|
 | 
						|
  // We can't just early-return here, because OnValueChanged below still need to
 | 
						|
  // be called.
 | 
						|
  if (!mValue.Equals(aHandlingSetValue.GetSettingValue())) {
 | 
						|
    bool handleSettingValue = true;
 | 
						|
    // If `SetValue()` call is nested, `GetSettingValue()` result will be
 | 
						|
    // modified.  So, we need to store input event data value before
 | 
						|
    // dispatching beforeinput event.
 | 
						|
    nsString inputEventData(aHandlingSetValue.GetSettingValue());
 | 
						|
    if (aHandlingSetValue.ValueSetterOptionsRef().contains(
 | 
						|
            ValueSetterOption::BySetUserInputAPI) &&
 | 
						|
        !aHandlingSetValue.HasBeforeInputEventDispatched()) {
 | 
						|
      // This probably occurs when session restorer sets the old value with
 | 
						|
      // `setUserInput`.  If so, we need to dispatch "beforeinput" event of
 | 
						|
      // "insertReplacementText" for conforming to the spec.  However, the
 | 
						|
      // spec does NOT treat the session restoring case.  Therefore, if this
 | 
						|
      // breaks session restorere in a lot of web apps, we should probably
 | 
						|
      // stop dispatching it or make it non-cancelable.
 | 
						|
      MOZ_ASSERT(aHandlingSetValue.GetTextControlElement());
 | 
						|
      MOZ_ASSERT(!aHandlingSetValue.GetSettingValue().IsVoid());
 | 
						|
      aHandlingSetValue.WillDispatchBeforeInputEvent();
 | 
						|
      nsEventStatus status = nsEventStatus_eIgnore;
 | 
						|
      DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
 | 
						|
          MOZ_KnownLive(aHandlingSetValue.GetTextControlElement()),
 | 
						|
          eEditorBeforeInput, EditorInputType::eInsertReplacementText, nullptr,
 | 
						|
          InputEventOptions(
 | 
						|
              inputEventData,
 | 
						|
              StaticPrefs::dom_input_event_allow_to_cancel_set_user_input()
 | 
						|
                  ? InputEventOptions::NeverCancelable::No
 | 
						|
                  : InputEventOptions::NeverCancelable::Yes),
 | 
						|
          &status);
 | 
						|
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
 | 
						|
                           "Failed to dispatch beforeinput event");
 | 
						|
      if (status == nsEventStatus_eConsumeNoDefault) {
 | 
						|
        return true;  // "beforeinput" event was canceled.
 | 
						|
      }
 | 
						|
      // If we were destroyed by "beforeinput" event listeners, probably, we
 | 
						|
      // don't need to keep handling it.
 | 
						|
      if (aHandlingSetValue.IsTextControlStateDestroyed()) {
 | 
						|
        return true;
 | 
						|
      }
 | 
						|
      // Even if "beforeinput" event was not canceled, its listeners may do
 | 
						|
      // something.  If it causes creating `TextEditor` and bind this to a
 | 
						|
      // frame, we need to use the path, but `TextEditor` shouldn't fire
 | 
						|
      // "beforeinput" event again.  Therefore, we need to prevent editor
 | 
						|
      // to dispatch it.
 | 
						|
      if (mTextEditor && mBoundFrame) {
 | 
						|
        AutoInputEventSuppresser suppressInputEvent(mTextEditor);
 | 
						|
        if (!SetValueWithTextEditor(aHandlingSetValue)) {
 | 
						|
          return false;
 | 
						|
        }
 | 
						|
        // If we were destroyed by "beforeinput" event listeners, probably, we
 | 
						|
        // don't need to keep handling it.
 | 
						|
        if (aHandlingSetValue.IsTextControlStateDestroyed()) {
 | 
						|
          return true;
 | 
						|
        }
 | 
						|
        handleSettingValue = false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (handleSettingValue) {
 | 
						|
      if (!mValue.Assign(aHandlingSetValue.GetSettingValue(), fallible)) {
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
 | 
						|
      // Since we have no editor we presumably have cached selection state.
 | 
						|
      if (IsSelectionCached()) {
 | 
						|
        MOZ_ASSERT(AreFlagsNotDemandingContradictingMovements(
 | 
						|
            aHandlingSetValue.ValueSetterOptionsRef()));
 | 
						|
 | 
						|
        SelectionProperties& props = GetSelectionProperties();
 | 
						|
        // Setting a max length and thus capping selection range early prevents
 | 
						|
        // selection change detection in setRangeText. Temporarily disable
 | 
						|
        // capping here with UINT32_MAX, and set it later in ::SetRangeText().
 | 
						|
        props.SetMaxLength(aHandlingSetValue.ValueSetterOptionsRef().contains(
 | 
						|
                               ValueSetterOption::BySetRangeTextAPI)
 | 
						|
                               ? UINT32_MAX
 | 
						|
                               : aHandlingSetValue.GetSettingValue().Length());
 | 
						|
        if (aHandlingSetValue.ValueSetterOptionsRef().contains(
 | 
						|
                ValueSetterOption::MoveCursorToEndIfValueChanged)) {
 | 
						|
          props.SetStart(aHandlingSetValue.GetSettingValue().Length());
 | 
						|
          props.SetEnd(aHandlingSetValue.GetSettingValue().Length());
 | 
						|
          props.SetDirection(SelectionDirection::Forward);
 | 
						|
        } else if (aHandlingSetValue.ValueSetterOptionsRef().contains(
 | 
						|
                       ValueSetterOption::
 | 
						|
                           MoveCursorToBeginSetSelectionDirectionForward)) {
 | 
						|
          props.SetStart(0);
 | 
						|
          props.SetEnd(0);
 | 
						|
          props.SetDirection(SelectionDirection::Forward);
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // Update the frame display if needed
 | 
						|
      if (mBoundFrame) {
 | 
						|
        mBoundFrame->UpdateValueDisplay(true);
 | 
						|
      }
 | 
						|
 | 
						|
      // If the text control element has focus, IMEContentObserver is not
 | 
						|
      // observing the content changes due to no bound frame or no TextEditor.
 | 
						|
      // Therefore, we need to let IMEContentObserver know all values are being
 | 
						|
      // replaced.
 | 
						|
      if (IMEContentObserver* observer = GetIMEContentObserver()) {
 | 
						|
        observer->OnTextControlValueChangedWhileNotObservable(mValue);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // If this is called as part of user input, we need to dispatch "input"
 | 
						|
    // event with "insertReplacementText" since web apps may want to know
 | 
						|
    // the user operation which changes editor value with a built-in function
 | 
						|
    // like autocomplete, password manager, session restore, etc.
 | 
						|
    // XXX Should we stop dispatching `input` event if the text control
 | 
						|
    //     element has already removed from the DOM tree by a `beforeinput`
 | 
						|
    //     event listener?
 | 
						|
    if (aHandlingSetValue.ValueSetterOptionsRef().contains(
 | 
						|
            ValueSetterOption::BySetUserInputAPI)) {
 | 
						|
      MOZ_ASSERT(aHandlingSetValue.GetTextControlElement());
 | 
						|
 | 
						|
      // Update validity state before dispatching "input" event for its
 | 
						|
      // listeners like `EditorBase::NotifyEditorObservers()`.
 | 
						|
      aHandlingSetValue.GetTextControlElement()->OnValueChanged(
 | 
						|
          ValueChangeKind::UserInteraction,
 | 
						|
          aHandlingSetValue.GetSettingValue());
 | 
						|
 | 
						|
      ClearLastInteractiveValue();
 | 
						|
 | 
						|
      MOZ_ASSERT(!aHandlingSetValue.GetSettingValue().IsVoid());
 | 
						|
      DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
 | 
						|
          MOZ_KnownLive(aHandlingSetValue.GetTextControlElement()),
 | 
						|
          eEditorInput, EditorInputType::eInsertReplacementText, nullptr,
 | 
						|
          InputEventOptions(inputEventData,
 | 
						|
                            InputEventOptions::NeverCancelable::No));
 | 
						|
      NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
 | 
						|
                           "Failed to dispatch input event");
 | 
						|
    }
 | 
						|
  } else {
 | 
						|
    // Even if our value is not actually changing, apparently we need to mark
 | 
						|
    // our SelectionProperties dirty to make accessibility tests happy.
 | 
						|
    // Probably because they depend on the SetSelectionRange() call we make on
 | 
						|
    // our frame in RestoreSelectionState, but I have no idea why they do.
 | 
						|
    if (IsSelectionCached()) {
 | 
						|
      SelectionProperties& props = GetSelectionProperties();
 | 
						|
      props.SetIsDirty();
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::InitializeKeyboardEventListeners() {
 | 
						|
  // register key listeners
 | 
						|
  EventListenerManager* manager =
 | 
						|
      mTextCtrlElement->GetOrCreateListenerManager();
 | 
						|
  if (manager) {
 | 
						|
    manager->AddEventListenerByType(mTextListener, u"keydown"_ns,
 | 
						|
                                    TrustedEventsAtSystemGroupBubble());
 | 
						|
    manager->AddEventListenerByType(mTextListener, u"keypress"_ns,
 | 
						|
                                    TrustedEventsAtSystemGroupBubble());
 | 
						|
    manager->AddEventListenerByType(mTextListener, u"keyup"_ns,
 | 
						|
                                    TrustedEventsAtSystemGroupBubble());
 | 
						|
  }
 | 
						|
 | 
						|
  mSelCon->SetScrollContainerFrame(mBoundFrame->GetScrollTargetFrame());
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::SetPreviewText(const nsAString& aValue, bool aNotify) {
 | 
						|
  // If we don't have a preview div, there's nothing to do.
 | 
						|
  Element* previewDiv = GetPreviewNode();
 | 
						|
  if (!previewDiv) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  nsAutoString previewValue(aValue);
 | 
						|
 | 
						|
  nsContentUtils::RemoveNewlines(previewValue);
 | 
						|
  MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
 | 
						|
  previewDiv->GetFirstChild()->AsText()->SetText(previewValue, aNotify);
 | 
						|
}
 | 
						|
 | 
						|
void TextControlState::GetPreviewText(nsAString& aValue) {
 | 
						|
  // If we don't have a preview div, there's nothing to do.
 | 
						|
  Element* previewDiv = GetPreviewNode();
 | 
						|
  if (!previewDiv) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  MOZ_ASSERT(previewDiv->GetFirstChild(), "preview div has no child");
 | 
						|
  const nsTextFragment* text = previewDiv->GetFirstChild()->GetText();
 | 
						|
 | 
						|
  aValue.Truncate();
 | 
						|
  text->AppendTo(aValue);
 | 
						|
}
 | 
						|
 | 
						|
bool TextControlState::EditorHasComposition() {
 | 
						|
  return mTextEditor && mTextEditor->IsIMEComposing();
 | 
						|
}
 | 
						|
 | 
						|
IMEContentObserver* TextControlState::GetIMEContentObserver() const {
 | 
						|
  if (NS_WARN_IF(!mTextCtrlElement) ||
 | 
						|
      mTextCtrlElement != IMEStateManager::GetFocusedElement()) {
 | 
						|
    return nullptr;
 | 
						|
  }
 | 
						|
  IMEContentObserver* observer = IMEStateManager::GetActiveContentObserver();
 | 
						|
  // The text control element may be an editing host.  In this case, the
 | 
						|
  // observer does not observe the anonymous nodes under mTextCtrlElement.
 | 
						|
  // So, it means that the observer is not for ours.
 | 
						|
  return observer && observer->EditorIsTextEditor() ? observer : nullptr;
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace mozilla
 |