tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

yaml_frame_reader.rs (78342B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      4 
      5 use euclid::SideOffsets2D;
      6 use gleam::gl;
      7 use image::GenericImageView;
      8 use crate::parse_function::parse_function;
      9 use crate::premultiply::premultiply;
     10 use std::collections::HashMap;
     11 use std::convert::TryInto;
     12 use std::fs::File;
     13 use std::io::Read;
     14 use std::path::{Path, PathBuf};
     15 use std::usize;
     16 use webrender::api::*;
     17 use webrender::render_api::*;
     18 use webrender::api::units::*;
     19 use webrender::api::FillRule;
     20 use crate::wrench::{FontDescriptor, Wrench, WrenchThing, DisplayList};
     21 use crate::yaml_helper::{StringEnum, YamlHelper, make_perspective};
     22 use yaml_rust::{Yaml, YamlLoader};
     23 use crate::PLATFORM_DEFAULT_FACE_NAME;
     24 
     25 macro_rules! try_intersect {
     26    ($first: expr, $second: expr) => {
     27        if let Some(rect) = ($first).intersection($second) {
     28            rect
     29        } else {
     30            warn!("skipping item with non-intersecting bounds and clip_rect");
     31            return;
     32        }
     33    }
     34 }
     35 
     36 fn rsrc_path(item: &Yaml, aux_dir: &Path) -> PathBuf {
     37    let filename = item.as_str().unwrap();
     38    let mut file = aux_dir.to_path_buf();
     39    file.push(filename);
     40    file
     41 }
     42 
     43 impl FontDescriptor {
     44    fn from_yaml(item: &Yaml, aux_dir: &Path) -> FontDescriptor {
     45        if !item["family"].is_badvalue() {
     46            FontDescriptor::Properties {
     47                family: item["family"].as_str().unwrap().to_owned(),
     48                weight: item["weight"].as_i64().unwrap_or(400) as u32,
     49                style: item["style"].as_i64().unwrap_or(0) as u32,
     50                stretch: item["stretch"].as_i64().unwrap_or(5) as u32,
     51            }
     52        } else if !item["font"].is_badvalue() {
     53            let path = rsrc_path(&item["font"], aux_dir);
     54            FontDescriptor::Path {
     55                path,
     56                font_index: item["font-index"].as_i64().unwrap_or(0) as u32,
     57            }
     58        } else {
     59            FontDescriptor::Family {
     60                name: PLATFORM_DEFAULT_FACE_NAME.to_string(),
     61            }
     62        }
     63    }
     64 }
     65 
     66 struct LocalExternalImageHandler {
     67    texture_ids: Vec<(gl::GLuint, ImageDescriptor)>,
     68 }
     69 
     70 impl LocalExternalImageHandler {
     71    pub fn new() -> LocalExternalImageHandler {
     72        LocalExternalImageHandler {
     73            texture_ids: Vec::new(),
     74        }
     75    }
     76 
     77    fn init_gl_texture(
     78        id: gl::GLuint,
     79        gl_target: gl::GLuint,
     80        format_desc: webrender::FormatDesc,
     81        width: gl::GLint,
     82        height: gl::GLint,
     83        bytes: &[u8],
     84        gl: &dyn gl::Gl,
     85    ) {
     86        gl.bind_texture(gl_target, id);
     87        gl.tex_parameter_i(gl_target, gl::TEXTURE_MAG_FILTER, gl::LINEAR as gl::GLint);
     88        gl.tex_parameter_i(gl_target, gl::TEXTURE_MIN_FILTER, gl::LINEAR as gl::GLint);
     89        gl.tex_parameter_i(gl_target, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as gl::GLint);
     90        gl.tex_parameter_i(gl_target, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as gl::GLint);
     91        gl.tex_image_2d(
     92            gl_target,
     93            0,
     94            format_desc.internal as gl::GLint,
     95            width,
     96            height,
     97            0,
     98            format_desc.external,
     99            format_desc.pixel_type,
    100            Some(bytes),
    101        );
    102        gl.bind_texture(gl_target, 0);
    103    }
    104 
    105    pub fn add_image(&mut self,
    106        device: &webrender::Device,
    107        desc: ImageDescriptor,
    108        target: ImageBufferKind,
    109        image_data: ImageData,
    110    ) -> ImageData {
    111        let (image_id, channel_idx) = match image_data {
    112            ImageData::Raw(ref data) => {
    113                let gl = device.gl();
    114                let texture_ids = gl.gen_textures(1);
    115                let format_desc = if desc.format == ImageFormat::BGRA8 {
    116                    // Force BGRA8 data to RGBA8 layout to avoid potential
    117                    // need for usage of texture-swizzle.
    118                    webrender::FormatDesc {
    119                        external: gl::BGRA,
    120                        .. device.gl_describe_format(ImageFormat::RGBA8)
    121                    }
    122                } else {
    123                    device.gl_describe_format(desc.format)
    124                };
    125 
    126                LocalExternalImageHandler::init_gl_texture(
    127                    texture_ids[0],
    128                    webrender::get_gl_target(target),
    129                    format_desc,
    130                    desc.size.width as gl::GLint,
    131                    desc.size.height as gl::GLint,
    132                    data,
    133                    gl,
    134                );
    135                self.texture_ids.push((texture_ids[0], desc));
    136                (ExternalImageId((self.texture_ids.len() - 1) as u64), 0)
    137            },
    138            _ => panic!("unsupported!"),
    139        };
    140 
    141        ImageData::External(
    142            ExternalImageData {
    143                id: image_id,
    144                channel_index: channel_idx,
    145                image_type: ExternalImageType::TextureHandle(target),
    146                normalized_uvs: false,
    147            }
    148        )
    149    }
    150 }
    151 
    152 impl ExternalImageHandler for LocalExternalImageHandler {
    153    fn lock(
    154        &mut self,
    155        key: ExternalImageId,
    156        _channel_index: u8,
    157        _is_composited: bool,
    158    ) -> ExternalImage<'_> {
    159        let (id, desc) = self.texture_ids[key.0 as usize];
    160        ExternalImage {
    161            uv: TexelRect::new(0.0, 0.0, desc.size.width as f32, desc.size.height as f32),
    162            source: ExternalImageSource::NativeTexture(id),
    163        }
    164    }
    165    fn unlock(&mut self, _key: ExternalImageId, _channel_index: u8) {}
    166 }
    167 
    168 fn broadcast<T: Clone>(base_vals: &[T], num_items: usize) -> Vec<T> {
    169    if base_vals.len() == num_items {
    170        return base_vals.to_vec();
    171    }
    172 
    173    assert_eq!(
    174        num_items % base_vals.len(),
    175        0,
    176        "Cannot broadcast {} elements into {}",
    177        base_vals.len(),
    178        num_items
    179    );
    180 
    181    let mut vals = vec![];
    182    loop {
    183        if vals.len() == num_items {
    184            break;
    185        }
    186        vals.extend_from_slice(base_vals);
    187    }
    188    vals
    189 }
    190 
    191 enum CheckerboardKind {
    192    BlackGrey,
    193    BlackTransparent,
    194 }
    195 
    196 fn generate_checkerboard_image(
    197    border: u32,
    198    tile_x_size: u32,
    199    tile_y_size: u32,
    200    tile_x_count: u32,
    201    tile_y_count: u32,
    202    kind: CheckerboardKind,
    203 ) -> (ImageDescriptor, ImageData) {
    204    let width = 2 * border + tile_x_size * tile_x_count;
    205    let height = 2 * border + tile_y_size * tile_y_count;
    206    let mut pixels = Vec::new();
    207 
    208    for y in 0 .. height {
    209        for x in 0 .. width {
    210            if y < border || y >= (height - border) ||
    211               x < border || x >= (width - border) {
    212                pixels.push(0);
    213                pixels.push(0);
    214                pixels.push(0xff);
    215                pixels.push(0xff);
    216            } else {
    217                let xon = ((x - border) % (2 * tile_x_size)) < tile_x_size;
    218                let yon = ((y - border) % (2 * tile_y_size)) < tile_y_size;
    219                match kind {
    220                    CheckerboardKind::BlackGrey => {
    221                        let value = if xon ^ yon { 0xff } else { 0x7f };
    222                        pixels.push(value);
    223                        pixels.push(value);
    224                        pixels.push(value);
    225                        pixels.push(0xff);
    226                    }
    227                    CheckerboardKind::BlackTransparent => {
    228                        let value = if xon ^ yon { 0xff } else { 0x00 };
    229                        pixels.push(value);
    230                        pixels.push(value);
    231                        pixels.push(value);
    232                        pixels.push(value);
    233                    }
    234                }
    235            }
    236        }
    237    }
    238 
    239    let flags = match kind {
    240        CheckerboardKind::BlackGrey => ImageDescriptorFlags::IS_OPAQUE,
    241        CheckerboardKind::BlackTransparent => ImageDescriptorFlags::empty(),
    242    };
    243 
    244    (
    245        ImageDescriptor::new(width as i32, height as i32, ImageFormat::BGRA8, flags),
    246        ImageData::new(pixels),
    247    )
    248 }
    249 
    250 fn generate_xy_gradient_image(w: u32, h: u32) -> (ImageDescriptor, ImageData) {
    251    let mut pixels = Vec::with_capacity((w * h * 4) as usize);
    252    for y in 0 .. h {
    253        for x in 0 .. w {
    254            let grid = if x % 100 < 3 || y % 100 < 3 { 0.9 } else { 1.0 };
    255            pixels.push((y as f32 / h as f32 * 255.0 * grid) as u8);
    256            pixels.push(0);
    257            pixels.push((x as f32 / w as f32 * 255.0 * grid) as u8);
    258            pixels.push(255);
    259        }
    260    }
    261 
    262    (
    263        ImageDescriptor::new(w as i32, h as i32, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
    264        ImageData::new(pixels),
    265    )
    266 }
    267 
    268 fn generate_solid_color_image(
    269    r: u8,
    270    g: u8,
    271    b: u8,
    272    a: u8,
    273    w: u32,
    274    h: u32,
    275 ) -> (ImageDescriptor, ImageData) {
    276    let num_pixels: usize = (w * h).try_into().unwrap();
    277    let pixels = [b, g, r, a].repeat(num_pixels);
    278 
    279    let mut flags = ImageDescriptorFlags::empty();
    280    if a == 255 {
    281        flags |= ImageDescriptorFlags::IS_OPAQUE;
    282    }
    283 
    284    (
    285        ImageDescriptor::new(w as i32, h as i32, ImageFormat::BGRA8, flags),
    286        ImageData::new(pixels),
    287    )
    288 }
    289 
    290 
    291 
    292 fn is_image_opaque(format: ImageFormat, bytes: &[u8]) -> bool {
    293    match format {
    294        ImageFormat::BGRA8 |
    295        ImageFormat::RGBA8 => {
    296            let mut is_opaque = true;
    297            for i in 0 .. (bytes.len() / 4) {
    298                if bytes[i * 4 + 3] != 255 {
    299                    is_opaque = false;
    300                    break;
    301                }
    302            }
    303            is_opaque
    304        }
    305        ImageFormat::RG8 => true,
    306        ImageFormat::RG16 => true,
    307        ImageFormat::R8 => false,
    308        ImageFormat::R16 => false,
    309        ImageFormat::RGBAF32 |
    310        ImageFormat::RGBAI32 => unreachable!(),
    311    }
    312 }
    313 
    314 struct IsRoot(bool);
    315 
    316 pub struct Snapshot {
    317    key: SnapshotImageKey,
    318    size: LayoutSize,
    319 }
    320 
    321 pub struct YamlFrameReader {
    322    yaml_path: PathBuf,
    323    aux_dir: PathBuf,
    324    frame_count: u32,
    325 
    326    display_lists: Vec<DisplayList>,
    327 
    328    watch_source: bool,
    329    list_resources: bool,
    330 
    331    /// A HashMap of offsets which specify what scroll offsets particular
    332    /// scroll layers should be initialized with.
    333    scroll_offsets: HashMap<ExternalScrollId, Vec<SampledScrollOffset>>,
    334    next_external_scroll_id: u64,
    335 
    336    image_map: HashMap<(PathBuf, Option<i64>), (ImageKey, LayoutSize)>,
    337 
    338    fonts: HashMap<FontDescriptor, FontKey>,
    339    font_instances: HashMap<(FontKey, FontSize, FontInstanceFlags, SyntheticItalics), FontInstanceKey>,
    340    font_render_mode: Option<FontRenderMode>,
    341    snapshots: HashMap<String, Snapshot>,
    342    allow_mipmaps: bool,
    343 
    344    /// A HashMap that allows specifying a numeric id for clip and clip chains in YAML
    345    /// and having each of those ids correspond to a unique ClipId.
    346    user_clip_id_map: HashMap<u64, ClipId>,
    347    user_clipchain_id_map: HashMap<u64, ClipChainId>,
    348    user_spatial_id_map: HashMap<u64, SpatialId>,
    349 
    350    spatial_id_stack: Vec<SpatialId>,
    351 
    352    requested_frame: usize,
    353    built_frame: usize,
    354 
    355    yaml_string: String,
    356    keyframes: Option<Yaml>,
    357 
    358    external_image_handler: Option<Box<LocalExternalImageHandler>>,
    359 
    360    next_spatial_key: u64,
    361 }
    362 
    363 impl YamlFrameReader {
    364    pub fn new(yaml_path: &Path) -> YamlFrameReader {
    365        YamlFrameReader {
    366            watch_source: false,
    367            list_resources: false,
    368            yaml_path: yaml_path.to_owned(),
    369            aux_dir: yaml_path.parent().unwrap().to_owned(),
    370            frame_count: 0,
    371            display_lists: Vec::new(),
    372            scroll_offsets: HashMap::new(),
    373            fonts: HashMap::new(),
    374            font_instances: HashMap::new(),
    375            font_render_mode: None,
    376            snapshots: HashMap::new(),
    377            allow_mipmaps: false,
    378            image_map: HashMap::new(),
    379            user_clip_id_map: HashMap::new(),
    380            user_clipchain_id_map: HashMap::new(),
    381            user_spatial_id_map: HashMap::new(),
    382            spatial_id_stack: Vec::new(),
    383            yaml_string: String::new(),
    384            requested_frame: 0,
    385            built_frame: usize::MAX,
    386            keyframes: None,
    387            external_image_handler: Some(Box::new(LocalExternalImageHandler::new())),
    388            next_external_scroll_id: 1000,      // arbitrary to easily see in logs which are implicit
    389            next_spatial_key: 0,
    390        }
    391    }
    392 
    393    pub fn deinit(mut self, wrench: &mut Wrench) {
    394        let mut txn = Transaction::new();
    395 
    396        for (_, font_instance) in self.font_instances.drain() {
    397            txn.delete_font_instance(font_instance);
    398        }
    399 
    400        for (_, font) in self.fonts.drain() {
    401            txn.delete_font(font);
    402        }
    403 
    404        wrench.api.send_transaction(wrench.document_id, txn);
    405    }
    406 
    407    fn top_space(&self) -> SpatialId {
    408        *self.spatial_id_stack.last().unwrap()
    409    }
    410 
    411    pub fn yaml_path(&self) -> &PathBuf {
    412        &self.yaml_path
    413    }
    414 
    415    pub fn new_from_args(args: &clap::ArgMatches) -> YamlFrameReader {
    416        let yaml_file = args.value_of("INPUT").map(PathBuf::from).unwrap();
    417 
    418        let mut y = YamlFrameReader::new(&yaml_file);
    419 
    420        y.keyframes = args.value_of("keyframes").map(|path| {
    421            let mut file = File::open(&path).unwrap();
    422            let mut keyframes_string = String::new();
    423            file.read_to_string(&mut keyframes_string).unwrap();
    424            YamlLoader::load_from_str(&keyframes_string)
    425                .expect("Failed to parse keyframes file")
    426                .pop()
    427                .unwrap()
    428        });
    429        y.list_resources = args.is_present("list-resources");
    430        y.watch_source = args.is_present("watch");
    431        y
    432    }
    433 
    434    pub fn reset(&mut self) {
    435        self.scroll_offsets.clear();
    436        self.display_lists.clear();
    437    }
    438 
    439    fn build(&mut self, wrench: &mut Wrench) {
    440        let yaml = YamlLoader::load_from_str(&self.yaml_string)
    441            .map(|mut yaml| {
    442                assert_eq!(yaml.len(), 1);
    443                yaml.pop().unwrap()
    444            })
    445            .expect("Failed to parse YAML file");
    446 
    447        self.reset();
    448 
    449        if let Some(pipelines) = yaml["pipelines"].as_vec() {
    450            for pipeline in pipelines {
    451                let pipeline_id = pipeline["id"].as_pipeline_id().unwrap();
    452                let mut builder = DisplayListBuilder::new(pipeline_id);
    453                self.build_pipeline(wrench, &mut builder, pipeline_id, false, pipeline);
    454            }
    455        }
    456 
    457        let mut builder = DisplayListBuilder::new(wrench.root_pipeline_id);
    458 
    459        if let Some(frames) = yaml["frames"].as_vec() {
    460            for frame in frames {
    461                self.build_pipeline(wrench, &mut builder, wrench.root_pipeline_id, true, frame);
    462            }
    463        } else {
    464            let root_stacking_context = &yaml["root"];
    465            assert_ne!(*root_stacking_context, Yaml::BadValue);
    466            self.build_pipeline(wrench, &mut builder, wrench.root_pipeline_id, true, root_stacking_context);
    467        }
    468 
    469        // If replaying the same frame during interactive use, the frame gets rebuilt,
    470        // but the external image handler has already been consumed by the renderer.
    471        if let Some(external_image_handler) = self.external_image_handler.take() {
    472            wrench.renderer.set_external_image_handler(external_image_handler);
    473        }
    474    }
    475 
    476    fn build_pipeline(
    477        &mut self,
    478        wrench: &mut Wrench,
    479        builder: &mut DisplayListBuilder,
    480        pipeline_id: PipelineId,
    481        send_transaction: bool,
    482        yaml: &Yaml
    483    ) {
    484        let offscreen = yaml["offscreen"].as_bool().unwrap_or(false);
    485        // By default, present if send_transaction is set to true. Can be overridden
    486        // by a field in the pipeline's root.
    487        let present = !offscreen && yaml["present"].as_bool().unwrap_or(send_transaction);
    488 
    489        // Don't allow referencing clips between pipelines for now.
    490        self.user_clip_id_map.clear();
    491        self.user_clipchain_id_map.clear();
    492        self.user_spatial_id_map.clear();
    493        self.spatial_id_stack.clear();
    494        self.spatial_id_stack.push(SpatialId::root_scroll_node(pipeline_id));
    495 
    496        builder.begin();
    497        let mut info = CommonItemProperties {
    498            clip_rect: LayoutRect::zero(),
    499            clip_chain_id: ClipChainId::INVALID,
    500            spatial_id: SpatialId::new(0, PipelineId::dummy()),
    501            flags: PrimitiveFlags::default(),
    502        };
    503        self.add_stacking_context_from_yaml(builder, wrench, yaml, IsRoot(true), &mut info);
    504        let (pipeline, payload) = builder.end();
    505        self.display_lists.push(DisplayList {
    506            pipeline,
    507            payload,
    508            present,
    509            send_transaction,
    510            render_offscreen: offscreen,
    511        });
    512 
    513        assert_eq!(self.spatial_id_stack.len(), 1);
    514    }
    515 
    516    fn to_clip_chain_id(
    517        &self,
    518        item: &Yaml,
    519        builder: &mut DisplayListBuilder,
    520    ) -> Option<ClipChainId> {
    521        match *item {
    522            Yaml::Integer(value) => {
    523                Some(self.user_clipchain_id_map[&(value as u64)])
    524            }
    525            Yaml::Array(ref array) => {
    526                let clip_ids: Vec<ClipId> = array
    527                    .iter()
    528                    .map(|id| {
    529                        let id = id.as_i64().expect("invalid clip id") as u64;
    530                        self.user_clip_id_map[&id]
    531                    })
    532                    .collect();
    533 
    534                Some(builder.define_clip_chain(None, clip_ids))
    535            }
    536            _ => None,
    537        }
    538    }
    539 
    540    fn to_spatial_id(&self, item: &Yaml, pipeline_id: PipelineId) -> Option<SpatialId> {
    541        match *item {
    542            Yaml::Integer(value) => Some(self.user_spatial_id_map[&(value as u64)]),
    543            Yaml::String(ref id_string) if id_string == "root-reference-frame" =>
    544                Some(SpatialId::root_reference_frame(pipeline_id)),
    545            Yaml::String(ref id_string) if id_string == "root-scroll-node" =>
    546                Some(SpatialId::root_scroll_node(pipeline_id)),
    547            Yaml::BadValue => None,
    548            _ => {
    549                println!("Unable to parse SpatialId {:?}", item);
    550                None
    551            }
    552        }
    553    }
    554 
    555    fn add_clip_id_mapping(&mut self, numeric_id: u64, real_id: ClipId) {
    556        assert_ne!(numeric_id, 0, "id=0 is reserved for the root clip");
    557        self.user_clip_id_map.insert(numeric_id, real_id);
    558    }
    559 
    560    fn add_clip_chain_id_mapping(&mut self, numeric_id: u64, real_id: ClipChainId) {
    561        assert_ne!(numeric_id, 0, "id=0 is reserved for the root clip-chain");
    562        self.user_clipchain_id_map.insert(numeric_id, real_id);
    563    }
    564 
    565    fn add_spatial_id_mapping(&mut self, numeric_id: u64, real_id: SpatialId) {
    566        assert_ne!(numeric_id, 0, "id=0 is reserved for the root reference frame");
    567        assert_ne!(numeric_id, 1, "id=1 is reserved for the root scroll node");
    568        self.user_spatial_id_map.insert(numeric_id, real_id);
    569    }
    570 
    571    fn to_hit_testing_tag(&self, item: &Yaml) -> Option<ItemTag> {
    572        match *item {
    573            Yaml::Array(ref array) if array.len() == 2 => {
    574                match (array[0].as_i64(), array[1].as_i64()) {
    575                    (Some(first), Some(second)) => Some((first as u64, second as u16)),
    576                    _ => None,
    577                }
    578            }
    579            _ => None,
    580        }
    581 
    582    }
    583 
    584    fn add_or_get_image(
    585        &mut self,
    586        file: &Path,
    587        tiling: Option<i64>,
    588        item: &Yaml,
    589        wrench: &mut Wrench,
    590    ) -> (ImageKey, LayoutSize) {
    591        let key = (file.to_owned(), tiling);
    592        if let Some(k) = self.image_map.get(&key) {
    593            return *k;
    594        }
    595 
    596        if self.list_resources { println!("{}", file.to_string_lossy()); }
    597        let (descriptor, image_data) = match image::open(file) {
    598            Ok(image) => {
    599                let (image_width, image_height) = image.dimensions();
    600                let (format, bytes) = match image {
    601                    image::DynamicImage::ImageLuma8(_) => {
    602                        (ImageFormat::R8, image.to_bytes())
    603                    }
    604                    image::DynamicImage::ImageRgba8(_) => {
    605                        let mut pixels = image.to_bytes();
    606                        premultiply(pixels.as_mut_slice());
    607                        (ImageFormat::BGRA8, pixels)
    608                    }
    609                    image::DynamicImage::ImageRgb8(_) => {
    610                        let bytes = image.to_bytes();
    611                        let mut pixels = Vec::with_capacity(image_width as usize * image_height as usize * 4);
    612                        for bgr in bytes.chunks(3) {
    613                            pixels.extend_from_slice(&[
    614                                bgr[2],
    615                                bgr[1],
    616                                bgr[0],
    617                                0xff
    618                            ]);
    619                        }
    620                        (ImageFormat::BGRA8, pixels)
    621                    }
    622                    _ => panic!("We don't support whatever your crazy image type is, come on"),
    623                };
    624                let mut flags = ImageDescriptorFlags::empty();
    625                if is_image_opaque(format, &bytes[..]) {
    626                    flags |= ImageDescriptorFlags::IS_OPAQUE;
    627                }
    628                if self.allow_mipmaps {
    629                    flags |= ImageDescriptorFlags::ALLOW_MIPMAPS;
    630                }
    631                let descriptor = ImageDescriptor::new(
    632                    image_width as i32,
    633                    image_height as i32,
    634                    format,
    635                    flags,
    636                );
    637                let data = ImageData::new(bytes);
    638                (descriptor, data)
    639            }
    640            _ => {
    641                // This is a hack but it is convenient when generating test cases and avoids
    642                // bloating the repository.
    643                match parse_function(
    644                    file.components()
    645                        .last()
    646                        .unwrap()
    647                        .as_os_str()
    648                        .to_str()
    649                        .unwrap(),
    650                ) {
    651                    ("xy-gradient", args, _) => generate_xy_gradient_image(
    652                        args.get(0).unwrap_or(&"1000").parse::<u32>().unwrap(),
    653                        args.get(1).unwrap_or(&"1000").parse::<u32>().unwrap(),
    654                    ),
    655                    ("solid-color", args, _) => generate_solid_color_image(
    656                        args.get(0).unwrap_or(&"255").parse::<u8>().unwrap(),
    657                        args.get(1).unwrap_or(&"255").parse::<u8>().unwrap(),
    658                        args.get(2).unwrap_or(&"255").parse::<u8>().unwrap(),
    659                        args.get(3).unwrap_or(&"255").parse::<u8>().unwrap(),
    660                        args.get(4).unwrap_or(&"1000").parse::<u32>().unwrap(),
    661                        args.get(5).unwrap_or(&"1000").parse::<u32>().unwrap(),
    662                    ),
    663                    (name @ "transparent-checkerboard", args, _) |
    664                    (name @ "checkerboard", args, _) => {
    665                        let border = args.get(0).unwrap_or(&"4").parse::<u32>().unwrap();
    666 
    667                        let (x_size, y_size, x_count, y_count) = match args.len() {
    668                            3 => {
    669                                let size = args.get(1).unwrap_or(&"32").parse::<u32>().unwrap();
    670                                let count = args.get(2).unwrap_or(&"8").parse::<u32>().unwrap();
    671                                (size, size, count, count)
    672                            }
    673                            5 => {
    674                                let x_size = args.get(1).unwrap_or(&"32").parse::<u32>().unwrap();
    675                                let y_size = args.get(2).unwrap_or(&"32").parse::<u32>().unwrap();
    676                                let x_count = args.get(3).unwrap_or(&"8").parse::<u32>().unwrap();
    677                                let y_count = args.get(4).unwrap_or(&"8").parse::<u32>().unwrap();
    678                                (x_size, y_size, x_count, y_count)
    679                            }
    680                            _ => {
    681                                panic!("invalid checkerboard function");
    682                            }
    683                        };
    684 
    685                        let kind = if name == "transparent-checkerboard" {
    686                            CheckerboardKind::BlackTransparent
    687                        } else {
    688                            CheckerboardKind::BlackGrey
    689                        };
    690 
    691                        generate_checkerboard_image(
    692                            border,
    693                            x_size,
    694                            y_size,
    695                            x_count,
    696                            y_count,
    697                            kind,
    698                        )
    699                    }
    700                    ("snapshot", args, _) => {
    701                        let snapshot = self.snapshots
    702                            .get(args[0])
    703                            .expect("Missing snapshot");
    704                        return (
    705                            snapshot.key.as_image(),
    706                            snapshot.size,
    707                        );
    708                    }
    709                    _ => {
    710                        panic!("Failed to load image {:?}", file.to_str());
    711                    }
    712                }
    713            }
    714        };
    715        let tiling = tiling.map(|tile_size| tile_size as u16);
    716        let image_key = wrench.api.generate_image_key();
    717        let mut txn = Transaction::new();
    718 
    719        let external = item["external"].as_bool().unwrap_or(false);
    720        if external {
    721            // This indicates we want to simulate an external texture,
    722            // ensure it gets created as such
    723            let external_target = match item["external-target"].as_str() {
    724                Some("2d") => ImageBufferKind::Texture2D,
    725                Some("rect") => ImageBufferKind::TextureRect,
    726                Some(t) => panic!("Unsupported external texture target: {}", t),
    727                None => ImageBufferKind::Texture2D,
    728            };
    729 
    730            let external_image_data =
    731                self.external_image_handler.as_mut().unwrap().add_image(
    732                    &wrench.renderer.device,
    733                    descriptor,
    734                    external_target,
    735                    image_data
    736                );
    737            txn.add_image(image_key, descriptor, external_image_data, tiling);
    738        } else {
    739            txn.add_image(image_key, descriptor, image_data, tiling);
    740        }
    741 
    742        wrench.api.send_transaction(wrench.document_id, txn);
    743        let val = (
    744            image_key,
    745            LayoutSize::new(descriptor.size.width as f32, descriptor.size.height as f32),
    746        );
    747        self.image_map.insert(key, val);
    748        val
    749    }
    750 
    751    fn get_or_create_font(&mut self, desc: FontDescriptor, wrench: &mut Wrench) -> FontKey {
    752        let list_resources = self.list_resources;
    753        *self.fonts
    754            .entry(desc.clone())
    755            .or_insert_with(|| match desc {
    756                FontDescriptor::Path {
    757                    ref path,
    758                    font_index,
    759                } => {
    760                    if list_resources { println!("{}", path.to_string_lossy()); }
    761                    let mut file = File::open(path).expect("Couldn't open font file");
    762                    let mut bytes = vec![];
    763                    file.read_to_end(&mut bytes)
    764                        .expect("failed to read font file");
    765                    wrench.font_key_from_bytes(bytes, font_index)
    766                }
    767                FontDescriptor::Family { ref name } => wrench.font_key_from_name(name),
    768                FontDescriptor::Properties {
    769                    ref family,
    770                    weight,
    771                    style,
    772                    stretch,
    773                } => wrench.font_key_from_properties(family, weight, style, stretch),
    774            })
    775    }
    776 
    777    pub fn allow_mipmaps(&mut self, allow_mipmaps: bool) {
    778        self.allow_mipmaps = allow_mipmaps;
    779    }
    780 
    781    pub fn set_font_render_mode(&mut self, render_mode: Option<FontRenderMode>) {
    782        self.font_render_mode = render_mode;
    783    }
    784 
    785    fn get_or_create_font_instance(
    786        &mut self,
    787        font_key: FontKey,
    788        size: f32,
    789        flags: FontInstanceFlags,
    790        synthetic_italics: SyntheticItalics,
    791        wrench: &mut Wrench,
    792    ) -> FontInstanceKey {
    793        let font_render_mode = self.font_render_mode;
    794 
    795        *self.font_instances
    796            .entry((font_key, size.into(), flags, synthetic_italics))
    797            .or_insert_with(|| {
    798                wrench.add_font_instance(
    799                    font_key,
    800                    size,
    801                    flags,
    802                    font_render_mode,
    803                    synthetic_italics,
    804                )
    805            })
    806    }
    807 
    808    fn as_image_mask(&mut self, item: &Yaml, wrench: &mut Wrench) -> Option<ImageMask> {
    809        item.as_hash()?;
    810 
    811        let tiling = item["tile-size"].as_i64();
    812 
    813        let (image_key, image_dims) = match item["image"].as_str() {
    814            Some("invalid") => (ImageKey::DUMMY, LayoutSize::new(100.0, 100.0)),
    815            Some(filename) => {
    816                let mut file = self.aux_dir.clone();
    817                file.push(filename);
    818                self.add_or_get_image(&file, tiling, item, wrench)
    819            }
    820            None => {
    821                warn!("No image provided for the image-mask!");
    822                return None;
    823            }
    824        };
    825 
    826        let image_rect = item["rect"]
    827            .as_rect()
    828            .unwrap_or_else(|| LayoutRect::from_size(image_dims));
    829        Some(ImageMask {
    830            image: image_key,
    831            rect: image_rect,
    832        })
    833    }
    834 
    835    fn handle_rect(
    836        &self,
    837        dl: &mut DisplayListBuilder,
    838        item: &Yaml,
    839        info: &CommonItemProperties,
    840    ) {
    841        let bounds_key = if item["type"].is_badvalue() {
    842            "rect"
    843        } else {
    844            "bounds"
    845        };
    846 
    847        let bounds = self.resolve_rect(&item[bounds_key]);
    848        let color = self.resolve_colorf(&item["color"]).unwrap_or(ColorF::BLACK);
    849        dl.push_rect(info, bounds, color);
    850    }
    851 
    852    fn handle_hit_test(
    853        &mut self,
    854        dl: &mut DisplayListBuilder,
    855        item: &Yaml,
    856        info: &mut CommonItemProperties,
    857    ) {
    858        info.clip_rect = try_intersect!(
    859            item["bounds"].as_rect().expect("hit-test type must have bounds"),
    860            &info.clip_rect
    861        );
    862 
    863        if let Some(tag) = self.to_hit_testing_tag(&item["hit-testing-tag"]) {
    864            dl.push_hit_test(
    865                info.clip_rect,
    866                info.clip_chain_id,
    867                info.spatial_id,
    868                info.flags,
    869                tag,
    870            );
    871        }
    872    }
    873 
    874    fn handle_line(
    875        &mut self,
    876        dl: &mut DisplayListBuilder,
    877        item: &Yaml,
    878        info: &mut CommonItemProperties,
    879    ) {
    880        let color = item["color"].as_colorf().unwrap_or(ColorF::BLACK);
    881        let orientation = item["orientation"]
    882            .as_str()
    883            .and_then(LineOrientation::from_str)
    884            .expect("line must have orientation");
    885        let style = item["style"]
    886            .as_str()
    887            .and_then(LineStyle::from_str)
    888            .expect("line must have style");
    889 
    890        let wavy_line_thickness = if let LineStyle::Wavy = style {
    891            item["thickness"].as_f32().expect("wavy lines must have a thickness")
    892        } else {
    893            0.0
    894        };
    895 
    896        let area = if item["baseline"].is_badvalue() {
    897            let bounds_key = if item["type"].is_badvalue() {
    898                "rect"
    899            } else {
    900                "bounds"
    901            };
    902 
    903            item[bounds_key]
    904                .as_rect()
    905                .expect("line type must have bounds")
    906        } else {
    907            // Legacy line representation
    908            let baseline = item["baseline"].as_f32().expect("line must have baseline");
    909            let start = item["start"].as_f32().expect("line must have start");
    910            let end = item["end"].as_f32().expect("line must have end");
    911            let width = item["width"].as_f32().expect("line must have width");
    912 
    913            match orientation {
    914                LineOrientation::Horizontal => {
    915                    LayoutRect::from_origin_and_size(
    916                        LayoutPoint::new(start, baseline),
    917                        LayoutSize::new(end - start, width),
    918                    )
    919                }
    920                LineOrientation::Vertical => {
    921                    LayoutRect::from_origin_and_size(
    922                        LayoutPoint::new(baseline, start),
    923                        LayoutSize::new(width, end - start),
    924                    )
    925                }
    926            }
    927            };
    928 
    929        dl.push_line(
    930            info,
    931            &area,
    932            wavy_line_thickness,
    933            orientation,
    934            &color,
    935            style,
    936        );
    937    }
    938 
    939    fn handle_gradient(
    940        &mut self,
    941        dl: &mut DisplayListBuilder,
    942        item: &Yaml,
    943        info: &mut CommonItemProperties,
    944    ) {
    945        let bounds_key = if item["type"].is_badvalue() {
    946            "gradient"
    947        } else {
    948            "bounds"
    949        };
    950        let bounds = item[bounds_key]
    951            .as_rect()
    952            .expect("gradient must have bounds");
    953 
    954        let gradient = item.as_gradient(dl);
    955        let tile_size = item["tile-size"].as_size().unwrap_or_else(|| bounds.size());
    956        let tile_spacing = item["tile-spacing"].as_size().unwrap_or_else(LayoutSize::zero);
    957 
    958        dl.push_gradient(
    959            info,
    960            bounds,
    961            gradient,
    962            tile_size,
    963            tile_spacing
    964        );
    965    }
    966 
    967    fn handle_radial_gradient(
    968        &mut self,
    969        dl: &mut DisplayListBuilder,
    970        item: &Yaml,
    971        info: &mut CommonItemProperties,
    972    ) {
    973        let bounds_key = if item["type"].is_badvalue() {
    974            "radial-gradient"
    975        } else {
    976            "bounds"
    977        };
    978        let bounds = item[bounds_key]
    979            .as_rect()
    980            .expect("radial gradient must have bounds");
    981        let gradient = item.as_radial_gradient(dl);
    982        let tile_size = item["tile-size"].as_size().unwrap_or_else(|| bounds.size());
    983        let tile_spacing = item["tile-spacing"].as_size().unwrap_or_else(LayoutSize::zero);
    984 
    985        dl.push_radial_gradient(
    986            info,
    987            bounds,
    988            gradient,
    989            tile_size,
    990            tile_spacing,
    991        );
    992    }
    993 
    994    fn handle_conic_gradient(
    995        &mut self,
    996        dl: &mut DisplayListBuilder,
    997        item: &Yaml,
    998        info: &mut CommonItemProperties,
    999    ) {
   1000        let bounds_key = if item["type"].is_badvalue() {
   1001            "conic-gradient"
   1002        } else {
   1003            "bounds"
   1004        };
   1005        let bounds = item[bounds_key]
   1006            .as_rect()
   1007            .expect("conic gradient must have bounds");
   1008        let gradient = item.as_conic_gradient(dl);
   1009        let tile_size = item["tile-size"].as_size().unwrap_or_else(|| bounds.size());
   1010        let tile_spacing = item["tile-spacing"].as_size().unwrap_or_else(LayoutSize::zero);
   1011 
   1012        dl.push_conic_gradient(
   1013            info,
   1014            bounds,
   1015            gradient,
   1016            tile_size,
   1017            tile_spacing,
   1018        );
   1019    }
   1020 
   1021    fn handle_border(
   1022        &mut self,
   1023        dl: &mut DisplayListBuilder,
   1024        wrench: &mut Wrench,
   1025        item: &Yaml,
   1026        info: &mut CommonItemProperties,
   1027    ) {
   1028        let bounds_key = if item["type"].is_badvalue() {
   1029            "border"
   1030        } else {
   1031            "bounds"
   1032        };
   1033        let bounds = item[bounds_key].as_rect().expect("borders must have bounds");
   1034        let widths = item["width"]
   1035            .as_vec_f32()
   1036            .expect("borders must have width(s)");
   1037        let widths = broadcast(&widths, 4);
   1038        let widths = LayoutSideOffsets::new(widths[0], widths[3], widths[2], widths[1]);
   1039        let border_details = if let Some(border_type) = item["border-type"].as_str() {
   1040            match border_type {
   1041                "normal" => {
   1042                    let colors = item["color"]
   1043                        .as_vec_colorf()
   1044                        .expect("borders must have color(s)");
   1045                    let styles = item["style"]
   1046                        .as_vec_string()
   1047                        .expect("borders must have style(s)");
   1048                    let styles = styles
   1049                        .iter()
   1050                        .map(|s| match s.as_str() {
   1051                            "none" => BorderStyle::None,
   1052                            "solid" => BorderStyle::Solid,
   1053                            "double" => BorderStyle::Double,
   1054                            "dotted" => BorderStyle::Dotted,
   1055                            "dashed" => BorderStyle::Dashed,
   1056                            "hidden" => BorderStyle::Hidden,
   1057                            "ridge" => BorderStyle::Ridge,
   1058                            "inset" => BorderStyle::Inset,
   1059                            "outset" => BorderStyle::Outset,
   1060                            "groove" => BorderStyle::Groove,
   1061                            s => {
   1062                                panic!("Unknown border style '{}'", s);
   1063                            }
   1064                        })
   1065                        .collect::<Vec<BorderStyle>>();
   1066                    let radius = item["radius"]
   1067                        .as_border_radius()
   1068                        .unwrap_or_else(BorderRadius::zero);
   1069 
   1070                    let colors = broadcast(&colors, 4);
   1071                    let styles = broadcast(&styles, 4);
   1072 
   1073                    let top = BorderSide {
   1074                        color: colors[0],
   1075                        style: styles[0],
   1076                    };
   1077                    let right = BorderSide {
   1078                        color: colors[1],
   1079                        style: styles[1],
   1080                    };
   1081                    let bottom = BorderSide {
   1082                        color: colors[2],
   1083                        style: styles[2],
   1084                    };
   1085                    let left = BorderSide {
   1086                        color: colors[3],
   1087                        style: styles[3],
   1088                    };
   1089                    let do_aa = item["do_aa"].as_bool().unwrap_or(true);
   1090                    Some(BorderDetails::Normal(NormalBorder {
   1091                        top,
   1092                        left,
   1093                        bottom,
   1094                        right,
   1095                        radius,
   1096                        do_aa,
   1097                    }))
   1098                }
   1099                "image" | "gradient" | "radial-gradient" | "conic-gradient" => {
   1100                    let image_width = item["image-width"]
   1101                        .as_i64()
   1102                        .unwrap_or(bounds.width() as i64);
   1103                    let image_height = item["image-height"]
   1104                        .as_i64()
   1105                        .unwrap_or(bounds.height() as i64);
   1106                    let fill = item["fill"].as_bool().unwrap_or(false);
   1107 
   1108                    let slice = if let Some(slice) = item["slice"].as_vec_u32() {
   1109                        broadcast(&slice, 4)
   1110                    } else {
   1111                        vec![widths.top as u32, widths.left as u32, widths.bottom as u32, widths.right as u32]
   1112                    };
   1113 
   1114                    let repeat_horizontal = match item["repeat-horizontal"]
   1115                        .as_str()
   1116                        .unwrap_or("stretch")
   1117                    {
   1118                        "stretch" => RepeatMode::Stretch,
   1119                        "repeat" => RepeatMode::Repeat,
   1120                        "round" => RepeatMode::Round,
   1121                        "space" => RepeatMode::Space,
   1122                        s => panic!("Unknown box border image repeat mode {}", s),
   1123                    };
   1124                    let repeat_vertical = match item["repeat-vertical"]
   1125                        .as_str()
   1126                        .unwrap_or("stretch")
   1127                    {
   1128                        "stretch" => RepeatMode::Stretch,
   1129                        "repeat" => RepeatMode::Repeat,
   1130                        "round" => RepeatMode::Round,
   1131                        "space" => RepeatMode::Space,
   1132                        s => panic!("Unknown box border image repeat mode {}", s),
   1133                    };
   1134                    let source = match border_type {
   1135                        "image" => {
   1136                            let file = rsrc_path(&item["image-source"], &self.aux_dir);
   1137                            let (image_key, _) = self
   1138                                .add_or_get_image(&file, None, item, wrench);
   1139                            NinePatchBorderSource::Image(image_key, ImageRendering::Auto)
   1140                        }
   1141                        "gradient" => {
   1142                            let gradient = item.as_gradient(dl);
   1143                            NinePatchBorderSource::Gradient(gradient)
   1144                        }
   1145                        "radial-gradient" => {
   1146                            let gradient = item.as_radial_gradient(dl);
   1147                            NinePatchBorderSource::RadialGradient(gradient)
   1148                        }
   1149                        "conic-gradient" => {
   1150                            let gradient = item.as_conic_gradient(dl);
   1151                            NinePatchBorderSource::ConicGradient(gradient)
   1152                        }
   1153                        _ => unreachable!("Unexpected border type"),
   1154                    };
   1155 
   1156                    Some(BorderDetails::NinePatch(NinePatchBorder {
   1157                        source,
   1158                        width: image_width as i32,
   1159                        height: image_height as i32,
   1160                        slice: SideOffsets2D::new(slice[0] as i32, slice[1] as i32, slice[2] as i32, slice[3] as i32),
   1161                        fill,
   1162                        repeat_horizontal,
   1163                        repeat_vertical,
   1164                    }))
   1165                }
   1166                _ => {
   1167                    println!("Unable to parse border {:?}", item);
   1168                    None
   1169                }
   1170            }
   1171        } else {
   1172            println!("Unable to parse border {:?}", item);
   1173            None
   1174        };
   1175        if let Some(details) = border_details {
   1176            dl.push_border(info, bounds, widths, details);
   1177        }
   1178    }
   1179 
   1180    fn handle_box_shadow(
   1181        &mut self,
   1182        dl: &mut DisplayListBuilder,
   1183        item: &Yaml,
   1184        info: &mut CommonItemProperties,
   1185    ) {
   1186        let bounds_key = if item["type"].is_badvalue() {
   1187            "box-shadow"
   1188        } else {
   1189            "bounds"
   1190        };
   1191        let bounds = item[bounds_key]
   1192            .as_rect()
   1193            .expect("box shadow must have bounds");
   1194        let box_bounds = item["box-bounds"].as_rect().unwrap_or(bounds);
   1195        let offset = self.resolve_vector(&item["offset"], LayoutVector2D::zero());
   1196        let color = item["color"]
   1197            .as_colorf()
   1198            .unwrap_or_else(|| ColorF::new(0.0, 0.0, 0.0, 1.0));
   1199        let blur_radius = item["blur-radius"].as_force_f32().unwrap_or(0.0);
   1200        let spread_radius = item["spread-radius"].as_force_f32().unwrap_or(0.0);
   1201        let border_radius = item["border-radius"]
   1202            .as_border_radius()
   1203            .unwrap_or_else(BorderRadius::zero);
   1204        let clip_mode = if let Some(mode) = item["clip-mode"].as_str() {
   1205            match mode {
   1206                "outset" => BoxShadowClipMode::Outset,
   1207                "inset" => BoxShadowClipMode::Inset,
   1208                s => panic!("Unknown box shadow clip mode {}", s),
   1209            }
   1210        } else {
   1211            BoxShadowClipMode::Outset
   1212        };
   1213 
   1214        dl.push_box_shadow(
   1215            info,
   1216            box_bounds,
   1217            offset,
   1218            color,
   1219            blur_radius,
   1220            spread_radius,
   1221            border_radius,
   1222            clip_mode,
   1223        );
   1224    }
   1225 
   1226    fn handle_yuv_image(
   1227        &mut self,
   1228        dl: &mut DisplayListBuilder,
   1229        wrench: &mut Wrench,
   1230        item: &Yaml,
   1231        info: &mut CommonItemProperties,
   1232    ) {
   1233        // TODO(gw): Support other YUV color depth and spaces.
   1234        let color_depth = ColorDepth::Color8;
   1235        let color_space = YuvColorSpace::Rec709;
   1236        let color_range = ColorRange::Limited;
   1237 
   1238        let yuv_data = match item["format"].as_str().expect("no format supplied") {
   1239            "planar" => {
   1240                let y_path = rsrc_path(&item["src-y"], &self.aux_dir);
   1241                let (y_key, _) = self.add_or_get_image(&y_path, None, item, wrench);
   1242 
   1243                let u_path = rsrc_path(&item["src-u"], &self.aux_dir);
   1244                let (u_key, _) = self.add_or_get_image(&u_path, None, item, wrench);
   1245 
   1246                let v_path = rsrc_path(&item["src-v"], &self.aux_dir);
   1247                let (v_key, _) = self.add_or_get_image(&v_path, None, item, wrench);
   1248 
   1249                YuvData::PlanarYCbCr(y_key, u_key, v_key)
   1250            }
   1251            "nv12" => {
   1252                let y_path = rsrc_path(&item["src-y"], &self.aux_dir);
   1253                let (y_key, _) = self.add_or_get_image(&y_path, None, item, wrench);
   1254 
   1255                let uv_path = rsrc_path(&item["src-uv"], &self.aux_dir);
   1256                let (uv_key, _) = self.add_or_get_image(&uv_path, None, item, wrench);
   1257 
   1258                YuvData::NV12(y_key, uv_key)
   1259            }
   1260            "p010" => {
   1261                let y_path = rsrc_path(&item["src-y"], &self.aux_dir);
   1262                let (y_key, _) = self.add_or_get_image(&y_path, None, item, wrench);
   1263 
   1264                let uv_path = rsrc_path(&item["src-uv"], &self.aux_dir);
   1265                let (uv_key, _) = self.add_or_get_image(&uv_path, None, item, wrench);
   1266 
   1267                YuvData::P010(y_key, uv_key)
   1268            }
   1269            "nv16" => {
   1270                let y_path = rsrc_path(&item["src-y"], &self.aux_dir);
   1271                let (y_key, _) = self.add_or_get_image(&y_path, None, item, wrench);
   1272 
   1273                let uv_path = rsrc_path(&item["src-uv"], &self.aux_dir);
   1274                let (uv_key, _) = self.add_or_get_image(&uv_path, None, item, wrench);
   1275 
   1276                YuvData::NV16(y_key, uv_key)
   1277            }
   1278            "interleaved" => {
   1279                let yuv_path = rsrc_path(&item["src"], &self.aux_dir);
   1280                let (yuv_key, _) = self.add_or_get_image(&yuv_path, None, item, wrench);
   1281 
   1282                YuvData::InterleavedYCbCr(yuv_key)
   1283            }
   1284            _ => {
   1285                panic!("unexpected yuv format");
   1286            }
   1287        };
   1288 
   1289        let bounds = item["bounds"].as_vec_f32().unwrap();
   1290        let bounds = LayoutRect::from_origin_and_size(
   1291            LayoutPoint::new(bounds[0], bounds[1]),
   1292            LayoutSize::new(bounds[2], bounds[3]),
   1293        );
   1294 
   1295        dl.push_yuv_image(
   1296            info,
   1297            bounds,
   1298            yuv_data,
   1299            color_depth,
   1300            color_space,
   1301            color_range,
   1302            ImageRendering::Auto,
   1303        );
   1304    }
   1305 
   1306    fn handle_image(
   1307        &mut self,
   1308        dl: &mut DisplayListBuilder,
   1309        wrench: &mut Wrench,
   1310        item: &Yaml,
   1311        info: &mut CommonItemProperties,
   1312    ) {
   1313        let filename = &item[if item["type"].is_badvalue() {
   1314                                 "image"
   1315                             } else {
   1316                                 "src"
   1317                             }];
   1318        let tiling = item["tile-size"].as_i64();
   1319 
   1320        let file = rsrc_path(filename, &self.aux_dir);
   1321        let (image_key, image_dims) =
   1322            self.add_or_get_image(&file, tiling, item, wrench);
   1323 
   1324        let bounds_raws = item["bounds"].as_vec_f32().unwrap();
   1325        let bounds = if bounds_raws.len() == 2 {
   1326            LayoutRect::from_origin_and_size(LayoutPoint::new(bounds_raws[0], bounds_raws[1]), image_dims)
   1327        } else if bounds_raws.len() == 4 {
   1328            LayoutRect::from_origin_and_size(
   1329                LayoutPoint::new(bounds_raws[0], bounds_raws[1]),
   1330                LayoutSize::new(bounds_raws[2], bounds_raws[3]),
   1331            )
   1332        } else {
   1333            panic!(
   1334                "image expected 2 or 4 values in bounds, got '{:?}'",
   1335                item["bounds"]
   1336            );
   1337        };
   1338        let rendering = match item["rendering"].as_str() {
   1339            Some("auto") | None => ImageRendering::Auto,
   1340            Some("crisp-edges") => ImageRendering::CrispEdges,
   1341            Some("pixelated") => ImageRendering::Pixelated,
   1342            Some(_) => panic!(
   1343                "ImageRendering can be auto, crisp-edges, or pixelated -- got {:?}",
   1344                item
   1345            ),
   1346        };
   1347        let alpha_type = match item["alpha-type"].as_str() {
   1348            Some("premultiplied-alpha") | None => AlphaType::PremultipliedAlpha,
   1349            Some("alpha") => AlphaType::Alpha,
   1350            Some(_) => panic!(
   1351                "AlphaType can be premultiplied-alpha or alpha -- got {:?}",
   1352                item
   1353            ),
   1354        };
   1355        let color = item["color"]
   1356            .as_colorf()
   1357            .unwrap_or_else(|| ColorF::WHITE);
   1358        let stretch_size = item["stretch-size"].as_size();
   1359        let tile_spacing = item["tile-spacing"].as_size();
   1360        if stretch_size.is_none() && tile_spacing.is_none() {
   1361            dl.push_image(
   1362                info,
   1363                bounds,
   1364                rendering,
   1365                alpha_type,
   1366                image_key,
   1367                color,
   1368           );
   1369        } else {
   1370            dl.push_repeating_image(
   1371                info,
   1372                bounds,
   1373                stretch_size.unwrap_or(image_dims),
   1374                tile_spacing.unwrap_or_else(LayoutSize::zero),
   1375                rendering,
   1376                alpha_type,
   1377                image_key,
   1378                color,
   1379           );
   1380        }
   1381    }
   1382 
   1383    fn handle_text(
   1384        &mut self,
   1385        dl: &mut DisplayListBuilder,
   1386        wrench: &mut Wrench,
   1387        item: &Yaml,
   1388        info: &mut CommonItemProperties,
   1389    ) {
   1390        let size = item["size"].as_pt_to_f32().unwrap_or(16.0);
   1391        let color = item["color"].as_colorf().unwrap_or(ColorF::BLACK);
   1392        let synthetic_italics = if let Some(angle) = item["synthetic-italics"].as_f32() {
   1393            SyntheticItalics::from_degrees(angle)
   1394        } else if item["synthetic-italics"].as_bool().unwrap_or(false) {
   1395            SyntheticItalics::enabled()
   1396        } else {
   1397            SyntheticItalics::disabled()
   1398        };
   1399 
   1400        let mut flags = FontInstanceFlags::empty();
   1401        if item["synthetic-bold"].as_bool().unwrap_or(false) {
   1402            flags |= FontInstanceFlags::SYNTHETIC_BOLD;
   1403        }
   1404        if item["embedded-bitmaps"].as_bool().unwrap_or(false) {
   1405            flags |= FontInstanceFlags::EMBEDDED_BITMAPS;
   1406        }
   1407        if item["transpose"].as_bool().unwrap_or(false) {
   1408            flags |= FontInstanceFlags::TRANSPOSE;
   1409        }
   1410        if item["flip-x"].as_bool().unwrap_or(false) {
   1411            flags |= FontInstanceFlags::FLIP_X;
   1412        }
   1413        if item["flip-y"].as_bool().unwrap_or(false) {
   1414            flags |= FontInstanceFlags::FLIP_Y;
   1415        }
   1416 
   1417        assert!(
   1418            item["blur-radius"].is_badvalue(),
   1419            "text no longer has a blur radius, use PushShadow and PopAllShadows"
   1420        );
   1421 
   1422        let desc = FontDescriptor::from_yaml(item, &self.aux_dir);
   1423        let font_key = self.get_or_create_font(desc, wrench);
   1424        let font_instance_key = self.get_or_create_font_instance(font_key,
   1425                                                                 size,
   1426                                                                 flags,
   1427                                                                 synthetic_italics,
   1428                                                                 wrench);
   1429 
   1430        assert!(
   1431            !(item["glyphs"].is_badvalue() && item["text"].is_badvalue()),
   1432            "text item had neither text nor glyphs!"
   1433        );
   1434 
   1435        let (glyphs, rect) = if item["text"].is_badvalue() {
   1436            // if glyphs are specified, then the glyph positions can have the
   1437            // origin baked in.
   1438            let origin = item["origin"]
   1439                .as_point()
   1440                .unwrap_or(LayoutPoint::new(0.0, 0.0));
   1441            let glyph_indices = item["glyphs"].as_vec_u32().unwrap();
   1442            let glyph_offsets = item["offsets"].as_vec_f32().unwrap();
   1443            assert_eq!(glyph_offsets.len(), glyph_indices.len() * 2);
   1444 
   1445            let glyphs = glyph_indices
   1446                .iter()
   1447                .enumerate()
   1448                .map(|k| {
   1449                    GlyphInstance {
   1450                        index: *k.1,
   1451                        // In the future we want to change the API to be relative, eliminating this
   1452                        point: LayoutPoint::new(
   1453                            origin.x + glyph_offsets[k.0 * 2],
   1454                            origin.y + glyph_offsets[k.0 * 2 + 1],
   1455                        ),
   1456                    }
   1457                })
   1458                .collect::<Vec<_>>();
   1459            // TODO(gw): We could optionally use the WR API to query glyph dimensions
   1460            //           here and calculate the bounding region here if we want to.
   1461            let rect = item["bounds"]
   1462                .as_rect()
   1463                .expect("Text items with glyphs require bounds [for now]");
   1464            (glyphs, rect)
   1465        } else {
   1466            let text = item["text"].as_str().unwrap();
   1467            let origin = item["origin"]
   1468                .as_point()
   1469                .expect("origin required for text without glyphs");
   1470            let (glyph_indices, glyph_positions, bounds) = wrench.layout_simple_ascii(
   1471                font_key,
   1472                font_instance_key,
   1473                text,
   1474                size,
   1475                origin,
   1476                flags,
   1477            );
   1478 
   1479            let glyphs = glyph_indices
   1480                .iter()
   1481                .zip(glyph_positions)
   1482                .map(|arg| {
   1483                    GlyphInstance {
   1484                        index: *arg.0 as u32,
   1485                        point: arg.1,
   1486                    }
   1487                })
   1488                .collect::<Vec<_>>();
   1489            (glyphs, bounds)
   1490        };
   1491 
   1492        dl.push_text(
   1493            info,
   1494            rect,
   1495            &glyphs,
   1496            font_instance_key,
   1497            color,
   1498            None,
   1499        );
   1500    }
   1501 
   1502    fn handle_iframe(
   1503        &mut self,
   1504        dl: &mut DisplayListBuilder,
   1505        item: &Yaml,
   1506        info: &mut CommonItemProperties,
   1507    ) {
   1508        let bounds = item["bounds"].as_rect().expect("iframe must have bounds");
   1509        let pipeline_id = item["id"].as_pipeline_id().unwrap();
   1510        let ignore = item["ignore_missing_pipeline"].as_bool().unwrap_or(true);
   1511        dl.push_iframe(
   1512            bounds,
   1513            info.clip_rect,
   1514            &SpaceAndClipInfo {
   1515                spatial_id: info.spatial_id,
   1516                clip_chain_id: info.clip_chain_id
   1517            },
   1518            pipeline_id,
   1519            ignore
   1520        );
   1521    }
   1522 
   1523    fn get_item_type_from_yaml(item: &Yaml) -> &str {
   1524        let shorthands = [
   1525            "rect",
   1526            "image",
   1527            "text",
   1528            "glyphs",
   1529            "box-shadow", // Note: box_shadow shorthand check has to come before border.
   1530            "border",
   1531            "gradient",
   1532            "radial-gradient",
   1533            "conic-gradient"
   1534        ];
   1535 
   1536        for shorthand in shorthands.iter() {
   1537            if !item[*shorthand].is_badvalue() {
   1538                return shorthand;
   1539            }
   1540        }
   1541        item["type"].as_str().unwrap_or("unknown")
   1542    }
   1543 
   1544    fn add_display_list_items_from_yaml(
   1545        &mut self,
   1546        dl: &mut DisplayListBuilder,
   1547        wrench: &mut Wrench,
   1548        yaml_items: &[Yaml],
   1549    ) {
   1550        // A very large number (but safely far away from finite limits of f32)
   1551        let big_number = 1.0e30;
   1552        // A rect that should in practical terms serve as a no-op for clipping
   1553        let full_clip = LayoutRect::from_origin_and_size(
   1554            LayoutPoint::new(-big_number / 2.0, -big_number / 2.0),
   1555            LayoutSize::new(big_number, big_number));
   1556 
   1557        for item in yaml_items {
   1558            let item_type = Self::get_item_type_from_yaml(item);
   1559 
   1560            let spatial_id = self.to_spatial_id(&item["spatial-id"], dl.pipeline_id);
   1561 
   1562            if let Some(spatial_id) = spatial_id {
   1563                self.spatial_id_stack.push(spatial_id);
   1564            }
   1565 
   1566            let clip_rect = item["clip-rect"].as_rect().unwrap_or(full_clip);
   1567            let clip_chain_id = self.to_clip_chain_id(&item["clip-chain"], dl).unwrap_or(ClipChainId::INVALID);
   1568 
   1569            let mut flags = PrimitiveFlags::default();
   1570            for (key, flag) in [
   1571                ("backface-visible", PrimitiveFlags::IS_BACKFACE_VISIBLE),
   1572                ("scrollbar-container", PrimitiveFlags::IS_SCROLLBAR_CONTAINER),
   1573                ("prefer-compositor-surface", PrimitiveFlags::PREFER_COMPOSITOR_SURFACE),
   1574            ] {
   1575                if let Some(value) = item[key].as_bool() {
   1576                    flags.set(flag, value);
   1577                }
   1578            }
   1579 
   1580 
   1581            let mut info = CommonItemProperties {
   1582                clip_rect,
   1583                clip_chain_id,
   1584                spatial_id: self.top_space(),
   1585                flags,
   1586            };
   1587 
   1588            match item_type {
   1589                "rect" => self.handle_rect(dl, item, &info),
   1590                "hit-test" => self.handle_hit_test(dl, item, &mut info),
   1591                "line" => self.handle_line(dl, item, &mut info),
   1592                "image" => self.handle_image(dl, wrench, item, &mut info),
   1593                "yuv-image" => self.handle_yuv_image(dl, wrench, item, &mut info),
   1594                "text" | "glyphs" => self.handle_text(dl, wrench, item, &mut info),
   1595                "scroll-frame" => self.handle_scroll_frame(dl, wrench, item),
   1596                "sticky-frame" => self.handle_sticky_frame(dl, wrench, item),
   1597                "clip" => self.handle_clip(dl, wrench, item),
   1598                "clip-chain" => self.handle_clip_chain(dl, item),
   1599                "border" => self.handle_border(dl, wrench, item, &mut info),
   1600                "gradient" => self.handle_gradient(dl, item, &mut info),
   1601                "radial-gradient" => self.handle_radial_gradient(dl, item, &mut info),
   1602                "conic-gradient" => self.handle_conic_gradient(dl, item, &mut info),
   1603                "box-shadow" => self.handle_box_shadow(dl, item, &mut info),
   1604                "iframe" => self.handle_iframe(dl, item, &mut info),
   1605                "stacking-context" => {
   1606                    self.add_stacking_context_from_yaml(dl, wrench, item, IsRoot(false), &mut info)
   1607                }
   1608                "reference-frame" => self.handle_reference_frame(dl, wrench, item),
   1609                "computed-frame" => self.handle_computed_frame(dl, wrench, item),
   1610                "shadow" => self.handle_push_shadow(dl, item, &mut info),
   1611                "pop-all-shadows" => self.handle_pop_all_shadows(dl),
   1612                "backdrop-filter" => self.handle_backdrop_filter(dl, item, &mut info),
   1613                _ => println!("Skipping unknown item type: {:?}", item),
   1614            }
   1615 
   1616            if spatial_id.is_some() {
   1617                self.spatial_id_stack.pop().unwrap();
   1618            }
   1619        }
   1620    }
   1621 
   1622    fn next_spatial_key(&mut self) -> SpatialTreeItemKey {
   1623        let key = SpatialTreeItemKey::new(self.next_spatial_key, 0);
   1624        self.next_spatial_key += 1;
   1625        key
   1626    }
   1627 
   1628    fn handle_scroll_frame(
   1629        &mut self,
   1630        dl: &mut DisplayListBuilder,
   1631        wrench: &mut Wrench,
   1632        yaml: &Yaml,
   1633    ) {
   1634        let clip_rect = yaml["bounds"]
   1635            .as_rect()
   1636            .expect("scroll frame must have a bounds");
   1637        let content_size = yaml["content-size"].as_size().unwrap_or_else(|| clip_rect.size());
   1638        let content_rect = LayoutRect::from_origin_and_size(clip_rect.min, content_size);
   1639        let external_scroll_offset = yaml["external-scroll-offset"].as_vector().unwrap_or_else(LayoutVector2D::zero);
   1640        let scroll_generation = yaml["scroll-generation"].as_i64().map_or(APZScrollGeneration::default(), |v| v as u64);
   1641        let has_scroll_linked_effect =
   1642        yaml["has-scroll-linked-effect"].as_bool().map_or(HasScrollLinkedEffect::default(),
   1643            |v| if v { HasScrollLinkedEffect::Yes } else { HasScrollLinkedEffect::No }
   1644        );
   1645 
   1646        let numeric_id = yaml["id"].as_i64().map(|id| id as u64);
   1647 
   1648        let external_id = ExternalScrollId(self.next_external_scroll_id, dl.pipeline_id);
   1649        self.next_external_scroll_id += 1;
   1650 
   1651        if let Some(vector) = yaml["scroll-offset"].as_vector() {
   1652            self.scroll_offsets.insert(
   1653                external_id,
   1654                vec![SampledScrollOffset {
   1655                    offset: vector,
   1656                    generation: APZScrollGeneration::default(),
   1657                }],
   1658            );
   1659        }
   1660 
   1661        if !yaml["scroll-offsets"].is_badvalue() {
   1662            let mut offsets = Vec::new();
   1663            for entry in yaml["scroll-offsets"].as_vec().unwrap() {
   1664                let offset = entry["offset"].as_vector().unwrap_or(LayoutVector2D::zero());
   1665                let generation = entry["generation"].as_i64().map_or(APZScrollGeneration::default(), |v| v as u64);
   1666                offsets.push(SampledScrollOffset { offset, generation });
   1667            }
   1668            self.scroll_offsets.insert(external_id, offsets);
   1669        }
   1670 
   1671        let clip_to_frame = yaml["clip-to-frame"].as_bool().unwrap_or(false);
   1672 
   1673        let clip_id = if clip_to_frame {
   1674            Some(dl.define_clip_rect(
   1675                self.top_space(),
   1676                clip_rect,
   1677            ))
   1678        } else {
   1679            None
   1680        };
   1681 
   1682        let spatial_id = dl.define_scroll_frame(
   1683            self.top_space(),
   1684            external_id,
   1685            content_rect,
   1686            clip_rect,
   1687            external_scroll_offset,
   1688            scroll_generation,
   1689            has_scroll_linked_effect,
   1690            self.next_spatial_key(),
   1691        );
   1692        if let Some(numeric_id) = numeric_id {
   1693            self.add_spatial_id_mapping(numeric_id, spatial_id);
   1694            if let Some(clip_id) = clip_id {
   1695                self.add_clip_id_mapping(numeric_id, clip_id);
   1696            }
   1697        }
   1698 
   1699        if let Some(yaml_items) = yaml["items"].as_vec() {
   1700            self.spatial_id_stack.push(spatial_id);
   1701            self.add_display_list_items_from_yaml(dl, wrench, yaml_items);
   1702            self.spatial_id_stack.pop().unwrap();
   1703        }
   1704    }
   1705 
   1706    fn handle_sticky_frame(
   1707        &mut self,
   1708        dl: &mut DisplayListBuilder,
   1709        wrench: &mut Wrench,
   1710        yaml: &Yaml,
   1711    ) {
   1712        let bounds = yaml["bounds"].as_rect().expect("sticky frame must have a bounds");
   1713        let numeric_id = yaml["id"].as_i64().map(|id| id as u64);
   1714 
   1715        let real_id = dl.define_sticky_frame(
   1716            *self.spatial_id_stack.last().unwrap(),
   1717            bounds,
   1718            SideOffsets2D::new(
   1719                yaml["margin-top"].as_f32(),
   1720                yaml["margin-right"].as_f32(),
   1721                yaml["margin-bottom"].as_f32(),
   1722                yaml["margin-left"].as_f32(),
   1723            ),
   1724            yaml["vertical-offset-bounds"].as_sticky_offset_bounds(),
   1725            yaml["horizontal-offset-bounds"].as_sticky_offset_bounds(),
   1726            yaml["previously-applied-offset"].as_vector().unwrap_or_else(LayoutVector2D::zero),
   1727            self.next_spatial_key(),
   1728            None,
   1729        );
   1730 
   1731        if let Some(numeric_id) = numeric_id {
   1732            self.add_spatial_id_mapping(numeric_id, real_id);
   1733        }
   1734 
   1735        if let Some(yaml_items) = yaml["items"].as_vec() {
   1736            self.spatial_id_stack.push(real_id);
   1737            self.add_display_list_items_from_yaml(dl, wrench, yaml_items);
   1738            self.spatial_id_stack.pop().unwrap();
   1739        }
   1740    }
   1741 
   1742    fn resolve_binding<'a>(
   1743        &'a self,
   1744        yaml: &'a Yaml,
   1745    ) -> &'a Yaml {
   1746        if let Some(keyframes) = &self.keyframes {
   1747            if let Some(s) = yaml.as_str() {
   1748                const PREFIX: &str = "key(";
   1749                const SUFFIX: &str = ")";
   1750                if let Some(key) = s.strip_prefix(PREFIX).and_then(|s| s.strip_suffix(SUFFIX)) {
   1751                    return &keyframes[key][self.requested_frame];
   1752                }
   1753            }
   1754        }
   1755 
   1756        yaml
   1757    }
   1758 
   1759    fn resolve_colorf(
   1760        &self,
   1761        yaml: &Yaml,
   1762    ) -> Option<ColorF> {
   1763        self.resolve_binding(yaml)
   1764            .as_colorf()
   1765    }
   1766 
   1767    fn resolve_rect(
   1768        &self,
   1769        yaml: &Yaml,
   1770    ) -> LayoutRect {
   1771        self.resolve_binding(yaml)
   1772            .as_rect()
   1773            .unwrap_or_else(|| panic!("invalid rect {:?}", yaml))
   1774    }
   1775 
   1776    fn resolve_vector(
   1777        &self,
   1778        yaml: &Yaml,
   1779        default: LayoutVector2D,
   1780    ) -> LayoutVector2D {
   1781        self.resolve_binding(yaml)
   1782            .as_vector()
   1783            .unwrap_or(default)
   1784    }
   1785 
   1786    fn handle_push_shadow(
   1787        &mut self,
   1788        dl: &mut DisplayListBuilder,
   1789        yaml: &Yaml,
   1790        info: &mut CommonItemProperties,
   1791    ) {
   1792        let blur_radius = yaml["blur-radius"].as_f32().unwrap_or(0.0);
   1793        let offset = yaml["offset"].as_vector().unwrap_or_else(LayoutVector2D::zero);
   1794        let color = yaml["color"].as_colorf().unwrap_or(ColorF::BLACK);
   1795 
   1796        dl.push_shadow(
   1797            &SpaceAndClipInfo { spatial_id: info.spatial_id, clip_chain_id: info.clip_chain_id },
   1798            Shadow {
   1799                blur_radius,
   1800                offset,
   1801                color,
   1802            },
   1803            true,
   1804        );
   1805    }
   1806 
   1807    fn handle_pop_all_shadows(&mut self, dl: &mut DisplayListBuilder) {
   1808        dl.pop_all_shadows();
   1809    }
   1810 
   1811    fn handle_clip_chain(&mut self, builder: &mut DisplayListBuilder, yaml: &Yaml) {
   1812        let numeric_id = yaml["id"].as_i64().expect("clip chains must have an id");
   1813        let clip_ids: Vec<ClipId> = yaml["clips"]
   1814            .as_vec_u64()
   1815            .unwrap_or_default()
   1816            .iter()
   1817            .map(|id| self.user_clip_id_map[id])
   1818            .collect();
   1819 
   1820        let parent = self.to_clip_chain_id(&yaml["parent"], builder);
   1821        let real_id = builder.define_clip_chain(parent, clip_ids);
   1822        self.add_clip_chain_id_mapping(numeric_id as u64, real_id);
   1823    }
   1824 
   1825    fn handle_clip(&mut self, dl: &mut DisplayListBuilder, wrench: &mut Wrench, yaml: &Yaml) {
   1826        let numeric_id = yaml["id"].as_i64();
   1827        let spatial_id = self.top_space();
   1828        let complex_clips = yaml["complex"].as_complex_clip_regions();
   1829        let mut clip_id = None;
   1830 
   1831        if let Some(clip_rect) = yaml["bounds"].as_rect() {
   1832            clip_id = Some(dl.define_clip_rect(
   1833                spatial_id,
   1834                clip_rect,
   1835            ));
   1836        }
   1837 
   1838        if let Some(image_mask) = self.as_image_mask(&yaml["image-mask"], wrench) {
   1839            assert!(clip_id.is_none(), "invalid clip definition");
   1840 
   1841            clip_id = Some(dl.define_clip_image_mask(
   1842                spatial_id,
   1843                image_mask,
   1844                &[],
   1845                FillRule::Nonzero,
   1846            ));
   1847        }
   1848 
   1849        if !complex_clips.is_empty() {
   1850            // Only 1 complex clip is supported per clip (todo: change yaml format)
   1851            assert_eq!(complex_clips.len(), 1);
   1852            assert!(clip_id.is_none(), "invalid clip definition");
   1853 
   1854            clip_id = Some(dl.define_clip_rounded_rect(
   1855                spatial_id,
   1856                complex_clips[0],
   1857            ));
   1858        }
   1859 
   1860        if let Some(clip_id) = clip_id {
   1861            if let Some(numeric_id) = numeric_id {
   1862                self.add_clip_id_mapping(numeric_id as u64, clip_id);
   1863            }
   1864        }
   1865    }
   1866 
   1867    fn push_reference_frame(
   1868        &mut self,
   1869        dl: &mut DisplayListBuilder,
   1870        default_bounds: impl Fn() -> LayoutRect,
   1871        yaml: &Yaml,
   1872    ) -> SpatialId {
   1873        let bounds = yaml["bounds"].as_rect().unwrap_or_else(default_bounds);
   1874        let default_transform_origin = LayoutPoint::new(
   1875            bounds.min.x + bounds.width() * 0.5,
   1876            bounds.min.y + bounds.height() * 0.5,
   1877        );
   1878 
   1879        let transform_style = yaml["transform-style"]
   1880            .as_transform_style()
   1881            .unwrap_or(TransformStyle::Flat);
   1882 
   1883        let transform_origin = yaml["transform-origin"]
   1884            .as_point()
   1885            .unwrap_or(default_transform_origin);
   1886 
   1887        assert!(
   1888            yaml["transform"].is_badvalue() ||
   1889            yaml["perspective"].is_badvalue(),
   1890            "Should have one of either transform or perspective"
   1891        );
   1892 
   1893        let perspective_origin = yaml["perspective-origin"]
   1894            .as_point()
   1895            .unwrap_or(default_transform_origin);
   1896 
   1897        let is_2d = yaml["is-2d"].as_bool().unwrap_or(false);
   1898        let should_snap = yaml["should-snap"].as_bool().unwrap_or(false);
   1899 
   1900        let reference_frame_kind = if !yaml["perspective"].is_badvalue() {
   1901            ReferenceFrameKind::Perspective { scrolling_relative_to: None }
   1902        } else {
   1903            ReferenceFrameKind::Transform {
   1904                is_2d_scale_translation: is_2d,
   1905                should_snap,
   1906                paired_with_perspective: yaml["paired-with-perspective"].as_bool().unwrap_or(false),
   1907            }
   1908        };
   1909 
   1910        let transform = yaml["transform"]
   1911            .as_transform(&transform_origin);
   1912 
   1913        let perspective = match yaml["perspective"].as_f32() {
   1914            Some(value) if value != 0.0 => {
   1915                Some(make_perspective(perspective_origin, value as f32))
   1916            }
   1917            Some(..) => None,
   1918            _ => yaml["perspective"].as_matrix4d(),
   1919        };
   1920 
   1921        let reference_frame_id = dl.push_reference_frame(
   1922            bounds.min,
   1923            *self.spatial_id_stack.last().unwrap(),
   1924            transform_style,
   1925            transform.or(perspective).unwrap_or_default().into(),
   1926            reference_frame_kind,
   1927            self.next_spatial_key(),
   1928        );
   1929 
   1930        let numeric_id = yaml["id"].as_i64();
   1931        if let Some(numeric_id) = numeric_id {
   1932            self.add_spatial_id_mapping(numeric_id as u64, reference_frame_id);
   1933        }
   1934 
   1935        reference_frame_id
   1936    }
   1937 
   1938    fn handle_reference_frame(
   1939        &mut self,
   1940        dl: &mut DisplayListBuilder,
   1941        wrench: &mut Wrench,
   1942        yaml: &Yaml,
   1943    ) {
   1944        let default_bounds = || LayoutRect::from_size(wrench.window_size_f32());
   1945        let real_id = self.push_reference_frame(dl, default_bounds, yaml);
   1946        self.spatial_id_stack.push(real_id);
   1947 
   1948        if let Some(yaml_items) = yaml["items"].as_vec() {
   1949            self.add_display_list_items_from_yaml(dl, wrench, yaml_items);
   1950        }
   1951 
   1952        self.spatial_id_stack.pop().unwrap();
   1953        dl.pop_reference_frame();
   1954    }
   1955 
   1956    fn push_computed_frame(
   1957        &mut self,
   1958        dl: &mut DisplayListBuilder,
   1959        default_bounds: impl Fn() -> LayoutRect,
   1960        yaml: &Yaml,
   1961    ) -> SpatialId {
   1962        let bounds = yaml["bounds"].as_rect().unwrap_or_else(default_bounds);
   1963 
   1964        let scale_from = yaml["scale-from"].as_size();
   1965        let vertical_flip = yaml["vertical-flip"].as_bool().unwrap_or(false);
   1966        let rotation = yaml["rotation"].as_rotation().unwrap_or(Rotation::Degree0);
   1967 
   1968        let reference_frame_id = dl.push_computed_frame(
   1969            bounds.min,
   1970            *self.spatial_id_stack.last().unwrap(),
   1971            scale_from,
   1972            vertical_flip,
   1973            rotation,
   1974            self.next_spatial_key(),
   1975        );
   1976 
   1977        let numeric_id = yaml["id"].as_i64();
   1978        if let Some(numeric_id) = numeric_id {
   1979            self.add_spatial_id_mapping(numeric_id as u64, reference_frame_id);
   1980        }
   1981 
   1982        reference_frame_id
   1983    }
   1984 
   1985    fn handle_computed_frame(
   1986        &mut self,
   1987        dl: &mut DisplayListBuilder,
   1988        wrench: &mut Wrench,
   1989        yaml: &Yaml,
   1990    ) {
   1991        let default_bounds = || LayoutRect::from_size(wrench.window_size_f32());
   1992        let real_id = self.push_computed_frame(dl, default_bounds, yaml);
   1993        self.spatial_id_stack.push(real_id);
   1994 
   1995        if let Some(yaml_items) = yaml["items"].as_vec() {
   1996            self.add_display_list_items_from_yaml(dl, wrench, yaml_items);
   1997        }
   1998 
   1999        self.spatial_id_stack.pop().unwrap();
   2000        dl.pop_reference_frame();
   2001    }
   2002 
   2003    fn add_stacking_context_from_yaml(
   2004        &mut self,
   2005        dl: &mut DisplayListBuilder,
   2006        wrench: &mut Wrench,
   2007        yaml: &Yaml,
   2008        IsRoot(is_root): IsRoot,
   2009        info: &mut CommonItemProperties,
   2010    ) {
   2011        let default_bounds = || LayoutRect::from_size(wrench.window_size_f32());
   2012        let mut bounds = yaml["bounds"].as_rect().unwrap_or_else(default_bounds);
   2013 
   2014        let pushed_reference_frame =
   2015            if !yaml["transform"].is_badvalue() || !yaml["perspective"].is_badvalue() {
   2016                let reference_frame_id = self.push_reference_frame(dl, default_bounds, yaml);
   2017                self.spatial_id_stack.push(reference_frame_id);
   2018                bounds.max -= bounds.min.to_vector();
   2019                bounds.min = LayoutPoint::zero();
   2020                true
   2021            } else {
   2022                false
   2023            };
   2024 
   2025        let clip_chain_id = self.to_clip_chain_id(&yaml["clip-chain"], dl);
   2026 
   2027        let transform_style = yaml["transform-style"]
   2028            .as_transform_style()
   2029            .unwrap_or(TransformStyle::Flat);
   2030        let mix_blend_mode = yaml["mix-blend-mode"]
   2031            .as_mix_blend_mode()
   2032            .unwrap_or(MixBlendMode::Normal);
   2033        let raster_space = yaml["raster-space"]
   2034            .as_raster_space()
   2035            .unwrap_or(RasterSpace::Screen);
   2036        let is_blend_container = yaml["blend-container"].as_bool().unwrap_or(false);
   2037        let wraps_backdrop_filter = yaml["wraps-backdrop-filter"].as_bool().unwrap_or(false);
   2038 
   2039        if is_root {
   2040            if let Some(vector) = yaml["scroll-offset"].as_vector() {
   2041                let external_id = ExternalScrollId(0, dl.pipeline_id);
   2042                self.scroll_offsets.insert(
   2043                    external_id,
   2044                    vec![SampledScrollOffset {
   2045                        offset: vector,
   2046                        generation: APZScrollGeneration::default(),
   2047                    }],
   2048                );
   2049            }
   2050        }
   2051 
   2052        let filters = yaml["filters"].as_vec_filter_op().unwrap_or_default();
   2053        let filter_datas = yaml["filter-datas"].as_vec_filter_data().unwrap_or_default();
   2054 
   2055        let snapshot = if !yaml["snapshot"].is_badvalue() {
   2056            let yaml = &yaml["snapshot"];
   2057            let name = yaml["name"].as_str().unwrap_or("snapshot");
   2058            let area = yaml["area"].as_rect().unwrap_or(bounds);
   2059            let detached = yaml["detached"].as_bool().unwrap_or(false);
   2060 
   2061            let key = SnapshotImageKey(wrench.api.generate_image_key());
   2062            self.snapshots.insert(name.to_string(), Snapshot {
   2063                key,
   2064                size: bounds.size(),
   2065            });
   2066 
   2067            let mut txn = Transaction::new();
   2068            txn.add_snapshot_image(key);
   2069            wrench.api.send_transaction(wrench.document_id, txn);
   2070 
   2071            Some(SnapshotInfo { key, area, detached })
   2072        } else {
   2073            None
   2074        };
   2075 
   2076        let mut flags = StackingContextFlags::empty();
   2077        flags.set(StackingContextFlags::IS_BLEND_CONTAINER, is_blend_container);
   2078        flags.set(StackingContextFlags::WRAPS_BACKDROP_FILTER, wraps_backdrop_filter);
   2079 
   2080        dl.push_stacking_context(
   2081            bounds.min,
   2082            *self.spatial_id_stack.last().unwrap(),
   2083            info.flags,
   2084            clip_chain_id,
   2085            transform_style,
   2086            mix_blend_mode,
   2087            &filters,
   2088            &filter_datas,
   2089            raster_space,
   2090            flags,
   2091            snapshot,
   2092        );
   2093 
   2094        if let Some(yaml_items) = yaml["items"].as_vec() {
   2095            self.add_display_list_items_from_yaml(dl, wrench, yaml_items);
   2096        }
   2097 
   2098        dl.pop_stacking_context();
   2099 
   2100        if pushed_reference_frame {
   2101            self.spatial_id_stack.pop().unwrap();
   2102            dl.pop_reference_frame();
   2103        }
   2104    }
   2105 
   2106    fn handle_backdrop_filter(
   2107        &mut self,
   2108        dl: &mut DisplayListBuilder,
   2109        item: &Yaml,
   2110        info: &mut CommonItemProperties,
   2111    ) {
   2112        info.clip_rect = try_intersect!(
   2113            self.resolve_rect(&item["bounds"]),
   2114            &info.clip_rect
   2115        );
   2116 
   2117        let filters = item["filters"].as_vec_filter_op().unwrap_or_default();
   2118        let filter_datas = item["filter-datas"].as_vec_filter_data().unwrap_or_default();
   2119 
   2120        dl.push_backdrop_filter(
   2121            info,
   2122            &filters,
   2123            &filter_datas,
   2124        );
   2125    }
   2126 }
   2127 
   2128 impl WrenchThing for YamlFrameReader {
   2129    fn do_frame(&mut self, wrench: &mut Wrench) -> u32 {
   2130        let mut should_build_yaml = false;
   2131 
   2132        // If YAML isn't read yet, or watching source file, reload from disk.
   2133        if self.yaml_string.is_empty() || self.watch_source {
   2134            self.yaml_string = std::fs::read_to_string(&self.yaml_path)
   2135                 .unwrap_or_else(|_| panic!("YAML '{:?}' doesn't exist", self.yaml_path));
   2136            should_build_yaml = true;
   2137        }
   2138 
   2139        // Evaluate conditions that require parsing the YAML.
   2140        if self.built_frame != self.requested_frame {
   2141            // Requested frame has changed
   2142            should_build_yaml = true;
   2143        }
   2144 
   2145        // Build the DL from YAML if required
   2146        if should_build_yaml {
   2147            self.build(wrench);
   2148        }
   2149 
   2150        // Determine whether to send a new DL, or just refresh.
   2151        if should_build_yaml || wrench.should_rebuild_display_lists() {
   2152            wrench.begin_frame();
   2153            wrench.send_lists(
   2154                &mut self.frame_count,
   2155                self.display_lists.clone(),
   2156                &self.scroll_offsets,
   2157            );
   2158        } else {
   2159            wrench.refresh();
   2160        }
   2161 
   2162        self.frame_count
   2163    }
   2164 
   2165    fn next_frame(&mut self) {
   2166        let mut max_frame_count = 0;
   2167        if let Some(ref keyframes) = self.keyframes {
   2168            for (_, values) in keyframes.as_hash().unwrap() {
   2169                max_frame_count = max_frame_count.max(values.as_vec().unwrap().len());
   2170            }
   2171        }
   2172        if self.requested_frame + 1 < max_frame_count {
   2173            self.requested_frame += 1;
   2174        }
   2175    }
   2176 
   2177    fn prev_frame(&mut self) {
   2178        if self.requested_frame > 0 {
   2179            self.requested_frame -= 1;
   2180        }
   2181    }
   2182 }