Skip to content
Merged
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
33 changes: 33 additions & 0 deletions docs/validation/code-oss-css-isolation-795.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down Expand Up @@ -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",
Expand All @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string_view, 62> native_storage_only_property_catalog{
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,4 @@ user_select,
overscroll_behavior,
overscroll_behavior_x,
overscroll_behavior_y,
isolation,
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -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;
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,25 @@ bool apply_overscroll_behavior_value(dom_node& node,const std::string& name,
textual.overscroll_y = y;
return true;
}

template<typename Decision>
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
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ inline constexpr auto specified_property_samples = std::to_array<specified_prope
{"object-fit","cover"},{"object-position","right 25%"},
{"user-select","none"},
{"overscroll-behavior","contain none"},{"overscroll-behavior-x","auto"},{"overscroll-behavior-y","contain"},
{"isolation","isolate"},
{"width","12px"},{"height","20%"},{"min-width","1rem"},{"min-height","2px"},
{"max-width","none"},{"max-height","fit-content"},
{"left","1px"},{"top","2px"},{"right","3px"},{"bottom","4px"},{"inset","1px 2px 3px 4px"},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ bool style_establishes_atomic_stacking_context(const dom_node& node) noexcept
|| (node.style.position != position_mode::normal
&& !node.style.z_index_auto)
|| node.style.contain_stacking_context
|| node.style.isolation_stacking_context
|| node.style.opacity < 0.999F
|| node.style.transform_stacking_context
|| node.has_painted_filter_override_value()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1037,6 +1037,9 @@ struct node_style final {
bool transform_stacking_context : 1 {false};
// layout/paint containment establishes an atomic stacking context.
bool contain_stacking_context : 1 {false};
// CSS isolation is consulted on every retained paint-order walk, so keep
// it in the existing packed hot flags rather than a token map lookup.
bool isolation_stacking_context : 1 {false};
bool content_visibility_hidden : 1 {false};
bool interpolate_size_allow_keywords : 1 {false};
// Margin parsing passes these four flags by reference, so unlike the other
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -742,6 +742,7 @@
std::string_view{"object-fit"},
std::string_view{"object-position"},
std::string_view{"user-select"},
std::string_view{"isolation"},
std::string_view{"pointer-events"},
std::string_view{"visibility"},
std::string_view{"-webkit-font-smoothing"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,8 @@
textual->overscroll_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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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") {
Expand Down Expand Up @@ -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") {
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,11 @@ int main()
static_assert(static_cast<uint16_t>(css_property_id::overscroll_behavior) == 150U);
static_assert(static_cast<uint16_t>(css_property_id::overscroll_behavior_x) == 151U);
static_assert(static_cast<uint16_t>(css_property_id::overscroll_behavior_y) == 152U);
static_assert(native_typed_property_identity_catalog.size() == 226U);
static_assert(static_cast<uint16_t>(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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '<div id="isolation-grid"></div>';
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<double, std::milli>(
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);
Expand Down Expand Up @@ -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 = `<style>
body { margin:0; }
#isolated { isolation:isolate; width:100px; height:40px; }
#escape { position:relative; z-index:100; width:100px; height:40px; }
#sibling { position:relative; z-index:1; width:100px; height:40px;
margin-top:-40px; }
</style><div id="isolated"><div id="escape"></div></div><div id="sibling"></div>`;
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(
Expand Down
Loading
Loading