From df46e51822f9cd7d296b699f8e2c90f5fb0b2698 Mon Sep 17 00:00:00 2001 From: lfpraca Date: Sat, 31 May 2025 21:49:26 -0300 Subject: [PATCH 1/3] selection: do not stringify non-visible text Signed-off-by: lfpraca --- components/script/dom/range.rs | 2 +- components/script/dom/selection.rs | 80 ++++++++++++++++++++++++++++-- 2 files changed, 76 insertions(+), 6 deletions(-) diff --git a/components/script/dom/range.rs b/components/script/dom/range.rs index 4bf3b7fb72a..50a1d87c6de 100644 --- a/components/script/dom/range.rs +++ b/components/script/dom/range.rs @@ -137,7 +137,7 @@ impl Range { } /// - fn contains(&self, node: &Node) -> bool { + pub(crate) fn contains(&self, node: &Node) -> bool { matches!( ( bp_position(node, 0, &self.start_container(), self.start_offset()), diff --git a/components/script/dom/selection.rs b/components/script/dom/selection.rs index 758a8608d2b..5c11c3029fc 100644 --- a/components/script/dom/selection.rs +++ b/components/script/dom/selection.rs @@ -5,7 +5,9 @@ use std::cell::Cell; use dom_struct::dom_struct; +use style::properties::generated::longhands; +use crate::dom::bindings::codegen::Bindings::CharacterDataBinding::CharacterDataMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, NodeMethods}; use crate::dom::bindings::codegen::Bindings::RangeBinding::RangeMethods; use crate::dom::bindings::codegen::Bindings::SelectionBinding::SelectionMethods; @@ -15,10 +17,14 @@ use crate::dom::bindings::refcounted::Trusted; use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::bindings::str::DOMString; +use crate::dom::characterdata::CharacterData; use crate::dom::document::Document; +use crate::dom::element::Element; use crate::dom::eventtarget::EventTarget; +use crate::dom::htmlscriptelement::HTMLScriptElement; use crate::dom::node::{Node, NodeTraits}; use crate::dom::range::Range; +use crate::dom::text::Text; use crate::script_runtime::CanGc; #[derive(Clone, Copy, JSTraceable, MallocSizeOf)] @@ -37,6 +43,19 @@ pub(crate) struct Selection { task_queued: Cell, } +// this should be changed once content-visibility gets implemented to check for content-visibility == visible too +fn text_node_is_selectable(text_node: &Text) -> bool { + text_node + .upcast::() + .GetParentElement() + .and_then(|p| p.style(CanGc::note())) + .is_some_and(|s| { + !s.get_box().display.is_none() + && s.get_inherited_box().visibility + == longhands::visibility::computed_value::T::Visible + }) +} + impl Selection { fn new_inherited(document: &Document) -> Selection { Selection { @@ -515,12 +534,63 @@ impl SelectionMethods for Selection { // https://w3c.github.io/selection-api/#dom-selection-stringifier fn Stringifier(&self) -> DOMString { - // The spec as of Jan 31 2020 just says - // "See W3C bug 10583." for this method. - // Stringifying the range seems at least approximately right - // and passes the non-style-dependent case in the WPT tests. if let Some(range) = self.range.get() { - range.Stringifier() + let start_node = range.start_container(); + let end_node = range.end_container(); + + // Step 1. + let mut s = DOMString::new(); + + if let Some(text_node) = start_node.downcast::() { + if text_node_is_selectable(text_node) { + let char_data = text_node.upcast::(); + + // Step 2. + if start_node == end_node { + let r = char_data + .SubstringData( + range.start_offset(), + range.end_offset() - range.start_offset(), + ) + .unwrap(); + + return r; + } + + // Step 3. + s.push_str( + &char_data + .SubstringData( + range.start_offset(), + char_data.Length() - range.start_offset(), + ) + .unwrap(), + ); + } else if start_node == end_node { + return s; + } + } + + // Step 4. + let ancestor = range.CommonAncestorContainer(); + for child in start_node + .following_nodes(&ancestor) + .filter_map(DomRoot::downcast::) + .filter(|t| text_node_is_selectable(t) && range.contains(t.upcast())) + { + s.push_str(&child.upcast::().Data()); + } + + // Step 5. + if let Some(text_node) = end_node.downcast::() { + if text_node_is_selectable(text_node) { + let char_data = text_node.upcast::(); + s.push_str(&char_data.SubstringData(0, range.end_offset()).unwrap()); + } + } + + // Step 6. + s } else { DOMString::from("") } From 415f75d5fc36976052bf5b683298feed32f4a0ac Mon Sep 17 00:00:00 2001 From: lfpraca Date: Sun, 1 Jun 2025 11:01:10 -0300 Subject: [PATCH 2/3] Removes unused imports Signed-off-by: lfpraca --- components/script/dom/selection.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/components/script/dom/selection.rs b/components/script/dom/selection.rs index 5c11c3029fc..f4ee0350155 100644 --- a/components/script/dom/selection.rs +++ b/components/script/dom/selection.rs @@ -19,9 +19,7 @@ use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::bindings::str::DOMString; use crate::dom::characterdata::CharacterData; use crate::dom::document::Document; -use crate::dom::element::Element; use crate::dom::eventtarget::EventTarget; -use crate::dom::htmlscriptelement::HTMLScriptElement; use crate::dom::node::{Node, NodeTraits}; use crate::dom::range::Range; use crate::dom::text::Text; From 1ae42386f307d0a1bbd4fba74e039f46e1625f04 Mon Sep 17 00:00:00 2001 From: lfpraca Date: Sun, 1 Jun 2025 14:41:57 -0300 Subject: [PATCH 3/3] Adjusts code style Signed-off-by: lfpraca --- components/script/dom/selection.rs | 111 ++++++++++++++--------------- 1 file changed, 53 insertions(+), 58 deletions(-) diff --git a/components/script/dom/selection.rs b/components/script/dom/selection.rs index f4ee0350155..9d59b605a5d 100644 --- a/components/script/dom/selection.rs +++ b/components/script/dom/selection.rs @@ -42,15 +42,15 @@ pub(crate) struct Selection { } // this should be changed once content-visibility gets implemented to check for content-visibility == visible too -fn text_node_is_selectable(text_node: &Text) -> bool { +fn text_node_should_be_stringified(text_node: &Text, can_gc: CanGc) -> bool { text_node .upcast::() .GetParentElement() - .and_then(|p| p.style(CanGc::note())) - .is_some_and(|s| { - !s.get_box().display.is_none() - && s.get_inherited_box().visibility - == longhands::visibility::computed_value::T::Visible + .and_then(|parent| parent.style(can_gc)) + .is_some_and(|style| { + !style.get_box().display.is_none() && + style.get_inherited_box().visibility == + longhands::visibility::computed_value::T::Visible }) } @@ -532,65 +532,60 @@ impl SelectionMethods for Selection { // https://w3c.github.io/selection-api/#dom-selection-stringifier fn Stringifier(&self) -> DOMString { - if let Some(range) = self.range.get() { - let start_node = range.start_container(); - let end_node = range.end_container(); + let Some(range) = self.range.get() else { + return DOMString::from(""); + }; - // Step 1. - let mut s = DOMString::new(); + let start_node = range.start_container(); + let end_node = range.end_container(); - if let Some(text_node) = start_node.downcast::() { - if text_node_is_selectable(text_node) { - let char_data = text_node.upcast::(); + let mut selection_text = DOMString::new(); - // Step 2. - if start_node == end_node { - let r = char_data - .SubstringData( - range.start_offset(), - range.end_offset() - range.start_offset(), - ) - .unwrap(); + if let Some(text_node) = start_node.downcast::() { + if text_node_should_be_stringified(text_node, CanGc::note()) { + let char_data = text_node.upcast::(); - return r; - } - - // Step 3. - s.push_str( - &char_data - .SubstringData( - range.start_offset(), - char_data.Length() - range.start_offset(), - ) - .unwrap(), - ); - } else if start_node == end_node { - return s; + if start_node == end_node { + return char_data + .SubstringData( + range.start_offset(), + range.end_offset() - range.start_offset(), + ) + .unwrap(); } - } - // Step 4. - let ancestor = range.CommonAncestorContainer(); - for child in start_node - .following_nodes(&ancestor) - .filter_map(DomRoot::downcast::) - .filter(|t| text_node_is_selectable(t) && range.contains(t.upcast())) - { - s.push_str(&child.upcast::().Data()); + selection_text.push_str( + &char_data + .SubstringData( + range.start_offset(), + char_data.Length() - range.start_offset(), + ) + .unwrap(), + ); + } else if start_node == end_node { + return selection_text; } - - // Step 5. - if let Some(text_node) = end_node.downcast::() { - if text_node_is_selectable(text_node) { - let char_data = text_node.upcast::(); - s.push_str(&char_data.SubstringData(0, range.end_offset()).unwrap()); - } - } - - // Step 6. - s - } else { - DOMString::from("") } + + let ancestor = range.CommonAncestorContainer(); + for child in start_node + .following_nodes(&ancestor) + .filter_map(DomRoot::downcast::) + .filter(|text_node| { + text_node_should_be_stringified(text_node, CanGc::note()) && + range.contains(text_node.upcast()) + }) + { + selection_text.push_str(&child.upcast::().Data()); + } + + if let Some(text_node) = end_node.downcast::() { + if text_node_should_be_stringified(text_node, CanGc::note()) { + let char_data = text_node.upcast::(); + selection_text.push_str(&char_data.SubstringData(0, range.end_offset()).unwrap()); + } + } + + selection_text } }