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

## Product trigger

Unchanged Code OSS contains 12 `overscroll-behavior` declarations. The
workbench root uses `none`; Markdown, Chat, question carousels, voice input,
sessions/mobile shells, and overlay views use `contain`.

## Implemented slice

- Adds generated native/managed identities and CSSOM accessors for the
shorthand and both physical-axis longhands.
- Parses and serializes `auto`, `contain`, and `none`, including one/two-value
shorthand, global keywords, inline mutation, removal, and cascade fallback.
- Stores two compact policies in the existing cold textual style allocation.
- Stops exhausted wheel-chain axes at qualifying retained scroll containers
for `contain` and `none`, while preserving `auto`, the other axis, root
viewport ownership, wheel cancellation, scrolling, and scroll events.
- Adds no hot-node field, timer, frame participant, platform widget, document
scan, browser shell, or Code OSS patch. Policy-only CSSOM mutation causes no
layout or scene publication.

WebScene does not provide a rubber-band affordance, so this bounded slice does
not invent a visual distinction between `contain` and `none`. Both values stop
scroll chaining as required by the product paths.

## Authored gates

- `contracts/css-overscroll-behavior.html`: shorthand/longhand CSSOM,
mutation, invalid writes, removal, and cascade fallback.
- `test_overscroll_behavior_wheel_chaining`: nested boundary containment for
`contain`, `none`, and restored `auto` chaining.
- `overscroll-performance`: 4,096 policies, ten transitions, zero added layout
and scene publications, 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 @@ -152,7 +152,10 @@
{"id": "aspect_ratio", "name": "aspect-ratio"},
{"id": "object_fit", "name": "object-fit", "aliases": ["objectfit"]},
{"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"]}
{"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"]}
],
"nativeStorageOnlyProperties": [
"accent-color", "align-content", "animation-direction", "animation-fill-mode", "animation-play-state",
Expand All @@ -173,7 +176,8 @@
"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", "user_select"
"list_style_type", "scrollbar_width", "container", "container_type", "content_visibility", "object_fit", "user_select",
"overscroll_behavior", "overscroll_behavior_x", "overscroll_behavior_y"
],
"componentList": [
"container_name", "background_position", "background_size", "transition", "transition_property", "transition_duration",
Expand Down Expand Up @@ -217,7 +221,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"
"object_fit", "object_position", "user_select", "overscroll_behavior", "overscroll_behavior_x", "overscroll_behavior_y"
],
"managedKnownProperties": [
"align-content", "align-items", "align-self", "all", "background", "background-color",
Expand All @@ -238,7 +242,8 @@
"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", "user-select"
"grid-template-areas", "aspect-ratio", "object-fit", "object-position", "user-select",
"overscroll-behavior", "overscroll-behavior-x", "overscroll-behavior-y"
],
"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 @@ -225,6 +225,12 @@ inline constexpr std::array native_typed_property_identity_catalog{
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},
native_typed_property_identity{"overscroll-behavior", css_property_id::overscroll_behavior},
native_typed_property_identity{"overscrollbehavior", css_property_id::overscroll_behavior},
native_typed_property_identity{"overscroll-behavior-x", css_property_id::overscroll_behavior_x},
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},
};

