Skip to content

Commit f21d9fb

Browse files
committed
fix(rendering): complete TradingView compatibility repairs
1 parent 646e089 commit f21d9fb

15 files changed

Lines changed: 805 additions & 183 deletions

‎experiments/WebScene.NativeEngine.Probe/native/webscene_native_dom.cpp‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -186,8 +186,14 @@ paint_z_index_update update_paint_z_index(
186186
dom_node& node) noexcept
187187
{
188188
auto descendant_z_index = 0;
189-
auto contains_retained_canvas =
190-
node.tag == "canvas" && !node.canvas().commands.empty();
189+
// Canvas paint order is structural. A width/height reset clears its
190+
// display list before the application redraws, but it does not move the
191+
// element or an authored ::after overlay to a different CSS paint phase.
192+
// Basing this bit on the current command list made the cached layout state
193+
// alternate between backdrop and overlay across ordinary chart redraws.
194+
auto contains_retained_canvas = node.tag == "canvas"
195+
&& node.visible
196+
&& node.style.display != display_mode::none;
191197
for (auto* child : document.composed_children(node)) {
192198
const auto child_update = update_paint_z_index(document, *child);
193199
descendant_z_index = std::max(
@@ -245,7 +251,12 @@ size_t count_retained_canvases(
245251
const native_document& document,
246252
const dom_node& node)
247253
{
248-
auto count = node.tag == "canvas" && !node.canvas().commands.empty()
254+
// Count visible canvas elements rather than non-empty display lists. The
255+
// latter can be transiently empty between reset and redraw and must not
256+
// change the stable backdrop/canvas/overlay partition.
257+
auto count = node.tag == "canvas"
258+
&& node.visible
259+
&& node.style.display != display_mode::none
249260
? size_t{1U}
250261
: size_t{0U};
251262
for (const auto* child : document.composed_children(node)) {
@@ -266,7 +277,9 @@ void update_retained_canvas_paint_phase(
266277
// the final canvas can safely use the global overlay by document order.
267278
node.paints_after_retained_canvas =
268279
retained_canvas_seen && retained_canvases_remaining == 0U;
269-
if (node.tag == "canvas" && !node.canvas().commands.empty()) {
280+
if (node.tag == "canvas"
281+
&& node.visible
282+
&& node.style.display != display_mode::none) {
270283
retained_canvas_seen = true;
271284
if (retained_canvases_remaining != 0U) {
272285
--retained_canvases_remaining;

‎experiments/WebScene.NativeEngine.Probe/native/webscene_native_dom.h‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,7 @@ struct node_style final {
300300
std::vector<track> template_columns;
301301
std::vector<track> template_rows;
302302
std::vector<track> auto_columns;
303+
bool subgrid_columns{false};
303304
bool two_columns{false};
304305
bool auto_flow_column{false};
305306
bool fractional_rows{false};
@@ -925,6 +926,11 @@ struct dom_node final {
925926
float row_spacing{0};
926927
};
927928

929+
struct grid_layout_data final {
930+
std::vector<float> column_widths;
931+
float column_gap{0};
932+
};
933+
928934
struct form_control_data final {
929935
std::string value;
930936
size_t selection_start{0};
@@ -1122,6 +1128,31 @@ struct dom_node final {
11221128
}
11231129

11241130
std::unique_ptr<table_layout_data> table_layout_state;
1131+
const grid_layout_data& grid_layout() const noexcept
1132+
{
1133+
static const grid_layout_data empty;
1134+
return grid_layout_state == nullptr ? empty : *grid_layout_state;
1135+
}
1136+
1137+
grid_layout_data& mutable_grid_layout()
1138+
{
1139+
if (grid_layout_state == nullptr) {
1140+
grid_layout_state = std::make_unique<grid_layout_data>();
1141+
}
1142+
return *grid_layout_state;
1143+
}
1144+
1145+
bool has_grid_layout() const noexcept
1146+
{
1147+
return grid_layout_state != nullptr;
1148+
}
1149+
1150+
void clear_grid_layout() noexcept
1151+
{
1152+
grid_layout_state.reset();
1153+
}
1154+
1155+
std::unique_ptr<grid_layout_data> grid_layout_state;
11251156
// Live value/selection/checked/focus state is absent from ordinary DOM
11261157
// nodes. Option selectedness and checkedness remain separate from their
11271158
// authored attributes inside this cold record.

‎experiments/WebScene.NativeEngine.Probe/native/webscene_native_dom_layout.inc‎

Lines changed: 98 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,6 +1471,9 @@ void native_document::layout_children(dom_node& parent)
14711471
size_t row_span{1};
14721472
size_t column_span{1};
14731473
};
1474+
const auto inherited_subgrid_columns = parent_grid.subgrid_columns
1475+
&& parent.has_grid_layout()
1476+
&& !parent.grid_layout().column_widths.empty();
14741477
auto effective_columns = parent_grid.template_columns;
14751478
if (effective_columns.empty() && implicit_column_grid) {
14761479
const auto implicit_count = std::max<size_t>(
@@ -1490,9 +1493,11 @@ void native_document::layout_children(dom_node& parent)
14901493
parent_grid.auto_columns[index % parent_grid.auto_columns.size()]);
14911494
}
14921495
}
1493-
const auto column_count = std::max<size_t>(
1494-
implicit_column_grid ? 1U : 2U,
1495-
effective_columns.size());
1496+
const auto column_count = inherited_subgrid_columns
1497+
? parent.grid_layout().column_widths.size()
1498+
: std::max<size_t>(
1499+
implicit_column_grid ? 1U : 2U,
1500+
effective_columns.size());
14961501
const auto parse_line = [](std::string_view value) -> std::optional<int32_t> {
14971502
const auto first = value.find_first_not_of(" \t\r\n");
14981503
if (first == std::string_view::npos) return std::nullopt;
@@ -1619,56 +1624,63 @@ void native_document::layout_children(dom_node& parent)
16191624
}
16201625
}
16211626

1622-
const auto column_gap = resolve_length(
1623-
parent, parent.style.column_gap, content.width, 0);
1627+
const auto column_gap = inherited_subgrid_columns
1628+
? parent.grid_layout().column_gap
1629+
: resolve_length(parent, parent.style.column_gap, content.width, 0);
16241630
const auto row_gap = resolve_length(
16251631
parent, parent.style.row_gap, content.height, 0);
1626-
std::vector<float> column_widths(column_count, 0.0F);
1632+
std::vector<float> column_widths = inherited_subgrid_columns
1633+
? parent.grid_layout().column_widths
1634+
: std::vector<float>(column_count, 0.0F);
16271635
auto fractional_weight = 0.0F;
1628-
for (size_t column = 0; column < effective_columns.size(); ++column) {
1629-
const auto& track = effective_columns[column];
1630-
if (track.kind == grid_track::sizing::fixed) {
1631-
column_widths[column] = resolve_length(
1632-
parent, track.maximum, content.width, 0);
1633-
} else if (track.kind == grid_track::sizing::minmax) {
1634-
column_widths[column] = resolve_length(
1635-
parent, track.minimum, content.width, 0);
1636-
if (track.fraction <= 0 && is_specified(track.maximum)) {
1637-
column_widths[column] = std::max(
1638-
column_widths[column],
1639-
resolve_length(parent, track.maximum, content.width, 0));
1636+
if (!inherited_subgrid_columns) {
1637+
for (size_t column = 0; column < effective_columns.size(); ++column) {
1638+
const auto& track = effective_columns[column];
1639+
if (track.kind == grid_track::sizing::fixed) {
1640+
column_widths[column] = resolve_length(
1641+
parent, track.maximum, content.width, 0);
1642+
} else if (track.kind == grid_track::sizing::minmax) {
1643+
column_widths[column] = resolve_length(
1644+
parent, track.minimum, content.width, 0);
1645+
if (track.fraction <= 0 && is_specified(track.maximum)) {
1646+
column_widths[column] = std::max(
1647+
column_widths[column],
1648+
resolve_length(parent, track.maximum, content.width, 0));
1649+
}
16401650
}
1651+
fractional_weight += track.fraction;
1652+
}
1653+
for (const auto& item : items) {
1654+
if (item.column_span != 1U) continue;
1655+
const auto authored_track = item.column < effective_columns.size()
1656+
? &effective_columns[item.column] : nullptr;
1657+
if (authored_track != nullptr
1658+
&& authored_track->kind != grid_track::sizing::automatic) continue;
1659+
const auto margins = resolve_length(
1660+
*item.node, item.node->style.margin_left, content.width, 0)
1661+
+ resolve_length(
1662+
*item.node, item.node->style.margin_right, content.width, 0);
1663+
column_widths[item.column] = std::max(
1664+
column_widths[item.column],
1665+
intrinsic_size(*item.node, true, content.width) + margins);
16411666
}
1642-
fractional_weight += track.fraction;
1643-
}
1644-
for (const auto& item : items) {
1645-
if (item.column_span != 1U) continue;
1646-
const auto authored_track = item.column < effective_columns.size()
1647-
? &effective_columns[item.column] : nullptr;
1648-
if (authored_track != nullptr
1649-
&& authored_track->kind != grid_track::sizing::automatic) continue;
1650-
const auto margins = resolve_length(
1651-
*item.node, item.node->style.margin_left, content.width, 0)
1652-
+ resolve_length(
1653-
*item.node, item.node->style.margin_right, content.width, 0);
1654-
column_widths[item.column] = std::max(
1655-
column_widths[item.column],
1656-
intrinsic_size(*item.node, true, content.width) + margins);
16571667
}
16581668
const auto total_gap = column_gap * static_cast<float>(column_count - 1U);
1659-
const auto committed_width = std::accumulate(
1660-
column_widths.begin(), column_widths.end(), total_gap);
1661-
const auto fractional_space = std::max(0.0F, content.width - committed_width);
1662-
if (fractional_weight > 0) {
1663-
for (size_t column = 0; column < effective_columns.size(); ++column) {
1664-
const auto& track = effective_columns[column];
1665-
if (track.fraction <= 0) continue;
1666-
column_widths[column] = std::max(
1667-
column_widths[column],
1668-
fractional_space * track.fraction / fractional_weight);
1669+
if (!inherited_subgrid_columns) {
1670+
const auto committed_width = std::accumulate(
1671+
column_widths.begin(), column_widths.end(), total_gap);
1672+
const auto fractional_space = std::max(0.0F, content.width - committed_width);
1673+
if (fractional_weight > 0) {
1674+
for (size_t column = 0; column < effective_columns.size(); ++column) {
1675+
const auto& track = effective_columns[column];
1676+
if (track.fraction <= 0) continue;
1677+
column_widths[column] = std::max(
1678+
column_widths[column],
1679+
fractional_space * track.fraction / fractional_weight);
1680+
}
1681+
} else if (committed_width < content.width && !column_widths.empty()) {
1682+
column_widths.back() += content.width - committed_width;
16691683
}
1670-
} else if (committed_width < content.width && !column_widths.empty()) {
1671-
column_widths.back() += content.width - committed_width;
16721684
}
16731685

16741686
const auto row_count = std::max<size_t>(
@@ -1772,6 +1784,16 @@ void native_document::layout_children(dom_node& parent)
17721784
auto height = is_specified(child->style.height)
17731785
? outer_authored_size(*child, false, available_height)
17741786
: std::max(0.0F, available_height - margin_top - margin_bottom);
1787+
if (child->style.grid().subgrid_columns) {
1788+
auto& inherited = child->mutable_grid_layout();
1789+
inherited.column_widths.assign(
1790+
column_widths.begin() + static_cast<std::ptrdiff_t>(item.column),
1791+
column_widths.begin() + static_cast<std::ptrdiff_t>(
1792+
item.column + item.column_span));
1793+
inherited.column_gap = column_gap;
1794+
} else {
1795+
child->clear_grid_layout();
1796+
}
17751797
layout_child(*child, parent.layout, {
17761798
column_offsets[item.column] + margin_left,
17771799
row_offsets[item.row] + margin_top,
@@ -2723,10 +2745,21 @@ void native_document::layout_children(dom_node& parent)
27232745
const auto propagates_document_overflow =
27242746
(&parent == body_ && child->tag == "html")
27252747
|| (parent.tag == "html" && child->tag == "body");
2726-
const auto flow_width = propagates_document_overflow
2748+
// A scroll container includes descendant overflow that reaches it
2749+
// through an overflow-visible child. The child's principal box still
2750+
// participates in normal flow at its own used size: descendant
2751+
// overflow must enlarge scrollWidth/scrollHeight without moving the
2752+
// next sibling or making an auto-height ancestor grow. TradingView's
2753+
// settings panes use exactly this shape: overflow:auto on the pane,
2754+
// then a height:100% wrapper whose visible descendants are taller.
2755+
const auto propagates_child_overflow_x = propagates_document_overflow
2756+
|| child->style.overflow_x == overflow_mode::visible;
2757+
const auto propagates_child_overflow_y = propagates_document_overflow
2758+
|| child->style.overflow_y == overflow_mode::visible;
2759+
const auto scrollable_flow_width = propagates_child_overflow_x
27272760
? std::max(child->layout.width, child->scroll_content_width)
27282761
: child->layout.width;
2729-
const auto flow_height = propagates_document_overflow
2762+
const auto scrollable_flow_height = propagates_child_overflow_y
27302763
? std::max(child->layout.height, child->scroll_content_height)
27312764
: child->layout.height;
27322765
// Scroll translation is applied to `positioned`; `assigned` remains in
@@ -2735,13 +2768,13 @@ void native_document::layout_children(dom_node& parent)
27352768
// or scrollHeight grow on every relayout and creates an endless range.
27362769
maximum_flow_right = std::max(
27372770
maximum_flow_right,
2738-
assigned.x + flow_width);
2771+
assigned.x + scrollable_flow_width);
27392772
maximum_flow_bottom = std::max(
27402773
maximum_flow_bottom,
2741-
assigned.y + flow_height);
2774+
assigned.y + scrollable_flow_height);
27422775
if (!horizontal) {
27432776
cursor = assigned.y - content.y
2744-
+ flow_height
2777+
+ child->layout.height
27452778
+ vertical_margin_bottom
27462779
+ justify_gap;
27472780
}
@@ -2923,7 +2956,9 @@ void native_document::layout_children(dom_node& parent)
29232956
}
29242957
}
29252958
parent.scroll_content_width = horizontal
2926-
? std::max(content.width, cursor)
2959+
? std::max(
2960+
content.width,
2961+
std::max(cursor, maximum_flow_right - content.x))
29272962
: std::max(content.width, maximum_flow_right - content.x);
29282963
parent.scroll_content_height = horizontal
29292964
? std::max(
@@ -2933,7 +2968,11 @@ void native_document::layout_children(dom_node& parent)
29332968
: maximum_flow_bottom - content.y)
29342969
: std::max(
29352970
content.height,
2936-
std::max(cursor, maximum_float_bottom - content.y));
2971+
std::max(
2972+
cursor,
2973+
std::max(
2974+
maximum_flow_bottom - content.y,
2975+
maximum_float_bottom - content.y)));
29372976
// Blink determines the exposed scroll dimensions from pixel-snapped
29382977
// layout sizes. A fractional descendant edge that remains inside the same
29392978
// snapped CSS pixel as the scrollport therefore creates no usable scroll
@@ -3552,6 +3591,13 @@ void native_document::layout_child(
35523591
std::max(0.0F, assigned.width),
35533592
std::max(0.0F, assigned.height)};
35543593
const auto* parent = composed_parent(child);
3594+
const auto inherits_parent_grid_columns = child.style.grid().subgrid_columns
3595+
&& parent != nullptr
3596+
&& is_grid_container(parent->style.display)
3597+
&& (parent->style.grid().two_columns
3598+
|| (parent->style.grid().auto_flow_column
3599+
&& !parent->style.grid().auto_columns.empty()));
3600+
if (!inherits_parent_grid_columns) child.clear_grid_layout();
35553601
child.used_height_is_definite = assigned_height_is_definite
35563602
|| (is_specified(child.style.height)
35573603
&& (child.style.height.unit != length_unit::percent

0 commit comments

Comments
 (0)