зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1566795 - part 6: Make `HTMLEditor::RemoveInlinePropertyInternal()` remove text node style which comes from block parent r=m_kato
Finally, `Document.execCommand()` still does not work fine if selection starts from very start of block and/or end at very end of block because `PromoteInlineRange()` extends selection range to contain the containers, then, `SubtreeContentIterator` won't list up text nodes. In this case, `RemoveInlinePropertyInternal()` expects that `RemoveStyleInside()` removes text node style with creating `<span>` elements. However, `RemoveStyleInsilde()` only handles `Element`s and it handles elements from most-descendants. Therefore, it cannot distinguish whether text node style comes from removing inline elements or parent block. This patch makes `RemoveInlinePropertyInternal()` collect descendant text nodes in the range after handling all nodes in the range except descendant text nodes, then, check the final style of descendant text nodes, finally, remove the style if coming from parent block. Differential Revision: https://phabricator.services.mozilla.com/D47865 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
f885567a48
Коммит
ac74e89b26
|
@ -3955,11 +3955,9 @@ class HTMLEditor final : public TextEditor,
|
|||
/**
|
||||
* Helper routines for inline style.
|
||||
*/
|
||||
MOZ_CAN_RUN_SCRIPT
|
||||
nsresult SetInlinePropertyOnTextNode(Text& aData, int32_t aStartOffset,
|
||||
int32_t aEndOffset, nsAtom& aProperty,
|
||||
nsAtom* aAttribute,
|
||||
const nsAString& aValue);
|
||||
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult SetInlinePropertyOnTextNode(
|
||||
Text& aData, uint32_t aStartOffset, uint32_t aEndOffset,
|
||||
nsAtom& aProperty, nsAtom* aAttribute, const nsAString& aValue);
|
||||
|
||||
nsresult PromoteInlineRange(nsRange& aRange);
|
||||
nsresult PromoteRangeIfStartsOrEndsInNamedAnchor(nsRange& aRange);
|
||||
|
@ -3967,11 +3965,27 @@ class HTMLEditor final : public TextEditor,
|
|||
/**
|
||||
* RemoveStyleInside() removes elements which represent aProperty/aAttribute
|
||||
* and removes CSS style. This handles aElement and all its descendants
|
||||
* recursively.
|
||||
* (including leaf text nodes) recursively.
|
||||
*/
|
||||
MOZ_CAN_RUN_SCRIPT MOZ_MUST_USE nsresult
|
||||
RemoveStyleInside(Element& aElement, nsAtom* aProperty, nsAtom* aAttribute);
|
||||
|
||||
/**
|
||||
* CollectEditableLeafTextNodes() collects text nodes in aElement.
|
||||
*/
|
||||
void CollectEditableLeafTextNodes(
|
||||
Element& aElement, nsTArray<OwningNonNull<Text>>& aLeafTextNodes) const;
|
||||
|
||||
/**
|
||||
* IsRemovableParentStyleWithNewSpanElement() checks whether
|
||||
* aProperty/aAttribute of parent block can be removed from aContent with
|
||||
* creating `<span>` element. Note that this does NOT check whether the
|
||||
* specified style comes from parent block or not.
|
||||
*/
|
||||
static bool IsRemovableParentStyleWithNewSpanElement(nsIContent& aContent,
|
||||
nsAtom* aProperty,
|
||||
nsAtom* aAttribute);
|
||||
|
||||
bool IsAtFrontOfNode(nsINode& aNode, int32_t aOffset);
|
||||
bool IsAtEndOfNode(nsINode& aNode, int32_t aOffset);
|
||||
bool IsOnlyAttribute(const Element* aElement, nsAtom* aAttribute);
|
||||
|
|
|
@ -208,9 +208,6 @@ nsresult HTMLEditor::SetInlinePropertyInternal(
|
|||
MOZ_KnownLive(*startOfRange.GetContainerAsText()),
|
||||
startOfRange.Offset(), endOfRange.Offset(), aProperty, aAttribute,
|
||||
aAttributeValue);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -242,9 +239,6 @@ nsresult HTMLEditor::SetInlinePropertyInternal(
|
|||
MOZ_KnownLive(*startOfRange.GetContainerAsText()),
|
||||
startOfRange.Offset(), startOfRange.GetContainer()->Length(),
|
||||
aProperty, aAttribute, aAttributeValue);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -267,9 +261,6 @@ nsresult HTMLEditor::SetInlinePropertyInternal(
|
|||
nsresult rv = SetInlinePropertyOnTextNode(
|
||||
MOZ_KnownLive(*endOfRange.GetContainerAsText()), 0,
|
||||
endOfRange.Offset(), aProperty, aAttribute, aAttributeValue);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
@ -349,7 +340,7 @@ bool HTMLEditor::IsSimpleModifiableNode(nsIContent* aContent, nsAtom* aProperty,
|
|||
}
|
||||
|
||||
nsresult HTMLEditor::SetInlinePropertyOnTextNode(
|
||||
Text& aText, int32_t aStartOffset, int32_t aEndOffset, nsAtom& aProperty,
|
||||
Text& aText, uint32_t aStartOffset, uint32_t aEndOffset, nsAtom& aProperty,
|
||||
nsAtom* aAttribute, const nsAString& aValue) {
|
||||
if (!aText.GetParentNode() ||
|
||||
!CanContainTag(*aText.GetParentNode(), aProperty)) {
|
||||
|
@ -383,6 +374,9 @@ nsresult HTMLEditor::SetInlinePropertyOnTextNode(
|
|||
// We need to split off back of text node
|
||||
ErrorResult error;
|
||||
textNodeForTheRange = SplitNodeWithTransaction(atEnd, error);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
return error.StealNSResult();
|
||||
}
|
||||
|
@ -394,6 +388,9 @@ nsresult HTMLEditor::SetInlinePropertyOnTextNode(
|
|||
// We need to split off front of text node
|
||||
ErrorResult error;
|
||||
nsCOMPtr<nsIContent> newLeftNode = SplitNodeWithTransaction(atStart, error);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
if (NS_WARN_IF(error.Failed())) {
|
||||
return error.StealNSResult();
|
||||
}
|
||||
|
@ -405,19 +402,37 @@ nsresult HTMLEditor::SetInlinePropertyOnTextNode(
|
|||
nsCOMPtr<nsIContent> sibling = GetPriorHTMLSibling(textNodeForTheRange);
|
||||
if (IsSimpleModifiableNode(sibling, &aProperty, aAttribute, &aValue)) {
|
||||
// Previous sib is already right kind of inline node; slide this over
|
||||
return MoveNodeToEndWithTransaction(*textNodeForTheRange, *sibling);
|
||||
nsresult rv =
|
||||
MoveNodeToEndWithTransaction(*textNodeForTheRange, *sibling);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"MoveNodeToEndWithTransaction() failed");
|
||||
return rv;
|
||||
}
|
||||
sibling = GetNextHTMLSibling(textNodeForTheRange);
|
||||
if (IsSimpleModifiableNode(sibling, &aProperty, aAttribute, &aValue)) {
|
||||
// Following sib is already right kind of inline node; slide this over
|
||||
return MoveNodeWithTransaction(*textNodeForTheRange,
|
||||
EditorDOMPoint(sibling, 0));
|
||||
nsresult rv = MoveNodeWithTransaction(*textNodeForTheRange,
|
||||
EditorDOMPoint(sibling, 0));
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
|
||||
"MoveNodeWithTransaction() failed");
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
// Reparent the node inside inline node with appropriate {attribute,value}
|
||||
return SetInlinePropertyOnNode(*textNodeForTheRange, aProperty, aAttribute,
|
||||
aValue);
|
||||
nsresult rv = SetInlinePropertyOnNode(*textNodeForTheRange, aProperty,
|
||||
aAttribute, aValue);
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "SetInlinePropertyOnNode() failed");
|
||||
return rv;
|
||||
}
|
||||
|
||||
nsresult HTMLEditor::SetInlinePropertyOnNodeImpl(nsIContent& aNode,
|
||||
|
@ -1636,36 +1651,27 @@ nsresult HTMLEditor::RemoveInlinePropertyInternal(nsAtom* aProperty,
|
|||
return rv;
|
||||
}
|
||||
}
|
||||
// If parent block has the removing style, we should create `<span>`
|
||||
// element to remove the style even in HTML mode since Chrome does it.
|
||||
if (!CSSEditUtils::IsCSSEditableProperty(content, aProperty,
|
||||
aAttribute)) {
|
||||
continue;
|
||||
}
|
||||
if (!CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
|
||||
content, aProperty, aAttribute, EmptyString(),
|
||||
CSSEditUtils::eComputed)) {
|
||||
continue;
|
||||
}
|
||||
// startOfRange's computed style indicates the CSS equivalence to
|
||||
// the HTML style to remove is applied; but we found no element
|
||||
// in the ancestors of startOfRange carrying specified styles;
|
||||
// assume it comes from a rule and let's try to insert a span
|
||||
// "inverting" the style
|
||||
if (!CSSEditUtils::IsCSSInvertible(*aProperty, aAttribute)) {
|
||||
|
||||
if (!HTMLEditor::IsRemovableParentStyleWithNewSpanElement(
|
||||
content, aProperty, aAttribute)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!content->IsText()) {
|
||||
// XXX Do we need to call this even when data node or something? If
|
||||
// so, for what?
|
||||
DebugOnly<nsresult> rvIgnored = SetInlinePropertyOnNode(
|
||||
content, *aProperty, aAttribute,
|
||||
NS_LITERAL_STRING("-moz-editor-invert-value"));
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
NS_WARNING_ASSERTION(NS_FAILED(rvIgnored),
|
||||
"SetInlinePropertyOnNode() failed, but ignored");
|
||||
NS_WARNING_ASSERTION(NS_SUCCEEDED(rvIgnored),
|
||||
"SetInlinePropertyOnNode() "
|
||||
"failed, but ignored");
|
||||
continue;
|
||||
}
|
||||
|
||||
// If current node is a text node, we need to create `<span>` element
|
||||
// for it to overwrite parent style. Unfortunately, all browsers
|
||||
// don't join text nodes when removing a style. Therefore, there
|
||||
|
@ -1680,13 +1686,35 @@ nsresult HTMLEditor::RemoveInlinePropertyInternal(nsAtom* aProperty,
|
|||
MOZ_KnownLive(*content->AsText()), startOffset, endOffset,
|
||||
*aProperty, aAttribute,
|
||||
NS_LITERAL_STRING("-moz-editor-invert-value"));
|
||||
if (NS_WARN_IF(Destroyed())) {
|
||||
return NS_ERROR_EDITOR_DESTROYED;
|
||||
}
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
|
||||
// For avoiding unnecessary loop cost, check whether the style is
|
||||
// invertible first.
|
||||
if (aProperty && CSSEditUtils::IsCSSInvertible(*aProperty, aAttribute)) {
|
||||
// Finally, we should remove the style from all leaf text nodes if they
|
||||
// still have the style.
|
||||
AutoTArray<OwningNonNull<Text>, 32> leafTextNodes;
|
||||
for (auto& content : arrayOfContents) {
|
||||
if (content->IsElement()) {
|
||||
CollectEditableLeafTextNodes(*content->AsElement(), leafTextNodes);
|
||||
}
|
||||
}
|
||||
for (auto& textNode : leafTextNodes) {
|
||||
if (!HTMLEditor::IsRemovableParentStyleWithNewSpanElement(
|
||||
textNode, aProperty, aAttribute)) {
|
||||
continue;
|
||||
}
|
||||
nsresult rv = SetInlinePropertyOnTextNode(
|
||||
textNode, 0, textNode->TextLength(), *aProperty, aAttribute,
|
||||
NS_LITERAL_STRING("-moz-editor-invert-value"));
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1694,6 +1722,50 @@ nsresult HTMLEditor::RemoveInlinePropertyInternal(nsAtom* aProperty,
|
|||
return NS_WARN_IF(Destroyed()) ? NS_ERROR_EDITOR_DESTROYED : NS_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
bool HTMLEditor::IsRemovableParentStyleWithNewSpanElement(nsIContent& aContent,
|
||||
nsAtom* aProperty,
|
||||
nsAtom* aAttribute) {
|
||||
// We don't support to remove all inline styles with this path.
|
||||
if (!aProperty) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// First check whether the style is invertible since this is the fastest
|
||||
// check.
|
||||
if (!CSSEditUtils::IsCSSInvertible(*aProperty, aAttribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If parent block has the removing style, we should create `<span>`
|
||||
// element to remove the style even in HTML mode since Chrome does it.
|
||||
if (!CSSEditUtils::IsCSSEditableProperty(&aContent, aProperty, aAttribute)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// aContent's computed style indicates the CSS equivalence to
|
||||
// the HTML style to remove is applied; but we found no element
|
||||
// in the ancestors of aContent carrying specified styles;
|
||||
// assume it comes from a rule and let's try to insert a span
|
||||
// "inverting" the style
|
||||
return CSSEditUtils::IsCSSEquivalentToHTMLInlineStyleSet(
|
||||
&aContent, aProperty, aAttribute, EmptyString(), CSSEditUtils::eComputed);
|
||||
}
|
||||
|
||||
void HTMLEditor::CollectEditableLeafTextNodes(
|
||||
Element& aElement, nsTArray<OwningNonNull<Text>>& aLeafTextNodes) const {
|
||||
for (nsIContent* child = aElement.GetFirstChild(); child;
|
||||
child = child->GetNextSibling()) {
|
||||
if (child->IsElement()) {
|
||||
CollectEditableLeafTextNodes(*child->AsElement(), aLeafTextNodes);
|
||||
continue;
|
||||
}
|
||||
if (child->IsText()) {
|
||||
aLeafTextNodes.AppendElement(*child->AsText());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
HTMLEditor::IncreaseFontSize() {
|
||||
nsresult rv = IncreaseFontSizeAsAction();
|
||||
|
|
|
@ -167,72 +167,12 @@
|
|||
[[["bold",""\]\] "<span style=\\"font-weight: 900\\">foo[barbaz</span>}" queryCommandState("bold") after]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","true"\],["bold",""\]\] "<h3>[foobarbaz\]</h3>" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","true"\],["bold",""\]\] "<h3>[foobarbaz\]</h3>" queryCommandState("bold") after]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","false"\],["bold",""\]\] "<h3>[foobarbaz\]</h3>" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","false"\],["bold",""\]\] "<h3>[foobarbaz\]</h3>" queryCommandState("bold") after]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","true"\],["bold",""\]\] "{<h3>foobarbaz\]</h3>" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","true"\],["bold",""\]\] "{<h3>foobarbaz\]</h3>" queryCommandState("bold") after]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","false"\],["bold",""\]\] "{<h3>foobarbaz\]</h3>" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","false"\],["bold",""\]\] "{<h3>foobarbaz\]</h3>" queryCommandState("bold") after]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","true"\],["bold",""\]\] "<h3>[foobarbaz</h3>}" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","true"\],["bold",""\]\] "<h3>[foobarbaz</h3>}" queryCommandState("bold") after]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","false"\],["bold",""\]\] "<h3>[foobarbaz</h3>}" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","false"\],["bold",""\]\] "<h3>[foobarbaz</h3>}" queryCommandState("bold") after]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","true"\],["bold",""\]\] "{<h3>foobarbaz</h3>}" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","true"\],["bold",""\]\] "{<h3>foobarbaz</h3>}" queryCommandState("bold") after]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","false"\],["bold",""\]\] "{<h3>foobarbaz</h3>}" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","false"\],["bold",""\]\] "{<h3>foobarbaz</h3>}" queryCommandState("bold") after]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","true"\],["bold",""\]\] "<b>foo<span style=\\"font-weight: normal\\">bar<b>[baz\]</b>quz</span>qoz</b>" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","false"\],["bold",""\]\] "<b>foo<span style=\\"font-weight: normal\\">bar<b>[baz\]</b>quz</span>qoz</b>" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","true"\],["bold",""\]\] "{<h3>foo</h3><b>bar</b>}" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","true"\],["bold",""\]\] "{<h3>foo</h3><b>bar</b>}" queryCommandIndeterm("bold") after]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","false"\],["bold",""\]\] "{<h3>foo</h3><b>bar</b>}" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","false"\],["bold",""\]\] "{<h3>foo</h3><b>bar</b>}" queryCommandIndeterm("bold") after]
|
||||
expected: FAIL
|
||||
|
||||
[[["stylewithcss","true"\],["bold",""\]\] "<i><b>foo</b></i>[bar\]<i><b>baz</b></i>" compare innerHTML]
|
||||
expected: FAIL
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче