tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit bdc0aa0314a69f9b24e5de7112b10ee86bb3634f
parent 91d2164cd7426b3def4dd0cbe0e25a6acf7d5faa
Author: Masayuki Nakano <masayuki@d-toybox.com>
Date:   Mon, 17 Nov 2025 23:53:08 +0000

Bug 2000127 - Rewrite `HTMLEditUtils::IsFlexOrGridItem()` again and make `HTMLEditUtils::IsBlockElement()` return true for the items r=m_kato

When a child of flex or grid container's `display` is `contents`, its
children become the flex/grid items.  Then, `<display-inside>` of
the children is computed as `flex` or `grid`, but the
`<display-outside>` may be `inline`.  For the consistency with normal
flex/grid items, such "inherited" items should also be treated as
blocks.  Therefore, we need to make `HTMLEditUtils::IsBlockElement` and
`HTMLEditUtils::IsInlineContent` check whether the given element is a
flex/grid item.

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

Diffstat:
Meditor/libeditor/HTMLEditUtils.cpp | 70++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
Meditor/libeditor/HTMLEditUtils.h | 16+++++++++++++---
Mtesting/web-platform/meta/editing/run/delete.html.ini | 6++++++
Mtesting/web-platform/meta/editing/run/forwarddelete.html.ini | 6++++++
Atesting/web-platform/tests/editing/crashtests/inserttext-to-delete-flex-item-boundary-whose-display-contents.html | 30++++++++++++++++++++++++++++++
Mtesting/web-platform/tests/editing/data/delete.js | 44++++++++++++++++++++++++++++++++++++++++++++
Mtesting/web-platform/tests/editing/data/forwarddelete.js | 46+++++++++++++++++++++++++++++++++++++++++++++-
7 files changed, 196 insertions(+), 22 deletions(-)

