diff --git a/docs/validation/code-oss-css-isolation-795.md b/docs/validation/code-oss-css-isolation-795.md new file mode 100644 index 00000000..97749e88 --- /dev/null +++ b/docs/validation/code-oss-css-isolation-795.md @@ -0,0 +1,33 @@ +# Code OSS CSS isolation implementation (#795) + +## Product trigger + +Unchanged Code OSS uses `isolation:isolate` on five retained surfaces: the +pixel spinner, sessions workbench/part, Chat session container, and voice glow. + +## Implemented slice + +- Adds generated native/managed identity, validation, and CSSOM exposure for + `isolation:auto|isolate`, including global keywords, mutation, removal, and + cascade reset. +- Retains one packed stacking-context bit beside the existing transform and + containment flags; no per-node token string or map is allocated. +- Includes isolation in the existing atomic stacking-context predicate shared + by retained paint and hit ordering. +- Treats mutation as paint-only and adds no layout invalidation, timer, frame + participant, platform widget, visual-tree node, browser shell, or Code OSS + patch. + +## Authored gates + +- `contracts/css-isolation-stacking-context.html`: initial/computed CSSOM, + invalid writes, mutation/removal, and overlapping descendant/sibling order. +- `test_css_isolation_stacking_context`: native hit-order mutation contract. +- `css-isolation-performance`: 4,096 nodes, ten transitions, zero added layout, + bounded scene publication, and a five-second guardrail. + +## Evidence state + +Implementation and gates are authored from source review. Per the active fast +merge direction, no build, test, WPT, visual, package, memory, lifecycle, or +performance command was executed. Evidence remains zero until full validation. diff --git a/experiments/WebScene.NativeEngine.Probe/native/css_property_metadata.json b/experiments/WebScene.NativeEngine.Probe/native/css_property_metadata.json index 59279259..15a86271 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/css_property_metadata.json +++ b/experiments/WebScene.NativeEngine.Probe/native/css_property_metadata.json @@ -155,7 +155,8 @@ {"id": "user_select", "name": "user-select", "aliases": ["userselect", "-webkit-user-select", "webkit-user-select", "webkituserselect", "-ms-user-select", "ms-user-select", "msuserselect"]}, {"id": "overscroll_behavior", "name": "overscroll-behavior", "aliases": ["overscrollbehavior"]}, {"id": "overscroll_behavior_x", "name": "overscroll-behavior-x", "aliases": ["overscrollbehaviorx"]}, - {"id": "overscroll_behavior_y", "name": "overscroll-behavior-y", "aliases": ["overscrollbehaviory"]} + {"id": "overscroll_behavior_y", "name": "overscroll-behavior-y", "aliases": ["overscrollbehaviory"]}, + {"id": "isolation", "name": "isolation"} ], "nativeStorageOnlyProperties": [ "accent-color", "align-content", "animation-direction", "animation-fill-mode", "animation-play-state", @@ -177,7 +178,7 @@ "justify_content", "box_sizing", "vertical_align", "grid_auto_flow", "border_collapse", "table_layout", "border_style", "background_repeat", "visibility", "pointer_events", "text_anchor", "font_smoothing", "text_align", "text_transform", "white_space", "word_break", "overflow_wrap", "list_style_position", "list_style_type", "scrollbar_width", "container", "container_type", "content_visibility", "object_fit", "user_select", - "overscroll_behavior", "overscroll_behavior_x", "overscroll_behavior_y" + "overscroll_behavior", "overscroll_behavior_x", "overscroll_behavior_y", "isolation" ], "componentList": [ "container_name", "background_position", "background_size", "transition", "transition_property", "transition_duration", @@ -221,7 +222,7 @@ "outline", "outline_width", "outline_color", "animation", "animation_name", "animation_duration", "animation_delay", "animation_timing_function", "animation_iteration_count", "background_repeat", "background_position", "background_size", "font", "text_transform", "word_break", "overflow_wrap", "list_style", "list_style_position", "list_style_type", - "object_fit", "object_position", "user_select", "overscroll_behavior", "overscroll_behavior_x", "overscroll_behavior_y" + "object_fit", "object_position", "user_select", "overscroll_behavior", "overscroll_behavior_x", "overscroll_behavior_y", "isolation" ], "managedKnownProperties": [ "align-content", "align-items", "align-self", "all", "background", "background-color", @@ -243,7 +244,7 @@ "top", "transform", "visibility", "white-space", "word-break", "overflow-wrap", "width", "word-spacing", "z-index", "outline", "outline-color", "outline-offset", "outline-style", "outline-width", "grid-template-areas", "aspect-ratio", "object-fit", "object-position", "user-select", - "overscroll-behavior", "overscroll-behavior-x", "overscroll-behavior-y" + "overscroll-behavior", "overscroll-behavior-x", "overscroll-behavior-y", "isolation" ], "supportedPropertyExtras": [ "animation", "animation-delay", "animation-direction", "animation-duration", "animation-fill-mode", diff --git a/experiments/WebScene.NativeEngine.Probe/native/generated/webscene_css_property_identity.inc b/experiments/WebScene.NativeEngine.Probe/native/generated/webscene_css_property_identity.inc index 0de98ce0..18ed80d2 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/generated/webscene_css_property_identity.inc +++ b/experiments/WebScene.NativeEngine.Probe/native/generated/webscene_css_property_identity.inc @@ -231,6 +231,7 @@ inline constexpr std::array native_typed_property_identity_catalog{ native_typed_property_identity{"overscrollbehaviorx", css_property_id::overscroll_behavior_x}, native_typed_property_identity{"overscroll-behavior-y", css_property_id::overscroll_behavior_y}, native_typed_property_identity{"overscrollbehaviory", css_property_id::overscroll_behavior_y}, + native_typed_property_identity{"isolation", css_property_id::isolation}, }; inline constexpr std::array native_storage_only_property_catalog{ @@ -454,6 +455,7 @@ inline constexpr std::array native_modeled_property_mask_catalog{ false, false, false, + false, }; inline constexpr bool generated_property_has_modeled_mask( @@ -656,6 +658,7 @@ inline constexpr std::array native_property_grammar_catalog{ native_property_grammar::keyword, native_property_grammar::keyword, native_property_grammar::keyword, + native_property_grammar::keyword, }; inline constexpr native_property_grammar generated_property_grammar( @@ -895,5 +898,6 @@ inline css_property_id generated_property_id_lowercase(std::string_view name) no if (name == "overscrollbehaviorx") return css_property_id::overscroll_behavior_x; if (name == "overscroll-behavior-y") return css_property_id::overscroll_behavior_y; if (name == "overscrollbehaviory") return css_property_id::overscroll_behavior_y; + if (name == "isolation") return css_property_id::isolation; return css_property_id::unknown; } diff --git a/experiments/WebScene.NativeEngine.Probe/native/generated/webscene_css_property_ids.inc b/experiments/WebScene.NativeEngine.Probe/native/generated/webscene_css_property_ids.inc index 6fd5e181..194b5afc 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/generated/webscene_css_property_ids.inc +++ b/experiments/WebScene.NativeEngine.Probe/native/generated/webscene_css_property_ids.inc @@ -154,3 +154,4 @@ user_select, overscroll_behavior, overscroll_behavior_x, overscroll_behavior_y, +isolation, diff --git a/experiments/WebScene.NativeEngine.Probe/native/generated/webscene_css_supported_properties.inc b/experiments/WebScene.NativeEngine.Probe/native/generated/webscene_css_supported_properties.inc index dcee1f49..a8d4b9ce 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/generated/webscene_css_supported_properties.inc +++ b/experiments/WebScene.NativeEngine.Probe/native/generated/webscene_css_supported_properties.inc @@ -158,6 +158,7 @@ inline constexpr std::array cssom_supported_property_catalog{ cssom_supported_property_metadata{"inset-inline", "insetInline"}, cssom_supported_property_metadata{"inset-inline-end", "insetInlineEnd"}, cssom_supported_property_metadata{"inset-inline-start", "insetInlineStart"}, + cssom_supported_property_metadata{"isolation", "isolation"}, cssom_supported_property_metadata{"justify-content", "justifyContent"}, cssom_supported_property_metadata{"left", "left"}, cssom_supported_property_metadata{"letter-spacing", "letterSpacing"}, @@ -259,4 +260,4 @@ inline constexpr std::array cssom_supported_property_catalog{ cssom_supported_property_metadata{"zoom", "zoom"}, }; -inline constexpr auto cssom_style_template_property_accessor_count = 461U; +inline constexpr auto cssom_style_template_property_accessor_count = 462U; diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_application.h b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_application.h index eb395206..76d1af64 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_application.h +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_application.h @@ -260,6 +260,7 @@ void apply_resolved_declaration(native_document& document,dom_node& node, } else if (css::apply_replaced_value(node,name,value,decision)) { } else if (css::apply_user_select_value(node,name,value,decision)) { } else if (css::apply_overscroll_behavior_value(node,name,value,decision)) { + } else if (css::apply_isolation_value(node,name,value,decision)) { } else if (css::apply_text_value(node,name,value,decision,is_inline)) { } else if (name == "border-style") { decision.classification = "partially-supported"; diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_cascade_reset.h b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_cascade_reset.h index 6d781721..f8843c7b 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_cascade_reset.h +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_cascade_reset.h @@ -233,6 +233,7 @@ inline void reset_cascaded_style(dom_node& node, textual->overscroll_y = overscroll_behavior::automatic; } if ((node.style.inline_property_mask & inline_font_weight) == 0U) node.style.font_weight = 0; + node.style.isolation_stacking_context = false; if ((node.style.inline_property_mask & inline_line_height) == 0U) node.style.line_height = -1; if ((node.style.inline_property_mask & inline_letter_spacing) == 0U) { node.style.letter_spacing = 0; diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_interaction_values.h b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_interaction_values.h index 1287d9a3..0f23e83e 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_interaction_values.h +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_interaction_values.h @@ -110,4 +110,25 @@ bool apply_overscroll_behavior_value(dom_node& node,const std::string& name, textual.overscroll_y = y; return true; } + +template +bool apply_isolation_value(dom_node& node,const std::string& name, + const std::string& raw_value,Decision& decision) +{ + if (canonical_property_name(name) != "isolation") return false; + auto value = ascii_lower(trim_value(raw_value)); + if (value == "inherit") { + node.style.isolation_stacking_context = node.parent != nullptr + && node.parent->style.isolation_stacking_context; + return true; + } + if (value == "initial" || value == "unset" || value == "revert" + || value == "revert-layer") value = "auto"; + if (value != "auto" && value != "isolate") { + decision.classification = "invalid-authoring"; + return true; + } + node.style.isolation_stacking_context = value == "isolate"; + return true; +} } // namespace webscene_native::css diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_reset.h b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_reset.h index 3eedc24b..e06ff67c 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_reset.h +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_reset.h @@ -248,6 +248,9 @@ inline void apply_all_unset( if (has_inline({"overscroll-behavior", "overscroll-behavior-y"})) { reset.mutable_textual().overscroll_y = previous.textual().overscroll_y; } + if (has_inline({"isolation"})) { + reset.isolation_stacking_context = previous.isolation_stacking_context; + } // These modeled properties do not yet have dedicated inline-mask // bits, so preserve their applied values by authored declaration. diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_specified_coverage.h b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_specified_coverage.h index aa5b8a17..bf748e7e 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_specified_coverage.h +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_specified_coverage.h @@ -11,6 +11,7 @@ inline constexpr auto specified_property_samples = std::to_arrayoverscroll_y = overscroll_behavior::automatic; } } + } else if (name == "isolation") { + node.style.isolation_stacking_context = false; } else if (name == "boxSizing" || name == "box-sizing") { node.style.border_box = false; node.style.inline_property_mask &= ~inline_box_sizing; @@ -993,6 +995,12 @@ std::string semantic_slice; } decision; css::apply_overscroll_behavior_value(*node, name, value, decision); + } else if (name == "isolation") { + struct interaction_decision final { + std::string classification{"supported"}; + std::string semantic_slice; + } decision; + css::apply_isolation_value(*node, name, value, decision); } else if (name == "box-sizing") { node->style.border_box = value == "border-box"; node->style.inline_property_mask |= inline_box_sizing; @@ -1612,6 +1620,8 @@ } else if (name == "overscroll-behavior-y") { value = css::serialize_overscroll_behavior( node->style.textual().overscroll_y); + } else if (name == "isolation") { + value = node->style.isolation_stacking_context ? "isolate" : "auto"; } else if (name == "cursor") { value = resolved_cursor(*node); } else if (name == "font-size") { @@ -2088,6 +2098,8 @@ } else if (name == "overscrollBehaviorY") { value = css::serialize_overscroll_behavior( node->style.textual().overscroll_y); + } else if (name == "isolation") { + value = node->style.isolation_stacking_context ? "isolate" : "auto"; } else if (name == "boxSizing") { value = node->style.border_box ? "border-box" : "content-box"; } else if (name == "borderRadius") { @@ -2687,6 +2699,12 @@ ? std::string{"overscroll-behavior-x"} : std::string{"overscroll-behavior-y"}; css::apply_overscroll_behavior_value(*node, property, value, decision); + } else if (name == "isolation") { + struct interaction_decision final { + std::string classification{"supported"}; + std::string semantic_slice; + } decision; + css::apply_isolation_value(*node, name, value, decision); } else if (name == "boxSizing") { node->style.border_box = value == "border-box"; node->style.inline_property_mask |= inline_box_sizing; diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_support.inc b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_support.inc index ea25b4bf..385ac3ba 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_support.inc +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_v8_runtime_support.inc @@ -679,6 +679,9 @@ bool valid_cssom_declaration_value(std::string_view property_name, std::string_v }; return valid(first) && (second.empty() || valid(second)) && extra.empty(); } + if (canonical_name == "isolation") { + return normalized_value == "auto" || normalized_value == "isolate"; + } if (canonical_name == "position") { return normalized_value == "static" || normalized_value == "relative" diff --git a/experiments/WebScene.NativeEngine.Probe/tests/css_property_identity_tests.cpp b/experiments/WebScene.NativeEngine.Probe/tests/css_property_identity_tests.cpp index 94c298da..dff09362 100644 --- a/experiments/WebScene.NativeEngine.Probe/tests/css_property_identity_tests.cpp +++ b/experiments/WebScene.NativeEngine.Probe/tests/css_property_identity_tests.cpp @@ -102,10 +102,11 @@ int main() static_assert(static_cast(css_property_id::overscroll_behavior) == 150U); static_assert(static_cast(css_property_id::overscroll_behavior_x) == 151U); static_assert(static_cast(css_property_id::overscroll_behavior_y) == 152U); - static_assert(native_typed_property_identity_catalog.size() == 226U); + static_assert(static_cast(css_property_id::isolation) == 153U); + static_assert(native_typed_property_identity_catalog.size() == 227U); static_assert(native_storage_only_property_catalog.size() == 62U); - static_assert(cssom_supported_property_catalog.size() == 252U); - static_assert(cssom_style_template_property_accessor_count == 461U); + static_assert(cssom_supported_property_catalog.size() == 253U); + static_assert(cssom_style_template_property_accessor_count == 462U); for (const auto& entry : native_typed_property_identity_catalog) { require(property_id(entry.name) == entry.id, "typed name maps to its generated id", entry.name); diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_css_layout_tests.inc b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_css_layout_tests.inc index ce29e424..068f3576 100644 --- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_css_layout_tests.inc +++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_css_layout_tests.inc @@ -240,6 +240,47 @@ void test_overscroll_behavior_performance_gate(webscene_engine* engine) + std::to_string(elapsed) + " ms"); } +void test_css_isolation_performance_gate(webscene_engine* engine) +{ + resize(engine, 1024, 768, 1U); + execute(engine, R"JS( + document.body.innerHTML = '
'; + globalThis.isolationGrid = document.getElementById('isolation-grid'); + const fragment = document.createDocumentFragment(); + for (let index = 0; index < 4096; ++index) { + const node = document.createElement('div'); + fragment.appendChild(node); + } + isolationGrid.appendChild(fragment); + )JS", "native-css-isolation-performance-setup.js"); + webscene_engine_metrics before{}; + webscene_engine_get_metrics(engine, &before); + const auto started = std::chrono::steady_clock::now(); + const auto result = evaluate(engine, R"JS( + (() => { + for (let cycle = 0; cycle < 10; ++cycle) { + const value = cycle % 2 === 0 ? 'isolate' : 'auto'; + for (const node of isolationGrid.children) node.style.isolation = value; + } + return [isolationGrid.children.length, + isolationGrid.lastElementChild.style.isolation]; + })() + )JS", "native-css-isolation-performance-gate.js"); + const auto elapsed = std::chrono::duration( + std::chrono::steady_clock::now() - started).count(); + webscene_engine_metrics after{}; + webscene_engine_get_metrics(engine, &after); + require(result == R"JSON([4096,"auto"])JSON", + "CSS isolation performance fixture regressed: " + result); + require(after.layout_passes == before.layout_passes, + "CSS isolation transitions performed layout"); + require(after.published_scenes <= before.published_scenes + 1U, + "CSS isolation transitions published unbounded scenes"); + require(elapsed < 5000.0, + "4,096-node CSS isolation gate exceeded 5 seconds: " + + std::to_string(elapsed) + " ms"); +} + void test_modal_backdrop_scene(webscene_engine* engine) { resize(engine,400,300,1); @@ -4982,6 +5023,36 @@ void test_pointer_hit_targets_and_related_targets_are_elements(webscene_engine* "pointer boundary target/relatedTarget did not retain browser element identity: " + state); } +void test_css_isolation_stacking_context(webscene_engine* engine) +{ + resize(engine, 320, 180, 79'500U); + const auto isolated = evaluate(engine, R"JS( + (() => { + document.body.innerHTML = `
`; + return [getComputedStyle(document.getElementById('isolated')).isolation, + document.elementFromPoint(10,10).id]; + })() + )JS", "native-css-isolation-stacking.js"); + require(isolated == R"JSON(["isolate","sibling"])JSON", + "CSS isolation did not contain descendant z-index: " + isolated); + const auto released = evaluate(engine, R"JS( + (() => { + const isolated = document.getElementById('isolated'); + isolated.style.isolation = 'auto'; + return [getComputedStyle(isolated).isolation, + document.elementFromPoint(10,10).id]; + })() + )JS", "native-css-isolation-auto.js"); + require(released == R"JSON(["auto","escape"])JSON", + "CSS isolation auto mutation retained an atomic context: " + released); +} + void test_z_index_orders_positioned_siblings_in_scene(webscene_engine* engine) { execute(engine, R"JS( diff --git a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_tests.cpp b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_tests.cpp index c5986fce..eddac1e0 100644 --- a/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_tests.cpp +++ b/experiments/WebScene.NativeEngine.Probe/tests/native_v8_runtime_tests.cpp @@ -413,6 +413,14 @@ int main() webscene_engine_destroy(focused_engine); return 0; } + if (selected == "css-isolation-performance") { + auto* focused_engine = webscene_engine_create(0); + require(focused_engine != nullptr, + "CSS isolation performance engine creation failed"); + test_css_isolation_performance_gate(focused_engine); + webscene_engine_destroy(focused_engine); + return 0; + } if(selected=="placeholder-pseudo") { auto* focused_engine=webscene_engine_create(0); require(focused_engine!=nullptr,"placeholder pseudo engine creation failed"); @@ -1530,6 +1538,7 @@ int main() webscene_engine_destroy(file_engine); } test_z_index_orders_positioned_siblings_in_scene(engine); + test_css_isolation_stacking_context(engine); test_popup_portal_tooltip_escapes_non_stacking_positioned_wrapper(engine); test_fixed_portal_descendant_stays_in_ancestor_stacking_context(engine); test_transform_origin_keywords_cascade_independently_from_inline_transform(engine); diff --git a/src/WebScene.Css/CssPropertyCatalog.cs b/src/WebScene.Css/CssPropertyCatalog.cs index 07e2f107..c8398694 100644 --- a/src/WebScene.Css/CssPropertyCatalog.cs +++ b/src/WebScene.Css/CssPropertyCatalog.cs @@ -90,6 +90,7 @@ public static bool IsValidCssomValue(string propertyName, string value) "overscroll-behavior-x" or "overscroll-behavior-y" => normalizedValue is "auto" or "contain" or "none", "overscroll-behavior" => IsOverscrollBehavior(normalizedValue), + "isolation" => normalizedValue is "auto" or "isolate", "font-size" => IsFontSize(normalizedValue), "color-scheme" => normalizedValue is "normal" or "light" or "dark" or "light dark" or "dark light" or "only light" or "only dark", diff --git a/src/WebScene.Css/CssPropertyMetadata.Generated.cs b/src/WebScene.Css/CssPropertyMetadata.Generated.cs index 95606de0..17e0851c 100644 --- a/src/WebScene.Css/CssPropertyMetadata.Generated.cs +++ b/src/WebScene.Css/CssPropertyMetadata.Generated.cs @@ -119,6 +119,7 @@ internal static class CssGeneratedPropertyMetadata "overscroll-behavior", "overscroll-behavior-x", "overscroll-behavior-y", + "isolation", ]; internal static readonly string[] SupportedNames = @@ -276,6 +277,7 @@ internal static class CssGeneratedPropertyMetadata "inset-inline", "inset-inline-end", "inset-inline-start", + "isolation", "justify-content", "left", "letter-spacing", @@ -495,6 +497,7 @@ internal static bool TryGetKnownId(string name, out int id) "overscroll-behavior" => 111, "overscroll-behavior-x" => 112, "overscroll-behavior-y" => 113, + "isolation" => 114, _ => -1, }; return id >= 0; diff --git a/tests/WebPlatformSubset/capabilities.json b/tests/WebPlatformSubset/capabilities.json index 5ba4683f..b230d94f 100644 --- a/tests/WebPlatformSubset/capabilities.json +++ b/tests/WebPlatformSubset/capabilities.json @@ -432,6 +432,14 @@ "productEvidence": ["Code OSS session workbench part entry", "Code OSS phone sidebar drawer"], "coverage": ["contracts/css-starting-style-entry-transition.html", "webscene_css_parser_tests", "native_web_css_service", "test_starting_style_entry_and_discrete_display_transitions"], "gap": "Issue #749 implements the exact entry-transition slice used by unchanged Code OSS. Static nodes retain only two lifecycle bits; temporary starting snapshots exist only during entry cascade and admitted transitions reuse host-clock runtime state. Product pixels, broader discrete properties, margin/border transition machines, and view transitions remain separate work." + }, + { + "family": "css-isolation-stacking-context", + "required": ["isolation auto and isolate CSSOM", "atomic descendant z-index containment", "retained paint and hit-order mutation", "cascade removal fallback"], + "candidate": ["unchanged Code OSS product pixels", "cross-RID performance qualification"], + "productEvidence": ["Code OSS pixel spinner", "Code OSS sessions workbench and part", "Code OSS Chat session container", "Code OSS voice glow"], + "coverage": ["contracts/css-isolation-stacking-context.html", "test_css_isolation_stacking_context", "css-isolation-performance"], + "gap": "Issue #795 implements the exact isolation:isolate slice used by unchanged Code OSS. One packed computed-style bit feeds the retained atomic stacking-context predicate, so a positive-z descendant remains scoped below a later sibling and CSSOM mutation changes paint and hit order without relayout. Direct browser/native execution, exact product pixels, package regions, memory, lifecycle, and performance evidence remain qualification debt." } ] } diff --git a/tests/WebPlatformSubset/contracts/css-isolation-stacking-context.html b/tests/WebPlatformSubset/contracts/css-isolation-stacking-context.html new file mode 100644 index 00000000..442bd47d --- /dev/null +++ b/tests/WebPlatformSubset/contracts/css-isolation-stacking-context.html @@ -0,0 +1,26 @@ + + +Isolation establishes an atomic stacking context + + + +
+
+ diff --git a/tests/WebPlatformSubset/webscene-component-profile.json b/tests/WebPlatformSubset/webscene-component-profile.json index d70ab16e..39006ead 100644 --- a/tests/WebPlatformSubset/webscene-component-profile.json +++ b/tests/WebPlatformSubset/webscene-component-profile.json @@ -2843,6 +2843,13 @@ "capabilities": ["overscroll-behavior-cssom", "per-axis-scroll-chain-policy", "nested-wheel-containment", "interaction-only-mutation"], "evidence": ["focused-native-wheel-contract-authored", "direct-chromium-contract-authored", "unchanged-code-oss-645f29c-inventory"], "reason": "Implementation-first WebScene #793 contract for the 12 unchanged Code OSS overscroll declarations used by the workbench root, Markdown, Chat, sessions/mobile shells, and overlay views. It covers shorthand/longhand CSSOM and retained wheel boundary containment. Direct browser/native execution, mixed-axis/nested-frame interaction, package regions, accessibility, performance, memory, and lifecycle evidence remain qualification debt." + }, + { + "path": "contracts/css-isolation-stacking-context.html", + "type": "testharness", + "capabilities": ["isolation-cssom", "atomic-stacking-context", "descendant-z-index-containment", "paint-order-mutation"], + "evidence": ["focused-native-hit-order-contract-authored", "direct-chromium-contract-authored", "unchanged-code-oss-645f29c-inventory"], + "reason": "Implementation-first WebScene #795 contract for the five isolation:isolate declarations in unchanged Code OSS. It covers initial/computed CSSOM, mutation/removal, invalid writes, descendant z-index containment, and retained hit order without relayout. Direct browser/native execution, exact product pixels, package regions, accessibility, performance, memory, and lifecycle evidence remain qualification debt." } ], "harnessBlocked": [], diff --git a/tests/WebScene.Css.Tests/CssPropertyCatalogTests.cs b/tests/WebScene.Css.Tests/CssPropertyCatalogTests.cs index 1bb70d33..b2e79ef1 100644 --- a/tests/WebScene.Css.Tests/CssPropertyCatalogTests.cs +++ b/tests/WebScene.Css.Tests/CssPropertyCatalogTests.cs @@ -58,6 +58,7 @@ public void DoesNotMisclassifyNonColorValueProperties(string propertyName) [InlineData("overscrollBehavior")] [InlineData("overscrollBehaviorX")] [InlineData("overscrollBehaviorY")] + [InlineData("isolation")] public void ExposesSupportedCssomProperties(string name) => Assert.True(CssPropertyCatalog.IsSupported(name)); @@ -122,6 +123,8 @@ public void DoesNotExposeUnknownOrCustomPropertiesAsIdlAttributes(string name) [InlineData("overscroll-behavior-x", "contain", true)] [InlineData("overscroll-behavior", "contain auto none", false)] [InlineData("overscroll-behavior-y", "bounce", false)] + [InlineData("isolation", "isolate", true)] + [InlineData("isolation", "group", false)] public void ValidatesCssomValuesWithoutFrameworkKnowledge(string name, string value, bool expected) => Assert.Equal(expected, CssPropertyCatalog.IsValidCssomValue(name, value)); }