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
43 changes: 43 additions & 0 deletions docs/validation/code-oss-user-select-791.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Code OSS user-select interaction implementation (#791)

## Product trigger

Unchanged Code OSS contains 276 `user-select` declarations across 94 files.
They control native selection in Monaco, lists, dialogs, hovers, terminal,
notebooks, Chat, media preview, Simple Browser, and webview content.

## Implemented slice

- Adds generated native/managed identities for `user-select` and the WebKit/MS
aliases used by the product.
- Cascades and serializes `auto`, `text`, `none`, and `all` in cold style state.
- Resolves `auto` from the nearest ancestor selection policy during pointer
default actions, avoiding a field on every hot node.
- Creates document Selection state from retained-text caret hit testing on
pointer down/move/up.
- Suppresses `none`, permits explicit descendant `text`, atomically selects the
nearest `all` subtree, and honors canceled `selectstart`.
- Reuses existing Selection/Range, selection paint, copy, and outbound-drag
paths; CSSOM mutation is paint-only. Non-collapsed selections use the
existing `Highlight` and `HighlightText` system colors by default, while
matching author `::selection` declarations override those defaults.
- Clears active pointer-selection state during navigation and teardown.

Programmatic Selection and `Window.find` remain independent of `user-select`,
matching the property's user-interaction boundary. Text controls retain their
dedicated UTF-16 editing selection implementation.

## Authored gates

- `contracts/css-user-select-interaction.html`: canonical/prefixed CSSOM,
invalid writes, mutation, removal, and cascade fallback.
- `test_user_select_pointer_default_action`: drag selection, `none`, `all`,
and cancelable `selectstart`.
- `user-select-performance`: 4,096 nodes, ten policy transitions, zero added
layout passes, 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 @@ -151,7 +151,8 @@
{"id": "contain_intrinsic_size", "name": "contain-intrinsic-size"},
{"id": "aspect_ratio", "name": "aspect-ratio"},
{"id": "object_fit", "name": "object-fit", "aliases": ["objectfit"]},
{"id": "object_position", "name": "object-position", "aliases": ["objectposition"]}
{"id": "object_position", "name": "object-position", "aliases": ["objectposition"]},
{"id": "user_select", "name": "user-select", "aliases": ["userselect", "-webkit-user-select", "webkit-user-select", "webkituserselect", "-ms-user-select", "ms-user-select", "msuserselect"]}
],
"nativeStorageOnlyProperties": [
"accent-color", "align-content", "animation-direction", "animation-fill-mode", "animation-play-state",
Expand All @@ -172,7 +173,7 @@
"all", "display", "position", "floating", "flex_direction", "flex_wrap", "align_items", "align_self",
"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"
"list_style_type", "scrollbar_width", "container", "container_type", "content_visibility", "object_fit", "user_select"
],
"componentList": [
"container_name", "background_position", "background_size", "transition", "transition_property", "transition_duration",
Expand Down Expand Up @@ -216,7 +217,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"
"object_fit", "object_position", "user_select"
],
"managedKnownProperties": [
"align-content", "align-items", "align-self", "all", "background", "background-color",
Expand All @@ -237,7 +238,7 @@
"stroke-linecap", "stroke-linejoin", "stroke-width", "text-align", "text-indent", "text-transform",
"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"
"grid-template-areas", "aspect-ratio", "object-fit", "object-position", "user-select"
],
"supportedPropertyExtras": [
"animation", "animation-delay", "animation-direction", "animation-duration", "animation-fill-mode",
Expand All @@ -260,7 +261,7 @@
"moz-transform", "-webkit-transform", "webkit-transform", "-webkit-font-smoothing", "webkit-font-smoothing", "orphans", "resize",
"scrollbar-color", "scrollbar-width", "table-layout", "text-anchor", "text-decoration", "text-overflow", "vertical-align", "transition", "transition-delay",
"transition-duration", "transition-property", "transition-timing-function", "transform-origin",
"widows", "word-wrap", "zoom"
"widows", "word-wrap", "zoom", "-webkit-user-select", "webkit-user-select", "-ms-user-select", "ms-user-select"
],
"properties": [
{"name": "border-block-color", "mask": ["inline_border"], "expansion": ["border-top-color", "border-bottom-color"], "applyExpansion": true},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,14 @@ inline constexpr std::array native_typed_property_identity_catalog{
native_typed_property_identity{"objectfit", css_property_id::object_fit},
native_typed_property_identity{"object-position", css_property_id::object_position},
native_typed_property_identity{"objectposition", css_property_id::object_position},
native_typed_property_identity{"user-select", css_property_id::user_select},
native_typed_property_identity{"userselect", css_property_id::user_select},
native_typed_property_identity{"-webkit-user-select", css_property_id::user_select},
native_typed_property_identity{"webkit-user-select", css_property_id::user_select},
native_typed_property_identity{"webkituserselect", css_property_id::user_select},
native_typed_property_identity{"-ms-user-select", css_property_id::user_select},
native_typed_property_identity{"ms-user-select", css_property_id::user_select},
native_typed_property_identity{"msuserselect", css_property_id::user_select},
};

inline constexpr std::array<std::string_view, 62> native_storage_only_property_catalog{
Expand Down Expand Up @@ -436,6 +444,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 @@ -634,6 +643,7 @@ inline constexpr std::array native_property_grammar_catalog{
native_property_grammar::complex,
native_property_grammar::keyword,
native_property_grammar::component_list,
native_property_grammar::keyword,
};

inline constexpr native_property_grammar generated_property_grammar(
Expand Down Expand Up @@ -859,5 +869,13 @@ inline css_property_id generated_property_id_lowercase(std::string_view name) no
if (name == "objectfit") return css_property_id::object_fit;
if (name == "object-position") return css_property_id::object_position;
if (name == "objectposition") return css_property_id::object_position;
if (name == "user-select") return css_property_id::user_select;
if (name == "userselect") return css_property_id::user_select;
if (name == "-webkit-user-select") return css_property_id::user_select;
if (name == "webkit-user-select") return css_property_id::user_select;
if (name == "webkituserselect") return css_property_id::user_select;
if (name == "-ms-user-select") return css_property_id::user_select;
if (name == "ms-user-select") return css_property_id::user_select;
if (name == "msuserselect") return css_property_id::user_select;
return css_property_id::unknown;
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,4 @@ contain_intrinsic_size,
aspect_ratio,
object_fit,
object_position,
user_select,
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ struct cssom_supported_property_metadata final {

inline constexpr std::array cssom_supported_property_catalog{
cssom_supported_property_metadata{"-moz-transform", "MozTransform"},
cssom_supported_property_metadata{"-ms-user-select", "MsUserSelect"},
cssom_supported_property_metadata{"-webkit-font-smoothing", "WebkitFontSmoothing"},
cssom_supported_property_metadata{"-webkit-mask", "WebkitMask"},
cssom_supported_property_metadata{"-webkit-mask-composite", "WebkitMaskComposite"},
Expand All @@ -14,6 +15,7 @@ inline constexpr std::array cssom_supported_property_catalog{
cssom_supported_property_metadata{"-webkit-mask-repeat", "WebkitMaskRepeat"},
cssom_supported_property_metadata{"-webkit-mask-size", "WebkitMaskSize"},
cssom_supported_property_metadata{"-webkit-transform", "WebkitTransform"},
cssom_supported_property_metadata{"-webkit-user-select", "WebkitUserSelect"},
cssom_supported_property_metadata{"accent-color", "accentColor"},
cssom_supported_property_metadata{"align-content", "alignContent"},
cssom_supported_property_metadata{"align-items", "alignItems"},
Expand Down Expand Up @@ -186,6 +188,7 @@ inline constexpr std::array cssom_supported_property_catalog{
cssom_supported_property_metadata{"min-height", "minHeight"},
cssom_supported_property_metadata{"min-width", "minWidth"},
cssom_supported_property_metadata{"moz-transform", "mozTransform"},
cssom_supported_property_metadata{"ms-user-select", "msUserSelect"},
cssom_supported_property_metadata{"object-fit", "objectFit"},
cssom_supported_property_metadata{"object-position", "objectPosition"},
cssom_supported_property_metadata{"opacity", "opacity"},
Expand Down Expand Up @@ -237,10 +240,12 @@ inline constexpr std::array cssom_supported_property_catalog{
cssom_supported_property_metadata{"transition-duration", "transitionDuration"},
cssom_supported_property_metadata{"transition-property", "transitionProperty"},
cssom_supported_property_metadata{"transition-timing-function", "transitionTimingFunction"},
cssom_supported_property_metadata{"user-select", "userSelect"},
cssom_supported_property_metadata{"vertical-align", "verticalAlign"},
cssom_supported_property_metadata{"visibility", "visibility"},
cssom_supported_property_metadata{"webkit-font-smoothing", "webkitFontSmoothing"},
cssom_supported_property_metadata{"webkit-transform", "webkitTransform"},
cssom_supported_property_metadata{"webkit-user-select", "webkitUserSelect"},
cssom_supported_property_metadata{"white-space", "whiteSpace"},
cssom_supported_property_metadata{"widows", "widows"},
cssom_supported_property_metadata{"width", "width"},
Expand All @@ -251,4 +256,4 @@ inline constexpr std::array cssom_supported_property_catalog{
cssom_supported_property_metadata{"zoom", "zoom"},
};

inline constexpr auto cssom_style_template_property_accessor_count = 445U;
inline constexpr auto cssom_style_template_property_accessor_count = 455U;
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "webscene_css_variables.h"
#include "webscene_css_scrollbar_values.h"
#include "webscene_css_replaced_values.h"
#include "webscene_css_interaction_values.h"

namespace webscene_native::css {
// Apply an already-resolved declaration. The caller owns variable resolution,
Expand Down Expand Up @@ -257,6 +258,7 @@ void apply_resolved_declaration(native_document& document,dom_node& node,
load_svg)) {
} else if (css::apply_visibility_value(document,node,name,value,is_inline)) {
} 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_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 @@ -228,6 +228,7 @@ inline void reset_cascaded_style(dom_node& node,
textual->overflow_wrap.clear();
textual->object_fit.clear();
textual->object_position.clear();
textual->user_select.clear();
}
if ((node.style.inline_property_mask & inline_font_weight) == 0U) node.style.font_weight = 0;
if ((node.style.inline_property_mask & inline_line_height) == 0U) node.style.line_height = -1;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once
#include "webscene_css_property_mask.h"
#include <utility>

namespace webscene_native::css {
template<typename Decision>
bool apply_user_select_value(dom_node& node,const std::string& name,
const std::string& raw_value,Decision& decision)
{
if (canonical_property_name(name) != "user-select") return false;
auto value = ascii_lower(trim_value(raw_value));
if (value == "inherit") {
value = node.parent == nullptr ? std::string{}
: node.parent->style.textual().user_select;
} else if (value == "initial" || value == "unset" || value == "revert"
|| value == "revert-layer") {
value.clear();
}
if (!value.empty() && value != "auto" && value != "text"
&& value != "none" && value != "all") {
decision.classification = "invalid-authoring";
return true;
}
node.style.mutable_textual().user_select = value == "auto"
? std::string{} : std::move(value);
return true;
}
} // namespace webscene_native::css
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ inline void apply_all_unset(
reset.mutable_textual().object_position =
previous.textual().object_position;
}
if (has_inline({"user-select", "-webkit-user-select", "-ms-user-select"})) {
reset.mutable_textual().user_select = previous.textual().user_select;
}

// 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 @@ -9,6 +9,7 @@ struct specified_property_sample final { std::string_view name; std::string_view
inline constexpr auto specified_property_samples = std::to_array<specified_property_sample>({
{"all","unset"},{"content","\"x\""},{"aspect-ratio","16 / 9"},
{"object-fit","cover"},{"object-position","right 25%"},
{"user-select","none"},
{"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 @@ -793,6 +793,9 @@ struct node_style final {
// `fill` and `50% 50%` values without enlarging the hot node record.
std::string object_fit;
std::string object_position;
// Empty is `auto`; used-value resolution walks ancestors only during
// the uncommon native text-selection default action.
std::string user_select;
std::string contain_value;
std::string container_type{"normal"};
std::string container_name{"none"};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ native_document::allocation_metrics native_document::read_allocation_metrics() c
+ textual->overflow_wrap.capacity() + 1U
+ textual->object_fit.capacity() + 1U
+ textual->object_position.capacity() + 1U
+ textual->user_select.capacity() + 1U
+ textual->cursor.capacity() + 1U
+ textual->svg_fill.capacity() + 1U
+ textual->svg_stroke.capacity() + 1U
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,7 @@
std::string_view{"opacity"},
std::string_view{"object-fit"},
std::string_view{"object-position"},
std::string_view{"user-select"},
std::string_view{"pointer-events"},
std::string_view{"visibility"},
std::string_view{"-webkit-font-smoothing"}
Expand Down
Loading
Loading