Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 


Quelle  rawtest.rs   Sprache: unbekannt

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use euclid::{point2, size2, rect, Box2D};
use std::sync::Arc;
use std::sync::atomic::{AtomicIsize, Ordering};
use std::sync::mpsc::Receiver;
use webrender::api::*;
use webrender::render_api::*;
use webrender::api::units::*;
use crate::{WindowWrapper, NotifierEvent};
use crate::blob;
use crate::reftest::{ReftestImage, ReftestImageComparison};
use crate::wrench::Wrench;

pub struct RawtestHarness<'a> {
    wrench: &'a mut Wrench,
    rx: &'a Receiver<NotifierEvent>,
    window: &'a mut WindowWrapper,
}


impl<'a> RawtestHarness<'a> {
    pub fn new(wrench: &'a mut Wrench,
               window: &'a mut WindowWrapper,
               rx: &'a Receiver<NotifierEvent>) -> Self {
        RawtestHarness {
            wrench,
            rx,
            window,
        }
    }

    pub fn run(mut self) {
        self.test_hit_testing();
        self.test_resize_image();
        self.test_retained_blob_images_test();
        self.test_blob_update_test();
        self.test_blob_update_epoch_test();
        self.test_tile_decomposition();
        self.test_very_large_blob();
        self.test_blob_visible_area();
        self.test_blob_set_visible_area();
        self.test_offscreen_blob();
        self.test_save_restore();
        self.test_blur_cache();
        self.test_capture();
        self.test_zero_height_window();
        self.test_clear_cache();
    }

    fn render_and_get_pixels(&mut self, window_rect: FramebufferIntRect) -> Vec<u8> {
        self.rx.recv().unwrap();
        self.wrench.render();
        self.wrench.renderer.read_pixels_rgba8(window_rect)
    }

    fn compare_pixels(&self, data1: Vec<u8>, data2: Vec<u8>, size: FramebufferIntSize) {
        let size = DeviceIntSize::new(size.width, size.height);
        let image1 = ReftestImage {
            data: data1,
            size,
        };
        let image2 = ReftestImage {
            data: data2,
            size,
        };

        match image1.compare(&image2) {
            ReftestImageComparison::Equal => {}
            ReftestImageComparison::NotEqual { max_difference, count_different, .. } => {
                let t = "rawtest";
                println!(
                    "REFTEST TEST-UNEXPECTED-FAIL | {t} \
                     | image comparison, max difference: {max_difference}, \
                     number of differing pixels: {count_different}");
                println!("REFTEST   IMAGE 1: {}", image1.create_data_uri());
                println!("REFTEST   IMAGE 2: {}", image2.create_data_uri());
                println!("REFTEST TEST-END | {}", t);
                panic!();
            }
        }
    }

    fn submit_dl(
        &mut self,
        epoch: &mut Epoch,
        mut builder: DisplayListBuilder,
        mut txn: Transaction,
    ) {
        txn.use_scene_builder_thread();

        txn.set_display_list(
            *epoch,
            builder.end(),
        );
        epoch.0 += 1;

        txn.generate_frame(0, RenderReasons::TESTING);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    }

    fn make_common_properties(&self, clip_rect: LayoutRect) -> CommonItemProperties {
        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        CommonItemProperties {
            clip_rect,
            clip_chain_id: space_and_clip.clip_chain_id,
            spatial_id: space_and_clip.spatial_id,
            flags: PrimitiveFlags::default(),
        }
    }

    fn make_common_properties_with_clip_and_spatial(
        &self,
        clip_rect: LayoutRect,
        clip_chain_id: ClipChainId,
        spatial_id: SpatialId
    ) -> CommonItemProperties {
        CommonItemProperties {
            clip_rect,
            clip_chain_id,
            spatial_id,
            flags: PrimitiveFlags::default(),
        }
    }

