Bug 1588745 - part 2: Move some TextControlState members to stack only class r=Ehsan

Some members of `TextControlState` are initialized and restored in same block
scopes.  Therefore, with creating new stack only class and storing latest one
with a new member variable, we can store all of them in the stack.

Depends on D51391

Differential Revision: https://phabricator.services.mozilla.com/D51392
This commit is contained in:
Masayuki Nakano
2019-11-01 20:47:35 +00:00
parent 43df83b332
commit 628cfe7154
2 changed files with 180 additions and 88 deletions

View File

@@ -1058,6 +1058,111 @@ nsresult TextInputListener::UpdateTextInputCommands(
return NS_OK;
}
/*****************************************************************************
* mozilla::AutoTextControlHandlingState
*
* This class is temporarily created in the stack and can manage nested
* handling state of TextControlState.
*****************************************************************************/
enum class TextControlAction {
PrepareEditor,
CommitComposition,
SetValue,
};
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.
*/
AutoTextControlHandlingState(TextControlState& aTextControlState,
TextControlAction aTextControlAction)
: mParent(aTextControlState.mHandlingState),
mTextControlState(aTextControlState),
mTextControlAction(aTextControlAction) {
MOZ_ASSERT(aTextControlAction != TextControlAction::SetValue,
"Use specific constructor");
mTextControlState.mHandlingState = this;
}
/**
* 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.
*/
AutoTextControlHandlingState(TextControlState& aTextControlState,
TextControlAction aTextControlAction,
const nsAString& aSettingValue, ErrorResult& aRv)
: mParent(aTextControlState.mHandlingState),
mTextControlState(aTextControlState),
mTextControlAction(aTextControlAction) {
MOZ_ASSERT(aTextControlAction == TextControlAction::SetValue,
"Use generic constructor");
mTextControlState.mHandlingState = this;
// FYI: Using nsString may avoid copying the string buffer.
nsString settingValue(aSettingValue);
if (!nsContentUtils::PlatformToDOMLineBreaks(settingValue, fallible)) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
// Update all setting value's new value because older value shouldn't
// overwrite newer value.
UpdateSettingValue(settingValue);
}
~AutoTextControlHandlingState() {
if (!mTextControlStateDestroyed) {
mTextControlState.mHandlingState = mParent;
}
}
void OnDestroyTextControlState() {
mTextControlStateDestroyed = true;
if (mParent) {
mParent->OnDestroyTextControlState();
}
}
bool IsHandling(TextControlAction aTextControlAction) const {
if (mTextControlAction == aTextControlAction) {
return true;
}
return mParent ? mParent->IsHandling(aTextControlAction) : false;
}
const nsString& GetSettingValue() const {
MOZ_ASSERT(IsHandling(TextControlAction::SetValue));
if (mTextControlAction == TextControlAction::SetValue) {
return mSettingValue;
}
return mParent->GetSettingValue();
}
private:
void UpdateSettingValue(const nsString& aSettingValue) {
if (mTextControlAction == TextControlAction::SetValue) {
mSettingValue = aSettingValue;
}
if (mParent) {
mParent->UpdateSettingValue(aSettingValue);
}
}
AutoTextControlHandlingState* mParent;
TextControlState& mTextControlState;
nsString mSettingValue;
TextControlAction mTextControlAction;
bool mTextControlStateDestroyed = false;
};
/*****************************************************************************
* mozilla::TextControlState
*****************************************************************************/
@@ -1067,13 +1172,11 @@ TextControlState::TextControlState(nsITextControlElement* aOwningElement)
mBoundFrame(nullptr),
mEverInited(false),
mEditorInitialized(false),
mInitializing(false),
mValueTransferInProgress(false),
mSelectionCached(true),
mSelectionRestoreEagerInit(false),
mPlaceholderVisibility(false),
mPreviewVisibility(false),
mIsCommittingComposition(false)
mPreviewVisibility(false)
// When adding more member variable initializations here, add the same
// also to ::Construct.
{
@@ -1084,7 +1187,7 @@ TextControlState::TextControlState(nsITextControlElement* aOwningElement)
TextControlState* TextControlState::Construct(
nsITextControlElement* aOwningElement, TextControlState** aReusedState) {
if (*aReusedState) {
if (*aReusedState && !(*aReusedState)->IsBusy()) {
TextControlState* state = *aReusedState;
*aReusedState = nullptr;
state->mTextCtrlElement = aOwningElement;
@@ -1092,13 +1195,11 @@ TextControlState* TextControlState::Construct(
state->mSelectionProperties = SelectionProperties();
state->mEverInited = false;
state->mEditorInitialized = false;
state->mInitializing = false;
state->mValueTransferInProgress = false;
state->mSelectionCached = true;
state->mSelectionRestoreEagerInit = false;
state->mPlaceholderVisibility = false;
state->mPreviewVisibility = false;
state->mIsCommittingComposition = false;
// When adding more member variable initializations here, add the same
// also to the constructor.
return state;
@@ -1125,6 +1226,11 @@ void TextControlState::Clear() {
mTextEditor->SetTextInputListener(nullptr);
}
if (mHandlingState) {
mHandlingState->OnDestroyTextControlState();
mHandlingState = nullptr;
}
if (mBoundFrame) {
// Oops, we still have a frame!
// This should happen when the type of a text input control is being changed
@@ -1316,10 +1422,12 @@ nsresult TextControlState::PrepareEditor(const nsAString* aValue) {
AutoHideSelectionChanges hideSelectionChanges(GetConstFrameSelection());
// Don't attempt to initialize recursively!
InitializationGuard guard(*this);
if (guard.IsInitializingRecursively()) {
if (mHandlingState &&
mHandlingState->IsHandling(TextControlAction::PrepareEditor)) {
return NS_ERROR_NOT_INITIALIZED;
}
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
@@ -2146,8 +2254,12 @@ void TextControlState::GetValue(nsAString& aValue, bool aIgnoreWrap) const {
// 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.
if (mIsCommittingComposition) {
aValue = mValueBeingSet;
// 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();
return;
}
@@ -2221,17 +2333,16 @@ bool AreFlagsNotDemandingContradictingMovements(uint32_t aFlags) {
bool TextControlState::SetValue(const nsAString& aValue,
const nsAString* aOldValue, uint32_t aFlags) {
nsAutoString newValue(aValue);
ErrorResult error;
AutoTextControlHandlingState handlingSetValue(
*this, TextControlAction::SetValue, aValue, error);
if (error.Failed()) {
MOZ_ASSERT(error.ErrorCodeIs(NS_ERROR_OUT_OF_MEMORY));
error.SuppressException();
return false;
}
// While mIsCommittingComposition is true (that means that some event
// handlers which are fired during committing composition are the caller of
// this method), GetValue() uses mValueBeingSet for its result because the
// first calls of this methods hasn't set the value yet. So, when it's true,
// we need to modify mValueBeingSet. In this case, we will back to the first
// call of this method, then, mValueBeingSet will be truncated when
// mIsCommittingComposition is set false. See below.
if (mIsCommittingComposition) {
mValueBeingSet = aValue;
if (handlingSetValue.IsHandling(TextControlAction::CommitComposition)) {
// GetValue doesn't return current text frame's content during committing.
// So we cannot trust this old value
aOldValue = nullptr;
@@ -2243,10 +2354,11 @@ bool TextControlState::SetValue(const nsAString& aValue,
if (aFlags & (eSetValue_BySetUserInput | eSetValue_ByContent)) {
if (EditorHasComposition()) {
// When this is called recursively, there shouldn't be composition.
if (NS_WARN_IF(mIsCommittingComposition)) {
if (NS_WARN_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 updated mValueBeingSet.
// be set later with the newest value.
return true;
}
if (NS_WARN_IF(!mBoundFrame)) {
@@ -2264,7 +2376,7 @@ bool TextControlState::SetValue(const nsAString& aValue,
} else {
mBoundFrame->GetText(currentValue);
}
if (newValue == currentValue) {
if (handlingSetValue.GetSettingValue() == currentValue) {
// 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
@@ -2287,25 +2399,20 @@ bool TextControlState::SetValue(const nsAString& aValue,
// WARNING: During this call, compositionupdate, compositionend, input
// events will be fired. Therefore, everything can occur. E.g., the
// document may be unloaded.
mValueBeingSet = aValue;
mIsCommittingComposition = true;
AutoTextControlHandlingState handlingCommitComposition(
*this, TextControlAction::CommitComposition);
RefPtr<TextEditor> textEditor = mTextEditor;
nsresult rv = textEditor->CommitComposition();
if (!self.get()) {
return true;
}
mIsCommittingComposition = false;
// If this is called recursively during committing composition and
// some of them may be skipped above. Therefore, we need to set
// value to the editor with the aValue of the latest call.
newValue = mValueBeingSet;
// When mIsCommittingComposition is false, mValueBeingSet won't be
// used. Therefore, let's clear it.
mValueBeingSet.Truncate();
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 "
@@ -2314,12 +2421,6 @@ bool TextControlState::SetValue(const nsAString& aValue,
}
}
// \r is an illegal character in the dom, but people use them,
// so convert windows and mac platform linebreaks to \n:
if (!nsContentUtils::PlatformToDOMLineBreaks(newValue, fallible)) {
return false;
}
// mTextCtrlElement may be cleared when we dispatch an event so that
// we should keep grabbing it with local variable.
nsCOMPtr<nsITextControlElement> textControlElement(mTextCtrlElement);
@@ -2334,7 +2435,8 @@ bool TextControlState::SetValue(const nsAString& aValue,
#ifdef DEBUG
if (IsSingleLineTextControl()) {
NS_ASSERTION(mEditorInitialized || mInitializing,
NS_ASSERTION(mEditorInitialized || handlingSetValue.IsHandling(
TextControlAction::PrepareEditor),
"We should never try to use the editor if we're not "
"initialized unless we're being initialized");
}
@@ -2354,7 +2456,7 @@ bool TextControlState::SetValue(const nsAString& aValue,
AutoWeakFrame weakFrame(mBoundFrame);
// this is necessary to avoid infinite recursion
if (!currentValue.Equals(newValue)) {
if (currentValue != handlingSetValue.GetSettingValue()) {
RefPtr<TextEditor> textEditor = mTextEditor;
nsCOMPtr<Document> document = textEditor->GetDocument();
@@ -2393,8 +2495,8 @@ bool TextControlState::SetValue(const nsAString& aValue,
// we're emulating the user's input. Passing nullptr as
// nsIPrincipal means that that may be user's input. So, let's
// do it.
DebugOnly<nsresult> rv =
textEditor->ReplaceTextAsAction(newValue, nullptr, nullptr);
DebugOnly<nsresult> rv = textEditor->ReplaceTextAsAction(
handlingSetValue.GetSettingValue(), nullptr, nullptr);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"Failed to set the new value");
} else {
@@ -2421,8 +2523,10 @@ bool TextControlState::SetValue(const nsAString& aValue,
// However, this path won't be used in web content anymore.
nsCOMPtr<nsISelectionController> kungFuDeathGrip = mSelCon.get();
uint32_t currentLength = currentValue.Length();
uint32_t newlength = newValue.Length();
if (!currentLength || !StringBeginsWith(newValue, currentValue)) {
uint32_t newlength = handlingSetValue.GetSettingValue().Length();
if (!currentLength ||
!StringBeginsWith(handlingSetValue.GetSettingValue(),
currentValue)) {
// Replace the whole text.
currentLength = 0;
kungFuDeathGrip->SelectAll();
@@ -2431,7 +2535,8 @@ bool TextControlState::SetValue(const nsAString& aValue,
mBoundFrame->SelectAllOrCollapseToEndOfText(false);
}
const nsAString& insertValue =
StringTail(newValue, newlength - currentLength);
StringTail(handlingSetValue.GetSettingValue(),
newlength - currentLength);
if (insertValue.IsEmpty()) {
// In this case, we makes the editor stop dispatching "input"
@@ -2466,7 +2571,8 @@ bool TextControlState::SetValue(const nsAString& aValue,
// In this case, we makes the editor stop dispatching "input"
// event so that passing nullptr as nsIPrincipal is safe for now.
textEditor->SetTextAsAction(newValue, nullptr);
textEditor->SetTextAsAction(handlingSetValue.GetSettingValue(),
nullptr);
// Call the listener's HandleValueChanged() callback manually,
// since we don't use the transaction manager in this path and it
@@ -2492,14 +2598,16 @@ bool TextControlState::SetValue(const nsAString& aValue,
// as deleting the existing selection -- see bug 574558), in which
// case we don't need to reset the value here.
if (!mBoundFrame) {
return SetValue(newValue, aFlags & eSetValue_Notify);
return SetValue(handlingSetValue.GetSettingValue(),
aFlags & eSetValue_Notify);
}
return true;
}
// The new value never includes line breaks caused by hard-wrap.
// So, mCachedValue can always cache the new value.
if (!mBoundFrame->CacheValue(newValue, fallible)) {
if (!mBoundFrame->CacheValue(handlingSetValue.GetSettingValue(),
fallible)) {
return false;
}
}
@@ -2509,11 +2617,11 @@ bool TextControlState::SetValue(const nsAString& aValue,
mValue.emplace();
}
// We can't just early-return here if mValue->Equals(newValue), because
// ValueWasChanged and OnValueChanged below still need to be called.
if (!mValue->Equals(newValue) ||
// We can't just early-return here, because ValueWasChanged and
// OnValueChanged below still need to be called.
if (!mValue->Equals(handlingSetValue.GetSettingValue()) ||
!StaticPrefs::dom_input_skip_cursor_move_for_same_value_set()) {
if (!mValue->Assign(newValue, fallible)) {
if (!mValue->Assign(handlingSetValue.GetSettingValue(), fallible)) {
return false;
}
@@ -2523,8 +2631,8 @@ bool TextControlState::SetValue(const nsAString& aValue,
SelectionProperties& props = GetSelectionProperties();
if (aFlags & eSetValue_MoveCursorToEndIfValueChanged) {
props.SetStart(newValue.Length());
props.SetEnd(newValue.Length());
props.SetStart(handlingSetValue.GetSettingValue().Length());
props.SetEnd(handlingSetValue.GetSettingValue().Length());
props.SetDirection(nsITextControlFrame::eForward);
} else if (aFlags &
eSetValue_MoveCursorToBeginSetSelectionDirectionForward) {
@@ -2534,8 +2642,10 @@ bool TextControlState::SetValue(const nsAString& aValue,
} else {
// Make sure our cached selection position is not outside the new
// value.
props.SetStart(std::min(props.GetStart(), newValue.Length()));
props.SetEnd(std::min(props.GetEnd(), newValue.Length()));
props.SetStart(std::min(props.GetStart(),
handlingSetValue.GetSettingValue().Length()));
props.SetEnd(std::min(props.GetEnd(),
handlingSetValue.GetSettingValue().Length()));
}
}
@@ -2551,10 +2661,11 @@ bool TextControlState::SetValue(const nsAString& aValue,
if (aFlags & eSetValue_BySetUserInput) {
nsCOMPtr<Element> element = do_QueryInterface(textControlElement);
MOZ_ASSERT(element);
MOZ_ASSERT(!newValue.IsVoid());
MOZ_ASSERT(!handlingSetValue.GetSettingValue().IsVoid());
DebugOnly<nsresult> rvIgnored = nsContentUtils::DispatchInputEvent(
element, EditorInputType::eInsertReplacementText, nullptr,
nsContentUtils::InputEventOptions(newValue));
nsContentUtils::InputEventOptions(
handlingSetValue.GetSettingValue()));
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
"Failed to dispatch input event");
}
@@ -2589,7 +2700,8 @@ bool TextControlState::HasNonEmptyValue() {
// If the frame for editor is alive, we can compute it with mTextEditor.
// Otherwise, we need to check cached value via GetValue().
if (mTextEditor && mBoundFrame && mEditorInitialized &&
!mIsCommittingComposition) {
!(mHandlingState &&
mHandlingState->IsHandling(TextControlAction::CommitComposition))) {
return !mTextEditor->IsEmpty();
}