Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Libraries/LibWeb/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,7 @@ set(SOURCES
Layout/TextAreaBox.cpp
Layout/TextInputBox.cpp
Layout/TextNode.cpp
Layout/TextOffsetMapping.cpp
Layout/TreeBuilder.cpp
Layout/VideoBox.cpp
Layout/Viewport.cpp
Expand Down
2 changes: 1 addition & 1 deletion Libraries/LibWeb/CSS/StyleComputer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1783,7 +1783,7 @@ GC::Ptr<ComputedProperties> StyleComputer::compute_style_impl(DOM::AbstractEleme
style_scope.build_rule_cache_if_needed();

// Special path for elements that represent a pseudo-element in some element's internal shadow tree.
if (abstract_element.element().use_pseudo_element().has_value()) {
if (abstract_element.element().use_pseudo_element().has_value() && !abstract_element.pseudo_element().has_value()) {
auto& element = abstract_element.element();
auto& host_element = *element.root().parent_or_shadow_host_element();

Expand Down
21 changes: 13 additions & 8 deletions Libraries/LibWeb/DOM/CharacterData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/MutationType.h>
#include <LibWeb/DOM/Range.h>
#include <LibWeb/DOM/Text.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Layout/TextOffsetMapping.h>

namespace Web::DOM {

Expand Down Expand Up @@ -131,14 +133,17 @@ WebIDL::ExceptionOr<void> CharacterData::replace_data(size_t offset, size_t coun
return {};

// NB: Called during DOM text mutation, layout is stale.
if (auto* text_node = as_if<Layout::TextNode>(unsafe_layout_node())) {
// NOTE: Since the text node's data has changed, we need to invalidate the text for rendering.
// This ensures that the new text is reflected in layout, even if we don't end up
// doing a full layout tree rebuild.
text_node->invalidate_text_for_rendering();

// We also need to relayout.
text_node->set_needs_layout_update(SetNeedsLayoutReason::CharacterDataReplaceData);
if (auto* text = as_if<Text>(*this)) {
Layout::TextOffsetMapping mapping { *text };
mapping.for_each_fragment([](Layout::TextNode& slice) {
// NB: Since the text node's data has changed, we need to invalidate the text for rendering.
// This ensures that the new text is reflected in layout, even if we don't end up doing a full layout
// tree rebuild.
slice.invalidate_text_for_rendering();

// We also need to relayout.
slice.set_needs_layout_update(SetNeedsLayoutReason::CharacterDataReplaceData);
});
}

document().bump_character_data_version();
Expand Down
4 changes: 2 additions & 2 deletions Libraries/LibWeb/DOM/Document.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6903,15 +6903,15 @@ Vector<GC::Root<Range>> Document::find_matching_text(String const& query, CaseSe
for (; i < text_block.positions.size() - 1 && match_index.value() > text_block.positions[i + 1].start_offset; ++i)
match_start_position = &text_block.positions[i + 1];

auto start_position = match_index.value() - match_start_position->start_offset;
auto start_position = match_index.value() - match_start_position->start_offset + match_start_position->dom_offset_within_node;
auto& start_dom_node = match_start_position->dom_node;

auto* match_end_position = match_start_position;
for (; i < text_block.positions.size() - 1 && (match_index.value() + utf16_query.length_in_code_units() > text_block.positions[i + 1].start_offset); ++i)
match_end_position = &text_block.positions[i + 1];

auto& end_dom_node = match_end_position->dom_node;
auto end_position = match_index.value() + utf16_query.length_in_code_units() - match_end_position->start_offset;
auto end_position = match_index.value() + utf16_query.length_in_code_units() - match_end_position->start_offset + match_end_position->dom_offset_within_node;

matches.append(Range::create(start_dom_node, start_position, end_dom_node, end_position));
match_start_position = match_end_position;
Expand Down
1 change: 1 addition & 0 deletions Libraries/LibWeb/DOM/Element.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -948,6 +948,7 @@ CSS::RequiredInvalidationAfterStyleChange Element::recompute_style(bool& did_cha

recompute_pseudo_element_style(CSS::PseudoElement::Before);
recompute_pseudo_element_style(CSS::PseudoElement::After);
recompute_pseudo_element_style(CSS::PseudoElement::FirstLetter);
recompute_pseudo_element_style(CSS::PseudoElement::Selection);
if (m_rendered_in_top_layer)
recompute_pseudo_element_style(CSS::PseudoElement::Backdrop);
Expand Down
12 changes: 10 additions & 2 deletions Libraries/LibWeb/DOM/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
#include <LibWeb/Infra/CharacterTypes.h>
#include <LibWeb/Layout/Node.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Layout/TextOffsetMapping.h>
#include <LibWeb/MathML/MathMLElement.h>
#include <LibWeb/Namespace.h>
#include <LibWeb/Page/Page.h>
Expand Down Expand Up @@ -3343,8 +3344,15 @@ ErrorOr<String> Node::name_or_description(NameOrDescription target, Document con
// cause traversal through element subtrees in way that’s necessary to check for descendants that are referenced by
// aria-labelledby or aria-describedby and/or un-hidden. See the comment for substep A above.
if (is_text() && (!parent_element() || (parent_element()->is_referenced() || !parent_element()->is_hidden() || !parent_element()->has_hidden_ancestor() || parent_element()->has_referenced_and_hidden_ancestor()))) {
if (layout_node() && layout_node()->is_text_node())
return as<Layout::TextNode>(layout_node())->text_for_rendering().to_utf8_but_should_be_ported_to_utf16();
if (layout_node()) {
StringBuilder builder { StringBuilder::Mode::UTF16 };
Layout::TextOffsetMapping mapping { static_cast<DOM::Text const&>(*this) };
mapping.for_each_fragment([&](Layout::TextNode const& slice) {
builder.append(slice.text_for_rendering());
});
if (!builder.is_empty())
return builder.to_utf16_string().to_utf8_but_should_be_ported_to_utf16();
}
return text_content()->to_utf8_but_should_be_ported_to_utf16();
}

Expand Down
44 changes: 33 additions & 11 deletions Libraries/LibWeb/DOM/Range.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@
#include <LibWeb/HTML/HTMLHtmlElement.h>
#include <LibWeb/HTML/HTMLScriptElement.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Layout/TextOffsetMapping.h>
#include <LibWeb/Namespace.h>
#include <LibWeb/Painting/PaintableFragment.h>
#include <LibWeb/Painting/PaintableWithLines.h>
#include <LibWeb/Painting/ViewportPaintable.h>
#include <LibWeb/TrustedTypes/RequireTrustedTypesForDirective.h>
#include <LibWeb/TrustedTypes/TrustedTypePolicy.h>
Expand Down Expand Up @@ -1193,18 +1197,36 @@ GC::Ref<Geometry::DOMRectList> Range::get_client_rects()
// 2. For each Text node selected or partially selected by the range (including when the boundary-points
// are identical), include scaled DOMRect object (for the part that is selected, not the whole line box).
auto const& text = static_cast<DOM::Text const&>(*node);
auto const* paintable = text.paintable();
if (paintable && selection_state != Painting::Paintable::SelectionState::None) {
if (auto const* paintable_lines = as_if<Painting::PaintableWithLines>(paintable->containing_block())) {
auto fragments = paintable_lines->fragments();
for (auto frag = fragments.begin(); frag != fragments.end(); frag++) {
auto rect = frag->range_rect(selection_state, start_offset(), end_offset());
rects.append(Geometry::DOMRect::create(realm(), rect.to_type<float>()));
}
} else {
dbgln("FIXME: Failed to get client rects for node {}", node->debug_description());
}
if (selection_state == Painting::Paintable::SelectionState::None)
continue;

Layout::TextOffsetMapping mapping { text };
if (!mapping.primary()) {
dbgln("FIXME: Failed to get client rects for node {}", node->debug_description());
continue;
}
size_t filter_dom_start = 0;
size_t filter_dom_end = NumericLimits<size_t>::max();
switch (selection_state) {
case Painting::Paintable::SelectionState::Full:
break;
case Painting::Paintable::SelectionState::StartAndEnd:
filter_dom_start = start_offset();
filter_dom_end = end_offset();
break;
case Painting::Paintable::SelectionState::Start:
filter_dom_start = start_offset();
break;
case Painting::Paintable::SelectionState::End:
filter_dom_end = end_offset();
break;
case Painting::Paintable::SelectionState::None:
VERIFY_NOT_REACHED();
}
mapping.for_each_paintable_fragment_in_dom_range(filter_dom_start, filter_dom_end, [&](Painting::PaintableFragment const& fragment) {
auto rect = fragment.range_rect(selection_state, start_offset(), end_offset());
rects.append(Geometry::DOMRect::create(realm(), rect.to_type<float>()));
});
}
}
return Geometry::DOMRectList::create(realm(), move(rects));
Expand Down
1 change: 1 addition & 0 deletions Libraries/LibWeb/Forward.h
Original file line number Diff line number Diff line change
Expand Up @@ -965,6 +965,7 @@ class ReplacedBox;
class SVGSVGBox;
class TableWrapper;
class TextNode;
class TextOffsetMapping;
class TreeBuilder;
class VideoBox;
class Viewport;
Expand Down
18 changes: 11 additions & 7 deletions Libraries/LibWeb/HTML/HTMLElement.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
#include <LibWeb/Infra/Strings.h>
#include <LibWeb/Layout/Box.h>
#include <LibWeb/Layout/TextNode.h>
#include <LibWeb/Layout/TextOffsetMapping.h>
#include <LibWeb/Namespace.h>
#include <LibWeb/Painting/PaintableBox.h>
#include <LibWeb/Selection/Selection.h>
Expand Down Expand Up @@ -331,13 +332,16 @@ static Vector<Variant<Utf16String, RequiredLineBreakCount>> rendered_text_collec
// element. Soft hyphens should be preserved. [CSSTEXT]

if (auto const* layout_text_node = as_if<Layout::TextNode>(layout_node)) {
Layout::TextNode::ChunkIterator iterator { *layout_text_node, false, false };
while (true) {
auto chunk = iterator.next();
if (!chunk.has_value())
break;
items.append(Utf16String::from_utf16(chunk.release_value().view));
}
Layout::TextOffsetMapping mapping { layout_text_node->dom_node() };
mapping.for_each_fragment([&](Layout::TextNode const& slice) {
Layout::TextNode::ChunkIterator iterator { slice, false, false };
while (true) {
auto chunk = iterator.next();
if (!chunk.has_value())
break;
items.append(Utf16String::from_utf16(chunk.release_value().view));
}
});
return items;
}

Expand Down
18 changes: 18 additions & 0 deletions Libraries/LibWeb/Layout/LayoutState.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,24 @@ void LayoutState::commit(Box& root)
transfer_box_model_metrics(line_paintable->box_model(), *used_values);
}

// Ensure anonymous InlineNode ancestors of painted InlineNodes also get paintables. This handles cases like
// ::first-letter inside ::before, where the first-letter wrapper has a paintable but the ::before InlineNode above
// it does not.
for (auto* paintable : inline_node_paintables) {
for (auto const* ancestor = paintable->layout_node().parent(); ancestor; ancestor = ancestor->parent()) {
if (!is<InlineNode>(*ancestor) || ancestor->dom_node())
break;
auto& inline_ancestor = const_cast<InlineNode&>(static_cast<InlineNode const&>(*ancestor));
if (inline_ancestor.first_paintable())
break;
auto line_paintable = inline_ancestor.create_paintable_for_line_with_index(paintable->line_index());
inline_ancestor.add_paintable(line_paintable);
inline_node_paintables.set(line_paintable.ptr());
if (auto const* used_values = try_get(inline_ancestor))
transfer_box_model_metrics(line_paintable->box_model(), *used_values);
}
}

// Resolve relative positions for regular boxes (not line box fragments):
// NOTE: This needs to occur before fragments are transferred into the corresponding inline paintables, because
// after this transfer, the containing_line_box_fragment will no longer be valid.
Expand Down
4 changes: 2 additions & 2 deletions Libraries/LibWeb/Layout/Node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@

namespace Web::Layout {

Node::Node(DOM::Document& document, DOM::Node* node)
Node::Node(DOM::Document& document, DOM::Node* node, AttachToDOMNode attach_to_dom_node)
: m_dom_node(node ? *node : document)
, m_anonymous(node == nullptr)
{
if (node)
if (node && attach_to_dom_node == AttachToDOMNode::Yes)
node->set_layout_node({}, *this);
}

Expand Down
8 changes: 7 additions & 1 deletion Libraries/LibWeb/Layout/Node.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ class WEB_API Node
virtual bool is_inline_node() const { return false; }
virtual bool is_break_node() const { return false; }
virtual bool is_text_node() const { return false; }
virtual bool is_text_slice_node() const { return false; }
virtual bool is_viewport() const { return false; }
virtual bool is_svg_box() const { return false; }
virtual bool is_svg_geometry_box() const { return false; }
Expand Down Expand Up @@ -220,8 +221,13 @@ class WEB_API Node
[[nodiscard]] bool has_been_wrapped_in_table_wrapper() const { return m_has_been_wrapped_in_table_wrapper; }
void set_has_been_wrapped_in_table_wrapper(bool value) { m_has_been_wrapped_in_table_wrapper = value; }

enum class AttachToDOMNode {
No,
Yes,
};

protected:
Node(DOM::Document&, DOM::Node*);
Node(DOM::Document&, DOM::Node*, AttachToDOMNode = AttachToDOMNode::Yes);

virtual void visit_edges(Cell::Visitor&) override;

Expand Down
24 changes: 24 additions & 0 deletions Libraries/LibWeb/Layout/TextNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,30 @@ TextNode::TextNode(DOM::Document& document, DOM::Text& text)
{
}

TextNode::TextNode(DOM::Document& document, DOM::Text& text, AttachToDOMNode attach_to_dom_node)
: Node(document, &text, attach_to_dom_node)
{
}

TextNode::~TextNode() = default;

GC_DEFINE_ALLOCATOR(TextSliceNode);

TextSliceNode::TextSliceNode(DOM::Document& document, DOM::Text& text, AttachToDOMNode attach_to_dom_node, size_t dom_start_offset, size_t dom_length)
: TextNode(document, text, attach_to_dom_node)
, m_dom_start_offset(dom_start_offset)
, m_dom_length_in_code_units(dom_length)
{
}

TextSliceNode::~TextSliceNode() = default;

void TextSliceNode::visit_edges(Cell::Visitor& visitor)
{
Base::visit_edges(visitor);
visitor.visit(m_first_letter_slice);
}

// https://w3c.github.io/mathml-core/#new-text-transform-values
static Utf16String apply_math_auto_text_transform(Utf16String const& string)
{
Expand Down Expand Up @@ -323,6 +345,8 @@ void TextNode::compute_text_for_rendering()
auto const maybe_lang = parent_element ? parent_element->lang() : Optional<String> {};
auto const lang = maybe_lang.has_value() ? maybe_lang.value() : Optional<StringView> {};
auto text = apply_text_transform(dom_node().data(), computed_values().text_transform(), lang);
if (dom_start_offset() > 0 || dom_length() < dom_node().data().length_in_code_units())
text = Utf16String::from_utf16(text.utf16_view().substring_view(dom_start_offset(), dom_length()));

// The logic below deals with converting whitespace characters. If we don't have them, return early.
if (text.is_empty() || !any_of(text, is_ascii_space)) {
Expand Down
40 changes: 39 additions & 1 deletion Libraries/LibWeb/Layout/TextNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@
namespace Web::Layout {

class LineBoxFragment;
class TextSliceNode;

class TextNode final : public Node {
class TextNode : public Node {
GC_CELL(TextNode, Node);
GC_DECLARE_ALLOCATOR(TextNode);

Expand All @@ -27,6 +28,10 @@ class TextNode final : public Node {

DOM::Text const& dom_node() const { return static_cast<DOM::Text const&>(*Node::dom_node()); }

virtual size_t dom_start_offset() const { return 0; }

virtual size_t dom_length() const { return dom_node().data().length_in_code_units(); }

Utf16String const& text_for_rendering() const;

struct Chunk {
Expand Down Expand Up @@ -85,6 +90,9 @@ class TextNode final : public Node {

virtual GC::Ptr<Painting::Paintable> create_paintable() const override;

protected:
TextNode(DOM::Document&, DOM::Text&, AttachToDOMNode);

private:
virtual bool is_text_node() const final { return true; }

Expand All @@ -95,7 +103,37 @@ class TextNode final : public Node {
mutable OwnPtr<Unicode::Segmenter> m_line_segmenter;
};

class TextSliceNode final : public TextNode {
GC_CELL(TextSliceNode, TextNode);
GC_DECLARE_ALLOCATOR(TextSliceNode);

public:
TextSliceNode(DOM::Document&, DOM::Text&, AttachToDOMNode, size_t dom_start_offset, size_t dom_length);
virtual ~TextSliceNode() override;

virtual size_t dom_start_offset() const override { return m_dom_start_offset; }
virtual size_t dom_length() const override { return m_dom_length_in_code_units; }

// Only meaningful on a remainder slice. Returns the first-letter slice that renders the leading
// sub-range of the same DOM::Text, or nullptr if first-letter is not active for this DOM::Text.
TextSliceNode const* first_letter_slice() const { return m_first_letter_slice; }
TextSliceNode* first_letter_slice() { return m_first_letter_slice; }

void set_first_letter_slice(TextSliceNode& slice) { m_first_letter_slice = slice; }

private:
virtual bool is_text_slice_node() const override { return true; }
virtual void visit_edges(Cell::Visitor&) override;

size_t m_dom_start_offset { 0 };
size_t m_dom_length_in_code_units { 0 };
GC::Ptr<TextSliceNode> m_first_letter_slice;
};

template<>
inline bool Node::fast_is<TextNode>() const { return is_text_node(); }

template<>
inline bool Node::fast_is<TextSliceNode>() const { return is_text_slice_node(); }

}
Loading
Loading