1use web_sys::{
4 CssStyleDeclaration, Document, Element, HtmlElement, Node, ShadowRoot, Window, css,
5 js_sys::Object, wasm_bindgen::JsCast, window,
6};
7
8use crate::ElementOrWindow;
9
10#[derive(Clone, Debug)]
11pub enum DomNodeOrWindow<'a> {
12 Node(&'a Node),
13 Window(&'a Window),
14}
15
16impl<'a> From<&'a Node> for DomNodeOrWindow<'a> {
17 fn from(value: &'a Node) -> Self {
18 DomNodeOrWindow::Node(value)
19 }
20}
21
22impl<'a> From<&'a Element> for DomNodeOrWindow<'a> {
23 fn from(value: &'a Element) -> Self {
24 DomNodeOrWindow::Node(value)
25 }
26}
27
28impl<'a> From<&'a Window> for DomNodeOrWindow<'a> {
29 fn from(value: &'a Window) -> Self {
30 DomNodeOrWindow::Window(value)
31 }
32}
33
34impl<'a> From<ElementOrWindow<'a, Element, Window>> for DomNodeOrWindow<'a> {
35 fn from(value: ElementOrWindow<'a, Element, Window>) -> Self {
36 match value {
37 ElementOrWindow::Element(element) => DomNodeOrWindow::Node(element),
38 ElementOrWindow::Window(window) => DomNodeOrWindow::Window(window),
39 }
40 }
41}
42
43impl<'a> From<&ElementOrWindow<'a, Element, Window>> for DomNodeOrWindow<'a> {
44 fn from(value: &ElementOrWindow<'a, Element, Window>) -> Self {
45 match value {
46 ElementOrWindow::Element(element) => DomNodeOrWindow::Node(element),
47 ElementOrWindow::Window(window) => DomNodeOrWindow::Window(window),
48 }
49 }
50}
51
52impl<'a> From<&'a DomElementOrWindow<'a>> for DomNodeOrWindow<'a> {
53 fn from(value: &'a DomElementOrWindow) -> Self {
54 match value {
55 DomElementOrWindow::Element(element) => DomNodeOrWindow::Node(element),
56 DomElementOrWindow::Window(window) => DomNodeOrWindow::Window(window),
57 }
58 }
59}
60
61#[derive(Clone, Debug)]
62pub enum DomElementOrWindow<'a> {
63 Element(&'a Element),
64 Window(&'a Window),
65}
66
67impl<'a> From<&'a Element> for DomElementOrWindow<'a> {
68 fn from(value: &'a Element) -> Self {
69 DomElementOrWindow::Element(value)
70 }
71}
72
73impl<'a> From<&'a Window> for DomElementOrWindow<'a> {
74 fn from(value: &'a Window) -> Self {
75 DomElementOrWindow::Window(value)
76 }
77}
78
79impl<'a> From<ElementOrWindow<'a, Element, Window>> for DomElementOrWindow<'a> {
80 fn from(value: ElementOrWindow<'a, Element, Window>) -> Self {
81 match value {
82 ElementOrWindow::Element(element) => DomElementOrWindow::Element(element),
83 ElementOrWindow::Window(window) => DomElementOrWindow::Window(window),
84 }
85 }
86}
87
88impl<'a> From<&ElementOrWindow<'a, Element, Window>> for DomElementOrWindow<'a> {
89 fn from(value: &ElementOrWindow<'a, Element, Window>) -> Self {
90 match value {
91 ElementOrWindow::Element(element) => DomElementOrWindow::Element(element),
92 ElementOrWindow::Window(window) => DomElementOrWindow::Window(window),
93 }
94 }
95}
96
97pub fn get_node_name(node_or_window: DomNodeOrWindow) -> String {
98 match node_or_window {
99 DomNodeOrWindow::Node(node) => node.node_name().to_lowercase(),
100 DomNodeOrWindow::Window(_) => "#document".into(),
101 }
102}
103
104pub fn get_window(node: Option<&Node>) -> Window {
105 match node {
106 Some(node) => match node.owner_document() {
107 Some(document) => document.default_view(),
108 None => window(),
109 },
110 None => window(),
111 }
112 .expect("Window should exist.")
113}
114
115pub fn get_document_element(node_or_window: Option<DomNodeOrWindow>) -> Element {
116 let document = match node_or_window {
117 Some(DomNodeOrWindow::Node(node)) => node.owner_document(),
118 Some(DomNodeOrWindow::Window(window)) => window.document(),
119 None => get_window(None).document(),
120 }
121 .expect("Node or window should have document.");
122
123 document
124 .document_element()
125 .expect("Document should have document element.")
126}
127
128pub fn is_element(node: &Node) -> bool {
129 node.is_instance_of::<Element>()
130}
131
132pub fn is_html_element(node: &Node) -> bool {
133 node.is_instance_of::<HtmlElement>()
134}
135
136const OVERFLOW_VALUES: [&str; 5] = ["auto", "scroll", "overlay", "hidden", "clip"];
137const DISPLAY_VALUES: [&str; 2] = ["inline", "contents"];
138
139pub fn is_overflow_element(element: &Element) -> bool {
140 let style = get_computed_style(element);
141 let overflow = style.get_property_value("overflow").unwrap_or_default();
142 let overflow_x = style.get_property_value("overflow-x").unwrap_or_default();
143 let overflow_y = style.get_property_value("overflow-y").unwrap_or_default();
144 let display = style.get_property_value("display").unwrap_or_default();
145
146 let overflow_combined = format!("{overflow}{overflow_x}{overflow_y}");
147
148 OVERFLOW_VALUES
149 .into_iter()
150 .any(|s| overflow_combined.contains(s))
151 && !DISPLAY_VALUES.into_iter().any(|s| display == s)
152}
153
154pub fn is_table_element(element: &Element) -> bool {
155 let node_name = get_node_name(element.into());
156 ["table", "td", "th"].into_iter().any(|s| node_name == s)
157}
158
159pub fn is_top_layer(element: &Element) -> bool {
160 [":popover-open", ":modal"]
161 .into_iter()
162 .any(|selector| element.matches(selector).unwrap_or(false))
163}
164
165const WILL_CHANGE_VALUES: [&str; 6] = [
166 "transform",
167 "translate",
168 "scale",
169 "rotate",
170 "perspective",
171 "filter",
172];
173const CONTAIN_VALUES: [&str; 4] = ["paint", "layout", "strict", "content"];
174
175pub enum ElementOrCss<'a> {
176 Element(&'a Element),
177 Css(CssStyleDeclaration),
178}
179
180impl<'a> From<&'a Element> for ElementOrCss<'a> {
181 fn from(value: &'a Element) -> Self {
182 ElementOrCss::Element(value)
183 }
184}
185
186impl<'a> From<&'a HtmlElement> for ElementOrCss<'a> {
187 fn from(value: &'a HtmlElement) -> Self {
188 ElementOrCss::Element(value)
189 }
190}
191
192impl From<CssStyleDeclaration> for ElementOrCss<'_> {
193 fn from(value: CssStyleDeclaration) -> Self {
194 ElementOrCss::Css(value)
195 }
196}
197
198pub fn is_containing_block(element: ElementOrCss) -> bool {
199 let webkit = is_web_kit();
200 let css = match element {
201 ElementOrCss::Element(element) => get_computed_style(element),
202 ElementOrCss::Css(css) => css,
203 };
204
205 ["transform", "translate", "scale", "rotate", "perspective"]
208 .into_iter()
209 .any(|property| {
210 css.get_property_value(property)
211 .map(|value| value != "none")
212 .unwrap_or(false)
213 })
214 || css
215 .get_property_value("container-type")
216 .map(|value| value != "normal")
217 .unwrap_or(false)
218 || (!webkit
219 && css
220 .get_property_value("backdrop-filter")
221 .map(|value| value != "none")
222 .unwrap_or(false))
223 || (!webkit
224 && css
225 .get_property_value("filter")
226 .map(|value| value != "none")
227 .unwrap_or(false))
228 || css
229 .get_property_value("will-change")
230 .map(|value| WILL_CHANGE_VALUES.into_iter().any(|v| v == value))
231 .unwrap_or(false)
232 || css
233 .get_property_value("contain")
234 .map(|value| CONTAIN_VALUES.into_iter().any(|v| v == value))
235 .unwrap_or(false)
236}
237
238pub fn get_containing_block(element: &Element) -> Option<HtmlElement> {
239 let mut current_node = get_parent_node(element);
240
241 while !is_last_traversable_node(¤t_node) {
242 match current_node.dyn_into::<HtmlElement>() {
243 Ok(element) => {
244 if is_containing_block((&element).into()) {
245 return Some(element);
246 } else if is_top_layer(&element) {
247 return None;
248 }
249
250 current_node = get_parent_node(&element);
251 }
252 _ => {
253 break;
254 }
255 }
256 }
257
258 None
259}
260
261pub fn is_web_kit() -> bool {
262 css::supports_with_value("-webkit-backdrop-filter", "none").unwrap_or(false)
263}
264
265pub fn is_last_traversable_node(node: &Node) -> bool {
266 let node_name = get_node_name(node.into());
267 ["html", "body", "#document"]
268 .into_iter()
269 .any(|s| node_name == s)
270}
271
272pub fn get_computed_style(element: &Element) -> CssStyleDeclaration {
273 get_window(Some(element))
274 .get_computed_style(element)
275 .expect("Valid element.")
276 .expect("Element should have computed style.")
277}
278
279#[derive(Clone, Debug)]
280pub struct NodeScroll {
281 pub scroll_left: f64,
282 pub scroll_top: f64,
283}
284
285impl NodeScroll {
286 pub fn new(value: f64) -> Self {
287 Self {
288 scroll_left: value,
289 scroll_top: value,
290 }
291 }
292}
293
294pub fn get_node_scroll(element_or_window: DomElementOrWindow) -> NodeScroll {
295 match element_or_window {
296 DomElementOrWindow::Element(element) => NodeScroll {
297 scroll_left: element.scroll_left() as f64,
298 scroll_top: element.scroll_top() as f64,
299 },
300 DomElementOrWindow::Window(window) => NodeScroll {
301 scroll_left: window.scroll_x().expect("Window should have scroll x."),
302 scroll_top: window.scroll_y().expect("Window should have scroll y."),
303 },
304 }
305}
306
307pub fn get_parent_node(node: &Node) -> Node {
308 if get_node_name(node.into()) == "html" {
309 return node.clone();
310 }
311
312 let element = node.dyn_ref::<Element>();
313
314 let result: Node;
315 match element.and_then(|element| element.assigned_slot()) {
316 Some(slot) => {
317 result = slot.into();
319 }
320 _ => {
321 match node.parent_node() {
322 Some(parent_node) => {
323 result = parent_node;
325 }
326 _ => {
327 if let Some(shadow_root) = node.dyn_ref::<ShadowRoot>() {
328 result = shadow_root.host().into();
330 } else {
331 result = get_document_element(Some(node.into())).into();
333 }
334 }
335 }
336 }
337 }
338
339 match node.dyn_ref::<ShadowRoot>() {
340 Some(shadow_root) => shadow_root.host().into(),
341 None => result,
342 }
343}
344
345pub fn get_nearest_overflow_ancestor(node: &Node) -> HtmlElement {
346 let parent_node = get_parent_node(node);
347
348 if is_last_traversable_node(&parent_node) {
349 node.owner_document()
350 .as_ref()
351 .or(node.dyn_ref::<Document>())
352 .expect("Node should be document or have owner document.")
353 .body()
354 .expect("Document should have body.")
355 } else if is_html_element(&parent_node)
356 && is_overflow_element(parent_node.unchecked_ref::<Element>())
357 {
358 parent_node.unchecked_into()
359 } else {
360 get_nearest_overflow_ancestor(&parent_node)
361 }
362}
363
364#[derive(Clone, Debug, PartialEq)]
365pub enum OverflowAncestor {
366 Element(Element),
367 Window(Window),
368 }
371
372pub fn get_overflow_ancestors(
373 node: &Node,
374 mut list: Vec<OverflowAncestor>,
375 traverse_iframe: bool,
376) -> Vec<OverflowAncestor> {
377 let scrollable_ancestor = get_nearest_overflow_ancestor(node);
378 let is_body = node
379 .owner_document()
380 .and_then(|document| document.body())
381 .is_some_and(|body| scrollable_ancestor == body);
382 let window = get_window(Some(&scrollable_ancestor));
383
384 if is_body {
385 let frame_element = get_frame_element(&window);
386
387 list.push(OverflowAncestor::Window(window));
388 if is_overflow_element(&scrollable_ancestor) {
391 list.push(OverflowAncestor::Element(scrollable_ancestor.into()));
392 }
393
394 if let Some(frame_element) = frame_element {
395 if traverse_iframe {
396 list.append(&mut get_overflow_ancestors(&frame_element, vec![], true))
397 }
398 }
399
400 list
401 } else {
402 let mut other_list = get_overflow_ancestors(&scrollable_ancestor, vec![], traverse_iframe);
403
404 list.push(OverflowAncestor::Element(scrollable_ancestor.into()));
405 list.append(&mut other_list);
406
407 list
408 }
409}
410
411pub fn get_frame_element(window: &Window) -> Option<Element> {
412 window
413 .parent()
414 .ok()
415 .flatten()
416 .and_then(|_| {
417 window
418 .frame_element()
419 .expect("Window should have frame element option.")
420 })
421 .and_then(|frame_element| {
422 Object::get_prototype_of(&frame_element)
423 .is_truthy()
424 .then_some(frame_element)
425 })
426}