auto merge of #1298 : recrack/servo/overflow_property, r=metajack

Impl #1290

@jaeminMoon @ksh8281 
Thanks @metajack
This commit is contained in:
bors-servo 2013-11-28 21:46:51 -08:00
commit b867e54dd4
8 changed files with 232 additions and 101 deletions

View file

@ -64,6 +64,7 @@ pub enum DisplayItem<E> {
TextDisplayItemClass(~TextDisplayItem<E>),
ImageDisplayItemClass(~ImageDisplayItem<E>),
BorderDisplayItemClass(~BorderDisplayItem<E>),
ClipDisplayItemClass(~ClipDisplayItem<E>)
}
/// Information common to all display items.
@ -111,6 +112,12 @@ pub struct BorderDisplayItem<E> {
style: SideOffsets2D<border_style::T>
}
pub struct ClipDisplayItem<E> {
base: BaseDisplayItem<E>,
child_list: ~[DisplayItem<E>],
need_clip: bool
}
impl<E> DisplayItem<E> {
/// Renders this display item into the given render context.
fn draw_into_context(&self, render_context: &RenderContext) {
@ -119,6 +126,18 @@ impl<E> DisplayItem<E> {
render_context.draw_solid_color(&solid_color.base.bounds, solid_color.color)
}
ClipDisplayItemClass(ref clip) => {
if clip.need_clip {
render_context.draw_push_clip(&clip.base.bounds);
}
for item in clip.child_list.iter() {
(*item).draw_into_context(render_context);
}
if clip.need_clip {
render_context.draw_pop_clip();
}
}
TextDisplayItemClass(ref text) => {
debug!("Drawing text at {:?}.", text.base.bounds);
@ -182,7 +201,8 @@ impl<E> DisplayItem<E> {
SolidColorDisplayItemClass(ref solid_color) => transmute_region(&solid_color.base),
TextDisplayItemClass(ref text) => transmute_region(&text.base),
ImageDisplayItemClass(ref image_item) => transmute_region(&image_item.base),
BorderDisplayItemClass(ref border) => transmute_region(&border.base)
BorderDisplayItemClass(ref border) => transmute_region(&border.base),
ClipDisplayItemClass(ref clip) => transmute_region(&clip.base),
}
}
}

22
src/components/gfx/render_context.rs Normal file → Executable file
View file

@ -101,6 +101,28 @@ impl<'self> RenderContext<'self> {
&draw_opts);
}
pub fn draw_push_clip(&self, bounds: &Rect<Au>) {
let rect = bounds.to_azure_rect();
let path_builder = self.draw_target.create_path_builder();
let left_top = Point2D(rect.origin.x, rect.origin.y);
let right_top = Point2D(rect.origin.x + rect.size.width, rect.origin.y);
let left_bottom = Point2D(rect.origin.x, rect.origin.y + rect.size.height);
let right_bottom = Point2D(rect.origin.x + rect.size.width, rect.origin.y + rect.size.height);
path_builder.move_to(left_top);
path_builder.line_to(right_top);
path_builder.line_to(right_bottom);
path_builder.line_to(left_bottom);
let path = path_builder.finish();
self.draw_target.push_clip(&path);
}
pub fn draw_pop_clip(&self) {
self.draw_target.pop_clip();
}
pub fn draw_image(&self, bounds: Rect<Au>, image: Arc<~Image>) {
let image = image.get();
let size = Size2D(image.width as i32, image.height as i32);

View file

@ -9,7 +9,7 @@ use geom::{Point2D, Rect, Size2D, SideOffsets2D};
use gfx::display_list::{BaseDisplayItem, BorderDisplayItem, BorderDisplayItemClass};
use gfx::display_list::{DisplayList, ImageDisplayItem, ImageDisplayItemClass};
use gfx::display_list::{SolidColorDisplayItem, SolidColorDisplayItemClass, TextDisplayItem};
use gfx::display_list::{TextDisplayItemClass};
use gfx::display_list::{TextDisplayItemClass, ClipDisplayItem, ClipDisplayItemClass};
use gfx::font::{FontStyle, FontWeight300};
use gfx::text::text_run::TextRun;
use gfx::color::rgb;
@ -28,7 +28,7 @@ use std::unstable::raw::Box;
use style::ComputedValues;
use style::computed_values::{
border_style, clear, float, font_family, font_style, line_height,
position, text_align, text_decoration, vertical_align, LengthOrPercentage};
position, text_align, text_decoration, vertical_align, LengthOrPercentage, overflow};
use css::node_style::StyledNode;
use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData, ToGfxColor};
@ -78,6 +78,13 @@ pub trait RenderBox {
fail!("as_unscanned_text_render_box() called on a non-unscanned-text-render-box")
}
/// If this is an unscanned text render box, returns the underlying object. Fails otherwise.
///
/// FIXME(pcwalton): Ugly. Replace with a real downcast operation.
fn as_generic_render_box(@self) -> @GenericRenderBox {
fail!("as_generic_render_box() called on a generic-render-box")
}
/// Cleans up all memory associated with this render box.
fn teardown(&self) {}
@ -188,6 +195,13 @@ impl GenericRenderBox {
base: base,
}
}
fn need_clip(&self) -> bool {
if self.base.node.style().Box.overflow == overflow::hidden {
return true;
}
false
}
}
impl RenderBox for GenericRenderBox {
@ -195,6 +209,10 @@ impl RenderBox for GenericRenderBox {
GenericRenderBoxClass
}
fn as_generic_render_box(@self) -> @GenericRenderBox {
self
}
fn minimum_and_preferred_widths(&self) -> (Au, Au) {
let guessed_width = self.base.guess_width();
(guessed_width, guessed_width)
@ -1029,6 +1047,19 @@ impl RenderBoxUtils for @RenderBox {
// Add the background to the list, if applicable.
self.paint_background_if_applicable(list, &absolute_box_bounds);
do list.with_mut_ref |list| {
let item = ~ClipDisplayItem {
base: BaseDisplayItem {
bounds: absolute_box_bounds,
extra: ExtraDisplayListData::new(self),
},
child_list: ~[],
need_clip: false
};
list.append_item(ClipDisplayItemClass(item));
}
let nearest_ancestor_element = base.nearest_ancestor_element();
let color = nearest_ancestor_element.style().Color.color.to_gfx_color();
@ -1099,6 +1130,19 @@ impl RenderBoxUtils for @RenderBox {
// Add the background to the list, if applicable.
self.paint_background_if_applicable(list, &absolute_box_bounds);
let generic_box = self.as_generic_render_box();
do list.with_mut_ref |list| {
let item = ~ClipDisplayItem {
base: BaseDisplayItem {
bounds: absolute_box_bounds,
extra: ExtraDisplayListData::new(self),
},
child_list: ~[],
need_clip: generic_box.need_clip()
};
list.append_item(ClipDisplayItemClass(item));
}
// FIXME(pcwalton): This is a bit of an abuse of the logging infrastructure. We
// should have a real `SERVO_DEBUG` system.
debug!("{:?}", {
@ -1122,11 +1166,23 @@ impl RenderBoxUtils for @RenderBox {
});
},
ImageRenderBoxClass => {
let image_box = self.as_image_render_box();
// Add the background to the list, if applicable.
self.paint_background_if_applicable(list, &absolute_box_bounds);
do list.with_mut_ref |list| {
let item = ~ClipDisplayItem {
base: BaseDisplayItem {
bounds: absolute_box_bounds,
extra: ExtraDisplayListData::new(self),
},
child_list: ~[],
need_clip: false
};
list.append_item(ClipDisplayItemClass(item));
}
let image_box = self.as_image_render_box();
match image_box.image.mutate().ptr.get_image() {
Some(image) => {
debug!("(building display list) building image box");

View file

@ -34,6 +34,7 @@ use layout::display_list_builder::{DisplayListBuilder, ExtraDisplayListData};
use layout::float_context::{FloatContext, Invalid};
use layout::incremental::RestyleDamage;
use layout::inline::InlineFlow;
use gfx::display_list::{ClipDisplayItemClass};
use extra::dlist::{DList, DListIterator, MutDListIterator};
use extra::container::Deque;
@ -526,7 +527,34 @@ impl<'self> MutableFlowUtils for &'self mut FlowContext {
InlineFlowClass => self.as_inline().build_display_list_inline(builder, dirty, list),
FloatFlowClass => self.as_float().build_display_list_float(builder, dirty, list),
_ => fail!("Tried to build_display_list_recurse of flow: {:?}", self),
};
if list.with_mut_ref(|list| list.list.len() == 0) {
return true;
}
let child_list = ~Cell::new(DisplayList::new());
for kid in child_iter(self) {
kid.build_display_list(builder,dirty,child_list);
}
do list.with_mut_ref |list| {
let result = list.list.mut_rev_iter().position(|item| {
match *item {
ClipDisplayItemClass(ref mut item) => {
item.child_list.push_all_move(child_list.take().list);
true
},
_ => false,
}
});
if result.is_none() {
fail!("fail to find parent item");
}
}
true
}
}

View file

@ -22,7 +22,7 @@ use extra::arc::{Arc, RWArc};
use geom::point::Point2D;
use geom::rect::Rect;
use geom::size::Size2D;
use gfx::display_list::DisplayList;
use gfx::display_list::{DisplayList,DisplayItem,ClipDisplayItemClass};
use gfx::font_context::FontContext;
use gfx::opts::Opts;
use gfx::render_task::{RenderMsg, RenderChan, RenderLayer};
@ -41,7 +41,6 @@ use servo_msg::constellation_msg::{ConstellationChan, PipelineId};
use servo_net::image_cache_task::{ImageCacheTask, ImageResponseMsg};
use servo_net::local_image_cache::{ImageResponder, LocalImageCache};
use servo_util::geometry::Au;
use servo_util::range::Range;
use servo_util::time::{ProfilerChan, profile};
use servo_util::time;
use servo_util::tree::TreeNodeRef;
@ -181,26 +180,6 @@ impl<'self> PostorderFlowTraversal for AssignHeightsAndStoreOverflowTraversal<'s
}
}
/// The display list building traversal. In WebKit this corresponds to `paint`. In Gecko this
/// corresponds to `BuildDisplayListForChild`.
struct DisplayListBuildingTraversal<'self> {
builder: DisplayListBuilder<'self>,
root_pos: Rect<Au>,
display_list: ~Cell<DisplayList<AbstractNode<()>>>,
}
impl<'self> PreorderFlowTraversal for DisplayListBuildingTraversal<'self> {
#[inline]
fn process(&mut self, _: &mut FlowContext) -> bool {
true
}
#[inline]
fn should_prune(&mut self, flow: &mut FlowContext) -> bool {
flow.build_display_list(&self.builder, &self.root_pos, self.display_list)
}
}
struct LayoutImageResponder {
id: PipelineId,
script_chan: ScriptChan,
@ -472,49 +451,20 @@ impl LayoutTask {
// Build the display list if necessary, and send it to the renderer.
if data.goal == ReflowForDisplay {
do profile(time::LayoutDispListBuildCategory, self.profiler_chan.clone()) {
// TODO: Set options on the builder before building.
// TODO: Be smarter about what needs painting.
let mut traversal = DisplayListBuildingTraversal {
builder: DisplayListBuilder {
let root_size = flow::base(layout_root).position.size;
let display_list= ~Cell::new(DisplayList::<AbstractNode<()>>::new());
let dirty = flow::base(layout_root).position.clone();
layout_root.build_display_list(
&DisplayListBuilder {
ctx: &layout_ctx,
},
root_pos: flow::base(layout_root).position.clone(),
display_list: ~Cell::new(DisplayList::<AbstractNode<()>>::new()),
};
&dirty,
display_list);
let _ = layout_root.traverse_preorder(&mut traversal);
let root_size = flow::base(layout_root).position.size;
let display_list = Arc::new(traversal.display_list.take());
let display_list = Arc::new(display_list.take());
for i in range(0,display_list.get().list.len()) {
let node: AbstractNode<LayoutView> = unsafe {
transmute(display_list.get().list[i].base().extra)
};
// FIXME(pcwalton): Why are we cloning the display list here?!
match *node.mutate_layout_data().ptr {
Some(ref mut layout_data) => {
let boxes = &mut layout_data.boxes;
boxes.display_list = Some(display_list.clone());
if boxes.range.is_none() {
debug!("Creating initial range for node");
boxes.range = Some(Range::new(i,1));
} else {
debug!("Appending item to range");
unsafe {
let old_node: AbstractNode<()> = transmute(node);
assert!(old_node == display_list.get().list[i-1].base().extra,
"Non-contiguous arrangement of display items");
}
boxes.range.unwrap().extend_by(1);
}
}
None => fail!("no layout data"),
}
self.display_item_bound_to_node(&display_list.get().list[i]);
}
let mut color = color::rgba(255.0, 255.0, 255.0, 255.0);
@ -556,6 +506,42 @@ impl LayoutTask {
data.script_chan.send(ReflowCompleteMsg(self.id, data.id));
}
fn display_item_bound_to_node(&mut self,item: &DisplayItem<AbstractNode<()>>) {
let node: AbstractNode<LayoutView> = unsafe {
transmute(item.base().extra)
};
match *node.mutate_layout_data().ptr {
Some(ref mut layout_data) => {
let boxes = &mut layout_data.boxes;
if boxes.display_bound_list.is_none() {
boxes.display_bound_list = Some(~[]);
}
match boxes.display_bound_list {
Some(ref mut list) => list.push(item.base().bounds),
None => {}
}
if boxes.display_bound.is_none() {
boxes.display_bound = Some(item.base().bounds);
} else {
boxes.display_bound = Some(boxes.display_bound.unwrap().union(&item.base().bounds));
}
}
None => fail!("no layout data"),
}
match *item {
ClipDisplayItemClass(ref cc) => {
for item in cc.child_list.iter() {
self.display_item_bound_to_node(item);
}
}
_ => {}
}
}
/// Handles a query from the script task. This is the main routine that DOM functions like
/// `getClientRects()` or `getBoundingClientRect()` ultimately invoke.
fn handle_query(&self, query: LayoutQuery) {
@ -570,19 +556,8 @@ impl LayoutTask {
// FIXME(pcwalton): Why are we cloning the display list here?!
let layout_data = node.borrow_layout_data();
let boxes = &layout_data.ptr.as_ref().unwrap().boxes;
match (boxes.display_list.clone(), boxes.range) {
(Some(display_list), Some(range)) => {
let mut rect: Option<Rect<Au>> = None;
for i in range.eachi() {
rect = match rect {
Some(acc) => {
Some(acc.union(&display_list.get().list[i].bounds()))
}
None => Some(display_list.get().list[i].bounds())
}
}
rect
}
match boxes.display_bound {
Some(_) => boxes.display_bound,
_ => {
let mut acc: Option<Rect<Au>> = None;
for child in node.children() {
@ -614,10 +589,10 @@ impl LayoutTask {
-> ~[Rect<Au>] {
let layout_data = node.borrow_layout_data();
let boxes = &layout_data.ptr.as_ref().unwrap().boxes;
match (boxes.display_list.clone(), boxes.range) {
(Some(display_list), Some(range)) => {
for i in range.eachi() {
box_accumulator.push(display_list.get().list[i].bounds());
match boxes.display_bound_list {
Some(ref display_bound_list) => {
for item in display_bound_list.iter() {
box_accumulator.push(*item);
}
}
_ => {
@ -634,30 +609,55 @@ impl LayoutTask {
reply_chan.send(ContentBoxesResponse(boxes))
}
HitTestQuery(_, point, reply_chan) => {
fn hit_test(x:Au, y:Au, list: &[DisplayItem<AbstractNode<()>>]) -> Option<HitTestResponse> {
for item in list.rev_iter() {
match *item {
ClipDisplayItemClass(ref cc) => {
let ret = hit_test(x, y, cc.child_list);
if !ret.is_none() {
return ret;
}
}
_ => {}
}
}
for item in list.rev_iter() {
match *item {
ClipDisplayItemClass(_) => continue,
_ => {}
}
let bounds = item.bounds();
// TODO this check should really be performed by a method of DisplayItem
if x < bounds.origin.x + bounds.size.width &&
bounds.origin.x <= x &&
y < bounds.origin.y + bounds.size.height &&
bounds.origin.y <= y {
let node: AbstractNode<LayoutView> = unsafe {
transmute(item.base().extra)
};
let resp = Some(HitTestResponse(node));
return resp;
}
}
let ret: Option<HitTestResponse> = None;
ret
}
let response = {
match self.display_list {
Some(ref list) => {
let display_list = list.get();
let (x, y) = (Au::from_frac_px(point.x as f64),
Au::from_frac_px(point.y as f64));
let mut resp = Err(());
// iterate in reverse to ensure we have the most recently painted render box
for display_item in display_list.list.rev_iter() {
let bounds = display_item.bounds();
// TODO this check should really be performed by a method of DisplayItem
if x <= bounds.origin.x + bounds.size.width &&
bounds.origin.x <= x &&
y < bounds.origin.y + bounds.size.height &&
bounds.origin.y < y {
let node: AbstractNode<LayoutView> = unsafe {
transmute(display_item.base().extra)
};
resp = Ok(HitTestResponse(node));
break;
let resp = hit_test(x,y,display_list.list);
if resp.is_none() {
Err(())
} else {
Ok(resp.unwrap())
}
}
resp
}
None => {
error!("Can't hit test: no display list");
Err(())

View file

@ -15,18 +15,22 @@ use std::cast;
use std::iter::Enumerate;
use std::vec::VecIterator;
use style::{ComputedValues, PropertyDeclaration};
use geom::rect::Rect;
use servo_util::geometry::Au;
/// The boxes associated with a node.
pub struct DisplayBoxes {
display_list: Option<Arc<DisplayList<AbstractNode<()>>>>,
range: Option<Range>,
display_bound_list: Option<~[Rect<Au>]>,
display_bound: Option<Rect<Au>>
}
impl DisplayBoxes {
pub fn init() -> DisplayBoxes {
DisplayBoxes {
display_list: None,
range: None,
display_bound_list: None,
display_bound: None,
}
}
}

View file

@ -371,6 +371,7 @@ pub mod longhands {
// CSS 2.1, Section 11 - Visual effects
${single_keyword("overflow", "visible hidden", inherited=False)} // TODO: scroll auto
// CSS 2.1, Section 12 - Generated content, automatic numbering, and lists

@ -1 +1 @@
Subproject commit 13fbdbeddfccbc3e451fa9ae47f334f4f626a051
Subproject commit 60ee86c802f45a8e87fa462cd21d4b074f837cec