Implement element.innerText getter

This commit is contained in:
Fernando Jiménez Moreno 2018-01-09 13:27:32 +01:00
parent 0d7c2271c2
commit 2a4535f43e
16 changed files with 844 additions and 16 deletions

View file

@ -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,
}
}

View file

@ -76,9 +76,9 @@ use layout::incremental::{LayoutDamageComputation, RelayoutMode, SpecialRestyleD
use layout::layout_debug;
use layout::parallel;
use layout::query::{LayoutRPCImpl, LayoutThreadData, process_content_box_request, process_content_boxes_request};
use layout::query::{process_node_geometry_request, process_node_scroll_area_request};
use layout::query::{process_node_scroll_id_request, process_offset_parent_query, process_resolved_style_request};
use layout::query::process_style_query;
use layout::query::{process_element_inner_text_query, process_node_geometry_request};
use layout::query::{process_node_scroll_area_request, process_node_scroll_id_request};
use layout::query::{process_offset_parent_query, process_resolved_style_request, process_style_query};
use layout::sequential;
use layout::traversal::{ComputeStackingRelativePositions, PreorderFlowTraversal, RecalcStyleAndConstructFlows};
use layout::wrapper::LayoutNodeLayoutData;
@ -526,6 +526,7 @@ impl LayoutThread {
scroll_offsets: HashMap::new(),
text_index_response: TextIndexResponse(None),
nodes_from_point_response: vec![],
element_inner_text_response: String::new(),
})),
webrender_image_cache:
Arc::new(RwLock::new(FnvHashMap::default())),
@ -1107,6 +1108,9 @@ impl LayoutThread {
ReflowGoal::TextIndexQuery(..) => {
rw_data.text_index_response = TextIndexResponse(None);
}
ReflowGoal::ElementInnerTextQuery(_) => {
rw_data.element_inner_text_response = String::new();
},
ReflowGoal::Full | ReflowGoal:: TickAnimations => {}
}
return;
@ -1419,7 +1423,11 @@ impl LayoutThread {
.map(|item| UntrustedNodeAddress(item.tag.0 as *const c_void))
.collect()
},
ReflowGoal::ElementInnerTextQuery(node) => {
let node = unsafe { ServoLayoutNode::new(&node) };
rw_data.element_inner_text_response =
process_element_inner_text_query(node, &rw_data.display_list);
},
ReflowGoal::Full | ReflowGoal::TickAnimations => {}
}
}

View file

@ -357,7 +357,7 @@ impl Element {
}
// https://drafts.csswg.org/cssom-view/#css-layout-box
fn has_css_layout_box(&self) -> bool {
pub fn has_css_layout_box(&self) -> bool {
self.style()
.map_or(false, |s| !s.get_box().clone_display().is_none())
}

View file

@ -8,6 +8,7 @@ use dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use dom::bindings::codegen::Bindings::HTMLElementBinding;
use dom::bindings::codegen::Bindings::HTMLElementBinding::HTMLElementMethods;
use dom::bindings::codegen::Bindings::NodeBinding::NodeBinding::NodeMethods;
use dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use dom::bindings::error::{Error, ErrorResult};
use dom::bindings::inheritance::{ElementTypeId, HTMLElementTypeId, NodeTypeId};
@ -28,8 +29,10 @@ use dom::node::{Node, NodeFlags};
use dom::node::{document_from_node, window_from_node};
use dom::nodelist::NodeList;
use dom::virtualmethods::VirtualMethods;
use dom::window::ReflowReason;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix};
use script_layout_interface::message::ReflowGoal;
use std::collections::HashSet;
use std::default::Default;
use std::rc::Rc;
@ -400,6 +403,27 @@ impl HTMLElementMethods for HTMLElement {
rect.size.height.to_nearest_px()
}
// https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute
fn InnerText(&self) -> DOMString {
let node = self.upcast::<Node>();
let window = window_from_node(node);
let element = self.upcast::<Element>();
// Step 1.
let element_not_rendered = !node.is_in_doc() || !element.has_css_layout_box();
if element_not_rendered {
return node.GetTextContent().unwrap();
}
window.reflow(ReflowGoal::ElementInnerTextQuery(node.to_trusted_node_address()), ReflowReason::Query);
DOMString::from(window.layout().element_inner_text())
}
// https://html.spec.whatwg.org/multipage/#the-innertext-idl-attribute
fn SetInnerText(&self, _: DOMString) {
// XXX (ferjm) implement this.
}
}
// https://html.spec.whatwg.org/multipage/#attr-data-*

