use std::collections::HashSet;
use std::ffi::c_void;
use std::fmt;
use embedder_traits::UntrustedNodeAddress;
use js::context::JSContext;
use js::conversions::FromJSValConvertible;
use js::rust::HandleValue;
use layout_api::HitTestFlags;
use script_bindings::cell::DomRefCell;
use script_bindings::codegen::GenericBindings::DocumentBinding::DocumentMethods;
use script_bindings::codegen::GenericBindings::ShadowRootBinding::ShadowRootMethods;
use script_bindings::codegen::GenericBindings::WindowBinding::WindowMethods;
use script_bindings::error::{Error, ErrorResult};
use servo_arc::Arc;
use servo_config::pref;
use style::media_queries::MediaList;
use style::shared_lock::{SharedRwLock as StyleSharedRwLock, SharedRwLockReadGuard};
use style::stylesheets::scope_rule::ImplicitScopeRoot;
use style::stylesheets::{Stylesheet, StylesheetContents};
use webrender_api::units::LayoutPoint;
use crate::css::stylesheet_set::StylesheetSetRef;
use crate::dom::Document;
use crate::dom::bindings::codegen::Bindings::NodeBinding::GetRootNodeOptions;
use crate::dom::bindings::codegen::Bindings::NodeBinding::Node_Binding::NodeMethods;
use crate::dom::bindings::conversions::ConversionResult;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::num::Finite;
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::css::stylesheetlist::StyleSheetListOwner;
use crate::dom::customelementregistry::CustomElementRegistry;
use crate::dom::element::Element;
use crate::dom::node::{self, Node};
use crate::dom::types::{CSSStyleSheet, EventTarget, ShadowRoot};
use crate::dom::window::Window;
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) enum StylesheetSource {
Element(Dom<Element>),
Constructed(Dom<CSSStyleSheet>),
}
impl StylesheetSource {
pub(crate) fn get_cssom_object(&self, cx: &mut JSContext) -> Option<DomRoot<CSSStyleSheet>> {
match self {
StylesheetSource::Element(el) => el.upcast::<Node>().get_cssom_stylesheet(cx),
StylesheetSource::Constructed(ss) => Some(ss.as_rooted()),
}
}
pub(crate) fn is_a_valid_owner(&self) -> bool {
match self {
StylesheetSource::Element(el) => el.as_stylesheet_owner().is_some(),
StylesheetSource::Constructed(ss) => ss.is_constructed(),
}
}
pub(crate) fn is_constructed(&self) -> bool {
matches!(self, StylesheetSource::Constructed(_))
}
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) struct ServoStylesheetInDocument {
#[ignore_malloc_size_of = "Stylo"]
#[no_trace]
pub(crate) sheet: Arc<Stylesheet>,
pub(crate) owner: StylesheetSource,
}
impl stylo_malloc_size_of::MallocSizeOf for ServoStylesheetInDocument {
fn size_of(&self, ops: &mut stylo_malloc_size_of::MallocSizeOfOps) -> usize {
<ServoStylesheetInDocument as malloc_size_of::MallocSizeOf>::size_of(self, ops)
}
}
impl fmt::Debug for ServoStylesheetInDocument {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
self.sheet.fmt(formatter)
}
}
impl PartialEq for ServoStylesheetInDocument {
fn eq(&self, other: &Self) -> bool {
Arc::ptr_eq(&self.sheet, &other.sheet)
}
}
impl ::style::stylesheets::StylesheetInDocument for ServoStylesheetInDocument {
fn enabled(&self) -> bool {
self.sheet.enabled()
}
fn media<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> Option<&'a MediaList> {
self.sheet.media(guard)
}
fn contents<'a>(&'a self, guard: &'a SharedRwLockReadGuard) -> &'a StylesheetContents {
self.sheet.contents(guard)
}
fn implicit_scope_root(&self) -> Option<ImplicitScopeRoot> {
None
}
}
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) struct DocumentOrShadowRoot {
window: Dom<Window>,
custom_element_registry: MutNullableDom<CustomElementRegistry>,
}
impl DocumentOrShadowRoot {
pub(crate) fn new(window: &Window) -> Self {
Self {
window: Dom::from_ref(window),
custom_element_registry: MutNullableDom::new(None),
}
}
pub(crate) fn custom_element_registry(&self) -> Option<DomRoot<CustomElementRegistry>> {
self.custom_element_registry.get()
}
pub(crate) fn set_custom_element_registry(&self, registry: Option<&CustomElementRegistry>) {
self.custom_element_registry.set(registry);
}
pub(crate) fn retarget_hit_test_result(
&self,
this: &Node,
node: &Node,
) -> Option<DomRoot<Element>> {
let retargeted_node =
DomRoot::downcast::<Node>(node.upcast::<EventTarget>().retarget(this.upcast()))?;
DomRoot::downcast::<Element>(retargeted_node.clone()).or_else(|| {
let parent_node = retargeted_node.GetParentNode()?;
if let Some(shadow_root) = parent_node.downcast::<ShadowRoot>() {
Some(shadow_root.Host())
} else {
retargeted_node.GetParentElement()
}
})
}
#[expect(unsafe_code)]
pub(crate) fn element_from_point(
&self,
this: &Node,
x: Finite<f64>,
y: Finite<f64>,
document_element: Option<DomRoot<Element>>,
has_browsing_context: bool,
) -> Option<DomRoot<Element>> {
let x = *x as f32;
let y = *y as f32;
let viewport = self.window.viewport_details().size;
if !has_browsing_context {
return None;
}
if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height {
return None;
}
let flags = HitTestFlags::empty();
let result = self
.window
.elements_from_point_query(flags, LayoutPoint::new(x, y));
let Some(result) = result.items.first() else {
return document_element;
};
let address = UntrustedNodeAddress(result.node.0 as *const c_void);
let node = unsafe { node::from_untrusted_node_address(address) };
self.retarget_hit_test_result(this, &node)
}
#[expect(unsafe_code)]
pub(crate) fn elements_from_point(
&self,
this: &Node,
x: Finite<f64>,
y: Finite<f64>,
document_element: Option<DomRoot<Element>>,
has_browsing_context: bool,
) -> Vec<DomRoot<Element>> {
let x = *x as f32;
let y = *y as f32;
let viewport = self.window.viewport_details().size;
if !has_browsing_context {
return vec![];
}
if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height {
return vec![];
}
let flags = HitTestFlags::empty();
let result = self
.window
.elements_from_point_query(flags, LayoutPoint::new(x, y));
let mut elements: Vec<_> = result
.items
.iter()
.flat_map(|result| {
let address = UntrustedNodeAddress(result.node.0 as *const c_void);
let node = unsafe { node::from_untrusted_node_address(address) };
self.retarget_hit_test_result(this, &node)
})
.collect();
let mut last_seen = None;
elements.retain(|element| {
if Some(element) == last_seen.as_ref() {
return false;
}
last_seen = Some(element.clone());
true
});
if let Some(root_element) = document_element &&
elements.last() != Some(&root_element)
{
elements.push(root_element);
}
elements
}
pub(crate) fn active_element(&self, this: &Node) -> Option<DomRoot<Element>> {
let document = self.window.Document();
let candidate = document
.focus_handler()
.focused_area()
.dom_anchor(&document);
let candidate =
DomRoot::downcast::<Node>(candidate.upcast::<EventTarget>().retarget(this.upcast()))?;
if this != &*candidate.GetRootNode(&GetRootNodeOptions::empty()) {
return None;
}
if let Some(candidate) = DomRoot::downcast::<Element>(candidate.clone()) {
return Some(candidate);
}
assert!(candidate.is::<Document>());
if let Some(body) = document.GetBody() {
return Some(DomRoot::upcast(body));
}
if let Some(document_element) = document.GetDocumentElement() {
return Some(document_element);
}
None
}
#[cfg_attr(crown, expect(crown::unrooted_must_root))] pub(crate) fn remove_stylesheet(
owner: StylesheetSource,
s: &Arc<Stylesheet>,
mut stylesheets: StylesheetSetRef<ServoStylesheetInDocument>,
) {
let guard = s.shared_lock.read();
stylesheets.remove_stylesheet(
None,
ServoStylesheetInDocument {
sheet: s.clone(),
owner,
},
&guard,
);
}
#[cfg_attr(crown, expect(crown::unrooted_must_root))] pub(crate) fn add_stylesheet(
owner: StylesheetSource,
mut stylesheets: StylesheetSetRef<ServoStylesheetInDocument>,
sheet: Arc<Stylesheet>,
insertion_point: Option<ServoStylesheetInDocument>,
style_shared_lock: &StyleSharedRwLock,
) {
debug_assert!(owner.is_a_valid_owner(), "Wat");
if owner.is_constructed() && !pref!(dom_adoptedstylesheet_enabled) {
return;
}
let sheet = ServoStylesheetInDocument { sheet, owner };
let guard = style_shared_lock.read();
match insertion_point {
Some(ip) => {
stylesheets.insert_stylesheet_before(None, sheet, ip, &guard);
},
None => {
stylesheets.append_stylesheet(None, sheet, &guard);
},
}
}
fn set_adopted_stylesheet(
adopted_stylesheets: &mut Vec<Dom<CSSStyleSheet>>,
incoming_stylesheets: &[Dom<CSSStyleSheet>],
owner: &StyleSheetListOwner,
) -> ErrorResult {
if !pref!(dom_adoptedstylesheet_enabled) {
return Ok(());
}
let owner_doc = match owner {
StyleSheetListOwner::Document(doc) => doc,
StyleSheetListOwner::ShadowRoot(root) => root.owner_doc(),
};
for sheet in incoming_stylesheets.iter() {
if !sheet.constructor_document_matches(owner_doc) {
return Err(Error::NotAllowed(Some(
"Stylesheet is not constructed or its constructor document does not match"
.into(),
)));
}
}
let mut stylesheet_remove_set = HashSet::with_capacity(adopted_stylesheets.len());
for sheet_to_remove in adopted_stylesheets.iter() {
if stylesheet_remove_set.insert(sheet_to_remove) {
owner.remove_stylesheet(
StylesheetSource::Constructed(sheet_to_remove.clone()),
&sheet_to_remove.style_stylesheet(),
);
sheet_to_remove.remove_adopter(owner);
}
}
let mut stylesheet_add_set = HashSet::with_capacity(incoming_stylesheets.len());
for sheet in incoming_stylesheets.iter() {
if !stylesheet_add_set.insert(sheet) {
owner.remove_stylesheet(
StylesheetSource::Constructed(sheet.clone()),
&sheet.style_stylesheet(),
);
} else {
sheet.add_adopter(owner.clone());
}
owner.append_constructed_stylesheet(sheet);
}
*adopted_stylesheets = incoming_stylesheets.to_vec();
Ok(())
}
pub(crate) fn set_adopted_stylesheet_from_jsval(
cx: &mut JSContext,
adopted_stylesheets: &DomRefCell<Vec<Dom<CSSStyleSheet>>>,
incoming_value: HandleValue,
owner: &StyleSheetListOwner,
) -> ErrorResult {
let maybe_stylesheets = Vec::<DomRoot<CSSStyleSheet>>::safe_from_jsval(cx, incoming_value, ())
.map_err(|_| Error::JSFailed)?;
match maybe_stylesheets {
ConversionResult::Success(stylesheets) => {
rooted_vec!(let stylesheets <- stylesheets.iter().map(|s| s.as_traced()));
let mut sheets = adopted_stylesheets.safe_borrow_mut(cx);
DocumentOrShadowRoot::set_adopted_stylesheet(sheets.as_mut(), &stylesheets, owner)
},
ConversionResult::Failure(msg) => Err(Error::Type(msg.into_owned())),
}
}
}