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-caret-color-804.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Code OSS CSS caret-color implementation (#804)

## Product trigger

Unchanged Code OSS authors `caret-color:
var(--vscode-editorCursor-foreground)` on the EditContext text area. WebScene
previously painted the focused form-control caret with the text foreground.

## Implemented slice

- Adds generated native/managed identity and CSSOM for inherited
`caret-color`, including `auto`, `currentColor`, CSS-wide values, mutation,
removal, and cascade fallback.
- Retains the uncommon token in the existing sparse cold style map, so the
feature adds no hot per-node field.
- Resolves the existing kind-14 caret scene command independently from text
foreground while preserving element opacity.
- Treats mutation as paint-only and adds no layout invalidation, extra scene
node, timer, platform widget, document scan, browser shell, or Code OSS
patch.

## Authored gates

- `contracts/css-caret-color.html`: inheritance, currentColor, computed CSSOM,
mutation, and removal.
- The existing native focus/blink/caret contract now requires distinct text
`#123456` and caret `#abcdef` scene colors.
- `css-caret-color-performance`: 4,096 inputs, 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 @@ -158,7 +158,8 @@
{"id": "overscroll_behavior_y", "name": "overscroll-behavior-y", "aliases": ["overscrollbehaviory"]},
{"id": "isolation", "name": "isolation"},
{"id": "will_change", "name": "will-change", "aliases": ["willchange"]},
{"id": "text_wrap", "name": "text-wrap", "aliases": ["textwrap"]}
{"id": "text_wrap", "name": "text-wrap", "aliases": ["textwrap"]},
{"id": "caret_color", "name": "caret-color", "aliases": ["caretcolor"]}
],
"nativeStorageOnlyProperties": [
"accent-color", "align-content", "animation-direction", "animation-fill-mode", "animation-play-state",
Expand Down Expand Up @@ -211,20 +212,20 @@
"grid_column_end", "border", "border_top", "border_right", "border_bottom", "border_left", "border_inline", "border_block",
"border_color", "outline", "transform", "transform_origin", "box_shadow", "background", "background_image", "overflow",
"overflow_x", "overflow_y", "opacity", "fill", "stroke", "cursor", "font", "font_family",
"font_weight", "list_style"
"font_weight", "list_style", "caret_color"
]
},
"nativeInheritedProperties": [
"all", "color", "cursor", "direction", "font", "font-family", "font-size", "font-style",
"font-weight", "letter-spacing", "line-height", "list-style", "list-style-position", "list-style-type",
"text-align", "text-transform", "visibility", "white-space", "word-break", "overflow-wrap", "word-spacing", "-webkit-font-smoothing"
"text-align", "text-transform", "visibility", "white-space", "text-wrap", "word-break", "overflow-wrap", "word-spacing", "caret-color", "-webkit-font-smoothing"
],
"nativeMasklessPropertyIds": [
"content", "aspect_ratio", "vertical_align", "grid_auto_columns", "grid_auto_flow", "table_layout", "border_inline", "border_block",
"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", "will_change", "text_wrap"
"object_fit", "object_position", "user_select", "overscroll_behavior", "overscroll_behavior_x", "overscroll_behavior_y", "isolation", "will_change", "text_wrap", "caret_color"
],
"managedKnownProperties": [
"align-content", "align-items", "align-self", "all", "background", "background-color",
Expand All @@ -246,7 +247,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", "will-change", "text-wrap"
"overscroll-behavior", "overscroll-behavior-x", "overscroll-behavior-y", "isolation", "will-change", "text-wrap", "caret-color"
],
"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 @@ -236,6 +236,8 @@ inline constexpr std::array native_typed_property_identity_catalog{
native_typed_property_identity{"willchange", css_property_id::will_change},
native_typed_property_identity{"text-wrap", css_property_id::text_wrap},
native_typed_property_identity{"textwrap", css_property_id::text_wrap},
native_typed_property_identity{"caret-color", css_property_id::caret_color},
native_typed_property_identity{"caretcolor", css_property_id::caret_color},
};

