mirror of
https://github.com/servo/servo.git
synced 2025-08-03 12:40:06 +01:00
auto merge of #1298 : recrack/servo/overflow_property, r=metajack
Impl #1290 @jaeminMoon @ksh8281 Thanks @metajack
This commit is contained in:
commit
b867e54dd4
8 changed files with 232 additions and 101 deletions
|
@ -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
22
src/components/gfx/render_context.rs
Normal file → Executable 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);
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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(())
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue