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 @@ -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") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ inline std::vector<css_compound_dependencies> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 &current;
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 &current;
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() &&
Expand Down
Loading