diff options
Diffstat (limited to 'components/layout/layout_task.rs')
-rw-r--r-- | components/layout/layout_task.rs | 353 |
1 files changed, 208 insertions, 145 deletions
diff --git a/components/layout/layout_task.rs b/components/layout/layout_task.rs index 3a511033934..33bf00e837f 100644 --- a/components/layout/layout_task.rs +++ b/components/layout/layout_task.rs @@ -7,9 +7,9 @@ #![allow(unsafe_code)] +use animation; use construct::ConstructionResult; use context::{SharedLayoutContext, SharedLayoutContextWrapper}; -use css::node_style::StyledNode; use display_list_builder::ToGfxColor; use flow::{self, Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils}; use flow_ref::FlowRef; @@ -20,8 +20,9 @@ use layout_debug; use opaque_node::OpaqueNodeMethods; use parallel::{self, UnsafeFlow}; use sequential; -use wrapper::{LayoutNode, TLayoutNode, ThreadSafeLayoutNode}; +use wrapper::{LayoutNode, TLayoutNode}; +use azure::azure::AzColor; use encoding::EncodingRef; use encoding::all::UTF_8; use geom::matrix2d::Matrix2D; @@ -47,14 +48,11 @@ use profile::mem::{self, Report, ReportsChan}; use profile::time::{self, ProfilerMetadata, profile}; use profile::time::{TimerMetadataFrameType, TimerMetadataReflowType}; use script::dom::bindings::js::LayoutJS; -use script::dom::element::ElementTypeId; -use script::dom::htmlelement::HTMLElementTypeId; -use script::dom::node::{LayoutData, Node, NodeTypeId}; -use script::layout_interface::ReflowQueryType; -use script::layout_interface::{ContentBoxResponse, ContentBoxesResponse}; +use script::dom::node::{LayoutData, Node}; +use script::layout_interface::{Animation, ContentBoxResponse, ContentBoxesResponse}; use script::layout_interface::{HitTestResponse, LayoutChan, LayoutRPC}; -use script::layout_interface::{MouseOverResponse, Msg}; -use script::layout_interface::{Reflow, ReflowGoal, ScriptLayoutChan, TrustedNodeAddress}; +use script::layout_interface::{MouseOverResponse, Msg, Reflow, ReflowGoal, ReflowQueryType}; +use script::layout_interface::{ScriptLayoutChan, ScriptReflow, TrustedNodeAddress}; use script_traits::{ConstellationControlMsg, CompositorEvent, OpaqueScriptLayoutChannel}; use script_traits::{ScriptControlChan, UntrustedNodeAddress}; use std::borrow::ToOwned; @@ -71,7 +69,7 @@ use style::selector_matching::Stylist; use style::stylesheets::{Origin, Stylesheet, iter_font_face_rules}; use url::Url; use util::cursor::Cursor; -use util::geometry::Au; +use util::geometry::{Au, MAX_RECT}; use util::logical_geometry::LogicalPoint; use util::mem::HeapSizeOf; use util::opts; @@ -84,6 +82,9 @@ use util::workqueue::WorkQueue; /// /// This needs to be protected by a mutex so we can do fast RPCs. pub struct LayoutTaskData { + /// The root of the flow tree. + pub root_flow: Option<FlowRef>, + /// The local image cache. pub local_image_cache: Arc<Mutex<LocalImageCache<UntrustedNodeAddress>>>, @@ -107,13 +108,23 @@ pub struct LayoutTaskData { /// Starts at zero, and increased by one every time a layout completes. /// This can be used to easily check for invalid stale data. - pub generation: uint, + pub generation: u32, /// A queued response for the union of the content boxes of a node. pub content_box_response: Rect<Au>, /// A queued response for the content boxes of a node. pub content_boxes_response: Vec<Rect<Au>>, + + /// The list of currently-running animations. + pub running_animations: Vec<Animation>, + + /// Receives newly-discovered animations. + pub new_animations_receiver: Receiver<Animation>, + + /// A channel on which new animations that have been triggered by style recalculation can be + /// sent. + pub new_animations_sender: Sender<Animation>, } /// Information needed by the layout task. @@ -130,7 +141,7 @@ pub struct LayoutTask { /// The port on which we receive messages from the constellation pub pipeline_port: Receiver<LayoutControlMsg>, - //// The channel to send messages to ourself. + /// The channel on which we or others can send messages to ourselves. pub chan: LayoutChan, /// The channel on which messages can be sent to the constellation. @@ -193,39 +204,37 @@ impl ImageResponder<UntrustedNodeAddress> for LayoutImageResponder { impl LayoutTaskFactory for LayoutTask { /// Spawns a new layout task. fn create(_phantom: Option<&mut LayoutTask>, - id: PipelineId, - url: Url, - chan: OpaqueScriptLayoutChannel, - pipeline_port: Receiver<LayoutControlMsg>, - constellation_chan: ConstellationChan, - failure_msg: Failure, - script_chan: ScriptControlChan, - paint_chan: PaintChan, - resource_task: ResourceTask, - img_cache_task: ImageCacheTask, - font_cache_task: FontCacheTask, - time_profiler_chan: time::ProfilerChan, - mem_profiler_chan: mem::ProfilerChan, - shutdown_chan: Sender<()>) { + id: PipelineId, + url: Url, + chan: OpaqueScriptLayoutChannel, + pipeline_port: Receiver<LayoutControlMsg>, + constellation_chan: ConstellationChan, + failure_msg: Failure, + script_chan: ScriptControlChan, + paint_chan: PaintChan, + resource_task: ResourceTask, + img_cache_task: ImageCacheTask, + font_cache_task: FontCacheTask, + time_profiler_chan: time::ProfilerChan, + memory_profiler_chan: mem::ProfilerChan, + shutdown_chan: Sender<()>) { let ConstellationChan(con_chan) = constellation_chan.clone(); spawn_named_with_send_on_failure("LayoutTask", task_state::LAYOUT, move || { { // Ensures layout task is destroyed before we send shutdown message let sender = chan.sender(); - let layout = - LayoutTask::new( - id, - url, - chan.receiver(), - LayoutChan(sender), - pipeline_port, - constellation_chan, - script_chan, - paint_chan, - resource_task, - img_cache_task, - font_cache_task, - time_profiler_chan, - mem_profiler_chan); + let layout = LayoutTask::new(id, + url, + chan.receiver(), + LayoutChan(sender), + pipeline_port, + constellation_chan, + script_chan, + paint_chan, + resource_task, + img_cache_task, + font_cache_task, + time_profiler_chan, + memory_profiler_chan); layout.start(); } shutdown_chan.send(()).unwrap(); @@ -294,10 +303,14 @@ impl LayoutTask { }; // Register this thread as a memory reporter, via its own channel. - let reporter = Box::new(chan.clone()); + let reporter = box chan.clone(); let reporter_name = format!("layout-reporter-{}", id.0); mem_profiler_chan.send(mem::ProfilerMsg::RegisterReporter(reporter_name.clone(), reporter)); + + // Create the channel on which new animations can be sent. + let (new_animations_sender, new_animations_receiver) = channel(); + LayoutTask { id: id, url: url, @@ -316,6 +329,7 @@ impl LayoutTask { first_reflow: Cell::new(true), rw_data: Arc::new(Mutex::new( LayoutTaskData { + root_flow: None, local_image_cache: local_image_cache, constellation_chan: constellation_chan, screen_size: screen_size, @@ -326,6 +340,9 @@ impl LayoutTask { generation: 0, content_box_response: Rect::zero(), content_boxes_response: Vec::new(), + running_animations: Vec::new(), + new_animations_receiver: new_animations_receiver, + new_animations_sender: new_animations_sender, })), } } @@ -342,7 +359,7 @@ impl LayoutTask { fn build_shared_layout_context(&self, rw_data: &LayoutTaskData, screen_size_changed: bool, - reflow_root: &LayoutNode, + reflow_root: Option<&LayoutNode>, url: &Url) -> SharedLayoutContext { SharedLayoutContext { @@ -354,9 +371,10 @@ impl LayoutTask { font_cache_task: self.font_cache_task.clone(), stylist: &*rw_data.stylist, url: (*url).clone(), - reflow_root: OpaqueNodeMethods::from_layout_node(reflow_root), + reflow_root: reflow_root.map(|node| OpaqueNodeMethods::from_layout_node(node)), dirty: Rect::zero(), generation: rw_data.generation, + new_animations_sender: rw_data.new_animations_sender.clone(), } } @@ -390,8 +408,12 @@ impl LayoutTask { match port_to_read { PortToRead::Pipeline => { match self.pipeline_port.recv().unwrap() { + LayoutControlMsg::TickAnimationsMsg => { + self.handle_request_helper(Msg::TickAnimations, possibly_locked_rw_data) + } LayoutControlMsg::ExitNowMsg(exit_type) => { - self.handle_request_helper(Msg::ExitNow(exit_type), possibly_locked_rw_data) + self.handle_request_helper(Msg::ExitNow(exit_type), + possibly_locked_rw_data) } } }, @@ -435,8 +457,12 @@ impl LayoutTask { LayoutTaskData>>) -> bool { match request { - Msg::AddStylesheet(sheet, mq) => self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data), - Msg::LoadStylesheet(url, mq) => self.handle_load_stylesheet(url, mq, possibly_locked_rw_data), + Msg::AddStylesheet(sheet, mq) => { + self.handle_add_stylesheet(sheet, mq, possibly_locked_rw_data) + } + Msg::LoadStylesheet(url, mq) => { + self.handle_load_stylesheet(url, mq, possibly_locked_rw_data) + } Msg::SetQuirksMode => self.handle_set_quirks_mode(possibly_locked_rw_data), Msg::GetRPC(response_chan) => { response_chan.send(box LayoutRPCImpl(self.rw_data.clone()) as @@ -444,10 +470,11 @@ impl LayoutTask { }, Msg::Reflow(data) => { profile(time::ProfilerCategory::LayoutPerform, - self.profiler_metadata(&*data), + self.profiler_metadata(&data.reflow_info), self.time_profiler_chan.clone(), || self.handle_reflow(&*data, possibly_locked_rw_data)); }, + Msg::TickAnimations => self.tick_all_animations(possibly_locked_rw_data), Msg::ReapLayoutData(dead_layout_data) => { unsafe { self.handle_reap_layout_data(dead_layout_data) @@ -481,15 +508,15 @@ impl LayoutTask { let stacking_context = rw_data.stacking_context.as_ref(); reports.push(Report { path: path!["pages", format!("url({})", self.url), "display-list"], - size: stacking_context.map_or(0, |sc| sc.heap_size_of_children() as u64), + size: stacking_context.map_or(0, |sc| sc.heap_size_of_children()), }); reports_chan.send(reports); } - /// Enters a quiescent state in which no new messages except for `layout_interface::Msg::ReapLayoutData` will be - /// processed until an `ExitNowMsg` is received. A pong is immediately sent on the given - /// response channel. + /// Enters a quiescent state in which no new messages except for + /// `layout_interface::Msg::ReapLayoutData` will be processed until an `ExitNowMsg` is + /// received. A pong is immediately sent on the given response channel. fn prepare_to_exit<'a>(&'a self, response_chan: Sender<()>, possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) { @@ -570,7 +597,7 @@ impl LayoutTask { if mq.evaluate(&rw_data.stylist.device) { iter_font_face_rules(&sheet, &rw_data.stylist.device, &|family, src| { - self.font_cache_task.add_web_font(family.to_owned(), (*src).clone()); + self.font_cache_task.add_web_font((*family).clone(), (*src).clone()); }); rw_data.stylist.add_stylesheet(sheet); } @@ -587,7 +614,6 @@ impl LayoutTask { LayoutTask::return_rw_data(possibly_locked_rw_data, rw_data); } - /// Retrieves the flow tree root from the root node. fn try_get_layout_root(&self, node: LayoutNode) -> Option<FlowRef> { let mut layout_data_ref = node.mutate_layout_data(); let layout_data = @@ -697,10 +723,9 @@ impl LayoutTask { fn build_display_list_for_reflow<'a>(&'a self, data: &Reflow, - node: &mut LayoutNode, layout_root: &mut FlowRef, shared_layout_context: &mut SharedLayoutContext, - rw_data: &mut RWGuard<'a>) { + rw_data: &mut LayoutTaskData) { let writing_mode = flow::base(&**layout_root).writing_mode; profile(time::ProfilerCategory::LayoutDispListBuild, self.profiler_metadata(data), @@ -716,7 +741,6 @@ impl LayoutTask { flow::mut_base(&mut **layout_root).clip = ClippingRegion::from_rect(&data.page_clip_rect); - let rw_data = &mut **rw_data; match rw_data.parallel_traversal { None => { sequential::build_display_list_for_subtree(layout_root, shared_layout_context); @@ -732,40 +756,7 @@ impl LayoutTask { debug!("Done building display list."); - // FIXME(pcwalton): This is really ugly and can't handle overflow: scroll. Refactor - // it with extreme prejudice. - - // The default computed value for background-color is transparent (see - // http://dev.w3.org/csswg/css-backgrounds/#background-color). However, we - // need to propagate the background color from the root HTML/Body - // element (http://dev.w3.org/csswg/css-backgrounds/#special-backgrounds) if - // it is non-transparent. The phrase in the spec "If the canvas background - // is not opaque, what shows through is UA-dependent." is handled by rust-layers - // clearing the frame buffer to white. This ensures that setting a background - // color on an iframe element, while the iframe content itself has a default - // transparent background color is handled correctly. - let mut color = color::transparent_black(); - for child in node.traverse_preorder() { - if child.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLHtmlElement))) || - child.type_id() == Some(NodeTypeId::Element(ElementTypeId::HTMLElement(HTMLElementTypeId::HTMLBodyElement))) { - let element_bg_color = { - let thread_safe_child = ThreadSafeLayoutNode::new(&child); - thread_safe_child.style() - .resolve_color(thread_safe_child.style() - .get_background() - .background_color) - .to_gfx_color() - }; - - let black = color::transparent_black(); - if element_bg_color != black { - - color = element_bg_color; - break; - } - } - } - + let root_background_color = get_root_flow_background_color(&mut **layout_root); let root_size = { let root_flow = flow::base(&**layout_root); root_flow.position.size.to_physical(root_flow.writing_mode) @@ -774,7 +765,7 @@ impl LayoutTask { flow::mut_base(&mut **layout_root).display_list_building_result .add_to(&mut *display_list); let paint_layer = Arc::new(PaintLayer::new(layout_root.layer_id(0), - color, + root_background_color, ScrollPolicy::Scrollable)); let origin = Rect(Point2D(Au(0), Au(0)), root_size); @@ -802,7 +793,7 @@ impl LayoutTask { /// The high-level routine that performs layout tasks. fn handle_reflow<'a>(&'a self, - data: &Reflow, + data: &ScriptReflow, possibly_locked_rw_data: &mut Option<MutexGuard<'a, LayoutTaskData>>) { // FIXME: Isolate this transmutation into a "bridge" module. // FIXME(rust#16366): The following line had to be moved because of a @@ -814,8 +805,7 @@ impl LayoutTask { transmute(&mut node) }; - debug!("layout: received layout request for: {}", data.url.serialize()); - debug!("layout: parsed Node tree"); + debug!("layout: received layout request for: {}", data.reflow_info.url.serialize()); if log_enabled!(log::DEBUG) { node.dump(); } @@ -831,7 +821,6 @@ impl LayoutTask { // TODO: Calculate the "actual viewport": // http://www.w3.org/TR/css-device-adapt/#actual-viewport let viewport_size = data.window_size.initial_viewport; - let old_screen_size = rw_data.screen_size; let current_screen_size = Size2D(Au::from_frac32_px(viewport_size.width.get()), Au::from_frac32_px(viewport_size.height.get())); @@ -839,23 +828,19 @@ impl LayoutTask { // Handle conditions where the entire flow tree is invalid. let screen_size_changed = current_screen_size != old_screen_size; - if screen_size_changed { let device = Device::new(MediaType::Screen, data.window_size.initial_viewport); rw_data.stylist.set_device(device); } - let needs_dirtying = rw_data.stylist.update(); - // If the entire flow tree is invalid, then it will be reflowed anyhow. + let needs_dirtying = rw_data.stylist.update(); let needs_reflow = screen_size_changed && !needs_dirtying; - unsafe { if needs_dirtying { LayoutTask::dirty_all_nodes(node); } } - if needs_reflow { match self.try_get_layout_root(*node) { None => {} @@ -868,13 +853,14 @@ impl LayoutTask { // Create a layout context for use throughout the following passes. let mut shared_layout_context = self.build_shared_layout_context(&*rw_data, screen_size_changed, - node, - &data.url); + Some(&node), + &data.reflow_info.url); - let mut layout_root = profile(time::ProfilerCategory::LayoutStyleRecalc, - self.profiler_metadata(data), - self.time_profiler_chan.clone(), - || { + // Recalculate CSS styles and rebuild flows and fragments. + profile(time::ProfilerCategory::LayoutStyleRecalc, + self.profiler_metadata(&data.reflow_info), + self.time_profiler_chan.clone(), + || { // Perform CSS selector matching and flow construction. let rw_data = &mut *rw_data; match rw_data.parallel_traversal { @@ -882,39 +868,105 @@ impl LayoutTask { sequential::traverse_dom_preorder(*node, &shared_layout_context); } Some(ref mut traversal) => { - parallel::traverse_dom_preorder(*node, &shared_layout_context, traversal) + parallel::traverse_dom_preorder(*node, &shared_layout_context, traversal); } } - - self.get_layout_root((*node).clone()) }); + // Retrieve the (possibly rebuilt) root flow. + rw_data.root_flow = Some(self.get_layout_root((*node).clone())); + + // Kick off animations if any were triggered. + animation::process_new_animations(&mut *rw_data, self.id); + + // Perform post-style recalculation layout passes. + self.perform_post_style_recalc_layout_passes(&data.reflow_info, + &mut rw_data, + &mut shared_layout_context); + + let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone(); + match data.query_type { + ReflowQueryType::ContentBoxQuery(node) => { + self.process_content_box_request(node, &mut root_flow, &mut rw_data) + } + ReflowQueryType::ContentBoxesQuery(node) => { + self.process_content_boxes_request(node, &mut root_flow, &mut rw_data) + } + ReflowQueryType::NoQuery => {} + } + + + // Tell script that we're done. + // + // FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without + // either select or a filtered recv() that only looks for messages of a given type. + data.script_join_chan.send(()).unwrap(); + let ScriptControlChan(ref chan) = data.script_chan; + chan.send(ConstellationControlMsg::ReflowComplete(self.id, data.id)).unwrap(); + } + + fn tick_all_animations<'a>(&'a self, + possibly_locked_rw_data: &mut Option<MutexGuard<'a, + LayoutTaskData>>) { + let mut rw_data = self.lock_rw_data(possibly_locked_rw_data); + animation::tick_all_animations(self, &mut rw_data) + } + + pub fn tick_animation<'a>(&'a self, animation: Animation, rw_data: &mut LayoutTaskData) { + // FIXME(#5466, pcwalton): These data are lies. + let reflow_info = Reflow { + goal: ReflowGoal::ForDisplay, + url: Url::parse("http://animation.com/").unwrap(), + iframe: false, + page_clip_rect: MAX_RECT, + }; + + // Perform an abbreviated style recalc that operates without access to the DOM. + let mut layout_context = self.build_shared_layout_context(&*rw_data, + false, + None, + &reflow_info.url); + let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone(); + profile(time::ProfilerCategory::LayoutStyleRecalc, + self.profiler_metadata(&reflow_info), + self.time_profiler_chan.clone(), + || animation::recalc_style_for_animation(root_flow.deref_mut(), &animation)); + + self.perform_post_style_recalc_layout_passes(&reflow_info, + &mut *rw_data, + &mut layout_context); + } + + fn perform_post_style_recalc_layout_passes<'a>(&'a self, + data: &Reflow, + rw_data: &mut LayoutTaskData, + layout_context: &mut SharedLayoutContext) { + let mut root_flow = (*rw_data.root_flow.as_ref().unwrap()).clone(); profile(time::ProfilerCategory::LayoutRestyleDamagePropagation, self.profiler_metadata(data), self.time_profiler_chan.clone(), || { - if opts::get().nonincremental_layout || layout_root.compute_layout_damage() - .contains(REFLOW_ENTIRE_DOCUMENT) { - layout_root.reflow_entire_document() + if opts::get().nonincremental_layout || root_flow.deref_mut() + .compute_layout_damage() + .contains(REFLOW_ENTIRE_DOCUMENT) { + root_flow.deref_mut().reflow_entire_document() } }); // Verification of the flow tree, which ensures that all nodes were either marked as leaves // or as non-leaves. This becomes a no-op in release builds. (It is inconsequential to // memory safety but is a useful debugging tool.) - self.verify_flow_tree(&mut layout_root); + self.verify_flow_tree(&mut root_flow); if opts::get().trace_layout { - layout_debug::begin_trace(layout_root.clone()); + layout_debug::begin_trace(root_flow.clone()); } // Resolve generated content. profile(time::ProfilerCategory::LayoutGeneratedContent, self.profiler_metadata(data), self.time_profiler_chan.clone(), - || { - sequential::resolve_generated_content(&mut layout_root, &shared_layout_context) - }); + || sequential::resolve_generated_content(&mut root_flow, &layout_context)); // Perform the primary layout passes over the flow tree to compute the locations of all // the boxes. @@ -922,18 +974,17 @@ impl LayoutTask { self.profiler_metadata(data), self.time_profiler_chan.clone(), || { - let rw_data = &mut *rw_data; match rw_data.parallel_traversal { None => { // Sequential mode. - self.solve_constraints(&mut layout_root, &shared_layout_context) + self.solve_constraints(&mut root_flow, &layout_context) } Some(_) => { // Parallel mode. self.solve_constraints_parallel(data, rw_data, - &mut layout_root, - &mut shared_layout_context); + &mut root_flow, + &mut *layout_context); } } }); @@ -942,24 +993,13 @@ impl LayoutTask { match data.goal { ReflowGoal::ForDisplay => { self.build_display_list_for_reflow(data, - node, - &mut layout_root, - &mut shared_layout_context, - &mut rw_data); + &mut root_flow, + &mut *layout_context, + rw_data); } ReflowGoal::ForScriptQuery => {} } - match data.query_type { - ReflowQueryType::ContentBoxQuery(node) => { - self.process_content_box_request(node, &mut layout_root, &mut rw_data) - } - ReflowQueryType::ContentBoxesQuery(node) => { - self.process_content_boxes_request(node, &mut layout_root, &mut rw_data) - } - ReflowQueryType::NoQuery => {} - } - self.first_reflow.set(false); if opts::get().trace_layout { @@ -967,18 +1007,10 @@ impl LayoutTask { } if opts::get().dump_flow_tree { - layout_root.dump(); + root_flow.dump(); } rw_data.generation += 1; - - // Tell script that we're done. - // - // FIXME(pcwalton): This should probably be *one* channel, but we can't fix this without - // either select or a filtered recv() that only looks for messages of a given type. - data.script_join_chan.send(()).unwrap(); - let ScriptControlChan(ref chan) = data.script_chan; - chan.send(ConstellationControlMsg::ReflowComplete(self.id, data.id)).unwrap(); } unsafe fn dirty_all_nodes(node: &mut LayoutNode) { @@ -1172,3 +1204,34 @@ impl FragmentBorderBoxIterator for CollectingFragmentBorderBoxIterator { self.node_address == fragment.node } } + +// The default computed value for background-color is transparent (see +// http://dev.w3.org/csswg/css-backgrounds/#background-color). However, we +// need to propagate the background color from the root HTML/Body +// element (http://dev.w3.org/csswg/css-backgrounds/#special-backgrounds) if +// it is non-transparent. The phrase in the spec "If the canvas background +// is not opaque, what shows through is UA-dependent." is handled by rust-layers +// clearing the frame buffer to white. This ensures that setting a background +// color on an iframe element, while the iframe content itself has a default +// transparent background color is handled correctly. +fn get_root_flow_background_color(flow: &mut Flow) -> AzColor { + if !flow.is_block_like() { + return color::transparent_black() + } + + let block_flow = flow.as_block(); + let kid = match block_flow.base.children.iter_mut().next() { + None => return color::transparent_black(), + Some(kid) => kid, + }; + if !kid.is_block_like() { + return color::transparent_black() + } + + let kid_block_flow = kid.as_block(); + kid_block_flow.fragment + .style + .resolve_color(kid_block_flow.fragment.style.get_background().background_color) + .to_gfx_color() +} + |