inline constexpr std::array<std::string_view, 62> native_storage_only_property_catalog{
Expand Down Expand Up @@ -445,6 +451,9 @@ inline constexpr std::array native_modeled_property_mask_catalog{
false,
false,
false,
false,
false,
false,
};

inline constexpr bool generated_property_has_modeled_mask(
Expand Down Expand Up @@ -644,6 +653,9 @@ inline constexpr std::array native_property_grammar_catalog{
native_property_grammar::keyword,
native_property_grammar::component_list,
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 @@ -877,5 +889,11 @@ inline css_property_id generated_property_id_lowercase(std::string_view name) no
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;
if (name == "overscroll-behavior") return css_property_id::overscroll_behavior;
if (name == "overscrollbehavior") return css_property_id::overscroll_behavior;
if (name == "overscroll-behavior-x") return css_property_id::overscroll_behavior_x;
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;
return css_property_id::unknown;
}
Original file line number Diff line number Diff line change
Expand Up @@ -151,3 +151,6 @@ aspect_ratio,
object_fit,
object_position,
user_select,
overscroll_behavior,
overscroll_behavior_x,
overscroll_behavior_y,
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,9 @@ inline constexpr std::array cssom_supported_property_catalog{
cssom_supported_property_metadata{"overflow-wrap", "overflowWrap"},
cssom_supported_property_metadata{"overflow-x", "overflowX"},
cssom_supported_property_metadata{"overflow-y", "overflowY"},
cssom_supported_property_metadata{"overscroll-behavior", "overscrollBehavior"},
cssom_supported_property_metadata{"overscroll-behavior-x", "overscrollBehaviorX"},
cssom_supported_property_metadata{"overscroll-behavior-y", "overscrollBehaviorY"},
cssom_supported_property_metadata{"padding", "padding"},
cssom_supported_property_metadata{"padding-block", "paddingBlock"},
cssom_supported_property_metadata{"padding-block-end", "paddingBlockEnd"},
Expand Down Expand Up @@ -256,4 +259,4 @@ inline constexpr std::array cssom_supported_property_catalog{
cssom_supported_property_metadata{"zoom", "zoom"},
};

inline constexpr auto cssom_style_template_property_accessor_count = 455U;
inline constexpr auto cssom_style_template_property_accessor_count = 461U;
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ void apply_resolved_declaration(native_document& document,dom_node& node,
} 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_overscroll_behavior_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,6 +229,8 @@ inline void reset_cascaded_style(dom_node& node,
textual->object_fit.clear();
textual->object_position.clear();
textual->user_select.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;
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
@@ -1,5 +1,6 @@
#pragma once
#include "webscene_css_property_mask.h"
#include <sstream>
#include <utility>

namespace webscene_native::css {
Expand All @@ -25,4 +26,88 @@ bool apply_user_select_value(dom_node& node,const std::string& name,
? std::string{} : std::move(value);
return true;
}

inline std::string_view serialize_overscroll_behavior(
overscroll_behavior value) noexcept
{
switch (value) {
case overscroll_behavior::contain: return "contain";
case overscroll_behavior::none: return "none";
default: return "auto";
}
}

inline std::optional<overscroll_behavior> parse_overscroll_behavior(
std::string_view value) noexcept
{
if (value == "auto") return overscroll_behavior::automatic;
if (value == "contain") return overscroll_behavior::contain;
if (value == "none") return overscroll_behavior::none;
return std::nullopt;
}

inline std::string serialize_overscroll_shorthand(const node_style& style)
{
const auto x = serialize_overscroll_behavior(style.textual().overscroll_x);
const auto y = serialize_overscroll_behavior(style.textual().overscroll_y);
return x == y ? std::string(x) : std::string(x) + " " + std::string(y);
}

template<typename Decision>
bool apply_overscroll_behavior_value(dom_node& node,const std::string& name,
const std::string& raw_value,Decision& decision)
{
const auto property = canonical_property_name(name);
if (property != "overscroll-behavior"
&& property != "overscroll-behavior-x"
&& property != "overscroll-behavior-y") return false;
auto value = ascii_lower(trim_value(raw_value));
const auto inherited = value == "inherit";
const auto initial = value == "initial" || value == "unset"
|| value == "revert" || value == "revert-layer";
const auto parent_x = node.parent == nullptr
? overscroll_behavior::automatic
: node.parent->style.textual().overscroll_x;
const auto parent_y = node.parent == nullptr
? overscroll_behavior::automatic
: node.parent->style.textual().overscroll_y;
auto x = node.style.textual().overscroll_x;
auto y = node.style.textual().overscroll_y;
if (inherited || initial) {
const auto reset_x = inherited ? parent_x : overscroll_behavior::automatic;
const auto reset_y = inherited ? parent_y : overscroll_behavior::automatic;
if (property != "overscroll-behavior-y") x = reset_x;
if (property != "overscroll-behavior-x") y = reset_y;
} else if (property == "overscroll-behavior") {
std::istringstream stream(value);
std::string first;
std::string second;
std::string extra;
stream >> first >> second >> extra;
const auto parsed_x = parse_overscroll_behavior(first);
const auto parsed_y = parse_overscroll_behavior(
second.empty() ? first : second);
if (!parsed_x.has_value() || !parsed_y.has_value() || !extra.empty()) {
decision.classification = "invalid-authoring";
return true;
}
x = *parsed_x;
y = *parsed_y;
} else {
const auto parsed = parse_overscroll_behavior(value);
if (!parsed.has_value()) {
decision.classification = "invalid-authoring";
return true;
}
if (property == "overscroll-behavior-x") x = *parsed;
else y = *parsed;
}
if (x == overscroll_behavior::automatic
&& y == overscroll_behavior::automatic
&& !node.style.has_textual_data()) return true;
auto& textual = node.style.mutable_textual();
textual.overscroll_x = x;
textual.overscroll_y = y;
return true;
}
} // namespace webscene_native::css
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,12 @@ inline void apply_all_unset(
if (has_inline({"user-select", "-webkit-user-select", "-ms-user-select"})) {
reset.mutable_textual().user_select = previous.textual().user_select;
}
if (has_inline({"overscroll-behavior", "overscroll-behavior-x"})) {
reset.mutable_textual().overscroll_x = previous.textual().overscroll_x;
}
if (has_inline({"overscroll-behavior", "overscroll-behavior-y"})) {
reset.mutable_textual().overscroll_y = previous.textual().overscroll_y;
}

// 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 @@ -10,6 +10,7 @@ inline constexpr auto specified_property_samples = std::to_array<specified_prope
{"all","unset"},{"content","\"x\""},{"aspect-ratio","16 / 9"},
{"object-fit","cover"},{"object-position","right 25%"},
{"user-select","none"},
{"overscroll-behavior","contain none"},{"overscroll-behavior-x","auto"},{"overscroll-behavior-y","contain"},
{"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 @@ -160,6 +160,12 @@ enum class overflow_mode : uint8_t {
scroll
};

enum class overscroll_behavior : uint8_t {
automatic,
contain,
none
};

enum class float_mode : uint8_t {
none,
left,
Expand Down Expand Up @@ -796,6 +802,10 @@ 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;
// 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};
overscroll_behavior overscroll_y{overscroll_behavior::automatic};
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 @@ -721,6 +721,11 @@
dom_node& node,
std::string_view canonical_property_name)
{
if (canonical_property_name == "overscroll-behavior"
|| canonical_property_name == "overscroll-behavior-x"
|| canonical_property_name == "overscroll-behavior-y") {
return;
}
// These properties affect painting or interaction, but never box
// measurement or flow. Publish the scene without synchronously
// traversing layout again.
Expand Down
Loading
Loading