View file

@ -2741,6 +2741,8 @@ impl Into<LayoutElementType> for ElementTypeId {
#[inline(always)]
fn into(self) -> LayoutElementType {
match self {
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBRElement) =>
LayoutElementType::HTMLBRElement,
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLCanvasElement) =>
LayoutElementType::HTMLCanvasElement,
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLIFrameElement) =>
@ -2751,6 +2753,8 @@ impl Into<LayoutElementType> for ElementTypeId {
LayoutElementType::HTMLInputElement,
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLObjectElement) =>
LayoutElementType::HTMLObjectElement,
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLParagraphElement) =>
LayoutElementType::HTMLParagraphElement,
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableCellElement(_)) =>
LayoutElementType::HTMLTableCellElement,
ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLTableColElement) =>

View file

@ -46,6 +46,8 @@ interface HTMLElement : Element {
// attribute boolean spellcheck;
// void forceSpellCheck();
[TreatNullAs=EmptyString] attribute DOMString innerText;
// command API
// readonly attribute DOMString? commandType;
// readonly attribute DOMString? commandLabel;

View file

@ -1898,6 +1898,7 @@ fn debug_reflow_events(id: PipelineId, reflow_goal: &ReflowGoal, reason: &Reflow
ReflowGoal::StyleQuery(_n) => "\tStyleQuery",
ReflowGoal::TextIndexQuery(..) => "\tTextIndexQuery",
ReflowGoal::TickAnimations => "\tTickAnimations",
ReflowGoal::ElementInnerTextQuery(_) => "\tElementInnerTextQuery",
});
debug_msg.push_str(match *reason {

View file

@ -108,11 +108,13 @@ pub enum LayoutNodeType {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum LayoutElementType {
Element,
HTMLBRElement,
HTMLCanvasElement,
HTMLIFrameElement,
HTMLImageElement,
HTMLInputElement,
HTMLObjectElement,
HTMLParagraphElement,
HTMLTableCellElement,
HTMLTableColElement,
HTMLTableElement,

View file

@ -121,6 +121,7 @@ pub enum ReflowGoal {
StyleQuery(TrustedNodeAddress),
TextIndexQuery(TrustedNodeAddress, Point2D<f32>),
NodesFromPointQuery(Point2D<f32>, NodesFromPointQueryType),
ElementInnerTextQuery(TrustedNodeAddress),
}
impl ReflowGoal {
@ -129,12 +130,13 @@ impl ReflowGoal {
pub fn needs_display_list(&self) -> bool {
match *self {
ReflowGoal::NodesFromPointQuery(..) | ReflowGoal::TextIndexQuery(..) |
ReflowGoal::TickAnimations | ReflowGoal::Full => true,
ReflowGoal::TickAnimations | ReflowGoal::ElementInnerTextQuery(_) |
ReflowGoal::Full => true,
ReflowGoal::ContentBoxQuery(_) | ReflowGoal::ContentBoxesQuery(_) |
ReflowGoal::NodeGeometryQuery(_) | ReflowGoal::NodeScrollGeometryQuery(_) |
ReflowGoal::NodeScrollIdQuery(_) |
ReflowGoal::ResolvedStyleQuery(..) | ReflowGoal::OffsetParentQuery(_) |
ReflowGoal::StyleQuery(_) => false,
ReflowGoal::StyleQuery(_) => false,
}
}
@ -148,6 +150,7 @@ impl ReflowGoal {
ReflowGoal::NodeScrollIdQuery(_) | ReflowGoal::ResolvedStyleQuery(..) |
ReflowGoal::OffsetParentQuery(_) => false,
ReflowGoal::NodesFromPointQuery(..) | ReflowGoal::Full |
ReflowGoal::ElementInnerTextQuery(_) |
ReflowGoal::TickAnimations => true,
}
}

View file

@ -38,7 +38,8 @@ pub trait LayoutRPC {
fn text_index(&self) -> TextIndexResponse;
/// Requests the list of nodes from the given point.
fn nodes_from_point_response(&self) -> Vec<UntrustedNodeAddress>;
/// Query layout to get the inner text for a given element.
fn element_inner_text(&self) -> String;
}
pub struct ContentBoxResponse(pub Option<Rect<Au>>);