gecko-dev/editor/libeditor/TextEditRules.h

625 lines
20 KiB
C
Raw Normal View History

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2012-05-21 11:12:37 +00:00
/* 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/EditAction.h"
#include "mozilla/EditorBase.h"
#include "mozilla/EditorDOMPoint.h"
#include "mozilla/EditorUtils.h"
#include "mozilla/HTMLEditor.h" // for nsIEditor::AsHTMLEditor()
#include "mozilla/TextEditor.h"
#include "nsCOMPtr.h"
#include "nsCycleCollectionParticipant.h"
#include "nsIEditor.h"
#include "nsINamed.h"
#include "nsISupportsImpl.h"
#include "nsITimer.h"
#include "nsString.h"
#include "nscore.h"
namespace mozilla {
class AutoLockRulesSniffing;
class EditSubActionInfo;
class HTMLEditor;
class HTMLEditRules;
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 <B>not</B> 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 <B>not</B> 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.
* 3. Stop handling edit action if method returns NS_ERROR_EDITOR_DESTROYED
* since if mutation event lister or selectionchange event listener disables
* the editor, we should not modify the DOM tree anymore.
* 4. Any method callers have to check nsresult return value (both directly or
* with simple class like EditActionResult) whether the value is
* NS_ERROR_EDITOR_DESTROYED at least.
* 5. Callers of methods of other classes such as TextEditor, have to check
* CanHandleEditAction() before checking its result and if the result is
* false, the method have to return NS_ERROR_EDITOR_DESTROYED. In other
* words, any methods which may change Selection or the DOM tree have to
* return nsresult directly or with simple class like EditActionResult.
* And such methods should be marked as MOZ_MUST_USE.
*/
class TextEditRules : public nsITimerCallback
, public nsINamed
{
protected:
typedef EditorBase::AutoSelectionRestorer
AutoSelectionRestorer;
typedef EditorBase::AutoTopLevelEditSubActionNotifier
AutoTopLevelEditSubActionNotifier;
typedef EditorBase::AutoTransactionsConserveSelection
AutoTransactionsConserveSelection;
public:
typedef dom::Element Element;
typedef dom::Selection Selection;
typedef dom::Text Text;
template<typename T> using OwningNonNull = OwningNonNull<T>;
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSINAMED
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(TextEditRules, nsITimerCallback)
TextEditRules();
HTMLEditRules* AsHTMLEditRules();
const HTMLEditRules* AsHTMLEditRules() const;
virtual nsresult Init(TextEditor* aTextEditor);
virtual nsresult SetInitialValue(const nsAString& aValue);
virtual nsresult DetachEditor();
virtual nsresult BeforeEdit(EditSubAction aEditSubAction,
nsIEditor::EDirection aDirection);
virtual nsresult AfterEdit(EditSubAction aEditSubAction,
nsIEditor::EDirection aDirection);
virtual nsresult WillDoAction(Selection* aSelection,
EditSubActionInfo& aInfo,
bool* aCancel,
bool* aHandled);
virtual nsresult DidDoAction(Selection* aSelection,
EditSubActionInfo& aInfo,
nsresult aResult);
/**
* Return false if the editor has non-empty text nodes or non-text
* nodes. Otherwise, i.e., there is no meaningful content,
* return true.
*/
virtual bool DocumentIsEmpty();
virtual nsresult DocumentModified();
protected:
virtual ~TextEditRules();
public:
void ResetIMETextPWBuf();
/**
* Handles the newline characters according to the default system prefs
* (editor.singleLine.pasteNewlines).
* Each value means:
* nsIPlaintextEditor::eNewlinesReplaceWithSpaces (2, Firefox default):
* replace newlines with spaces.
* nsIPlaintextEditor::eNewlinesStrip (3):
* remove newlines from the string.
* nsIPlaintextEditor::eNewlinesReplaceWithCommas (4, Thunderbird default):
* replace newlines with commas.
* nsIPlaintextEditor::eNewlinesStripSurroundingWhitespace (5):
* collapse newlines and surrounding whitespace characters and
* remove them from the string.
* nsIPlaintextEditor::eNewlinesPasteIntact (0):
* only remove the leading and trailing newlines.
* nsIPlaintextEditor::eNewlinesPasteToFirst (1) or any other value:
* remove the first newline and all characters following it.
*
* @param aString the string to be modified in place.
*/
void HandleNewLines(nsString& aString);
/**
* 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);
bool HasBogusNode()
{
return !!mBogusNode;
}
1999-04-05 17:21:59 +00:00
protected:
void InitFields();
// TextEditRules implementation methods
/**
* Called before inserting text.
* This method may actually inserts text into the editor. Therefore, this
* might cause destroying the editor.
*
* @param aEditSubAction Must be EditSubAction::eInsertTextComingFromIME
* or EditSubAction::eInsertText.
* @param aCancel Returns true if the operation is canceled.
* @param aHandled Returns true if the edit action is handled.
* @param inString String to be inserted.
* @param outString String actually inserted.
* @param aMaxLength The maximum string length which the editor
* allows to set.
*/
MOZ_MUST_USE nsresult
WillInsertText(EditSubAction aEditSubAction, bool* aCancel, bool* aHandled,
const nsAString* inString, nsAString* outString,
int32_t aMaxLength);
/**
* Called before inserting a line break into the editor.
* This method removes selected text if selection isn't collapsed.
* Therefore, this might cause destroying the editor.
*
* @param aCancel Returns true if the operation is canceled.
* @param aHandled Returns true if the edit action is handled.
* @param aMaxLength The maximum string length which the editor
* allows to set.
*/
MOZ_MUST_USE nsresult
WillInsertBreak(bool* aCancel, bool* aHandled, int32_t aMaxLength);
Preparation for ender-based text control * added focus listener. Doesn't do much yet, but when focus notifications start appearing, we'll be ready for them. The code is in place to hide selection when we lose focus and paint selection when we get focus. That's probably not quite right, but it's a start. We will need to be able to determine the distinction between losing focus to another control within our app, and losing focus to another app. * added support for disabled and readonly states in the editor. This is accomplished by having flags set by the client, and letting the rules system deal with those flags. The flags I added are: TEXT_EDITOR_FLAG_PLAINTEXT 0x01 // only plain text editing is allowed TEXT_EDITOR_FLAG_SINGLELINE 0x02 // enter key and CR-LF handled specially TEXT_EDITOR_FLAG_PASSWORD 0x04 // text is not entered into content, only a representative character TEXT_EDITOR_FLAG_READONLY 0x08 // editing events are disabled. Editor may still accept focus. TEXT_EDITOR_FLAG_DISALBED 0x10 // all events are disabled (like scrolling). Editor will not accept focus. * added WillInsertBreak/DidInsertBreak into text rules, so flags could be checked. This gets us readonly, disabled, and single line behavior. * cleaned up the code that allocates, registers, and destroys event listeners. Thanks to Kin and Simon for cleaning up the ownership model on the listeners, it was a big help. * added support for a max text length. You can now tell the text editor, be no bigger than n characters.
1999-05-28 21:24:18 +00:00
/**
* Called before setting text to the text editor.
* This method may actually set text to it. Therefore, this might cause
* destroying the text editor.
*
* @param aCancel Returns true if the operation is canceled.
* @param aHandled Returns true if the edit action is handled.
* @param inString String to be set.
* @param aMaxLength The maximum string length which the text editor
* allows to set.
*/
MOZ_MUST_USE nsresult
WillSetText(bool* aCancel, bool* aHandled,
const nsAString* inString, int32_t aMaxLength);
/**
* Called before inserting something into the editor.
* This method may removes mBougsNode if there is. Therefore, this method
* might cause destroying the editor.
*
* @param aCancel Returns true if the operation is canceled.
* This can be nullptr.
*/
MOZ_MUST_USE nsresult WillInsert(bool* aCancel = nullptr);
/**
* Called before deleting selected content.
* This method may actually remove the selected content with
* DeleteSelectionWithTransaction(). So, this might cause destroying the
* editor.
*
* @param aCaollapsedAction Direction to extend the selection.
* @param aCancel Returns true if the operation is canceled.
* @param aHandled Returns true if the edit action is handled.
*/
MOZ_MUST_USE nsresult
WillDeleteSelection(nsIEditor::EDirection aCollapsedAction,
bool* aCancel, bool* aHandled);
/**
* DeleteSelectionWithTransaction() is internal method of
* WillDeleteSelection() since it needs to create SelectionBatcher in
* big scope and destroying it might causes destroying the editor.
* So, after calling this method, callers need to check CanHandleEditAction()
* manually.
*
* @param aCaollapsedAction Direction to extend the selection.
* @param aCancel Returns true if the operation is canceled.
* @param aHandled Returns true if the edit action is handled.
*/
MOZ_MUST_USE nsresult
DeleteSelectionWithTransaction(nsIEditor::EDirection aCollapsedAction,
bool* aCancel, bool* aHandled);
/**
* Called after deleted selected content.
* This method may remove empty text node and makes guarantee that caret
* is never at left of <br> element.
*/
MOZ_MUST_USE nsresult DidDeleteSelection();
nsresult WillSetTextProperty(bool* aCancel, bool* aHandled);
nsresult WillRemoveTextProperty(bool* aCancel, bool* aHandled);
nsresult WillUndo(bool* aCancel, bool* aHandled);
nsresult DidUndo(nsresult aResult);
nsresult WillRedo(bool* aCancel, bool* aHandled);
nsresult DidRedo(nsresult aResult);
/**
* Called prior to nsIEditor::OutputToString.
*
* @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(const nsAString* aInFormat,
nsAString* aOutText,
uint32_t aFlags,
bool* aOutCancel,
bool* aHandled);
/**
* Check for and replace a redundant trailing break.
*/
MOZ_MUST_USE nsresult RemoveRedundantTrailingBR();
/**
* Creates a trailing break in the text doc if there is not one already.
*/
MOZ_MUST_USE nsresult CreateTrailingBRIfNeeded();
/**
* Creates a bogus <br> node if the root element has no editable content.
*/
MOZ_MUST_USE nsresult CreateBogusNodeIfNeeded();
1999-06-24 23:36:56 +00:00
/**
* Returns a truncated insertion string if insertion would place us over
* aMaxLength
*/
nsresult TruncateInsertionIfNeeded(const nsAString* aInString,
nsAString* aOutString,
int32_t aMaxLength,
bool* aTruncated);
/**
* Remove IME composition text from password buffer.
*/
void RemoveIMETextFromPWBuf(uint32_t& aStart, nsAString* aIMEString);
/**
* Create a normal <br> element and insert it to aPointToInsert.
*
* @param aPointToInsert The point where the new <br> element will be
* inserted.
* @return Returns created <br> element or an error code
* if couldn't create new <br> element.
*/
template<typename PT, typename CT>
CreateElementResult
CreateBR(const EditorDOMPointBase<PT, CT>& aPointToInsert)
{
CreateElementResult ret = CreateBRInternal(aPointToInsert, false);
#ifdef DEBUG
// If editor is destroyed, it must return NS_ERROR_EDITOR_DESTROYED.
if (!CanHandleEditAction()) {
MOZ_ASSERT(ret.Rv() == NS_ERROR_EDITOR_DESTROYED);
}
#endif // #ifdef DEBUG
return ret;
}
/**
* Create a moz-<br> element and insert it to aPointToInsert.
*
* @param aPointToInsert The point where the new moz-<br> element will be
* inserted.
* @return Returns created <br> element or an error code
* if couldn't create new <br> element.
*/
template<typename PT, typename CT>
CreateElementResult
CreateMozBR(const EditorDOMPointBase<PT, CT>& aPointToInsert)
{
CreateElementResult ret = CreateBRInternal(aPointToInsert, true);
#ifdef DEBUG
// If editor is destroyed, it must return NS_ERROR_EDITOR_DESTROYED.
if (!CanHandleEditAction()) {
MOZ_ASSERT(ret.Rv() == NS_ERROR_EDITOR_DESTROYED);
}
#endif // #ifdef DEBUG
return ret;
}
void UndefineCaretBidiLevel();
nsresult CheckBidiLevelForDeletion(const EditorRawDOMPoint& aSelectionPoint,
nsIEditor::EDirection aAction,
bool* aCancel);
/**
* HideLastPWInput() replaces last password characters which have not
* been replaced with mask character like '*' with with the mask character.
* This method may cause destroying the editor.
*/
MOZ_MUST_USE nsresult HideLastPWInput();
/**
* CollapseSelectionToTrailingBRIfNeeded() collapses selection after the
* text node if:
* - the editor is text editor
* - and Selection is collapsed at the end of the text node
* - and the text node is followed by moz-<br>.
*/
MOZ_MUST_USE nsresult CollapseSelectionToTrailingBRIfNeeded();
bool IsPasswordEditor() const;
bool IsSingleLineEditor() const;
bool IsPlaintextEditor() const;
bool IsReadonly() const;
bool IsDisabled() const;
bool IsMailEditor() const;
bool DontEchoPassword() const;
private:
TextEditor* MOZ_NON_OWNING_REF mTextEditor;
/**
* Create a normal <br> element or a moz-<br> element and insert it to
* aPointToInsert.
*
* @param aParentToInsert The point where the new <br> element will be
* inserted.
* @param aCreateMozBR true if the caller wants to create a moz-<br>
* element. Otherwise, false.
* @return Returns created <br> element and error code.
* If it succeeded, never returns nullptr.
*/
template<typename PT, typename CT>
CreateElementResult
CreateBRInternal(const EditorDOMPointBase<PT, CT>& aPointToInsert,
bool aCreateMozBR);
protected:
/**
* AutoSafeEditorData grabs editor instance and related instances during
* handling an edit action. When this is created, its pointer is set to
* the mSafeData, and this guarantees the lifetime of grabbing objects
* until it's destroyed.
*/
class MOZ_STACK_CLASS AutoSafeEditorData
{
public:
AutoSafeEditorData(TextEditRules& aTextEditRules,
TextEditor& aTextEditor,
Selection& aSelection)
: mTextEditRules(aTextEditRules)
, mHTMLEditor(nullptr)
{
// mTextEditRules may have AutoSafeEditorData instance since in some
// cases. E.g., while public methods of *EditRules are called, it
// calls attaching editor's method, then, editor will call public
// methods of *EditRules again.
if (mTextEditRules.mData) {
return;
}
mTextEditor = &aTextEditor;
mHTMLEditor = aTextEditor.AsHTMLEditor();
mSelection = &aSelection;
mTextEditRules.mData = this;
}
~AutoSafeEditorData()
{
if (mTextEditRules.mData != this) {
return;
}
mTextEditRules.mData = nullptr;
}
TextEditor& TextEditorRef() const { return *mTextEditor; }
HTMLEditor& HTMLEditorRef() const
{
MOZ_ASSERT(mHTMLEditor);
return *mHTMLEditor;
}
Selection& SelectionRef() const { return *mSelection; }
private:
// This class should be created by public methods TextEditRules and
// HTMLEditRules and in the stack. So, the lifetime of this should
// be guaranteed the callers of the public methods.
TextEditRules& MOZ_NON_OWNING_REF mTextEditRules;
RefPtr<TextEditor> mTextEditor;
// Shortcut for HTMLEditorRef(). So, not necessary to use RefPtr.
HTMLEditor* MOZ_NON_OWNING_REF mHTMLEditor;
RefPtr<Selection> mSelection;
};
AutoSafeEditorData* mData;
TextEditor& TextEditorRef() const
{
MOZ_ASSERT(mData);
return mData->TextEditorRef();
}
Selection& SelectionRef() const
{
MOZ_ASSERT(mData);
return mData->SelectionRef();
}
bool CanHandleEditAction() const
{
if (!mTextEditor) {
return false;
}
if (mTextEditor->Destroyed()) {
return false;
}
MOZ_ASSERT(mTextEditor->IsInitialized());
return true;
}
#ifdef DEBUG
bool IsEditorDataAvailable() const { return !!mData; }
#endif // #ifdef DEBUG
/**
* GetTextNodeAroundSelectionStartContainer() may return a Text node around
* start container of Selection. If current selection container is not
* a text node, this will look for descendants and next siblings of the
* container.
*/
inline already_AddRefed<nsINode> GetTextNodeAroundSelectionStartContainer();
// 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<nsIContent> mBogusNode;
// Cached selected node.
nsCOMPtr<nsINode> mCachedSelectionNode;
// Cached selected offset.
uint32_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;
bool mIsHTMLEditRules;
// The top level editor action.
EditSubAction mTopLevelEditSubAction;
nsCOMPtr<nsITimer> mTimer;
uint32_t mLastStart;
uint32_t mLastLength;
// friends
friend class AutoLockRulesSniffing;
};
/**
* An object to encapsulate any additional info needed to be passed
* to rules system by the editor.
* TODO: This class (almost struct, though) is ugly and its size isn't
* optimized. Should be refined later.
*/
class MOZ_STACK_CLASS EditSubActionInfo final
1999-04-12 12:01:32 +00:00
{
public:
explicit EditSubActionInfo(EditSubAction aEditSubAction)
: mEditSubAction(aEditSubAction)
, inString(nullptr)
, outString(nullptr)
, outputFormat(nullptr)
, maxLength(-1)
, flags(0)
, collapsedAction(nsIEditor::eNext)
, stripWrappers(nsIEditor::eStrip)
, entireList(false)
, bulletType(nullptr)
, alignType(nullptr)
, blockType(nullptr)
{}
EditSubAction mEditSubAction;
// EditSubAction::eInsertText / EditSubAction::eInsertTextComingFromIME
const nsAString* inString;
nsAString* outString;
const nsAString* outputFormat;
int32_t maxLength;
// EditSubAction::eComputeTextToOutput
uint32_t flags;
// EditSubAction::eDeleteSelectedContent
nsIEditor::EDirection collapsedAction;
nsIEditor::EStripWrappers stripWrappers;
// EditSubAction::eCreateOrChangeList
bool entireList;
const nsAString* bulletType;
// EditSubAction::eSetOrClearAlignment
const nsAString* alignType;
// EditSubAction::eCreateOrRemoveBlock
const nsAString* blockType;
1999-04-12 12:01:32 +00:00
};
/**
* Stack based helper class for managing TextEditRules::mLockRluesSniffing.
* 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