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:
Masayuki Nakano 2018-04-26 22:41:34 +09:00
Родитель 876eddddaa
Коммит 5f8ba21730
8 изменённых файлов: 443 добавлений и 192 удалений

Просмотреть файл

@ -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;
}