/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_TextEditRules_h
#define mozilla_TextEditRules_h
#include "mozilla/EditorBase.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIEditRules.h"
#include "nsIEditor.h"
#include "nsISupportsImpl.h"
#include "nsITimer.h"
#include "nsString.h"
#include "nscore.h"
class nsIDOMElement;
class nsIDOMNode;
namespace mozilla {
class AutoLockRulesSniffing;
class TextEditor;
namespace dom {
class Selection;
} // namespace dom
/**
* Object that encapsulates HTML text-specific editing rules.
*
* To be a good citizen, edit rules must live by these restrictions:
* 1. All data manipulation is through the editor.
* Content nodes in the document tree must not be manipulated
* directly. Content nodes in document fragments that are not part of the
* document itself may be manipulated at will. Operations on document
* fragments must not go through the editor.
* 2. Selection must not be explicitly set by the rule method.
* Any manipulation of Selection must be done by the editor.
*/
class TextEditRules : public nsIEditRules
, public nsITimerCallback
{
public:
typedef dom::Element Element;
typedef dom::Selection Selection;
typedef dom::Text Text;
template using OwningNonNull = OwningNonNull;
NS_DECL_NSITIMERCALLBACK
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TextEditRules, nsIEditRules)
TextEditRules();
// nsIEditRules methods
NS_IMETHOD Init(TextEditor* aTextEditor) override;
NS_IMETHOD SetInitialValue(const nsAString& aValue) override;
NS_IMETHOD DetachEditor() override;
NS_IMETHOD BeforeEdit(EditAction action,
nsIEditor::EDirection aDirection) override;
NS_IMETHOD AfterEdit(EditAction action,
nsIEditor::EDirection aDirection) override;
NS_IMETHOD WillDoAction(Selection* aSelection, RulesInfo* aInfo,
bool* aCancel, bool* aHandled) override;
NS_IMETHOD DidDoAction(Selection* aSelection, RulesInfo* aInfo,
nsresult aResult) override;
NS_IMETHOD_(bool) DocumentIsEmpty() override;
NS_IMETHOD DocumentModified() override;
protected:
virtual ~TextEditRules();
public:
void ResetIMETextPWBuf();
/**
* Handles the newline characters either according to aNewLineHandling
* or to the default system prefs if aNewLineHandling is negative.
*
* @param aString the string to be modified in place.
* @param aNewLineHandling determine the desired type of newline handling:
* * negative values:
* handle newlines according to platform defaults.
* * nsIPlaintextEditor::eNewlinesReplaceWithSpaces:
* replace newlines with spaces.
* * nsIPlaintextEditor::eNewlinesStrip:
* remove newlines from the string.
* * nsIPlaintextEditor::eNewlinesReplaceWithCommas:
* replace newlines with commas.
* * nsIPlaintextEditor::eNewlinesStripSurroundingWhitespace:
* collapse newlines and surrounding whitespace characters and
* remove them from the string.
* * nsIPlaintextEditor::eNewlinesPasteIntact:
* only remove the leading and trailing newlines.
* * nsIPlaintextEditor::eNewlinesPasteToFirst or any other value:
* remove the first newline and all characters following it.
*/
static void HandleNewLines(nsString& aString, int32_t aNewLineHandling);
/**
* Prepare a string buffer for being displayed as the contents of a password
* field. This function uses the platform-specific character for representing
* characters entered into password fields.
*
* @param aOutString the output string. When this function returns,
* aOutString will contain aLength password characters.
* @param aLength the number of password characters that aOutString should
* contain.
*/
static void FillBufWithPWChars(nsAString* aOutString, int32_t aLength);
protected:
void InitFields();
// TextEditRules implementation methods
nsresult WillInsertText(EditAction aAction,
Selection* aSelection,
bool* aCancel,
bool* aHandled,
const nsAString* inString,
nsAString* outString,
int32_t aMaxLength);
nsresult DidInsertText(Selection* aSelection, nsresult aResult);
nsresult GetTopEnclosingPre(nsIDOMNode* aNode, nsIDOMNode** aOutPreNode);
nsresult WillInsertBreak(Selection* aSelection, bool* aCancel,
bool* aHandled, int32_t aMaxLength);
nsresult DidInsertBreak(Selection* aSelection, nsresult aResult);
void WillInsert(Selection& aSelection, bool* aCancel);
nsresult DidInsert(Selection* aSelection, nsresult aResult);
nsresult WillDeleteSelection(Selection* aSelection,
nsIEditor::EDirection aCollapsedAction,
bool* aCancel,
bool* aHandled);
nsresult DidDeleteSelection(Selection* aSelection,
nsIEditor::EDirection aCollapsedAction,
nsresult aResult);
nsresult WillSetTextProperty(Selection* aSelection, bool* aCancel,
bool* aHandled);
nsresult DidSetTextProperty(Selection* aSelection, nsresult aResult);
nsresult WillRemoveTextProperty(Selection* aSelection, bool* aCancel,
bool* aHandled);
nsresult DidRemoveTextProperty(Selection* aSelection, nsresult aResult);
nsresult WillUndo(Selection* aSelection, bool* aCancel, bool* aHandled);
nsresult DidUndo(Selection* aSelection, nsresult aResult);
nsresult WillRedo(Selection* aSelection, bool* aCancel, bool* aHandled);
nsresult DidRedo(Selection* aSelection, nsresult aResult);
/**
* Called prior to nsIEditor::OutputToString.
* @param aSelection
* @param aInFormat The format requested for the output, a MIME type.
* @param aOutText The string to use for output, if aCancel is set to true.
* @param aOutCancel If set to true, the caller should cancel the operation
* and use aOutText as the result.
*/
nsresult WillOutputText(Selection* aSelection,
const nsAString* aInFormat,
nsAString* aOutText,
bool* aOutCancel,
bool* aHandled);
nsresult DidOutputText(Selection* aSelection, nsresult aResult);
/**
* Check for and replace a redundant trailing break.
*/
nsresult RemoveRedundantTrailingBR();
/**
* Creates a trailing break in the text doc if there is not one already.
*/
nsresult CreateTrailingBRIfNeeded();
/**
* Creates a bogus text node if the document has no editable content.
*/
nsresult CreateBogusNodeIfNeeded(Selection* aSelection);
/**
* Returns a truncated insertion string if insertion would place us over
* aMaxLength
*/
nsresult TruncateInsertionIfNeeded(Selection* aSelection,
const nsAString* aInString,
nsAString* aOutString,
int32_t aMaxLength,
bool* aTruncated);
/**
* Remove IME composition text from password buffer.
*/
void RemoveIMETextFromPWBuf(int32_t& aStart, nsAString* aIMEString);
nsresult CreateMozBR(nsIDOMNode* inParent, int32_t inOffset,
nsIDOMNode** outBRNode = nullptr);
void UndefineCaretBidiLevel(Selection* aSelection);
nsresult CheckBidiLevelForDeletion(Selection* aSelection,
nsIDOMNode* aSelNode,
int32_t aSelOffset,
nsIEditor::EDirection aAction,
bool* aCancel);
nsresult HideLastPWInput();
nsresult CollapseSelectionToTrailingBRIfNeeded(Selection* aSelection);
bool IsPasswordEditor() const;
bool IsSingleLineEditor() const;
bool IsPlaintextEditor() const;
bool IsReadonly() const;
bool IsDisabled() const;
bool IsMailEditor() const;
bool DontEchoPassword() const;
// Note that we do not refcount the editor.
TextEditor* mTextEditor;
// A buffer we use to store the real value of password editors.
nsString mPasswordText;
// A buffer we use to track the IME composition string.
nsString mPasswordIMEText;
uint32_t mPasswordIMEIndex;
// Magic node acts as placeholder in empty doc.
nsCOMPtr mBogusNode;
// Cached selected node.
nsCOMPtr mCachedSelectionNode;
// Cached selected offset.
int32_t mCachedSelectionOffset;
uint32_t mActionNesting;
bool mLockRulesSniffing;
bool mDidExplicitlySetInterline;
// In bidirectional text, delete characters not visually adjacent to the
// caret without moving the caret first.
bool mDeleteBidiImmediately;
// The top level editor action.
EditAction mTheAction;
nsCOMPtr mTimer;
uint32_t mLastStart;
uint32_t mLastLength;
// friends
friend class AutoLockRulesSniffing;
};
class TextRulesInfo final : public RulesInfo
{
public:
explicit TextRulesInfo(EditAction aAction)
: RulesInfo(aAction)
, inString(nullptr)
, outString(nullptr)
, outputFormat(nullptr)
, maxLength(-1)
, collapsedAction(nsIEditor::eNext)
, stripWrappers(nsIEditor::eStrip)
, bOrdered(false)
, entireList(false)
, bulletType(nullptr)
, alignType(nullptr)
, blockType(nullptr)
, insertElement(nullptr)
{}
// kInsertText
const nsAString* inString;
nsAString* outString;
const nsAString* outputFormat;
int32_t maxLength;
// kDeleteSelection
nsIEditor::EDirection collapsedAction;
nsIEditor::EStripWrappers stripWrappers;
// kMakeList
bool bOrdered;
bool entireList;
const nsAString* bulletType;
// kAlign
const nsAString* alignType;
// kMakeBasicBlock
const nsAString* blockType;
// kInsertElement
const nsIDOMElement* insertElement;
};
/**
* Stack based helper class for StartOperation()/EndOperation() sandwich.
* This class sets a bool letting us know to ignore any rules sniffing
* that tries to occur reentrantly.
*/
class MOZ_STACK_CLASS AutoLockRulesSniffing final
{
public:
explicit AutoLockRulesSniffing(TextEditRules* aRules)
: mRules(aRules)
{
if (mRules) {
mRules->mLockRulesSniffing = true;
}
}
~AutoLockRulesSniffing()
{
if (mRules) {
mRules->mLockRulesSniffing = false;
}
}
protected:
TextEditRules* mRules;
};
/**
* Stack based helper class for turning on/off the edit listener.
*/
class MOZ_STACK_CLASS AutoLockListener final
{
public:
explicit AutoLockListener(bool* aEnabled)
: mEnabled(aEnabled)
, mOldState(false)
{
if (mEnabled) {
mOldState = *mEnabled;
*mEnabled = false;
}
}
~AutoLockListener()
{
if (mEnabled) {
*mEnabled = mOldState;
}
}
protected:
bool* mEnabled;
bool mOldState;
};
} // namespace mozilla
#endif // #ifndef mozilla_TextEditRules_h