diff --git a/editor/libeditor/HTMLEditUtils.cpp b/editor/libeditor/HTMLEditUtils.cpp @@ -279,6 +279,8 @@ bool HTMLEditUtils::IsBlockElement(const nsIContent& aContent, MOZ_ASSERT(aBlockInlineCheck != BlockInlineCheck::Auto); if (MOZ_UNLIKELY(!aContent.IsElement())) { + // FIXME: If aContent is a visible `Text` and a flex/grid item, we should + // treat it as block. return false; } // If it's a <br>, we should always treat it as an inline element because @@ -313,6 +315,11 @@ bool HTMLEditUtils::IsBlockElement(const nsIContent& aContent, if (!styleDisplay->IsInlineOutsideStyle()) { return true; } + // Special case. If aContent is a grid or flex item, we want to treat it as a + // block to handle it with the general paths. + if (HTMLEditUtils::ParentElementIsGridOrFlexContainer(aContent)) { + return true; + } // If we're checking display-inside, inline-block, etc should be a block too. return aBlockInlineCheck == BlockInlineCheck::UseComputedDisplayStyle && styleDisplay->DisplayInside() == StyleDisplayInside::FlowRoot && @@ -327,6 +334,8 @@ bool HTMLEditUtils::IsInlineContent(const nsIContent& aContent, MOZ_ASSERT(aBlockInlineCheck != BlockInlineCheck::Auto); if (!aContent.IsElement()) { + // FIXME: If aContent is a visible `Text` and a flex/grid item, we should + // treat it as block. return true; } // If it's a <br>, we should always treat it as an inline element because @@ -357,13 +366,28 @@ bool HTMLEditUtils::IsInlineContent(const nsIContent& aContent, // style if aContent. return !IsHTMLBlockElementByDefault(aContent); } + // Special case. If aContent is a grid or flex item, we want to treat it as a + // block to handle it with the general paths. + if (HTMLEditUtils::ParentElementIsGridOrFlexContainer(aContent)) { + return false; + } // Different block IsBlockElement, when the display-outside is inline, it's // simply an inline element. return styleDisplay->IsInlineOutsideStyle(); } -bool HTMLEditUtils::IsFlexOrGridItem(const Element& aElement) { - Element* const parentElement = aElement.GetParentElement(); +bool HTMLEditUtils::ParentElementIsGridOrFlexContainer( + const nsIContent& aMaybeFlexOrGridItemContent) { + if (!aMaybeFlexOrGridItemContent.IsElement()) { + if (!aMaybeFlexOrGridItemContent.IsText() || + !aMaybeFlexOrGridItemContent.AsText()->TextDataLength()) { + return false; + } + // FIXME: If aMaybeFlexOrGridItemContent has only collapsible white-spaces + // and next to a block boundary, it's invisible and shouldn't be a flex/grid + // item. However, scanning block boundary requires to call this method. + } + Element* const parentElement = aMaybeFlexOrGridItemContent.GetParentElement(); // Editable state does not affect to elements across shadow DOM boundaries. // Therefore, we don't need to treat aElement as an flex item nor a grid item // as so if aElement is a root element of a shadow DOM unless we'll support @@ -376,8 +400,11 @@ bool HTMLEditUtils::IsFlexOrGridItem(const Element& aElement) { // We should consider whether the element is a flex item or a grid item // without nsIFrame since we don't want to refresh the layout while // `HTMLEditor` handles an action to avoid to run script. - RefPtr<const ComputedStyle> elementStyle = - nsComputedDOMStyle::GetComputedStyleNoFlush(&aElement); + const RefPtr<const ComputedStyle> elementStyle = + nsComputedDOMStyle::GetComputedStyleNoFlush( + aMaybeFlexOrGridItemContent.IsElement() + ? aMaybeFlexOrGridItemContent.AsElement() + : parentElement); if (MOZ_UNLIKELY(!elementStyle)) { return false; } @@ -386,23 +413,30 @@ bool HTMLEditUtils::IsFlexOrGridItem(const Element& aElement) { return false; } const RefPtr<const ComputedStyle> parentElementStyle = - nsComputedDOMStyle::GetComputedStyleNoFlush(parentElement); + aMaybeFlexOrGridItemContent.IsElement() + ? nsComputedDOMStyle::GetComputedStyleNoFlush(parentElement) + : elementStyle; if (MOZ_UNLIKELY(!parentElementStyle)) { return false; } - const nsStyleDisplay* parentStyleDisplay = parentElementStyle->StyleDisplay(); - const auto parentDisplayInside = parentStyleDisplay->DisplayInside(); - const bool parentIsFlexContainerOrGridContainer = - parentDisplayInside == StyleDisplayInside::Flex || - parentDisplayInside == StyleDisplayInside::Grid; - // We assume that the computed `display` value of a flex item and a grid item - // is "block". If it would be false, we need to update - // HTMLEditUtils::IsBlockElement() and HTMLEditUtils::IsInlineContent(). - // However, they are called a lot so that we need to be careful about changing - // them to check the parent style. - MOZ_ASSERT_IF(parentIsFlexContainerOrGridContainer, - styleDisplay->IsBlockOutsideStyle()); - return parentIsFlexContainerOrGridContainer; + const auto parentDisplayInside = + parentElementStyle->StyleDisplay()->DisplayInside(); + return parentDisplayInside == StyleDisplayInside::Flex || + parentDisplayInside == StyleDisplayInside::Grid; +} + +bool HTMLEditUtils::IsFlexOrGridItem(const nsIContent& aContent) { + if (!HTMLEditUtils::ParentElementIsGridOrFlexContainer(aContent)) { + return false; + } + // Note that if parent element's `display` is `contents`, the + // `display-outside` style of aElement may not be block. However, even in + // such case, HTMLEditUtils::IsBlockElement() should return true. + MOZ_ASSERT_IF(aContent.IsElement(), + HTMLEditUtils::IsBlockElement( + *aContent.AsElement(), + BlockInlineCheck::UseComputedDisplayOutsideStyle)); + return true; } bool HTMLEditUtils::IsInclusiveAncestorCSSDisplayNone( diff --git a/editor/libeditor/HTMLEditUtils.h b/editor/libeditor/HTMLEditUtils.h @@ -233,10 +233,11 @@ class HTMLEditUtils final { [[nodiscard]] static bool IsDisplayInsideFlowRoot(const Element& aElement); /** - * Return true if aElement is a flex item or a grid item. This works only - * when aElement has a primary frame. + * Return true if aContent is a flex item or a grid item. Note that if + * aContent is the `Text` node in the following case, this returns `true`. + * <div style="display:flex"><span style="display:contents">text</span></div> */ - [[nodiscard]] static bool IsFlexOrGridItem(const Element& aElement); + [[nodiscard]] static bool IsFlexOrGridItem(const nsIContent& aContent); /** * IsRemovableInlineStyleElement() returns true if aElement is an inline @@ -3214,6 +3215,15 @@ class HTMLEditUtils final { */ static Element* GetElementOfImmediateBlockBoundary( const nsIContent& aContent, const WalkTreeDirection aDirection); + + /** + * Return true if parent element is a grid or flex container. + * Note that even if the parent is a grid/flex container, the + * <display-outside> of aMaybeFlexOrGridItemContent may be "inline" if the + * parent is also a grid/flex item but has `display:contents`. + */ + [[nodiscard]] static bool ParentElementIsGridOrFlexContainer( + const nsIContent& aMaybeFlexOrGridItemContent); }; /** diff --git a/testing/web-platform/meta/editing/run/delete.html.ini b/testing/web-platform/meta/editing/run/delete.html.ini @@ -657,3 +657,9 @@ [[["delete",""\]\] "<div style=display:inline-grid><span>{}<br></span></div><div>abc</div>" compare innerHTML] expected: FAIL + + [[["delete",""\]\] "<div style=display:flex><span>abc</span><span style=display:contents><span>[\]def</span></span></div>" compare innerHTML] + expected: FAIL + + [[["delete",""\]\] "<div style=display:grid><span>abc</span><span style=display:contents><span>[\]def</span></span></div>" compare innerHTML] + expected: FAIL diff --git a/testing/web-platform/meta/editing/run/forwarddelete.html.ini b/testing/web-platform/meta/editing/run/forwarddelete.html.ini @@ -568,3 +568,9 @@ [[["forwarddelete",""\]\] "<div>abc</div><div style=display:inline-grid><span>{}<br></span></div>" compare innerHTML] expected: FAIL + + [[["forwarddelete",""\]\] "<div style=display:flex><span>abc[\]</span><span style=display:contents><span>def</span></span></div>" compare innerHTML] + expected: FAIL + + [[["forwarddelete",""\]\] "<div style=display:grid><span>abc[\]</span><span style=display:contents><span>def</span></span></div>" compare innerHTML] + expected: FAIL diff --git a/testing/web-platform/tests/editing/crashtests/inserttext-to-delete-flex-item-boundary-whose-display-contents.html b/testing/web-platform/tests/editing/crashtests/inserttext-to-delete-flex-item-boundary-whose-display-contents.html @@ -0,0 +1,30 @@ +<!doctype html> +<html> +<head> +<meta charset="utf-8"> +<script> +"use strict"; + +addEventListener("load", () => { + document.designMode = "on"; + getSelection().setBaseAndExtent( + document.querySelector("b"), + 1, + document.querySelector("span"), + 1 + ); + document.execCommand("insertText", false, "A"); +}, {once: true}); +</script> +</head> +<body> +<div style="display:inline-grid"> + <b> + </b> + <i style="display:contents"> + <span> + </span> + </i> +</div> +</body> +</html> diff --git a/testing/web-platform/tests/editing/data/delete.js b/testing/web-platform/tests/editing/data/delete.js @@ -3400,4 +3400,48 @@ var browserTests = [ "<div><div>abc</div></div><div>def</div>"], [true], {}], + +// display:contents of grid/flex item makes its children as items of its container. +// Therefore, they should be treated as items correctly, and the display:contents elements should +// be treated as meaningless container. Therefore, they should be deleted once they become empty. +["<div style=display:flex><span>abc</span><span style=display:contents>[]def</span></div>", + [["delete",""]], + "<div style=\"display:flex\"><span>abcdef</span></div>", + [true], + {}], +["<div style=display:flex><span>abc</span><span style=display:contents><span>[]def</span></span></div>", + [["delete",""]], + "<div style=\"display:flex\"><span>abcdef</span></div>", + [true], + {}], +["<div style=display:flex><span style=display:contents>abc</span><span>[]def</span></div>", + [["delete",""]], + "<div style=\"display:flex\"><span style=\"display:contents\">abcdef</span></div>", + [true], + {}], +["<div style=display:flex><span style=display:contents><span>abc</span></span><span>[]def</span></div>", + [["delete",""]], + "<div style=\"display:flex\"><span style=\"display:contents\"><span>abcdef</span></span></div>", + [true], + {}], +["<div style=display:grid><span>abc</span><span style=display:contents>[]def</span></div>", + [["delete",""]], + "<div style=\"display:grid\"><span>abcdef</span></div>", + [true], + {}], +["<div style=display:grid><span>abc</span><span style=display:contents><span>[]def</span></span></div>", + [["delete",""]], + "<div style=\"display:grid\"><span>abcdef</span></div>", + [true], + {}], +["<div style=display:grid><span style=display:contents>abc</span><span>[]def</span></div>", + [["delete",""]], + "<div style=\"display:grid\"><span style=\"display:contents\">abcdef</span></div>", + [true], + {}], +["<div style=display:grid><span style=display:contents><span>abc</span></span><span>[]def</span></div>", + [["delete",""]], + "<div style=\"display:grid\"><span style=\"display:contents\"><span>abcdef</span></span></div>", + [true], + {}], ] diff --git a/testing/web-platform/tests/editing/data/forwarddelete.js b/testing/web-platform/tests/editing/data/forwarddelete.js @@ -3257,9 +3257,53 @@ var browserTests = [ {}], // XXX I'm not sure which result is better. ["<div><div>abc</div>{}<br></div><div>def</div>", - [["delete",""]], + [["forwarddelete",""]], ["<div><div>abc</div>def</div>", "<div><div>abc</div></div><div>def</div>"], [true], {}], + +// display:contents of grid/flex item makes its children as items of its container. +// Therefore, they should be treated as items correctly, and the display:contents elements should +// be treated as meaningless container. Therefore, they should be deleted once they become empty. +["<div style=display:flex><span>abc[]</span><span style=display:contents>def</span></div>", + [["forwarddelete",""]], + "<div style=\"display:flex\"><span>abcdef</span></div>", + [true], + {}], +["<div style=display:flex><span>abc[]</span><span style=display:contents><span>def</span></span></div>", + [["forwarddelete",""]], + "<div style=\"display:flex\"><span>abcdef</span></div>", + [true], + {}], +["<div style=display:flex><span style=display:contents>abc[]</span><span>def</span></div>", + [["forwarddelete",""]], + "<div style=\"display:flex\"><span style=\"display:contents\">abcdef</span></div>", + [true], + {}], +["<div style=display:flex><span style=display:contents><span>abc[]</span></span><span>def</span></div>", + [["forwarddelete",""]], + "<div style=\"display:flex\"><span style=\"display:contents\"><span>abcdef</span></span></div>", + [true], + {}], +["<div style=display:grid><span>abc[]</span><span style=display:contents>def</span></div>", + [["forwarddelete",""]], + "<div style=\"display:grid\"><span>abcdef</span></div>", + [true], + {}], +["<div style=display:grid><span>abc[]</span><span style=display:contents><span>def</span></span></div>", + [["forwarddelete",""]], + "<div style=\"display:grid\"><span>abcdef</span></div>", + [true], + {}], +["<div style=display:grid><span style=display:contents>abc[]</span><span>def</span></div>", + [["forwarddelete",""]], + "<div style=\"display:grid\"><span style=\"display:contents\">abcdef</span></div>", + [true], + {}], +["<div style=display:grid><span style=display:contents><span>abc[]</span></span><span>def</span></div>", + [["forwarddelete",""]], + "<div style=\"display:grid\"><span style=\"display:contents\"><span>abcdef</span></span></div>", + [true], + {}], ]