    fn test_resize_image(&mut self) {
        println!("\tresize image...");
        // This test changes the size of an image to make it go switch back and forth
        // between tiled and non-tiled.
        // The resource cache should be able to handle this without crashing.

        let mut txn = Transaction::new();
        let img = self.wrench.api.generate_image_key();

        // Start with a non-tiled image.
        txn.add_image(
            img,
            ImageDescriptor::new(64, 64, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
            ImageData::new(vec![255; 64 * 64 * 4]),
            None,
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 0.0, 64.0, 64.0).to_box2d());

        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            img,
            ColorF::WHITE,
        );

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);
        self.rx.recv().unwrap();
        self.wrench.render();

        let mut txn = Transaction::new();
        // Resize the image to something bigger than the max texture size (8196) to force tiling.
        txn.update_image(
            img,
            ImageDescriptor::new(8200, 32, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
            ImageData::new(vec![255; 8200 * 32 * 4]),
            &DirtyRect::All,
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 0.0, 1024.0, 1024.0).to_box2d());

        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            img,
            ColorF::WHITE,
        );

        self.submit_dl(&mut epoch, builder, txn);
        self.rx.recv().unwrap();
        self.wrench.render();

        let mut txn = Transaction::new();
        // Resize back to something doesn't require tiling.
        txn.update_image(
            img,
            ImageDescriptor::new(64, 64, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
            ImageData::new(vec![64; 64 * 64 * 4]),
            &DirtyRect::All,
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 0.0, 1024.0, 1024.0).to_box2d());

        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            img,
            ColorF::WHITE,
        );

        self.submit_dl(&mut epoch, builder, txn);
        self.rx.recv().unwrap();
        self.wrench.render();

        txn = Transaction::new();
        txn.delete_image(img);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    }

    fn test_tile_decomposition(&mut self) {
        println!("\ttile decomposition...");
        // This exposes a crash in tile decomposition
        let mut txn = Transaction::new();

        let blob_img = self.wrench.api.generate_blob_image_key();
        txn.add_blob_image(
            blob_img,
            ImageDescriptor::new(151, 56, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect::from_size(DeviceIntSize::new(151, 56)),
            Some(128),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let info = self.make_common_properties(rect(448.9, 74.0, 151.000_03, 56.).to_box2d());

        // setup some malicious image size parameters
        builder.push_repeating_image(
            &info,
            info.clip_rect,
            size2(151., 56.0),
            size2(151.0, 56.0),
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);

        self.rx.recv().unwrap();
        self.wrench.render();

        // Leaving a tiled blob image in the resource cache
        // confuses the `test_capture`. TODO: remove this
        txn = Transaction::new();
        txn.delete_blob_image(blob_img);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    }

    fn test_very_large_blob(&mut self) {
        println!("\tvery large blob...");

        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(800, 800);

        let window_rect = FramebufferIntRect::from_origin_and_size(
            FramebufferIntPoint::new(0, window_size.height - test_size.height),
            test_size,
        );

        // This exposes a crash in tile decomposition
        let mut txn = Transaction::new();

        let blob_img = self.wrench.api.generate_blob_image_key();
        txn.add_blob_image(
            blob_img,
            ImageDescriptor::new(15000, 15000, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect::from_size(DeviceIntSize::new(15000, 15000)),
            Some(100),
        );

        let called = Arc::new(AtomicIsize::new(0));
        let called_inner = Arc::clone(&called);

        self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
            called_inner.fetch_add(1, Ordering::SeqCst);
        });

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        let clip_id = builder.define_clip_rect(
            root_space_and_clip.spatial_id,
            rect(40., 41., 200., 201.).to_box2d(),
        );
        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);

        let info = CommonItemProperties {
            clip_rect: rect(0.0, 0.0, 800.0, 800.0).to_box2d(),
            clip_chain_id,
            spatial_id: root_space_and_clip.spatial_id,
            flags: PrimitiveFlags::default(),
        };

        // setup some malicious image size parameters
        builder.push_repeating_image(
            &info,
            size2(15000.0, 15000.0).into(),
            size2(15000.0, 15000.0),
            size2(0.0, 0.0),
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);

        let pixels = self.render_and_get_pixels(window_rect);

        // make sure we didn't request too many blobs
        assert!(called.load(Ordering::SeqCst) < 20);

        //use crate::png;
        //png::save_flipped("out.png", pixels.clone(), size2(window_rect.size.width, window_rect.size.height));

        // make sure things are in the right spot
        let w = window_rect.width() as usize;
        let h = window_rect.height() as usize;
        let p1 = (40 + (h - 100) * w) * 4;
        assert_eq!(pixels[p1    ], 50);
        assert_eq!(pixels[p1 + 1], 50);
        assert_eq!(pixels[p1 + 2], 150);
        assert_eq!(pixels[p1 + 3], 255);

        // Leaving a tiled blob image in the resource cache
        // confuses the `test_capture`. TODO: remove this
        txn = Transaction::new();
        txn.delete_blob_image(blob_img);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);

        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
    }

    fn test_blob_visible_area(&mut self) {
        println!("\tblob visible area...");

        let window_size = self.window.get_inner_size();
        let test_size = FramebufferIntSize::new(800, 800);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            FramebufferIntPoint::new(0, window_size.height - test_size.height),
            test_size,
        );
        let mut txn = Transaction::new();

        let blob_img = self.wrench.api.generate_blob_image_key();
        txn.add_blob_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect {
                min: point2(50, 20),
                max: point2(450, 420),
            },
            Some(100),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let image_size = size2(400.0, 400.0);

        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        let clip_id = builder.define_clip_rect(
            root_space_and_clip.spatial_id,
            rect(-1000.0, -1000.0, 2000.0, 2000.0).to_box2d(),
        );
        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);

        let info = CommonItemProperties {
            clip_rect: rect(10.0, 10.0, 400.0, 400.0).to_box2d(),
            clip_chain_id,
            spatial_id: root_space_and_clip.spatial_id,
            flags: PrimitiveFlags::default(),
        };

        builder.push_repeating_image(
            &info,
            info.clip_rect,
            image_size,
            image_size,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );
        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);

        let pixels = self.render_and_get_pixels(window_rect);

        //use super::png;
        //png::save_flipped("out.png", pixels.clone(), size2(window_rect.size.width, window_rect.size.height));


        // make sure things are in the right spot
        let w = window_rect.width() as usize;
        let h = window_rect.height() as usize;
        let p1 = (65 + (h - 15) * w) * 4;
        assert_eq!(pixels[p1    ], 255);
        assert_eq!(pixels[p1 + 1], 255);
        assert_eq!(pixels[p1 + 2], 255);
        assert_eq!(pixels[p1 + 3], 255);

        let p2 = (25 + (h - 15) * w) * 4;
        assert_eq!(pixels[p2    ], 221);
        assert_eq!(pixels[p2 + 1], 221);
        assert_eq!(pixels[p2 + 2], 221);
        assert_eq!(pixels[p2 + 3], 255);

        let p3 = (15 + (h - 15) * w) * 4;
        assert_eq!(pixels[p3    ], 50);
        assert_eq!(pixels[p3 + 1], 50);
        assert_eq!(pixels[p3 + 2], 150);
        assert_eq!(pixels[p3 + 3], 255);

        // Leaving a tiled blob image in the resource cache
        // confuses the `test_capture`. TODO: remove this
        txn = Transaction::new();
        txn.delete_blob_image(blob_img);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);

        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
    }

    fn test_blob_set_visible_area(&mut self) {
        // In this test we first render a blob with a certain visible area,
        // then change the visible area without updating the blob image.

        println!("\tblob visible area update...");

        let window_size = self.window.get_inner_size();
        let test_size = FramebufferIntSize::new(800, 800);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            FramebufferIntPoint::new(0, window_size.height - test_size.height),
            test_size,
        );
        let mut txn = Transaction::new();

        let blob_img = self.wrench.api.generate_blob_image_key();
        txn.add_blob_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect {
                min: point2(0, 0),
                max: point2(500, 500),
            },
            Some(128),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        let clip_id = builder.define_clip_rect(
            root_space_and_clip.spatial_id,
            rect(-1000.0, -1000.0, 2000.0, 2000.0).to_box2d(),
        );
        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);

        let info = CommonItemProperties {
            clip_rect: rect(0.0, 0.0, 1000.0, 1000.0).to_box2d(),
            clip_chain_id,
            spatial_id: root_space_and_clip.spatial_id,
            flags: PrimitiveFlags::default(),
        };

        builder.push_repeating_image(
            &info,
            rect(0.0, 0.0, 500.0, 500.0).to_box2d(),
            size2(500.0, 500.0),
            size2(500.0, 500.0),
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );
        let mut epoch = Epoch(0);

        // Render the first display list. We don't care about the result but we
        // want to make sure the next display list updates an already rendered
        // state.
        self.submit_dl(&mut epoch, builder, txn);
        let _ = self.render_and_get_pixels(window_rect);

        // Now render a similar scene with an updated blob visible area.
        // In this test we care about the fact that the visible area was updated
        // without using update_blob_image.

        let mut txn = Transaction::new();

        txn.set_blob_image_visible_area(blob_img, DeviceIntRect {
            min: point2(50, 50),
            max: point2(450, 450),
        });

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        let clip_id = builder.define_clip_rect(
            root_space_and_clip.spatial_id,
            rect(-1000.0, -1000.0, 2000.0, 2000.0).to_box2d(),
        );
        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);

        let info = CommonItemProperties {
            clip_rect: rect(0.0, 0.0, 1000.0, 1000.0).to_box2d(),
            clip_chain_id,
            spatial_id: root_space_and_clip.spatial_id,
            flags: PrimitiveFlags::default(),
        };

        builder.push_repeating_image(
            &info,
            rect(50.0, 50.0, 400.0, 400.0).to_box2d(),
            size2(400.0, 400.0),
            size2(400.0, 400.0),
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        self.submit_dl(&mut epoch, builder, txn);
        let resized_pixels = self.render_and_get_pixels(window_rect);

        // Now render the same scene with a new blob image created with the same
        // visible area as the previous scene, without going through an update.

        let mut txn = Transaction::new();

        let blob_img2 = self.wrench.api.generate_blob_image_key();
        txn.add_blob_image(
            blob_img2,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect {
                min: point2(50, 50),
                max: point2(450, 450),
            },
            Some(128),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let root_space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        let clip_id = builder.define_clip_rect(
            root_space_and_clip.spatial_id,
            rect(-1000.0, -1000.0, 2000.0, 2000.0).to_box2d(),
        );
        let clip_chain_id = builder.define_clip_chain(None, [clip_id]);

        let info = CommonItemProperties {
            clip_rect: rect(0.0, 0.0, 1000.0, 1000.0).to_box2d(),
            clip_chain_id,
            spatial_id: root_space_and_clip.spatial_id,
            flags: PrimitiveFlags::default(),
        };

        builder.push_repeating_image(
            &info,
            rect(50.0, 50.0, 400.0, 400.0).to_box2d(),
            size2(400.0, 400.0),
            size2(400.0, 400.0),
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img2.as_image(),
            ColorF::WHITE,
        );
        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);

        let reference_pixels = self.render_and_get_pixels(window_rect);

        assert_eq!(resized_pixels, reference_pixels);

        txn = Transaction::new();
        txn.delete_blob_image(blob_img);
        txn.delete_blob_image(blob_img2);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    }

    fn test_offscreen_blob(&mut self) {
        println!("\toffscreen blob update...");

        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(800, 800);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            point2(0, window_size.height - test_size.height),
            test_size,
        );

        // This exposes a crash in tile decomposition
        let mut txn = Transaction::new();

        let blob_img = self.wrench.api.generate_blob_image_key();
        txn.add_blob_image(
            blob_img,
            ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect::from_size(size2(1510, 1510)),
            None,
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let info = self.make_common_properties(rect(0., 0.0, 1510., 1510.).to_box2d());

        let image_size = size2(1510., 1510.);

        // setup some malicious image size parameters
        builder.push_repeating_image(
            &info,
            info.clip_rect,
            image_size,
            image_size,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);

        let original_pixels = self.render_and_get_pixels(window_rect);

        let mut epoch = Epoch(1);

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let info = self.make_common_properties(rect(-10000., 0.0, 1510., 1510.).to_box2d());

        let image_size = size2(1510., 1510.);

        // setup some malicious image size parameters
        builder.push_repeating_image(
            &info,
            info.clip_rect,
            image_size,
            image_size,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        self.submit_dl(&mut epoch, builder, Transaction::new());

        let _offscreen_pixels = self.render_and_get_pixels(window_rect);

        let mut txn = Transaction::new();

        txn.update_blob_image(
            blob_img,
            ImageDescriptor::new(1510, 1510, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect::from_size(size2(1510, 1510)),
            &Box2D { min: point2(10, 10), max: point2(110, 110) }.into(),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let info = self.make_common_properties(rect(0., 0.0, 1510., 1510.).to_box2d());

        let image_size = size2(1510., 1510.);

        // setup some malicious image size parameters
        builder.push_repeating_image(
            &info,
            info.clip_rect,
            image_size,
            image_size,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut epoch = Epoch(2);

        self.submit_dl(&mut epoch, builder, txn);

        let pixels = self.render_and_get_pixels(window_rect);

        self.compare_pixels(original_pixels, pixels, window_rect.size());

        // Leaving a tiled blob image in the resource cache
        // confuses the `test_capture`. TODO: remove this
        txn = Transaction::new();
        txn.delete_blob_image(blob_img);
        self.wrench.api.send_transaction(self.wrench.document_id, txn);
    }

    fn test_retained_blob_images_test(&mut self) {
        println!("\tretained blob images test...");
        let blob_img;
        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(400, 400);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            FramebufferIntPoint::new(0, window_size.height - test_size.height),
            test_size,
        );

        let mut txn = Transaction::new();
        {
            let api = &self.wrench.api;

            blob_img = api.generate_blob_image_key();
            txn.add_blob_image(
                blob_img,
                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
                blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
                DeviceIntRect::from_size(size2(500, 500)),
                None,
            );
        }

        let called = Arc::new(AtomicIsize::new(0));
        let called_inner = Arc::clone(&called);

        self.wrench.callbacks.lock().unwrap().request = Box::new(move |_| {
            assert_eq!(0, called_inner.fetch_add(1, Ordering::SeqCst));
        });

        // draw the blob the first time
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());

        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);

        let pixels_first = self.render_and_get_pixels(window_rect);

        assert_eq!(1, called.load(Ordering::SeqCst));

        // draw the blob image a second time at a different location

        // make a new display list that refers to the first image
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(1.0, 60.0, 200.0, 200.0).to_box2d());
        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut txn = Transaction::new();
        txn.resource_updates.clear();

        self.submit_dl(&mut epoch, builder, txn);

        let pixels_second = self.render_and_get_pixels(window_rect);

        // make sure we only requested once
        assert_eq!(1, called.load(Ordering::SeqCst));

        // use png;
        // png::save_flipped("out1.png", &pixels_first, window_rect.size);
        // png::save_flipped("out2.png", &pixels_second, window_rect.size);
        assert!(pixels_first != pixels_second);

        // cleanup
        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
    }

    fn test_blob_update_epoch_test(&mut self) {
        println!("\tblob update epoch test...");
        let (blob_img, blob_img2);
        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(400, 400);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            point2(0, window_size.height - test_size.height),
            test_size,
        );

        let mut txn = Transaction::new();
        let (blob_img, blob_img2) = {
            let api = &self.wrench.api;

            blob_img = api.generate_blob_image_key();
            txn.add_blob_image(
                blob_img,
                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
                blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
                DeviceIntRect::from_size(size2(500, 500)),
                None,
            );
            blob_img2 = api.generate_blob_image_key();
            txn.add_blob_image(
                blob_img2,
                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
                blob::serialize_blob(ColorU::new(80, 50, 150, 255)),
                DeviceIntRect::from_size(size2(500, 500)),
                None,
            );
            (blob_img, blob_img2)
        };

        // setup some counters to count how many times each image is requested
        let img1_requested = Arc::new(AtomicIsize::new(0));
        let img1_requested_inner = Arc::clone(&img1_requested);
        let img2_requested = Arc::new(AtomicIsize::new(0));
        let img2_requested_inner = Arc::clone(&img2_requested);

        // track the number of times that the second image has been requested
        self.wrench.callbacks.lock().unwrap().request = Box::new(move |requests| {
            for item in requests {
                if item.request.key == blob_img {
                    img1_requested_inner.fetch_add(1, Ordering::SeqCst);
                }
                if item.request.key == blob_img2 {
                    img2_requested_inner.fetch_add(1, Ordering::SeqCst);
                }
            }
        });

        // create two blob images and draw them
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());
        let info2 = self.make_common_properties(rect(200.0, 60.0, 200.0, 200.0).to_box2d());
        let push_images = |builder: &mut DisplayListBuilder| {
            builder.push_image(
                &info,
                info.clip_rect,
                ImageRendering::Auto,
                AlphaType::PremultipliedAlpha,
                blob_img.as_image(),
                ColorF::WHITE,
            );
            builder.push_image(
                &info2,
                info2.clip_rect,
                ImageRendering::Auto,
                AlphaType::PremultipliedAlpha,
                blob_img2.as_image(),
                ColorF::WHITE,
            );
        };

        push_images(&mut builder);

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);
        let _pixels_first = self.render_and_get_pixels(window_rect);

        // update and redraw both images
        let mut txn = Transaction::new();
        txn.update_blob_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect::from_size(size2(500, 500)),
            &Box2D { min: point2(100, 100), max: point2(200, 200) }.into(),
        );
        txn.update_blob_image(
            blob_img2,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(59, 50, 150, 255)),
            DeviceIntRect::from_size(size2(500, 500)),
            &Box2D { min: point2(100, 100), max: point2(200, 200) }.into(),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        push_images(&mut builder);
        self.submit_dl(&mut epoch, builder, txn);
        let _pixels_second = self.render_and_get_pixels(window_rect);

        // only update the first image
        let mut txn = Transaction::new();
        txn.update_blob_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 150, 150, 255)),
            DeviceIntRect::from_size(size2(500, 500)),
            &Box2D { min: point2(200, 200), max: point2(300, 300) }.into(),
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        push_images(&mut builder);
        self.submit_dl(&mut epoch, builder, txn);
        let _pixels_third = self.render_and_get_pixels(window_rect);

        // the first image should be requested 3 times
        assert_eq!(img1_requested.load(Ordering::SeqCst), 3);
        // the second image should've been requested twice
        assert_eq!(img2_requested.load(Ordering::SeqCst), 2);

        // cleanup
        *self.wrench.callbacks.lock().unwrap() = blob::BlobCallbacks::new();
    }

    fn test_blob_update_test(&mut self) {
        println!("\tblob update test...");
        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(400, 400);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            point2(0, window_size.height - test_size.height),
            test_size,
        );
        let mut txn = Transaction::new();

        let blob_img = {
            let img = self.wrench.api.generate_blob_image_key();
            txn.add_blob_image(
                img,
                ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
                blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
                DeviceIntRect::from_size(size2(500, 500)),
                None,
            );
            img
        };

        // draw the blobs the first time
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());

        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        let mut epoch = Epoch(0);

        self.submit_dl(&mut epoch, builder, txn);
        let pixels_first = self.render_and_get_pixels(window_rect);

        // draw the blob image a second time after updating it with the same color
        let mut txn = Transaction::new();
        txn.update_blob_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 50, 150, 255)),
            DeviceIntRect::from_size(size2(500, 500)),
            &Box2D { min: point2(100, 100), max: point2(200, 200) }.into(),
        );

        // make a new display list that refers to the first image
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());
        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        self.submit_dl(&mut epoch, builder, txn);
        let pixels_second = self.render_and_get_pixels(window_rect);

        // draw the blob image a third time after updating it with a different color
        let mut txn = Transaction::new();
        txn.update_blob_image(
            blob_img,
            ImageDescriptor::new(500, 500, ImageFormat::BGRA8, ImageDescriptorFlags::empty()),
            blob::serialize_blob(ColorU::new(50, 150, 150, 255)),
            DeviceIntRect::from_size(size2(500, 500)),
            &Box2D { min: point2(200, 200), max: point2(300, 300) }.into(),
        );

        // make a new display list that refers to the first image
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(rect(0.0, 60.0, 200.0, 200.0).to_box2d());
        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            blob_img.as_image(),
            ColorF::WHITE,
        );

        self.submit_dl(&mut epoch, builder, txn);
        let pixels_third = self.render_and_get_pixels(window_rect);

        assert!(pixels_first != pixels_third);
        self.compare_pixels(pixels_first, pixels_second, window_rect.size());
    }

    // Ensures that content doing a save-restore produces the same results as not
    fn test_save_restore(&mut self) {
        println!("\tsave/restore...");
        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(400, 400);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            point2(0, window_size.height - test_size.height),
            test_size,
        );

        let mut do_test = |should_try_and_fail| {
            let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
            builder.begin();

            let spatial_id = SpatialId::root_scroll_node(self.wrench.root_pipeline_id);
            let clip_id = builder.define_clip_rect(
                SpatialId::root_scroll_node(self.wrench.root_pipeline_id),
                rect(110., 120., 200., 200.).to_box2d(),
            );
            let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
            builder.push_rect(
                &self.make_common_properties_with_clip_and_spatial(
                    rect(100., 100., 100., 100.).to_box2d(),
                    clip_chain_id,
                    spatial_id),
                rect(100., 100., 100., 100.).to_box2d(),
                ColorF::new(0.0, 0.0, 1.0, 1.0),
            );

            if should_try_and_fail {
                builder.save();
                let clip_id = builder.define_clip_rect(
                    spatial_id,
                    rect(80., 80., 90., 90.).to_box2d(),
                );
                let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
                let space_and_clip = SpaceAndClipInfo {
                    spatial_id,
                    clip_chain_id,
                };
                builder.push_rect(
                    &self.make_common_properties_with_clip_and_spatial(
                        rect(110., 110., 50., 50.).to_box2d(),
                        clip_chain_id,
                        spatial_id),
                    rect(110., 110., 50., 50.).to_box2d(),
                    ColorF::new(0.0, 1.0, 0.0, 1.0),
                );
                builder.push_shadow(
                    &space_and_clip,
                    Shadow {
                        offset: LayoutVector2D::new(1.0, 1.0),
                        blur_radius: 1.0,
                        color: ColorF::new(0.0, 0.0, 0.0, 1.0),
                    },
                    true,
                );
                let info = CommonItemProperties {
                    clip_rect: rect(110., 110., 50., 2.).to_box2d(),
                    clip_chain_id,
                    spatial_id,
                    flags: PrimitiveFlags::default(),
                };
                builder.push_line(
                    &info,
                    &info.clip_rect,
                    0.0, LineOrientation::Horizontal,
                    &ColorF::new(0.0, 0.0, 0.0, 1.0),
                    LineStyle::Solid,
                );
                builder.restore();
            }

            {
                builder.save();
                let clip_id = builder.define_clip_rect(
                    spatial_id,
                    rect(80., 80., 100., 100.).to_box2d(),
                );
                let clip_chain_id = builder.define_clip_chain(None, [clip_id]);
                builder.push_rect(
                    &self.make_common_properties_with_clip_and_spatial(
                        rect(150., 150., 100., 100.).to_box2d(),
                        clip_chain_id,
                        spatial_id),
                    rect(150., 150., 100., 100.).to_box2d(),
                    ColorF::new(0.0, 0.0, 1.0, 1.0),
                );
                builder.clear_save();
            }

            let txn = Transaction::new();

            self.submit_dl(&mut Epoch(0), builder, txn);

            self.render_and_get_pixels(window_rect)
        };

        let first = do_test(false);
        let second = do_test(true);

        self.compare_pixels(first, second, window_rect.size());
    }

    // regression test for #2769
    // "async scene building: cache collisions from reused picture ids"
    fn test_blur_cache(&mut self) {
        println!("\tblur cache...");
        let window_size = self.window.get_inner_size();

        let test_size = FramebufferIntSize::new(400, 400);
        let window_rect = FramebufferIntRect::from_origin_and_size(
            point2(0, window_size.height - test_size.height),
            test_size,
        );

        let mut do_test = |shadow_is_red| {
            let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
            builder.begin();
            let shadow_color = if shadow_is_red {
                ColorF::new(1.0, 0.0, 0.0, 1.0)
            } else {
                ColorF::new(0.0, 1.0, 0.0, 1.0)
            };

            builder.push_shadow(
                &SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id),
                Shadow {
                    offset: LayoutVector2D::new(1.0, 1.0),
                    blur_radius: 1.0,
                    color: shadow_color,
                },
                true,
            );
            let info = self.make_common_properties(rect(110., 110., 50., 2.).to_box2d());
            builder.push_line(
                &info,
                &info.clip_rect,
                0.0, LineOrientation::Horizontal,
                &ColorF::new(0.0, 0.0, 0.0, 1.0),
                LineStyle::Solid,
            );
            builder.pop_all_shadows();

            let txn = Transaction::new();
            self.submit_dl(&mut Epoch(0), builder, txn);

            self.render_and_get_pixels(window_rect)
        };

        let first = do_test(false);
        let second = do_test(true);

        assert_ne!(first, second);
    }

    fn test_capture(&mut self) {
        println!("\tcapture...");
        let path = "../captures/test";
        let layout_size = LayoutSize::new(400., 400.);
        let dim = self.window.get_inner_size();
        let window_rect = FramebufferIntRect::from_origin_and_size(
            point2(0, dim.height - layout_size.height as i32),
            size2(layout_size.width as i32, layout_size.height as i32),
        );

        // 1. render some scene

        let mut txn = Transaction::new();
        let image = self.wrench.api.generate_image_key();
        txn.add_image(
            image,
            ImageDescriptor::new(1, 1, ImageFormat::BGRA8, ImageDescriptorFlags::IS_OPAQUE),
            ImageData::new(vec![0xFF, 0, 0, 0xFF]),
            None,
        );

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let info = self.make_common_properties(rect(300.0, 70.0, 150.0, 50.0).to_box2d());
        builder.push_image(
            &info,
            info.clip_rect,
            ImageRendering::Auto,
            AlphaType::PremultipliedAlpha,
            image,
            ColorF::WHITE,
        );

        let mut txn = Transaction::new();

        txn.set_display_list(
            Epoch(0),
            builder.end(),
        );
        txn.generate_frame(0, RenderReasons::TESTING);

        self.wrench.api.send_transaction(self.wrench.document_id, txn);

        let pixels0 = self.render_and_get_pixels(window_rect);

        // 2. capture it
        self.wrench.api.save_capture(path.into(), CaptureBits::all());

        // 3. set a different scene

        builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let mut txn = Transaction::new();
        txn.set_display_list(
            Epoch(1),
            builder.end(),
        );
        self.wrench.api.send_transaction(self.wrench.document_id, txn);

        // 4. load the first one

        let mut documents = self.wrench.api.load_capture(path.into(), None);
        let captured = documents.swap_remove(0);

        // 5. render the built frame and compare
        let pixels1 = self.render_and_get_pixels(window_rect);
        self.compare_pixels(pixels0.clone(), pixels1, window_rect.size());

        // 6. rebuild the scene and compare again
        let mut txn = Transaction::new();
        txn.set_root_pipeline(captured.root_pipeline_id.unwrap());
        txn.generate_frame(0, RenderReasons::TESTING);
        self.wrench.api.send_transaction(captured.document_id, txn);
        let pixels2 = self.render_and_get_pixels(window_rect);
        self.compare_pixels(pixels0, pixels2, window_rect.size());
    }

    fn test_zero_height_window(&mut self) {
        println!("\tzero height test...");

        let layout_size = LayoutSize::new(120.0, 0.0);
        let window_size = DeviceIntSize::new(layout_size.width as i32, layout_size.height as i32);
        let doc_id = self.wrench.api.add_document(window_size);

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();
        let info = self.make_common_properties(
            LayoutRect::from_size(LayoutSize::new(100.0, 100.0))
        );
        builder.push_rect(
            &info,
            info.clip_rect,
            ColorF::new(0.0, 1.0, 0.0, 1.0),
        );

        let mut txn = Transaction::new();
        txn.set_root_pipeline(self.wrench.root_pipeline_id);
        txn.set_display_list(
            Epoch(1),
            builder.end(),
        );
        txn.generate_frame(0, RenderReasons::TESTING);
        self.wrench.api.send_transaction(doc_id, txn);

        // Ensure we get a notification from rendering the above, even though
        // there are zero visible pixels
        assert!(self.rx.recv().unwrap() == NotifierEvent::WakeUp { composite_needed: true });
    }


    fn test_hit_testing(&mut self) {
        println!("\thit testing test...");

        let layout_size = LayoutSize::new(400., 400.);
        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        // Add a rectangle that covers the entire scene.
        let space_and_clip = SpaceAndClipInfo::root_scroll(self.wrench.root_pipeline_id);
        builder.push_hit_test(
            LayoutRect::from_size(layout_size),
            ClipChainId::INVALID,
            space_and_clip.spatial_id,
            PrimitiveFlags::default(),
            (0, 1),
        );

        // Add a simple 100x100 rectangle at 100,0.
        builder.push_hit_test(
            LayoutRect::from_origin_and_size(
                LayoutPoint::new(100., 0.),
                LayoutSize::new(100., 100.)
            ),
            ClipChainId::INVALID,
            space_and_clip.spatial_id,
            PrimitiveFlags::default(),
            (0, 2),
        );

        let make_rounded_complex_clip = |rect: &LayoutRect, radius: f32| -> ComplexClipRegion {
            ComplexClipRegion::new(
                *rect,
                BorderRadius::uniform_size(LayoutSize::new(radius, radius)),
                ClipMode::Clip
            )
        };

        // Add a rectangle that is clipped by a rounded rect clip item.
        let rect = LayoutRect::from_origin_and_size(LayoutPoint::new(100., 100.), LayoutSize::new(100., 100.));
        let temp_clip_id = builder.define_clip_rounded_rect(
            space_and_clip.spatial_id,
            make_rounded_complex_clip(&rect, 20.),
        );
        let clip_chain_id = builder.define_clip_chain(None, vec![temp_clip_id]);
        builder.push_hit_test(
            rect,
            clip_chain_id,
            space_and_clip.spatial_id,
            PrimitiveFlags::default(),
            (0, 4),
        );

        // Add a rectangle that is clipped by a ClipChain containing a rounded rect.
        let rect = LayoutRect::from_origin_and_size(LayoutPoint::new(200., 100.), LayoutSize::new(100., 100.));
        let clip_id = builder.define_clip_rounded_rect(
            space_and_clip.spatial_id,
            make_rounded_complex_clip(&rect, 20.),
        );
        let clip_chain_id = builder.define_clip_chain(None, vec![clip_id]);
        builder.push_hit_test(
            rect,
            clip_chain_id,
            space_and_clip.spatial_id,
            PrimitiveFlags::default(),
            (0, 5),
        );

        let mut epoch = Epoch(0);
        let txn = Transaction::new();
        self.submit_dl(&mut epoch, builder, txn);

        // We render to ensure that the hit tester is up to date with the current scene.
        self.rx.recv().unwrap();
        self.wrench.render();

        let hit_test = |point: WorldPoint| -> HitTestResult {
            self.wrench.api.hit_test(
                self.wrench.document_id,
                point,
            )
        };

        let assert_hit_test = |point: WorldPoint, tags: Vec<ItemTag>| {
            let result = hit_test(point);
            assert_eq!(result.items.len(), tags.len());

            for (hit_test_item, item_b) in result.items.iter().zip(tags.iter()) {
                assert_eq!(hit_test_item.tag, *item_b);
            }
        };

        // We should not have any hits outside the boundaries of the scene.
        assert_hit_test(WorldPoint::new(-10., -10.), Vec::new());
        assert_hit_test(WorldPoint::new(-10., 10.), Vec::new());
        assert_hit_test(WorldPoint::new(450., 450.), Vec::new());
        assert_hit_test(WorldPoint::new(100., 450.), Vec::new());

        // The top left corner of the scene should only contain the background.
        assert_hit_test(WorldPoint::new(50., 50.), vec![(0, 1)]);

        // The middle of the normal rectangle should be hit.
        assert_hit_test(WorldPoint::new(150., 50.), vec![(0, 2), (0, 1)]);

        let test_rounded_rectangle = |point: WorldPoint, size: WorldSize, tag: ItemTag| {
            // The cut out corners of the rounded rectangle should not be hit.
            let top_left = point + WorldVector2D::new(5., 5.);
            let bottom_right = point + size.to_vector() - WorldVector2D::new(5., 5.);

            assert_hit_test(
                WorldPoint::new(point.x + (size.width / 2.), point.y + (size.height / 2.)),
                vec![tag, (0, 1)]
            );

            assert_hit_test(top_left, vec![(0, 1)]);
            assert_hit_test(WorldPoint::new(bottom_right.x, top_left.y), vec![(0, 1)]);
            assert_hit_test(WorldPoint::new(top_left.x, bottom_right.y), vec![(0, 1)]);
            assert_hit_test(bottom_right, vec![(0, 1)]);
        };

        test_rounded_rectangle(WorldPoint::new(100., 100.), WorldSize::new(100., 100.), (0, 4));
        test_rounded_rectangle(WorldPoint::new(200., 100.), WorldSize::new(100., 100.), (0, 5));
    }

    fn test_clear_cache(&mut self) {
        println!("\tclear cache test...");

        self.wrench.api.send_message(ApiMsg::DebugCommand(DebugCommand::ClearCaches(ClearCache::all())));

        let mut builder = DisplayListBuilder::new(self.wrench.root_pipeline_id);
        builder.begin();

        let txn = Transaction::new();
        let mut epoch = Epoch(0);
        self.submit_dl(&mut epoch, builder, txn);

        self.rx.recv().unwrap();
        self.wrench.render();
    }
}

[ Dauer der Verarbeitung: 0.20 Sekunden  (vorverarbeitet)  ]

                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Produkte
     Quellcodebibliothek

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik

Monitoring

Montastic status badge