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
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
namespace webscene_native {
// The pinned WebScene exposes a stable native HTMLStyleElement.sheet object,
// but no rule mutation methods. Its textContent setter *does* replace that
// owner's parsed native rules, recascade, and invalidate layout. Bridge the
// dynamic style-rule operations used by Code OSS to that existing renderer.
// owner's parsed native rules. Bridge dynamic CSSOM operations to a native
// staged rule-set replacement so one task publishes each owner's final rules
// and recascades once.
// This is a bounded adapter, not a complete CSSOM implementation: constructed
// sheets, imported-sheet inspection and nested rule mutation remain unsupported.
// Semantics: https://www.w3.org/TR/cssom-1/
Expand Down Expand Up @@ -72,10 +73,19 @@ inline constexpr std::string_view cssCompatibilityScript = R"JS(
return state;
};
const publish = state => {
// Native textContent replacement is the rendering operation. Do not merely
// update JavaScript records: every insertion/deletion reaches the cascade.
const source = state.rules.map(rule => rule.cssText).join('\n');
state.owner.textContent = source;
const publishedSource = state.disabled ? ''
: state.mediaText.trim() ? `@media ${state.mediaText} {\n${source}\n}`
: source;
if (typeof state.sheet.__webSceneStageRules === 'function') {
// CSSOM mutation does not replace the style element's DOM text nodes.
// Stage the final serialized rule set in native state; synchronous
// style/layout reads and the browser-task boundary flush it.
state.sheet.__webSceneStageRules(publishedSource);
} else {
state.owner.textContent = publishedSource;
state.ownerSource = publishedSource;
}
state.source = source;
};
const makeRule = (state, parsed) => {
Expand Down Expand Up @@ -129,15 +139,22 @@ inline constexpr std::string_view cssCompatibilityScript = R"JS(
};
const synchronize = state => {
const source = state.owner.textContent || '';
if (source === state.source) return;
if (source === state.ownerSource) return;
const parsed = splitRules(source);
for (const rule of state.rules) rule.detach();
state.rules = parsed.map(rule => makeRule(state, rule));
state.source = source;
state.ownerSource = source;
};
const augment = sheet => {
if (!sheet || typeof sheet.insertRule === 'function') return sheet;
const state = { sheet, owner: sheet.ownerNode, source: undefined, rules: [] };
const state = {
sheet, owner: sheet.ownerNode,
source: undefined, ownerSource: undefined, rules: [], disabled: false,
mediaText: typeof sheet.ownerNode?.getAttribute === 'function'
? sheet.ownerNode.getAttribute('media') || ''
: ''
};
const list = new Proxy({}, {
get(_target, key) {
synchronize(state);
Expand All @@ -152,9 +169,39 @@ inline constexpr std::string_view cssCompatibilityScript = R"JS(
deleteProperty() { return false; },
defineProperty() { return false; }
});
const media = {};
Object.defineProperties(media, {
mediaText: {
enumerable: true,
get() { return state.mediaText; },
set(value) {
state.mediaText = text(value).trim();
if (state.mediaText) state.owner.setAttribute('media', state.mediaText);
else state.owner.removeAttribute('media');
publish(state);
}
},
length: { enumerable: true, get() {
return state.mediaText ? state.mediaText.split(',').length : 0;
} },
item: { value(index) {
return state.mediaText.split(',').map(value => value.trim())[Number(index)] || null;
} }
});
sheets.set(sheet, state);
Object.defineProperties(sheet, {
cssRules: { configurable: true, enumerable: true, get() { stateFor(this); return list; } },
disabled: {
configurable: true, enumerable: true,
get() { return stateFor(this).disabled; },
set(value) {
const current = stateFor(this), disabled = Boolean(value);
if (current.disabled === disabled) return;
current.disabled = disabled;
publish(current);
}
},
media: { configurable: true, enumerable: true, get() { stateFor(this); return media; } },
insertRule: { configurable: true, writable: true, value(rule, index = 0) {
if (arguments.length === 0) throw new TypeError('A CSS rule is required');
const current = stateFor(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7145,13 +7145,27 @@ v8_dom_runtime::memory_metrics v8_dom_runtime::read_memory_metrics() const noexc
+ impl_->class_list_wrappers.size()
+ impl_->sandbox_token_list_wrappers.size()
+ impl_->style_wrappers.size()
+ impl_->stylesheet_wrappers.size()
+ impl_->computed_style_wrappers.size();
result.native_wrapper_storage_bytes =
wrapper_map_storage(impl_->node_wrappers)
+ wrapper_map_storage(impl_->class_list_wrappers)
+ wrapper_map_storage(impl_->sandbox_token_list_wrappers)
+ wrapper_map_storage(impl_->style_wrappers)
+ wrapper_map_storage(impl_->stylesheet_wrappers)
+ wrapper_map_storage(impl_->computed_style_wrappers);
result.native_wrapper_storage_bytes +=
impl_->stylesheet_cssom_sources.bucket_count() * sizeof(void*);
for (const auto& [owner_id, source] : impl_->stylesheet_cssom_sources) {
static_cast<void>(owner_id);
result.native_wrapper_storage_bytes +=
sizeof(std::pair<const uint32_t, std::string>)
+ 2U * sizeof(void*) + source.capacity() + 1U;
}
result.native_wrapper_storage_bytes +=
impl_->pending_stylesheet_replacements.capacity()
* sizeof(impl_->pending_stylesheet_replacements.front())
+ impl_->pending_stylesheet_replacement_bytes;
const auto listener_map_storage = [](const auto& map) {
using map_type = std::decay_t<decltype(map)>;
using vector_type = typename map_type::mapped_type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1876,8 +1876,77 @@
std::move(custom_property)});
}

void erase_pending_stylesheet_replacement(uint32_t owner_id)
{
std::erase_if(
pending_stylesheet_replacements,
[&](const auto& pending) {
if (pending.owner_id != owner_id) return false;
pending_stylesheet_replacement_bytes -= std::min(
pending_stylesheet_replacement_bytes,
pending.source.size());
return true;
});
}

void flush_pending_stylesheet_replacements()
{
if (pending_stylesheet_replacements.empty()) return;
auto pending = std::move(pending_stylesheet_replacements);
pending_stylesheet_replacements.clear();
pending_stylesheet_replacement_bytes = 0U;
auto* previous_root = const_cast<dom_node*>(active_css_cascade_root);
std::vector<dom_node*> roots;
for (const auto& replacement : pending) {
auto* owner = document.find_by_native_id(replacement.owner_id);
auto* root = document.find_by_native_id(replacement.cascade_root_id);
if (owner == nullptr || root == nullptr || !is_connected(*owner)) {
continue;
}
if (std::find(roots.begin(), roots.end(), root)
== roots.end()) {
roots.push_back(root);
}
}
for (auto* root : roots) {
activate_css_cascade(root);
std::unordered_set<uint32_t> owners;
for (const auto& replacement : pending) {
auto* owner = document.find_by_native_id(replacement.owner_id);
if (replacement.cascade_root_id == root->id
&& owner != nullptr
&& is_connected(*owner)) {
owners.insert(owner->id);
}
}
std::erase_if(css_rules, [&](const auto& rule) {
return owners.contains(rule.stylesheet_owner_id);
});
for (auto& replacement : pending) {
auto* owner = document.find_by_native_id(replacement.owner_id);
if (replacement.cascade_root_id != root->id
|| owner == nullptr
|| !is_connected(*owner)
|| replacement.source.empty()) {
continue;
}
const auto* shadow_root =
document.containing_shadow_root(*owner);
add_stylesheet(
std::move(replacement.source),
replacement.base_address,
owner->id,
shadow_root == nullptr ? 0U : shadow_root->id);
}
rebuild_css_rule_indexes_and_root_variables();
schedule_style_recascade(*root, true, "stylesheet-change");
}
if (previous_root != nullptr) activate_css_cascade(previous_root);
}

void flush_pending_style_recascades()
{
flush_pending_stylesheet_replacements();
if (!pending_attribute_selector_transitions.empty()) {
auto transitions = std::move(pending_attribute_selector_transitions);
pending_attribute_selector_transitions.clear();
Expand Down Expand Up @@ -2293,6 +2362,14 @@

void recascade_after_stylesheet_change()
{
if (style_recascade_batch_depth != 0U) {
schedule_style_recascade(
active_root(),
true,
"stylesheet-change");
document.mark_dirty();
return;
}
apply_css_rules_subtree(active_root());
document.mark_dirty();
}
Expand Down Expand Up @@ -2333,11 +2410,19 @@
});
if (styles.empty()) return;

for (const auto* style : styles) {
if (style != nullptr) erase_pending_stylesheet_replacement(style->id);
}
for (auto* style : styles) deactivate_connected_stylesheets(*style, false);
const auto& base = current_base_address();
for (auto* style : styles) {
std::string stylesheet;
append_node_text(*style, stylesheet);
auto stylesheet = std::string{};
if (const auto staged = stylesheet_cssom_sources.find(style->id);
staged != stylesheet_cssom_sources.end()) {
stylesheet = staged->second;
} else {
append_node_text(*style, stylesheet);
}
if (!stylesheet.empty()) {
const auto* shadow_root = document.containing_shadow_root(*style);
add_stylesheet(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,10 +352,74 @@
local_context,
js_string(isolate, "ownerNode"),
wrap_node(node)).Check();
auto stage_rules = v8::Function::New(
local_context,
stage_style_sheet_rules,
wrap_node(node)).ToLocalChecked();
result->DefineOwnProperty(
local_context,
js_string(isolate, "__webSceneStageRules"),
stage_rules,
static_cast<v8::PropertyAttribute>(
v8::DontEnum | v8::DontDelete | v8::ReadOnly))
.Check();
stylesheet_wrappers.emplace(key, v8::Global<v8::Object>(isolate, result));
return augment_style_sheet(result);
}

static void stage_style_sheet_rules(
const v8::FunctionCallbackInfo<v8::Value>& info)
{
auto* self = current(info.GetIsolate());
auto* owner = info.Data()->IsObject()
? unwrap_node(info.Data().As<v8::Object>())
: nullptr;
if (self == nullptr || owner == nullptr || info.Length() < 1) return;
v8::Local<v8::String> converted;
if (!info[0]->ToString(info.GetIsolate()->GetCurrentContext())
.ToLocal(&converted)) {
return;
}
auto source = to_wtf8(info.GetIsolate(), converted);
self->erase_pending_stylesheet_replacement(owner->id);
constexpr auto maximum_pending_owners = size_t{1024U};
constexpr auto maximum_pending_bytes = size_t{32U * 1024U * 1024U};
if (source.size() > maximum_pending_bytes) {
// A single source cannot fit in the bounded coalescing queue. Keep
// the CSSOM source for disconnect/reconnect semantics, but publish
// connected owners immediately so the same bytes are never copied
// into pending replacement storage as well.
self->flush_pending_stylesheet_replacements();
self->stylesheet_cssom_sources.insert_or_assign(
owner->id, std::move(source));
self->activate_connected_stylesheet(*owner);
self->document.mark_dirty();
info.GetReturnValue().Set(v8::False(info.GetIsolate()));
return;
}
if (self->pending_stylesheet_replacements.size() >= maximum_pending_owners
|| source.size() > maximum_pending_bytes
- std::min(
self->pending_stylesheet_replacement_bytes,
maximum_pending_bytes)) {
self->flush_pending_stylesheet_replacements();
}
self->stylesheet_cssom_sources.insert_or_assign(owner->id, source);
auto* cascade_root = self->css_cascade_root_for_node(*owner);
const auto base_address = cascade_root != nullptr
&& self->frame_base_addresses.contains(cascade_root)
? self->frame_base_addresses.at(cascade_root)
: self->document_base_address;
self->pending_stylesheet_replacement_bytes += source.size();
self->pending_stylesheet_replacements.push_back({
owner->id,
cascade_root == nullptr ? 0U : cascade_root->id,
base_address,
std::move(source)});
self->document.mark_dirty();
info.GetReturnValue().Set(v8::True(info.GetIsolate()));
}

v8::Local<v8::Object> augment_style_sheet(v8::Local<v8::Object> sheet)
{
const auto local_context = isolate->GetCurrentContext();
Expand Down Expand Up @@ -949,6 +1013,7 @@
collect(class_list_wrappers, key);
collect(sandbox_token_list_wrappers, key);
collect(style_wrappers, key);
collect(stylesheet_wrappers, key);
collect(computed_style_wrappers, key);
collect(canvas_contexts, key);
});
Expand Down Expand Up @@ -987,6 +1052,7 @@
weaken(class_list_wrappers, key);
weaken(sandbox_token_list_wrappers, key);
weaken(style_wrappers, key);
weaken(stylesheet_wrappers, key);
weaken(computed_style_wrappers, key);
weaken(canvas_contexts, key);
});
Expand All @@ -1012,6 +1078,7 @@
|| weak(class_list_wrappers, key)
|| weak(sandbox_token_list_wrappers, key)
|| weak(style_wrappers, key)
|| weak(stylesheet_wrappers, key)
|| weak(computed_style_wrappers, key)
|| weak(canvas_contexts, key)) {
return true;
Expand Down Expand Up @@ -1071,6 +1138,7 @@
retain(class_list_wrappers, key);
retain(sandbox_token_list_wrappers, key);
retain(style_wrappers, key);
retain(stylesheet_wrappers, key);
retain(computed_style_wrappers, key);
retain(canvas_contexts, key);
});
Expand Down Expand Up @@ -1351,6 +1419,7 @@
|| inspect(class_list_wrappers, key)
|| inspect(sandbox_token_list_wrappers, key)
|| inspect(style_wrappers, key)
|| inspect(stylesheet_wrappers, key)
|| inspect(computed_style_wrappers, key)
|| inspect(canvas_contexts, key);
}
Expand Down Expand Up @@ -1383,6 +1452,7 @@
erase_wrappers(class_list_wrappers);
erase_wrappers(sandbox_token_list_wrappers);
erase_wrappers(style_wrappers);
erase_wrappers(stylesheet_wrappers);
erase_wrappers(computed_style_wrappers);
erase_wrappers(canvas_contexts);
std::erase_if(detached_documents, [&](const auto& entry) {
Expand All @@ -1391,6 +1461,10 @@
std::erase_if(detached_document_roots, [&](const auto id) {
return node_ids.contains(static_cast<uint32_t>(id));
});
for (const auto id : node_ids) {
erase_pending_stylesheet_replacement(id);
stylesheet_cssom_sources.erase(id);
}
std::erase_if(canvas_states, [&](const auto& entry) {
return node_ids.contains(static_cast<uint32_t>(entry.first));
});
Expand Down
Loading
Loading