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
35 changes: 35 additions & 0 deletions docs/validation/code-oss-css-will-change-800.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Code OSS CSS will-change implementation (#800)

## Product trigger

Unchanged Code OSS has 16 `will-change` declarations across 12 CSS files.
Transform/opacity hints cover Quick Input, Action Widget, sessions/mobile,
Chat, counters, voice glow, and onboarding. Other hints include
background-position, box-shadow, left, and top.

## Implemented slice

- Adds generated native/managed identity, bounded validation, and CSSOM for
`auto` or up to eight comma-separated ASCII custom identifiers.
- Retains the normalized authored list in cold textual style and derives one
packed hot bit when a hinted non-initial property establishes a stacking
context.
- Reuses the retained atomic stacking-context predicate for paint and hit
ordering. It does not preallocate a compositor layer.
- Treats mutation as paint-only and adds no layout invalidation, visual-tree
node, timer, frame participant, platform widget, browser shell, or Code OSS
patch.

## Authored gates

- `contracts/css-will-change-stacking-context.html`: CSSOM, invalid writes,
mutation/removal, and overlapping descendant/sibling order.
- `test_css_will_change_stacking_context`: native hit-order mutation contract.
- `css-will-change-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 @@ -156,7 +156,8 @@
{"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": "isolation", "name": "isolation"}
{"id": "isolation", "name": "isolation"},
{"id": "will_change", "name": "will-change", "aliases": ["willchange"]}
],
"nativeStorageOnlyProperties": [
"accent-color", "align-content", "animation-direction", "animation-fill-mode", "animation-play-state",
Expand All @@ -178,7 +179,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", "isolation"
"overscroll_behavior", "overscroll_behavior_x", "overscroll_behavior_y", "isolation", "will_change"
],
"componentList": [
"container_name", "background_position", "background_size", "transition", "transition_property", "transition_duration",
Expand Down Expand Up @@ -222,7 +223,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", "isolation"
"object_fit", "object_position", "user_select", "overscroll_behavior", "overscroll_behavior_x", "overscroll_behavior_y", "isolation", "will_change"
],
"managedKnownProperties": [
"align-content", "align-items", "align-self", "all", "background", "background-color",
Expand All @@ -244,7 +245,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", "isolation"
"overscroll-behavior", "overscroll-behavior-x", "overscroll-behavior-y", "isolation", "will-change"
],
"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 @@ -232,6 +232,8 @@ inline constexpr std::array native_typed_property_identity_catalog{
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},
native_typed_property_identity{"will-change", css_property_id::will_change},
native_typed_property_identity{"willchange", css_property_id::will_change},
};

inline constexpr std::array<std::string_view, 62> native_storage_only_property_catalog{
Expand Down Expand Up @@ -456,6 +458,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 @@ -659,6 +662,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 @@ -899,5 +903,7 @@ inline css_property_id generated_property_id_lowercase(std::string_view name) no
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;
if (name == "will-change") return css_property_id::will_change;
if (name == "willchange") return css_property_id::will_change;
return css_property_id::unknown;
}
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,4 @@ overscroll_behavior,
overscroll_behavior_x,
overscroll_behavior_y,
isolation,
will_change,
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,12 @@ inline constexpr std::array cssom_supported_property_catalog{
cssom_supported_property_metadata{"white-space", "whiteSpace"},
cssom_supported_property_metadata{"widows", "widows"},
cssom_supported_property_metadata{"width", "width"},
cssom_supported_property_metadata{"will-change", "willChange"},
cssom_supported_property_metadata{"word-break", "wordBreak"},
cssom_supported_property_metadata{"word-spacing", "wordSpacing"},
cssom_supported_property_metadata{"word-wrap", "wordWrap"},
cssom_supported_property_metadata{"z-index", "zIndex"},
cssom_supported_property_metadata{"zoom", "zoom"},
};

inline constexpr auto cssom_style_template_property_accessor_count = 462U;
inline constexpr auto cssom_style_template_property_accessor_count = 464U;
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,7 @@ void apply_resolved_declaration(native_document& document,dom_node& node,
} 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_will_change_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 @@ -229,11 +229,13 @@ inline void reset_cascaded_style(dom_node& node,
textual->object_fit.clear();
textual->object_position.clear();
textual->user_select.clear();
textual->will_change.clear();
textual->overscroll_x = overscroll_behavior::automatic;
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;
node.style.will_change_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
@@ -1,5 +1,6 @@
#pragma once
#include "webscene_css_property_mask.h"
#include <cctype>
#include <sstream>
#include <utility>

Expand Down Expand Up @@ -131,4 +132,76 @@ bool apply_isolation_value(dom_node& node,const std::string& name,
node.style.isolation_stacking_context = value == "isolate";
return true;
}

inline std::optional<std::string> normalize_will_change_value(
std::string_view raw_value)
{
auto value = ascii_lower(trim_value(raw_value));
if (value == "auto") return std::string{};
if (value.empty() || value.size() > 256U) return std::nullopt;
std::string result;
auto start = size_t{0};
auto count = size_t{0};
while (start <= value.size()) {
const auto comma = value.find(',', start);
const auto token = trim_value(value.substr(
start, comma == std::string::npos ? std::string::npos : comma - start));
if (token.empty() || token.size() > 64U || ++count > 8U) return std::nullopt;
const auto reserved = token == "auto" || token == "none"
|| token == "default" || token == "initial" || token == "inherit"
|| token == "unset" || token == "revert" || token == "revert-layer";
const auto identifier = std::all_of(token.begin(), token.end(), [](unsigned char c) {
return std::isalnum(c) != 0 || c == '-' || c == '_';
}) && (std::isalpha(static_cast<unsigned char>(token.front())) != 0
|| token.front() == '-' || token.front() == '_');
if (reserved || !identifier) return std::nullopt;
if (!result.empty()) result += ", ";
result += token;
if (comma == std::string::npos) break;
start = comma + 1U;
}
return result;
}

inline bool will_change_establishes_stacking_context(std::string_view value)
{
auto start = size_t{0};
while (start <= value.size()) {
const auto comma = value.find(',', start);
const auto token = trim_value(value.substr(
start, comma == std::string::npos ? std::string::npos : comma - start));
if (token == "transform" || token == "opacity" || token == "filter"
|| token == "perspective" || token == "clip-path"
|| token == "mask" || token == "mask-image"
|| token == "backdrop-filter") return true;
if (comma == std::string::npos) break;
start = comma + 1U;
}
return false;
}

template<typename Decision>
bool apply_will_change_value(dom_node& node,const std::string& name,
const std::string& raw_value,Decision& decision)
{
if (canonical_property_name(name) != "will-change") return false;
auto value = ascii_lower(trim_value(raw_value));
if (value == "inherit") {
value = node.parent == nullptr
? std::string{} : node.parent->style.textual().will_change;
} else if (value == "initial" || value == "unset" || value == "revert"
|| value == "revert-layer") {
value = "auto";
}
const auto normalized = normalize_will_change_value(value.empty() ? "auto" : value);
if (!normalized.has_value()) {
decision.classification = "invalid-authoring";
return true;
}
node.style.will_change_stacking_context =
will_change_establishes_stacking_context(*normalized);
if (normalized->empty() && !node.style.has_textual_data()) return true;
node.style.mutable_textual().will_change = *normalized;
return true;
}
} // namespace webscene_native::css
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,10 @@ inline void apply_all_unset(
if (has_inline({"isolation"})) {
reset.isolation_stacking_context = previous.isolation_stacking_context;
}
if (has_inline({"will-change"})) {
reset.mutable_textual().will_change = previous.textual().will_change;
reset.will_change_stacking_context = previous.will_change_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,7 +11,8 @@ 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"},
{"isolation","isolate"},
{"will-change","transform, opacity"},
{"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 @@ -210,6 +210,7 @@ bool style_establishes_atomic_stacking_context(const dom_node& node) noexcept
&& !node.style.z_index_auto)
|| node.style.contain_stacking_context
|| node.style.isolation_stacking_context
|| node.style.will_change_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 @@ -802,6 +802,9 @@ struct node_style final {
// Empty is `auto`; used-value resolution walks ancestors only during
// the uncommon native text-selection default action.
std::string user_select;
// Retain only authored hints. The hot record stores the derived
// stacking-context decision used by every paint/hit traversal.
std::string will_change;
// Scroll-chain policy is consulted only while a wheel default action
// walks exhausted scroll containers, so keep both axes in cold style.
overscroll_behavior overscroll_x{overscroll_behavior::automatic};
Expand Down Expand Up @@ -1040,6 +1043,7 @@ struct node_style final {
// 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 will_change_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 @@ -743,6 +743,7 @@
std::string_view{"object-position"},
std::string_view{"user-select"},
std::string_view{"isolation"},
std::string_view{"will-change"},
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 @@ -281,6 +281,11 @@
}
} else if (name == "isolation") {
node.style.isolation_stacking_context = false;
} else if (name == "willChange" || name == "will-change") {
if (auto* textual = node.style.mutable_textual_if_present()) {
textual->will_change.clear();
}
node.style.will_change_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 @@ -1001,6 +1006,12 @@
std::string semantic_slice;
} decision;
css::apply_isolation_value(*node, name, value, decision);
} else if (name == "will-change") {
struct interaction_decision final {
std::string classification{"supported"};
std::string semantic_slice;
} decision;
css::apply_will_change_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 @@ -1622,6 +1633,9 @@
node->style.textual().overscroll_y);
} else if (name == "isolation") {
value = node->style.isolation_stacking_context ? "isolate" : "auto";
} else if (name == "will-change") {
value = node->style.textual().will_change.empty()
? "auto" : node->style.textual().will_change;
} else if (name == "cursor") {
value = resolved_cursor(*node);
} else if (name == "font-size") {
Expand Down Expand Up @@ -2100,6 +2114,9 @@
node->style.textual().overscroll_y);
} else if (name == "isolation") {
value = node->style.isolation_stacking_context ? "isolate" : "auto";
} else if (name == "willChange") {
value = node->style.textual().will_change.empty()
? "auto" : node->style.textual().will_change;
} else if (name == "boxSizing") {
value = node->style.border_box ? "border-box" : "content-box";
} else if (name == "borderRadius") {
Expand Down Expand Up @@ -2705,6 +2722,12 @@
std::string semantic_slice;
} decision;
css::apply_isolation_value(*node, name, value, decision);
} else if (name == "willChange") {
struct interaction_decision final {
std::string classification{"supported"};
std::string semantic_slice;
} decision;
css::apply_will_change_value(*node, "will-change", 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 @@ -682,6 +682,9 @@ bool valid_cssom_declaration_value(std::string_view property_name, std::string_v
if (canonical_name == "isolation") {
return normalized_value == "auto" || normalized_value == "isolate";
}
if (canonical_name == "will-change") {
return css::normalize_will_change_value(normalized_value).has_value();
}

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 @@ -103,10 +103,11 @@ int main()
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(static_cast<uint16_t>(css_property_id::isolation) == 153U);
static_assert(native_typed_property_identity_catalog.size() == 227U);
static_assert(static_cast<uint16_t>(css_property_id::will_change) == 154U);
static_assert(native_typed_property_identity_catalog.size() == 229U);
static_assert(native_storage_only_property_catalog.size() == 62U);
static_assert(cssom_supported_property_catalog.size() == 253U);
static_assert(cssom_style_template_property_accessor_count == 462U);
static_assert(cssom_supported_property_catalog.size() == 254U);
static_assert(cssom_style_template_property_accessor_count == 463U);

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
Loading
Loading