Bug 1574852 - part 42: Merge HTMLEditRules::WillMakeBasicBlock() into HTMLEditor::InsertBasicBlockWithTransaction() r=m_kato

`HTMLEditRules::WillMakeBasicBlock()` just calls
`HTMLEditor::FormatBlockContainer()` and it's called only for
`EditSubAction::eCreateOrRemoveBlock` and it's used only in
`HTMLEditor::InsertBasicBlockWithTransaction()`.  Therefore, we can replace
calling `HTMLEditRules::WillDoAction()` in it with what
`HTMLEditRules::WIllMakeBasicBlock()` does.

First, `HTMLEditRules::WillDoAction()` checks whether first selection range
is editable.  If it's not so, it returns `NS_OK` with setting `aCancel` to
`true`.  Therefore, this patch moves this part to
`HTMLEditor::CanHandleHTMLEditSubAction()` for making
`HTMLEditor::InsertBasicBlockWithTransaction()` can check it easier.

Next, `HTMLEditor::InsertBasicBlockWithTransaction()` does something if
`HTMLEditRules::WillDoAction()` returns as not handled nor canceled.
However, this special handling requires at least one selection range:
https://searchfox.org/mozilla-central/rev/597a69c70a5cce6f42f159eb54ad1ef6745f5432/editor/libeditor/HTMLEditor.cpp#2284-2288
Surprisingly, `handled` is set to `false` only when there is an error or
there is no selection range.  Therefore, this long block has already been
dead code so that we can remove this.  Removing this block causes that we
become not throwing exception when `Document.execCommand("formatblock")`
without selection ranges.  But this is better since Chrome does not throw
excption.

Finally, this patch renames some related methods:
- `HTMLEditor::FormatBlockContainer()` -> `HTMLEditor::FormatBlockContainerWithTransaction()`
- `HTMLEditor::InsertBasicBlockWithTransaction()` -> `HTMLEditor::FormatBlockContainerAsSubAction()`

Differential Revision: https://phabricator.services.mozilla.com/D44179

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Masayuki Nakano 2019-09-02 06:08:43 +00:00
parent b388d67f46
commit 8438c04ae7
4 changed files with 112 additions and 157 deletions

View File

