diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_compound.h b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_compound.h index 9e4b78b0..b5dc40fd 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_compound.h +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_compound.h @@ -339,6 +339,8 @@ inline bool compound_matches(const Host& host,const dom_node& node, if (!hash || !css::target_matches(node,*hash)) return false; } else if (name == "checked") { if (!css::checked_matches(node)) return false; + } else if (name == "default") { + if (!css::default_matches(document,node)) return false; } else if (name == "indeterminate") { if (!css::indeterminate_matches(node)) return false; } else if (name == "in-range") { diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_invalidation.h b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_invalidation.h index bcd37dcb..88b6c779 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_invalidation.h +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_invalidation.h @@ -108,6 +108,16 @@ inline std::vector compile_invalidation_plan( } else if (pseudo.name == "checked") { for (const auto* name : {"checked", "selected", "type"}) add(output.attributes[name], route); + } else if (pseudo.name == "default") { + // Authored defaults update the subject directly. Submit-button + // ownership/order changes can select a different control, so + // those rare mutations conservatively recascade the document. + add(output.attributes["checked"], route); + add(output.attributes["selected"], route); + for (const auto* name : {"type", "form", "id"}) + output.attributes[name].scope |= invalidation_fallback; + output.child_list_sensitive = true; + output.child_list.scope |= invalidation_fallback; } else if (pseudo.name == "indeterminate") { for (const auto* name : {"$live-form-indeterminate", "checked", "type", "name"}) add(output.attributes[name], route); diff --git a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_matching.h b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_matching.h index f918e970..ebd17d70 100644 --- a/experiments/WebScene.NativeEngine.Probe/native/webscene_css_matching.h +++ b/experiments/WebScene.NativeEngine.Probe/native/webscene_css_matching.h @@ -263,6 +263,73 @@ inline bool direction_matches(const native_document& document,const dom_node& no } return true; } +inline const dom_node* form_owner_for_selector( + const native_document& document,const dom_node& control) { + const auto explicit_owner=control.attributes.find("form"); + if(explicit_owner!=control.attributes.end()) { + if(explicit_owner->second.empty()) return nullptr; + const dom_node* root=&control; + while(auto* parent=document.dom_parent(*root)) { + if(parent->tag=="iframe") break; + root=parent; + } + const auto find=[&](const auto& recurse,const dom_node& current)->const dom_node* { + if(current.tag=="form" && current.id_attribute==explicit_owner->second) + return ¤t; + for(const auto* child:current.children) { + if(child==nullptr || child->tag=="iframe") continue; + if(const auto* matched=recurse(recurse,*child)) return matched; + } + return nullptr; + }; + return find(find,*root); + } + for(auto* ancestor=document.dom_parent(control);ancestor!=nullptr; + ancestor=document.dom_parent(*ancestor)) { + if(ancestor->tag=="iframe") break; + if(ancestor->tag=="form") return ancestor; + } + return nullptr; +} + +inline bool is_submit_button_for_selector(const dom_node& node) { + const auto authored=node.attributes.find("type"); + const auto type=authored==node.attributes.end() + ? std::string_view{}:std::string_view{authored->second}; + if(node.tag=="button") return type.empty() || html_keyword_equals(type,"submit"); + return node.tag=="input" + && (html_keyword_equals(type,"submit") || html_keyword_equals(type,"image")); +} + +inline bool default_matches(const native_document& document,const dom_node& node) { + if(node.tag=="option") return node.attributes.contains("selected"); + if(node.tag=="input") { + const auto type=node.attributes.find("type"); + if(type!=node.attributes.end() + && (html_keyword_equals(type->second,"checkbox") + || html_keyword_equals(type->second,"radio"))) + return node.attributes.contains("checked"); + } + if(!is_submit_button_for_selector(node)) return false; + const auto* owner=form_owner_for_selector(document,node); + if(owner==nullptr) return false; + const dom_node* root=&node; + while(auto* parent=document.dom_parent(*root)) { + if(parent->tag=="iframe") break; + root=parent; + } + const auto first=[&](const auto& recurse,const dom_node& current)->const dom_node* { + if(is_submit_button_for_selector(current) + && form_owner_for_selector(document,current)==owner) return ¤t; + for(const auto* child:current.children) { + if(child==nullptr || child->tag=="iframe") continue; + if(const auto* matched=recurse(recurse,*child)) return matched; + } + return nullptr; + }; + return first(first,*root)==&node; +} + inline bool checked_matches(const dom_node& node) { const auto type=node.attributes.find("type"); const bool checkable=node.tag=="input" && type!=node.attributes.end() &&