зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1457083 - part 1: Make public methods of TextEditUtils and HTMLEditUtils guarantee that the editor instance and selection instance won't be destroyed while it handles any edit actions r=m_kato
This patch creates a stack class in TextEditRules, its name is AutoSafeEditorData and make all public methods (except editor observer methods) which may change DOM tree, fire some DOM events or calling some XPCOM listeners create its instance in the stack. Then, it grabs associated editor instance and its selection instance. Next patch will make remaining public methods create AutoSafeEditorData. MozReview-Commit-ID: 8oshdhL3ONQ --HG-- extra : rebase_source : 591db71e45fe28ca93cbebd9bb7da8c16eae4466
This commit is contained in:
Родитель
876eddddaa
Коммит
5f8ba21730
|
@ -14,7 +14,6 @@
|
|||
#include "mozilla/RangeBoundary.h" // for RawRangeBoundary, RangeBoundary
|
||||
#include "mozilla/SelectionState.h" // for RangeUpdater, etc.
|
||||
#include "mozilla/StyleSheet.h" // for StyleSheet
|
||||
#include "mozilla/TextEditRules.h" // for TextEditRules
|
||||
#include "mozilla/TransactionManager.h" // for TransactionManager
|
||||
#include "mozilla/WeakPtr.h" // for WeakPtr
|
||||
#include "mozilla/dom/Selection.h"
|
||||
|
@ -76,6 +75,7 @@ class SplitNodeResult;
|
|||
class SplitNodeTransaction;
|
||||
class TextComposition;
|
||||
class TextEditor;
|
||||
class TextEditRules;
|
||||
class TextInputListener;
|
||||
class TextServicesDocument;
|
||||
enum class EditAction : int32_t;
|
||||
|
|
|
@ -527,7 +527,7 @@ HTMLEditor::SetPositionToStatic(Element& aElement)
|
|||
RefPtr<HTMLEditRules> htmlRules =
|
||||
static_cast<HTMLEditRules*>(mRules.get());
|
||||
NS_ENSURE_TRUE(htmlRules, NS_ERROR_FAILURE);
|
||||
nsresult rv = htmlRules->MakeSureElemStartsOrEndsOnCR(aElement);
|
||||
nsresult rv = htmlRules->MakeSureElemStartsAndEndsOnCR(aElement);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = RemoveContainerWithTransaction(aElement);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
|
|
@ -245,7 +245,8 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLEditRules, TextEditRules,
|
|||
nsresult
|
||||
HTMLEditRules::Init(TextEditor* aTextEditor)
|
||||
{
|
||||
if (NS_WARN_IF(!aTextEditor)) {
|
||||
if (NS_WARN_IF(!aTextEditor) ||
|
||||
NS_WARN_IF(!aTextEditor->AsHTMLEditor())) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
|
@ -253,12 +254,23 @@ HTMLEditRules::Init(TextEditor* aTextEditor)
|
|||
|
||||
mHTMLEditor = aTextEditor->AsHTMLEditor();
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
Selection* selection = aTextEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// call through to base class Init
|
||||
AutoSafeEditorData setData(*this, *mHTMLEditor, *selection);
|
||||
|
||||
nsresult rv = TextEditRules::Init(aTextEditor);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// cache any prefs we care about
|
||||
static const char kPrefName[] =
|
||||
|
@ -271,13 +283,14 @@ HTMLEditRules::Init(TextEditor* aTextEditor)
|
|||
mReturnInEmptyLIKillsList = !returnInEmptyLIKillsList.EqualsLiteral("false");
|
||||
|
||||
// make a utility range for use by the listenter
|
||||
nsCOMPtr<nsINode> node = mHTMLEditor->GetRoot();
|
||||
nsCOMPtr<nsINode> node = HTMLEditorRef().GetRoot();
|
||||
if (!node) {
|
||||
node = mHTMLEditor->GetDocument();
|
||||
node = HTMLEditorRef().GetDocument();
|
||||
if (NS_WARN_IF(!node)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
NS_ENSURE_STATE(node);
|
||||
|
||||
mUtilRange = new nsRange(node);
|
||||
|
||||
// set up mDocChangeRange to be whole doc
|
||||
|
@ -290,7 +303,9 @@ HTMLEditRules::Init(TextEditor* aTextEditor)
|
|||
if (node->IsElement()) {
|
||||
ErrorResult rv;
|
||||
mDocChangeRange->SelectNode(*node, rv);
|
||||
NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
|
||||
if (NS_WARN_IF(rv.Failed())) {
|
||||
return rv.StealNSResult();
|
||||
}
|
||||
AdjustSpecialBreaks();
|
||||
}
|
||||
|
||||
|
@ -315,8 +330,9 @@ HTMLEditRules::BeforeEdit(EditAction aAction,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
AutoLockRulesSniffing lockIt(this);
|
||||
mDidExplicitlySetInterline = false;
|
||||
|
@ -327,24 +343,25 @@ HTMLEditRules::BeforeEdit(EditAction aAction,
|
|||
// Clear our flag about if just deleted a range
|
||||
mDidRangedDelete = false;
|
||||
|
||||
Selection* selection = mHTMLEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mHTMLEditor, *selection);
|
||||
|
||||
// Remember where our selection was before edit action took place:
|
||||
|
||||
// Get selection
|
||||
RefPtr<Selection> selection = htmlEditor->GetSelection();
|
||||
|
||||
// Get the selection location
|
||||
if (NS_WARN_IF(!selection) || !selection->RangeCount()) {
|
||||
if (!SelectionRef().RangeCount()) {
|
||||
return NS_ERROR_UNEXPECTED;
|
||||
}
|
||||
mRangeItem->mStartContainer = selection->GetRangeAt(0)->GetStartContainer();
|
||||
mRangeItem->mStartOffset = selection->GetRangeAt(0)->StartOffset();
|
||||
mRangeItem->mEndContainer = selection->GetRangeAt(0)->GetEndContainer();
|
||||
mRangeItem->mEndOffset = selection->GetRangeAt(0)->EndOffset();
|
||||
mRangeItem->StoreRange(SelectionRef().GetRangeAt(0));
|
||||
nsCOMPtr<nsINode> selStartNode = mRangeItem->mStartContainer;
|
||||
nsCOMPtr<nsINode> selEndNode = mRangeItem->mEndContainer;
|
||||
|
||||
// Register with range updater to track this as we perturb the doc
|
||||
htmlEditor->mRangeUpdater.RegisterRangeItem(mRangeItem);
|
||||
HTMLEditorRef().mRangeUpdater.RegisterRangeItem(mRangeItem);
|
||||
|
||||
// Clear deletion state bool
|
||||
mDidDeleteSelection = false;
|
||||
|
@ -367,11 +384,13 @@ HTMLEditRules::BeforeEdit(EditAction aAction,
|
|||
nsCOMPtr<nsINode> selNode =
|
||||
aDirection == nsIEditor::eNext ? selEndNode : selStartNode;
|
||||
nsresult rv = CacheInlineStyles(selNode);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
// Stabilize the document against contenteditable count changes
|
||||
nsHTMLDocument* htmlDoc = htmlEditor->GetHTMLDocument();
|
||||
nsHTMLDocument* htmlDoc = HTMLEditorRef().GetHTMLDocument();
|
||||
if (NS_WARN_IF(!htmlDoc)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
@ -397,24 +416,31 @@ HTMLEditRules::AfterEdit(EditAction aAction,
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
|
||||
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
AutoLockRulesSniffing lockIt(this);
|
||||
|
||||
MOZ_ASSERT(mActionNesting > 0);
|
||||
nsresult rv = NS_OK;
|
||||
mActionNesting--;
|
||||
if (!mActionNesting) {
|
||||
Selection* selection = mHTMLEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mHTMLEditor, *selection);
|
||||
|
||||
// Do all the tricky stuff
|
||||
rv = AfterEditInner(aAction, aDirection);
|
||||
|
||||
// Free up selectionState range item
|
||||
htmlEditor->mRangeUpdater.DropRangeItem(mRangeItem);
|
||||
HTMLEditorRef().mRangeUpdater.DropRangeItem(mRangeItem);
|
||||
|
||||
// Reset the contenteditable count to its previous value
|
||||
if (mRestoreContentEditableCount) {
|
||||
nsHTMLDocument* htmlDoc = htmlEditor->GetHTMLDocument();
|
||||
nsHTMLDocument* htmlDoc = HTMLEditorRef().GetHTMLDocument();
|
||||
if (NS_WARN_IF(!htmlDoc)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
@ -425,8 +451,9 @@ HTMLEditRules::AfterEdit(EditAction aAction,
|
|||
}
|
||||
}
|
||||
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
@ -578,7 +605,15 @@ HTMLEditRules::WillDoAction(Selection* aSelection,
|
|||
bool* aCancel,
|
||||
bool* aHandled)
|
||||
{
|
||||
MOZ_ASSERT(aInfo && aCancel && aHandled);
|
||||
if (NS_WARN_IF(!aSelection) || NS_WARN_IF(!aInfo)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aCancel);
|
||||
MOZ_ASSERT(aHandled);
|
||||
|
||||
*aCancel = false;
|
||||
*aHandled = false;
|
||||
|
@ -591,17 +626,17 @@ HTMLEditRules::WillDoAction(Selection* aSelection,
|
|||
return TextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled);
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mHTMLEditor, *aSelection);
|
||||
|
||||
// Nothing to do if there's no selection to act on
|
||||
if (!aSelection) {
|
||||
if (NS_WARN_IF(!SelectionRef().RangeCount())) {
|
||||
return NS_OK;
|
||||
}
|
||||
NS_ENSURE_TRUE(aSelection->RangeCount(), NS_OK);
|
||||
|
||||
RefPtr<nsRange> range = aSelection->GetRangeAt(0);
|
||||
nsCOMPtr<nsINode> selStartNode = range->GetStartContainer();
|
||||
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
if (!mHTMLEditor->IsModifiableNode(selStartNode)) {
|
||||
if (!HTMLEditorRef().IsModifiableNode(selStartNode)) {
|
||||
*aCancel = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -609,14 +644,13 @@ HTMLEditRules::WillDoAction(Selection* aSelection,
|
|||
nsCOMPtr<nsINode> selEndNode = range->GetEndContainer();
|
||||
|
||||
if (selStartNode != selEndNode) {
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
if (!mHTMLEditor->IsModifiableNode(selEndNode)) {
|
||||
if (!HTMLEditorRef().IsModifiableNode(selEndNode)) {
|
||||
*aCancel = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
if (!mHTMLEditor->IsModifiableNode(range->GetCommonAncestor())) {
|
||||
if (!HTMLEditorRef().IsModifiableNode(range->GetCommonAncestor())) {
|
||||
*aCancel = true;
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -625,48 +659,49 @@ HTMLEditRules::WillDoAction(Selection* aSelection,
|
|||
switch (aInfo->action) {
|
||||
case EditAction::insertText:
|
||||
case EditAction::insertIMEText:
|
||||
UndefineCaretBidiLevel(aSelection);
|
||||
return WillInsertText(aInfo->action, aSelection, aCancel, aHandled,
|
||||
UndefineCaretBidiLevel(&SelectionRef());
|
||||
return WillInsertText(aInfo->action, &SelectionRef(), aCancel, aHandled,
|
||||
aInfo->inString, aInfo->outString,
|
||||
aInfo->maxLength);
|
||||
case EditAction::loadHTML:
|
||||
return WillLoadHTML(aSelection, aCancel);
|
||||
return WillLoadHTML(&SelectionRef(), aCancel);
|
||||
case EditAction::insertBreak:
|
||||
UndefineCaretBidiLevel(aSelection);
|
||||
return WillInsertBreak(*aSelection, aCancel, aHandled);
|
||||
UndefineCaretBidiLevel(&SelectionRef());
|
||||
return WillInsertBreak(SelectionRef(), aCancel, aHandled);
|
||||
case EditAction::deleteSelection:
|
||||
return WillDeleteSelection(aSelection, aInfo->collapsedAction,
|
||||
return WillDeleteSelection(&SelectionRef(), aInfo->collapsedAction,
|
||||
aInfo->stripWrappers, aCancel, aHandled);
|
||||
case EditAction::makeList:
|
||||
return WillMakeList(aSelection, aInfo->blockType, aInfo->entireList,
|
||||
return WillMakeList(&SelectionRef(), aInfo->blockType, aInfo->entireList,
|
||||
aInfo->bulletType, aCancel, aHandled);
|
||||
case EditAction::indent:
|
||||
return WillIndent(aSelection, aCancel, aHandled);
|
||||
return WillIndent(&SelectionRef(), aCancel, aHandled);
|
||||
case EditAction::outdent:
|
||||
return WillOutdent(*aSelection, aCancel, aHandled);
|
||||
return WillOutdent(SelectionRef(), aCancel, aHandled);
|
||||
case EditAction::setAbsolutePosition:
|
||||
return WillAbsolutePosition(*aSelection, aCancel, aHandled);
|
||||
return WillAbsolutePosition(SelectionRef(), aCancel, aHandled);
|
||||
case EditAction::removeAbsolutePosition:
|
||||
return WillRemoveAbsolutePosition(aSelection, aCancel, aHandled);
|
||||
return WillRemoveAbsolutePosition(&SelectionRef(), aCancel, aHandled);
|
||||
case EditAction::align:
|
||||
return WillAlign(*aSelection, *aInfo->alignType, aCancel, aHandled);
|
||||
return WillAlign(SelectionRef(), *aInfo->alignType, aCancel, aHandled);
|
||||
case EditAction::makeBasicBlock:
|
||||
return WillMakeBasicBlock(*aSelection, *aInfo->blockType, aCancel,
|
||||
return WillMakeBasicBlock(SelectionRef(), *aInfo->blockType, aCancel,
|
||||
aHandled);
|
||||
case EditAction::removeList:
|
||||
return WillRemoveList(aSelection, aInfo->bOrdered, aCancel, aHandled);
|
||||
return WillRemoveList(&SelectionRef(), aInfo->bOrdered,
|
||||
aCancel, aHandled);
|
||||
case EditAction::makeDefListItem:
|
||||
return WillMakeDefListItem(aSelection, aInfo->blockType,
|
||||
return WillMakeDefListItem(&SelectionRef(), aInfo->blockType,
|
||||
aInfo->entireList, aCancel, aHandled);
|
||||
case EditAction::insertElement:
|
||||
WillInsert(*aSelection, aCancel);
|
||||
WillInsert(SelectionRef(), aCancel);
|
||||
return NS_OK;
|
||||
case EditAction::decreaseZIndex:
|
||||
return WillRelativeChangeZIndex(aSelection, -1, aCancel, aHandled);
|
||||
return WillRelativeChangeZIndex(&SelectionRef(), -1, aCancel, aHandled);
|
||||
case EditAction::increaseZIndex:
|
||||
return WillRelativeChangeZIndex(aSelection, 1, aCancel, aHandled);
|
||||
return WillRelativeChangeZIndex(&SelectionRef(), 1, aCancel, aHandled);
|
||||
default:
|
||||
return TextEditRules::WillDoAction(aSelection, aInfo,
|
||||
return TextEditRules::WillDoAction(&SelectionRef(), aInfo,
|
||||
aCancel, aHandled);
|
||||
}
|
||||
}
|
||||
|
@ -676,23 +711,34 @@ HTMLEditRules::DidDoAction(Selection* aSelection,
|
|||
RulesInfo* aInfo,
|
||||
nsresult aResult)
|
||||
{
|
||||
if (NS_WARN_IF(!aSelection) || NS_WARN_IF(!aInfo)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mHTMLEditor, *aSelection);
|
||||
|
||||
switch (aInfo->action) {
|
||||
case EditAction::insertBreak:
|
||||
return DidInsertBreak(aSelection, aResult);
|
||||
return DidInsertBreak(&SelectionRef(), aResult);
|
||||
case EditAction::deleteSelection:
|
||||
return DidDeleteSelection(aSelection, aInfo->collapsedAction, aResult);
|
||||
return DidDeleteSelection(&SelectionRef(), aInfo->collapsedAction,
|
||||
aResult);
|
||||
case EditAction::makeBasicBlock:
|
||||
case EditAction::indent:
|
||||
case EditAction::outdent:
|
||||
case EditAction::align:
|
||||
return DidMakeBasicBlock(aSelection, aInfo, aResult);
|
||||
return DidMakeBasicBlock(&SelectionRef(), aInfo, aResult);
|
||||
case EditAction::setAbsolutePosition: {
|
||||
nsresult rv = DidMakeBasicBlock(aSelection, aInfo, aResult);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsresult rv = DidMakeBasicBlock(&SelectionRef(), aInfo, aResult);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
return DidAbsolutePosition();
|
||||
}
|
||||
default:
|
||||
// pass through to TextEditRules
|
||||
return TextEditRules::DidDoAction(aSelection, aInfo, aResult);
|
||||
}
|
||||
}
|
||||
|
@ -716,10 +762,22 @@ HTMLEditRules::GetListState(bool* aMixed,
|
|||
*aDL = false;
|
||||
bool bNonList = false;
|
||||
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
Selection* selection = mHTMLEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mHTMLEditor, *selection);
|
||||
|
||||
nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
|
||||
nsresult rv = GetListActionNodes(arrayOfNodes, EntireList::no,
|
||||
TouchContent::no);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Examine list type for nodes in selection.
|
||||
for (const auto& curNode : arrayOfNodes) {
|
||||
|
@ -767,10 +825,22 @@ HTMLEditRules::GetListItemState(bool* aMixed,
|
|||
*aDD = false;
|
||||
bool bNonList = false;
|
||||
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
Selection* selection = mHTMLEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mHTMLEditor, *selection);
|
||||
|
||||
nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
|
||||
nsresult rv = GetListActionNodes(arrayOfNodes, EntireList::no,
|
||||
TouchContent::no);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// examine list type for nodes in selection
|
||||
for (const auto& node : arrayOfNodes) {
|
||||
|
@ -809,8 +879,15 @@ HTMLEditRules::GetAlignment(bool* aMixed,
|
|||
{
|
||||
MOZ_ASSERT(aMixed && aAlign);
|
||||
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
Selection* selection = mHTMLEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mHTMLEditor, *selection);
|
||||
|
||||
// For now, just return first alignment. We'll lie about if it's mixed.
|
||||
// This is for efficiency given that our current ui doesn't care if it's
|
||||
|
@ -824,18 +901,16 @@ HTMLEditRules::GetAlignment(bool* aMixed,
|
|||
*aMixed = false;
|
||||
*aAlign = nsIHTMLEditor::eLeft;
|
||||
|
||||
// Get selection
|
||||
NS_ENSURE_STATE(htmlEditor->GetSelection());
|
||||
OwningNonNull<Selection> selection = *htmlEditor->GetSelection();
|
||||
|
||||
// Get selection location
|
||||
NS_ENSURE_TRUE(htmlEditor->GetRoot(), NS_ERROR_FAILURE);
|
||||
OwningNonNull<Element> root = *htmlEditor->GetRoot();
|
||||
if (NS_WARN_IF(!HTMLEditorRef().GetRoot())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
OwningNonNull<Element> root = *HTMLEditorRef().GetRoot();
|
||||
|
||||
int32_t rootOffset = root->GetParentNode() ?
|
||||
root->GetParentNode()->ComputeIndexOf(root) : -1;
|
||||
|
||||
nsRange* firstRange = selection->GetRangeAt(0);
|
||||
nsRange* firstRange = SelectionRef().GetRangeAt(0);
|
||||
if (NS_WARN_IF(!firstRange)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
@ -852,29 +927,39 @@ HTMLEditRules::GetAlignment(bool* aMixed,
|
|||
// start and its ancestors for divs with alignment on them. If we are in a
|
||||
// text node, then that is the node of interest.
|
||||
nodeToExamine = atStartOfSelection.GetContainer();
|
||||
if (NS_WARN_IF(!nodeToExamine)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
} else if (atStartOfSelection.IsContainerHTMLElement(nsGkAtoms::html) &&
|
||||
atStartOfSelection.Offset() == static_cast<uint32_t>(rootOffset)) {
|
||||
// If we have selected the body, let's look at the first editable node
|
||||
nodeToExamine = htmlEditor->GetNextEditableNode(atStartOfSelection);
|
||||
nodeToExamine = HTMLEditorRef().GetNextEditableNode(atStartOfSelection);
|
||||
if (NS_WARN_IF(!nodeToExamine)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
} else {
|
||||
nsTArray<RefPtr<nsRange>> arrayOfRanges;
|
||||
GetPromotedRanges(selection, arrayOfRanges, EditAction::align);
|
||||
GetPromotedRanges(SelectionRef(), arrayOfRanges, EditAction::align);
|
||||
|
||||
// Use these ranges to construct a list of nodes to act on.
|
||||
nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
|
||||
nsresult rv = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
|
||||
EditAction::align, TouchContent::no);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
nodeToExamine = arrayOfNodes.SafeElementAt(0);
|
||||
if (NS_WARN_IF(!nodeToExamine)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER);
|
||||
RefPtr<Element> blockParent = HTMLEditorRef().GetBlock(*nodeToExamine);
|
||||
if (NS_WARN_IF(!blockParent)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
nsCOMPtr<Element> blockParent = htmlEditor->GetBlock(*nodeToExamine);
|
||||
|
||||
NS_ENSURE_TRUE(blockParent, NS_ERROR_FAILURE);
|
||||
|
||||
if (htmlEditor->IsCSSEnabled() &&
|
||||
if (HTMLEditorRef().IsCSSEnabled() &&
|
||||
CSSEditUtils::IsCSSEditableProperty(blockParent, nullptr,
|
||||
nsGkAtoms::align)) {
|
||||
// We are in CSS mode and we know how to align this element with CSS
|
||||
|
@ -978,28 +1063,38 @@ nsresult
|
|||
HTMLEditRules::GetIndentState(bool* aCanIndent,
|
||||
bool* aCanOutdent)
|
||||
{
|
||||
if (NS_WARN_IF(!aCanIndent) || NS_WARN_IF(!aCanOutdent)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// XXX Looks like that this is implementation of
|
||||
// nsIHTMLEditor::getIndentState() however nobody calls this method
|
||||
// even with the interface method.
|
||||
NS_ENSURE_TRUE(aCanIndent && aCanOutdent, NS_ERROR_FAILURE);
|
||||
*aCanIndent = true;
|
||||
*aCanOutdent = false;
|
||||
|
||||
// get selection
|
||||
NS_ENSURE_STATE(mHTMLEditor && mHTMLEditor->GetSelection());
|
||||
OwningNonNull<Selection> selection = *mHTMLEditor->GetSelection();
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
Selection* selection = mHTMLEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mHTMLEditor, *selection);
|
||||
|
||||
// contruct a list of nodes to act on.
|
||||
nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
|
||||
nsresult rv = GetNodesFromSelection(*selection, EditAction::indent,
|
||||
nsresult rv = GetNodesFromSelection(SelectionRef(), EditAction::indent,
|
||||
arrayOfNodes, TouchContent::no);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// examine nodes in selection for blockquotes or list elements;
|
||||
// these we can outdent. Note that we return true for canOutdent
|
||||
// if *any* of the selection is outdentable, rather than all of it.
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
bool useCSS = mHTMLEditor->IsCSSEnabled();
|
||||
bool useCSS = HTMLEditorRef().IsCSSEnabled();
|
||||
for (auto& curNode : Reversed(arrayOfNodes)) {
|
||||
if (HTMLEditUtils::IsNodeThatCanOutdent(curNode)) {
|
||||
*aCanOutdent = true;
|
||||
|
@ -1030,17 +1125,14 @@ HTMLEditRules::GetIndentState(bool* aCanIndent,
|
|||
// of selection endpoints. We might have a blockquote or list item
|
||||
// in the parent hierarchy.
|
||||
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
Element* rootElement = mHTMLEditor->GetRoot();
|
||||
Element* rootElement = HTMLEditorRef().GetRoot();
|
||||
if (NS_WARN_IF(!rootElement)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// Test selection start container hierarchy.
|
||||
EditorRawDOMPoint selectionStartPoint(EditorBase::GetStartPoint(selection));
|
||||
EditorRawDOMPoint selectionStartPoint(
|
||||
EditorBase::GetStartPoint(&SelectionRef()));
|
||||
if (NS_WARN_IF(!selectionStartPoint.IsSet())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
@ -1054,7 +1146,7 @@ HTMLEditRules::GetIndentState(bool* aCanIndent,
|
|||
}
|
||||
|
||||
// Test selection end container hierarchy.
|
||||
EditorRawDOMPoint selectionEndPoint(EditorBase::GetEndPoint(selection));
|
||||
EditorRawDOMPoint selectionEndPoint(EditorBase::GetEndPoint(&SelectionRef()));
|
||||
if (NS_WARN_IF(!selectionEndPoint.IsSet())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
@ -1074,19 +1166,34 @@ nsresult
|
|||
HTMLEditRules::GetParagraphState(bool* aMixed,
|
||||
nsAString& outFormat)
|
||||
{
|
||||
if (NS_WARN_IF(!aMixed)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
// This routine is *heavily* tied to our ui choices in the paragraph
|
||||
// style popup. I can't see a way around that.
|
||||
NS_ENSURE_TRUE(aMixed, NS_ERROR_NULL_POINTER);
|
||||
*aMixed = true;
|
||||
outFormat.Truncate(0);
|
||||
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
Selection* selection = mHTMLEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mHTMLEditor, *selection);
|
||||
|
||||
bool bMixed = false;
|
||||
// using "x" as an uninitialized value, since "" is meaningful
|
||||
nsAutoString formatStr(NS_LITERAL_STRING("x"));
|
||||
|
||||
nsTArray<OwningNonNull<nsINode>> arrayOfNodes;
|
||||
nsresult rv = GetParagraphFormatNodes(arrayOfNodes, TouchContent::no);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// post process list. We need to replace any block nodes that are not format
|
||||
// nodes with their content. This is so we only have to look "up" the hierarchy
|
||||
|
@ -1098,21 +1205,17 @@ HTMLEditRules::GetParagraphState(bool* aMixed,
|
|||
if (IsBlockNode(curNode) && !HTMLEditUtils::IsFormatNode(curNode)) {
|
||||
// arrayOfNodes.RemoveObject(curNode);
|
||||
rv = AppendInnerFormatNodes(arrayOfNodes, curNode);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// we might have an empty node list. if so, find selection parent
|
||||
// and put that on the list
|
||||
if (arrayOfNodes.IsEmpty()) {
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
Selection* selection = mHTMLEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
EditorRawDOMPoint selectionStartPoint(EditorBase::GetStartPoint(selection));
|
||||
EditorRawDOMPoint selectionStartPoint(
|
||||
EditorBase::GetStartPoint(&SelectionRef()));
|
||||
if (NS_WARN_IF(!selectionStartPoint.IsSet())) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
@ -1120,9 +1223,10 @@ HTMLEditRules::GetParagraphState(bool* aMixed,
|
|||
}
|
||||
|
||||
// remember root node
|
||||
NS_ENSURE_STATE(mHTMLEditor);
|
||||
nsCOMPtr<Element> rootElem = mHTMLEditor->GetRoot();
|
||||
NS_ENSURE_TRUE(rootElem, NS_ERROR_NULL_POINTER);
|
||||
Element* rootElement = HTMLEditorRef().GetRoot();
|
||||
if (NS_WARN_IF(!rootElement)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
// loop through the nodes in selection and examine their paragraph format
|
||||
for (auto& curNode : Reversed(arrayOfNodes)) {
|
||||
|
@ -1139,7 +1243,7 @@ HTMLEditRules::GetParagraphState(bool* aMixed,
|
|||
} else {
|
||||
nsINode* node = curNode->GetParentNode();
|
||||
while (node) {
|
||||
if (node == rootElem) {
|
||||
if (node == rootElement) {
|
||||
format.Truncate(0);
|
||||
break;
|
||||
} else if (HTMLEditUtils::IsFormatNode(node)) {
|
||||
|
@ -9396,7 +9500,7 @@ HTMLEditRules::RemoveAlignment(nsINode& aNode,
|
|||
|
||||
// we may have to insert BRs in first and last position of element's children
|
||||
// if the nodes before/after are not blocks and not BRs
|
||||
rv = MakeSureElemStartsOrEndsOnCR(*child);
|
||||
rv = MakeSureElemStartsAndEndsOnCR(*child);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// now remove the CENTER container
|
||||
|
@ -9502,11 +9606,27 @@ HTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsINode& aNode,
|
|||
}
|
||||
|
||||
nsresult
|
||||
HTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsINode& aNode)
|
||||
HTMLEditRules::MakeSureElemStartsAndEndsOnCR(nsINode& aNode)
|
||||
{
|
||||
if (NS_WARN_IF(!mHTMLEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
Selection* selection = mHTMLEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mHTMLEditor, *selection);
|
||||
|
||||
nsresult rv = MakeSureElemStartsOrEndsOnCR(aNode, false);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return MakeSureElemStartsOrEndsOnCR(aNode, true);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
rv = MakeSureElemStartsOrEndsOnCR(aNode, true);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
|
@ -9948,27 +10068,29 @@ HTMLEditRules::DocumentModifiedWorker()
|
|||
return;
|
||||
}
|
||||
|
||||
Selection* selection = mHTMLEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mHTMLEditor, *selection);
|
||||
|
||||
// DeleteNodeWithTransaction() below may cause a flush, which could destroy
|
||||
// the editor
|
||||
nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
|
||||
|
||||
RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
|
||||
RefPtr<Selection> selection = htmlEditor->GetSelection();
|
||||
if (!selection) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Delete our bogus node, if we have one, since the document might not be
|
||||
// empty any more.
|
||||
if (mBogusNode) {
|
||||
DebugOnly<nsresult> rv = htmlEditor->DeleteNodeWithTransaction(*mBogusNode);
|
||||
DebugOnly<nsresult> rv =
|
||||
HTMLEditorRef().DeleteNodeWithTransaction(*mBogusNode);
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"Failed to remove the bogus node");
|
||||
mBogusNode = nullptr;
|
||||
}
|
||||
|
||||
// Try to recreate the bogus node if needed.
|
||||
CreateBogusNodeIfNeeded(selection);
|
||||
CreateBogusNodeIfNeeded(&SelectionRef());
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -102,7 +102,7 @@ public:
|
|||
nsresult GetIndentState(bool* aCanIndent, bool* aCanOutdent);
|
||||
nsresult GetAlignment(bool* aMixed, nsIHTMLEditor::EAlignment* aAlign);
|
||||
nsresult GetParagraphState(bool* aMixed, nsAString& outFormat);
|
||||
nsresult MakeSureElemStartsOrEndsOnCR(nsINode& aNode);
|
||||
nsresult MakeSureElemStartsAndEndsOnCR(nsINode& aNode);
|
||||
|
||||
void DidCreateNode(Element* aNewElement);
|
||||
void DidInsertNode(nsIContent& aNode);
|
||||
|
@ -116,14 +116,18 @@ public:
|
|||
void DidDeleteText(nsINode* aTextNode, int32_t aOffset, int32_t aLength);
|
||||
void WillDeleteSelection(Selection* aSelection);
|
||||
|
||||
void DeleteNodeIfCollapsedText(nsINode& aNode);
|
||||
|
||||
void StartToListenToEditActions() { mListenerEnabled = true; }
|
||||
void EndListeningToEditActions() { mListenerEnabled = false; }
|
||||
|
||||
protected:
|
||||
virtual ~HTMLEditRules();
|
||||
|
||||
HTMLEditor& HTMLEditorRef() const
|
||||
{
|
||||
MOZ_ASSERT(mData);
|
||||
return mData->HTMLEditorRef();
|
||||
}
|
||||
|
||||
enum RulesEndpoint
|
||||
{
|
||||
kStart,
|
||||
|
@ -144,6 +148,8 @@ protected:
|
|||
nsresult WillInsertBreak(Selection& aSelection, bool* aCancel,
|
||||
bool* aHandled);
|
||||
|
||||
void DeleteNodeIfCollapsedText(nsINode& aNode);
|
||||
|
||||
/**
|
||||
* InsertBRElement() inserts a <br> element into aInsertToBreak.
|
||||
*
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
#include "mozilla/ManualNAC.h"
|
||||
#include "mozilla/StyleSheet.h"
|
||||
#include "mozilla/TextEditor.h"
|
||||
#include "mozilla/TextEditRules.h"
|
||||
#include "mozilla/UniquePtr.h"
|
||||
#include "mozilla/dom/Element.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
|
|
|
@ -57,6 +57,7 @@ using namespace dom;
|
|||
|
||||
TextEditRules::TextEditRules()
|
||||
: mTextEditor(nullptr)
|
||||
, mData(nullptr)
|
||||
, mPasswordIMEIndex(0)
|
||||
, mCachedSelectionOffset(0)
|
||||
, mActionNesting(0)
|
||||
|
@ -127,35 +128,46 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(TextEditRules)
|
|||
nsresult
|
||||
TextEditRules::Init(TextEditor* aTextEditor)
|
||||
{
|
||||
if (!aTextEditor) {
|
||||
return NS_ERROR_NULL_POINTER;
|
||||
if (NS_WARN_IF(!aTextEditor)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
Selection* selection = aTextEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
InitFields();
|
||||
|
||||
// We hold a non-refcounted reference back to our editor.
|
||||
mTextEditor = aTextEditor;
|
||||
RefPtr<Selection> selection = mTextEditor->GetSelection();
|
||||
NS_WARNING_ASSERTION(selection, "editor cannot get selection");
|
||||
AutoSafeEditorData setData(*this, *mTextEditor, *selection);
|
||||
|
||||
// Put in a magic br if needed. This method handles null selection,
|
||||
// Put in a magic <br> if needed. This method handles null selection,
|
||||
// which should never happen anyway
|
||||
nsresult rv = CreateBogusNodeIfNeeded(selection);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsresult rv = CreateBogusNodeIfNeeded(&SelectionRef());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// If the selection hasn't been set up yet, set it up collapsed to the end of
|
||||
// our editable content.
|
||||
if (!selection->RangeCount()) {
|
||||
rv = mTextEditor->CollapseSelectionToEnd(selection);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (!SelectionRef().RangeCount()) {
|
||||
rv = TextEditorRef().CollapseSelectionToEnd(&SelectionRef());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
if (IsPlaintextEditor()) {
|
||||
// ensure trailing br node
|
||||
rv = CreateTrailingBRIfNeeded();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
// XXX We should use AddBoolVarCache and use "current" value at initializing.
|
||||
mDeleteBidiImmediately =
|
||||
Preferences::GetBool("bidi.edit.delete_immediately", false);
|
||||
|
||||
|
@ -199,20 +211,21 @@ TextEditRules::BeforeEdit(EditAction aAction,
|
|||
|
||||
// get the selection and cache the position before editing
|
||||
if (NS_WARN_IF(!mTextEditor)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
RefPtr<TextEditor> textEditor = mTextEditor;
|
||||
RefPtr<Selection> selection = textEditor->GetSelection();
|
||||
NS_ENSURE_STATE(selection);
|
||||
|
||||
if (aAction == EditAction::setText) {
|
||||
// setText replaces all text, so mCachedSelectionNode might be invalid on
|
||||
// AfterEdit.
|
||||
// Since this will be used as start position of spellchecker, we should
|
||||
// use root instead.
|
||||
mCachedSelectionNode = textEditor->GetRoot();
|
||||
mCachedSelectionNode = mTextEditor->GetRoot();
|
||||
mCachedSelectionOffset = 0;
|
||||
} else {
|
||||
Selection* selection = mTextEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
mCachedSelectionNode = selection->GetAnchorNode();
|
||||
mCachedSelectionOffset = selection->AnchorOffset();
|
||||
}
|
||||
|
@ -232,17 +245,24 @@ TextEditRules::AfterEdit(EditAction aAction,
|
|||
|
||||
MOZ_ASSERT(mActionNesting>0, "bad action nesting!");
|
||||
if (!--mActionNesting) {
|
||||
NS_ENSURE_STATE(mTextEditor);
|
||||
RefPtr<Selection> selection = mTextEditor->GetSelection();
|
||||
NS_ENSURE_STATE(selection);
|
||||
if (NS_WARN_IF(!mTextEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
Selection* selection = mTextEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mTextEditor, *selection);
|
||||
|
||||
NS_ENSURE_STATE(mTextEditor);
|
||||
nsresult rv =
|
||||
mTextEditor->HandleInlineSpellCheck(aAction, *selection,
|
||||
mCachedSelectionNode,
|
||||
mCachedSelectionOffset,
|
||||
nullptr, 0, nullptr, 0);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
TextEditorRef().HandleInlineSpellCheck(aAction, *selection,
|
||||
mCachedSelectionNode,
|
||||
mCachedSelectionOffset,
|
||||
nullptr, 0, nullptr, 0);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// no longer uses mCachedSelectionNode, so release it.
|
||||
mCachedSelectionNode = nullptr;
|
||||
|
@ -254,15 +274,19 @@ TextEditRules::AfterEdit(EditAction aAction,
|
|||
}
|
||||
|
||||
// detect empty doc
|
||||
rv = CreateBogusNodeIfNeeded(selection);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = CreateBogusNodeIfNeeded(&SelectionRef());
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// ensure trailing br node
|
||||
rv = CreateTrailingBRIfNeeded();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
// collapse the selection to the trailing BR if it's at the end of our text node
|
||||
CollapseSelectionToTrailingBRIfNeeded(selection);
|
||||
CollapseSelectionToTrailingBRIfNeeded(&SelectionRef());
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -273,45 +297,55 @@ TextEditRules::WillDoAction(Selection* aSelection,
|
|||
bool* aCancel,
|
||||
bool* aHandled)
|
||||
{
|
||||
// null selection is legal
|
||||
MOZ_ASSERT(aInfo && aCancel && aHandled);
|
||||
if (NS_WARN_IF(!aSelection) || NS_WARN_IF(!aInfo)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (NS_WARN_IF(!mTextEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(aCancel);
|
||||
MOZ_ASSERT(aHandled);
|
||||
|
||||
*aCancel = false;
|
||||
*aHandled = false;
|
||||
|
||||
AutoSafeEditorData setData(*this, *mTextEditor, *aSelection);
|
||||
|
||||
// my kingdom for dynamic cast
|
||||
switch (aInfo->action) {
|
||||
case EditAction::insertBreak:
|
||||
UndefineCaretBidiLevel(aSelection);
|
||||
return WillInsertBreak(aSelection, aCancel, aHandled, aInfo->maxLength);
|
||||
UndefineCaretBidiLevel(&SelectionRef());
|
||||
return WillInsertBreak(&SelectionRef(), aCancel, aHandled,
|
||||
aInfo->maxLength);
|
||||
case EditAction::insertText:
|
||||
case EditAction::insertIMEText:
|
||||
UndefineCaretBidiLevel(aSelection);
|
||||
return WillInsertText(aInfo->action, aSelection, aCancel, aHandled,
|
||||
UndefineCaretBidiLevel(&SelectionRef());
|
||||
return WillInsertText(aInfo->action, &SelectionRef(), aCancel, aHandled,
|
||||
aInfo->inString, aInfo->outString,
|
||||
aInfo->maxLength);
|
||||
case EditAction::setText:
|
||||
UndefineCaretBidiLevel(aSelection);
|
||||
return WillSetText(*aSelection, aCancel, aHandled, aInfo->inString,
|
||||
UndefineCaretBidiLevel(&SelectionRef());
|
||||
return WillSetText(SelectionRef(), aCancel, aHandled, aInfo->inString,
|
||||
aInfo->maxLength);
|
||||
case EditAction::deleteSelection:
|
||||
return WillDeleteSelection(aSelection, aInfo->collapsedAction,
|
||||
return WillDeleteSelection(&SelectionRef(), aInfo->collapsedAction,
|
||||
aCancel, aHandled);
|
||||
case EditAction::undo:
|
||||
return WillUndo(aSelection, aCancel, aHandled);
|
||||
return WillUndo(&SelectionRef(), aCancel, aHandled);
|
||||
case EditAction::redo:
|
||||
return WillRedo(aSelection, aCancel, aHandled);
|
||||
return WillRedo(&SelectionRef(), aCancel, aHandled);
|
||||
case EditAction::setTextProperty:
|
||||
return WillSetTextProperty(aSelection, aCancel, aHandled);
|
||||
return WillSetTextProperty(&SelectionRef(), aCancel, aHandled);
|
||||
case EditAction::removeTextProperty:
|
||||
return WillRemoveTextProperty(aSelection, aCancel, aHandled);
|
||||
return WillRemoveTextProperty(&SelectionRef(), aCancel, aHandled);
|
||||
case EditAction::outputText:
|
||||
return WillOutputText(aSelection, aInfo->outputFormat, aInfo->outString,
|
||||
aInfo->flags, aCancel, aHandled);
|
||||
return WillOutputText(&SelectionRef(), aInfo->outputFormat,
|
||||
aInfo->outString, aInfo->flags, aCancel, aHandled);
|
||||
case EditAction::insertElement:
|
||||
// i had thought this would be html rules only. but we put pre elements
|
||||
// into plaintext mail when doing quoting for reply! doh!
|
||||
WillInsert(*aSelection, aCancel);
|
||||
WillInsert(SelectionRef(), aCancel);
|
||||
return NS_OK;
|
||||
default:
|
||||
return NS_ERROR_FAILURE;
|
||||
|
@ -323,33 +357,40 @@ TextEditRules::DidDoAction(Selection* aSelection,
|
|||
RulesInfo* aInfo,
|
||||
nsresult aResult)
|
||||
{
|
||||
NS_ENSURE_STATE(mTextEditor);
|
||||
if (NS_WARN_IF(!aSelection) || NS_WARN_IF(!aInfo)) {
|
||||
return NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
if (NS_WARN_IF(!mTextEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mTextEditor, *aSelection);
|
||||
|
||||
// don't let any txns in here move the selection around behind our back.
|
||||
// Note that this won't prevent explicit selection setting from working.
|
||||
AutoTransactionsConserveSelection dontChangeMySelection(mTextEditor);
|
||||
|
||||
NS_ENSURE_TRUE(aSelection && aInfo, NS_ERROR_NULL_POINTER);
|
||||
AutoTransactionsConserveSelection dontChangeMySelection(&TextEditorRef());
|
||||
|
||||
switch (aInfo->action) {
|
||||
case EditAction::insertBreak:
|
||||
return DidInsertBreak(aSelection, aResult);
|
||||
return DidInsertBreak(&SelectionRef(), aResult);
|
||||
case EditAction::insertText:
|
||||
case EditAction::insertIMEText:
|
||||
return DidInsertText(aSelection, aResult);
|
||||
return DidInsertText(&SelectionRef(), aResult);
|
||||
case EditAction::setText:
|
||||
return DidSetText(*aSelection, aResult);
|
||||
return DidSetText(SelectionRef(), aResult);
|
||||
case EditAction::deleteSelection:
|
||||
return DidDeleteSelection(aSelection, aInfo->collapsedAction, aResult);
|
||||
return DidDeleteSelection(&SelectionRef(), aInfo->collapsedAction,
|
||||
aResult);
|
||||
case EditAction::undo:
|
||||
return DidUndo(aSelection, aResult);
|
||||
return DidUndo(&SelectionRef(), aResult);
|
||||
case EditAction::redo:
|
||||
return DidRedo(aSelection, aResult);
|
||||
return DidRedo(&SelectionRef(), aResult);
|
||||
case EditAction::setTextProperty:
|
||||
return DidSetTextProperty(aSelection, aResult);
|
||||
return DidSetTextProperty(&SelectionRef(), aResult);
|
||||
case EditAction::removeTextProperty:
|
||||
return DidRemoveTextProperty(aSelection, aResult);
|
||||
return DidRemoveTextProperty(&SelectionRef(), aResult);
|
||||
case EditAction::outputText:
|
||||
return DidOutputText(aSelection, aResult);
|
||||
return DidOutputText(&SelectionRef(), aResult);
|
||||
default:
|
||||
// Don't fail on transactions we don't handle here!
|
||||
return NS_OK;
|
||||
|
@ -1602,6 +1643,17 @@ TextEditRules::Notify(nsITimer* aTimer)
|
|||
{
|
||||
MOZ_ASSERT(mTimer);
|
||||
|
||||
if (NS_WARN_IF(!mTextEditor)) {
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
}
|
||||
|
||||
Selection* selection = mTextEditor->GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
AutoSafeEditorData setData(*this, *mTextEditor, *selection);
|
||||
|
||||
// Check whether our text editor's password flag was changed before this
|
||||
// "hide password character" timer actually fires.
|
||||
nsresult rv = IsPasswordEditor() ? HideLastPWInput() : NS_OK;
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include "mozilla/EditAction.h"
|
||||
#include "mozilla/EditorDOMPoint.h"
|
||||
#include "mozilla/HTMLEditor.h" // for nsIEditor::AsHTMLEditor()
|
||||
#include "mozilla/TextEditor.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsCycleCollectionParticipant.h"
|
||||
#include "nsIEditor.h"
|
||||
|
@ -20,9 +22,9 @@
|
|||
namespace mozilla {
|
||||
|
||||
class AutoLockRulesSniffing;
|
||||
class HTMLEditor;
|
||||
class HTMLEditRules;
|
||||
class RulesInfo;
|
||||
class TextEditor;
|
||||
namespace dom {
|
||||
class Selection;
|
||||
} // namespace dom
|
||||
|
@ -281,10 +283,76 @@ protected:
|
|||
bool DontEchoPassword() const;
|
||||
|
||||
private:
|
||||
// Note that we do not refcount the editor.
|
||||
TextEditor* mTextEditor;
|
||||
TextEditor* MOZ_NON_OWNING_REF mTextEditor;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
// A buffer we use to store the real value of password editors.
|
||||
nsString mPasswordText;
|
||||
// A buffer we use to track the IME composition string.
|
||||
|
|
|
@ -1743,8 +1743,12 @@ TextEditor::OutputToString(const nsAString& aFormatType,
|
|||
// XXX Struct should store a nsAReadable*
|
||||
nsAutoString str(aFormatType);
|
||||
ruleInfo.outputFormat = &str;
|
||||
Selection* selection = GetSelection();
|
||||
if (NS_WARN_IF(!selection)) {
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
bool cancel, handled;
|
||||
nsresult rv = rules->WillDoAction(nullptr, &ruleInfo, &cancel, &handled);
|
||||
nsresult rv = rules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
|
||||
if (cancel || NS_FAILED(rv)) {
|
||||
return rv;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче