mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
Implement element.innerText getter
This commit is contained in:
parent
0d7c2271c2
commit
2a4535f43e
16 changed files with 844 additions and 16 deletions
|
@ -10,11 +10,13 @@ use context::LayoutContext;
|
|||
use euclid::{Point2D, Vector2D, Rect, Size2D};
|
||||
use flow::{Flow, GetBaseFlow};
|
||||
use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo};
|
||||
use gfx::display_list::{DisplayList, OpaqueNode, ScrollOffsetMap};
|
||||
use gfx::display_list::{DisplayItem, DisplayList, OpaqueNode, ScrollOffsetMap};
|
||||
use inline::InlineFragmentNodeFlags;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use msg::constellation_msg::PipelineId;
|
||||
use opaque_node::OpaqueNodeMethods;
|
||||
use script_layout_interface::{LayoutElementType, LayoutNodeType};
|
||||
use script_layout_interface::StyleData;
|
||||
use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC};
|
||||
use script_layout_interface::rpc::{NodeGeometryResponse, NodeScrollIdResponse};
|
||||
use script_layout_interface::rpc::{OffsetParentResponse, ResolvedStyleResponse, StyleResponse};
|
||||
|
@ -24,10 +26,12 @@ use script_traits::LayoutMsg as ConstellationMsg;
|
|||
use script_traits::UntrustedNodeAddress;
|
||||
use sequential;
|
||||
use std::cmp::{min, max};
|
||||
use std::collections::HashMap;
|
||||
use std::ops::Deref;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use style::computed_values::display::T as Display;
|
||||
use style::computed_values::position::T as Position;
|
||||
use style::computed_values::visibility::T as Visibility;
|
||||
use style::context::{StyleContext, ThreadLocalStyleContext};
|
||||
use style::dom::TElement;
|
||||
use style::logical_geometry::{WritingMode, BlockFlowDirection, InlineBaseDirection};
|
||||
|
@ -79,6 +83,9 @@ pub struct LayoutThreadData {
|
|||
|
||||
/// A queued response for the list of nodes at a given point.
|
||||
pub nodes_from_point_response: Vec<UntrustedNodeAddress>,
|
||||
|
||||
/// A queued response for the inner text of a given element.
|
||||
pub element_inner_text_response: String,
|
||||
}
|
||||
|
||||
pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>);
|
||||
|
@ -161,6 +168,12 @@ impl LayoutRPC for LayoutRPCImpl {
|
|||
let rw_data = rw_data.lock().unwrap();
|
||||
rw_data.text_index_response.clone()
|
||||
}
|
||||
|
||||
fn element_inner_text(&self) -> String {
|
||||
let &LayoutRPCImpl(ref rw_data) = self;
|
||||
let rw_data = rw_data.lock().unwrap();
|
||||
rw_data.element_inner_text_response.clone()
|
||||
}
|
||||
}
|
||||
|
||||
struct UnioningFragmentBorderBoxIterator {
|
||||
|
@ -864,3 +877,167 @@ pub fn process_style_query<N: LayoutNode>(requested_node: N)
|
|||
|
||||
StyleResponse(data.map(|d| d.styles.primary().clone()))
|
||||
}
|
||||
|
||||
enum InnerTextItem {
|
||||
Text(String),
|
||||
RequiredLineBreakCount(u32),
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute
|
||||
pub fn process_element_inner_text_query<N: LayoutNode>(node: N,
|
||||
display_list: &Option<Arc<DisplayList>>) -> String {
|
||||
if !display_list.is_some() {
|
||||
warn!("We should have a display list at this point. Cannot get inner text");
|
||||
return String::new();
|
||||
}
|
||||
|
||||
// Step 1.
|
||||
let mut results = Vec::new();
|
||||
// Step 2.
|
||||
inner_text_collection_steps(node, display_list.as_ref().unwrap(), &mut results);
|
||||
let mut max_req_line_break_count = 0;
|
||||
let mut inner_text = Vec::new();
|
||||
for item in results {
|
||||
match item {
|
||||
InnerTextItem::Text(s) => {
|
||||
if max_req_line_break_count > 0 {
|
||||
// Step 5.
|
||||
for _ in 0..max_req_line_break_count {
|
||||
inner_text.push("\u{000A}".to_owned());
|
||||
}
|
||||
max_req_line_break_count = 0;
|
||||
}
|
||||
// Step 3.
|
||||
if !s.is_empty() {
|
||||
inner_text.push(s.to_owned());
|
||||
}
|
||||
},
|
||||
InnerTextItem::RequiredLineBreakCount(count) => {
|
||||
// Step 4.
|
||||
if inner_text.len() == 0 {
|
||||
// Remove required line break count at the start.
|
||||
continue;
|
||||
}
|
||||
// Store the count if it's the max of this run,
|
||||
// but it may be ignored if no text item is found afterwards,
|
||||
// which means that these are consecutive line breaks at the end.
|
||||
if count > max_req_line_break_count {
|
||||
max_req_line_break_count = count;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
inner_text.into_iter().collect()
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/#inner-text-collection-steps
|
||||
#[allow(unsafe_code)]
|
||||
fn inner_text_collection_steps<N: LayoutNode>(node: N,
|
||||
display_list: &Arc<DisplayList>,
|
||||
results: &mut Vec<InnerTextItem>) {
|
||||
// Extracts the text nodes from the display list to avoid traversing it
|
||||
// for each child node.
|
||||
let mut text = HashMap::new();
|
||||
for item in &display_list.as_ref().list {
|
||||
if let &DisplayItem::Text(ref text_content) = item {
|
||||
let entries = text.entry(&item.base().metadata.node).or_insert(Vec::new());
|
||||
entries.push(&text_content.text_run.text);
|
||||
}
|
||||
}
|
||||
|
||||
let mut items = Vec::new();
|
||||
for child in node.traverse_preorder() {
|
||||
let node = match child.type_id() {
|
||||
LayoutNodeType::Text => {
|
||||
child.parent_node().unwrap()
|
||||
},
|
||||
_ => child,
|
||||
};
|
||||
|
||||
let element_data = unsafe {
|
||||
node.get_style_and_layout_data().map(|d| {
|
||||
&(*(d.ptr.as_ptr() as *mut StyleData)).element_data
|
||||
})
|
||||
};
|
||||
|
||||
if element_data.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let style = match element_data.unwrap().borrow().styles.get_primary() {
|
||||
None => continue,
|
||||
Some(style) => style.clone(),
|
||||
};
|
||||
|
||||
// Step 2.
|
||||
if style.get_inheritedbox().visibility != Visibility::Visible {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Step 3.
|
||||
let display = style.get_box().display;
|
||||
if !child.is_in_document() || display == Display::None {
|
||||
continue;
|
||||
}
|
||||
|
||||
match child.type_id() {
|
||||
LayoutNodeType::Text => {
|
||||
// Step 4.
|
||||
if let Some(text_content) = text.get(&child.opaque()) {
|
||||
for content in text_content {
|
||||
items.push(InnerTextItem::Text(content.to_string()));
|
||||
}
|
||||
}
|
||||
},
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLBRElement) => {
|
||||
// Step 5.
|
||||
items.push(InnerTextItem::Text(String::from("\u{000A}" /* line feed */)));
|
||||
},
|
||||
LayoutNodeType::Element(LayoutElementType::HTMLParagraphElement) => {
|
||||
// Step 8.
|
||||
items.insert(0, InnerTextItem::RequiredLineBreakCount(2));
|
||||
items.push(InnerTextItem::RequiredLineBreakCount(2));
|
||||
}
|
||||
_ => {},
|
||||
|
||||
}
|
||||
|
||||
match display {
|
||||
Display::TableCell if !is_last_table_cell() => {
|
||||
// Step 6.
|
||||
items.push(InnerTextItem::Text(String::from("\u{0009}" /* tab */)));
|
||||
},
|
||||
Display::TableRow if !is_last_table_row() => {
|
||||
// Step 7.
|
||||
items.push(InnerTextItem::Text(String::from("\u{000A}" /* line feed */)));
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
|
||||
// Step 9.
|
||||
if is_block_level_or_table_caption(&display) {
|
||||
items.insert(0, InnerTextItem::RequiredLineBreakCount(1));
|
||||
items.push(InnerTextItem::RequiredLineBreakCount(1));
|
||||
}
|
||||
}
|
||||
|
||||
results.append(&mut items);
|
||||
}
|
||||
|
||||
fn is_last_table_cell() -> bool {
|
||||
// FIXME(ferjm) Implement this.
|
||||
false
|
||||
}
|
||||
|
||||
fn is_last_table_row() -> bool {
|
||||
// FIXME(ferjm) Implement this.
|
||||
false
|
||||
}
|
||||
|
||||
fn is_block_level_or_table_caption(display: &Display) -> bool {
|
||||
match *display {
|
||||
Display::Block | Display::Flex |
|
||||
Display::TableCaption | Display::Table => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue