Bug 1859245 - part 4: Make HTMLEditor handle only the new direction mode r=m_kato

Depends on D191607

Differential Revision: https://phabricator.services.mozilla.com/D191608
This commit is contained in:
Masayuki Nakano 2023-10-27 01:57:29 +00:00
parent 982ddb7a9a
commit 7e6da5b9b6
11 changed files with 51 additions and 343 deletions

View File

@ -115,7 +115,6 @@
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_docshell.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_editor.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/StaticPrefs_full_screen_api.h"
#include "mozilla/StaticPrefs_layout.h"
@ -5386,12 +5385,13 @@ bool Document::ExecCommand(const nsAString& aHTMLCommandName, bool aShowUI,
SetUseCounter(eUseCounter_custom_DocumentExecCommandContentReadOnly);
break;
case Command::EnableCompatibleJoinSplitNodeDirection:
// We don't allow to take the legacy behavior back if the new one is
// enabled by default.
if (StaticPrefs::
editor_join_split_direction_compatible_with_the_other_browsers() &&
!adjustedValue.EqualsLiteral("true") &&
!aSubjectPrincipal.IsSystemPrincipal()) {
// We didn't allow to enable the legacy behavior once we've enabled the
// new behavior by default. For keeping the behavior at supporting both
// mode, we should keep returning `false` if the web app to enable the
// legacy mode. Additionally, we don't support the legacy direction
// anymore. Therefore, we can return `false` here even if the caller is
// an addon or chrome script.
if (!adjustedValue.EqualsLiteral("true")) {
return false;
}
break;

View File

@ -44,10 +44,8 @@ enum class CollectChildrenOption; // HTMLEditUtils.h
enum class EditAction; // mozilla/EditAction.h
enum class EditorCommandParamType : uint16_t; // mozilla/EditorCommands.h
enum class EditSubAction : int32_t; // mozilla/EditAction.h
enum class JoinNodesDirection; // JoinSplitNodeDirection.h
enum class ParagraphSeparator; // mozilla/HTMLEditor.h
enum class SpecifiedStyle : uint8_t; // mozilla/PendingStyles.h
enum class SplitNodeDirection; // JoinSplitNodeDirection.h
enum class SuggestCaret; // EditorUtils.h
enum class WithTransaction; // HTMLEditHelpers.h

View File

@ -6445,8 +6445,7 @@ HTMLEditor::RemoveBlockContainerElementWithTransactionBetween(
if (NS_WARN_IF(!rightmostElement)) {
return Err(NS_ERROR_FAILURE);
}
MOZ_ASSERT_IF(GetSplitNodeDirection() == SplitNodeDirection::LeftNodeIsNewOne,
rightmostElement == &aBlockContainerElement);
{
// MOZ_KnownLive(rightmostElement) because it's grabbed by
// unwrappedSplitResult.

View File

@ -18,7 +18,6 @@
#include "HTMLEditUtils.h"
#include "InsertNodeTransaction.h"
#include "JoinNodesTransaction.h"
#include "JoinSplitNodeDirection.h"
#include "MoveNodeTransaction.h"
#include "PendingStyles.h"
#include "ReplaceTextTransaction.h"
@ -239,26 +238,9 @@ HTMLEditor::AttributeFilter HTMLEditor::CopyAllAttributesExceptIdAndDir =
aAttr.NodeInfo()->NameAtom() == nsGkAtoms::dir));
};
static bool ShouldUseTraditionalJoinSplitDirection(const Document& aDocument) {
if (nsIPrincipal* principal = aDocument.GetPrincipalForPrefBasedHacks()) {
if (principal->IsURIInPrefList("editor.join_split_direction."
"force_use_traditional_direction")) {
return true;
}
if (principal->IsURIInPrefList("editor.join_split_direction."
"force_use_compatible_direction")) {
return false;
}
}
return !StaticPrefs::
editor_join_split_direction_compatible_with_the_other_browsers();
}
HTMLEditor::HTMLEditor(const Document& aDocument)
: EditorBase(EditorBase::EditorType::HTML),
mCRInParagraphCreatesParagraph(false),
mUseGeckoTraditionalJoinSplitBehavior(
ShouldUseTraditionalJoinSplitDirection(aDocument)),
mIsObjectResizingEnabled(
StaticPrefs::editor_resizing_enabled_by_default()),
mIsResizing(false),
@ -5090,7 +5072,6 @@ Result<SplitNodeResult, nsresult> HTMLEditor::SplitNodeWithTransaction(
!ignoredError.Failed(),
"OnStartToHandleTopLevelEditSubAction() failed, but ignored");
mMaybeHasJoinSplitTransactions = true;
RefPtr<SplitNodeTransaction> transaction =
SplitNodeTransaction::Create(*this, aStartOfRightNode);
nsresult rv = DoTransactionInternal(transaction);
@ -5494,7 +5475,6 @@ Result<JoinNodesResult, nsresult> HTMLEditor::JoinNodesWithTransaction(
return Err(NS_ERROR_FAILURE);
}
mMaybeHasJoinSplitTransactions = true;
const nsresult rv = DoTransactionInternal(transaction);
// FYI: Now, DidJoinNodesTransaction() must have been run if succeeded.
if (NS_WARN_IF(Destroyed())) {

View File

@ -454,42 +454,6 @@ class HTMLEditor final : public EditorBase,
return mIsAbsolutelyPositioningEnabled;
}
/**
* Enable/disable Gecko's traditional join/split node direction, that is,
* creating left node at splitting a node and removing left node at joining 2
* nodes. This is acceptable only before first join/split transaction is
* created.
*/
bool EnableCompatibleJoinSplitNodeDirection(bool aEnable) {
if (!CanChangeJoinSplitNodeDirection()) {
return false;
}
mUseGeckoTraditionalJoinSplitBehavior = !aEnable;
return true;
}
/**
* Return true if the instance works with the legacy join/split node
* direction.
*/
[[nodiscard]] bool IsCompatibleJoinSplitNodeDirectionEnabled() const {
return !mUseGeckoTraditionalJoinSplitBehavior;
}
/**
* Return true if web apps can still change the join split node direction.
* For saving the footprint, each transaction does not store join/split node
* direction at first run. Therefore, join/split node transactions need to
* refer the direction of corresponding HTMLEditor. So if the direction were
* changed after creating join/split transactions, they would break the DOM
* tree with undoing/redoing within wrong direction. Therefore, once this
* instance created a join or split node transaction, this returns false to
* block to change the direction.
*/
[[nodiscard]] bool CanChangeJoinSplitNodeDirection() const {
return !mMaybeHasJoinSplitTransactions;
}
/**
* returns the deepest absolutely positioned container of the selection
* if it exists or null.
@ -2702,12 +2666,6 @@ class HTMLEditor final : public EditorBase,
MOZ_CAN_RUN_SCRIPT nsresult OnDocumentModified();
protected: // Called by helper classes.
/**
* Get split/join node(s) direction for **this** instance.
*/
[[nodiscard]] inline SplitNodeDirection GetSplitNodeDirection() const;
[[nodiscard]] inline JoinNodesDirection GetJoinNodesDirection() const;
MOZ_CAN_RUN_SCRIPT void OnStartToHandleTopLevelEditSubAction(
EditSubAction aTopLevelEditSubAction,
nsIEditor::EDirection aDirectionOfTopLevelEditSubAction,
@ -4452,10 +4410,6 @@ class HTMLEditor final : public EditorBase,
bool mCRInParagraphCreatesParagraph;
// Whether use Blink/WebKit compatible joining nodes and split a node
// direction or Gecko's traditional direction.
bool mUseGeckoTraditionalJoinSplitBehavior;
// resizing
bool mIsObjectResizingEnabled;
bool mIsResizing;
@ -4549,10 +4503,6 @@ class HTMLEditor final : public EditorBase,
bool mHasBeforeInputBeenCanceled = false;
// Set to true once the instance creates a JoinNodesTransaction or
// SplitNodeTransaction. See also CanChangeJoinSplitNodeDirection().
bool mMaybeHasJoinSplitTransactions = false;
ParagraphSeparator mDefaultParagraphSeparator;
friend class AlignStateAtSelection; // CollectEditableTargetNodes,
@ -4574,8 +4524,7 @@ class HTMLEditor final : public EditorBase,
// RemoveEmptyInclusiveAncestorInlineElements,
// mComposerUpdater, mHasBeforeInputBeenCanceled
friend class JoinNodesTransaction; // DidJoinNodesTransaction, DoJoinNodes,
// DoSplitNode, GetJoinNodesDirection,
// RangeUpdaterRef
// DoSplitNode, // RangeUpdaterRef
friend class ListElementSelectionState; // CollectEditTargetNodes,
// CollectNonEditableNodes
friend class ListItemElementSelectionState; // CollectEditTargetNodes,
@ -4589,8 +4538,7 @@ class HTMLEditor final : public EditorBase,
// CollectNonEditableNodes,
// CollectTableChildren
friend class SlurpBlobEventListener; // BlobReader
friend class SplitNodeTransaction; // DoJoinNodes, DoSplitNode,
// GetSplitNodeDirection
friend class SplitNodeTransaction; // DoJoinNodes, DoSplitNode
friend class TransactionManager; // DidDoTransaction, DidRedoTransaction,
// DidUndoTransaction
friend class

View File

@ -6,10 +6,10 @@
#include "EditorCommands.h"
#include "EditorBase.h" // for EditorBase
#include "ErrorList.h"
#include "HTMLEditor.h" // for HTMLEditor
#include "mozilla/BasePrincipal.h" // for nsIPrincipal::IsSystemPrincipal()
#include "mozilla/StaticPrefs_editor.h"
#include "mozilla/dom/Element.h" // for Element
#include "mozilla/dom/Document.h" // for Document
#include "mozilla/dom/HTMLInputElement.h" // for HTMLInputElement
@ -50,9 +50,6 @@ bool SetDocumentStateCommand::IsCommandEnabled(Command aCommand,
switch (aCommand) {
case Command::SetDocumentReadOnly:
return !!aEditorBase;
case Command::EnableCompatibleJoinSplitNodeDirection:
return aEditorBase && aEditorBase->IsHTMLEditor() &&
aEditorBase->AsHTMLEditor()->CanChangeJoinSplitNodeDirection();
default:
// The other commands are always enabled if given editor is an HTMLEditor.
return aEditorBase && aEditorBase->IsHTMLEditor();
@ -170,16 +167,12 @@ nsresult SetDocumentStateCommand::DoCommandParam(
return NS_OK;
}
case Command::EnableCompatibleJoinSplitNodeDirection:
MOZ_ASSERT_IF(
StaticPrefs::
editor_join_split_direction_compatible_with_the_other_browsers() &&
aPrincipal && !aPrincipal->IsSystemPrincipal(),
aBoolParam.value());
return MOZ_KnownLive(aEditorBase.AsHTMLEditor())
->EnableCompatibleJoinSplitNodeDirection(
aBoolParam.value())
? NS_OK
: NS_SUCCESS_DOM_NO_OPERATION;
// Now we don't support the legacy join/split node direction anymore, but
// this result may be used for the feature detection whether Gecko
// supports the new direction mode. Therefore, even though we do nothing,
// but we should return NS_OK to return `true` from
// `Document.execCommand()`.
return NS_OK;
default:
return NS_ERROR_NOT_IMPLEMENTED;
}
@ -370,8 +363,11 @@ nsresult SetDocumentStateCommand::GetCommandStateParams(
if (NS_WARN_IF(!htmlEditor)) {
return NS_ERROR_INVALID_ARG;
}
return aParams.SetBool(
STATE_ALL, htmlEditor->IsCompatibleJoinSplitNodeDirectionEnabled());
// Now we don't support the legacy join/split node direction anymore, but
// this result may be used for the feature detection whether Gecko
// supports the new direction mode. Therefore, we should return `true`
// even though executing the command does nothing.
return aParams.SetBool(STATE_ALL, true);
}
default:
return NS_ERROR_NOT_IMPLEMENTED;

View File

@ -10,7 +10,6 @@
#include "EditorDOMPoint.h"
#include "HTMLEditHelpers.h"
#include "JoinSplitNodeDirection.h" // for JoinNodesDirection and SplitNodeDirection
#include "SelectionState.h" // for RangeItem
#include "ErrorList.h" // for nsresult
@ -32,18 +31,6 @@ namespace mozilla {
using namespace dom;
SplitNodeDirection HTMLEditor::GetSplitNodeDirection() const {
return MOZ_LIKELY(mUseGeckoTraditionalJoinSplitBehavior)
? SplitNodeDirection::LeftNodeIsNewOne
: SplitNodeDirection::RightNodeIsNewOne;
}
JoinNodesDirection HTMLEditor::GetJoinNodesDirection() const {
return MOZ_LIKELY(mUseGeckoTraditionalJoinSplitBehavior)
? JoinNodesDirection::LeftNodeIntoRightNode
: JoinNodesDirection::RightNodeIntoLeftNode;
}
Result<CreateElementResult, nsresult>
HTMLEditor::ReplaceContainerAndCloneAttributesWithTransaction(
Element& aOldContainer, const nsAtom& aTagName) {

View File

@ -1,51 +0,0 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef JoinSplitNodeDirection_h
#define JoinSplitNodeDirection_h
#include <ostream>
namespace mozilla {
// JoinNodesDirection is also affected to which one is new node at splitting
// a node because a couple of undo/redo.
enum class JoinNodesDirection {
LeftNodeIntoRightNode,
RightNodeIntoLeftNode,
};
static inline std::ostream& operator<<(std::ostream& aStream,
JoinNodesDirection aJoinNodesDirection) {
if (aJoinNodesDirection == JoinNodesDirection::LeftNodeIntoRightNode) {
return aStream << "JoinNodesDirection::LeftNodeIntoRightNode";
}
if (aJoinNodesDirection == JoinNodesDirection::RightNodeIntoLeftNode) {
return aStream << "JoinNodesDirection::RightNodeIntoLeftNode";
}
return aStream << "Invalid value";
}
// SplitNodeDirection is also affected to which one is removed at joining a
// node because a couple of undo/redo.
enum class SplitNodeDirection {
LeftNodeIsNewOne,
RightNodeIsNewOne,
};
static inline std::ostream& operator<<(std::ostream& aStream,
SplitNodeDirection aSplitNodeDirection) {
if (aSplitNodeDirection == SplitNodeDirection::LeftNodeIsNewOne) {
return aStream << "SplitNodeDirection::LeftNodeIsNewOne";
}
if (aSplitNodeDirection == SplitNodeDirection::RightNodeIsNewOne) {
return aStream << "SplitNodeDirection::RightNodeIsNewOne";
}
return aStream << "Invalid value";
}
} // namespace mozilla
#endif // JoinSplitNodeDirection_h

View File

@ -8,7 +8,6 @@
#include "AutoRangeArray.h" // for AutoRangeArray
#include "EditorUtils.h" // for EditorUtils, AutoRangeArray
#include "ErrorList.h"
#include "JoinSplitNodeDirection.h" // for JoinNodesDirection, SplitNodeDirection
#include "mozilla/Assertions.h" // for MOZ_ASSERT, etc.
#include "mozilla/IntegerRange.h" // for IntegerRange

View File

@ -22,172 +22,32 @@ SimpleTest.waitForFocus(async () => {
}
await resetIframe();
await SpecialPowers.pushPrefEnv({
set: [["editor.join_split_direction.compatible_with_the_other_browsers", false]],
});
(function test_command_when_legacy_behavior_is_enabled_by_default() {
iframe.contentDocument.body.innerHTML = "<div contenteditable><br></div>";
ok(
iframe.contentDocument.queryCommandSupported("enableCompatibleJoinSplitDirection"),
"test_command_when_legacy_behavior_is_enabled_by_default: command should be supported"
"command should be supported"
);
ok(
iframe.contentDocument.queryCommandEnabled("enableCompatibleJoinSplitDirection"),
"test_command_when_legacy_behavior_is_enabled_by_default: command should be enabled"
);
ok(
!iframe.contentDocument.queryCommandState("enableCompatibleJoinSplitDirection"),
"test_command_when_legacy_behavior_is_enabled_by_default: command state should be false"
);
is(
iframe.contentDocument.queryCommandValue("enableCompatibleJoinSplitDirection"),
"",
"test_command_when_legacy_behavior_is_enabled_by_default: command value should be empty string"
);
ok(
iframe.contentDocument.execCommand("enableCompatibleJoinSplitDirection", false, "true"),
"test_command_when_legacy_behavior_is_enabled_by_default: command to enable it should return true"
);
})();
(function test_command_when_enabling_new_behavior_when_legacy_one_is_enabled_by_default() {
ok(
iframe.contentDocument.queryCommandSupported("enableCompatibleJoinSplitDirection"),
"test_command_when_enabling_new_behavior_when_legacy_one_is_enabled_by_default: command should be supported"
);
ok(
iframe.contentDocument.queryCommandEnabled("enableCompatibleJoinSplitDirection"),
"test_command_when_enabling_new_behavior_when_legacy_one_is_enabled_by_default: command should be enabled"
"command should be enabled"
);
ok(
iframe.contentDocument.queryCommandState("enableCompatibleJoinSplitDirection"),
"test_command_when_enabling_new_behavior_when_legacy_one_is_enabled_by_default: command state should be true"
"command state should be true"
);
is(
iframe.contentDocument.queryCommandValue("enableCompatibleJoinSplitDirection"),
"",
"test_command_when_enabling_new_behavior_when_legacy_one_is_enabled_by_default: command value should be empty string"
);
ok(
iframe.contentDocument.execCommand("enableCompatibleJoinSplitDirection", false, "false"),
"test_command_when_enabling_new_behavior_when_legacy_one_is_enabled_by_default: command to disable it should return true"
);
})();
(function test_command_when_disabling_new_behavior_when_the_legacy_one_is_enabled_by_default() {
ok(
iframe.contentDocument.queryCommandSupported("enableCompatibleJoinSplitDirection"),
"test_command_when_disabling_new_behavior_when_the_legacy_one_is_enabled_by_default: command should be supported"
);
ok(
iframe.contentDocument.queryCommandEnabled("enableCompatibleJoinSplitDirection"),
"test_command_when_disabling_new_behavior_when_the_legacy_one_is_enabled_by_default: command should be enabled"
);
ok(
!iframe.contentDocument.queryCommandState("enableCompatibleJoinSplitDirection"),
"test_command_when_disabling_new_behavior_when_the_legacy_one_is_enabled_by_default: command state should be false"
);
is(
iframe.contentDocument.queryCommandValue("enableCompatibleJoinSplitDirection"),
"",
"test_command_when_disabling_new_behavior_when_the_legacy_one_is_enabled_by_default: command value should be empty string"
);
})();
await resetIframe();
await SpecialPowers.pushPrefEnv({
set: [["editor.join_split_direction.compatible_with_the_other_browsers", true]],
});
(function test_command_when_new_behavior_is_enabled_by_default() {
iframe.contentDocument.body.innerHTML = "<div contenteditable><br></div>";
ok(
iframe.contentDocument.queryCommandSupported("enableCompatibleJoinSplitDirection"),
"test_command_when_new_behavior_is_enabled_by_default: command should be supported"
);
ok(
iframe.contentDocument.queryCommandEnabled("enableCompatibleJoinSplitDirection"),
"test_command_when_new_behavior_is_enabled_by_default: command should be enabled"
);
ok(
iframe.contentDocument.queryCommandState("enableCompatibleJoinSplitDirection"),
"test_command_when_new_behavior_is_enabled_by_default: command state should be true"
);
is(
iframe.contentDocument.queryCommandValue("enableCompatibleJoinSplitDirection"),
"",
"test_command_when_new_behavior_is_enabled_by_default: command value should be empty string"
"command value should be empty string"
);
ok(
!iframe.contentDocument.execCommand("enableCompatibleJoinSplitDirection", false, "false"),
"test_command_when_new_behavior_is_enabled_by_default: command to disable it should return false"
"command to disable it should return false"
);
ok(
iframe.contentDocument.queryCommandState("enableCompatibleJoinSplitDirection"),
"test_command_when_new_behavior_is_enabled_by_default: command state should be true even after executing the command to disable it"
"command state should be true even after executing the command to disable it"
);
})();
await resetIframe();
await SpecialPowers.pushPrefEnv({
set: [["editor.join_split_direction.compatible_with_the_other_browsers", false]],
});
(function test_command_disabled_after_joining_nodes() {
iframe.contentDocument.body.innerHTML = "<div contenteditable><p>abc</p><p>def</p></div>";
iframe.contentWindow.getSelection().collapse(iframe.contentDocument.querySelector("p + p").firstChild, 0);
iframe.contentDocument.execCommand("delete");
ok(
!iframe.contentDocument.execCommand("enableCompatibleJoinSplitDirection", false, "true"),
"test_command_disabled_after_joining_nodes: command should return false"
);
ok(
!iframe.contentDocument.queryCommandEnabled("enableCompatibleJoinSplitDirection"),
"test_command_when_new_behavior_is_enabled_by_default: command should be disabled"
);
ok(
!iframe.contentDocument.queryCommandState("enableCompatibleJoinSplitDirection"),
"test_command_when_new_behavior_is_enabled_by_default: command state should be false"
);
})();
await resetIframe();
await SpecialPowers.pushPrefEnv({
set: [["editor.join_split_direction.compatible_with_the_other_browsers", false]],
});
(function test_command_disabled_after_splitting_node() {
iframe.contentDocument.body.innerHTML = "<div contenteditable><p>abcdef</p></div>";
iframe.contentWindow.getSelection().collapse(iframe.contentDocument.querySelector("p").firstChild, "abc".length);
iframe.contentDocument.execCommand("insertParagraph");
ok(
!iframe.contentDocument.execCommand("enableCompatibleJoinSplitDirection", false, "true"),
"test_command_disabled_after_splitting_node: command should return false"
);
ok(
!iframe.contentDocument.queryCommandEnabled("enableCompatibleJoinSplitDirection"),
"test_command_disabled_after_splitting_node: command should be disabled"
);
ok(
!iframe.contentDocument.queryCommandState("enableCompatibleJoinSplitDirection"),
"test_command_disabled_after_splitting_node: command state should be false"
);
})();
await resetIframe();
await SpecialPowers.pushPrefEnv({
set: [["editor.join_split_direction.compatible_with_the_other_browsers", false]],
});
(function test_split_direction_after_enabling_new_direction() {
iframe.contentDocument.body.innerHTML = "<div contenteditable><p>abc</p><p>def</p></div>";
const rightP = iframe.contentDocument.querySelector("p + p");
iframe.contentWindow.getSelection().collapse(rightP.firstChild, 0);
iframe.contentDocument.execCommand("delete");
iframe.contentDocument.execCommand("enableCompatibleJoinSplitDirection", false, "true");
is(
iframe.contentDocument.querySelector("p"),
rightP,
"test_split_direction_after_enabling_new_direction: left paragraph should be deleted and right paragraph should be alive"
);
})();
SimpleTest.finish();
});

View File

@ -4758,14 +4758,6 @@
value: true
mirror: always
# Whether use Blink/WebKit compatbile joining nodes and split a node direction.
# false: Left node will be created (at splitting) and deleted (at joining)
# true: Right node will be created (at splitting) and deleted (at joinining)
- name: editor.join_split_direction.compatible_with_the_other_browsers
type: bool
value: true
mirror: always
# Delay to mask last input character in password fields.
# If negative value, to use platform's default behavior.
# If 0, no delay to mask password.