@ -700,6 +700,52 @@ nsresult HTMLEditRules::AfterEditInner() {
return NS_OK;
}
EditActionResult HTMLEditor::CanHandleHTMLEditSubAction() const {
MOZ_ASSERT(IsEditActionDataAvailable());
if (NS_WARN_IF(Destroyed())) {
return EditActionResult(NS_ERROR_EDITOR_DESTROYED);
}
// If there is not selection ranges, we should ignore the result.
if (!SelectionRefPtr()->RangeCount()) {
return EditActionCanceled();
}
nsRange* range = SelectionRefPtr()->GetRangeAt(0);
nsINode* selStartNode = range->GetStartContainer();
if (NS_WARN_IF(!selStartNode)) {
return EditActionResult(NS_ERROR_FAILURE);
}
if (!IsModifiableNode(*selStartNode)) {
return EditActionCanceled();
}
nsINode* selEndNode = range->GetEndContainer();
if (NS_WARN_IF(!selEndNode)) {
return EditActionResult(NS_ERROR_FAILURE);
}
if (selStartNode == selEndNode) {
return EditActionIgnored();
}
if (!IsModifiableNode(*selEndNode)) {
return EditActionCanceled();
}
nsINode* commonAncestor = range->GetCommonAncestor();
if (NS_WARN_IF(!commonAncestor)) {
return EditActionResult(NS_ERROR_FAILURE);
}
if (!IsModifiableNode(*commonAncestor)) {
return EditActionCanceled();
}
return EditActionIgnored();
}
nsresult HTMLEditRules::WillDoAction(EditSubActionInfo& aInfo, bool* aCancel,
bool* aHandled) {
if (NS_WARN_IF(!CanHandleEditAction())) {
@ -720,43 +766,20 @@ nsresult HTMLEditRules::WillDoAction(EditSubActionInfo& aInfo, bool* aCancel,
AutoSafeEditorData setData(*this, *mHTMLEditor);
// Nothing to do if there's no selection to act on
if (NS_WARN_IF(!SelectionRefPtr()->RangeCount())) {
return NS_OK;
EditActionResult result = HTMLEditorRef().CanHandleHTMLEditSubAction();
if (NS_WARN_IF(result.Failed())) {
return result.Rv();
}
RefPtr<nsRange> range = SelectionRefPtr()->GetRangeAt(0);
nsCOMPtr<nsINode> selStartNode = range->GetStartContainer();
if (NS_WARN_IF(!selStartNode)) {
return NS_ERROR_FAILURE;
}
if (!HTMLEditorRef().IsModifiableNode(*selStartNode)) {
if (result.Canceled()) {
if (!SelectionRefPtr()->RangeCount()) {
// Special case. If there is no selection range, we do nothing here,
// but return as not handled nor canceled.
return NS_OK;
}
*aCancel = true;
return NS_OK;
}
nsCOMPtr<nsINode> selEndNode = range->GetEndContainer();
if (NS_WARN_IF(!selEndNode)) {
return NS_ERROR_FAILURE;
}
if (selStartNode != selEndNode) {
if (!HTMLEditorRef().IsModifiableNode(*selEndNode)) {
*aCancel = true;
return NS_OK;
}
nsINode* commonAncestor = range->GetCommonAncestor();
if (NS_WARN_IF(!commonAncestor)) {
return NS_ERROR_FAILURE;
}
if (!HTMLEditorRef().IsModifiableNode(*commonAncestor)) {
*aCancel = true;
return NS_OK;
}
}
switch (aInfo.mEditSubAction) {
case EditSubAction::eInsertText:
case EditSubAction::eInsertTextComingFromIME:
@ -791,8 +814,6 @@ nsresult HTMLEditRules::WillDoAction(EditSubActionInfo& aInfo, bool* aCancel,
return WillRemoveAbsolutePosition(aCancel, aHandled);
case EditSubAction::eSetOrClearAlignment:
return WillAlign(*aInfo.alignType, aCancel, aHandled);
case EditSubAction::eCreateOrRemoveBlock:
return WillMakeBasicBlock(*aInfo.blockType, aCancel, aHandled);
case EditSubAction::eRemoveList: {
nsresult rv = WillRemoveList(aCancel, aHandled);
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) ||
@ -820,6 +841,7 @@ nsresult HTMLEditRules::WillDoAction(EditSubActionInfo& aInfo, bool* aCancel,
return WillRelativeChangeZIndex(-1, aCancel, aHandled);
case EditSubAction::eIncreaseZIndex:
return WillRelativeChangeZIndex(1, aCancel, aHandled);
case EditSubAction::eCreateOrRemoveBlock:
case EditSubAction::eInsertHTMLSource:
case EditSubAction::eUndo:
case EditSubAction::eRedo:
@ -1848,11 +1870,11 @@ EditActionResult HTMLEditRules::WillInsertParagraphSeparator() {
// Insert a new block first
MOZ_ASSERT(separator == ParagraphSeparator::div ||
separator == ParagraphSeparator::p);
// FormatBlockContainer() creates AutoSelectionRestorer.
// FormatBlockContainerWithTransaction() creates AutoSelectionRestorer.
// Therefore, even if it returns NS_OK, editor might have been destroyed
// at restoring Selection.
nsresult rv = MOZ_KnownLive(HTMLEditorRef())
.FormatBlockContainer(
.FormatBlockContainerWithTransaction(
MOZ_KnownLive(ParagraphSeparatorElement(separator)));
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED) ||
NS_WARN_IF(!CanHandleEditAction())) {
@ -1860,8 +1882,9 @@ EditActionResult HTMLEditRules::WillInsertParagraphSeparator() {
}
// We warn on failure, but don't handle it, because it might be harmless.
// Instead we just check that a new block was actually created.
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"HTMLEditor::FormatBlockContainer() failed");
NS_WARNING_ASSERTION(
NS_SUCCEEDED(rv),
"HTMLEditor::FormatBlockContainerWithTransaction() failed");
firstRange = SelectionRefPtr()->GetRangeAt(0);
if (NS_WARN_IF(!firstRange)) {
@ -1890,10 +1913,10 @@ EditActionResult HTMLEditRules::WillInsertParagraphSeparator() {
// Now, mNewBlockElement is last created block element for wrapping inline
// elements around the caret position and AfterEditInner() will move
// caret into it. However, it may be different from block parent of
// the caret position. E.g., FormatBlockContainer() may wrap following
// inline elements of a <br> element which is next sibling of container
// of the caret. So, we need to adjust mNewBlockElement here for avoiding
// jumping caret to odd position.
// the caret position. E.g., FormatBlockContainerWithTransaction() may
// wrap following inline elements of a <br> element which is next sibling
// of container of the caret. So, we need to adjust mNewBlockElement here
// for avoiding jumping caret to odd position.
HTMLEditorRef().TopLevelEditSubActionDataRef().mNewBlockElement =
blockParent;
}
@ -4535,37 +4558,7 @@ nsresult HTMLEditRules::WillMakeDefListItem(const nsAString* aItemType,
return NS_OK;
}
nsresult HTMLEditRules::WillMakeBasicBlock(const nsAString& aBlockType,
bool* aCancel, bool* aHandled) {
MOZ_ASSERT(IsEditorDataAvailable());
MOZ_ASSERT(aCancel && aHandled);
OwningNonNull<nsAtom> blockType = NS_Atomize(aBlockType);
// FYI: Ignore cancel result of WillInsert().
nsresult rv = MOZ_KnownLive(HTMLEditorRef()).WillInsert();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed");
*aCancel = false;
*aHandled = true;
// FormatBlockContainer() creates AutoSelectionRestorer.
// Therefore, even if it returns NS_OK, editor might have been destroyed
// at restoring Selection.
rv = MOZ_KnownLive(HTMLEditorRef()).FormatBlockContainer(blockType);
if (NS_WARN_IF(!CanHandleEditAction())) {
return NS_ERROR_EDITOR_DESTROYED;
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
return NS_OK;
}
nsresult HTMLEditor::FormatBlockContainer(nsAtom& blockType) {
nsresult HTMLEditor::FormatBlockContainerWithTransaction(nsAtom& blockType) {
MOZ_ASSERT(IsTopLevelEditSubActionDataAvailable());
if (!SelectionRefPtr()->IsCollapsed()) {

View File

@ -366,18 +366,6 @@ class HTMLEditRules : public TextEditRules {
bool aEntireList, bool* aCancel,
bool* aHandled);
/**
* WillMakeBasicBlock() called before changing block style around Selection.
* This method actually does something with calling FormatBlockContainer().
*
* @param aBlockType Necessary block style as string.
* @param aCancel Returns true if the operation is canceled.
* @param aHandled Returns true if the edit action is handled.
*/
MOZ_CAN_RUN_SCRIPT
MOZ_MUST_USE nsresult WillMakeBasicBlock(const nsAString& aBlockType,
bool* aCancel, bool* aHandled);
/**
* Called after creating a basic block, indenting, outdenting or aligning
* contents. This method inserts a padding <br> element for empty last line

View File

@ -1798,9 +1798,9 @@ nsresult HTMLEditor::SetParagraphFormatAsAction(
"MakeDefinitionListItemWithTransaction() failed");
return EditorBase::ToGenericNSResult(rv);
}
nsresult rv = InsertBasicBlockWithTransaction(*tagName);
nsresult rv = FormatBlockContainerAsSubAction(*tagName);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
"InsertBasicBlockWithTransaction() failed");
"FormatBlockContainerAsSubAction() failed");
return EditorBase::ToGenericNSResult(rv);
}
@ -2264,7 +2264,7 @@ nsresult HTMLEditor::MakeDefinitionListItemWithTransaction(nsAtom& aTagName) {
return NS_OK;
}
nsresult HTMLEditor::InsertBasicBlockWithTransaction(nsAtom& aTagName) {
nsresult HTMLEditor::FormatBlockContainerAsSubAction(nsAtom& aTagName) {
MOZ_ASSERT(IsEditActionDataAvailable());
if (!mRules) {
@ -2276,75 +2276,37 @@ nsresult HTMLEditor::InsertBasicBlockWithTransaction(nsAtom& aTagName) {
// Protect the edit rules object from dying
RefPtr<TextEditRules> rules(mRules);
bool cancel, handled;
AutoPlaceholderBatch treatAsOneTransaction(*this);
AutoEditSubActionNotifier startToHandleEditSubAction(
*this, EditSubAction::eCreateOrRemoveBlock, nsIEditor::eNext);
nsDependentAtomString tagName(&aTagName);
EditSubActionInfo subActionInfo(EditSubAction::eCreateOrRemoveBlock);
subActionInfo.blockType = &tagName;
nsresult rv = rules->WillDoAction(subActionInfo, &cancel, &handled);
if (cancel || NS_FAILED(rv)) {
EditActionResult result = CanHandleHTMLEditSubAction();
if (result.Canceled() || NS_WARN_IF(result.Failed())) {
return result.Rv();
}
// FYI: Ignore cancel result of WillInsert() since we've already checked
// whether we can or cannot edit current selection range.
nsresult rv = WillInsert();
if (NS_WARN_IF(rv == NS_ERROR_EDITOR_DESTROYED)) {
return NS_ERROR_EDITOR_DESTROYED;
}
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "WillInsert() failed, but ignored");
// FormatBlockContainerWithTransaction() creates AutoSelectionRestorer.
// Therefore, even if it returns NS_OK, editor might have been destroyed
// at restoring Selection.
rv = FormatBlockContainerWithTransaction(aTagName);
if (NS_WARN_IF(Destroyed())) {
return NS_ERROR_EDITOR_DESTROYED;
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!handled && SelectionRefPtr()->IsCollapsed()) {
nsRange* firstRange = SelectionRefPtr()->GetRangeAt(0);
if (NS_WARN_IF(!firstRange)) {
return NS_ERROR_FAILURE;
}
EditorDOMPoint atStartOfSelection(firstRange->StartRef());
if (NS_WARN_IF(!atStartOfSelection.IsSet()) ||
NS_WARN_IF(!atStartOfSelection.GetContainerAsContent())) {
return NS_ERROR_FAILURE;
}
// Have to find a place to put the block.
EditorDOMPoint pointToInsertBlock(atStartOfSelection);
while (!CanContainTag(*pointToInsertBlock.GetContainer(), aTagName)) {
pointToInsertBlock.Set(pointToInsertBlock.GetContainer());
if (NS_WARN_IF(!pointToInsertBlock.IsSet()) ||
NS_WARN_IF(!pointToInsertBlock.GetContainerAsContent())) {
return NS_ERROR_FAILURE;
}
}
if (pointToInsertBlock.GetContainer() !=
atStartOfSelection.GetContainer()) {
// We need to split up to the child of the point to insert a block.
SplitNodeResult splitBlockResult = SplitNodeDeepWithTransaction(
MOZ_KnownLive(*pointToInsertBlock.GetChild()), atStartOfSelection,
SplitAtEdges::eAllowToCreateEmptyContainer);
if (NS_WARN_IF(splitBlockResult.Failed())) {
return splitBlockResult.Rv();
}
pointToInsertBlock = splitBlockResult.SplitPoint();
if (NS_WARN_IF(!pointToInsertBlock.IsSet())) {
return NS_ERROR_FAILURE;
}
}
// Create a block and insert it before the right node if we split some
// parents of start of selection above, or just start of selection
// otherwise.
RefPtr<Element> newBlock =
CreateNodeWithTransaction(aTagName, pointToInsertBlock);
if (NS_WARN_IF(!newBlock)) {
return NS_ERROR_FAILURE;
}
// reposition selection to inside the block
ErrorResult error;
SelectionRefPtr()->Collapse(RawRangeBoundary(newBlock, 0), error);
if (NS_WARN_IF(error.Failed())) {
return error.StealNSResult();
}
}
nsDependentAtomString tagName(&aTagName);
EditSubActionInfo subActionInfo(EditSubAction::eCreateOrRemoveBlock);
subActionInfo.blockType = &tagName;
rv = rules->DidDoAction(subActionInfo, rv);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;

View File

@ -1108,6 +1108,17 @@ class HTMLEditor final : public TextEditor,
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult OnModifyDocument();
protected: // edit sub-action handler
/**
* CanHandleHTMLEditSubAction() checks whether there is at least one
* selection range or not, and whether the first range is editable.
* If it's not editable, `Canceled()` of the result returns true.
* If `Selection` is in odd situation, returns an error.
*
* XXX I think that `IsSelectionEditable()` is better name, but it's already
* in `EditorBase`...
*/
EditActionResult CanHandleHTMLEditSubAction() const;
/**
* Called before inserting something into the editor.
* This method may removes mPaddingBRElementForEmptyEditor if there is.
@ -1696,8 +1707,9 @@ class HTMLEditor final : public TextEditor,
nsTArray<OwningNonNull<nsINode>>& aNodeArray, nsAtom& aBlockTag);
/**
* FormatBlockContainer() is implementation of "formatBlock" command of
* `Document.execCommand()`. This applies block style or removes it.
* FormatBlockContainerWithTransaction() is implementation of "formatBlock"
* command of `Document.execCommand()`. This applies block style or removes
* it.
* NOTE: This creates AutoSelectionRestorer. Therefore, even when this
* return NS_OK, editor may have been destroyed.
*
@ -1711,7 +1723,7 @@ class HTMLEditor final : public TextEditor,
* called.
*/
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult
FormatBlockContainer(nsAtom& aBlockType);
FormatBlockContainerWithTransaction(nsAtom& aBlockType);
/**
* InsertBRElementIfHardLineIsEmptyAndEndsWithBlockBoundary() determines if
@ -2855,14 +2867,14 @@ class HTMLEditor final : public TextEditor,
nsresult MakeDefinitionListItemWithTransaction(nsAtom& aTagName);
/**
* InsertBasicBlockWithTransaction() inserts a block element whose name
* is aTagName at selection.
* FormatBlockContainerAsSubAction() inserts a block element whose name
* is aTagName at selection. If selection is not collapsed and aTagName is
* nsGkAtoms::normal or nsGkAtoms::_empty, this removes block containers.
*
* @param aTagName A block level element name. Must NOT be
* nsGkAtoms::dt nor nsGkAtoms::dd.
*/
MOZ_CAN_RUN_SCRIPT
nsresult InsertBasicBlockWithTransaction(nsAtom& aTagName);
MOZ_CAN_RUN_SCRIPT nsresult FormatBlockContainerAsSubAction(nsAtom& aTagName);
/**
* Increase/decrease the font size of selection.