inline constexpr std::array<std::string_view, 62> native_storage_only_property_catalog{
Expand Down Expand Up @@ -462,6 +464,7 @@ inline constexpr std::array native_modeled_property_mask_catalog{
false,
false,
false,
false,
};

inline constexpr bool generated_property_has_modeled_mask(
Expand All @@ -472,9 +475,10 @@ inline constexpr bool generated_property_has_modeled_mask(
&& native_modeled_property_mask_catalog[index];
}

inline constexpr std::array<std::string_view, 22> native_inherited_property_catalog{
inline constexpr std::array<std::string_view, 24> native_inherited_property_catalog{
"-webkit-font-smoothing",
"all",
"caret-color",
"color",
"cursor",
"direction",
Expand All @@ -491,6 +495,7 @@ inline constexpr std::array<std::string_view, 22> native_inherited_property_cata
"overflow-wrap",
"text-align",
"text-transform",
"text-wrap",
"visibility",
"white-space",
"word-break",
Expand Down Expand Up @@ -667,6 +672,7 @@ inline constexpr std::array native_property_grammar_catalog{
native_property_grammar::keyword,
native_property_grammar::keyword,
native_property_grammar::keyword,
native_property_grammar::complex,
};

inline constexpr native_property_grammar generated_property_grammar(
Expand Down Expand Up @@ -911,5 +917,7 @@ inline css_property_id generated_property_id_lowercase(std::string_view name) no
if (name == "willchange") return css_property_id::will_change;
if (name == "text-wrap") return css_property_id::text_wrap;
if (name == "textwrap") return css_property_id::text_wrap;
if (name == "caret-color") return css_property_id::caret_color;
if (name == "caretcolor") return css_property_id::caret_color;
return css_property_id::unknown;
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,4 @@ overscroll_behavior_y,
isolation,
will_change,
text_wrap,
caret_color,
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ inline constexpr std::array cssom_supported_property_catalog{
cssom_supported_property_metadata{"bottom", "bottom"},
cssom_supported_property_metadata{"box-shadow", "boxShadow"},
cssom_supported_property_metadata{"box-sizing", "boxSizing"},
cssom_supported_property_metadata{"caret-color", "caretColor"},
cssom_supported_property_metadata{"clear", "clear"},
cssom_supported_property_metadata{"clip-path", "clipPath"},
cssom_supported_property_metadata{"clip-rule", "clipRule"},
Expand Down Expand Up @@ -262,4 +263,4 @@ inline constexpr std::array cssom_supported_property_catalog{
cssom_supported_property_metadata{"zoom", "zoom"},
};

inline constexpr auto cssom_style_template_property_accessor_count = 466U;
inline constexpr auto cssom_style_template_property_accessor_count = 468U;
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,20 @@ template<typename Decision,typename Protected,typename LoadSvg>
bool apply_paint_value(dom_node& node,const std::string& name,const std::string& value,
Decision& decision,Protected&& is_inline,LoadSvg&& load_svg)
{
if (name == "caret-color") {
auto normalized = ascii_lower(trim_value(value));
auto& effects = node.style.mutable_textual().effect_values;
if (normalized == "inherit" || normalized == "unset") {
effects.erase(name);
} else {
if (normalized == "initial" || normalized == "revert"
|| normalized == "revert-layer") normalized = "auto";
effects[name] = std::move(normalized);
}
decision.classification = "supported";
decision.semantic_slice = "retained inherited caret paint";
return true;
}
if (name == "mask-image") {
auto& effects = node.style.mutable_textual().effect_values;
effects.erase("-webscene-mask-markup");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,12 @@ inline void apply_all_unset(
reset.mutable_textual().will_change = previous.textual().will_change;
reset.will_change_stacking_context = previous.will_change_stacking_context;
}
if (has_inline({"caret-color"})) {
const auto value = previous.textual().effect_values.find("caret-color");
if (value != previous.textual().effect_values.end()) {
reset.mutable_textual().effect_values["caret-color"] = value->second;
}
}

// 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 @@ -14,6 +14,7 @@ inline constexpr auto specified_property_samples = std::to_array<specified_prope
{"isolation","isolate"},
{"will-change","transform, opacity"},
{"text-wrap","nowrap"},
{"caret-color","#123456"},
{"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 @@ -240,6 +240,9 @@ inline specified_css_value compile_specified_value(css_property_id property,std:
case native_property_grammar::special:return out;
}
switch(property){
case css_property_id::caret_color:
if(css_ir_lower(value)=="auto")keyword();else color();
break;
case css_property_id::aspect_ratio:components();break;
case css_property_id::contain:case css_property_id::cursor:case css_property_id::font_family:components();break;
case css_property_id::fill:case css_property_id::stroke:color();break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,10 @@ inline specified_css_value compile_specified_value(css_property_id property, std
}

switch (property) {
case css_property_id::caret_color:
if (specified_ascii_lower(value) == "auto") set_keyword();
else set_color();
break;
case css_property_id::aspect_ratio:
set_component_list(); break;
case css_property_id::contain:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -723,6 +723,15 @@ uint32_t resolved_foreground(const dom_node& node)
return 0xD1D4DCFFU;
}

uint32_t resolved_caret_color(const dom_node& node)
{
const auto value = resolved_caret_color_token(node);
if (value == "auto" || value == "currentcolor") {
return resolved_foreground(node);
}
return native_document::parse_color(std::string(value));
}

void append_xml_escaped(std::string_view value, std::string& output, bool attribute)
{
for (const auto character : value) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2245,6 +2245,16 @@ inline std::string_view resolved_text_wrap_mode(const dom_node& node) noexcept
return "wrap";
}

inline std::string_view resolved_caret_color_token(const dom_node& node) noexcept
{
for (auto* current = &node; current != nullptr; current = current->parent) {
const auto& effects = current->style.textual().effect_values;
const auto value = effects.find("caret-color");
if (value != effects.end()) return value->second;
}
return "auto";
}

// Shared by retained layout/paint and the V8-free CSS service contract so the
// contract exercises the production marker-suppression path.
std::string list_marker_text(const dom_node& node);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2967,7 +2967,7 @@ void native_document::append_scene(
text_y + 1.0F,
caret_x,
text_y + std::max(1.0F, line_height - 1.0F),
with_opacity(resolved_foreground(node)),
with_opacity(resolved_caret_color(node)),
node.id});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,7 @@
std::string_view{"user-select"},
std::string_view{"isolation"},
std::string_view{"will-change"},
std::string_view{"caret-color"},
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 @@ -51,6 +51,29 @@
return "normal";
}

static std::string serialized_caret_color(const dom_node& node)
{
const auto token = resolved_caret_color_token(node);
auto color = uint32_t{0xD1D4DCFFU};
if (token == "auto" || token == "currentcolor") {
color = resolved_current_color_for_cssom(node);
} else {
color = native_document::parse_color(std::string(token));
}
std::ostringstream stream;
const auto red = static_cast<unsigned>((color >> 24U) & 0xFFU);
const auto green = static_cast<unsigned>((color >> 16U) & 0xFFU);
const auto blue = static_cast<unsigned>((color >> 8U) & 0xFFU);
const auto alpha = static_cast<unsigned>(color & 0xFFU);
if (alpha == 0xFFU) {
stream << "rgb(" << red << ", " << green << ", " << blue << ')';
} else {
stream << "rgba(" << red << ", " << green << ", " << blue << ", "
<< serialize_css_number(static_cast<double>(alpha) / 255.0) << ')';
}
return stream.str();
}

static bool clear_inline_style(dom_node& node, const std::string& name)
{
const auto canonical_name = canonical_css_property_name(name);
Expand Down Expand Up @@ -286,6 +309,10 @@
textual->will_change.clear();
}
node.style.will_change_stacking_context = false;
} else if (name == "caretColor" || name == "caret-color") {
if (auto* textual = node.style.mutable_textual_if_present()) {
textual->effect_values.erase("caret-color");
}
} else if (name == "boxSizing" || name == "box-sizing") {
node.style.border_box = false;
node.style.inline_property_mask &= ~inline_box_sizing;
Expand Down Expand Up @@ -781,7 +808,8 @@
self->apply_css_declaration(
*node, {canonical_name, specified_value, important}, true);
node->style.inline_property_mask |= property_mask;
} else if (value.find("var(") != std::string::npos && property_mask != 0U) {
} else if (value.find("var(") != std::string::npos
&& (property_mask != 0U || canonical_name == "caret-color")) {
// A CSSOM write is observable immediately. Resolve variable-backed
// declarations through the same computed-value path used during a
// cascade instead of feeding raw var() tokens to numeric parsers.
Expand Down Expand Up @@ -1016,6 +1044,16 @@
std::string semantic_slice;
} decision;
css::apply_will_change_value(*node, name, value, decision);
} else if (name == "caret-color") {
auto normalized = ascii_lower(trim_value(specified_value));
auto& effects = node->style.mutable_textual().effect_values;
if (normalized == "inherit" || normalized == "unset") {
effects.erase("caret-color");
} else {
if (normalized == "initial" || normalized == "revert"
|| normalized == "revert-layer") normalized = "auto";
effects["caret-color"] = std::move(normalized);
}
} else if (name == "box-sizing") {
node->style.border_box = value == "border-box";
node->style.inline_property_mask |= inline_box_sizing;
Expand Down Expand Up @@ -1648,6 +1686,15 @@
} else if (name == "will-change") {
value = node->style.textual().will_change.empty()
? "auto" : node->style.textual().will_change;
} else if (name == "caret-color") {
if (computed) {
value = serialized_caret_color(*node);
} else {
const auto& declarations = node->authored_style().declarations;
const auto authored = declarations.find("caret-color");
value = authored == declarations.end()
? std::string{} : authored->second;
}
} else if (name == "cursor") {
value = resolved_cursor(*node);
} else if (name == "font-size") {
Expand Down Expand Up @@ -2129,6 +2176,15 @@
} else if (name == "willChange") {
value = node->style.textual().will_change.empty()
? "auto" : node->style.textual().will_change;
} else if (name == "caretColor") {
if (computed) {
value = serialized_caret_color(*node);
} else {
const auto& declarations = node->authored_style().declarations;
const auto authored = declarations.find("caret-color");
value = authored == declarations.end()
? std::string{} : authored->second;
}
} else if (name == "boxSizing") {
value = node->style.border_box ? "border-box" : "content-box";
} else if (name == "borderRadius") {
Expand Down Expand Up @@ -2743,6 +2799,16 @@
std::string semantic_slice;
} decision;
css::apply_will_change_value(*node, "will-change", value, decision);
} else if (name == "caretColor") {
auto normalized = ascii_lower(trim_value(specified_value));
auto& effects = node->style.mutable_textual().effect_values;
if (normalized == "inherit" || normalized == "unset") {
effects.erase("caret-color");
} else {
if (normalized == "initial" || normalized == "revert"
|| normalized == "revert-layer") normalized = "auto";
effects["caret-color"] = std::move(normalized);
}
} 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 @@ -688,6 +688,11 @@ bool valid_cssom_declaration_value(std::string_view property_name, std::string_v
if (canonical_name == "text-wrap") {
return normalized_value == "wrap" || normalized_value == "nowrap";
}
if (canonical_name == "caret-color") {
const auto compiled = css::compile_specified_value(
css_property_id::caret_color, trimmed);
return compiled.valid;
}

if (canonical_name == "position") {
return normalized_value == "static" || normalized_value == "relative"
Expand Down
Loading
Loading